toilscript 0.1.26 → 0.1.28
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 +528 -31
- 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 +169 -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 +311 -0
- package/std/assembly/toilscript.d.ts +5 -2
package/dist/web.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var ASSEMBLYSCRIPT_VERSION = "0.1.
|
|
1
|
+
var ASSEMBLYSCRIPT_VERSION = "0.1.28";
|
|
2
2
|
var ASSEMBLYSCRIPT_IMPORTMAP = {
|
|
3
3
|
"imports": {
|
|
4
|
-
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
5
|
-
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
4
|
+
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.28/dist/toilscript.js",
|
|
5
|
+
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.28/dist/cli.js",
|
|
6
6
|
"binaryen": "https://cdn.jsdelivr.net/npm/binaryen@129.0.0-nightly.20260428/index.js",
|
|
7
7
|
"long": "https://cdn.jsdelivr.net/npm/long@5.3.2/index.js"
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Host-import declarations for the ToilDB data API. The production edge
|
|
2
|
+
// (toil-backend `src/wasm/host/import_functions/db`) and the toiljs dev server
|
|
3
|
+
// both provide these under the `env` namespace. All byte regions are passed as
|
|
4
|
+
// a (pointer, length) pair into guest linear memory.
|
|
5
|
+
//
|
|
6
|
+
// Collections are resolved by name ONCE at module init (`resolveCollection`),
|
|
7
|
+
// which returns an opaque numeric handle; request-time ops pass the handle.
|
|
8
|
+
//
|
|
9
|
+
// Return convention (see toildb/ABI.md):
|
|
10
|
+
// >= 0 success: a length (a value stashed for `takeResult`), a count, a
|
|
11
|
+
// boolean (0/1), a tag, or 0 (ok).
|
|
12
|
+
// -1 output buffer too small (retry `takeResult` with a bigger buffer).
|
|
13
|
+
// -2 absent (a normal null / not-found).
|
|
14
|
+
// <= -1000 a typed failure; the diagnostic is TDL(|v| - 1000).
|
|
15
|
+
//
|
|
16
|
+
// Variable-length results (a fetched value, a patched record, a claim owner)
|
|
17
|
+
// use the two-step pull: the op returns the stashed length, then the guest
|
|
18
|
+
// allocates a buffer and calls `takeResult` to copy the bytes out.
|
|
19
|
+
//
|
|
20
|
+
// `idemPtr` is 0 for none, otherwise a pointer to a 16-byte idempotency key.
|
|
21
|
+
//
|
|
22
|
+
// This is the guest half of the ABI contract; the byte framing of keys/values
|
|
23
|
+
// is the `@data` binary codec (`DataWriter`/`DataReader`).
|
|
24
|
+
|
|
25
|
+
export namespace toildbHost {
|
|
26
|
+
// Resolve a collection by its "<db>/<collection>" name; writes the u32 handle
|
|
27
|
+
// to outHandlePtr. Returns 0 on success or a negative TDL code.
|
|
28
|
+
// @ts-ignore: decorator
|
|
29
|
+
@external("env", "data.resolve_collection")
|
|
30
|
+
export declare function resolveCollection(namePtr: usize, nameLen: i32, outHandlePtr: usize): i32;
|
|
31
|
+
|
|
32
|
+
// record.get -> value length (stashed) | -2 absent | negative error.
|
|
33
|
+
// @ts-ignore: decorator
|
|
34
|
+
@external("env", "data.get")
|
|
35
|
+
export declare function get(handle: u32, keyPtr: usize, keyLen: i32): i32;
|
|
36
|
+
|
|
37
|
+
// record bounded multi-get. Input at keysPtr: u32 count + per key (u32 len +
|
|
38
|
+
// bytes). Result (stashed): u32 count + per item u8 present (+ u32 len + bytes
|
|
39
|
+
// when present), in request order. Returns the stashed length | negative error.
|
|
40
|
+
// @ts-ignore: decorator
|
|
41
|
+
@external("env", "data.get_many")
|
|
42
|
+
export declare function getMany(handle: u32, keysPtr: usize, keysLen: i32): i32;
|
|
43
|
+
|
|
44
|
+
// record.exists -> 1 | 0 | negative error.
|
|
45
|
+
// @ts-ignore: decorator
|
|
46
|
+
@external("env", "data.exists")
|
|
47
|
+
export declare function exists(handle: u32, keyPtr: usize, keyLen: i32): i32;
|
|
48
|
+
|
|
49
|
+
// record.create -> 0 ok | AlreadyExists/typed code.
|
|
50
|
+
// @ts-ignore: decorator
|
|
51
|
+
@external("env", "data.create")
|
|
52
|
+
export declare function create(
|
|
53
|
+
handle: u32,
|
|
54
|
+
keyPtr: usize,
|
|
55
|
+
keyLen: i32,
|
|
56
|
+
valPtr: usize,
|
|
57
|
+
valLen: i32,
|
|
58
|
+
idemPtr: usize
|
|
59
|
+
): i32;
|
|
60
|
+
|
|
61
|
+
// record.patch -> length of the new record (stashed) | negative error.
|
|
62
|
+
// @ts-ignore: decorator
|
|
63
|
+
@external("env", "data.patch")
|
|
64
|
+
export declare function patch(
|
|
65
|
+
handle: u32,
|
|
66
|
+
keyPtr: usize,
|
|
67
|
+
keyLen: i32,
|
|
68
|
+
patchPtr: usize,
|
|
69
|
+
patchLen: i32,
|
|
70
|
+
idemPtr: usize
|
|
71
|
+
): i32;
|
|
72
|
+
|
|
73
|
+
// record.delete -> 0 ok | negative error.
|
|
74
|
+
// @ts-ignore: decorator
|
|
75
|
+
@external("env", "data.delete")
|
|
76
|
+
export declare function del(handle: u32, keyPtr: usize, keyLen: i32, idemPtr: usize): i32;
|
|
77
|
+
|
|
78
|
+
// record consume-once fetch-and-delete -> prior value length (stashed) | -2.
|
|
79
|
+
// @ts-ignore: decorator
|
|
80
|
+
@external("env", "data.get_delete")
|
|
81
|
+
export declare function getDelete(handle: u32, keyPtr: usize, keyLen: i32, idemPtr: usize): i32;
|
|
82
|
+
|
|
83
|
+
// unique.lookup -> owner value length (stashed) | -2 absent.
|
|
84
|
+
// @ts-ignore: decorator
|
|
85
|
+
@external("env", "data.unique_lookup")
|
|
86
|
+
export declare function uniqueLookup(handle: u32, keyPtr: usize, keyLen: i32): i32;
|
|
87
|
+
|
|
88
|
+
// unique.claim -> 0 Claimed | 1 AlreadyClaimed (owner stashed) | 2 owned-by-caller | neg.
|
|
89
|
+
// @ts-ignore: decorator
|
|
90
|
+
@external("env", "data.unique_claim")
|
|
91
|
+
export declare function uniqueClaim(
|
|
92
|
+
handle: u32,
|
|
93
|
+
keyPtr: usize,
|
|
94
|
+
keyLen: i32,
|
|
95
|
+
valPtr: usize,
|
|
96
|
+
valLen: i32,
|
|
97
|
+
idemPtr: usize
|
|
98
|
+
): i32;
|
|
99
|
+
|
|
100
|
+
// unique.release -> 0 ok | neg (Conflict if not the owner).
|
|
101
|
+
// @ts-ignore: decorator
|
|
102
|
+
@external("env", "data.unique_release")
|
|
103
|
+
export declare function uniqueRelease(
|
|
104
|
+
handle: u32,
|
|
105
|
+
keyPtr: usize,
|
|
106
|
+
keyLen: i32,
|
|
107
|
+
valPtr: usize,
|
|
108
|
+
valLen: i32,
|
|
109
|
+
idemPtr: usize
|
|
110
|
+
): i32;
|
|
111
|
+
|
|
112
|
+
// view.get -> view value length (stashed) | -2 absent | negative error.
|
|
113
|
+
// @ts-ignore: decorator
|
|
114
|
+
@external("env", "data.view_get")
|
|
115
|
+
export declare function viewGet(handle: u32, keyPtr: usize, keyLen: i32): i32;
|
|
116
|
+
|
|
117
|
+
// view.publish -> 0 ok | negative error (derive/job only; the host gate enforces).
|
|
118
|
+
// @ts-ignore: decorator
|
|
119
|
+
@external("env", "data.view_publish")
|
|
120
|
+
export declare function viewPublish(
|
|
121
|
+
handle: u32,
|
|
122
|
+
keyPtr: usize,
|
|
123
|
+
keyLen: i32,
|
|
124
|
+
valPtr: usize,
|
|
125
|
+
valLen: i32,
|
|
126
|
+
idemPtr: usize
|
|
127
|
+
): i32;
|
|
128
|
+
|
|
129
|
+
// counter.get -> 8 (the i64 sum stashed as 8 LE bytes) | negative error.
|
|
130
|
+
// @ts-ignore: decorator
|
|
131
|
+
@external("env", "data.counter_get")
|
|
132
|
+
export declare function counterGet(handle: u32, keyPtr: usize, keyLen: i32): i32;
|
|
133
|
+
|
|
134
|
+
// counter.add(delta: i64) -> 0 ok | negative error.
|
|
135
|
+
// @ts-ignore: decorator
|
|
136
|
+
@external("env", "data.counter_add")
|
|
137
|
+
export declare function counterAdd(
|
|
138
|
+
handle: u32,
|
|
139
|
+
keyPtr: usize,
|
|
140
|
+
keyLen: i32,
|
|
141
|
+
delta: i64,
|
|
142
|
+
idemPtr: usize
|
|
143
|
+
): i32;
|
|
144
|
+
|
|
145
|
+
// events.append -> 0 ok | negative error.
|
|
146
|
+
// @ts-ignore: decorator
|
|
147
|
+
@external("env", "data.append")
|
|
148
|
+
export declare function append(
|
|
149
|
+
handle: u32,
|
|
150
|
+
keyPtr: usize,
|
|
151
|
+
keyLen: i32,
|
|
152
|
+
evPtr: usize,
|
|
153
|
+
evLen: i32,
|
|
154
|
+
idemPtr: usize
|
|
155
|
+
): i32;
|
|
156
|
+
|
|
157
|
+
// events.latest(limit) -> framed-list length (stashed) | negative error.
|
|
158
|
+
// The blob is `u32 count` then per event `u32 len + bytes`, newest first.
|
|
159
|
+
// @ts-ignore: decorator
|
|
160
|
+
@external("env", "data.latest")
|
|
161
|
+
export declare function latest(handle: u32, keyPtr: usize, keyLen: i32, limit: i32): i32;
|
|
162
|
+
|
|
163
|
+
// Copy the last stashed variable-length result into outPtr (outLen must equal
|
|
164
|
+
// the length the producing op returned). Returns bytes written, or -1 if the
|
|
165
|
+
// buffer is too small.
|
|
166
|
+
// @ts-ignore: decorator
|
|
167
|
+
@external("env", "data.take_result")
|
|
168
|
+
export declare function takeResult(outPtr: usize, outLen: i32): i32;
|
|
169
|
+
}
|
|
@@ -7,12 +7,11 @@ import { webcrypto } from "bindings/webcrypto";
|
|
|
7
7
|
import { CryptoKey } from "crypto/key";
|
|
8
8
|
import {
|
|
9
9
|
AlgorithmParams,
|
|
10
|
-
algId,
|
|
11
|
-
formatId,
|
|
12
10
|
cryptoError,
|
|
13
|
-
|
|
11
|
+
FMT_RAW,
|
|
14
12
|
FMT_PKCS8,
|
|
15
13
|
FMT_SPKI,
|
|
14
|
+
FMT_JWK,
|
|
16
15
|
ALG_AES_GCM,
|
|
17
16
|
ALG_AES_CBC,
|
|
18
17
|
ALG_AES_CTR,
|
|
@@ -43,26 +42,24 @@ function isSymmetricAlg(alg: i32): bool {
|
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
export class SubtleCrypto {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return drain(webcrypto.digest(id, data.dataStart, data.byteLength));
|
|
45
|
+
// `algorithm` is a hash-id selector: an `ALG_SHA_*` const (NOT a magic string).
|
|
46
|
+
digest(algorithm: i32, data: Uint8Array): Uint8Array {
|
|
47
|
+
return drain(webcrypto.digest(algorithm, data.dataStart, data.byteLength));
|
|
50
48
|
}
|
|
51
49
|
|
|
50
|
+
// `format` is an `FMT_*` selector const (FMT_RAW / FMT_PKCS8 / FMT_SPKI).
|
|
52
51
|
importKey(
|
|
53
|
-
format:
|
|
52
|
+
format: i32,
|
|
54
53
|
keyData: Uint8Array,
|
|
55
54
|
algorithm: AlgorithmParams,
|
|
56
55
|
extractable: bool,
|
|
57
56
|
usages: i32
|
|
58
57
|
): CryptoKey {
|
|
59
|
-
|
|
60
|
-
if (fmt < 0) throw new Error("Unknown key format: " + format);
|
|
61
|
-
if (fmt == FMT_JWK) throw new Error("jwk key format is not supported");
|
|
58
|
+
if (format == FMT_JWK) throw new Error("jwk key format is not supported");
|
|
62
59
|
let p = algorithm.pack();
|
|
63
60
|
let alg = load<i32>(p.dataStart); // first packed field is the alg id
|
|
64
61
|
let handle = webcrypto.importKey(
|
|
65
|
-
|
|
62
|
+
format,
|
|
66
63
|
keyData.dataStart,
|
|
67
64
|
keyData.byteLength,
|
|
68
65
|
p.dataStart,
|
|
@@ -73,18 +70,17 @@ export class SubtleCrypto {
|
|
|
73
70
|
if (handle < 0) throw new Error(cryptoError(handle));
|
|
74
71
|
|
|
75
72
|
let type: string;
|
|
76
|
-
if (
|
|
77
|
-
else if (
|
|
73
|
+
if (format == FMT_PKCS8) type = "private";
|
|
74
|
+
else if (format == FMT_SPKI) type = "public";
|
|
78
75
|
else type = isSymmetricAlg(alg) ? "secret" : "public";
|
|
79
76
|
|
|
80
77
|
return new CryptoKey(handle, type, extractable, alg, usages);
|
|
81
78
|
}
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
return drain(webcrypto.exportKey(fmt, key.handle));
|
|
80
|
+
// `format` is an `FMT_*` selector const (FMT_RAW / FMT_PKCS8 / FMT_SPKI).
|
|
81
|
+
exportKey(format: i32, key: CryptoKey): Uint8Array {
|
|
82
|
+
if (format == FMT_JWK) throw new Error("jwk key format is not supported");
|
|
83
|
+
return drain(webcrypto.exportKey(format, key.handle));
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
encrypt(algorithm: AlgorithmParams, key: CryptoKey, data: Uint8Array): Uint8Array {
|
|
@@ -146,6 +142,6 @@ export class SubtleCrypto {
|
|
|
146
142
|
usages: i32
|
|
147
143
|
): CryptoKey {
|
|
148
144
|
let bits = this.deriveBits(algorithm, baseKey, lengthBits);
|
|
149
|
-
return this.importKey(
|
|
145
|
+
return this.importKey(FMT_RAW, bits, derivedKeyAlgorithm, extractable, usages);
|
|
150
146
|
}
|
|
151
147
|
}
|
package/std/assembly/crypto.ts
CHANGED
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
|
|
12
12
|
import { webcrypto } from "bindings/webcrypto";
|
|
13
13
|
import { SubtleCrypto } from "crypto/subtle";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
HmacImportParams,
|
|
16
|
+
HmacParams,
|
|
17
|
+
ALG_SHA_1,
|
|
18
|
+
ALG_SHA_256,
|
|
19
|
+
ALG_SHA_384,
|
|
20
|
+
ALG_SHA_512,
|
|
21
|
+
FMT_RAW,
|
|
22
|
+
USAGE_SIGN,
|
|
23
|
+
} from "crypto/algorithms";
|
|
15
24
|
import { Encoding } from "encoding";
|
|
16
25
|
|
|
17
26
|
// Re-export the public surface so guests can import everything from "crypto".
|
|
@@ -51,6 +60,13 @@ export {
|
|
|
51
60
|
ALG_X25519,
|
|
52
61
|
ALG_HKDF,
|
|
53
62
|
ALG_PBKDF2,
|
|
63
|
+
ALG_SHA3_256,
|
|
64
|
+
ALG_SHA3_384,
|
|
65
|
+
ALG_SHA3_512,
|
|
66
|
+
FMT_RAW,
|
|
67
|
+
FMT_PKCS8,
|
|
68
|
+
FMT_SPKI,
|
|
69
|
+
FMT_JWK,
|
|
54
70
|
CURVE_P256,
|
|
55
71
|
CURVE_P384,
|
|
56
72
|
USAGE_ENCRYPT,
|
|
@@ -101,16 +117,16 @@ export namespace crypto {
|
|
|
101
117
|
|
|
102
118
|
// --- Ergonomic digest helpers (thin wrappers over subtle.digest) ----------
|
|
103
119
|
export function sha1(data: Uint8Array): Uint8Array {
|
|
104
|
-
return subtle.digest(
|
|
120
|
+
return subtle.digest(ALG_SHA_1, data);
|
|
105
121
|
}
|
|
106
122
|
export function sha256(data: Uint8Array): Uint8Array {
|
|
107
|
-
return subtle.digest(
|
|
123
|
+
return subtle.digest(ALG_SHA_256, data);
|
|
108
124
|
}
|
|
109
125
|
export function sha384(data: Uint8Array): Uint8Array {
|
|
110
|
-
return subtle.digest(
|
|
126
|
+
return subtle.digest(ALG_SHA_384, data);
|
|
111
127
|
}
|
|
112
128
|
export function sha512(data: Uint8Array): Uint8Array {
|
|
113
|
-
return subtle.digest(
|
|
129
|
+
return subtle.digest(ALG_SHA_512, data);
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
// String-input variants (UTF-8 encode, then hash).
|
|
@@ -129,7 +145,7 @@ export namespace crypto {
|
|
|
129
145
|
|
|
130
146
|
/// One-shot HMAC-SHA-256 over raw key + message bytes.
|
|
131
147
|
export function hmacSha256(key: Uint8Array, msg: Uint8Array): Uint8Array {
|
|
132
|
-
let k = subtle.importKey(
|
|
148
|
+
let k = subtle.importKey(FMT_RAW, key, new HmacImportParams(ALG_SHA_256), false, USAGE_SIGN);
|
|
133
149
|
return subtle.sign(new HmacParams(), k, msg);
|
|
134
150
|
}
|
|
135
151
|
export function hmacSha256Text(key: Uint8Array, msg: string): Uint8Array {
|
package/std/assembly/index.d.ts
CHANGED
|
@@ -2853,11 +2853,14 @@ declare class X25519ImportParams extends AlgorithmParams {}
|
|
|
2853
2853
|
declare class EcdhParams extends AlgorithmParams {
|
|
2854
2854
|
constructor(alg: i32, publicKeyHandle: i32);
|
|
2855
2855
|
}
|
|
2856
|
-
/** Synchronous SubtleCrypto (no Promises). Returns values directly.
|
|
2856
|
+
/** Synchronous SubtleCrypto (no Promises). Returns values directly. The hash and
|
|
2857
|
+
* key-format arguments are typed ABI ids (no magic strings): pass an `ALG_SHA_*`
|
|
2858
|
+
* const to `digest`, and an `FMT_*` const (`FMT_RAW` / `FMT_PKCS8` / `FMT_SPKI`)
|
|
2859
|
+
* to `importKey` / `exportKey`. */
|
|
2857
2860
|
declare class SubtleCrypto {
|
|
2858
|
-
digest(algorithm:
|
|
2859
|
-
importKey(format:
|
|
2860
|
-
exportKey(format:
|
|
2861
|
+
digest(algorithm: i32, data: Uint8Array): Uint8Array;
|
|
2862
|
+
importKey(format: i32, keyData: Uint8Array, algorithm: AlgorithmParams, extractable: bool, usages: i32): CryptoKey;
|
|
2863
|
+
exportKey(format: i32, key: CryptoKey): Uint8Array;
|
|
2861
2864
|
encrypt(algorithm: AlgorithmParams, key: CryptoKey, data: Uint8Array): Uint8Array;
|
|
2862
2865
|
decrypt(algorithm: AlgorithmParams, key: CryptoKey, data: Uint8Array): Uint8Array;
|
|
2863
2866
|
sign(algorithm: AlgorithmParams, key: CryptoKey, data: Uint8Array): Uint8Array;
|
|
@@ -2868,6 +2871,8 @@ declare class SubtleCrypto {
|
|
|
2868
2871
|
|
|
2869
2872
|
// Algorithm / format / curve / usage ids (the Web Crypto ABI contract).
|
|
2870
2873
|
declare const ALG_SHA_1: i32, ALG_SHA_256: i32, ALG_SHA_384: i32, ALG_SHA_512: i32;
|
|
2874
|
+
declare const ALG_SHA3_256: i32, ALG_SHA3_384: i32, ALG_SHA3_512: i32;
|
|
2875
|
+
declare const FMT_RAW: i32, FMT_PKCS8: i32, FMT_SPKI: i32, FMT_JWK: i32;
|
|
2871
2876
|
declare const ALG_AES_GCM: i32, ALG_AES_CBC: i32, ALG_AES_CTR: i32, ALG_AES_KW: i32;
|
|
2872
2877
|
declare const ALG_HMAC: i32, ALG_ECDSA: i32, ALG_ED25519: i32, ALG_ECDH: i32, ALG_X25519: i32, ALG_HKDF: i32, ALG_PBKDF2: i32;
|
|
2873
2878
|
declare const CURVE_P256: i32, CURVE_P384: i32;
|
|
@@ -0,0 +1,311 @@
|
|
|
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
|
+
export class Record<V, K> {
|
|
52
|
+
private __handle: u32;
|
|
53
|
+
|
|
54
|
+
constructor(handle: u32) {
|
|
55
|
+
this.__handle = handle;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Return the record, or `null` if it does not exist.
|
|
59
|
+
get(key: K): V | null {
|
|
60
|
+
const kb = key.encode();
|
|
61
|
+
const status = toildbHost.get(this.__handle, kb.dataStart, kb.byteLength);
|
|
62
|
+
if (status < 0) return null;
|
|
63
|
+
const v = instantiate<V>();
|
|
64
|
+
v.decodeInto(__toildbTake(status));
|
|
65
|
+
return v;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Like `get`, but traps if the record is absent.
|
|
69
|
+
require(key: K): V {
|
|
70
|
+
const v = this.get(key);
|
|
71
|
+
if (v == null) unreachable();
|
|
72
|
+
return v!;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Bounded multi-get: one op, one result per key (in order), each the value
|
|
76
|
+
/// or `null` if absent. The key count is capped by the request budget.
|
|
77
|
+
getMany(keys: K[]): Array<V | null> {
|
|
78
|
+
const w = new DataWriter();
|
|
79
|
+
w.writeU32(<u32>keys.length);
|
|
80
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
81
|
+
w.writeBytes(keys[i].encode());
|
|
82
|
+
}
|
|
83
|
+
const blob = w.toBytes();
|
|
84
|
+
const status = toildbHost.getMany(this.__handle, blob.dataStart, blob.byteLength);
|
|
85
|
+
if (status < 0) unreachable();
|
|
86
|
+
const out = __toildbTake(status);
|
|
87
|
+
const results = new Array<V | null>();
|
|
88
|
+
let off: i32 = 0;
|
|
89
|
+
const count = load<u32>(out.dataStart + off);
|
|
90
|
+
off += 4;
|
|
91
|
+
for (let i: u32 = 0; i < count; i++) {
|
|
92
|
+
const present = load<u8>(out.dataStart + off);
|
|
93
|
+
off += 1;
|
|
94
|
+
if (present == 0) {
|
|
95
|
+
results.push(null);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const len = <i32>load<u32>(out.dataStart + off);
|
|
99
|
+
off += 4;
|
|
100
|
+
const v = instantiate<V>();
|
|
101
|
+
v.decodeInto(out.subarray(off, off + len));
|
|
102
|
+
off += len;
|
|
103
|
+
results.push(v);
|
|
104
|
+
}
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Whether the record exists.
|
|
109
|
+
exists(key: K): bool {
|
|
110
|
+
const kb = key.encode();
|
|
111
|
+
return toildbHost.exists(this.__handle, kb.dataStart, kb.byteLength) == 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Create the record if absent. Returns false if it already existed.
|
|
115
|
+
create(key: K, value: V): bool {
|
|
116
|
+
const kb = key.encode();
|
|
117
|
+
const vb = value.encode();
|
|
118
|
+
return toildbHost.create(
|
|
119
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
120
|
+
) == 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Apply a write through the key's home cell; returns the stored record.
|
|
124
|
+
patch(key: K, value: V): V {
|
|
125
|
+
const kb = key.encode();
|
|
126
|
+
const vb = value.encode();
|
|
127
|
+
const status = toildbHost.patch(
|
|
128
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
129
|
+
);
|
|
130
|
+
if (status < 0) unreachable();
|
|
131
|
+
const v = instantiate<V>();
|
|
132
|
+
v.decodeInto(__toildbTake(status));
|
|
133
|
+
return v;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Delete the record (idempotent).
|
|
137
|
+
delete(key: K): void {
|
|
138
|
+
const kb = key.encode();
|
|
139
|
+
toildbHost.del(this.__handle, kb.dataStart, kb.byteLength, 0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Atomic fetch-and-delete (consume-once); returns the prior value or `null`.
|
|
143
|
+
getDelete(key: K): V | null {
|
|
144
|
+
const kb = key.encode();
|
|
145
|
+
const status = toildbHost.getDelete(this.__handle, kb.dataStart, kb.byteLength, 0);
|
|
146
|
+
if (status < 0) return null;
|
|
147
|
+
const v = instantiate<V>();
|
|
148
|
+
v.decodeInto(__toildbTake(status));
|
|
149
|
+
return v;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// A precomputed, read-optimized projection (spec 7.2): home pages,
|
|
154
|
+
/// leaderboards, rendered fragments. Read by any function kind; PUBLISHED only
|
|
155
|
+
/// by a `@derive`/`@job` (the host kind gate enforces it). `V` is the `@data`
|
|
156
|
+
/// value type, `K` the `@data` key type.
|
|
157
|
+
export class View<V, K> {
|
|
158
|
+
private __handle: u32;
|
|
159
|
+
|
|
160
|
+
constructor(handle: u32) {
|
|
161
|
+
this.__handle = handle;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// The published view for `key`, or `null` if none has been published.
|
|
165
|
+
get(key: K): V | null {
|
|
166
|
+
const kb = key.encode();
|
|
167
|
+
const status = toildbHost.viewGet(this.__handle, kb.dataStart, kb.byteLength);
|
|
168
|
+
if (status < 0) return null;
|
|
169
|
+
const v = instantiate<V>();
|
|
170
|
+
v.decodeInto(__toildbTake(status));
|
|
171
|
+
return v;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Like `get`, but traps if no view is published.
|
|
175
|
+
require(key: K): V {
|
|
176
|
+
const v = this.get(key);
|
|
177
|
+
if (v == null) unreachable();
|
|
178
|
+
return v!;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Publish (overwrite) the view for `key`. Derive/job only; the host assigns
|
|
182
|
+
/// the version so a later publish always supersedes an earlier one.
|
|
183
|
+
publish(key: K, value: V): void {
|
|
184
|
+
const kb = key.encode();
|
|
185
|
+
const vb = value.encode();
|
|
186
|
+
toildbHost.viewPublish(
|
|
187
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// The result of a `unique.claim` (spec 8.6). `claimed` is true when the caller
|
|
193
|
+
/// owns the key (a fresh claim or an idempotent re-claim of its own); when
|
|
194
|
+
/// false, `owner` is the value that currently holds the key.
|
|
195
|
+
export class ClaimResult<V> {
|
|
196
|
+
constructor(public claimed: bool, public owner: V | null) {}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// A globally-unique claim collection (spec 7.6): username, email, slug, ...
|
|
200
|
+
/// `V` is the `@data` OWNER value type, `K` the `@data` claim-key type.
|
|
201
|
+
export class Unique<V, K> {
|
|
202
|
+
private __handle: u32;
|
|
203
|
+
|
|
204
|
+
constructor(handle: u32) {
|
|
205
|
+
this.__handle = handle;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// The value that owns `key`, or `null` if unclaimed.
|
|
209
|
+
lookup(key: K): V | null {
|
|
210
|
+
const kb = key.encode();
|
|
211
|
+
const status = toildbHost.uniqueLookup(this.__handle, kb.dataStart, kb.byteLength);
|
|
212
|
+
if (status < 0) return null;
|
|
213
|
+
const v = instantiate<V>();
|
|
214
|
+
v.decodeInto(__toildbTake(status));
|
|
215
|
+
return v;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Claim `key` for `value`. Returns whether the caller owns it, and (when
|
|
219
|
+
/// another owns it) who.
|
|
220
|
+
claim(key: K, value: V): ClaimResult<V> {
|
|
221
|
+
const kb = key.encode();
|
|
222
|
+
const vb = value.encode();
|
|
223
|
+
const tag = toildbHost.uniqueClaim(
|
|
224
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
225
|
+
);
|
|
226
|
+
if (tag < 0) unreachable();
|
|
227
|
+
if (tag == 1) {
|
|
228
|
+
// AlreadyClaimed: the current owner is stashed.
|
|
229
|
+
const owner = instantiate<V>();
|
|
230
|
+
owner.decodeInto(__toildbTakeGrow());
|
|
231
|
+
return new ClaimResult<V>(false, owner);
|
|
232
|
+
}
|
|
233
|
+
// 0 Claimed, 2 AlreadyOwnedByCaller -> the caller owns it.
|
|
234
|
+
return new ClaimResult<V>(true, null);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Release `key` (only the current owner may; a non-owner release traps).
|
|
238
|
+
release(key: K, value: V): void {
|
|
239
|
+
const kb = key.encode();
|
|
240
|
+
const vb = value.encode();
|
|
241
|
+
toildbHost.uniqueRelease(
|
|
242
|
+
this.__handle, kb.dataStart, kb.byteLength, vb.dataStart, vb.byteLength, 0
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// A commutative integer counter (spec 7.4): likes, view counts, inventory.
|
|
248
|
+
/// `K` is the `@data` key type; the value is a host-aggregated i64 rollup (there
|
|
249
|
+
/// is no `set`, only `add` and `get`, so concurrent deltas never lose writes).
|
|
250
|
+
export class Counter<K> {
|
|
251
|
+
private __handle: u32;
|
|
252
|
+
|
|
253
|
+
constructor(handle: u32) {
|
|
254
|
+
this.__handle = handle;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// The current sum (0 if no deltas have been applied).
|
|
258
|
+
get(key: K): i64 {
|
|
259
|
+
const kb = key.encode();
|
|
260
|
+
const status = toildbHost.counterGet(this.__handle, kb.dataStart, kb.byteLength);
|
|
261
|
+
if (status < 0) unreachable();
|
|
262
|
+
const buf = __toildbTake(status);
|
|
263
|
+
return load<i64>(buf.dataStart);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// Apply a (possibly negative) delta; saturates at the i64 bounds.
|
|
267
|
+
add(key: K, delta: i64): void {
|
|
268
|
+
const kb = key.encode();
|
|
269
|
+
toildbHost.counterAdd(this.__handle, kb.dataStart, kb.byteLength, delta, 0);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// An append-only event log (spec 7.5): activity feeds, audit trails, the
|
|
274
|
+
/// fact stream a `@derive` consumes. `V` is the `@data` event type, `K` the
|
|
275
|
+
/// `@data` stream-key type.
|
|
276
|
+
export class Events<V, K> {
|
|
277
|
+
private __handle: u32;
|
|
278
|
+
|
|
279
|
+
constructor(handle: u32) {
|
|
280
|
+
this.__handle = handle;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Append an event to the stream.
|
|
284
|
+
append(key: K, event: V): void {
|
|
285
|
+
const kb = key.encode();
|
|
286
|
+
const eb = event.encode();
|
|
287
|
+
toildbHost.append(this.__handle, kb.dataStart, kb.byteLength, eb.dataStart, eb.byteLength, 0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// The newest `limit` events, newest first. Decodes each framed event into a
|
|
291
|
+
/// `V`. The host frames them as `u32 count` then per event `u32 len + bytes`.
|
|
292
|
+
latest(key: K, limit: i32): V[] {
|
|
293
|
+
const kb = key.encode();
|
|
294
|
+
const status = toildbHost.latest(this.__handle, kb.dataStart, kb.byteLength, limit);
|
|
295
|
+
if (status < 0) unreachable();
|
|
296
|
+
const blob = __toildbTake(status);
|
|
297
|
+
const out = new Array<V>();
|
|
298
|
+
let off: i32 = 0;
|
|
299
|
+
const count = load<u32>(blob.dataStart + off);
|
|
300
|
+
off += 4;
|
|
301
|
+
for (let i: u32 = 0; i < count; i++) {
|
|
302
|
+
const len = <i32>load<u32>(blob.dataStart + off);
|
|
303
|
+
off += 4;
|
|
304
|
+
const ev = instantiate<V>();
|
|
305
|
+
ev.decodeInto(blob.subarray(off, off + len));
|
|
306
|
+
out.push(ev);
|
|
307
|
+
off += len;
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
}
|