@delali/narsil 0.1.7 → 0.1.8
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 +44 -2
- package/dist/adapters/filesystem.d.ts +1 -1
- package/dist/adapters/filesystem.mjs +46 -2
- 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-BbRkoiu9.d.ts → adapters-Bo2JQg8b.d.ts} +2 -1
- package/dist/chunk-ALB35PRQ.mjs +98 -0
- package/dist/{chunk-KHL4RPC3.mjs → chunk-FS7XLIUS.mjs} +2536 -2284
- package/dist/chunk-GQEHCZG7.mjs +6825 -0
- package/dist/chunk-KMMJMDUA.mjs +44 -0
- package/dist/chunk-RQLXC2YS.mjs +269 -0
- package/dist/chunk-S7RJJMHI.mjs +1086 -0
- package/dist/{chunk-I24SGLFS.mjs → chunk-TW6BXTOL.mjs} +5 -1
- package/dist/chunk-WISGW24C.mjs +455 -0
- package/dist/config-cQLlYR3U.d.ts +470 -0
- package/dist/direct-executor-FX56DYD3.mjs +4 -0
- package/dist/distribution/coordinator/etcd.d.ts +16 -0
- package/dist/distribution/coordinator/etcd.mjs +2 -0
- package/dist/distribution/coordinator/in-memory.d.ts +7 -0
- package/dist/distribution/coordinator/in-memory.mjs +1 -0
- package/dist/distribution.d.ts +122 -0
- package/dist/distribution.mjs +7410 -0
- package/dist/embeddings/openai.d.ts +1 -1
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/index.browser.mjs +11469 -7481
- package/dist/index.d.ts +6 -450
- package/dist/index.mjs +7 -3236
- package/dist/invalidation/broadcast-channel.d.ts +1 -1
- package/dist/invalidation/filesystem.d.ts +1 -1
- package/dist/invalidation/noop.d.ts +1 -1
- package/dist/{language-ClUGLuoC.d.ts → language-ONQ3iVDf.d.ts} +1 -0
- package/dist/languages/arabic.d.ts +1 -1
- package/dist/languages/armenian.d.ts +1 -1
- package/dist/languages/bulgarian.d.ts +1 -1
- package/dist/languages/chinese.d.ts +1 -1
- package/dist/languages/dagbani.d.ts +1 -1
- package/dist/languages/danish.d.ts +1 -1
- package/dist/languages/dutch.d.ts +1 -1
- package/dist/languages/english.d.ts +1 -1
- package/dist/languages/english.mjs +1 -1
- package/dist/languages/ewe.d.ts +1 -1
- package/dist/languages/finnish.d.ts +1 -1
- package/dist/languages/french.d.ts +1 -1
- package/dist/languages/ga.d.ts +1 -1
- package/dist/languages/german.d.ts +1 -1
- package/dist/languages/greek.d.ts +1 -1
- package/dist/languages/hausa.d.ts +1 -1
- package/dist/languages/hindi.d.ts +1 -1
- package/dist/languages/hungarian.d.ts +1 -1
- package/dist/languages/igbo.d.ts +1 -1
- package/dist/languages/indonesian.d.ts +1 -1
- package/dist/languages/irish.d.ts +1 -1
- package/dist/languages/italian.d.ts +1 -1
- package/dist/languages/japanese.d.ts +1 -1
- package/dist/languages/nepali.d.ts +1 -1
- package/dist/languages/norwegian.d.ts +1 -1
- package/dist/languages/portuguese.d.ts +1 -1
- package/dist/languages/romanian.d.ts +1 -1
- package/dist/languages/russian.d.ts +1 -1
- package/dist/languages/sanskrit.d.ts +1 -1
- package/dist/languages/serbian.d.ts +1 -1
- package/dist/languages/slovenian.d.ts +1 -1
- package/dist/languages/spanish.d.ts +1 -1
- package/dist/languages/swahili.d.ts +1 -1
- package/dist/languages/swedish.d.ts +1 -1
- package/dist/languages/tamil.d.ts +1 -1
- package/dist/languages/turkish.d.ts +1 -1
- package/dist/languages/twi.d.ts +1 -1
- package/dist/languages/ukrainian.d.ts +1 -1
- package/dist/languages/yoruba.d.ts +1 -1
- package/dist/languages/zulu.d.ts +1 -1
- package/dist/narsil-ChTdkwPu.d.ts +68 -0
- package/dist/persistence/durability/checkpoint-worker.d.ts +41 -0
- package/dist/persistence/durability/checkpoint-worker.mjs +59 -0
- package/dist/schema-BVaw7nqz.d.ts +58 -0
- package/dist/serialization/crc32-worker.d.ts +20 -0
- package/dist/serialization/crc32-worker.mjs +61 -0
- package/dist/server.d.ts +218 -0
- package/dist/server.mjs +1185 -0
- package/dist/types-Bt7c4hDF.d.ts +73 -0
- package/dist/vector/hnsw-build-worker.mjs +1 -1
- package/dist/workers/entry.mjs +1 -1
- package/package.json +37 -2
- package/dist/chunk-R2C2EQGO.mjs +0 -53
- package/dist/chunk-VVORJOXR.mjs +0 -759
- package/dist/direct-executor-LBJLGMJ2.mjs +0 -4
|
@@ -328,7 +328,11 @@ var stopWords = /* @__PURE__ */ new Set([
|
|
|
328
328
|
var english = {
|
|
329
329
|
name: "english",
|
|
330
330
|
stemmer: stemWord,
|
|
331
|
-
stopWords
|
|
331
|
+
stopWords,
|
|
332
|
+
tokenizer: {
|
|
333
|
+
splitPattern: /[^\p{L}\p{N}_']+/u,
|
|
334
|
+
stripPossessive: true
|
|
335
|
+
}
|
|
332
336
|
};
|
|
333
337
|
|
|
334
338
|
export { english };
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { NarsilError, ErrorCodes } from './chunk-ALB35PRQ.mjs';
|
|
2
|
+
import { decode, encode } from '@msgpack/msgpack';
|
|
3
|
+
|
|
4
|
+
// src/distribution/coordinator/etcd/leases.ts
|
|
5
|
+
var LeaseManager = class {
|
|
6
|
+
leases = /* @__PURE__ */ new Map();
|
|
7
|
+
track(key, lease, nodeId) {
|
|
8
|
+
this.leases.set(key, { lease, nodeId, key });
|
|
9
|
+
}
|
|
10
|
+
get(key) {
|
|
11
|
+
return this.leases.get(key);
|
|
12
|
+
}
|
|
13
|
+
getByNodeId(key, nodeId) {
|
|
14
|
+
const entry = this.leases.get(key);
|
|
15
|
+
if (entry === void 0 || entry.nodeId !== nodeId) {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
return entry;
|
|
19
|
+
}
|
|
20
|
+
remove(key) {
|
|
21
|
+
const entry = this.leases.get(key);
|
|
22
|
+
if (entry !== void 0) {
|
|
23
|
+
this.leases.delete(key);
|
|
24
|
+
}
|
|
25
|
+
return entry;
|
|
26
|
+
}
|
|
27
|
+
async revokeAll() {
|
|
28
|
+
const revokePromises = [];
|
|
29
|
+
for (const [, entry] of this.leases) {
|
|
30
|
+
revokePromises.push(
|
|
31
|
+
entry.lease.revoke().catch(() => {
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
this.leases.clear();
|
|
36
|
+
await Promise.all(revokePromises);
|
|
37
|
+
}
|
|
38
|
+
entries() {
|
|
39
|
+
return this.leases.entries();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/distribution/coordinator/etcd/loader.ts
|
|
44
|
+
async function loadEtcd3Module() {
|
|
45
|
+
try {
|
|
46
|
+
return await import('etcd3');
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new NarsilError(
|
|
49
|
+
ErrorCodes.COORDINATOR_DEPENDENCY_MISSING,
|
|
50
|
+
"The `etcd3` package is not installed. Install it with `pnpm add etcd3` (or the npm or yarn equivalent) to use createEtcdCoordinator.",
|
|
51
|
+
{ cause: err instanceof Error ? err.message : String(err) }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function serializeAllocationTable(table) {
|
|
56
|
+
const serializable = {
|
|
57
|
+
indexName: table.indexName,
|
|
58
|
+
version: table.version,
|
|
59
|
+
replicationFactor: table.replicationFactor,
|
|
60
|
+
assignments: Array.from(table.assignments.entries())
|
|
61
|
+
};
|
|
62
|
+
return new Uint8Array(encode(serializable));
|
|
63
|
+
}
|
|
64
|
+
function deserializeAllocationTable(data) {
|
|
65
|
+
const raw = decode(data);
|
|
66
|
+
if (typeof raw.indexName !== "string") {
|
|
67
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "AllocationTable missing or invalid indexName field");
|
|
68
|
+
}
|
|
69
|
+
if (typeof raw.version !== "number") {
|
|
70
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "AllocationTable missing or invalid version field");
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(raw.assignments)) {
|
|
73
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "AllocationTable missing or invalid assignments field");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
indexName: raw.indexName,
|
|
77
|
+
version: raw.version,
|
|
78
|
+
replicationFactor: typeof raw.replicationFactor === "number" ? raw.replicationFactor : 0,
|
|
79
|
+
assignments: new Map(raw.assignments)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function serializeNodeRegistration(reg) {
|
|
83
|
+
return new Uint8Array(encode(reg));
|
|
84
|
+
}
|
|
85
|
+
function deserializeNodeRegistration(data) {
|
|
86
|
+
const raw = decode(data);
|
|
87
|
+
if (typeof raw.nodeId !== "string") {
|
|
88
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "NodeRegistration missing or invalid nodeId field");
|
|
89
|
+
}
|
|
90
|
+
if (typeof raw.address !== "string") {
|
|
91
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "NodeRegistration missing or invalid address field");
|
|
92
|
+
}
|
|
93
|
+
return raw;
|
|
94
|
+
}
|
|
95
|
+
function serializeSchema(schema) {
|
|
96
|
+
return Buffer.from(new Uint8Array(encode(schema)));
|
|
97
|
+
}
|
|
98
|
+
function deserializeSchema(data) {
|
|
99
|
+
return decode(data);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/distribution/coordinator/etcd/types.ts
|
|
103
|
+
var DEFAULT_ETCD_CONFIG = {
|
|
104
|
+
endpoints: ["http://localhost:2379"],
|
|
105
|
+
keyPrefix: "_narsil",
|
|
106
|
+
nodeHeartbeatTtlSeconds: 30,
|
|
107
|
+
leaseTtlSeconds: 15
|
|
108
|
+
};
|
|
109
|
+
var ETCD_KEY_NODES = "nodes";
|
|
110
|
+
var ETCD_KEY_ALLOCATION = "allocation";
|
|
111
|
+
var ETCD_KEY_PARTITION = "partition";
|
|
112
|
+
var ETCD_KEY_SCHEMA = "schema";
|
|
113
|
+
function buildKey(prefix, ...segments) {
|
|
114
|
+
return [prefix, ...segments].join("/");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/distribution/coordinator/etcd/validation.ts
|
|
118
|
+
var MAX_NODE_ID_LENGTH = 255;
|
|
119
|
+
var VALID_PARTITION_STATES = /* @__PURE__ */ new Set(["UNASSIGNED", "INITIALISING", "ACTIVE", "MIGRATING", "DECOMMISSIONING"]);
|
|
120
|
+
var MAX_WATCHERS = 64;
|
|
121
|
+
function validateNodeId(nodeId) {
|
|
122
|
+
if (nodeId.length === 0) {
|
|
123
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "Node ID must not be empty");
|
|
124
|
+
}
|
|
125
|
+
if (nodeId.length > MAX_NODE_ID_LENGTH) {
|
|
126
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Node ID exceeds the ${MAX_NODE_ID_LENGTH} character limit`, {
|
|
127
|
+
nodeId,
|
|
128
|
+
length: nodeId.length
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (nodeId.includes("/") || nodeId.includes("\\") || nodeId.includes("..") || nodeId.includes("\0")) {
|
|
132
|
+
throw new NarsilError(
|
|
133
|
+
ErrorCodes.CONFIG_INVALID,
|
|
134
|
+
"Node ID contains forbidden characters (/, \\, .., or null bytes)",
|
|
135
|
+
{
|
|
136
|
+
nodeId
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function validatePartitionState(value) {
|
|
142
|
+
if (!VALID_PARTITION_STATES.has(value)) {
|
|
143
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Unknown partition state '${value}'`, {
|
|
144
|
+
state: value,
|
|
145
|
+
validStates: Array.from(VALID_PARTITION_STATES)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/distribution/coordinator/etcd/index.ts
|
|
152
|
+
async function createEtcdCoordinator(config) {
|
|
153
|
+
const resolvedConfig = {
|
|
154
|
+
...DEFAULT_ETCD_CONFIG,
|
|
155
|
+
...config
|
|
156
|
+
};
|
|
157
|
+
const etcd3Module = await loadEtcd3Module();
|
|
158
|
+
const client = new etcd3Module.Etcd3({ hosts: resolvedConfig.endpoints });
|
|
159
|
+
const leaseManager = new LeaseManager();
|
|
160
|
+
const watchers = /* @__PURE__ */ new Set();
|
|
161
|
+
let isShutdown = false;
|
|
162
|
+
function assertNotShutdown() {
|
|
163
|
+
if (isShutdown) {
|
|
164
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "Coordinator has been shut down");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function nodeKey(nodeId) {
|
|
168
|
+
return buildKey(resolvedConfig.keyPrefix, ETCD_KEY_NODES, nodeId);
|
|
169
|
+
}
|
|
170
|
+
function allocationKey(indexName) {
|
|
171
|
+
return buildKey(resolvedConfig.keyPrefix, ETCD_KEY_ALLOCATION, indexName);
|
|
172
|
+
}
|
|
173
|
+
function partitionKey(indexName, partitionId) {
|
|
174
|
+
return buildKey(resolvedConfig.keyPrefix, ETCD_KEY_PARTITION, indexName, String(partitionId));
|
|
175
|
+
}
|
|
176
|
+
function schemaKey(indexName) {
|
|
177
|
+
return buildKey(resolvedConfig.keyPrefix, ETCD_KEY_SCHEMA, indexName);
|
|
178
|
+
}
|
|
179
|
+
function leaseKey(key) {
|
|
180
|
+
return buildKey(resolvedConfig.keyPrefix, "lease", key);
|
|
181
|
+
}
|
|
182
|
+
function genericKey(key) {
|
|
183
|
+
return buildKey(resolvedConfig.keyPrefix, "kv", key);
|
|
184
|
+
}
|
|
185
|
+
function extractSuffix(fullKey, prefix) {
|
|
186
|
+
return fullKey.slice(prefix.length + 1);
|
|
187
|
+
}
|
|
188
|
+
function addWatcher(watcher) {
|
|
189
|
+
if (watchers.size >= MAX_WATCHERS) {
|
|
190
|
+
watcher.cancel().catch(() => {
|
|
191
|
+
});
|
|
192
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Maximum watcher limit (${MAX_WATCHERS}) reached`, {
|
|
193
|
+
currentCount: watchers.size
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
watchers.add(watcher);
|
|
197
|
+
}
|
|
198
|
+
function removeWatcher(watcher) {
|
|
199
|
+
watcher.cancel().catch(() => {
|
|
200
|
+
});
|
|
201
|
+
watchers.delete(watcher);
|
|
202
|
+
}
|
|
203
|
+
const coordinator = {
|
|
204
|
+
async registerNode(registration) {
|
|
205
|
+
assertNotShutdown();
|
|
206
|
+
validateNodeId(registration.nodeId);
|
|
207
|
+
const ttl = resolvedConfig.nodeHeartbeatTtlSeconds;
|
|
208
|
+
const lease = client.lease(ttl);
|
|
209
|
+
await lease.grant();
|
|
210
|
+
const key = nodeKey(registration.nodeId);
|
|
211
|
+
const data = Buffer.from(serializeNodeRegistration(registration));
|
|
212
|
+
await lease.put(key).value(data).exec();
|
|
213
|
+
const existing = leaseManager.get(key);
|
|
214
|
+
if (existing !== void 0) {
|
|
215
|
+
await existing.lease.revoke().catch(() => {
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
leaseManager.track(key, lease, registration.nodeId);
|
|
219
|
+
},
|
|
220
|
+
async deregisterNode(nodeId) {
|
|
221
|
+
assertNotShutdown();
|
|
222
|
+
validateNodeId(nodeId);
|
|
223
|
+
const key = nodeKey(nodeId);
|
|
224
|
+
const entry = leaseManager.remove(key);
|
|
225
|
+
if (entry !== void 0) {
|
|
226
|
+
await entry.lease.revoke().catch(() => {
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
await client.delete().key(key).exec();
|
|
230
|
+
},
|
|
231
|
+
async listNodes() {
|
|
232
|
+
assertNotShutdown();
|
|
233
|
+
const prefix = buildKey(resolvedConfig.keyPrefix, ETCD_KEY_NODES);
|
|
234
|
+
const result = await client.getAll().prefix(prefix).buffers();
|
|
235
|
+
const nodes = [];
|
|
236
|
+
for (const key of Object.keys(result)) {
|
|
237
|
+
const buf = result[key];
|
|
238
|
+
if (buf !== void 0) {
|
|
239
|
+
nodes.push(deserializeNodeRegistration(buf));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return nodes;
|
|
243
|
+
},
|
|
244
|
+
async watchNodes(handler) {
|
|
245
|
+
assertNotShutdown();
|
|
246
|
+
const prefix = buildKey(resolvedConfig.keyPrefix, ETCD_KEY_NODES);
|
|
247
|
+
const watcher = await client.watch().prefix(prefix).create();
|
|
248
|
+
addWatcher(watcher);
|
|
249
|
+
watcher.on("put", (kv) => {
|
|
250
|
+
try {
|
|
251
|
+
const registration = deserializeNodeRegistration(kv.value);
|
|
252
|
+
handler({ type: "node_joined", nodeId: registration.nodeId, registration });
|
|
253
|
+
} catch (_) {
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
watcher.on("delete", (kv) => {
|
|
257
|
+
const fullKey = kv.key.toString();
|
|
258
|
+
const nodeId = extractSuffix(fullKey, prefix);
|
|
259
|
+
handler({ type: "node_left", nodeId, registration: null });
|
|
260
|
+
});
|
|
261
|
+
return () => removeWatcher(watcher);
|
|
262
|
+
},
|
|
263
|
+
async getAllocation(indexName) {
|
|
264
|
+
assertNotShutdown();
|
|
265
|
+
const key = allocationKey(indexName);
|
|
266
|
+
const buf = await client.get(key).buffer();
|
|
267
|
+
if (buf === null) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
return deserializeAllocationTable(buf);
|
|
271
|
+
},
|
|
272
|
+
async putAllocation(indexName, table, expectedVersion) {
|
|
273
|
+
assertNotShutdown();
|
|
274
|
+
const key = allocationKey(indexName);
|
|
275
|
+
const data = Buffer.from(serializeAllocationTable(table));
|
|
276
|
+
if (expectedVersion === void 0) {
|
|
277
|
+
await client.put(key).value(data).exec();
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
if (expectedVersion === null) {
|
|
281
|
+
const txn2 = await client.if(key, "Create", "==", 0).then(client.put(key).value(data)).commit();
|
|
282
|
+
return txn2.succeeded;
|
|
283
|
+
}
|
|
284
|
+
const response = await client.get(key).exec();
|
|
285
|
+
if (response.kvs.length === 0) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
const kv = response.kvs[0];
|
|
289
|
+
const currentTable = deserializeAllocationTable(Buffer.from(kv.value));
|
|
290
|
+
if (currentTable.version !== expectedVersion) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const txn = await client.if(key, "Mod", "==", kv.mod_revision).then(client.put(key).value(data)).commit();
|
|
294
|
+
return txn.succeeded;
|
|
295
|
+
},
|
|
296
|
+
async watchAllocation(handler) {
|
|
297
|
+
assertNotShutdown();
|
|
298
|
+
const prefix = buildKey(resolvedConfig.keyPrefix, ETCD_KEY_ALLOCATION);
|
|
299
|
+
const watcher = await client.watch().prefix(prefix).create();
|
|
300
|
+
addWatcher(watcher);
|
|
301
|
+
watcher.on("put", (kv) => {
|
|
302
|
+
try {
|
|
303
|
+
const table = deserializeAllocationTable(kv.value);
|
|
304
|
+
handler({ indexName: table.indexName, table });
|
|
305
|
+
} catch (_) {
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
return () => removeWatcher(watcher);
|
|
309
|
+
},
|
|
310
|
+
async getPartitionState(indexName, partitionId) {
|
|
311
|
+
assertNotShutdown();
|
|
312
|
+
const key = partitionKey(indexName, partitionId);
|
|
313
|
+
const value = await client.get(key).string();
|
|
314
|
+
if (value === null) {
|
|
315
|
+
return "UNASSIGNED";
|
|
316
|
+
}
|
|
317
|
+
return validatePartitionState(value);
|
|
318
|
+
},
|
|
319
|
+
async putPartitionState(indexName, partitionId, state) {
|
|
320
|
+
assertNotShutdown();
|
|
321
|
+
const key = partitionKey(indexName, partitionId);
|
|
322
|
+
await client.put(key).value(state).exec();
|
|
323
|
+
},
|
|
324
|
+
async acquireLease(key, nodeId, ttlMs) {
|
|
325
|
+
assertNotShutdown();
|
|
326
|
+
const etcdKey = leaseKey(key);
|
|
327
|
+
const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1e3));
|
|
328
|
+
const existing = leaseManager.get(etcdKey);
|
|
329
|
+
if (existing !== void 0 && existing.nodeId === nodeId) {
|
|
330
|
+
try {
|
|
331
|
+
await existing.lease.keepaliveOnce();
|
|
332
|
+
return true;
|
|
333
|
+
} catch (_) {
|
|
334
|
+
leaseManager.remove(etcdKey);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const lease = client.lease(ttlSeconds);
|
|
338
|
+
await lease.grant();
|
|
339
|
+
const txn = await client.if(etcdKey, "Create", "==", 0).then(lease.put(etcdKey).value(nodeId)).commit();
|
|
340
|
+
if (!txn.succeeded) {
|
|
341
|
+
await lease.revoke().catch(() => {
|
|
342
|
+
});
|
|
343
|
+
const currentValue = await client.get(etcdKey).string();
|
|
344
|
+
return currentValue === nodeId;
|
|
345
|
+
}
|
|
346
|
+
leaseManager.track(etcdKey, lease, nodeId);
|
|
347
|
+
return true;
|
|
348
|
+
},
|
|
349
|
+
async renewLease(key, nodeId, _ttlMs) {
|
|
350
|
+
assertNotShutdown();
|
|
351
|
+
const etcdKey = leaseKey(key);
|
|
352
|
+
const entry = leaseManager.getByNodeId(etcdKey, nodeId);
|
|
353
|
+
if (entry === void 0) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
await entry.lease.keepaliveOnce();
|
|
358
|
+
return true;
|
|
359
|
+
} catch (_) {
|
|
360
|
+
leaseManager.remove(etcdKey);
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
async releaseLease(key) {
|
|
365
|
+
assertNotShutdown();
|
|
366
|
+
const etcdKey = leaseKey(key);
|
|
367
|
+
const entry = leaseManager.remove(etcdKey);
|
|
368
|
+
if (entry !== void 0) {
|
|
369
|
+
await entry.lease.revoke().catch(() => {
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
await client.delete().key(etcdKey).exec();
|
|
373
|
+
},
|
|
374
|
+
async get(key) {
|
|
375
|
+
assertNotShutdown();
|
|
376
|
+
const etcdKey = genericKey(key);
|
|
377
|
+
const buf = await client.get(etcdKey).buffer();
|
|
378
|
+
if (buf === null) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
return new Uint8Array(buf);
|
|
382
|
+
},
|
|
383
|
+
async compareAndSet(key, expected, value) {
|
|
384
|
+
assertNotShutdown();
|
|
385
|
+
const etcdKey = genericKey(key);
|
|
386
|
+
const newValue = Buffer.from(value);
|
|
387
|
+
if (expected === null) {
|
|
388
|
+
const txn2 = await client.if(etcdKey, "Create", "==", 0).then(client.put(etcdKey).value(newValue)).commit();
|
|
389
|
+
return txn2.succeeded;
|
|
390
|
+
}
|
|
391
|
+
const expectedBuf = Buffer.from(expected);
|
|
392
|
+
const txn = await client.if(etcdKey, "Value", "==", expectedBuf).then(client.put(etcdKey).value(newValue)).commit();
|
|
393
|
+
return txn.succeeded;
|
|
394
|
+
},
|
|
395
|
+
async getSchema(indexName) {
|
|
396
|
+
assertNotShutdown();
|
|
397
|
+
const key = schemaKey(indexName);
|
|
398
|
+
const buf = await client.get(key).buffer();
|
|
399
|
+
if (buf === null) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
return deserializeSchema(buf);
|
|
403
|
+
},
|
|
404
|
+
async putSchema(indexName, schema) {
|
|
405
|
+
assertNotShutdown();
|
|
406
|
+
const key = schemaKey(indexName);
|
|
407
|
+
await client.put(key).value(serializeSchema(schema)).exec();
|
|
408
|
+
},
|
|
409
|
+
async watchSchemas(handler) {
|
|
410
|
+
assertNotShutdown();
|
|
411
|
+
const prefix = buildKey(resolvedConfig.keyPrefix, ETCD_KEY_SCHEMA);
|
|
412
|
+
const watcher = await client.watch().prefix(prefix).create();
|
|
413
|
+
addWatcher(watcher);
|
|
414
|
+
watcher.on("put", (kv) => {
|
|
415
|
+
try {
|
|
416
|
+
const schema = deserializeSchema(kv.value);
|
|
417
|
+
const fullKey = kv.key.toString();
|
|
418
|
+
const indexName = extractSuffix(fullKey, prefix);
|
|
419
|
+
handler({ type: "schema_created", indexName, schema });
|
|
420
|
+
} catch (_) {
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
watcher.on("delete", (kv) => {
|
|
424
|
+
const fullKey = kv.key.toString();
|
|
425
|
+
const indexName = extractSuffix(fullKey, prefix);
|
|
426
|
+
handler({ type: "schema_dropped", indexName, schema: null });
|
|
427
|
+
});
|
|
428
|
+
return () => removeWatcher(watcher);
|
|
429
|
+
},
|
|
430
|
+
async getLeaseHolder(key) {
|
|
431
|
+
assertNotShutdown();
|
|
432
|
+
const etcdKey = leaseKey(key);
|
|
433
|
+
const value = await client.get(etcdKey).string();
|
|
434
|
+
return value ?? null;
|
|
435
|
+
},
|
|
436
|
+
async shutdown() {
|
|
437
|
+
if (isShutdown) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
isShutdown = true;
|
|
441
|
+
const cancelPromises = [];
|
|
442
|
+
for (const watcher of watchers) {
|
|
443
|
+
cancelPromises.push(watcher.cancel().catch(() => {
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
await Promise.all(cancelPromises);
|
|
447
|
+
watchers.clear();
|
|
448
|
+
await leaseManager.revokeAll();
|
|
449
|
+
client.close();
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
return coordinator;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export { createEtcdCoordinator, validateNodeId };
|