@lde/search 0.37.0 → 0.38.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 +144 -49
- 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,EAwBL,KAAK,eAAe,EAIpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAGhB,MAAM,aAAa,CAAC;AAGrB;;;;;;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;
|
|
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,EAwBL,KAAK,eAAe,EAIpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAGhB,MAAM,aAAa,CAAC;AAGrB;;;;;;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
|
@@ -2,7 +2,7 @@ import { fold } from '@lde/text-normalization';
|
|
|
2
2
|
import { matchLanguage } from './language.js';
|
|
3
3
|
import { buildSubjectIndex, frameSubjects, } from './frame-by-type.js';
|
|
4
4
|
import { companionNames, assertTypeInSchema, displayFieldName, displayLangOf, documentKeyOf, endpointFieldOf, fieldNamed, identityFieldOf, inheritedFacetPolicies, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference, isoToUnixSeconds, localLookupTargetsOf, physicalFields, referencedTargetsOf, referenceTypeNamed, storableUnixSeconds, storedTargetOf, TARGET_FIELD, } from './schema.js';
|
|
5
|
-
import {
|
|
5
|
+
import { periodCovering, periodOf } from './period.js';
|
|
6
6
|
/** The context a projection runs with when its caller supplied none. */
|
|
7
7
|
const NO_PROJECTION_CONTEXT = {};
|
|
8
8
|
/**
|
|
@@ -20,12 +20,13 @@ const NO_PROJECTION_CONTEXT = {};
|
|
|
20
20
|
* the engine collection definition and the query compiler.
|
|
21
21
|
*/
|
|
22
22
|
export function projectDocument(node, searchType, schema, context = NO_PROJECTION_CONTEXT) {
|
|
23
|
-
|
|
23
|
+
const key = documentKey(node);
|
|
24
|
+
if (key === undefined) {
|
|
24
25
|
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
26
|
}
|
|
26
27
|
// The guard above is what makes this a SearchDocument: projectFields sets `id`
|
|
27
28
|
// for exactly the nodes documentKey resolves.
|
|
28
|
-
const document = projectFields(node, searchType, schema, context);
|
|
29
|
+
const document = projectFields(node, searchType, schema, context, unreadableSink(context, key));
|
|
29
30
|
pruneInternalFields(document, searchType, schema);
|
|
30
31
|
return document;
|
|
31
32
|
}
|
|
@@ -93,10 +94,10 @@ function pruneInternalFields(document, searchType, schema) {
|
|
|
93
94
|
* Pruning is deferred to a single recursive pass ({@link pruneInternalFields})
|
|
94
95
|
* once the whole nested structure is projected, so a derive at any depth can
|
|
95
96
|
* still read an internal field before it is removed. */
|
|
96
|
-
function projectFields(node, searchType, schema, context, nested = false) {
|
|
97
|
+
function projectFields(node, searchType, schema, context, sink, nested = false) {
|
|
97
98
|
const document = emptyDocument(documentIdOf(node, searchType), searchType);
|
|
98
99
|
for (const field of searchType.fields) {
|
|
99
|
-
applyField(document, node, field, searchType, schema, context, nested);
|
|
100
|
+
applyField(document, node, field, searchType, schema, context, sink, nested);
|
|
100
101
|
}
|
|
101
102
|
return document;
|
|
102
103
|
}
|
|
@@ -255,7 +256,13 @@ function hasValue(document, field, searchType) {
|
|
|
255
256
|
(displayLangOf(field, key) !== undefined &&
|
|
256
257
|
fieldNamed(searchType, key) === undefined)));
|
|
257
258
|
}
|
|
258
|
-
function applyField(document, node, field, searchType, schema, context, nested) {
|
|
259
|
+
function applyField(document, node, field, searchType, schema, context, sink, nested) {
|
|
260
|
+
// A blank value states nothing, so there is nothing to correct at the source.
|
|
261
|
+
const unread = (value) => {
|
|
262
|
+
if (value.trim() !== '') {
|
|
263
|
+
sink(field.name, value);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
259
266
|
// The three value sources, mutually exclusive by declaration
|
|
260
267
|
// (`validateSearchType`): a projection value, a computed value, a graph path.
|
|
261
268
|
if ((field.kind === 'keyword' || field.kind === 'reference') &&
|
|
@@ -271,13 +278,13 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
271
278
|
// value that survives nothing leaves the field absent, exactly as a derive
|
|
272
279
|
// returning `undefined` does.
|
|
273
280
|
if (field.kind === 'date') {
|
|
274
|
-
return applyDerivedDate(document, field, derived);
|
|
281
|
+
return applyDerivedDate(document, field, derived, unread);
|
|
275
282
|
}
|
|
276
283
|
if (field.kind === 'period') {
|
|
277
|
-
return setPeriod(document, field, derivedStrings(derived));
|
|
284
|
+
return setPeriod(document, field, derivedStrings(derived, unread), unread);
|
|
278
285
|
}
|
|
279
286
|
const value = field.kind === 'reference' && derived !== undefined
|
|
280
|
-
? derivedIris(derived)
|
|
287
|
+
? derivedIris(derived, unread)
|
|
281
288
|
: derived;
|
|
282
289
|
// NaN is not a value any kind stores, and a derive is the only route that
|
|
283
290
|
// can produce one (`setNumber` guards the read route). It serializes as
|
|
@@ -286,6 +293,9 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
286
293
|
if (value !== undefined && !Number.isNaN(value)) {
|
|
287
294
|
document[field.name] = value;
|
|
288
295
|
}
|
|
296
|
+
else if (value !== undefined) {
|
|
297
|
+
unread(String(value));
|
|
298
|
+
}
|
|
289
299
|
return;
|
|
290
300
|
}
|
|
291
301
|
if (field.path === undefined && field.name !== searchType.literalField) {
|
|
@@ -307,7 +317,7 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
307
317
|
// project nothing rather than fall through and emit the referent IRIs under
|
|
308
318
|
// the field name (the wrong shape).
|
|
309
319
|
if (schema !== undefined) {
|
|
310
|
-
applyInlineReference(document, node, alias, field, schema, context, nested);
|
|
320
|
+
applyInlineReference(document, node, alias, field, schema, context, sink, nested);
|
|
311
321
|
}
|
|
312
322
|
return;
|
|
313
323
|
}
|
|
@@ -318,7 +328,7 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
318
328
|
if (field.kind === 'reference' && schema !== undefined) {
|
|
319
329
|
const localTargets = localLookupTargetsOf(field, schema);
|
|
320
330
|
if (localTargets.length > 0) {
|
|
321
|
-
const endpoints = applyNestedReferents(document, valuesOf(node, alias), field, localTargets, schema, context);
|
|
331
|
+
const endpoints = applyNestedReferents(document, valuesOf(node, alias), field, localTargets, schema, context, sink);
|
|
322
332
|
// A `local` lookup stores an object, so its own id is a level deeper than
|
|
323
333
|
// a filter can reach – an engine welds conditions on an entry's LEAF
|
|
324
334
|
// fields only, and facets flat values. `filterable` and `facetable`
|
|
@@ -335,26 +345,41 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
335
345
|
return applyFacet(document, literalsOf(node, alias), field, schema);
|
|
336
346
|
case 'reference': {
|
|
337
347
|
const targetsById = new Map();
|
|
338
|
-
const values = referenceValues(node, alias, field, schema, targetsById);
|
|
348
|
+
const values = referenceValues(node, alias, field, schema, targetsById, unread);
|
|
339
349
|
rememberTargets(document, targetsById);
|
|
340
350
|
return applyFacet(document, values, field, schema, targetsById);
|
|
341
351
|
}
|
|
342
352
|
case 'integer':
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
353
|
+
case 'number': {
|
|
354
|
+
const literal = firstLiteralOf(node, alias);
|
|
355
|
+
const value = field.kind === 'integer' ? toInteger(literal) : toNumber(literal);
|
|
356
|
+
if (literal !== undefined && Number.isNaN(value)) {
|
|
357
|
+
unread(literal);
|
|
358
|
+
}
|
|
359
|
+
return setNumber(document, field.name, value);
|
|
360
|
+
}
|
|
346
361
|
case 'date': {
|
|
347
362
|
const literal = firstLiteralOf(node, alias);
|
|
348
|
-
|
|
363
|
+
const stored = literal === undefined ? undefined : isoToUnixSeconds(literal);
|
|
364
|
+
if (literal !== undefined && stored === undefined) {
|
|
365
|
+
unread(literal);
|
|
366
|
+
}
|
|
367
|
+
return setDate(document, field, stored);
|
|
349
368
|
}
|
|
350
369
|
case 'period':
|
|
351
|
-
return setPeriod(document, field, literalsOf(node, alias));
|
|
370
|
+
return setPeriod(document, field, literalsOf(node, alias), unread);
|
|
352
371
|
case 'boolean': {
|
|
353
|
-
// The xsd:boolean lexical space: true/false/1/0.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
372
|
+
// The xsd:boolean lexical space: true/false/1/0. Anything else – `ja`,
|
|
373
|
+
// `yes` – states neither, so it is left out rather than stored as false.
|
|
374
|
+
// Surrounding whitespace is collapsed, as xsd:boolean’s facet says.
|
|
375
|
+
const literal = firstLiteralOf(node, alias)?.trim();
|
|
376
|
+
if (literal === undefined) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (!BOOLEAN_LEXICAL_SPACE.has(literal)) {
|
|
380
|
+
return unread(literal);
|
|
357
381
|
}
|
|
382
|
+
document[field.name] = literal === 'true' || literal === '1';
|
|
358
383
|
return;
|
|
359
384
|
}
|
|
360
385
|
}
|
|
@@ -385,7 +410,16 @@ function applyField(document, node, field, searchType, schema, context, nested)
|
|
|
385
410
|
* referent’s node IRIs therefore sees a key instead, which is why the two are
|
|
386
411
|
* worth declaring together only deliberately.
|
|
387
412
|
*/
|
|
388
|
-
function referenceValues(node, alias, field, schema, targetsById) {
|
|
413
|
+
function referenceValues(node, alias, field, schema, targetsById, unread) {
|
|
414
|
+
// A literal where a referent belongs states no IRI to store. A node without
|
|
415
|
+
// one – a blank node – carries no value a publisher could look up, so only
|
|
416
|
+
// literals are reported.
|
|
417
|
+
for (const value of valuesOf(node, alias)) {
|
|
418
|
+
const literal = iriString(value) === undefined ? literalString(value) : undefined;
|
|
419
|
+
if (literal !== undefined) {
|
|
420
|
+
unread(literal);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
389
423
|
// Guaranteed declared: `searchSchema` validates that every named target
|
|
390
424
|
// resolves, and the projection only ever runs against a type of its schema.
|
|
391
425
|
const targets = referencedTargetsOf(field, schema);
|
|
@@ -685,7 +719,7 @@ function applyProjectionValue(document, field, from, context, schema) {
|
|
|
685
719
|
* the schema’s declared depth. The referent type is guaranteed declared by
|
|
686
720
|
* {@link searchSchema}.
|
|
687
721
|
*/
|
|
688
|
-
function applyInlineReference(document, node, alias, field, schema, context, nested) {
|
|
722
|
+
function applyInlineReference(document, node, alias, field, schema, context, sink, nested) {
|
|
689
723
|
// Resolves for a schema that declares the referent (always so for the schema a
|
|
690
724
|
// type is projected through); a type framed against a foreign schema that
|
|
691
725
|
// omits it simply contributes no nesting.
|
|
@@ -695,7 +729,7 @@ function applyInlineReference(document, node, alias, field, schema, context, nes
|
|
|
695
729
|
}
|
|
696
730
|
const asEdge = edgeReader(referenceType, field.ref.identity);
|
|
697
731
|
const asLiteralEntry = literalReader(referenceType);
|
|
698
|
-
const referents = applyNestedReferents(document, valuesOf(node, alias).map((value) => asLiteralEntry(value) ?? asEdge(value)), field, [referenceType], schema, context);
|
|
732
|
+
const referents = applyNestedReferents(document, valuesOf(node, alias).map((value) => asLiteralEntry(value) ?? asEdge(value)), field, [referenceType], schema, context, sink);
|
|
699
733
|
applyIdentityCompanion(document, referents, field, schema, nested);
|
|
700
734
|
}
|
|
701
735
|
/**
|
|
@@ -897,7 +931,7 @@ function identityValue(value) {
|
|
|
897
931
|
* top-level document of that type would be – so the stored id is the one the
|
|
898
932
|
* target’s collection files it under, and the lookup can find it.
|
|
899
933
|
*/
|
|
900
|
-
function applyNestedReferents(document, values, field, nestedTypes, schema, context) {
|
|
934
|
+
function applyNestedReferents(document, values, field, nestedTypes, schema, context, sink) {
|
|
901
935
|
// One node may stand for several entries: a weldable leaf is single-valued,
|
|
902
936
|
// so an edge the graph gave several roles or several endpoints fans out into
|
|
903
937
|
// one entry per combination BEFORE it is projected (ADR 26).
|
|
@@ -910,8 +944,18 @@ function applyNestedReferents(document, values, field, nestedTypes, schema, cont
|
|
|
910
944
|
const nestedType = nestedTypes.length === 1
|
|
911
945
|
? nestedTypes[0]
|
|
912
946
|
: targetOfReferent(value, nestedTypes);
|
|
947
|
+
// Every tuple of a node carries the node’s other values, so each value
|
|
948
|
+
// is reported once per node, not once per tuple it was copied into.
|
|
949
|
+
let reported;
|
|
950
|
+
const nestedSink = (name, value) => {
|
|
951
|
+
const key = JSON.stringify([name, value]);
|
|
952
|
+
if (!(reported ??= new Set()).has(key)) {
|
|
953
|
+
reported.add(key);
|
|
954
|
+
sink(`${field.name}.${name}`, value);
|
|
955
|
+
}
|
|
956
|
+
};
|
|
913
957
|
return tuplesOf(value, nestedType, field).map((tuple) => {
|
|
914
|
-
const projected = projectFields(tuple, nestedType, schema, context, true);
|
|
958
|
+
const projected = projectFields(tuple, nestedType, schema, context, nestedSink, true);
|
|
915
959
|
if (nestedTypes.length > 1 && Object.keys(projected).length > 0) {
|
|
916
960
|
projected[TARGET_FIELD] = nestedType.name;
|
|
917
961
|
}
|
|
@@ -1080,11 +1124,18 @@ function iriString(value) {
|
|
|
1080
1124
|
* promises (a list stays a list, a single value stays single) so `array` still
|
|
1081
1125
|
* decides the stored shape. A lone non-IRI becomes `undefined`, i.e. absent –
|
|
1082
1126
|
* the same outcome the graph path gives it. */
|
|
1083
|
-
function derivedIris(value) {
|
|
1127
|
+
function derivedIris(value, unread) {
|
|
1128
|
+
const isIri = (entry) => {
|
|
1129
|
+
const iri = typeof entry === 'string' && isAbsoluteIri(entry);
|
|
1130
|
+
if (!iri && entry !== undefined) {
|
|
1131
|
+
unread(String(entry));
|
|
1132
|
+
}
|
|
1133
|
+
return iri;
|
|
1134
|
+
};
|
|
1084
1135
|
if (Array.isArray(value)) {
|
|
1085
|
-
return value.filter(
|
|
1136
|
+
return value.filter(isIri);
|
|
1086
1137
|
}
|
|
1087
|
-
return
|
|
1138
|
+
return isIri(value) ? value : undefined;
|
|
1088
1139
|
}
|
|
1089
1140
|
function toInteger(literal) {
|
|
1090
1141
|
return literal === undefined ? undefined : Math.trunc(Number(literal));
|
|
@@ -1102,16 +1153,22 @@ function toNumber(literal) {
|
|
|
1102
1153
|
* field is stored in would be refused by the engine where an unparseable
|
|
1103
1154
|
* string merely leaves the field absent – so drop those too.
|
|
1104
1155
|
*/
|
|
1105
|
-
function applyDerivedDate(document, field, derived) {
|
|
1156
|
+
function applyDerivedDate(document, field, derived, unread) {
|
|
1157
|
+
let stored;
|
|
1106
1158
|
if (typeof derived === 'string') {
|
|
1107
|
-
|
|
1159
|
+
stored = isoToUnixSeconds(derived);
|
|
1108
1160
|
}
|
|
1109
|
-
if (typeof derived === 'number') {
|
|
1161
|
+
else if (typeof derived === 'number') {
|
|
1110
1162
|
// Truncated, as the codec truncates a string’s fractional seconds: the
|
|
1111
1163
|
// field is an int64. `storableUnixSeconds` drops NaN.
|
|
1112
1164
|
const seconds = storableUnixSeconds(Math.trunc(derived));
|
|
1113
|
-
|
|
1165
|
+
stored =
|
|
1166
|
+
seconds === undefined ? undefined : { seconds, precision: 'dateTime' };
|
|
1167
|
+
}
|
|
1168
|
+
if (derived !== undefined && stored === undefined) {
|
|
1169
|
+
unread(String(derived));
|
|
1114
1170
|
}
|
|
1171
|
+
setDate(document, field, stored);
|
|
1115
1172
|
}
|
|
1116
1173
|
/** Store a `date` field’s seconds, and its precision in the companion beside
|
|
1117
1174
|
* them ({@link physicalFields}), so the surface can serve the value in the
|
|
@@ -1124,35 +1181,54 @@ function setDate(document, field, stored) {
|
|
|
1124
1181
|
document[physicalFields(field).precision] = stored.precision;
|
|
1125
1182
|
}
|
|
1126
1183
|
/**
|
|
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
|
|
1184
|
+
* Store a `period` field: the values that state a period, verbatim under the
|
|
1185
|
+
* field’s own name, for output, and the stretch they cover between them in the
|
|
1186
|
+
* bound companions ({@link physicalFields}). A value that states no period –
|
|
1187
|
+
* `onbekend`, `1987/1897` – is reported and left out, so every value served is
|
|
1188
|
+
* the EDTF its scalar promises; a deployment that wants to show such text
|
|
1189
|
+
* declares a `keyword` field over the same path.
|
|
1131
1190
|
*
|
|
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.
|
|
1191
|
+
* Blank and unreadable values are dropped before a single-valued field keeps
|
|
1192
|
+
* its first, so neither can cost the field the period behind it; and the
|
|
1193
|
+
* bounds are taken from exactly the values stored, so what is served and what
|
|
1194
|
+
* is filtered cannot disagree.
|
|
1136
1195
|
*/
|
|
1137
|
-
function setPeriod(document, field, values) {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1196
|
+
function setPeriod(document, field, values, unread) {
|
|
1197
|
+
const readable = dedupe(values.map((value) => value.trim())).flatMap((value) => {
|
|
1198
|
+
if (value === '') {
|
|
1199
|
+
return [];
|
|
1200
|
+
}
|
|
1201
|
+
const period = periodOf(value);
|
|
1202
|
+
if (period === undefined) {
|
|
1203
|
+
unread(value);
|
|
1204
|
+
return [];
|
|
1205
|
+
}
|
|
1206
|
+
return [{ value, period }];
|
|
1207
|
+
});
|
|
1208
|
+
const stated = field.array === true ? readable : readable.slice(0, 1);
|
|
1140
1209
|
if (stated.length === 0) {
|
|
1141
1210
|
return;
|
|
1142
1211
|
}
|
|
1143
|
-
document[field.name] =
|
|
1212
|
+
document[field.name] =
|
|
1213
|
+
field.array === true ? stated.map(({ value }) => value) : stated[0].value;
|
|
1144
1214
|
const companions = physicalFields(field).period;
|
|
1145
|
-
|
|
1146
|
-
|
|
1215
|
+
if (companions !== undefined) {
|
|
1216
|
+
const [first, ...rest] = stated.map(({ period }) => period);
|
|
1217
|
+
const covered = periodCovering(first, ...rest);
|
|
1147
1218
|
document[companions.start] = covered.start;
|
|
1148
1219
|
document[companions.end] = covered.end;
|
|
1149
1220
|
}
|
|
1150
1221
|
}
|
|
1151
1222
|
/** A derive’s value as the strings a `period` stores: one string, or the
|
|
1152
1223
|
* strings in a list; anything else states no value. */
|
|
1153
|
-
function derivedStrings(derived) {
|
|
1224
|
+
function derivedStrings(derived, unread) {
|
|
1154
1225
|
const values = Array.isArray(derived) ? derived : [derived];
|
|
1155
|
-
return values.filter((value) =>
|
|
1226
|
+
return values.filter((value) => {
|
|
1227
|
+
if (typeof value !== 'string' && value !== undefined) {
|
|
1228
|
+
unread(String(value));
|
|
1229
|
+
}
|
|
1230
|
+
return typeof value === 'string';
|
|
1231
|
+
});
|
|
1156
1232
|
}
|
|
1157
1233
|
function setNumber(document, field, value) {
|
|
1158
1234
|
if (value !== undefined && !Number.isNaN(value)) {
|
|
@@ -1187,3 +1263,22 @@ function setArray(document, field, values) {
|
|
|
1187
1263
|
function isObject(value) {
|
|
1188
1264
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1189
1265
|
}
|
|
1266
|
+
/** The sink a root document’s fields report to: the context’s
|
|
1267
|
+
* {@link ProjectionContext.onUnreadable}, told which node the value was
|
|
1268
|
+
* dropped from. */
|
|
1269
|
+
function unreadableSink(context, id) {
|
|
1270
|
+
const { onUnreadable } = context;
|
|
1271
|
+
return onUnreadable === undefined
|
|
1272
|
+
? ignoreUnreadable
|
|
1273
|
+
: (field, value) => onUnreadable({ field, value, id });
|
|
1274
|
+
}
|
|
1275
|
+
function ignoreUnreadable() {
|
|
1276
|
+
// Nobody asked for the report.
|
|
1277
|
+
}
|
|
1278
|
+
/** The xsd:boolean lexical space. */
|
|
1279
|
+
const BOOLEAN_LEXICAL_SPACE = new Set([
|
|
1280
|
+
'true',
|
|
1281
|
+
'false',
|
|
1282
|
+
'1',
|
|
1283
|
+
'0',
|
|
1284
|
+
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.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",
|