@lde/search 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/period.d.ts +6 -6
- package/dist/period.d.ts.map +1 -1
- package/dist/period.js +10 -21
- package/dist/project.d.ts +18 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +130 -111
- package/dist/read-value.d.ts +44 -0
- package/dist/read-value.d.ts.map +1 -0
- package/dist/read-value.js +96 -0
- package/package.json +1 -1
package/dist/period.d.ts
CHANGED
|
@@ -53,11 +53,11 @@ export declare const OPEN_END: number;
|
|
|
53
53
|
*/
|
|
54
54
|
export declare function periodOf(value: string): StoredPeriod | undefined;
|
|
55
55
|
/**
|
|
56
|
-
* The period several
|
|
57
|
-
* latest end
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
56
|
+
* The period several periods cover between them: the earliest start and the
|
|
57
|
+
* latest end. One stored range per document is what an engine can sort on and
|
|
58
|
+
* compare in a single clause, so a work dated `1668`, `1676` and `1686` is
|
|
59
|
+
* stored as 1668 through 1686 – and a range falling between its dates still
|
|
60
|
+
* matches it.
|
|
61
61
|
*/
|
|
62
|
-
export declare function
|
|
62
|
+
export declare function periodCovering(first: StoredPeriod, ...rest: readonly StoredPeriod[]): StoredPeriod;
|
|
63
63
|
//# sourceMappingURL=period.d.ts.map
|
package/dist/period.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"period.d.ts","sourceRoot":"","sources":["../src/period.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,QAAa,CAAC;AAErC;oCACoC;AACpC,eAAO,MAAM,QAAQ,QAAU,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAehE;AAED;;;;;;GAMG;AACH,wBAAgB,
|
|
1
|
+
{"version":3,"file":"period.d.ts","sourceRoot":"","sources":["../src/period.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,QAAa,CAAC;AAErC;oCACoC;AACpC,eAAO,MAAM,QAAQ,QAAU,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAehE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,GAAG,IAAI,EAAE,SAAS,YAAY,EAAE,GAC/B,YAAY,CAQd"}
|
package/dist/period.js
CHANGED
|
@@ -59,28 +59,17 @@ export function periodOf(value) {
|
|
|
59
59
|
return start <= end && !openAtBothEnds ? { start, end } : undefined;
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
* The period several
|
|
63
|
-
* latest end
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
62
|
+
* The period several periods cover between them: the earliest start and the
|
|
63
|
+
* latest end. One stored range per document is what an engine can sort on and
|
|
64
|
+
* compare in a single clause, so a work dated `1668`, `1676` and `1686` is
|
|
65
|
+
* stored as 1668 through 1686 – and a range falling between its dates still
|
|
66
|
+
* matches it.
|
|
67
67
|
*/
|
|
68
|
-
export function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
covered =
|
|
76
|
-
covered === undefined
|
|
77
|
-
? period
|
|
78
|
-
: {
|
|
79
|
-
start: Math.min(covered.start, period.start),
|
|
80
|
-
end: Math.max(covered.end, period.end),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
return covered;
|
|
68
|
+
export function periodCovering(first, ...rest) {
|
|
69
|
+
return rest.reduce((covered, period) => ({
|
|
70
|
+
start: Math.min(covered.start, period.start),
|
|
71
|
+
end: Math.max(covered.end, period.end),
|
|
72
|
+
}), first);
|
|
84
73
|
}
|
|
85
74
|
/** One side of an interval, or a whole plain value. */
|
|
86
75
|
function sideOf(text) {
|
package/dist/project.d.ts
CHANGED
|
@@ -27,6 +27,24 @@ export type SearchDocument = {
|
|
|
27
27
|
export interface ProjectionContext extends Partial<Readonly<Record<ProjectionValue, string>>> {
|
|
28
28
|
/** The IRI of the dataset being indexed. */
|
|
29
29
|
readonly dataset?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Called once for each value a typed field could not read and so left out:
|
|
32
|
+
* a `date` over `Z.j`, an `integer` over `onbekend`, a `period` over
|
|
33
|
+
* `1987/1897`, a `reference` over a literal, a `boolean` outside
|
|
34
|
+
* `true`/`false`/`1`/`0`. Values from a `derive` count too; one that is not
|
|
35
|
+
* a string is reported as `String(value)`.
|
|
36
|
+
*/
|
|
37
|
+
readonly onUnreadable?: (unreadable: UnreadableValue) => void;
|
|
38
|
+
}
|
|
39
|
+
/** A value a typed field could not read ({@link ProjectionContext.onUnreadable}). */
|
|
40
|
+
export interface UnreadableValue {
|
|
41
|
+
/** The field that could not read it; dotted for a field of a nested
|
|
42
|
+
* referent (`creator.birthDate`). */
|
|
43
|
+
readonly field: string;
|
|
44
|
+
/** The value as the source – or the derive – stated it. */
|
|
45
|
+
readonly value: string;
|
|
46
|
+
/** The IRI of the node the document was projected from. */
|
|
47
|
+
readonly id: string;
|
|
30
48
|
}
|
|
31
49
|
/**
|
|
32
50
|
* Project one framed JSON-LD node into a flat search document: apply each field
|
package/dist/project.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAsBL,KAAK,eAAe,EAIpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAGhB,MAAM,aAAa,CAAC;AAUrB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAChD,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAC1C;IACC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAC;CAC/D;AAED,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B;0CACsC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,EACrB,OAAO,GAAE,iBAAyC,GACjD,cAAc,CAkBhB;AAgMD;;;;;;;;;;;;GAYG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,QAAQ,EACpB,OAAO,GAAE,iBAAyC,GACjD,aAAa,CAAC,cAAc,CAAC,CAc/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,GACpB,SAAS,MAAM,EAAE,CAmCnB"}
|
package/dist/project.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { fold } from '@lde/text-normalization';
|
|
2
2
|
import { matchLanguage } from './language.js';
|
|
3
3
|
import { buildSubjectIndex, frameSubjects, } from './frame-by-type.js';
|
|
4
|
-
import { companionNames, assertTypeInSchema, displayFieldName, displayLangOf, documentKeyOf, endpointFieldOf, fieldNamed, identityFieldOf, inheritedFacetPolicies, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference,
|
|
5
|
-
import {
|
|
4
|
+
import { companionNames, assertTypeInSchema, displayFieldName, displayLangOf, documentKeyOf, endpointFieldOf, fieldNamed, identityFieldOf, inheritedFacetPolicies, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference, localLookupTargetsOf, physicalFields, referencedTargetsOf, referenceTypeNamed, storedTargetOf, TARGET_FIELD, } from './schema.js';
|
|
5
|
+
import { periodCovering, periodOf } from './period.js';
|
|
6
|
+
import { describeValue, isReadKind, readValue, UNREADABLE, } from './read-value.js';
|
|
6
7
|
/** The context a projection runs with when its caller supplied none. */
|
|
7
8
|
const NO_PROJECTION_CONTEXT = {};
|
|
8
9
|
/**
|
|
@@ -20,12 +21,13 @@ const NO_PROJECTION_CONTEXT = {};
|
|
|
20
21
|
* the engine collection definition and the query compiler.
|
|
21
22
|
*/
|
|
22
23
|
export function projectDocument(node, searchType, schema, context = NO_PROJECTION_CONTEXT) {
|
|
23
|
-
|
|
24
|
+
const key = documentKey(node);
|
|
25
|
+
if (key === undefined) {
|
|
24
26
|
throw new Error(`Cannot project a “${searchType.name}” node whose @id is not an absolute IRI (got ${JSON.stringify(node['@id']) ?? 'none'}; a blank node label or a relative reference is not one): every search document needs a stable key, and an empty one would collide with other keyless nodes.`);
|
|
25
27
|
}
|
|
26
28
|
// The guard above is what makes this a SearchDocument: projectFields sets `id`
|
|
27
29
|
// for exactly the nodes documentKey resolves.
|
|
28
|
-
const document = projectFields(node, searchType, schema, context);
|
|
30
|
+
const document = projectFields(node, searchType, schema, context, unreadableSink(context, key));
|
|
29
31
|
pruneInternalFields(document, searchType, schema);
|
|
30
32
|
return document;
|
|
31
33
|
}
|
|
@@ -93,10 +95,10 @@ function pruneInternalFields(document, searchType, schema) {
|
|
|
93
95
|
* Pruning is deferred to a single recursive pass ({@link pruneInternalFields})
|
|
94
96
|
* once the whole nested structure is projected, so a derive at any depth can
|
|
95
97
|
* still read an internal field before it is removed. */
|
|
96
|
-
function projectFields(node, searchType, schema, context, nested = false) {
|
|
98
|
+
function projectFields(node, searchType, schema, context, sink, nested = false) {
|
|
97
99
|
const document = emptyDocument(documentIdOf(node, searchType), searchType);
|
|
98
100
|
for (const field of searchType.fields) {
|
|
99
|
-
applyField(document, node, field, searchType, schema, context, nested);
|
|
101
|
+
applyField(document, node, field, searchType, schema, context, sink, nested);
|
|
100
102
|
}
|
|
101
103
|
return document;
|
|
102
104
|
}
|
|
@@ -255,7 +257,8 @@ function hasValue(document, field, searchType) {
|
|
|
255
257
|
(displayLangOf(field, key) !== undefined &&
|
|
256
258
|
fieldNamed(searchType, key) === undefined)));
|
|
257
259
|
}
|
|
258
|
-
function applyField(document, node, field, searchType, schema, context, nested) {
|
|
260
|
+
function applyField(document, node, field, searchType, schema, context, sink, nested) {
|
|
261
|
+
const unread = (value) => sink(field.name, value);
|
|
259
262
|
// The three value sources, mutually exclusive by declaration
|
|
260
263
|
// (`validateSearchType`): a projection value, a computed value, a graph path.
|
|
261
264
|
if ((field.kind === 'keyword' || field.kind === 'reference') &&
|
|
@@ -264,27 +267,24 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
264
267
|
}
|
|
265
268
|
if (field.derive !== undefined) {
|
|
266
269
|
const derived = field.derive(document, context);
|
|
267
|
-
// A derived `reference` is held to the same rule as a read one: what it
|
|
268
|
-
// stores is a selection key, and the surface types it `IRI`. ADR 21 sends
|
|
269
|
-
// an IRI-valued `keyword` derive here by converting it to `reference`, so
|
|
270
|
-
// this is the busiest way a non-IRI could otherwise reach the index. A
|
|
271
|
-
// value that survives nothing leaves the field absent, exactly as a derive
|
|
272
|
-
// returning `undefined` does.
|
|
273
|
-
if (field.kind === 'date') {
|
|
274
|
-
return applyDerivedDate(document, field, derived);
|
|
275
|
-
}
|
|
276
270
|
if (field.kind === 'period') {
|
|
277
|
-
return setPeriod(document, field, derivedStrings(derived));
|
|
271
|
+
return setPeriod(document, field, derivedStrings(derived, unread), unread);
|
|
272
|
+
}
|
|
273
|
+
// A derived value is held to the same rule as a read one, through the same
|
|
274
|
+
// reader: a derived `reference` stores a selection key the surface types
|
|
275
|
+
// `IRI` – ADR 21 sends an IRI-valued `keyword` derive here by converting it
|
|
276
|
+
// to `reference`, so this is the busiest way a non-IRI could otherwise
|
|
277
|
+
// reach the index – and a derived `integer` an int64.
|
|
278
|
+
if (isReadKind(field.kind)) {
|
|
279
|
+
return applyRead(document, field, field.kind, Array.isArray(derived) ? derived : [derived], unread);
|
|
280
|
+
}
|
|
281
|
+
// A `keyword` or `text` field reads any value but NaN, which serializes as
|
|
282
|
+
// `null` – stored by an engine as no value, or refused with the document.
|
|
283
|
+
if (Number.isNaN(derived)) {
|
|
284
|
+
unread('NaN');
|
|
278
285
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
: derived;
|
|
282
|
-
// NaN is not a value any kind stores, and a derive is the only route that
|
|
283
|
-
// can produce one (`setNumber` guards the read route). It serializes as
|
|
284
|
-
// `null`, which an engine rejects for a numeric field – so drop it, leaving
|
|
285
|
-
// the field absent as an unparseable string already does.
|
|
286
|
-
if (value !== undefined && !Number.isNaN(value)) {
|
|
287
|
-
document[field.name] = value;
|
|
286
|
+
else if (derived !== undefined) {
|
|
287
|
+
document[field.name] = derived;
|
|
288
288
|
}
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
@@ -307,7 +307,7 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
307
307
|
// project nothing rather than fall through and emit the referent IRIs under
|
|
308
308
|
// the field name (the wrong shape).
|
|
309
309
|
if (schema !== undefined) {
|
|
310
|
-
applyInlineReference(document, node, alias, field, schema, context, nested);
|
|
310
|
+
applyInlineReference(document, node, alias, field, schema, context, sink, nested);
|
|
311
311
|
}
|
|
312
312
|
return;
|
|
313
313
|
}
|
|
@@ -318,7 +318,7 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
318
318
|
if (field.kind === 'reference' && schema !== undefined) {
|
|
319
319
|
const localTargets = localLookupTargetsOf(field, schema);
|
|
320
320
|
if (localTargets.length > 0) {
|
|
321
|
-
const endpoints = applyNestedReferents(document, valuesOf(node, alias), field, localTargets, schema, context);
|
|
321
|
+
const endpoints = applyNestedReferents(document, valuesOf(node, alias), field, localTargets, schema, context, sink);
|
|
322
322
|
// A `local` lookup stores an object, so its own id is a level deeper than
|
|
323
323
|
// a filter can reach – an engine welds conditions on an entry's LEAF
|
|
324
324
|
// fields only, and facets flat values. `filterable` and `facetable`
|
|
@@ -335,28 +335,42 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
335
335
|
return applyFacet(document, literalsOf(node, alias), field, schema);
|
|
336
336
|
case 'reference': {
|
|
337
337
|
const targetsById = new Map();
|
|
338
|
-
const values = referenceValues(node, alias, field, schema, targetsById);
|
|
338
|
+
const values = referenceValues(node, alias, field, schema, targetsById, unread);
|
|
339
339
|
rememberTargets(document, targetsById);
|
|
340
340
|
return applyFacet(document, values, field, schema, targetsById);
|
|
341
341
|
}
|
|
342
342
|
case 'integer':
|
|
343
|
-
return setNumber(document, field.name, toInteger(firstLiteralOf(node, alias)));
|
|
344
343
|
case 'number':
|
|
345
|
-
|
|
346
|
-
case '
|
|
347
|
-
|
|
348
|
-
return setDate(document, field, literal === undefined ? undefined : isoToUnixSeconds(literal));
|
|
349
|
-
}
|
|
344
|
+
case 'date':
|
|
345
|
+
case 'boolean':
|
|
346
|
+
return applyRead(document, field, field.kind, literalsOf(node, alias), unread);
|
|
350
347
|
case 'period':
|
|
351
|
-
return setPeriod(document, field, literalsOf(node, alias));
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
348
|
+
return setPeriod(document, field, literalsOf(node, alias), unread);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Store what {@link readValue} reads of `values` as `kind`, reporting every
|
|
353
|
+
* value it cannot. An `array` reference stores the list; every other kind is a
|
|
354
|
+
* scalar in the engine, so it stores the first value read – and an unreadable
|
|
355
|
+
* or blank value cannot cost the field a readable one behind it.
|
|
356
|
+
*/
|
|
357
|
+
function applyRead(document, field, kind, values, unread) {
|
|
358
|
+
const read = values.flatMap((value) => {
|
|
359
|
+
const reading = readValue(kind, value);
|
|
360
|
+
if (reading === UNREADABLE) {
|
|
361
|
+
unread(describeValue(value));
|
|
362
|
+
return [];
|
|
359
363
|
}
|
|
364
|
+
return reading === undefined ? [] : [reading];
|
|
365
|
+
});
|
|
366
|
+
if (kind === 'date') {
|
|
367
|
+
return setDate(document, field, read[0]);
|
|
368
|
+
}
|
|
369
|
+
if (kind === 'reference' && field.array === true) {
|
|
370
|
+
document[field.name] = read;
|
|
371
|
+
}
|
|
372
|
+
else if (read.length > 0) {
|
|
373
|
+
document[field.name] = read[0];
|
|
360
374
|
}
|
|
361
375
|
}
|
|
362
376
|
/**
|
|
@@ -385,7 +399,16 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
385
399
|
* referent’s node IRIs therefore sees a key instead, which is why the two are
|
|
386
400
|
* worth declaring together only deliberately.
|
|
387
401
|
*/
|
|
388
|
-
function referenceValues(node, alias, field, schema, targetsById) {
|
|
402
|
+
function referenceValues(node, alias, field, schema, targetsById, unread) {
|
|
403
|
+
// A literal where a referent belongs states no IRI to store. A node without
|
|
404
|
+
// one – a blank node – carries no value a publisher could look up, so only
|
|
405
|
+
// literals are reported, and a blank one states nothing to correct.
|
|
406
|
+
for (const value of valuesOf(node, alias)) {
|
|
407
|
+
const literal = iriString(value) === undefined ? literalString(value) : undefined;
|
|
408
|
+
if (literal !== undefined && literal.trim() !== '') {
|
|
409
|
+
unread(literal);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
389
412
|
// Guaranteed declared: `searchSchema` validates that every named target
|
|
390
413
|
// resolves, and the projection only ever runs against a type of its schema.
|
|
391
414
|
const targets = referencedTargetsOf(field, schema);
|
|
@@ -685,7 +708,7 @@ function applyProjectionValue(document, field, from, context, schema) {
|
|
|
685
708
|
* the schema’s declared depth. The referent type is guaranteed declared by
|
|
686
709
|
* {@link searchSchema}.
|
|
687
710
|
*/
|
|
688
|
-
function applyInlineReference(document, node, alias, field, schema, context, nested) {
|
|
711
|
+
function applyInlineReference(document, node, alias, field, schema, context, sink, nested) {
|
|
689
712
|
// Resolves for a schema that declares the referent (always so for the schema a
|
|
690
713
|
// type is projected through); a type framed against a foreign schema that
|
|
691
714
|
// omits it simply contributes no nesting.
|
|
@@ -695,7 +718,7 @@ function applyInlineReference(document, node, alias, field, schema, context, nes
|
|
|
695
718
|
}
|
|
696
719
|
const asEdge = edgeReader(referenceType, field.ref.identity);
|
|
697
720
|
const asLiteralEntry = literalReader(referenceType);
|
|
698
|
-
const referents = applyNestedReferents(document, valuesOf(node, alias).map((value) => asLiteralEntry(value) ?? asEdge(value)), field, [referenceType], schema, context);
|
|
721
|
+
const referents = applyNestedReferents(document, valuesOf(node, alias).map((value) => asLiteralEntry(value) ?? asEdge(value)), field, [referenceType], schema, context, sink);
|
|
699
722
|
applyIdentityCompanion(document, referents, field, schema, nested);
|
|
700
723
|
}
|
|
701
724
|
/**
|
|
@@ -897,7 +920,7 @@ function identityValue(value) {
|
|
|
897
920
|
* top-level document of that type would be – so the stored id is the one the
|
|
898
921
|
* target’s collection files it under, and the lookup can find it.
|
|
899
922
|
*/
|
|
900
|
-
function applyNestedReferents(document, values, field, nestedTypes, schema, context) {
|
|
923
|
+
function applyNestedReferents(document, values, field, nestedTypes, schema, context, sink) {
|
|
901
924
|
// One node may stand for several entries: a weldable leaf is single-valued,
|
|
902
925
|
// so an edge the graph gave several roles or several endpoints fans out into
|
|
903
926
|
// one entry per combination BEFORE it is projected (ADR 26).
|
|
@@ -910,8 +933,18 @@ function applyNestedReferents(document, values, field, nestedTypes, schema, cont
|
|
|
910
933
|
const nestedType = nestedTypes.length === 1
|
|
911
934
|
? nestedTypes[0]
|
|
912
935
|
: targetOfReferent(value, nestedTypes);
|
|
936
|
+
// Every tuple of a node carries the node’s other values, so each value
|
|
937
|
+
// is reported once per node, not once per tuple it was copied into.
|
|
938
|
+
let reported;
|
|
939
|
+
const nestedSink = (name, value) => {
|
|
940
|
+
const key = JSON.stringify([name, value]);
|
|
941
|
+
if (!(reported ??= new Set()).has(key)) {
|
|
942
|
+
reported.add(key);
|
|
943
|
+
sink(`${field.name}.${name}`, value);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
913
946
|
return tuplesOf(value, nestedType, field).map((tuple) => {
|
|
914
|
-
const projected = projectFields(tuple, nestedType, schema, context, true);
|
|
947
|
+
const projected = projectFields(tuple, nestedType, schema, context, nestedSink, true);
|
|
915
948
|
if (nestedTypes.length > 1 && Object.keys(projected).length > 0) {
|
|
916
949
|
projected[TARGET_FIELD] = nestedType.name;
|
|
917
950
|
}
|
|
@@ -1010,9 +1043,6 @@ function literalsOf(node, key) {
|
|
|
1010
1043
|
.map(literalString)
|
|
1011
1044
|
.filter((value) => value !== undefined);
|
|
1012
1045
|
}
|
|
1013
|
-
function firstLiteralOf(node, key) {
|
|
1014
|
-
return literalsOf(node, key)[0];
|
|
1015
|
-
}
|
|
1016
1046
|
function irisOf(node, key) {
|
|
1017
1047
|
return valuesOf(node, key)
|
|
1018
1048
|
.map(iriString)
|
|
@@ -1075,44 +1105,6 @@ function iriString(value) {
|
|
|
1075
1105
|
const iri = isObject(value) ? value['@id'] : value;
|
|
1076
1106
|
return typeof iri === 'string' && isAbsoluteIri(iri) ? iri : undefined;
|
|
1077
1107
|
}
|
|
1078
|
-
/** What a `derive` on a `reference` field is allowed to have produced: the
|
|
1079
|
-
* absolute IRIs among its return value, keeping the shape the declaration
|
|
1080
|
-
* promises (a list stays a list, a single value stays single) so `array` still
|
|
1081
|
-
* decides the stored shape. A lone non-IRI becomes `undefined`, i.e. absent –
|
|
1082
|
-
* the same outcome the graph path gives it. */
|
|
1083
|
-
function derivedIris(value) {
|
|
1084
|
-
if (Array.isArray(value)) {
|
|
1085
|
-
return value.filter((entry) => typeof entry === 'string' && isAbsoluteIri(entry));
|
|
1086
|
-
}
|
|
1087
|
-
return typeof value === 'string' && isAbsoluteIri(value) ? value : undefined;
|
|
1088
|
-
}
|
|
1089
|
-
function toInteger(literal) {
|
|
1090
|
-
return literal === undefined ? undefined : Math.trunc(Number(literal));
|
|
1091
|
-
}
|
|
1092
|
-
function toNumber(literal) {
|
|
1093
|
-
return literal === undefined ? undefined : Number(literal);
|
|
1094
|
-
}
|
|
1095
|
-
/**
|
|
1096
|
-
* A derived `date` goes through the same storage codec as a read one: the field
|
|
1097
|
-
* is stored as Unix seconds, so an ISO string returned here would otherwise
|
|
1098
|
-
* land as a string in an int64 field – and it is parsed like any literal, its
|
|
1099
|
-
* precision included. A derive that computed seconds itself returns a number,
|
|
1100
|
-
* which states no precision and so is stored as a date-time; the codec never
|
|
1101
|
-
* saw it and so never range-checked it, and seconds outside the int64 a `date`
|
|
1102
|
-
* field is stored in would be refused by the engine where an unparseable
|
|
1103
|
-
* string merely leaves the field absent – so drop those too.
|
|
1104
|
-
*/
|
|
1105
|
-
function applyDerivedDate(document, field, derived) {
|
|
1106
|
-
if (typeof derived === 'string') {
|
|
1107
|
-
return setDate(document, field, isoToUnixSeconds(derived));
|
|
1108
|
-
}
|
|
1109
|
-
if (typeof derived === 'number') {
|
|
1110
|
-
// Truncated, as the codec truncates a string’s fractional seconds: the
|
|
1111
|
-
// field is an int64. `storableUnixSeconds` drops NaN.
|
|
1112
|
-
const seconds = storableUnixSeconds(Math.trunc(derived));
|
|
1113
|
-
return setDate(document, field, seconds === undefined ? undefined : { seconds, precision: 'dateTime' });
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
1108
|
/** Store a `date` field’s seconds, and its precision in the companion beside
|
|
1117
1109
|
* them ({@link physicalFields}), so the surface can serve the value in the
|
|
1118
1110
|
* form its source gave. */
|
|
@@ -1124,40 +1116,55 @@ function setDate(document, field, stored) {
|
|
|
1124
1116
|
document[physicalFields(field).precision] = stored.precision;
|
|
1125
1117
|
}
|
|
1126
1118
|
/**
|
|
1127
|
-
* Store a `period` field:
|
|
1128
|
-
* output, and the stretch they cover between them in the
|
|
1129
|
-
* ({@link physicalFields}). A value that states no period
|
|
1130
|
-
* is
|
|
1119
|
+
* Store a `period` field: the values that state a period, verbatim under the
|
|
1120
|
+
* field’s own name, for output, and the stretch they cover between them in the
|
|
1121
|
+
* bound companions ({@link physicalFields}). A value that states no period –
|
|
1122
|
+
* `onbekend`, `1987/1897` – is reported and left out, so every value served is
|
|
1123
|
+
* the EDTF its scalar promises; a deployment that wants to show such text
|
|
1124
|
+
* declares a `keyword` field over the same path.
|
|
1131
1125
|
*
|
|
1132
|
-
* Blank values are dropped before a single-valued field keeps
|
|
1133
|
-
*
|
|
1134
|
-
* are taken from exactly the values stored, so what is served and what
|
|
1135
|
-
* filtered cannot disagree.
|
|
1126
|
+
* Blank and unreadable values are dropped before a single-valued field keeps
|
|
1127
|
+
* its first, so neither can cost the field the period behind it; and the
|
|
1128
|
+
* bounds are taken from exactly the values stored, so what is served and what
|
|
1129
|
+
* is filtered cannot disagree.
|
|
1136
1130
|
*/
|
|
1137
|
-
function setPeriod(document, field, values) {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1131
|
+
function setPeriod(document, field, values, unread) {
|
|
1132
|
+
const readable = dedupe(values.map((value) => value.trim())).flatMap((value) => {
|
|
1133
|
+
if (value === '') {
|
|
1134
|
+
return [];
|
|
1135
|
+
}
|
|
1136
|
+
const period = periodOf(value);
|
|
1137
|
+
if (period === undefined) {
|
|
1138
|
+
unread(value);
|
|
1139
|
+
return [];
|
|
1140
|
+
}
|
|
1141
|
+
return [{ value, period }];
|
|
1142
|
+
});
|
|
1143
|
+
const stated = field.array === true ? readable : readable.slice(0, 1);
|
|
1140
1144
|
if (stated.length === 0) {
|
|
1141
1145
|
return;
|
|
1142
1146
|
}
|
|
1143
|
-
document[field.name] =
|
|
1147
|
+
document[field.name] =
|
|
1148
|
+
field.array === true ? stated.map(({ value }) => value) : stated[0].value;
|
|
1144
1149
|
const companions = physicalFields(field).period;
|
|
1145
|
-
|
|
1146
|
-
|
|
1150
|
+
if (companions !== undefined) {
|
|
1151
|
+
const [first, ...rest] = stated.map(({ period }) => period);
|
|
1152
|
+
const covered = periodCovering(first, ...rest);
|
|
1147
1153
|
document[companions.start] = covered.start;
|
|
1148
1154
|
document[companions.end] = covered.end;
|
|
1149
1155
|
}
|
|
1150
1156
|
}
|
|
1151
1157
|
/** A derive’s value as the strings a `period` stores: one string, or the
|
|
1152
|
-
* strings in a list
|
|
1153
|
-
|
|
1158
|
+
* strings in a list. `null` and `undefined` state no value; anything else
|
|
1159
|
+
* is reported. */
|
|
1160
|
+
function derivedStrings(derived, unread) {
|
|
1154
1161
|
const values = Array.isArray(derived) ? derived : [derived];
|
|
1155
|
-
return values.filter((value) =>
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
}
|
|
1162
|
+
return values.filter((value) => {
|
|
1163
|
+
if (typeof value !== 'string' && value !== undefined && value !== null) {
|
|
1164
|
+
unread(describeValue(value));
|
|
1165
|
+
}
|
|
1166
|
+
return typeof value === 'string';
|
|
1167
|
+
});
|
|
1161
1168
|
}
|
|
1162
1169
|
function dedupe(values) {
|
|
1163
1170
|
return [...new Set(values)];
|
|
@@ -1187,3 +1194,15 @@ function setArray(document, field, values) {
|
|
|
1187
1194
|
function isObject(value) {
|
|
1188
1195
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1189
1196
|
}
|
|
1197
|
+
/** The sink a root document’s fields report to: the context’s
|
|
1198
|
+
* {@link ProjectionContext.onUnreadable}, told which node the value was
|
|
1199
|
+
* dropped from. */
|
|
1200
|
+
function unreadableSink(context, id) {
|
|
1201
|
+
const { onUnreadable } = context;
|
|
1202
|
+
return onUnreadable === undefined
|
|
1203
|
+
? ignoreUnreadable
|
|
1204
|
+
: (field, value) => onUnreadable({ field, value, id });
|
|
1205
|
+
}
|
|
1206
|
+
function ignoreUnreadable() {
|
|
1207
|
+
// Nobody asked for the report.
|
|
1208
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type StoredDate } from './schema.js';
|
|
2
|
+
/** What reading a value that is there, but not one its kind can store, gives:
|
|
3
|
+
* `onbekend` for an integer, `ja` for a boolean. */
|
|
4
|
+
export declare const UNREADABLE: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* One value read as a field’s kind: what the field stores, {@link UNREADABLE}
|
|
7
|
+
* for a value it cannot store, or `undefined` for no value at all – a missing
|
|
8
|
+
* or blank one, which states nothing to correct at the source.
|
|
9
|
+
*/
|
|
10
|
+
type Reading<T> = T | typeof UNREADABLE | undefined;
|
|
11
|
+
/** The kinds that can refuse a value: the rest store any literal. */
|
|
12
|
+
declare const READ_KINDS: readonly ["integer", "number", "boolean", "date", "reference"];
|
|
13
|
+
/** One of {@link READ_KINDS}. */
|
|
14
|
+
export type ReadKind = (typeof READ_KINDS)[number];
|
|
15
|
+
/** Whether `kind` is one {@link readValue} reads. */
|
|
16
|
+
export declare function isReadKind(kind: string): kind is ReadKind;
|
|
17
|
+
/**
|
|
18
|
+
* Read one value – a literal from the graph, or whatever a `derive` returned –
|
|
19
|
+
* as `kind` stores it. Both value routes read through here, so a kind accepts
|
|
20
|
+
* and refuses the same values whichever route they came by.
|
|
21
|
+
*
|
|
22
|
+
* - `integer`: a finite number, or an `xsd:decimal` or `xsd:double` lexical
|
|
23
|
+
* form, truncated, within ±2⁵³ – beyond that a double no longer holds every
|
|
24
|
+
* integer, so the stored value would not be the stated one. `INF` and `NaN`
|
|
25
|
+
* are refused: an engine cannot store them.
|
|
26
|
+
* - `number`: any finite one, untruncated.
|
|
27
|
+
* - `boolean`: `true` or `false`, or the `xsd:boolean` lexical space
|
|
28
|
+
* (`true`/`false`/`1`/`0`).
|
|
29
|
+
* - `date`: anything the `date` codec reads ({@link isoToUnixSeconds}), or
|
|
30
|
+
* Unix seconds as a number.
|
|
31
|
+
* - `reference`: an absolute IRI.
|
|
32
|
+
*
|
|
33
|
+
* A string is read with surrounding whitespace collapsed, as XSD does.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readValue(kind: ReadKind, value: unknown): Reading<number | boolean | string | StoredDate>;
|
|
36
|
+
/**
|
|
37
|
+
* A value as the report shows it: `String(value)`, or its `typeof` when it has
|
|
38
|
+
* no string form, as an object built without a prototype has not. Never
|
|
39
|
+
* throws, so a derive’s odd return is reported rather than crashing the
|
|
40
|
+
* projection.
|
|
41
|
+
*/
|
|
42
|
+
export declare function describeValue(value: unknown): string;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=read-value.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-value.d.ts","sourceRoot":"","sources":["../src/read-value.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AAErB;qDACqD;AACrD,eAAO,MAAM,UAAU,eAAuB,CAAC;AAE/C;;;;GAIG;AACH,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,UAAU,GAAG,SAAS,CAAC;AAEpD,qEAAqE;AACrE,QAAA,MAAM,UAAU,gEAMN,CAAC;AAEX,iCAAiC;AACjC,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,QAAQ,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC,CA8CjD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMpD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isAbsoluteIri, isoToUnixSeconds, storableUnixSeconds, } from './schema.js';
|
|
2
|
+
/** What reading a value that is there, but not one its kind can store, gives:
|
|
3
|
+
* `onbekend` for an integer, `ja` for a boolean. */
|
|
4
|
+
export const UNREADABLE = Symbol('unreadable');
|
|
5
|
+
/** The kinds that can refuse a value: the rest store any literal. */
|
|
6
|
+
const READ_KINDS = [
|
|
7
|
+
'integer',
|
|
8
|
+
'number',
|
|
9
|
+
'boolean',
|
|
10
|
+
'date',
|
|
11
|
+
'reference',
|
|
12
|
+
];
|
|
13
|
+
/** Whether `kind` is one {@link readValue} reads. */
|
|
14
|
+
export function isReadKind(kind) {
|
|
15
|
+
return READ_KINDS.includes(kind);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read one value – a literal from the graph, or whatever a `derive` returned –
|
|
19
|
+
* as `kind` stores it. Both value routes read through here, so a kind accepts
|
|
20
|
+
* and refuses the same values whichever route they came by.
|
|
21
|
+
*
|
|
22
|
+
* - `integer`: a finite number, or an `xsd:decimal` or `xsd:double` lexical
|
|
23
|
+
* form, truncated, within ±2⁵³ – beyond that a double no longer holds every
|
|
24
|
+
* integer, so the stored value would not be the stated one. `INF` and `NaN`
|
|
25
|
+
* are refused: an engine cannot store them.
|
|
26
|
+
* - `number`: any finite one, untruncated.
|
|
27
|
+
* - `boolean`: `true` or `false`, or the `xsd:boolean` lexical space
|
|
28
|
+
* (`true`/`false`/`1`/`0`).
|
|
29
|
+
* - `date`: anything the `date` codec reads ({@link isoToUnixSeconds}), or
|
|
30
|
+
* Unix seconds as a number.
|
|
31
|
+
* - `reference`: an absolute IRI.
|
|
32
|
+
*
|
|
33
|
+
* A string is read with surrounding whitespace collapsed, as XSD does.
|
|
34
|
+
*/
|
|
35
|
+
export function readValue(kind, value) {
|
|
36
|
+
if (value === undefined || value === null) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const text = typeof value === 'string' ? value.trim() : undefined;
|
|
40
|
+
if (text === '') {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
switch (kind) {
|
|
44
|
+
case 'integer':
|
|
45
|
+
case 'number': {
|
|
46
|
+
const number = typeof value === 'number'
|
|
47
|
+
? value
|
|
48
|
+
: text !== undefined && NUMERIC.test(text)
|
|
49
|
+
? Number(text)
|
|
50
|
+
: Number.NaN;
|
|
51
|
+
if (kind === 'number') {
|
|
52
|
+
return Number.isFinite(number) ? number : UNREADABLE;
|
|
53
|
+
}
|
|
54
|
+
const integer = Math.trunc(number);
|
|
55
|
+
return Number.isSafeInteger(integer) ? integer : UNREADABLE;
|
|
56
|
+
}
|
|
57
|
+
case 'boolean':
|
|
58
|
+
if (typeof value === 'boolean') {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
return text !== undefined && BOOLEAN.has(text)
|
|
62
|
+
? text === 'true' || text === '1'
|
|
63
|
+
: UNREADABLE;
|
|
64
|
+
case 'date': {
|
|
65
|
+
if (typeof value === 'number') {
|
|
66
|
+
// Truncated, as the codec truncates a string’s fractional seconds: the
|
|
67
|
+
// field is an int64. `storableUnixSeconds` drops NaN.
|
|
68
|
+
const seconds = storableUnixSeconds(Math.trunc(value));
|
|
69
|
+
return seconds === undefined
|
|
70
|
+
? UNREADABLE
|
|
71
|
+
: { seconds, precision: 'dateTime' };
|
|
72
|
+
}
|
|
73
|
+
return ((text === undefined ? undefined : isoToUnixSeconds(text)) ?? UNREADABLE);
|
|
74
|
+
}
|
|
75
|
+
case 'reference':
|
|
76
|
+
return text !== undefined && isAbsoluteIri(text) ? text : UNREADABLE;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A value as the report shows it: `String(value)`, or its `typeof` when it has
|
|
81
|
+
* no string form, as an object built without a prototype has not. Never
|
|
82
|
+
* throws, so a derive’s odd return is reported rather than crashing the
|
|
83
|
+
* projection.
|
|
84
|
+
*/
|
|
85
|
+
export function describeValue(value) {
|
|
86
|
+
try {
|
|
87
|
+
return String(value);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return typeof value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** The `xsd:decimal` and `xsd:double` lexical forms, `INF` and `NaN` aside. */
|
|
94
|
+
const NUMERIC = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
95
|
+
/** The `xsd:boolean` lexical space. */
|
|
96
|
+
const BOOLEAN = new Set(['true', 'false', '1', '0']);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Engine- and domain-agnostic core for RDF-backed search: a unified declarative field model (SearchField/SearchType/SearchSchema), a neutral query IR, the SearchEngine port with logical result types, and a streaming CONSTRUCT-to-document projection. Bakes in no engine, protocol, or domain.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git+https://github.com/ldelements/lde.git",
|