Close the derive hole: make path the whole statement of what the projection reads.
Problem
derive receives the framed node and reads arbitrary predicates through irisOf / literalsOf / firstLiteralOf – exported, per project.ts, “so derivations can read arbitrary paths”. So SearchField.path is not a complete statement of what the projection reads.
In the Dataset Register’s Dataset type, 10 predicates are reachable via path and 13 only from inside a closure: class, format, conformsTo, additionalType, validUntil, iiifEntities, manifestsSampled, manifestsValidated, quadsValidated, schemaApNdeConformant, subjectUrisSampled, subjectUrisResolved, subjectNamespaceDurable. More than half.
Two consequences:
The drift is already real, not hypothetical: register-source.ts CONSTRUCTs dr:dateRead, and no field in the schema reads it. Nothing catches that.
Scope
Split the two concerns: path says what to read from the graph; derive says what to compute from what was read.
derive: (document) => unknown – drop the FramedNode argument. A derive reads fields declared before it, exactly as status_rank and terms already do.
- Every predicate a derive needs becomes a real field with a
path.
- Honour the model’s existing promise for every kind.
schema.ts says “a field exposes exactly the roles it declares”, so a field declaring no role (output / searchable / filterable / facetable / sortable) is internal: projected into the document so later derives can read it, pruned before the writer, absent from the collection definition. text already behaves this way (applyText and typesenseFields both no-op); keyword / reference / integer / number / date / boolean write and declare unconditionally. Make them consistent.
- Unexport
irisOf / literalsOf / firstLiteralOf.
Acceptance
- The set of
field.path over a type is exactly the set of predicates the projection reads.
- A zero-role field of every kind reaches neither the document handed to a writer nor the collection definition.
Consequences
Breaking for @lde/search: consumers migrate derives to internal fields plus document reads.
Net code goes down. The Dataset Register additionally deletes parseNumber, parseBoolean and the qualityMeasurements WeakMap memo – an internal field is projected once and read by N derives, so the memo becomes structural rather than hand-rolled. An internal field costs nothing at the engine: not stored, not indexed, no RAM.
Relates to
Precondition for #548. Design context: #534.
Close the
derivehole: makepaththe whole statement of what the projection reads.Problem
derivereceives the framed node and reads arbitrary predicates throughirisOf/literalsOf/firstLiteralOf– exported, perproject.ts, “so derivations can read arbitrary paths”. SoSearchField.pathis not a complete statement of what the projection reads.In the Dataset Register’s
Datasettype, 10 predicates are reachable viapathand 13 only from inside a closure:class,format,conformsTo,additionalType,validUntil,iiifEntities,manifestsSampled,manifestsValidated,quadsValidated,schemaApNdeConformant,subjectUrisSampled,subjectUrisResolved,subjectNamespaceDurable. More than half.Two consequences:
statusand all five compatibility booleans.The drift is already real, not hypothetical:
register-source.tsCONSTRUCTsdr:dateRead, and no field in the schema reads it. Nothing catches that.Scope
Split the two concerns:
pathsays what to read from the graph;derivesays what to compute from what was read.derive: (document) => unknown– drop theFramedNodeargument. A derive reads fields declared before it, exactly asstatus_rankandtermsalready do.path.schema.tssays “a field exposes exactly the roles it declares”, so a field declaring no role (output/searchable/filterable/facetable/sortable) is internal: projected into the document so later derives can read it, pruned before the writer, absent from the collection definition.textalready behaves this way (applyTextandtypesenseFieldsboth no-op);keyword/reference/integer/number/date/booleanwrite and declare unconditionally. Make them consistent.irisOf/literalsOf/firstLiteralOf.Acceptance
field.pathover a type is exactly the set of predicates the projection reads.Consequences
Breaking for
@lde/search: consumers migrate derives to internal fields plusdocumentreads.Net code goes down. The Dataset Register additionally deletes
parseNumber,parseBooleanand thequalityMeasurementsWeakMapmemo – an internal field is projected once and read by N derives, so the memo becomes structural rather than hand-rolled. An internal field costs nothing at the engine: not stored, not indexed, no RAM.Relates to
Precondition for #548. Design context: #534.