@thadeu/ndb 0.3.2 → 1.0.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/README.md +111 -47
- package/dist/index.cjs +231 -131
- package/dist/index.d.cts +72 -25
- package/dist/index.d.ts +72 -25
- package/dist/index.js +230 -130
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
Collection: () => Collection,
|
|
23
24
|
CursorError: () => CursorError,
|
|
24
25
|
DuplicateIdError: () => DuplicateIdError,
|
|
25
26
|
HistoryTruncatedError: () => HistoryTruncatedError,
|
|
26
27
|
InvalidQueryError: () => InvalidQueryError,
|
|
27
28
|
InvalidUpdateError: () => InvalidUpdateError,
|
|
28
|
-
Model: () => Model,
|
|
29
29
|
Ndb: () => Ndb,
|
|
30
30
|
NdbError: () => NdbError,
|
|
31
31
|
NotFoundError: () => NotFoundError,
|
|
@@ -150,16 +150,23 @@ function toWhere(v) {
|
|
|
150
150
|
|
|
151
151
|
// src/query.ts
|
|
152
152
|
var Query = class _Query {
|
|
153
|
-
constructor(
|
|
154
|
-
this.
|
|
153
|
+
constructor(coll, state = { clauses: [] }) {
|
|
154
|
+
this.coll = coll;
|
|
155
155
|
this.state = state;
|
|
156
156
|
}
|
|
157
|
-
|
|
157
|
+
coll;
|
|
158
158
|
state;
|
|
159
159
|
nextCursor = "";
|
|
160
|
-
|
|
160
|
+
// Adds conditions, ANDed with the ones already there.
|
|
161
|
+
query(w) {
|
|
161
162
|
return this.with({ clauses: [...this.state.clauses, toWhere(w)] });
|
|
162
163
|
}
|
|
164
|
+
// A query built elsewhere, such as from a search form. It is not
|
|
165
|
+
// type-checked and is sent as is; the daemon validates it. It is ANDed
|
|
166
|
+
// with the other clauses.
|
|
167
|
+
rawQuery(w) {
|
|
168
|
+
return this.with({ clauses: [...this.state.clauses, w] });
|
|
169
|
+
}
|
|
163
170
|
not(w) {
|
|
164
171
|
return this.with({ clauses: [...this.state.clauses, { $not: toWhere(w) }] });
|
|
165
172
|
}
|
|
@@ -200,14 +207,15 @@ var Query = class _Query {
|
|
|
200
207
|
}
|
|
201
208
|
return null;
|
|
202
209
|
}
|
|
210
|
+
// How many documents match (POST /{coll}/_count). limit, skip and sort do
|
|
211
|
+
// not change it.
|
|
203
212
|
async count() {
|
|
204
|
-
await this.
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
await res.body.dump();
|
|
208
|
-
return Number(res.header("x-ndb-total") ?? 0);
|
|
213
|
+
await this.coll.ready();
|
|
214
|
+
const out = await this.coll.db.transport.request("POST", this.coll.path("_count"), { query: this.whereDoc() });
|
|
215
|
+
return out.count;
|
|
209
216
|
}
|
|
210
|
-
|
|
217
|
+
// Every row, following the cursor page by page (limit is the page size).
|
|
218
|
+
async *each() {
|
|
211
219
|
let body = this.body();
|
|
212
220
|
for (; ; ) {
|
|
213
221
|
yield* this.rows(body);
|
|
@@ -218,16 +226,16 @@ var Query = class _Query {
|
|
|
218
226
|
}
|
|
219
227
|
}
|
|
220
228
|
async *rows(body) {
|
|
221
|
-
await this.
|
|
222
|
-
const res = await this.
|
|
229
|
+
await this.coll.ready();
|
|
230
|
+
const res = await this.coll.db.transport.stream("POST", this.coll.path("_search"), body);
|
|
223
231
|
this.nextCursor = res.header("x-ndb-next") ?? "";
|
|
224
232
|
for await (const raw of res.rows()) {
|
|
225
|
-
yield this.
|
|
233
|
+
yield this.coll.decode(raw);
|
|
226
234
|
}
|
|
227
235
|
}
|
|
228
236
|
body() {
|
|
229
237
|
const { fields, sort, limit, skip, after } = this.state;
|
|
230
|
-
return {
|
|
238
|
+
return { query: this.whereDoc(), fields, sort, limit, skip, after, versions: true };
|
|
231
239
|
}
|
|
232
240
|
whereDoc() {
|
|
233
241
|
const merged = {};
|
|
@@ -242,7 +250,7 @@ var Query = class _Query {
|
|
|
242
250
|
return merged;
|
|
243
251
|
}
|
|
244
252
|
with(patch) {
|
|
245
|
-
return new _Query(this.
|
|
253
|
+
return new _Query(this.coll, { ...this.state, ...patch });
|
|
246
254
|
}
|
|
247
255
|
};
|
|
248
256
|
|
|
@@ -256,12 +264,12 @@ async function* watchEvents(lines2, consumer) {
|
|
|
256
264
|
if (line.error) {
|
|
257
265
|
throw makeError(line.error.code, line.error.message);
|
|
258
266
|
}
|
|
259
|
-
yield { consumer, pos: line.pos, op: line.op, _id: line._id,
|
|
267
|
+
yield { consumer, pos: line.pos, op: line.op, _id: line._id ?? "", _v: line._v, doc: line.doc };
|
|
260
268
|
}
|
|
261
269
|
}
|
|
262
270
|
|
|
263
|
-
// src/
|
|
264
|
-
var
|
|
271
|
+
// src/collection.ts
|
|
272
|
+
var Collection = class {
|
|
265
273
|
constructor(db, name, schema) {
|
|
266
274
|
this.db = db;
|
|
267
275
|
this.name = name;
|
|
@@ -271,11 +279,16 @@ var Model = class {
|
|
|
271
279
|
name;
|
|
272
280
|
schema;
|
|
273
281
|
ensured;
|
|
274
|
-
path(
|
|
275
|
-
|
|
276
|
-
|
|
282
|
+
// The path of the collection, or of one of its actions ("_search").
|
|
283
|
+
path(action) {
|
|
284
|
+
const base = `/${encodeURIComponent(this.name)}`;
|
|
285
|
+
return action ? `${base}/${action}` : base;
|
|
286
|
+
}
|
|
287
|
+
get isPattern() {
|
|
288
|
+
return this.name.includes("*");
|
|
277
289
|
}
|
|
278
290
|
ready() {
|
|
291
|
+
if (this.isPattern) return Promise.resolve();
|
|
279
292
|
this.ensured ??= this.db.transport.request("PUT", this.path(), this.catalog()).catch((err) => {
|
|
280
293
|
this.ensured = void 0;
|
|
281
294
|
throw err;
|
|
@@ -283,137 +296,167 @@ var Model = class {
|
|
|
283
296
|
return this.ensured;
|
|
284
297
|
}
|
|
285
298
|
async insert(docs, opts = {}) {
|
|
286
|
-
|
|
299
|
+
if (!Array.isArray(docs) && opts.expireIn === void 0) {
|
|
300
|
+
return this.send("POST", "_doc", docs);
|
|
301
|
+
}
|
|
287
302
|
const list = Array.isArray(docs) ? docs : [docs];
|
|
288
|
-
const out = await this.
|
|
289
|
-
|
|
303
|
+
const out = await this.send("POST", "_bulk", { docs: list, expire_in: opts.expireIn });
|
|
304
|
+
return Array.isArray(docs) ? out.results : out.results[0];
|
|
305
|
+
}
|
|
306
|
+
// Writes a document by its _id: creates it when the _id is free, and
|
|
307
|
+
// merges its fields into the stored document when it exists (the other
|
|
308
|
+
// fields stay), like doc_as_upsert in Elasticsearch. No _id is an insert.
|
|
309
|
+
async upsert(doc, opts = {}) {
|
|
310
|
+
return (await this.upsertAll([doc], opts))[0];
|
|
311
|
+
}
|
|
312
|
+
// upsert for many documents in one transaction, all or nothing
|
|
313
|
+
// (POST /{coll}/_bulk with upsert).
|
|
314
|
+
async upsertAll(docs, opts = {}) {
|
|
315
|
+
const out = await this.send("POST", "_bulk", { docs, expire_in: opts.expireIn, upsert: true });
|
|
316
|
+
return out.results;
|
|
317
|
+
}
|
|
318
|
+
// Applies ops to the document with this _id, in one atomic step
|
|
319
|
+
// (POST /{coll}/_update/{id}). guard adds conditions on the same document,
|
|
320
|
+
// such as { _v: 7 }: matched is 0 when it fails. Matching nothing is not an
|
|
321
|
+
// error.
|
|
322
|
+
async update(id, ops, opts = {}) {
|
|
323
|
+
const out = await this.send("POST", docPath("_update", id), {
|
|
324
|
+
query: opts.guard === void 0 ? void 0 : toWhere(opts.guard),
|
|
325
|
+
update: checkOps(ops),
|
|
326
|
+
upsert: opts.upsert,
|
|
290
327
|
expire_in: opts.expireIn
|
|
291
328
|
});
|
|
292
|
-
return out
|
|
329
|
+
return this.updated(out);
|
|
330
|
+
}
|
|
331
|
+
// The atomic update by query (POST /{coll}/_update_by_query): the daemon
|
|
332
|
+
// matches and writes in one step on the newest state, so concurrent updates
|
|
333
|
+
// never interleave. Without many it updates the first match; many updates
|
|
334
|
+
// every match and needs _id or an indexed field in the query.
|
|
335
|
+
async updateBy(query, ops, opts = {}) {
|
|
336
|
+
const out = await this.send("POST", "_update_by_query", {
|
|
337
|
+
query: toWhere(query),
|
|
338
|
+
update: checkOps(ops),
|
|
339
|
+
many: opts.many,
|
|
340
|
+
upsert: opts.upsert,
|
|
341
|
+
expire_in: opts.expireIn
|
|
342
|
+
});
|
|
343
|
+
return this.updated(out);
|
|
293
344
|
}
|
|
294
|
-
|
|
295
|
-
|
|
345
|
+
// Deletes the document with this _id (DELETE /{coll}/_doc/{id}).
|
|
346
|
+
delete(id) {
|
|
347
|
+
return this.send("DELETE", docPath("_doc", id));
|
|
296
348
|
}
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// { _id: 'c1', _v: 7 }. Matching nothing is not an error: matched is 0.
|
|
302
|
-
// many does not apply to an _id.
|
|
303
|
-
async update(idOrFilter, ops, opts = {}) {
|
|
304
|
-
await this.ready();
|
|
305
|
-
const byId = typeof idOrFilter === "string";
|
|
306
|
-
const body = mergeUpdate(byId ? {} : idOrFilter, ops);
|
|
307
|
-
const query = writeQuery({ many: byId ? void 0 : opts.many, upsert: opts.upsert, expire_in: opts.expireIn });
|
|
308
|
-
const op = byId ? `${encodeURIComponent(idOrFilter)}/update` : "update";
|
|
309
|
-
const out = await this.db.transport.request(
|
|
310
|
-
"POST",
|
|
311
|
-
this.path(op) + query,
|
|
312
|
-
body
|
|
313
|
-
);
|
|
314
|
-
return { matched: out.matched, modified: out.modified, doc: out.doc && this.decode(out.doc) };
|
|
349
|
+
// Deletes the first document the query matches, or every match with many
|
|
350
|
+
// (POST /{coll}/_delete_by_query, same rule as updateBy).
|
|
351
|
+
deleteBy(query, opts = {}) {
|
|
352
|
+
return this.send("POST", "_delete_by_query", { query: toWhere(query), many: opts.many });
|
|
315
353
|
}
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
354
|
+
// Removes every document and keeps the definition and indexes. The cost
|
|
355
|
+
// does not depend on the number of documents. Watch consumers get one event
|
|
356
|
+
// with op "truncate".
|
|
357
|
+
async truncate() {
|
|
358
|
+
await this.db.transport.request("POST", this.path("_truncate") + this.confirm());
|
|
359
|
+
}
|
|
360
|
+
// Removes the collection: its documents, its definition and the positions
|
|
361
|
+
// of its watch consumers. The next call declares it again.
|
|
362
|
+
async drop() {
|
|
363
|
+
this.ensured = void 0;
|
|
364
|
+
await this.db.transport.request("DELETE", this.path() + this.confirm());
|
|
365
|
+
}
|
|
366
|
+
confirm() {
|
|
367
|
+
return `?confirm=${encodeURIComponent(this.name)}`;
|
|
325
368
|
}
|
|
326
369
|
// Makes a document expire, like EXPIRE in Redis: seconds or "30m". It throws
|
|
327
370
|
// NotFoundError for a missing or expired document, and TtlError when the
|
|
328
371
|
// schema has no expires field.
|
|
329
372
|
expire(id, duration) {
|
|
330
|
-
return this.setExpiry(
|
|
373
|
+
return this.setExpiry(id, { in: duration });
|
|
331
374
|
}
|
|
332
375
|
expireAt(id, at) {
|
|
333
|
-
return this.setExpiry(
|
|
376
|
+
return this.setExpiry(id, { at: at.toISOString() });
|
|
334
377
|
}
|
|
335
378
|
// Removes the expiry of a document (PERSIST in Redis).
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return this.db.transport.request("POST", this.path("persist"), { _id: id });
|
|
379
|
+
persist(id) {
|
|
380
|
+
return this.send("POST", docPath("_persist", id));
|
|
339
381
|
}
|
|
340
382
|
// The seconds left (rounded up), -1 when the document never expires, null
|
|
341
383
|
// when it is missing or expired.
|
|
342
384
|
async ttl(id) {
|
|
343
|
-
await this.
|
|
344
|
-
|
|
345
|
-
const out = await this.db.transport.request("GET", this.path(`${encodeURIComponent(id)}/ttl`));
|
|
346
|
-
return out.ttl;
|
|
347
|
-
} catch (err) {
|
|
348
|
-
if (err instanceof NotFoundError) {
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
throw err;
|
|
352
|
-
}
|
|
385
|
+
const out = await orNull(this.send("GET", docPath("_ttl", id)));
|
|
386
|
+
return out === null ? null : out.ttl;
|
|
353
387
|
}
|
|
354
|
-
async setExpiry(body) {
|
|
355
|
-
await this.
|
|
356
|
-
|
|
357
|
-
return { _id: out._id, v: out.v, expiresAt: new Date(out.expires_at) };
|
|
388
|
+
async setExpiry(id, body) {
|
|
389
|
+
const out = await this.send("POST", docPath("_expire", id), body);
|
|
390
|
+
return { _id: out._id, _v: out._v, expiresAt: new Date(out.expires_at) };
|
|
358
391
|
}
|
|
359
392
|
// The document with this _id, or null.
|
|
360
393
|
async get(id) {
|
|
361
|
-
await this.
|
|
362
|
-
|
|
363
|
-
const doc = await this.db.transport.request("GET", this.path(encodeURIComponent(id)));
|
|
364
|
-
return this.decode(doc);
|
|
365
|
-
} catch (err) {
|
|
366
|
-
if (err instanceof NotFoundError) {
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
throw err;
|
|
370
|
-
}
|
|
394
|
+
const doc = await orNull(this.send("GET", docPath("_doc", id)));
|
|
395
|
+
return doc === null ? null : this.decode(doc);
|
|
371
396
|
}
|
|
372
|
-
|
|
373
|
-
|
|
397
|
+
// A search (POST /{coll}/_search). No query matches every document.
|
|
398
|
+
query(w) {
|
|
399
|
+
const q = new Query(this);
|
|
400
|
+
return w === void 0 ? q : q.query(w);
|
|
374
401
|
}
|
|
375
|
-
|
|
376
|
-
|
|
402
|
+
// A query built elsewhere, such as from a search form: it is not
|
|
403
|
+
// type-checked and is sent as is; the daemon validates it.
|
|
404
|
+
rawQuery(w) {
|
|
405
|
+
return new Query(this).rawQuery(w);
|
|
377
406
|
}
|
|
378
407
|
not(w) {
|
|
379
|
-
return this
|
|
408
|
+
return new Query(this).not(w);
|
|
380
409
|
}
|
|
381
410
|
or(...ws) {
|
|
382
|
-
return this
|
|
411
|
+
return new Query(this).or(...ws);
|
|
383
412
|
}
|
|
384
413
|
sort(spec) {
|
|
385
|
-
return this
|
|
414
|
+
return new Query(this).sort(spec);
|
|
386
415
|
}
|
|
387
416
|
limit(n) {
|
|
388
|
-
return this
|
|
417
|
+
return new Query(this).limit(n);
|
|
389
418
|
}
|
|
390
419
|
skip(n) {
|
|
391
|
-
return this
|
|
420
|
+
return new Query(this).skip(n);
|
|
392
421
|
}
|
|
393
422
|
page(n, size) {
|
|
394
|
-
return this
|
|
423
|
+
return new Query(this).page(n, size);
|
|
395
424
|
}
|
|
396
425
|
after(token) {
|
|
397
|
-
return this
|
|
426
|
+
return new Query(this).after(token);
|
|
398
427
|
}
|
|
399
428
|
fields(...names) {
|
|
400
|
-
return this
|
|
429
|
+
return new Query(this).fields(...names);
|
|
401
430
|
}
|
|
402
431
|
all() {
|
|
403
|
-
return this
|
|
432
|
+
return new Query(this).all();
|
|
404
433
|
}
|
|
405
|
-
|
|
406
|
-
|
|
434
|
+
// How many documents match (POST /{coll}/_count).
|
|
435
|
+
count(w) {
|
|
436
|
+
return this.query(w).count();
|
|
407
437
|
}
|
|
408
438
|
first() {
|
|
409
|
-
return this
|
|
439
|
+
return new Query(this).first();
|
|
410
440
|
}
|
|
411
|
-
|
|
412
|
-
|
|
441
|
+
// Every row, page by page.
|
|
442
|
+
each() {
|
|
443
|
+
return new Query(this).each();
|
|
444
|
+
}
|
|
445
|
+
// Runs a whole search body built elsewhere, such as from a search form:
|
|
446
|
+
// { query, sort, limit, after, count }. The body is sent as is (the daemon
|
|
447
|
+
// validates it); rows are decoded like the rows of query().
|
|
448
|
+
async search(body) {
|
|
449
|
+
await this.ready();
|
|
450
|
+
const res = await this.db.transport.stream("POST", this.path("_search"), { ...body, versions: true });
|
|
451
|
+
const rows = [];
|
|
452
|
+
for await (const raw of res.rows()) {
|
|
453
|
+
rows.push(this.decode(raw));
|
|
454
|
+
}
|
|
455
|
+
return { rows, next: res.header("x-ndb-next") ?? "", total: Number(res.header("x-ndb-total") ?? 0) };
|
|
413
456
|
}
|
|
414
457
|
async aggregate(pipeline, opts = {}) {
|
|
415
458
|
await this.ready();
|
|
416
|
-
const res = await this.db.transport.stream("POST", this.path("
|
|
459
|
+
const res = await this.db.transport.stream("POST", this.path("_aggregate"), { pipeline, ...opts });
|
|
417
460
|
const rows = [];
|
|
418
461
|
for await (const row of res.rows()) {
|
|
419
462
|
rows.push(row);
|
|
@@ -422,14 +465,30 @@ var Model = class {
|
|
|
422
465
|
}
|
|
423
466
|
async *watch(consumer, opts = {}) {
|
|
424
467
|
await this.ready();
|
|
425
|
-
const body = { consumer, from: opts.from, limit: opts.limit,
|
|
426
|
-
const res = await this.db.transport.stream("POST", this.path("
|
|
468
|
+
const body = { consumer, from: opts.from, limit: opts.limit, query: opts.query === void 0 ? void 0 : toWhere(opts.query) };
|
|
469
|
+
const res = await this.db.transport.stream("POST", this.path("_watch"), body, opts.signal);
|
|
427
470
|
for await (const ev of watchEvents(res.rows(), consumer)) {
|
|
428
471
|
yield { ...ev, doc: ev.doc && this.decode(ev.doc) };
|
|
429
472
|
}
|
|
430
473
|
}
|
|
431
474
|
async ack(event) {
|
|
432
|
-
await this.
|
|
475
|
+
await this.send("POST", "_ack", { consumer: event.consumer, pos: event.pos });
|
|
476
|
+
}
|
|
477
|
+
// Rows, indexes, files and bytes (GET /{coll}/_stats).
|
|
478
|
+
stats() {
|
|
479
|
+
return this.send("GET", "_stats");
|
|
480
|
+
}
|
|
481
|
+
// Seals the memtable and merges every segment now (POST /{coll}/_compact).
|
|
482
|
+
compact() {
|
|
483
|
+
return this.send("POST", "_compact");
|
|
484
|
+
}
|
|
485
|
+
// Adds a secondary index and builds it in the background. Returns the
|
|
486
|
+
// indexed fields.
|
|
487
|
+
async ensureIndex(field) {
|
|
488
|
+
return (await this.send("PUT", docPath("_indexes", field))).indexes;
|
|
489
|
+
}
|
|
490
|
+
async dropIndex(field) {
|
|
491
|
+
return (await this.send("DELETE", docPath("_indexes", field))).indexes;
|
|
433
492
|
}
|
|
434
493
|
decode(raw) {
|
|
435
494
|
const out = { ...raw };
|
|
@@ -441,6 +500,15 @@ var Model = class {
|
|
|
441
500
|
}
|
|
442
501
|
return out;
|
|
443
502
|
}
|
|
503
|
+
// Declares the collection on first use, then sends one request to
|
|
504
|
+
// /{coll}/{action}.
|
|
505
|
+
async send(method, action, body) {
|
|
506
|
+
await this.ready();
|
|
507
|
+
return this.db.transport.request(method, this.path(action), body);
|
|
508
|
+
}
|
|
509
|
+
updated(out) {
|
|
510
|
+
return { matched: out.matched, modified: out.modified, doc: out.doc && this.decode(out.doc) };
|
|
511
|
+
}
|
|
444
512
|
catalog() {
|
|
445
513
|
const entries = Object.entries(this.schema.fields);
|
|
446
514
|
return {
|
|
@@ -451,23 +519,26 @@ var Model = class {
|
|
|
451
519
|
};
|
|
452
520
|
}
|
|
453
521
|
};
|
|
454
|
-
function
|
|
455
|
-
|
|
456
|
-
for (const [name, value] of Object.entries(params)) {
|
|
457
|
-
if (value !== void 0 && value !== false) {
|
|
458
|
-
q.set(name, String(value));
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
const text = q.toString();
|
|
462
|
-
return text === "" ? "" : `?${text}`;
|
|
522
|
+
function docPath(action, id) {
|
|
523
|
+
return `${action}/${encodeURIComponent(id)}`;
|
|
463
524
|
}
|
|
464
|
-
function
|
|
525
|
+
function checkOps(ops) {
|
|
465
526
|
for (const key of Object.keys(ops)) {
|
|
466
527
|
if (!key.startsWith("$")) {
|
|
467
528
|
throw new InvalidUpdateError("invalid_update", `update operator "${key}" must start with $ (for example "$set")`);
|
|
468
529
|
}
|
|
469
530
|
}
|
|
470
|
-
return
|
|
531
|
+
return ops;
|
|
532
|
+
}
|
|
533
|
+
async function orNull(p) {
|
|
534
|
+
try {
|
|
535
|
+
return await p;
|
|
536
|
+
} catch (err) {
|
|
537
|
+
if (err instanceof NotFoundError) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
throw err;
|
|
541
|
+
}
|
|
471
542
|
}
|
|
472
543
|
|
|
473
544
|
// src/transport.ts
|
|
@@ -505,7 +576,7 @@ var notConnected = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOENT"]);
|
|
|
505
576
|
var retryBaseMs = 50;
|
|
506
577
|
var retryCapMs = 1e3;
|
|
507
578
|
var defaultReadyTimeoutMs = 1e4;
|
|
508
|
-
var readOps = /\/(
|
|
579
|
+
var readOps = /\/(_search|_count|_aggregate)$/;
|
|
509
580
|
var Transport = class {
|
|
510
581
|
client;
|
|
511
582
|
token;
|
|
@@ -524,9 +595,9 @@ var Transport = class {
|
|
|
524
595
|
throw new TypeError(`ndb: address "${target}" must be unix:///path or tcp://host:port`);
|
|
525
596
|
}
|
|
526
597
|
}
|
|
527
|
-
async stream(method, path, body, signal) {
|
|
598
|
+
async stream(method, path, body, signal, contentType) {
|
|
528
599
|
const res = await this.withReadyRetry(method, path, signal, async () => {
|
|
529
|
-
const r = await this.send(method, path, body, signal);
|
|
600
|
+
const r = await this.send(method, path, body, signal, contentType);
|
|
530
601
|
if (r.statusCode >= 300) {
|
|
531
602
|
throw await apiError(r);
|
|
532
603
|
}
|
|
@@ -540,8 +611,9 @@ var Transport = class {
|
|
|
540
611
|
header: (name) => headerOf(res.headers, name)
|
|
541
612
|
};
|
|
542
613
|
}
|
|
543
|
-
|
|
544
|
-
|
|
614
|
+
// contentType sends body as raw bytes (blobs) instead of JSON.
|
|
615
|
+
async request(method, path, body, contentType) {
|
|
616
|
+
const res = await this.stream(method, path, body, void 0, contentType);
|
|
545
617
|
const text = await res.body.setEncoding("utf8").toArray();
|
|
546
618
|
return text.length > 0 ? JSON.parse(text.join("")) : void 0;
|
|
547
619
|
}
|
|
@@ -569,20 +641,22 @@ var Transport = class {
|
|
|
569
641
|
}
|
|
570
642
|
}
|
|
571
643
|
}
|
|
572
|
-
|
|
573
|
-
|
|
644
|
+
// Rows come as NDJSON: the SDK reads them one by one (jsonRows).
|
|
645
|
+
headers(contentType = "application/json") {
|
|
646
|
+
const headers = { "content-type": contentType, accept: "application/x-ndjson" };
|
|
574
647
|
if (this.token) {
|
|
575
648
|
headers.authorization = `Bearer ${this.token}`;
|
|
576
649
|
}
|
|
577
650
|
return headers;
|
|
578
651
|
}
|
|
579
|
-
async send(method, path, body, signal) {
|
|
652
|
+
async send(method, path, body, signal, contentType) {
|
|
653
|
+
const raw = contentType !== void 0;
|
|
580
654
|
const opts = {
|
|
581
655
|
method,
|
|
582
656
|
path,
|
|
583
657
|
signal,
|
|
584
|
-
headers: this.headers(),
|
|
585
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
658
|
+
headers: this.headers(contentType),
|
|
659
|
+
body: body === void 0 ? void 0 : raw ? body : JSON.stringify(body)
|
|
586
660
|
};
|
|
587
661
|
try {
|
|
588
662
|
return await this.client.request(opts);
|
|
@@ -649,11 +723,37 @@ var Ndb = class {
|
|
|
649
723
|
constructor(addr, opts) {
|
|
650
724
|
this.transport = new Transport(addr, opts);
|
|
651
725
|
}
|
|
652
|
-
|
|
653
|
-
|
|
726
|
+
// A collection of the daemon; see Collection. Nothing is sent until its
|
|
727
|
+
// first call.
|
|
728
|
+
collection(name, schema) {
|
|
729
|
+
return new Collection(this, name, schema);
|
|
654
730
|
}
|
|
731
|
+
// Resolves when the daemon is ready (GET /_health).
|
|
655
732
|
async health() {
|
|
656
|
-
await this.transport.request("GET", "/
|
|
733
|
+
await this.transport.request("GET", "/_health");
|
|
734
|
+
}
|
|
735
|
+
// The catalog: one definition per collection (GET /_collections).
|
|
736
|
+
async collections() {
|
|
737
|
+
return (await this.transport.request("GET", "/_collections")).collections;
|
|
738
|
+
}
|
|
739
|
+
// Every collection, totals, version and uptime (GET /_stats).
|
|
740
|
+
stats() {
|
|
741
|
+
return this.transport.request("GET", "/_stats");
|
|
742
|
+
}
|
|
743
|
+
// Disk use per collection and per part (GET /_size).
|
|
744
|
+
size() {
|
|
745
|
+
return this.transport.request("GET", "/_size");
|
|
746
|
+
}
|
|
747
|
+
// Stores bytes in the blob store (POST /_blobs) and returns the reference
|
|
748
|
+
// to put in a document field.
|
|
749
|
+
putBlob(data, contentType = "application/octet-stream") {
|
|
750
|
+
return this.transport.request("POST", "/_blobs", data, contentType);
|
|
751
|
+
}
|
|
752
|
+
// The bytes of a blob (GET /_blobs/{ref}).
|
|
753
|
+
async getBlob(blob) {
|
|
754
|
+
const ref = typeof blob === "string" ? blob : blob.$blob;
|
|
755
|
+
const res = await this.transport.stream("GET", `/_blobs/${encodeURIComponent(ref)}`);
|
|
756
|
+
return new Uint8Array(await res.body.arrayBuffer());
|
|
657
757
|
}
|
|
658
758
|
request(method, path, body) {
|
|
659
759
|
return this.transport.request(method, path, body);
|
|
@@ -702,12 +802,12 @@ function seedCounter() {
|
|
|
702
802
|
}
|
|
703
803
|
// Annotate the CommonJS export names for ESM import in node:
|
|
704
804
|
0 && (module.exports = {
|
|
805
|
+
Collection,
|
|
705
806
|
CursorError,
|
|
706
807
|
DuplicateIdError,
|
|
707
808
|
HistoryTruncatedError,
|
|
708
809
|
InvalidQueryError,
|
|
709
810
|
InvalidUpdateError,
|
|
710
|
-
Model,
|
|
711
811
|
Ndb,
|
|
712
812
|
NdbError,
|
|
713
813
|
NotFoundError,
|