toiljs 0.0.40 → 0.0.42

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 (32) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/build/cli/.tsbuildinfo +1 -1
  3. package/build/cli/index.js +12 -0
  4. package/build/compiler/.tsbuildinfo +1 -1
  5. package/build/compiler/emails.js +5 -1
  6. package/build/compiler/generate.js +13 -1
  7. package/build/compiler/index.js +11 -1
  8. package/examples/basic/client/routes/auth.tsx +2 -0
  9. package/examples/basic/client/routes/cookies.tsx +1 -0
  10. package/examples/basic/client/routes/pq.tsx +2 -0
  11. package/examples/basic/server/main.ts +1 -0
  12. package/package.json +2 -2
  13. package/src/cli/create.ts +12 -0
  14. package/src/compiler/emails.ts +8 -1
  15. package/src/compiler/generate.ts +13 -1
  16. package/src/compiler/index.ts +16 -1
  17. package/examples/basic/server/AuthTestHandler.ts +0 -15
  18. package/examples/basic/server/AuthVerifyHandler.ts +0 -23
  19. package/examples/basic/server/CacheHandler.ts +0 -25
  20. package/examples/basic/server/DecoCache.ts +0 -18
  21. package/examples/basic/server/FastTrapHandler.ts +0 -8
  22. package/examples/basic/server/SpinHandler.ts +0 -18
  23. package/examples/basic/server/SsrGreetingRender.ts +0 -27
  24. package/examples/basic/server/authexample-main.ts +0 -8
  25. package/examples/basic/server/authtest-main.ts +0 -8
  26. package/examples/basic/server/authverify-main.ts +0 -8
  27. package/examples/basic/server/cache-main.ts +0 -8
  28. package/examples/basic/server/deco-main.ts +0 -18
  29. package/examples/basic/server/spin-main.ts +0 -13
  30. package/examples/basic/server/ssr/greeting.slots.ts +0 -19
  31. package/examples/basic/server/ssr-main.ts +0 -18
  32. package/examples/basic/server/trap-main.ts +0 -8
@@ -291,8 +291,15 @@ export async function renderEmails(cfg: ResolvedToilConfig): Promise<void> {
291
291
  }
292
292
 
293
293
  if (rendered.length === 0) return;
294
+ // Only (re)write when the output actually changed: an unconditional write
295
+ // bumps the file's mtime every rebuild, which under `dev` would re-trigger
296
+ // the watcher. (The watcher also ignores this file by name; this is belt-and-
297
+ // suspenders and avoids needless work.)
298
+ const next = renderModuleSource(rendered);
299
+ const prev = fs.existsSync(generatedPath) ? fs.readFileSync(generatedPath, 'utf8') : null;
300
+ if (prev === next) return;
294
301
  fs.mkdirSync(path.dirname(generatedPath), { recursive: true });
295
- fs.writeFileSync(generatedPath, renderModuleSource(rendered));
302
+ fs.writeFileSync(generatedPath, next);
296
303
  process.stdout.write(
297
304
  ` ✓ emails: generated ${String(rendered.length)} template${rendered.length === 1 ? '' : 's'} (${rendered
298
305
  .map((r) => r.name)
@@ -107,7 +107,19 @@ export const TOIL_SERVER_ENV_DTS =
107
107
  `declare const Cookies: typeof import('toiljs/server/runtime/http/cookies').Cookies;\n` +
108
108
  `type Cookies = import('toiljs/server/runtime/http/cookies').Cookies;\n` +
109
109
  `declare const SecureCookies: typeof import('toiljs/server/runtime/http/securecookies').SecureCookies;\n` +
110
- `type SecureCookies = import('toiljs/server/runtime/http/securecookies').SecureCookies;\n`;
110
+ `type SecureCookies = import('toiljs/server/runtime/http/securecookies').SecureCookies;\n` +
111
+ `declare const Time: typeof import('toiljs/server/runtime/time').Time;\n` +
112
+ `// Email, rate-limit, 2FA, and auth globals (server/globals/*), hand-declared\n` +
113
+ `// because their AssemblyScript source can't be type-aliased from tsc.\n` +
114
+ `declare enum EmailStatus { Sent, Disabled, Budget, RecipientCapped, Deduped, TryLater, BadRecipient, ProviderError }\n` +
115
+ `declare namespace EmailService { function send(to: string, subject: string, body: string, purpose?: string, html?: string): EmailStatus; }\n` +
116
+ `declare class RenderedEmail { subject: string; body: string; html: string; constructor(subject: string, body: string, html: string); }\n` +
117
+ `declare class EmailTemplate { constructor(subject: string, body: string, html?: string); render(vars: Map<string, string>): RenderedEmail; send(to: string, vars: Map<string, string>, purpose?: string): EmailStatus; }\n` +
118
+ `declare enum RateLimit { FixedWindow, SlidingWindow, TokenBucket }\n` +
119
+ `declare class TwoFactorIssue { code: string; token: string; constructor(code: string, token: string); }\n` +
120
+ `declare class TwoFactorChallenge { token: string; status: EmailStatus; constructor(token: string, status: EmailStatus); }\n` +
121
+ `declare namespace TwoFactor { function setSecret(secret: Uint8Array): void; function issue(recipient: string, purpose: string, ttlSecs?: u64, digits?: i32): TwoFactorIssue; function send(recipient: string, purpose: string, ttlSecs?: u64, digits?: i32): TwoFactorChallenge; function verify(token: string, recipient: string, code: string): bool; }\n` +
122
+ `declare namespace AuthService { const SESSION_COOKIE: string; const USER_COOKIE: string; const LOGIN_CONTEXT: string; const PUBLIC_KEY_LEN: i32; const SIGNATURE_LEN: i32; const DEFAULT_SESSION_TTL_SECS: u64; function setSecret(secret: Uint8Array): void; function hasSession(): bool; function getSessionBytes(): Uint8Array | null; function mintSession(userData: Uint8Array, ttlSecs?: u64): Cookie; function clearSession(): Cookie; function userCookie(userData: Uint8Array, ttlSecs?: u64): Cookie; function clearUserCookie(): Cookie; function buildLoginMessage(sub: string, aud: string, cid: Uint8Array, nonce: Uint8Array, iat: u64, exp: u64): Uint8Array; function verifyLogin(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): bool; }\n`;
111
123
 
112
124
  /**
113
125
  * Returns a `./`-prefixed, **extensionless** POSIX module specifier from `.toil` to `abs`, for use
@@ -143,7 +143,19 @@ async function buildServer(root: string): Promise<void> {
143
143
  // Explicit entries (every server file) override the toilconfig entries; the target options
144
144
  // (optimization, features, runtime) still come from the toilconfig's `release` target.
145
145
  const files = serverEntryFiles(root);
146
- const args = [binJs, ...files, '--target', 'release', '--rpcModule', 'shared/server.ts'];
146
+ // Suppress AS235 ("only variables/functions/enums become wasm exports"): a
147
+ // `@data`/`@rest` class is intentionally `export class` (so other server
148
+ // files import it), but never a wasm export — the warning is pure noise here.
149
+ const args = [
150
+ binJs,
151
+ ...files,
152
+ '--target',
153
+ 'release',
154
+ '--rpcModule',
155
+ 'shared/server.ts',
156
+ '--disableWarning',
157
+ '235',
158
+ ];
147
159
 
148
160
  await new Promise<void>((resolve, reject) => {
149
161
  const child = spawn(process.execPath, args, { cwd: root, stdio: 'inherit' });
@@ -202,6 +214,9 @@ function watchServer(cfg: ResolvedToilConfig, watcher: ViteDevServer['watcher'])
202
214
  const isServerSource = (file: string): boolean =>
203
215
  file.endsWith('.ts') &&
204
216
  !file.endsWith('.d.ts') &&
217
+ // `_emails.ts` is GENERATED by renderEmails on every rebuild; reacting to
218
+ // our own output would loop forever (rebuild -> write -> rebuild -> ...).
219
+ path.basename(file) !== '_emails.ts' &&
205
220
  dirs.some((dir) => file === dir || file.startsWith(dir + path.sep));
206
221
  const isEmailSource = (file: string): boolean =>
207
222
  /\.(tsx|jsx)$/.test(file) && (file === emailsDir || file.startsWith(emailsDir + path.sep));
@@ -1,15 +0,0 @@
1
- import { Request, Response, ToilHandler } from 'toiljs/server/runtime';
2
-
3
- // AuthService is used with NO import (a global via toilscript --lib).
4
- export class AuthTestHandler extends ToilHandler {
5
- public handle(req: Request): Response {
6
- const cid = new Uint8Array(16);
7
- const nonce = new Uint8Array(32);
8
- const msg = AuthService.buildLoginMessage('alice', 'toil-demo', cid, nonce, 1000, 2000);
9
- // Dummy pk/sig -> verify must be false (also exercises the host import binding).
10
- const pk = new Uint8Array(AuthService.PUBLIC_KEY_LEN);
11
- const sig = new Uint8Array(AuthService.SIGNATURE_LEN);
12
- const ok = AuthService.verifyLogin(pk, msg, sig);
13
- return Response.text('msglen=' + msg.length.toString() + ' verify=' + (ok ? '1' : '0') + '\n');
14
- }
15
- }
@@ -1,23 +0,0 @@
1
- import { Method, Request, Response, ToilHandler } from 'toiljs/server/runtime';
2
- import { DataReader } from 'data';
3
-
4
- // Reads {sub, aud, cid, nonce, iat, exp, pk, sig} as a binary body, rebuilds
5
- // the login message with the AS AuthService (server-authoritative encoding),
6
- // and verifies the client signature. Proves the full client->edge chain.
7
- export class AuthVerifyHandler extends ToilHandler {
8
- public handle(req: Request): Response {
9
- if (req.method != Method.POST) return Response.empty(405);
10
- const r = new DataReader(req.body);
11
- const sub = r.readString();
12
- const aud = r.readString();
13
- const cid = r.readBytes();
14
- const nonce = r.readBytes();
15
- const iat = r.readU64();
16
- const exp = r.readU64();
17
- const pk = r.readBytes();
18
- const sig = r.readBytes();
19
- const msg = AuthService.buildLoginMessage(sub, aud, cid, nonce, iat, exp);
20
- const ok = AuthService.verifyLogin(pk, msg, sig);
21
- return Response.text((ok ? '1' : '0') + '\n');
22
- }
23
- }
@@ -1,25 +0,0 @@
1
- import { Method, Request, Response, ToilHandler } from 'toiljs/server/runtime';
2
-
3
- // Exercises the tenant-directed cache. Detection is via the host's
4
- // `Toil-Cache` response header (MISS on first compute+store, HIT on reuse,
5
- // DYNAMIC when not cached), so the body need not vary.
6
- export class CacheHandler extends ToilHandler {
7
- public handle(req: Request): Response {
8
- if (req.method == Method.GET && req.path == '/cacheable') {
9
- // edge-cache 5 min + browser Cache-Control max-age=60
10
- return Response.json('{"cached":true}').cache(5, 60);
11
- }
12
- if (req.method == Method.GET && req.path == '/auth-ok') {
13
- // edge-cache even when the request carries auth (allowAuth)
14
- return Response.json('{"authok":true}').cache(5, 0, false, true);
15
- }
16
- if (req.method == Method.GET && req.path == '/uncacheable') {
17
- return Response.json('{"cached":false}');
18
- }
19
- if (req.method == Method.POST && req.path == '/echo') {
20
- // cache per (path, body): same body hits, different body misses
21
- return Response.bytes(req.body).cache(5);
22
- }
23
- return Response.notFound();
24
- }
25
- }
@@ -1,18 +0,0 @@
1
- import { Response, RouteContext } from 'toiljs/server/runtime';
2
-
3
- // @rest controller exercising the new compile-time @cache decorator.
4
- @rest('deco')
5
- class DecoCache {
6
- // edge-cache 5 min + browser Cache-Control max-age=60, via the decorator
7
- @get('/cached')
8
- @cache(5, 60)
9
- public cached(ctx: RouteContext): Response {
10
- return Response.json('{"deco":"cached"}');
11
- }
12
-
13
- // no @cache -> never cached
14
- @get('/plain')
15
- public plain(ctx: RouteContext): Response {
16
- return Response.json('{"deco":"plain"}');
17
- }
18
- }
@@ -1,8 +0,0 @@
1
- import { Request, Response, ToilHandler } from 'toiljs/server/runtime';
2
-
3
- export class FastTrapHandler extends ToilHandler {
4
- public handle(req: Request): Response {
5
- unreachable(); // wasm `unreachable` -> instant trap, ~0 gas
6
- return Response.text('x');
7
- }
8
- }
@@ -1,18 +0,0 @@
1
- import { Request, Response, ToilHandler } from 'toiljs/server/runtime';
2
-
3
- // Module-level counter so the loop body has an observable side effect the
4
- // optimizer cannot remove (and even a bare loop would still be gas-metered).
5
- let counter: i64 = 0;
6
-
7
- export class SpinHandler extends ToilHandler {
8
- public handle(req: Request): Response {
9
- // Infinite CPU burn on EVERY request. The edge's per-request gas
10
- // budget (MAX_GAS_WASM_INIT) must trap this and 502 instead of
11
- // freezing the worker.
12
- while (true) {
13
- counter = counter + 1;
14
- }
15
- // unreachable
16
- return Response.text('unreachable\n');
17
- }
18
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * A hand-written edge-SSR `render` for the `/hello` route, authored against the
3
- * generated typed `Slot` enum + `HASH`. It derives its data from the request
4
- * and fills the holes; the host splices these values into the precompiled
5
- * template. Registers itself with the `Ssr` router (compiler-injected in a real
6
- * build; explicit here).
7
- */
8
- import { Request } from 'toiljs/server/runtime';
9
- import { HtmlBuilder, SlotValues, Ssr } from 'toiljs/server/runtime';
10
- import { HASH, Slot } from './ssr/greeting.slots';
11
-
12
- function renderGreeting(req: Request): SlotValues | null {
13
- if (req.path != '/hello') return null;
14
- const v = new SlotValues(HASH);
15
- // A text hole: React-escaped (note the `&` and `<>` get entities).
16
- v.setText(Slot.greeting, 'world & <friends>');
17
- // A repeat region: three stamped rows.
18
- const rows = new HtmlBuilder();
19
- const items: string[] = ['a & b', '<c>', 'd'];
20
- for (let i = 0; i < items.length; i++) {
21
- rows.raw('<li>').text(items[i]).raw('</li>');
22
- }
23
- v.setRepeat(Slot.count, rows);
24
- return v;
25
- }
26
-
27
- Ssr.register(renderGreeting);
@@ -1,8 +0,0 @@
1
- import { Server } from 'toiljs/server/runtime';
2
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
3
- import { Request, Response, Rest, ToilHandler } from 'toiljs/server/runtime';
4
- import './routes/Auth';
5
- class H extends ToilHandler { public handle(req: Request): Response { const h = Rest.dispatch(req); return h != null ? h : Response.notFound(); } }
6
- Server.handler = () => { return new H(); };
7
- export * from 'toiljs/server/runtime/exports';
8
- export function abort(m: string, f: string, l: u32, c: u32): void { revertOnError(m, f, l, c); }
@@ -1,8 +0,0 @@
1
- import { Server } from 'toiljs/server/runtime';
2
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
3
- import { AuthTestHandler } from './AuthTestHandler';
4
- Server.handler = () => { return new AuthTestHandler(); };
5
- export * from 'toiljs/server/runtime/exports';
6
- export function abort(message: string, fileName: string, line: u32, column: u32): void {
7
- revertOnError(message, fileName, line, column);
8
- }
@@ -1,8 +0,0 @@
1
- import { Server } from 'toiljs/server/runtime';
2
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
3
- import { AuthVerifyHandler } from './AuthVerifyHandler';
4
- Server.handler = () => { return new AuthVerifyHandler(); };
5
- export * from 'toiljs/server/runtime/exports';
6
- export function abort(message: string, fileName: string, line: u32, column: u32): void {
7
- revertOnError(message, fileName, line, column);
8
- }
@@ -1,8 +0,0 @@
1
- import { Server } from 'toiljs/server/runtime';
2
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
3
- import { CacheHandler } from './CacheHandler';
4
- Server.handler = () => { return new CacheHandler(); };
5
- export * from 'toiljs/server/runtime/exports';
6
- export function abort(message: string, fileName: string, line: u32, column: u32): void {
7
- revertOnError(message, fileName, line, column);
8
- }
@@ -1,18 +0,0 @@
1
- import { Server } from 'toiljs/server/runtime';
2
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
3
- import { Request, Response, Rest, ToilHandler } from 'toiljs/server/runtime';
4
- import './DecoCache';
5
-
6
- class DecoHandler extends ToilHandler {
7
- public handle(req: Request): Response {
8
- const hit = Rest.dispatch(req);
9
- if (hit != null) return hit;
10
- return Response.notFound();
11
- }
12
- }
13
-
14
- Server.handler = () => { return new DecoHandler(); };
15
- export * from 'toiljs/server/runtime/exports';
16
- export function abort(message: string, fileName: string, line: u32, column: u32): void {
17
- revertOnError(message, fileName, line, column);
18
- }
@@ -1,13 +0,0 @@
1
- import { Server } from 'toiljs/server/runtime';
2
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
3
- import { SpinHandler } from './SpinHandler';
4
-
5
- Server.handler = () => {
6
- return new SpinHandler();
7
- };
8
-
9
- export * from 'toiljs/server/runtime/exports';
10
-
11
- export function abort(message: string, fileName: string, line: u32, column: u32): void {
12
- revertOnError(message, fileName, line, column);
13
- }
@@ -1,19 +0,0 @@
1
- // AUTO-GENERATED by toil (edge SSR). Do not edit.
2
- // Route: greeting. Slot ids match the deployed .slots manifest; HASH is the
3
- // coherence hash the host checks against the template (deploy-skew guard).
4
- //
5
- // (For this example the values are hand-fixed; in a real build `template.ts`
6
- // computes them from the route's rendered template.)
7
-
8
- /** Stable hole ids for this route's template. */
9
- export enum Slot {
10
- greeting = 0,
11
- count = 1,
12
- }
13
-
14
- /** Coherence hash (32 bytes) baked into the guest and echoed in every values
15
- * envelope; the host rejects a response whose hash != the deployed template. */
16
- export const HASH: StaticArray<u8> = [
17
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
18
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
19
- ];
@@ -1,18 +0,0 @@
1
- /**
2
- * Edge-SSR example entry. Registers the `/hello` render (side-effect import),
3
- * sets a no-op HTTP handler (so the `handle` export is well-formed), and
4
- * surfaces the wasm exports — including `render(i32, i32) -> i64`.
5
- */
6
- import { Server, ToilHandler } from 'toiljs/server/runtime';
7
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
8
- import './SsrGreetingRender';
9
-
10
- Server.handler = () => {
11
- return new ToilHandler();
12
- };
13
-
14
- export * from 'toiljs/server/runtime/exports';
15
-
16
- export function abort(message: string, fileName: string, line: u32, column: u32): void {
17
- revertOnError(message, fileName, line, column);
18
- }
@@ -1,8 +0,0 @@
1
- import { Server } from 'toiljs/server/runtime';
2
- import { revertOnError } from 'toiljs/server/runtime/abort/abort';
3
- import { FastTrapHandler } from './FastTrapHandler';
4
- Server.handler = () => { return new FastTrapHandler(); };
5
- export * from 'toiljs/server/runtime/exports';
6
- export function abort(message: string, fileName: string, line: u32, column: u32): void {
7
- revertOnError(message, fileName, line, column);
8
- }