@thadeu/ndb 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +282 -2
- package/dist/index.cjs +598 -0
- package/dist/index.d.cts +231 -0
- package/dist/index.d.ts +231 -0
- package/dist/index.js +557 -0
- package/package.json +66 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CursorError: () => CursorError,
|
|
24
|
+
DuplicateIdError: () => DuplicateIdError,
|
|
25
|
+
HistoryTruncatedError: () => HistoryTruncatedError,
|
|
26
|
+
InvalidQueryError: () => InvalidQueryError,
|
|
27
|
+
Model: () => Model,
|
|
28
|
+
Ndb: () => Ndb,
|
|
29
|
+
NdbError: () => NdbError,
|
|
30
|
+
NotFoundError: () => NotFoundError,
|
|
31
|
+
Query: () => Query,
|
|
32
|
+
UnauthorizedError: () => UnauthorizedError,
|
|
33
|
+
UniqueViolationError: () => UniqueViolationError,
|
|
34
|
+
VersionConflictError: () => VersionConflictError,
|
|
35
|
+
WatchLaggedError: () => WatchLaggedError,
|
|
36
|
+
connect: () => connect,
|
|
37
|
+
makeError: () => makeError
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/errors.ts
|
|
42
|
+
var NdbError = class extends Error {
|
|
43
|
+
code;
|
|
44
|
+
status;
|
|
45
|
+
constructor(code, message, status = 0) {
|
|
46
|
+
super(message ? `ndb: ${code}: ${message}` : `ndb: ${code}`);
|
|
47
|
+
this.name = new.target.name;
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.status = status;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var UnauthorizedError = class extends NdbError {
|
|
53
|
+
};
|
|
54
|
+
var NotFoundError = class extends NdbError {
|
|
55
|
+
};
|
|
56
|
+
var VersionConflictError = class extends NdbError {
|
|
57
|
+
};
|
|
58
|
+
var DuplicateIdError = class extends NdbError {
|
|
59
|
+
};
|
|
60
|
+
var UniqueViolationError = class extends NdbError {
|
|
61
|
+
};
|
|
62
|
+
var InvalidQueryError = class extends NdbError {
|
|
63
|
+
};
|
|
64
|
+
var CursorError = class extends NdbError {
|
|
65
|
+
};
|
|
66
|
+
var WatchLaggedError = class extends NdbError {
|
|
67
|
+
};
|
|
68
|
+
var HistoryTruncatedError = class extends NdbError {
|
|
69
|
+
};
|
|
70
|
+
var subclasses = {
|
|
71
|
+
unauthorized: UnauthorizedError,
|
|
72
|
+
not_found: NotFoundError,
|
|
73
|
+
unknown_collection: NotFoundError,
|
|
74
|
+
version_conflict: VersionConflictError,
|
|
75
|
+
duplicate_id: DuplicateIdError,
|
|
76
|
+
unique_violation: UniqueViolationError,
|
|
77
|
+
invalid_query: InvalidQueryError,
|
|
78
|
+
invalid_pipeline: InvalidQueryError,
|
|
79
|
+
cursor_invalid: CursorError,
|
|
80
|
+
cursor_mismatch: CursorError,
|
|
81
|
+
cursor_expired: CursorError,
|
|
82
|
+
watch_lagged: WatchLaggedError,
|
|
83
|
+
history_truncated: HistoryTruncatedError
|
|
84
|
+
};
|
|
85
|
+
function makeError(code, message, status = 0) {
|
|
86
|
+
const Cls = subclasses[code] ?? NdbError;
|
|
87
|
+
return new Cls(code, message, status);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/where.ts
|
|
91
|
+
var supportedFlags = /* @__PURE__ */ new Set(["i", "m", "s"]);
|
|
92
|
+
function isPlain(v) {
|
|
93
|
+
if (typeof v !== "object" || v === null) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const proto = Object.getPrototypeOf(v);
|
|
97
|
+
return proto === Object.prototype || proto === null;
|
|
98
|
+
}
|
|
99
|
+
function regexParts(re) {
|
|
100
|
+
const bad = [...re.flags].filter((f) => !supportedFlags.has(f));
|
|
101
|
+
if (bad.length > 0) {
|
|
102
|
+
throw new InvalidQueryError(
|
|
103
|
+
"invalid_query",
|
|
104
|
+
`RegExp flag "${bad.join("")}" is not supported by $regex (use i, m or s): ${String(re)}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return re.flags === "" ? { $regex: re.source } : { $regex: re.source, $options: re.flags };
|
|
108
|
+
}
|
|
109
|
+
function operators(o) {
|
|
110
|
+
const out = {};
|
|
111
|
+
for (const [key, value] of Object.entries(o)) {
|
|
112
|
+
if (key === "$regex" && value instanceof RegExp) {
|
|
113
|
+
const parts = regexParts(value);
|
|
114
|
+
if (parts.$options !== void 0 && typeof o.$options === "string" && o.$options !== "") {
|
|
115
|
+
throw new InvalidQueryError("invalid_query", "use the RegExp flags or $options, not both");
|
|
116
|
+
}
|
|
117
|
+
out.$regex = parts.$regex;
|
|
118
|
+
if (parts.$options !== void 0) {
|
|
119
|
+
out.$options = parts.$options;
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
out[key] = toWhere(value);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
function toWhere(v) {
|
|
128
|
+
if (v instanceof RegExp) {
|
|
129
|
+
return regexParts(v);
|
|
130
|
+
}
|
|
131
|
+
if (Array.isArray(v)) {
|
|
132
|
+
return v.map(toWhere);
|
|
133
|
+
}
|
|
134
|
+
if (isPlain(v)) {
|
|
135
|
+
return operators(v);
|
|
136
|
+
}
|
|
137
|
+
return v;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/query.ts
|
|
141
|
+
var Query = class _Query {
|
|
142
|
+
constructor(model, state = { clauses: [] }) {
|
|
143
|
+
this.model = model;
|
|
144
|
+
this.state = state;
|
|
145
|
+
}
|
|
146
|
+
model;
|
|
147
|
+
state;
|
|
148
|
+
nextCursor = "";
|
|
149
|
+
where(w) {
|
|
150
|
+
return this.with({ clauses: [...this.state.clauses, toWhere(w)] });
|
|
151
|
+
}
|
|
152
|
+
not(w) {
|
|
153
|
+
return this.with({ clauses: [...this.state.clauses, { $not: toWhere(w) }] });
|
|
154
|
+
}
|
|
155
|
+
or(...ws) {
|
|
156
|
+
return this.with({ clauses: [...this.state.clauses, { $or: ws.map(toWhere) }] });
|
|
157
|
+
}
|
|
158
|
+
sort(spec) {
|
|
159
|
+
return this.with({ sort: { ...this.state.sort, ...spec } });
|
|
160
|
+
}
|
|
161
|
+
limit(n) {
|
|
162
|
+
return this.with({ limit: n });
|
|
163
|
+
}
|
|
164
|
+
skip(n) {
|
|
165
|
+
return this.with({ skip: n });
|
|
166
|
+
}
|
|
167
|
+
page(n, size) {
|
|
168
|
+
return this.with({ skip: (n - 1) * size, limit: size });
|
|
169
|
+
}
|
|
170
|
+
after(token) {
|
|
171
|
+
return this.with({ after: token });
|
|
172
|
+
}
|
|
173
|
+
fields(...names) {
|
|
174
|
+
return this.with({ fields: names });
|
|
175
|
+
}
|
|
176
|
+
then(onfulfilled, onrejected) {
|
|
177
|
+
return this.all().then(onfulfilled, onrejected);
|
|
178
|
+
}
|
|
179
|
+
async all() {
|
|
180
|
+
const out = [];
|
|
181
|
+
for await (const row of this.rows(this.body())) {
|
|
182
|
+
out.push(row);
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
async first() {
|
|
187
|
+
for await (const row of this.rows({ ...this.body(), limit: 1 })) {
|
|
188
|
+
return row;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
async count() {
|
|
193
|
+
await this.model.ready();
|
|
194
|
+
const body = { where: this.whereDoc(), count: true, limit: 1 };
|
|
195
|
+
const res = await this.model.db.transport.stream("POST", this.model.path("find"), body);
|
|
196
|
+
await res.body.dump();
|
|
197
|
+
return Number(res.header("x-ndb-total") ?? 0);
|
|
198
|
+
}
|
|
199
|
+
async *iterate() {
|
|
200
|
+
let body = this.body();
|
|
201
|
+
for (; ; ) {
|
|
202
|
+
yield* this.rows(body);
|
|
203
|
+
if (this.nextCursor === "") {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
body = { ...body, after: this.nextCursor, skip: void 0 };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async *rows(body) {
|
|
210
|
+
await this.model.ready();
|
|
211
|
+
const res = await this.model.db.transport.stream("POST", this.model.path("find"), body);
|
|
212
|
+
this.nextCursor = res.header("x-ndb-next") ?? "";
|
|
213
|
+
for await (const raw of res.rows()) {
|
|
214
|
+
yield this.model.decode(raw);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
body() {
|
|
218
|
+
const { fields, sort, limit, skip, after } = this.state;
|
|
219
|
+
return { where: this.whereDoc(), fields, sort, limit, skip, after, versions: true };
|
|
220
|
+
}
|
|
221
|
+
whereDoc() {
|
|
222
|
+
const merged = {};
|
|
223
|
+
for (const clause of this.state.clauses) {
|
|
224
|
+
for (const key of Object.keys(clause)) {
|
|
225
|
+
if (key in merged) {
|
|
226
|
+
return { $and: this.state.clauses };
|
|
227
|
+
}
|
|
228
|
+
merged[key] = clause[key];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return merged;
|
|
232
|
+
}
|
|
233
|
+
with(patch) {
|
|
234
|
+
return new _Query(this.model, { ...this.state, ...patch });
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/watch.ts
|
|
239
|
+
async function* watchEvents(lines2, consumer) {
|
|
240
|
+
for await (const raw of lines2) {
|
|
241
|
+
const line = raw;
|
|
242
|
+
if (line.heartbeat) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (line.error) {
|
|
246
|
+
throw makeError(line.error.code, line.error.message);
|
|
247
|
+
}
|
|
248
|
+
yield { consumer, pos: line.pos, op: line.op, id: line.id, v: line.v, doc: line.doc };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/model.ts
|
|
253
|
+
var Model = class {
|
|
254
|
+
constructor(db, name, schema) {
|
|
255
|
+
this.db = db;
|
|
256
|
+
this.name = name;
|
|
257
|
+
this.schema = schema;
|
|
258
|
+
}
|
|
259
|
+
db;
|
|
260
|
+
name;
|
|
261
|
+
schema;
|
|
262
|
+
ensured;
|
|
263
|
+
get idField() {
|
|
264
|
+
return this.schema.id ?? "_id";
|
|
265
|
+
}
|
|
266
|
+
path(op) {
|
|
267
|
+
const base = `/v1/c/${encodeURIComponent(this.name)}`;
|
|
268
|
+
return op ? `${base}/${op}` : base;
|
|
269
|
+
}
|
|
270
|
+
ready() {
|
|
271
|
+
this.ensured ??= this.db.transport.request("PUT", this.path(), this.catalog()).catch((err) => {
|
|
272
|
+
this.ensured = void 0;
|
|
273
|
+
throw err;
|
|
274
|
+
});
|
|
275
|
+
return this.ensured;
|
|
276
|
+
}
|
|
277
|
+
async insert(docs) {
|
|
278
|
+
await this.ready();
|
|
279
|
+
const list = Array.isArray(docs) ? docs : [docs];
|
|
280
|
+
const out = await this.db.transport.request("POST", this.path("insert"), {
|
|
281
|
+
insert: list
|
|
282
|
+
});
|
|
283
|
+
return out.results;
|
|
284
|
+
}
|
|
285
|
+
create(docs) {
|
|
286
|
+
return this.insert(docs);
|
|
287
|
+
}
|
|
288
|
+
async update(id, set, opts = {}) {
|
|
289
|
+
await this.ready();
|
|
290
|
+
const update = { id, set, unset: opts.unset, if_version: opts.ifVersion };
|
|
291
|
+
return this.db.transport.request("POST", this.path("update"), { update });
|
|
292
|
+
}
|
|
293
|
+
async delete(id, opts = {}) {
|
|
294
|
+
await this.ready();
|
|
295
|
+
await this.db.transport.request("POST", this.path("delete"), { delete: { id, if_version: opts.ifVersion } });
|
|
296
|
+
}
|
|
297
|
+
find(id) {
|
|
298
|
+
return this.query().where({ [this.idField]: id }).first();
|
|
299
|
+
}
|
|
300
|
+
query() {
|
|
301
|
+
return new Query(this);
|
|
302
|
+
}
|
|
303
|
+
where(w) {
|
|
304
|
+
return this.query().where(w);
|
|
305
|
+
}
|
|
306
|
+
not(w) {
|
|
307
|
+
return this.query().not(w);
|
|
308
|
+
}
|
|
309
|
+
or(...ws) {
|
|
310
|
+
return this.query().or(...ws);
|
|
311
|
+
}
|
|
312
|
+
sort(spec) {
|
|
313
|
+
return this.query().sort(spec);
|
|
314
|
+
}
|
|
315
|
+
limit(n) {
|
|
316
|
+
return this.query().limit(n);
|
|
317
|
+
}
|
|
318
|
+
skip(n) {
|
|
319
|
+
return this.query().skip(n);
|
|
320
|
+
}
|
|
321
|
+
page(n, size) {
|
|
322
|
+
return this.query().page(n, size);
|
|
323
|
+
}
|
|
324
|
+
after(token) {
|
|
325
|
+
return this.query().after(token);
|
|
326
|
+
}
|
|
327
|
+
fields(...names) {
|
|
328
|
+
return this.query().fields(...names);
|
|
329
|
+
}
|
|
330
|
+
all() {
|
|
331
|
+
return this.query().all();
|
|
332
|
+
}
|
|
333
|
+
count() {
|
|
334
|
+
return this.query().count();
|
|
335
|
+
}
|
|
336
|
+
first() {
|
|
337
|
+
return this.query().first();
|
|
338
|
+
}
|
|
339
|
+
iterate() {
|
|
340
|
+
return this.query().iterate();
|
|
341
|
+
}
|
|
342
|
+
async aggregate(pipeline, opts = {}) {
|
|
343
|
+
await this.ready();
|
|
344
|
+
const res = await this.db.transport.stream("POST", this.path("aggregate"), { pipeline, ...opts });
|
|
345
|
+
const rows = [];
|
|
346
|
+
for await (const row of res.rows()) {
|
|
347
|
+
rows.push(row);
|
|
348
|
+
}
|
|
349
|
+
return { rows, next: res.header("x-ndb-next") ?? "", total: Number(res.header("x-ndb-total") ?? 0) };
|
|
350
|
+
}
|
|
351
|
+
async *watch(consumer, opts = {}) {
|
|
352
|
+
await this.ready();
|
|
353
|
+
const body = { consumer, from: opts.from, limit: opts.limit, where: opts.where === void 0 ? void 0 : toWhere(opts.where) };
|
|
354
|
+
const res = await this.db.transport.stream("POST", this.path("watch"), body, opts.signal);
|
|
355
|
+
for await (const ev of watchEvents(res.rows(), consumer)) {
|
|
356
|
+
yield { ...ev, doc: ev.doc && this.decode(ev.doc) };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async ack(event) {
|
|
360
|
+
await this.db.transport.request("POST", this.path("ack"), { consumer: event.consumer, pos: event.pos });
|
|
361
|
+
}
|
|
362
|
+
decode(raw) {
|
|
363
|
+
const out = { ...raw };
|
|
364
|
+
for (const [name, def] of Object.entries(this.schema.fields)) {
|
|
365
|
+
const v = out[name];
|
|
366
|
+
if (def.type === "date" && typeof v === "string") {
|
|
367
|
+
out[name] = new Date(v);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
catalog() {
|
|
373
|
+
const entries = Object.entries(this.schema.fields);
|
|
374
|
+
return {
|
|
375
|
+
id: this.schema.id,
|
|
376
|
+
time: this.schema.time,
|
|
377
|
+
indexes: entries.filter(([, d]) => d.index).map(([n]) => n),
|
|
378
|
+
unique: entries.filter(([, d]) => d.unique).map(([n]) => n)
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/transport.ts
|
|
384
|
+
var import_undici = require("undici");
|
|
385
|
+
|
|
386
|
+
// src/ndjson.ts
|
|
387
|
+
async function* lines(body) {
|
|
388
|
+
let rest = "";
|
|
389
|
+
body.setEncoding("utf8");
|
|
390
|
+
for await (const chunk of body) {
|
|
391
|
+
rest += chunk;
|
|
392
|
+
let at = rest.indexOf("\n");
|
|
393
|
+
while (at >= 0) {
|
|
394
|
+
const line = rest.slice(0, at);
|
|
395
|
+
rest = rest.slice(at + 1);
|
|
396
|
+
if (line.trim() !== "") {
|
|
397
|
+
yield line;
|
|
398
|
+
}
|
|
399
|
+
at = rest.indexOf("\n");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (rest.trim() !== "") {
|
|
403
|
+
yield rest;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function* jsonRows(body) {
|
|
407
|
+
for await (const line of lines(body)) {
|
|
408
|
+
yield JSON.parse(line);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/transport.ts
|
|
413
|
+
var staleSocket = /* @__PURE__ */ new Set(["UND_ERR_SOCKET", "ECONNRESET", "EPIPE"]);
|
|
414
|
+
var notConnected = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOENT"]);
|
|
415
|
+
var retryBaseMs = 50;
|
|
416
|
+
var retryCapMs = 1e3;
|
|
417
|
+
var defaultReadyTimeoutMs = 1e4;
|
|
418
|
+
var readOps = /\/(find|count|aggregate|get|stats)$/;
|
|
419
|
+
var Transport = class {
|
|
420
|
+
client;
|
|
421
|
+
token;
|
|
422
|
+
readyTimeout;
|
|
423
|
+
// addr: unix:///path or tcp://host:port. Default: env NDB_URL.
|
|
424
|
+
constructor(addr, opts = {}) {
|
|
425
|
+
const target = addr || process.env.NDB_URL || "";
|
|
426
|
+
const url = new URL(target);
|
|
427
|
+
this.token = opts.token || process.env.NDB_TOKEN || void 0;
|
|
428
|
+
this.readyTimeout = opts.readyTimeout ?? defaultReadyTimeoutMs;
|
|
429
|
+
if (url.protocol === "unix:" && url.pathname !== "") {
|
|
430
|
+
this.client = new import_undici.Pool("http://ndb", { socketPath: decodeURIComponent(url.pathname), connections: 16 });
|
|
431
|
+
} else if (url.protocol === "tcp:" && url.host !== "") {
|
|
432
|
+
this.client = new import_undici.Pool(`http://${url.host}`, { connections: 16 });
|
|
433
|
+
} else {
|
|
434
|
+
throw new TypeError(`ndb: address "${target}" must be unix:///path or tcp://host:port`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async stream(method, path, body, signal) {
|
|
438
|
+
const res = await this.withReadyRetry(method, path, signal, async () => {
|
|
439
|
+
const r = await this.send(method, path, body, signal);
|
|
440
|
+
if (r.statusCode >= 300) {
|
|
441
|
+
throw await apiError(r);
|
|
442
|
+
}
|
|
443
|
+
return r;
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
status: res.statusCode,
|
|
447
|
+
headers: res.headers,
|
|
448
|
+
body: res.body,
|
|
449
|
+
rows: () => jsonRows(res.body),
|
|
450
|
+
header: (name) => headerOf(res.headers, name)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
async request(method, path, body) {
|
|
454
|
+
const res = await this.stream(method, path, body);
|
|
455
|
+
const text = await res.body.setEncoding("utf8").toArray();
|
|
456
|
+
return text.length > 0 ? JSON.parse(text.join("")) : void 0;
|
|
457
|
+
}
|
|
458
|
+
close() {
|
|
459
|
+
return this.client.close();
|
|
460
|
+
}
|
|
461
|
+
// Runs fn again while the daemon is not ready, with exponential backoff and
|
|
462
|
+
// jitter. It repeats only when the request certainly was not processed
|
|
463
|
+
// (not_ready, a failed connect), or for a read whose connection dropped.
|
|
464
|
+
// When the deadline passes, the last error is thrown as it was.
|
|
465
|
+
async withReadyRetry(method, path, signal, fn) {
|
|
466
|
+
const deadline = Date.now() + this.readyTimeout;
|
|
467
|
+
const idempotent = isIdempotent(method, path);
|
|
468
|
+
let delay = retryBaseMs;
|
|
469
|
+
for (; ; ) {
|
|
470
|
+
try {
|
|
471
|
+
return await fn();
|
|
472
|
+
} catch (err) {
|
|
473
|
+
const wait = delay / 2 + Math.random() * (delay / 2);
|
|
474
|
+
if (this.readyTimeout <= 0 || signal?.aborted || !retryable(err, idempotent) || Date.now() + wait > deadline) {
|
|
475
|
+
throw err;
|
|
476
|
+
}
|
|
477
|
+
await sleep(wait, signal);
|
|
478
|
+
delay = Math.min(delay * 2, retryCapMs);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
headers() {
|
|
483
|
+
const headers = { "content-type": "application/json" };
|
|
484
|
+
if (this.token) {
|
|
485
|
+
headers.authorization = `Bearer ${this.token}`;
|
|
486
|
+
}
|
|
487
|
+
return headers;
|
|
488
|
+
}
|
|
489
|
+
async send(method, path, body, signal) {
|
|
490
|
+
const opts = {
|
|
491
|
+
method,
|
|
492
|
+
path,
|
|
493
|
+
signal,
|
|
494
|
+
headers: this.headers(),
|
|
495
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
496
|
+
};
|
|
497
|
+
try {
|
|
498
|
+
return await this.client.request(opts);
|
|
499
|
+
} catch (err) {
|
|
500
|
+
if (!isIdempotent(method, path) || !staleSocket.has(err.code ?? "") || signal?.aborted) {
|
|
501
|
+
throw err;
|
|
502
|
+
}
|
|
503
|
+
return await this.client.request(opts);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
function isIdempotent(method, path) {
|
|
508
|
+
return method === "GET" || readOps.test(path.split("?")[0] ?? path);
|
|
509
|
+
}
|
|
510
|
+
function errorCodes(err) {
|
|
511
|
+
const e = err;
|
|
512
|
+
const nested = [...e.errors ?? [], ...e.cause ? [e.cause] : []];
|
|
513
|
+
return [e.code ?? "", ...nested.flatMap(errorCodes)];
|
|
514
|
+
}
|
|
515
|
+
function retryable(err, idempotent) {
|
|
516
|
+
if (err instanceof NdbError) {
|
|
517
|
+
return err.code === "not_ready";
|
|
518
|
+
}
|
|
519
|
+
const codes = errorCodes(err);
|
|
520
|
+
if (codes.some((c) => notConnected.has(c))) {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
return idempotent && codes.some((c) => staleSocket.has(c));
|
|
524
|
+
}
|
|
525
|
+
function sleep(ms, signal) {
|
|
526
|
+
return new Promise((resolve) => {
|
|
527
|
+
const done = () => {
|
|
528
|
+
clearTimeout(timer);
|
|
529
|
+
signal?.removeEventListener("abort", done);
|
|
530
|
+
resolve();
|
|
531
|
+
};
|
|
532
|
+
const timer = setTimeout(done, ms);
|
|
533
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
function headerOf(headers, name) {
|
|
537
|
+
const v = headers[name.toLowerCase()];
|
|
538
|
+
return Array.isArray(v) ? v[0] : v;
|
|
539
|
+
}
|
|
540
|
+
async function apiError(res) {
|
|
541
|
+
const text = (await res.body.setEncoding("utf8").toArray()).join("");
|
|
542
|
+
try {
|
|
543
|
+
const { error, status } = JSON.parse(text);
|
|
544
|
+
if (res.statusCode === 503 && status === "recovering") {
|
|
545
|
+
return makeError("not_ready", "ndb is recovering", 503);
|
|
546
|
+
}
|
|
547
|
+
if (error?.code) {
|
|
548
|
+
return makeError(error.code, error.message ?? "", res.statusCode);
|
|
549
|
+
}
|
|
550
|
+
} catch {
|
|
551
|
+
return makeError("http_status", text.trim(), res.statusCode);
|
|
552
|
+
}
|
|
553
|
+
return makeError("http_status", text.trim(), res.statusCode);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/ndb.ts
|
|
557
|
+
var Ndb = class {
|
|
558
|
+
transport;
|
|
559
|
+
constructor(addr, opts) {
|
|
560
|
+
this.transport = new Transport(addr, opts);
|
|
561
|
+
}
|
|
562
|
+
model(name, schema) {
|
|
563
|
+
return new Model(this, name, schema);
|
|
564
|
+
}
|
|
565
|
+
async health() {
|
|
566
|
+
await this.transport.request("GET", "/health");
|
|
567
|
+
}
|
|
568
|
+
request(method, path, body) {
|
|
569
|
+
return this.transport.request(method, path, body);
|
|
570
|
+
}
|
|
571
|
+
stream(...args) {
|
|
572
|
+
return this.transport.stream(...args);
|
|
573
|
+
}
|
|
574
|
+
close() {
|
|
575
|
+
return this.transport.close();
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
function connect(addr, opts) {
|
|
579
|
+
return new Ndb(addr, opts);
|
|
580
|
+
}
|
|
581
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
582
|
+
0 && (module.exports = {
|
|
583
|
+
CursorError,
|
|
584
|
+
DuplicateIdError,
|
|
585
|
+
HistoryTruncatedError,
|
|
586
|
+
InvalidQueryError,
|
|
587
|
+
Model,
|
|
588
|
+
Ndb,
|
|
589
|
+
NdbError,
|
|
590
|
+
NotFoundError,
|
|
591
|
+
Query,
|
|
592
|
+
UnauthorizedError,
|
|
593
|
+
UniqueViolationError,
|
|
594
|
+
VersionConflictError,
|
|
595
|
+
WatchLaggedError,
|
|
596
|
+
connect,
|
|
597
|
+
makeError
|
|
598
|
+
});
|