toiljs 0.0.115 → 0.0.117
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 +16 -7
- package/TYPESCRIPT_LAW.md +1946 -2073
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2375 -258
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.js +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/auth-emails.js +7 -3
- package/build/compiler/emails.js +1 -1
- package/build/compiler/index.d.ts +1 -0
- package/build/compiler/index.js +25 -4
- package/build/compiler/pages.js +2 -3
- package/build/compiler/prerender.d.ts +3 -8
- package/build/compiler/prerender.js +88 -127
- package/build/compiler/ssg.js +3 -4
- package/build/compiler/template-build.js +3 -4
- package/build/compiler/toil-docs.generated.js +6 -5
- package/build/compiler/vite.js +3 -6
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/db/database.js +7 -3
- package/build/devserver/db/types.js +1 -1
- package/build/devserver/production.js +4 -3
- package/build/devserver/runtime/crypto.d.ts +1 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/examples/basic/client/routes/analytics.tsx +7 -5
- package/examples/basic/server/tsconfig.json +1 -3
- package/package.json +45 -44
- package/presets/no-uint8array-tostring.js +120 -153
- package/presets/oxlint.d.ts +5 -0
- package/presets/oxlint.js +224 -0
- package/server/runtime/tsconfig.json +1 -3
- package/tsconfig.json +2 -11
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
- package/.github/changelog-config.json +0 -45
- package/.github/dependabot.yml +0 -27
- package/.github/workflows/ci.yml +0 -222
- package/.vscode/settings.json +0 -10
- package/docs/README.md +0 -107
- package/docs/auth/README.md +0 -104
- package/docs/auth/configuration.md +0 -105
- package/docs/auth/emails.md +0 -92
- package/docs/auth/extending.md +0 -202
- package/docs/auth/how-it-works.md +0 -138
- package/docs/auth/usage.md +0 -188
- package/docs/backend/README.md +0 -143
- package/docs/backend/data.md +0 -351
- package/docs/backend/rest.md +0 -402
- package/docs/backend/rpc.md +0 -226
- package/docs/background/README.md +0 -114
- package/docs/background/daemons.md +0 -276
- package/docs/background/derive.md +0 -179
- package/docs/cli/README.md +0 -379
- package/docs/concepts/ai-guide.md +0 -132
- package/docs/concepts/config.md +0 -416
- package/docs/concepts/decorators.md +0 -127
- package/docs/concepts/security.md +0 -108
- package/docs/concepts/tiers.md +0 -166
- package/docs/concepts/types.md +0 -216
- package/docs/database/README.md +0 -143
- package/docs/database/capacity.md +0 -350
- package/docs/database/counters.md +0 -174
- package/docs/database/documents.md +0 -277
- package/docs/database/events.md +0 -307
- package/docs/database/membership.md +0 -246
- package/docs/database/setup.md +0 -155
- package/docs/database/unique.md +0 -216
- package/docs/database/views.md +0 -246
- package/docs/frontend/README.md +0 -104
- package/docs/frontend/components.md +0 -286
- package/docs/frontend/data-fetching.md +0 -260
- package/docs/frontend/images.md +0 -148
- package/docs/frontend/metadata.md +0 -344
- package/docs/frontend/navigation.md +0 -296
- package/docs/frontend/rendering.md +0 -236
- package/docs/frontend/routing.md +0 -346
- package/docs/frontend/scripts.md +0 -118
- package/docs/frontend/search.md +0 -191
- package/docs/frontend/styling.md +0 -147
- package/docs/frontend/toil-global.md +0 -216
- package/docs/getting-started/README.md +0 -81
- package/docs/getting-started/create-project.md +0 -131
- package/docs/getting-started/deploy.md +0 -101
- package/docs/getting-started/first-app.md +0 -215
- package/docs/getting-started/installation.md +0 -124
- package/docs/getting-started/migrating.md +0 -125
- package/docs/getting-started/project-structure.md +0 -163
- package/docs/introduction/README.md +0 -66
- package/docs/introduction/design-principles.md +0 -81
- package/docs/introduction/distributed.md +0 -91
- package/docs/introduction/how-it-works.md +0 -121
- package/docs/introduction/hyperscale.md +0 -70
- package/docs/introduction/modern-stack.md +0 -85
- package/docs/introduction/vs-other-frameworks.md +0 -64
- package/docs/introduction/why-toil.md +0 -99
- package/docs/llms.txt +0 -95
- package/docs/realtime/README.md +0 -102
- package/docs/realtime/channels.md +0 -211
- package/docs/realtime/streams.md +0 -369
- package/docs/services/README.md +0 -60
- package/docs/services/analytics.md +0 -279
- package/docs/services/caching.md +0 -175
- package/docs/services/cookies.md +0 -235
- package/docs/services/crypto.md +0 -209
- package/docs/services/email.md +0 -289
- package/docs/services/environment.md +0 -141
- package/docs/services/ratelimit.md +0 -174
- package/docs/services/time.md +0 -85
- package/eslint.config.js +0 -57
- package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
- package/presets/eslint.js +0 -88
- package/src/backend/index.ts +0 -204
- package/src/cli/configure.ts +0 -363
- package/src/cli/create.ts +0 -924
- package/src/cli/db.ts +0 -158
- package/src/cli/diagnostics.ts +0 -665
- package/src/cli/doctor.ts +0 -982
- package/src/cli/features.ts +0 -170
- package/src/cli/index.ts +0 -326
- package/src/cli/notify.ts +0 -120
- package/src/cli/proc.ts +0 -56
- package/src/cli/ui.ts +0 -169
- package/src/cli/update.ts +0 -248
- package/src/cli/updates.ts +0 -92
- package/src/cli/validate.ts +0 -31
- package/src/cli/version-check.ts +0 -125
- package/src/client/auth.ts +0 -869
- package/src/client/channel/channel.ts +0 -146
- package/src/client/components/Form.tsx +0 -65
- package/src/client/components/Image.tsx +0 -160
- package/src/client/components/Script.tsx +0 -113
- package/src/client/components/Slot.tsx +0 -21
- package/src/client/dev/devtools.tsx +0 -1089
- package/src/client/dev/error-overlay.tsx +0 -225
- package/src/client/errors.ts +0 -11
- package/src/client/head/head.ts +0 -182
- package/src/client/head/metadata.ts +0 -110
- package/src/client/index.ts +0 -130
- package/src/client/navigation/Link.tsx +0 -108
- package/src/client/navigation/NavLink.tsx +0 -86
- package/src/client/navigation/navigation.ts +0 -235
- package/src/client/navigation/prefetch.ts +0 -169
- package/src/client/navigation/scroll.ts +0 -53
- package/src/client/routing/Router.tsx +0 -216
- package/src/client/routing/action.ts +0 -122
- package/src/client/routing/error-boundary.tsx +0 -43
- package/src/client/routing/hooks.ts +0 -140
- package/src/client/routing/lazy.ts +0 -77
- package/src/client/routing/loader.ts +0 -381
- package/src/client/routing/match.ts +0 -47
- package/src/client/routing/mount.tsx +0 -90
- package/src/client/routing/params-context.ts +0 -10
- package/src/client/routing/slot-context.ts +0 -7
- package/src/client/rpc.ts +0 -97
- package/src/client/search/search.ts +0 -189
- package/src/client/search/use-page-search.ts +0 -73
- package/src/client/ssr/markers.tsx +0 -172
- package/src/client/stream/client.ts +0 -237
- package/src/client/types.ts +0 -73
- package/src/compiler/auth-emails.ts +0 -312
- package/src/compiler/config.ts +0 -369
- package/src/compiler/docs.ts +0 -127
- package/src/compiler/email-preview.ts +0 -314
- package/src/compiler/emails.ts +0 -404
- package/src/compiler/fonts.ts +0 -89
- package/src/compiler/generate.ts +0 -471
- package/src/compiler/image-blur.ts +0 -42
- package/src/compiler/image-report.ts +0 -88
- package/src/compiler/index.ts +0 -1362
- package/src/compiler/pages.ts +0 -49
- package/src/compiler/plugin.ts +0 -391
- package/src/compiler/prerender.ts +0 -263
- package/src/compiler/routes.ts +0 -170
- package/src/compiler/seo.ts +0 -415
- package/src/compiler/sri.ts +0 -144
- package/src/compiler/ssg.ts +0 -177
- package/src/compiler/ssr-codegen.ts +0 -85
- package/src/compiler/template-build.ts +0 -676
- package/src/compiler/template.ts +0 -262
- package/src/compiler/toil-docs.generated.ts +0 -77
- package/src/compiler/vite.ts +0 -245
- package/src/devserver/analytics/index.ts +0 -317
- package/src/devserver/config/dotenv.ts +0 -102
- package/src/devserver/config/env.ts +0 -30
- package/src/devserver/config/ratelimit.ts +0 -123
- package/src/devserver/daemon/catalog.ts +0 -120
- package/src/devserver/daemon/cron.ts +0 -87
- package/src/devserver/daemon/host.ts +0 -134
- package/src/devserver/daemon/index.ts +0 -369
- package/src/devserver/daemon/runtime.ts +0 -48
- package/src/devserver/db/catalog.ts +0 -109
- package/src/devserver/db/database.ts +0 -1834
- package/src/devserver/db/derives.ts +0 -121
- package/src/devserver/db/index.ts +0 -20
- package/src/devserver/db/routeKinds.ts +0 -191
- package/src/devserver/db/types.ts +0 -168
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +0 -141
- package/src/devserver/email/console.ts +0 -348
- package/src/devserver/email/index.ts +0 -155
- package/src/devserver/email/providers.ts +0 -131
- package/src/devserver/email/status.ts +0 -23
- package/src/devserver/email/validate.ts +0 -37
- package/src/devserver/email/wire.ts +0 -55
- package/src/devserver/http/cache.ts +0 -0
- package/src/devserver/http/envelope.ts +0 -157
- package/src/devserver/http/proxy.ts +0 -172
- package/src/devserver/http/runtime.ts +0 -387
- package/src/devserver/index.ts +0 -43
- package/src/devserver/production-ipc.ts +0 -63
- package/src/devserver/production-worker.ts +0 -83
- package/src/devserver/production.ts +0 -718
- package/src/devserver/runtime/crypto.ts +0 -587
- package/src/devserver/runtime/host.ts +0 -427
- package/src/devserver/runtime/module.ts +0 -442
- package/src/devserver/server.ts +0 -251
- package/src/devserver/ssr.ts +0 -208
- package/src/devserver/stream/catalog.ts +0 -106
- package/src/devserver/stream/host.ts +0 -42
- package/src/devserver/stream/index.ts +0 -301
- package/src/devserver/stream/manager.ts +0 -167
- package/src/devserver/stream/router.ts +0 -101
- package/src/devserver/stream/wire.ts +0 -58
- package/src/devserver/stream/ws.ts +0 -78
- package/src/devserver/wasm/sections.ts +0 -59
- package/src/devserver/wasm/surface.ts +0 -115
- package/src/io/FastMap.ts +0 -151
- package/src/io/FastSet.ts +0 -113
- package/src/io/codec.ts +0 -362
- package/src/io/index.ts +0 -10
- package/src/io/lengths.ts +0 -14
- package/src/io/types.ts +0 -19
- package/src/logger/index.ts +0 -22
- package/src/shared/index.ts +0 -46
- package/test/analytics-dev.test.ts +0 -113
- package/test/assembly/aspect-shim.ts +0 -33
- package/test/assembly/cookie.spec.ts +0 -303
- package/test/assembly/example.spec.ts +0 -22
- package/test/assembly/ssr.spec.ts +0 -95
- package/test/assembly.test.ts +0 -59
- package/test/auth-emails.test.ts +0 -118
- package/test/built-ssr.test.ts +0 -148
- package/test/channel.test.ts +0 -21
- package/test/config-host-port.test.ts +0 -59
- package/test/configure.test.ts +0 -94
- package/test/daemon-build.test.ts +0 -278
- package/test/daemon-catalog.test.ts +0 -349
- package/test/daemon-emulation.test.ts +0 -256
- package/test/db.test.ts +0 -0
- package/test/devserver-database.test.ts +0 -1102
- package/test/devserver-email.test.ts +0 -306
- package/test/devserver-pqauth.test.ts +0 -93
- package/test/devserver-secrets.test.ts +0 -63
- package/test/devserver.test.ts +0 -259
- package/test/doctor.test.ts +0 -298
- package/test/dom/Image.test.tsx +0 -144
- package/test/dom/Link.test.tsx +0 -67
- package/test/dom/NavLink.test.tsx +0 -37
- package/test/dom/Script.test.tsx +0 -48
- package/test/dom/action.test.tsx +0 -146
- package/test/dom/error-overlay.test.tsx +0 -44
- package/test/dom/loader.test.tsx +0 -121
- package/test/dom/navigation.test.ts +0 -59
- package/test/dom/revalidate.test.tsx +0 -38
- package/test/dom/route-head.test.tsx +0 -56
- package/test/dom/router-loading.test.tsx +0 -44
- package/test/dom/scroll.test.ts +0 -56
- package/test/dom/slot.test.tsx +0 -131
- package/test/dom/use-metadata.test.tsx +0 -58
- package/test/dom/view-transitions.test.tsx +0 -53
- package/test/dynamic-routing.test.ts +0 -132
- package/test/email-console.test.ts +0 -86
- package/test/email-preview.test.ts +0 -73
- package/test/emails.test.ts +0 -58
- package/test/errors.test.ts +0 -21
- package/test/example-guestbook.test.ts +0 -123
- package/test/features.test.ts +0 -149
- package/test/fixtures/bignum-wire/spec.ts +0 -25
- package/test/fixtures/daemon-app.ts +0 -65
- package/test/fixtures/stream-echo.ts +0 -26
- package/test/fixtures/stream-gate.ts +0 -28
- package/test/fixtures/stream-trap.ts +0 -18
- package/test/fixtures/stream-typed.ts +0 -41
- package/test/fonts.test.ts +0 -28
- package/test/global-setup.ts +0 -17
- package/test/head.test.ts +0 -42
- package/test/image-blur.test.ts +0 -50
- package/test/io.test.ts +0 -117
- package/test/metadata.test.ts +0 -40
- package/test/navlink.test.ts +0 -28
- package/test/pages.test.ts +0 -105
- package/test/placeholder.test.ts +0 -9
- package/test/pqauth-e2e.test.ts +0 -216
- package/test/pqauth-email.test.ts +0 -801
- package/test/prerender.test.ts +0 -116
- package/test/prettier-plugin.test.ts +0 -46
- package/test/routes.test.ts +0 -76
- package/test/rpc-bignum-wire.test.ts +0 -164
- package/test/rpc-dispatch.test.ts +0 -132
- package/test/rpc-kinds.test.ts +0 -18
- package/test/rpc.test.ts +0 -66
- package/test/search.test.ts +0 -114
- package/test/seo.test.ts +0 -175
- package/test/slot-layouts.test.ts +0 -69
- package/test/sri.test.ts +0 -129
- package/test/ssg.test.ts +0 -36
- package/test/ssr-hydration.test.tsx +0 -122
- package/test/ssr-render.test.ts +0 -197
- package/test/ssr-template.test.tsx +0 -482
- package/test/stream-emulation.test.ts +0 -479
- package/test/ui.test.ts +0 -40
- package/test/update.test.ts +0 -58
- package/test/validate.test.ts +0 -42
- package/test/version-check.test.ts +0 -130
- package/tests/data-parity/generated-parity.ts +0 -99
- package/tests/data-parity/parity.ts +0 -80
- package/tests/data-parity/spec.ts +0 -46
package/test/global-setup.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Vitest global setup: generate `src/compiler/toil-docs.generated.ts` before the
|
|
7
|
-
* suite runs. `src/compiler/docs.ts` imports that module, but it is gitignored
|
|
8
|
-
* and only (re)created by the build's `gen:docs` step, so a fresh checkout that
|
|
9
|
-
* runs the tests without a prior build would otherwise fail to resolve it
|
|
10
|
-
* ("Cannot find module './toil-docs.generated.js'"). Running the same generator
|
|
11
|
-
* the npm scripts use keeps the tests self-contained. Runs once per vitest
|
|
12
|
-
* invocation, so it also covers `npx vitest` / watch mode.
|
|
13
|
-
*/
|
|
14
|
-
export default function setup(): void {
|
|
15
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
-
execFileSync('node', ['scripts/gen-toil-docs.mjs'], { cwd: root, stdio: 'ignore' });
|
|
17
|
-
}
|
package/test/head.test.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { mergeHead } from '../src/client/head/head';
|
|
4
|
-
|
|
5
|
-
describe('mergeHead', () => {
|
|
6
|
-
it('takes the last title', () => {
|
|
7
|
-
expect(mergeHead([{ title: 'Home' }]).title).toBe('Home');
|
|
8
|
-
expect(mergeHead([{ title: 'A' }, { title: 'B' }]).title).toBe('B');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('leaves title undefined when nothing sets it', () => {
|
|
12
|
-
expect(mergeHead([{ meta: [{ name: 'x', content: 'y' }] }]).title).toBeUndefined();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('dedupes meta by name/property, last wins', () => {
|
|
16
|
-
const resolved = mergeHead([
|
|
17
|
-
{ meta: [{ name: 'description', content: 'old' }] },
|
|
18
|
-
{
|
|
19
|
-
meta: [
|
|
20
|
-
{ name: 'description', content: 'new' },
|
|
21
|
-
{ property: 'og:title', content: 'T' },
|
|
22
|
-
],
|
|
23
|
-
},
|
|
24
|
-
]);
|
|
25
|
-
expect(resolved.meta).toHaveLength(2);
|
|
26
|
-
expect(resolved.meta.find((m) => m.name === 'description')?.content).toBe('new');
|
|
27
|
-
expect(resolved.meta.find((m) => m.property === 'og:title')?.content).toBe('T');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('dedupes links by rel+href', () => {
|
|
31
|
-
const resolved = mergeHead([
|
|
32
|
-
{ link: [{ rel: 'icon', href: '/a.svg' }] },
|
|
33
|
-
{
|
|
34
|
-
link: [
|
|
35
|
-
{ rel: 'icon', href: '/a.svg' },
|
|
36
|
-
{ rel: 'canonical', href: '/x' },
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
]);
|
|
40
|
-
expect(resolved.link).toHaveLength(2);
|
|
41
|
-
});
|
|
42
|
-
});
|
package/test/image-blur.test.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
import sharp from 'sharp';
|
|
6
|
-
import { describe, expect, it } from 'vitest';
|
|
7
|
-
|
|
8
|
-
import { imageBlurPlugin } from '../src/compiler/image-blur';
|
|
9
|
-
|
|
10
|
-
/** Call the plugin's `load` hook directly (it uses no Rollup `this` context). */
|
|
11
|
-
function loadHook(): (id: string) => Promise<string | undefined> {
|
|
12
|
-
const plugin = imageBlurPlugin();
|
|
13
|
-
return plugin.load as unknown as (id: string) => Promise<string | undefined>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('imageBlurPlugin', () => {
|
|
17
|
-
it('emits a { src, width, height, blurDataURL } module for a ?toil image import', async () => {
|
|
18
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'toil-blur-'));
|
|
19
|
-
const file = path.join(dir, 'pic.png');
|
|
20
|
-
await sharp({
|
|
21
|
-
create: { width: 40, height: 30, channels: 3, background: { r: 200, g: 100, b: 50 } },
|
|
22
|
-
})
|
|
23
|
-
.png()
|
|
24
|
-
.toFile(file);
|
|
25
|
-
try {
|
|
26
|
-
const load = loadHook();
|
|
27
|
-
const out = await load(`${file}?toil`);
|
|
28
|
-
expect(typeof out).toBe('string');
|
|
29
|
-
// re-imports the bare asset for src (so Vite/imagetools still optimize + hash it)
|
|
30
|
-
expect(out).toContain(`import src from ${JSON.stringify(file)}`);
|
|
31
|
-
// carries the intrinsic size for the aspect-ratio
|
|
32
|
-
expect(out).toContain('width: 40');
|
|
33
|
-
expect(out).toContain('height: 30');
|
|
34
|
-
// a real inlined LQIP, not a placeholder string
|
|
35
|
-
expect(out).toContain('blurDataURL: "data:image/webp;base64,');
|
|
36
|
-
const b64 = /base64,([^"]+)"/.exec(out as string)?.[1] ?? '';
|
|
37
|
-
expect(b64.length).toBeGreaterThan(20);
|
|
38
|
-
} finally {
|
|
39
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('ignores imports without the ?toil flag', async () => {
|
|
44
|
-
const load = loadHook();
|
|
45
|
-
expect(await load('/some/pic.png')).toBeUndefined();
|
|
46
|
-
expect(await load('/some/pic.png?w=400')).toBeUndefined();
|
|
47
|
-
// non-raster ?toil (e.g. svg) is skipped too
|
|
48
|
-
expect(await load('/some/icon.svg?toil')).toBeUndefined();
|
|
49
|
-
});
|
|
50
|
-
});
|
package/test/io.test.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { DataReader, DataWriter } from '../src/io/codec';
|
|
4
|
-
import { FastMap } from '../src/io/FastMap';
|
|
5
|
-
import { FastSet } from '../src/io/FastSet';
|
|
6
|
-
|
|
7
|
-
describe('DataWriter / DataReader', () => {
|
|
8
|
-
it('round-trips fixed-width integers', () => {
|
|
9
|
-
const w = new DataWriter();
|
|
10
|
-
w.writeU8(255).writeU16(65535).writeU32(4294967295).writeU64(18446744073709551615n);
|
|
11
|
-
w.writeI8(-128).writeI16(-32768).writeI32(-2147483648).writeI64(-9223372036854775808n);
|
|
12
|
-
|
|
13
|
-
const r = new DataReader(w.toBytes());
|
|
14
|
-
expect(r.readU8()).toBe(255);
|
|
15
|
-
expect(r.readU16()).toBe(65535);
|
|
16
|
-
expect(r.readU32()).toBe(4294967295);
|
|
17
|
-
expect(r.readU64()).toBe(18446744073709551615n);
|
|
18
|
-
expect(r.readI8()).toBe(-128);
|
|
19
|
-
expect(r.readI16()).toBe(-32768);
|
|
20
|
-
expect(r.readI32()).toBe(-2147483648);
|
|
21
|
-
expect(r.readI64()).toBe(-9223372036854775808n);
|
|
22
|
-
expect(r.ok).toBe(true);
|
|
23
|
-
expect(r.remaining()).toBe(0);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('round-trips floats, bool, bytes and strings', () => {
|
|
27
|
-
const w = new DataWriter();
|
|
28
|
-
w.writeF32(0.5).writeF64(3.141592653589793).writeBool(true).writeBool(false);
|
|
29
|
-
w.writeBytes(new Uint8Array([1, 2, 3, 0, 255]));
|
|
30
|
-
w.writeString('hello toil 🛠');
|
|
31
|
-
|
|
32
|
-
const r = new DataReader(w.toBytes());
|
|
33
|
-
expect(r.readF32()).toBe(0.5);
|
|
34
|
-
expect(r.readF64()).toBe(3.141592653589793);
|
|
35
|
-
expect(r.readBool()).toBe(true);
|
|
36
|
-
expect(r.readBool()).toBe(false);
|
|
37
|
-
expect([...r.readBytes()]).toEqual([1, 2, 3, 0, 255]);
|
|
38
|
-
expect(r.readString()).toBe('hello toil 🛠');
|
|
39
|
-
expect(r.ok).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('round-trips u128 / i128 / u256 / i256', () => {
|
|
43
|
-
const u = 123456789012345678901234567890n;
|
|
44
|
-
const big256 = (2n ** 256n) - 1n;
|
|
45
|
-
const w = new DataWriter();
|
|
46
|
-
w.writeU128(u).writeI128(-1234567890123456789n).writeU256(big256).writeI256(-(2n ** 200n));
|
|
47
|
-
|
|
48
|
-
const r = new DataReader(w.toBytes());
|
|
49
|
-
expect(r.readU128()).toBe(u);
|
|
50
|
-
expect(r.readI128()).toBe(-1234567890123456789n);
|
|
51
|
-
expect(r.readU256()).toBe(big256);
|
|
52
|
-
expect(r.readI256()).toBe(-(2n ** 200n));
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('grows past the initial capacity without corruption (regression)', () => {
|
|
56
|
-
// The default buffer is 64 bytes; this forces several reallocations.
|
|
57
|
-
const w = new DataWriter();
|
|
58
|
-
const text = 'z'.repeat(500);
|
|
59
|
-
for (let i = 0; i < 40; i++) w.writeU64(BigInt(i)); // 320 bytes
|
|
60
|
-
w.writeString(text);
|
|
61
|
-
|
|
62
|
-
const r = new DataReader(w.toBytes());
|
|
63
|
-
for (let i = 0; i < 40; i++) expect(r.readU64()).toBe(BigInt(i));
|
|
64
|
-
expect(r.readString()).toBe(text);
|
|
65
|
-
expect(r.ok).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('round-trips big-endian when be is set, and be flips byte order', () => {
|
|
69
|
-
expect([...new DataWriter().writeU32(0x01020304).toBytes()]).toEqual([4, 3, 2, 1]);
|
|
70
|
-
expect([...new DataWriter().writeU32(0x01020304, true).toBytes()]).toEqual([1, 2, 3, 4]);
|
|
71
|
-
|
|
72
|
-
const w = new DataWriter();
|
|
73
|
-
w.writeU16(0xabcd, true).writeI32(-2, true).writeU64(0xdeadbeefn, true).writeU128(123456789012345n, true);
|
|
74
|
-
const r = new DataReader(w.toBytes());
|
|
75
|
-
expect(r.readU16(true)).toBe(0xabcd);
|
|
76
|
-
expect(r.readI32(true)).toBe(-2);
|
|
77
|
-
expect(r.readU64(true)).toBe(0xdeadbeefn);
|
|
78
|
-
expect(r.readU128(true)).toBe(123456789012345n);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('is little-endian and masks instead of throwing', () => {
|
|
82
|
-
// u32 1 → 01 00 00 00 (LE); writeU8 masks to a byte, no throw.
|
|
83
|
-
const bytes = new DataWriter().writeU32(1).writeU8(256).toBytes();
|
|
84
|
-
expect([...bytes]).toEqual([1, 0, 0, 0, 0]);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('reports ok=false when reading past the end (no throw)', () => {
|
|
88
|
-
const r = new DataReader(new Uint8Array(2));
|
|
89
|
-
expect(r.readU32()).toBe(0);
|
|
90
|
-
expect(r.ok).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('FastMap', () => {
|
|
95
|
-
it('supports bigint keys and basic ops', () => {
|
|
96
|
-
const m = new FastMap<bigint, string>();
|
|
97
|
-
m.set(1n, 'one').set(2n, 'two');
|
|
98
|
-
expect(m.size).toBe(2);
|
|
99
|
-
expect(m.get(1n)).toBe('one');
|
|
100
|
-
expect(m.has(2n)).toBe(true);
|
|
101
|
-
expect(m.delete(1n)).toBe(true);
|
|
102
|
-
expect(m.has(1n)).toBe(false);
|
|
103
|
-
expect([...m.entries()]).toEqual([[2n, 'two']]);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('FastSet', () => {
|
|
108
|
-
it('dedupes and preserves insertion order', () => {
|
|
109
|
-
const s = new FastSet<bigint>();
|
|
110
|
-
s.add(2n).add(1n).add(2n);
|
|
111
|
-
expect(s.size).toBe(2);
|
|
112
|
-
expect(s.has(1n)).toBe(true);
|
|
113
|
-
expect([...s.values()]).toEqual([2n, 1n]);
|
|
114
|
-
expect(s.delete(2n)).toBe(true);
|
|
115
|
-
expect([...s.values()]).toEqual([1n]);
|
|
116
|
-
});
|
|
117
|
-
});
|
package/test/metadata.test.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { resolveMetadata } from '../src/client/head/metadata';
|
|
4
|
-
|
|
5
|
-
describe('resolveMetadata', () => {
|
|
6
|
-
it('expands convenience fields into meta/link tags', () => {
|
|
7
|
-
const head = resolveMetadata({
|
|
8
|
-
title: 'About',
|
|
9
|
-
description: 'desc',
|
|
10
|
-
keywords: ['a', 'b'],
|
|
11
|
-
robots: 'noindex',
|
|
12
|
-
themeColor: '#000',
|
|
13
|
-
canonical: 'https://x.test/about',
|
|
14
|
-
openGraph: { title: 'OG', type: 'website', image: 'https://x.test/og.png' },
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
expect(head.title).toBe('About');
|
|
18
|
-
const byName = (name: string) => head.meta?.find((m) => m.name === name)?.content;
|
|
19
|
-
const byProp = (property: string) =>
|
|
20
|
-
head.meta?.find((m) => m.property === property)?.content;
|
|
21
|
-
expect(byName('description')).toBe('desc');
|
|
22
|
-
expect(byName('keywords')).toBe('a, b');
|
|
23
|
-
expect(byName('robots')).toBe('noindex');
|
|
24
|
-
expect(byName('theme-color')).toBe('#000');
|
|
25
|
-
expect(byProp('og:title')).toBe('OG');
|
|
26
|
-
expect(byProp('og:type')).toBe('website');
|
|
27
|
-
expect(byProp('og:image')).toBe('https://x.test/og.png');
|
|
28
|
-
expect(head.link?.find((l) => l.rel === 'canonical')?.href).toBe('https://x.test/about');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('passes through raw meta/link and omits unset fields', () => {
|
|
32
|
-
const head = resolveMetadata({
|
|
33
|
-
title: 'X',
|
|
34
|
-
meta: [{ name: 'author', content: 'me' }],
|
|
35
|
-
link: [{ rel: 'alternate', href: '/rss' }],
|
|
36
|
-
});
|
|
37
|
-
expect(head.meta).toEqual([{ name: 'author', content: 'me' }]);
|
|
38
|
-
expect(head.link).toEqual([{ rel: 'alternate', href: '/rss' }]);
|
|
39
|
-
});
|
|
40
|
-
});
|
package/test/navlink.test.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { matchActive } from '../src/client/navigation/NavLink';
|
|
4
|
-
|
|
5
|
-
describe('matchActive', () => {
|
|
6
|
-
it('matches exact paths', () => {
|
|
7
|
-
expect(matchActive('/about', '/about', false)).toBe(true);
|
|
8
|
-
expect(matchActive('/about', '/about/', false)).toBe(true);
|
|
9
|
-
expect(matchActive('/about', '/contact', false)).toBe(false);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('matches sub-paths when not exact (end=false)', () => {
|
|
13
|
-
expect(matchActive('/blog', '/blog/42', false)).toBe(true);
|
|
14
|
-
expect(matchActive('/blog', '/blog', false)).toBe(true);
|
|
15
|
-
expect(matchActive('/blog', '/blogger', false)).toBe(false);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('honors end for exact-only matching', () => {
|
|
19
|
-
expect(matchActive('/blog', '/blog/42', true)).toBe(false);
|
|
20
|
-
expect(matchActive('/blog', '/blog', true)).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('treats "/" as active everywhere unless end', () => {
|
|
24
|
-
expect(matchActive('/', '/anything/deep', false)).toBe(true);
|
|
25
|
-
expect(matchActive('/', '/anything', true)).toBe(false);
|
|
26
|
-
expect(matchActive('/', '/', true)).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
});
|
package/test/pages.test.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
-
|
|
7
|
-
import { buildPageIndex, pagesModuleSource } from '../src/compiler/pages';
|
|
8
|
-
import type { ScannedRoute } from '../src/compiler/routes';
|
|
9
|
-
|
|
10
|
-
const dirs: string[] = [];
|
|
11
|
-
/** Writes a throwaway routes dir with the given `{ relPath: source }` files, returns its path. */
|
|
12
|
-
function tmpRoutes(files: Record<string, string>): string {
|
|
13
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'toil-pages-'));
|
|
14
|
-
dirs.push(dir);
|
|
15
|
-
for (const [rel, src] of Object.entries(files)) {
|
|
16
|
-
const full = path.join(dir, rel);
|
|
17
|
-
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
18
|
-
fs.writeFileSync(full, src);
|
|
19
|
-
}
|
|
20
|
-
return dir;
|
|
21
|
-
}
|
|
22
|
-
function route(
|
|
23
|
-
dir: string,
|
|
24
|
-
file: string,
|
|
25
|
-
pattern: string,
|
|
26
|
-
extra: Partial<ScannedRoute> = {},
|
|
27
|
-
): ScannedRoute {
|
|
28
|
-
return { file: path.join(dir, file), pattern, ...extra };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
for (const d of dirs.splice(0)) fs.rmSync(d, { recursive: true, force: true });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('buildPageIndex', () => {
|
|
36
|
-
it('extracts each page metadata and flags dynamic routes', () => {
|
|
37
|
-
const dir = tmpRoutes({
|
|
38
|
-
'index.tsx': `export const metadata = { title: 'Home' };\nexport default () => null;\n`,
|
|
39
|
-
'blog/[id].tsx': `export const metadata = { title: 'Post' };\nexport default () => null;\n`,
|
|
40
|
-
});
|
|
41
|
-
const pages = buildPageIndex(process.cwd(), [
|
|
42
|
-
route(dir, 'index.tsx', '/'),
|
|
43
|
-
route(dir, 'blog/[id].tsx', '/blog/:id'),
|
|
44
|
-
]);
|
|
45
|
-
expect(pages).toEqual([
|
|
46
|
-
{ path: '/', dynamic: false, metadata: { title: 'Home' } },
|
|
47
|
-
{ path: '/blog/:id', dynamic: true, metadata: { title: 'Post' } },
|
|
48
|
-
]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('merges searchHints over static metadata (hints win) for dynamic discoverability', () => {
|
|
52
|
-
const dir = tmpRoutes({
|
|
53
|
-
'blog/[id].tsx':
|
|
54
|
-
`export const searchHints = { title: 'Blog', keywords: ['posts', 'articles'] };\n` +
|
|
55
|
-
`export const generateMetadata = ({ params }) => ({ title: params.id });\n` +
|
|
56
|
-
`export default () => null;\n`,
|
|
57
|
-
});
|
|
58
|
-
const [page] = buildPageIndex(process.cwd(), [route(dir, 'blog/[id].tsx', '/blog/:id')]);
|
|
59
|
-
expect(page).toEqual({
|
|
60
|
-
path: '/blog/:id',
|
|
61
|
-
dynamic: true,
|
|
62
|
-
metadata: { title: 'Blog', keywords: ['posts', 'articles'] },
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('uses an empty metadata object when a route declares none', () => {
|
|
67
|
-
const dir = tmpRoutes({ 'about.tsx': `export default () => null;\n` });
|
|
68
|
-
const pages = buildPageIndex(process.cwd(), [route(dir, 'about.tsx', '/about')]);
|
|
69
|
-
expect(pages).toEqual([{ path: '/about', dynamic: false, metadata: {} }]);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('excludes slots and intercepting routes, and dedupes patterns', () => {
|
|
73
|
-
const dir = tmpRoutes({
|
|
74
|
-
'index.tsx': `export const metadata = { title: 'Home' };\nexport default () => null;\n`,
|
|
75
|
-
'@modal/photo.tsx': `export default () => null;\n`,
|
|
76
|
-
'(.)photo.tsx': `export default () => null;\n`,
|
|
77
|
-
});
|
|
78
|
-
const pages = buildPageIndex(process.cwd(), [
|
|
79
|
-
route(dir, 'index.tsx', '/'),
|
|
80
|
-
route(dir, '@modal/photo.tsx', '/photo', { slot: 'modal' }),
|
|
81
|
-
route(dir, '(.)photo.tsx', '/photo', { intercept: true }),
|
|
82
|
-
]);
|
|
83
|
-
expect(pages.map((p) => p.path)).toEqual(['/']);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('sorts the index by path for deterministic output', () => {
|
|
87
|
-
const dir = tmpRoutes({
|
|
88
|
-
'zed.tsx': `export default () => null;\n`,
|
|
89
|
-
'about.tsx': `export default () => null;\n`,
|
|
90
|
-
});
|
|
91
|
-
const pages = buildPageIndex(process.cwd(), [
|
|
92
|
-
route(dir, 'zed.tsx', '/zed'),
|
|
93
|
-
route(dir, 'about.tsx', '/about'),
|
|
94
|
-
]);
|
|
95
|
-
expect(pages.map((p) => p.path)).toEqual(['/about', '/zed']);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('pagesModuleSource', () => {
|
|
100
|
-
it('emits a typed pages export with JSON-serialized entries', () => {
|
|
101
|
-
const src = pagesModuleSource([{ path: '/', dynamic: false, metadata: { title: 'Home' } }]);
|
|
102
|
-
expect(src).toContain('export const pages: PageMeta[] = [');
|
|
103
|
-
expect(src).toContain('{"path":"/","dynamic":false,"metadata":{"title":"Home"}}');
|
|
104
|
-
});
|
|
105
|
-
});
|
package/test/placeholder.test.ts
DELETED
package/test/pqauth-e2e.test.ts
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-end post-quantum auth: drives the REAL browser client (`src/client/auth.ts`
|
|
3
|
-
* — OPRF blind/finalize, Argon2id, ML-DSA keygen/sign, ML-KEM encapsulate,
|
|
4
|
-
* mutual-auth confirm) against the toilscript-compiled example server wasm
|
|
5
|
-
* (`examples/basic` Auth route + the AuthService global), through the dev-server
|
|
6
|
-
* host imports (OPRF + ML-KEM mocks + the ToilDB emulator). A `fetch` shim routes
|
|
7
|
-
* the client's requests into `WasmServerModule.dispatch`, and the in-process
|
|
8
|
-
* ToilDB store persists across dispatches so register -> login spans "requests".
|
|
9
|
-
*
|
|
10
|
-
* This is the full chain interop gate: if the noble client and the
|
|
11
|
-
* voprf/fips203-equivalent dev mocks + the AS AuthService disagree on a single
|
|
12
|
-
* byte, register or login fails here.
|
|
13
|
-
*/
|
|
14
|
-
import fs from 'node:fs';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
|
|
18
|
-
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
|
19
|
-
|
|
20
|
-
import { ristretto255_oprf } from '@noble/curves/ed25519.js';
|
|
21
|
-
|
|
22
|
-
import { WasmServerModule } from '../src/devserver/index.js';
|
|
23
|
-
import { __resetDbForTests } from '../src/devserver/db/index.js';
|
|
24
|
-
import { __resetRatelimitForTests } from '../src/devserver/config/ratelimit.js';
|
|
25
|
-
import { Auth } from '../src/client/auth.js';
|
|
26
|
-
import { DataReader, DataWriter } from '../src/io/codec.js';
|
|
27
|
-
|
|
28
|
-
const EXAMPLE_WASM = path.resolve(
|
|
29
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
30
|
-
'../examples/basic/build/server/release.wasm',
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const haveWasm = fs.existsSync(EXAMPLE_WASM);
|
|
34
|
-
|
|
35
|
-
function loadModule(): WasmServerModule {
|
|
36
|
-
const m = new WasmServerModule(EXAMPLE_WASM);
|
|
37
|
-
m.refresh();
|
|
38
|
-
return m;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Route the client's `fetch(path, {body})` into the dev wasm dispatcher. */
|
|
42
|
-
function installFetchShim(m: WasmServerModule): () => void {
|
|
43
|
-
const original = globalThis.fetch;
|
|
44
|
-
const jar = new Map<string, string>();
|
|
45
|
-
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
46
|
-
const url = typeof input === 'string' ? input : input.toString();
|
|
47
|
-
const pathname = new URL(url, 'http://localhost').pathname;
|
|
48
|
-
const bodyBytes =
|
|
49
|
-
init?.body == null ? new Uint8Array(0) : new Uint8Array(init.body as ArrayBuffer);
|
|
50
|
-
const headers: [string, string][] = [
|
|
51
|
-
['host', 'localhost:3000'],
|
|
52
|
-
['content-type', 'application/octet-stream'],
|
|
53
|
-
];
|
|
54
|
-
if (jar.size > 0)
|
|
55
|
-
headers.push(['cookie', [...jar.entries()].map(([k, v]) => `${k}=${v}`).join('; ')]);
|
|
56
|
-
const r = m.dispatch({
|
|
57
|
-
method: (init?.method ?? 'GET') as 'GET' | 'POST',
|
|
58
|
-
path: pathname,
|
|
59
|
-
headers,
|
|
60
|
-
body: bodyBytes,
|
|
61
|
-
});
|
|
62
|
-
// Fold any Set-Cookie (`name=value; Path=/; ...`) into the jar so the next
|
|
63
|
-
// request replays it, exactly like a browser.
|
|
64
|
-
for (const [name, value] of r.headers) {
|
|
65
|
-
if (name.toLowerCase() !== 'set-cookie') continue;
|
|
66
|
-
const pair = value.split(';', 1)[0];
|
|
67
|
-
const eq = pair.indexOf('=');
|
|
68
|
-
if (eq > 0) jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
|
|
69
|
-
}
|
|
70
|
-
const ab = r.body.buffer.slice(r.body.byteOffset, r.body.byteOffset + r.body.byteLength);
|
|
71
|
-
return {
|
|
72
|
-
ok: r.status >= 200 && r.status < 300,
|
|
73
|
-
status: r.status,
|
|
74
|
-
arrayBuffer: async () => ab,
|
|
75
|
-
text: async () => Buffer.from(r.body).toString('utf8'),
|
|
76
|
-
} as Response;
|
|
77
|
-
}) as typeof fetch;
|
|
78
|
-
return () => {
|
|
79
|
-
globalThis.fetch = original;
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
describe.skipIf(!haveWasm)('post-quantum auth end-to-end (client <-> example wasm)', () => {
|
|
84
|
-
let restoreFetch: () => void;
|
|
85
|
-
let mod: WasmServerModule;
|
|
86
|
-
|
|
87
|
-
beforeEach(() => {
|
|
88
|
-
__resetDbForTests();
|
|
89
|
-
// The built-in controller decorates every route with `@ratelimit`; the dev limiter is a
|
|
90
|
-
// module-level singleton, so reset it per case to keep the many dispatches in this file isolated.
|
|
91
|
-
__resetRatelimitForTests();
|
|
92
|
-
mod = loadModule();
|
|
93
|
-
restoreFetch = installFetchShim(mod);
|
|
94
|
-
});
|
|
95
|
-
afterEach(() => restoreFetch());
|
|
96
|
-
|
|
97
|
-
it(
|
|
98
|
-
'registers then logs in (full OPRF + ML-DSA + ML-KEM mutual-auth chain)',
|
|
99
|
-
async () => {
|
|
100
|
-
await Auth.register('ada', 'correct horse battery stapleA1', 'ada@example.com');
|
|
101
|
-
// login resolves ONLY if the server's mutual-auth confirmation tag
|
|
102
|
-
// verified against the client's own shared secret.
|
|
103
|
-
const session = await Auth.login('ada', 'correct horse battery stapleA1');
|
|
104
|
-
expect(session.length).toBeGreaterThan(0);
|
|
105
|
-
|
|
106
|
-
// Regression guard: the freshly minted session cookie (captured by the
|
|
107
|
-
// shim jar) must be ACCEPTED by the `@auth`-gated `/session/me`, which
|
|
108
|
-
// runs in a separate fresh wasm instance from `login/finish`. The bug
|
|
109
|
-
// was that login configured a different HMAC secret than the verify
|
|
110
|
-
// path used, so the MAC failed and `/session/me` returned 401.
|
|
111
|
-
const meRes = await fetch('/session/me');
|
|
112
|
-
expect(meRes.status).toBe(200);
|
|
113
|
-
const me = new DataReader(new Uint8Array(await meRes.arrayBuffer()));
|
|
114
|
-
expect(me.readString()).toBe('ada');
|
|
115
|
-
expect(me.readBool()).toBe(false); // 'ada' is not 'root' -> not admin
|
|
116
|
-
},
|
|
117
|
-
60_000,
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
it(
|
|
121
|
-
'rejects /session/me with no session cookie (the @auth gate holds)',
|
|
122
|
-
async () => {
|
|
123
|
-
const meRes = await fetch('/session/me');
|
|
124
|
-
expect(meRes.status).toBe(401);
|
|
125
|
-
},
|
|
126
|
-
60_000,
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
it(
|
|
130
|
-
'rejects a wrong password at login',
|
|
131
|
-
async () => {
|
|
132
|
-
await Auth.register('bob', 'hunter2-correctA1', 'bob@example.com');
|
|
133
|
-
await expect(Auth.login('bob', 'hunter2-WRONG')).rejects.toThrow(/login failed|request failed/);
|
|
134
|
-
},
|
|
135
|
-
60_000,
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
it(
|
|
139
|
-
'rejects login for a never-registered user',
|
|
140
|
-
async () => {
|
|
141
|
-
await expect(Auth.login('ghost', 'whatever')).rejects.toThrow(/login failed|request failed/);
|
|
142
|
-
},
|
|
143
|
-
60_000,
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
it(
|
|
147
|
-
'rejects a duplicate registration with a clear (not generic) message',
|
|
148
|
-
async () => {
|
|
149
|
-
await Auth.register('dupe', 'correct horse battery stapleA1', 'dupe@example.com');
|
|
150
|
-
// Second registration of the same username must say so explicitly,
|
|
151
|
-
// not return the generic "request failed". (The username-taken check fires
|
|
152
|
-
// before the email check, so it stays `already registered` even with the same email.)
|
|
153
|
-
await expect(
|
|
154
|
-
Auth.register('dupe', 'correct horse battery stapleA1', 'dupe@example.com'),
|
|
155
|
-
).rejects.toThrow(/already registered/);
|
|
156
|
-
},
|
|
157
|
-
60_000,
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Lower-level wire checks that don't need the heavy Argon2id derivation.
|
|
162
|
-
describe.skipIf(!haveWasm)('post-quantum auth wire-level (anti-enumeration, replay)', () => {
|
|
163
|
-
beforeEach(() => {
|
|
164
|
-
__resetDbForTests();
|
|
165
|
-
__resetRatelimitForTests();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const oprf = ristretto255_oprf.oprf;
|
|
169
|
-
const loginStart = (m: WasmServerModule, username: string) => {
|
|
170
|
-
const { blinded } = oprf.blind(new TextEncoder().encode('pw'));
|
|
171
|
-
const body = new DataWriter().writeString(username).writeBytes(blinded).toBytes();
|
|
172
|
-
const r = m.dispatch({
|
|
173
|
-
method: 'POST',
|
|
174
|
-
path: '/auth/login/start',
|
|
175
|
-
headers: [['host', 'localhost:3000'], ['content-type', 'application/octet-stream']],
|
|
176
|
-
body,
|
|
177
|
-
});
|
|
178
|
-
expect(r.status).toBe(200);
|
|
179
|
-
const rd = new DataReader(r.body);
|
|
180
|
-
const cid = rd.readBytes();
|
|
181
|
-
const aud = rd.readString();
|
|
182
|
-
const mem = rd.readU32();
|
|
183
|
-
const iters = rd.readU32();
|
|
184
|
-
const par = rd.readU32();
|
|
185
|
-
const salt = rd.readBytes();
|
|
186
|
-
const nonce = rd.readBytes();
|
|
187
|
-
const iat = rd.readU64();
|
|
188
|
-
const exp = rd.readU64();
|
|
189
|
-
const evaluated = rd.readBytes();
|
|
190
|
-
return { cid, aud, mem, iters, par, salt, nonce, iat, exp, evaluated };
|
|
191
|
-
};
|
|
192
|
-
const hex = (u: Uint8Array) => Buffer.from(u).toString('hex');
|
|
193
|
-
|
|
194
|
-
it('returns a STABLE per-user salt for an unknown user across calls (no enumeration)', () => {
|
|
195
|
-
const m = loadModule();
|
|
196
|
-
const a = loginStart(m, 'no-such-user');
|
|
197
|
-
const b = loginStart(m, 'no-such-user');
|
|
198
|
-
// The original bug returned fresh random salt each call for unknown users.
|
|
199
|
-
expect(hex(a.salt)).toBe(hex(b.salt));
|
|
200
|
-
// Shape is fully formed and identical-looking to a real user's response.
|
|
201
|
-
expect(a.salt.length).toBe(16);
|
|
202
|
-
expect(a.nonce.length).toBe(32);
|
|
203
|
-
expect(a.evaluated.length).toBe(32);
|
|
204
|
-
// The built-in controller returns `TOIL_AUTH_AUDIENCE` or the literal `'toil'` (dev has no secret set).
|
|
205
|
-
expect(a.aud).toBe('toil');
|
|
206
|
-
// The randomized fields DO differ (fresh challenge each call).
|
|
207
|
-
expect(hex(a.cid)).not.toBe(hex(b.cid));
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('two unknown users get different (per-user) salts', () => {
|
|
211
|
-
const m = loadModule();
|
|
212
|
-
const a = loginStart(m, 'alpha-unknown');
|
|
213
|
-
const b = loginStart(m, 'beta-unknown');
|
|
214
|
-
expect(hex(a.salt)).not.toBe(hex(b.salt));
|
|
215
|
-
});
|
|
216
|
-
});
|