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.
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +9 -5
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.js +1 -1
- package/build/client/components/Image.d.ts +1 -1
- package/build/client/dev/devtools.js +3 -1
- package/build/client/index.d.ts +2 -2
- package/build/client/index.js +2 -2
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/docs.js +1 -1
- package/build/compiler/seo.js +1 -3
- package/build/compiler/template-build.js +1 -1
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/cache.js +0 -0
- package/build/devserver/crypto.js +45 -17
- package/build/devserver/database.js +82 -0
- package/build/devserver/email/caps.js +0 -0
- package/build/devserver/email/config.js +7 -2
- package/build/devserver/email/validate.js +1 -4
- package/build/devserver/index.d.ts +1 -1
- package/build/devserver/index.js +3 -2
- package/build/devserver/module.js +51 -12
- package/build/devserver/proxy.js +2 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/codec.d.ts +5 -5
- package/build/io/codec.js +193 -77
- package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
- package/examples/basic/client/public/images/logo.svg +37 -34
- package/examples/basic/client/public/index.html +14 -14
- package/examples/basic/client/routes/auth.tsx +18 -10
- package/examples/basic/client/routes/cookies.tsx +15 -24
- package/examples/basic/client/routes/crypto.tsx +4 -5
- package/examples/basic/client/routes/features/template/template.tsx +1 -1
- package/examples/basic/client/routes/hello.tsx +1 -1
- package/examples/basic/client/routes/pq.tsx +14 -14
- package/examples/basic/client/routes/rest.tsx +1 -3
- package/examples/basic/client/styles/main.css +25 -22
- package/examples/basic/client/toil.tsx +1 -1
- package/examples/basic/server/README.md +8 -8
- package/examples/basic/server/core/AppHandler.ts +4 -7
- package/examples/basic/server/routes/Auth.ts +11 -3
- package/examples/basic/server/routes/EnvDemo.ts +9 -3
- package/package.json +1 -1
- package/src/backend/index.ts +4 -2
- package/src/cli/doctor.ts +10 -3
- package/src/cli/notify.ts +1 -6
- package/src/cli/ui.ts +3 -3
- package/src/cli/version-check.ts +5 -1
- package/src/client/auth.ts +33 -10
- package/src/client/components/Form.tsx +2 -2
- package/src/client/components/Image.tsx +1 -1
- package/src/client/components/Script.tsx +1 -1
- package/src/client/components/Slot.tsx +1 -1
- package/src/client/dev/devtools.tsx +121 -54
- package/src/client/dev/error-overlay.tsx +7 -1
- package/src/client/head/metadata.ts +1 -1
- package/src/client/index.ts +13 -2
- package/src/client/routing/Router.tsx +2 -2
- package/src/client/routing/error-boundary.tsx +1 -1
- package/src/client/routing/loader.ts +2 -2
- package/src/client/routing/mount.tsx +5 -6
- package/src/compiler/docs.ts +1 -1
- package/src/compiler/email-preview.ts +1 -1
- package/src/compiler/generate.ts +1 -1
- package/src/compiler/seo.ts +1 -3
- package/src/compiler/ssg.ts +10 -4
- package/src/compiler/template-build.ts +2 -7
- package/src/compiler/template.ts +1 -4
- package/src/compiler/vite.ts +1 -1
- package/src/devserver/cache.ts +0 -0
- package/src/devserver/crypto.ts +140 -51
- package/src/devserver/database.ts +149 -8
- package/src/devserver/dotenv.ts +10 -2
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +8 -2
- package/src/devserver/email/index.ts +3 -3
- package/src/devserver/email/validate.ts +1 -4
- package/src/devserver/envelope.ts +3 -3
- package/src/devserver/host.ts +14 -5
- package/src/devserver/index.ts +15 -6
- package/src/devserver/module.ts +56 -14
- package/src/devserver/proxy.ts +5 -7
- package/src/io/codec.ts +226 -83
- 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 =
|
|
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 {
|
|
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 {
|
|
15
|
-
import {
|
|
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 {
|
|
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)
|
|
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
|
}
|
package/src/devserver/host.ts
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* `WebAssembly.Instance`, so offering the full surface costs nothing.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { buildCryptoImports,
|
|
21
|
-
import { buildDatabaseImports,
|
|
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: (
|
|
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(
|
|
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(
|
|
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
|
},
|
package/src/devserver/index.ts
CHANGED
|
@@ -22,18 +22,23 @@
|
|
|
22
22
|
import fs from 'node:fs';
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
|
|
25
|
-
import {
|
|
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 {
|
|
32
|
+
import { type EnvelopeRequest, METHOD_CODES } from './envelope.js';
|
|
33
33
|
import { WasmServerModule } from './module.js';
|
|
34
|
-
import { proxyToVite,
|
|
34
|
+
import { proxyToVite, type ViteTarget, wireWebsocketProxy } from './proxy.js';
|
|
35
35
|
|
|
36
|
-
export {
|
|
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(
|
|
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)}`) +
|
|
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;
|
package/src/devserver/module.ts
CHANGED
|
@@ -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',
|
|
56
|
-
'
|
|
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',
|
|
59
|
-
'crypto.
|
|
60
|
-
'crypto.
|
|
61
|
-
'crypto.
|
|
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',
|
|
65
|
-
'data.
|
|
66
|
-
'data.
|
|
67
|
-
'data.
|
|
68
|
-
'data.
|
|
69
|
-
'data.
|
|
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(
|
|
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(
|
package/src/devserver/proxy.ts
CHANGED
|
@@ -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 (
|
|
149
|
+
if (
|
|
150
|
+
upstream.readyState === WebSocket.OPEN ||
|
|
151
|
+
upstream.readyState === WebSocket.CONNECTING
|
|
152
|
+
) {
|
|
155
153
|
upstream.close();
|
|
156
154
|
}
|
|
157
155
|
});
|