simile-search 0.3.2 → 0.4.1
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/README.md +74 -1
- package/dist/ann.d.ts +110 -0
- package/dist/ann.js +374 -0
- package/dist/cache.d.ts +94 -0
- package/dist/cache.js +179 -0
- package/dist/embedder.d.ts +55 -4
- package/dist/embedder.js +144 -12
- package/dist/engine.d.ts +16 -3
- package/dist/engine.js +279 -64
- package/dist/engine.test.js +70 -256
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/quantization.d.ts +50 -0
- package/dist/quantization.js +271 -0
- package/dist/similarity.d.ts +24 -0
- package/dist/similarity.js +105 -0
- package/dist/types.d.ts +37 -0
- package/dist/updater.d.ts +172 -0
- package/dist/updater.js +336 -0
- package/package.json +1 -1
package/dist/updater.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background Updater - Non-blocking updates with queue-based processing.
|
|
3
|
+
*
|
|
4
|
+
* Allows adding items asynchronously without blocking search operations.
|
|
5
|
+
* Items are batched and processed in the background for efficiency.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Background updater for non-blocking item additions.
|
|
9
|
+
*/
|
|
10
|
+
export class BackgroundUpdater {
|
|
11
|
+
constructor(simile, config = {}) {
|
|
12
|
+
this._queue = [];
|
|
13
|
+
this.processing = false;
|
|
14
|
+
this.timeoutId = null;
|
|
15
|
+
this.eventHandlers = new Map();
|
|
16
|
+
// Stats
|
|
17
|
+
this.totalProcessed = 0;
|
|
18
|
+
this.batchCount = 0;
|
|
19
|
+
this.totalBatchItems = 0;
|
|
20
|
+
this.simile = simile;
|
|
21
|
+
this.config = {
|
|
22
|
+
batchDelay: config.batchDelay ?? 100,
|
|
23
|
+
maxBatchSize: config.maxBatchSize ?? 50,
|
|
24
|
+
onProgress: config.onProgress,
|
|
25
|
+
onError: config.onError,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Queue items for background embedding.
|
|
30
|
+
* Items are batched and processed after batchDelay ms.
|
|
31
|
+
*/
|
|
32
|
+
enqueue(items) {
|
|
33
|
+
this._queue.push(...items);
|
|
34
|
+
this.scheduleProcessing();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Queue a single item.
|
|
38
|
+
*/
|
|
39
|
+
enqueueOne(item) {
|
|
40
|
+
this._queue.push(item);
|
|
41
|
+
this.scheduleProcessing();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Force immediate processing of queued items.
|
|
45
|
+
*/
|
|
46
|
+
async flush() {
|
|
47
|
+
if (this.timeoutId) {
|
|
48
|
+
clearTimeout(this.timeoutId);
|
|
49
|
+
this.timeoutId = null;
|
|
50
|
+
}
|
|
51
|
+
await this.processQueue();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Wait for all pending items to be processed.
|
|
55
|
+
*/
|
|
56
|
+
async waitForCompletion() {
|
|
57
|
+
while (this._queue.length > 0 || this.processing) {
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the number of items waiting to be processed.
|
|
63
|
+
*/
|
|
64
|
+
getPendingCount() {
|
|
65
|
+
return this._queue.length;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if currently processing.
|
|
69
|
+
*/
|
|
70
|
+
isProcessing() {
|
|
71
|
+
return this.processing;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clear all pending items without processing.
|
|
75
|
+
*/
|
|
76
|
+
clear() {
|
|
77
|
+
if (this.timeoutId) {
|
|
78
|
+
clearTimeout(this.timeoutId);
|
|
79
|
+
this.timeoutId = null;
|
|
80
|
+
}
|
|
81
|
+
this._queue = [];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get updater statistics.
|
|
85
|
+
*/
|
|
86
|
+
getStats() {
|
|
87
|
+
return {
|
|
88
|
+
totalProcessed: this.totalProcessed,
|
|
89
|
+
pendingCount: this._queue.length,
|
|
90
|
+
isProcessing: this.processing,
|
|
91
|
+
avgBatchSize: this.batchCount > 0 ? this.totalBatchItems / this.batchCount : 0,
|
|
92
|
+
batchCount: this.batchCount,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Reset statistics.
|
|
97
|
+
*/
|
|
98
|
+
resetStats() {
|
|
99
|
+
this.totalProcessed = 0;
|
|
100
|
+
this.batchCount = 0;
|
|
101
|
+
this.totalBatchItems = 0;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Register an event handler.
|
|
105
|
+
*/
|
|
106
|
+
on(event, handler) {
|
|
107
|
+
if (!this.eventHandlers.has(event)) {
|
|
108
|
+
this.eventHandlers.set(event, new Set());
|
|
109
|
+
}
|
|
110
|
+
this.eventHandlers.get(event).add(handler);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Remove an event handler.
|
|
114
|
+
*/
|
|
115
|
+
off(event, handler) {
|
|
116
|
+
this.eventHandlers.get(event)?.delete(handler);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Emit an event to all registered handlers.
|
|
120
|
+
*/
|
|
121
|
+
emit(event, ...args) {
|
|
122
|
+
const handlers = this.eventHandlers.get(event);
|
|
123
|
+
if (handlers) {
|
|
124
|
+
for (const handler of handlers) {
|
|
125
|
+
try {
|
|
126
|
+
handler(...args);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
console.error(`Error in ${event} handler:`, e);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
scheduleProcessing() {
|
|
135
|
+
if (this.timeoutId || this.processing)
|
|
136
|
+
return;
|
|
137
|
+
this.timeoutId = setTimeout(() => {
|
|
138
|
+
this.timeoutId = null;
|
|
139
|
+
this.processQueue().catch(console.error);
|
|
140
|
+
}, this.config.batchDelay);
|
|
141
|
+
}
|
|
142
|
+
async processQueue() {
|
|
143
|
+
if (this.processing || this._queue.length === 0)
|
|
144
|
+
return;
|
|
145
|
+
this.processing = true;
|
|
146
|
+
const total = this._queue.length;
|
|
147
|
+
try {
|
|
148
|
+
while (this._queue.length > 0) {
|
|
149
|
+
// Take batch from queue
|
|
150
|
+
const batch = this._queue.splice(0, this.config.maxBatchSize);
|
|
151
|
+
const processed = total - this._queue.length;
|
|
152
|
+
this.emit('batch', batch, processed, total);
|
|
153
|
+
try {
|
|
154
|
+
await this.simile.add(batch);
|
|
155
|
+
this.totalProcessed += batch.length;
|
|
156
|
+
this.batchCount++;
|
|
157
|
+
this.totalBatchItems += batch.length;
|
|
158
|
+
if (this.config.onProgress) {
|
|
159
|
+
this.config.onProgress(processed, total);
|
|
160
|
+
}
|
|
161
|
+
this.emit('progress', processed, total);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
// Handle errors per-item if possible
|
|
165
|
+
for (const item of batch) {
|
|
166
|
+
if (this.config.onError) {
|
|
167
|
+
this.config.onError(error, item);
|
|
168
|
+
}
|
|
169
|
+
this.emit('error', error, item);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this.emit('complete', this.totalProcessed);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
this.processing = false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Debounced updater - coalesces rapid updates.
|
|
182
|
+
* Useful when items change frequently (e.g., user typing).
|
|
183
|
+
*/
|
|
184
|
+
export class DebouncedUpdater {
|
|
185
|
+
constructor(simile, debounceMs = 300, config = {}) {
|
|
186
|
+
this.pending = new Map();
|
|
187
|
+
this.timeoutId = null;
|
|
188
|
+
this.updater = new BackgroundUpdater(simile, config);
|
|
189
|
+
this.debounceMs = debounceMs;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Queue an item for update. If same ID is queued again before flush,
|
|
193
|
+
* only the latest version is processed.
|
|
194
|
+
*/
|
|
195
|
+
update(item) {
|
|
196
|
+
this.pending.set(item.id, item);
|
|
197
|
+
this.scheduleFlush();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Force immediate flush of pending items.
|
|
201
|
+
*/
|
|
202
|
+
async flush() {
|
|
203
|
+
if (this.timeoutId) {
|
|
204
|
+
clearTimeout(this.timeoutId);
|
|
205
|
+
this.timeoutId = null;
|
|
206
|
+
}
|
|
207
|
+
await this.doFlush();
|
|
208
|
+
}
|
|
209
|
+
scheduleFlush() {
|
|
210
|
+
if (this.timeoutId) {
|
|
211
|
+
clearTimeout(this.timeoutId);
|
|
212
|
+
}
|
|
213
|
+
this.timeoutId = setTimeout(() => {
|
|
214
|
+
this.timeoutId = null;
|
|
215
|
+
this.doFlush().catch(console.error);
|
|
216
|
+
}, this.debounceMs);
|
|
217
|
+
}
|
|
218
|
+
async doFlush() {
|
|
219
|
+
const items = Array.from(this.pending.values());
|
|
220
|
+
this.pending.clear();
|
|
221
|
+
if (items.length > 0) {
|
|
222
|
+
this.updater.enqueue(items);
|
|
223
|
+
await this.updater.flush();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get pending count (not yet flushed + in queue).
|
|
228
|
+
*/
|
|
229
|
+
getPendingCount() {
|
|
230
|
+
return this.pending.size + this.updater.getPendingCount();
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Clear all pending updates.
|
|
234
|
+
*/
|
|
235
|
+
clear() {
|
|
236
|
+
if (this.timeoutId) {
|
|
237
|
+
clearTimeout(this.timeoutId);
|
|
238
|
+
this.timeoutId = null;
|
|
239
|
+
}
|
|
240
|
+
this.pending.clear();
|
|
241
|
+
this.updater.clear();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get the underlying updater for event handling.
|
|
245
|
+
*/
|
|
246
|
+
getUpdater() {
|
|
247
|
+
return this.updater;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Priority queue for updates - high priority items processed first.
|
|
252
|
+
*/
|
|
253
|
+
export class PriorityUpdater {
|
|
254
|
+
constructor(simile, config = {}) {
|
|
255
|
+
this.highPriority = [];
|
|
256
|
+
this.normalPriority = [];
|
|
257
|
+
this.processing = false;
|
|
258
|
+
this.timeoutId = null;
|
|
259
|
+
this.simile = simile;
|
|
260
|
+
this.config = {
|
|
261
|
+
batchDelay: config.batchDelay ?? 50,
|
|
262
|
+
maxBatchSize: config.maxBatchSize ?? 50,
|
|
263
|
+
...config,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Queue high priority item (processed first).
|
|
268
|
+
*/
|
|
269
|
+
queueHigh(items) {
|
|
270
|
+
this.highPriority.push(...items);
|
|
271
|
+
this.scheduleProcessing();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Queue normal priority item.
|
|
275
|
+
*/
|
|
276
|
+
enqueue(items) {
|
|
277
|
+
this.normalPriority.push(...items);
|
|
278
|
+
this.scheduleProcessing();
|
|
279
|
+
}
|
|
280
|
+
scheduleProcessing() {
|
|
281
|
+
if (this.timeoutId || this.processing)
|
|
282
|
+
return;
|
|
283
|
+
this.timeoutId = setTimeout(() => {
|
|
284
|
+
this.timeoutId = null;
|
|
285
|
+
this.processQueue().catch(console.error);
|
|
286
|
+
}, this.config.batchDelay);
|
|
287
|
+
}
|
|
288
|
+
async processQueue() {
|
|
289
|
+
if (this.processing)
|
|
290
|
+
return;
|
|
291
|
+
this.processing = true;
|
|
292
|
+
const maxBatch = this.config.maxBatchSize ?? 50;
|
|
293
|
+
try {
|
|
294
|
+
// Process high priority first
|
|
295
|
+
while (this.highPriority.length > 0) {
|
|
296
|
+
const batch = this.highPriority.splice(0, maxBatch);
|
|
297
|
+
await this.simile.add(batch);
|
|
298
|
+
}
|
|
299
|
+
// Then normal priority
|
|
300
|
+
while (this.normalPriority.length > 0) {
|
|
301
|
+
const batch = this.normalPriority.splice(0, maxBatch);
|
|
302
|
+
await this.simile.add(batch);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
this.processing = false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Force immediate processing.
|
|
311
|
+
*/
|
|
312
|
+
async flush() {
|
|
313
|
+
if (this.timeoutId) {
|
|
314
|
+
clearTimeout(this.timeoutId);
|
|
315
|
+
this.timeoutId = null;
|
|
316
|
+
}
|
|
317
|
+
await this.processQueue();
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get total pending count.
|
|
321
|
+
*/
|
|
322
|
+
getPendingCount() {
|
|
323
|
+
return this.highPriority.length + this.normalPriority.length;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Clear all pending items.
|
|
327
|
+
*/
|
|
328
|
+
clear() {
|
|
329
|
+
if (this.timeoutId) {
|
|
330
|
+
clearTimeout(this.timeoutId);
|
|
331
|
+
this.timeoutId = null;
|
|
332
|
+
}
|
|
333
|
+
this.highPriority = [];
|
|
334
|
+
this.normalPriority = [];
|
|
335
|
+
}
|
|
336
|
+
}
|