toiljs 0.0.55 → 0.0.56

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 (87) hide show
  1. package/build/backend/.tsbuildinfo +1 -1
  2. package/build/cli/.tsbuildinfo +1 -1
  3. package/build/cli/index.js +9 -5
  4. package/build/client/.tsbuildinfo +1 -1
  5. package/build/client/auth.js +1 -1
  6. package/build/client/components/Image.d.ts +1 -1
  7. package/build/client/dev/devtools.js +3 -1
  8. package/build/client/index.d.ts +2 -2
  9. package/build/client/index.js +2 -2
  10. package/build/client/routing/Router.js +1 -1
  11. package/build/client/routing/mount.js +1 -1
  12. package/build/compiler/.tsbuildinfo +1 -1
  13. package/build/compiler/docs.js +1 -1
  14. package/build/compiler/seo.js +1 -3
  15. package/build/compiler/template-build.js +1 -1
  16. package/build/devserver/.tsbuildinfo +1 -1
  17. package/build/devserver/cache.js +0 -0
  18. package/build/devserver/crypto.js +45 -17
  19. package/build/devserver/database.js +82 -0
  20. package/build/devserver/email/caps.js +0 -0
  21. package/build/devserver/email/config.js +7 -2
  22. package/build/devserver/email/validate.js +1 -4
  23. package/build/devserver/index.d.ts +1 -1
  24. package/build/devserver/index.js +3 -2
  25. package/build/devserver/module.js +51 -12
  26. package/build/devserver/proxy.js +2 -1
  27. package/build/io/.tsbuildinfo +1 -1
  28. package/build/io/codec.d.ts +5 -5
  29. package/build/io/codec.js +193 -77
  30. package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
  31. package/examples/basic/client/public/images/logo.svg +37 -34
  32. package/examples/basic/client/public/index.html +14 -14
  33. package/examples/basic/client/routes/auth.tsx +18 -10
  34. package/examples/basic/client/routes/cookies.tsx +15 -24
  35. package/examples/basic/client/routes/crypto.tsx +4 -5
  36. package/examples/basic/client/routes/features/template/template.tsx +1 -1
  37. package/examples/basic/client/routes/hello.tsx +1 -1
  38. package/examples/basic/client/routes/pq.tsx +14 -14
  39. package/examples/basic/client/routes/rest.tsx +1 -3
  40. package/examples/basic/client/styles/main.css +25 -22
  41. package/examples/basic/client/toil.tsx +1 -1
  42. package/examples/basic/server/README.md +8 -8
  43. package/examples/basic/server/core/AppHandler.ts +4 -7
  44. package/examples/basic/server/routes/Auth.ts +11 -3
  45. package/examples/basic/server/routes/EnvDemo.ts +9 -3
  46. package/package.json +1 -1
  47. package/src/backend/index.ts +4 -2
  48. package/src/cli/doctor.ts +10 -3
  49. package/src/cli/notify.ts +1 -6
  50. package/src/cli/ui.ts +3 -3
  51. package/src/cli/version-check.ts +5 -1
  52. package/src/client/auth.ts +33 -10
  53. package/src/client/components/Form.tsx +2 -2
  54. package/src/client/components/Image.tsx +1 -1
  55. package/src/client/components/Script.tsx +1 -1
  56. package/src/client/components/Slot.tsx +1 -1
  57. package/src/client/dev/devtools.tsx +121 -54
  58. package/src/client/dev/error-overlay.tsx +7 -1
  59. package/src/client/head/metadata.ts +1 -1
  60. package/src/client/index.ts +13 -2
  61. package/src/client/routing/Router.tsx +2 -2
  62. package/src/client/routing/error-boundary.tsx +1 -1
  63. package/src/client/routing/loader.ts +2 -2
  64. package/src/client/routing/mount.tsx +5 -6
  65. package/src/compiler/docs.ts +1 -1
  66. package/src/compiler/email-preview.ts +1 -1
  67. package/src/compiler/generate.ts +1 -1
  68. package/src/compiler/seo.ts +1 -3
  69. package/src/compiler/ssg.ts +10 -4
  70. package/src/compiler/template-build.ts +2 -7
  71. package/src/compiler/template.ts +1 -4
  72. package/src/compiler/vite.ts +1 -1
  73. package/src/devserver/cache.ts +0 -0
  74. package/src/devserver/crypto.ts +140 -51
  75. package/src/devserver/database.ts +149 -8
  76. package/src/devserver/dotenv.ts +10 -2
  77. package/src/devserver/email/caps.ts +0 -0
  78. package/src/devserver/email/config.ts +8 -2
  79. package/src/devserver/email/index.ts +3 -3
  80. package/src/devserver/email/validate.ts +1 -4
  81. package/src/devserver/envelope.ts +3 -3
  82. package/src/devserver/host.ts +14 -5
  83. package/src/devserver/index.ts +15 -6
  84. package/src/devserver/module.ts +56 -14
  85. package/src/devserver/proxy.ts +5 -7
  86. package/src/io/codec.ts +226 -83
  87. package/test/devserver-database.test.ts +60 -0
@@ -94,7 +94,10 @@ export function resolveEmailConfig(
94
94
  } else if (providerId === 'gmail' || providerId === 'smtp') {
95
95
  provider = 'smtp';
96
96
  const isGmail = providerId === 'gmail';
97
- const host = envOf(reserved, 'SMTP_HOST') ?? c.smtp?.host?.trim() ?? (isGmail ? 'smtp.gmail.com' : '');
97
+ const host =
98
+ envOf(reserved, 'SMTP_HOST') ??
99
+ c.smtp?.host?.trim() ??
100
+ (isGmail ? 'smtp.gmail.com' : '');
98
101
  if (!host) {
99
102
  return { config: null, warning: 'provider `smtp` requires TOIL_EMAIL_SMTP_HOST' };
100
103
  }
@@ -102,7 +105,10 @@ export function resolveEmailConfig(
102
105
  const user = envOf(reserved, 'SMTP_USER') ?? c.smtp?.user?.trim() ?? from;
103
106
  smtp = { host, port, user };
104
107
  } else {
105
- return { config: null, warning: `unknown email provider "${providerId}" (resend|gmail|smtp)` };
108
+ return {
109
+ config: null,
110
+ warning: `unknown email provider "${providerId}" (resend|gmail|smtp)`,
111
+ };
106
112
  }
107
113
 
108
114
  return {
@@ -11,11 +11,11 @@
11
11
  */
12
12
  import { loadEnvFiles } from '../dotenv.js';
13
13
  import { EmailCaps } from './caps.js';
14
- import { resolveEmailConfig, type ResolvedEmailConfig } from './config.js';
15
- import { sendVia, type OutboundMessage } from './providers.js';
14
+ import { type ResolvedEmailConfig, resolveEmailConfig } from './config.js';
15
+ import { type OutboundMessage, sendVia } from './providers.js';
16
16
  import { EmailStatus } from './status.js';
17
17
  import { validRecipient } from './validate.js';
18
- import { parseEmailBlob, type ParsedEmail } from './wire.js';
18
+ import { type ParsedEmail, parseEmailBlob } from './wire.js';
19
19
 
20
20
  import type { EmailBackendConfig } from 'toiljs/shared';
21
21
 
@@ -17,10 +17,7 @@ export function validRecipient(s: string): boolean {
17
17
  if (parts.length !== 2) return false; // not exactly one '@'
18
18
  const [local, domain] = parts;
19
19
  return (
20
- local.length > 0 &&
21
- domain.includes('.') &&
22
- !domain.startsWith('.') &&
23
- !domain.endsWith('.')
20
+ local.length > 0 && domain.includes('.') && !domain.startsWith('.') && !domain.endsWith('.')
24
21
  );
25
22
  }
26
23
 
@@ -69,15 +69,15 @@ export function encodeRequestEnvelope(req: EnvelopeRequest): Buffer {
69
69
  if (path.length > U16_MAX) throw new Error(`path too long: ${String(path.length)} bytes`);
70
70
  if (req.headers.length > U16_MAX)
71
71
  throw new Error(`too many headers: ${String(req.headers.length)}`);
72
- if (req.body.length > U32_MAX) throw new Error(`body too long: ${String(req.body.length)} bytes`);
72
+ if (req.body.length > U32_MAX)
73
+ throw new Error(`body too long: ${String(req.body.length)} bytes`);
73
74
 
74
75
  const headers: { name: Buffer; value: Buffer }[] = [];
75
76
  let headersSize = 0;
76
77
  for (const [name, value] of req.headers) {
77
78
  const n = Buffer.from(name, 'utf8');
78
79
  const v = Buffer.from(value, 'utf8');
79
- if (n.length > U16_MAX || v.length > U16_MAX)
80
- throw new Error(`header too long: ${name}`);
80
+ if (n.length > U16_MAX || v.length > U16_MAX) throw new Error(`header too long: ${name}`);
81
81
  headers.push({ name: n, value: v });
82
82
  headersSize += 4 + n.length + v.length;
83
83
  }
@@ -17,8 +17,8 @@
17
17
  * `WebAssembly.Instance`, so offering the full surface costs nothing.
18
18
  */
19
19
 
20
- import { buildCryptoImports, freshCryptoState, type CryptoState } from './crypto.js';
21
- import { buildDatabaseImports, freshDbState, type DbDevState } from './database.js';
20
+ import { buildCryptoImports, type CryptoState, freshCryptoState } from './crypto.js';
21
+ import { buildDatabaseImports, type DbDevState, freshDbState } from './database.js';
22
22
  import { EmailStatus, getEmailService } from './email/index.js';
23
23
  import { parseEmailBlob } from './email/wire.js';
24
24
  import { devEnvGet, devEnvGetSecure } from './env.js';
@@ -158,7 +158,12 @@ export function buildHostImports(ref: MemoryRef, state: DispatchState): WebAssem
158
158
  state.status = code >= 100 && code <= 599 ? code : 500;
159
159
  },
160
160
 
161
- set_header: (namePtr: number, nameLen: number, valPtr: number, valLen: number): void => {
161
+ set_header: (
162
+ namePtr: number,
163
+ nameLen: number,
164
+ valPtr: number,
165
+ valLen: number,
166
+ ): void => {
162
167
  if (nameLen > MAX_HEADER_NAME_LEN)
163
168
  throw new Error(`header name too long: ${String(nameLen)} bytes`);
164
169
  if (valLen > MAX_HEADER_VALUE_LEN)
@@ -228,7 +233,9 @@ export function buildHostImports(ref: MemoryRef, state: DispatchState): WebAssem
228
233
  const svc = getEmailService();
229
234
  if (svc === null) {
230
235
  const to = parseEmailBlob(raw)?.to ?? '<unparsed>';
231
- process.stdout.write(` ✉ dev email_send -> ${to} (no email config; not sent)\n`);
236
+ process.stdout.write(
237
+ ` ✉ dev email_send -> ${to} (no email config; not sent)\n`,
238
+ );
232
239
  return EmailStatus.Sent;
233
240
  }
234
241
  const { status, parsed } = svc.prepare(raw);
@@ -243,7 +250,9 @@ export function buildHostImports(ref: MemoryRef, state: DispatchState): WebAssem
243
250
  process.stdout.write(` ✉ dev email_send -> ${parsed.to} (${label})\n`);
244
251
  })
245
252
  .catch((e: unknown) => {
246
- process.stdout.write(` ✉ dev email_send -> ${parsed.to} (error: ${String(e)})\n`);
253
+ process.stdout.write(
254
+ ` ✉ dev email_send -> ${parsed.to} (error: ${String(e)})\n`,
255
+ );
247
256
  });
248
257
  return EmailStatus.Sent; // optimistic; sync wasm can't await the send
249
258
  },
@@ -22,18 +22,23 @@
22
22
  import fs from 'node:fs';
23
23
  import path from 'node:path';
24
24
 
25
- import { Server, type Request, type Response } from '@dacely/hyper-express';
25
+ import { type Request, type Response, Server } from '@dacely/hyper-express';
26
26
  import pc from 'picocolors';
27
27
 
28
28
  import type { EmailBackendConfig } from 'toiljs/shared';
29
29
 
30
30
  import { applyCacheRule, lookupCache } from './cache.js';
31
31
  import { initEmailService } from './email/index.js';
32
- import { METHOD_CODES, type EnvelopeRequest } from './envelope.js';
32
+ import { type EnvelopeRequest, METHOD_CODES } from './envelope.js';
33
33
  import { WasmServerModule } from './module.js';
34
- import { proxyToVite, wireWebsocketProxy, type ViteTarget } from './proxy.js';
34
+ import { proxyToVite, type ViteTarget, wireWebsocketProxy } from './proxy.js';
35
35
 
36
- export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult } from './envelope.js';
36
+ export {
37
+ METHOD_CODES,
38
+ encodeRequestEnvelope,
39
+ decodeResponseEnvelope,
40
+ unpackHandleResult,
41
+ } from './envelope.js';
37
42
  export type { EnvelopeRequest, EnvelopeResponse } from './envelope.js';
38
43
  export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './module.js';
39
44
  export type { WasmDispatchResult } from './module.js';
@@ -161,7 +166,10 @@ function sendWasmResponse(
161
166
  if (!hasContentType) {
162
167
  // The edge defaults file bodies to application/octet-stream; in dev we
163
168
  // guess from the extension so a guest-served asset renders in the browser.
164
- response.header('content-type', MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream');
169
+ response.header(
170
+ 'content-type',
171
+ MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream',
172
+ );
165
173
  }
166
174
  response.sendFile(file);
167
175
  return;
@@ -267,7 +275,8 @@ export async function startDevServer(options: DevServerOptions): Promise<Running
267
275
  // A trap (ToilScript abort, OOB, malformed envelope) is isolated to
268
276
  // this request, exactly like the edge poisoning one instance.
269
277
  process.stdout.write(
270
- pc.red(` ✗ ${request.method} ${request.path} server error: ${String(e)}`) + '\n',
278
+ pc.red(` ✗ ${request.method} ${request.path} server error: ${String(e)}`) +
279
+ '\n',
271
280
  );
272
281
  response.status(500).send('internal error\n');
273
282
  return;
@@ -16,8 +16,8 @@ import fs from 'node:fs';
16
16
  import {
17
17
  decodeResponseEnvelope,
18
18
  encodeRequestEnvelope,
19
- unpackHandleResult,
20
19
  type EnvelopeRequest,
20
+ unpackHandleResult,
21
21
  } from './envelope.js';
22
22
  import { buildHostImports, freshDispatchState, type MemoryRef } from './host.js';
23
23
 
@@ -52,21 +52,60 @@ interface HandleExports {
52
52
 
53
53
  /** Host functions the dev server provides under `env` (see `host.ts`). */
54
54
  const PROVIDED_IMPORTS = new Set([
55
- 'abort', 'set_status', 'set_header', 'respond_file', 'thread_spawn', 'Date.now',
56
- 'client_ip', 'ratelimit_check', 'email_send', 'env_get', 'env_get_secure',
55
+ 'abort',
56
+ 'set_status',
57
+ 'set_header',
58
+ 'respond_file',
59
+ 'thread_spawn',
60
+ 'Date.now',
61
+ 'client_ip',
62
+ 'ratelimit_check',
63
+ 'email_send',
64
+ 'env_get',
65
+ 'env_get_secure',
57
66
  // Web Crypto host functions (see ./crypto.ts).
58
- 'crypto.fill_random', 'crypto.random_uuid', 'crypto.take_result', 'crypto.digest',
59
- 'crypto.import_key', 'crypto.export_key', 'crypto.encrypt', 'crypto.decrypt',
60
- 'crypto.sign', 'crypto.verify', 'crypto.derive_bits',
61
- 'crypto.mldsa_verify', 'crypto.mlkem_decapsulate', 'crypto.voprf_evaluate',
67
+ 'crypto.fill_random',
68
+ 'crypto.random_uuid',
69
+ 'crypto.take_result',
70
+ 'crypto.digest',
71
+ 'crypto.import_key',
72
+ 'crypto.export_key',
73
+ 'crypto.encrypt',
74
+ 'crypto.decrypt',
75
+ 'crypto.sign',
76
+ 'crypto.verify',
77
+ 'crypto.derive_bits',
78
+ 'crypto.mldsa_verify',
79
+ 'crypto.mlkem_decapsulate',
80
+ 'crypto.voprf_evaluate',
62
81
  // ToilDB data API (see ./database.ts). Backed by ScyllaDB on the production
63
82
  // edge; backs the auth example's accounts + login challenges in dev.
64
- 'data.resolve_collection', 'data.get', 'data.get_many', 'data.exists', 'data.create',
65
- 'data.patch', 'data.delete', 'data.get_delete',
66
- 'data.unique_lookup', 'data.unique_claim', 'data.unique_release',
67
- 'data.view_get', 'data.view_publish',
68
- 'data.membership_contains', 'data.membership_add', 'data.membership_remove', 'data.membership_list',
69
- 'data.counter_get', 'data.counter_add', 'data.append', 'data.latest',
83
+ 'data.resolve_collection',
84
+ 'data.get',
85
+ 'data.get_many',
86
+ 'data.exists',
87
+ 'data.create',
88
+ 'data.patch',
89
+ 'data.delete',
90
+ 'data.get_delete',
91
+ 'data.unique_lookup',
92
+ 'data.unique_claim',
93
+ 'data.unique_release',
94
+ 'data.view_get',
95
+ 'data.view_publish',
96
+ 'data.membership_contains',
97
+ 'data.membership_add',
98
+ 'data.membership_remove',
99
+ 'data.membership_list',
100
+ 'data.counter_get',
101
+ 'data.counter_add',
102
+ 'data.append',
103
+ 'data.latest',
104
+ 'data.capacity_set_total',
105
+ 'data.capacity_available',
106
+ 'data.capacity_reserve',
107
+ 'data.capacity_confirm',
108
+ 'data.capacity_cancel',
70
109
  'data.take_result',
71
110
  ]);
72
111
 
@@ -204,7 +243,10 @@ export class WasmServerModule {
204
243
  /** Fail instantiation up front, with names, when the guest needs imports we do not provide. */
205
244
  private assertImportSurface(module: WebAssembly.Module): void {
206
245
  const missing = WebAssembly.Module.imports(module)
207
- .filter((i) => i.kind === 'function' && (i.module !== 'env' || !PROVIDED_IMPORTS.has(i.name)))
246
+ .filter(
247
+ (i) =>
248
+ i.kind === 'function' && (i.module !== 'env' || !PROVIDED_IMPORTS.has(i.name)),
249
+ )
208
250
  .map((i) => `${i.module}.${i.name}`);
209
251
  if (missing.length > 0)
210
252
  throw new Error(
@@ -6,12 +6,7 @@
6
6
  * websocket through Node's built-in `WebSocket` client, both loopback-only.
7
7
  */
8
8
 
9
- import {
10
- type Request,
11
- type Response,
12
- type Server,
13
- type Websocket,
14
- } from '@dacely/hyper-express';
9
+ import { type Request, type Response, type Server, type Websocket } from '@dacely/hyper-express';
15
10
 
16
11
  /** Where the internal Vite dev server listens (always loopback). */
17
12
  export interface ViteTarget {
@@ -151,7 +146,10 @@ export function wireWebsocketProxy(app: Server, target: ViteTarget): void {
151
146
  else pending.push(m);
152
147
  });
153
148
  ws.on('close', () => {
154
- if (upstream.readyState === WebSocket.OPEN || upstream.readyState === WebSocket.CONNECTING) {
149
+ if (
150
+ upstream.readyState === WebSocket.OPEN ||
151
+ upstream.readyState === WebSocket.CONNECTING
152
+ ) {
155
153
  upstream.close();
156
154
  }
157
155
  });