@thadeu/ndb 0.4.0 → 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 +102 -48
- package/dist/index.cjs +208 -151
- package/dist/index.d.cts +53 -29
- package/dist/index.d.ts +53 -29
- package/dist/index.js +207 -150
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -108,20 +108,21 @@ function toWhere(v) {
|
|
|
108
108
|
|
|
109
109
|
// src/query.ts
|
|
110
110
|
var Query = class _Query {
|
|
111
|
-
constructor(
|
|
112
|
-
this.
|
|
111
|
+
constructor(coll, state = { clauses: [] }) {
|
|
112
|
+
this.coll = coll;
|
|
113
113
|
this.state = state;
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
coll;
|
|
116
116
|
state;
|
|
117
117
|
nextCursor = "";
|
|
118
|
-
|
|
118
|
+
// Adds conditions, ANDed with the ones already there.
|
|
119
|
+
query(w) {
|
|
119
120
|
return this.with({ clauses: [...this.state.clauses, toWhere(w)] });
|
|
120
121
|
}
|
|
121
|
-
// A
|
|
122
|
+
// A query built elsewhere, such as from a search form. It is not
|
|
122
123
|
// type-checked and is sent as is; the daemon validates it. It is ANDed
|
|
123
124
|
// with the other clauses.
|
|
124
|
-
|
|
125
|
+
rawQuery(w) {
|
|
125
126
|
return this.with({ clauses: [...this.state.clauses, w] });
|
|
126
127
|
}
|
|
127
128
|
not(w) {
|
|
@@ -164,14 +165,15 @@ var Query = class _Query {
|
|
|
164
165
|
}
|
|
165
166
|
return null;
|
|
166
167
|
}
|
|
168
|
+
// How many documents match (POST /{coll}/_count). limit, skip and sort do
|
|
169
|
+
// not change it.
|
|
167
170
|
async count() {
|
|
168
|
-
await this.
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
await res.body.dump();
|
|
172
|
-
return Number(res.header("x-ndb-total") ?? 0);
|
|
171
|
+
await this.coll.ready();
|
|
172
|
+
const out = await this.coll.db.transport.request("POST", this.coll.path("_count"), { query: this.whereDoc() });
|
|
173
|
+
return out.count;
|
|
173
174
|
}
|
|
174
|
-
|
|
175
|
+
// Every row, following the cursor page by page (limit is the page size).
|
|
176
|
+
async *each() {
|
|
175
177
|
let body = this.body();
|
|
176
178
|
for (; ; ) {
|
|
177
179
|
yield* this.rows(body);
|
|
@@ -182,16 +184,16 @@ var Query = class _Query {
|
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
async *rows(body) {
|
|
185
|
-
await this.
|
|
186
|
-
const res = await this.
|
|
187
|
+
await this.coll.ready();
|
|
188
|
+
const res = await this.coll.db.transport.stream("POST", this.coll.path("_search"), body);
|
|
187
189
|
this.nextCursor = res.header("x-ndb-next") ?? "";
|
|
188
190
|
for await (const raw of res.rows()) {
|
|
189
|
-
yield this.
|
|
191
|
+
yield this.coll.decode(raw);
|
|
190
192
|
}
|
|
191
193
|
}
|
|
192
194
|
body() {
|
|
193
195
|
const { fields, sort, limit, skip, after } = this.state;
|
|
194
|
-
return {
|
|
196
|
+
return { query: this.whereDoc(), fields, sort, limit, skip, after, versions: true };
|
|
195
197
|
}
|
|
196
198
|
whereDoc() {
|
|
197
199
|
const merged = {};
|
|
@@ -206,7 +208,7 @@ var Query = class _Query {
|
|
|
206
208
|
return merged;
|
|
207
209
|
}
|
|
208
210
|
with(patch) {
|
|
209
|
-
return new _Query(this.
|
|
211
|
+
return new _Query(this.coll, { ...this.state, ...patch });
|
|
210
212
|
}
|
|
211
213
|
};
|
|
212
214
|
|
|
@@ -220,12 +222,12 @@ async function* watchEvents(lines2, consumer) {
|
|
|
220
222
|
if (line.error) {
|
|
221
223
|
throw makeError(line.error.code, line.error.message);
|
|
222
224
|
}
|
|
223
|
-
yield { consumer, pos: line.pos, op: line.op, _id: line._id ?? "",
|
|
225
|
+
yield { consumer, pos: line.pos, op: line.op, _id: line._id ?? "", _v: line._v, doc: line.doc };
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
// src/
|
|
228
|
-
var
|
|
229
|
+
// src/collection.ts
|
|
230
|
+
var Collection = class {
|
|
229
231
|
constructor(db, name, schema) {
|
|
230
232
|
this.db = db;
|
|
231
233
|
this.name = name;
|
|
@@ -235,12 +237,11 @@ var Model = class {
|
|
|
235
237
|
name;
|
|
236
238
|
schema;
|
|
237
239
|
ensured;
|
|
238
|
-
path(
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
// The path of the collection, or of one of its actions ("_search").
|
|
241
|
+
path(action) {
|
|
242
|
+
const base = `/${encodeURIComponent(this.name)}`;
|
|
243
|
+
return action ? `${base}/${action}` : base;
|
|
241
244
|
}
|
|
242
|
-
// A collection pattern ("logs-2026-*") reads many collections and declares
|
|
243
|
-
// none: its writes fail with pattern_read_only.
|
|
244
245
|
get isPattern() {
|
|
245
246
|
return this.name.includes("*");
|
|
246
247
|
}
|
|
@@ -253,58 +254,72 @@ var Model = class {
|
|
|
253
254
|
return this.ensured;
|
|
254
255
|
}
|
|
255
256
|
async insert(docs, opts = {}) {
|
|
256
|
-
|
|
257
|
+
if (!Array.isArray(docs) && opts.expireIn === void 0) {
|
|
258
|
+
return this.send("POST", "_doc", docs);
|
|
259
|
+
}
|
|
257
260
|
const list = Array.isArray(docs) ? docs : [docs];
|
|
258
|
-
const out = await this.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
const out = await this.send("POST", "_bulk", { docs: list, expire_in: opts.expireIn });
|
|
262
|
+
return Array.isArray(docs) ? out.results : out.results[0];
|
|
263
|
+
}
|
|
264
|
+
// Writes a document by its _id: creates it when the _id is free, and
|
|
265
|
+
// merges its fields into the stored document when it exists (the other
|
|
266
|
+
// fields stay), like doc_as_upsert in Elasticsearch. No _id is an insert.
|
|
267
|
+
async upsert(doc, opts = {}) {
|
|
268
|
+
return (await this.upsertAll([doc], opts))[0];
|
|
269
|
+
}
|
|
270
|
+
// upsert for many documents in one transaction, all or nothing
|
|
271
|
+
// (POST /{coll}/_bulk with upsert).
|
|
272
|
+
async upsertAll(docs, opts = {}) {
|
|
273
|
+
const out = await this.send("POST", "_bulk", { docs, expire_in: opts.expireIn, upsert: true });
|
|
262
274
|
return out.results;
|
|
263
275
|
}
|
|
264
|
-
|
|
265
|
-
|
|
276
|
+
// Applies ops to the document with this _id, in one atomic step
|
|
277
|
+
// (POST /{coll}/_update/{id}). guard adds conditions on the same document,
|
|
278
|
+
// such as { _v: 7 }: matched is 0 when it fails. Matching nothing is not an
|
|
279
|
+
// error.
|
|
280
|
+
async update(id, ops, opts = {}) {
|
|
281
|
+
const out = await this.send("POST", docPath("_update", id), {
|
|
282
|
+
query: opts.guard === void 0 ? void 0 : toWhere(opts.guard),
|
|
283
|
+
update: checkOps(ops),
|
|
284
|
+
upsert: opts.upsert,
|
|
285
|
+
expire_in: opts.expireIn
|
|
286
|
+
});
|
|
287
|
+
return this.updated(out);
|
|
288
|
+
}
|
|
289
|
+
// The atomic update by query (POST /{coll}/_update_by_query): the daemon
|
|
290
|
+
// matches and writes in one step on the newest state, so concurrent updates
|
|
291
|
+
// never interleave. Without many it updates the first match; many updates
|
|
292
|
+
// every match and needs _id or an indexed field in the query.
|
|
293
|
+
async updateBy(query, ops, opts = {}) {
|
|
294
|
+
const out = await this.send("POST", "_update_by_query", {
|
|
295
|
+
query: toWhere(query),
|
|
296
|
+
update: checkOps(ops),
|
|
297
|
+
many: opts.many,
|
|
298
|
+
upsert: opts.upsert,
|
|
299
|
+
expire_in: opts.expireIn
|
|
300
|
+
});
|
|
301
|
+
return this.updated(out);
|
|
266
302
|
}
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// concurrent updates never interleave. A version guard is a filter:
|
|
271
|
-
// { _id: 'c1', _v: 7 }. Matching nothing is not an error: matched is 0.
|
|
272
|
-
// many does not apply to an _id.
|
|
273
|
-
async update(idOrFilter, ops, opts = {}) {
|
|
274
|
-
await this.ready();
|
|
275
|
-
const byId = typeof idOrFilter === "string";
|
|
276
|
-
const body = mergeUpdate(byId ? {} : idOrFilter, ops);
|
|
277
|
-
const query = writeQuery({ many: byId ? void 0 : opts.many, upsert: opts.upsert, expire_in: opts.expireIn });
|
|
278
|
-
const op = byId ? `${encodeURIComponent(idOrFilter)}/update` : "update";
|
|
279
|
-
const out = await this.db.transport.request(
|
|
280
|
-
"POST",
|
|
281
|
-
this.path(op) + query,
|
|
282
|
-
body
|
|
283
|
-
);
|
|
284
|
-
return { matched: out.matched, modified: out.modified, doc: out.doc && this.decode(out.doc) };
|
|
303
|
+
// Deletes the document with this _id (DELETE /{coll}/_doc/{id}).
|
|
304
|
+
delete(id) {
|
|
305
|
+
return this.send("DELETE", docPath("_doc", id));
|
|
285
306
|
}
|
|
286
|
-
// Deletes
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (typeof idOrFilter === "string") {
|
|
291
|
-
return this.db.transport.request("DELETE", this.path(encodeURIComponent(idOrFilter)));
|
|
292
|
-
}
|
|
293
|
-
const filter = idOrFilter;
|
|
294
|
-
return this.db.transport.request("POST", this.path("delete") + writeQuery({ many: opts.many }), toWhere(filter));
|
|
307
|
+
// Deletes the first document the query matches, or every match with many
|
|
308
|
+
// (POST /{coll}/_delete_by_query, same rule as updateBy).
|
|
309
|
+
deleteBy(query, opts = {}) {
|
|
310
|
+
return this.send("POST", "_delete_by_query", { query: toWhere(query), many: opts.many });
|
|
295
311
|
}
|
|
296
|
-
// Removes every document
|
|
297
|
-
//
|
|
298
|
-
//
|
|
312
|
+
// Removes every document and keeps the definition and indexes. The cost
|
|
313
|
+
// does not depend on the number of documents. Watch consumers get one event
|
|
314
|
+
// with op "truncate".
|
|
299
315
|
async truncate() {
|
|
300
|
-
await this.
|
|
301
|
-
await this.db.transport.request("POST", this.path("truncate") + this.confirm());
|
|
316
|
+
await this.db.transport.request("POST", this.path("_truncate") + this.confirm());
|
|
302
317
|
}
|
|
303
318
|
// Removes the collection: its documents, its definition and the positions
|
|
304
|
-
// of its watch consumers. The next call
|
|
319
|
+
// of its watch consumers. The next call declares it again.
|
|
305
320
|
async drop() {
|
|
306
|
-
await this.db.transport.request("DELETE", this.path() + this.confirm());
|
|
307
321
|
this.ensured = void 0;
|
|
322
|
+
await this.db.transport.request("DELETE", this.path() + this.confirm());
|
|
308
323
|
}
|
|
309
324
|
confirm() {
|
|
310
325
|
return `?confirm=${encodeURIComponent(this.name)}`;
|
|
@@ -313,99 +328,84 @@ var Model = class {
|
|
|
313
328
|
// NotFoundError for a missing or expired document, and TtlError when the
|
|
314
329
|
// schema has no expires field.
|
|
315
330
|
expire(id, duration) {
|
|
316
|
-
return this.setExpiry(
|
|
331
|
+
return this.setExpiry(id, { in: duration });
|
|
317
332
|
}
|
|
318
333
|
expireAt(id, at) {
|
|
319
|
-
return this.setExpiry(
|
|
334
|
+
return this.setExpiry(id, { at: at.toISOString() });
|
|
320
335
|
}
|
|
321
336
|
// Removes the expiry of a document (PERSIST in Redis).
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return this.db.transport.request("POST", this.path("persist"), { _id: id });
|
|
337
|
+
persist(id) {
|
|
338
|
+
return this.send("POST", docPath("_persist", id));
|
|
325
339
|
}
|
|
326
340
|
// The seconds left (rounded up), -1 when the document never expires, null
|
|
327
341
|
// when it is missing or expired.
|
|
328
342
|
async ttl(id) {
|
|
329
|
-
await this.
|
|
330
|
-
|
|
331
|
-
const out = await this.db.transport.request("GET", this.path(`${encodeURIComponent(id)}/ttl`));
|
|
332
|
-
return out.ttl;
|
|
333
|
-
} catch (err) {
|
|
334
|
-
if (err instanceof NotFoundError) {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
throw err;
|
|
338
|
-
}
|
|
343
|
+
const out = await orNull(this.send("GET", docPath("_ttl", id)));
|
|
344
|
+
return out === null ? null : out.ttl;
|
|
339
345
|
}
|
|
340
|
-
async setExpiry(body) {
|
|
341
|
-
await this.
|
|
342
|
-
|
|
343
|
-
return { _id: out._id, v: out.v, expiresAt: new Date(out.expires_at) };
|
|
346
|
+
async setExpiry(id, body) {
|
|
347
|
+
const out = await this.send("POST", docPath("_expire", id), body);
|
|
348
|
+
return { _id: out._id, _v: out._v, expiresAt: new Date(out.expires_at) };
|
|
344
349
|
}
|
|
345
350
|
// The document with this _id, or null.
|
|
346
351
|
async get(id) {
|
|
347
|
-
await this.
|
|
348
|
-
|
|
349
|
-
const doc = await this.db.transport.request("GET", this.path(encodeURIComponent(id)));
|
|
350
|
-
return this.decode(doc);
|
|
351
|
-
} catch (err) {
|
|
352
|
-
if (err instanceof NotFoundError) {
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
throw err;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
query() {
|
|
359
|
-
return new Query(this);
|
|
352
|
+
const doc = await orNull(this.send("GET", docPath("_doc", id)));
|
|
353
|
+
return doc === null ? null : this.decode(doc);
|
|
360
354
|
}
|
|
361
|
-
|
|
362
|
-
|
|
355
|
+
// A search (POST /{coll}/_search). No query matches every document.
|
|
356
|
+
query(w) {
|
|
357
|
+
const q = new Query(this);
|
|
358
|
+
return w === void 0 ? q : q.query(w);
|
|
363
359
|
}
|
|
364
|
-
|
|
365
|
-
|
|
360
|
+
// A query built elsewhere, such as from a search form: it is not
|
|
361
|
+
// type-checked and is sent as is; the daemon validates it.
|
|
362
|
+
rawQuery(w) {
|
|
363
|
+
return new Query(this).rawQuery(w);
|
|
366
364
|
}
|
|
367
365
|
not(w) {
|
|
368
|
-
return this
|
|
366
|
+
return new Query(this).not(w);
|
|
369
367
|
}
|
|
370
368
|
or(...ws) {
|
|
371
|
-
return this
|
|
369
|
+
return new Query(this).or(...ws);
|
|
372
370
|
}
|
|
373
371
|
sort(spec) {
|
|
374
|
-
return this
|
|
372
|
+
return new Query(this).sort(spec);
|
|
375
373
|
}
|
|
376
374
|
limit(n) {
|
|
377
|
-
return this
|
|
375
|
+
return new Query(this).limit(n);
|
|
378
376
|
}
|
|
379
377
|
skip(n) {
|
|
380
|
-
return this
|
|
378
|
+
return new Query(this).skip(n);
|
|
381
379
|
}
|
|
382
380
|
page(n, size) {
|
|
383
|
-
return this
|
|
381
|
+
return new Query(this).page(n, size);
|
|
384
382
|
}
|
|
385
383
|
after(token) {
|
|
386
|
-
return this
|
|
384
|
+
return new Query(this).after(token);
|
|
387
385
|
}
|
|
388
386
|
fields(...names) {
|
|
389
|
-
return this
|
|
387
|
+
return new Query(this).fields(...names);
|
|
390
388
|
}
|
|
391
389
|
all() {
|
|
392
|
-
return this
|
|
390
|
+
return new Query(this).all();
|
|
393
391
|
}
|
|
394
|
-
|
|
395
|
-
|
|
392
|
+
// How many documents match (POST /{coll}/_count).
|
|
393
|
+
count(w) {
|
|
394
|
+
return this.query(w).count();
|
|
396
395
|
}
|
|
397
396
|
first() {
|
|
398
|
-
return this
|
|
397
|
+
return new Query(this).first();
|
|
399
398
|
}
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
// Every row, page by page.
|
|
400
|
+
each() {
|
|
401
|
+
return new Query(this).each();
|
|
402
402
|
}
|
|
403
|
-
// Runs a
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
async
|
|
403
|
+
// Runs a whole search body built elsewhere, such as from a search form:
|
|
404
|
+
// { query, sort, limit, after, count }. The body is sent as is (the daemon
|
|
405
|
+
// validates it); rows are decoded like the rows of query().
|
|
406
|
+
async search(body) {
|
|
407
407
|
await this.ready();
|
|
408
|
-
const res = await this.db.transport.stream("POST", this.path("
|
|
408
|
+
const res = await this.db.transport.stream("POST", this.path("_search"), { ...body, versions: true });
|
|
409
409
|
const rows = [];
|
|
410
410
|
for await (const raw of res.rows()) {
|
|
411
411
|
rows.push(this.decode(raw));
|
|
@@ -414,7 +414,7 @@ var Model = class {
|
|
|
414
414
|
}
|
|
415
415
|
async aggregate(pipeline, opts = {}) {
|
|
416
416
|
await this.ready();
|
|
417
|
-
const res = await this.db.transport.stream("POST", this.path("
|
|
417
|
+
const res = await this.db.transport.stream("POST", this.path("_aggregate"), { pipeline, ...opts });
|
|
418
418
|
const rows = [];
|
|
419
419
|
for await (const row of res.rows()) {
|
|
420
420
|
rows.push(row);
|
|
@@ -423,14 +423,30 @@ var Model = class {
|
|
|
423
423
|
}
|
|
424
424
|
async *watch(consumer, opts = {}) {
|
|
425
425
|
await this.ready();
|
|
426
|
-
const body = { consumer, from: opts.from, limit: opts.limit,
|
|
427
|
-
const res = await this.db.transport.stream("POST", this.path("
|
|
426
|
+
const body = { consumer, from: opts.from, limit: opts.limit, query: opts.query === void 0 ? void 0 : toWhere(opts.query) };
|
|
427
|
+
const res = await this.db.transport.stream("POST", this.path("_watch"), body, opts.signal);
|
|
428
428
|
for await (const ev of watchEvents(res.rows(), consumer)) {
|
|
429
429
|
yield { ...ev, doc: ev.doc && this.decode(ev.doc) };
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
async ack(event) {
|
|
433
|
-
await this.
|
|
433
|
+
await this.send("POST", "_ack", { consumer: event.consumer, pos: event.pos });
|
|
434
|
+
}
|
|
435
|
+
// Rows, indexes, files and bytes (GET /{coll}/_stats).
|
|
436
|
+
stats() {
|
|
437
|
+
return this.send("GET", "_stats");
|
|
438
|
+
}
|
|
439
|
+
// Seals the memtable and merges every segment now (POST /{coll}/_compact).
|
|
440
|
+
compact() {
|
|
441
|
+
return this.send("POST", "_compact");
|
|
442
|
+
}
|
|
443
|
+
// Adds a secondary index and builds it in the background. Returns the
|
|
444
|
+
// indexed fields.
|
|
445
|
+
async ensureIndex(field) {
|
|
446
|
+
return (await this.send("PUT", docPath("_indexes", field))).indexes;
|
|
447
|
+
}
|
|
448
|
+
async dropIndex(field) {
|
|
449
|
+
return (await this.send("DELETE", docPath("_indexes", field))).indexes;
|
|
434
450
|
}
|
|
435
451
|
decode(raw) {
|
|
436
452
|
const out = { ...raw };
|
|
@@ -442,6 +458,15 @@ var Model = class {
|
|
|
442
458
|
}
|
|
443
459
|
return out;
|
|
444
460
|
}
|
|
461
|
+
// Declares the collection on first use, then sends one request to
|
|
462
|
+
// /{coll}/{action}.
|
|
463
|
+
async send(method, action, body) {
|
|
464
|
+
await this.ready();
|
|
465
|
+
return this.db.transport.request(method, this.path(action), body);
|
|
466
|
+
}
|
|
467
|
+
updated(out) {
|
|
468
|
+
return { matched: out.matched, modified: out.modified, doc: out.doc && this.decode(out.doc) };
|
|
469
|
+
}
|
|
445
470
|
catalog() {
|
|
446
471
|
const entries = Object.entries(this.schema.fields);
|
|
447
472
|
return {
|
|
@@ -452,23 +477,26 @@ var Model = class {
|
|
|
452
477
|
};
|
|
453
478
|
}
|
|
454
479
|
};
|
|
455
|
-
function
|
|
456
|
-
|
|
457
|
-
for (const [name, value] of Object.entries(params)) {
|
|
458
|
-
if (value !== void 0 && value !== false) {
|
|
459
|
-
q.set(name, String(value));
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
const text = q.toString();
|
|
463
|
-
return text === "" ? "" : `?${text}`;
|
|
480
|
+
function docPath(action, id) {
|
|
481
|
+
return `${action}/${encodeURIComponent(id)}`;
|
|
464
482
|
}
|
|
465
|
-
function
|
|
483
|
+
function checkOps(ops) {
|
|
466
484
|
for (const key of Object.keys(ops)) {
|
|
467
485
|
if (!key.startsWith("$")) {
|
|
468
486
|
throw new InvalidUpdateError("invalid_update", `update operator "${key}" must start with $ (for example "$set")`);
|
|
469
487
|
}
|
|
470
488
|
}
|
|
471
|
-
return
|
|
489
|
+
return ops;
|
|
490
|
+
}
|
|
491
|
+
async function orNull(p) {
|
|
492
|
+
try {
|
|
493
|
+
return await p;
|
|
494
|
+
} catch (err) {
|
|
495
|
+
if (err instanceof NotFoundError) {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
throw err;
|
|
499
|
+
}
|
|
472
500
|
}
|
|
473
501
|
|
|
474
502
|
// src/transport.ts
|
|
@@ -506,7 +534,7 @@ var notConnected = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOENT"]);
|
|
|
506
534
|
var retryBaseMs = 50;
|
|
507
535
|
var retryCapMs = 1e3;
|
|
508
536
|
var defaultReadyTimeoutMs = 1e4;
|
|
509
|
-
var readOps = /\/(
|
|
537
|
+
var readOps = /\/(_search|_count|_aggregate)$/;
|
|
510
538
|
var Transport = class {
|
|
511
539
|
client;
|
|
512
540
|
token;
|
|
@@ -525,9 +553,9 @@ var Transport = class {
|
|
|
525
553
|
throw new TypeError(`ndb: address "${target}" must be unix:///path or tcp://host:port`);
|
|
526
554
|
}
|
|
527
555
|
}
|
|
528
|
-
async stream(method, path, body, signal) {
|
|
556
|
+
async stream(method, path, body, signal, contentType) {
|
|
529
557
|
const res = await this.withReadyRetry(method, path, signal, async () => {
|
|
530
|
-
const r = await this.send(method, path, body, signal);
|
|
558
|
+
const r = await this.send(method, path, body, signal, contentType);
|
|
531
559
|
if (r.statusCode >= 300) {
|
|
532
560
|
throw await apiError(r);
|
|
533
561
|
}
|
|
@@ -541,8 +569,9 @@ var Transport = class {
|
|
|
541
569
|
header: (name) => headerOf(res.headers, name)
|
|
542
570
|
};
|
|
543
571
|
}
|
|
544
|
-
|
|
545
|
-
|
|
572
|
+
// contentType sends body as raw bytes (blobs) instead of JSON.
|
|
573
|
+
async request(method, path, body, contentType) {
|
|
574
|
+
const res = await this.stream(method, path, body, void 0, contentType);
|
|
546
575
|
const text = await res.body.setEncoding("utf8").toArray();
|
|
547
576
|
return text.length > 0 ? JSON.parse(text.join("")) : void 0;
|
|
548
577
|
}
|
|
@@ -570,20 +599,22 @@ var Transport = class {
|
|
|
570
599
|
}
|
|
571
600
|
}
|
|
572
601
|
}
|
|
573
|
-
|
|
574
|
-
|
|
602
|
+
// Rows come as NDJSON: the SDK reads them one by one (jsonRows).
|
|
603
|
+
headers(contentType = "application/json") {
|
|
604
|
+
const headers = { "content-type": contentType, accept: "application/x-ndjson" };
|
|
575
605
|
if (this.token) {
|
|
576
606
|
headers.authorization = `Bearer ${this.token}`;
|
|
577
607
|
}
|
|
578
608
|
return headers;
|
|
579
609
|
}
|
|
580
|
-
async send(method, path, body, signal) {
|
|
610
|
+
async send(method, path, body, signal, contentType) {
|
|
611
|
+
const raw = contentType !== void 0;
|
|
581
612
|
const opts = {
|
|
582
613
|
method,
|
|
583
614
|
path,
|
|
584
615
|
signal,
|
|
585
|
-
headers: this.headers(),
|
|
586
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
616
|
+
headers: this.headers(contentType),
|
|
617
|
+
body: body === void 0 ? void 0 : raw ? body : JSON.stringify(body)
|
|
587
618
|
};
|
|
588
619
|
try {
|
|
589
620
|
return await this.client.request(opts);
|
|
@@ -650,11 +681,37 @@ var Ndb = class {
|
|
|
650
681
|
constructor(addr, opts) {
|
|
651
682
|
this.transport = new Transport(addr, opts);
|
|
652
683
|
}
|
|
653
|
-
|
|
654
|
-
|
|
684
|
+
// A collection of the daemon; see Collection. Nothing is sent until its
|
|
685
|
+
// first call.
|
|
686
|
+
collection(name, schema) {
|
|
687
|
+
return new Collection(this, name, schema);
|
|
655
688
|
}
|
|
689
|
+
// Resolves when the daemon is ready (GET /_health).
|
|
656
690
|
async health() {
|
|
657
|
-
await this.transport.request("GET", "/
|
|
691
|
+
await this.transport.request("GET", "/_health");
|
|
692
|
+
}
|
|
693
|
+
// The catalog: one definition per collection (GET /_collections).
|
|
694
|
+
async collections() {
|
|
695
|
+
return (await this.transport.request("GET", "/_collections")).collections;
|
|
696
|
+
}
|
|
697
|
+
// Every collection, totals, version and uptime (GET /_stats).
|
|
698
|
+
stats() {
|
|
699
|
+
return this.transport.request("GET", "/_stats");
|
|
700
|
+
}
|
|
701
|
+
// Disk use per collection and per part (GET /_size).
|
|
702
|
+
size() {
|
|
703
|
+
return this.transport.request("GET", "/_size");
|
|
704
|
+
}
|
|
705
|
+
// Stores bytes in the blob store (POST /_blobs) and returns the reference
|
|
706
|
+
// to put in a document field.
|
|
707
|
+
putBlob(data, contentType = "application/octet-stream") {
|
|
708
|
+
return this.transport.request("POST", "/_blobs", data, contentType);
|
|
709
|
+
}
|
|
710
|
+
// The bytes of a blob (GET /_blobs/{ref}).
|
|
711
|
+
async getBlob(blob) {
|
|
712
|
+
const ref = typeof blob === "string" ? blob : blob.$blob;
|
|
713
|
+
const res = await this.transport.stream("GET", `/_blobs/${encodeURIComponent(ref)}`);
|
|
714
|
+
return new Uint8Array(await res.body.arrayBuffer());
|
|
658
715
|
}
|
|
659
716
|
request(method, path, body) {
|
|
660
717
|
return this.transport.request(method, path, body);
|
|
@@ -702,12 +759,12 @@ function seedCounter() {
|
|
|
702
759
|
return r[0] & 2047;
|
|
703
760
|
}
|
|
704
761
|
export {
|
|
762
|
+
Collection,
|
|
705
763
|
CursorError,
|
|
706
764
|
DuplicateIdError,
|
|
707
765
|
HistoryTruncatedError,
|
|
708
766
|
InvalidQueryError,
|
|
709
767
|
InvalidUpdateError,
|
|
710
|
-
Model,
|
|
711
768
|
Ndb,
|
|
712
769
|
NdbError,
|
|
713
770
|
NotFoundError,
|