@thadeu/ndb 0.1.1 → 0.1.2
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 +19 -21
- package/dist/index.cjs +50 -15
- package/dist/index.d.cts +38 -14
- package/dist/index.d.ts +38 -14
- package/dist/index.js +49 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,30 +86,28 @@ const results = await calls.insert([
|
|
|
86
86
|
// Find by id. Returns null when missing.
|
|
87
87
|
const row = await calls.find('c1')
|
|
88
88
|
|
|
89
|
-
//
|
|
90
|
-
|
|
89
|
+
// Atomic update by filter: filter first, then the write operators.
|
|
90
|
+
// _v in the filter is an optimistic lock.
|
|
91
|
+
const res = await calls.update({ call_id: 'c1', _v: row!._v }, { $set: { status: 'done' }, $unset: { duration: true } })
|
|
92
|
+
// res: { matched, modified, doc } (doc is the new row with _v; absent for many)
|
|
91
93
|
|
|
92
|
-
//
|
|
93
|
-
await
|
|
94
|
+
// A guarded decrement: exactly one of two racing calls wins.
|
|
95
|
+
await stock.update({ sku: 'a', qty: { $gte: 1 } }, { $inc: { qty: -1 } })
|
|
94
96
|
|
|
95
|
-
//
|
|
96
|
-
await calls.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`update` returns `{ id, v }` with the new version. A stale `ifVersion` throws `VersionConflictError`:
|
|
97
|
+
// Every match (the filter needs the id or an indexed field), or create it.
|
|
98
|
+
await calls.update({ status: 'failed' }, { $set: { status: 'retry' } }, { many: true })
|
|
99
|
+
await calls.update({ call_id: 'c9' }, { $inc: { duration: 1 } }, { upsert: true })
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
await calls.update('c1', { status: 'x' }, { ifVersion: row!._v })
|
|
106
|
-
} catch (err) {
|
|
107
|
-
if (err instanceof VersionConflictError) {
|
|
108
|
-
// Someone else wrote first. Read again and retry.
|
|
109
|
-
}
|
|
110
|
-
}
|
|
101
|
+
// Delete by filter: { deleted }.
|
|
102
|
+
await calls.delete({ call_id: 'c2' })
|
|
103
|
+
await calls.delete({ status: 'failed' }, { many: true })
|
|
111
104
|
```
|
|
112
105
|
|
|
106
|
+
- Operators: `$set`, `$unset`, `$inc`, `$push` (with `{ $each: [...] }`), `$pull`, `$min`, `$max`. Field names can be dot paths.
|
|
107
|
+
- Nothing matched is not an error: `matched` is 0 (for example when `_v` is stale, so read again and retry). `modified` is 0 when the operators leave the document as it is.
|
|
108
|
+
- `many` needs the id or an indexed field in the filter. `upsert` creates the document from the equality fields of the filter.
|
|
109
|
+
- A key of the operators without `$` throws `InvalidUpdateError` before anything is sent. The daemon contract errors (`unknown_operator`, `no_update`, `conflicting_update`, `invalid_field`, `unindexed_many`, `empty_filter`) are `InvalidUpdateError` too.
|
|
110
|
+
|
|
113
111
|
An insert with a duplicate id throws `DuplicateIdError`. A duplicate `unique` value throws `UniqueViolationError`. An insert without the id when the schema names one fails with code `missing_id`.
|
|
114
112
|
|
|
115
113
|
### Expiry (TTL like Redis)
|
|
@@ -122,7 +120,7 @@ const sessions = db.model('sessions', {
|
|
|
122
120
|
})
|
|
123
121
|
|
|
124
122
|
await sessions.insert({ sid: 's1' }, { expireIn: '1h' }) // or 3600; all docs of the call
|
|
125
|
-
await sessions.update('s1', { user: 'u' }, { expireIn: '30m' })
|
|
123
|
+
await sessions.update({ sid: 's1' }, { $set: { user: 'u' } }, { expireIn: '30m' })
|
|
126
124
|
|
|
127
125
|
const { v, expiresAt } = await sessions.expire('s1', '30m') // EXPIRE
|
|
128
126
|
await sessions.expireAt('s1', new Date(Date.now() + 3_600_000))
|
|
@@ -268,7 +266,6 @@ Daemon errors are `NdbError` or a subclass. They have `code` (the daemon code) a
|
|
|
268
266
|
|---|---|
|
|
269
267
|
| `UnauthorizedError` | `unauthorized` |
|
|
270
268
|
| `NotFoundError` | `not_found`, `unknown_collection` |
|
|
271
|
-
| `VersionConflictError` | `version_conflict` |
|
|
272
269
|
| `DuplicateIdError` | `duplicate_id` |
|
|
273
270
|
| `UniqueViolationError` | `unique_violation` |
|
|
274
271
|
| `InvalidQueryError` | `invalid_query`, `invalid_pipeline` |
|
|
@@ -276,6 +273,7 @@ Daemon errors are `NdbError` or a subclass. They have `code` (the daemon code) a
|
|
|
276
273
|
| `WatchLaggedError` | `watch_lagged` |
|
|
277
274
|
| `HistoryTruncatedError` | `history_truncated` |
|
|
278
275
|
| `TtlError` | `ttl_not_enabled`, `ttl_derived` |
|
|
276
|
+
| `InvalidUpdateError` | `unknown_operator`, `no_update`, `conflicting_update`, `invalid_field`, `unindexed_many`, `empty_filter`, `invalid_update` |
|
|
279
277
|
| `NdbError` | every other code: `not_ready`, `catalog_conflict`, `catalog_invalid`, `direct_collection`, `invalid_request`, `group_limit_exceeded`, `request_too_large`, `line_too_large`, `closed`, `invalid_position`, `http_status`, ... |
|
|
280
278
|
|
|
281
279
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
DuplicateIdError: () => DuplicateIdError,
|
|
25
25
|
HistoryTruncatedError: () => HistoryTruncatedError,
|
|
26
26
|
InvalidQueryError: () => InvalidQueryError,
|
|
27
|
+
InvalidUpdateError: () => InvalidUpdateError,
|
|
27
28
|
Model: () => Model,
|
|
28
29
|
Ndb: () => Ndb,
|
|
29
30
|
NdbError: () => NdbError,
|
|
@@ -32,7 +33,6 @@ __export(index_exports, {
|
|
|
32
33
|
TtlError: () => TtlError,
|
|
33
34
|
UnauthorizedError: () => UnauthorizedError,
|
|
34
35
|
UniqueViolationError: () => UniqueViolationError,
|
|
35
|
-
VersionConflictError: () => VersionConflictError,
|
|
36
36
|
WatchLaggedError: () => WatchLaggedError,
|
|
37
37
|
connect: () => connect,
|
|
38
38
|
makeError: () => makeError
|
|
@@ -54,8 +54,6 @@ var UnauthorizedError = class extends NdbError {
|
|
|
54
54
|
};
|
|
55
55
|
var NotFoundError = class extends NdbError {
|
|
56
56
|
};
|
|
57
|
-
var VersionConflictError = class extends NdbError {
|
|
58
|
-
};
|
|
59
57
|
var DuplicateIdError = class extends NdbError {
|
|
60
58
|
};
|
|
61
59
|
var UniqueViolationError = class extends NdbError {
|
|
@@ -68,13 +66,14 @@ var WatchLaggedError = class extends NdbError {
|
|
|
68
66
|
};
|
|
69
67
|
var HistoryTruncatedError = class extends NdbError {
|
|
70
68
|
};
|
|
69
|
+
var InvalidUpdateError = class extends NdbError {
|
|
70
|
+
};
|
|
71
71
|
var TtlError = class extends NdbError {
|
|
72
72
|
};
|
|
73
73
|
var subclasses = {
|
|
74
74
|
unauthorized: UnauthorizedError,
|
|
75
75
|
not_found: NotFoundError,
|
|
76
76
|
unknown_collection: NotFoundError,
|
|
77
|
-
version_conflict: VersionConflictError,
|
|
78
77
|
duplicate_id: DuplicateIdError,
|
|
79
78
|
unique_violation: UniqueViolationError,
|
|
80
79
|
invalid_query: InvalidQueryError,
|
|
@@ -85,7 +84,13 @@ var subclasses = {
|
|
|
85
84
|
watch_lagged: WatchLaggedError,
|
|
86
85
|
history_truncated: HistoryTruncatedError,
|
|
87
86
|
ttl_not_enabled: TtlError,
|
|
88
|
-
ttl_derived: TtlError
|
|
87
|
+
ttl_derived: TtlError,
|
|
88
|
+
unknown_operator: InvalidUpdateError,
|
|
89
|
+
no_update: InvalidUpdateError,
|
|
90
|
+
conflicting_update: InvalidUpdateError,
|
|
91
|
+
invalid_field: InvalidUpdateError,
|
|
92
|
+
unindexed_many: InvalidUpdateError,
|
|
93
|
+
empty_filter: InvalidUpdateError
|
|
89
94
|
};
|
|
90
95
|
function makeError(code, message, status = 0) {
|
|
91
96
|
const Cls = subclasses[code] ?? NdbError;
|
|
@@ -291,23 +296,35 @@ var Model = class {
|
|
|
291
296
|
create(docs, opts = {}) {
|
|
292
297
|
return this.insert(docs, opts);
|
|
293
298
|
}
|
|
294
|
-
|
|
299
|
+
// Atomic update by filter. The filter and the operators are sent as one
|
|
300
|
+
// body; the daemon matches and writes in one step on the newest state, so
|
|
301
|
+
// concurrent updates never interleave. A version guard is a filter:
|
|
302
|
+
// { call_id: 'c1', _v: 7 }. Matching nothing is not an error: matched is 0.
|
|
303
|
+
async update(filter, ops, opts = {}) {
|
|
295
304
|
await this.ready();
|
|
296
|
-
const
|
|
297
|
-
|
|
305
|
+
const body = mergeUpdate(filter, ops);
|
|
306
|
+
const query = writeQuery({ many: opts.many, upsert: opts.upsert, expire_in: opts.expireIn });
|
|
307
|
+
const out = await this.db.transport.request(
|
|
308
|
+
"POST",
|
|
309
|
+
this.path("update") + query,
|
|
310
|
+
body
|
|
311
|
+
);
|
|
312
|
+
return { matched: out.matched, modified: out.modified, doc: out.doc && this.decode(out.doc) };
|
|
298
313
|
}
|
|
299
|
-
|
|
314
|
+
// Deletes the documents that the filter matches: one, or every match with
|
|
315
|
+
// many (same rule as update).
|
|
316
|
+
async delete(filter, opts = {}) {
|
|
300
317
|
await this.ready();
|
|
301
|
-
|
|
318
|
+
return this.db.transport.request("POST", this.path("delete") + writeQuery({ many: opts.many }), toWhere(filter));
|
|
302
319
|
}
|
|
303
320
|
// Makes a document expire, like EXPIRE in Redis: seconds or "30m". It throws
|
|
304
321
|
// NotFoundError for a missing or expired document, and TtlError when the
|
|
305
322
|
// schema has no expires field.
|
|
306
|
-
expire(id, duration
|
|
307
|
-
return this.setExpiry({ id, in: duration
|
|
323
|
+
expire(id, duration) {
|
|
324
|
+
return this.setExpiry({ id, in: duration });
|
|
308
325
|
}
|
|
309
|
-
expireAt(id, at
|
|
310
|
-
return this.setExpiry({ id, at: at.toISOString()
|
|
326
|
+
expireAt(id, at) {
|
|
327
|
+
return this.setExpiry({ id, at: at.toISOString() });
|
|
311
328
|
}
|
|
312
329
|
// Removes the expiry of a document (PERSIST in Redis).
|
|
313
330
|
async persist(id) {
|
|
@@ -419,6 +436,24 @@ var Model = class {
|
|
|
419
436
|
};
|
|
420
437
|
}
|
|
421
438
|
};
|
|
439
|
+
function writeQuery(params) {
|
|
440
|
+
const q = new URLSearchParams();
|
|
441
|
+
for (const [name, value] of Object.entries(params)) {
|
|
442
|
+
if (value !== void 0 && value !== false) {
|
|
443
|
+
q.set(name, String(value));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const text = q.toString();
|
|
447
|
+
return text === "" ? "" : `?${text}`;
|
|
448
|
+
}
|
|
449
|
+
function mergeUpdate(filter, ops) {
|
|
450
|
+
for (const key of Object.keys(ops)) {
|
|
451
|
+
if (!key.startsWith("$")) {
|
|
452
|
+
throw new InvalidUpdateError("invalid_update", `update operator "${key}" must start with $ (for example "$set")`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return { ...toWhere(filter), ...ops };
|
|
456
|
+
}
|
|
422
457
|
|
|
423
458
|
// src/transport.ts
|
|
424
459
|
var import_undici = require("undici");
|
|
@@ -624,6 +659,7 @@ function connect(addr, opts) {
|
|
|
624
659
|
DuplicateIdError,
|
|
625
660
|
HistoryTruncatedError,
|
|
626
661
|
InvalidQueryError,
|
|
662
|
+
InvalidUpdateError,
|
|
627
663
|
Model,
|
|
628
664
|
Ndb,
|
|
629
665
|
NdbError,
|
|
@@ -632,7 +668,6 @@ function connect(addr, opts) {
|
|
|
632
668
|
TtlError,
|
|
633
669
|
UnauthorizedError,
|
|
634
670
|
UniqueViolationError,
|
|
635
|
-
VersionConflictError,
|
|
636
671
|
WatchLaggedError,
|
|
637
672
|
connect,
|
|
638
673
|
makeError
|
package/dist/index.d.cts
CHANGED
|
@@ -66,14 +66,38 @@ interface WriteResult {
|
|
|
66
66
|
id: string;
|
|
67
67
|
v: number;
|
|
68
68
|
}
|
|
69
|
-
interface WriteOptions {
|
|
70
|
-
ifVersion?: number;
|
|
71
|
-
}
|
|
72
69
|
type Duration = number | string;
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
type Filter<F extends Fields> = Where<F> & {
|
|
71
|
+
_v?: number | Ops<number>;
|
|
72
|
+
$and?: Filter<F>[];
|
|
73
|
+
$or?: Filter<F>[];
|
|
74
|
+
$nor?: Filter<F>[];
|
|
75
|
+
};
|
|
76
|
+
interface UpdateOps<F extends Fields = Fields> {
|
|
77
|
+
$set?: Input<F> & Record<string, unknown>;
|
|
78
|
+
$unset?: Record<string, true | ''>;
|
|
79
|
+
$inc?: Record<string, number>;
|
|
80
|
+
$push?: Record<string, unknown>;
|
|
81
|
+
$pull?: Record<string, unknown>;
|
|
82
|
+
$min?: Record<string, number | string>;
|
|
83
|
+
$max?: Record<string, number | string>;
|
|
84
|
+
}
|
|
85
|
+
interface UpdateOptions {
|
|
86
|
+
many?: boolean;
|
|
87
|
+
upsert?: boolean;
|
|
75
88
|
expireIn?: Duration;
|
|
76
89
|
}
|
|
90
|
+
interface DeleteOptions {
|
|
91
|
+
many?: boolean;
|
|
92
|
+
}
|
|
93
|
+
interface UpdateResult<F extends Fields = Fields> {
|
|
94
|
+
matched: number;
|
|
95
|
+
modified: number;
|
|
96
|
+
doc?: Row<F>;
|
|
97
|
+
}
|
|
98
|
+
interface DeleteResult {
|
|
99
|
+
deleted: number;
|
|
100
|
+
}
|
|
77
101
|
interface InsertOptions {
|
|
78
102
|
expireIn?: Duration;
|
|
79
103
|
}
|
|
@@ -150,10 +174,10 @@ declare class Model<F extends Fields> {
|
|
|
150
174
|
ready(): Promise<void>;
|
|
151
175
|
insert(docs: Input<F> | Input<F>[], opts?: InsertOptions): Promise<WriteResult[]>;
|
|
152
176
|
create(docs: Input<F> | Input<F>[], opts?: InsertOptions): Promise<WriteResult[]>;
|
|
153
|
-
update(
|
|
154
|
-
delete(
|
|
155
|
-
expire(id: string, duration: Duration
|
|
156
|
-
expireAt(id: string, at: Date
|
|
177
|
+
update(filter: Filter<F>, ops: UpdateOps<F>, opts?: UpdateOptions): Promise<UpdateResult<F>>;
|
|
178
|
+
delete(filter: Filter<F>, opts?: DeleteOptions): Promise<DeleteResult>;
|
|
179
|
+
expire(id: string, duration: Duration): Promise<ExpireResult>;
|
|
180
|
+
expireAt(id: string, at: Date): Promise<ExpireResult>;
|
|
157
181
|
persist(id: string): Promise<WriteResult>;
|
|
158
182
|
ttl(id: string): Promise<number | null>;
|
|
159
183
|
private setExpiry;
|
|
@@ -215,7 +239,7 @@ declare class Ndb {
|
|
|
215
239
|
}
|
|
216
240
|
declare function connect(addr?: string, opts?: ConnectOptions): Ndb;
|
|
217
241
|
|
|
218
|
-
type ErrorCode = 'unauthorized' | 'not_found' | '
|
|
242
|
+
type ErrorCode = 'unauthorized' | 'not_found' | 'duplicate_id' | 'unique_violation' | 'catalog_conflict' | 'catalog_invalid' | 'unknown_collection' | 'direct_collection' | 'invalid_request' | 'invalid_query' | 'invalid_pipeline' | 'group_limit_exceeded' | 'cursor_invalid' | 'cursor_mismatch' | 'cursor_expired' | 'request_too_large' | 'line_too_large' | 'not_ready' | 'closed' | 'watch_lagged' | 'invalid_position' | 'history_truncated' | 'ttl_not_enabled' | 'ttl_derived' | 'unknown_operator' | 'no_update' | 'conflicting_update' | 'invalid_field' | 'unindexed_many' | 'empty_filter' | 'invalid_update' | 'http_status' | (string & {});
|
|
219
243
|
declare class NdbError extends Error {
|
|
220
244
|
readonly code: ErrorCode;
|
|
221
245
|
readonly status: number;
|
|
@@ -225,8 +249,6 @@ declare class UnauthorizedError extends NdbError {
|
|
|
225
249
|
}
|
|
226
250
|
declare class NotFoundError extends NdbError {
|
|
227
251
|
}
|
|
228
|
-
declare class VersionConflictError extends NdbError {
|
|
229
|
-
}
|
|
230
252
|
declare class DuplicateIdError extends NdbError {
|
|
231
253
|
}
|
|
232
254
|
declare class UniqueViolationError extends NdbError {
|
|
@@ -239,9 +261,11 @@ declare class WatchLaggedError extends NdbError {
|
|
|
239
261
|
}
|
|
240
262
|
declare class HistoryTruncatedError extends NdbError {
|
|
241
263
|
}
|
|
264
|
+
declare class InvalidUpdateError extends NdbError {
|
|
265
|
+
}
|
|
242
266
|
declare class TtlError extends NdbError {
|
|
243
267
|
}
|
|
244
268
|
declare function makeError(code: ErrorCode, message: string, status?: number): NdbError;
|
|
245
269
|
|
|
246
|
-
export { CursorError, DuplicateIdError, HistoryTruncatedError, InvalidQueryError, Model, Ndb, NdbError, NotFoundError, Query, TtlError, UnauthorizedError, UniqueViolationError,
|
|
247
|
-
export type { AggregateOptions, AggregateResult, ConnectOptions, Doc, Duration, ErrorCode, ExpireResult, FieldDef, FieldType, Fields, Input, InsertOptions, Ops, Row, Schema, SortSpec, TypeMap, UpdateOptions, WatchEvent, WatchOptions, Where,
|
|
270
|
+
export { CursorError, DuplicateIdError, HistoryTruncatedError, InvalidQueryError, InvalidUpdateError, Model, Ndb, NdbError, NotFoundError, Query, TtlError, UnauthorizedError, UniqueViolationError, WatchLaggedError, connect, makeError };
|
|
271
|
+
export type { AggregateOptions, AggregateResult, ConnectOptions, DeleteOptions, DeleteResult, Doc, Duration, ErrorCode, ExpireResult, FieldDef, FieldType, Fields, Filter, Input, InsertOptions, Ops, Row, Schema, SortSpec, TypeMap, UpdateOps, UpdateOptions, UpdateResult, WatchEvent, WatchOptions, Where, WriteResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -66,14 +66,38 @@ interface WriteResult {
|
|
|
66
66
|
id: string;
|
|
67
67
|
v: number;
|
|
68
68
|
}
|
|
69
|
-
interface WriteOptions {
|
|
70
|
-
ifVersion?: number;
|
|
71
|
-
}
|
|
72
69
|
type Duration = number | string;
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
type Filter<F extends Fields> = Where<F> & {
|
|
71
|
+
_v?: number | Ops<number>;
|
|
72
|
+
$and?: Filter<F>[];
|
|
73
|
+
$or?: Filter<F>[];
|
|
74
|
+
$nor?: Filter<F>[];
|
|
75
|
+
};
|
|
76
|
+
interface UpdateOps<F extends Fields = Fields> {
|
|
77
|
+
$set?: Input<F> & Record<string, unknown>;
|
|
78
|
+
$unset?: Record<string, true | ''>;
|
|
79
|
+
$inc?: Record<string, number>;
|
|
80
|
+
$push?: Record<string, unknown>;
|
|
81
|
+
$pull?: Record<string, unknown>;
|
|
82
|
+
$min?: Record<string, number | string>;
|
|
83
|
+
$max?: Record<string, number | string>;
|
|
84
|
+
}
|
|
85
|
+
interface UpdateOptions {
|
|
86
|
+
many?: boolean;
|
|
87
|
+
upsert?: boolean;
|
|
75
88
|
expireIn?: Duration;
|
|
76
89
|
}
|
|
90
|
+
interface DeleteOptions {
|
|
91
|
+
many?: boolean;
|
|
92
|
+
}
|
|
93
|
+
interface UpdateResult<F extends Fields = Fields> {
|
|
94
|
+
matched: number;
|
|
95
|
+
modified: number;
|
|
96
|
+
doc?: Row<F>;
|
|
97
|
+
}
|
|
98
|
+
interface DeleteResult {
|
|
99
|
+
deleted: number;
|
|
100
|
+
}
|
|
77
101
|
interface InsertOptions {
|
|
78
102
|
expireIn?: Duration;
|
|
79
103
|
}
|
|
@@ -150,10 +174,10 @@ declare class Model<F extends Fields> {
|
|
|
150
174
|
ready(): Promise<void>;
|
|
151
175
|
insert(docs: Input<F> | Input<F>[], opts?: InsertOptions): Promise<WriteResult[]>;
|
|
152
176
|
create(docs: Input<F> | Input<F>[], opts?: InsertOptions): Promise<WriteResult[]>;
|
|
153
|
-
update(
|
|
154
|
-
delete(
|
|
155
|
-
expire(id: string, duration: Duration
|
|
156
|
-
expireAt(id: string, at: Date
|
|
177
|
+
update(filter: Filter<F>, ops: UpdateOps<F>, opts?: UpdateOptions): Promise<UpdateResult<F>>;
|
|
178
|
+
delete(filter: Filter<F>, opts?: DeleteOptions): Promise<DeleteResult>;
|
|
179
|
+
expire(id: string, duration: Duration): Promise<ExpireResult>;
|
|
180
|
+
expireAt(id: string, at: Date): Promise<ExpireResult>;
|
|
157
181
|
persist(id: string): Promise<WriteResult>;
|
|
158
182
|
ttl(id: string): Promise<number | null>;
|
|
159
183
|
private setExpiry;
|
|
@@ -215,7 +239,7 @@ declare class Ndb {
|
|
|
215
239
|
}
|
|
216
240
|
declare function connect(addr?: string, opts?: ConnectOptions): Ndb;
|
|
217
241
|
|
|
218
|
-
type ErrorCode = 'unauthorized' | 'not_found' | '
|
|
242
|
+
type ErrorCode = 'unauthorized' | 'not_found' | 'duplicate_id' | 'unique_violation' | 'catalog_conflict' | 'catalog_invalid' | 'unknown_collection' | 'direct_collection' | 'invalid_request' | 'invalid_query' | 'invalid_pipeline' | 'group_limit_exceeded' | 'cursor_invalid' | 'cursor_mismatch' | 'cursor_expired' | 'request_too_large' | 'line_too_large' | 'not_ready' | 'closed' | 'watch_lagged' | 'invalid_position' | 'history_truncated' | 'ttl_not_enabled' | 'ttl_derived' | 'unknown_operator' | 'no_update' | 'conflicting_update' | 'invalid_field' | 'unindexed_many' | 'empty_filter' | 'invalid_update' | 'http_status' | (string & {});
|
|
219
243
|
declare class NdbError extends Error {
|
|
220
244
|
readonly code: ErrorCode;
|
|
221
245
|
readonly status: number;
|
|
@@ -225,8 +249,6 @@ declare class UnauthorizedError extends NdbError {
|
|
|
225
249
|
}
|
|
226
250
|
declare class NotFoundError extends NdbError {
|
|
227
251
|
}
|
|
228
|
-
declare class VersionConflictError extends NdbError {
|
|
229
|
-
}
|
|
230
252
|
declare class DuplicateIdError extends NdbError {
|
|
231
253
|
}
|
|
232
254
|
declare class UniqueViolationError extends NdbError {
|
|
@@ -239,9 +261,11 @@ declare class WatchLaggedError extends NdbError {
|
|
|
239
261
|
}
|
|
240
262
|
declare class HistoryTruncatedError extends NdbError {
|
|
241
263
|
}
|
|
264
|
+
declare class InvalidUpdateError extends NdbError {
|
|
265
|
+
}
|
|
242
266
|
declare class TtlError extends NdbError {
|
|
243
267
|
}
|
|
244
268
|
declare function makeError(code: ErrorCode, message: string, status?: number): NdbError;
|
|
245
269
|
|
|
246
|
-
export { CursorError, DuplicateIdError, HistoryTruncatedError, InvalidQueryError, Model, Ndb, NdbError, NotFoundError, Query, TtlError, UnauthorizedError, UniqueViolationError,
|
|
247
|
-
export type { AggregateOptions, AggregateResult, ConnectOptions, Doc, Duration, ErrorCode, ExpireResult, FieldDef, FieldType, Fields, Input, InsertOptions, Ops, Row, Schema, SortSpec, TypeMap, UpdateOptions, WatchEvent, WatchOptions, Where,
|
|
270
|
+
export { CursorError, DuplicateIdError, HistoryTruncatedError, InvalidQueryError, InvalidUpdateError, Model, Ndb, NdbError, NotFoundError, Query, TtlError, UnauthorizedError, UniqueViolationError, WatchLaggedError, connect, makeError };
|
|
271
|
+
export type { AggregateOptions, AggregateResult, ConnectOptions, DeleteOptions, DeleteResult, Doc, Duration, ErrorCode, ExpireResult, FieldDef, FieldType, Fields, Filter, Input, InsertOptions, Ops, Row, Schema, SortSpec, TypeMap, UpdateOps, UpdateOptions, UpdateResult, WatchEvent, WatchOptions, Where, WriteResult };
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,6 @@ var UnauthorizedError = class extends NdbError {
|
|
|
13
13
|
};
|
|
14
14
|
var NotFoundError = class extends NdbError {
|
|
15
15
|
};
|
|
16
|
-
var VersionConflictError = class extends NdbError {
|
|
17
|
-
};
|
|
18
16
|
var DuplicateIdError = class extends NdbError {
|
|
19
17
|
};
|
|
20
18
|
var UniqueViolationError = class extends NdbError {
|
|
@@ -27,13 +25,14 @@ var WatchLaggedError = class extends NdbError {
|
|
|
27
25
|
};
|
|
28
26
|
var HistoryTruncatedError = class extends NdbError {
|
|
29
27
|
};
|
|
28
|
+
var InvalidUpdateError = class extends NdbError {
|
|
29
|
+
};
|
|
30
30
|
var TtlError = class extends NdbError {
|
|
31
31
|
};
|
|
32
32
|
var subclasses = {
|
|
33
33
|
unauthorized: UnauthorizedError,
|
|
34
34
|
not_found: NotFoundError,
|
|
35
35
|
unknown_collection: NotFoundError,
|
|
36
|
-
version_conflict: VersionConflictError,
|
|
37
36
|
duplicate_id: DuplicateIdError,
|
|
38
37
|
unique_violation: UniqueViolationError,
|
|
39
38
|
invalid_query: InvalidQueryError,
|
|
@@ -44,7 +43,13 @@ var subclasses = {
|
|
|
44
43
|
watch_lagged: WatchLaggedError,
|
|
45
44
|
history_truncated: HistoryTruncatedError,
|
|
46
45
|
ttl_not_enabled: TtlError,
|
|
47
|
-
ttl_derived: TtlError
|
|
46
|
+
ttl_derived: TtlError,
|
|
47
|
+
unknown_operator: InvalidUpdateError,
|
|
48
|
+
no_update: InvalidUpdateError,
|
|
49
|
+
conflicting_update: InvalidUpdateError,
|
|
50
|
+
invalid_field: InvalidUpdateError,
|
|
51
|
+
unindexed_many: InvalidUpdateError,
|
|
52
|
+
empty_filter: InvalidUpdateError
|
|
48
53
|
};
|
|
49
54
|
function makeError(code, message, status = 0) {
|
|
50
55
|
const Cls = subclasses[code] ?? NdbError;
|
|
@@ -250,23 +255,35 @@ var Model = class {
|
|
|
250
255
|
create(docs, opts = {}) {
|
|
251
256
|
return this.insert(docs, opts);
|
|
252
257
|
}
|
|
253
|
-
|
|
258
|
+
// Atomic update by filter. The filter and the operators are sent as one
|
|
259
|
+
// body; the daemon matches and writes in one step on the newest state, so
|
|
260
|
+
// concurrent updates never interleave. A version guard is a filter:
|
|
261
|
+
// { call_id: 'c1', _v: 7 }. Matching nothing is not an error: matched is 0.
|
|
262
|
+
async update(filter, ops, opts = {}) {
|
|
254
263
|
await this.ready();
|
|
255
|
-
const
|
|
256
|
-
|
|
264
|
+
const body = mergeUpdate(filter, ops);
|
|
265
|
+
const query = writeQuery({ many: opts.many, upsert: opts.upsert, expire_in: opts.expireIn });
|
|
266
|
+
const out = await this.db.transport.request(
|
|
267
|
+
"POST",
|
|
268
|
+
this.path("update") + query,
|
|
269
|
+
body
|
|
270
|
+
);
|
|
271
|
+
return { matched: out.matched, modified: out.modified, doc: out.doc && this.decode(out.doc) };
|
|
257
272
|
}
|
|
258
|
-
|
|
273
|
+
// Deletes the documents that the filter matches: one, or every match with
|
|
274
|
+
// many (same rule as update).
|
|
275
|
+
async delete(filter, opts = {}) {
|
|
259
276
|
await this.ready();
|
|
260
|
-
|
|
277
|
+
return this.db.transport.request("POST", this.path("delete") + writeQuery({ many: opts.many }), toWhere(filter));
|
|
261
278
|
}
|
|
262
279
|
// Makes a document expire, like EXPIRE in Redis: seconds or "30m". It throws
|
|
263
280
|
// NotFoundError for a missing or expired document, and TtlError when the
|
|
264
281
|
// schema has no expires field.
|
|
265
|
-
expire(id, duration
|
|
266
|
-
return this.setExpiry({ id, in: duration
|
|
282
|
+
expire(id, duration) {
|
|
283
|
+
return this.setExpiry({ id, in: duration });
|
|
267
284
|
}
|
|
268
|
-
expireAt(id, at
|
|
269
|
-
return this.setExpiry({ id, at: at.toISOString()
|
|
285
|
+
expireAt(id, at) {
|
|
286
|
+
return this.setExpiry({ id, at: at.toISOString() });
|
|
270
287
|
}
|
|
271
288
|
// Removes the expiry of a document (PERSIST in Redis).
|
|
272
289
|
async persist(id) {
|
|
@@ -378,6 +395,24 @@ var Model = class {
|
|
|
378
395
|
};
|
|
379
396
|
}
|
|
380
397
|
};
|
|
398
|
+
function writeQuery(params) {
|
|
399
|
+
const q = new URLSearchParams();
|
|
400
|
+
for (const [name, value] of Object.entries(params)) {
|
|
401
|
+
if (value !== void 0 && value !== false) {
|
|
402
|
+
q.set(name, String(value));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const text = q.toString();
|
|
406
|
+
return text === "" ? "" : `?${text}`;
|
|
407
|
+
}
|
|
408
|
+
function mergeUpdate(filter, ops) {
|
|
409
|
+
for (const key of Object.keys(ops)) {
|
|
410
|
+
if (!key.startsWith("$")) {
|
|
411
|
+
throw new InvalidUpdateError("invalid_update", `update operator "${key}" must start with $ (for example "$set")`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return { ...toWhere(filter), ...ops };
|
|
415
|
+
}
|
|
381
416
|
|
|
382
417
|
// src/transport.ts
|
|
383
418
|
import { Pool } from "undici";
|
|
@@ -582,6 +617,7 @@ export {
|
|
|
582
617
|
DuplicateIdError,
|
|
583
618
|
HistoryTruncatedError,
|
|
584
619
|
InvalidQueryError,
|
|
620
|
+
InvalidUpdateError,
|
|
585
621
|
Model,
|
|
586
622
|
Ndb,
|
|
587
623
|
NdbError,
|
|
@@ -590,7 +626,6 @@ export {
|
|
|
590
626
|
TtlError,
|
|
591
627
|
UnauthorizedError,
|
|
592
628
|
UniqueViolationError,
|
|
593
|
-
VersionConflictError,
|
|
594
629
|
WatchLaggedError,
|
|
595
630
|
connect,
|
|
596
631
|
makeError
|