@delali/narsil 0.2.4-next.49 → 0.2.4-next.51

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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/{options-Dtk99qUs.d.ts → capabilities-BWm4IITU.d.ts} +56 -48
  3. package/dist/chunk-AV4IJKD2.mjs +35 -0
  4. package/dist/{chunk-M6JGMKA5.mjs → chunk-BEIK3EOZ.mjs} +503 -2273
  5. package/dist/chunk-CMVG2UEF.mjs +2038 -0
  6. package/dist/chunk-CV5H7XL7.mjs +876 -0
  7. package/dist/{chunk-KGRYQVZ5.mjs → chunk-MHE7YWAU.mjs} +2 -1
  8. package/dist/{chunk-56PJBIJ6.mjs → chunk-ND34I3SV.mjs} +427 -1455
  9. package/dist/chunk-PIWKZPP7.mjs +64 -0
  10. package/dist/{chunk-HGJC5L65.mjs → chunk-PMBHH44R.mjs} +121 -2
  11. package/dist/chunk-RP23PWMV.mjs +11 -0
  12. package/dist/{chunk-RDGXYRF4.mjs → chunk-WYJXSLER.mjs} +8 -19
  13. package/dist/{chunk-JXHUHFJ5.mjs → chunk-X6YZO4MQ.mjs} +2 -11
  14. package/dist/{chunk-OWCFK42Q.mjs → chunk-XE6XR2OE.mjs} +3 -10
  15. package/dist/chunk-ZHUQKZBE.mjs +1292 -0
  16. package/dist/{client-DX7xPcNA.d.ts → client-B_X6_AbT.d.ts} +34 -10
  17. package/dist/client.d.ts +8 -8
  18. package/dist/client.mjs +26 -10
  19. package/dist/{config-B41W23Us.d.ts → config-Czh2YNDi.d.ts} +2 -2
  20. package/dist/{config-Co-zAXqO.d.ts → config-OON7IJ_B.d.ts} +26 -5
  21. package/dist/direct-executor-OREXU72C.mjs +12 -0
  22. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  23. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  24. package/dist/distribution.d.ts +8 -8
  25. package/dist/distribution.mjs +28 -11
  26. package/dist/{engine-CABZg25p.d.ts → engine-9PtFY50q.d.ts} +114 -62
  27. package/dist/index.browser.mjs +3255 -2516
  28. package/dist/index.d.ts +7 -7
  29. package/dist/index.mjs +11 -6
  30. package/dist/{search-FjWhZN3f.d.ts → memory-Bzd5xcEW.d.ts} +88 -79
  31. package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
  32. package/dist/persistence/durability/checkpoint-worker.mjs +11 -6
  33. package/dist/react.d.ts +6 -6
  34. package/dist/{schema-B7juWg8Q.d.ts → schema-BqRx9t4E.d.ts} +22 -3
  35. package/dist/server.d.ts +48 -43
  36. package/dist/server.mjs +326 -819
  37. package/dist/{tasks-Bn4R1NPN.d.ts → tasks-DT7L1p6w.d.ts} +20 -1
  38. package/dist/thread-PZLEO6JL.mjs +310 -0
  39. package/dist/{types-DsSSjY4d.d.ts → types-DXPxaXPO.d.ts} +1 -1
  40. package/dist/vector/hnsw-build-worker.mjs +2 -1
  41. package/dist/vector/search-worker.mjs +3 -121
  42. package/dist/workers/entry.mjs +47 -3
  43. package/package.json +5 -1
  44. package/dist/direct-executor-HRTDWMVB.mjs +0 -9
@@ -1,3 +1,5 @@
1
+ import { M as MemoryStats } from './memory-Bzd5xcEW.js';
2
+
1
3
  /** Declarative index configuration accepted over HTTP. Function-valued engine
2
4
  * options (custom tokenizer, stopWords-as-function, group reducer, embedding
3
5
  * adapter object) are not representable here; the embedding adapter is named
@@ -115,6 +117,23 @@ interface CapabilitiesResponse {
115
117
  /** Each name marks one capability this server serves. */
116
118
  capabilities: string[];
117
119
  }
120
+ /**
121
+ * The body `GET /stats/memory` answers with, which is the engine's {@link MemoryStats}
122
+ * plus the number of worker threads the server receives requests on. Only the
123
+ * server can report that count, because it starts those threads.
124
+ *
125
+ * @public
126
+ */
127
+ interface MemoryStatsResponse extends MemoryStats {
128
+ /**
129
+ * This many worker threads receive requests and answer searches from the
130
+ * copies they hold. The count is zero where the main thread answers everything
131
+ * itself, which happens under `workers.enabled: false`, on a runtime without
132
+ * worker threads, and on a cluster node's engine. See
133
+ * {@link NarsilServer.requestThreadCount}.
134
+ */
135
+ requestThreads: number;
136
+ }
118
137
 
119
138
  /**
120
139
  * Which long-running operation a task record tracks.
@@ -271,4 +290,4 @@ interface TaskStore {
271
290
  shutdown?(): Promise<void>;
272
291
  }
273
292
 
274
- export type { CapabilitiesResponse as C, HttpIndexConfig as H, ImportError as I, TaskListPage as T, CreateIndexEmbedding as a, ImportResult as b, TaskListQuery as c, TaskProgress as d, TaskRecord as e, TaskStatus as f, TaskType as g, TaskStore as h, CreateIndexRequest as i };
293
+ export type { CapabilitiesResponse as C, HttpIndexConfig as H, ImportError as I, MemoryStatsResponse as M, TaskListPage as T, CreateIndexEmbedding as a, ImportResult as b, TaskListQuery as c, TaskProgress as d, TaskRecord as e, TaskStatus as f, TaskType as g, TaskStore as h, CreateIndexRequest as i };
@@ -0,0 +1,310 @@
1
+ import { loadUWebSockets, childAppOf, createRouteRunner, corsWriter, createSharedRequestGate, registerServerRoutes, sendRelayed, createDocumentReadHandlers, respondJson, SERVER_CAPABILITIES, sendError, serverRoutes, parseJson, validateSuggest, rejectInvalid, respondError, validateQuery } from './chunk-CV5H7XL7.mjs';
2
+ import { executeListDocuments, executeSuggest, executePreflight, queryBindingOf, executeQuery } from './chunk-ZHUQKZBE.mjs';
3
+ import './chunk-XE6XR2OE.mjs';
4
+ import { extractVectorFieldsFromSchema } from './chunk-CMVG2UEF.mjs';
5
+ import './chunk-CBLS46IE.mjs';
6
+ import './chunk-RP23PWMV.mjs';
7
+ import './chunk-2GWVQWGA.mjs';
8
+ import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-P4ELXVJQ.mjs';
9
+
10
+ // src/server/request-threads/read-engine.ts
11
+ function createThreadReadEngine(options) {
12
+ const { executor, relay, searchHooks } = options;
13
+ function requireContext(indexName) {
14
+ const context = executor.queryContextOf(indexName);
15
+ if (context === void 0) {
16
+ throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `This thread holds no copy of index "${indexName}"`, {
17
+ indexName
18
+ });
19
+ }
20
+ relay.touch(indexName);
21
+ return context;
22
+ }
23
+ async function resolvedParams(indexName, params) {
24
+ if (params.vector?.text === void 0) return params;
25
+ const value = await relay.embed(indexName, params.vector);
26
+ const { text: _text, ...vector } = params.vector;
27
+ return { ...params, vector: { ...vector, value } };
28
+ }
29
+ function canAnswer(indexName, params) {
30
+ if (searchHooks) return false;
31
+ const context = executor.queryContextOf(indexName);
32
+ if (context === void 0) return false;
33
+ const field = params.vector?.field;
34
+ if (field === void 0) return true;
35
+ const vectorFields = extractVectorFieldsFromSchema(context.config.schema);
36
+ if (!vectorFields.has(field)) return true;
37
+ return context.vectorSearchers.has(field);
38
+ }
39
+ return {
40
+ holdsCopyOf: (indexName) => executor.queryContextOf(indexName) !== void 0,
41
+ canAnswer,
42
+ async query(indexName, params) {
43
+ const context = requireContext(indexName);
44
+ const result = await executeQuery(await resolvedParams(indexName, params), {
45
+ manager: context.manager,
46
+ language: context.language,
47
+ config: context.config,
48
+ indexName,
49
+ cursorBinding: queryBindingOf(params),
50
+ vectorSearchers: context.vectorSearchers
51
+ });
52
+ if (context.analysisStale) result.analysisStale = true;
53
+ return result;
54
+ },
55
+ async preflight(indexName, params) {
56
+ const context = requireContext(indexName);
57
+ const result = await executePreflight(await resolvedParams(indexName, params), {
58
+ manager: context.manager,
59
+ language: context.language,
60
+ config: context.config,
61
+ indexName,
62
+ cursorBinding: queryBindingOf(params),
63
+ vectorSearchers: context.vectorSearchers
64
+ });
65
+ if (context.analysisStale) result.analysisStale = true;
66
+ return result;
67
+ },
68
+ async suggest(indexName, params) {
69
+ const context = requireContext(indexName);
70
+ const result = executeSuggest(context.manager, context.language, params);
71
+ if (context.analysisStale) result.analysisStale = true;
72
+ return result;
73
+ },
74
+ async get(indexName, docId) {
75
+ return requireContext(indexName).manager.get(docId);
76
+ },
77
+ async getMultiple(indexName, docIds) {
78
+ const { manager } = requireContext(indexName);
79
+ const found = /* @__PURE__ */ new Map();
80
+ for (const docId of docIds) {
81
+ const document = manager.get(docId);
82
+ if (document !== void 0) found.set(docId, document);
83
+ }
84
+ return found;
85
+ },
86
+ async has(indexName, docId) {
87
+ return requireContext(indexName).manager.has(docId);
88
+ },
89
+ async countDocuments(indexName) {
90
+ return requireContext(indexName).manager.countDocuments();
91
+ },
92
+ async listDocuments(indexName, params) {
93
+ const context = requireContext(indexName);
94
+ return executeListDocuments(params ?? {}, { manager: context.manager, schema: context.config.schema });
95
+ }
96
+ };
97
+ }
98
+
99
+ // src/server/request-threads/relay-client.ts
100
+ function isRelayReply(message) {
101
+ if (typeof message !== "object" || message === null) return false;
102
+ const { type, requestId } = message;
103
+ return typeof requestId === "number" && (type === "response" || type === "authorized" || type === "embedded");
104
+ }
105
+ function errorOf(reply) {
106
+ const error = reply.error ?? { code: ErrorCodes.EMBEDDING_FAILED, message: "The main thread returned no vector" };
107
+ return new NarsilError(error.code, error.message, error.details);
108
+ }
109
+ function portClosedError() {
110
+ return new NarsilError(ErrorCodes.WORKER_CRASHED, "The port to the main thread closed before it answered");
111
+ }
112
+ var SHUTTING_DOWN_DENIAL = {
113
+ allowed: false,
114
+ denial: { status: 503, code: ServerErrorCodes.INTERNAL_ERROR, message: "The server is shutting down" }
115
+ };
116
+ function sendUnavailable(res) {
117
+ sendError(res, 503, ServerErrorCodes.INTERNAL_ERROR, "The server is shutting down");
118
+ }
119
+ function createRelayClient(port, touchIntervalMs) {
120
+ const pending = /* @__PURE__ */ new Map();
121
+ const touchedAt = /* @__PURE__ */ new Map();
122
+ let nextRequestId = 0;
123
+ let closed = false;
124
+ function failAll() {
125
+ closed = true;
126
+ const waiting = [...pending.values()];
127
+ pending.clear();
128
+ for (const entry of waiting) entry.reject(portClosedError());
129
+ }
130
+ port.on("message", (message) => {
131
+ if (!isRelayReply(message)) return;
132
+ const waiting = pending.get(message.requestId);
133
+ if (waiting === void 0) return;
134
+ pending.delete(message.requestId);
135
+ waiting.resolve(message);
136
+ });
137
+ port.on("close", failAll);
138
+ function send(build) {
139
+ if (closed) return Promise.reject(portClosedError());
140
+ nextRequestId += 1;
141
+ const requestId = nextRequestId;
142
+ return new Promise((resolve, reject) => {
143
+ pending.set(requestId, { resolve, reject });
144
+ port.postMessage(build(requestId));
145
+ });
146
+ }
147
+ return {
148
+ async authorize(context) {
149
+ let reply;
150
+ try {
151
+ reply = await send((requestId) => ({ type: "authorize", requestId, context }));
152
+ } catch {
153
+ return SHUTTING_DOWN_DENIAL;
154
+ }
155
+ if (reply.type !== "authorized") return { allowed: false, denial: null };
156
+ if (reply.hookError) return { allowed: false, denial: null };
157
+ if (reply.denial !== null) return { allowed: false, denial: reply.denial };
158
+ return { allowed: true };
159
+ },
160
+ async request(route, ctx) {
161
+ let reply;
162
+ try {
163
+ reply = await send((requestId) => ({
164
+ type: "request",
165
+ requestId,
166
+ route,
167
+ params: ctx.params,
168
+ query: ctx.query.toString(),
169
+ contentType: ctx.contentType,
170
+ rawBody: ctx.rawBody === null ? null : new Uint8Array(ctx.rawBody),
171
+ hookContext: ctx.hookContext
172
+ }));
173
+ } catch {
174
+ if (!ctx.abort.aborted) sendUnavailable(ctx.res);
175
+ return;
176
+ }
177
+ if (reply.type !== "response" || ctx.abort.aborted) return;
178
+ sendRelayed(ctx.res, ctx.abort, reply.status, reply.headers, reply.body);
179
+ },
180
+ async embed(indexName, vector) {
181
+ const reply = await send((requestId) => ({ type: "embed", requestId, indexName, vector }));
182
+ if (reply.type !== "embedded" || reply.value === null) {
183
+ throw reply.type === "embedded" ? errorOf(reply) : new NarsilError(ErrorCodes.EMBEDDING_FAILED, "The main thread returned no vector");
184
+ }
185
+ return reply.value;
186
+ },
187
+ touch(indexName) {
188
+ if (closed) return;
189
+ const now = Date.now();
190
+ const last = touchedAt.get(indexName);
191
+ if (last !== void 0 && now - last < touchIntervalMs) return;
192
+ touchedAt.set(indexName, now);
193
+ port.postMessage({ type: "touch", indexName });
194
+ },
195
+ close() {
196
+ failAll();
197
+ port.close();
198
+ }
199
+ };
200
+ }
201
+
202
+ // src/server/request-threads/thread.ts
203
+ var servingApp = null;
204
+ function relayed(relay, route) {
205
+ return (ctx) => relay.request(route, ctx);
206
+ }
207
+ function onHeldIndex(local, handler, fallback) {
208
+ return (ctx) => local.holdsCopyOf(ctx.params[0]) ? handler(ctx) : fallback(ctx);
209
+ }
210
+ function searchHandler(local, fallback, maxResultWindow, answer) {
211
+ return async (ctx) => {
212
+ const params = parseJson(ctx);
213
+ if (!params) return;
214
+ const failure = validateQuery(params, maxResultWindow);
215
+ if (failure) {
216
+ rejectInvalid(ctx, failure);
217
+ return;
218
+ }
219
+ if (!local.canAnswer(ctx.params[0], params)) {
220
+ await fallback(ctx);
221
+ return;
222
+ }
223
+ try {
224
+ respondJson(ctx, await answer(ctx.params[0], params));
225
+ } catch (err) {
226
+ respondError(ctx, err);
227
+ }
228
+ };
229
+ }
230
+ function suggestHandler(local, fallback) {
231
+ return async (ctx) => {
232
+ const params = parseJson(ctx);
233
+ if (!params) return;
234
+ const failure = validateSuggest(params);
235
+ if (failure) {
236
+ rejectInvalid(ctx, failure);
237
+ return;
238
+ }
239
+ if (!local.holdsCopyOf(ctx.params[0])) {
240
+ await fallback(ctx);
241
+ return;
242
+ }
243
+ try {
244
+ respondJson(ctx, await local.suggest(ctx.params[0], params));
245
+ } catch (err) {
246
+ respondError(ctx, err);
247
+ }
248
+ };
249
+ }
250
+ var THREAD_ONLY_ROUTES = /* @__PURE__ */ new Set(["livez", "version", "capabilities"]);
251
+ function isRelayedRoute(handler) {
252
+ return !THREAD_ONLY_ROUTES.has(handler);
253
+ }
254
+ function relayedHandlers(relay, limits) {
255
+ const forwarded = {};
256
+ for (const route of serverRoutes(limits)) {
257
+ if (isRelayedRoute(route.handler)) forwarded[route.handler] = relayed(relay, route.handler);
258
+ }
259
+ return forwarded;
260
+ }
261
+ function threadHandlers(local, relay, settings) {
262
+ const reads = createDocumentReadHandlers({ engine: local, limits: settings.limits });
263
+ const forwarded = relayedHandlers(relay, settings.limits);
264
+ const { maxResultWindow } = settings.limits;
265
+ return {
266
+ ...forwarded,
267
+ livez: (ctx) => respondJson(ctx, { status: "ok" }),
268
+ version: (ctx) => respondJson(ctx, { name: "narsil", ...settings.build }),
269
+ capabilities: (ctx) => respondJson(ctx, { capabilities: [...SERVER_CAPABILITIES] }),
270
+ search: searchHandler(local, forwarded.search, maxResultWindow, (name, params) => local.query(name, params)),
271
+ preflight: searchHandler(
272
+ local,
273
+ forwarded.preflight,
274
+ maxResultWindow,
275
+ (name, params) => local.preflight(name, params)
276
+ ),
277
+ suggest: suggestHandler(local, forwarded.suggest),
278
+ get: onHeldIndex(local, reads.get, forwarded.get),
279
+ exists: onHeldIndex(local, reads.exists, forwarded.exists),
280
+ count: onHeldIndex(local, reads.count, forwarded.count),
281
+ list: onHeldIndex(local, reads.list, forwarded.list),
282
+ multiGet: onHeldIndex(local, reads.multiGet, forwarded.multiGet)
283
+ };
284
+ }
285
+ async function serveRequests(executor, settings) {
286
+ if (servingApp !== null) throw new Error("This worker already serves requests");
287
+ const uws = await loadUWebSockets();
288
+ const app = uws.App();
289
+ const child = childAppOf(app);
290
+ if (child === null) throw new Error("This uWebSockets.js build offers no child apps");
291
+ const relay = createRelayClient(settings.port, settings.touchIntervalMs);
292
+ const local = createThreadReadEngine({ executor, relay, searchHooks: settings.searchHooks });
293
+ const run = createRouteRunner({
294
+ authorize: settings.authorizes ? (context) => relay.authorize(context) : void 0,
295
+ gate: createSharedRequestGate(settings.gate, settings.maxConcurrentRequests, settings.gateSlot),
296
+ writeCors: settings.cors ? corsWriter(settings.cors) : void 0
297
+ });
298
+ registerServerRoutes(app, run, threadHandlers(local, relay, settings), settings.limits, settings.cors);
299
+ servingApp = { app: child, relay };
300
+ return { descriptor: child.getDescriptor() };
301
+ }
302
+ function closeRequestThread() {
303
+ if (servingApp === null) return;
304
+ const { app, relay } = servingApp;
305
+ servingApp = null;
306
+ app.close();
307
+ relay.close();
308
+ }
309
+
310
+ export { closeRequestThread, serveRequests };
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './schema-B7juWg8Q.js';
1
+ import { S as SchemaDefinition } from './schema-BqRx9t4E.js';
2
2
 
3
3
  /**
4
4
  * What a node does in a cluster. A node may hold several roles at once.
@@ -1,4 +1,5 @@
1
- import { createVectorStore, createHNSWIndex, MAX_WORKER_DIMENSION, MAX_WORKER_VECTORS } from '../chunk-JXHUHFJ5.mjs';
1
+ import { createVectorStore, createHNSWIndex, MAX_WORKER_DIMENSION, MAX_WORKER_VECTORS } from '../chunk-X6YZO4MQ.mjs';
2
+ import '../chunk-RP23PWMV.mjs';
2
3
  import '../chunk-2GWVQWGA.mjs';
3
4
 
4
5
  // src/vector/hnsw-build-worker.ts
@@ -1,126 +1,8 @@
1
- import { createScalarQuantizer, deriveSq8Constants, arenaQuantizedDistance, quantizeVectorInto, quantizedMagnitude, scalarQuantizedDistance } from '../chunk-HGJC5L65.mjs';
2
- import { searchOrdinals, MAX_WORKER_COPIES, createVectorStore, createHNSWIndex, createSharedArenaSimd, importAdjacency, createHNSWWorkspace, arenaFloat32Distance, ABSENT_DOCUMENT_RANK } from '../chunk-JXHUHFJ5.mjs';
1
+ import { openSharedWorkerCopy, createScalarQuantizer } from '../chunk-PMBHH44R.mjs';
2
+ import { searchOrdinals, MAX_WORKER_COPIES, createVectorStore, createHNSWIndex } from '../chunk-X6YZO4MQ.mjs';
3
+ import '../chunk-RP23PWMV.mjs';
3
4
  import '../chunk-2GWVQWGA.mjs';
4
5
 
5
- // src/vector/shared-generation/worker-view.ts
6
- function createVectorReader(snapshot, simd, scratchSlot) {
7
- const { layout, magnitudes, rankByOrdinal } = snapshot;
8
- const { dimension, slots } = layout;
9
- const vectors = new Float32Array(snapshot.memory.buffer, layout.vectorsOffset, slots * dimension);
10
- const scratchByteOffset = scratchSlot * layout.float32ScratchStride;
11
- const scratch = simd ? new Float32Array(snapshot.memory.buffer, scratchByteOffset, dimension) : null;
12
- const holdsDocument = (ordinal) => ordinal >= 0 && ordinal < slots && rankByOrdinal[ordinal] !== ABSENT_DOCUMENT_RANK;
13
- return {
14
- entryForOrdinal(ordinal) {
15
- if (!holdsDocument(ordinal)) return void 0;
16
- const base = ordinal * dimension;
17
- return { vector: vectors.subarray(base, base + dimension), magnitude: magnitudes[ordinal] };
18
- },
19
- prepareQueryArena(query) {
20
- if (!simd || !scratch || query.length !== dimension) return null;
21
- scratch.set(query, 0);
22
- return { magnitude: simd.magnitude(scratchByteOffset, dimension) };
23
- },
24
- distanceFromArena(prepared, ordinal, metric) {
25
- if (!simd || !holdsDocument(ordinal)) return Number.POSITIVE_INFINITY;
26
- const byteOffset = layout.vectorsOffset + ordinal * dimension * 4;
27
- return arenaFloat32Distance(
28
- simd,
29
- scratchByteOffset,
30
- byteOffset,
31
- dimension,
32
- metric,
33
- prepared.magnitude,
34
- magnitudes[ordinal]
35
- );
36
- }
37
- };
38
- }
39
- function createQuantizerReader(snapshot, simd, scratchSlot) {
40
- if (snapshot.quantization !== "sq8" || snapshot.calibration === null) return void 0;
41
- const { layout, codeSums, codeMagnitudes, codePresent } = snapshot;
42
- const { dimension, slots } = layout;
43
- const constants = deriveSq8Constants(snapshot.calibration.alpha, snapshot.calibration.offset, dimension);
44
- const codes = new Uint8Array(snapshot.memory.buffer, layout.codesOffset, slots * dimension);
45
- const scratchByteOffset = layout.codeScratchOffset + scratchSlot * layout.codeScratchStride;
46
- const scratch = simd ? new Uint8Array(snapshot.memory.buffer, scratchByteOffset, dimension) : null;
47
- let liveCodes = 0;
48
- for (let ordinal = 0; ordinal < codePresent.length; ordinal++) {
49
- if (codePresent[ordinal] === 1) liveCodes++;
50
- }
51
- const holdsCodes = (ordinal) => ordinal >= 0 && ordinal < slots && codePresent[ordinal] === 1;
52
- return {
53
- get size() {
54
- return liveCodes;
55
- },
56
- isCalibrated: () => true,
57
- prepareQuery(query) {
58
- const quantized = new Uint8Array(dimension);
59
- const { sum, sumSq } = quantizeVectorInto(quantized, query, dimension, constants);
60
- return { quantized, sum, sumSq, magnitude: quantizedMagnitude(constants, sumSq, sum) };
61
- },
62
- distanceFromPreparedByOrdinal(prepared, ordinal, metric) {
63
- if (!holdsCodes(ordinal)) return Number.POSITIVE_INFINITY;
64
- return scalarQuantizedDistance(
65
- codes,
66
- codeSums,
67
- codeMagnitudes,
68
- dimension,
69
- constants,
70
- prepared.quantized,
71
- prepared.sum,
72
- prepared.magnitude,
73
- ordinal,
74
- metric
75
- );
76
- },
77
- prepareQueryArena(query) {
78
- if (!simd || !scratch) return null;
79
- const { sum, sumSq } = quantizeVectorInto(scratch, query, dimension, constants);
80
- return { sum, sumSq, magnitude: quantizedMagnitude(constants, sumSq, sum) };
81
- },
82
- distanceFromArena(prepared, ordinal, metric) {
83
- if (!simd || !holdsCodes(ordinal)) return Number.POSITIVE_INFINITY;
84
- return arenaQuantizedDistance(
85
- simd,
86
- scratchByteOffset,
87
- layout.codesOffset + ordinal * dimension,
88
- dimension,
89
- constants,
90
- prepared.sum,
91
- prepared.magnitude,
92
- codeSums[ordinal],
93
- codeMagnitudes[ordinal],
94
- metric
95
- );
96
- }
97
- };
98
- }
99
- function openSharedWorkerCopy(snapshot, scratchSlot) {
100
- if (scratchSlot < 0 || scratchSlot >= snapshot.layout.workerSlots) {
101
- throw new Error(`Scratch slot ${scratchSlot} is outside the ${snapshot.layout.workerSlots} reserved slots`);
102
- }
103
- const simd = createSharedArenaSimd(snapshot.memory);
104
- const adjacency = importAdjacency(snapshot.graph.adjacency);
105
- const capacity = Math.max(snapshot.graph.capacity, adjacency.capacity);
106
- const searchState = {
107
- dimension: snapshot.dimension,
108
- store: createVectorReader(snapshot, simd, scratchSlot),
109
- quantizer: createQuantizerReader(snapshot, simd, scratchSlot),
110
- adjacency,
111
- tombstones: snapshot.graph.tombstones,
112
- tombstoneCount: snapshot.graph.tombstoneCount,
113
- nodeCount: snapshot.graph.nodeCount,
114
- capacity,
115
- visited: new Uint32Array(capacity),
116
- visitStamp: 0,
117
- entryPointOrd: snapshot.graph.entryPointOrd,
118
- topLayer: snapshot.graph.topLayer,
119
- workspace: createHNSWWorkspace()
120
- };
121
- return { searchState, rankByOrdinal: snapshot.rankByOrdinal };
122
- }
123
-
124
6
  // src/vector/worker-copy.ts
125
7
  function restoreWorkerCopy(snapshot) {
126
8
  const store = createVectorStore();
@@ -1,4 +1,4 @@
1
- import { isValidWorkerAction } from '../chunk-RDGXYRF4.mjs';
1
+ import { isValidWorkerAction } from '../chunk-WYJXSLER.mjs';
2
2
  import { NarsilError, ErrorCodes } from '../chunk-P4ELXVJQ.mjs';
3
3
 
4
4
  // src/workers/action-handler.ts
@@ -16,9 +16,24 @@ async function importBootstrapModule(moduleUrl) {
16
16
  }
17
17
  await import(moduleUrl);
18
18
  }
19
+ async function stopServingRequests() {
20
+ const { closeRequestThread } = await import('../thread-PZLEO6JL.mjs');
21
+ closeRequestThread();
22
+ }
23
+ function holdsCopies(executor) {
24
+ return typeof executor.queryContextOf === "function";
25
+ }
19
26
  function createActionHandler(executor) {
27
+ let servingRequests = false;
28
+ const serving = { start: null };
29
+ async function stopServingOnceStarted() {
30
+ if (serving.start !== null) await serving.start;
31
+ if (servingRequests) await stopServingRequests();
32
+ servingRequests = false;
33
+ }
20
34
  return async function handleAction(action, post) {
21
35
  if (action.type === "shutdown") {
36
+ await stopServingOnceStarted();
22
37
  await executor.shutdown();
23
38
  post(buildSuccessResponse(action.requestId, void 0));
24
39
  return true;
@@ -29,6 +44,28 @@ function createActionHandler(executor) {
29
44
  post(buildSuccessResponse(action.requestId, void 0));
30
45
  return false;
31
46
  }
47
+ if (action.type === "serveRequests") {
48
+ if (!holdsCopies(executor)) {
49
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, "This executor holds no copies to answer requests from");
50
+ }
51
+ const starting = import('../thread-PZLEO6JL.mjs').then(
52
+ ({ serveRequests }) => serveRequests(executor, action.settings)
53
+ );
54
+ serving.start = starting.then(
55
+ () => {
56
+ servingRequests = true;
57
+ },
58
+ () => void 0
59
+ );
60
+ const result2 = await starting;
61
+ post(buildSuccessResponse(action.requestId, result2));
62
+ return false;
63
+ }
64
+ if (action.type === "stopServing") {
65
+ await stopServingOnceStarted();
66
+ post(buildSuccessResponse(action.requestId, void 0));
67
+ return false;
68
+ }
32
69
  const result = await executor.execute(action);
33
70
  post(buildSuccessResponse(action.requestId, result));
34
71
  } catch (err) {
@@ -47,16 +84,23 @@ function startWorker() {
47
84
  });
48
85
  }
49
86
  startWorker();
87
+ function workerIdOf(workerData) {
88
+ if (typeof workerData !== "object" || workerData === null) return void 0;
89
+ const workerId = workerData.workerId;
90
+ return typeof workerId === "number" ? workerId : void 0;
91
+ }
50
92
  async function setup() {
51
93
  let parentPort = null;
94
+ let scratchSlot;
52
95
  try {
53
96
  const workerThreads = await import('worker_threads');
54
97
  parentPort = workerThreads.parentPort ?? null;
98
+ scratchSlot = workerIdOf(workerThreads.workerData);
55
99
  } catch {
56
100
  parentPort = null;
57
101
  }
58
- const { createDirectExecutor } = await import('../direct-executor-HRTDWMVB.mjs');
59
- const handleAction = createActionHandler(createDirectExecutor());
102
+ const { createDirectExecutor } = await import('../direct-executor-OREXU72C.mjs');
103
+ const handleAction = createActionHandler(createDirectExecutor({ scratchSlot }));
60
104
  if (parentPort) {
61
105
  const port = parentPort;
62
106
  port.on("message", (raw) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delali/narsil",
3
- "version": "0.2.4-next.49",
3
+ "version": "0.2.4-next.51",
4
4
  "type": "module",
5
5
  "description": "A distributed full-text search engine with cross-language portable binary format.",
6
6
  "author": "Delali (https://sondelali.com)",
@@ -30,6 +30,10 @@
30
30
  "#platform/node-worker": {
31
31
  "browser": "./src/workers/node-worker.browser.ts",
32
32
  "default": "./src/workers/node-worker.ts"
33
+ },
34
+ "#platform/heap-statistics": {
35
+ "browser": "./src/runtime/heap-statistics.browser.ts",
36
+ "default": "./src/runtime/heap-statistics.ts"
33
37
  }
34
38
  },
35
39
  "sideEffects": false,
@@ -1,9 +0,0 @@
1
- export { createDirectExecutor } from './chunk-M6JGMKA5.mjs';
2
- import './chunk-CBLS46IE.mjs';
3
- import './chunk-TGP5M5R7.mjs';
4
- import './chunk-HGJC5L65.mjs';
5
- import './chunk-JXHUHFJ5.mjs';
6
- import './chunk-U2BKYEC7.mjs';
7
- import './chunk-2GWVQWGA.mjs';
8
- import './chunk-RDGXYRF4.mjs';
9
- import './chunk-P4ELXVJQ.mjs';