cry-search 1.0.4 → 1.0.6

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/CLAUDE.md CHANGED
@@ -70,6 +70,14 @@ The numeric implementation uses a global token registry (`src/utils/tokenRegistr
70
70
  5. multiple periods are NOT stripped: "1.2.3" -> "1.2.3"
71
71
  2. we remove spaces in pattern digits - digits
72
72
  1. eg. "12 - 25" => "12-25"
73
+ 2b. we split tokens at letter-dash-digit, digit-dash-letter and letter-dash-letter boundaries
74
+ 1. eg "ABC-123" -> "ABC 123"
75
+ 2. eg "123-ABC" -> "123 ABC"
76
+ 3. eg "ABC-00123" -> "ABC 123" (after leading zero removal)
77
+ 4. eg "COVID-19" -> "COVID 19"
78
+ 5. eg "Ioana-Gabriela" -> "Ioana Gabriela"
79
+ 6. eg "Ioana-Maria-Sara" -> "Ioana Maria Sara" (chains split in one pass)
80
+ 7. digit-dash-digit stays as-is: "12-25" -> "12-25"
73
81
  3. we insert spaces between starting letters and digits (only at letter-to-digit boundaries)
74
82
  1. eg "SI123" -> "SI 123"
75
83
  2. eg "123SI" -> "123 SI"
@@ -85,9 +93,10 @@ The numeric implementation uses a global token registry (`src/utils/tokenRegistr
85
93
  3. matching
86
94
  1. we match words in any order
87
95
  2. number matching depends on TARGET token length (not query length):
88
- 1. short numbers (≤6 digits in target): whole match only
96
+ 1. short numbers (≤6 digits in target): start match (left-match)
97
+ - eg. query "12" vs target "1234" → match (starts with "12")
89
98
  - eg. query "123" vs target "123" → match
90
- - eg. query "12" vs target "123" → no match (target is short, needs exact)
99
+ - eg. query "34" vs target "1234" → no match (not at start)
91
100
  2. long numbers (>6 digits in target): startEnd match
92
101
  - eg. query "60731" vs target "3838989760731" → match (13-digit barcode)
93
102
  - eg. query "989" vs target "3838989760731" → no match (not at start/end)
@@ -186,7 +195,7 @@ The numeric implementation uses a global token registry (`src/utils/tokenRegistr
186
195
  11. SearchOpts:
187
196
  1. matchWords?: MatchMode (default: 'start') - how to match words
188
197
  2. matchNumbers?: MatchMode (default: 'startEnd') - how to match long numbers (>threshold digits)
189
- 3. matchNumbersShort?: MatchMode (default: 'whole') - how to match short numbers (≤threshold digits)
198
+ 3. matchNumbersShort?: MatchMode (default: 'start') - how to match short numbers (≤threshold digits)
190
199
  4. matchNumbersThreshold?: number (default: 6) - digit count threshold for short vs long
191
200
  12. LinkedSearchResultWithMeta<TPrimary, TLinked>:
192
201
  1. queryTokens: string[] - tokenized query
@@ -209,7 +218,7 @@ Override match behavior per token at search time. Priority: query prefix > field
209
218
  | `--word` | negation | Exclude (works for words and numbers) | `--123` excludes items with "123" |
210
219
  | `~word` | negation | Exclude (works for words and numbers) | `~iphone` or `~123` |
211
220
 
212
- **Note:** `-5` is NOT negation (it's a negative number). Use `--5` or `~5` to negate numbers.
221
+ **Note:** `-5` is NOT negation — the leading dash is stripped, so `-5` searches for `5`. Use `--5` or `~5` to negate numbers.
213
222
 
214
223
  ### Programmatic prefixes
215
224
  | Prefix | Mode |
@@ -229,6 +238,9 @@ Override match behavior per token at search time. Priority: query prefix > field
229
238
  6. `"..nik"` → finds tokens ending with "nik" (usenik, krajnik)
230
239
  7. `"60731"` → finds barcode "3838989760731" (13-digit target uses startEnd)
231
240
  8. `"=9001"` → finds exact "9001" but NOT "035585249001" or "9001917"
241
+ 9. `"12"` → finds "1234" (short number, start match by default)
242
+ 10. `"-123"` → finds invoice "ABC-00123" (dash stripped → "123", metadata has split token "123")
243
+ 11. `"abc 123"` → finds "ABC-00123" (letter-dash-digit split into separate tokens)
232
244
 
233
245
 
234
246
  ## Main Functions (Default = Numeric)
package/README.md CHANGED
@@ -323,7 +323,7 @@ Override match behavior per token at search time using prefixes:
323
323
  | `--word` | negation | Exclude results (works for words and numbers) |
324
324
  | `~word` | negation | Exclude results (works for words and numbers) |
325
325
 
326
- **Note:** `-5` is NOT negation (it's a negative number). Use `--5` or `~5` to negate numbers.
326
+ **Note:** `-5` is NOT negation — the leading dash is stripped, so `-5` searches for `5`. Use `--5` or `~5` to negate numbers.
327
327
 
328
328
  **Detecting negation:** Linked search results include a `hasNegation: boolean` flag indicating if the query contained negation tokens.
329
329
 
@@ -350,13 +350,35 @@ searchInDataReturnObjects('=9001', metadata, data);
350
350
  ### Number Matching
351
351
 
352
352
  Match mode for numbers is determined by the **target token's length** (not the query):
353
- - **Short numbers (≤6 digits)**: require exact (whole) match
353
+ - **Short numbers (≤6 digits)**: start (left) match
354
354
  - **Long numbers (>6 digits)**: allow start or end match (for barcodes)
355
355
 
356
356
  ```typescript
357
+ // Query "12" against code "1234" (4 digits) → MATCH (starts with 12)
357
358
  // Query "60731" against barcode "3838989760731" (13 digits) → MATCH (ends with 60731)
358
- // Query "123" against code "123" (3 digits) → MATCH (exact)
359
- // Query "12" against code "123" (3 digits) → NO MATCH (short number needs exact)
359
+ // Query "34" against code "1234" (4 digits) → NO MATCH (not at start)
360
+ // Use "=12" for exact match on short numbers
361
+ ```
362
+
363
+ ### Hyphen Splitting
364
+
365
+ Tokens are split at letter-dash-digit, digit-dash-letter and letter-dash-letter boundaries during preprocessing. Digit-dash-digit ranges (e.g., `12-25`) are preserved.
366
+
367
+ ```typescript
368
+ // "ABC-00123" → tokens "abc" and "123"
369
+ // "Ioana-Gabriela" → tokens "ioana" and "gabriela"
370
+ // "COVID-19" → tokens "covid" and "19"
371
+ // "12-25" → token "12-25" (range preserved)
372
+ ```
373
+
374
+ This enables matching by either part:
375
+
376
+ ```typescript
377
+ // Query "123" against "ABC-00123" → MATCH (token "123" exists)
378
+ // Query "-123" against "ABC-00123" → MATCH (leading dash stripped → "123")
379
+ // Query "-0123" against "ABC-00123" → MATCH (leading zeros + dash stripped → "123")
380
+ // Query "abc 123" against "ABC-00123" → MATCH (both tokens match)
381
+ // Query "gabriela" against "Ioana-Gabriela" → MATCH (split into "ioana" and "gabriela")
360
382
  ```
361
383
 
362
384
  Priority: query prefix > field match mode > global SearchOpts default.
package/UNIVERSE.md CHANGED
@@ -620,6 +620,43 @@ SearchUniverse provides methods to update data and metadata after initial load.
620
620
  └─────────────────────────────────────────────────────────────────────────────┘
621
621
  ```
622
622
 
623
+ ### Sync Collection (Snapshot Diff)
624
+
625
+ ```
626
+ ┌─────────────────────────────────────────────────────────────────────────────┐
627
+ │ universe.syncCollection(collectionName, newItems, options?) │
628
+ ├─────────────────────────────────────────────────────────────────────────────┤
629
+ │ │
630
+ │ Diffs a fresh full snapshot against the current collection state. │
631
+ │ Use for periodic refresh cycles when you don't have a delta. │
632
+ │ │
633
+ │ newItems = [ │
634
+ │ { _id: "s1", name: "Krajnik", address: "Ljubljana" }, ← unchanged │
635
+ │ { _id: "s2", name: "Novak", address: "Celje" }, ← updated │
636
+ │ { _id: "s3", name: "Horvat", address: "Koper" }, ← added │
637
+ │ // s4 from previous state is missing → removed │
638
+ │ ] │
639
+ │ │
640
+ │ Behavior: │
641
+ │ 1. Items in collection but absent from newItems → removed │
642
+ │ (data + metadata + primaryToLinked all cleaned) │
643
+ │ 2. Items in both → tokens compared; metadata.set only if changed │
644
+ │ 3. New items → tokenized, added to metadata + primaryToLinked │
645
+ │ 4. _deleted/_blocked items in newItems → removed from metadata │
646
+ │ (kept in data, same convention as updateSearchMetadata) │
647
+ │ │
648
+ │ Options: │
649
+ │ skipUpdateCheck: true → don't re-tokenize existing items (faster) │
650
+ │ data refs still get updated │
651
+ │ │
652
+ │ Returns: SyncResult │
653
+ │ { added, updated, removed, addedIds[], updatedIds[], removedIds[] } │
654
+ │ │
655
+ │ Single shared tokensMap is used across the whole snapshot for efficiency. │
656
+ │ │
657
+ └─────────────────────────────────────────────────────────────────────────────┘
658
+ ```
659
+
623
660
  ### Clear Universe
624
661
 
625
662
  ```
@@ -1,4 +1,4 @@
1
- import type { Id, NumericSearchMetadata, SearchableData, SearchableObject, UniversalSearchOpts, CollectionConfig, UpdateResult, BatchUpdateResult } from '../types';
1
+ import type { Id, NumericSearchMetadata, SearchableData, SearchableObject, UniversalSearchOpts, CollectionConfig, UpdateResult, BatchUpdateResult, SyncResult, SyncOptions } from '../types';
2
2
  export type { UniversalSearchOpts, CollectionConfig, UpdateResult, BatchUpdateResult } from '../types';
3
3
  /**
4
4
  * SearchUniverse manages multiple collections with their metadata and relationships.
@@ -154,6 +154,34 @@ export declare class SearchUniverse {
154
154
  * @returns Number of items actually removed
155
155
  */
156
156
  removeFromSearchMetadataBatch(collectionName: string, ids: Id[]): number;
157
+ /**
158
+ * Syncs a collection against a fresh snapshot of items.
159
+ *
160
+ * Compares the provided `newItems` to the current collection state and applies
161
+ * the diff: items not in `newItems` are removed, new items are added, and existing
162
+ * items are updated when their tokens change. Items marked `_deleted` or `_blocked`
163
+ * are removed from metadata (and from `primaryToLinked` for linked collections),
164
+ * but remain in `collection.data` for reference — same convention as
165
+ * {@link updateSearchMetadata}.
166
+ *
167
+ * Use this for full refresh cycles (e.g. periodic snapshot from server) when you
168
+ * don't have a delta. For incremental delta updates use
169
+ * {@link updateSearchMetadataBatch} + {@link removeFromSearchMetadataBatch}.
170
+ *
171
+ * @typeParam T - Type of items in the collection
172
+ * @param collectionName - Name of the collection
173
+ * @param newItems - Full new dataset (snapshot)
174
+ * @param options - Optional sync options (e.g. `skipUpdateCheck` to avoid re-tokenizing existing items)
175
+ * @returns SyncResult with counts and IDs of added/updated/removed items
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * const fresh = await fetchAllProducts();
180
+ * const result = universe.syncCollection('products', fresh);
181
+ * // { added: 3, updated: 12, removed: 1, addedIds: [...], ... }
182
+ * ```
183
+ */
184
+ syncCollection<T extends SearchableObject>(collectionName: string, newItems: T[], options?: SyncOptions): SyncResult;
157
185
  /**
158
186
  * Gets collection or throws an error if not found.
159
187
  *
@@ -1 +1 @@
1
- {"version":3,"file":"SearchUniverse.d.ts","sourceRoot":"","sources":["../../src/common/SearchUniverse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,EAAE,EACF,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAEhB,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAWlB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAcvG;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAiC;IAEpD;;;;;OAKG;IACH,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,EAAE,CAAC,GAAG,OAAO,EACxD,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAM,GAClC,IAAI;IAmBP;;;;;OAKG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;OAIG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;;;;;OAMG;IACH,cAAc,CAAC,CAAC,SAAS,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;IAsC1E;;;;;;;OAOG;IACH,OAAO,CAAC,CAAC,SAAS,gBAAgB,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,GAAG,SAAS;IAKlF;;;;;;OAMG;IACH,QAAQ,CAAC,CAAC,SAAS,gBAAgB,EAAE,cAAc,EAAE,MAAM,GAAG,CAAC,EAAE;IAKjE;;;;;OAKG;IACH,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM;IAKjD;;;;;OAKG;IACH,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB;IAK1D;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,SAAS,gBAAgB,EAAE,cAAc,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC;IAK9E;;;;;;;OAOG;IACH,YAAY,CAAC,oBAAoB,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,EAAE;IAQ/D;;;;;;;OAOG;IACH,cAAc,CAAC,CAAC,SAAS,gBAAgB,EACvC,oBAAoB,EAAE,MAAM,EAC5B,SAAS,EAAE,EAAE,GACZ,CAAC,EAAE;IAMN;;;;;;;;;;;;OAYG;IACH,oBAAoB,CAAC,CAAC,SAAS,gBAAgB,EAC7C,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,CAAC,GACN,YAAY;IAoFf;;;;;;OAMG;IACH,yBAAyB,CAAC,CAAC,SAAS,gBAAgB,EAClD,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,CAAC,EAAE,GACT,iBAAiB;IA0BpB;;;;;;OAMG;IACH,wBAAwB,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO;IAmCjE;;;;;;OAMG;IACH,6BAA6B,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,MAAM;IAcxE;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IAQ5B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,iBAAiB,CACf,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,EAClB,IAAI,CAAC,EAAE,mBAAmB,GACzB,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,KAAK,CAAC;QAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAC;QAAC,SAAS,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;KAAE,CAAC,CAAC;IA6FlJ;;;;;;;;OAQG;IACH,KAAK,IAAI,IAAI;CAUd"}
1
+ {"version":3,"file":"SearchUniverse.d.ts","sourceRoot":"","sources":["../../src/common/SearchUniverse.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -87,7 +87,7 @@ module.exports = __toCommonJS(exports_src);
87
87
  var DEFAULT_SEARCH_OPTS = {
88
88
  matchWords: "start",
89
89
  matchNumbers: "startEnd",
90
- matchNumbersShort: "whole",
90
+ matchNumbersShort: "start",
91
91
  matchNumbersThreshold: 6
92
92
  };
93
93
  function arrayToSearchableData(items) {
@@ -199,10 +199,13 @@ var RE_LEADING_ZEROS = /\b0+(\d)/g;
199
199
  var RE_DECIMAL_COMMA = /(\d),(\d)/g;
200
200
  var RE_STANDALONE_COMMA = /(?<!\d),(\d)/g;
201
201
  var RE_ZERO_DECIMAL = /\b0+\./g;
202
- var RE_PURE_DECIMAL = /(?<![a-zA-Z\d.])(\d+)\.(\d+)(?![a-zA-Z.\d])/g;
202
+ var RE_PURE_DECIMAL = /(?<![a-zčćšžđñ\d.])(\d+)\.(\d+)(?![a-zčćšžđñ.\d])/gi;
203
203
  var RE_DIGIT_DASH = /(\d)\s*-\s*(\d)/g;
204
- var RE_LETTERS_DIGITS = /(^|\s)([a-zA-Z]+)(\d+)(?=\s|$)/g;
205
- var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zA-Z]+)(?=\s|$)/g;
204
+ var RE_LETTER_DASH_DIGIT = /([a-zčćšžđñ])-(\d)/gi;
205
+ var RE_DIGIT_DASH_LETTER = /(\d)-([a-zčćšžđñ])/gi;
206
+ var RE_LETTER_DASH_LETTER = /(?<=[a-zčćšžđñ])-(?=[a-zčćšžđñ])/gi;
207
+ var RE_LETTERS_DIGITS = /(^|\s)([a-zčćšžđñ]+)(\d+)(?=\s|$)/gi;
208
+ var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zčćšžđñ]+)(?=\s|$)/gi;
206
209
  function preprocessString(input) {
207
210
  let result = input;
208
211
  result = result.replace(RE_LEADING_ZEROS, "$1");
@@ -211,6 +214,9 @@ function preprocessString(input) {
211
214
  result = result.replace(RE_ZERO_DECIMAL, ".");
212
215
  result = result.replace(RE_PURE_DECIMAL, "$1$2");
213
216
  result = result.replace(RE_DIGIT_DASH, "$1-$2");
217
+ result = result.replace(RE_LETTER_DASH_DIGIT, "$1 $2");
218
+ result = result.replace(RE_DIGIT_DASH_LETTER, "$1 $2");
219
+ result = result.replace(RE_LETTER_DASH_LETTER, " ");
214
220
  result = result.replace(RE_LETTERS_DIGITS, "$1$2 $3");
215
221
  result = result.replace(RE_DIGITS_LETTERS, "$1$2 $3");
216
222
  return result;
@@ -267,7 +273,7 @@ function tokenize(input, config) {
267
273
  const replaced = config.replaceTokens.get(withoutPrefix);
268
274
  return replaced ? "--" + replaced : token;
269
275
  }
270
- if (token.length > 1 && token[0] === "-" && /^[a-z]/i.test(token[1])) {
276
+ if (token.length > 1 && token[0] === "-" && /^[a-zčćšžđñ]/i.test(token[1])) {
271
277
  const withoutPrefix = token.slice(1);
272
278
  const replaced = config.replaceTokens.get(withoutPrefix);
273
279
  return replaced ? "-" + replaced : token;
@@ -616,7 +622,7 @@ function prepareQueryTokens(queryTokenStrs) {
616
622
  if (str.length > 2 && str.startsWith("--")) {
617
623
  negated = true;
618
624
  cleanStr = str.slice(2);
619
- } else if (str.length > 1 && str[0] === "-" && /^[a-z]/i.test(str[1])) {
625
+ } else if (str.length > 1 && str[0] === "-" && /^[a-zčćšžđñ]/i.test(str[1])) {
620
626
  negated = true;
621
627
  cleanStr = str.slice(1);
622
628
  } else if (str.length > 1 && str.startsWith("~")) {
@@ -641,6 +647,9 @@ function prepareQueryTokens(queryTokenStrs) {
641
647
  cleanStr = str.slice(1);
642
648
  }
643
649
  }
650
+ if (!negated && cleanStr.length > 1 && cleanStr[0] === "-" && /\d/.test(cleanStr[1])) {
651
+ cleanStr = cleanStr.slice(1);
652
+ }
644
653
  const result = {
645
654
  str: cleanStr,
646
655
  id: findTokenId(cleanStr),
@@ -1323,6 +1332,16 @@ function syncLinkedItemsForPrimaryNumeric(linked, primaryId, newLinkedItems, for
1323
1332
  return result;
1324
1333
  }
1325
1334
  // src/common/SearchUniverse.ts
1335
+ function tokensEqual3(a, b) {
1336
+ if (a.length !== b.length)
1337
+ return false;
1338
+ for (let i = 0;i < a.length; i++) {
1339
+ if (a[i] !== b[i])
1340
+ return false;
1341
+ }
1342
+ return true;
1343
+ }
1344
+
1326
1345
  class SearchUniverse {
1327
1346
  collections = new Map;
1328
1347
  registerCollection(name, config = {}) {
@@ -1526,6 +1545,113 @@ class SearchUniverse {
1526
1545
  }
1527
1546
  return removed;
1528
1547
  }
1548
+ syncCollection(collectionName, newItems, options) {
1549
+ const collection = this.getCollectionOrThrow(collectionName);
1550
+ const result = {
1551
+ added: 0,
1552
+ updated: 0,
1553
+ removed: 0,
1554
+ addedIds: [],
1555
+ updatedIds: [],
1556
+ removedIds: []
1557
+ };
1558
+ const skipUpdateCheck = options?.skipUpdateCheck ?? false;
1559
+ const fkGetter = collection.config.linkedTo ? collection.config.linkedTo.foreignKeyGetter : undefined;
1560
+ const newIds = new Set;
1561
+ for (const item of newItems)
1562
+ newIds.add(item._id);
1563
+ const toRemove = [];
1564
+ for (const id of collection.data.keys()) {
1565
+ if (!newIds.has(id))
1566
+ toRemove.push(id);
1567
+ }
1568
+ for (const id of toRemove) {
1569
+ const existing = collection.data.get(id);
1570
+ if (fkGetter && collection.primaryToLinked) {
1571
+ const fk = fkGetter(existing);
1572
+ if (fk) {
1573
+ const list = collection.primaryToLinked.get(fk);
1574
+ if (list) {
1575
+ const idx = list.indexOf(id);
1576
+ if (idx !== -1) {
1577
+ list.splice(idx, 1);
1578
+ if (list.length === 0)
1579
+ collection.primaryToLinked.delete(fk);
1580
+ }
1581
+ }
1582
+ }
1583
+ }
1584
+ const hadMetadata = collection.metadata.has(id);
1585
+ collection.data.delete(id);
1586
+ collection.metadata.delete(id);
1587
+ if (hadMetadata) {
1588
+ result.removed++;
1589
+ result.removedIds.push(id);
1590
+ }
1591
+ }
1592
+ const tokensMap = createTokensMap();
1593
+ try {
1594
+ for (const item of newItems) {
1595
+ const id = item._id;
1596
+ const existing = collection.data.get(id);
1597
+ const hadMetadata = collection.metadata.has(id);
1598
+ const shouldTokenize = !existing || !skipUpdateCheck;
1599
+ let newTokens;
1600
+ if (shouldTokenize) {
1601
+ newTokens = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1602
+ }
1603
+ if (fkGetter && collection.primaryToLinked) {
1604
+ const newFk = fkGetter(item);
1605
+ const oldFk = existing ? fkGetter(existing) : undefined;
1606
+ const wasInIndex = hadMetadata;
1607
+ const shouldBeInIndex = !!newFk && !item._deleted && !item._blocked;
1608
+ if (oldFk !== newFk || wasInIndex !== shouldBeInIndex) {
1609
+ if (oldFk && wasInIndex) {
1610
+ const list = collection.primaryToLinked.get(oldFk);
1611
+ if (list) {
1612
+ const idx = list.indexOf(id);
1613
+ if (idx !== -1) {
1614
+ list.splice(idx, 1);
1615
+ if (list.length === 0)
1616
+ collection.primaryToLinked.delete(oldFk);
1617
+ }
1618
+ }
1619
+ }
1620
+ if (shouldBeInIndex && newFk) {
1621
+ const list = collection.primaryToLinked.get(newFk) || [];
1622
+ if (!list.includes(id))
1623
+ list.push(id);
1624
+ collection.primaryToLinked.set(newFk, list);
1625
+ }
1626
+ }
1627
+ }
1628
+ collection.data.set(id, item);
1629
+ if (!shouldTokenize)
1630
+ continue;
1631
+ if (newTokens === undefined) {
1632
+ if (hadMetadata) {
1633
+ collection.metadata.delete(id);
1634
+ result.removed++;
1635
+ result.removedIds.push(id);
1636
+ }
1637
+ } else if (!hadMetadata) {
1638
+ collection.metadata.set(id, newTokens);
1639
+ result.added++;
1640
+ result.addedIds.push(id);
1641
+ } else {
1642
+ const existingTokens = collection.metadata.get(id);
1643
+ if (!tokensEqual3(existingTokens, newTokens)) {
1644
+ collection.metadata.set(id, newTokens);
1645
+ result.updated++;
1646
+ result.updatedIds.push(id);
1647
+ }
1648
+ }
1649
+ }
1650
+ } finally {
1651
+ clearTokensMap(tokensMap);
1652
+ }
1653
+ return result;
1654
+ }
1529
1655
  getCollectionOrThrow(name) {
1530
1656
  const collection = this.collections.get(name);
1531
1657
  if (!collection) {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  var DEFAULT_SEARCH_OPTS = {
3
3
  matchWords: "start",
4
4
  matchNumbers: "startEnd",
5
- matchNumbersShort: "whole",
5
+ matchNumbersShort: "start",
6
6
  matchNumbersThreshold: 6
7
7
  };
8
8
  function arrayToSearchableData(items) {
@@ -114,10 +114,13 @@ var RE_LEADING_ZEROS = /\b0+(\d)/g;
114
114
  var RE_DECIMAL_COMMA = /(\d),(\d)/g;
115
115
  var RE_STANDALONE_COMMA = /(?<!\d),(\d)/g;
116
116
  var RE_ZERO_DECIMAL = /\b0+\./g;
117
- var RE_PURE_DECIMAL = /(?<![a-zA-Z\d.])(\d+)\.(\d+)(?![a-zA-Z.\d])/g;
117
+ var RE_PURE_DECIMAL = /(?<![a-zčćšžđñ\d.])(\d+)\.(\d+)(?![a-zčćšžđñ.\d])/gi;
118
118
  var RE_DIGIT_DASH = /(\d)\s*-\s*(\d)/g;
119
- var RE_LETTERS_DIGITS = /(^|\s)([a-zA-Z]+)(\d+)(?=\s|$)/g;
120
- var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zA-Z]+)(?=\s|$)/g;
119
+ var RE_LETTER_DASH_DIGIT = /([a-zčćšžđñ])-(\d)/gi;
120
+ var RE_DIGIT_DASH_LETTER = /(\d)-([a-zčćšžđñ])/gi;
121
+ var RE_LETTER_DASH_LETTER = /(?<=[a-zčćšžđñ])-(?=[a-zčćšžđñ])/gi;
122
+ var RE_LETTERS_DIGITS = /(^|\s)([a-zčćšžđñ]+)(\d+)(?=\s|$)/gi;
123
+ var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zčćšžđñ]+)(?=\s|$)/gi;
121
124
  function preprocessString(input) {
122
125
  let result = input;
123
126
  result = result.replace(RE_LEADING_ZEROS, "$1");
@@ -126,6 +129,9 @@ function preprocessString(input) {
126
129
  result = result.replace(RE_ZERO_DECIMAL, ".");
127
130
  result = result.replace(RE_PURE_DECIMAL, "$1$2");
128
131
  result = result.replace(RE_DIGIT_DASH, "$1-$2");
132
+ result = result.replace(RE_LETTER_DASH_DIGIT, "$1 $2");
133
+ result = result.replace(RE_DIGIT_DASH_LETTER, "$1 $2");
134
+ result = result.replace(RE_LETTER_DASH_LETTER, " ");
129
135
  result = result.replace(RE_LETTERS_DIGITS, "$1$2 $3");
130
136
  result = result.replace(RE_DIGITS_LETTERS, "$1$2 $3");
131
137
  return result;
@@ -182,7 +188,7 @@ function tokenize(input, config) {
182
188
  const replaced = config.replaceTokens.get(withoutPrefix);
183
189
  return replaced ? "--" + replaced : token;
184
190
  }
185
- if (token.length > 1 && token[0] === "-" && /^[a-z]/i.test(token[1])) {
191
+ if (token.length > 1 && token[0] === "-" && /^[a-zčćšžđñ]/i.test(token[1])) {
186
192
  const withoutPrefix = token.slice(1);
187
193
  const replaced = config.replaceTokens.get(withoutPrefix);
188
194
  return replaced ? "-" + replaced : token;
@@ -531,7 +537,7 @@ function prepareQueryTokens(queryTokenStrs) {
531
537
  if (str.length > 2 && str.startsWith("--")) {
532
538
  negated = true;
533
539
  cleanStr = str.slice(2);
534
- } else if (str.length > 1 && str[0] === "-" && /^[a-z]/i.test(str[1])) {
540
+ } else if (str.length > 1 && str[0] === "-" && /^[a-zčćšžđñ]/i.test(str[1])) {
535
541
  negated = true;
536
542
  cleanStr = str.slice(1);
537
543
  } else if (str.length > 1 && str.startsWith("~")) {
@@ -556,6 +562,9 @@ function prepareQueryTokens(queryTokenStrs) {
556
562
  cleanStr = str.slice(1);
557
563
  }
558
564
  }
565
+ if (!negated && cleanStr.length > 1 && cleanStr[0] === "-" && /\d/.test(cleanStr[1])) {
566
+ cleanStr = cleanStr.slice(1);
567
+ }
559
568
  const result = {
560
569
  str: cleanStr,
561
570
  id: findTokenId(cleanStr),
@@ -1238,6 +1247,16 @@ function syncLinkedItemsForPrimaryNumeric(linked, primaryId, newLinkedItems, for
1238
1247
  return result;
1239
1248
  }
1240
1249
  // src/common/SearchUniverse.ts
1250
+ function tokensEqual3(a, b) {
1251
+ if (a.length !== b.length)
1252
+ return false;
1253
+ for (let i = 0;i < a.length; i++) {
1254
+ if (a[i] !== b[i])
1255
+ return false;
1256
+ }
1257
+ return true;
1258
+ }
1259
+
1241
1260
  class SearchUniverse {
1242
1261
  collections = new Map;
1243
1262
  registerCollection(name, config = {}) {
@@ -1441,6 +1460,113 @@ class SearchUniverse {
1441
1460
  }
1442
1461
  return removed;
1443
1462
  }
1463
+ syncCollection(collectionName, newItems, options) {
1464
+ const collection = this.getCollectionOrThrow(collectionName);
1465
+ const result = {
1466
+ added: 0,
1467
+ updated: 0,
1468
+ removed: 0,
1469
+ addedIds: [],
1470
+ updatedIds: [],
1471
+ removedIds: []
1472
+ };
1473
+ const skipUpdateCheck = options?.skipUpdateCheck ?? false;
1474
+ const fkGetter = collection.config.linkedTo ? collection.config.linkedTo.foreignKeyGetter : undefined;
1475
+ const newIds = new Set;
1476
+ for (const item of newItems)
1477
+ newIds.add(item._id);
1478
+ const toRemove = [];
1479
+ for (const id of collection.data.keys()) {
1480
+ if (!newIds.has(id))
1481
+ toRemove.push(id);
1482
+ }
1483
+ for (const id of toRemove) {
1484
+ const existing = collection.data.get(id);
1485
+ if (fkGetter && collection.primaryToLinked) {
1486
+ const fk = fkGetter(existing);
1487
+ if (fk) {
1488
+ const list = collection.primaryToLinked.get(fk);
1489
+ if (list) {
1490
+ const idx = list.indexOf(id);
1491
+ if (idx !== -1) {
1492
+ list.splice(idx, 1);
1493
+ if (list.length === 0)
1494
+ collection.primaryToLinked.delete(fk);
1495
+ }
1496
+ }
1497
+ }
1498
+ }
1499
+ const hadMetadata = collection.metadata.has(id);
1500
+ collection.data.delete(id);
1501
+ collection.metadata.delete(id);
1502
+ if (hadMetadata) {
1503
+ result.removed++;
1504
+ result.removedIds.push(id);
1505
+ }
1506
+ }
1507
+ const tokensMap = createTokensMap();
1508
+ try {
1509
+ for (const item of newItems) {
1510
+ const id = item._id;
1511
+ const existing = collection.data.get(id);
1512
+ const hadMetadata = collection.metadata.has(id);
1513
+ const shouldTokenize = !existing || !skipUpdateCheck;
1514
+ let newTokens;
1515
+ if (shouldTokenize) {
1516
+ newTokens = prepareObjectForSearchNumeric(item, collection.config.spec, tokensMap);
1517
+ }
1518
+ if (fkGetter && collection.primaryToLinked) {
1519
+ const newFk = fkGetter(item);
1520
+ const oldFk = existing ? fkGetter(existing) : undefined;
1521
+ const wasInIndex = hadMetadata;
1522
+ const shouldBeInIndex = !!newFk && !item._deleted && !item._blocked;
1523
+ if (oldFk !== newFk || wasInIndex !== shouldBeInIndex) {
1524
+ if (oldFk && wasInIndex) {
1525
+ const list = collection.primaryToLinked.get(oldFk);
1526
+ if (list) {
1527
+ const idx = list.indexOf(id);
1528
+ if (idx !== -1) {
1529
+ list.splice(idx, 1);
1530
+ if (list.length === 0)
1531
+ collection.primaryToLinked.delete(oldFk);
1532
+ }
1533
+ }
1534
+ }
1535
+ if (shouldBeInIndex && newFk) {
1536
+ const list = collection.primaryToLinked.get(newFk) || [];
1537
+ if (!list.includes(id))
1538
+ list.push(id);
1539
+ collection.primaryToLinked.set(newFk, list);
1540
+ }
1541
+ }
1542
+ }
1543
+ collection.data.set(id, item);
1544
+ if (!shouldTokenize)
1545
+ continue;
1546
+ if (newTokens === undefined) {
1547
+ if (hadMetadata) {
1548
+ collection.metadata.delete(id);
1549
+ result.removed++;
1550
+ result.removedIds.push(id);
1551
+ }
1552
+ } else if (!hadMetadata) {
1553
+ collection.metadata.set(id, newTokens);
1554
+ result.added++;
1555
+ result.addedIds.push(id);
1556
+ } else {
1557
+ const existingTokens = collection.metadata.get(id);
1558
+ if (!tokensEqual3(existingTokens, newTokens)) {
1559
+ collection.metadata.set(id, newTokens);
1560
+ result.updated++;
1561
+ result.updatedIds.push(id);
1562
+ }
1563
+ }
1564
+ }
1565
+ } finally {
1566
+ clearTokensMap(tokensMap);
1567
+ }
1568
+ return result;
1569
+ }
1444
1570
  getCollectionOrThrow(name) {
1445
1571
  const collection = this.collections.get(name);
1446
1572
  if (!collection) {
package/dist/string.cjs CHANGED
@@ -167,10 +167,13 @@ var RE_LEADING_ZEROS = /\b0+(\d)/g;
167
167
  var RE_DECIMAL_COMMA = /(\d),(\d)/g;
168
168
  var RE_STANDALONE_COMMA = /(?<!\d),(\d)/g;
169
169
  var RE_ZERO_DECIMAL = /\b0+\./g;
170
- var RE_PURE_DECIMAL = /(?<![a-zA-Z\d.])(\d+)\.(\d+)(?![a-zA-Z.\d])/g;
170
+ var RE_PURE_DECIMAL = /(?<![a-zčćšžđñ\d.])(\d+)\.(\d+)(?![a-zčćšžđñ.\d])/gi;
171
171
  var RE_DIGIT_DASH = /(\d)\s*-\s*(\d)/g;
172
- var RE_LETTERS_DIGITS = /(^|\s)([a-zA-Z]+)(\d+)(?=\s|$)/g;
173
- var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zA-Z]+)(?=\s|$)/g;
172
+ var RE_LETTER_DASH_DIGIT = /([a-zčćšžđñ])-(\d)/gi;
173
+ var RE_DIGIT_DASH_LETTER = /(\d)-([a-zčćšžđñ])/gi;
174
+ var RE_LETTER_DASH_LETTER = /(?<=[a-zčćšžđñ])-(?=[a-zčćšžđñ])/gi;
175
+ var RE_LETTERS_DIGITS = /(^|\s)([a-zčćšžđñ]+)(\d+)(?=\s|$)/gi;
176
+ var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zčćšžđñ]+)(?=\s|$)/gi;
174
177
  function preprocessString(input) {
175
178
  let result = input;
176
179
  result = result.replace(RE_LEADING_ZEROS, "$1");
@@ -179,6 +182,9 @@ function preprocessString(input) {
179
182
  result = result.replace(RE_ZERO_DECIMAL, ".");
180
183
  result = result.replace(RE_PURE_DECIMAL, "$1$2");
181
184
  result = result.replace(RE_DIGIT_DASH, "$1-$2");
185
+ result = result.replace(RE_LETTER_DASH_DIGIT, "$1 $2");
186
+ result = result.replace(RE_DIGIT_DASH_LETTER, "$1 $2");
187
+ result = result.replace(RE_LETTER_DASH_LETTER, " ");
182
188
  result = result.replace(RE_LETTERS_DIGITS, "$1$2 $3");
183
189
  result = result.replace(RE_DIGITS_LETTERS, "$1$2 $3");
184
190
  return result;
@@ -235,7 +241,7 @@ function tokenize(input, config) {
235
241
  const replaced = config.replaceTokens.get(withoutPrefix);
236
242
  return replaced ? "--" + replaced : token;
237
243
  }
238
- if (token.length > 1 && token[0] === "-" && /^[a-z]/i.test(token[1])) {
244
+ if (token.length > 1 && token[0] === "-" && /^[a-zčćšžđñ]/i.test(token[1])) {
239
245
  const withoutPrefix = token.slice(1);
240
246
  const replaced = config.replaceTokens.get(withoutPrefix);
241
247
  return replaced ? "-" + replaced : token;
@@ -490,7 +496,7 @@ function removeFromSearchMetadataBatch(metadata, objectIds) {
490
496
  var DEFAULT_SEARCH_OPTS = {
491
497
  matchWords: "start",
492
498
  matchNumbers: "startEnd",
493
- matchNumbersShort: "whole",
499
+ matchNumbersShort: "start",
494
500
  matchNumbersThreshold: 6
495
501
  };
496
502
  function arrayToSearchableData(items) {
@@ -629,7 +635,7 @@ function findInArray(query, data, metadata, limit = 10, opts) {
629
635
  // src/common/findInLinkedArrays.ts
630
636
  function findInLinkedArrays(query, linked, limit = 10, opts) {
631
637
  const queryTokens = prepareStringForSearch(query);
632
- const hasNegation = queryTokens.some((t) => t.startsWith("--") || t.startsWith("~") || t.length > 1 && t[0] === "-" && /^[a-z]/i.test(t[1]));
638
+ const hasNegation = queryTokens.some((t) => t.startsWith("--") || t.startsWith("~") || t.length > 1 && t[0] === "-" && /^[a-zčćšžđñ]/i.test(t[1]));
633
639
  if (queryTokens.length === 0) {
634
640
  return { queryTokens, results: [], hasNegation: false };
635
641
  }
package/dist/string.js CHANGED
@@ -97,10 +97,13 @@ var RE_LEADING_ZEROS = /\b0+(\d)/g;
97
97
  var RE_DECIMAL_COMMA = /(\d),(\d)/g;
98
98
  var RE_STANDALONE_COMMA = /(?<!\d),(\d)/g;
99
99
  var RE_ZERO_DECIMAL = /\b0+\./g;
100
- var RE_PURE_DECIMAL = /(?<![a-zA-Z\d.])(\d+)\.(\d+)(?![a-zA-Z.\d])/g;
100
+ var RE_PURE_DECIMAL = /(?<![a-zčćšžđñ\d.])(\d+)\.(\d+)(?![a-zčćšžđñ.\d])/gi;
101
101
  var RE_DIGIT_DASH = /(\d)\s*-\s*(\d)/g;
102
- var RE_LETTERS_DIGITS = /(^|\s)([a-zA-Z]+)(\d+)(?=\s|$)/g;
103
- var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zA-Z]+)(?=\s|$)/g;
102
+ var RE_LETTER_DASH_DIGIT = /([a-zčćšžđñ])-(\d)/gi;
103
+ var RE_DIGIT_DASH_LETTER = /(\d)-([a-zčćšžđñ])/gi;
104
+ var RE_LETTER_DASH_LETTER = /(?<=[a-zčćšžđñ])-(?=[a-zčćšžđñ])/gi;
105
+ var RE_LETTERS_DIGITS = /(^|\s)([a-zčćšžđñ]+)(\d+)(?=\s|$)/gi;
106
+ var RE_DIGITS_LETTERS = /(^|\s)(\d+)([a-zčćšžđñ]+)(?=\s|$)/gi;
104
107
  function preprocessString(input) {
105
108
  let result = input;
106
109
  result = result.replace(RE_LEADING_ZEROS, "$1");
@@ -109,6 +112,9 @@ function preprocessString(input) {
109
112
  result = result.replace(RE_ZERO_DECIMAL, ".");
110
113
  result = result.replace(RE_PURE_DECIMAL, "$1$2");
111
114
  result = result.replace(RE_DIGIT_DASH, "$1-$2");
115
+ result = result.replace(RE_LETTER_DASH_DIGIT, "$1 $2");
116
+ result = result.replace(RE_DIGIT_DASH_LETTER, "$1 $2");
117
+ result = result.replace(RE_LETTER_DASH_LETTER, " ");
112
118
  result = result.replace(RE_LETTERS_DIGITS, "$1$2 $3");
113
119
  result = result.replace(RE_DIGITS_LETTERS, "$1$2 $3");
114
120
  return result;
@@ -165,7 +171,7 @@ function tokenize(input, config) {
165
171
  const replaced = config.replaceTokens.get(withoutPrefix);
166
172
  return replaced ? "--" + replaced : token;
167
173
  }
168
- if (token.length > 1 && token[0] === "-" && /^[a-z]/i.test(token[1])) {
174
+ if (token.length > 1 && token[0] === "-" && /^[a-zčćšžđñ]/i.test(token[1])) {
169
175
  const withoutPrefix = token.slice(1);
170
176
  const replaced = config.replaceTokens.get(withoutPrefix);
171
177
  return replaced ? "-" + replaced : token;
@@ -420,7 +426,7 @@ function removeFromSearchMetadataBatch(metadata, objectIds) {
420
426
  var DEFAULT_SEARCH_OPTS = {
421
427
  matchWords: "start",
422
428
  matchNumbers: "startEnd",
423
- matchNumbersShort: "whole",
429
+ matchNumbersShort: "start",
424
430
  matchNumbersThreshold: 6
425
431
  };
426
432
  function arrayToSearchableData(items) {
@@ -559,7 +565,7 @@ function findInArray(query, data, metadata, limit = 10, opts) {
559
565
  // src/common/findInLinkedArrays.ts
560
566
  function findInLinkedArrays(query, linked, limit = 10, opts) {
561
567
  const queryTokens = prepareStringForSearch(query);
562
- const hasNegation = queryTokens.some((t) => t.startsWith("--") || t.startsWith("~") || t.length > 1 && t[0] === "-" && /^[a-z]/i.test(t[1]));
568
+ const hasNegation = queryTokens.some((t) => t.startsWith("--") || t.startsWith("~") || t.length > 1 && t[0] === "-" && /^[a-zčćšžđñ]/i.test(t[1]));
563
569
  if (queryTokens.length === 0) {
564
570
  return { queryTokens, results: [], hasNegation: false };
565
571
  }
@@ -73,7 +73,7 @@ export interface SearchOpts {
73
73
  matchNumbers?: MatchMode;
74
74
  /**
75
75
  * How to match short numbers (digits <= matchNumbersThreshold)
76
- * @default 'whole'
76
+ * @default 'start'
77
77
  */
78
78
  matchNumbersShort?: MatchMode;
79
79
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"matchTokenNumeric.d.ts","sourceRoot":"","sources":["../../src/utils/matchTokenNumeric.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAiC9E;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,uFAAuF;IACvF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAE9E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,kBAAkB,EAAE,CA8DjF;AAoED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAiB5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAC5C,aAAa,EAAE,kBAAkB,EACjC,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CA4DT;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAaT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,kBAAkB,EAAE,EACpC,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAYT;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EAAE,EACzB,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAGT"}
1
+ {"version":3,"file":"matchTokenNumeric.d.ts","sourceRoot":"","sources":["../../src/utils/matchTokenNumeric.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAiC9E;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,uFAAuF;IACvF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAE9E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAqEjF;AAoED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAiB5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAC5C,aAAa,EAAE,kBAAkB,EACjC,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CA4DT;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAaT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,kBAAkB,EAAE,EACpC,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAYT;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EAAE,EACzB,YAAY,EAAE,sBAAsB,EACpC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAGT"}
@@ -6,7 +6,10 @@
6
6
  * 2. Strips decimal period from pure standalone numbers (e.g., "11.22" → "1122", ".3" → "3")
7
7
  * Does NOT strip from mixed tokens (".5kg") or multi-period tokens ("1.2.3")
8
8
  * 3. Removes spaces in digit-dash-digit patterns (e.g., "12 - 25" → "12-25")
9
- * 4. Inserts spaces at letter-to-digit boundaries (e.g., "SI123" → "SI 123", "123SI" → "123 SI")
9
+ * 4. Splits at letter-dash-digit, digit-dash-letter, and letter-dash-letter boundaries
10
+ * (e.g., "ABC-123" → "ABC 123", "Ioana-Gabriela" → "Ioana Gabriela");
11
+ * digit-dash-digit ranges stay intact (e.g., "12-25")
12
+ * 5. Inserts spaces at letter-to-digit boundaries (e.g., "SI123" → "SI 123", "123SI" → "123 SI")
10
13
  *
11
14
  * @param input - The raw input string to preprocess
12
15
  * @returns Preprocessed string ready for sanitization
@@ -1 +1 @@
1
- {"version":3,"file":"preprocessString.d.ts","sourceRoot":"","sources":["../../src/utils/preprocessString.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAyBtD"}
1
+ {"version":3,"file":"preprocessString.d.ts","sourceRoot":"","sources":["../../src/utils/preprocessString.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgCtD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-search",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A fast, memory-efficient search library for large datasets with support for tokenized matching, linked collections, and per-field match modes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",