toiljs 0.0.65 → 0.0.67
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/build/client/.tsbuildinfo +1 -1
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/index.js +22 -11
- package/build/compiler/toil-docs.generated.js +1 -1
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/daemon/index.js +4 -3
- package/build/devserver/db/catalog.js +8 -12
- package/build/devserver/server.js +3 -1
- package/build/devserver/wasm/surface.d.ts +1 -1
- package/build/devserver/wasm/surface.js +1 -1
- package/docs/tiers.md +15 -9
- package/examples/basic/client/public/index.html +11 -1
- package/examples/basic/client/routes/features/index.tsx +3 -0
- package/examples/basic/client/routes/get-started.tsx +3 -0
- package/examples/basic/client/routes/index.tsx +3 -0
- package/examples/basic/client/routes/search.tsx +4 -0
- package/package.json +2 -2
- package/src/client/routing/mount.tsx +4 -5
- package/src/compiler/index.ts +44 -29
- package/src/compiler/toil-docs.generated.ts +1 -1
- package/src/devserver/daemon/index.ts +7 -7
- package/src/devserver/db/catalog.ts +9 -13
- package/src/devserver/server.ts +8 -1
- package/src/devserver/wasm/surface.ts +5 -7
- package/test/daemon-build.test.ts +15 -7
- package/test/daemon-catalog.test.ts +17 -8
- package/test/devserver-database.test.ts +8 -8
- package/test/rpc-bignum-wire.test.ts +8 -8
package/src/devserver/server.ts
CHANGED
|
@@ -362,7 +362,14 @@ export async function startDevServer(options: DevServerOptions): Promise<Running
|
|
|
362
362
|
const route = ssrRoutes.find((r) => r.test(pathnameOf(request.url)));
|
|
363
363
|
if (route) {
|
|
364
364
|
try {
|
|
365
|
-
|
|
365
|
+
// A static route (its template has no holes -> no slots) needs no guest
|
|
366
|
+
// render: serve the prerendered template directly so it paints instantly
|
|
367
|
+
// instead of falling through to a (blank-until-JS) client render. Dynamic
|
|
368
|
+
// routes run the guest `render` and splice its values in.
|
|
369
|
+
const out: SsrResult | null =
|
|
370
|
+
route.entries.length === 0
|
|
371
|
+
? { status: 200, headers: [], html: route.tmpl }
|
|
372
|
+
: assembleSsr(route, module.dispatchRender(envelopeReq));
|
|
366
373
|
if (out !== null) {
|
|
367
374
|
sendSsr(response, out, request.method === 'HEAD');
|
|
368
375
|
return;
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
* u32 data_coherence_hash
|
|
19
19
|
* u32 pair_coherence_hash (exactly THREE u32 after build_id, not four)
|
|
20
20
|
*
|
|
21
|
-
* Fail-closed per Part 5's host rule: an
|
|
22
|
-
*
|
|
23
|
-
* a corrupt artifact -> do not start that artifact's emulator.
|
|
21
|
+
* Fail-closed per Part 5's host rule: an absent or unparseable section is a
|
|
22
|
+
* corrupt Toil artifact -> do not start that artifact's emulator.
|
|
24
23
|
*/
|
|
25
24
|
|
|
26
25
|
import { DataReader } from 'toiljs/io';
|
|
@@ -47,16 +46,15 @@ export interface Surface {
|
|
|
47
46
|
readonly pairCoherenceHash: number;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
/** `'
|
|
51
|
-
|
|
52
|
-
export function parseSurface(wasm: Buffer): Surface | 'absent' | 'invalid' {
|
|
49
|
+
/** `'invalid'` => absent or corrupt (fail closed). Otherwise the parsed surface. */
|
|
50
|
+
export function parseSurface(wasm: Buffer): Surface | 'invalid' {
|
|
53
51
|
let sec: Buffer | null;
|
|
54
52
|
try {
|
|
55
53
|
sec = customSection(wasm, 'toil.surface');
|
|
56
54
|
} catch {
|
|
57
55
|
return 'invalid'; // garbage section table
|
|
58
56
|
}
|
|
59
|
-
if (sec === null) return '
|
|
57
|
+
if (sec === null) return 'invalid';
|
|
60
58
|
|
|
61
59
|
const r = new DataReader(sec);
|
|
62
60
|
r.readU16(); // format_version
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* passes (one `--targetMode cold`, one `--targetMode hot`) and produces BOTH
|
|
10
10
|
* `release-hot.wasm` and `release-cold.wasm`; the cold artifact decodes to a
|
|
11
11
|
* daemon catalog and its `toil.surface` is target_mode = cold.
|
|
12
|
-
* - a project with only the
|
|
12
|
+
* - a project with only the default request surface keeps the single-artifact
|
|
13
13
|
* path (no cold pass, no cold artifact).
|
|
14
14
|
*
|
|
15
15
|
* The build invokes the LOCAL toilscript (branch feat/streams-phase0-compiler),
|
|
@@ -17,7 +17,15 @@
|
|
|
17
17
|
* `node_modules` the same way the dev build resolves it (`require.resolve`).
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
existsSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
mkdtempSync,
|
|
24
|
+
readFileSync,
|
|
25
|
+
rmSync,
|
|
26
|
+
symlinkSync,
|
|
27
|
+
writeFileSync,
|
|
28
|
+
} from 'node:fs';
|
|
21
29
|
import { tmpdir } from 'node:os';
|
|
22
30
|
import { dirname, join } from 'node:path';
|
|
23
31
|
import { fileURLToPath } from 'node:url';
|
|
@@ -190,8 +198,8 @@ describe.skipIf(!existsSync(LOCAL_TOILSCRIPT))('buildServer two-pass (daemon pro
|
|
|
190
198
|
// The cold artifact carries the daemon surface + catalog (decoded byte-for-byte).
|
|
191
199
|
const coldBytes = readFileSync(cold);
|
|
192
200
|
const surface = parseSurface(coldBytes);
|
|
193
|
-
expect(surface !== '
|
|
194
|
-
expect(surface !== '
|
|
201
|
+
expect(surface !== 'invalid' && surface.targetMode).toBe('cold');
|
|
202
|
+
expect(surface !== 'invalid' && surface.flags.daemon).toBe(true);
|
|
195
203
|
|
|
196
204
|
const catalog = parseDaemonCatalog(coldBytes);
|
|
197
205
|
expect(catalog).not.toBeNull();
|
|
@@ -201,12 +209,12 @@ describe.skipIf(!existsSync(LOCAL_TOILSCRIPT))('buildServer two-pass (daemon pro
|
|
|
201
209
|
expect(catalog!.tasks[1].schedule.kind).toBe('cron');
|
|
202
210
|
|
|
203
211
|
// A daemon-only project (no request/stream surface) has no hot files, so the hot pass is
|
|
204
|
-
// skipped (toilscript would HARD-ERROR a @daemon class under --targetMode hot). The
|
|
205
|
-
//
|
|
212
|
+
// skipped (toilscript would HARD-ERROR a @daemon class under --targetMode hot). The default
|
|
213
|
+
// request artifact `release.wasm` is therefore not produced for a pure background worker.
|
|
206
214
|
expect(existsSync(join(tmp, 'build/server/release.wasm'))).toBe(false);
|
|
207
215
|
}, 60_000);
|
|
208
216
|
|
|
209
|
-
it('keeps the single-artifact path for a
|
|
217
|
+
it('keeps the single-artifact path for a request-only project', async () => {
|
|
210
218
|
scaffold(LEGACY_SRC, BASE_TOILCONFIG);
|
|
211
219
|
await buildServer(tmp);
|
|
212
220
|
|
|
@@ -117,7 +117,13 @@ describe('parseDaemonCatalog (Part 5)', () => {
|
|
|
117
117
|
name: 'lateMinute',
|
|
118
118
|
taskIndex: 0,
|
|
119
119
|
kind: 1,
|
|
120
|
-
cron: {
|
|
120
|
+
cron: {
|
|
121
|
+
minute: minuteMask,
|
|
122
|
+
hour: 0xffffff,
|
|
123
|
+
dom: 0xfffffffe,
|
|
124
|
+
month: 0x1ffe,
|
|
125
|
+
dow: 0x7f,
|
|
126
|
+
},
|
|
121
127
|
},
|
|
122
128
|
]);
|
|
123
129
|
const cat = parseDaemonCatalog(wasmWithSection('toildaemon.catalog', payload));
|
|
@@ -173,10 +179,11 @@ function buildSurfaceBytes(opts: {
|
|
|
173
179
|
describe('parseSurface (Part 5)', () => {
|
|
174
180
|
it('decodes a cold daemon surface with exactly three trailing u32 hashes', () => {
|
|
175
181
|
const flags = 0b000100 | 0b001000; // daemon (bit2) + scheduled (bit3)
|
|
176
|
-
const s = parseSurface(
|
|
177
|
-
|
|
182
|
+
const s = parseSurface(
|
|
183
|
+
wasmWithSection('toil.surface', buildSurfaceBytes({ mode: 1, flags })),
|
|
184
|
+
);
|
|
178
185
|
expect(s).not.toBe('invalid');
|
|
179
|
-
if (s !== '
|
|
186
|
+
if (s !== 'invalid') {
|
|
180
187
|
expect(s.targetMode).toBe('cold');
|
|
181
188
|
expect(s.flags.daemon).toBe(true);
|
|
182
189
|
expect(s.flags.scheduled).toBe(true);
|
|
@@ -188,13 +195,15 @@ describe('parseSurface (Part 5)', () => {
|
|
|
188
195
|
});
|
|
189
196
|
|
|
190
197
|
it('decodes a hot surface (target_mode 0)', () => {
|
|
191
|
-
const s = parseSurface(
|
|
192
|
-
|
|
198
|
+
const s = parseSurface(
|
|
199
|
+
wasmWithSection('toil.surface', buildSurfaceBytes({ mode: 0, flags: 1 })),
|
|
200
|
+
);
|
|
201
|
+
expect(s !== 'invalid' && s.targetMode).toBe('hot');
|
|
193
202
|
});
|
|
194
203
|
|
|
195
|
-
it(
|
|
204
|
+
it('fails closed when toil.surface is absent', () => {
|
|
196
205
|
const wasm = wasmWithSection('toildb.catalog', Buffer.from([0x01, 0x00]));
|
|
197
|
-
expect(parseSurface(wasm)).toBe('
|
|
206
|
+
expect(parseSurface(wasm)).toBe('invalid');
|
|
198
207
|
});
|
|
199
208
|
|
|
200
209
|
it("fails closed: a PRESENT but truncated section is 'invalid'", () => {
|
|
@@ -92,7 +92,7 @@ function routeKindsSection(routes: readonly (readonly [number, number, string])[
|
|
|
92
92
|
return Buffer.concat(chunks);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function
|
|
95
|
+
function catalogSectionV1(fillMaxWaitMs: number, fillAllowStale: number, replication = 5): Buffer {
|
|
96
96
|
const chunks: Buffer[] = [];
|
|
97
97
|
const u8 = (v: number) => chunks.push(Buffer.from([v & 0xff]));
|
|
98
98
|
const u16 = (v: number) => {
|
|
@@ -111,7 +111,7 @@ function catalogSectionV2(fillMaxWaitMs: number, fillAllowStale: number, replica
|
|
|
111
111
|
chunks.push(b);
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
-
u16(
|
|
114
|
+
u16(1); // catalog version
|
|
115
115
|
u16(1); // databases
|
|
116
116
|
str('App');
|
|
117
117
|
u16(1); // collections
|
|
@@ -215,8 +215,8 @@ describe('toildb dev emulator (record family)', () => {
|
|
|
215
215
|
expect(imports['data.resolve_collection'](p, l, 16)).toBe(-1070);
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
it('parses catalog
|
|
219
|
-
setDbCatalog(wasmWithSection('toildb.catalog',
|
|
218
|
+
it('parses catalog v1 fill policy and backend replication bytes', () => {
|
|
219
|
+
setDbCatalog(wasmWithSection('toildb.catalog', catalogSectionV1(7, 0)));
|
|
220
220
|
const { imports, buf, db } = setupRaw();
|
|
221
221
|
const h = resolve(imports, buf, 'App/users');
|
|
222
222
|
|
|
@@ -231,9 +231,9 @@ describe('toildb dev emulator (record family)', () => {
|
|
|
231
231
|
});
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
it('rejects replication policies that require
|
|
234
|
+
it('rejects replication policies that require explicit policy metadata', () => {
|
|
235
235
|
for (const replication of [3, 4]) {
|
|
236
|
-
setDbCatalog(wasmWithSection('toildb.catalog',
|
|
236
|
+
setDbCatalog(wasmWithSection('toildb.catalog', catalogSectionV1(7, 0, replication)));
|
|
237
237
|
const { imports, buf } = setupRaw();
|
|
238
238
|
const [p, l] = put(buf, 0, 'App/users');
|
|
239
239
|
|
|
@@ -241,8 +241,8 @@ describe('toildb dev emulator (record family)', () => {
|
|
|
241
241
|
}
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
-
it('rejects malformed catalog
|
|
245
|
-
setDbCatalog(wasmWithSection('toildb.catalog',
|
|
244
|
+
it('rejects malformed catalog v1 fill policy', () => {
|
|
245
|
+
setDbCatalog(wasmWithSection('toildb.catalog', catalogSectionV1(7, 2)));
|
|
246
246
|
const { imports, buf } = setupRaw();
|
|
247
247
|
const [p, l] = put(buf, 0, 'App/users');
|
|
248
248
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Compiles test/fixtures/bignum-wire/spec.ts with the installed toilscript (so it
|
|
8
8
|
* exercises the published compiler + generated client, not a hand-written stub), then
|
|
9
9
|
* imports the generated TS client and asserts the wire shape both directions, including
|
|
10
|
-
* values far above 2^53
|
|
10
|
+
* values far above 2^53.
|
|
11
11
|
*/
|
|
12
12
|
import { spawnSync } from 'node:child_process';
|
|
13
13
|
import fs from 'node:fs';
|
|
@@ -24,6 +24,9 @@ const codec = path.join(here, '..', 'src', 'io', 'codec.ts');
|
|
|
24
24
|
|
|
25
25
|
/** Resolves the installed toilscript CLI entry (no PATH / .bin assumptions). */
|
|
26
26
|
function toilscriptBin(): string {
|
|
27
|
+
if (process.env.TOILSCRIPT_BIN) return process.env.TOILSCRIPT_BIN;
|
|
28
|
+
const sibling = path.resolve(here, '..', '..', 'toilscript', 'bin', 'toilscript.js');
|
|
29
|
+
if (fs.existsSync(sibling)) return sibling;
|
|
27
30
|
const pkgPath = require.resolve('toilscript/package.json');
|
|
28
31
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as { bin?: Record<string, string> };
|
|
29
32
|
const binRel = pkg.bin?.toilscript;
|
|
@@ -83,6 +86,10 @@ beforeAll(async () => {
|
|
|
83
86
|
{ encoding: 'utf8' },
|
|
84
87
|
);
|
|
85
88
|
if (res.status !== 0) throw new Error('toilscript compile failed:\n' + res.stderr);
|
|
89
|
+
const generatedSource = fs.readFileSync(mod, 'utf8');
|
|
90
|
+
const removedHelper = ['__toil', 'Unlimb'].join('');
|
|
91
|
+
expect(generatedSource).toContain('__toilBigInt');
|
|
92
|
+
expect(generatedSource).not.toContain(removedHelper);
|
|
86
93
|
const gen = (await import(pathToFileURL(mod).href)) as {
|
|
87
94
|
Wallet: WalletStatic;
|
|
88
95
|
Account: AccountStatic;
|
|
@@ -142,13 +149,6 @@ describe('generated client bignum JSON wire format', () => {
|
|
|
142
149
|
expect(back.d).toBe(BigInt('-' + huge));
|
|
143
150
|
});
|
|
144
151
|
|
|
145
|
-
it('still revives the legacy little-endian limb-array shape (back-compat)', () => {
|
|
146
|
-
// u256 [5,0,4,0] little-endian = 5 + 4*2^128.
|
|
147
|
-
const w = Wallet.fromJSONValue({ c: [5, 0, 4, 0], a: [9, 1] });
|
|
148
|
-
expect(w.c).toBe(2n ** 130n + 5n);
|
|
149
|
-
expect(w.a).toBe(2n ** 64n + 9n);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
152
|
it('recurses into nested @data and arrays of bignums', () => {
|
|
153
153
|
const a = new Account();
|
|
154
154
|
a.main.c = BigInt(huge);
|