@delali/narsil 0.2.2 → 0.2.3-next.32
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 +12 -9
- package/dist/adapters/filesystem.d.ts +1 -1
- package/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.d.ts +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/adapters/memory.d.ts +1 -1
- package/dist/{adapters-CVqI5NBt.d.ts → adapters-79OIa4xm.d.ts} +1 -1
- package/dist/brute-force-DbHYCTc3.d.ts +8 -0
- package/dist/chunk-BFIH7OV5.mjs +185 -0
- package/dist/chunk-C6VVLMRX.mjs +63 -0
- package/dist/{chunk-UNJZ7ZDB.mjs → chunk-EMI63EXP.mjs} +33 -2
- package/dist/chunk-MFFAJ2NR.mjs +135 -0
- package/dist/chunk-OBXS7OZM.mjs +376 -0
- package/dist/{chunk-ASI6AOW2.mjs → chunk-RO4EFWE6.mjs} +7833 -3524
- package/dist/{chunk-WLR5BFL3.mjs → chunk-TEAJO7CU.mjs} +3067 -2087
- package/dist/{chunk-S7RJJMHI.mjs → chunk-WDL632CN.mjs} +699 -222
- package/dist/{chunk-BKAI2WXI.mjs → chunk-WTONLDU3.mjs} +5 -0
- package/dist/client-DQHB_swQ.d.ts +793 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.mjs +843 -0
- package/dist/{config-DXypXAfN.d.ts → config-ChZ7lfqc.d.ts} +3 -3
- package/dist/direct-executor-YARCDMJF.mjs +8 -0
- package/dist/distribution/coordinator/etcd.d.ts +3 -3
- package/dist/distribution/coordinator/etcd.mjs +471 -2
- package/dist/distribution/coordinator/in-memory.d.ts +3 -3
- package/dist/distribution/coordinator/in-memory.mjs +286 -1
- package/dist/distribution/transport/grpc.d.ts +45 -0
- package/dist/distribution/transport/grpc.mjs +586 -0
- package/dist/distribution/transport/in-memory.d.ts +118 -0
- package/dist/distribution/transport/in-memory.mjs +214 -0
- package/dist/distribution/transport/tcp.d.ts +27 -0
- package/dist/distribution/transport/tcp.mjs +595 -0
- package/dist/distribution.d.ts +241 -192
- package/dist/distribution.mjs +6620 -4795
- package/dist/embeddings/openai.d.ts +1 -1
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-CO9Wnfie.d.ts → engine-DScGlT2Z.d.ts} +30 -3
- package/dist/index.browser.mjs +10035 -3914
- package/dist/index.d.ts +8 -8
- package/dist/index.mjs +8 -5
- package/dist/invalidation/broadcast-channel.d.ts +1 -1
- package/dist/invalidation/filesystem.d.ts +1 -1
- package/dist/invalidation/filesystem.mjs +3 -1
- package/dist/invalidation/noop.d.ts +1 -1
- package/dist/options-BORJg5XB.d.ts +234 -0
- package/dist/persistence/durability/checkpoint-worker.d.ts +4 -2
- package/dist/persistence/durability/checkpoint-worker.mjs +8 -5
- package/dist/react.d.ts +340 -0
- package/dist/react.mjs +687 -0
- package/dist/{schema-CE6S_aAr.d.ts → schema-emTQfEiX.d.ts} +21 -7
- package/dist/{search-DLBtiPdQ.d.ts → search-CQ-9bKxo.d.ts} +226 -14
- package/dist/server.d.ts +42 -356
- package/dist/server.mjs +493 -106
- package/dist/tasks-CnMLpfk-.d.ts +274 -0
- package/dist/types-BQDlIvfS.d.ts +34 -0
- package/dist/{types-BV3nAKTL.d.ts → types-Bfhhnh3G.d.ts} +23 -1
- package/dist/types-D5OwhtsQ.d.ts +97 -0
- package/dist/vector/hnsw-build-worker.d.ts +2 -7
- package/dist/vector/hnsw-build-worker.mjs +2 -1
- package/dist/vector/search-worker.d.ts +373 -0
- package/dist/vector/search-worker.mjs +279 -0
- package/dist/workers/entry.mjs +43 -25
- package/package.json +42 -2
- package/dist/chunk-FKK76OR7.mjs +0 -456
- package/dist/chunk-RQLXC2YS.mjs +0 -269
- package/dist/direct-executor-3W4QRA4E.mjs +0 -5
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import { createContext, useState, useEffect, createElement, useRef, useEffectEvent, useCallback, useMemo, useContext, useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var ErrorCodes = {
|
|
5
|
+
SCHEMA_INVALID_TYPE: "SCHEMA_INVALID_TYPE",
|
|
6
|
+
SCHEMA_MISSING_FIELD: "SCHEMA_MISSING_FIELD",
|
|
7
|
+
SCHEMA_DEPTH_EXCEEDED: "SCHEMA_DEPTH_EXCEEDED",
|
|
8
|
+
SCHEMA_INVALID_VECTOR_DIMENSION: "SCHEMA_INVALID_VECTOR_DIMENSION",
|
|
9
|
+
SCHEMA_INVALID_GEOPOINT: "SCHEMA_INVALID_GEOPOINT",
|
|
10
|
+
DOC_NOT_FOUND: "DOC_NOT_FOUND",
|
|
11
|
+
DOC_ALREADY_EXISTS: "DOC_ALREADY_EXISTS",
|
|
12
|
+
DOC_VALIDATION_FAILED: "DOC_VALIDATION_FAILED",
|
|
13
|
+
INDEX_NOT_FOUND: "INDEX_NOT_FOUND",
|
|
14
|
+
INDEX_ALREADY_EXISTS: "INDEX_ALREADY_EXISTS",
|
|
15
|
+
INDEX_ORPHANED: "INDEX_ORPHANED",
|
|
16
|
+
PARTITION_CORRUPTED: "PARTITION_CORRUPTED",
|
|
17
|
+
PARTITION_REBALANCING_BACKPRESSURE: "PARTITION_REBALANCING_BACKPRESSURE",
|
|
18
|
+
WORKER_CRASHED: "WORKER_CRASHED",
|
|
19
|
+
WORKER_BUSY: "WORKER_BUSY",
|
|
20
|
+
WORKER_TIMEOUT: "WORKER_TIMEOUT",
|
|
21
|
+
PERSISTENCE_SAVE_FAILED: "PERSISTENCE_SAVE_FAILED",
|
|
22
|
+
PERSISTENCE_LOAD_FAILED: "PERSISTENCE_LOAD_FAILED",
|
|
23
|
+
PERSISTENCE_DELETE_FAILED: "PERSISTENCE_DELETE_FAILED",
|
|
24
|
+
PERSISTENCE_CRC_MISMATCH: "PERSISTENCE_CRC_MISMATCH",
|
|
25
|
+
PERSISTENCE_WAL_CORRUPT: "PERSISTENCE_WAL_CORRUPT",
|
|
26
|
+
PERSISTENCE_FSYNC_FAILED: "PERSISTENCE_FSYNC_FAILED",
|
|
27
|
+
SEARCH_INVALID_FIELD: "SEARCH_INVALID_FIELD",
|
|
28
|
+
SEARCH_INVALID_VECTOR_SIZE: "SEARCH_INVALID_VECTOR_SIZE",
|
|
29
|
+
VECTOR_DIMENSION_MISMATCH: "VECTOR_DIMENSION_MISMATCH",
|
|
30
|
+
SEARCH_INVALID_FILTER: "SEARCH_INVALID_FILTER",
|
|
31
|
+
SEARCH_INVALID_MODE: "SEARCH_INVALID_MODE",
|
|
32
|
+
SEARCH_INVALID_CURSOR: "SEARCH_INVALID_CURSOR",
|
|
33
|
+
SEARCH_RESULT_WINDOW_EXCEEDED: "SEARCH_RESULT_WINDOW_EXCEEDED",
|
|
34
|
+
LANGUAGE_NOT_SUPPORTED: "LANGUAGE_NOT_SUPPORTED",
|
|
35
|
+
ENVELOPE_VERSION_MISMATCH: "ENVELOPE_VERSION_MISMATCH",
|
|
36
|
+
ENVELOPE_INVALID_MAGIC: "ENVELOPE_INVALID_MAGIC",
|
|
37
|
+
PARTITION_CAPACITY_EXCEEDED: "PARTITION_CAPACITY_EXCEEDED",
|
|
38
|
+
EMBEDDING_FAILED: "EMBEDDING_FAILED",
|
|
39
|
+
EMBEDDING_DIMENSION_MISMATCH: "EMBEDDING_DIMENSION_MISMATCH",
|
|
40
|
+
EMBEDDING_NO_SOURCE: "EMBEDDING_NO_SOURCE",
|
|
41
|
+
EMBEDDING_CONFIG_INVALID: "EMBEDDING_CONFIG_INVALID",
|
|
42
|
+
DOC_MISSING_REQUIRED_FIELD: "DOC_MISSING_REQUIRED_FIELD",
|
|
43
|
+
CONFIG_INVALID: "CONFIG_INVALID",
|
|
44
|
+
QUERY_ROUTING_FAILED: "QUERY_ROUTING_FAILED",
|
|
45
|
+
QUERY_PARTIAL_FAILURE: "QUERY_PARTIAL_FAILURE",
|
|
46
|
+
QUERY_NODE_TIMEOUT: "QUERY_NODE_TIMEOUT",
|
|
47
|
+
QUERY_NO_ACTIVE_REPLICA: "QUERY_NO_ACTIVE_REPLICA",
|
|
48
|
+
CLUSTER_OPERATION_UNSUPPORTED: "CLUSTER_OPERATION_UNSUPPORTED",
|
|
49
|
+
ALLOCATION_NO_DATA_NODES: "ALLOCATION_NO_DATA_NODES",
|
|
50
|
+
ALLOCATION_INVALID_CONFIG: "ALLOCATION_INVALID_CONFIG",
|
|
51
|
+
ALLOCATION_FAILED: "ALLOCATION_FAILED",
|
|
52
|
+
CONTROLLER_LEASE_LOST: "CONTROLLER_LEASE_LOST",
|
|
53
|
+
CONTROLLER_NOT_ACTIVE: "CONTROLLER_NOT_ACTIVE",
|
|
54
|
+
CONTROLLER_METADATA_INVALID: "CONTROLLER_METADATA_INVALID",
|
|
55
|
+
NODE_BOOTSTRAP_FAILED: "NODE_BOOTSTRAP_FAILED",
|
|
56
|
+
NODE_ALREADY_JOINED: "NODE_ALREADY_JOINED",
|
|
57
|
+
NODE_NOT_JOINED: "NODE_NOT_JOINED",
|
|
58
|
+
COORDINATOR_DEPENDENCY_MISSING: "COORDINATOR_DEPENDENCY_MISSING",
|
|
59
|
+
TRANSPORT_DEPENDENCY_MISSING: "TRANSPORT_DEPENDENCY_MISSING",
|
|
60
|
+
REPLICATION_ENTRY_INVALID: "REPLICATION_ENTRY_INVALID",
|
|
61
|
+
REPLICATION_INSYNC_REMOVAL_FAILED: "REPLICATION_INSYNC_REMOVAL_FAILED",
|
|
62
|
+
REPLICATION_ROLLBACK_FAILED: "REPLICATION_ROLLBACK_FAILED",
|
|
63
|
+
REPLICATION_LOG_FULL: "REPLICATION_LOG_FULL",
|
|
64
|
+
REPLICATION_ENTRY_CORRUPT: "REPLICATION_ENTRY_CORRUPT",
|
|
65
|
+
REPLICATION_SNAPSHOT_CORRUPT: "REPLICATION_SNAPSHOT_CORRUPT",
|
|
66
|
+
REPLICATION_TERM_MISMATCH: "REPLICATION_TERM_MISMATCH",
|
|
67
|
+
REPLICATION_SYNC_FAILED: "REPLICATION_SYNC_FAILED",
|
|
68
|
+
PARTITION_NOT_PRIMARY: "PARTITION_NOT_PRIMARY",
|
|
69
|
+
PARTITION_UNASSIGNED: "PARTITION_UNASSIGNED",
|
|
70
|
+
INSUFFICIENT_REPLICAS: "INSUFFICIENT_REPLICAS",
|
|
71
|
+
SNAPSHOT_SYNC_UNAUTHORIZED: "SNAPSHOT_SYNC_UNAUTHORIZED",
|
|
72
|
+
SNAPSHOT_SYNC_REQUEST_INVALID: "SNAPSHOT_SYNC_REQUEST_INVALID",
|
|
73
|
+
SNAPSHOT_SYNC_INDEX_NOT_FOUND: "SNAPSHOT_SYNC_INDEX_NOT_FOUND",
|
|
74
|
+
SNAPSHOT_SYNC_TOO_LARGE: "SNAPSHOT_SYNC_TOO_LARGE",
|
|
75
|
+
SNAPSHOT_SYNC_CAPACITY_EXHAUSTED: "SNAPSHOT_SYNC_CAPACITY_EXHAUSTED",
|
|
76
|
+
SNAPSHOT_SYNC_SNAPSHOT_FAILED: "SNAPSHOT_SYNC_SNAPSHOT_FAILED",
|
|
77
|
+
SNAPSHOT_SYNC_DECODE_FAILED: "SNAPSHOT_SYNC_DECODE_FAILED",
|
|
78
|
+
SNAPSHOT_SYNC_FRAME_INVALID: "SNAPSHOT_SYNC_FRAME_INVALID",
|
|
79
|
+
SNAPSHOT_SYNC_HEADER_INVALID: "SNAPSHOT_SYNC_HEADER_INVALID",
|
|
80
|
+
SNAPSHOT_SYNC_HEADER_MISMATCH: "SNAPSHOT_SYNC_HEADER_MISMATCH",
|
|
81
|
+
SNAPSHOT_SYNC_CHUNK_OUT_OF_ORDER: "SNAPSHOT_SYNC_CHUNK_OUT_OF_ORDER",
|
|
82
|
+
SNAPSHOT_SYNC_CHUNK_OVERFLOW: "SNAPSHOT_SYNC_CHUNK_OVERFLOW",
|
|
83
|
+
SNAPSHOT_SYNC_CHUNK_SIZE_EXCEEDED: "SNAPSHOT_SYNC_CHUNK_SIZE_EXCEEDED",
|
|
84
|
+
SNAPSHOT_SYNC_CHUNK_MISSING: "SNAPSHOT_SYNC_CHUNK_MISSING",
|
|
85
|
+
SNAPSHOT_SYNC_END_MISSING: "SNAPSHOT_SYNC_END_MISSING",
|
|
86
|
+
SNAPSHOT_SYNC_CHECKSUM_MISMATCH: "SNAPSHOT_SYNC_CHECKSUM_MISMATCH",
|
|
87
|
+
SNAPSHOT_SYNC_PRIMARY_ERROR: "SNAPSHOT_SYNC_PRIMARY_ERROR",
|
|
88
|
+
SNAPSHOT_SYNC_NO_TARGETS: "SNAPSHOT_SYNC_NO_TARGETS",
|
|
89
|
+
SNAPSHOT_SYNC_TRANSPORT_FAILED: "SNAPSHOT_SYNC_TRANSPORT_FAILED",
|
|
90
|
+
SNAPSHOT_SYNC_SCHEMA_UNAVAILABLE: "SNAPSHOT_SYNC_SCHEMA_UNAVAILABLE",
|
|
91
|
+
SNAPSHOT_SYNC_RESTORE_FAILED: "SNAPSHOT_SYNC_RESTORE_FAILED",
|
|
92
|
+
SNAPSHOT_SYNC_RESTORE_CLEANUP_FAILED: "SNAPSHOT_SYNC_RESTORE_CLEANUP_FAILED",
|
|
93
|
+
SNAPSHOT_SYNC_TIMEOUT: "SNAPSHOT_SYNC_TIMEOUT",
|
|
94
|
+
SNAPSHOT_SYNC_ALLOCATION_UNAVAILABLE: "SNAPSHOT_SYNC_ALLOCATION_UNAVAILABLE",
|
|
95
|
+
SNAPSHOT_SYNC_NOT_ASSIGNED: "SNAPSHOT_SYNC_NOT_ASSIGNED",
|
|
96
|
+
SNAPSHOT_SYNC_ABORTED: "SNAPSHOT_SYNC_ABORTED"
|
|
97
|
+
};
|
|
98
|
+
var ServerErrorCodes = {
|
|
99
|
+
INVALID_REQUEST: "INVALID_REQUEST",
|
|
100
|
+
INVALID_JSON: "INVALID_JSON",
|
|
101
|
+
EMPTY_BODY: "EMPTY_BODY",
|
|
102
|
+
PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
|
|
103
|
+
NOT_FOUND: "NOT_FOUND",
|
|
104
|
+
TASK_NOT_FOUND: "TASK_NOT_FOUND",
|
|
105
|
+
TASK_NOT_CANCELLABLE: "TASK_NOT_CANCELLABLE",
|
|
106
|
+
TASK_OWNED_BY_ANOTHER_INSTANCE: "TASK_OWNED_BY_ANOTHER_INSTANCE",
|
|
107
|
+
TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
|
|
108
|
+
HOOK_ERROR: "HOOK_ERROR",
|
|
109
|
+
INTERNAL_ERROR: "INTERNAL_ERROR"
|
|
110
|
+
};
|
|
111
|
+
var ClientErrorCodes = {
|
|
112
|
+
CLIENT_CONNECTION_FAILED: "CLIENT_CONNECTION_FAILED",
|
|
113
|
+
CLIENT_REQUEST_TIMEOUT: "CLIENT_REQUEST_TIMEOUT",
|
|
114
|
+
CLIENT_REQUEST_ABORTED: "CLIENT_REQUEST_ABORTED",
|
|
115
|
+
CLIENT_INVALID_RESPONSE: "CLIENT_INVALID_RESPONSE",
|
|
116
|
+
CLIENT_TASK_TIMEOUT: "CLIENT_TASK_TIMEOUT",
|
|
117
|
+
CLIENT_UNEXPECTED_ERROR: "CLIENT_UNEXPECTED_ERROR"
|
|
118
|
+
};
|
|
119
|
+
var NarsilError = class extends Error {
|
|
120
|
+
/** This says which failure it is. */
|
|
121
|
+
code;
|
|
122
|
+
/** This carries the values behind the failure, such as the field, index, or limit involved. It is empty when the code says everything. */
|
|
123
|
+
details;
|
|
124
|
+
/**
|
|
125
|
+
* Builds an error carrying a code the caller can branch on.
|
|
126
|
+
*
|
|
127
|
+
* @param code - The failure this error reports, from {@link ErrorCodes} for
|
|
128
|
+
* an engine failure, {@link ServerErrorCodes} for one the HTTP layer raised,
|
|
129
|
+
* or {@link ClientErrorCodes} for one that stopped a request from reaching a
|
|
130
|
+
* server.
|
|
131
|
+
* @param message - Plain description, for a log or a person.
|
|
132
|
+
* @param details - Values behind the failure, which reach
|
|
133
|
+
* {@link NarsilError.details}.
|
|
134
|
+
*/
|
|
135
|
+
constructor(code, message, details) {
|
|
136
|
+
super(message);
|
|
137
|
+
this.name = "NarsilError";
|
|
138
|
+
this.code = code;
|
|
139
|
+
this.details = details ?? {};
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/react/failure.ts
|
|
144
|
+
function asNarsilError(err, whatFailed) {
|
|
145
|
+
if (err instanceof NarsilError) return err;
|
|
146
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
+
return new NarsilError(ClientErrorCodes.CLIENT_UNEXPECTED_ERROR, `${whatFailed} failed unexpectedly: ${message}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/react/store.ts
|
|
151
|
+
var DEFAULT_KEEP_ALIVE_MS = 2e3;
|
|
152
|
+
var LOADING_SNAPSHOT = Object.freeze({
|
|
153
|
+
data: void 0,
|
|
154
|
+
error: void 0,
|
|
155
|
+
isLoading: true,
|
|
156
|
+
isFetching: true
|
|
157
|
+
});
|
|
158
|
+
var IDLE_SNAPSHOT = Object.freeze({
|
|
159
|
+
data: void 0,
|
|
160
|
+
error: void 0,
|
|
161
|
+
isLoading: false,
|
|
162
|
+
isFetching: false
|
|
163
|
+
});
|
|
164
|
+
function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
|
|
165
|
+
const entries = /* @__PURE__ */ new Map();
|
|
166
|
+
let holders = 0;
|
|
167
|
+
let closing = null;
|
|
168
|
+
function publish(entry, next) {
|
|
169
|
+
entry.snapshot = next;
|
|
170
|
+
for (const listener of entry.listeners) listener();
|
|
171
|
+
}
|
|
172
|
+
function load(key, entry) {
|
|
173
|
+
if (entry.controller !== null) return;
|
|
174
|
+
const controller = new AbortController();
|
|
175
|
+
entry.controller = controller;
|
|
176
|
+
entry.version++;
|
|
177
|
+
const version = entry.version;
|
|
178
|
+
if (!entry.snapshot.isFetching) publish(entry, { ...entry.snapshot, isFetching: true });
|
|
179
|
+
const current = () => entries.get(key) === entry && entry.version === version;
|
|
180
|
+
entry.loader(controller.signal).then(
|
|
181
|
+
(data) => {
|
|
182
|
+
if (!current()) return;
|
|
183
|
+
entry.controller = null;
|
|
184
|
+
publish(entry, { data, error: void 0, isLoading: false, isFetching: false });
|
|
185
|
+
},
|
|
186
|
+
(err) => {
|
|
187
|
+
if (!current()) return;
|
|
188
|
+
entry.controller = null;
|
|
189
|
+
if (controller.signal.aborted) return;
|
|
190
|
+
publish(entry, {
|
|
191
|
+
data: entry.snapshot.data,
|
|
192
|
+
error: asNarsilError(err, "A Narsil hook"),
|
|
193
|
+
isLoading: false,
|
|
194
|
+
isFetching: false
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
function disposeAll() {
|
|
200
|
+
for (const [key, entry] of [...entries]) drop(key, entry);
|
|
201
|
+
entries.clear();
|
|
202
|
+
}
|
|
203
|
+
function drop(key, entry) {
|
|
204
|
+
if (entries.get(key) === entry) entries.delete(key);
|
|
205
|
+
if (entry.disposal !== null) clearTimeout(entry.disposal);
|
|
206
|
+
entry.disposal = null;
|
|
207
|
+
entry.controller?.abort();
|
|
208
|
+
entry.controller = null;
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
subscribe(key, loader, onChange) {
|
|
212
|
+
let entry = entries.get(key);
|
|
213
|
+
if (entry === void 0) {
|
|
214
|
+
entry = {
|
|
215
|
+
snapshot: LOADING_SNAPSHOT,
|
|
216
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
217
|
+
loader,
|
|
218
|
+
controller: null,
|
|
219
|
+
version: 0,
|
|
220
|
+
disposal: null
|
|
221
|
+
};
|
|
222
|
+
entries.set(key, entry);
|
|
223
|
+
} else {
|
|
224
|
+
entry.loader = loader;
|
|
225
|
+
if (entry.disposal !== null) {
|
|
226
|
+
clearTimeout(entry.disposal);
|
|
227
|
+
entry.disposal = null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const held = entry;
|
|
231
|
+
held.listeners.add(onChange);
|
|
232
|
+
const retryable = held.snapshot.isLoading || held.snapshot.error !== void 0;
|
|
233
|
+
if (retryable && held.controller === null) load(key, held);
|
|
234
|
+
return () => {
|
|
235
|
+
held.listeners.delete(onChange);
|
|
236
|
+
if (held.listeners.size > 0 || entries.get(key) !== held || held.disposal !== null) return;
|
|
237
|
+
held.disposal = setTimeout(() => {
|
|
238
|
+
held.disposal = null;
|
|
239
|
+
if (held.listeners.size === 0) drop(key, held);
|
|
240
|
+
}, keepAliveMs);
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
snapshot(key) {
|
|
244
|
+
return entries.get(key)?.snapshot ?? LOADING_SNAPSHOT;
|
|
245
|
+
},
|
|
246
|
+
refresh(key) {
|
|
247
|
+
const entry = entries.get(key);
|
|
248
|
+
if (entry !== void 0) load(key, entry);
|
|
249
|
+
},
|
|
250
|
+
retain() {
|
|
251
|
+
holders++;
|
|
252
|
+
if (closing !== null) {
|
|
253
|
+
clearTimeout(closing);
|
|
254
|
+
closing = null;
|
|
255
|
+
}
|
|
256
|
+
let released = false;
|
|
257
|
+
return () => {
|
|
258
|
+
if (released) return;
|
|
259
|
+
released = true;
|
|
260
|
+
holders--;
|
|
261
|
+
if (holders > 0) return;
|
|
262
|
+
closing = setTimeout(() => {
|
|
263
|
+
closing = null;
|
|
264
|
+
if (holders === 0) disposeAll();
|
|
265
|
+
}, keepAliveMs);
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
dispose: disposeAll
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/react/context.ts
|
|
273
|
+
var NarsilContext = createContext(null);
|
|
274
|
+
function build(client, keepAliveMs) {
|
|
275
|
+
return { client, store: createResourceStore(keepAliveMs), keepAliveMs };
|
|
276
|
+
}
|
|
277
|
+
function NarsilProvider(props) {
|
|
278
|
+
const { client, keepAliveMs, children } = props;
|
|
279
|
+
const [value, setValue] = useState(() => build(client, keepAliveMs));
|
|
280
|
+
if (value.client !== client || value.keepAliveMs !== keepAliveMs) {
|
|
281
|
+
setValue(build(client, keepAliveMs));
|
|
282
|
+
}
|
|
283
|
+
useEffect(() => value.store.retain(), [value]);
|
|
284
|
+
return createElement(NarsilContext.Provider, { value }, children);
|
|
285
|
+
}
|
|
286
|
+
function useNarsilContext() {
|
|
287
|
+
const value = useContext(NarsilContext);
|
|
288
|
+
if (value === null) {
|
|
289
|
+
throw new NarsilError(
|
|
290
|
+
ErrorCodes.CONFIG_INVALID,
|
|
291
|
+
"A Narsil hook ran outside a NarsilProvider, so it found no client to send through"
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
function useNarsilClient() {
|
|
297
|
+
return useNarsilContext().client;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/react/key.ts
|
|
301
|
+
var MAX_DEPTH = 32;
|
|
302
|
+
function refuse(reason) {
|
|
303
|
+
return new NarsilError(
|
|
304
|
+
ErrorCodes.CONFIG_INVALID,
|
|
305
|
+
`A hook argument ${reason}, so the React bindings cannot tell one request from another`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
function writeList(values, out, depth, seen) {
|
|
309
|
+
out.push("[");
|
|
310
|
+
for (let index = 0; index < values.length; index++) {
|
|
311
|
+
if (index > 0) out.push(",");
|
|
312
|
+
write(values[index], out, depth, seen);
|
|
313
|
+
}
|
|
314
|
+
out.push("]");
|
|
315
|
+
}
|
|
316
|
+
function writeObject(value, out, depth, seen) {
|
|
317
|
+
const record = value;
|
|
318
|
+
const keys = Object.keys(record).sort();
|
|
319
|
+
out.push("{");
|
|
320
|
+
let written = 0;
|
|
321
|
+
for (const key of keys) {
|
|
322
|
+
const held = record[key];
|
|
323
|
+
if (held === void 0) continue;
|
|
324
|
+
if (written > 0) out.push(",");
|
|
325
|
+
out.push(JSON.stringify(key), ":");
|
|
326
|
+
write(held, out, depth, seen);
|
|
327
|
+
written++;
|
|
328
|
+
}
|
|
329
|
+
out.push("}");
|
|
330
|
+
}
|
|
331
|
+
function write(value, out, depth, seen) {
|
|
332
|
+
if (value === null) {
|
|
333
|
+
out.push("null");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
switch (typeof value) {
|
|
337
|
+
case "undefined":
|
|
338
|
+
out.push("undefined");
|
|
339
|
+
return;
|
|
340
|
+
case "string":
|
|
341
|
+
out.push(JSON.stringify(value));
|
|
342
|
+
return;
|
|
343
|
+
case "number":
|
|
344
|
+
case "boolean":
|
|
345
|
+
out.push(String(value));
|
|
346
|
+
return;
|
|
347
|
+
case "bigint":
|
|
348
|
+
out.push(`${value}n`);
|
|
349
|
+
return;
|
|
350
|
+
case "function":
|
|
351
|
+
case "symbol":
|
|
352
|
+
throw refuse(`is a ${typeof value}, which carries no value a request depends on`);
|
|
353
|
+
}
|
|
354
|
+
if (depth >= MAX_DEPTH) throw refuse(`nests deeper than ${MAX_DEPTH} levels`);
|
|
355
|
+
const held = value;
|
|
356
|
+
if (seen.includes(held)) throw refuse("holds a reference back to itself");
|
|
357
|
+
seen.push(held);
|
|
358
|
+
const custom = held.toJSON;
|
|
359
|
+
if (typeof custom === "function") {
|
|
360
|
+
write(custom.call(held), out, depth + 1, seen);
|
|
361
|
+
} else if (Array.isArray(held)) {
|
|
362
|
+
writeList(held, out, depth + 1, seen);
|
|
363
|
+
} else if (ArrayBuffer.isView(held) && !(held instanceof DataView)) {
|
|
364
|
+
writeList(held, out, depth + 1, seen);
|
|
365
|
+
} else {
|
|
366
|
+
writeObject(held, out, depth + 1, seen);
|
|
367
|
+
}
|
|
368
|
+
seen.pop();
|
|
369
|
+
}
|
|
370
|
+
function hashKey(parts) {
|
|
371
|
+
const out = [];
|
|
372
|
+
writeList(parts, out, 0, []);
|
|
373
|
+
return out.join("");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/react/options.ts
|
|
377
|
+
function requestOf(settings) {
|
|
378
|
+
return { headers: settings?.headers, timeoutMs: settings?.timeoutMs };
|
|
379
|
+
}
|
|
380
|
+
var watchers = /* @__PURE__ */ new Set();
|
|
381
|
+
var listening = false;
|
|
382
|
+
function isHidden() {
|
|
383
|
+
return typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
384
|
+
}
|
|
385
|
+
function announce() {
|
|
386
|
+
for (const watcher of [...watchers]) watcher();
|
|
387
|
+
}
|
|
388
|
+
function watchVisibility(watcher) {
|
|
389
|
+
if (typeof document === "undefined") return () => void 0;
|
|
390
|
+
watchers.add(watcher);
|
|
391
|
+
if (!listening) {
|
|
392
|
+
listening = true;
|
|
393
|
+
document.addEventListener("visibilitychange", announce);
|
|
394
|
+
}
|
|
395
|
+
return () => {
|
|
396
|
+
watchers.delete(watcher);
|
|
397
|
+
if (watchers.size > 0) return;
|
|
398
|
+
listening = false;
|
|
399
|
+
document.removeEventListener("visibilitychange", announce);
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function usePolling(action, intervalMs, active) {
|
|
403
|
+
const run = useEffectEvent(action);
|
|
404
|
+
useEffect(() => {
|
|
405
|
+
if (!active || intervalMs <= 0) return;
|
|
406
|
+
let timer;
|
|
407
|
+
const stop = () => {
|
|
408
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
409
|
+
timer = void 0;
|
|
410
|
+
};
|
|
411
|
+
const tick = () => {
|
|
412
|
+
run();
|
|
413
|
+
timer = setTimeout(tick, intervalMs);
|
|
414
|
+
};
|
|
415
|
+
const start = () => {
|
|
416
|
+
stop();
|
|
417
|
+
timer = setTimeout(tick, intervalMs);
|
|
418
|
+
};
|
|
419
|
+
const onVisibilityChange = () => {
|
|
420
|
+
if (isHidden()) {
|
|
421
|
+
stop();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
run();
|
|
425
|
+
start();
|
|
426
|
+
};
|
|
427
|
+
if (!isHidden()) start();
|
|
428
|
+
const unwatch = watchVisibility(onVisibilityChange);
|
|
429
|
+
return () => {
|
|
430
|
+
stop();
|
|
431
|
+
unwatch();
|
|
432
|
+
};
|
|
433
|
+
}, [active, intervalMs]);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/react/read.ts
|
|
437
|
+
var NO_SUBSCRIPTION = () => {
|
|
438
|
+
};
|
|
439
|
+
function useKey(parts) {
|
|
440
|
+
const cached = useRef(null);
|
|
441
|
+
const held = cached.current;
|
|
442
|
+
if (held !== null && held.parts.length === parts.length && parts.every((part, at) => Object.is(part, held.parts[at]))) {
|
|
443
|
+
return held.key;
|
|
444
|
+
}
|
|
445
|
+
const key = hashKey(parts);
|
|
446
|
+
cached.current = { parts, key };
|
|
447
|
+
return key;
|
|
448
|
+
}
|
|
449
|
+
function useRead(parts, run, options) {
|
|
450
|
+
const { client, store } = useNarsilContext();
|
|
451
|
+
const enabled = options?.enabled ?? true;
|
|
452
|
+
const headers = options?.headers;
|
|
453
|
+
const timeoutMs = options?.timeoutMs;
|
|
454
|
+
const key = useKey([...parts, headers, timeoutMs]);
|
|
455
|
+
const call = useRef({ run, request: requestOf(options) });
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
call.current = { run, request: requestOf(options) };
|
|
458
|
+
});
|
|
459
|
+
const subscribe = useCallback(
|
|
460
|
+
(onChange) => {
|
|
461
|
+
if (!enabled) return NO_SUBSCRIPTION;
|
|
462
|
+
const loader = (signal) => {
|
|
463
|
+
const held = call.current;
|
|
464
|
+
return held.run(client, { ...held.request, signal });
|
|
465
|
+
};
|
|
466
|
+
return store.subscribe(key, loader, onChange);
|
|
467
|
+
},
|
|
468
|
+
[enabled, store, client, key]
|
|
469
|
+
);
|
|
470
|
+
const readSnapshot = useCallback(
|
|
471
|
+
() => enabled ? store.snapshot(key) : IDLE_SNAPSHOT,
|
|
472
|
+
[enabled, store, key]
|
|
473
|
+
);
|
|
474
|
+
const readServerSnapshot = useCallback(
|
|
475
|
+
() => enabled ? LOADING_SNAPSHOT : IDLE_SNAPSHOT,
|
|
476
|
+
[enabled]
|
|
477
|
+
);
|
|
478
|
+
const snapshot = useSyncExternalStore(subscribe, readSnapshot, readServerSnapshot);
|
|
479
|
+
const kept = useRef(void 0);
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
if (snapshot.data !== void 0) kept.current = snapshot.data;
|
|
482
|
+
}, [snapshot.data]);
|
|
483
|
+
const refresh = useCallback(() => {
|
|
484
|
+
if (enabled) store.refresh(key);
|
|
485
|
+
}, [enabled, store, key]);
|
|
486
|
+
usePolling(refresh, options?.refreshIntervalMs ?? 0, enabled);
|
|
487
|
+
const keepPrevious = (options?.keepPreviousData ?? false) && snapshot.isLoading;
|
|
488
|
+
const data = keepPrevious && snapshot.data === void 0 ? kept.current : snapshot.data;
|
|
489
|
+
return useMemo(
|
|
490
|
+
() => ({
|
|
491
|
+
data,
|
|
492
|
+
error: snapshot.error,
|
|
493
|
+
isLoading: snapshot.isLoading && data === void 0,
|
|
494
|
+
isFetching: snapshot.isFetching,
|
|
495
|
+
refresh
|
|
496
|
+
}),
|
|
497
|
+
[data, snapshot, refresh]
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/react/documents.ts
|
|
502
|
+
function useDocument(indexName, docId, options) {
|
|
503
|
+
const id = docId ?? "";
|
|
504
|
+
const enabled = (options?.enabled ?? true) && id.length > 0;
|
|
505
|
+
return useRead(["get", indexName, id], (client, request) => client.get(indexName, id, request), {
|
|
506
|
+
...options,
|
|
507
|
+
enabled
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function useDocuments(indexName, params, options) {
|
|
511
|
+
return useRead(
|
|
512
|
+
["listDocuments", indexName, params],
|
|
513
|
+
(client, request) => client.listDocuments(indexName, params, request),
|
|
514
|
+
options
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/react/task-state.ts
|
|
519
|
+
var DEFAULT_TASK_POLL_INTERVAL_MS = 250;
|
|
520
|
+
var ERROR_RETRY_INTERVAL_MS = 5e3;
|
|
521
|
+
function pollInterval(interval, failed) {
|
|
522
|
+
return failed ? Math.max(interval, ERROR_RETRY_INTERVAL_MS) : interval;
|
|
523
|
+
}
|
|
524
|
+
function isTerminalTask(record) {
|
|
525
|
+
if (record === null || record === void 0) return false;
|
|
526
|
+
return record.status === "succeeded" || record.status === "failed" || record.status === "cancelled";
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/react/import.ts
|
|
530
|
+
var NOTHING = { task: void 0, error: void 0, starting: false };
|
|
531
|
+
function useImport(indexName, options) {
|
|
532
|
+
const client = useNarsilClient();
|
|
533
|
+
const [progress, setProgress] = useState(NOTHING);
|
|
534
|
+
const polling = useRef(null);
|
|
535
|
+
const sending = useRef(null);
|
|
536
|
+
const inFlight = useRef(false);
|
|
537
|
+
const reported = useRef(null);
|
|
538
|
+
const request = useRef(requestOf(options));
|
|
539
|
+
const settled = useEffectEvent((record) => options?.onSettled?.(record));
|
|
540
|
+
useEffect(() => {
|
|
541
|
+
request.current = requestOf(options);
|
|
542
|
+
});
|
|
543
|
+
useEffect(() => {
|
|
544
|
+
return () => {
|
|
545
|
+
polling.current?.abort();
|
|
546
|
+
};
|
|
547
|
+
}, []);
|
|
548
|
+
const finished = progress.task !== void 0 && isTerminalTask(progress.task);
|
|
549
|
+
useEffect(() => {
|
|
550
|
+
const record = progress.task;
|
|
551
|
+
if (record === void 0 || !finished || reported.current === record.id) return;
|
|
552
|
+
reported.current = record.id;
|
|
553
|
+
settled(record);
|
|
554
|
+
}, [progress.task, finished]);
|
|
555
|
+
const start = useCallback(
|
|
556
|
+
async (source) => {
|
|
557
|
+
const controller = new AbortController();
|
|
558
|
+
sending.current = controller;
|
|
559
|
+
setProgress({ task: void 0, error: void 0, starting: true });
|
|
560
|
+
try {
|
|
561
|
+
const record = await client.startImport(indexName, source, { ...request.current, signal: controller.signal });
|
|
562
|
+
setProgress({ task: record, error: void 0, starting: false });
|
|
563
|
+
return record;
|
|
564
|
+
} catch (err) {
|
|
565
|
+
const failure = asNarsilError(err, "The import");
|
|
566
|
+
const stopped = controller.signal.aborted;
|
|
567
|
+
setProgress({ task: void 0, error: stopped ? void 0 : failure, starting: false });
|
|
568
|
+
throw failure;
|
|
569
|
+
} finally {
|
|
570
|
+
if (sending.current === controller) sending.current = null;
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
[client, indexName]
|
|
574
|
+
);
|
|
575
|
+
const taskId = progress.task?.id;
|
|
576
|
+
const running = progress.task !== void 0 && !finished;
|
|
577
|
+
const readTask = () => {
|
|
578
|
+
if (taskId === void 0 || inFlight.current) return;
|
|
579
|
+
const controller = new AbortController();
|
|
580
|
+
polling.current = controller;
|
|
581
|
+
inFlight.current = true;
|
|
582
|
+
client.getTask(taskId, { ...request.current, signal: controller.signal }).then((next) => {
|
|
583
|
+
setProgress((prev) => {
|
|
584
|
+
if (prev.task?.id !== taskId) return prev;
|
|
585
|
+
if (next === null) {
|
|
586
|
+
return {
|
|
587
|
+
...prev,
|
|
588
|
+
error: new NarsilError(
|
|
589
|
+
ServerErrorCodes.TASK_NOT_FOUND,
|
|
590
|
+
`The server no longer holds task "${taskId}", so the outcome of the load is unknown`,
|
|
591
|
+
{ taskId }
|
|
592
|
+
)
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
return { task: next, error: void 0, starting: false };
|
|
596
|
+
});
|
|
597
|
+
}).catch((err) => {
|
|
598
|
+
if (controller.signal.aborted) return;
|
|
599
|
+
setProgress((prev) => prev.task?.id === taskId ? { ...prev, error: asNarsilError(err, "The import") } : prev);
|
|
600
|
+
}).finally(() => {
|
|
601
|
+
inFlight.current = false;
|
|
602
|
+
});
|
|
603
|
+
};
|
|
604
|
+
const cancel = useCallback(() => {
|
|
605
|
+
if (progress.starting) {
|
|
606
|
+
sending.current?.abort();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (taskId === void 0 || !running) return;
|
|
610
|
+
client.cancelTask(taskId, request.current).then((next) => {
|
|
611
|
+
setProgress((prev) => prev.task?.id === taskId ? { task: next, error: void 0, starting: false } : prev);
|
|
612
|
+
}).catch((err) => {
|
|
613
|
+
if (err instanceof NarsilError && err.code === ServerErrorCodes.TASK_NOT_CANCELLABLE) return;
|
|
614
|
+
setProgress((prev) => prev.task?.id === taskId ? { ...prev, error: asNarsilError(err, "The import") } : prev);
|
|
615
|
+
});
|
|
616
|
+
}, [client, taskId, running, progress.starting]);
|
|
617
|
+
const reset = useCallback(() => {
|
|
618
|
+
reported.current = null;
|
|
619
|
+
setProgress(NOTHING);
|
|
620
|
+
}, []);
|
|
621
|
+
const interval = pollInterval(options?.pollIntervalMs ?? DEFAULT_TASK_POLL_INTERVAL_MS, progress.error !== void 0);
|
|
622
|
+
usePolling(readTask, interval, running);
|
|
623
|
+
return useMemo(
|
|
624
|
+
() => ({
|
|
625
|
+
start,
|
|
626
|
+
cancel,
|
|
627
|
+
reset,
|
|
628
|
+
task: progress.task,
|
|
629
|
+
progress: progress.task?.progress,
|
|
630
|
+
result: progress.task?.result,
|
|
631
|
+
error: progress.error,
|
|
632
|
+
isImporting: progress.starting || running
|
|
633
|
+
}),
|
|
634
|
+
[start, cancel, reset, progress, running]
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/react/indexes.ts
|
|
639
|
+
function useIndexes(options) {
|
|
640
|
+
return useRead(["listIndexes"], (client, request) => client.listIndexes(request), options);
|
|
641
|
+
}
|
|
642
|
+
function useStats(indexName, options) {
|
|
643
|
+
return useRead(["getStats", indexName], (client, request) => client.getStats(indexName, request), options);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/react/search.ts
|
|
647
|
+
function useQuery(indexName, params, options) {
|
|
648
|
+
return useRead(
|
|
649
|
+
["query", indexName, params],
|
|
650
|
+
(client, request) => client.query(indexName, params, request),
|
|
651
|
+
options
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
function usePreflight(indexName, params, options) {
|
|
655
|
+
return useRead(
|
|
656
|
+
["preflight", indexName, params],
|
|
657
|
+
(client, request) => client.preflight(indexName, params, request),
|
|
658
|
+
options
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
function useSuggest(indexName, params, options) {
|
|
662
|
+
return useRead(
|
|
663
|
+
["suggest", indexName, params],
|
|
664
|
+
(client, request) => client.suggest(indexName, params, request),
|
|
665
|
+
options
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/react/tasks.ts
|
|
670
|
+
function useTask(taskId, options) {
|
|
671
|
+
const id = taskId ?? "";
|
|
672
|
+
const enabled = (options?.enabled ?? true) && id.length > 0;
|
|
673
|
+
const state = useRead(["getTask", id], (client, request) => client.getTask(id, request), {
|
|
674
|
+
...options,
|
|
675
|
+
enabled,
|
|
676
|
+
refreshIntervalMs: 0
|
|
677
|
+
});
|
|
678
|
+
const running = state.data !== null && !isTerminalTask(state.data);
|
|
679
|
+
const interval = pollInterval(options?.pollIntervalMs ?? DEFAULT_TASK_POLL_INTERVAL_MS, state.error !== void 0);
|
|
680
|
+
usePolling(state.refresh, interval, enabled && running);
|
|
681
|
+
return state;
|
|
682
|
+
}
|
|
683
|
+
function useTasks(query, options) {
|
|
684
|
+
return useRead(["listTasks", query], (client, request) => client.listTasks(query, request), options);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export { ClientErrorCodes, ErrorCodes, NarsilError, NarsilProvider, ServerErrorCodes, useDocument, useDocuments, useImport, useIndexes, useNarsilClient, usePreflight, useQuery, useStats, useSuggest, useTask, useTasks };
|