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.
Files changed (99) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +72 -14
  3. package/build/backend/.tsbuildinfo +1 -1
  4. package/build/cli/.tsbuildinfo +1 -1
  5. package/build/cli/index.js +293 -142
  6. package/build/client/.tsbuildinfo +1 -1
  7. package/build/client/auth.js +1 -1
  8. package/build/client/components/Image.d.ts +1 -1
  9. package/build/client/dev/devtools.js +4 -2
  10. package/build/client/index.d.ts +2 -2
  11. package/build/client/index.js +2 -2
  12. package/build/client/routing/Router.js +1 -1
  13. package/build/client/routing/hooks.js +2 -2
  14. package/build/client/routing/mount.js +1 -1
  15. package/build/compiler/.tsbuildinfo +1 -1
  16. package/build/compiler/docs.js +1 -1
  17. package/build/compiler/seo.js +1 -3
  18. package/build/compiler/template-build.d.ts +5 -2
  19. package/build/compiler/template-build.js +19 -7
  20. package/build/devserver/.tsbuildinfo +1 -1
  21. package/build/devserver/cache.js +0 -0
  22. package/build/devserver/crypto.js +45 -17
  23. package/build/devserver/database.d.ts +1 -1
  24. package/build/devserver/database.js +84 -0
  25. package/build/devserver/email/caps.js +0 -0
  26. package/build/devserver/email/config.js +7 -2
  27. package/build/devserver/email/validate.js +1 -4
  28. package/build/devserver/host.js +18 -1
  29. package/build/devserver/index.d.ts +1 -1
  30. package/build/devserver/index.js +3 -2
  31. package/build/devserver/module.js +51 -12
  32. package/build/devserver/proxy.js +2 -1
  33. package/build/io/.tsbuildinfo +1 -1
  34. package/build/io/codec.d.ts +5 -5
  35. package/build/io/codec.js +193 -77
  36. package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
  37. package/examples/basic/client/public/images/logo.svg +37 -34
  38. package/examples/basic/client/public/index.html +14 -14
  39. package/examples/basic/client/routes/auth.tsx +18 -10
  40. package/examples/basic/client/routes/cookies.tsx +15 -24
  41. package/examples/basic/client/routes/crypto.tsx +4 -5
  42. package/examples/basic/client/routes/features/template/template.tsx +1 -1
  43. package/examples/basic/client/routes/hello.tsx +1 -1
  44. package/examples/basic/client/routes/pq.tsx +14 -14
  45. package/examples/basic/client/routes/rest.tsx +1 -3
  46. package/examples/basic/client/styles/main.css +25 -22
  47. package/examples/basic/client/toil.tsx +1 -1
  48. package/examples/basic/server/README.md +8 -8
  49. package/examples/basic/server/core/AppHandler.ts +4 -7
  50. package/examples/basic/server/routes/Auth.ts +13 -10
  51. package/examples/basic/server/routes/EnvDemo.ts +9 -3
  52. package/examples/basic/server/routes/Guestbook.ts +2 -4
  53. package/package.json +26 -26
  54. package/src/backend/index.ts +4 -2
  55. package/src/cli/create.ts +19 -4
  56. package/src/cli/diagnostics.ts +48 -0
  57. package/src/cli/doctor.ts +155 -9
  58. package/src/cli/notify.ts +1 -6
  59. package/src/cli/ui.ts +3 -3
  60. package/src/cli/version-check.ts +5 -1
  61. package/src/client/auth.ts +33 -10
  62. package/src/client/components/Form.tsx +2 -2
  63. package/src/client/components/Image.tsx +1 -1
  64. package/src/client/components/Script.tsx +1 -1
  65. package/src/client/components/Slot.tsx +1 -1
  66. package/src/client/dev/devtools.tsx +126 -55
  67. package/src/client/dev/error-overlay.tsx +7 -1
  68. package/src/client/head/metadata.ts +1 -1
  69. package/src/client/index.ts +13 -2
  70. package/src/client/routing/Router.tsx +2 -2
  71. package/src/client/routing/error-boundary.tsx +1 -1
  72. package/src/client/routing/hooks.ts +5 -3
  73. package/src/client/routing/loader.ts +2 -2
  74. package/src/client/routing/mount.tsx +5 -6
  75. package/src/compiler/docs.ts +1 -1
  76. package/src/compiler/email-preview.ts +1 -1
  77. package/src/compiler/generate.ts +1 -1
  78. package/src/compiler/seo.ts +1 -3
  79. package/src/compiler/ssg.ts +10 -4
  80. package/src/compiler/template-build.ts +43 -11
  81. package/src/compiler/template.ts +1 -4
  82. package/src/compiler/vite.ts +1 -1
  83. package/src/devserver/cache.ts +0 -0
  84. package/src/devserver/crypto.ts +140 -51
  85. package/src/devserver/database.ts +168 -9
  86. package/src/devserver/dotenv.ts +10 -2
  87. package/src/devserver/email/caps.ts +0 -0
  88. package/src/devserver/email/config.ts +8 -2
  89. package/src/devserver/email/index.ts +3 -3
  90. package/src/devserver/email/validate.ts +1 -4
  91. package/src/devserver/envelope.ts +3 -3
  92. package/src/devserver/host.ts +46 -6
  93. package/src/devserver/index.ts +15 -6
  94. package/src/devserver/module.ts +56 -14
  95. package/src/devserver/proxy.ts +5 -7
  96. package/src/io/codec.ts +226 -83
  97. package/test/devserver-database.test.ts +60 -0
  98. package/test/devserver-secrets.test.ts +59 -0
  99. package/test/doctor.test.ts +30 -0
@@ -1,37 +1,40 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500">
3
- <!-- Generator: Adobe Illustrator 30.4.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 226) -->
4
- <defs>
5
- <style>
6
- .st0 {
7
- fill: #fff;
8
- }
2
+ <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1"
3
+ viewBox="0 0 500 500">
4
+ <!-- Generator: Adobe Illustrator 30.4.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 226) -->
5
+ <defs>
6
+ <style>
7
+ .st0 {
8
+ fill: #fff;
9
+ }
9
10
 
10
- .st1 {
11
- fill: url(#linear-gradient1);
12
- }
11
+ .st1 {
12
+ fill: url(#linear-gradient1);
13
+ }
13
14
 
14
- .st2 {
15
- fill: url(#linear-gradient);
16
- }
17
- </style>
18
- <linearGradient id="linear-gradient" x1="43.27" y1="43.27" x2="467.12" y2="467.12" gradientUnits="userSpaceOnUse">
19
- <stop offset="0" stop-color="#6990ff"/>
20
- <stop offset=".03" stop-color="#6479f9"/>
21
- <stop offset=".08" stop-color="#5d57f0"/>
22
- <stop offset=".12" stop-color="#583de9"/>
23
- <stop offset=".17" stop-color="#542ae3"/>
24
- <stop offset=".23" stop-color="#521ee0"/>
25
- <stop offset=".28" stop-color="#521be0"/>
26
- <stop offset=".66" stop-color="#6900f4"/>
27
- <stop offset="1" stop-color="#7f00f6"/>
28
- </linearGradient>
29
- <linearGradient id="linear-gradient1" x1="149.99" y1="355.49" x2="149.99" y2="0" gradientUnits="userSpaceOnUse">
30
- <stop offset=".15" stop-color="#6990ff" stop-opacity=".6"/>
31
- <stop offset=".55" stop-color="#531ae1"/>
32
- </linearGradient>
33
- </defs>
34
- <rect class="st2" width="500" height="500" rx="130" ry="130"/>
35
- <path class="st1" d="M299.98,0L0,355.49v-225.49C0,58.2,58.2,0,130,0h169.98Z"/>
36
- <path class="st0" d="M106.17,111.11h285.24c9.9,0,16.7,9.96,13.09,19.18l-17.98,45.96c-2.11,5.39-7.31,8.94-13.09,8.94h-74.65c-7.76,0-14.06,6.29-14.06,14.06v214.94c0,7.76-6.29,14.06-14.06,14.06h-45.96c-7.76,0-14.06-6.29-14.06-14.06v-217.25c0-7.76-6.29-14.06-14.06-14.06h-73.66c-5.82,0-11.04-3.59-13.12-9.02l-16.76-43.64c-3.54-9.21,3.26-19.1,13.12-19.1Z"/>
37
- </svg>
15
+ .st2 {
16
+ fill: url(#linear-gradient);
17
+ }
18
+ </style>
19
+ <linearGradient id="linear-gradient" x1="43.27" y1="43.27" x2="467.12" y2="467.12"
20
+ gradientUnits="userSpaceOnUse">
21
+ <stop offset="0" stop-color="#6990ff"/>
22
+ <stop offset=".03" stop-color="#6479f9"/>
23
+ <stop offset=".08" stop-color="#5d57f0"/>
24
+ <stop offset=".12" stop-color="#583de9"/>
25
+ <stop offset=".17" stop-color="#542ae3"/>
26
+ <stop offset=".23" stop-color="#521ee0"/>
27
+ <stop offset=".28" stop-color="#521be0"/>
28
+ <stop offset=".66" stop-color="#6900f4"/>
29
+ <stop offset="1" stop-color="#7f00f6"/>
30
+ </linearGradient>
31
+ <linearGradient id="linear-gradient1" x1="149.99" y1="355.49" x2="149.99" y2="0" gradientUnits="userSpaceOnUse">
32
+ <stop offset=".15" stop-color="#6990ff" stop-opacity=".6"/>
33
+ <stop offset=".55" stop-color="#531ae1"/>
34
+ </linearGradient>
35
+ </defs>
36
+ <rect class="st2" width="500" height="500" rx="130" ry="130"/>
37
+ <path class="st1" d="M299.98,0L0,355.49v-225.49C0,58.2,58.2,0,130,0h169.98Z"/>
38
+ <path class="st0"
39
+ d="M106.17,111.11h285.24c9.9,0,16.7,9.96,13.09,19.18l-17.98,45.96c-2.11,5.39-7.31,8.94-13.09,8.94h-74.65c-7.76,0-14.06,6.29-14.06,14.06v214.94c0,7.76-6.29,14.06-14.06,14.06h-45.96c-7.76,0-14.06-6.29-14.06-14.06v-217.25c0-7.76-6.29-14.06-14.06-14.06h-73.66c-5.82,0-11.04-3.59-13.12-9.02l-16.76-43.64c-3.54-9.21,3.26-19.1,13.12-19.1Z"/>
40
+ </svg>
@@ -1,18 +1,18 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta name="theme-color" content="#080D11" />
7
- <link rel="icon" type="image/x-icon" href="/favicon.ico" />
8
- <title>ToilJS</title>
9
- <link rel="preconnect" href="https://fonts.googleapis.com" />
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
- <link
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <meta content="width=device-width, initial-scale=1" name="viewport"/>
6
+ <meta content="#080D11" name="theme-color"/>
7
+ <link href="/favicon.ico" rel="icon" type="image/x-icon"/>
8
+ <title>ToilJS</title>
9
+ <link href="https://fonts.googleapis.com" rel="preconnect"/>
10
+ <link crossorigin href="https://fonts.gstatic.com" rel="preconnect"/>
11
+ <link
12
12
  href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900&display=swap"
13
- rel="stylesheet" />
14
- </head>
15
- <body>
16
- <div id="root"></div>
17
- </body>
13
+ rel="stylesheet"/>
14
+ </head>
15
+ <body>
16
+ <div id="root"></div>
17
+ </body>
18
18
  </html>
@@ -56,7 +56,7 @@ function readCompanion(): Account | null {
56
56
  export const metadata: Toil.Metadata = {
57
57
  title: 'Auth',
58
58
  description:
59
- 'Sessions and the @user / @auth surface: a dev login mints a signed session cookie, the guarded /session/me returns the verified user, and getUser() reads the readable companion.',
59
+ 'Sessions and the @user / @auth surface: a dev login mints a signed session cookie, the guarded /session/me returns the verified user, and getUser() reads the readable companion.'
60
60
  };
61
61
 
62
62
  /** Encode a bare string the way the server reads it (`DataReader.readString`). */
@@ -84,7 +84,11 @@ export default function Auth(): React.JSX.Element {
84
84
  const devLogin = useCallback(async () => {
85
85
  setBusy(true);
86
86
  try {
87
- const res = await fetch('/session/dev-login', { method: 'POST', credentials: 'same-origin', body: encodeString(username) as BodyInit });
87
+ const res = await fetch('/session/dev-login', {
88
+ method: 'POST',
89
+ credentials: 'same-origin',
90
+ body: encodeString(username) as BodyInit
91
+ });
88
92
  setLog(`POST /session/dev-login -> ${res.status} ${(await res.text()).trim()}`);
89
93
  refreshCompanion();
90
94
  setVerified(null);
@@ -127,10 +131,10 @@ export default function Auth(): React.JSX.Element {
127
131
  <main style={{ maxWidth: 640, margin: '0 auto', padding: '2rem 1rem', lineHeight: 1.5 }}>
128
132
  <h1>Auth and sessions</h1>
129
133
  <p>
130
- A dev login mints an HMAC-signed <code>__Host-toil_sess</code> session cookie plus a
131
- readable <code>__Secure-toil_user</code> companion. The guarded <code>/session/me</code>{' '}
132
- route ( <code>@auth</code> ) re-verifies the signed session; <code>getUser()</code> reads
133
- only the companion (display-only, untrusted). Needs the server running.
134
+ A dev login mints an HMAC-signed <code>__Host-toil_sess</code> session cookie plus a readable{' '}
135
+ <code>__Secure-toil_user</code> companion. The guarded <code>/session/me</code> route ({' '}
136
+ <code>@auth</code> ) re-verifies the signed session; <code>getUser()</code> reads only the companion
137
+ (display-only, untrusted). Needs the server running.
134
138
  </p>
135
139
 
136
140
  <section style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
@@ -162,9 +166,13 @@ export default function Auth(): React.JSX.Element {
162
166
  {companion ? (
163
167
  <pre>
164
168
  {JSON.stringify(
165
- { username: companion.username, admin: companion.admin, score: String(companion.score) },
169
+ {
170
+ username: companion.username,
171
+ admin: companion.admin,
172
+ score: String(companion.score)
173
+ },
166
174
  null,
167
- 2,
175
+ 2
168
176
  )}
169
177
  </pre>
170
178
  ) : (
@@ -194,8 +202,8 @@ export default function Auth(): React.JSX.Element {
194
202
  ) : null}
195
203
 
196
204
  <p style={{ marginTop: '1.5rem', fontSize: '0.85em', opacity: 0.7 }}>
197
- The full post-quantum register/login (ML-DSA-44, password-derived) needs an account
198
- store and is stubbed in <code>server/routes/Auth.ts</code>. See <code>docs/auth.md</code>.
205
+ The full post-quantum register/login (ML-DSA-44, password-derived) needs an account store and is stubbed
206
+ in <code>server/routes/Auth.ts</code>. See <code>docs/auth.md</code>.
199
207
  </p>
200
208
  </main>
201
209
  );
@@ -3,14 +3,14 @@
3
3
  // surface plus HMAC signing and AES-256-GCM encryption, running in the server wasm.
4
4
  // These controls call the `/api/cookies/*` routes in `server/core/AppHandler.ts`.
5
5
  // Needs the server running to respond.
6
- import { useState, type CSSProperties } from 'react';
6
+ import { type CSSProperties, useState } from 'react';
7
7
 
8
8
  import { useBrowserValue } from '../lib/useBrowserValue';
9
9
 
10
10
  export const metadata: Toil.Metadata = {
11
11
  title: 'Cookies',
12
12
  description:
13
- 'Server-side cookies as a global: the Cookie builder, parsing, HMAC signing, and AES-256-GCM encryption, running in the server wasm.',
13
+ 'Server-side cookies as a global: the Cookie builder, parsing, HMAC signing, and AES-256-GCM encryption, running in the server wasm.'
14
14
  };
15
15
 
16
16
  interface SetResp {
@@ -39,7 +39,7 @@ const card: CSSProperties = {
39
39
  borderRadius: 8,
40
40
  padding: '12px 16px',
41
41
  margin: '12px 0',
42
- background: '#0c1218',
42
+ background: '#0c1218'
43
43
  };
44
44
  const label: CSSProperties = { opacity: 0.7, fontSize: '0.8rem', marginTop: 6 };
45
45
 
@@ -94,22 +94,18 @@ export default function CookiesDemo() {
94
94
  readJs();
95
95
  });
96
96
  const doSeal = (): Promise<void> =>
97
- guard(async () =>
98
- setSeal(await getJSON<SealResp>('/api/cookies/seal?v=' + encodeURIComponent(sealInput))),
99
- );
97
+ guard(async () => setSeal(await getJSON<SealResp>('/api/cookies/seal?v=' + encodeURIComponent(sealInput))));
100
98
 
101
99
  return (
102
100
  <main style={{ maxWidth: 760 }}>
103
101
  <h1>Cookies</h1>
104
102
  <p>
105
- <code>Cookie</code>, <code>Cookies</code>, and <code>SecureCookies</code> are globals in
106
- the server (no import), exactly like <code>crypto</code>: the full RFC 6265bis surface
107
- plus HMAC signing and AES-256-GCM encryption, running in the server wasm. See{' '}
108
- <code>server/core/AppHandler.ts</code>. Needs the server running (<code>toiljs dev</code>).
103
+ <code>Cookie</code>, <code>Cookies</code>, and <code>SecureCookies</code> are globals in the server (no
104
+ import), exactly like <code>crypto</code>: the full RFC 6265bis surface plus HMAC signing and
105
+ AES-256-GCM encryption, running in the server wasm. See <code>server/core/AppHandler.ts</code>. Needs
106
+ the server running (<code>toiljs dev</code>).
109
107
  </p>
110
-
111
108
  {err ? <p style={{ color: '#ff6b6b', ...mono }}>{err}</p> : null}
112
-
113
109
  <h2>1. Everything you can do</h2>
114
110
  <p>Every attribute and cookie type, with the exact `Set-Cookie` string it serializes to.</p>
115
111
  <button onClick={showGallery}>Show the gallery</button>
@@ -123,12 +119,11 @@ export default function CookiesDemo() {
123
119
  ))}
124
120
  </div>
125
121
  ) : null}
126
-
127
122
  <h2>2. Set cookies</h2>
128
123
  <p>
129
124
  Stores three real cookies: a plain <code>visits</code> counter, an HMAC-signed{' '}
130
- <code>__Host-session</code>, and an AES-GCM-encrypted <code>secret</code>. The last two
131
- are <code>HttpOnly</code>, so JavaScript cannot read them, only the server can.
125
+ <code>__Host-session</code>, and an AES-GCM-encrypted <code>secret</code>. The last two are{' '}
126
+ <code>HttpOnly</code>, so JavaScript cannot read them, only the server can.
132
127
  </p>
133
128
  <button onClick={doSet}>Set cookies</button>
134
129
  {setResp ? (
@@ -141,12 +136,11 @@ export default function CookiesDemo() {
141
136
  ))}
142
137
  </div>
143
138
  ) : null}
144
-
145
139
  <h2>3. What JS sees vs what the server sees</h2>
146
140
  <p>
147
- <code>document.cookie</code> only exposes non-<code>HttpOnly</code> cookies, so the
148
- signed session and encrypted secret are hidden from it. The server parses all of them
149
- and verifies/decrypts the protected ones.
141
+ <code>document.cookie</code> only exposes non-<code>HttpOnly</code> cookies, so the signed session and
142
+ encrypted secret are hidden from it. The server parses all of them and verifies/decrypts the protected
143
+ ones.
150
144
  </p>
151
145
  <button onClick={readJs}>Read document.cookie</button>{' '}
152
146
  <button onClick={doInspect}>Ask the server (/inspect)</button>
@@ -163,7 +157,6 @@ export default function CookiesDemo() {
163
157
  <div style={mono}>secret (AES-GCM-decrypted): {inspect.secret ?? 'null (missing or tampered)'}</div>
164
158
  </div>
165
159
  ) : null}
166
-
167
160
  <h2>4. Clear</h2>
168
161
  <button onClick={doClear}>Clear the demo cookies</button>
169
162
  {cleared ? (
@@ -175,12 +168,11 @@ export default function CookiesDemo() {
175
168
  ))}
176
169
  </div>
177
170
  ) : null}
178
-
179
171
  <h2>5. Sign &amp; encrypt a value</h2>
180
172
  <p>
181
173
  <code>SecureCookies.signed(key)</code> (HMAC-SHA256, readable but tamper-proof) and{' '}
182
- <code>SecureCookies.encrypted(key)</code> (AES-256-GCM, confidential). Both bind the
183
- value to the cookie name, and a tampered signature fails to verify.
174
+ <code>SecureCookies.encrypted(key)</code> (AES-256-GCM, confidential). Both bind the value to the cookie
175
+ name, and a tampered signature fails to verify.
184
176
  </p>
185
177
  <input
186
178
  value={sealInput}
@@ -198,7 +190,6 @@ export default function CookiesDemo() {
198
190
  <div style={mono}>tampered signature verifies? {String(seal.tamperVerifies)}</div>
199
191
  </div>
200
192
  ) : null}
201
-
202
193
  <p style={{ marginTop: 24 }}>
203
194
  <Toil.Link href="/features">Back to features</Toil.Link>
204
195
  </p>
@@ -37,11 +37,10 @@ export default function CryptoDemo() {
37
37
  <main>
38
38
  <h1>Web Crypto</h1>
39
39
  <p>
40
- <code>crypto</code> is a global in the server (no import), synchronous, the same
41
- SubtleCrypto-style API as the browser, running in the server wasm via metered host
42
- functions. These buttons call the server&apos;s <code>/api/hash</code> and{' '}
43
- <code>/api/uuid</code> routes (see <code>server/HelloHandler.ts</code>). Needs the
44
- server running to respond.
40
+ <code>crypto</code> is a global in the server (no import), synchronous, the same SubtleCrypto-style API
41
+ as the browser, running in the server wasm via metered host functions. These buttons call the
42
+ server&apos;s <code>/api/hash</code> and <code>/api/uuid</code> routes (see{' '}
43
+ <code>server/HelloHandler.ts</code>). Needs the server running to respond.
45
44
  </p>
46
45
  <button onClick={onHash}>SHA-256</button> <button onClick={onUuid}>random UUID</button>
47
46
  <ul style={{ marginTop: 16, listStyle: 'none', padding: 0 }}>
@@ -1,4 +1,4 @@
1
- import { useState, type ReactNode } from 'react';
1
+ import { type ReactNode, useState } from 'react';
2
2
 
3
3
  // A template wraps a segment like a layout, but RE-MOUNTS on every navigation within it (a layout
4
4
  // persists). This counter increments each time the template mounts, so navigating between the two
@@ -19,7 +19,7 @@ interface HelloData {
19
19
 
20
20
  export const loader = ({ params }: { params: Record<string, string> }): HelloData => ({
21
21
  name: params.name ?? 'world',
22
- items: ['alpha', 'beta', 'gamma'],
22
+ items: ['alpha', 'beta', 'gamma']
23
23
  });
24
24
 
25
25
  export default function Hello(): React.JSX.Element {
@@ -49,7 +49,7 @@ interface VerifiedUser {
49
49
  export const metadata: Toil.Metadata = {
50
50
  title: 'Post-quantum auth',
51
51
  description:
52
- 'A server-keyed-salt OPRF + ML-DSA-44 (FIPS 204) auth + ML-KEM-768 (FIPS 203) mutual auth. The password never leaves the browser.',
52
+ 'A server-keyed-salt OPRF + ML-DSA-44 (FIPS 204) auth + ML-KEM-768 (FIPS 203) mutual auth. The password never leaves the browser.'
53
53
  };
54
54
 
55
55
  type Note = { kind: 'ok' | 'err'; text: string } | null;
@@ -73,7 +73,7 @@ export default function Pq(): React.JSX.Element {
73
73
  await Auth.register(username, password);
74
74
  setNote({
75
75
  kind: 'ok',
76
- text: 'registered: the server stored only your public key and a proof-of-possession. Now log in to run the ML-KEM-768 mutual-auth step.',
76
+ text: 'registered: the server stored only your public key and a proof-of-possession. Now log in to run the ML-KEM-768 mutual-auth step.'
77
77
  });
78
78
  } catch (e) {
79
79
  setNote({ kind: 'err', text: e instanceof Error ? e.message : String(e) });
@@ -91,7 +91,7 @@ export default function Pq(): React.JSX.Element {
91
91
  await Auth.login(username, password);
92
92
  setNote({
93
93
  kind: 'ok',
94
- text: 'logged in: ML-KEM-768 mutual auth verified (the server proved it holds the KEM secret key).',
94
+ text: 'logged in: ML-KEM-768 mutual auth verified (the server proved it holds the KEM secret key).'
95
95
  });
96
96
  refreshCompanion();
97
97
  } catch (e) {
@@ -151,11 +151,7 @@ export default function Pq(): React.JSX.Element {
151
151
  </label>
152
152
  <label>
153
153
  Password
154
- <input
155
- value={password}
156
- onChange={(e) => setPassword(e.target.value)}
157
- style={{ width: '100%' }}
158
- />
154
+ <input value={password} onChange={(e) => setPassword(e.target.value)} style={{ width: '100%' }} />
159
155
  </label>
160
156
  <div style={{ display: 'flex', gap: 8 }}>
161
157
  <button onClick={doRegister} disabled={busy}>
@@ -167,8 +163,8 @@ export default function Pq(): React.JSX.Element {
167
163
  </div>
168
164
  <p style={{ fontSize: '0.8rem', opacity: 0.7, margin: 0 }}>
169
165
  Demo: pre-filled <code>ada</code> / <code>correct horse battery staple</code>. Register once, then
170
- log in. A wrong password fails at login (the derived key won&apos;t match the stored one). Storage is
171
- the DEV-only in-process KV (<code>src/devserver/kv.ts</code>); a real deployment wires an atomic
166
+ log in. A wrong password fails at login (the derived key won&apos;t match the stored one). Storage
167
+ is the DEV-only in-process KV (<code>src/devserver/kv.ts</code>); a real deployment wires an atomic
172
168
  store, <code>server/routes/Auth.ts</code>.
173
169
  </p>
174
170
  </div>
@@ -188,9 +184,13 @@ export default function Pq(): React.JSX.Element {
188
184
  <div style={{ fontSize: '0.8em', opacity: 0.7 }}>readable companion, untrusted</div>
189
185
  <pre>
190
186
  {JSON.stringify(
191
- { username: companion.username, admin: companion.admin, score: String(companion.score) },
187
+ {
188
+ username: companion.username,
189
+ admin: companion.admin,
190
+ score: String(companion.score)
191
+ },
192
192
  null,
193
- 2,
193
+ 2
194
194
  )}
195
195
  </pre>
196
196
  </div>
@@ -220,8 +220,8 @@ export default function Pq(): React.JSX.Element {
220
220
  )}
221
221
 
222
222
  <p style={{ marginTop: 24, opacity: 0.7, fontSize: '0.9rem' }}>
223
- This is the full augmented-PAKE chain: OPRF keyed salt + ML-DSA client auth + ML-KEM mutual auth, with an
224
- atomic single-use challenge consume. The OPRF layer is classical ristretto255 (the one non-PQ piece);
223
+ This is the full augmented-PAKE chain: OPRF keyed salt + ML-DSA client auth + ML-KEM mutual auth, with
224
+ an atomic single-use challenge consume. The OPRF layer is classical ristretto255 (the one non-PQ piece);
225
225
  auth and key agreement are post-quantum. Plain sessions are on the{' '}
226
226
  <Toil.Link href="/auth">Auth</Toil.Link> page.
227
227
  </p>
@@ -17,7 +17,7 @@ export default function RestDemo() {
17
17
  // page, and the total is still there.
18
18
  const [book, setBook] = useState<{ total: bigint; entries: { author: string; message: string }[] }>({
19
19
  total: 0n,
20
- entries: [],
20
+ entries: []
21
21
  });
22
22
 
23
23
  // POST /players -> typed Promise<Player>, body is a @data class. The server returns the
@@ -116,7 +116,6 @@ export default function RestDemo() {
116
116
  <li key={i}>{line}</li>
117
117
  ))}
118
118
  </ul>
119
-
120
119
  <h2>Guestbook (persisted via ToilDB)</h2>
121
120
  <p>
122
121
  The same <code>Server.REST.*</code> client, but the route stores each signature in a ToilDB{' '}
@@ -133,7 +132,6 @@ export default function RestDemo() {
133
132
  </li>
134
133
  ))}
135
134
  </ul>
136
-
137
135
  <Toil.Link href="/">Back home</Toil.Link>
138
136
  </main>
139
137
  );
@@ -23,10 +23,9 @@ body {
23
23
  min-height: 100vh;
24
24
  background: var(--bg);
25
25
  color: var(--text);
26
- font-family:
27
- system-ui,
28
- -apple-system,
29
- sans-serif;
26
+ font-family: system-ui,
27
+ -apple-system,
28
+ sans-serif;
30
29
  line-height: 1.6;
31
30
  }
32
31
 
@@ -34,6 +33,7 @@ a {
34
33
  color: var(--accent);
35
34
  text-decoration: none;
36
35
  }
36
+
37
37
  a:hover {
38
38
  color: var(--accent3);
39
39
  }
@@ -99,6 +99,7 @@ a:hover {
99
99
  .nav-links a {
100
100
  color: var(--muted);
101
101
  }
102
+
102
103
  .nav-links a:hover {
103
104
  color: var(--text);
104
105
  }
@@ -117,9 +118,8 @@ a:hover {
117
118
  border-radius: 6px;
118
119
  font-size: 0.9rem;
119
120
  color: var(--muted) !important;
120
- transition:
121
- color 150ms,
122
- background 150ms;
121
+ transition: color 150ms,
122
+ background 150ms;
123
123
  }
124
124
 
125
125
  .nav-center-link:hover {
@@ -182,10 +182,9 @@ a:hover {
182
182
  transform: scale(1.1);
183
183
  filter: blur(18px) saturate(1.4);
184
184
  opacity: 0.65;
185
- transition:
186
- opacity 300ms,
187
- filter 300ms,
188
- transform 300ms;
185
+ transition: opacity 300ms,
186
+ filter 300ms,
187
+ transform 300ms;
189
188
  }
190
189
 
191
190
  .hero-logo:hover .hero-logo-glow {
@@ -248,9 +247,8 @@ a:hover {
248
247
  border-radius: 999px;
249
248
  font-size: 0.88rem;
250
249
  color: var(--muted);
251
- transition:
252
- border-color 200ms,
253
- color 200ms;
250
+ transition: border-color 200ms,
251
+ color 200ms;
254
252
  }
255
253
 
256
254
  .feature-badge svg {
@@ -289,10 +287,9 @@ a:hover {
289
287
  font-weight: 600;
290
288
  font-family: inherit;
291
289
  cursor: pointer;
292
- transition:
293
- filter 200ms,
294
- background 200ms,
295
- border-color 200ms;
290
+ transition: filter 200ms,
291
+ background 200ms,
292
+ border-color 200ms;
296
293
  text-decoration: none !important;
297
294
  }
298
295
 
@@ -422,10 +419,9 @@ code {
422
419
  display: flex;
423
420
  flex-direction: column;
424
421
  gap: 0.6rem;
425
- transition:
426
- border-color 200ms,
427
- transform 200ms,
428
- box-shadow 200ms;
422
+ transition: border-color 200ms,
423
+ transform 200ms,
424
+ box-shadow 200ms;
429
425
  }
430
426
 
431
427
  .gs-card {
@@ -436,6 +432,7 @@ code {
436
432
  .gs-card--accent1 {
437
433
  border-top: 2px solid var(--accent);
438
434
  }
435
+
439
436
  .gs-card--accent1:hover {
440
437
  box-shadow: 0 8px 32px rgba(37, 99, 255, 0.15);
441
438
  }
@@ -443,6 +440,7 @@ code {
443
440
  .gs-card--accent2 {
444
441
  border-top: 2px solid var(--accent2);
445
442
  }
443
+
446
444
  .gs-card--accent2:hover {
447
445
  box-shadow: 0 8px 32px rgba(124, 58, 237, 0.15);
448
446
  }
@@ -450,6 +448,7 @@ code {
450
448
  .gs-card--accent3 {
451
449
  border-top: 2px solid var(--accent3);
452
450
  }
451
+
453
452
  .gs-card--accent3:hover {
454
453
  box-shadow: 0 8px 32px rgba(34, 227, 171, 0.12);
455
454
  }
@@ -457,6 +456,7 @@ code {
457
456
  .gs-card--accent4 {
458
457
  border-top: 2px solid #f59e0b;
459
458
  }
459
+
460
460
  .gs-card--accent4:hover {
461
461
  box-shadow: 0 8px 32px rgba(245, 158, 11, 0.12);
462
462
  }
@@ -464,6 +464,7 @@ code {
464
464
  .gs-card--flat {
465
465
  border-top: 2px solid var(--accent);
466
466
  }
467
+
467
468
  .gs-card--flat:hover {
468
469
  border-color: var(--accent);
469
470
  box-shadow: 0 8px 32px rgba(37, 99, 255, 0.12);
@@ -485,10 +486,12 @@ code {
485
486
  background: rgba(124, 58, 237, 0.1);
486
487
  color: var(--accent2);
487
488
  }
489
+
488
490
  .gs-card--accent3 .gs-card-icon {
489
491
  background: rgba(34, 227, 171, 0.1);
490
492
  color: var(--accent3);
491
493
  }
494
+
492
495
  .gs-card--accent4 .gs-card-icon {
493
496
  background: rgba(245, 158, 11, 0.1);
494
497
  color: #f59e0b;
@@ -1,4 +1,4 @@
1
- import { routes, layout, notFound, globalError, slots } from 'toiljs/routes';
1
+ import { globalError, layout, notFound, routes, slots } from 'toiljs/routes';
2
2
 
3
3
  import './styles/main.css';
4
4
 
@@ -2,14 +2,14 @@
2
2
 
3
3
  Your ToilScript backend, compiled to a single WebAssembly module. One folder per concern:
4
4
 
5
- | Folder | What lives here |
6
- | --- | --- |
7
- | `main.ts` | The entry point: wires the handler and imports the surface modules. |
8
- | `core/` | The request handler and shared app logic (state, helpers). |
9
- | `models/` | `@data` classes, the typed wire model shared by HTTP and RPC. One type per file. |
10
- | `routes/` | `@rest` controllers (HTTP). One controller per file, named after its class. |
11
- | `services/` | `@service` classes and free `@remote` functions (typed RPC). |
12
- | `scheduled/` | Reserved for scheduled tasks (see its README). |
5
+ | Folder | What lives here |
6
+ |--------------|----------------------------------------------------------------------------------|
7
+ | `main.ts` | The entry point: wires the handler and imports the surface modules. |
8
+ | `core/` | The request handler and shared app logic (state, helpers). |
9
+ | `models/` | `@data` classes, the typed wire model shared by HTTP and RPC. One type per file. |
10
+ | `routes/` | `@rest` controllers (HTTP). One controller per file, named after its class. |
11
+ | `services/` | `@service` classes and free `@remote` functions (typed RPC). |
12
+ | `scheduled/` | Reserved for scheduled tasks (see its README). |
13
13
 
14
14
  Conventions:
15
15
 
@@ -104,7 +104,7 @@ export class AppHandler extends ToilHandler {
104
104
  .partitioned()
105
105
  .priority('Medium')
106
106
  .extension('CustomFlag')
107
- .serialize(),
107
+ .serialize()
108
108
  );
109
109
 
110
110
  let json = '{';
@@ -127,10 +127,10 @@ export class AppHandler extends ToilHandler {
127
127
 
128
128
  const visits = Cookie.create('visits', next).path('/').sameSite(SameSite.Lax).maxAge(86400);
129
129
  const session = SecureCookies.signed(this.demoKey()).seal(
130
- Cookie.create('session', 'user-42').httpOnly().sameSite(SameSite.Strict).asHostPrefixed(),
130
+ Cookie.create('session', 'user-42').httpOnly().sameSite(SameSite.Strict).asHostPrefixed()
131
131
  );
132
132
  const secret = SecureCookies.encrypted(this.demoKey()).seal(
133
- Cookie.create('secret', 'top-secret-value').httpOnly().path('/'),
133
+ Cookie.create('secret', 'top-secret-value').httpOnly().path('/')
134
134
  );
135
135
 
136
136
  const json =
@@ -189,10 +189,7 @@ export class AppHandler extends ToilHandler {
189
189
  '","' +
190
190
  this.esc(this.clearString('secret')) +
191
191
  '"]}';
192
- return Response.json(json)
193
- .clearCookie('visits')
194
- .clearCookie('__Host-session')
195
- .clearCookie('secret');
192
+ return Response.json(json).clearCookie('visits').clearCookie('__Host-session').clearCookie('secret');
196
193
  }
197
194
 
198
195
  // SEAL: sign and encrypt a value (from `?v=`), then recover both and show