toilscript 0.1.59 → 0.1.60

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.
@@ -72,6 +72,151 @@ function __toildbTakeGrow(): Uint8Array {
72
72
  return buf.subarray(0, n);
73
73
  }
74
74
 
75
+ /// A typed ToilDB failure, the `TDLnnn` diagnostic of spec 27.1. The host bridges
76
+ /// one as `-(1000 + nnn)`; the two small sentinels are NOT failures (`-1` output
77
+ /// buffer too small, `-2` absent), so both decode to `None`.
78
+ ///
79
+ /// Only a SUBSET can ever reach guest code. A hard fault (budget/quota, storage,
80
+ /// integrity, capability/schema) is indistinguishable from a benign outcome at the
81
+ /// ABI, so returning it would silently drop the op; the host TRAPS the request
82
+ /// instead and the guest never runs again (toil-backend `status_is_hard_fault`).
83
+ /// The members below say which is which. Every code is still listed, because
84
+ /// `Db.errorOf` decodes any raw status.
85
+ ///
86
+ /// So after a read returns `null`, `Db.lastError()` is either `None` (the row is
87
+ /// absent) or one of the four RETRYABLE availability signals. `AlreadyExists` and
88
+ /// `Conflict` are write outcomes, not read ones.
89
+ @global
90
+ export enum DbError {
91
+ /// No failure: the operation succeeded, or the row was simply absent.
92
+ None = 0,
93
+ /// TDL001: the collection handle is not one the host issued. HARD FAULT: traps.
94
+ InvalidHandle = 1,
95
+ /// TDL002: wrong tenant, or the row's MAC did not verify. HARD FAULT: traps.
96
+ TenantMismatch = 2,
97
+ /// TDL003: a `create` found the key already present. A write outcome.
98
+ AlreadyExists = 3,
99
+ /// TDL004: a compare-and-set lost, or a `@unique` value is owned elsewhere. A
100
+ /// write outcome.
101
+ Conflict = 4,
102
+ /// TDL005: the operation is not supported here. HARD FAULT: traps.
103
+ Unsupported = 5,
104
+ /// TDL006: the value did not decode against its schema. HARD FAULT: traps.
105
+ Codec = 6,
106
+ /// TDL007: an internal storage failure. HARD FAULT: traps.
107
+ Backend = 7,
108
+ /// TDL010: the op does not exist for this collection family. HARD FAULT: traps.
109
+ OpNotAllowedForFamily = 10,
110
+ /// TDL011: the op is not allowed for this kind, or at this tier. HARD FAULT: traps.
111
+ OpNotAllowedInKind = 11,
112
+ /// TDL020: too many ops, or too many keys in one multi-op. HARD FAULT: traps.
113
+ OpBudget = 20,
114
+ /// TDL021: the request or its result exceeds the byte budget. HARD FAULT: traps.
115
+ ByteBudget = 21,
116
+ /// TDL022: the value nests too deeply. HARD FAULT: traps.
117
+ DepthBudget = 22,
118
+ /// TDL025: the tenant is over its plan quota. HARD FAULT: traps, so a guest can
119
+ /// never observe this. It is here only to decode a raw status.
120
+ QuotaExceeded = 25,
121
+ /// TDL030: a backfill is throttling this read. RETRYABLE.
122
+ FillThrottled = 30,
123
+ /// TDL031: the home region, or the data, is temporarily unavailable. RETRYABLE.
124
+ Unavailable = 31,
125
+ /// TDL040: this key is a hot partition. RETRYABLE.
126
+ HotPartition = 40,
127
+ /// TDL041: too many cold reads at once. RETRYABLE.
128
+ ColdStorm = 41,
129
+ /// TDL070: the schema is temporarily unavailable. HARD FAULT: traps.
130
+ SchemaUnavailable = 70,
131
+ /// TDL090: a catalog op outside an admin context. HARD FAULT: traps.
132
+ CatalogOutsideAdmin = 90,
133
+ /// A negative status that carries no known `TDLnnn`.
134
+ Unknown = -1,
135
+ }
136
+
137
+ /// The typed failure of the last read that returned `null`/empty. See `Db`.
138
+ // @ts-ignore: decorator
139
+ @lazy
140
+ var __toildbLastError: DbError = DbError.None;
141
+
142
+ /// Decode a raw host status into a typed failure. Non-negative, `-1` (buffer too
143
+ /// small) and `-2` (absent) are all `None`: only the `<= -1000` band is a failure.
144
+ function __toildbErrorOf(status: i32): DbError {
145
+ if (status >= -2) return DbError.None;
146
+ const code = -status - 1000;
147
+ if (code < 0) return DbError.Unknown;
148
+ switch (code) {
149
+ case 1: return DbError.InvalidHandle;
150
+ case 2: return DbError.TenantMismatch;
151
+ case 3: return DbError.AlreadyExists;
152
+ case 4: return DbError.Conflict;
153
+ case 5: return DbError.Unsupported;
154
+ case 6: return DbError.Codec;
155
+ case 7: return DbError.Backend;
156
+ case 10: return DbError.OpNotAllowedForFamily;
157
+ case 11: return DbError.OpNotAllowedInKind;
158
+ case 20: return DbError.OpBudget;
159
+ case 21: return DbError.ByteBudget;
160
+ case 22: return DbError.DepthBudget;
161
+ case 25: return DbError.QuotaExceeded;
162
+ case 30: return DbError.FillThrottled;
163
+ case 31: return DbError.Unavailable;
164
+ case 40: return DbError.HotPartition;
165
+ case 41: return DbError.ColdStorm;
166
+ case 70: return DbError.SchemaUnavailable;
167
+ case 90: return DbError.CatalogOutsideAdmin;
168
+ default: return DbError.Unknown;
169
+ }
170
+ }
171
+
172
+ /// Record the outcome of a read that is about to return `null`. An absent row
173
+ /// records `None`, so `null` + `None` means "not found" and `null` + anything else
174
+ /// means "failed".
175
+ function __toildbMiss(status: i32): void {
176
+ __toildbLastError = __toildbErrorOf(status);
177
+ }
178
+
179
+ /// ToilDB diagnostics. Read `lastError()` immediately after a read returns `null`
180
+ /// to tell an absent row from a failed one; the next read overwrites it.
181
+ ///
182
+ /// ```ts
183
+ /// const u = App.users.get(id);
184
+ /// if (u == null && Db.isRetryable(Db.lastError())) {
185
+ /// // The row may well exist: the store is briefly unavailable. Retry or 503.
186
+ /// }
187
+ /// ```
188
+ ///
189
+ /// ONLY `Documents.get`, `Documents.getDelete`, `View.get` and `Unique.lookup`
190
+ /// record into this. Every other ToilDB op either succeeds or traps. The `Analytics`
191
+ /// reads also answer `null`, but on their own `-2 absent` / `-3 forbidden` statuses
192
+ /// rather than a `TDLnnn`, so they leave `lastError()` untouched: do not consult it
193
+ /// after one, or you will read a stale value from an earlier ToilDB read.
194
+ @global
195
+ export class Db {
196
+ /// The failure recorded by the most recent read that returned `null`. `None`
197
+ /// when that read simply found nothing.
198
+ static lastError(): DbError {
199
+ return __toildbLastError;
200
+ }
201
+
202
+ /// Decode a raw negative host status into its typed failure.
203
+ static errorOf(status: i32): DbError {
204
+ return __toildbErrorOf(status);
205
+ }
206
+
207
+ /// Whether `err` is a transient availability signal, so the same read may well
208
+ /// succeed later. These are the ONLY failures a read can hand back: everything
209
+ /// else traps the request before the guest resumes.
210
+ static isRetryable(err: DbError): bool {
211
+ return (
212
+ err == DbError.FillThrottled ||
213
+ err == DbError.Unavailable ||
214
+ err == DbError.HotPartition ||
215
+ err == DbError.ColdStorm
216
+ );
217
+ }
218
+ }
219
+
75
220
  /// A mutable keyed-entity collection (spec 7.1). `V` is the `@data` value type,
76
221
  /// `K` the `@data` key type.
77
222
  @global
@@ -98,7 +243,7 @@ export class Documents<K, V> {
98
243
  get(key: K): V | null {
99
244
  const kb = key.encode();
100
245
  const status = toildbHost.get(this.__handle, kb.dataStart, kb.byteLength);
101
- if (status < 0) return null;
246
+ if (status < 0) { __toildbMiss(status); return null; }
102
247
  __toildbResetMigrated();
103
248
  const v = instantiate<V>();
104
249
  v.decodeInto(__toildbTake(status));
@@ -226,7 +371,7 @@ export class Documents<K, V> {
226
371
  getDelete(key: K): V | null {
227
372
  const kb = key.encode();
228
373
  const status = toildbHost.getDelete(this.__handle, kb.dataStart, kb.byteLength, 0);
229
- if (status < 0) return null;
374
+ if (status < 0) { __toildbMiss(status); return null; }
230
375
  const v = instantiate<V>();
231
376
  v.decodeInto(__toildbTake(status));
232
377
  return v;
@@ -249,7 +394,7 @@ export class View<K, V> {
249
394
  get(key: K): V | null {
250
395
  const kb = key.encode();
251
396
  const status = toildbHost.viewGet(this.__handle, kb.dataStart, kb.byteLength);
252
- if (status < 0) return null;
397
+ if (status < 0) { __toildbMiss(status); return null; }
253
398
  const v = instantiate<V>();
254
399
  v.decodeInto(__toildbTake(status));
255
400
  return v;
@@ -295,7 +440,7 @@ export class Unique<K, V> {
295
440
  lookup(key: K): V | null {
296
441
  const kb = key.encode();
297
442
  const status = toildbHost.uniqueLookup(this.__handle, kb.dataStart, kb.byteLength);
298
- if (status < 0) return null;
443
+ if (status < 0) { __toildbMiss(status); return null; }
299
444
  const v = instantiate<V>();
300
445
  v.decodeInto(__toildbTake(status));
301
446
  return v;
@@ -441,6 +441,120 @@ declare class Analytics {
441
441
  static listSites(cursor?: string, limit?: i32): SiteList;
442
442
  }
443
443
 
444
+ /**
445
+ * A typed ToilDB failure (`TDLnnn`, spec 27.1). A read that returns `null` either
446
+ * found nothing (`Db.lastError() == DbError.None`) or hit one of the four RETRYABLE
447
+ * availability signals. A HARD FAULT never reaches guest code: the host traps the
448
+ * request rather than hand back a status the ABI would mistake for a benign result.
449
+ */
450
+ declare const enum DbError {
451
+ None = 0,
452
+ /** TDL001. Hard fault: traps. */ InvalidHandle = 1,
453
+ /** TDL002. Hard fault: traps. */ TenantMismatch = 2,
454
+ /** TDL003. A write outcome. */ AlreadyExists = 3,
455
+ /** TDL004. A write outcome. */ Conflict = 4,
456
+ /** TDL005. Hard fault: traps. */ Unsupported = 5,
457
+ /** TDL006. Hard fault: traps. */ Codec = 6,
458
+ /** TDL007. Hard fault: traps. */ Backend = 7,
459
+ /** TDL010. Hard fault: traps. */ OpNotAllowedForFamily = 10,
460
+ /** TDL011. Hard fault: traps. */ OpNotAllowedInKind = 11,
461
+ /** TDL020. Hard fault: traps. */ OpBudget = 20,
462
+ /** TDL021. Hard fault: traps. */ ByteBudget = 21,
463
+ /** TDL022. Hard fault: traps. */ DepthBudget = 22,
464
+ /** TDL025. Hard fault: traps, so a guest never observes it. */ QuotaExceeded = 25,
465
+ /** TDL030. Retryable. */ FillThrottled = 30,
466
+ /** TDL031. Retryable. */ Unavailable = 31,
467
+ /** TDL040. Retryable. */ HotPartition = 40,
468
+ /** TDL041. Retryable. */ ColdStorm = 41,
469
+ /** TDL070. Hard fault: traps. */ SchemaUnavailable = 70,
470
+ /** TDL090. Hard fault: traps. */ CatalogOutsideAdmin = 90,
471
+ /** A negative status carrying no known `TDLnnn`. */ Unknown = -1,
472
+ }
473
+
474
+ /** ToilDB diagnostics: tell an absent row from a failed read. */
475
+ declare class Db {
476
+ /** The failure recorded by the most recent read that returned `null`. */
477
+ static lastError(): DbError;
478
+ /** Decode a raw negative host status into its typed failure. */
479
+ static errorOf(status: i32): DbError;
480
+ /** Whether `err` is a transient availability signal worth retrying. */
481
+ static isRetryable(err: DbError): bool;
482
+ }
483
+
484
+ // The daemon (L4 cold box) scheduler surface is an ambient global, like the
485
+ // ToilDB handles above (`std/assembly/daemon`). It resolves ONLY inside a
486
+ // `@daemon` cold box: its host imports live in the `daemon` wasm module, which a
487
+ // request handler's module does not get.
488
+
489
+ /** A typed daemon failure. Positive values are the host's u16 subsystem codes. */
490
+ declare const enum DaemonError {
491
+ None = 0,
492
+ /** This node does not hold the lease; the host refused without acting. */
493
+ NotLeader = 0x0401,
494
+ /** The lease was lost while the call was in flight. */
495
+ LeaseLost = 0x0402,
496
+ /** The outbound request did not complete (SSRF guard, timeout, transport). */
497
+ CallFailed = 0x0405,
498
+ /** The response did not fit the buffer. The call ALREADY happened: do not retry. */
499
+ ResponseTooLarge = -1,
500
+ /** A request over a host cap (nothing sent), or a malformed response envelope. */
501
+ BadEnvelope = -2,
502
+ /** A negative status outside every band above. */
503
+ Unknown = -3,
504
+ }
505
+
506
+ /** One `name: value` header of an outbound request or an inbound response. */
507
+ declare class DaemonHttpHeader {
508
+ name: string;
509
+ value: string;
510
+ constructor(name: string, value: string);
511
+ }
512
+
513
+ /** An outbound HTTP request. `url` must be an absolute `http(s)` URL. */
514
+ declare class DaemonHttpRequest {
515
+ method: string;
516
+ url: string;
517
+ headers: DaemonHttpHeader[];
518
+ body: Uint8Array;
519
+ constructor(method?: string, url?: string);
520
+ /** Append a header. Returns `this` so calls chain. */
521
+ header(name: string, value: string): DaemonHttpRequest;
522
+ /** The wire envelope, or `null` when a field breaks a host cap. */
523
+ encode(): Uint8Array | null;
524
+ }
525
+
526
+ /** An inbound HTTP response. `status` is 0 when the host failed before a reply. */
527
+ declare class DaemonHttpResponse {
528
+ status: u16;
529
+ headers: DaemonHttpHeader[];
530
+ body: Uint8Array;
531
+ /** A header's value (case-sensitive, first match), or `null`. */
532
+ header(name: string): string | null;
533
+ /** The body decoded as UTF-8. */
534
+ text(): string;
535
+ static decode(buf: Uint8Array, len: i32): DaemonHttpResponse | null;
536
+ }
537
+
538
+ /** The daemon scheduler surface, available inside a `@daemon` class. */
539
+ declare class Daemon {
540
+ /** Whether THIS node currently holds the lease. A snapshot: re-check it. */
541
+ static isLeader(): bool;
542
+ /** The monotonic fencing token, or -1 when this node does not hold the lease. */
543
+ static epoch(): i64;
544
+ /** The number of registered `@scheduled` tasks. */
545
+ static taskCount(): i32;
546
+ /** The next fire time for `taskId` in epoch ms, or -1 when it never fires. */
547
+ static nextFireMs(taskId: i32): i64;
548
+ /** Let the host observe a lost lease. `None` means still leader. */
549
+ static yieldNow(): DaemonError;
550
+ /** Park for `ms` (host-clamped to 3s). `LeaseLost` means the task should stop. */
551
+ static sleep(ms: i64): DaemonError;
552
+ /** Make an outbound HTTP request, or `null` on failure (see `lastError()`). */
553
+ static httpCall(request: DaemonHttpRequest, responseCap?: i32): DaemonHttpResponse | null;
554
+ /** The failure recorded by the most recent `Daemon` call. */
555
+ static lastError(): DaemonError;
556
+ }
557
+
444
558
  // Big integers, native globals implemented in std/assembly/bignum. The
445
559
  // arithmetic/bitwise/comparison operators
446
560
  // (+ - * / % & | ^ << >> == != < > <= >=) are operator overloads resolved by