@lde/search-typesense 0.44.2 → 0.46.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/collection-definition.js +40 -0
- package/dist/query-compiler.d.ts.map +1 -1
- package/dist/query-compiler.js +29 -19
- package/dist/search.js +3 -1
- package/package.json +3 -3
|
@@ -196,6 +196,9 @@ function typesenseFields(searchType, field, defaultLocale, defaultSortingField,
|
|
|
196
196
|
})),
|
|
197
197
|
];
|
|
198
198
|
}
|
|
199
|
+
if (field.kind === 'period') {
|
|
200
|
+
return periodFields(field, names);
|
|
201
|
+
}
|
|
199
202
|
const valueType = typesenseValueType(field);
|
|
200
203
|
const fields = [
|
|
201
204
|
{
|
|
@@ -245,6 +248,32 @@ function typesenseFields(searchType, field, defaultLocale, defaultSortingField,
|
|
|
245
248
|
}
|
|
246
249
|
return fields;
|
|
247
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* A `period` field: its values verbatim, stored for output but never indexed –
|
|
253
|
+
* nothing filters or sorts on the text – and the two int64 bound companions
|
|
254
|
+
* ({@link physicalFields}) that filters and sorts read instead. The bounds are
|
|
255
|
+
* indexed for sorting whatever the field declares: a deployment may sort on a
|
|
256
|
+
* field it does not expose as sortable, and one more sort index on a numeric
|
|
257
|
+
* field costs little next to a collection rebuilt the day it becomes so.
|
|
258
|
+
*/
|
|
259
|
+
function periodFields(field, names) {
|
|
260
|
+
// Every period has bounds; `physicalFields` names them for no other kind.
|
|
261
|
+
const { start, end } = names.period;
|
|
262
|
+
return [
|
|
263
|
+
{
|
|
264
|
+
name: field.name,
|
|
265
|
+
type: typesenseValueType(field),
|
|
266
|
+
index: false,
|
|
267
|
+
optional: field.required !== true,
|
|
268
|
+
},
|
|
269
|
+
...[start, end].map((name) => ({
|
|
270
|
+
name,
|
|
271
|
+
type: 'int64',
|
|
272
|
+
sort: true,
|
|
273
|
+
optional: true,
|
|
274
|
+
})),
|
|
275
|
+
];
|
|
276
|
+
}
|
|
248
277
|
/** A `date` field’s precision companion ({@link physicalFields}): read back
|
|
249
278
|
* with the hit to format the value, never queried, so stored but not
|
|
250
279
|
* indexed – it costs disk, not RAM. */
|
|
@@ -524,6 +553,16 @@ function nestedLeafFields(prefix, field, defaultLocale, flattensToArray) {
|
|
|
524
553
|
}));
|
|
525
554
|
return fields;
|
|
526
555
|
}
|
|
556
|
+
if (names.period !== undefined) {
|
|
557
|
+
// No filter reaches a nested period, so its value and its bounds are
|
|
558
|
+
// stored for output and never indexed.
|
|
559
|
+
return periodFields(field, names).map(({ name, type }) => ({
|
|
560
|
+
name: nestedFieldName(prefix, name),
|
|
561
|
+
type,
|
|
562
|
+
index: false,
|
|
563
|
+
optional: true,
|
|
564
|
+
}));
|
|
565
|
+
}
|
|
527
566
|
// A nested field can be `filterable` or `searchable`, never `facetable` or
|
|
528
567
|
// `sortable` (`searchSchema`), so `filterable` is what indexes the value
|
|
529
568
|
// itself – its search companion below indexes on its own account.
|
|
@@ -601,6 +640,7 @@ function typesenseValueType(field) {
|
|
|
601
640
|
case 'keyword':
|
|
602
641
|
case 'reference':
|
|
603
642
|
case 'text':
|
|
643
|
+
case 'period':
|
|
604
644
|
return field.array === true ? 'string[]' : 'string';
|
|
605
645
|
}
|
|
606
646
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-compiler.d.ts","sourceRoot":"","sources":["../src/query-compiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEzE,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,QAAQ,EAEb,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EAIhB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"query-compiler.d.ts","sourceRoot":"","sources":["../src/query-compiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEzE,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,QAAQ,EAEb,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EAIhB,MAAM,aAAa,CAAC;AAqBrB;mDACmD;AACnD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,SAAS,MAAM,EAAE,KACpB,UAAU,GAAG,SAAS,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,MAAM,CAAC,CAkCtB;AAwhBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
package/dist/query-compiler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fold } from '@lde/text-normalization';
|
|
2
|
-
import { fieldNamed, filterOperator, filterOperatorFor, ID_FIELD,
|
|
2
|
+
import { fieldNamed, filterOperator, filterOperatorFor, ID_FIELD, storedFacetRanges, storedRangeBound, isInternalField, isBinnedRangeFacet, isWelded, joinGraph, localLookupTargetsOf, matchLanguage, pageForOffset, physicalFields, nestedReferenceType, resolvePath, searchableFields, } from '@lde/search/adapter';
|
|
3
3
|
/**
|
|
4
4
|
* Compile the engine-neutral {@link SearchQuery} into Typesense search
|
|
5
5
|
* parameters – the query half of the engine adapter. Pure (no client, no env),
|
|
@@ -411,7 +411,8 @@ function compileMembership(field, values, schema, prefix = '') {
|
|
|
411
411
|
const name = qualify(prefix, names.identity ?? field.name);
|
|
412
412
|
return names.facet === name ? `${name}:${list}` : `${name}:=${list}`;
|
|
413
413
|
}
|
|
414
|
-
/** An inclusive Typesense range clause
|
|
414
|
+
/** An inclusive Typesense range clause; `UNUSABLE` for a range that ends
|
|
415
|
+
* before it starts, which holds no value; or `undefined` when neither bound is
|
|
415
416
|
* usable. Which of the two readings that is – the caller set no bounds, or the
|
|
416
417
|
* codec rejected the ones they set – is deliberately NOT decided here: a
|
|
417
418
|
* criterion dropped from a clause narrows the query while a clause dropped
|
|
@@ -419,13 +420,29 @@ function compileMembership(field, values, schema, prefix = '') {
|
|
|
419
420
|
* positions. `validateQuery` rejects an unreadable bound outright instead,
|
|
420
421
|
* which is the only answer that neither widens nor narrows. */
|
|
421
422
|
function compileRange(field, range, prefix = '') {
|
|
422
|
-
const name = qualify(prefix, field.name);
|
|
423
423
|
// A bound a caller sent as `null` – a GraphQL variable left unfilled, which
|
|
424
424
|
// the surface passes through – is a bound NOT SET, not a bound of `null`.
|
|
425
425
|
// Read literally it reaches the engine as `datePosted:[null..…]`, which
|
|
426
426
|
// Typesense rejects outright.
|
|
427
|
-
const min =
|
|
428
|
-
const max =
|
|
427
|
+
const min = storedRangeBound(field, range.min ?? undefined, 'min');
|
|
428
|
+
const max = storedRangeBound(field, range.max ?? undefined, 'max');
|
|
429
|
+
// A range that ends before it starts holds no value. Compiled, a period’s
|
|
430
|
+
// overlap would match everything spanning the gap instead.
|
|
431
|
+
if (min !== undefined && max !== undefined && min > max) {
|
|
432
|
+
return UNUSABLE;
|
|
433
|
+
}
|
|
434
|
+
const period = physicalFields(field).period;
|
|
435
|
+
if (period !== undefined) {
|
|
436
|
+
// Overlap: the period starts no later than the range ends, and ends no
|
|
437
|
+
// earlier than it starts – so `1935/1955` matches 1940–1950.
|
|
438
|
+
const startsBefore = max === undefined ? undefined : `${period.start}:<=${max}`;
|
|
439
|
+
const endsAfter = min === undefined ? undefined : `${period.end}:>=${min}`;
|
|
440
|
+
// Parenthesised, because the clause may stand beside others in an `||`.
|
|
441
|
+
return startsBefore !== undefined && endsAfter !== undefined
|
|
442
|
+
? `(${startsBefore} && ${endsAfter})`
|
|
443
|
+
: (startsBefore ?? endsAfter);
|
|
444
|
+
}
|
|
445
|
+
const name = qualify(prefix, field.name);
|
|
429
446
|
if (min !== undefined && max !== undefined) {
|
|
430
447
|
return `${name}:[${min}..${max}]`;
|
|
431
448
|
}
|
|
@@ -437,20 +454,6 @@ function compileRange(field, range, prefix = '') {
|
|
|
437
454
|
}
|
|
438
455
|
return undefined;
|
|
439
456
|
}
|
|
440
|
-
/** A range bound as stored: a `date` field’s ISO 8601 bound becomes the indexed
|
|
441
|
-
* Unix seconds ({@link isoToUnixSeconds}) – a `min` its period’s first second,
|
|
442
|
-
* a `max` its last ({@link lastSecondOf}), so the inclusive clause takes in the
|
|
443
|
-
* whole period each names. An unparseable bound is dropped. */
|
|
444
|
-
function storedBound(field, bound, side) {
|
|
445
|
-
if (field.kind !== 'date' || typeof bound !== 'string') {
|
|
446
|
-
return bound;
|
|
447
|
-
}
|
|
448
|
-
const stored = isoToUnixSeconds(bound);
|
|
449
|
-
if (stored === undefined) {
|
|
450
|
-
return undefined;
|
|
451
|
-
}
|
|
452
|
-
return side === 'min' ? stored.seconds : lastSecondOf(stored);
|
|
453
|
-
}
|
|
454
457
|
/**
|
|
455
458
|
* One `sort_by` term. `relevance` maps to Typesense’s `_text_match`; a localized
|
|
456
459
|
* text field sorts on its active-locale folded key; any other field (including a
|
|
@@ -472,6 +475,13 @@ function compileSort(sort, searchType, locale) {
|
|
|
472
475
|
return `${sortName}:${sort.direction}`;
|
|
473
476
|
}
|
|
474
477
|
}
|
|
478
|
+
const period = field === undefined ? undefined : physicalFields(field).period;
|
|
479
|
+
if (period !== undefined) {
|
|
480
|
+
// Earliest first orders on where a period begins, latest first on where it
|
|
481
|
+
// ends, so each direction ranks a period by the end of it that direction
|
|
482
|
+
// reaches first.
|
|
483
|
+
return `${sort.direction === 'asc' ? period.start : period.end}:${sort.direction}`;
|
|
484
|
+
}
|
|
475
485
|
return `${sort.field}:${sort.direction}`;
|
|
476
486
|
}
|
|
477
487
|
/**
|
package/dist/search.js
CHANGED
|
@@ -846,7 +846,9 @@ function logicalValue(flat, field, labels, schema, referents) {
|
|
|
846
846
|
? undefined
|
|
847
847
|
: referenceValue(flat, field, labels);
|
|
848
848
|
}
|
|
849
|
-
|
|
849
|
+
// A period is served as stated; its bound companions are for the engine.
|
|
850
|
+
case 'keyword':
|
|
851
|
+
case 'period': {
|
|
850
852
|
const value = flat[field.name];
|
|
851
853
|
return Array.isArray(value) || typeof value === 'string'
|
|
852
854
|
? value
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search-typesense",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Typesense implementation of the @lde/search SearchEngine port: collection-schema builder, query compiler, label-resolving result reconstruction, and blue/green index lifecycle. Engine-specific (Typesense) but domain-agnostic.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"!**/*.tsbuildinfo"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@lde/search": "^0.
|
|
28
|
+
"@lde/search": "^0.38.0",
|
|
29
29
|
"@lde/text-normalization": "^0.3.0",
|
|
30
30
|
"tslib": "^2.3.0",
|
|
31
31
|
"typesense": "^3.2.0"
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@lde/dataset": "^0.10.1",
|
|
39
|
-
"@lde/pipeline": "^0.
|
|
39
|
+
"@lde/pipeline": "^0.43.0"
|
|
40
40
|
}
|
|
41
41
|
}
|