toiljs 0.0.34 → 0.0.36
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/CHANGELOG.md +10 -0
- package/README.md +1 -0
- package/as-pect.config.js +8 -2
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +97 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.d.ts +42 -0
- package/build/client/auth.js +179 -0
- package/build/client/index.d.ts +5 -1
- package/build/client/index.js +3 -1
- package/build/client/routing/loader.d.ts +1 -0
- package/build/client/routing/loader.js +37 -0
- package/build/client/routing/mount.js +32 -1
- package/build/client/ssr/markers.d.ts +34 -0
- package/build/client/ssr/markers.js +49 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/docs.js +88 -1
- package/build/compiler/generate.d.ts +2 -0
- package/build/compiler/generate.js +2 -2
- package/build/compiler/index.js +2 -0
- package/build/compiler/ssr-codegen.d.ts +2 -0
- package/build/compiler/ssr-codegen.js +36 -0
- package/build/compiler/template-build.d.ts +29 -0
- package/build/compiler/template-build.js +150 -0
- package/build/compiler/template.d.ts +22 -0
- package/build/compiler/template.js +169 -0
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/crypto.js +15 -0
- package/build/devserver/host.js +1 -0
- package/build/devserver/module.d.ts +1 -0
- package/build/devserver/module.js +23 -1
- package/docs/README.md +56 -0
- package/docs/auth.md +261 -0
- package/docs/caching.md +115 -0
- package/docs/cookies.md +457 -0
- package/docs/crypto.md +130 -0
- package/docs/data.md +131 -0
- package/docs/getting-started.md +128 -0
- package/docs/routing.md +259 -0
- package/docs/rpc.md +149 -0
- package/docs/ssr.md +184 -0
- package/docs/time.md +43 -0
- package/examples/basic/client/routes/auth.tsx +198 -0
- package/examples/basic/client/routes/cookies.tsx +199 -0
- package/examples/basic/client/routes/features/index.tsx +34 -10
- package/examples/basic/client/routes/hello.tsx +43 -0
- package/examples/basic/client/routes/pq.tsx +135 -0
- package/examples/basic/server/AuthTestHandler.ts +15 -0
- package/examples/basic/server/AuthVerifyHandler.ts +23 -0
- package/examples/basic/server/CacheHandler.ts +25 -0
- package/examples/basic/server/DecoCache.ts +18 -0
- package/examples/basic/server/FastTrapHandler.ts +8 -0
- package/examples/basic/server/SpinHandler.ts +18 -0
- package/examples/basic/server/SsrGreetingRender.ts +27 -0
- package/examples/basic/server/authexample-main.ts +8 -0
- package/examples/basic/server/authtest-main.ts +8 -0
- package/examples/basic/server/authverify-main.ts +8 -0
- package/examples/basic/server/cache-main.ts +8 -0
- package/examples/basic/server/core/AppHandler.ts +243 -0
- package/examples/basic/server/deco-main.ts +18 -0
- package/examples/basic/server/main.ts +2 -0
- package/examples/basic/server/routes/Auth.ts +184 -0
- package/examples/basic/server/routes/PqDemo.ts +109 -0
- package/examples/basic/server/routes/Session.ts +73 -0
- package/examples/basic/server/spin-main.ts +13 -0
- package/examples/basic/server/ssr/greeting.slots.ts +19 -0
- package/examples/basic/server/ssr-main.ts +18 -0
- package/examples/basic/server/toil-server-env.d.ts +94 -0
- package/examples/basic/server/trap-main.ts +8 -0
- package/package.json +5 -3
- package/server/globals/auth.ts +281 -0
- package/server/runtime/README.md +61 -0
- package/server/runtime/env/Server.ts +12 -0
- package/server/runtime/exports/index.ts +17 -0
- package/server/runtime/exports/render.ts +51 -0
- package/server/runtime/http/base64.ts +104 -0
- package/server/runtime/http/cookie.ts +416 -0
- package/server/runtime/http/cookies.ts +197 -0
- package/server/runtime/http/date.ts +72 -0
- package/server/runtime/http/percent.ts +76 -0
- package/server/runtime/http/securecookies.ts +224 -0
- package/server/runtime/index.ts +17 -0
- package/server/runtime/request.ts +24 -0
- package/server/runtime/response.ts +29 -0
- package/server/runtime/ssr/Ssr.ts +43 -0
- package/server/runtime/ssr/encode.ts +110 -0
- package/server/runtime/ssr/escape.ts +83 -0
- package/server/runtime/ssr/slots.ts +144 -0
- package/server/runtime/time.ts +29 -0
- package/src/cli/create.ts +105 -0
- package/src/client/auth.ts +322 -0
- package/src/client/index.ts +5 -1
- package/src/client/routing/loader.ts +56 -0
- package/src/client/routing/mount.tsx +37 -1
- package/src/client/ssr/markers.tsx +140 -0
- package/src/compiler/docs.ts +88 -1
- package/src/compiler/generate.ts +2 -2
- package/src/compiler/index.ts +5 -0
- package/src/compiler/ssr-codegen.ts +85 -0
- package/src/compiler/template-build.ts +275 -0
- package/src/compiler/template.ts +265 -0
- package/src/devserver/crypto.ts +23 -0
- package/src/devserver/host.ts +4 -0
- package/src/devserver/module.ts +39 -1
- package/test/assembly/cookie.spec.ts +302 -0
- package/test/assembly/example.spec.ts +5 -1
- package/test/assembly/ssr.spec.ts +94 -0
- package/test/devserver.test.ts +42 -0
- package/test/ssr-render.test.ts +128 -0
- package/test/ssr-template.test.tsx +348 -0
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// template) and is baked into build/client/features/index.html at build time.
|
|
4
4
|
export const metadata: Toil.Metadata = {
|
|
5
5
|
title: 'Features',
|
|
6
|
-
description:
|
|
6
|
+
description:
|
|
7
|
+
'Live demos of every ToilJS feature: routing, data, REST + RPC, post-quantum auth and sessions, cookies, Web Crypto, head/SEO, components, realtime, and binary IO.',
|
|
7
8
|
openGraph: { title: 'Every ToilJS feature, demoed', type: 'website' }
|
|
8
9
|
};
|
|
9
10
|
|
|
@@ -32,7 +33,37 @@ const groups: { heading: string; items: { href: Toil.Href; label: string; note:
|
|
|
32
33
|
href: '/features/actions',
|
|
33
34
|
label: 'Actions + Form',
|
|
34
35
|
note: 'useAction / <Form>, pending state, revalidate'
|
|
35
|
-
}
|
|
36
|
+
},
|
|
37
|
+
{ href: '/io', label: 'Binary IO', note: 'DataWriter / DataReader / FastSet, no import' }
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
heading: 'Server API',
|
|
42
|
+
items: [
|
|
43
|
+
{ href: '/rest', label: 'REST controllers', note: '@rest / @get / @post, typed body + RouteContext' },
|
|
44
|
+
{ href: '/rpc', label: 'Typed RPC', note: '@service / @remote, called as Server.* with no fetch' },
|
|
45
|
+
{ href: '/search', label: 'Search', note: 'server-backed search endpoint' }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
heading: 'Auth and security',
|
|
50
|
+
items: [
|
|
51
|
+
{
|
|
52
|
+
href: '/pq',
|
|
53
|
+
label: 'Post-quantum auth',
|
|
54
|
+
note: 'ML-DSA-44: derive + sign in the browser, edge verifies (crypto.mldsa_verify)'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
href: '/auth',
|
|
58
|
+
label: 'Sessions and @user / @auth',
|
|
59
|
+
note: 'signed session cookie, guarded /session/me, typed getUser()'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
href: '/cookies',
|
|
63
|
+
label: 'Cookies and SecureCookies',
|
|
64
|
+
note: 'Cookie builder + HMAC-signed / AES-GCM cookies, no import'
|
|
65
|
+
},
|
|
66
|
+
{ href: '/crypto', label: 'Web Crypto', note: 'crypto.sha256 / subtle, global, runs in the server wasm' }
|
|
36
67
|
]
|
|
37
68
|
},
|
|
38
69
|
{
|
|
@@ -46,14 +77,7 @@ const groups: { heading: string; items: { href: Toil.Href; label: string; note:
|
|
|
46
77
|
heading: 'Components and runtime',
|
|
47
78
|
items: [
|
|
48
79
|
{ href: '/features/script', label: 'Script', note: 'Toil.Script with a load strategy' },
|
|
49
|
-
{ href: '/features/realtime', label: 'WebSocket channel', note: 'Toil.useChannel against /_toil' }
|
|
50
|
-
{ href: '/io', label: 'Binary IO', note: 'DataWriter / DataReader / FastSet, no import' }
|
|
51
|
-
]
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
heading: 'Server',
|
|
55
|
-
items: [
|
|
56
|
-
{ href: '/crypto', label: 'Web Crypto', note: 'crypto.sha256 / subtle, global, runs in the server wasm' }
|
|
80
|
+
{ href: '/features/realtime', label: 'WebSocket channel', note: 'Toil.useChannel against /_toil' }
|
|
57
81
|
]
|
|
58
82
|
}
|
|
59
83
|
];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An edge-SSR route. `export const ssr = true` opts it into the template
|
|
3
|
+
* extractor: at build time toil renders it once into a template-with-holes
|
|
4
|
+
* (`_ssr/hello.{tmpl,slots}` + a guest `Slot` module); at request time the edge
|
|
5
|
+
* splices the guest's hole values into that template (no per-request render).
|
|
6
|
+
*
|
|
7
|
+
* SSR routes must render under static markup: use the hole markers (`Hole`,
|
|
8
|
+
* `Repeat`, `RawHtml`) and `useLoaderData`, and keep router-hook-dependent or
|
|
9
|
+
* client-only bits inside an `<Island>`.
|
|
10
|
+
*/
|
|
11
|
+
import { Hole, Repeat, useLoaderData } from 'toiljs/client';
|
|
12
|
+
|
|
13
|
+
export const ssr = true;
|
|
14
|
+
|
|
15
|
+
interface HelloData {
|
|
16
|
+
name: string;
|
|
17
|
+
items: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const loader = ({ params }: { params: Record<string, string> }): HelloData => ({
|
|
21
|
+
name: params.name ?? 'world',
|
|
22
|
+
items: ['alpha', 'beta', 'gamma'],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default function Hello(): React.JSX.Element {
|
|
26
|
+
const d = useLoaderData<typeof loader>();
|
|
27
|
+
return (
|
|
28
|
+
<section>
|
|
29
|
+
<h1>
|
|
30
|
+
Hello <Hole id="name">{d.name}</Hole>
|
|
31
|
+
</h1>
|
|
32
|
+
<ul>
|
|
33
|
+
<Repeat id="items" each={d.items}>
|
|
34
|
+
{(s: string) => (
|
|
35
|
+
<li>
|
|
36
|
+
<Hole id="item">{s}</Hole>
|
|
37
|
+
</li>
|
|
38
|
+
)}
|
|
39
|
+
</Repeat>
|
|
40
|
+
</ul>
|
|
41
|
+
</section>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Post-quantum identity demo, challenge-response. The browser fetches a
|
|
2
|
+
// SERVER-issued challenge (a fresh nonce the edge HMAC-signs into a token),
|
|
3
|
+
// derives an ML-DSA-44 keypair from a password (Argon2id, all client-side, the
|
|
4
|
+
// password never leaves), signs the message built from the server's nonce, and
|
|
5
|
+
// POSTs the public key + signature + token to the edge, which re-opens the token
|
|
6
|
+
// (rejecting a forged/expired one) and verifies via `crypto.mldsa_verify`
|
|
7
|
+
// (server/routes/PqDemo.ts). The secret key is wiped right after signing.
|
|
8
|
+
//
|
|
9
|
+
// The nonce is server-chosen and tamper-proof, so a client cannot pre-sign or
|
|
10
|
+
// swap in its own. It still isn't the full production login (no single-use
|
|
11
|
+
// consume -> within the TTL a captured proof could be replayed; that needs a
|
|
12
|
+
// store) -- see Auth.login / server/routes/Auth.ts and docs/auth.md.
|
|
13
|
+
import { useCallback, useState } from 'react';
|
|
14
|
+
|
|
15
|
+
import { Auth, type IdentityProof } from 'toiljs/client';
|
|
16
|
+
|
|
17
|
+
export const metadata: Toil.Metadata = {
|
|
18
|
+
title: 'Post-quantum auth',
|
|
19
|
+
description:
|
|
20
|
+
'ML-DSA-44 (FIPS 204) end-to-end: the browser derives a keypair from a password (Argon2id) and signs; the edge verifies via crypto.mldsa_verify. No secret ever leaves the client.',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type Result = { ok: boolean; status: number; text: string } | { error: string };
|
|
24
|
+
|
|
25
|
+
async function postVerify(envelope: Uint8Array): Promise<Result> {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch('/pq/verify', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
30
|
+
body: envelope as BodyInit,
|
|
31
|
+
});
|
|
32
|
+
return { ok: res.ok, status: res.status, text: (await res.text()).trim() };
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Flip one byte of the signature (last field) so the proof must fail. */
|
|
39
|
+
function tamper(envelope: Uint8Array): Uint8Array {
|
|
40
|
+
const out = envelope.slice();
|
|
41
|
+
if (out.length > 0) out[out.length - 1] ^= 0x01;
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function Pq(): React.JSX.Element {
|
|
46
|
+
const [username, setUsername] = useState('ada');
|
|
47
|
+
const [password, setPassword] = useState('correct horse battery staple');
|
|
48
|
+
const [busy, setBusy] = useState(false);
|
|
49
|
+
const [proof, setProof] = useState<IdentityProof | null>(null);
|
|
50
|
+
const [result, setResult] = useState<Result | null>(null);
|
|
51
|
+
|
|
52
|
+
const prove = useCallback(
|
|
53
|
+
async (doTamper: boolean) => {
|
|
54
|
+
setBusy(true);
|
|
55
|
+
setResult(null);
|
|
56
|
+
try {
|
|
57
|
+
const p = await Auth.proveIdentity(username, password);
|
|
58
|
+
setProof(p);
|
|
59
|
+
setResult(await postVerify(doTamper ? tamper(p.envelope) : p.envelope));
|
|
60
|
+
} catch (e) {
|
|
61
|
+
setResult({ error: e instanceof Error ? e.message : String(e) });
|
|
62
|
+
} finally {
|
|
63
|
+
setBusy(false);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[username, password],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<main style={{ maxWidth: 680 }}>
|
|
71
|
+
<h1>Post-quantum identity</h1>
|
|
72
|
+
<p>
|
|
73
|
+
The edge issues a fresh, HMAC-signed <strong>challenge</strong> (a server-chosen nonce). The browser
|
|
74
|
+
stretches the password with <strong>Argon2id</strong>, expands it into an <strong>ML-DSA-44</strong>{' '}
|
|
75
|
+
(FIPS 204) keypair, and signs the message built from <em>that</em> nonce. Only the public key,
|
|
76
|
+
signature, and the server's token are sent back; the edge re-opens the token and verifies with the{' '}
|
|
77
|
+
<code>crypto.mldsa_verify</code> host import. The password and secret key never leave this tab.
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
<div style={{ display: 'grid', gap: 8, maxWidth: 420 }}>
|
|
81
|
+
<label>
|
|
82
|
+
Username
|
|
83
|
+
<input value={username} onChange={(e) => setUsername(e.target.value)} style={{ width: '100%' }} />
|
|
84
|
+
</label>
|
|
85
|
+
<label>
|
|
86
|
+
Password
|
|
87
|
+
<input
|
|
88
|
+
value={password}
|
|
89
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
90
|
+
style={{ width: '100%' }}
|
|
91
|
+
/>
|
|
92
|
+
</label>
|
|
93
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
94
|
+
<button onClick={() => prove(false)} disabled={busy}>
|
|
95
|
+
{busy ? 'Deriving + signing…' : 'Prove identity'}
|
|
96
|
+
</button>
|
|
97
|
+
<button onClick={() => prove(true)} disabled={busy} title="Flip a signature byte: must fail">
|
|
98
|
+
Tamper, then verify
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{proof && (
|
|
104
|
+
<p style={{ marginTop: 16, fontFamily: 'monospace', fontSize: '0.85rem', opacity: 0.8 }}>
|
|
105
|
+
server nonce {proof.nonceHex}… · Argon2id {proof.deriveMs} ms · public key {proof.publicKeyHex}…
|
|
106
|
+
(1312 B) · signature {proof.signatureLen} B
|
|
107
|
+
</p>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
{result && (
|
|
111
|
+
<p
|
|
112
|
+
style={{
|
|
113
|
+
marginTop: 8,
|
|
114
|
+
fontWeight: 600,
|
|
115
|
+
color: 'error' in result ? '#c0392b' : result.ok ? '#1e8449' : '#c0392b',
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{'error' in result
|
|
119
|
+
? `error: ${result.error}`
|
|
120
|
+
: `POST /pq/verify -> ${result.status}: ${result.text}`}
|
|
121
|
+
</p>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
<p style={{ marginTop: 24, opacity: 0.7, fontSize: '0.9rem' }}>
|
|
125
|
+
The challenge is server-issued and tamper-proof, but stateless, so it has no single-use consume (within
|
|
126
|
+
the TTL a captured proof could be replayed). The full register/login protocol with an atomic
|
|
127
|
+
challenge consume is in <code>server/routes/Auth.ts</code>; sessions and <code>getUser()</code> are on
|
|
128
|
+
the <Toil.Link href="/auth">Auth</Toil.Link> page.
|
|
129
|
+
</p>
|
|
130
|
+
<p>
|
|
131
|
+
<Toil.Link href="/features">Back to features</Toil.Link>
|
|
132
|
+
</p>
|
|
133
|
+
</main>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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);
|
|
@@ -0,0 +1,8 @@
|
|
|
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); }
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
}
|