toilscript 0.1.63 → 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/web.js CHANGED
@@ -1,8 +1,8 @@
1
- var ASSEMBLYSCRIPT_VERSION = "0.1.63";
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.63/dist/toilscript.js",
5
- "toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.63/dist/cli.js",
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.63",
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",
@@ -81,7 +81,7 @@
81
81
  "prepublishOnly": "node scripts/build",
82
82
  "watch": "node scripts/build --watch",
83
83
  "coverage": "npx c8 -- npm test",
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",
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",
85
85
  "test:parser": "node --enable-source-maps tests/parser",
86
86
  "test:compiler": "node --enable-source-maps --no-warnings tests/compiler",
87
87
  "test:browser": "node --enable-source-maps tests/browser",
@@ -98,6 +98,8 @@
98
98
  "test:routekinds": "node tests/routekinds/run.mjs",
99
99
  "test:derive": "node tests/derive/run.mjs",
100
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",
101
103
  "test:daemon": "node tests/daemon/run.mjs",
102
104
  "test:uniquebatch": "node tests/uniquebatch/run.mjs",
103
105
  "test:events": "node tests/events/run.mjs",
@@ -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")
@@ -232,6 +262,16 @@ export namespace toildbHost {
232
262
 
233
263
  // Point reads: stashed value length | -2 absent | typed negative error.
234
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
+
235
275
  // @ts-ignore: decorator
236
276
  @external("env", "data.events_get")
237
277
  export declare function eventsGet(handle: u32, keyPtr: usize, keyLen: i32, evidPtr: usize, evidLen: i32): i32;
@@ -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
+ }
@@ -233,6 +233,14 @@ export enum UpsertResult {
233
233
  Conflict = 2,
234
234
  }
235
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
236
244
  export class Documents<K, V> {
237
245
  private __handle: u32;
238
246
 
@@ -259,6 +267,47 @@ export class Documents<K, V> {
259
267
  return v;
260
268
  }
261
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
+
262
311
  /// Like `get`, but traps if the record is absent.
263
312
  require(key: K): V {
264
313
  const v = this.get(key);
@@ -276,7 +325,13 @@ export class Documents<K, V> {
276
325
  }
277
326
  const blob = w.toBytes();
278
327
  const status = toildbHost.getMany(this.__handle, blob.dataStart, blob.byteLength);
279
- if (status < 0) unreachable();
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
+ }
280
335
  const out = __toildbTake(status);
281
336
  const results = new Array<V | null>();
282
337
  let off: i32 = 0;
@@ -354,6 +409,21 @@ export class Documents<K, V> {
354
409
  return v;
355
410
  }
356
411
 
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
+
357
427
  /// Submit a replacement of an EXISTING record: true when accepted, false on
358
428
  /// absence or rejection. The ABI carries no caller-observed version or value,
359
429
  /// so this cannot protect a read-modify-write based on an earlier application read.
@@ -363,10 +433,16 @@ export class Documents<K, V> {
363
433
  return toildbHost.enqueue(this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0) == 0;
364
434
  }
365
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
+
366
441
  /// Delete the record (idempotent).
367
442
  delete(key: K): void {
368
443
  const kb = key.encode();
369
- 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);
370
446
  }
371
447
 
372
448
  /// Atomic fetch-and-delete (consume-once); returns the prior value or `null`.
@@ -409,14 +485,22 @@ export class View<K, V> {
409
485
  return v!;
410
486
  }
411
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
+
412
495
  /// Publish (overwrite) the view for `key`. Derive/job only; the host assigns
413
496
  /// the version so a later publish always supersedes an earlier one.
414
497
  publish(key: K, value: V): void {
415
498
  const kb = key.encode();
416
499
  const vb = value.encode();
417
- toildbHost.viewPublish(
500
+ const status = toildbHost.viewPublish(
418
501
  this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
419
502
  );
503
+ if (status < 0) abort("ToilDB view.publish failed: status " + status.toString(), "toildb", 0, 0);
420
504
  }
421
505
  }
422
506
 
@@ -483,6 +567,17 @@ export class Unique<K, V> {
483
567
  }
484
568
  }
485
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
+
486
581
  /// An unordered set (spec 7.3): followers, tags, ACLs, room members. `M` is the
487
582
  /// `@data` member type, `K` the `@data` set-key type.
488
583
  @global
@@ -520,6 +615,36 @@ export class Membership<K, M> {
520
615
  );
521
616
  }
522
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
+
523
648
  /// Up to `limit` members of the set. Decodes each framed member into an `M`.
524
649
  list(key: K, limit: i32): M[] {
525
650
  const kb = key.encode();
@@ -944,6 +1069,21 @@ export class Analytics {
944
1069
  /// An append-only event log (spec 7.5): activity feeds, audit trails, the
945
1070
  /// fact stream a `@derive` consumes. `V` is the `@data` event type, `K` the
946
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
+
947
1087
  @global
948
1088
  export class Events<K, V> {
949
1089
  private __handle: u32;
@@ -952,6 +1092,49 @@ export class Events<K, V> {
952
1092
  this.__handle = handle;
953
1093
  }
954
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
+
955
1138
  /// Read an event by its appendOnce eventId, or null if absent. Bounded request-path read.
956
1139
  get(key: K, eventId: string): V | null {
957
1140
  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,6 +253,14 @@ 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
@@ -265,6 +279,7 @@ declare class Documents<K, V> {
265
279
  declare class View<K, V> {
266
280
  get(key: K): V | null;
267
281
  require(key: K): V;
282
+ delete(key: K): void;
268
283
  publish(key: K, value: V): void;
269
284
  }
270
285
 
@@ -284,12 +299,20 @@ declare class Unique<K, V> {
284
299
  release(key: K, value: V): void;
285
300
  }
286
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
+
287
309
  /** An unordered set (spec 7.3): followers, tags, ACLs, room members. */
288
310
  declare class Membership<K, M> {
289
311
  contains(key: K, member: M): bool;
290
312
  add(key: K, member: M): void;
291
313
  remove(key: K, member: M): void;
292
314
  list(key: K, limit: i32): M[];
315
+ listPage(key: K, limit: i32, cursor?: Uint8Array | null): MembershipPage<M>;
293
316
  }
294
317
 
295
318
  /** A finite, strongly-consistent resource via escrow (spec 7.7): stock, seats. */
@@ -308,7 +331,17 @@ declare class Counter<K> {
308
331
  }
309
332
 
310
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); }
311
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>;
312
345
  /** Read an event by its appendOnce eventId, or null if absent. */
313
346
  get(key: K, eventId: string): V | null;
314
347
  /** Read the newest appended event, or null for an empty stream. */
@@ -872,3 +905,11 @@ declare class JSON {
872
905
  /** Serialize this value tree to a JSON string. */
873
906
  toString(): string;
874
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
+ }