toiljs 0.0.15 → 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 (273) hide show
  1. package/.babelrc +13 -13
  2. package/.gitattributes +2 -2
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -90
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  7. package/.github/PULL_REQUEST_TEMPLATE.md +43 -43
  8. package/.github/changelog-config.json +45 -45
  9. package/.github/dependabot.yml +27 -27
  10. package/.github/workflows/ci.yml +191 -191
  11. package/.prettierrc.json +11 -11
  12. package/.vscode/settings.json +9 -9
  13. package/CHANGELOG.md +116 -5
  14. package/LICENSE +187 -187
  15. package/README.md +524 -315
  16. package/as-pect.asconfig.json +34 -34
  17. package/as-pect.config.js +65 -65
  18. package/assets/logo.svg +36 -36
  19. package/build/backend/.tsbuildinfo +1 -1
  20. package/build/backend/index.d.ts +1 -0
  21. package/build/backend/index.js +20 -1
  22. package/build/cli/.tsbuildinfo +1 -1
  23. package/build/cli/index.js +1320 -696
  24. package/build/client/.tsbuildinfo +1 -1
  25. package/build/client/dev/devtools.d.ts +6 -0
  26. package/build/client/dev/devtools.js +479 -0
  27. package/build/client/dev/error-overlay.d.ts +9 -0
  28. package/build/client/dev/error-overlay.js +19 -4
  29. package/build/client/errors.d.ts +1 -0
  30. package/build/client/errors.js +3 -0
  31. package/build/client/index.d.ts +2 -0
  32. package/build/client/index.js +2 -0
  33. package/build/client/navigation/prefetch.d.ts +1 -0
  34. package/build/client/navigation/prefetch.js +35 -0
  35. package/build/client/routing/Router.js +1 -1
  36. package/build/client/routing/hooks.js +6 -2
  37. package/build/client/routing/loader.d.ts +23 -0
  38. package/build/client/routing/loader.js +53 -7
  39. package/build/client/routing/mount.js +4 -3
  40. package/build/client/rpc.d.ts +1 -0
  41. package/build/client/rpc.js +37 -0
  42. package/build/compiler/.tsbuildinfo +1 -1
  43. package/build/compiler/config.d.ts +16 -0
  44. package/build/compiler/config.js +9 -0
  45. package/build/compiler/docs.js +78 -21
  46. package/build/compiler/generate.js +5 -4
  47. package/build/compiler/index.d.ts +3 -2
  48. package/build/compiler/index.js +2 -2
  49. package/build/compiler/plugin.js +228 -0
  50. package/build/compiler/prerender.d.ts +1 -0
  51. package/build/compiler/prerender.js +1 -1
  52. package/build/compiler/seo.d.ts +1 -1
  53. package/build/compiler/seo.js +20 -5
  54. package/build/compiler/ssg.js +39 -2
  55. package/build/compiler/vite.js +25 -0
  56. package/build/io/.tsbuildinfo +1 -1
  57. package/build/io/codec.d.ts +54 -0
  58. package/build/io/codec.js +143 -0
  59. package/build/io/index.d.ts +1 -2
  60. package/build/io/index.js +1 -2
  61. package/build/logger/.tsbuildinfo +1 -1
  62. package/build/shared/.tsbuildinfo +1 -1
  63. package/eslint.config.js +48 -48
  64. package/examples/basic/client/404.tsx +11 -11
  65. package/examples/basic/client/components/.gitkeep +1 -1
  66. package/examples/basic/client/global-error.tsx +13 -13
  67. package/examples/basic/client/layout.tsx +25 -25
  68. package/examples/basic/client/public/images/.gitkeep +1 -1
  69. package/examples/basic/client/public/images/logo.svg +36 -36
  70. package/examples/basic/client/public/robots.txt +2 -2
  71. package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
  72. package/examples/basic/client/routes/features/error/error.tsx +16 -16
  73. package/examples/basic/client/routes/features/index.tsx +1 -1
  74. package/examples/basic/client/routes/features/template/b.tsx +14 -14
  75. package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
  76. package/examples/basic/client/routes/gallery/layout.tsx +13 -13
  77. package/examples/basic/client/routes/io.tsx +23 -24
  78. package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
  79. package/examples/basic/client/routes/rest.tsx +74 -0
  80. package/examples/basic/client/routes/rpc.tsx +43 -0
  81. package/examples/basic/client/routes/search.tsx +61 -61
  82. package/examples/basic/client/toil.tsx +5 -5
  83. package/package.json +167 -148
  84. package/presets/eslint.js +88 -88
  85. package/presets/no-uint8array-tostring.js +200 -200
  86. package/presets/prettier-plugin.js +51 -0
  87. package/presets/prettier.json +19 -18
  88. package/presets/tsconfig.json +37 -37
  89. package/server/runtime/README.md +97 -0
  90. package/server/runtime/abort/abort.ts +27 -0
  91. package/server/runtime/env/Server.ts +61 -0
  92. package/server/runtime/envelope.ts +191 -0
  93. package/server/runtime/exports/index.ts +52 -0
  94. package/server/runtime/handlers/ToilHandler.ts +34 -0
  95. package/server/runtime/index.ts +26 -0
  96. package/server/runtime/lang/Potential.ts +5 -0
  97. package/server/runtime/memory.ts +81 -0
  98. package/server/runtime/request.ts +55 -0
  99. package/server/runtime/response.ts +86 -0
  100. package/server/runtime/rest/Rest.ts +39 -0
  101. package/server/runtime/rest/RestHandler.ts +20 -0
  102. package/server/runtime/rest/RouteContext.ts +82 -0
  103. package/server/runtime/rest/match.ts +48 -0
  104. package/server/runtime/tsconfig.json +7 -0
  105. package/src/backend/index.ts +202 -160
  106. package/src/cli/create.ts +15 -5
  107. package/src/cli/diagnostics.ts +81 -0
  108. package/src/cli/doctor.ts +384 -7
  109. package/src/cli/index.ts +11 -2
  110. package/src/cli/proc.ts +50 -50
  111. package/src/cli/updates.ts +69 -69
  112. package/src/cli/validate.ts +31 -31
  113. package/src/client/channel/channel.ts +146 -146
  114. package/src/client/components/Form.tsx +65 -65
  115. package/src/client/components/Script.tsx +113 -113
  116. package/src/client/components/Slot.tsx +21 -21
  117. package/src/client/dev/devtools.tsx +1018 -0
  118. package/src/client/dev/error-overlay.tsx +30 -4
  119. package/src/client/errors.ts +11 -0
  120. package/src/client/head/head.ts +167 -167
  121. package/src/client/head/metadata.ts +112 -112
  122. package/src/client/index.ts +91 -89
  123. package/src/client/navigation/NavLink.tsx +86 -86
  124. package/src/client/navigation/navigation.ts +235 -235
  125. package/src/client/navigation/prefetch.ts +169 -130
  126. package/src/client/navigation/scroll.ts +53 -53
  127. package/src/client/routing/Router.tsx +8 -2
  128. package/src/client/routing/action.ts +122 -122
  129. package/src/client/routing/error-boundary.tsx +43 -43
  130. package/src/client/routing/hooks.ts +21 -6
  131. package/src/client/routing/loader.ts +325 -235
  132. package/src/client/routing/match.ts +47 -47
  133. package/src/client/routing/mount.tsx +54 -52
  134. package/src/client/routing/params-context.ts +10 -10
  135. package/src/client/routing/slot-context.ts +7 -7
  136. package/src/client/rpc.ts +64 -0
  137. package/src/client/search/search.ts +189 -189
  138. package/src/client/search/use-page-search.ts +73 -73
  139. package/src/client/types.ts +73 -73
  140. package/src/compiler/config.ts +221 -182
  141. package/src/compiler/docs.ts +285 -228
  142. package/src/compiler/generate.ts +395 -394
  143. package/src/compiler/index.ts +66 -57
  144. package/src/compiler/pages.ts +70 -70
  145. package/src/compiler/plugin.ts +258 -2
  146. package/src/compiler/prerender.ts +156 -156
  147. package/src/compiler/seo.ts +417 -390
  148. package/src/compiler/ssg.ts +171 -126
  149. package/src/compiler/vite.ts +34 -0
  150. package/src/io/FastMap.ts +151 -127
  151. package/src/io/FastSet.ts +15 -1
  152. package/src/io/codec.ts +217 -0
  153. package/src/io/index.ts +10 -11
  154. package/src/io/lengths.ts +14 -14
  155. package/src/io/types.ts +19 -18
  156. package/src/logger/index.ts +22 -22
  157. package/src/shared/index.ts +10 -10
  158. package/std/client/index.d.ts +15 -15
  159. package/std/client/package.json +3 -3
  160. package/test/assembly/example.spec.ts +17 -7
  161. package/test/channel.test.ts +21 -21
  162. package/test/doctor.test.ts +65 -0
  163. package/test/dom/Link.test.tsx +47 -47
  164. package/test/dom/NavLink.test.tsx +37 -37
  165. package/test/dom/error-overlay.test.tsx +44 -44
  166. package/test/dom/loader.test.tsx +121 -121
  167. package/test/dom/navigation.test.ts +59 -59
  168. package/test/dom/revalidate.test.tsx +38 -38
  169. package/test/dom/route-head.test.tsx +78 -78
  170. package/test/dom/router-loading.test.tsx +44 -44
  171. package/test/dom/scroll.test.ts +56 -56
  172. package/test/dom/use-metadata.test.tsx +58 -58
  173. package/test/errors.test.ts +21 -0
  174. package/test/io.test.ts +117 -93
  175. package/test/navlink.test.ts +28 -28
  176. package/test/placeholder.test.ts +9 -9
  177. package/test/prettier-plugin.test.ts +46 -0
  178. package/test/routes.test.ts +76 -76
  179. package/test/rpc.test.ts +50 -0
  180. package/test/seo.test.ts +175 -164
  181. package/test/slot-layouts.test.ts +69 -69
  182. package/test/ssg.test.ts +36 -36
  183. package/test/update.test.ts +44 -44
  184. package/test/validate.test.ts +42 -42
  185. package/tests/data-parity/generated-parity.ts +99 -0
  186. package/tests/data-parity/parity.ts +80 -0
  187. package/tests/data-parity/spec.ts +46 -0
  188. package/toil-routes.d.ts +7 -0
  189. package/tsconfig.backend.json +13 -13
  190. package/tsconfig.base.json +35 -35
  191. package/tsconfig.cli.json +13 -13
  192. package/tsconfig.client.json +14 -14
  193. package/tsconfig.compiler.json +13 -13
  194. package/tsconfig.io.json +12 -12
  195. package/tsconfig.json +22 -22
  196. package/tsconfig.logger.json +12 -12
  197. package/tsconfig.server.json +10 -10
  198. package/tsconfig.shared.json +12 -12
  199. package/vitest.config.ts +26 -26
  200. package/.idea/codeStyles/Project.xml +0 -54
  201. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  202. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  203. package/.idea/modules.xml +0 -8
  204. package/.idea/prettier.xml +0 -7
  205. package/.idea/toiljs.iml +0 -8
  206. package/.idea/vcs.xml +0 -6
  207. package/.toil/entry.tsx +0 -9
  208. package/.toil/index.html +0 -12
  209. package/.toil/routes.ts +0 -9
  210. package/build/cli/configure.d.ts +0 -16
  211. package/build/cli/configure.js +0 -272
  212. package/build/cli/create.d.ts +0 -16
  213. package/build/cli/create.js +0 -420
  214. package/build/cli/diagnostics.d.ts +0 -55
  215. package/build/cli/diagnostics.js +0 -333
  216. package/build/cli/doctor.d.ts +0 -6
  217. package/build/cli/doctor.js +0 -249
  218. package/build/cli/features.d.ts +0 -25
  219. package/build/cli/features.js +0 -107
  220. package/build/cli/index.d.ts +0 -2
  221. package/build/cli/proc.d.ts +0 -6
  222. package/build/cli/proc.js +0 -31
  223. package/build/cli/ui.d.ts +0 -9
  224. package/build/cli/ui.js +0 -75
  225. package/build/cli/update.d.ts +0 -7
  226. package/build/cli/update.js +0 -117
  227. package/build/cli/updates.d.ts +0 -10
  228. package/build/cli/updates.js +0 -45
  229. package/build/cli/validate.d.ts +0 -4
  230. package/build/cli/validate.js +0 -19
  231. package/build/client/Link.d.ts +0 -8
  232. package/build/client/Link.js +0 -44
  233. package/build/client/NavLink.d.ts +0 -14
  234. package/build/client/NavLink.js +0 -37
  235. package/build/client/Router.d.ts +0 -7
  236. package/build/client/Router.js +0 -55
  237. package/build/client/channel.d.ts +0 -23
  238. package/build/client/channel.js +0 -94
  239. package/build/client/error-boundary.d.ts +0 -16
  240. package/build/client/error-boundary.js +0 -19
  241. package/build/client/head.d.ts +0 -26
  242. package/build/client/head.js +0 -87
  243. package/build/client/hooks.d.ts +0 -17
  244. package/build/client/hooks.js +0 -48
  245. package/build/client/lazy.d.ts +0 -16
  246. package/build/client/lazy.js +0 -53
  247. package/build/client/match.d.ts +0 -2
  248. package/build/client/match.js +0 -32
  249. package/build/client/mount.d.ts +0 -2
  250. package/build/client/mount.js +0 -13
  251. package/build/client/navigation.d.ts +0 -13
  252. package/build/client/navigation.js +0 -97
  253. package/build/client/params-context.d.ts +0 -2
  254. package/build/client/params-context.js +0 -2
  255. package/build/client/prefetch.d.ts +0 -11
  256. package/build/client/prefetch.js +0 -100
  257. package/build/client/runtime.d.ts +0 -31
  258. package/build/client/runtime.js +0 -112
  259. package/build/client/scroll.d.ts +0 -8
  260. package/build/client/scroll.js +0 -36
  261. package/build/io/BinaryReader.d.ts +0 -44
  262. package/build/io/BinaryReader.js +0 -244
  263. package/build/io/BinaryWriter.d.ts +0 -44
  264. package/build/io/BinaryWriter.js +0 -297
  265. package/build/server/release.wasm +0 -0
  266. package/build/server/release.wat +0 -9
  267. package/src/io/BinaryReader.ts +0 -340
  268. package/src/io/BinaryWriter.ts +0 -385
  269. package/src/server/index.ts +0 -10
  270. package/src/server/main.ts +0 -13
  271. package/src/server/tsconfig.json +0 -4
  272. package/toil-env.d.ts +0 -16
  273. package/toilconfig.json +0 -30
package/test/io.test.ts CHANGED
@@ -1,93 +1,117 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { BinaryReader } from '../src/io/BinaryReader';
4
- import { BinaryWriter } from '../src/io/BinaryWriter';
5
- import { FastMap } from '../src/io/FastMap';
6
- import { FastSet } from '../src/io/FastSet';
7
-
8
- describe('BinaryWriter / BinaryReader', () => {
9
- 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);
17
-
18
- const r = new BinaryReader(w.getBuffer());
19
- expect(r.readU8()).toBe(255);
20
- expect(r.readU16()).toBe(65535);
21
- expect(r.readU32()).toBe(4294967295);
22
- expect(r.readU64()).toBe(18446744073709551615n);
23
- expect(r.readI8()).toBe(-128);
24
- expect(r.readI32()).toBe(-2147483648);
25
- });
26
-
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);
33
-
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);
38
- });
39
-
40
- it('round-trips arrays', () => {
41
- const w = new BinaryWriter();
42
- w.writeU32Array([1, 2, 3]);
43
- w.writeStringArray(['a', 'bb', 'ccc']);
44
-
45
- const r = new BinaryReader(w.getBuffer());
46
- expect(r.readU32Array()).toEqual([1, 2, 3]);
47
- expect(r.readStringArray()).toEqual(['a', 'bb', 'ccc']);
48
- });
49
-
50
- it('rejects out-of-range values', () => {
51
- const w = new BinaryWriter();
52
- expect(() => w.writeU8(256)).toThrow();
53
- expect(() => w.writeI8(128)).toThrow();
54
- });
55
-
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();
62
- });
63
-
64
- it('throws when reading past the end', () => {
65
- const r = new BinaryReader(new Uint8Array(2));
66
- expect(() => r.readU32()).toThrow();
67
- });
68
- });
69
-
70
- describe('FastMap', () => {
71
- it('supports bigint keys and basic ops', () => {
72
- const m = new FastMap<bigint, string>();
73
- m.set(1n, 'one').set(2n, 'two');
74
- expect(m.size).toBe(2);
75
- expect(m.get(1n)).toBe('one');
76
- expect(m.has(2n)).toBe(true);
77
- expect(m.delete(1n)).toBe(true);
78
- expect(m.has(1n)).toBe(false);
79
- expect([...m.entries()]).toEqual([[2n, 'two']]);
80
- });
81
- });
82
-
83
- describe('FastSet', () => {
84
- it('dedupes and preserves insertion order', () => {
85
- const s = new FastSet<bigint>();
86
- s.add(2n).add(1n).add(2n);
87
- expect(s.size).toBe(2);
88
- expect(s.has(1n)).toBe(true);
89
- expect([...s.values()]).toEqual([2n, 1n]);
90
- expect(s.delete(2n)).toBe(true);
91
- expect([...s.values()]).toEqual([1n]);
92
- });
93
- });
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { DataReader, DataWriter } from '../src/io/codec';
4
+ import { FastMap } from '../src/io/FastMap';
5
+ import { FastSet } from '../src/io/FastSet';
6
+
7
+ describe('DataWriter / DataReader', () => {
8
+ it('round-trips fixed-width integers', () => {
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);
12
+
13
+ const r = new DataReader(w.toBytes());
14
+ expect(r.readU8()).toBe(255);
15
+ expect(r.readU16()).toBe(65535);
16
+ expect(r.readU32()).toBe(4294967295);
17
+ expect(r.readU64()).toBe(18446744073709551615n);
18
+ expect(r.readI8()).toBe(-128);
19
+ expect(r.readI16()).toBe(-32768);
20
+ expect(r.readI32()).toBe(-2147483648);
21
+ expect(r.readI64()).toBe(-9223372036854775808n);
22
+ expect(r.ok).toBe(true);
23
+ expect(r.remaining()).toBe(0);
24
+ });
25
+
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 🛠');
31
+
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);
40
+ });
41
+
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));
47
+
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));
53
+ });
54
+
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);
79
+ });
80
+
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]);
85
+ });
86
+
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);
91
+ });
92
+ });
93
+
94
+ describe('FastMap', () => {
95
+ it('supports bigint keys and basic ops', () => {
96
+ const m = new FastMap<bigint, string>();
97
+ m.set(1n, 'one').set(2n, 'two');
98
+ expect(m.size).toBe(2);
99
+ expect(m.get(1n)).toBe('one');
100
+ expect(m.has(2n)).toBe(true);
101
+ expect(m.delete(1n)).toBe(true);
102
+ expect(m.has(1n)).toBe(false);
103
+ expect([...m.entries()]).toEqual([[2n, 'two']]);
104
+ });
105
+ });
106
+
107
+ describe('FastSet', () => {
108
+ it('dedupes and preserves insertion order', () => {
109
+ const s = new FastSet<bigint>();
110
+ s.add(2n).add(1n).add(2n);
111
+ expect(s.size).toBe(2);
112
+ expect(s.has(1n)).toBe(true);
113
+ expect([...s.values()]).toEqual([2n, 1n]);
114
+ expect(s.delete(2n)).toBe(true);
115
+ expect([...s.values()]).toEqual([1n]);
116
+ });
117
+ });
@@ -1,28 +1,28 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { matchActive } from '../src/client/navigation/NavLink';
4
-
5
- describe('matchActive', () => {
6
- it('matches exact paths', () => {
7
- expect(matchActive('/about', '/about', false)).toBe(true);
8
- expect(matchActive('/about', '/about/', false)).toBe(true);
9
- expect(matchActive('/about', '/contact', false)).toBe(false);
10
- });
11
-
12
- it('matches sub-paths when not exact (end=false)', () => {
13
- expect(matchActive('/blog', '/blog/42', false)).toBe(true);
14
- expect(matchActive('/blog', '/blog', false)).toBe(true);
15
- expect(matchActive('/blog', '/blogger', false)).toBe(false);
16
- });
17
-
18
- it('honors end for exact-only matching', () => {
19
- expect(matchActive('/blog', '/blog/42', true)).toBe(false);
20
- expect(matchActive('/blog', '/blog', true)).toBe(true);
21
- });
22
-
23
- it('treats "/" as active everywhere unless end', () => {
24
- expect(matchActive('/', '/anything/deep', false)).toBe(true);
25
- expect(matchActive('/', '/anything', true)).toBe(false);
26
- expect(matchActive('/', '/', true)).toBe(true);
27
- });
28
- });
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { matchActive } from '../src/client/navigation/NavLink';
4
+
5
+ describe('matchActive', () => {
6
+ it('matches exact paths', () => {
7
+ expect(matchActive('/about', '/about', false)).toBe(true);
8
+ expect(matchActive('/about', '/about/', false)).toBe(true);
9
+ expect(matchActive('/about', '/contact', false)).toBe(false);
10
+ });
11
+
12
+ it('matches sub-paths when not exact (end=false)', () => {
13
+ expect(matchActive('/blog', '/blog/42', false)).toBe(true);
14
+ expect(matchActive('/blog', '/blog', false)).toBe(true);
15
+ expect(matchActive('/blog', '/blogger', false)).toBe(false);
16
+ });
17
+
18
+ it('honors end for exact-only matching', () => {
19
+ expect(matchActive('/blog', '/blog/42', true)).toBe(false);
20
+ expect(matchActive('/blog', '/blog', true)).toBe(true);
21
+ });
22
+
23
+ it('treats "/" as active everywhere unless end', () => {
24
+ expect(matchActive('/', '/anything/deep', false)).toBe(true);
25
+ expect(matchActive('/', '/anything', true)).toBe(false);
26
+ expect(matchActive('/', '/', true)).toBe(true);
27
+ });
28
+ });
@@ -1,9 +1,9 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { FRAMEWORK_NAME } from '../src/shared/index';
4
-
5
- describe('toiljs scaffold', () => {
6
- it('exposes the framework name', () => {
7
- expect(FRAMEWORK_NAME).toBe('toiljs');
8
- });
9
- });
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { FRAMEWORK_NAME } from '../src/shared/index';
4
+
5
+ describe('toiljs scaffold', () => {
6
+ it('exposes the framework name', () => {
7
+ expect(FRAMEWORK_NAME).toBe('toiljs');
8
+ });
9
+ });
@@ -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
+ });
@@ -1,76 +1,76 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { matchRoute } from '../src/client/routing/match';
4
- import { filePathToRoute, interceptTarget } from '../src/compiler/routes';
5
-
6
- describe('filePathToRoute', () => {
7
- it('maps index, static, nested, and dynamic files to patterns', () => {
8
- expect(filePathToRoute('index.tsx')).toBe('/');
9
- expect(filePathToRoute('about.tsx')).toBe('/about');
10
- expect(filePathToRoute('blog/index.tsx')).toBe('/blog');
11
- expect(filePathToRoute('blog/[id].tsx')).toBe('/blog/:id');
12
- expect(filePathToRoute('docs/guide/intro.jsx')).toBe('/docs/guide/intro');
13
- expect(filePathToRoute('docs/[...slug].tsx')).toBe('/docs/*slug');
14
- });
15
-
16
- it('maps optional catch-all and strips route groups', () => {
17
- expect(filePathToRoute('docs/[[...slug]].tsx')).toBe('/docs/**slug');
18
- expect(filePathToRoute('[[...slug]].tsx')).toBe('/**slug');
19
- expect(filePathToRoute('(marketing)/about.tsx')).toBe('/about');
20
- expect(filePathToRoute('(shop)/index.tsx')).toBe('/');
21
- expect(filePathToRoute('(a)/(b)/deep.tsx')).toBe('/deep');
22
- });
23
-
24
- it('strips parallel-slot (@slot) segments from the URL', () => {
25
- expect(filePathToRoute('@modal/photo/[id].tsx')).toBe('/photo/:id');
26
- expect(filePathToRoute('@sidebar/index.tsx')).toBe('/');
27
- expect(filePathToRoute('dashboard/@chart/views.tsx')).toBe('/dashboard/views');
28
- });
29
- });
30
-
31
- describe('interceptTarget', () => {
32
- it('resolves (.)/(..)/(...) marker targets', () => {
33
- expect(interceptTarget('@modal/(.)photo/[id].tsx')).toBe('/photo/:id');
34
- expect(interceptTarget('feed/@modal/(..)photo/[id].tsx')).toBe('/photo/:id');
35
- expect(interceptTarget('a/b/@m/(...)login.tsx')).toBe('/login');
36
- });
37
-
38
- it('returns null for routes with no interception marker', () => {
39
- expect(interceptTarget('photo/[id].tsx')).toBeNull();
40
- expect(interceptTarget('@modal/settings.tsx')).toBeNull();
41
- });
42
- });
43
-
44
- describe('matchRoute', () => {
45
- it('matches static routes', () => {
46
- expect(matchRoute('/', '/')).toEqual({});
47
- expect(matchRoute('/about', '/about')).toEqual({});
48
- });
49
-
50
- it('rejects non-matches', () => {
51
- expect(matchRoute('/about', '/x')).toBeNull();
52
- expect(matchRoute('/blog/:id', '/blog')).toBeNull();
53
- expect(matchRoute('/', '/about')).toBeNull();
54
- });
55
-
56
- it('extracts dynamic params', () => {
57
- expect(matchRoute('/blog/:id', '/blog/42')).toEqual({ id: '42' });
58
- expect(matchRoute('/u/:user/p/:post', '/u/ann/p/7')).toEqual({ user: 'ann', post: '7' });
59
- expect(matchRoute('/blog/:id', '/blog/a%20b')).toEqual({ id: 'a b' });
60
- });
61
-
62
- it('captures the tail with catch-all routes', () => {
63
- expect(matchRoute('/docs/*slug', '/docs/a/b/c')).toEqual({ slug: 'a/b/c' });
64
- expect(matchRoute('/docs/*slug', '/docs/intro')).toEqual({ slug: 'intro' });
65
- expect(matchRoute('/files/*path', '/files/a%20b/c')).toEqual({ path: 'a b/c' });
66
- // catch-all needs at least one trailing segment
67
- expect(matchRoute('/docs/*slug', '/docs')).toBeNull();
68
- });
69
-
70
- it('matches optional catch-all with zero or more segments', () => {
71
- expect(matchRoute('/docs/**slug', '/docs')).toEqual({ slug: '' });
72
- expect(matchRoute('/docs/**slug', '/docs/a/b')).toEqual({ slug: 'a/b' });
73
- expect(matchRoute('/**slug', '/')).toEqual({ slug: '' });
74
- expect(matchRoute('/**slug', '/x/y')).toEqual({ slug: 'x/y' });
75
- });
76
- });
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { matchRoute } from '../src/client/routing/match';
4
+ import { filePathToRoute, interceptTarget } from '../src/compiler/routes';
5
+
6
+ describe('filePathToRoute', () => {
7
+ it('maps index, static, nested, and dynamic files to patterns', () => {
8
+ expect(filePathToRoute('index.tsx')).toBe('/');
9
+ expect(filePathToRoute('about.tsx')).toBe('/about');
10
+ expect(filePathToRoute('blog/index.tsx')).toBe('/blog');
11
+ expect(filePathToRoute('blog/[id].tsx')).toBe('/blog/:id');
12
+ expect(filePathToRoute('docs/guide/intro.jsx')).toBe('/docs/guide/intro');
13
+ expect(filePathToRoute('docs/[...slug].tsx')).toBe('/docs/*slug');
14
+ });
15
+
16
+ it('maps optional catch-all and strips route groups', () => {
17
+ expect(filePathToRoute('docs/[[...slug]].tsx')).toBe('/docs/**slug');
18
+ expect(filePathToRoute('[[...slug]].tsx')).toBe('/**slug');
19
+ expect(filePathToRoute('(marketing)/about.tsx')).toBe('/about');
20
+ expect(filePathToRoute('(shop)/index.tsx')).toBe('/');
21
+ expect(filePathToRoute('(a)/(b)/deep.tsx')).toBe('/deep');
22
+ });
23
+
24
+ it('strips parallel-slot (@slot) segments from the URL', () => {
25
+ expect(filePathToRoute('@modal/photo/[id].tsx')).toBe('/photo/:id');
26
+ expect(filePathToRoute('@sidebar/index.tsx')).toBe('/');
27
+ expect(filePathToRoute('dashboard/@chart/views.tsx')).toBe('/dashboard/views');
28
+ });
29
+ });
30
+
31
+ describe('interceptTarget', () => {
32
+ it('resolves (.)/(..)/(...) marker targets', () => {
33
+ expect(interceptTarget('@modal/(.)photo/[id].tsx')).toBe('/photo/:id');
34
+ expect(interceptTarget('feed/@modal/(..)photo/[id].tsx')).toBe('/photo/:id');
35
+ expect(interceptTarget('a/b/@m/(...)login.tsx')).toBe('/login');
36
+ });
37
+
38
+ it('returns null for routes with no interception marker', () => {
39
+ expect(interceptTarget('photo/[id].tsx')).toBeNull();
40
+ expect(interceptTarget('@modal/settings.tsx')).toBeNull();
41
+ });
42
+ });
43
+
44
+ describe('matchRoute', () => {
45
+ it('matches static routes', () => {
46
+ expect(matchRoute('/', '/')).toEqual({});
47
+ expect(matchRoute('/about', '/about')).toEqual({});
48
+ });
49
+
50
+ it('rejects non-matches', () => {
51
+ expect(matchRoute('/about', '/x')).toBeNull();
52
+ expect(matchRoute('/blog/:id', '/blog')).toBeNull();
53
+ expect(matchRoute('/', '/about')).toBeNull();
54
+ });
55
+
56
+ it('extracts dynamic params', () => {
57
+ expect(matchRoute('/blog/:id', '/blog/42')).toEqual({ id: '42' });
58
+ expect(matchRoute('/u/:user/p/:post', '/u/ann/p/7')).toEqual({ user: 'ann', post: '7' });
59
+ expect(matchRoute('/blog/:id', '/blog/a%20b')).toEqual({ id: 'a b' });
60
+ });
61
+
62
+ it('captures the tail with catch-all routes', () => {
63
+ expect(matchRoute('/docs/*slug', '/docs/a/b/c')).toEqual({ slug: 'a/b/c' });
64
+ expect(matchRoute('/docs/*slug', '/docs/intro')).toEqual({ slug: 'intro' });
65
+ expect(matchRoute('/files/*path', '/files/a%20b/c')).toEqual({ path: 'a b/c' });
66
+ // catch-all needs at least one trailing segment
67
+ expect(matchRoute('/docs/*slug', '/docs')).toBeNull();
68
+ });
69
+
70
+ it('matches optional catch-all with zero or more segments', () => {
71
+ expect(matchRoute('/docs/**slug', '/docs')).toEqual({ slug: '' });
72
+ expect(matchRoute('/docs/**slug', '/docs/a/b')).toEqual({ slug: 'a/b' });
73
+ expect(matchRoute('/**slug', '/')).toEqual({ slug: '' });
74
+ expect(matchRoute('/**slug', '/x/y')).toEqual({ slug: 'x/y' });
75
+ });
76
+ });
@@ -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
+ });