@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.
- package/README.md +1 -1
- package/dist/{options-Dtk99qUs.d.ts → capabilities-BWm4IITU.d.ts} +56 -48
- package/dist/chunk-AV4IJKD2.mjs +35 -0
- package/dist/{chunk-M6JGMKA5.mjs → chunk-BEIK3EOZ.mjs} +503 -2273
- package/dist/chunk-CMVG2UEF.mjs +2038 -0
- package/dist/chunk-CV5H7XL7.mjs +876 -0
- package/dist/{chunk-KGRYQVZ5.mjs → chunk-MHE7YWAU.mjs} +2 -1
- package/dist/{chunk-56PJBIJ6.mjs → chunk-ND34I3SV.mjs} +427 -1455
- package/dist/chunk-PIWKZPP7.mjs +64 -0
- package/dist/{chunk-HGJC5L65.mjs → chunk-PMBHH44R.mjs} +121 -2
- package/dist/chunk-RP23PWMV.mjs +11 -0
- package/dist/{chunk-RDGXYRF4.mjs → chunk-WYJXSLER.mjs} +8 -19
- package/dist/{chunk-JXHUHFJ5.mjs → chunk-X6YZO4MQ.mjs} +2 -11
- package/dist/{chunk-OWCFK42Q.mjs → chunk-XE6XR2OE.mjs} +3 -10
- package/dist/chunk-ZHUQKZBE.mjs +1292 -0
- package/dist/{client-DX7xPcNA.d.ts → client-B_X6_AbT.d.ts} +34 -10
- package/dist/client.d.ts +8 -8
- package/dist/client.mjs +26 -10
- package/dist/{config-B41W23Us.d.ts → config-Czh2YNDi.d.ts} +2 -2
- package/dist/{config-Co-zAXqO.d.ts → config-OON7IJ_B.d.ts} +26 -5
- package/dist/direct-executor-OREXU72C.mjs +12 -0
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution.d.ts +8 -8
- package/dist/distribution.mjs +28 -11
- package/dist/{engine-CABZg25p.d.ts → engine-9PtFY50q.d.ts} +114 -62
- package/dist/index.browser.mjs +3255 -2516
- package/dist/index.d.ts +7 -7
- package/dist/index.mjs +11 -6
- package/dist/{search-FjWhZN3f.d.ts → memory-Bzd5xcEW.d.ts} +88 -79
- package/dist/persistence/durability/checkpoint-worker.d.ts +1 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +11 -6
- package/dist/react.d.ts +6 -6
- package/dist/{schema-B7juWg8Q.d.ts → schema-BqRx9t4E.d.ts} +22 -3
- package/dist/server.d.ts +48 -43
- package/dist/server.mjs +326 -819
- package/dist/{tasks-Bn4R1NPN.d.ts → tasks-DT7L1p6w.d.ts} +20 -1
- package/dist/thread-PZLEO6JL.mjs +310 -0
- package/dist/{types-DsSSjY4d.d.ts → types-DXPxaXPO.d.ts} +1 -1
- package/dist/vector/hnsw-build-worker.mjs +2 -1
- package/dist/vector/search-worker.mjs +3 -121
- package/dist/workers/entry.mjs +47 -3
- package/package.json +5 -1
- package/dist/direct-executor-HRTDWMVB.mjs +0 -9
package/dist/server.mjs
CHANGED
|
@@ -1,494 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { DEFAULT_MAX_RETAINED_TASKS, loadUWebSockets, resolveCors, authorizerFor, createSharedGateBuffer, gateSlotOfWorker, createRouteRunner, corsWriter, createSharedRequestGate, MAIN_THREAD_GATE_SLOT, registerServerRoutes, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_MAX_TASK_PAGE_SIZE, DEFAULT_MAX_IMPORT_ERRORS, DEFAULT_MAX_FETCH_DOCUMENTS, DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_CONCURRENT_REQUESTS, DEFAULT_IMPORT_BATCH_SIZE, DEFAULT_MAX_LINE_BYTES, DEFAULT_MAX_IMPORT_BYTES, DEFAULT_MAX_BODY_BYTES, RUNNING_TASK_TTL_MS, DEFAULT_TASK_PAGE_SIZE, TERMINAL_TASK_TTL_MS, TASK_PROGRESS_WRITE_INTERVAL_MS, serializeNarsilError, acceptorOf, createDocumentReadHandlers, sendError, respondError, respondJson, MAX_INDEX_TOUCH_INTERVAL_MS, INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT, clearGateSlot, parseJson, validateCreateIndex, rejectInvalid, validateBatch, serializeBatchResult, validateDocumentBody, validateSuggest, validateQuery, parseTaskListQuery, parseJsonOptional, validateRebalance, sendBinary, badRequest, SERVER_CAPABILITIES } from './chunk-CV5H7XL7.mjs';
|
|
2
|
+
export { ASYNC_IMPORT_CAPABILITY, INDEX_LIFECYCLE_CAPABILITY, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, httpStatusForNarsilError } from './chunk-CV5H7XL7.mjs';
|
|
3
|
+
import { validateIndexName } from './chunk-MHE7YWAU.mjs';
|
|
4
|
+
import { engineCoreOf, resolveVectorText } from './chunk-PIWKZPP7.mjs';
|
|
5
|
+
import './chunk-XE6XR2OE.mjs';
|
|
6
|
+
import { detectRuntime } from './chunk-AV4IJKD2.mjs';
|
|
7
|
+
import './chunk-CBLS46IE.mjs';
|
|
8
|
+
import { NarsilError, ServerErrorCodes, ErrorCodes } from './chunk-P4ELXVJQ.mjs';
|
|
5
9
|
export { ServerErrorCodes } from './chunk-P4ELXVJQ.mjs';
|
|
6
10
|
import { randomUUID } from 'crypto';
|
|
7
|
-
|
|
8
|
-
// src/server/capabilities.ts
|
|
9
|
-
var ASYNC_IMPORT_CAPABILITY = "documents.import.async";
|
|
10
|
-
var TASK_CANCEL_CAPABILITY = "tasks.cancel";
|
|
11
|
-
var TASK_FILTER_CAPABILITY = "tasks.filter";
|
|
12
|
-
var REBUILD_ANALYSIS_CAPABILITY = "indexes.rebuildAnalysis";
|
|
13
|
-
var INDEX_LIFECYCLE_CAPABILITY = "indexes.lifecycle";
|
|
14
|
-
var SERVER_CAPABILITIES = [
|
|
15
|
-
ASYNC_IMPORT_CAPABILITY,
|
|
16
|
-
TASK_CANCEL_CAPABILITY,
|
|
17
|
-
TASK_FILTER_CAPABILITY,
|
|
18
|
-
REBUILD_ANALYSIS_CAPABILITY,
|
|
19
|
-
INDEX_LIFECYCLE_CAPABILITY
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
// src/server/constants.ts
|
|
23
|
-
var BYTES_PER_MB = 1024 * 1024;
|
|
24
|
-
var DEFAULT_MAX_BODY_BYTES = 16 * BYTES_PER_MB;
|
|
25
|
-
var DEFAULT_MAX_IMPORT_BYTES = 100 * BYTES_PER_MB;
|
|
26
|
-
var DEFAULT_MAX_LINE_BYTES = 4 * BYTES_PER_MB;
|
|
27
|
-
var DEFAULT_IMPORT_BATCH_SIZE = 1e3;
|
|
28
|
-
var DEFAULT_MAX_CONCURRENT_REQUESTS = 0;
|
|
29
|
-
var DEFAULT_MAX_RESULT_WINDOW = 1e4;
|
|
30
|
-
var DEFAULT_MAX_FETCH_DOCUMENTS = 1e4;
|
|
31
|
-
var DEFAULT_MAX_IMPORT_ERRORS = 100;
|
|
32
|
-
var DEFAULT_MAX_TASK_PAGE_SIZE = 1e3;
|
|
33
|
-
var DEFAULT_MAX_CONCURRENT_TASKS = 4;
|
|
34
|
-
var RESPONSE_STREAM_THRESHOLD_BYTES = 64 * 1024;
|
|
35
|
-
var DEFAULT_MAX_RETAINED_TASKS = 1e3;
|
|
36
|
-
var DEFAULT_TASK_PAGE_SIZE = 20;
|
|
37
|
-
var TASK_PROGRESS_WRITE_INTERVAL_MS = 250;
|
|
38
|
-
var RUNNING_TASK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
39
|
-
var TERMINAL_TASK_TTL_MS = 60 * 60 * 1e3;
|
|
40
|
-
|
|
41
|
-
// src/server/cors.ts
|
|
42
|
-
function resolveCors(cors) {
|
|
43
|
-
if (!cors) return null;
|
|
44
|
-
if (cors === true) {
|
|
45
|
-
return { origin: "*", methods: "GET, POST, PUT, PATCH, DELETE, OPTIONS", headers: "Content-Type, Authorization" };
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
origin: cors.origin ?? "*",
|
|
49
|
-
methods: cors.methods?.join(", ") ?? "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
50
|
-
headers: cors.headers?.join(", ") ?? "Content-Type, Authorization"
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function matchOrigin(cors, requestOrigin) {
|
|
54
|
-
if (cors.origin === "*") return "*";
|
|
55
|
-
if (typeof cors.origin === "string") return cors.origin;
|
|
56
|
-
return cors.origin.includes(requestOrigin) ? requestOrigin : null;
|
|
57
|
-
}
|
|
58
|
-
function writeCorsOrigin(res, cors, requestOrigin) {
|
|
59
|
-
const allowed = matchOrigin(cors, requestOrigin);
|
|
60
|
-
if (!allowed) return;
|
|
61
|
-
res.writeHeader("Access-Control-Allow-Origin", allowed);
|
|
62
|
-
if (allowed !== "*") res.writeHeader("Vary", "Origin");
|
|
63
|
-
}
|
|
64
|
-
function corsWriter(cors) {
|
|
65
|
-
return (res, req) => writeCorsOrigin(res, cors, req.getHeader("origin"));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/server/errors.ts
|
|
69
|
-
var STATUS_BY_CODE = {
|
|
70
|
-
INVALID_REQUEST: 400,
|
|
71
|
-
INVALID_JSON: 400,
|
|
72
|
-
EMPTY_BODY: 400,
|
|
73
|
-
PAYLOAD_TOO_LARGE: 413,
|
|
74
|
-
NOT_FOUND: 404,
|
|
75
|
-
TASK_NOT_FOUND: 404,
|
|
76
|
-
TASK_NOT_CANCELLABLE: 409,
|
|
77
|
-
TASK_OWNED_BY_ANOTHER_INSTANCE: 409,
|
|
78
|
-
TASK_INTERRUPTED: 503,
|
|
79
|
-
TOO_MANY_REQUESTS: 429,
|
|
80
|
-
HOOK_ERROR: 500,
|
|
81
|
-
INTERNAL_ERROR: 500,
|
|
82
|
-
ENVELOPE_INVALID_MAGIC: 400,
|
|
83
|
-
ENVELOPE_VERSION_MISMATCH: 400,
|
|
84
|
-
SCHEMA_INVALID_TYPE: 400,
|
|
85
|
-
SCHEMA_DEPTH_EXCEEDED: 400,
|
|
86
|
-
SCHEMA_INVALID_VECTOR_DIMENSION: 400,
|
|
87
|
-
SCHEMA_INVALID_GEOPOINT: 400,
|
|
88
|
-
DOC_VALIDATION_FAILED: 400,
|
|
89
|
-
DOC_MISSING_REQUIRED_FIELD: 400,
|
|
90
|
-
SEARCH_INVALID_FIELD: 400,
|
|
91
|
-
VECTOR_DIMENSION_MISMATCH: 400,
|
|
92
|
-
SEARCH_INVALID_FILTER: 400,
|
|
93
|
-
SEARCH_INVALID_MODE: 400,
|
|
94
|
-
SEARCH_INVALID_CURSOR: 400,
|
|
95
|
-
SEARCH_RESULT_WINDOW_EXCEEDED: 400,
|
|
96
|
-
LANGUAGE_NOT_SUPPORTED: 400,
|
|
97
|
-
CONFIG_INVALID: 400,
|
|
98
|
-
EMBEDDING_DIMENSION_MISMATCH: 400,
|
|
99
|
-
EMBEDDING_NO_SOURCE: 400,
|
|
100
|
-
EMBEDDING_CONFIG_INVALID: 400,
|
|
101
|
-
INDEX_NOT_FOUND: 404,
|
|
102
|
-
DOC_NOT_FOUND: 404,
|
|
103
|
-
INDEX_ALREADY_EXISTS: 409,
|
|
104
|
-
INDEX_ORPHANED: 409,
|
|
105
|
-
INDEX_REOPEN_CAPACITY_EXHAUSTED: 503,
|
|
106
|
-
DOC_ALREADY_EXISTS: 409,
|
|
107
|
-
PARTITION_REBALANCING_BACKPRESSURE: 409,
|
|
108
|
-
PARTITION_CAPACITY_EXCEEDED: 409,
|
|
109
|
-
WORKER_BUSY: 429,
|
|
110
|
-
PARTITION_CORRUPTED: 503,
|
|
111
|
-
WORKER_CRASHED: 503,
|
|
112
|
-
WORKER_TIMEOUT: 503,
|
|
113
|
-
PERSISTENCE_SAVE_FAILED: 503,
|
|
114
|
-
PERSISTENCE_LOAD_FAILED: 503,
|
|
115
|
-
PERSISTENCE_DELETE_FAILED: 503,
|
|
116
|
-
PERSISTENCE_CRC_MISMATCH: 503,
|
|
117
|
-
PERSISTENCE_WAL_CORRUPT: 503,
|
|
118
|
-
PERSISTENCE_FSYNC_FAILED: 503,
|
|
119
|
-
EMBEDDING_FAILED: 503,
|
|
120
|
-
QUERY_ROUTING_FAILED: 503,
|
|
121
|
-
QUERY_PARTIAL_FAILURE: 503,
|
|
122
|
-
QUERY_NODE_TIMEOUT: 503,
|
|
123
|
-
QUERY_NO_ACTIVE_REPLICA: 503,
|
|
124
|
-
CLUSTER_OPERATION_UNSUPPORTED: 501,
|
|
125
|
-
REPLICATION_ENTRY_INVALID: 503,
|
|
126
|
-
REPLICATION_INSYNC_REMOVAL_FAILED: 503,
|
|
127
|
-
REPLICATION_ROLLBACK_FAILED: 503,
|
|
128
|
-
REPLICATION_LOG_FULL: 503,
|
|
129
|
-
REPLICATION_ENTRY_CORRUPT: 503,
|
|
130
|
-
REPLICATION_SNAPSHOT_CORRUPT: 503,
|
|
131
|
-
REPLICATION_TERM_MISMATCH: 503,
|
|
132
|
-
REPLICATION_SYNC_FAILED: 503,
|
|
133
|
-
PARTITION_NOT_PRIMARY: 503,
|
|
134
|
-
PARTITION_UNASSIGNED: 503,
|
|
135
|
-
INSUFFICIENT_REPLICAS: 503,
|
|
136
|
-
TRANSPORT_DEPENDENCY_MISSING: 500,
|
|
137
|
-
ALLOCATION_INVALID_CONFIG: 400,
|
|
138
|
-
ALLOCATION_NO_DATA_NODES: 503,
|
|
139
|
-
ALLOCATION_FAILED: 503,
|
|
140
|
-
NODE_ALREADY_JOINED: 409,
|
|
141
|
-
NODE_NOT_JOINED: 503,
|
|
142
|
-
NODE_NOT_READY: 503,
|
|
143
|
-
NODE_NOT_CONTROLLER: 503,
|
|
144
|
-
NODE_BOOTSTRAP_FAILED: 503,
|
|
145
|
-
COORDINATOR_DEPENDENCY_MISSING: 500,
|
|
146
|
-
SNAPSHOT_SYNC_UNAUTHORIZED: 403,
|
|
147
|
-
SNAPSHOT_SYNC_REQUEST_INVALID: 400,
|
|
148
|
-
SNAPSHOT_SYNC_HEADER_INVALID: 400,
|
|
149
|
-
SNAPSHOT_SYNC_FRAME_INVALID: 400,
|
|
150
|
-
SNAPSHOT_SYNC_INDEX_NOT_FOUND: 404,
|
|
151
|
-
SNAPSHOT_SYNC_TOO_LARGE: 413,
|
|
152
|
-
SNAPSHOT_SYNC_CHUNK_SIZE_EXCEEDED: 413,
|
|
153
|
-
SNAPSHOT_SYNC_ABORTED: 409,
|
|
154
|
-
SNAPSHOT_SYNC_CAPACITY_EXHAUSTED: 503,
|
|
155
|
-
SNAPSHOT_SYNC_SNAPSHOT_FAILED: 503,
|
|
156
|
-
SNAPSHOT_SYNC_DECODE_FAILED: 503,
|
|
157
|
-
SNAPSHOT_SYNC_HEADER_MISMATCH: 503,
|
|
158
|
-
SNAPSHOT_SYNC_CHUNK_OUT_OF_ORDER: 503,
|
|
159
|
-
SNAPSHOT_SYNC_CHUNK_OVERFLOW: 503,
|
|
160
|
-
SNAPSHOT_SYNC_CHUNK_MISSING: 503,
|
|
161
|
-
SNAPSHOT_SYNC_END_MISSING: 503,
|
|
162
|
-
SNAPSHOT_SYNC_CHECKSUM_MISMATCH: 503,
|
|
163
|
-
SNAPSHOT_SYNC_PRIMARY_ERROR: 503,
|
|
164
|
-
SNAPSHOT_SYNC_NO_TARGETS: 503,
|
|
165
|
-
SNAPSHOT_SYNC_TRANSPORT_FAILED: 503,
|
|
166
|
-
SNAPSHOT_SYNC_SCHEMA_UNAVAILABLE: 503,
|
|
167
|
-
SNAPSHOT_SYNC_RESTORE_FAILED: 503,
|
|
168
|
-
SNAPSHOT_SYNC_RESTORE_CLEANUP_FAILED: 503,
|
|
169
|
-
SNAPSHOT_SYNC_TIMEOUT: 503,
|
|
170
|
-
SNAPSHOT_SYNC_ALLOCATION_UNAVAILABLE: 503,
|
|
171
|
-
SNAPSHOT_SYNC_NOT_ASSIGNED: 503,
|
|
172
|
-
CONTROLLER_METADATA_INVALID: 500
|
|
173
|
-
};
|
|
174
|
-
function httpStatusForNarsilError(code) {
|
|
175
|
-
return STATUS_BY_CODE[code] ?? 500;
|
|
176
|
-
}
|
|
177
|
-
function serializeNarsilError(err) {
|
|
178
|
-
const hasDetails = err.details && Object.keys(err.details).length > 0;
|
|
179
|
-
return hasDetails ? { code: err.code, message: err.message, details: err.details } : { code: err.code, message: err.message };
|
|
180
|
-
}
|
|
181
|
-
function toHttpError(err) {
|
|
182
|
-
if (err instanceof NarsilError) {
|
|
183
|
-
return { status: httpStatusForNarsilError(err.code), body: serializeNarsilError(err) };
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
status: 500,
|
|
187
|
-
body: { code: ServerErrorCodes.INTERNAL_ERROR, message: "An unexpected error occurred" }
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// src/json-encoding.ts
|
|
192
|
-
function toPlainNumbers(key, value) {
|
|
193
|
-
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
194
|
-
return Array.from(value);
|
|
195
|
-
}
|
|
196
|
-
return value;
|
|
197
|
-
}
|
|
198
|
-
function encodeJson(value) {
|
|
199
|
-
return JSON.stringify(value, toPlainNumbers);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// src/server/response.ts
|
|
203
|
-
var STATUS_TEXT = {
|
|
204
|
-
200: "200 OK",
|
|
205
|
-
201: "201 Created",
|
|
206
|
-
202: "202 Accepted",
|
|
207
|
-
204: "204 No Content",
|
|
208
|
-
400: "400 Bad Request",
|
|
209
|
-
401: "401 Unauthorized",
|
|
210
|
-
403: "403 Forbidden",
|
|
211
|
-
404: "404 Not Found",
|
|
212
|
-
405: "405 Method Not Allowed",
|
|
213
|
-
409: "409 Conflict",
|
|
214
|
-
413: "413 Payload Too Large",
|
|
215
|
-
415: "415 Unsupported Media Type",
|
|
216
|
-
429: "429 Too Many Requests",
|
|
217
|
-
500: "500 Internal Server Error",
|
|
218
|
-
503: "503 Service Unavailable"
|
|
219
|
-
};
|
|
220
|
-
function statusLine(status) {
|
|
221
|
-
return STATUS_TEXT[status] ?? `${status}`;
|
|
222
|
-
}
|
|
223
|
-
function streamBody(res, abort, body, status, contentType) {
|
|
224
|
-
if (abort.aborted) return;
|
|
225
|
-
const totalSize = body.byteLength;
|
|
226
|
-
res.cork(() => {
|
|
227
|
-
res.writeStatus(statusLine(status)).writeHeader("Content-Type", contentType);
|
|
228
|
-
const startOffset = res.getWriteOffset();
|
|
229
|
-
const [, done] = res.tryEnd(body, totalSize);
|
|
230
|
-
if (done) return;
|
|
231
|
-
res.onWritable((offset) => {
|
|
232
|
-
if (abort.aborted) return true;
|
|
233
|
-
const [retryOk] = res.tryEnd(body.subarray(offset - startOffset), totalSize);
|
|
234
|
-
return retryOk;
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
function sendJson(res, data, status = 200, abort) {
|
|
239
|
-
const payload = encodeJson(data);
|
|
240
|
-
if (abort && payload !== void 0 && Buffer.byteLength(payload) >= RESPONSE_STREAM_THRESHOLD_BYTES) {
|
|
241
|
-
streamBody(res, abort, Buffer.from(payload, "utf8"), status, "application/json");
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
res.cork(() => {
|
|
245
|
-
res.writeStatus(statusLine(status)).writeHeader("Content-Type", "application/json").end(payload);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
function sendError(res, status, code, message, details) {
|
|
249
|
-
const body = { error: details ? { code, message, details } : { code, message } };
|
|
250
|
-
const payload = encodeJson(body);
|
|
251
|
-
res.cork(() => {
|
|
252
|
-
res.writeStatus(statusLine(status)).writeHeader("Content-Type", "application/json").end(payload);
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
function sendBinary(res, data, abort, status = 200) {
|
|
256
|
-
streamBody(res, abort, data, status, "application/octet-stream");
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// src/server/handler-utils.ts
|
|
260
|
-
function parseJson(ctx) {
|
|
261
|
-
const raw = ctx.rawBody;
|
|
262
|
-
if (!raw || raw.length === 0) {
|
|
263
|
-
sendError(ctx.res, 400, ServerErrorCodes.EMPTY_BODY, "Request body is empty");
|
|
264
|
-
return void 0;
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
return JSON.parse(raw.toString("utf8"));
|
|
268
|
-
} catch {
|
|
269
|
-
sendError(ctx.res, 400, ServerErrorCodes.INVALID_JSON, "Request body is not valid JSON");
|
|
270
|
-
return void 0;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function parseJsonOptional(ctx) {
|
|
274
|
-
const raw = ctx.rawBody;
|
|
275
|
-
if (!raw || raw.length === 0) return {};
|
|
276
|
-
try {
|
|
277
|
-
return JSON.parse(raw.toString("utf8"));
|
|
278
|
-
} catch {
|
|
279
|
-
sendError(ctx.res, 400, ServerErrorCodes.INVALID_JSON, "Request body is not valid JSON");
|
|
280
|
-
return void 0;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
function respondError(ctx, err) {
|
|
284
|
-
if (ctx.abort.aborted) return;
|
|
285
|
-
const { status, body } = toHttpError(err);
|
|
286
|
-
sendError(ctx.res, status, body.code, body.message, body.details);
|
|
287
|
-
}
|
|
288
|
-
function respondJson(ctx, data, status = 200) {
|
|
289
|
-
if (ctx.abort.aborted) return;
|
|
290
|
-
sendJson(ctx.res, data, status, ctx.abort);
|
|
291
|
-
}
|
|
292
|
-
function badRequest(res, message, details) {
|
|
293
|
-
sendError(res, 400, ServerErrorCodes.INVALID_REQUEST, message, details);
|
|
294
|
-
}
|
|
295
|
-
function rejectInvalid(ctx, failure) {
|
|
296
|
-
if (ctx.abort.aborted) return;
|
|
297
|
-
badRequest(ctx.res, failure.message, failure.details);
|
|
298
|
-
}
|
|
299
|
-
function serializeBatchResult(result) {
|
|
300
|
-
return {
|
|
301
|
-
succeeded: result.succeeded,
|
|
302
|
-
failed: result.failed.map((f) => ({
|
|
303
|
-
docId: f.docId,
|
|
304
|
-
error: f.error instanceof NarsilError ? serializeNarsilError(f.error) : { code: "INTERNAL_ERROR", message: String(f.error) }
|
|
305
|
-
}))
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// src/server/validation.ts
|
|
310
|
-
function isPlainObject(value) {
|
|
311
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
312
|
-
}
|
|
313
|
-
function overWindow(field, value, maxWindow) {
|
|
314
|
-
if (typeof value === "number" && value > maxWindow) {
|
|
315
|
-
return {
|
|
316
|
-
message: `Field "${field}" exceeds the maximum result window of ${maxWindow}`,
|
|
317
|
-
details: { value, limit: maxWindow }
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
function validateQuery(params, maxWindow) {
|
|
323
|
-
if (params.group && typeof params.group === "object" && "reduce" in params.group) {
|
|
324
|
-
return {
|
|
325
|
-
message: 'Custom group reducers are not available over HTTP; use "group.fields" and "group.maxPerGroup" only'
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
const rowBound = overWindow("limit", params.limit, maxWindow) ?? overWindow("offset", params.offset, maxWindow);
|
|
329
|
-
if (rowBound) return rowBound;
|
|
330
|
-
if (params.group && typeof params.group === "object") {
|
|
331
|
-
const grouped = overWindow("group.maxPerGroup", params.group.maxPerGroup, maxWindow) ?? overWindow("group.limit", params.group.limit, maxWindow);
|
|
332
|
-
if (grouped) return grouped;
|
|
333
|
-
}
|
|
334
|
-
if (params.facets && typeof params.facets === "object") {
|
|
335
|
-
for (const [field, facet] of Object.entries(params.facets)) {
|
|
336
|
-
if (facet && typeof facet === "object") {
|
|
337
|
-
const faceted = overWindow(`facets.${field}.limit`, facet.limit, maxWindow);
|
|
338
|
-
if (faceted) return faceted;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
function validateSuggest(params) {
|
|
345
|
-
if (typeof params.prefix !== "string") {
|
|
346
|
-
return { message: 'Field "prefix" is required and must be a string' };
|
|
347
|
-
}
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
function validateDocumentBody(body) {
|
|
351
|
-
if (!isPlainObject(body.document)) {
|
|
352
|
-
return { message: 'Field "document" is required and must be an object' };
|
|
353
|
-
}
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
function validateMultiGet(body, maxFetch) {
|
|
357
|
-
if (!Array.isArray(body.docIds)) {
|
|
358
|
-
return { message: 'Field "docIds" is required and must be an array of strings' };
|
|
359
|
-
}
|
|
360
|
-
if (body.docIds.length > maxFetch) {
|
|
361
|
-
return {
|
|
362
|
-
message: `Field "docIds" exceeds the maximum of ${maxFetch} ids per request`,
|
|
363
|
-
details: { count: body.docIds.length, limit: maxFetch }
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
function validateList(params, maxFetch) {
|
|
369
|
-
if (params.cursor !== void 0 && typeof params.cursor !== "string") {
|
|
370
|
-
return { message: 'Field "cursor" must be a string' };
|
|
371
|
-
}
|
|
372
|
-
if (params.limit !== void 0 && typeof params.limit !== "number") {
|
|
373
|
-
return { message: 'Field "limit" must be a number' };
|
|
374
|
-
}
|
|
375
|
-
if (typeof params.limit === "number" && params.limit > maxFetch) {
|
|
376
|
-
return {
|
|
377
|
-
message: `Field "limit" exceeds the maximum of ${maxFetch} documents per request`,
|
|
378
|
-
details: { value: params.limit, limit: maxFetch }
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
return validateListSort(params.sort);
|
|
382
|
-
}
|
|
383
|
-
function validateListSort(sort) {
|
|
384
|
-
if (sort === void 0) return null;
|
|
385
|
-
if (typeof sort !== "object" || sort === null) {
|
|
386
|
-
return { message: 'Field "sort" must be an object keyed by field name, or a list of fields' };
|
|
387
|
-
}
|
|
388
|
-
const entries = Array.isArray(sort) ? sort.map(
|
|
389
|
-
(entry) => typeof entry === "object" && entry !== null ? [entry.field, entry.direction] : [entry, void 0]
|
|
390
|
-
) : Object.entries(sort);
|
|
391
|
-
if (entries.length > MAX_SORT_FIELDS) {
|
|
392
|
-
return {
|
|
393
|
-
message: `Field "sort" exceeds the maximum of ${MAX_SORT_FIELDS} fields`,
|
|
394
|
-
details: { count: entries.length, limit: MAX_SORT_FIELDS }
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
for (const [field, direction] of entries) {
|
|
398
|
-
if (typeof field !== "string" || field.length === 0) {
|
|
399
|
-
return { message: 'Every entry of "sort" must name a field', details: { value: field } };
|
|
400
|
-
}
|
|
401
|
-
if (direction !== "asc" && direction !== "desc") {
|
|
402
|
-
return { message: `Field "sort.${field}" must be "asc" or "desc"`, details: { value: direction } };
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
function validateBatch(body) {
|
|
408
|
-
const action = body.action ?? "insert";
|
|
409
|
-
if (action === "insert") {
|
|
410
|
-
if (!Array.isArray(body.documents)) return { message: 'Batch insert requires a "documents" array' };
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
if (action === "update") {
|
|
414
|
-
if (!Array.isArray(body.updates)) return { message: 'Batch update requires an "updates" array' };
|
|
415
|
-
return null;
|
|
416
|
-
}
|
|
417
|
-
if (action === "delete") {
|
|
418
|
-
if (!Array.isArray(body.docIds)) return { message: 'Batch delete requires a "docIds" array' };
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
return { message: 'Field "action" must be one of "insert", "update", or "delete"' };
|
|
422
|
-
}
|
|
423
|
-
function validateCreateIndex(body) {
|
|
424
|
-
if (typeof body.name !== "string" || body.name.length === 0) {
|
|
425
|
-
return { message: 'Field "name" is required and must be a non-empty string' };
|
|
426
|
-
}
|
|
427
|
-
if (!isPlainObject(body.config)) {
|
|
428
|
-
return { message: 'Field "config" is required and must be an object' };
|
|
429
|
-
}
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
function validateRebalance(body) {
|
|
433
|
-
const target = body.targetPartitionCount;
|
|
434
|
-
if (typeof target !== "number" || !Number.isInteger(target) || target <= 0) {
|
|
435
|
-
return { message: 'Field "targetPartitionCount" is required and must be a positive integer' };
|
|
436
|
-
}
|
|
437
|
-
return null;
|
|
438
|
-
}
|
|
439
|
-
var TASK_TYPES = ["optimizeVectors", "rebalance", "restore", "import", "rebuildAnalysis"];
|
|
440
|
-
var TASK_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled"];
|
|
441
|
-
function parseCountParam(raw, field, minimum) {
|
|
442
|
-
if (raw === null) return minimum;
|
|
443
|
-
const value = Number(raw);
|
|
444
|
-
if (!Number.isInteger(value) || value < minimum) {
|
|
445
|
-
return { message: `Query parameter "${field}" must be an integer of ${minimum} or more`, details: { value: raw } };
|
|
446
|
-
}
|
|
447
|
-
return value;
|
|
448
|
-
}
|
|
449
|
-
function parseEnumParam(raw, field, allowed) {
|
|
450
|
-
if (raw === null) return void 0;
|
|
451
|
-
const requested = raw.split(",").map((entry) => entry.trim());
|
|
452
|
-
const unknown = requested.filter((entry) => !allowed.includes(entry));
|
|
453
|
-
if (requested.length === 0 || unknown.length > 0) {
|
|
454
|
-
return {
|
|
455
|
-
message: `Query parameter "${field}" accepts only ${allowed.join(", ")}`,
|
|
456
|
-
details: { unknown }
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
return requested;
|
|
460
|
-
}
|
|
461
|
-
function isFailure(value) {
|
|
462
|
-
return isPlainObject(value) && typeof value.message === "string";
|
|
463
|
-
}
|
|
464
|
-
function parseTaskListQuery(params, maxPageSize) {
|
|
465
|
-
const type = parseEnumParam(params.get("type"), "type", TASK_TYPES);
|
|
466
|
-
if (isFailure(type)) return { failure: type };
|
|
467
|
-
const status = parseEnumParam(params.get("status"), "status", TASK_STATUSES);
|
|
468
|
-
if (isFailure(status)) return { failure: status };
|
|
469
|
-
const from = parseCountParam(params.get("from"), "from", 0);
|
|
470
|
-
if (isFailure(from)) return { failure: from };
|
|
471
|
-
const limit = parseCountParam(params.get("limit"), "limit", 1);
|
|
472
|
-
if (isFailure(limit)) return { failure: limit };
|
|
473
|
-
if (params.get("limit") !== null && limit > maxPageSize) {
|
|
474
|
-
return {
|
|
475
|
-
failure: {
|
|
476
|
-
message: `Query parameter "limit" exceeds the maximum page size of ${maxPageSize}`,
|
|
477
|
-
details: { value: limit, limit: maxPageSize }
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
const indexName = params.get("indexName");
|
|
482
|
-
return {
|
|
483
|
-
query: {
|
|
484
|
-
...indexName === null ? {} : { indexName },
|
|
485
|
-
...type === void 0 ? {} : { type },
|
|
486
|
-
...status === void 0 ? {} : { status },
|
|
487
|
-
from,
|
|
488
|
-
...params.get("limit") === null ? {} : { limit }
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
}
|
|
11
|
+
import { MessageChannel } from 'worker_threads';
|
|
492
12
|
|
|
493
13
|
// src/server/handlers/admin.ts
|
|
494
14
|
function createAdminHandlers(deps) {
|
|
@@ -539,7 +59,9 @@ function createAdminHandlers(deps) {
|
|
|
539
59
|
}
|
|
540
60
|
async function memory(ctx) {
|
|
541
61
|
try {
|
|
542
|
-
|
|
62
|
+
const stats = await engine.getMemoryStats();
|
|
63
|
+
const response = { ...stats, requestThreads: deps.requestThreadCount() };
|
|
64
|
+
respondJson(ctx, response);
|
|
543
65
|
} catch (err) {
|
|
544
66
|
respondError(ctx, err);
|
|
545
67
|
}
|
|
@@ -746,7 +268,8 @@ function createClusterHandlers(deps) {
|
|
|
746
268
|
|
|
747
269
|
// src/server/handlers/documents.ts
|
|
748
270
|
function createDocumentHandlers(deps) {
|
|
749
|
-
const { engine
|
|
271
|
+
const { engine } = deps;
|
|
272
|
+
const reads = createDocumentReadHandlers(deps);
|
|
750
273
|
async function insert(ctx) {
|
|
751
274
|
const body = parseJson(ctx);
|
|
752
275
|
if (!body) return;
|
|
@@ -762,25 +285,6 @@ function createDocumentHandlers(deps) {
|
|
|
762
285
|
respondError(ctx, err);
|
|
763
286
|
}
|
|
764
287
|
}
|
|
765
|
-
async function get(ctx) {
|
|
766
|
-
try {
|
|
767
|
-
const document = await engine.get(ctx.params[0], ctx.params[1]);
|
|
768
|
-
if (document === void 0) {
|
|
769
|
-
respondError(ctx, indexDocNotFound(ctx.params[1]));
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
respondJson(ctx, { document });
|
|
773
|
-
} catch (err) {
|
|
774
|
-
respondError(ctx, err);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
async function exists(ctx) {
|
|
778
|
-
try {
|
|
779
|
-
respondJson(ctx, { exists: await engine.has(ctx.params[0], ctx.params[1]) });
|
|
780
|
-
} catch (err) {
|
|
781
|
-
respondError(ctx, err);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
288
|
async function put(ctx) {
|
|
785
289
|
const body = parseJson(ctx);
|
|
786
290
|
if (!body) return;
|
|
@@ -792,10 +296,10 @@ function createDocumentHandlers(deps) {
|
|
|
792
296
|
const [name, id] = ctx.params;
|
|
793
297
|
try {
|
|
794
298
|
if (await engine.has(name, id)) {
|
|
795
|
-
await engine.update(name, id, body.document);
|
|
299
|
+
await engine.update(name, id, body.document, body.options);
|
|
796
300
|
respondJson(ctx, { id, created: false });
|
|
797
301
|
} else {
|
|
798
|
-
const created = await engine.insert(name, body.document, id);
|
|
302
|
+
const created = await engine.insert(name, body.document, id, body.options);
|
|
799
303
|
respondJson(ctx, { id: created, created: true }, 201);
|
|
800
304
|
}
|
|
801
305
|
} catch (err) {
|
|
@@ -811,7 +315,7 @@ function createDocumentHandlers(deps) {
|
|
|
811
315
|
return;
|
|
812
316
|
}
|
|
813
317
|
try {
|
|
814
|
-
await engine.update(ctx.params[0], ctx.params[1], body.document);
|
|
318
|
+
await engine.update(ctx.params[0], ctx.params[1], body.document, body.options);
|
|
815
319
|
respondJson(ctx, { id: ctx.params[1] });
|
|
816
320
|
} catch (err) {
|
|
817
321
|
respondError(ctx, err);
|
|
@@ -819,46 +323,16 @@ function createDocumentHandlers(deps) {
|
|
|
819
323
|
}
|
|
820
324
|
async function remove(ctx) {
|
|
821
325
|
try {
|
|
822
|
-
await engine.remove(ctx.params[0], ctx.params[1]);
|
|
326
|
+
await engine.remove(ctx.params[0], ctx.params[1], { wait: ctx.query.get("wait") === "true" });
|
|
823
327
|
respondJson(ctx, { id: ctx.params[1], removed: true });
|
|
824
328
|
} catch (err) {
|
|
825
329
|
respondError(ctx, err);
|
|
826
330
|
}
|
|
827
331
|
}
|
|
828
|
-
async function
|
|
829
|
-
try {
|
|
830
|
-
respondJson(ctx, { count: await engine.countDocuments(ctx.params[0]) });
|
|
831
|
-
} catch (err) {
|
|
832
|
-
respondError(ctx, err);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
async function multiGet(ctx) {
|
|
836
|
-
const body = parseJson(ctx);
|
|
837
|
-
if (!body) return;
|
|
838
|
-
const failure = validateMultiGet(body, limits.maxFetchDocuments);
|
|
839
|
-
if (failure) {
|
|
840
|
-
rejectInvalid(ctx, failure);
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
332
|
+
async function waitForWrites(ctx) {
|
|
843
333
|
try {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
for (const [id, doc] of found) documents[id] = doc;
|
|
847
|
-
respondJson(ctx, { documents });
|
|
848
|
-
} catch (err) {
|
|
849
|
-
respondError(ctx, err);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
async function list(ctx) {
|
|
853
|
-
const params = parseJson(ctx);
|
|
854
|
-
if (!params) return;
|
|
855
|
-
const failure = validateList(params, limits.maxFetchDocuments);
|
|
856
|
-
if (failure) {
|
|
857
|
-
rejectInvalid(ctx, failure);
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
try {
|
|
861
|
-
respondJson(ctx, await engine.listDocuments(ctx.params[0], params));
|
|
334
|
+
await engine.waitForWrites(ctx.params[0]);
|
|
335
|
+
respondJson(ctx, { ok: true });
|
|
862
336
|
} catch (err) {
|
|
863
337
|
respondError(ctx, err);
|
|
864
338
|
}
|
|
@@ -874,9 +348,12 @@ function createDocumentHandlers(deps) {
|
|
|
874
348
|
const action = body.action ?? "insert";
|
|
875
349
|
try {
|
|
876
350
|
if (action === "update") {
|
|
877
|
-
respondJson(
|
|
351
|
+
respondJson(
|
|
352
|
+
ctx,
|
|
353
|
+
serializeBatchResult(await engine.updateBatch(ctx.params[0], body.updates ?? [], body.options))
|
|
354
|
+
);
|
|
878
355
|
} else if (action === "delete") {
|
|
879
|
-
respondJson(ctx, serializeBatchResult(await engine.removeBatch(ctx.params[0], body.docIds ?? [])));
|
|
356
|
+
respondJson(ctx, serializeBatchResult(await engine.removeBatch(ctx.params[0], body.docIds ?? [], body.options)));
|
|
880
357
|
} else {
|
|
881
358
|
respondJson(
|
|
882
359
|
ctx,
|
|
@@ -887,10 +364,7 @@ function createDocumentHandlers(deps) {
|
|
|
887
364
|
respondError(ctx, err);
|
|
888
365
|
}
|
|
889
366
|
}
|
|
890
|
-
return {
|
|
891
|
-
}
|
|
892
|
-
function indexDocNotFound(docId) {
|
|
893
|
-
return new NarsilError(ErrorCodes.DOC_NOT_FOUND, `Document "${docId}" not found`, { docId });
|
|
367
|
+
return { ...reads, insert, put, patch, remove, batch, waitForWrites };
|
|
894
368
|
}
|
|
895
369
|
|
|
896
370
|
// src/server/handlers/health.ts
|
|
@@ -955,7 +429,7 @@ function* iterateNdjson(buffer, maxLineBytes) {
|
|
|
955
429
|
function yieldToEventLoop() {
|
|
956
430
|
return new Promise((resolve) => setImmediate(resolve));
|
|
957
431
|
}
|
|
958
|
-
function
|
|
432
|
+
function isPlainObject(value) {
|
|
959
433
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
960
434
|
}
|
|
961
435
|
async function runImport(engine, options) {
|
|
@@ -993,7 +467,7 @@ async function runImport(engine, options) {
|
|
|
993
467
|
recordFailure({ line: line.lineNumber, code: ServerErrorCodes.INVALID_JSON, message: "Line is not valid JSON" });
|
|
994
468
|
continue;
|
|
995
469
|
}
|
|
996
|
-
if (!
|
|
470
|
+
if (!isPlainObject(parsed)) {
|
|
997
471
|
recordFailure({
|
|
998
472
|
line: line.lineNumber,
|
|
999
473
|
code: ServerErrorCodes.INVALID_REQUEST,
|
|
@@ -1245,185 +719,237 @@ function createVersionHandler(deps) {
|
|
|
1245
719
|
return { report };
|
|
1246
720
|
}
|
|
1247
721
|
|
|
1248
|
-
// src/server/request.ts
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
722
|
+
// src/server/request-threads/captured-response.ts
|
|
723
|
+
var DEFAULT_STATUS = 200;
|
|
724
|
+
var NO_RESPONSE_STATUS = 500;
|
|
725
|
+
function statusOf(line) {
|
|
726
|
+
const parsed = Number.parseInt(line, 10);
|
|
727
|
+
return Number.isFinite(parsed) ? parsed : DEFAULT_STATUS;
|
|
728
|
+
}
|
|
729
|
+
function bodyBytes(body) {
|
|
730
|
+
if (body === void 0) return new Uint8Array(0);
|
|
731
|
+
if (typeof body === "string") return Buffer.from(body, "utf8");
|
|
732
|
+
return body;
|
|
733
|
+
}
|
|
734
|
+
function createCapturingResponse(remoteAddress) {
|
|
735
|
+
let status = DEFAULT_STATUS;
|
|
736
|
+
const headers = [];
|
|
737
|
+
let finished = false;
|
|
738
|
+
let resolveDone = () => void 0;
|
|
739
|
+
const done = new Promise((resolve) => {
|
|
740
|
+
resolveDone = resolve;
|
|
1255
741
|
});
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
742
|
+
function finish(body) {
|
|
743
|
+
if (finished) return;
|
|
744
|
+
finished = true;
|
|
745
|
+
resolveDone({ status, headers, body });
|
|
746
|
+
}
|
|
747
|
+
const sink = {
|
|
748
|
+
cork(callback) {
|
|
749
|
+
callback();
|
|
750
|
+
return sink;
|
|
751
|
+
},
|
|
752
|
+
writeStatus(line) {
|
|
753
|
+
if (!finished) status = statusOf(line);
|
|
754
|
+
return sink;
|
|
755
|
+
},
|
|
756
|
+
writeHeader(key, value) {
|
|
757
|
+
if (!finished) headers.push([key, value]);
|
|
758
|
+
return sink;
|
|
1259
759
|
},
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
760
|
+
end(body) {
|
|
761
|
+
finish(bodyBytes(body));
|
|
762
|
+
return sink;
|
|
763
|
+
},
|
|
764
|
+
endWithoutBody() {
|
|
765
|
+
finish(new Uint8Array(0));
|
|
766
|
+
return sink;
|
|
767
|
+
},
|
|
768
|
+
tryEnd(body) {
|
|
769
|
+
finish(body);
|
|
770
|
+
return [true, true];
|
|
771
|
+
},
|
|
772
|
+
getWriteOffset: () => 0,
|
|
773
|
+
onWritable: () => sink,
|
|
774
|
+
onAborted: () => sink,
|
|
775
|
+
onData: () => sink,
|
|
776
|
+
getRemoteAddressAsText: () => new TextEncoder().encode(remoteAddress).buffer
|
|
777
|
+
};
|
|
778
|
+
return {
|
|
779
|
+
sink,
|
|
780
|
+
done,
|
|
781
|
+
finishUnanswered() {
|
|
782
|
+
if (finished) return;
|
|
783
|
+
status = NO_RESPONSE_STATUS;
|
|
784
|
+
finish(new Uint8Array(0));
|
|
1263
785
|
}
|
|
1264
786
|
};
|
|
1265
787
|
}
|
|
1266
|
-
function
|
|
1267
|
-
return
|
|
1268
|
-
if (abort.aborted) {
|
|
1269
|
-
reject(new Error("aborted"));
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
let done = false;
|
|
1273
|
-
let total = 0;
|
|
1274
|
-
const chunks = [];
|
|
1275
|
-
abort.onAbort(() => {
|
|
1276
|
-
if (done) return;
|
|
1277
|
-
done = true;
|
|
1278
|
-
reject(new Error("aborted"));
|
|
1279
|
-
});
|
|
1280
|
-
res.onData((chunk, isLast) => {
|
|
1281
|
-
if (done || abort.aborted) return;
|
|
1282
|
-
total += chunk.byteLength;
|
|
1283
|
-
if (total > maxBytes) {
|
|
1284
|
-
done = true;
|
|
1285
|
-
if (!abort.aborted) {
|
|
1286
|
-
sendError(res, 413, ServerErrorCodes.PAYLOAD_TOO_LARGE, "Request body exceeds the configured size limit");
|
|
1287
|
-
}
|
|
1288
|
-
reject(new Error("payload too large"));
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
chunks.push(Buffer.from(new Uint8Array(chunk)));
|
|
1292
|
-
if (isLast) {
|
|
1293
|
-
done = true;
|
|
1294
|
-
resolve(Buffer.concat(chunks));
|
|
1295
|
-
}
|
|
1296
|
-
});
|
|
1297
|
-
});
|
|
788
|
+
function toRelayedResponse(requestId, captured) {
|
|
789
|
+
return { type: "response", requestId, status: captured.status, headers: captured.headers, body: captured.body };
|
|
1298
790
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
791
|
+
|
|
792
|
+
// src/server/request-threads/host.ts
|
|
793
|
+
var NO_ABORT = { aborted: false, onAbort: () => void 0 };
|
|
794
|
+
function isRelayRequest(message) {
|
|
795
|
+
if (typeof message !== "object" || message === null) return false;
|
|
796
|
+
const { type, requestId } = message;
|
|
797
|
+
if (type === "touch") return true;
|
|
798
|
+
return typeof requestId === "number" && (type === "request" || type === "authorize" || type === "embed");
|
|
799
|
+
}
|
|
800
|
+
async function answerRequest(handlers, request) {
|
|
801
|
+
const captured = createCapturingResponse(request.hookContext?.remoteAddress ?? "");
|
|
802
|
+
const ctx = {
|
|
803
|
+
res: captured.sink,
|
|
804
|
+
params: request.params,
|
|
805
|
+
query: new URLSearchParams(request.query),
|
|
806
|
+
contentType: request.contentType,
|
|
807
|
+
rawBody: request.rawBody === null ? null : Buffer.from(request.rawBody),
|
|
808
|
+
abort: NO_ABORT,
|
|
809
|
+
hookContext: request.hookContext
|
|
810
|
+
};
|
|
1317
811
|
try {
|
|
1318
|
-
|
|
812
|
+
await handlers[request.route](ctx);
|
|
1319
813
|
} catch {
|
|
1320
|
-
|
|
814
|
+
sendError(ctx.res, 500, ServerErrorCodes.INTERNAL_ERROR, "An unexpected error occurred");
|
|
1321
815
|
}
|
|
816
|
+
captured.finishUnanswered();
|
|
817
|
+
return toRelayedResponse(request.requestId, await captured.done);
|
|
1322
818
|
}
|
|
1323
|
-
function
|
|
1324
|
-
|
|
819
|
+
async function answerAuthorize(authorize, request) {
|
|
820
|
+
if (authorize === void 0) {
|
|
821
|
+
return { type: "authorized", requestId: request.requestId, denial: null, hookError: false };
|
|
822
|
+
}
|
|
823
|
+
const authorization = await authorize(request.context);
|
|
824
|
+
if (authorization.allowed) {
|
|
825
|
+
return { type: "authorized", requestId: request.requestId, denial: null, hookError: false };
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
type: "authorized",
|
|
829
|
+
requestId: request.requestId,
|
|
830
|
+
denial: authorization.denial,
|
|
831
|
+
hookError: authorization.denial === null
|
|
832
|
+
};
|
|
1325
833
|
}
|
|
1326
|
-
async function
|
|
834
|
+
async function answerEmbed(core, request) {
|
|
1327
835
|
try {
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
836
|
+
const entry = core.requireIndex(request.indexName);
|
|
837
|
+
const resolved = await resolveVectorText(
|
|
838
|
+
{ vector: request.vector },
|
|
839
|
+
entry.embeddingAdapter,
|
|
840
|
+
core.abortController.signal,
|
|
841
|
+
entry.embeddingAdapterName
|
|
842
|
+
);
|
|
843
|
+
const value = resolved.vector?.value;
|
|
844
|
+
return {
|
|
845
|
+
type: "embedded",
|
|
846
|
+
requestId: request.requestId,
|
|
847
|
+
value: value === void 0 ? null : Array.from(value),
|
|
848
|
+
error: null
|
|
849
|
+
};
|
|
850
|
+
} catch (err) {
|
|
851
|
+
const error = err instanceof NarsilError ? serializeNarsilError(err) : { code: "INTERNAL_ERROR", message: err instanceof Error ? err.message : String(err) };
|
|
852
|
+
return { type: "embedded", requestId: request.requestId, value: null, error };
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function touchIntervalFor(copyIdleTimeoutMs) {
|
|
856
|
+
return Math.min(MAX_INDEX_TOUCH_INTERVAL_MS, Math.floor(copyIdleTimeoutMs / INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT));
|
|
857
|
+
}
|
|
858
|
+
async function startRequestThreads(options) {
|
|
859
|
+
const found = acceptorOf(options.app);
|
|
860
|
+
if (found === null) return null;
|
|
861
|
+
const acceptor = found;
|
|
862
|
+
const { core, handlers, authorize } = options;
|
|
863
|
+
const searchHooks = core.pluginRegistry.hasHooks("beforeSearch") || core.pluginRegistry.hasHooks("afterSearch");
|
|
864
|
+
const touchIntervalMs = touchIntervalFor(core.orchestrator.copyIdleTimeoutMs());
|
|
865
|
+
const threads = /* @__PURE__ */ new Map();
|
|
866
|
+
let stopped = false;
|
|
867
|
+
function relayFrom(port) {
|
|
868
|
+
port.on("message", (message) => {
|
|
869
|
+
if (!isRelayRequest(message)) return;
|
|
870
|
+
if (message.type === "touch") {
|
|
871
|
+
core.orchestrator.noteAccess(message.indexName);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const reply = message.type === "request" ? answerRequest(handlers, message) : message.type === "authorize" ? answerAuthorize(authorize, message) : answerEmbed(core, message);
|
|
875
|
+
void reply.then((answer) => port.postMessage(answer));
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
async function stopThread(workerId, thread) {
|
|
879
|
+
try {
|
|
880
|
+
acceptor.removeChildAppDescriptor(thread.descriptor);
|
|
881
|
+
} catch {
|
|
1332
882
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
return false;
|
|
883
|
+
thread.port.close();
|
|
884
|
+
clearGateSlot(options.gate, gateSlotOfWorker(workerId));
|
|
885
|
+
await thread.executor.execute({ type: "stopServing", requestId: `stop-serving-${workerId}` }).catch(() => void 0);
|
|
1337
886
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
req.forEach((key, value) => {
|
|
1364
|
-
headers[key] = value;
|
|
1365
|
-
});
|
|
1366
|
-
hookCtx = {
|
|
1367
|
-
method: req.getMethod(),
|
|
1368
|
-
path: req.getUrl(),
|
|
1369
|
-
headers,
|
|
1370
|
-
remoteAddress: Buffer.from(res.getRemoteAddressAsText()).toString()
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
if (writeCors) writeCors(res, req);
|
|
1374
|
-
const abort = initAbortHandler(res);
|
|
1375
|
-
if (malformed !== null) {
|
|
1376
|
-
sendError(
|
|
1377
|
-
res,
|
|
1378
|
-
400,
|
|
1379
|
-
ServerErrorCodes.INVALID_REQUEST,
|
|
1380
|
-
"A path segment holds a percent-escape the server cannot decode",
|
|
1381
|
-
{ segment: malformed }
|
|
887
|
+
const listener = {
|
|
888
|
+
async onWorkerReady(workerId, executor) {
|
|
889
|
+
if (stopped) return;
|
|
890
|
+
const channel = new MessageChannel();
|
|
891
|
+
relayFrom(channel.port2);
|
|
892
|
+
let result;
|
|
893
|
+
try {
|
|
894
|
+
result = await executor.execute(
|
|
895
|
+
{
|
|
896
|
+
type: "serveRequests",
|
|
897
|
+
requestId: `serve-${workerId}`,
|
|
898
|
+
settings: {
|
|
899
|
+
port: channel.port1,
|
|
900
|
+
gate: options.gate,
|
|
901
|
+
gateSlot: gateSlotOfWorker(workerId),
|
|
902
|
+
maxConcurrentRequests: options.limits.maxConcurrentRequests,
|
|
903
|
+
touchIntervalMs,
|
|
904
|
+
cors: options.cors,
|
|
905
|
+
limits: options.limits,
|
|
906
|
+
build: options.build,
|
|
907
|
+
authorizes: authorize !== void 0,
|
|
908
|
+
searchHooks
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
[channel.port1]
|
|
1382
912
|
);
|
|
913
|
+
} catch (err) {
|
|
914
|
+
channel.port2.close();
|
|
915
|
+
console.warn(`Worker ${workerId} could not start taking requests:`, err);
|
|
1383
916
|
return;
|
|
1384
917
|
}
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
};
|
|
918
|
+
const thread = { descriptor: result.descriptor, port: channel.port2, executor };
|
|
919
|
+
if (stopped) {
|
|
920
|
+
await stopThread(workerId, thread);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
acceptor.addChildAppDescriptor(result.descriptor);
|
|
924
|
+
threads.set(workerId, thread);
|
|
925
|
+
},
|
|
926
|
+
onWorkerGone(workerId) {
|
|
927
|
+
const thread = threads.get(workerId);
|
|
928
|
+
if (thread === void 0) return;
|
|
929
|
+
threads.delete(workerId);
|
|
930
|
+
try {
|
|
931
|
+
acceptor.removeChildAppDescriptor(thread.descriptor);
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
thread.port.close();
|
|
935
|
+
clearGateSlot(options.gate, gateSlotOfWorker(workerId));
|
|
936
|
+
}
|
|
1405
937
|
};
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
938
|
+
await core.orchestrator.serveRequestsOnWorkers(listener);
|
|
939
|
+
return {
|
|
940
|
+
get threadCount() {
|
|
941
|
+
return threads.size;
|
|
942
|
+
},
|
|
943
|
+
async stop() {
|
|
944
|
+
stopped = true;
|
|
945
|
+
core.orchestrator.stopRequestThreads();
|
|
946
|
+
const stopping = [...threads.entries()].map(([workerId, thread]) => {
|
|
947
|
+
threads.delete(workerId);
|
|
948
|
+
return stopThread(workerId, thread);
|
|
949
|
+
});
|
|
950
|
+
await Promise.all(stopping);
|
|
1417
951
|
}
|
|
1418
|
-
|
|
1419
|
-
return resolved;
|
|
1420
|
-
} catch (err) {
|
|
1421
|
-
throw new NarsilError(
|
|
1422
|
-
ErrorCodes.CONFIG_INVALID,
|
|
1423
|
-
"The `uWebSockets.js` package is required to run the Narsil HTTP server. Install it with `pnpm add github:uNetworking/uWebSockets.js#v20.58.0` (or the npm or yarn equivalent).",
|
|
1424
|
-
{ cause: err instanceof Error ? err.message : String(err) }
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
952
|
+
};
|
|
1427
953
|
}
|
|
1428
954
|
|
|
1429
955
|
// src/server/task-store.ts
|
|
@@ -1672,21 +1198,79 @@ function assertSecureBinding(host, options) {
|
|
|
1672
1198
|
{ host }
|
|
1673
1199
|
);
|
|
1674
1200
|
}
|
|
1201
|
+
function mainThreadHandlers(deps) {
|
|
1202
|
+
const idx = createIndexHandlers(deps);
|
|
1203
|
+
const doc = createDocumentHandlers(deps);
|
|
1204
|
+
const search = createSearchHandlers(deps);
|
|
1205
|
+
const admin = createAdminHandlers(deps);
|
|
1206
|
+
const health = createHealthHandlers(deps);
|
|
1207
|
+
const cluster = createClusterHandlers(deps);
|
|
1208
|
+
const version = createVersionHandler(deps);
|
|
1209
|
+
const capabilities = createCapabilitiesHandler();
|
|
1210
|
+
return {
|
|
1211
|
+
livez: health.livez,
|
|
1212
|
+
readyz: health.readyz,
|
|
1213
|
+
version: version.report,
|
|
1214
|
+
capabilities: capabilities.report,
|
|
1215
|
+
clusterTopology: cluster.topology,
|
|
1216
|
+
clusterAllocation: cluster.allocation,
|
|
1217
|
+
memory: admin.memory,
|
|
1218
|
+
listTasks: admin.listTasks,
|
|
1219
|
+
getTask: admin.getTask,
|
|
1220
|
+
cancelTask: admin.cancelTask,
|
|
1221
|
+
createIndex: idx.create,
|
|
1222
|
+
listIndexes: idx.list,
|
|
1223
|
+
dropIndex: idx.drop,
|
|
1224
|
+
indexStats: idx.stats,
|
|
1225
|
+
indexPartitions: idx.partitions,
|
|
1226
|
+
clearIndex: idx.clear,
|
|
1227
|
+
openIndex: idx.open,
|
|
1228
|
+
closeIndex: idx.close,
|
|
1229
|
+
insert: doc.insert,
|
|
1230
|
+
batch: doc.batch,
|
|
1231
|
+
importNdjson: createImportHandler(deps),
|
|
1232
|
+
put: doc.put,
|
|
1233
|
+
patch: doc.patch,
|
|
1234
|
+
remove: doc.remove,
|
|
1235
|
+
waitForWrites: doc.waitForWrites,
|
|
1236
|
+
search: search.search,
|
|
1237
|
+
preflight: search.preflight,
|
|
1238
|
+
suggest: search.suggest,
|
|
1239
|
+
get: doc.get,
|
|
1240
|
+
exists: doc.exists,
|
|
1241
|
+
count: doc.count,
|
|
1242
|
+
list: doc.list,
|
|
1243
|
+
multiGet: doc.multiGet,
|
|
1244
|
+
checkpoint: admin.checkpoint,
|
|
1245
|
+
rebuildAnalysis: admin.rebuildAnalysis,
|
|
1246
|
+
snapshot: admin.snapshot,
|
|
1247
|
+
vectorMaintenance: admin.vectorMaintenance,
|
|
1248
|
+
compact: admin.compact,
|
|
1249
|
+
optimize: admin.optimize,
|
|
1250
|
+
rebalance: admin.rebalance,
|
|
1251
|
+
partitionConfig: admin.partitionConfig,
|
|
1252
|
+
restore: admin.restore
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1675
1255
|
var NarsilHttpServer = class {
|
|
1676
1256
|
host;
|
|
1677
1257
|
port;
|
|
1678
1258
|
options;
|
|
1259
|
+
engine;
|
|
1679
1260
|
deps;
|
|
1680
1261
|
uws = null;
|
|
1681
1262
|
listenSocket = null;
|
|
1682
1263
|
ready = false;
|
|
1264
|
+
threads = null;
|
|
1683
1265
|
constructor(engine, options = {}) {
|
|
1684
1266
|
this.host = options.host ?? "127.0.0.1";
|
|
1685
1267
|
this.port = options.port ?? 9876;
|
|
1686
1268
|
this.options = options;
|
|
1269
|
+
this.engine = engine;
|
|
1687
1270
|
for (const [name, adapter] of Object.entries(options.embeddingAdapters ?? {})) {
|
|
1688
1271
|
engine.registerEmbeddingAdapter(name, adapter);
|
|
1689
1272
|
}
|
|
1273
|
+
engineCoreOf(engine)?.orchestrator.shareMainThread();
|
|
1690
1274
|
const taskStore = options.taskStore ?? new InMemoryTaskStore();
|
|
1691
1275
|
const instanceId = options.instanceId ?? randomUUID();
|
|
1692
1276
|
const limits = resolveLimits(options.limits);
|
|
@@ -1696,7 +1280,8 @@ var NarsilHttpServer = class {
|
|
|
1696
1280
|
limits,
|
|
1697
1281
|
isReady: () => this.ready,
|
|
1698
1282
|
build: resolveBuild(options.build),
|
|
1699
|
-
cluster: options.cluster
|
|
1283
|
+
cluster: options.cluster,
|
|
1284
|
+
requestThreadCount: () => this.requestThreadCount
|
|
1700
1285
|
};
|
|
1701
1286
|
}
|
|
1702
1287
|
async listen() {
|
|
@@ -1704,7 +1289,7 @@ var NarsilHttpServer = class {
|
|
|
1704
1289
|
const uws = await loadUWebSockets();
|
|
1705
1290
|
this.uws = uws;
|
|
1706
1291
|
const app = uws.App();
|
|
1707
|
-
this.registerRoutes(app);
|
|
1292
|
+
await this.registerRoutes(app);
|
|
1708
1293
|
await new Promise((resolve, reject) => {
|
|
1709
1294
|
app.listen(this.host, this.port, (socket) => {
|
|
1710
1295
|
if (socket) {
|
|
@@ -1724,121 +1309,43 @@ var NarsilHttpServer = class {
|
|
|
1724
1309
|
this.uws.us_listen_socket_close(this.listenSocket);
|
|
1725
1310
|
this.listenSocket = null;
|
|
1726
1311
|
}
|
|
1312
|
+
if (this.threads !== null) {
|
|
1313
|
+
const threads = this.threads;
|
|
1314
|
+
this.threads = null;
|
|
1315
|
+
await threads.stop();
|
|
1316
|
+
}
|
|
1727
1317
|
}
|
|
1728
1318
|
get listeningPort() {
|
|
1729
1319
|
if (!this.listenSocket || !this.uws) return -1;
|
|
1730
1320
|
return this.uws.us_socket_local_port(this.listenSocket);
|
|
1731
1321
|
}
|
|
1732
|
-
|
|
1322
|
+
get requestThreadCount() {
|
|
1323
|
+
return this.threads?.threadCount ?? 0;
|
|
1324
|
+
}
|
|
1325
|
+
async registerRoutes(app) {
|
|
1733
1326
|
const cors = resolveCors(this.options.cors);
|
|
1327
|
+
const authorize = this.options.onRequest ? authorizerFor(this.options.onRequest) : void 0;
|
|
1328
|
+
const core = engineCoreOf(this.engine);
|
|
1329
|
+
const threadCount = core === void 0 ? 0 : core.orchestrator.requestThreadCount();
|
|
1330
|
+
const gate = createSharedGateBuffer(gateSlotOfWorker(threadCount));
|
|
1331
|
+
const { limits, build } = this.deps;
|
|
1332
|
+
const handlers = mainThreadHandlers(this.deps);
|
|
1734
1333
|
const run = createRouteRunner({
|
|
1735
|
-
|
|
1736
|
-
|
|
1334
|
+
authorize,
|
|
1335
|
+
gate: createSharedRequestGate(gate, limits.maxConcurrentRequests, MAIN_THREAD_GATE_SLOT),
|
|
1737
1336
|
writeCors: cors ? corsWriter(cors) : void 0
|
|
1738
1337
|
});
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
const cluster = createClusterHandlers(this.deps);
|
|
1746
|
-
const version = createVersionHandler(this.deps);
|
|
1747
|
-
const capabilities = createCapabilitiesHandler();
|
|
1748
|
-
const importNdjson = createImportHandler(this.deps);
|
|
1749
|
-
const json = (handler, opts) => run(handler, opts);
|
|
1750
|
-
if (cors) {
|
|
1751
|
-
app.options("/*", (res, req) => {
|
|
1752
|
-
const origin = req.getHeader("origin");
|
|
1753
|
-
res.cork(() => {
|
|
1754
|
-
res.writeStatus("204 No Content");
|
|
1755
|
-
writeCorsOrigin(res, cors, origin);
|
|
1756
|
-
res.writeHeader("Access-Control-Allow-Methods", cors.methods).writeHeader("Access-Control-Allow-Headers", cors.headers).writeHeader("Access-Control-Max-Age", "86400").endWithoutBody();
|
|
1757
|
-
});
|
|
1758
|
-
});
|
|
1338
|
+
registerServerRoutes(app, run, handlers, limits, cors);
|
|
1339
|
+
if (core === void 0 || !detectRuntime().supportsWorkerThreads) return;
|
|
1340
|
+
try {
|
|
1341
|
+
this.threads = await startRequestThreads({ core, app, handlers, authorize, gate, limits, build, cors });
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
console.warn("The server could not start request threads, so the main thread answers every request:", err);
|
|
1759
1344
|
}
|
|
1760
|
-
app.get("/livez", json(health.livez, { maxBytes: 0, skipHooks: true }));
|
|
1761
|
-
app.get("/readyz", json(health.readyz, { maxBytes: 0, skipHooks: true }));
|
|
1762
|
-
app.get("/health", json(health.livez, { maxBytes: 0, skipHooks: true }));
|
|
1763
|
-
app.get("/version", json(version.report, { maxBytes: 0, skipHooks: true }));
|
|
1764
|
-
app.get("/capabilities", json(capabilities.report, { maxBytes: 0, skipHooks: true }));
|
|
1765
|
-
app.get("/cluster", json(cluster.topology, { maxBytes: 0 }));
|
|
1766
|
-
app.get("/stats/memory", json(admin.memory, { maxBytes: 0 }));
|
|
1767
|
-
app.get("/tasks", json(admin.listTasks, { maxBytes: 0 }));
|
|
1768
|
-
app.get("/tasks/:id", json(admin.getTask, { paramCount: 1, maxBytes: 0 }));
|
|
1769
|
-
app.post("/tasks/:id/_cancel", json(admin.cancelTask, { paramCount: 1, maxBytes: 0 }));
|
|
1770
|
-
app.post("/indexes", json(idx.create, { needsBody: true, maxBytes: maxBodyBytes }));
|
|
1771
|
-
app.get("/indexes", json(idx.list, { maxBytes: 0 }));
|
|
1772
|
-
app.del("/indexes/:name", json(idx.drop, { paramCount: 1, maxBytes: 0 }));
|
|
1773
|
-
app.get("/indexes/:name/stats", json(idx.stats, { paramCount: 1, maxBytes: 0 }));
|
|
1774
|
-
app.get("/indexes/:name/partitions", json(idx.partitions, { paramCount: 1, maxBytes: 0 }));
|
|
1775
|
-
app.get("/indexes/:name/cluster", json(cluster.allocation, { paramCount: 1, maxBytes: 0 }));
|
|
1776
|
-
app.post("/indexes/:name/_clear", json(idx.clear, { paramCount: 1, maxBytes: 0 }));
|
|
1777
|
-
app.post("/indexes/:name/_open", json(idx.open, { paramCount: 1, maxBytes: 0 }));
|
|
1778
|
-
app.post("/indexes/:name/_close", json(idx.close, { paramCount: 1, maxBytes: 0 }));
|
|
1779
|
-
app.get("/indexes/:name/count", json(doc.count, { paramCount: 1, maxBytes: 0 }));
|
|
1780
|
-
app.post("/indexes/:name/documents", json(doc.insert, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes }));
|
|
1781
|
-
app.post(
|
|
1782
|
-
"/indexes/:name/documents/_batch",
|
|
1783
|
-
json(doc.batch, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1784
|
-
);
|
|
1785
|
-
app.post(
|
|
1786
|
-
"/indexes/:name/documents/_list",
|
|
1787
|
-
json(doc.list, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1788
|
-
);
|
|
1789
|
-
app.post(
|
|
1790
|
-
"/indexes/:name/documents/_multi-get",
|
|
1791
|
-
json(doc.multiGet, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1792
|
-
);
|
|
1793
|
-
app.post(
|
|
1794
|
-
"/indexes/:name/documents/_import",
|
|
1795
|
-
json(importNdjson, { paramCount: 1, needsBody: true, maxBytes: maxImportBytes })
|
|
1796
|
-
);
|
|
1797
|
-
app.get("/indexes/:name/documents/:id", json(doc.get, { paramCount: 2, maxBytes: 0 }));
|
|
1798
|
-
app.get("/indexes/:name/documents/:id/_exists", json(doc.exists, { paramCount: 2, maxBytes: 0 }));
|
|
1799
|
-
app.put("/indexes/:name/documents/:id", json(doc.put, { paramCount: 2, needsBody: true, maxBytes: maxBodyBytes }));
|
|
1800
|
-
app.patch(
|
|
1801
|
-
"/indexes/:name/documents/:id",
|
|
1802
|
-
json(doc.patch, { paramCount: 2, needsBody: true, maxBytes: maxBodyBytes })
|
|
1803
|
-
);
|
|
1804
|
-
app.del("/indexes/:name/documents/:id", json(doc.remove, { paramCount: 2, maxBytes: 0 }));
|
|
1805
|
-
app.post("/indexes/:name/search", json(search.search, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes }));
|
|
1806
|
-
app.post(
|
|
1807
|
-
"/indexes/:name/search/preflight",
|
|
1808
|
-
json(search.preflight, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1809
|
-
);
|
|
1810
|
-
app.post("/indexes/:name/suggest", json(search.suggest, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes }));
|
|
1811
|
-
app.post("/indexes/:name/_checkpoint", json(admin.checkpoint, { paramCount: 1, maxBytes: 0 }));
|
|
1812
|
-
app.post("/indexes/:name/_rebuild-analysis", json(admin.rebuildAnalysis, { paramCount: 1, maxBytes: 0 }));
|
|
1813
|
-
app.get("/indexes/:name/snapshot", json(admin.snapshot, { paramCount: 1, maxBytes: 0 }));
|
|
1814
|
-
app.get("/indexes/:name/vector-maintenance", json(admin.vectorMaintenance, { paramCount: 1, maxBytes: 0 }));
|
|
1815
|
-
app.post(
|
|
1816
|
-
"/indexes/:name/vectors/_compact",
|
|
1817
|
-
json(admin.compact, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1818
|
-
);
|
|
1819
|
-
app.post(
|
|
1820
|
-
"/indexes/:name/vectors/_optimize",
|
|
1821
|
-
json(admin.optimize, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1822
|
-
);
|
|
1823
|
-
app.post(
|
|
1824
|
-
"/indexes/:name/_rebalance",
|
|
1825
|
-
json(admin.rebalance, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1826
|
-
);
|
|
1827
|
-
app.post(
|
|
1828
|
-
"/indexes/:name/partition-config",
|
|
1829
|
-
json(admin.partitionConfig, { paramCount: 1, needsBody: true, maxBytes: maxBodyBytes })
|
|
1830
|
-
);
|
|
1831
|
-
app.post(
|
|
1832
|
-
"/indexes/:name/restore",
|
|
1833
|
-
json(admin.restore, { paramCount: 1, needsBody: true, maxBytes: maxImportBytes })
|
|
1834
|
-
);
|
|
1835
|
-
app.any("/*", (res) => {
|
|
1836
|
-
sendError(res, 404, ServerErrorCodes.NOT_FOUND, "Route not found");
|
|
1837
|
-
});
|
|
1838
1345
|
}
|
|
1839
1346
|
};
|
|
1840
1347
|
function createServer(engine, options) {
|
|
1841
1348
|
return new NarsilHttpServer(engine, options);
|
|
1842
1349
|
}
|
|
1843
1350
|
|
|
1844
|
-
export {
|
|
1351
|
+
export { InMemoryTaskStore, createServer };
|