cry-search 1.0.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/CLAUDE.md +285 -0
- package/LICENSE.md +67 -0
- package/README.md +283 -0
- package/UNIVERSE.md +818 -0
- package/dist/common/SearchUniverse.d.ts +210 -0
- package/dist/common/SearchUniverse.d.ts.map +1 -0
- package/dist/common/findInArray.d.ts +37 -0
- package/dist/common/findInArray.d.ts.map +1 -0
- package/dist/common/findInArrayReturnDataAndMeta.d.ts +52 -0
- package/dist/common/findInArrayReturnDataAndMeta.d.ts.map +1 -0
- package/dist/common/findInLinkedArrays.d.ts +66 -0
- package/dist/common/findInLinkedArrays.d.ts.map +1 -0
- package/dist/common/numeric/createSearchArrayMetadata.d.ts +65 -0
- package/dist/common/numeric/createSearchArrayMetadata.d.ts.map +1 -0
- package/dist/common/numeric/createSearchLinkedMetadata.d.ts +46 -0
- package/dist/common/numeric/createSearchLinkedMetadata.d.ts.map +1 -0
- package/dist/common/numeric/findInLinkedArrays.d.ts +32 -0
- package/dist/common/numeric/findInLinkedArrays.d.ts.map +1 -0
- package/dist/common/numeric/index.d.ts +9 -0
- package/dist/common/numeric/index.d.ts.map +1 -0
- package/dist/common/numeric/searchInData.d.ts +31 -0
- package/dist/common/numeric/searchInData.d.ts.map +1 -0
- package/dist/common/numeric/updateSearchMetadata.d.ts +60 -0
- package/dist/common/numeric/updateSearchMetadata.d.ts.map +1 -0
- package/dist/common/string/createSearchArrayMetadata.d.ts +117 -0
- package/dist/common/string/createSearchArrayMetadata.d.ts.map +1 -0
- package/dist/common/string/createSearchLinkedMetadata.d.ts +36 -0
- package/dist/common/string/createSearchLinkedMetadata.d.ts.map +1 -0
- package/dist/common/string/index.d.ts +10 -0
- package/dist/common/string/index.d.ts.map +1 -0
- package/dist/common/string/updateSearchMetadata.d.ts +103 -0
- package/dist/common/string/updateSearchMetadata.d.ts.map +1 -0
- package/dist/common/syncSearchArrayMetadata.d.ts +64 -0
- package/dist/common/syncSearchArrayMetadata.d.ts.map +1 -0
- package/dist/common/updateSearchLinkedMetadata.d.ts +129 -0
- package/dist/common/updateSearchLinkedMetadata.d.ts.map +1 -0
- package/dist/index.cjs +1827 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1795 -0
- package/dist/types/index.d.ts +300 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/matchToken.d.ts +65 -0
- package/dist/utils/matchToken.d.ts.map +1 -0
- package/dist/utils/matchTokenNumeric.d.ts +94 -0
- package/dist/utils/matchTokenNumeric.d.ts.map +1 -0
- package/dist/utils/normalizeDates.d.ts +39 -0
- package/dist/utils/normalizeDates.d.ts.map +1 -0
- package/dist/utils/prepareStringForSearch.d.ts +28 -0
- package/dist/utils/prepareStringForSearch.d.ts.map +1 -0
- package/dist/utils/prepareStringForSearchNumeric.d.ts +42 -0
- package/dist/utils/prepareStringForSearchNumeric.d.ts.map +1 -0
- package/dist/utils/preprocessString.d.ts +26 -0
- package/dist/utils/preprocessString.d.ts.map +1 -0
- package/dist/utils/sanitiseString.d.ts +26 -0
- package/dist/utils/sanitiseString.d.ts.map +1 -0
- package/dist/utils/tokenRegistry.d.ts +70 -0
- package/dist/utils/tokenRegistry.d.ts.map +1 -0
- package/dist/utils/tokenize.d.ts +46 -0
- package/dist/utils/tokenize.d.ts.map +1 -0
- package/package.json +67 -0
package/UNIVERSE.md
ADDED
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
# cry-search Data Structures
|
|
2
|
+
|
|
3
|
+
This document provides a graphical representation of the data structures used in the cry-search library.
|
|
4
|
+
|
|
5
|
+
## Core Types
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ CORE TYPES │
|
|
10
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ │
|
|
12
|
+
│ Id = string // Unique identifier, e.g. "abc123" │
|
|
13
|
+
│ │
|
|
14
|
+
│ SearchableObject = { // Base interface for searchable items │
|
|
15
|
+
│ _id: Id, │
|
|
16
|
+
│ _deleted?: Date, │
|
|
17
|
+
│ _blocked?: Date, │
|
|
18
|
+
│ ...other fields │
|
|
19
|
+
│ } │
|
|
20
|
+
│ │
|
|
21
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
22
|
+
│ │
|
|
23
|
+
│ NUMERIC (Default - Memory Optimized): │
|
|
24
|
+
│ │
|
|
25
|
+
│ NumericToken = number // Index into global tokens array │
|
|
26
|
+
│ │
|
|
27
|
+
│ NumericTokenSortedList = Uint32Array // Sorted token IDs │
|
|
28
|
+
│ ┌─────────────────────────────────────┐ │
|
|
29
|
+
│ │ Uint32Array [42, 156, 891] │ │
|
|
30
|
+
│ │ ↓ ↓ ↓ │ │
|
|
31
|
+
│ │ tokens[42]="doe" [156]="john" [891]="smith" │
|
|
32
|
+
│ └─────────────────────────────────────┘ │
|
|
33
|
+
│ │
|
|
34
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
35
|
+
│ │
|
|
36
|
+
│ STRING (Legacy): │
|
|
37
|
+
│ │
|
|
38
|
+
│ SearchToken = string // Single preprocessed token, e.g. "john" │
|
|
39
|
+
│ │
|
|
40
|
+
│ SearchTokenSortedList = string[] // Sorted array of tokens │
|
|
41
|
+
│ ┌─────────────────────────────────────┐ │
|
|
42
|
+
│ │ ["doe", "john", "smith"] │ │
|
|
43
|
+
│ └─────────────────────────────────────┘ │
|
|
44
|
+
│ │
|
|
45
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Token Registry (Numeric Implementation)
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
52
|
+
│ GLOBAL TOKEN REGISTRY │
|
|
53
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
54
|
+
│ │
|
|
55
|
+
│ tokens: string[] // Global array, preallocated ~200k slots │
|
|
56
|
+
│ │
|
|
57
|
+
│ Index Token │
|
|
58
|
+
│ ┌──────┬─────────────────────────────────────────────────────────────┐ │
|
|
59
|
+
│ │ 0 │ "berlin" │ │
|
|
60
|
+
│ │ 1 │ "bob" │ │
|
|
61
|
+
│ │ 2 │ "doe" │ │
|
|
62
|
+
│ │ 3 │ "jane" │ │
|
|
63
|
+
│ │ 4 │ "john" │ │
|
|
64
|
+
│ │ 5 │ "london" │ │
|
|
65
|
+
│ │ 6 │ "paris" │ │
|
|
66
|
+
│ │ 7 │ "smith" │ │
|
|
67
|
+
│ │ 8 │ "wilson" │ │
|
|
68
|
+
│ │ ... │ ... │ │
|
|
69
|
+
│ └──────┴─────────────────────────────────────────────────────────────┘ │
|
|
70
|
+
│ │
|
|
71
|
+
│ During metadata build: │
|
|
72
|
+
│ tokensMap: Map<string, number> // Temporary fast lookup │
|
|
73
|
+
│ ┌──────────┬─────────────────────────────────────────────────────────┐ │
|
|
74
|
+
│ │ "john" │ 4 │ │
|
|
75
|
+
│ │ "smith" │ 7 │ │
|
|
76
|
+
│ └──────────┴─────────────────────────────────────────────────────────┘ │
|
|
77
|
+
│ → Cleared after build (only global tokens[] stays in memory) │
|
|
78
|
+
│ │
|
|
79
|
+
│ During search: │
|
|
80
|
+
│ findTokenId(token) → linear search in tokens[] (no Map in memory) │
|
|
81
|
+
│ │
|
|
82
|
+
│ Match modes encoded in high 3 bits of Uint32 token ID: │
|
|
83
|
+
│ Bits 0-28: token index (up to ~268M unique tokens) │
|
|
84
|
+
│ Bits 29-31: match mode (0=start, 1=end, 2=startEnd, 3=anywhere, 4=whole) │
|
|
85
|
+
│ │
|
|
86
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Data Storage
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
93
|
+
│ SearchableData<T> │
|
|
94
|
+
│ Map<Id, T> │
|
|
95
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
96
|
+
│ │
|
|
97
|
+
│ Key (Id) Value (T extends SearchableObject) │
|
|
98
|
+
│ ┌──────────┬─────────────────────────────────────────────────────────┐ │
|
|
99
|
+
│ │ "s1" │ { _id: "s1", name: "John Smith", city: "London" } │ │
|
|
100
|
+
│ ├──────────┼─────────────────────────────────────────────────────────┤ │
|
|
101
|
+
│ │ "s2" │ { _id: "s2", name: "Jane Doe", city: "Paris" } │ │
|
|
102
|
+
│ ├──────────┼─────────────────────────────────────────────────────────┤ │
|
|
103
|
+
│ │ "s3" │ { _id: "s3", name: "Bob Wilson", city: "Berlin" } │ │
|
|
104
|
+
│ └──────────┴─────────────────────────────────────────────────────────┘ │
|
|
105
|
+
│ │
|
|
106
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Metadata Storage
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
113
|
+
│ NumericSearchMetadata (Default) │
|
|
114
|
+
│ Map<Id, Uint32Array> │
|
|
115
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
116
|
+
│ │
|
|
117
|
+
│ Key (Id) Value (sorted token IDs as Uint32Array) │
|
|
118
|
+
│ ┌──────────┬─────────────────────────────────────────────────────────┐ │
|
|
119
|
+
│ │ "s1" │ Uint32Array [4, 5, 7] │ │
|
|
120
|
+
│ │ │ → tokens[4]="john", [5]="london", [7]="smith" │ │
|
|
121
|
+
│ ├──────────┼─────────────────────────────────────────────────────────┤ │
|
|
122
|
+
│ │ "s2" │ Uint32Array [2, 3, 6] │ │
|
|
123
|
+
│ │ │ → tokens[2]="doe", [3]="jane", [6]="paris" │ │
|
|
124
|
+
│ ├──────────┼─────────────────────────────────────────────────────────┤ │
|
|
125
|
+
│ │ "s3" │ Uint32Array [0, 1, 8] │ │
|
|
126
|
+
│ │ │ → tokens[0]="berlin", [1]="bob", [8]="wilson" │ │
|
|
127
|
+
│ └──────────┴─────────────────────────────────────────────────────────┘ │
|
|
128
|
+
│ │
|
|
129
|
+
│ Note: Token IDs are sorted numerically for binary search matching │
|
|
130
|
+
│ ~45% less memory than string-based implementation │
|
|
131
|
+
│ │
|
|
132
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
133
|
+
|
|
134
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
135
|
+
│ StringSearchMetadata (Legacy) │
|
|
136
|
+
│ Map<Id, string[]> │
|
|
137
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
138
|
+
│ │
|
|
139
|
+
│ Key (Id) Value (sorted token strings) │
|
|
140
|
+
│ ┌──────────┬─────────────────────────────────────────────────────────┐ │
|
|
141
|
+
│ │ "s1" │ ["john", "london", "smith"] │ │
|
|
142
|
+
│ ├──────────┼─────────────────────────────────────────────────────────┤ │
|
|
143
|
+
│ │ "s2" │ ["doe", "jane", "paris"] │ │
|
|
144
|
+
│ ├──────────┼─────────────────────────────────────────────────────────┤ │
|
|
145
|
+
│ │ "s3" │ ["berlin", "bob", "wilson"] │ │
|
|
146
|
+
│ └──────────┴─────────────────────────────────────────────────────────┘ │
|
|
147
|
+
│ │
|
|
148
|
+
│ Note: Tokens sorted alphabetically for efficient matching │
|
|
149
|
+
│ │
|
|
150
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Linked Search Structures
|
|
154
|
+
|
|
155
|
+
When searching across related entities (e.g., owners and their pets):
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
159
|
+
│ LinkedSearchMetadata │
|
|
160
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
161
|
+
│ │
|
|
162
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
163
|
+
│ │ primaryMeta: SearchMetadata │ │
|
|
164
|
+
│ │ ┌──────────┬───────────────────────────────────────────────────┐ │ │
|
|
165
|
+
│ │ │ "o1" │ ["john", "london", "smith"] │ │ │
|
|
166
|
+
│ │ │ "o2" │ ["doe", "jane", "paris"] │ │ │
|
|
167
|
+
│ │ └──────────┴───────────────────────────────────────────────────┘ │ │
|
|
168
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
169
|
+
│ │
|
|
170
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
171
|
+
│ │ linkedMeta: SearchMetadata │ │
|
|
172
|
+
│ │ ┌──────────┬───────────────────────────────────────────────────┐ │ │
|
|
173
|
+
│ │ │ "p1" │ ["cat", "fluffy"] │ │ │
|
|
174
|
+
│ │ │ "p2" │ ["dog", "rex"] │ │ │
|
|
175
|
+
│ │ │ "p3" │ ["cat", "whiskers"] │ │ │
|
|
176
|
+
│ │ └──────────┴───────────────────────────────────────────────────┘ │ │
|
|
177
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
178
|
+
│ │
|
|
179
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
180
|
+
│ │ primaryToLinked: Map<Id, Id[]> (reverse index) │ │
|
|
181
|
+
│ │ ┌──────────┬───────────────────────────────────────────────────┐ │ │
|
|
182
|
+
│ │ │ "o1" │ ["p1", "p2"] (John owns Fluffy & Rex) │ │ │
|
|
183
|
+
│ │ │ "o2" │ ["p3"] (Jane owns Whiskers) │ │ │
|
|
184
|
+
│ │ └──────────┴───────────────────────────────────────────────────┘ │ │
|
|
185
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
186
|
+
│ │
|
|
187
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## SearchLinkedMetadataWithData
|
|
191
|
+
|
|
192
|
+
Extended structure that includes data references for convenient access:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
196
|
+
│ SearchLinkedMetadataWithData<TPrimary, TLinked> │
|
|
197
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
198
|
+
│ │
|
|
199
|
+
│ Extends LinkedSearchMetadata with: │
|
|
200
|
+
│ │
|
|
201
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
202
|
+
│ │ primaryData: SearchableData<TPrimary> │ │
|
|
203
|
+
│ │ ┌──────────┬───────────────────────────────────────────────────┐ │ │
|
|
204
|
+
│ │ │ "o1" │ { _id: "o1", name: "John Smith", city: "London" }│ │ │
|
|
205
|
+
│ │ │ "o2" │ { _id: "o2", name: "Jane Doe", city: "Paris" } │ │ │
|
|
206
|
+
│ │ └──────────┴───────────────────────────────────────────────────┘ │ │
|
|
207
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
208
|
+
│ │
|
|
209
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
210
|
+
│ │ linkedData: SearchableData<TLinked> │ │
|
|
211
|
+
│ │ ┌──────────┬───────────────────────────────────────────────────┐ │ │
|
|
212
|
+
│ │ │ "p1" │ { _id: "p1", name: "Fluffy", species: "cat" } │ │ │
|
|
213
|
+
│ │ │ "p2" │ { _id: "p2", name: "Rex", species: "dog" } │ │ │
|
|
214
|
+
│ │ │ "p3" │ { _id: "p3", name: "Whiskers", species: "cat" } │ │ │
|
|
215
|
+
│ │ └──────────┴───────────────────────────────────────────────────┘ │ │
|
|
216
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
217
|
+
│ │
|
|
218
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## SearchUniverse
|
|
222
|
+
|
|
223
|
+
Manages multiple collections with their relationships using numeric tokens:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
227
|
+
│ SearchUniverse │
|
|
228
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
229
|
+
│ │
|
|
230
|
+
│ Uses numeric implementation for memory efficiency (~45% less than string) │
|
|
231
|
+
│ │
|
|
232
|
+
│ collections: Map<string, Collection> │
|
|
233
|
+
│ │
|
|
234
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
235
|
+
│ │ "stranke" (standalone collection) │ │
|
|
236
|
+
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
|
237
|
+
│ │ │ data: SearchableData<Stranka> │ │ │
|
|
238
|
+
│ │ │ metadata: NumericSearchMetadata ← Uint32Array tokens │ │ │
|
|
239
|
+
│ │ │ config: { spec: {...} } │ │ │
|
|
240
|
+
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
|
241
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
242
|
+
│ │
|
|
243
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
244
|
+
│ │ "pacienti" (linked to "stranke") │ │
|
|
245
|
+
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
|
246
|
+
│ │ │ data: SearchableData<Pacient> │ │ │
|
|
247
|
+
│ │ │ metadata: NumericSearchMetadata ← Uint32Array tokens │ │ │
|
|
248
|
+
│ │ │ config: { │ │ │
|
|
249
|
+
│ │ │ spec: {...}, │ │ │
|
|
250
|
+
│ │ │ linkedTo: { │ │ │
|
|
251
|
+
│ │ │ collectionName: "stranke", │ │ │
|
|
252
|
+
│ │ │ foreignKeyGetter: (p) => p.stranka_id │ │ │
|
|
253
|
+
│ │ │ } │ │ │
|
|
254
|
+
│ │ │ } │ │ │
|
|
255
|
+
│ │ │ primaryToLinked: Map<Id, Id[]> ← reverse index │ │ │
|
|
256
|
+
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
|
257
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
258
|
+
│ │
|
|
259
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
260
|
+
│ │ "artikli" (standalone collection) │ │
|
|
261
|
+
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
|
262
|
+
│ │ │ data: SearchableData<Artikel> │ │ │
|
|
263
|
+
│ │ │ metadata: NumericSearchMetadata ← Uint32Array tokens │ │ │
|
|
264
|
+
│ │ │ config: { spec: {...} } │ │ │
|
|
265
|
+
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
|
266
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
267
|
+
│ │
|
|
268
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Data Flow: Token Preparation Pipeline
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
275
|
+
│ prepareStringForSearch Pipeline │
|
|
276
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
277
|
+
│ │
|
|
278
|
+
│ Input: "Čokolada 100g SI123 27.12.2025" │
|
|
279
|
+
│ │ │
|
|
280
|
+
│ ▼ │
|
|
281
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
282
|
+
│ │ 1. normalizeDates() │ │
|
|
283
|
+
│ │ "Čokolada 100g SI123 20251227" │ │
|
|
284
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
285
|
+
│ │ │
|
|
286
|
+
│ ▼ │
|
|
287
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
288
|
+
│ │ 2. preprocessString() │ │
|
|
289
|
+
│ │ - decimal comma → period │ │
|
|
290
|
+
│ │ - remove leading zeros │ │
|
|
291
|
+
│ │ - split letter-digit boundaries │ │
|
|
292
|
+
│ │ "Čokolada 100 g SI 123 20251227" │ │
|
|
293
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
294
|
+
│ │ │
|
|
295
|
+
│ ▼ │
|
|
296
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
297
|
+
│ │ 3. sanitiseString() │ │
|
|
298
|
+
│ │ - lowercase │ │
|
|
299
|
+
│ │ - remove diacritics │ │
|
|
300
|
+
│ │ - keep only [a-z0-9.-] │ │
|
|
301
|
+
│ │ "cokolada 100 g si 123 20251227" │ │
|
|
302
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
303
|
+
│ │ │
|
|
304
|
+
│ ▼ │
|
|
305
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
306
|
+
│ │ 4. tokenize() │ │
|
|
307
|
+
│ │ - split on spaces │ │
|
|
308
|
+
│ │ - trim and filter empty │ │
|
|
309
|
+
│ │ ["cokolada", "100", "g", "si", "123", "20251227"] │ │
|
|
310
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
311
|
+
│ │ │
|
|
312
|
+
│ ▼ │
|
|
313
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
314
|
+
│ │ 5. sort() │ │
|
|
315
|
+
│ │ ["100", "123", "20251227", "cokolada", "g", "si"] │ │
|
|
316
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
317
|
+
│ │ │
|
|
318
|
+
│ ▼ │
|
|
319
|
+
│ Output: SearchTokenSortedList │
|
|
320
|
+
│ │
|
|
321
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Match Modes & Encoding
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
328
|
+
│ MatchMode Encoding │
|
|
329
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
330
|
+
│ │
|
|
331
|
+
│ MatchMode Behavior │
|
|
332
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
333
|
+
│ 'start' "rex" matches "rexona" not "ceporex" │
|
|
334
|
+
│ 'end' "rex" matches "ceporex" not "rexona" │
|
|
335
|
+
│ 'startEnd' "rex" matches both "rexona" and "ceporex" │
|
|
336
|
+
│ 'anywhere' "rex" matches "unrexy", "rexona", "ceporex" │
|
|
337
|
+
│ 'whole' "rex" matches only "rex" exactly │
|
|
338
|
+
│ │
|
|
339
|
+
│ Default behavior (via SearchOpts): │
|
|
340
|
+
│ matchWords: 'start' (words match from beginning) │
|
|
341
|
+
│ matchNumbers: 'startEnd' (numbers match from start or end) │
|
|
342
|
+
│ │
|
|
343
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
344
|
+
│ │
|
|
345
|
+
│ NUMERIC Implementation: │
|
|
346
|
+
│ │
|
|
347
|
+
│ Match mode encoded in high 3 bits of Uint32 token ID: │
|
|
348
|
+
│ │
|
|
349
|
+
│ 31 30 29 28 ─────────────────────────────── 0 │
|
|
350
|
+
│ ┌───┬───┬───┬─────────────────────────────────────┐ │
|
|
351
|
+
│ │ M │ M │ M │ TOKEN INDEX (29 bits) │ │
|
|
352
|
+
│ └───┴───┴───┴─────────────────────────────────────┘ │
|
|
353
|
+
│ └─────┬─────┘ │
|
|
354
|
+
│ Match Mode: 0=start, 1=end, 2=startEnd, 3=anywhere, 4=whole │
|
|
355
|
+
│ │
|
|
356
|
+
│ Example: token "barcode123" at index 42 with 'startEnd' mode │
|
|
357
|
+
│ = (2 << 29) | 42 = 0x40000000 | 0x2A = 1073741866 │
|
|
358
|
+
│ │
|
|
359
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
360
|
+
│ │
|
|
361
|
+
│ STRING Implementation (Legacy): │
|
|
362
|
+
│ │
|
|
363
|
+
│ Match mode encoded as single-char prefix: │
|
|
364
|
+
│ Prefix Mode │
|
|
365
|
+
│ < start │
|
|
366
|
+
│ > end │
|
|
367
|
+
│ + startEnd │
|
|
368
|
+
│ * anywhere │
|
|
369
|
+
│ ! whole │
|
|
370
|
+
│ │
|
|
371
|
+
│ Example: metadata: ["<barcode123", "!exactcode", "*anywhere"] │
|
|
372
|
+
│ │
|
|
373
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Search Flow: Linked Search
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
380
|
+
│ findInLinkedArrays Flow │
|
|
381
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
382
|
+
│ │
|
|
383
|
+
│ Query: "john fluffy" │
|
|
384
|
+
│ │ │
|
|
385
|
+
│ ▼ │
|
|
386
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
387
|
+
│ │ prepareStringForSearch("john fluffy") → ["fluffy", "john"] │ │
|
|
388
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
389
|
+
│ │ │
|
|
390
|
+
│ ▼ │
|
|
391
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
392
|
+
│ │ For each primary (owner): │ │
|
|
393
|
+
│ │ 1. Collect tokens: primary + all linked items │ │
|
|
394
|
+
│ │ Owner o1: ["john", "london", "smith"] │ │
|
|
395
|
+
│ │ + Pet p1: ["cat", "fluffy"] │ │
|
|
396
|
+
│ │ + Pet p2: ["dog", "rex"] │ │
|
|
397
|
+
│ │ = Combined: ["cat","dog","fluffy","john","london","rex","smith"]│ │
|
|
398
|
+
│ │ │ │
|
|
399
|
+
│ │ 2. Check if ALL query tokens match combined │ │
|
|
400
|
+
│ │ "fluffy" matches "fluffy" ✓ │ │
|
|
401
|
+
│ │ "john" matches "john" ✓ │ │
|
|
402
|
+
│ │ → All match! │ │
|
|
403
|
+
│ │ │ │
|
|
404
|
+
│ │ 3. Determine matchedIn: │ │
|
|
405
|
+
│ │ "john" matches in primary → anyMatchInPrimary = true │ │
|
|
406
|
+
│ │ "fluffy" matches in linked → anyMatchInLinked = true │ │
|
|
407
|
+
│ │ → matchedIn = 'both' │ │
|
|
408
|
+
│ │ │ │
|
|
409
|
+
│ │ 4. Return only contributing linked items: │ │
|
|
410
|
+
│ │ p1 (Fluffy) matched "fluffy" → included │ │
|
|
411
|
+
│ │ p2 (Rex) didn't contribute → excluded │ │
|
|
412
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
413
|
+
│ │ │
|
|
414
|
+
│ ▼ │
|
|
415
|
+
│ Result: │
|
|
416
|
+
│ { │
|
|
417
|
+
│ queryTokens: ["fluffy", "john"], │
|
|
418
|
+
│ results: [{ │
|
|
419
|
+
│ primary: { _id: "o1", name: "John Smith", city: "London" }, │
|
|
420
|
+
│ linked: [{ _id: "p1", name: "Fluffy", species: "cat" }], │
|
|
421
|
+
│ matchedIn: "both" │
|
|
422
|
+
│ }] │
|
|
423
|
+
│ } │
|
|
424
|
+
│ │
|
|
425
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Memory Layout Example
|
|
429
|
+
|
|
430
|
+
```
|
|
431
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
432
|
+
│ Typical Memory Layout │
|
|
433
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
434
|
+
│ │
|
|
435
|
+
│ For 10,000 items with ~5 tokens each: │
|
|
436
|
+
│ │
|
|
437
|
+
│ SearchableData<T>: │
|
|
438
|
+
│ └── Map with 10,000 entries │
|
|
439
|
+
│ └── Each entry: Id string + full object reference │
|
|
440
|
+
│ │
|
|
441
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
442
|
+
│ │
|
|
443
|
+
│ NUMERIC (Default): │
|
|
444
|
+
│ │
|
|
445
|
+
│ Global Token Registry: │
|
|
446
|
+
│ └── Single string[] with all unique tokens (~50k-200k entries typical) │
|
|
447
|
+
│ └── Each token stored once, referenced by index │
|
|
448
|
+
│ │
|
|
449
|
+
│ NumericSearchMetadata: │
|
|
450
|
+
│ └── Map with 10,000 entries │
|
|
451
|
+
│ └── Each entry: Id string + Uint32Array(5) ← 20 bytes fixed │
|
|
452
|
+
│ │
|
|
453
|
+
│ Memory savings: ~45% less than string implementation │
|
|
454
|
+
│ String: 10,000 × 5 tokens × ~10 chars = ~500KB in token strings │
|
|
455
|
+
│ Numeric: 10,000 × 5 × 4 bytes = ~200KB + shared registry │
|
|
456
|
+
│ │
|
|
457
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
458
|
+
│ │
|
|
459
|
+
│ STRING (Legacy): │
|
|
460
|
+
│ │
|
|
461
|
+
│ StringSearchMetadata: │
|
|
462
|
+
│ └── Map with 10,000 entries │
|
|
463
|
+
│ └── Each entry: Id string + array of 5 token strings │
|
|
464
|
+
│ └── Each token string duplicated per item │
|
|
465
|
+
│ │
|
|
466
|
+
│ ───────────────────────────────────────────────────────────────────────── │
|
|
467
|
+
│ │
|
|
468
|
+
│ Linked Index (primaryToLinked): │
|
|
469
|
+
│ └── Map with N primary entries │
|
|
470
|
+
│ └── Each entry: Id string + array of linked Id strings │
|
|
471
|
+
│ │
|
|
472
|
+
│ Note: Objects are stored by reference, not duplicated. │
|
|
473
|
+
│ Metadata stores only preprocessed tokens, not original text. │
|
|
474
|
+
│ │
|
|
475
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## Modifying Data After Creation
|
|
479
|
+
|
|
480
|
+
SearchUniverse provides methods to update data and metadata after initial load.
|
|
481
|
+
|
|
482
|
+
### Single Item Update
|
|
483
|
+
|
|
484
|
+
```
|
|
485
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
486
|
+
│ universe.updateSearchMetadata(collectionName, id, item) │
|
|
487
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
488
|
+
│ │
|
|
489
|
+
│ Handles all cases automatically: │
|
|
490
|
+
│ │
|
|
491
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
492
|
+
│ │ CASE 1: Add new item │ │
|
|
493
|
+
│ │ ───────────────────────────────────────────────────────────────── │ │
|
|
494
|
+
│ │ Input: { _id: "s4", name: "New Person", city: "Rome" } │ │
|
|
495
|
+
│ │ │ │
|
|
496
|
+
│ │ Actions: │ │
|
|
497
|
+
│ │ 1. data.set("s4", item) │ │
|
|
498
|
+
│ │ 2. metadata.set("s4", ["new", "person", "rome"]) │ │
|
|
499
|
+
│ │ 3. If linked: primaryToLinked updated │ │
|
|
500
|
+
│ │ │ │
|
|
501
|
+
│ │ Returns: { added: true } │ │
|
|
502
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
503
|
+
│ │
|
|
504
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
505
|
+
│ │ CASE 2: Update existing item │ │
|
|
506
|
+
│ │ ───────────────────────────────────────────────────────────────── │ │
|
|
507
|
+
│ │ Input: { _id: "s1", name: "John Smith", city: "Madrid" } │ │
|
|
508
|
+
│ │ (was London, now Madrid) │ │
|
|
509
|
+
│ │ │ │
|
|
510
|
+
│ │ Actions: │ │
|
|
511
|
+
│ │ 1. data.set("s1", item) ← replaces old │ │
|
|
512
|
+
│ │ 2. metadata.set("s1", ["john", "madrid", "smith"]) │ │
|
|
513
|
+
│ │ │ │
|
|
514
|
+
│ │ Returns: { added: false } │ │
|
|
515
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
516
|
+
│ │
|
|
517
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
518
|
+
│ │ CASE 3: Delete/Block item │ │
|
|
519
|
+
│ │ ───────────────────────────────────────────────────────────────── │ │
|
|
520
|
+
│ │ Input: { _id: "s1", ..., _deleted: new Date() } │ │
|
|
521
|
+
│ │ │ │
|
|
522
|
+
│ │ Actions: │ │
|
|
523
|
+
│ │ 1. data.set("s1", item) ← keeps item │ │
|
|
524
|
+
│ │ 2. metadata.delete("s1") ← removes from search │ │
|
|
525
|
+
│ │ 3. If linked: removed from primaryToLinked │ │
|
|
526
|
+
│ │ │ │
|
|
527
|
+
│ │ Note: Item stays in data but is unsearchable │ │
|
|
528
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
529
|
+
│ │
|
|
530
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
531
|
+
│ │ CASE 4: Pass null/undefined │ │
|
|
532
|
+
│ │ ───────────────────────────────────────────────────────────────── │ │
|
|
533
|
+
│ │ Input: null or undefined │ │
|
|
534
|
+
│ │ │ │
|
|
535
|
+
│ │ Actions: │ │
|
|
536
|
+
│ │ 1. metadata.delete("s1") ← removes from search │ │
|
|
537
|
+
│ │ 2. If linked: removed from primaryToLinked │ │
|
|
538
|
+
│ │ │ │
|
|
539
|
+
│ │ Note: Equivalent to removeFromSearchMetadata(). Use when you │ │
|
|
540
|
+
│ │ don't have the object but need to remove from search. │ │
|
|
541
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
542
|
+
│ │
|
|
543
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Linked Item Foreign Key Change
|
|
547
|
+
|
|
548
|
+
```
|
|
549
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
550
|
+
│ Updating Linked Item with FK Change │
|
|
551
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
552
|
+
│ │
|
|
553
|
+
│ Scenario: Pet "p1" (Fluffy) moves from owner "o1" to "o2" │
|
|
554
|
+
│ │
|
|
555
|
+
│ BEFORE: │
|
|
556
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
557
|
+
│ │ primaryToLinked: │ │
|
|
558
|
+
│ │ "o1" → ["p1", "p2"] (John owns Fluffy & Rex) │ │
|
|
559
|
+
│ │ "o2" → ["p3"] (Jane owns Whiskers) │ │
|
|
560
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
561
|
+
│ │
|
|
562
|
+
│ Call: universe.updateSearchMetadata("pacienti", "p1", │
|
|
563
|
+
│ { _id: "p1", name: "Fluffy", owner_id: "o2" }) │
|
|
564
|
+
│ ↑ was "o1", now "o2" │
|
|
565
|
+
│ │
|
|
566
|
+
│ AFTER: │
|
|
567
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
568
|
+
│ │ primaryToLinked: │ │
|
|
569
|
+
│ │ "o1" → ["p2"] (John owns only Rex now) │ │
|
|
570
|
+
│ │ "o2" → ["p3", "p1"] (Jane owns Whiskers & Fluffy) │ │
|
|
571
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
572
|
+
│ │
|
|
573
|
+
│ Returns: { added: false, previousPrimaryId: "o1" } │
|
|
574
|
+
│ │
|
|
575
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Batch Updates
|
|
579
|
+
|
|
580
|
+
```
|
|
581
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
582
|
+
│ universe.updateSearchMetadataBatch(collectionName, items) │
|
|
583
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
584
|
+
│ │
|
|
585
|
+
│ Input: Array of items to update │
|
|
586
|
+
│ │
|
|
587
|
+
│ items = [ │
|
|
588
|
+
│ { _id: "s1", name: "Updated John", city: "Madrid" }, ← update │
|
|
589
|
+
│ { _id: "s5", name: "New Person", city: "Vienna" }, ← add │
|
|
590
|
+
│ { _id: "s2", name: "Jane", _deleted: new Date() }, ← delete │
|
|
591
|
+
│ ] │
|
|
592
|
+
│ │
|
|
593
|
+
│ Returns: { added: 1, updated: 1, removed: 1 } │
|
|
594
|
+
│ │
|
|
595
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Remove Item
|
|
599
|
+
|
|
600
|
+
```
|
|
601
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
602
|
+
│ universe.removeFromSearchMetadata(collectionName, id) │
|
|
603
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
604
|
+
│ │
|
|
605
|
+
│ Completely removes item from universe (vs _deleted which keeps it) │
|
|
606
|
+
│ │
|
|
607
|
+
│ Call: universe.removeFromSearchMetadata("stranke", "s1") │
|
|
608
|
+
│ │
|
|
609
|
+
│ Actions: │
|
|
610
|
+
│ 1. data.delete("s1") │
|
|
611
|
+
│ 2. metadata.delete("s1") │
|
|
612
|
+
│ 3. If linked: remove from primaryToLinked index │
|
|
613
|
+
│ │
|
|
614
|
+
│ Returns: true (found and removed) or false (not found) │
|
|
615
|
+
│ │
|
|
616
|
+
│ Batch version: │
|
|
617
|
+
│ universe.removeFromSearchMetadataBatch("stranke", ["s1", "s2"]) │
|
|
618
|
+
│ Returns: number of items removed │
|
|
619
|
+
│ │
|
|
620
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Clear Universe
|
|
624
|
+
|
|
625
|
+
```
|
|
626
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
627
|
+
│ universe.clear() │
|
|
628
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
629
|
+
│ │
|
|
630
|
+
│ Clears all collections, removing all data, metadata, and linked indexes. │
|
|
631
|
+
│ │
|
|
632
|
+
│ Call: universe.clear() │
|
|
633
|
+
│ │
|
|
634
|
+
│ Actions: │
|
|
635
|
+
│ 1. For each collection: │
|
|
636
|
+
│ - data.clear() │
|
|
637
|
+
│ - metadata.clear() │
|
|
638
|
+
│ - primaryToLinked.clear() (if linked) │
|
|
639
|
+
│ 2. collections.clear() │
|
|
640
|
+
│ │
|
|
641
|
+
│ Note: The global token registry is NOT cleared. Use resetTokenRegistry() │
|
|
642
|
+
│ directly if you need to clear it (e.g., in tests or full app reset). │
|
|
643
|
+
│ │
|
|
644
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Update Flow Diagram
|
|
648
|
+
|
|
649
|
+
```
|
|
650
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
651
|
+
│ updateSearchMetadata Flow │
|
|
652
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
653
|
+
│ │
|
|
654
|
+
│ Input: (collectionName, id, item) │
|
|
655
|
+
│ │ │
|
|
656
|
+
│ ▼ │
|
|
657
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
658
|
+
│ │ 1. Check if item exists │ │
|
|
659
|
+
│ │ existingItem = data.get(id) │ │
|
|
660
|
+
│ │ wasAdded = !existingItem │ │
|
|
661
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
662
|
+
│ │ │
|
|
663
|
+
│ ▼ │
|
|
664
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
665
|
+
│ │ 2. If linked collection: handle FK changes │ │
|
|
666
|
+
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
|
|
667
|
+
│ │ │ previousPrimaryId = fkGetter(existingItem) │ │ │
|
|
668
|
+
│ │ │ newPrimaryId = fkGetter(item) │ │ │
|
|
669
|
+
│ │ │ │ │ │
|
|
670
|
+
│ │ │ if (previousPrimaryId !== newPrimaryId) { │ │ │
|
|
671
|
+
│ │ │ Remove id from primaryToLinked[previousPrimaryId] │ │ │
|
|
672
|
+
│ │ │ Add id to primaryToLinked[newPrimaryId] │ │ │
|
|
673
|
+
│ │ │ } │ │ │
|
|
674
|
+
│ │ │ │ │ │
|
|
675
|
+
│ │ │ if (item._deleted || item._blocked) { │ │ │
|
|
676
|
+
│ │ │ Remove id from primaryToLinked[previousPrimaryId] │ │ │
|
|
677
|
+
│ │ │ } │ │ │
|
|
678
|
+
│ │ └───────────────────────────────────────────────────────────┘ │ │
|
|
679
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
680
|
+
│ │ │
|
|
681
|
+
│ ▼ │
|
|
682
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
683
|
+
│ │ 3. Update data │ │
|
|
684
|
+
│ │ data.set(id, item) │ │
|
|
685
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
686
|
+
│ │ │
|
|
687
|
+
│ ▼ │
|
|
688
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
689
|
+
│ │ 4. Update metadata (numeric) │ │
|
|
690
|
+
│ │ tokensMap = createTokensMap() │ │
|
|
691
|
+
│ │ tokens = prepareObjectForSearchNumeric(item, spec, tokensMap) │ │
|
|
692
|
+
│ │ clearTokensMap(tokensMap) │ │
|
|
693
|
+
│ │ │ │
|
|
694
|
+
│ │ if (tokens !== undefined) { │ │
|
|
695
|
+
│ │ metadata.set(id, tokens) ← Uint32Array searchable │ │
|
|
696
|
+
│ │ } else { │ │
|
|
697
|
+
│ │ metadata.delete(id) ← _deleted or _blocked │ │
|
|
698
|
+
│ │ } │ │
|
|
699
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
700
|
+
│ │ │
|
|
701
|
+
│ ▼ │
|
|
702
|
+
│ Return: { added: wasAdded, previousPrimaryId } │
|
|
703
|
+
│ │
|
|
704
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Standalone Functions (without SearchUniverse)
|
|
708
|
+
|
|
709
|
+
For direct metadata manipulation without SearchUniverse:
|
|
710
|
+
|
|
711
|
+
```
|
|
712
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
713
|
+
│ Standalone Update Functions │
|
|
714
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
715
|
+
│ │
|
|
716
|
+
│ NUMERIC (Default) - import from 'cry-search': │
|
|
717
|
+
│ │
|
|
718
|
+
│ createSearchArrayMetadata(data, spec) │
|
|
719
|
+
│ Creates NumericSearchMetadata with Uint32Array tokens │
|
|
720
|
+
│ Creates/clears temporary tokensMap internally │
|
|
721
|
+
│ │
|
|
722
|
+
│ updateSearchMetadata(metadata, id, obj, spec) │
|
|
723
|
+
│ Updates a single item's tokens in the metadata map │
|
|
724
|
+
│ │
|
|
725
|
+
│ updateSearchMetadataBatch(metadata, batch, spec) │
|
|
726
|
+
│ batch = [{ objectId, obj }, ...] │
|
|
727
|
+
│ Updates multiple items efficiently │
|
|
728
|
+
│ Creates/clears temporary tokensMap internally │
|
|
729
|
+
│ │
|
|
730
|
+
│ removeFromSearchMetadata(metadata, id) │
|
|
731
|
+
│ Removes item from metadata │
|
|
732
|
+
│ │
|
|
733
|
+
│ removeFromSearchMetadataBatch(metadata, ids) │
|
|
734
|
+
│ Removes multiple items from metadata │
|
|
735
|
+
│ │
|
|
736
|
+
│ searchInData(query, metadata) / searchInDataReturnIds(query, metadata) │
|
|
737
|
+
│ Returns matching IDs │
|
|
738
|
+
│ │
|
|
739
|
+
│ searchInDataReturnObjects(query, metadata, data) │
|
|
740
|
+
│ Returns matching objects │
|
|
741
|
+
│ │
|
|
742
|
+
│ ────────────────────────────────────────────────────────────────────── │
|
|
743
|
+
│ │
|
|
744
|
+
│ STRING (Legacy) - import with *String suffix: │
|
|
745
|
+
│ │
|
|
746
|
+
│ createSearchArrayMetadataString(data, spec) │
|
|
747
|
+
│ updateSearchMetadataString(metadata, id, obj, spec) │
|
|
748
|
+
│ updateSearchMetadataBatchString(metadata, batch, spec) │
|
|
749
|
+
│ removeFromSearchMetadataString(metadata, id) │
|
|
750
|
+
│ findInArray(query, data, metadata) │
|
|
751
|
+
│ │
|
|
752
|
+
│ ────────────────────────────────────────────────────────────────────── │
|
|
753
|
+
│ │
|
|
754
|
+
│ SYNC (String-based): │
|
|
755
|
+
│ │
|
|
756
|
+
│ syncSearchArrayMetadata(data, metadata, spec) │
|
|
757
|
+
│ Syncs metadata with current data state │
|
|
758
|
+
│ Returns: { added, updated, removed, addedIds, updatedIds, removedIds } │
|
|
759
|
+
│ │
|
|
760
|
+
│ ────────────────────────────────────────────────────────────────────── │
|
|
761
|
+
│ │
|
|
762
|
+
│ For linked metadata (String-based): │
|
|
763
|
+
│ │
|
|
764
|
+
│ upsertPrimaryItem(linked, id, item, spec) │
|
|
765
|
+
│ Adds/updates primary item in linked structure │
|
|
766
|
+
│ │
|
|
767
|
+
│ upsertLinkedItem(linked, id, item, fkGetter, spec) │
|
|
768
|
+
│ Adds/updates linked item, handles FK changes │
|
|
769
|
+
│ │
|
|
770
|
+
│ removePrimaryItem(linked, id) │
|
|
771
|
+
│ Removes primary item │
|
|
772
|
+
│ │
|
|
773
|
+
│ removeLinkedItem(linked, id, primaryId) │
|
|
774
|
+
│ Removes linked item from structure and index │
|
|
775
|
+
│ │
|
|
776
|
+
│ syncLinkedItemsForPrimary(linked, primaryId, items, fkGetter, spec) │
|
|
777
|
+
│ Syncs all linked items for a specific primary │
|
|
778
|
+
│ │
|
|
779
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
## Token Registry Functions
|
|
783
|
+
|
|
784
|
+
```
|
|
785
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
786
|
+
│ Token Registry API │
|
|
787
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
788
|
+
│ │
|
|
789
|
+
│ tokens: string[] // Global array (read-only export) │
|
|
790
|
+
│ │
|
|
791
|
+
│ getTokenCount(): number // Current number of registered tokens │
|
|
792
|
+
│ │
|
|
793
|
+
│ getTokenById(id: number): string | undefined │
|
|
794
|
+
│ Returns token string for given ID │
|
|
795
|
+
│ │
|
|
796
|
+
│ findTokenId(token: string): number │
|
|
797
|
+
│ Returns token ID or -1 if not found │
|
|
798
|
+
│ Uses linear search (no Map overhead during search) │
|
|
799
|
+
│ │
|
|
800
|
+
│ registerToken(token: string): number │
|
|
801
|
+
│ Registers new token, returns its ID │
|
|
802
|
+
│ If token exists, returns existing ID │
|
|
803
|
+
│ │
|
|
804
|
+
│ getOrCreateTokenId(token: string, tokensMap): number │
|
|
805
|
+
│ Fast lookup/create using temporary Map │
|
|
806
|
+
│ Used during metadata building │
|
|
807
|
+
│ │
|
|
808
|
+
│ createTokensMap(): Map<string, number> │
|
|
809
|
+
│ Creates temporary Map for fast lookups during build │
|
|
810
|
+
│ │
|
|
811
|
+
│ clearTokensMap(tokensMap): void │
|
|
812
|
+
│ Clears the temporary Map to free memory │
|
|
813
|
+
│ │
|
|
814
|
+
│ resetTokenRegistry(): void │
|
|
815
|
+
│ Clears all tokens (for testing only) │
|
|
816
|
+
│ │
|
|
817
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
818
|
+
```
|