@lde/search-typesense 0.29.3 → 0.30.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.
@@ -73,7 +73,8 @@ export interface CollectionDefinitionOptions {
73
73
  * size of the fields you search, facet or sort on – not the whole document.
74
74
  * This builder keeps that surface minimal: the `output` display labels land in
75
75
  * a single `index: false` regex field (`${name}_<lang>`, one value per present
76
- * language), kept on disk and read back only for a hit, so they cost no RAM;
76
+ * language, or every value of it as a `string[]` for an `array` field), kept
77
+ * on disk and read back only for a hit, so they cost no RAM;
77
78
  * only the folded `*_search_${locale}`, facet/reference and `*_sort_${locale}`
78
79
  * companions are indexed. Keeping retrieval-only fields un-indexed is the lever
79
80
  * for holding a large index’s RAM down.
@@ -1 +1 @@
1
- {"version":3,"file":"collection-definition.d.ts","sourceRoot":"","sources":["../src/collection-definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AAcrB,0EAA0E;AAC1E,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,MAAM,CAAC;IAChE;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B;;;4BAGwB;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,kEAAkE;IAClE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,2BAAgC,GACxC,sBAAsB,CA2BxB"}
1
+ {"version":3,"file":"collection-definition.d.ts","sourceRoot":"","sources":["../src/collection-definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AAcrB,0EAA0E;AAC1E,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,MAAM,CAAC;IAChE;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B;;;4BAGwB;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,kEAAkE;IAClE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,2BAAgC,GACxC,sBAAsB,CA2BxB"}
@@ -27,7 +27,8 @@ import { deriveCollectionName } from './collection-name.js';
27
27
  * size of the fields you search, facet or sort on – not the whole document.
28
28
  * This builder keeps that surface minimal: the `output` display labels land in
29
29
  * a single `index: false` regex field (`${name}_<lang>`, one value per present
30
- * language), kept on disk and read back only for a hit, so they cost no RAM;
30
+ * language, or every value of it as a `string[]` for an `array` field), kept
31
+ * on disk and read back only for a hit, so they cost no RAM;
31
32
  * only the folded `*_search_${locale}`, facet/reference and `*_sort_${locale}`
32
33
  * companions are indexed. Keeping retrieval-only fields un-indexed is the lever
33
34
  * for holding a large index’s RAM down.
@@ -163,12 +164,14 @@ function typesenseFields(searchType, field, defaultLocale, defaultSortingField,
163
164
  // the folded `*_search` companions, so the display copies stay on disk and
164
165
  // off RAM (fetched only for a hit), accents preserved, and a language
165
166
  // outside `locales` still renders. This is the memory lever: RAM tracks
166
- // the search surface, not the display text. Absent for a non-output field.
167
+ // the search surface, not the display text. Absent for a non-output
168
+ // field. An `array` field stores every value of a language, so its
169
+ // pattern is typed a list – `array` decides the shape for every kind.
167
170
  ...(displayPattern !== undefined
168
171
  ? [
169
172
  {
170
173
  name: displayPattern,
171
- type: 'string',
174
+ type: typesenseValueType(field),
172
175
  index: false,
173
176
  optional: true,
174
177
  },
@@ -314,7 +317,7 @@ function nestedFields(prefix, reference, nestedType, schema, defaultLocale, with
314
317
  type: boundaryArray ? 'string[]' : 'string',
315
318
  index: false,
316
319
  optional: true,
317
- }, ...nestedIdentityFields(prefix, field, schema));
320
+ }, ...nestedIdentityFields(prefix, field, schema, flattensToArray));
318
321
  continue;
319
322
  }
320
323
  if (deeper !== undefined) {
@@ -324,7 +327,7 @@ function nestedFields(prefix, reference, nestedType, schema, defaultLocale, with
324
327
  // an engine welds conditions on an entry's LEAF fields only. Its
325
328
  // identity companion is that leaf, so it sits beside the object rather
326
329
  // than inside it.
327
- ...nestedIdentityFields(prefix, field, schema));
330
+ ...nestedIdentityFields(prefix, field, schema, flattensToArray));
328
331
  continue;
329
332
  }
330
333
  children.push(...nestedLeafFields(prefix, field, defaultLocale, flattensToArray));
@@ -381,7 +384,7 @@ function identityCompanionFields(field, schema) {
381
384
  /** The identity companion of a nested field that stores an object: a flat leaf
382
385
  * beside it, holding the ids a filter welds on. Empty for a nested field that
383
386
  * declares no query Role, which needs none. */
384
- function nestedIdentityFields(prefix, field, schema) {
387
+ function nestedIdentityFields(prefix, field, schema, flattensToArray) {
385
388
  const names = physicalFields(field, schema);
386
389
  if (names.identity === undefined) {
387
390
  return [];
@@ -389,11 +392,23 @@ function nestedIdentityFields(prefix, field, schema) {
389
392
  return [
390
393
  {
391
394
  name: nestedFieldName(prefix, names.identity),
392
- // Always a list, whatever the enclosing reference's arity: the projection
393
- // writes it with `setArray`, and an indexed field's declared type is
394
- // checked against what is stored – a `string` here rejects every
395
- // document carrying such an edge, at import.
396
- type: 'string[]',
395
+ // Typed by what the PATH yields across the document, exactly as the `id`
396
+ // beside it is, and by the same two routes every other declaration here
397
+ // uses: an `object[]` ancestor multiplying the entries, or the field's
398
+ // own `array` making each entry hold a list. A weldable leaf is
399
+ // single-valued (ADR 26), so a companion that a weld names contributes
400
+ // one id per entry – but a multi-valued reference reached through a
401
+ // locally-nested Root Type is not weldable and still harvests a list.
402
+ // Getting either route wrong rejects the document at import: Typesense
403
+ // enforces the declared arity wherever nothing widens the path.
404
+ // `filterable` is the condition because it is what the projection writes
405
+ // a single id under, not because it decides the split: `tuplesOf` splits
406
+ // the identity field whatever Role earned it. A `facetable`-only
407
+ // companion is declared a list, which is what a facet reads and what the
408
+ // projection writes for it.
409
+ type: flattensToArray || field.array === true || field.filterable !== true
410
+ ? 'string[]'
411
+ : 'string',
397
412
  index: true,
398
413
  optional: true,
399
414
  },
@@ -426,10 +441,11 @@ function nestedLeafFields(prefix, field, defaultLocale, flattensToArray) {
426
441
  if (pattern !== undefined) {
427
442
  // Display values are never indexed, whatever Roles the field declares:
428
443
  // search hits the folded companions below, so the display copies stay on
429
- // disk with every language they carry.
444
+ // disk with every language they carry – every value of it for an
445
+ // `array` field.
430
446
  fields.push({
431
447
  name: nestedFieldName(prefix, pattern),
432
- type: 'string',
448
+ type: typesenseValueType(field),
433
449
  index: false,
434
450
  optional: true,
435
451
  });
@@ -454,6 +470,10 @@ function nestedLeafFields(prefix, field, defaultLocale, flattensToArray) {
454
470
  // `object[]` flattens it into one. `typesenseValueType` honours `array` only
455
471
  // for the string-shaped kinds, so a multi-valued nested `integer` would
456
472
  // otherwise be declared scalar and rejected at import.
473
+ //
474
+ // A weldable leaf of a Reference Type is single-valued (ADR 26), but that
475
+ // narrows nothing here: this same path declares the fields of the Root Type a
476
+ // `local` lookup nests, where `array` and `filterable` may legitimately meet.
457
477
  const storesAList = flattensToArray || field.array === true;
458
478
  fields.push({
459
479
  name: nestedFieldName(prefix, field.name),
@@ -494,8 +514,11 @@ function arrayValueType(type) {
494
514
  return 'float[]';
495
515
  case 'bool':
496
516
  return 'bool[]';
497
- // Already a list: a multi-valued declaration under a multi-valued ancestor
498
- // flattens no further.
517
+ // Already a list, and reachable even though a weldable leaf of a Reference
518
+ // Type is single-valued (ADR 26): this path also declares the fields of the
519
+ // Root Type a `local` lookup nests, and `searchSchema` constrains only
520
+ // Reference Types. A root keyword may be `array` and `filterable` at once –
521
+ // an ordinary facet – so flattening it must widen no further.
499
522
  case 'string[]':
500
523
  return 'string[]';
501
524
  }
package/dist/search.js CHANGED
@@ -656,16 +656,20 @@ function logicalValue(flat, field, labels, schema, referents) {
656
656
  * recovers languages outside the declared `locales` too – a value tagged in an
657
657
  * undeclared language, or untagged (`und`), still reconstructs rather than being
658
658
  * dropped.
659
+ *
660
+ * A display field holds one string for a plain field, or a list of them for an
661
+ * `array` field.
659
662
  */
660
663
  function localizedValue(flat, field) {
661
664
  const map = {};
662
665
  for (const [key, value] of Object.entries(flat)) {
663
- if (typeof value !== 'string') {
666
+ const lang = displayLangOf(field, key);
667
+ if (lang === undefined) {
664
668
  continue;
665
669
  }
666
- const lang = displayLangOf(field, key);
667
- if (lang !== undefined) {
668
- map[lang] = [value];
670
+ const values = (Array.isArray(value) ? value : [value]).filter((entry) => typeof entry === 'string');
671
+ if (values.length > 0) {
672
+ map[lang] = values;
669
673
  }
670
674
  }
671
675
  return Object.keys(map).length > 0 ? map : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search-typesense",
3
- "version": "0.29.3",
3
+ "version": "0.30.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.24.3",
28
+ "@lde/search": "^0.25.0",
29
29
  "@lde/text-normalization": "^0.1.2",
30
30
  "tslib": "^2.3.0",
31
31
  "typesense": "^3.0.6"