@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.js
CHANGED
|
@@ -108,16 +108,23 @@ 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
|
}
|
|
122
|
+
// A query built elsewhere, such as from a search form. It is not
|
|
123
|
+
// type-checked and is sent as is; the daemon validates it. It is ANDed
|
|
124
|
+
// with the other clauses.
|
|
125
|
+
rawQuery(w) {
|
|
126
|
+
return this.with({ clauses: [...this.state.clauses, w] });
|
|
127
|
+
}
|
|
121
128
|
not(w) {
|
|
122
129
|
return this.with({ clauses: [...this.state.clauses, { $not: toWhere(w) }] });
|
|
123
130
|
}
|
|
@@ -158,14 +165,15 @@ var Query = class _Query {
|
|
|
158
165
|
}
|
|
159
166
|
return null;
|
|
160
167
|
}
|
|
168
|
+
// How many documents match (POST /{coll}/_count). limit, skip and sort do
|
|
169
|
+
// not change it.
|
|
161
170
|
async count() {
|
|
162
|
-
await this.
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
await res.body.dump();
|
|
166
|
-
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;
|
|
167
174
|
}
|
|
168
|
-
|
|
175
|
+
// Every row, following the cursor page by page (limit is the page size).
|
|
176
|
+
async *each() {
|
|
169
177
|
let body = this.body();
|
|
170
178
|
for (; ; ) {
|
|
171
179
|
yield* this.rows(body);
|
|
@@ -176,16 +184,16 @@ var Query = class _Query {
|
|
|
176
184
|
}
|
|
177
185
|
}
|
|
178
186
|
async *rows(body) {
|
|
179
|
-
await this.
|
|
180
|
-
const res = await this.
|
|
187
|
+
await this.coll.ready();
|
|
188
|
+
const res = await this.coll.db.transport.stream("POST", this.coll.path("_search"), body);
|
|
181
189
|
this.nextCursor = res.header("x-ndb-next") ?? "";
|
|
182
190
|
for await (const raw of res.rows()) {
|
|
183
|
-
yield this.
|
|
191
|
+
yield this.coll.decode(raw);
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
194
|
body() {
|
|
187
195
|
const { fields, sort, limit, skip, after } = this.state;
|
|
188
|
-
return {
|
|
196
|
+
return { query: this.whereDoc(), fields, sort, limit, skip, after, versions: true };
|
|
189
197
|
}
|
|
190
198
|
whereDoc() {
|
|
191
199
|
const merged = {};
|
|
@@ -200,7 +208,7 @@ var Query = class _Query {
|
|
|
200
208
|
return merged;
|
|
201
209
|
}
|
|
202
210
|
with(patch) {
|
|
203
|
-
return new _Query(this.
|
|
211
|
+
return new _Query(this.coll, { ...this.state, ...patch });
|
|
204
212
|
}
|
|
205
213
|
};
|
|
206
214
|
|
|
@@ -214,12 +222,12 @@ async function* watchEvents(lines2, consumer) {
|
|
|
214
222
|
if (line.error) {
|
|
215
223
|
throw makeError(line.error.code, line.error.message);
|
|
216
224
|
}
|
|
217
|
-
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 };
|
|
218
226
|
}
|
|
219
227
|
}
|
|
220
228
|
|
|
221
|
-
// src/
|
|
222
|
-
var
|
|
229
|
+
// src/collection.ts
|
|
230
|
+
var Collection = class {
|
|
223
231
|
constructor(db, name, schema) {
|
|
224
232
|
this.db = db;
|
|
225
233
|
this.name = name;
|
|
@@ -229,11 +237,16 @@ var Model = class {
|
|
|
229
237
|
name;
|
|
230
238
|
schema;
|
|
231
239
|
ensured;
|
|
232
|
-
path(
|
|
233
|
-
|
|
234
|
-
|
|
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;
|
|
244
|
+
}
|
|
245
|
+
get isPattern() {
|
|
246
|
+
return this.name.includes("*");
|
|
235
247
|
}
|
|
236
248
|
ready() {
|
|
249
|
+
if (this.isPattern) return Promise.resolve();
|
|
237
250
|
this.ensured ??= this.db.transport.request("PUT", this.path(), this.catalog()).catch((err) => {
|
|
238
251
|
this.ensured = void 0;
|
|
239
252
|
throw err;
|
|
@@ -241,137 +254,167 @@ var Model = class {
|
|
|
241
254
|
return this.ensured;
|
|
242
255
|
}
|
|
243
256
|
async insert(docs, opts = {}) {
|
|
244
|
-
|
|
257
|
+
if (!Array.isArray(docs) && opts.expireIn === void 0) {
|
|
258
|
+
return this.send("POST", "_doc", docs);
|
|
259
|
+
}
|
|
245
260
|
const list = Array.isArray(docs) ? docs : [docs];
|
|
246
|
-
const out = await this.
|
|
247
|
-
|
|
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 });
|
|
274
|
+
return out.results;
|
|
275
|
+
}
|
|
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,
|
|
248
285
|
expire_in: opts.expireIn
|
|
249
286
|
});
|
|
250
|
-
return out
|
|
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);
|
|
251
302
|
}
|
|
252
|
-
|
|
253
|
-
|
|
303
|
+
// Deletes the document with this _id (DELETE /{coll}/_doc/{id}).
|
|
304
|
+
delete(id) {
|
|
305
|
+
return this.send("DELETE", docPath("_doc", id));
|
|
254
306
|
}
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// { _id: 'c1', _v: 7 }. Matching nothing is not an error: matched is 0.
|
|
260
|
-
// many does not apply to an _id.
|
|
261
|
-
async update(idOrFilter, ops, opts = {}) {
|
|
262
|
-
await this.ready();
|
|
263
|
-
const byId = typeof idOrFilter === "string";
|
|
264
|
-
const body = mergeUpdate(byId ? {} : idOrFilter, ops);
|
|
265
|
-
const query = writeQuery({ many: byId ? void 0 : opts.many, upsert: opts.upsert, expire_in: opts.expireIn });
|
|
266
|
-
const op = byId ? `${encodeURIComponent(idOrFilter)}/update` : "update";
|
|
267
|
-
const out = await this.db.transport.request(
|
|
268
|
-
"POST",
|
|
269
|
-
this.path(op) + query,
|
|
270
|
-
body
|
|
271
|
-
);
|
|
272
|
-
return { matched: out.matched, modified: out.modified, doc: out.doc && this.decode(out.doc) };
|
|
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 });
|
|
273
311
|
}
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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".
|
|
315
|
+
async truncate() {
|
|
316
|
+
await this.db.transport.request("POST", this.path("_truncate") + this.confirm());
|
|
317
|
+
}
|
|
318
|
+
// Removes the collection: its documents, its definition and the positions
|
|
319
|
+
// of its watch consumers. The next call declares it again.
|
|
320
|
+
async drop() {
|
|
321
|
+
this.ensured = void 0;
|
|
322
|
+
await this.db.transport.request("DELETE", this.path() + this.confirm());
|
|
323
|
+
}
|
|
324
|
+
confirm() {
|
|
325
|
+
return `?confirm=${encodeURIComponent(this.name)}`;
|
|
283
326
|
}
|
|
284
327
|
// Makes a document expire, like EXPIRE in Redis: seconds or "30m". It throws
|
|
285
328
|
// NotFoundError for a missing or expired document, and TtlError when the
|
|
286
329
|
// schema has no expires field.
|
|
287
330
|
expire(id, duration) {
|
|
288
|
-
return this.setExpiry(
|
|
331
|
+
return this.setExpiry(id, { in: duration });
|
|
289
332
|
}
|
|
290
333
|
expireAt(id, at) {
|
|
291
|
-
return this.setExpiry(
|
|
334
|
+
return this.setExpiry(id, { at: at.toISOString() });
|
|
292
335
|
}
|
|
293
336
|
// Removes the expiry of a document (PERSIST in Redis).
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return this.db.transport.request("POST", this.path("persist"), { _id: id });
|
|
337
|
+
persist(id) {
|
|
338
|
+
return this.send("POST", docPath("_persist", id));
|
|
297
339
|
}
|
|
298
340
|
// The seconds left (rounded up), -1 when the document never expires, null
|
|
299
341
|
// when it is missing or expired.
|
|
300
342
|
async ttl(id) {
|
|
301
|
-
await this.
|
|
302
|
-
|
|
303
|
-
const out = await this.db.transport.request("GET", this.path(`${encodeURIComponent(id)}/ttl`));
|
|
304
|
-
return out.ttl;
|
|
305
|
-
} catch (err) {
|
|
306
|
-
if (err instanceof NotFoundError) {
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
throw err;
|
|
310
|
-
}
|
|
343
|
+
const out = await orNull(this.send("GET", docPath("_ttl", id)));
|
|
344
|
+
return out === null ? null : out.ttl;
|
|
311
345
|
}
|
|
312
|
-
async setExpiry(body) {
|
|
313
|
-
await this.
|
|
314
|
-
|
|
315
|
-
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) };
|
|
316
349
|
}
|
|
317
350
|
// The document with this _id, or null.
|
|
318
351
|
async get(id) {
|
|
319
|
-
await this.
|
|
320
|
-
|
|
321
|
-
const doc = await this.db.transport.request("GET", this.path(encodeURIComponent(id)));
|
|
322
|
-
return this.decode(doc);
|
|
323
|
-
} catch (err) {
|
|
324
|
-
if (err instanceof NotFoundError) {
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
throw err;
|
|
328
|
-
}
|
|
352
|
+
const doc = await orNull(this.send("GET", docPath("_doc", id)));
|
|
353
|
+
return doc === null ? null : this.decode(doc);
|
|
329
354
|
}
|
|
330
|
-
|
|
331
|
-
|
|
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);
|
|
332
359
|
}
|
|
333
|
-
|
|
334
|
-
|
|
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);
|
|
335
364
|
}
|
|
336
365
|
not(w) {
|
|
337
|
-
return this
|
|
366
|
+
return new Query(this).not(w);
|
|
338
367
|
}
|
|
339
368
|
or(...ws) {
|
|
340
|
-
return this
|
|
369
|
+
return new Query(this).or(...ws);
|
|
341
370
|
}
|
|
342
371
|
sort(spec) {
|
|
343
|
-
return this
|
|
372
|
+
return new Query(this).sort(spec);
|
|
344
373
|
}
|
|
345
374
|
limit(n) {
|
|
346
|
-
return this
|
|
375
|
+
return new Query(this).limit(n);
|
|
347
376
|
}
|
|
348
377
|
skip(n) {
|
|
349
|
-
return this
|
|
378
|
+
return new Query(this).skip(n);
|
|
350
379
|
}
|
|
351
380
|
page(n, size) {
|
|
352
|
-
return this
|
|
381
|
+
return new Query(this).page(n, size);
|
|
353
382
|
}
|
|
354
383
|
after(token) {
|
|
355
|
-
return this
|
|
384
|
+
return new Query(this).after(token);
|
|
356
385
|
}
|
|
357
386
|
fields(...names) {
|
|
358
|
-
return this
|
|
387
|
+
return new Query(this).fields(...names);
|
|
359
388
|
}
|
|
360
389
|
all() {
|
|
361
|
-
return this
|
|
390
|
+
return new Query(this).all();
|
|
362
391
|
}
|
|
363
|
-
|
|
364
|
-
|
|
392
|
+
// How many documents match (POST /{coll}/_count).
|
|
393
|
+
count(w) {
|
|
394
|
+
return this.query(w).count();
|
|
365
395
|
}
|
|
366
396
|
first() {
|
|
367
|
-
return this
|
|
397
|
+
return new Query(this).first();
|
|
368
398
|
}
|
|
369
|
-
|
|
370
|
-
|
|
399
|
+
// Every row, page by page.
|
|
400
|
+
each() {
|
|
401
|
+
return new Query(this).each();
|
|
402
|
+
}
|
|
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
|
+
await this.ready();
|
|
408
|
+
const res = await this.db.transport.stream("POST", this.path("_search"), { ...body, versions: true });
|
|
409
|
+
const rows = [];
|
|
410
|
+
for await (const raw of res.rows()) {
|
|
411
|
+
rows.push(this.decode(raw));
|
|
412
|
+
}
|
|
413
|
+
return { rows, next: res.header("x-ndb-next") ?? "", total: Number(res.header("x-ndb-total") ?? 0) };
|
|
371
414
|
}
|
|
372
415
|
async aggregate(pipeline, opts = {}) {
|
|
373
416
|
await this.ready();
|
|
374
|
-
const res = await this.db.transport.stream("POST", this.path("
|
|
417
|
+
const res = await this.db.transport.stream("POST", this.path("_aggregate"), { pipeline, ...opts });
|
|
375
418
|
const rows = [];
|
|
376
419
|
for await (const row of res.rows()) {
|
|
377
420
|
rows.push(row);
|
|
@@ -380,14 +423,30 @@ var Model = class {
|
|
|
380
423
|
}
|
|
381
424
|
async *watch(consumer, opts = {}) {
|
|
382
425
|
await this.ready();
|
|
383
|
-
const body = { consumer, from: opts.from, limit: opts.limit,
|
|
384
|
-
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);
|
|
385
428
|
for await (const ev of watchEvents(res.rows(), consumer)) {
|
|
386
429
|
yield { ...ev, doc: ev.doc && this.decode(ev.doc) };
|
|
387
430
|
}
|
|
388
431
|
}
|
|
389
432
|
async ack(event) {
|
|
390
|
-
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;
|
|
391
450
|
}
|
|
392
451
|
decode(raw) {
|
|
393
452
|
const out = { ...raw };
|
|
@@ -399,6 +458,15 @@ var Model = class {
|
|
|
399
458
|
}
|
|
400
459
|
return out;
|
|
401
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
|
+
}
|
|
402
470
|
catalog() {
|
|
403
471
|
const entries = Object.entries(this.schema.fields);
|
|
404
472
|
return {
|
|
@@ -409,23 +477,26 @@ var Model = class {
|
|
|
409
477
|
};
|
|
410
478
|
}
|
|
411
479
|
};
|
|
412
|
-
function
|
|
413
|
-
|
|
414
|
-
for (const [name, value] of Object.entries(params)) {
|
|
415
|
-
if (value !== void 0 && value !== false) {
|
|
416
|
-
q.set(name, String(value));
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
const text = q.toString();
|
|
420
|
-
return text === "" ? "" : `?${text}`;
|
|
480
|
+
function docPath(action, id) {
|
|
481
|
+
return `${action}/${encodeURIComponent(id)}`;
|
|
421
482
|
}
|
|
422
|
-
function
|
|
483
|
+
function checkOps(ops) {
|
|
423
484
|
for (const key of Object.keys(ops)) {
|
|
424
485
|
if (!key.startsWith("$")) {
|
|
425
486
|
throw new InvalidUpdateError("invalid_update", `update operator "${key}" must start with $ (for example "$set")`);
|
|
426
487
|
}
|
|
427
488
|
}
|
|
428
|
-
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
|
+
}
|
|
429
500
|
}
|
|
430
501
|
|
|
431
502
|
// src/transport.ts
|
|
@@ -463,7 +534,7 @@ var notConnected = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOENT"]);
|
|
|
463
534
|
var retryBaseMs = 50;
|
|
464
535
|
var retryCapMs = 1e3;
|
|
465
536
|
var defaultReadyTimeoutMs = 1e4;
|
|
466
|
-
var readOps = /\/(
|
|
537
|
+
var readOps = /\/(_search|_count|_aggregate)$/;
|
|
467
538
|
var Transport = class {
|
|
468
539
|
client;
|
|
469
540
|
token;
|
|
@@ -482,9 +553,9 @@ var Transport = class {
|
|
|
482
553
|
throw new TypeError(`ndb: address "${target}" must be unix:///path or tcp://host:port`);
|
|
483
554
|
}
|
|
484
555
|
}
|
|
485
|
-
async stream(method, path, body, signal) {
|
|
556
|
+
async stream(method, path, body, signal, contentType) {
|
|
486
557
|
const res = await this.withReadyRetry(method, path, signal, async () => {
|
|
487
|
-
const r = await this.send(method, path, body, signal);
|
|
558
|
+
const r = await this.send(method, path, body, signal, contentType);
|
|
488
559
|
if (r.statusCode >= 300) {
|
|
489
560
|
throw await apiError(r);
|
|
490
561
|
}
|
|
@@ -498,8 +569,9 @@ var Transport = class {
|
|
|
498
569
|
header: (name) => headerOf(res.headers, name)
|
|
499
570
|
};
|
|
500
571
|
}
|
|
501
|
-
|
|
502
|
-
|
|
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);
|
|
503
575
|
const text = await res.body.setEncoding("utf8").toArray();
|
|
504
576
|
return text.length > 0 ? JSON.parse(text.join("")) : void 0;
|
|
505
577
|
}
|
|
@@ -527,20 +599,22 @@ var Transport = class {
|
|
|
527
599
|
}
|
|
528
600
|
}
|
|
529
601
|
}
|
|
530
|
-
|
|
531
|
-
|
|
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" };
|
|
532
605
|
if (this.token) {
|
|
533
606
|
headers.authorization = `Bearer ${this.token}`;
|
|
534
607
|
}
|
|
535
608
|
return headers;
|
|
536
609
|
}
|
|
537
|
-
async send(method, path, body, signal) {
|
|
610
|
+
async send(method, path, body, signal, contentType) {
|
|
611
|
+
const raw = contentType !== void 0;
|
|
538
612
|
const opts = {
|
|
539
613
|
method,
|
|
540
614
|
path,
|
|
541
615
|
signal,
|
|
542
|
-
headers: this.headers(),
|
|
543
|
-
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)
|
|
544
618
|
};
|
|
545
619
|
try {
|
|
546
620
|
return await this.client.request(opts);
|
|
@@ -607,11 +681,37 @@ var Ndb = class {
|
|
|
607
681
|
constructor(addr, opts) {
|
|
608
682
|
this.transport = new Transport(addr, opts);
|
|
609
683
|
}
|
|
610
|
-
|
|
611
|
-
|
|
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);
|
|
612
688
|
}
|
|
689
|
+
// Resolves when the daemon is ready (GET /_health).
|
|
613
690
|
async health() {
|
|
614
|
-
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());
|
|
615
715
|
}
|
|
616
716
|
request(method, path, body) {
|
|
617
717
|
return this.transport.request(method, path, body);
|
|
@@ -659,12 +759,12 @@ function seedCounter() {
|
|
|
659
759
|
return r[0] & 2047;
|
|
660
760
|
}
|
|
661
761
|
export {
|
|
762
|
+
Collection,
|
|
662
763
|
CursorError,
|
|
663
764
|
DuplicateIdError,
|
|
664
765
|
HistoryTruncatedError,
|
|
665
766
|
InvalidQueryError,
|
|
666
767
|
InvalidUpdateError,
|
|
667
|
-
Model,
|
|
668
768
|
Ndb,
|
|
669
769
|
NdbError,
|
|
670
770
|
NotFoundError,
|