toilscript 0.1.62 → 0.1.64
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/dist/cli.generated.d.ts +6 -5
- package/dist/cli.js +288 -12
- package/dist/cli.js.map +2 -2
- package/dist/importmap.json +2 -2
- package/dist/toilscript.generated.d.ts +6 -5
- package/dist/toilscript.js +37 -37
- package/dist/toilscript.js.map +3 -3
- package/dist/web.js +3 -3
- package/package.json +8 -2
- package/std/assembly/bindings/toildb.ts +50 -0
- package/std/assembly/log.ts +14 -0
- package/std/assembly/toildb.ts +221 -9
- package/std/assembly/toilscript.d.ts +54 -3
package/dist/web.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var ASSEMBLYSCRIPT_VERSION = "0.1.
|
|
1
|
+
var ASSEMBLYSCRIPT_VERSION = "0.1.64";
|
|
2
2
|
var ASSEMBLYSCRIPT_IMPORTMAP = {
|
|
3
3
|
"imports": {
|
|
4
|
-
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
5
|
-
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
4
|
+
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.64/dist/toilscript.js",
|
|
5
|
+
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.64/dist/cli.js",
|
|
6
6
|
"binaryen": "https://cdn.jsdelivr.net/npm/binaryen@130.0.0/index.js",
|
|
7
7
|
"long": "https://cdn.jsdelivr.net/npm/long@5.3.2/index.js"
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"toilscript",
|
|
9
9
|
"wasm"
|
|
10
10
|
],
|
|
11
|
-
"version": "0.1.
|
|
11
|
+
"version": "0.1.64",
|
|
12
12
|
"author": "Daniel Wirtz <dcode+assemblyscript@dcode.io>",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"homepage": "https://github.com/dacely-cloud/toilscript",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"toilinit": "./bin/toilinit.js"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
|
+
"prepare": "node scripts/build",
|
|
75
76
|
"check": "npm run check:config && npm run check:import && npm run lint",
|
|
76
77
|
"check:config": "tsc --noEmit -p src --diagnostics --listFiles",
|
|
77
78
|
"check:import": "tsc --noEmit --skipLibCheck --target ESNEXT --module nodenext --moduleResolution nodenext --experimentalDecorators tests/import/index",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"prepublishOnly": "node scripts/build",
|
|
81
82
|
"watch": "node scripts/build --watch",
|
|
82
83
|
"coverage": "npx c8 -- npm test",
|
|
83
|
-
"test": "npm run test:parser && npm run test:compiler -- --parallel && npm run test:browser && npm run test:toilconfig && npm run test:transform && npm run test:cli && npm run test:json && npm run test:data && npm run test:dbresolve && npm run test:dbstatic && npm run test:routekinds && npm run test:derive && npm run test:streams && npm run test:daemon && npm run test:dberror",
|
|
84
|
+
"test": "npm run test:parser && npm run test:compiler -- --parallel && npm run test:browser && npm run test:toilconfig && npm run test:transform && npm run test:cli && npm run test:json && npm run test:data && npm run test:dbresolve && npm run test:dbstatic && npm run test:dbtypes && npm run test:routekinds && npm run test:derive && npm run test:streams && npm run test:daemon && npm run test:dberror && npm run test:events && npm run test:uniquebatch && npm run test:membership && npm run test:event-redaction",
|
|
84
85
|
"test:parser": "node --enable-source-maps tests/parser",
|
|
85
86
|
"test:compiler": "node --enable-source-maps --no-warnings tests/compiler",
|
|
86
87
|
"test:browser": "node --enable-source-maps tests/browser",
|
|
@@ -93,10 +94,15 @@
|
|
|
93
94
|
"test:data": "node tests/data/run.mjs",
|
|
94
95
|
"test:dbresolve": "node tests/dbresolve/run.mjs",
|
|
95
96
|
"test:dbstatic": "node tests/dbstatic/run.mjs",
|
|
97
|
+
"test:dbtypes": "node tests/dbtypes/run.mjs",
|
|
96
98
|
"test:routekinds": "node tests/routekinds/run.mjs",
|
|
97
99
|
"test:derive": "node tests/derive/run.mjs",
|
|
98
100
|
"test:streams": "node tests/streams/run.mjs && node tests/streams/catalog.mjs && node tests/streams/codegen.mjs",
|
|
101
|
+
"test:membership": "node tests/membership/run.mjs",
|
|
102
|
+
"test:event-redaction": "node tests/event-redaction/run.mjs",
|
|
99
103
|
"test:daemon": "node tests/daemon/run.mjs",
|
|
104
|
+
"test:uniquebatch": "node tests/uniquebatch/run.mjs",
|
|
105
|
+
"test:events": "node tests/events/run.mjs",
|
|
100
106
|
"test:dberror": "node tests/dberror/run.mjs",
|
|
101
107
|
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
|
|
102
108
|
"asbuild:debug": "node bin/toilscript --config src/toilconfig.json --target debug",
|
|
@@ -71,6 +71,24 @@ export namespace toildbHost {
|
|
|
71
71
|
idemPtr: usize
|
|
72
72
|
): i32;
|
|
73
73
|
|
|
74
|
+
// Caller-observed byte CAS: expectedLen=-1 expects absence. 1 applied, 0 mismatch.
|
|
75
|
+
// @ts-ignore: decorator
|
|
76
|
+
@external("env", "data.compare_exchange")
|
|
77
|
+
export declare function compareExchange(
|
|
78
|
+
handle: u32, keyPtr: usize, keyLen: i32,
|
|
79
|
+
expectedPtr: usize, expectedLen: i32, valPtr: usize, valLen: i32
|
|
80
|
+
): i32;
|
|
81
|
+
|
|
82
|
+
// @ts-ignore: decorator
|
|
83
|
+
@external("env", "data.compare_delete")
|
|
84
|
+
export declare function compareDelete(
|
|
85
|
+
handle: u32, keyPtr: usize, keyLen: i32, expectedPtr: usize, expectedLen: i32
|
|
86
|
+
): i32;
|
|
87
|
+
|
|
88
|
+
// @ts-ignore: decorator
|
|
89
|
+
@external("env", "data.get_current")
|
|
90
|
+
export declare function getCurrent(handle: u32, keyPtr: usize, keyLen: i32): i32;
|
|
91
|
+
|
|
74
92
|
// record.upsert -> 0 created | 1 updated | 2 @unique-conflict (not written) |
|
|
75
93
|
// negative error. A single blind last-writer-wins put: creates the row if
|
|
76
94
|
// absent, overwrites it if present, in ONE op (no create-then-patch round trip).
|
|
@@ -141,6 +159,11 @@ export namespace toildbHost {
|
|
|
141
159
|
idemPtr: usize
|
|
142
160
|
): i32;
|
|
143
161
|
|
|
162
|
+
// Remove a view key (Action/Derive/Job), idempotently.
|
|
163
|
+
// @ts-ignore: decorator
|
|
164
|
+
@external("env", "data.view_delete")
|
|
165
|
+
export declare function viewDelete(handle: u32, keyPtr: usize, keyLen: i32, idemPtr: usize): i32;
|
|
166
|
+
|
|
144
167
|
// membership.contains -> 1 present | 0 absent | negative error.
|
|
145
168
|
// @ts-ignore: decorator
|
|
146
169
|
@external("env", "data.membership_contains")
|
|
@@ -168,6 +191,13 @@ export namespace toildbHost {
|
|
|
168
191
|
@external("env", "data.membership_list")
|
|
169
192
|
export declare function membershipList(handle: u32, setPtr: usize, setLen: i32, limit: i32): i32;
|
|
170
193
|
|
|
194
|
+
// Ordered membership page: list frame + optional length-prefixed raw cursor.
|
|
195
|
+
// @ts-ignore: decorator
|
|
196
|
+
@external("env", "data.membership_page")
|
|
197
|
+
export declare function membershipPage(
|
|
198
|
+
handle: u32, setPtr: usize, setLen: i32, cursorPtr: usize, cursorLen: i32, limit: i32
|
|
199
|
+
): i32;
|
|
200
|
+
|
|
171
201
|
// capacity.set_total(total: i64) -> 0 ok | negative error (job/derive only).
|
|
172
202
|
// @ts-ignore: decorator
|
|
173
203
|
@external("env", "data.capacity_set_total")
|
|
@@ -230,6 +260,26 @@ export namespace toildbHost {
|
|
|
230
260
|
idemPtr: usize
|
|
231
261
|
): i32;
|
|
232
262
|
|
|
263
|
+
// Point reads: stashed value length | -2 absent | typed negative error.
|
|
264
|
+
// result_schema_version exposes the stored event's schema version.
|
|
265
|
+
// @ts-ignore: decorator
|
|
266
|
+
@external("env", "data.events_reference")
|
|
267
|
+
export declare function eventsReference(handle: u32, keyPtr: usize, keyLen: i32, idPtr: usize, idLen: i32): i32;
|
|
268
|
+
// @ts-ignore: decorator
|
|
269
|
+
@external("env", "data.events_redact")
|
|
270
|
+
export declare function eventsRedact(handle: u32, keyPtr: usize, keyLen: i32, sequence: u64, hlc: u64): i32;
|
|
271
|
+
// @ts-ignore: decorator
|
|
272
|
+
@external("env", "data.events_entries")
|
|
273
|
+
export declare function eventsEntries(handle: u32, keyPtr: usize, keyLen: i32, limit: i32, after: u64): i32;
|
|
274
|
+
|
|
275
|
+
// @ts-ignore: decorator
|
|
276
|
+
@external("env", "data.events_get")
|
|
277
|
+
export declare function eventsGet(handle: u32, keyPtr: usize, keyLen: i32, evidPtr: usize, evidLen: i32): i32;
|
|
278
|
+
|
|
279
|
+
// @ts-ignore: decorator
|
|
280
|
+
@external("env", "data.events_last")
|
|
281
|
+
export declare function eventsLast(handle: u32, keyPtr: usize, keyLen: i32): i32;
|
|
282
|
+
|
|
233
283
|
// events.latest(limit) -> framed-list length (stashed) | negative error.
|
|
234
284
|
// The blob is `u32 count` then per event `u32 schema_version + u32 len + bytes`,
|
|
235
285
|
// newest first.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Structured server logging, available to request, stream, and daemon code.
|
|
2
|
+
@external("env", "server_log")
|
|
3
|
+
declare function __serverLog(level: i32, ptr: usize, len: i32): i32;
|
|
4
|
+
|
|
5
|
+
@global
|
|
6
|
+
export class Log {
|
|
7
|
+
private static write(level: i32, message: string): bool {
|
|
8
|
+
const bytes = Uint8Array.wrap(String.UTF8.encode(message));
|
|
9
|
+
return __serverLog(level, bytes.dataStart, bytes.byteLength) == 1;
|
|
10
|
+
}
|
|
11
|
+
static info(message: string): bool { return Log.write(0, message); }
|
|
12
|
+
static warn(message: string): bool { return Log.write(1, message); }
|
|
13
|
+
static error(message: string): bool { return Log.write(2, message); }
|
|
14
|
+
}
|
package/std/assembly/toildb.ts
CHANGED
|
@@ -186,7 +186,8 @@ function __toildbMiss(status: i32): void {
|
|
|
186
186
|
/// }
|
|
187
187
|
/// ```
|
|
188
188
|
///
|
|
189
|
-
///
|
|
189
|
+
/// `Documents.get`, `Documents.getDelete`, `View.get`, `Unique.lookup`,
|
|
190
|
+
/// `Events.get` and `Events.last`
|
|
190
191
|
/// record into this. Every other ToilDB op either succeeds or traps. The `Analytics`
|
|
191
192
|
/// reads also answer `null`, but on their own `-2 absent` / `-3 forbidden` statuses
|
|
192
193
|
/// rather than a `TDLnnn`, so they leave `lastError()` untouched: do not consult it
|
|
@@ -232,6 +233,14 @@ export enum UpsertResult {
|
|
|
232
233
|
Conflict = 2,
|
|
233
234
|
}
|
|
234
235
|
|
|
236
|
+
/// An observed value plus its original storage bytes (before decoding/migration).
|
|
237
|
+
/// Keep the snapshot unchanged while constructing a replacement value.
|
|
238
|
+
@global
|
|
239
|
+
export class DocumentSnapshot<V> {
|
|
240
|
+
constructor(public value: V, public encoded: Uint8Array) {}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@global
|
|
235
244
|
export class Documents<K, V> {
|
|
236
245
|
private __handle: u32;
|
|
237
246
|
|
|
@@ -258,6 +267,47 @@ export class Documents<K, V> {
|
|
|
258
267
|
return v;
|
|
259
268
|
}
|
|
260
269
|
|
|
270
|
+
/// Read without rewrite-on-read. Retains the exact bytes for conditional writes,
|
|
271
|
+
/// including records whose codec or schema migrated during decoding.
|
|
272
|
+
getSnapshot(key: K): DocumentSnapshot<V> | null {
|
|
273
|
+
const kb = key.encode();
|
|
274
|
+
const status = toildbHost.get(this.__handle, kb.dataStart, kb.byteLength);
|
|
275
|
+
if (status < 0) { __toildbMiss(status); return null; }
|
|
276
|
+
const bytes = __toildbTake(status);
|
|
277
|
+
const value = instantiate<V>();
|
|
278
|
+
value.decodeInto(bytes);
|
|
279
|
+
return new DocumentSnapshot<V>(value, bytes);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// Owner-authoritative snapshot, bypassing replica and request caches.
|
|
283
|
+
getCurrentSnapshot(key: K): DocumentSnapshot<V> | null {
|
|
284
|
+
const kb = key.encode();
|
|
285
|
+
const status = toildbHost.getCurrent(this.__handle, kb.dataStart, kb.byteLength);
|
|
286
|
+
if (status < 0) { __toildbMiss(status); return null; }
|
|
287
|
+
const bytes = __toildbTake(status);
|
|
288
|
+
const value = instantiate<V>();
|
|
289
|
+
value.decodeInto(bytes);
|
|
290
|
+
return new DocumentSnapshot<V>(value, bytes);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
compareExchangeSnapshot(key: K, expected: DocumentSnapshot<V> | null, value: V): bool {
|
|
294
|
+
const kb = key.encode();
|
|
295
|
+
const vb = value.encode();
|
|
296
|
+
const eb = expected == null ? new Uint8Array(0) : expected.encoded;
|
|
297
|
+
const status = toildbHost.compareExchange(this.__handle, kb.dataStart, kb.byteLength,
|
|
298
|
+
eb.dataStart, expected == null ? -1 : eb.byteLength, vb.dataStart, vb.byteLength);
|
|
299
|
+
if (status < 0) abort("ToilDB compareExchange failed: status " + status.toString(), "toildb", 0, 0);
|
|
300
|
+
return status == 1;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
compareDeleteSnapshot(key: K, expected: DocumentSnapshot<V>): bool {
|
|
304
|
+
const kb = key.encode();
|
|
305
|
+
const eb = expected.encoded;
|
|
306
|
+
const status = toildbHost.compareDelete(this.__handle, kb.dataStart, kb.byteLength, eb.dataStart, eb.byteLength);
|
|
307
|
+
if (status < 0) abort("ToilDB compareDelete failed: status " + status.toString(), "toildb", 0, 0);
|
|
308
|
+
return status == 1;
|
|
309
|
+
}
|
|
310
|
+
|
|
261
311
|
/// Like `get`, but traps if the record is absent.
|
|
262
312
|
require(key: K): V {
|
|
263
313
|
const v = this.get(key);
|
|
@@ -275,7 +325,13 @@ export class Documents<K, V> {
|
|
|
275
325
|
}
|
|
276
326
|
const blob = w.toBytes();
|
|
277
327
|
const status = toildbHost.getMany(this.__handle, blob.dataStart, blob.byteLength);
|
|
278
|
-
if (status < 0)
|
|
328
|
+
if (status < 0) {
|
|
329
|
+
__toildbMiss(status);
|
|
330
|
+
abort("ToilDB getMany failed: status " + status.toString() +
|
|
331
|
+
"; DbError=" + (<i32>Db.lastError()).toString() +
|
|
332
|
+
"; keys=" + keys.length.toString() +
|
|
333
|
+
". Query accepts 32 keys, Action 64, background 1024; split larger batches.", "toildb", 0, 0);
|
|
334
|
+
}
|
|
279
335
|
const out = __toildbTake(status);
|
|
280
336
|
const results = new Array<V | null>();
|
|
281
337
|
let off: i32 = 0;
|
|
@@ -325,8 +381,9 @@ export class Documents<K, V> {
|
|
|
325
381
|
/// conflict or retry - the later one wins and overwrites the earlier. Correct
|
|
326
382
|
/// for writing a whole value (a profile, a settings blob); wrong for a
|
|
327
383
|
/// read-modify-write of accumulating state (a balance, a like count), where a
|
|
328
|
-
/// concurrent write would be lost - use `counter.add` or
|
|
329
|
-
///
|
|
384
|
+
/// concurrent write would be lost - use `counter.add` or a caller-observed
|
|
385
|
+
/// conditional write there. A lost write is a data-integrity concern, not a
|
|
386
|
+
/// boundary one:
|
|
330
387
|
/// the key is tenant-scoped, so a race is only ever one tenant's own concurrent
|
|
331
388
|
/// writes, and `@unique` stays race-safe (claim-once at the value's home).
|
|
332
389
|
upsert(key: K, value: V): UpsertResult {
|
|
@@ -352,19 +409,40 @@ export class Documents<K, V> {
|
|
|
352
409
|
return v;
|
|
353
410
|
}
|
|
354
411
|
|
|
355
|
-
///
|
|
356
|
-
///
|
|
357
|
-
|
|
412
|
+
/// Replace only if the stored bytes equal `expected`; null expects absence.
|
|
413
|
+
/// Returns false on a mismatch. Routed to the owner; @unique values are rejected.
|
|
414
|
+
compareExchange(key: K, expected: V | null, value: V): bool {
|
|
415
|
+
const kb = key.encode();
|
|
416
|
+
const vb = value.encode();
|
|
417
|
+
const eb = expected == null ? new Uint8Array(0) : expected.encode();
|
|
418
|
+
const status = toildbHost.compareExchange(
|
|
419
|
+
this.__handle, kb.dataStart, kb.byteLength,
|
|
420
|
+
eb.dataStart, expected == null ? -1 : eb.byteLength,
|
|
421
|
+
vb.dataStart, vb.byteLength
|
|
422
|
+
);
|
|
423
|
+
if (status < 0) abort("ToilDB compareExchange failed: status " + status.toString(), "toildb", 0, 0);
|
|
424
|
+
return status == 1;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/// Submit a replacement of an EXISTING record: true when accepted, false on
|
|
428
|
+
/// absence or rejection. The ABI carries no caller-observed version or value,
|
|
429
|
+
/// so this cannot protect a read-modify-write based on an earlier application read.
|
|
358
430
|
enqueue(key: K, value: V): bool {
|
|
359
431
|
const kb = key.encode();
|
|
360
432
|
const vb = value.encode();
|
|
361
433
|
return toildbHost.enqueue(this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0) == 0;
|
|
362
434
|
}
|
|
363
435
|
|
|
436
|
+
/// Delete only when the stored bytes still equal the caller's expectation.
|
|
437
|
+
compareDelete(key: K, expected: V): bool {
|
|
438
|
+
return this.compareDeleteSnapshot(key, new DocumentSnapshot<V>(expected, expected.encode()));
|
|
439
|
+
}
|
|
440
|
+
|
|
364
441
|
/// Delete the record (idempotent).
|
|
365
442
|
delete(key: K): void {
|
|
366
443
|
const kb = key.encode();
|
|
367
|
-
toildbHost.del(this.__handle, kb.dataStart, kb.byteLength, 0);
|
|
444
|
+
const status = toildbHost.del(this.__handle, kb.dataStart, kb.byteLength, 0);
|
|
445
|
+
if (status < 0) abort("ToilDB delete failed: status " + status.toString(), "toildb", 0, 0);
|
|
368
446
|
}
|
|
369
447
|
|
|
370
448
|
/// Atomic fetch-and-delete (consume-once); returns the prior value or `null`.
|
|
@@ -407,14 +485,22 @@ export class View<K, V> {
|
|
|
407
485
|
return v!;
|
|
408
486
|
}
|
|
409
487
|
|
|
488
|
+
/// Remove a projection key. Delete its source personal data before rebuilding.
|
|
489
|
+
delete(key: K): void {
|
|
490
|
+
const kb = key.encode();
|
|
491
|
+
const status = toildbHost.viewDelete(this.__handle, kb.dataStart, kb.byteLength, 0);
|
|
492
|
+
if (status < 0) abort("ToilDB view.delete failed: status " + status.toString(), "toildb", 0, 0);
|
|
493
|
+
}
|
|
494
|
+
|
|
410
495
|
/// Publish (overwrite) the view for `key`. Derive/job only; the host assigns
|
|
411
496
|
/// the version so a later publish always supersedes an earlier one.
|
|
412
497
|
publish(key: K, value: V): void {
|
|
413
498
|
const kb = key.encode();
|
|
414
499
|
const vb = value.encode();
|
|
415
|
-
toildbHost.viewPublish(
|
|
500
|
+
const status = toildbHost.viewPublish(
|
|
416
501
|
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
417
502
|
);
|
|
503
|
+
if (status < 0) abort("ToilDB view.publish failed: status " + status.toString(), "toildb", 0, 0);
|
|
418
504
|
}
|
|
419
505
|
}
|
|
420
506
|
|
|
@@ -446,6 +532,12 @@ export class Unique<K, V> {
|
|
|
446
532
|
return v;
|
|
447
533
|
}
|
|
448
534
|
|
|
535
|
+
/// Bounded owner lookup in input order, with null for each unclaimed key.
|
|
536
|
+
/// Uses one read operation; the host enforces the per-request batch limit.
|
|
537
|
+
lookupMany(keys: K[]): Array<V | null> {
|
|
538
|
+
return new Documents<K, V>(this.__handle).getMany(keys);
|
|
539
|
+
}
|
|
540
|
+
|
|
449
541
|
/// Claim `key` for `value`. Returns whether the caller owns it, and (when
|
|
450
542
|
/// another owns it) who.
|
|
451
543
|
claim(key: K, value: V): ClaimResult<V> {
|
|
@@ -475,6 +567,17 @@ export class Unique<K, V> {
|
|
|
475
567
|
}
|
|
476
568
|
}
|
|
477
569
|
|
|
570
|
+
/// A page in encoded-member order; pass cursor to listPage to continue.
|
|
571
|
+
@global
|
|
572
|
+
export class MembershipPage<M> {
|
|
573
|
+
members: M[];
|
|
574
|
+
cursor: Uint8Array | null;
|
|
575
|
+
constructor(members: M[], cursor: Uint8Array | null) {
|
|
576
|
+
this.members = members;
|
|
577
|
+
this.cursor = cursor;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
478
581
|
/// An unordered set (spec 7.3): followers, tags, ACLs, room members. `M` is the
|
|
479
582
|
/// `@data` member type, `K` the `@data` set-key type.
|
|
480
583
|
@global
|
|
@@ -512,6 +615,36 @@ export class Membership<K, M> {
|
|
|
512
615
|
);
|
|
513
616
|
}
|
|
514
617
|
|
|
618
|
+
/// Read members strictly after an opaque cursor. A full page may require an
|
|
619
|
+
/// empty final page to establish the end. Pagination is not a snapshot.
|
|
620
|
+
listPage(key: K, limit: i32, cursor: Uint8Array | null = null): MembershipPage<M> {
|
|
621
|
+
const kb = key.encode();
|
|
622
|
+
const cb = cursor == null ? new Uint8Array(0) : cursor;
|
|
623
|
+
const status = toildbHost.membershipPage(
|
|
624
|
+
this.__handle, kb.dataStart, kb.byteLength, cb.dataStart,
|
|
625
|
+
cursor == null ? -1 : cb.byteLength, limit
|
|
626
|
+
);
|
|
627
|
+
if (status < 0) abort("ToilDB membership.listPage failed: status " + status.toString(), "toildb", 0, 0);
|
|
628
|
+
const blob = __toildbTake(status);
|
|
629
|
+
const out = new Array<M>();
|
|
630
|
+
let off: i32 = 4;
|
|
631
|
+
const count = load<u32>(blob.dataStart);
|
|
632
|
+
for (let i: u32 = 0; i < count; i++) {
|
|
633
|
+
const version = <i64>load<u32>(blob.dataStart + off); off += 4;
|
|
634
|
+
const len = <i32>load<u32>(blob.dataStart + off); off += 4;
|
|
635
|
+
const member = instantiate<M>();
|
|
636
|
+
member.decodeIntoVersioned(blob.subarray(off, off + len), version);
|
|
637
|
+
out.push(member); off += len;
|
|
638
|
+
}
|
|
639
|
+
const hasCursor = load<u8>(blob.dataStart + off); off++;
|
|
640
|
+
let next: Uint8Array | null = null;
|
|
641
|
+
if (hasCursor != 0) {
|
|
642
|
+
const len = <i32>load<u32>(blob.dataStart + off); off += 4;
|
|
643
|
+
next = blob.slice(off, off + len);
|
|
644
|
+
}
|
|
645
|
+
return new MembershipPage<M>(out, next);
|
|
646
|
+
}
|
|
647
|
+
|
|
515
648
|
/// Up to `limit` members of the set. Decodes each framed member into an `M`.
|
|
516
649
|
list(key: K, limit: i32): M[] {
|
|
517
650
|
const kb = key.encode();
|
|
@@ -936,6 +1069,21 @@ export class Analytics {
|
|
|
936
1069
|
/// An append-only event log (spec 7.5): activity feeds, audit trails, the
|
|
937
1070
|
/// fact stream a `@derive` consumes. `V` is the `@data` event type, `K` the
|
|
938
1071
|
/// `@data` stream-key type.
|
|
1072
|
+
@global
|
|
1073
|
+
export class EventRef {
|
|
1074
|
+
constructor(public sequence: u64 = 0, public hlc: u64 = 0) {}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
@global
|
|
1078
|
+
export class EventEntry<V> {
|
|
1079
|
+
constructor(public reference: EventRef, public value: V) {}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
@global
|
|
1083
|
+
export class EventPage<V> {
|
|
1084
|
+
constructor(public entries: Array<EventEntry<V>>, public cursor: u64, public hasMore: bool) {}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
939
1087
|
@global
|
|
940
1088
|
export class Events<K, V> {
|
|
941
1089
|
private __handle: u32;
|
|
@@ -944,6 +1092,70 @@ export class Events<K, V> {
|
|
|
944
1092
|
this.__handle = handle;
|
|
945
1093
|
}
|
|
946
1094
|
|
|
1095
|
+
/// Read an appendOnce reference, including during pending cleanup. No payload is returned.
|
|
1096
|
+
reference(key: K, eventId: string): EventRef | null {
|
|
1097
|
+
const kb = key.encode();
|
|
1098
|
+
const id = Uint8Array.wrap(String.UTF8.encode(eventId));
|
|
1099
|
+
const status = toildbHost.eventsReference(this.__handle, kb.dataStart, kb.byteLength, id.dataStart, id.byteLength);
|
|
1100
|
+
if (status < 0) { __toildbMiss(status); return null; }
|
|
1101
|
+
const reader = new DataReader(__toildbTake(status));
|
|
1102
|
+
const reference = new EventRef(reader.readU64(), reader.readU64());
|
|
1103
|
+
if (!reader.ok) abort("Invalid event reference", "toildb", 0, 0);
|
|
1104
|
+
return reference;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/// Erase this entry and one bounded page of recovery copies. Repeat in later
|
|
1108
|
+
/// background calls until true. The payload is hidden from the first successful call.
|
|
1109
|
+
redact(key: K, reference: EventRef): bool {
|
|
1110
|
+
const kb = key.encode();
|
|
1111
|
+
const status = toildbHost.eventsRedact(this.__handle, kb.dataStart, kb.byteLength, reference.sequence, reference.hlc);
|
|
1112
|
+
if (status < 0) abort("ToilDB events.redact failed: status " + status.toString(), "toildb", 0, 0);
|
|
1113
|
+
return status == 1;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/// Bounded, oldest-first page with references for ordinary and appendOnce events.
|
|
1117
|
+
/// Keep the opaque cursor and use hasMore even if an erased page is empty.
|
|
1118
|
+
entries(key: K, limit: i32 = 128, after: u64 = 0): EventPage<V> {
|
|
1119
|
+
const kb = key.encode();
|
|
1120
|
+
const status = toildbHost.eventsEntries(this.__handle, kb.dataStart, kb.byteLength, limit, after);
|
|
1121
|
+
if (status < 0) abort("ToilDB events.entries failed: status " + status.toString(), "toildb", 0, 0);
|
|
1122
|
+
const reader = new DataReader(__toildbTake(status));
|
|
1123
|
+
const count = reader.readU32();
|
|
1124
|
+
const entries = new Array<EventEntry<V>>();
|
|
1125
|
+
for (let i: u32 = 0; i < count; i++) {
|
|
1126
|
+
const reference = new EventRef(reader.readU64(), reader.readU64());
|
|
1127
|
+
const version = <i64>reader.readU32();
|
|
1128
|
+
const bytes = reader.readBytes();
|
|
1129
|
+
const value = instantiate<V>(); value.decodeIntoVersioned(bytes, version);
|
|
1130
|
+
entries.push(new EventEntry<V>(reference, value));
|
|
1131
|
+
}
|
|
1132
|
+
const hasMore = reader.readU8() != 0;
|
|
1133
|
+
const cursor: u64 = hasMore ? reader.readU64() : 0;
|
|
1134
|
+
if (!reader.ok) abort("Invalid event page", "toildb", 0, 0);
|
|
1135
|
+
return new EventPage<V>(entries, cursor, hasMore);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/// Read an event by its appendOnce eventId, or null if absent. Bounded request-path read.
|
|
1139
|
+
get(key: K, eventId: string): V | null {
|
|
1140
|
+
const kb = key.encode();
|
|
1141
|
+
const idb = Uint8Array.wrap(String.UTF8.encode(eventId));
|
|
1142
|
+
const status = toildbHost.eventsGet(this.__handle, kb.dataStart, kb.byteLength, idb.dataStart, idb.byteLength);
|
|
1143
|
+
if (status < 0) { __toildbMiss(status); return null; }
|
|
1144
|
+
const v = instantiate<V>();
|
|
1145
|
+
v.decodeInto(__toildbTake(status));
|
|
1146
|
+
return v;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/// Read the newest appended event, or null for an empty stream. Does not scan.
|
|
1150
|
+
last(key: K): V | null {
|
|
1151
|
+
const kb = key.encode();
|
|
1152
|
+
const status = toildbHost.eventsLast(this.__handle, kb.dataStart, kb.byteLength);
|
|
1153
|
+
if (status < 0) { __toildbMiss(status); return null; }
|
|
1154
|
+
const v = instantiate<V>();
|
|
1155
|
+
v.decodeInto(__toildbTake(status));
|
|
1156
|
+
return v;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
947
1159
|
/// Append an event to the stream.
|
|
948
1160
|
append(key: K, event: V): void {
|
|
949
1161
|
const kb = key.encode();
|
|
@@ -240,6 +240,12 @@ declare const enum UpsertResult {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/** A mutable keyed-entity collection (spec 7.1): user profiles, items, sessions. */
|
|
243
|
+
declare class DocumentSnapshot<V> {
|
|
244
|
+
value: V;
|
|
245
|
+
encoded: Uint8Array;
|
|
246
|
+
constructor(value: V, encoded: Uint8Array);
|
|
247
|
+
}
|
|
248
|
+
|
|
243
249
|
declare class Documents<K, V> {
|
|
244
250
|
get(key: K): V | null;
|
|
245
251
|
require(key: K): V;
|
|
@@ -247,13 +253,24 @@ declare class Documents<K, V> {
|
|
|
247
253
|
getMany(keys: K[]): (V | null)[];
|
|
248
254
|
create(key: K, value: V): bool;
|
|
249
255
|
patch(key: K, value: V): V;
|
|
256
|
+
/** Replace if stored bytes equal expected; null expects absence. Owner-cell only,
|
|
257
|
+
* rejects @unique values. False on mismatch; other failures abort. */
|
|
258
|
+
compareExchange(key: K, expected: V | null, value: V): bool;
|
|
259
|
+
compareDelete(key: K, expected: V): bool;
|
|
260
|
+
getSnapshot(key: K): DocumentSnapshot<V> | null;
|
|
261
|
+
getCurrentSnapshot(key: K): DocumentSnapshot<V> | null;
|
|
262
|
+
compareExchangeSnapshot(key: K, expected: DocumentSnapshot<V> | null, value: V): bool;
|
|
263
|
+
compareDeleteSnapshot(key: K, expected: DocumentSnapshot<V>): bool;
|
|
250
264
|
/** Create-or-overwrite in one op (blind last-writer-wins); replaces the two-op
|
|
251
265
|
* `if (!create(k,v)) patch(k,v)` idiom. Right for writing a whole value; wrong
|
|
252
266
|
* for read-modify-write of accumulating state (a balance/count), where a
|
|
253
|
-
* concurrent write would be lost - use `counter.add` or
|
|
254
|
-
*
|
|
255
|
-
* tenant's own writes. See {@link UpsertResult}. */
|
|
267
|
+
* concurrent write would be lost - use `counter.add` or a caller-observed
|
|
268
|
+
* conditional write there. The key is tenant-scoped, so a race is only ever
|
|
269
|
+
* the same tenant's own writes. See {@link UpsertResult}. */
|
|
256
270
|
upsert(key: K, value: V): UpsertResult;
|
|
271
|
+
/** Submit a replacement of an existing record; true when accepted, false on
|
|
272
|
+
* absence or rejection. Does not compare against an earlier application read. */
|
|
273
|
+
enqueue(key: K, value: V): bool;
|
|
257
274
|
delete(key: K): void;
|
|
258
275
|
getDelete(key: K): V | null;
|
|
259
276
|
}
|
|
@@ -262,6 +279,7 @@ declare class Documents<K, V> {
|
|
|
262
279
|
declare class View<K, V> {
|
|
263
280
|
get(key: K): V | null;
|
|
264
281
|
require(key: K): V;
|
|
282
|
+
delete(key: K): void;
|
|
265
283
|
publish(key: K, value: V): void;
|
|
266
284
|
}
|
|
267
285
|
|
|
@@ -275,16 +293,26 @@ declare class ClaimResult<V> {
|
|
|
275
293
|
/** A globally-unique claim collection (spec 7.6): usernames, emails, slugs. */
|
|
276
294
|
declare class Unique<K, V> {
|
|
277
295
|
lookup(key: K): V | null;
|
|
296
|
+
/** Bounded owner lookup in input order; null for unclaimed keys. */
|
|
297
|
+
lookupMany(keys: K[]): Array<V | null>;
|
|
278
298
|
claim(key: K, value: V): ClaimResult<V>;
|
|
279
299
|
release(key: K, value: V): void;
|
|
280
300
|
}
|
|
281
301
|
|
|
302
|
+
/** A page in encoded-member byte order, with an opaque continuation cursor. */
|
|
303
|
+
declare class MembershipPage<M> {
|
|
304
|
+
members: M[];
|
|
305
|
+
cursor: Uint8Array | null;
|
|
306
|
+
constructor(members: M[], cursor: Uint8Array | null);
|
|
307
|
+
}
|
|
308
|
+
|
|
282
309
|
/** An unordered set (spec 7.3): followers, tags, ACLs, room members. */
|
|
283
310
|
declare class Membership<K, M> {
|
|
284
311
|
contains(key: K, member: M): bool;
|
|
285
312
|
add(key: K, member: M): void;
|
|
286
313
|
remove(key: K, member: M): void;
|
|
287
314
|
list(key: K, limit: i32): M[];
|
|
315
|
+
listPage(key: K, limit: i32, cursor?: Uint8Array | null): MembershipPage<M>;
|
|
288
316
|
}
|
|
289
317
|
|
|
290
318
|
/** A finite, strongly-consistent resource via escrow (spec 7.7): stock, seats. */
|
|
@@ -303,7 +331,22 @@ declare class Counter<K> {
|
|
|
303
331
|
}
|
|
304
332
|
|
|
305
333
|
/** An append-only event log (spec 7.5): activity feeds, audit trails. */
|
|
334
|
+
declare class EventRef {
|
|
335
|
+
sequence: u64;
|
|
336
|
+
hlc: u64;
|
|
337
|
+
constructor(sequence?: u64, hlc?: u64);
|
|
338
|
+
}
|
|
339
|
+
declare class EventEntry<V> { reference: EventRef; value: V; constructor(reference: EventRef, value: V); }
|
|
340
|
+
declare class EventPage<V> { entries: EventEntry<V>[]; cursor: u64; hasMore: bool; constructor(entries: EventEntry<V>[], cursor: u64, hasMore: bool); }
|
|
306
341
|
declare class Events<K, V> {
|
|
342
|
+
reference(key: K, eventId: string): EventRef | null;
|
|
343
|
+
redact(key: K, reference: EventRef): bool;
|
|
344
|
+
entries(key: K, limit?: i32, after?: u64): EventPage<V>;
|
|
345
|
+
/** Read an event by its appendOnce eventId, or null if absent. */
|
|
346
|
+
get(key: K, eventId: string): V | null;
|
|
347
|
+
/** Read the newest appended event, or null for an empty stream. */
|
|
348
|
+
last(key: K): V | null;
|
|
349
|
+
appendOnce(key: K, eventId: string, event: V): bool;
|
|
307
350
|
append(key: K, event: V): void;
|
|
308
351
|
latest(key: K, limit: i32): V[];
|
|
309
352
|
since(key: K, limit: i32): V[];
|
|
@@ -862,3 +905,11 @@ declare class JSON {
|
|
|
862
905
|
/** Serialize this value tree to a JSON string. */
|
|
863
906
|
toString(): string;
|
|
864
907
|
}
|
|
908
|
+
|
|
909
|
+
/** Server logging. At most 4096 UTF-8 bytes/message, 32 messages per call.
|
|
910
|
+
* Returns false when the message allowance is exhausted. */
|
|
911
|
+
declare class Log {
|
|
912
|
+
static info(message: string): bool;
|
|
913
|
+
static warn(message: string): bool;
|
|
914
|
+
static error(message: string): bool;
|
|
915
|
+
}
|