toiljs 0.0.16 → 0.0.19

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +313 -128
  3. package/as-pect.config.js +1 -1
  4. package/build/backend/.tsbuildinfo +1 -1
  5. package/build/backend/index.d.ts +1 -0
  6. package/build/backend/index.js +20 -1
  7. package/build/cli/.tsbuildinfo +1 -1
  8. package/build/cli/index.js +1320 -696
  9. package/build/client/.tsbuildinfo +1 -1
  10. package/build/client/dev/devtools.js +42 -5
  11. package/build/client/errors.d.ts +1 -0
  12. package/build/client/errors.js +3 -0
  13. package/build/client/index.d.ts +2 -0
  14. package/build/client/index.js +2 -0
  15. package/build/client/rpc.d.ts +1 -0
  16. package/build/client/rpc.js +37 -0
  17. package/build/compiler/.tsbuildinfo +1 -1
  18. package/build/compiler/config.js +3 -1
  19. package/build/compiler/docs.js +62 -5
  20. package/build/compiler/generate.js +5 -4
  21. package/build/compiler/index.d.ts +1 -0
  22. package/build/compiler/index.js +1 -1
  23. package/build/compiler/plugin.js +80 -8
  24. package/build/compiler/seo.js +15 -1
  25. package/build/compiler/ssg.js +7 -1
  26. package/build/compiler/vite.js +25 -0
  27. package/build/io/.tsbuildinfo +1 -1
  28. package/build/io/codec.d.ts +54 -0
  29. package/build/io/codec.js +143 -0
  30. package/build/io/index.d.ts +1 -2
  31. package/build/io/index.js +1 -2
  32. package/eslint.config.js +1 -1
  33. package/examples/basic/client/routes/features/index.tsx +1 -1
  34. package/examples/basic/client/routes/io.tsx +6 -7
  35. package/examples/basic/client/routes/rest.tsx +74 -0
  36. package/examples/basic/client/routes/rpc.tsx +43 -0
  37. package/package.json +19 -7
  38. package/presets/prettier-plugin.js +51 -0
  39. package/presets/prettier.json +1 -0
  40. package/server/runtime/README.md +97 -0
  41. package/server/runtime/abort/abort.ts +27 -0
  42. package/server/runtime/env/Server.ts +61 -0
  43. package/server/runtime/envelope.ts +191 -0
  44. package/server/runtime/exports/index.ts +52 -0
  45. package/server/runtime/handlers/ToilHandler.ts +34 -0
  46. package/server/runtime/index.ts +26 -0
  47. package/server/runtime/lang/Potential.ts +5 -0
  48. package/server/runtime/memory.ts +81 -0
  49. package/server/runtime/request.ts +55 -0
  50. package/server/runtime/response.ts +86 -0
  51. package/server/runtime/rest/Rest.ts +39 -0
  52. package/server/runtime/rest/RestHandler.ts +20 -0
  53. package/server/runtime/rest/RouteContext.ts +82 -0
  54. package/server/runtime/rest/match.ts +48 -0
  55. package/server/runtime/tsconfig.json +7 -0
  56. package/src/backend/index.ts +45 -3
  57. package/src/cli/create.ts +15 -5
  58. package/src/cli/diagnostics.ts +81 -0
  59. package/src/cli/doctor.ts +384 -7
  60. package/src/cli/index.ts +11 -2
  61. package/src/client/dev/devtools.tsx +49 -4
  62. package/src/client/errors.ts +11 -0
  63. package/src/client/index.ts +2 -0
  64. package/src/client/rpc.ts +64 -0
  65. package/src/compiler/config.ts +3 -1
  66. package/src/compiler/docs.ts +62 -5
  67. package/src/compiler/generate.ts +6 -5
  68. package/src/compiler/index.ts +3 -1
  69. package/src/compiler/plugin.ts +99 -11
  70. package/src/compiler/seo.ts +23 -3
  71. package/src/compiler/ssg.ts +10 -1
  72. package/src/compiler/vite.ts +34 -0
  73. package/src/io/FastMap.ts +24 -0
  74. package/src/io/FastSet.ts +15 -1
  75. package/src/io/codec.ts +217 -0
  76. package/src/io/index.ts +1 -2
  77. package/src/io/types.ts +2 -1
  78. package/test/assembly/example.spec.ts +14 -4
  79. package/test/doctor.test.ts +65 -0
  80. package/test/errors.test.ts +21 -0
  81. package/test/io.test.ts +65 -41
  82. package/test/prettier-plugin.test.ts +46 -0
  83. package/test/rpc.test.ts +50 -0
  84. package/tests/data-parity/generated-parity.ts +99 -0
  85. package/tests/data-parity/parity.ts +80 -0
  86. package/tests/data-parity/spec.ts +46 -0
  87. package/tsconfig.json +1 -1
  88. package/tsconfig.server.json +1 -1
  89. package/build/io/BinaryReader.d.ts +0 -44
  90. package/build/io/BinaryReader.js +0 -244
  91. package/build/io/BinaryWriter.d.ts +0 -44
  92. package/build/io/BinaryWriter.js +0 -297
  93. package/build/server/release.wasm +0 -0
  94. package/build/server/release.wat +0 -9
  95. package/src/io/BinaryReader.ts +0 -340
  96. package/src/io/BinaryWriter.ts +0 -385
  97. package/src/server/index.ts +0 -10
  98. package/src/server/main.ts +0 -13
  99. package/src/server/tsconfig.json +0 -4
  100. package/toilconfig.json +0 -30
package/test/io.test.ts CHANGED
@@ -1,69 +1,93 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { BinaryReader } from '../src/io/BinaryReader';
4
- import { BinaryWriter } from '../src/io/BinaryWriter';
3
+ import { DataReader, DataWriter } from '../src/io/codec';
5
4
  import { FastMap } from '../src/io/FastMap';
6
5
  import { FastSet } from '../src/io/FastSet';
7
6
 
8
- describe('BinaryWriter / BinaryReader', () => {
7
+ describe('DataWriter / DataReader', () => {
9
8
  it('round-trips fixed-width integers', () => {
10
- const w = new BinaryWriter();
11
- w.writeU8(255);
12
- w.writeU16(65535);
13
- w.writeU32(4294967295);
14
- w.writeU64(18446744073709551615n);
15
- w.writeI8(-128);
16
- w.writeI32(-2147483648);
9
+ const w = new DataWriter();
10
+ w.writeU8(255).writeU16(65535).writeU32(4294967295).writeU64(18446744073709551615n);
11
+ w.writeI8(-128).writeI16(-32768).writeI32(-2147483648).writeI64(-9223372036854775808n);
17
12
 
18
- const r = new BinaryReader(w.getBuffer());
13
+ const r = new DataReader(w.toBytes());
19
14
  expect(r.readU8()).toBe(255);
20
15
  expect(r.readU16()).toBe(65535);
21
16
  expect(r.readU32()).toBe(4294967295);
22
17
  expect(r.readU64()).toBe(18446744073709551615n);
23
18
  expect(r.readI8()).toBe(-128);
19
+ expect(r.readI16()).toBe(-32768);
24
20
  expect(r.readI32()).toBe(-2147483648);
21
+ expect(r.readI64()).toBe(-9223372036854775808n);
22
+ expect(r.ok).toBe(true);
23
+ expect(r.remaining()).toBe(0);
25
24
  });
26
25
 
27
- it('round-trips u256 and strings', () => {
28
- const big = 123456789012345678901234567890n;
29
- const w = new BinaryWriter();
30
- w.writeU256(big);
31
- w.writeStringWithLength('hello toil 🛠');
32
- w.writeBoolean(true);
26
+ it('round-trips floats, bool, bytes and strings', () => {
27
+ const w = new DataWriter();
28
+ w.writeF32(0.5).writeF64(3.141592653589793).writeBool(true).writeBool(false);
29
+ w.writeBytes(new Uint8Array([1, 2, 3, 0, 255]));
30
+ w.writeString('hello toil 🛠');
33
31
 
34
- const r = new BinaryReader(w.getBuffer());
35
- expect(r.readU256()).toBe(big);
36
- expect(r.readStringWithLength()).toBe('hello toil 🛠');
37
- expect(r.readBoolean()).toBe(true);
32
+ const r = new DataReader(w.toBytes());
33
+ expect(r.readF32()).toBe(0.5);
34
+ expect(r.readF64()).toBe(3.141592653589793);
35
+ expect(r.readBool()).toBe(true);
36
+ expect(r.readBool()).toBe(false);
37
+ expect([...r.readBytes()]).toEqual([1, 2, 3, 0, 255]);
38
+ expect(r.readString()).toBe('hello toil 🛠');
39
+ expect(r.ok).toBe(true);
38
40
  });
39
41
 
40
- it('round-trips arrays', () => {
41
- const w = new BinaryWriter();
42
- w.writeU32Array([1, 2, 3]);
43
- w.writeStringArray(['a', 'bb', 'ccc']);
42
+ it('round-trips u128 / i128 / u256 / i256', () => {
43
+ const u = 123456789012345678901234567890n;
44
+ const big256 = (2n ** 256n) - 1n;
45
+ const w = new DataWriter();
46
+ w.writeU128(u).writeI128(-1234567890123456789n).writeU256(big256).writeI256(-(2n ** 200n));
44
47
 
45
- const r = new BinaryReader(w.getBuffer());
46
- expect(r.readU32Array()).toEqual([1, 2, 3]);
47
- expect(r.readStringArray()).toEqual(['a', 'bb', 'ccc']);
48
+ const r = new DataReader(w.toBytes());
49
+ expect(r.readU128()).toBe(u);
50
+ expect(r.readI128()).toBe(-1234567890123456789n);
51
+ expect(r.readU256()).toBe(big256);
52
+ expect(r.readI256()).toBe(-(2n ** 200n));
48
53
  });
49
54
 
50
- it('rejects out-of-range values', () => {
51
- const w = new BinaryWriter();
52
- expect(() => w.writeU8(256)).toThrow();
53
- expect(() => w.writeI8(128)).toThrow();
55
+ it('grows past the initial capacity without corruption (regression)', () => {
56
+ // The default buffer is 64 bytes; this forces several reallocations.
57
+ const w = new DataWriter();
58
+ const text = 'z'.repeat(500);
59
+ for (let i = 0; i < 40; i++) w.writeU64(BigInt(i)); // 320 bytes
60
+ w.writeString(text);
61
+
62
+ const r = new DataReader(w.toBytes());
63
+ for (let i = 0; i < 40; i++) expect(r.readU64()).toBe(BigInt(i));
64
+ expect(r.readString()).toBe(text);
65
+ expect(r.ok).toBe(true);
66
+ });
67
+
68
+ it('round-trips big-endian when be is set, and be flips byte order', () => {
69
+ expect([...new DataWriter().writeU32(0x01020304).toBytes()]).toEqual([4, 3, 2, 1]);
70
+ expect([...new DataWriter().writeU32(0x01020304, true).toBytes()]).toEqual([1, 2, 3, 4]);
71
+
72
+ const w = new DataWriter();
73
+ w.writeU16(0xabcd, true).writeI32(-2, true).writeU64(0xdeadbeefn, true).writeU128(123456789012345n, true);
74
+ const r = new DataReader(w.toBytes());
75
+ expect(r.readU16(true)).toBe(0xabcd);
76
+ expect(r.readI32(true)).toBe(-2);
77
+ expect(r.readU64(true)).toBe(0xdeadbeefn);
78
+ expect(r.readU128(true)).toBe(123456789012345n);
54
79
  });
55
80
 
56
- it('rejects negative / overflowing u256 and u128', () => {
57
- const w = new BinaryWriter();
58
- expect(() => w.writeU256(-1n)).toThrow();
59
- expect(() => w.writeU256(2n ** 256n)).toThrow();
60
- expect(() => w.writeU128(-1n)).toThrow();
61
- expect(() => w.writeU128(2n ** 128n)).toThrow();
81
+ it('is little-endian and masks instead of throwing', () => {
82
+ // u32 1 01 00 00 00 (LE); writeU8 masks to a byte, no throw.
83
+ const bytes = new DataWriter().writeU32(1).writeU8(256).toBytes();
84
+ expect([...bytes]).toEqual([1, 0, 0, 0, 0]);
62
85
  });
63
86
 
64
- it('throws when reading past the end', () => {
65
- const r = new BinaryReader(new Uint8Array(2));
66
- expect(() => r.readU32()).toThrow();
87
+ it('reports ok=false when reading past the end (no throw)', () => {
88
+ const r = new DataReader(new Uint8Array(2));
89
+ expect(r.readU32()).toBe(0);
90
+ expect(r.ok).toBe(false);
67
91
  });
68
92
  });
69
93
 
@@ -0,0 +1,46 @@
1
+ import prettier from 'prettier';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import * as plugin from '../presets/prettier-plugin.js';
5
+
6
+ function fmt(src: string): Promise<string> {
7
+ return prettier.format(src, {
8
+ parser: 'typescript',
9
+ plugins: [plugin],
10
+ tabWidth: 4,
11
+ singleQuote: true,
12
+ semi: true,
13
+ });
14
+ }
15
+
16
+ // The plugin lets prettier format toilscript server code, whose native decorators on free
17
+ // functions (@main, @remote function) are not valid JS/TS grammar (so stock prettier throws).
18
+ describe('toiljs prettier-plugin', () => {
19
+ it('formats a free @remote function and keeps the decorator', async () => {
20
+ const out = await fmt('@remote\nfunction ping(n:i32):i32{return n+1}\n');
21
+ expect(out).toContain('@remote');
22
+ expect(out).toContain('function ping(n: i32): i32 {');
23
+ expect(out).not.toContain('toil-decorator'); // marker fully restored
24
+ });
25
+
26
+ it('formats @main', async () => {
27
+ const out = await fmt('@main\nfunction run():i32{return 42}\n');
28
+ expect(out).toMatch(/@main\nfunction run\(\): i32 \{/);
29
+ });
30
+
31
+ it('handles @remote export function', async () => {
32
+ const out = await fmt('@remote\nexport function pong():void{}\n');
33
+ expect(out).toContain('@remote\nexport function pong(): void {}');
34
+ });
35
+
36
+ it('leaves class/method decorators untouched', async () => {
37
+ const out = await fmt('@data\nclass A{ x:i32=0 }\n');
38
+ expect(out).toContain('@data');
39
+ expect(out).toContain('class A {');
40
+ });
41
+
42
+ it('is idempotent', async () => {
43
+ const once = await fmt('@remote\nfunction f():void{}\n@main\nfunction g():void{}\n');
44
+ expect(await fmt(once)).toBe(once);
45
+ });
46
+ });
@@ -0,0 +1,50 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+
3
+ import { Server } from '../src/client/rpc';
4
+
5
+ // `Server` is the runtime behind the generated typed surface. Until the transport
6
+ // is wired, it is a recursive proxy that throws on call.
7
+ describe('Server RPC stub', () => {
8
+ it('throws on a direct call, naming the path', () => {
9
+ const s = Server as { ping: () => unknown };
10
+ expect(() => s.ping()).toThrow(/Server\.ping\(\)/);
11
+ });
12
+
13
+ it('throws on a nested service.method call', () => {
14
+ const s = Server as { accounts: { getUser: () => unknown } };
15
+ expect(() => s.accounts.getUser()).toThrow(/Server\.accounts\.getUser\(\)/);
16
+ expect(() => s.accounts.getUser()).toThrow(/not available yet/);
17
+ });
18
+
19
+ it('is not thenable (so it is not mistaken for a promise)', () => {
20
+ const s = Server as Record<string, unknown>;
21
+ expect(s.then).toBeUndefined();
22
+ });
23
+
24
+ it('ignores symbol probes without throwing', () => {
25
+ const s = Server as Record<PropertyKey, unknown>;
26
+ expect(s[Symbol.iterator]).toBeUndefined();
27
+ });
28
+ });
29
+
30
+ // `Server.REST` surfaces the working fetch client that the generated `shared/server.ts`
31
+ // attaches to `globalThis.__toilRest` on import.
32
+ describe('Server.REST surface', () => {
33
+ afterEach(() => {
34
+ delete (globalThis as { __toilRest?: unknown }).__toilRest;
35
+ });
36
+
37
+ it('returns the attached REST client when shared/server has loaded', () => {
38
+ const fake = { todos: { getTodo: async () => 'ok' } };
39
+ (globalThis as { __toilRest?: unknown }).__toilRest = fake;
40
+ const s = Server as { REST: typeof fake };
41
+ expect(s.REST).toBe(fake);
42
+ expect(s.REST.todos.getTodo).toBeTypeOf('function');
43
+ });
44
+
45
+ it('throws a helpful "not loaded" error when the REST client is absent', () => {
46
+ const s = Server as { REST: { todos: { getTodo: () => unknown } } };
47
+ expect(() => s.REST.todos.getTodo()).toThrow(/Server\.REST\.todos\.getTodo\(\)/);
48
+ expect(() => s.REST.todos.getTodo()).toThrow(/has not loaded/);
49
+ });
50
+ });
@@ -0,0 +1,99 @@
1
+ // Proves the fork's GENERATED @data class (from --rpcModule) matches the AS @data
2
+ // wire byte-for-byte. Compiles spec.ts (which has `@data class Foo`) with the
3
+ // ToilScript fork, emitting both the wasm and the generated server.ts, then checks
4
+ // the generated Foo class against the AS Foo.encode() bytes, both directions.
5
+ // Run with: node tests/data-parity/generated-parity.ts
6
+ import { readFileSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join, dirname } from "node:path";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { spawnSync } from "node:child_process";
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ // The generated module imports DataWriter/DataReader from this specifier.
13
+ const codec = join(here, "..", "..", "src", "io", "codec.ts");
14
+ const fork = "/root/toil-stuff/toilscript";
15
+ const spec = join(here, "spec.ts");
16
+ const tmp = mkdtempSync(join(tmpdir(), "gen-parity-"));
17
+ const wasmPath = join(tmp, "spec.wasm");
18
+ const modPath = join(tmp, "server.ts");
19
+ writeFileSync(join(tmp, "package.json"), '{ "type": "module" }\n');
20
+
21
+ const compile = spawnSync(
22
+ "node",
23
+ [join(fork, "bin", "toilscript.js"), spec, "-o", wasmPath, "--runtime", "stub", "--initialMemory", "32", "--rpcModule", modPath, "--rpcRuntime", codec],
24
+ { stdio: "inherit" },
25
+ );
26
+ if (compile.status !== 0) {
27
+ console.error("generated parity: COMPILE FAILED");
28
+ rmSync(tmp, { recursive: true, force: true });
29
+ process.exit(1);
30
+ }
31
+
32
+ function fail(msg: string): never {
33
+ console.error("generated parity: FAIL,", msg);
34
+ rmSync(tmp, { recursive: true, force: true });
35
+ process.exit(1);
36
+ }
37
+ function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
38
+ if (a.length !== b.length) return false;
39
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
40
+ return true;
41
+ }
42
+ const hex = (b: Uint8Array): string => Buffer.from(b).toString("hex");
43
+
44
+ // the known sample (must match spec.ts `sample()`)
45
+ const ID = 0xcafebabedeadbeefn;
46
+ const COUNT = -42;
47
+ const FLAG = true;
48
+ const BIG = 123456789n;
49
+ const NAME = "cross-lang";
50
+
51
+ // AS side: instantiate and read the sample bytes out of linear memory.
52
+ const { instance } = await WebAssembly.instantiate(readFileSync(wasmPath), {
53
+ env: { abort: (_m: number, _f: number, line: number) => { throw new Error("wasm abort @ line " + line); } },
54
+ });
55
+ const x = instance.exports as Record<string, CallableFunction> & { memory: WebAssembly.Memory };
56
+ const SCRATCH = 0x100000;
57
+ const len = x.encodeSampleTo(SCRATCH) as number;
58
+ const wasmBytes = new Uint8Array(x.memory.buffer, SCRATCH, len).slice();
59
+
60
+ // Generated side: build the same value with the generated Foo class and encode.
61
+ const gen = (await import(pathToFileURL(modPath).href)) as {
62
+ Foo: new () => {
63
+ id: bigint; count: number; flag: boolean; big: bigint; name: string;
64
+ encode(): Uint8Array;
65
+ } & Record<string, unknown>;
66
+ };
67
+ const FooClass = gen.Foo as unknown as {
68
+ new (): { id: bigint; count: number; flag: boolean; big: bigint; name: string; encode(): Uint8Array };
69
+ decode(buf: Uint8Array): { id: bigint; count: number; flag: boolean; big: bigint; name: string };
70
+ dataId(): number;
71
+ };
72
+
73
+ const foo = new FooClass();
74
+ foo.id = ID;
75
+ foo.count = COUNT;
76
+ foo.flag = FLAG;
77
+ foo.big = BIG;
78
+ foo.name = NAME;
79
+ const genBytes = foo.encode();
80
+
81
+ // 1) byte-for-byte identical to the AS @data encoding.
82
+ if (!bytesEqual(genBytes, wasmBytes)) {
83
+ fail(`byte mismatch\n AS: ${hex(wasmBytes)}\n generated: ${hex(genBytes)}`);
84
+ }
85
+
86
+ // 2) the generated decode round-trips the AS bytes back to the sample.
87
+ const back = FooClass.decode(wasmBytes);
88
+ if (back.id !== ID) fail("id (decode)");
89
+ if (back.count !== COUNT) fail("count (decode)");
90
+ if (back.flag !== FLAG) fail("flag (decode)");
91
+ if (back.big !== BIG) fail("big (decode)");
92
+ if (back.name !== NAME) fail("name (decode)");
93
+
94
+ // 3) the AS side accepts the generated bytes.
95
+ new Uint8Array(x.memory.buffer, SCRATCH, genBytes.length).set(genBytes);
96
+ if ((x.checkBytes(SCRATCH, genBytes.length) as number) !== 1) fail("AS rejected generated bytes");
97
+
98
+ console.log(`@data generated-class parity: PASS (generated <-> AS both ways, byte-for-byte, ${len} bytes)`);
99
+ rmSync(tmp, { recursive: true, force: true });
@@ -0,0 +1,80 @@
1
+ // Cross-language @data byte-parity proof. Compiles spec.ts with the ToilScript
2
+ // fork (which has @data), then checks the TS codec (src/io/codec.ts) against it
3
+ // both directions, including byte-for-byte. Run with: node tests/data-parity/parity.ts
4
+ import { readFileSync, mkdtempSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join, dirname } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { spawnSync } from "node:child_process";
9
+ import { DataWriter, DataReader } from "../../src/io/codec.ts";
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const fork = "/root/toil-stuff/toilscript";
13
+ const spec = join(here, "spec.ts");
14
+ const tmp = mkdtempSync(join(tmpdir(), "parity-"));
15
+ const wasmPath = join(tmp, "spec.wasm");
16
+
17
+ const compile = spawnSync(
18
+ "node",
19
+ [join(fork, "bin", "toilscript.js"), spec, "-o", wasmPath, "--runtime", "stub", "--initialMemory", "32"],
20
+ { stdio: "inherit" },
21
+ );
22
+ if (compile.status !== 0) {
23
+ console.error("@data parity: COMPILE FAILED");
24
+ rmSync(tmp, { recursive: true, force: true });
25
+ process.exit(1);
26
+ }
27
+
28
+ function fail(msg: string): never {
29
+ console.error("@data parity: FAIL,", msg);
30
+ rmSync(tmp, { recursive: true, force: true });
31
+ process.exit(1);
32
+ }
33
+ function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
34
+ if (a.length !== b.length) return false;
35
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
36
+ return true;
37
+ }
38
+ const hex = (b: Uint8Array): string => Buffer.from(b).toString("hex");
39
+
40
+ const { instance } = await WebAssembly.instantiate(readFileSync(wasmPath), {
41
+ env: { abort: (_m: number, _f: number, line: number) => { throw new Error("wasm abort @ line " + line); } },
42
+ });
43
+ const x = instance.exports as Record<string, CallableFunction> & { memory: WebAssembly.Memory };
44
+ const mem = x.memory;
45
+ const SCRATCH = 0x100000; // 1 MiB, above the low heap
46
+
47
+ // the known sample (must match spec.ts)
48
+ const ID = 0xcafebabedeadbeefn;
49
+ const COUNT = -42;
50
+ const FLAG = true;
51
+ const BIG = 123456789n;
52
+ const NAME = "cross-lang";
53
+ const fooId = (x.fooId() as number) >>> 0;
54
+
55
+ // 1) ToilScript encodes the sample; read its bytes out of linear memory.
56
+ const len = x.encodeSampleTo(SCRATCH) as number;
57
+ const wasmBytes = new Uint8Array(mem.buffer, SCRATCH, len).slice();
58
+
59
+ // 2) TS decodes the ToilScript bytes.
60
+ const r = new DataReader(wasmBytes);
61
+ if ((r.readU32() >>> 0) !== fooId) fail("typeId mismatch");
62
+ if (r.readU64() !== ID) fail("id (ToilScript -> TS)");
63
+ if (r.readI32() !== COUNT) fail("count (ToilScript -> TS)");
64
+ if (r.readBool() !== FLAG) fail("flag (ToilScript -> TS)");
65
+ if (r.readU128() !== BIG) fail("big (ToilScript -> TS)");
66
+ if (r.readString() !== NAME) fail("name (ToilScript -> TS)");
67
+ if (!r.ok || r.remaining() !== 0) fail("trailing/ok (ToilScript -> TS)");
68
+
69
+ // 3) TS encodes the same value; it must be byte-for-byte identical.
70
+ const w = new DataWriter();
71
+ w.writeU32(fooId).writeU64(ID).writeI32(COUNT).writeBool(FLAG).writeU128(BIG).writeString(NAME);
72
+ const tsBytes = w.toBytes();
73
+ if (!bytesEqual(tsBytes, wasmBytes)) fail(`byte mismatch\n ToilScript: ${hex(wasmBytes)}\n TS: ${hex(tsBytes)}`);
74
+
75
+ // 4) ToilScript decodes the TS bytes and confirms the value.
76
+ new Uint8Array(mem.buffer, SCRATCH, tsBytes.length).set(tsBytes);
77
+ if ((x.checkBytes(SCRATCH, tsBytes.length) as number) !== 1) fail("ToilScript rejected TS bytes (TS -> ToilScript)");
78
+
79
+ console.log(`@data parity: PASS (ToilScript<->TS both ways, byte-for-byte, ${len} bytes)`);
80
+ rmSync(tmp, { recursive: true, force: true });
@@ -0,0 +1,46 @@
1
+ // ToilScript side of the cross-language @data parity proof. Compiled by the
2
+ // ToilScript fork (which has @data + std/assembly/data.ts). Exposes a known sample so the
3
+ // TS codec can be checked against it byte-for-byte, both directions.
4
+ //
5
+ // A fixed scratch region (high in linear memory, away from the low heap) is used
6
+ // to move bytes across the JS boundary without the loader.
7
+
8
+ @data
9
+ class Foo {
10
+ id: u64 = 0;
11
+ count: i32 = 0;
12
+ flag: bool = false;
13
+ big: u128 = u128.Zero;
14
+ name: string = "";
15
+ }
16
+
17
+ function sample(): Foo {
18
+ const f = new Foo();
19
+ f.id = 0xCAFEBABEDEADBEEF;
20
+ f.count = -42;
21
+ f.flag = true;
22
+ f.big = u128.fromU64(123456789);
23
+ f.name = "cross-lang";
24
+ return f;
25
+ }
26
+
27
+ export function fooId(): u32 {
28
+ return Foo.dataId();
29
+ }
30
+
31
+ /** Encode the sample, copy it to `out`, return the byte length. */
32
+ export function encodeSampleTo(out: usize): i32 {
33
+ const bytes = sample().encode();
34
+ memory.copy(out, bytes.dataStart, <usize>bytes.length);
35
+ return bytes.length;
36
+ }
37
+
38
+ /** Decode `len` bytes at `inp` and return 1 if they equal the sample, else 0. */
39
+ export function checkBytes(inp: usize, len: i32): i32 {
40
+ const bytes = new Uint8Array(len);
41
+ memory.copy(bytes.dataStart, inp, <usize>len);
42
+ const f = Foo.decode(bytes);
43
+ const s = sample();
44
+ const ok = f.id == s.id && f.count == s.count && f.flag == s.flag && f.big == s.big && f.name == s.name;
45
+ return ok ? 1 : 0;
46
+ }
package/tsconfig.json CHANGED
@@ -17,6 +17,6 @@
17
17
  "exclude": [
18
18
  "node_modules",
19
19
  "build",
20
- "src/server"
20
+ "server"
21
21
  ]
22
22
  }
@@ -5,6 +5,6 @@
5
5
  "noEmit": true
6
6
  },
7
7
  "include": [
8
- "src/server/**/*.ts"
8
+ "server/**/*.ts"
9
9
  ]
10
10
  }
@@ -1,44 +0,0 @@
1
- import type { BufferLike, i16, i32, i64, i8, Selector, u16, u32, u8 } from './types.js';
2
- export declare class BinaryReader {
3
- private buffer;
4
- private currentOffset;
5
- constructor(bytes: BufferLike);
6
- get byteLength(): number;
7
- static stringCompare(a: string, b: string): number;
8
- static bigintCompare(a: bigint, b: bigint): number;
9
- static numberCompare(a: number, b: number): number;
10
- setBuffer(bytes: BufferLike): void;
11
- length(): number;
12
- bytesLeft(): number;
13
- readI8(): i8;
14
- readI16(be?: boolean): i16;
15
- readI32(be?: boolean): i32;
16
- readI64(be?: boolean): i64;
17
- readU8(): u8;
18
- readU16(be?: boolean): u16;
19
- readU32(be?: boolean): u32;
20
- readU64(be?: boolean): bigint;
21
- readU128(be?: boolean): bigint;
22
- readU256(be?: boolean): bigint;
23
- readI128(be?: boolean): bigint;
24
- readBoolean(): boolean;
25
- readSelector(): Selector;
26
- readBytes(length: u32, zeroStop?: boolean): Uint8Array;
27
- readString(length: u32): string;
28
- readStringWithLength(be?: boolean): string;
29
- readBytesWithLength(maxLength?: number, be?: boolean): Uint8Array;
30
- readArrayOfBuffer(be?: boolean): Uint8Array[];
31
- readU256Array(be?: boolean): bigint[];
32
- readU128Array(be?: boolean): bigint[];
33
- readU64Array(be?: boolean): bigint[];
34
- readU32Array(be?: boolean): u32[];
35
- readU16Array(be?: boolean): u16[];
36
- readU8Array(): u8[];
37
- readStringArray(be?: boolean): string[];
38
- readBytesArray(be?: boolean): Uint8Array[];
39
- getOffset(): u16;
40
- setOffset(offset: u16): void;
41
- verifyEnd(size: number): void;
42
- private reverseBytes;
43
- private toHexString;
44
- }