toilscript 0.1.27 → 0.1.29
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 +46 -2
- package/dist/cli.js +713 -34
- package/dist/cli.js.map +2 -2
- package/dist/importmap.json +2 -2
- package/dist/toilscript.generated.d.ts +46 -2
- package/dist/toilscript.js +179 -177
- package/dist/toilscript.js.map +4 -4
- package/dist/web.js +3 -3
- package/package.json +1 -1
- package/std/assembly/bindings/toildb.ts +230 -0
- package/std/assembly/crypto/subtle.ts +16 -20
- package/std/assembly/crypto.ts +22 -6
- package/std/assembly/index.d.ts +9 -4
- package/std/assembly/toildb.ts +432 -0
- package/std/assembly/toilscript.d.ts +96 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// ToilDB: the developer-facing edge-database API.
|
|
2
|
+
//
|
|
3
|
+
// `@database class App { @collection(...) users!: Record<User, UserId>; ... }`
|
|
4
|
+
// declares logical collections; the compiler (see parser `injectDatabaseBinding`)
|
|
5
|
+
// synthesizes a `@global App` singleton whose fields are these typed handles,
|
|
6
|
+
// resolved to numeric host handles once at module init.
|
|
7
|
+
//
|
|
8
|
+
// The handles marshal `@data` keys/values to the `env.data.*` host imports
|
|
9
|
+
// (`bindings/toildb`). Keys and values are `@data` types (they have the injected
|
|
10
|
+
// `encode(): Uint8Array` + `decodeInto(buf): void` instance methods). Both
|
|
11
|
+
// encode and decode go through INSTANCE methods on the generic type parameter,
|
|
12
|
+
// which AssemblyScript resolves at specialization (it cannot call the `decode`
|
|
13
|
+
// static through a type parameter, but `instantiate<V>()` + `v.decodeInto(buf)`
|
|
14
|
+
// works). Value types must be default-constructible.
|
|
15
|
+
|
|
16
|
+
import { toildbHost } from "bindings/toildb";
|
|
17
|
+
import { DataWriter } from "data";
|
|
18
|
+
|
|
19
|
+
/// Resolve a `"<db>/<collection>"` name to its numeric host handle. Called once
|
|
20
|
+
/// per collection at module init by the generated `App` binding.
|
|
21
|
+
export function __toildbResolve(name: string): u32 {
|
|
22
|
+
const nb = Uint8Array.wrap(String.UTF8.encode(name));
|
|
23
|
+
const out = new Uint8Array(4);
|
|
24
|
+
toildbHost.resolveCollection(nb.dataStart, nb.byteLength, out.dataStart);
|
|
25
|
+
return load<u32>(out.dataStart);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Pull the last stashed variable-length result of `len` bytes into a buffer.
|
|
29
|
+
function __toildbTake(len: i32): Uint8Array {
|
|
30
|
+
const buf = new Uint8Array(len);
|
|
31
|
+
toildbHost.takeResult(buf.dataStart, len);
|
|
32
|
+
return buf;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Pull a stashed result whose length is NOT known up front (e.g. the owner
|
|
36
|
+
/// returned by a failed `claim`): grow the buffer until `take_result` fits.
|
|
37
|
+
function __toildbTakeGrow(): Uint8Array {
|
|
38
|
+
let cap = 256;
|
|
39
|
+
let buf = new Uint8Array(cap);
|
|
40
|
+
let n = toildbHost.takeResult(buf.dataStart, cap);
|
|
41
|
+
while (n == -1) {
|
|
42
|
+
cap = cap * 2;
|
|
43
|
+
buf = new Uint8Array(cap);
|
|
44
|
+
n = toildbHost.takeResult(buf.dataStart, cap);
|
|
45
|
+
}
|
|
46
|
+
return buf.subarray(0, n);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// A mutable keyed-entity collection (spec 7.1). `V` is the `@data` value type,
|
|
50
|
+
/// `K` the `@data` key type.
|
|
51
|
+
@global
|
|
52
|
+
export class Record<V, K> {
|
|
53
|
+
private __handle: u32;
|
|
54
|
+
|
|
55
|
+
constructor(handle: u32) {
|
|
56
|
+
this.__handle = handle;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Return the record, or `null` if it does not exist.
|
|
60
|
+
get(key: K): V | null {
|
|
61
|
+
const kb = key.encode();
|
|
62
|
+
const status = toildbHost.get(this.__handle, kb.dataStart, kb.byteLength);
|
|
63
|
+
if (status < 0) return null;
|
|
64
|
+
const v = instantiate<V>();
|
|
65
|
+
v.decodeInto(__toildbTake(status));
|
|
66
|
+
return v;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Like `get`, but traps if the record is absent.
|
|
70
|
+
require(key: K): V {
|
|
71
|
+
const v = this.get(key);
|
|
72
|
+
if (v == null) unreachable();
|
|
73
|
+
return v!;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Bounded multi-get: one op, one result per key (in order), each the value
|
|
77
|
+
/// or `null` if absent. The key count is capped by the request budget.
|
|
78
|
+
getMany(keys: K[]): Array<V | null> {
|
|
79
|
+
const w = new DataWriter();
|
|
80
|
+
w.writeU32(<u32>keys.length);
|
|
81
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
82
|
+
w.writeBytes(keys[i].encode());
|
|
83
|
+
}
|
|
84
|
+
const blob = w.toBytes();
|
|
85
|
+
const status = toildbHost.getMany(this.__handle, blob.dataStart, blob.byteLength);
|
|
86
|
+
if (status < 0) unreachable();
|
|
87
|
+
const out = __toildbTake(status);
|
|
88
|
+
const results = new Array<V | null>();
|
|
89
|
+
let off: i32 = 0;
|
|
90
|
+
const count = load<u32>(out.dataStart + off);
|
|
91
|
+
off += 4;
|
|
92
|
+
for (let i: u32 = 0; i < count; i++) {
|
|
93
|
+
const present = load<u8>(out.dataStart + off);
|
|
94
|
+
off += 1;
|
|
95
|
+
if (present == 0) {
|
|
96
|
+
results.push(null);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const len = <i32>load<u32>(out.dataStart + off);
|
|
100
|
+
off += 4;
|
|
101
|
+
const v = instantiate<V>();
|
|
102
|
+
v.decodeInto(out.subarray(off, off + len));
|
|
103
|
+
off += len;
|
|
104
|
+
results.push(v);
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Whether the record exists.
|
|
110
|
+
exists(key: K): bool {
|
|
111
|
+
const kb = key.encode();
|
|
112
|
+
return toildbHost.exists(this.__handle, kb.dataStart, kb.byteLength) == 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Create the record if absent. Returns false if it already existed.
|
|
116
|
+
create(key: K, value: V): bool {
|
|
117
|
+
const kb = key.encode();
|
|
118
|
+
const vb = value.encode();
|
|
119
|
+
return toildbHost.create(
|
|
120
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
121
|
+
) == 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Apply a write through the key's home cell; returns the stored record.
|
|
125
|
+
patch(key: K, value: V): V {
|
|
126
|
+
const kb = key.encode();
|
|
127
|
+
const vb = value.encode();
|
|
128
|
+
const status = toildbHost.patch(
|
|
129
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
130
|
+
);
|
|
131
|
+
if (status < 0) unreachable();
|
|
132
|
+
const v = instantiate<V>();
|
|
133
|
+
v.decodeInto(__toildbTake(status));
|
|
134
|
+
return v;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Delete the record (idempotent).
|
|
138
|
+
delete(key: K): void {
|
|
139
|
+
const kb = key.encode();
|
|
140
|
+
toildbHost.del(this.__handle, kb.dataStart, kb.byteLength, 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Atomic fetch-and-delete (consume-once); returns the prior value or `null`.
|
|
144
|
+
getDelete(key: K): V | null {
|
|
145
|
+
const kb = key.encode();
|
|
146
|
+
const status = toildbHost.getDelete(this.__handle, kb.dataStart, kb.byteLength, 0);
|
|
147
|
+
if (status < 0) return null;
|
|
148
|
+
const v = instantiate<V>();
|
|
149
|
+
v.decodeInto(__toildbTake(status));
|
|
150
|
+
return v;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// A precomputed, read-optimized projection (spec 7.2): home pages,
|
|
155
|
+
/// leaderboards, rendered fragments. Read by any function kind; PUBLISHED only
|
|
156
|
+
/// by a `@derive`/`@job` (the host kind gate enforces it). `V` is the `@data`
|
|
157
|
+
/// value type, `K` the `@data` key type.
|
|
158
|
+
@global
|
|
159
|
+
export class View<V, K> {
|
|
160
|
+
private __handle: u32;
|
|
161
|
+
|
|
162
|
+
constructor(handle: u32) {
|
|
163
|
+
this.__handle = handle;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// The published view for `key`, or `null` if none has been published.
|
|
167
|
+
get(key: K): V | null {
|
|
168
|
+
const kb = key.encode();
|
|
169
|
+
const status = toildbHost.viewGet(this.__handle, kb.dataStart, kb.byteLength);
|
|
170
|
+
if (status < 0) return null;
|
|
171
|
+
const v = instantiate<V>();
|
|
172
|
+
v.decodeInto(__toildbTake(status));
|
|
173
|
+
return v;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// Like `get`, but traps if no view is published.
|
|
177
|
+
require(key: K): V {
|
|
178
|
+
const v = this.get(key);
|
|
179
|
+
if (v == null) unreachable();
|
|
180
|
+
return v!;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Publish (overwrite) the view for `key`. Derive/job only; the host assigns
|
|
184
|
+
/// the version so a later publish always supersedes an earlier one.
|
|
185
|
+
publish(key: K, value: V): void {
|
|
186
|
+
const kb = key.encode();
|
|
187
|
+
const vb = value.encode();
|
|
188
|
+
toildbHost.viewPublish(
|
|
189
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// The result of a `unique.claim` (spec 8.6). `claimed` is true when the caller
|
|
195
|
+
/// owns the key (a fresh claim or an idempotent re-claim of its own); when
|
|
196
|
+
/// false, `owner` is the value that currently holds the key.
|
|
197
|
+
@global
|
|
198
|
+
export class ClaimResult<V> {
|
|
199
|
+
constructor(public claimed: bool, public owner: V | null) {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// A globally-unique claim collection (spec 7.6): username, email, slug, ...
|
|
203
|
+
/// `V` is the `@data` OWNER value type, `K` the `@data` claim-key type.
|
|
204
|
+
@global
|
|
205
|
+
export class Unique<V, K> {
|
|
206
|
+
private __handle: u32;
|
|
207
|
+
|
|
208
|
+
constructor(handle: u32) {
|
|
209
|
+
this.__handle = handle;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// The value that owns `key`, or `null` if unclaimed.
|
|
213
|
+
lookup(key: K): V | null {
|
|
214
|
+
const kb = key.encode();
|
|
215
|
+
const status = toildbHost.uniqueLookup(this.__handle, kb.dataStart, kb.byteLength);
|
|
216
|
+
if (status < 0) return null;
|
|
217
|
+
const v = instantiate<V>();
|
|
218
|
+
v.decodeInto(__toildbTake(status));
|
|
219
|
+
return v;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Claim `key` for `value`. Returns whether the caller owns it, and (when
|
|
223
|
+
/// another owns it) who.
|
|
224
|
+
claim(key: K, value: V): ClaimResult<V> {
|
|
225
|
+
const kb = key.encode();
|
|
226
|
+
const vb = value.encode();
|
|
227
|
+
const tag = toildbHost.uniqueClaim(
|
|
228
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
229
|
+
);
|
|
230
|
+
if (tag < 0) unreachable();
|
|
231
|
+
if (tag == 1) {
|
|
232
|
+
// AlreadyClaimed: the current owner is stashed.
|
|
233
|
+
const owner = instantiate<V>();
|
|
234
|
+
owner.decodeInto(__toildbTakeGrow());
|
|
235
|
+
return new ClaimResult<V>(false, owner);
|
|
236
|
+
}
|
|
237
|
+
// 0 Claimed, 2 AlreadyOwnedByCaller -> the caller owns it.
|
|
238
|
+
return new ClaimResult<V>(true, null);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Release `key` (only the current owner may; a non-owner release traps).
|
|
242
|
+
release(key: K, value: V): void {
|
|
243
|
+
const kb = key.encode();
|
|
244
|
+
const vb = value.encode();
|
|
245
|
+
toildbHost.uniqueRelease(
|
|
246
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// An unordered set (spec 7.3): followers, tags, ACLs, room members. `M` is the
|
|
252
|
+
/// `@data` member type, `K` the `@data` set-key type.
|
|
253
|
+
@global
|
|
254
|
+
export class Membership<M, K> {
|
|
255
|
+
private __handle: u32;
|
|
256
|
+
|
|
257
|
+
constructor(handle: u32) {
|
|
258
|
+
this.__handle = handle;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/// Whether `member` is in the set keyed by `key`.
|
|
262
|
+
contains(key: K, member: M): bool {
|
|
263
|
+
const kb = key.encode();
|
|
264
|
+
const mb = member.encode();
|
|
265
|
+
return toildbHost.membershipContains(
|
|
266
|
+
this.__handle, kb.dataStart, kb.byteLength, mb.dataStart, mb.byteLength
|
|
267
|
+
) == 1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Add `member` to the set (idempotent).
|
|
271
|
+
add(key: K, member: M): void {
|
|
272
|
+
const kb = key.encode();
|
|
273
|
+
const mb = member.encode();
|
|
274
|
+
toildbHost.membershipAdd(
|
|
275
|
+
this.__handle, kb.dataStart, kb.byteLength, mb.dataStart, mb.byteLength, 0
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// Remove `member` from the set (idempotent).
|
|
280
|
+
remove(key: K, member: M): void {
|
|
281
|
+
const kb = key.encode();
|
|
282
|
+
const mb = member.encode();
|
|
283
|
+
toildbHost.membershipRemove(
|
|
284
|
+
this.__handle, kb.dataStart, kb.byteLength, mb.dataStart, mb.byteLength, 0
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/// Up to `limit` members of the set. Decodes each framed member into an `M`.
|
|
289
|
+
list(key: K, limit: i32): M[] {
|
|
290
|
+
const kb = key.encode();
|
|
291
|
+
const status = toildbHost.membershipList(this.__handle, kb.dataStart, kb.byteLength, limit);
|
|
292
|
+
if (status < 0) unreachable();
|
|
293
|
+
const blob = __toildbTake(status);
|
|
294
|
+
const out = new Array<M>();
|
|
295
|
+
let off: i32 = 0;
|
|
296
|
+
const count = load<u32>(blob.dataStart + off);
|
|
297
|
+
off += 4;
|
|
298
|
+
for (let i: u32 = 0; i < count; i++) {
|
|
299
|
+
const len = <i32>load<u32>(blob.dataStart + off);
|
|
300
|
+
off += 4;
|
|
301
|
+
const m = instantiate<M>();
|
|
302
|
+
m.decodeInto(blob.subarray(off, off + len));
|
|
303
|
+
out.push(m);
|
|
304
|
+
off += len;
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// A finite, strongly-consistent resource (spec 7.7): limited stock, seats,
|
|
311
|
+
/// rate grants. Reserve/confirm/cancel two-phase holds prevent oversell. `K` is
|
|
312
|
+
/// the `@data` key type (the value is the host-owned escrow ledger).
|
|
313
|
+
@global
|
|
314
|
+
export class Capacity<K> {
|
|
315
|
+
private __handle: u32;
|
|
316
|
+
|
|
317
|
+
constructor(handle: u32) {
|
|
318
|
+
this.__handle = handle;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// Units available to reserve right now.
|
|
322
|
+
available(key: K): i64 {
|
|
323
|
+
const kb = key.encode();
|
|
324
|
+
const status = toildbHost.capacityAvailable(this.__handle, kb.dataStart, kb.byteLength);
|
|
325
|
+
if (status < 0) unreachable();
|
|
326
|
+
return load<i64>(__toildbTake(status).dataStart);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/// Hold `amount` for `ttlMs` (it auto-releases if not confirmed in time).
|
|
330
|
+
/// Returns the reservation id (> 0), or 0 if there was not enough available
|
|
331
|
+
/// (no oversell).
|
|
332
|
+
reserve(key: K, amount: i64, ttlMs: i64): u64 {
|
|
333
|
+
const kb = key.encode();
|
|
334
|
+
const status = toildbHost.capacityReserve(
|
|
335
|
+
this.__handle, kb.dataStart, kb.byteLength, amount, ttlMs, 0
|
|
336
|
+
);
|
|
337
|
+
if (status == -2) return 0; // insufficient
|
|
338
|
+
if (status < 0) unreachable();
|
|
339
|
+
return load<u64>(__toildbTake(status).dataStart);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/// Finalize a hold into a permanent consume. Returns whether the id was valid.
|
|
343
|
+
confirm(key: K, reservationId: u64): bool {
|
|
344
|
+
const kb = key.encode();
|
|
345
|
+
return toildbHost.capacityConfirm(
|
|
346
|
+
this.__handle, kb.dataStart, kb.byteLength, reservationId as i64, 0
|
|
347
|
+
) == 1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/// Release a hold back to available (a confirmed sale cannot be cancelled).
|
|
351
|
+
cancel(key: K, reservationId: u64): bool {
|
|
352
|
+
const kb = key.encode();
|
|
353
|
+
return toildbHost.capacityCancel(
|
|
354
|
+
this.__handle, kb.dataStart, kb.byteLength, reservationId as i64, 0
|
|
355
|
+
) == 1;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// Set the ceiling (restock / reduce). `@job`/`@derive` only; the kind gate
|
|
359
|
+
/// (compile + runtime) enforces it.
|
|
360
|
+
setTotal(key: K, total: i64): void {
|
|
361
|
+
const kb = key.encode();
|
|
362
|
+
toildbHost.capacitySetTotal(this.__handle, kb.dataStart, kb.byteLength, total, 0);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/// A commutative integer counter (spec 7.4): likes, view counts, inventory.
|
|
367
|
+
/// `K` is the `@data` key type; the value is a host-aggregated i64 rollup (there
|
|
368
|
+
/// is no `set`, only `add` and `get`, so concurrent deltas never lose writes).
|
|
369
|
+
@global
|
|
370
|
+
export class Counter<K> {
|
|
371
|
+
private __handle: u32;
|
|
372
|
+
|
|
373
|
+
constructor(handle: u32) {
|
|
374
|
+
this.__handle = handle;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/// The current sum (0 if no deltas have been applied).
|
|
378
|
+
get(key: K): i64 {
|
|
379
|
+
const kb = key.encode();
|
|
380
|
+
const status = toildbHost.counterGet(this.__handle, kb.dataStart, kb.byteLength);
|
|
381
|
+
if (status < 0) unreachable();
|
|
382
|
+
const buf = __toildbTake(status);
|
|
383
|
+
return load<i64>(buf.dataStart);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/// Apply a (possibly negative) delta; saturates at the i64 bounds.
|
|
387
|
+
add(key: K, delta: i64): void {
|
|
388
|
+
const kb = key.encode();
|
|
389
|
+
toildbHost.counterAdd(this.__handle, kb.dataStart, kb.byteLength, delta, 0);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/// An append-only event log (spec 7.5): activity feeds, audit trails, the
|
|
394
|
+
/// fact stream a `@derive` consumes. `V` is the `@data` event type, `K` the
|
|
395
|
+
/// `@data` stream-key type.
|
|
396
|
+
@global
|
|
397
|
+
export class Events<V, K> {
|
|
398
|
+
private __handle: u32;
|
|
399
|
+
|
|
400
|
+
constructor(handle: u32) {
|
|
401
|
+
this.__handle = handle;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/// Append an event to the stream.
|
|
405
|
+
append(key: K, event: V): void {
|
|
406
|
+
const kb = key.encode();
|
|
407
|
+
const eb = event.encode();
|
|
408
|
+
toildbHost.append(this.__handle, kb.dataStart, kb.byteLength, eb.dataStart, eb.byteLength, 0);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/// The newest `limit` events, newest first. Decodes each framed event into a
|
|
412
|
+
/// `V`. The host frames them as `u32 count` then per event `u32 len + bytes`.
|
|
413
|
+
latest(key: K, limit: i32): V[] {
|
|
414
|
+
const kb = key.encode();
|
|
415
|
+
const status = toildbHost.latest(this.__handle, kb.dataStart, kb.byteLength, limit);
|
|
416
|
+
if (status < 0) unreachable();
|
|
417
|
+
const blob = __toildbTake(status);
|
|
418
|
+
const out = new Array<V>();
|
|
419
|
+
let off: i32 = 0;
|
|
420
|
+
const count = load<u32>(blob.dataStart + off);
|
|
421
|
+
off += 4;
|
|
422
|
+
for (let i: u32 = 0; i < count; i++) {
|
|
423
|
+
const len = <i32>load<u32>(blob.dataStart + off);
|
|
424
|
+
off += 4;
|
|
425
|
+
const ev = instantiate<V>();
|
|
426
|
+
ev.decodeInto(blob.subarray(off, off + len));
|
|
427
|
+
out.push(ev);
|
|
428
|
+
off += len;
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
@@ -85,6 +85,102 @@ declare function auth(target: Object, propertyKey: string | symbol, descriptor:
|
|
|
85
85
|
/** Declare the authenticated-user type (`@user class User { ... }`); enables `AuthService.getUser()`. */
|
|
86
86
|
declare function user(target: Function): void;
|
|
87
87
|
|
|
88
|
+
// --- ToilDB (@database / @collection + the @query/@action/... function kinds),
|
|
89
|
+
// handled natively by the compiler; typed here so editors accept the bare forms ---
|
|
90
|
+
|
|
91
|
+
/** Marks a class as a ToilDB database: each `@collection` field becomes a typed,
|
|
92
|
+
* lazily-resolved collection handle (`App.users.get(...)`). */
|
|
93
|
+
declare function database(target: Function): void;
|
|
94
|
+
|
|
95
|
+
/** Declares a `@database` field as a collection - a `Record`/`View`/`Unique`/
|
|
96
|
+
* `Counter`/`Events`/`Membership`/`Capacity` handle. */
|
|
97
|
+
declare function collection(target: Object, propertyKey: string | symbol): void;
|
|
98
|
+
|
|
99
|
+
/** ToilDB function kinds (spec 6) - the data ops a function may issue. `@query`
|
|
100
|
+
* is read-only; `@action` adds bounded writes/claims; `@derive` publishes
|
|
101
|
+
* views/rollups; `@job` is background work; `@admin` is control-plane only. The
|
|
102
|
+
* compiler enforces the family x kind matrix (a `@query` calling `.patch` is a
|
|
103
|
+
* compile error). */
|
|
104
|
+
declare function query(target: Function): void;
|
|
105
|
+
declare function action(target: Function): void;
|
|
106
|
+
declare function job(target: Function): void;
|
|
107
|
+
declare function derive(target: Function): void;
|
|
108
|
+
declare function admin(target: Function): void;
|
|
109
|
+
|
|
110
|
+
// The ToilDB collection HANDLES are ambient globals (no import) - the compiler
|
|
111
|
+
// provides them (`std/assembly/toildb`, `@global`); these typings let editors
|
|
112
|
+
// recognize `@collection users!: Record<User, UserId>` and `App.users.get(...)`.
|
|
113
|
+
// `K`/`V`/`M` are `@data` types (the binary codec the host marshals).
|
|
114
|
+
|
|
115
|
+
/** A mutable keyed-entity collection (spec 7.1): user profiles, items, sessions. */
|
|
116
|
+
declare class Record<V, K> {
|
|
117
|
+
get(key: K): V | null;
|
|
118
|
+
require(key: K): V;
|
|
119
|
+
exists(key: K): bool;
|
|
120
|
+
getMany(keys: K[]): (V | null)[];
|
|
121
|
+
create(key: K, value: V): bool;
|
|
122
|
+
patch(key: K, value: V): V;
|
|
123
|
+
delete(key: K): void;
|
|
124
|
+
getDelete(key: K): V | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A precomputed, read-optimized projection (spec 7.2): pages, leaderboards. */
|
|
128
|
+
declare class View<V, K> {
|
|
129
|
+
get(key: K): V | null;
|
|
130
|
+
require(key: K): V;
|
|
131
|
+
publish(key: K, value: V): void;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The result of a `unique.claim` (spec 8.6). */
|
|
135
|
+
declare class ClaimResult<V> {
|
|
136
|
+
claimed: bool;
|
|
137
|
+
owner: V | null;
|
|
138
|
+
constructor(claimed: bool, owner: V | null);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A globally-unique claim collection (spec 7.6): usernames, emails, slugs. */
|
|
142
|
+
declare class Unique<V, K> {
|
|
143
|
+
lookup(key: K): V | null;
|
|
144
|
+
claim(key: K, value: V): ClaimResult<V>;
|
|
145
|
+
release(key: K, value: V): void;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** An unordered set (spec 7.3): followers, tags, ACLs, room members. */
|
|
149
|
+
declare class Membership<M, K> {
|
|
150
|
+
contains(key: K, member: M): bool;
|
|
151
|
+
add(key: K, member: M): void;
|
|
152
|
+
remove(key: K, member: M): void;
|
|
153
|
+
list(key: K, limit: i32): M[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** A finite, strongly-consistent resource via escrow (spec 7.7): stock, seats. */
|
|
157
|
+
declare class Capacity<K> {
|
|
158
|
+
available(key: K): i64;
|
|
159
|
+
reserve(key: K, amount: i64, ttlMs: i64): u64;
|
|
160
|
+
confirm(key: K, reservationId: u64): bool;
|
|
161
|
+
cancel(key: K, reservationId: u64): bool;
|
|
162
|
+
setTotal(key: K, total: i64): void;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** A commutative integer counter (spec 7.4): likes, view counts, inventory. */
|
|
166
|
+
declare class Counter<K> {
|
|
167
|
+
get(key: K): i64;
|
|
168
|
+
add(key: K, delta: i64): void;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** An append-only event log (spec 7.5): activity feeds, audit trails. */
|
|
172
|
+
declare class Events<V, K> {
|
|
173
|
+
append(key: K, event: V): void;
|
|
174
|
+
latest(key: K, limit: i32): V[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// The handles are ambient (no import needed), but `import { Counter } from
|
|
178
|
+
// 'toildb'` is also accepted for editors that prefer explicit imports - it just
|
|
179
|
+
// re-exports the same globals.
|
|
180
|
+
declare module 'toildb' {
|
|
181
|
+
export { Record, View, Unique, ClaimResult, Membership, Capacity, Counter, Events };
|
|
182
|
+
}
|
|
183
|
+
|
|
88
184
|
// Big integers, native globals implemented in std/assembly/bignum. The
|
|
89
185
|
// arithmetic/bitwise/comparison operators
|
|
90
186
|
// (+ - * / % & | ^ << >> == != < > <= >=) are operator overloads resolved by
|