toiljs 0.0.55 → 0.0.57
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/CHANGELOG.md +10 -0
- package/README.md +72 -14
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +293 -142
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.js +1 -1
- package/build/client/components/Image.d.ts +1 -1
- package/build/client/dev/devtools.js +4 -2
- package/build/client/index.d.ts +2 -2
- package/build/client/index.js +2 -2
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/hooks.js +2 -2
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/docs.js +1 -1
- package/build/compiler/seo.js +1 -3
- package/build/compiler/template-build.d.ts +5 -2
- package/build/compiler/template-build.js +19 -7
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/cache.js +0 -0
- package/build/devserver/crypto.js +45 -17
- package/build/devserver/database.d.ts +1 -1
- package/build/devserver/database.js +84 -0
- package/build/devserver/email/caps.js +0 -0
- package/build/devserver/email/config.js +7 -2
- package/build/devserver/email/validate.js +1 -4
- package/build/devserver/host.js +18 -1
- package/build/devserver/index.d.ts +1 -1
- package/build/devserver/index.js +3 -2
- package/build/devserver/module.js +51 -12
- package/build/devserver/proxy.js +2 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/codec.d.ts +5 -5
- package/build/io/codec.js +193 -77
- package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
- package/examples/basic/client/public/images/logo.svg +37 -34
- package/examples/basic/client/public/index.html +14 -14
- package/examples/basic/client/routes/auth.tsx +18 -10
- package/examples/basic/client/routes/cookies.tsx +15 -24
- package/examples/basic/client/routes/crypto.tsx +4 -5
- package/examples/basic/client/routes/features/template/template.tsx +1 -1
- package/examples/basic/client/routes/hello.tsx +1 -1
- package/examples/basic/client/routes/pq.tsx +14 -14
- package/examples/basic/client/routes/rest.tsx +1 -3
- package/examples/basic/client/styles/main.css +25 -22
- package/examples/basic/client/toil.tsx +1 -1
- package/examples/basic/server/README.md +8 -8
- package/examples/basic/server/core/AppHandler.ts +4 -7
- package/examples/basic/server/routes/Auth.ts +13 -10
- package/examples/basic/server/routes/EnvDemo.ts +9 -3
- package/examples/basic/server/routes/Guestbook.ts +2 -4
- package/package.json +26 -26
- package/src/backend/index.ts +4 -2
- package/src/cli/create.ts +19 -4
- package/src/cli/diagnostics.ts +48 -0
- package/src/cli/doctor.ts +155 -9
- package/src/cli/notify.ts +1 -6
- package/src/cli/ui.ts +3 -3
- package/src/cli/version-check.ts +5 -1
- package/src/client/auth.ts +33 -10
- package/src/client/components/Form.tsx +2 -2
- package/src/client/components/Image.tsx +1 -1
- package/src/client/components/Script.tsx +1 -1
- package/src/client/components/Slot.tsx +1 -1
- package/src/client/dev/devtools.tsx +126 -55
- package/src/client/dev/error-overlay.tsx +7 -1
- package/src/client/head/metadata.ts +1 -1
- package/src/client/index.ts +13 -2
- package/src/client/routing/Router.tsx +2 -2
- package/src/client/routing/error-boundary.tsx +1 -1
- package/src/client/routing/hooks.ts +5 -3
- package/src/client/routing/loader.ts +2 -2
- package/src/client/routing/mount.tsx +5 -6
- package/src/compiler/docs.ts +1 -1
- package/src/compiler/email-preview.ts +1 -1
- package/src/compiler/generate.ts +1 -1
- package/src/compiler/seo.ts +1 -3
- package/src/compiler/ssg.ts +10 -4
- package/src/compiler/template-build.ts +43 -11
- package/src/compiler/template.ts +1 -4
- package/src/compiler/vite.ts +1 -1
- package/src/devserver/cache.ts +0 -0
- package/src/devserver/crypto.ts +140 -51
- package/src/devserver/database.ts +168 -9
- package/src/devserver/dotenv.ts +10 -2
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +8 -2
- package/src/devserver/email/index.ts +3 -3
- package/src/devserver/email/validate.ts +1 -4
- package/src/devserver/envelope.ts +3 -3
- package/src/devserver/host.ts +46 -6
- package/src/devserver/index.ts +15 -6
- package/src/devserver/module.ts +56 -14
- package/src/devserver/proxy.ts +5 -7
- package/src/io/codec.ts +226 -83
- package/test/devserver-database.test.ts +60 -0
- package/test/devserver-secrets.test.ts +59 -0
- package/test/doctor.test.ts +30 -0
package/src/io/codec.ts
CHANGED
|
@@ -34,60 +34,81 @@ export class DataWriter {
|
|
|
34
34
|
this.view = new DataView(this.buf.buffer);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*/
|
|
43
|
-
private reserve(extra: number): number {
|
|
44
|
-
const need = this.off + extra;
|
|
45
|
-
if (need > this.buf.length) {
|
|
46
|
-
let n = this.buf.length;
|
|
47
|
-
while (n < need) n <<= 1;
|
|
48
|
-
const bigger = new Uint8Array(n);
|
|
49
|
-
bigger.set(this.buf.subarray(0, this.off));
|
|
50
|
-
this.buf = bigger;
|
|
51
|
-
this.view = new DataView(this.buf.buffer);
|
|
52
|
-
}
|
|
53
|
-
const at = this.off;
|
|
54
|
-
this.off += extra;
|
|
55
|
-
return at;
|
|
37
|
+
/** Writes an unsigned 8-bit byte (the low 8 bits of `v`). */
|
|
38
|
+
writeU8(v: number): this {
|
|
39
|
+
const at = this.reserve(1);
|
|
40
|
+
this.view.setUint8(at, v & 0xff);
|
|
41
|
+
return this;
|
|
56
42
|
}
|
|
57
43
|
|
|
58
|
-
/** Writes an unsigned 8-bit byte (the low 8 bits of `v`). */
|
|
59
|
-
writeU8(v: number): this { const at = this.reserve(1); this.view.setUint8(at, v & 0xff); return this; }
|
|
60
44
|
/** Writes an unsigned 16-bit integer. @param be - big-endian if true (default little-endian). */
|
|
61
|
-
writeU16(v: number, be?: boolean): this {
|
|
45
|
+
writeU16(v: number, be?: boolean): this {
|
|
46
|
+
const at = this.reserve(2);
|
|
47
|
+
this.view.setUint16(at, v & 0xffff, !be);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
62
51
|
/** Writes an unsigned 32-bit integer. @param be - big-endian if true (default little-endian). */
|
|
63
|
-
writeU32(v: number, be?: boolean): this {
|
|
52
|
+
writeU32(v: number, be?: boolean): this {
|
|
53
|
+
const at = this.reserve(4);
|
|
54
|
+
this.view.setUint32(at, v >>> 0, !be);
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
64
58
|
/** Writes an unsigned 64-bit integer (low 64 bits of `v`). @param be - big-endian if true. */
|
|
65
|
-
writeU64(v: bigint, be?: boolean): this {
|
|
59
|
+
writeU64(v: bigint, be?: boolean): this {
|
|
60
|
+
const at = this.reserve(8);
|
|
61
|
+
this.view.setBigUint64(at, v & MASK64, !be);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
66
65
|
/** Writes a signed 8-bit integer. */
|
|
67
|
-
writeI8(v: number): this {
|
|
66
|
+
writeI8(v: number): this {
|
|
67
|
+
const at = this.reserve(1);
|
|
68
|
+
this.view.setInt8(at, v);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
68
72
|
/** Writes a signed 16-bit integer. @param be - big-endian if true (default little-endian). */
|
|
69
|
-
writeI16(v: number, be?: boolean): this {
|
|
73
|
+
writeI16(v: number, be?: boolean): this {
|
|
74
|
+
const at = this.reserve(2);
|
|
75
|
+
this.view.setInt16(at, v, !be);
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
/** Writes a signed 32-bit integer. @param be - big-endian if true (default little-endian). */
|
|
71
|
-
writeI32(v: number, be?: boolean): this {
|
|
80
|
+
writeI32(v: number, be?: boolean): this {
|
|
81
|
+
const at = this.reserve(4);
|
|
82
|
+
this.view.setInt32(at, v | 0, !be);
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
/** Writes a signed 64-bit integer. @param be - big-endian if true (default little-endian). */
|
|
73
|
-
writeI64(v: bigint, be?: boolean): this {
|
|
87
|
+
writeI64(v: bigint, be?: boolean): this {
|
|
88
|
+
const at = this.reserve(8);
|
|
89
|
+
this.view.setBigInt64(at, BigInt.asIntN(64, v), !be);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
74
93
|
/** Writes a 32-bit float. @param be - big-endian if true (default little-endian). */
|
|
75
|
-
writeF32(v: number, be?: boolean): this {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
writeF32(v: number, be?: boolean): this {
|
|
95
|
+
const at = this.reserve(4);
|
|
96
|
+
this.view.setFloat32(at, v, !be);
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
80
99
|
|
|
81
|
-
/** Writes
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
} else {
|
|
86
|
-
for (let i = 0; i < count; i++) this.writeU64((u >> BigInt(i * 64)) & MASK64, false);
|
|
87
|
-
}
|
|
100
|
+
/** Writes a 64-bit float. @param be - big-endian if true (default little-endian). */
|
|
101
|
+
writeF64(v: number, be?: boolean): this {
|
|
102
|
+
const at = this.reserve(8);
|
|
103
|
+
this.view.setFloat64(at, v, !be);
|
|
88
104
|
return this;
|
|
89
105
|
}
|
|
90
106
|
|
|
107
|
+
/** Writes a boolean as one byte (1 or 0). */
|
|
108
|
+
writeBool(v: boolean): this {
|
|
109
|
+
return this.writeU8(v ? 1 : 0);
|
|
110
|
+
}
|
|
111
|
+
|
|
91
112
|
/** Writes a `u32` length prefix followed by the raw bytes. @param be - endianness of the prefix. */
|
|
92
113
|
writeBytes(bytes: Uint8Array, be?: boolean): this {
|
|
93
114
|
this.writeU32(bytes.length, be);
|
|
@@ -110,19 +131,66 @@ export class DataWriter {
|
|
|
110
131
|
}
|
|
111
132
|
|
|
112
133
|
/** Writes an unsigned 128-bit integer as two 64-bit limbs. @param be - big-endian if true. */
|
|
113
|
-
writeU128(v: bigint, be?: boolean): this {
|
|
134
|
+
writeU128(v: bigint, be?: boolean): this {
|
|
135
|
+
return this.writeLimbs(BigInt.asUintN(128, v), 2, !!be);
|
|
136
|
+
}
|
|
137
|
+
|
|
114
138
|
/** Writes a signed 128-bit integer as two 64-bit limbs (two's complement). @param be - big-endian if true. */
|
|
115
|
-
writeI128(v: bigint, be?: boolean): this {
|
|
139
|
+
writeI128(v: bigint, be?: boolean): this {
|
|
140
|
+
return this.writeLimbs(BigInt.asUintN(128, v), 2, !!be);
|
|
141
|
+
}
|
|
142
|
+
|
|
116
143
|
/** Writes an unsigned 256-bit integer as four 64-bit limbs. @param be - big-endian if true. */
|
|
117
|
-
writeU256(v: bigint, be?: boolean): this {
|
|
144
|
+
writeU256(v: bigint, be?: boolean): this {
|
|
145
|
+
return this.writeLimbs(BigInt.asUintN(256, v), 4, !!be);
|
|
146
|
+
}
|
|
147
|
+
|
|
118
148
|
/** Writes a signed 256-bit integer as four 64-bit limbs (two's complement). @param be - big-endian if true. */
|
|
119
|
-
writeI256(v: bigint, be?: boolean): this {
|
|
149
|
+
writeI256(v: bigint, be?: boolean): this {
|
|
150
|
+
return this.writeLimbs(BigInt.asUintN(256, v), 4, !!be);
|
|
151
|
+
}
|
|
120
152
|
|
|
121
153
|
/** Number of bytes written so far. */
|
|
122
|
-
length(): number {
|
|
154
|
+
length(): number {
|
|
155
|
+
return this.off;
|
|
156
|
+
}
|
|
123
157
|
|
|
124
158
|
/** A fresh copy of exactly the bytes written. */
|
|
125
|
-
toBytes(): Uint8Array<ArrayBuffer> {
|
|
159
|
+
toBytes(): Uint8Array<ArrayBuffer> {
|
|
160
|
+
return this.buf.slice(0, this.off);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Ensures room for `extra` more bytes and returns the offset to write at. Grows
|
|
165
|
+
* (doubling) when needed, reassigning `buf`/`view`. Callers MUST read the returned
|
|
166
|
+
* offset into a local before touching `this.view`/`this.buf`, since a grow swaps
|
|
167
|
+
* them out from under a stale receiver.
|
|
168
|
+
*/
|
|
169
|
+
private reserve(extra: number): number {
|
|
170
|
+
const need = this.off + extra;
|
|
171
|
+
if (need > this.buf.length) {
|
|
172
|
+
let n = this.buf.length;
|
|
173
|
+
while (n < need) n <<= 1;
|
|
174
|
+
const bigger = new Uint8Array(n);
|
|
175
|
+
bigger.set(this.buf.subarray(0, this.off));
|
|
176
|
+
this.buf = bigger;
|
|
177
|
+
this.view = new DataView(this.buf.buffer);
|
|
178
|
+
}
|
|
179
|
+
const at = this.off;
|
|
180
|
+
this.off += extra;
|
|
181
|
+
return at;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Writes the `count` 64-bit limbs of `u` (low limb first in LE, high limb first in BE). */
|
|
185
|
+
private writeLimbs(u: bigint, count: number, be: boolean): this {
|
|
186
|
+
if (be) {
|
|
187
|
+
for (let i = count - 1; i >= 0; i--)
|
|
188
|
+
this.writeU64((u >> BigInt(i * 64)) & MASK64, true);
|
|
189
|
+
} else {
|
|
190
|
+
for (let i = 0; i < count; i++) this.writeU64((u >> BigInt(i * 64)) & MASK64, false);
|
|
191
|
+
}
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
126
194
|
}
|
|
127
195
|
|
|
128
196
|
/**
|
|
@@ -132,11 +200,11 @@ export class DataWriter {
|
|
|
132
200
|
* big-endian writer.
|
|
133
201
|
*/
|
|
134
202
|
export class DataReader {
|
|
203
|
+
/** Cleared to false if any read ran past the end of the buffer. */
|
|
204
|
+
ok = true;
|
|
135
205
|
private buf: Uint8Array;
|
|
136
206
|
private view: DataView;
|
|
137
207
|
private off = 0;
|
|
138
|
-
/** Cleared to false if any read ran past the end of the buffer. */
|
|
139
|
-
ok = true;
|
|
140
208
|
|
|
141
209
|
/** @param bytes - the buffer to read from (its byteOffset/length are respected). */
|
|
142
210
|
constructor(bytes: Uint8Array) {
|
|
@@ -144,47 +212,89 @@ export class DataReader {
|
|
|
144
212
|
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
145
213
|
}
|
|
146
214
|
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return true;
|
|
215
|
+
/** Reads an unsigned 8-bit byte (0 past end). */
|
|
216
|
+
readU8(): number {
|
|
217
|
+
if (!this.has(1)) return 0;
|
|
218
|
+
const v = this.view.getUint8(this.off);
|
|
219
|
+
this.off += 1;
|
|
220
|
+
return v;
|
|
154
221
|
}
|
|
155
222
|
|
|
156
|
-
/** Reads an unsigned 8-bit byte (0 past end). */
|
|
157
|
-
readU8(): number { if (!this.has(1)) return 0; const v = this.view.getUint8(this.off); this.off += 1; return v; }
|
|
158
223
|
/** Reads an unsigned 16-bit integer. @param be - big-endian if true (default little-endian). */
|
|
159
|
-
readU16(be?: boolean): number {
|
|
224
|
+
readU16(be?: boolean): number {
|
|
225
|
+
if (!this.has(2)) return 0;
|
|
226
|
+
const v = this.view.getUint16(this.off, !be);
|
|
227
|
+
this.off += 2;
|
|
228
|
+
return v;
|
|
229
|
+
}
|
|
230
|
+
|
|
160
231
|
/** Reads an unsigned 32-bit integer. @param be - big-endian if true (default little-endian). */
|
|
161
|
-
readU32(be?: boolean): number {
|
|
232
|
+
readU32(be?: boolean): number {
|
|
233
|
+
if (!this.has(4)) return 0;
|
|
234
|
+
const v = this.view.getUint32(this.off, !be);
|
|
235
|
+
this.off += 4;
|
|
236
|
+
return v >>> 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
162
239
|
/** Reads an unsigned 64-bit integer. @param be - big-endian if true (default little-endian). */
|
|
163
|
-
readU64(be?: boolean): bigint {
|
|
240
|
+
readU64(be?: boolean): bigint {
|
|
241
|
+
if (!this.has(8)) return 0n;
|
|
242
|
+
const v = this.view.getBigUint64(this.off, !be);
|
|
243
|
+
this.off += 8;
|
|
244
|
+
return v;
|
|
245
|
+
}
|
|
246
|
+
|
|
164
247
|
/** Reads a signed 8-bit integer (0 past end). */
|
|
165
|
-
readI8(): number {
|
|
248
|
+
readI8(): number {
|
|
249
|
+
if (!this.has(1)) return 0;
|
|
250
|
+
const v = this.view.getInt8(this.off);
|
|
251
|
+
this.off += 1;
|
|
252
|
+
return v;
|
|
253
|
+
}
|
|
254
|
+
|
|
166
255
|
/** Reads a signed 16-bit integer. @param be - big-endian if true (default little-endian). */
|
|
167
|
-
readI16(be?: boolean): number {
|
|
256
|
+
readI16(be?: boolean): number {
|
|
257
|
+
if (!this.has(2)) return 0;
|
|
258
|
+
const v = this.view.getInt16(this.off, !be);
|
|
259
|
+
this.off += 2;
|
|
260
|
+
return v;
|
|
261
|
+
}
|
|
262
|
+
|
|
168
263
|
/** Reads a signed 32-bit integer. @param be - big-endian if true (default little-endian). */
|
|
169
|
-
readI32(be?: boolean): number {
|
|
264
|
+
readI32(be?: boolean): number {
|
|
265
|
+
if (!this.has(4)) return 0;
|
|
266
|
+
const v = this.view.getInt32(this.off, !be);
|
|
267
|
+
this.off += 4;
|
|
268
|
+
return v;
|
|
269
|
+
}
|
|
270
|
+
|
|
170
271
|
/** Reads a signed 64-bit integer. @param be - big-endian if true (default little-endian). */
|
|
171
|
-
readI64(be?: boolean): bigint {
|
|
272
|
+
readI64(be?: boolean): bigint {
|
|
273
|
+
if (!this.has(8)) return 0n;
|
|
274
|
+
const v = this.view.getBigInt64(this.off, !be);
|
|
275
|
+
this.off += 8;
|
|
276
|
+
return v;
|
|
277
|
+
}
|
|
278
|
+
|
|
172
279
|
/** Reads a 32-bit float. @param be - big-endian if true (default little-endian). */
|
|
173
|
-
readF32(be?: boolean): number {
|
|
280
|
+
readF32(be?: boolean): number {
|
|
281
|
+
if (!this.has(4)) return 0;
|
|
282
|
+
const v = this.view.getFloat32(this.off, !be);
|
|
283
|
+
this.off += 4;
|
|
284
|
+
return v;
|
|
285
|
+
}
|
|
286
|
+
|
|
174
287
|
/** Reads a 64-bit float. @param be - big-endian if true (default little-endian). */
|
|
175
|
-
readF64(be?: boolean): number {
|
|
176
|
-
|
|
177
|
-
|
|
288
|
+
readF64(be?: boolean): number {
|
|
289
|
+
if (!this.has(8)) return 0;
|
|
290
|
+
const v = this.view.getFloat64(this.off, !be);
|
|
291
|
+
this.off += 8;
|
|
292
|
+
return v;
|
|
293
|
+
}
|
|
178
294
|
|
|
179
|
-
/** Reads
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (be) {
|
|
183
|
-
for (let i = count - 1; i >= 0; i--) result |= this.readU64(true) << BigInt(i * 64);
|
|
184
|
-
} else {
|
|
185
|
-
for (let i = 0; i < count; i++) result |= this.readU64(false) << BigInt(i * 64);
|
|
186
|
-
}
|
|
187
|
-
return result;
|
|
295
|
+
/** Reads a boolean (any non-zero byte is true). */
|
|
296
|
+
readBool(): boolean {
|
|
297
|
+
return this.readU8() !== 0;
|
|
188
298
|
}
|
|
189
299
|
|
|
190
300
|
/** Reads a `u32`-length-prefixed byte blob (empty past end). @param be - endianness of the prefix. */
|
|
@@ -199,21 +309,54 @@ export class DataReader {
|
|
|
199
309
|
/** Reads a `u32`-byte-length-prefixed UTF-8 string (empty past end). @param be - endianness of the prefix. */
|
|
200
310
|
readString(be?: boolean): string {
|
|
201
311
|
const len = this.readU32(be);
|
|
202
|
-
if (!this.has(len)) return
|
|
312
|
+
if (!this.has(len)) return '';
|
|
203
313
|
const s = utf8Decoder.decode(this.buf.subarray(this.off, this.off + len));
|
|
204
314
|
this.off += len;
|
|
205
315
|
return s;
|
|
206
316
|
}
|
|
207
317
|
|
|
208
318
|
/** Reads an unsigned 128-bit integer. @param be - big-endian if true (default little-endian). */
|
|
209
|
-
readU128(be?: boolean): bigint {
|
|
319
|
+
readU128(be?: boolean): bigint {
|
|
320
|
+
return this.readLimbs(2, !!be);
|
|
321
|
+
}
|
|
322
|
+
|
|
210
323
|
/** Reads a signed 128-bit integer (two's complement). @param be - big-endian if true. */
|
|
211
|
-
readI128(be?: boolean): bigint {
|
|
324
|
+
readI128(be?: boolean): bigint {
|
|
325
|
+
return BigInt.asIntN(128, this.readLimbs(2, !!be));
|
|
326
|
+
}
|
|
327
|
+
|
|
212
328
|
/** Reads an unsigned 256-bit integer. @param be - big-endian if true (default little-endian). */
|
|
213
|
-
readU256(be?: boolean): bigint {
|
|
329
|
+
readU256(be?: boolean): bigint {
|
|
330
|
+
return this.readLimbs(4, !!be);
|
|
331
|
+
}
|
|
332
|
+
|
|
214
333
|
/** Reads a signed 256-bit integer (two's complement). @param be - big-endian if true. */
|
|
215
|
-
readI256(be?: boolean): bigint {
|
|
334
|
+
readI256(be?: boolean): bigint {
|
|
335
|
+
return BigInt.asIntN(256, this.readLimbs(4, !!be));
|
|
336
|
+
}
|
|
216
337
|
|
|
217
338
|
/** Bytes left to read. */
|
|
218
|
-
remaining(): number {
|
|
339
|
+
remaining(): number {
|
|
340
|
+
return this.buf.length - this.off;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Returns true (and leaves `off` advanceable) if `n` more bytes are available; else clears `ok`. */
|
|
344
|
+
private has(n: number): boolean {
|
|
345
|
+
if (n < 0 || this.off + n > this.buf.length) {
|
|
346
|
+
this.ok = false;
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Reads `count` 64-bit limbs and recombines them (low limb first in LE, high first in BE). */
|
|
353
|
+
private readLimbs(count: number, be: boolean): bigint {
|
|
354
|
+
let result = 0n;
|
|
355
|
+
if (be) {
|
|
356
|
+
for (let i = count - 1; i >= 0; i--) result |= this.readU64(true) << BigInt(i * 64);
|
|
357
|
+
} else {
|
|
358
|
+
for (let i = 0; i < count; i++) result |= this.readU64(false) << BigInt(i * 64);
|
|
359
|
+
}
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
219
362
|
}
|
|
@@ -302,3 +302,63 @@ describe('toildb dev emulator (record family)', () => {
|
|
|
302
302
|
expect(imports['data.get'](posts, kPtr, kLen)).toBe(-2);
|
|
303
303
|
});
|
|
304
304
|
});
|
|
305
|
+
|
|
306
|
+
type Imports = Record<string, (...args: number[]) => number>;
|
|
307
|
+
|
|
308
|
+
describe('toildb dev emulator (capacity family)', () => {
|
|
309
|
+
// Pull the last stashed i64 available into the buffer and read it.
|
|
310
|
+
function avail(imports: Imports, buf: Buffer, h: number, kPtr: number, kLen: number): bigint {
|
|
311
|
+
expect(imports['data.capacity_available'](h, kPtr, kLen)).toBe(8);
|
|
312
|
+
expect(imports['data.take_result'](512, 16)).toBe(8);
|
|
313
|
+
return buf.readBigInt64LE(512);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
it('set_total / reserve / confirm / cancel keep the ledger consistent', () => {
|
|
317
|
+
const { imports, buf } = setup();
|
|
318
|
+
const h = resolve(imports, buf, 'App/seats');
|
|
319
|
+
const [kPtr, kLen] = put(buf, 32, 'showA');
|
|
320
|
+
|
|
321
|
+
// an empty ledger reads 0 available, never absent.
|
|
322
|
+
expect(avail(imports, buf, h, kPtr, kLen)).toBe(0n);
|
|
323
|
+
|
|
324
|
+
// restock to 10.
|
|
325
|
+
expect(imports['data.capacity_set_total'](h, kPtr, kLen, 10, 0)).toBe(0);
|
|
326
|
+
expect(avail(imports, buf, h, kPtr, kLen)).toBe(10n);
|
|
327
|
+
|
|
328
|
+
// reserve 3 -> a u64 id (> 0) is stashed, available drops to 7.
|
|
329
|
+
expect(imports['data.capacity_reserve'](h, kPtr, kLen, 3, 60000, 0)).toBe(8);
|
|
330
|
+
expect(imports['data.take_result'](512, 16)).toBe(8);
|
|
331
|
+
const id = buf.readBigUInt64LE(512);
|
|
332
|
+
expect(id > 0n).toBe(true);
|
|
333
|
+
expect(avail(imports, buf, h, kPtr, kLen)).toBe(7n);
|
|
334
|
+
|
|
335
|
+
// cancel returns the hold -> back to 10; a double-cancel is a no-op.
|
|
336
|
+
expect(imports['data.capacity_cancel'](h, kPtr, kLen, Number(id), 0)).toBe(1);
|
|
337
|
+
expect(avail(imports, buf, h, kPtr, kLen)).toBe(10n);
|
|
338
|
+
expect(imports['data.capacity_cancel'](h, kPtr, kLen, Number(id), 0)).toBe(0);
|
|
339
|
+
|
|
340
|
+
// reserve 4 then confirm -> a permanent consume; available holds at 6.
|
|
341
|
+
expect(imports['data.capacity_reserve'](h, kPtr, kLen, 4, 60000, 0)).toBe(8);
|
|
342
|
+
imports['data.take_result'](512, 16);
|
|
343
|
+
const id2 = Number(buf.readBigUInt64LE(512));
|
|
344
|
+
expect(imports['data.capacity_confirm'](h, kPtr, kLen, id2, 0)).toBe(1);
|
|
345
|
+
expect(avail(imports, buf, h, kPtr, kLen)).toBe(6n);
|
|
346
|
+
// a confirmed hold cannot be cancelled nor re-confirmed.
|
|
347
|
+
expect(imports['data.capacity_cancel'](h, kPtr, kLen, id2, 0)).toBe(0);
|
|
348
|
+
expect(imports['data.capacity_confirm'](h, kPtr, kLen, id2, 0)).toBe(0);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('never oversells (a reserve beyond available is refused)', () => {
|
|
352
|
+
const { imports, buf } = setup();
|
|
353
|
+
const h = resolve(imports, buf, 'App/tickets');
|
|
354
|
+
const [kPtr, kLen] = put(buf, 32, 'vip');
|
|
355
|
+
imports['data.capacity_set_total'](h, kPtr, kLen, 5, 0);
|
|
356
|
+
|
|
357
|
+
// a hold for all 5 succeeds; a further hold for 1 is refused (-2 -> guest 0).
|
|
358
|
+
expect(imports['data.capacity_reserve'](h, kPtr, kLen, 5, 60000, 0)).toBe(8);
|
|
359
|
+
expect(imports['data.capacity_reserve'](h, kPtr, kLen, 1, 60000, 0)).toBe(-2);
|
|
360
|
+
// a non-positive amount is refused, and an invalid handle is rejected.
|
|
361
|
+
expect(imports['data.capacity_reserve'](h, kPtr, kLen, 0, 60000, 0)).toBe(-2);
|
|
362
|
+
expect(imports['data.capacity_available'](999, kPtr, kLen)).toBe(-1001);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { buildHostImports, freshDispatchState, type MemoryRef } from '../src/devserver/host.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The dev host warns (once) when the guest reads a framework auth secret that is unset, since the
|
|
7
|
+
* wasm then falls back to a PUBLISHED dev key (see `server/globals/auth.ts`). This is the visible
|
|
8
|
+
* counterpart to the silent fallback: without it, a server running on the dev host would sign
|
|
9
|
+
* sessions under a forgeable key with no signal. The repo root has no `.env.secrets`, so every
|
|
10
|
+
* lookup below is absent.
|
|
11
|
+
*/
|
|
12
|
+
function setup() {
|
|
13
|
+
const memory = new WebAssembly.Memory({ initial: 1 });
|
|
14
|
+
const ref: MemoryRef = { memory };
|
|
15
|
+
const env = buildHostImports(ref, freshDispatchState()).env as Record<
|
|
16
|
+
string,
|
|
17
|
+
(...a: number[]) => number
|
|
18
|
+
>;
|
|
19
|
+
const buf = Buffer.from(memory.buffer);
|
|
20
|
+
return { env, buf };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Writes `key` at offset 0 and runs `env_get_secure`, returning its status code. */
|
|
24
|
+
function getSecure(env: Record<string, (...a: number[]) => number>, buf: Buffer, key: string): number {
|
|
25
|
+
const b = Buffer.from(key, 'utf8');
|
|
26
|
+
b.copy(buf, 0);
|
|
27
|
+
return env.env_get_secure(0, b.length, 256, 256);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
vi.restoreAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('dev host secret fallback warning', () => {
|
|
35
|
+
it('warns once that an unset AUTH_SESSION_SECRET falls back to a published key', () => {
|
|
36
|
+
const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
|
|
37
|
+
const { env, buf } = setup();
|
|
38
|
+
|
|
39
|
+
expect(getSecure(env, buf, 'AUTH_SESSION_SECRET')).toBe(-2); // ABSENT
|
|
40
|
+
// Repeated reads (a fresh wasm instance per request hits this every time) warn only once.
|
|
41
|
+
expect(getSecure(env, buf, 'AUTH_SESSION_SECRET')).toBe(-2);
|
|
42
|
+
|
|
43
|
+
const warnings = write.mock.calls
|
|
44
|
+
.map((c) => String(c[0]))
|
|
45
|
+
.filter((s) => s.includes('AUTH_SESSION_SECRET'));
|
|
46
|
+
expect(warnings).toHaveLength(1);
|
|
47
|
+
expect(warnings[0]).toContain('forge');
|
|
48
|
+
expect(warnings[0]).toContain('deployed node');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not warn for an ordinary (non-framework) absent secret', () => {
|
|
52
|
+
const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
|
|
53
|
+
const { env, buf } = setup();
|
|
54
|
+
|
|
55
|
+
expect(getSecure(env, buf, 'STRIPE_KEY')).toBe(-2); // ABSENT, no published default
|
|
56
|
+
const warned = write.mock.calls.some((c) => String(c[0]).includes('STRIPE_KEY'));
|
|
57
|
+
expect(warned).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
});
|
package/test/doctor.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
checkAuthSecrets,
|
|
4
5
|
checkBasePath,
|
|
5
6
|
checkDevScripts,
|
|
6
7
|
checkDuplicatePatterns,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
checkRootElement,
|
|
15
16
|
checkRpcWiring,
|
|
16
17
|
checkSeoUrl,
|
|
18
|
+
checkServerTsPlugin,
|
|
17
19
|
checkStyling,
|
|
18
20
|
findRelativeAssets,
|
|
19
21
|
satisfiesMin,
|
|
@@ -189,6 +191,34 @@ describe('checkPrettierPlugin', () => {
|
|
|
189
191
|
});
|
|
190
192
|
});
|
|
191
193
|
|
|
194
|
+
describe('checkServerTsPlugin', () => {
|
|
195
|
+
it('passes when the toilscript LS plugin is wired', () => {
|
|
196
|
+
expect(checkServerTsPlugin(true).status).toBe('pass');
|
|
197
|
+
});
|
|
198
|
+
it('warns (not fails) when missing, naming the TS2339 false positive and --fix', () => {
|
|
199
|
+
const c = checkServerTsPlugin(false);
|
|
200
|
+
expect(c.status).toBe('warn');
|
|
201
|
+
expect(c.detail).toContain('TS2339');
|
|
202
|
+
expect(c.fix).toContain('--fix');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('checkAuthSecrets', () => {
|
|
207
|
+
it('passes when auth is not used', () => {
|
|
208
|
+
expect(checkAuthSecrets({ usesAuth: false, sessionSecretSet: false }).status).toBe('pass');
|
|
209
|
+
});
|
|
210
|
+
it('passes when auth is used and the session secret is set', () => {
|
|
211
|
+
expect(checkAuthSecrets({ usesAuth: true, sessionSecretSet: true }).status).toBe('pass');
|
|
212
|
+
});
|
|
213
|
+
it('warns about session forgery when auth is used but the secret is unset', () => {
|
|
214
|
+
const c = checkAuthSecrets({ usesAuth: true, sessionSecretSet: false });
|
|
215
|
+
expect(c.status).toBe('warn');
|
|
216
|
+
expect(c.detail).toContain('AUTH_SESSION_SECRET');
|
|
217
|
+
expect(c.detail).toContain('forge');
|
|
218
|
+
expect(c.fix).toContain('.env.secrets');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
192
222
|
describe('summarize', () => {
|
|
193
223
|
it('tallies pass/warn/fail across groups', () => {
|
|
194
224
|
const groups: CheckGroup[] = [
|