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.
- package/dist/cli.js +586 -9
- package/dist/cli.js.map +2 -2
- package/dist/importmap.json +3 -3
- package/dist/web.js +4 -4
- package/package.json +11 -9
- package/std/assembly/bindings/daemon.ts +69 -0
- package/std/assembly/bindings/toildb.ts +1 -1
- package/std/assembly/bindings/webcrypto.ts +48 -0
- package/std/assembly/daemon.ts +315 -0
- package/std/assembly/toildb.ts +149 -4
- package/std/assembly/toilscript.d.ts +114 -0
package/std/assembly/toildb.ts
CHANGED
|
@@ -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
|