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.
@@ -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