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/docs/services/time.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Time
|
|
2
|
-
|
|
3
|
-
**`Time` is the edge's clock: the blessed way to read the current time in your backend.** It gives you the number of milliseconds or seconds since a fixed reference point, so you can stamp events and compute expiries.
|
|
4
|
-
|
|
5
|
-
## How to use it
|
|
6
|
-
|
|
7
|
-
`Time` is an ambient global (no import needed), and is also exported from `toiljs/server/runtime`:
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
const ms = Time.nowMillis(); // milliseconds since the Unix epoch (a u64)
|
|
11
|
-
const s = Time.nowSeconds(); // whole seconds since the Unix epoch (a u64)
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
| Member | Returns | Description |
|
|
15
|
-
| --- | --- | --- |
|
|
16
|
-
| `Time.nowMillis()` | `u64` | Milliseconds since the **Unix epoch** (midnight UTC on 1 January 1970). This is the same value as JavaScript's `Date.now()`. |
|
|
17
|
-
| `Time.nowSeconds()` | `u64` | Whole seconds since the epoch (`nowMillis() / 1000`). This is the unit sessions and login challenges use. |
|
|
18
|
-
|
|
19
|
-
The **Unix epoch** is just the agreed "time zero" that computers count from. "Milliseconds since the epoch" is a single number that unambiguously names an instant, with no time zone to worry about.
|
|
20
|
-
|
|
21
|
-
## Why time is a "host import" (and what that means)
|
|
22
|
-
|
|
23
|
-
Your backend runs as a **WebAssembly** module: a small, sandboxed program. A sandbox is deliberately sealed off from the outside world. It has no clock of its own, no network, no files, nothing except the memory it was given and a short list of functions the host chooses to hand it. Each of those handed-in functions is called a **host import**.
|
|
24
|
-
|
|
25
|
-
This is on purpose. WebAssembly aims to be **deterministic**: given the same inputs, the same code produces the same outputs, every time. That makes it safe to run untrusted code, easy to cache, and easy to reason about. But the current time is not deterministic: it is different every time you ask. So a WebAssembly module cannot just "know" the time; it has to **ask the host** for it. Reading the clock is a call across the sandbox boundary.
|
|
26
|
-
|
|
27
|
-
`Time` is the thin, friendly wrapper over that call. Under the hood it uses the host's `Date.now()` binding (`env.Date.now`).
|
|
28
|
-
|
|
29
|
-
```mermaid
|
|
30
|
-
flowchart LR
|
|
31
|
-
subgraph Sandbox["Your backend (WebAssembly sandbox)"]
|
|
32
|
-
C["Time.nowMillis()"]
|
|
33
|
-
end
|
|
34
|
-
subgraph Host["Dacely edge host"]
|
|
35
|
-
D["env.Date.now"]
|
|
36
|
-
K["real system clock"]
|
|
37
|
-
end
|
|
38
|
-
C -->|host import| D
|
|
39
|
-
D --> K
|
|
40
|
-
K -->|milliseconds| C
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Both the edge and the dev server provide this binding, so time behaves **identically** in `toiljs dev` and in production.
|
|
44
|
-
|
|
45
|
-
## `Time` vs calling `Date.now()`
|
|
46
|
-
|
|
47
|
-
ToilScript's `Date.now()` lowers to the exact same host import, so you *can* call it directly. Prefer `Time`:
|
|
48
|
-
|
|
49
|
-
- It makes the host boundary (and the single millisecond unit) explicit and easy to find in a codebase.
|
|
50
|
-
- It gives you `nowSeconds()` without writing an open-coded `/ 1000` at every call site.
|
|
51
|
-
|
|
52
|
-
Both are the same clock, so timing you do in a handler lines up with timing the framework does. For example, the auth system uses `Time.nowSeconds()` for session issue and expiry times.
|
|
53
|
-
|
|
54
|
-
## Correct usage: timestamps and TTLs
|
|
55
|
-
|
|
56
|
-
`Time` is **wall-clock time**, exactly like a browser's `Date.now()`. It follows the system clock, which means it can occasionally **step backward** (for instance when the machine corrects its clock against a time server).
|
|
57
|
-
|
|
58
|
-
Do:
|
|
59
|
-
|
|
60
|
-
- **Stamp an instant.** Record when something happened, so you can compare it later: an event time, a "last seen" marker.
|
|
61
|
-
- **Compute an expiry (a TTL).** A **TTL** ("time to live") is a deadline. You set it by adding a duration to *now*, and you check it by comparing *now* against the stored deadline:
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
const ttlSeconds: u64 = 3600; // valid for one hour
|
|
65
|
-
const expiresAt = Time.nowSeconds() + ttlSeconds;
|
|
66
|
-
|
|
67
|
-
// ...later, on another request...
|
|
68
|
-
const expired = Time.nowSeconds() >= expiresAt;
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Do not:
|
|
72
|
-
|
|
73
|
-
- **Measure elapsed time with it.** Do not use it as a stopwatch to time how long an operation took. Because the wall clock can step backward, `end - start` could come out zero or negative. `Time` answers "what instant is it now?", not "how much time has passed?".
|
|
74
|
-
|
|
75
|
-
## Gotchas
|
|
76
|
-
|
|
77
|
-
- **It is wall-clock, not monotonic.** It can jump backward across a clock correction. Fine for timestamps and expiries; wrong for measuring durations.
|
|
78
|
-
- **Milliseconds vs seconds.** `nowMillis()` and `nowSeconds()` differ by a factor of 1000. Mixing them (comparing a millisecond stamp to a second stamp) is a common bug. Sessions and login challenges use **seconds**.
|
|
79
|
-
- **UTC only.** These are epoch counts, with no time zone. Format for display on the client if you need a local time.
|
|
80
|
-
|
|
81
|
-
## Related
|
|
82
|
-
|
|
83
|
-
- [Auth, sessions, and `@user`](../auth/usage.md): sessions use `Time.nowSeconds()` for their issue and expiry stamps.
|
|
84
|
-
- [Caching](./caching.md): TTLs are the same idea, a deadline after which a saved value is stale.
|
|
85
|
-
- [Compute tiers](../concepts/tiers.md): more on the WebAssembly sandbox your backend runs in.
|
package/eslint.config.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import eslint from '@eslint/js';
|
|
4
|
-
import tseslint from 'typescript-eslint';
|
|
5
|
-
|
|
6
|
-
export default tseslint.config(
|
|
7
|
-
// toilscript server (WASM) + its ambient std + build output are not part of the TS project.
|
|
8
|
-
// toil-docs.generated.ts is a build artifact (gitignored, emitted by gen:docs) — not linted.
|
|
9
|
-
{
|
|
10
|
-
ignores: [
|
|
11
|
-
'build/**',
|
|
12
|
-
'server/**',
|
|
13
|
-
'std/server/**',
|
|
14
|
-
'toil-env.d.ts',
|
|
15
|
-
'src/compiler/toil-docs.generated.ts',
|
|
16
|
-
],
|
|
17
|
-
},
|
|
18
|
-
eslint.configs.recommended,
|
|
19
|
-
...tseslint.configs.strictTypeChecked,
|
|
20
|
-
{
|
|
21
|
-
languageOptions: {
|
|
22
|
-
parserOptions: {
|
|
23
|
-
projectService: true,
|
|
24
|
-
tsconfigDirName: import.meta.dirname,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
rules: {
|
|
28
|
-
'no-undef': 'off',
|
|
29
|
-
'@typescript-eslint/no-unused-vars': 'off',
|
|
30
|
-
'no-empty': 'off',
|
|
31
|
-
'@typescript-eslint/restrict-template-expressions': 'off',
|
|
32
|
-
'@typescript-eslint/only-throw-error': 'off',
|
|
33
|
-
'@typescript-eslint/no-unnecessary-condition': 'off',
|
|
34
|
-
'@typescript-eslint/unbound-method': 'warn',
|
|
35
|
-
'@typescript-eslint/no-confusing-void-expression': 'off',
|
|
36
|
-
'@typescript-eslint/no-extraneous-class': 'off',
|
|
37
|
-
'no-async-promise-executor': 'off',
|
|
38
|
-
'@typescript-eslint/no-misused-promises': 'off',
|
|
39
|
-
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
|
|
40
|
-
'@typescript-eslint/no-duplicate-enum-values': 'off',
|
|
41
|
-
'prefer-spread': 'off',
|
|
42
|
-
'@typescript-eslint/no-empty-object-type': 'off',
|
|
43
|
-
'@typescript-eslint/no-base-to-string': 'off',
|
|
44
|
-
'@typescript-eslint/no-dynamic-delete': 'off',
|
|
45
|
-
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
|
46
|
-
|
|
47
|
-
// This is stupid. null and undefined is not the same thing. who ever made this eslint rule is stupid.
|
|
48
|
-
'no-useless-assignment': 'off',
|
|
49
|
-
'@typescript-eslint/no-unnecessary-type-arguments': 'off',
|
|
50
|
-
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
files: ['**/*.js'],
|
|
55
|
-
...tseslint.configs.disableTypeChecked,
|
|
56
|
-
},
|
|
57
|
-
);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export default function Docs() {
|
|
2
|
-
const { slug } = Toil.useParams();
|
|
3
|
-
return (
|
|
4
|
-
<main>
|
|
5
|
-
<h1>Docs</h1>
|
|
6
|
-
<p>
|
|
7
|
-
Catch-all route <code>client/routes/docs/[...slug].tsx</code> matched: <code>{slug}</code>
|
|
8
|
-
</p>
|
|
9
|
-
<Toil.Link href="/">Back home</Toil.Link>
|
|
10
|
-
</main>
|
|
11
|
-
);
|
|
12
|
-
}
|
package/presets/eslint.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/** toiljs shared ESLint flat config: `import toiljs from 'toiljs/eslint'; export default toiljs;` */
|
|
2
|
-
import eslintReact from '@eslint-react/eslint-plugin';
|
|
3
|
-
import eslint from '@eslint/js';
|
|
4
|
-
import reactHooks from 'eslint-plugin-react-hooks';
|
|
5
|
-
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
6
|
-
import tseslint from 'typescript-eslint';
|
|
7
|
-
|
|
8
|
-
import noUint8ArrayToString from './no-uint8array-tostring.js';
|
|
9
|
-
|
|
10
|
-
export default tseslint.config(
|
|
11
|
-
{ ignores: ['dist', 'build', '.toil', 'node_modules', 'toil-env.d.ts', 'server/**'] },
|
|
12
|
-
{
|
|
13
|
-
extends: [
|
|
14
|
-
eslint.configs.recommended,
|
|
15
|
-
...tseslint.configs.recommended,
|
|
16
|
-
...tseslint.configs.strictTypeChecked,
|
|
17
|
-
],
|
|
18
|
-
files: ['**/*.{ts,tsx}'],
|
|
19
|
-
languageOptions: {
|
|
20
|
-
ecmaVersion: 2023,
|
|
21
|
-
parserOptions: {
|
|
22
|
-
projectService: true,
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
plugins: {
|
|
26
|
-
'react-hooks': reactHooks,
|
|
27
|
-
'react-refresh': reactRefresh,
|
|
28
|
-
'@eslint-react': eslintReact,
|
|
29
|
-
custom: noUint8ArrayToString,
|
|
30
|
-
},
|
|
31
|
-
rules: {
|
|
32
|
-
...reactHooks.configs.recommended.rules,
|
|
33
|
-
// Route files conventionally export `loader` / `revalidate` / `metadata` /
|
|
34
|
-
// `generateMetadata` / `searchHints` alongside the default component; the toil compiler
|
|
35
|
-
// consumes them at runtime/build. Allow them (plus primitive constants) so Fast Refresh
|
|
36
|
-
// doesn't flag the pattern.
|
|
37
|
-
'react-refresh/only-export-components': [
|
|
38
|
-
'warn',
|
|
39
|
-
{
|
|
40
|
-
allowConstantExport: true,
|
|
41
|
-
allowExportNames: [
|
|
42
|
-
'loader',
|
|
43
|
-
'revalidate',
|
|
44
|
-
'metadata',
|
|
45
|
-
'generateMetadata',
|
|
46
|
-
'searchHints',
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
'no-undef': 'off',
|
|
51
|
-
'@typescript-eslint/no-unused-vars': 'off',
|
|
52
|
-
'no-empty': 'off',
|
|
53
|
-
'@typescript-eslint/restrict-template-expressions': 'off',
|
|
54
|
-
'@typescript-eslint/only-throw-error': 'off',
|
|
55
|
-
'@typescript-eslint/no-unnecessary-condition': 'off',
|
|
56
|
-
'@typescript-eslint/unbound-method': 'warn',
|
|
57
|
-
'@typescript-eslint/no-confusing-void-expression': 'off',
|
|
58
|
-
'@typescript-eslint/no-extraneous-class': 'off',
|
|
59
|
-
'no-async-promise-executor': 'off',
|
|
60
|
-
'@typescript-eslint/no-misused-promises': 'off',
|
|
61
|
-
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
|
|
62
|
-
'@typescript-eslint/no-duplicate-enum-values': 'off',
|
|
63
|
-
'prefer-spread': 'off',
|
|
64
|
-
'@typescript-eslint/no-empty-object-type': 'off',
|
|
65
|
-
'@typescript-eslint/no-floating-promises': 'off',
|
|
66
|
-
'@typescript-eslint/ban-ts-comment': 'off',
|
|
67
|
-
'no-constant-binary-expression': 'off',
|
|
68
|
-
'no-useless-assignment': 'off',
|
|
69
|
-
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
70
|
-
'@typescript-eslint/no-unsafe-call': 'off',
|
|
71
|
-
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
72
|
-
'@typescript-eslint/no-unsafe-argument': 'off',
|
|
73
|
-
'@typescript-eslint/no-unnecessary-type-conversion': 'warn',
|
|
74
|
-
'react-hooks/set-state-in-effect': 'warn',
|
|
75
|
-
'custom/no-uint8array-tostring': 'error',
|
|
76
|
-
'padding-line-between-statements': [
|
|
77
|
-
'error',
|
|
78
|
-
{ blankLine: 'always', prev: 'block-like', next: '*' },
|
|
79
|
-
],
|
|
80
|
-
'@typescript-eslint/no-deprecated': 'off',
|
|
81
|
-
'@typescript-eslint/no-unnecessary-type-arguments': 'off',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
files: ['**/*.js'],
|
|
86
|
-
...tseslint.configs.disableTypeChecked,
|
|
87
|
-
},
|
|
88
|
-
);
|
package/src/backend/index.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* toiljs backend, the self-host / dev server, built on @dacely/hyper-express (uWebSockets.js)
|
|
3
|
-
* for very high throughput. It serves the built client (static assets + SPA fallback) and exposes
|
|
4
|
-
* a WebSocket channel for realtime / live updates.
|
|
5
|
-
*
|
|
6
|
-
* This is the Node "server" that hosts the app on a local machine; it is distinct from the
|
|
7
|
-
* toilscript WASM runtime in `server/runtime` (the `toiljs/server/runtime` library export).
|
|
8
|
-
*/
|
|
9
|
-
import fs from 'node:fs';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
type MiddlewareNext,
|
|
14
|
-
type Request,
|
|
15
|
-
type Response,
|
|
16
|
-
Server,
|
|
17
|
-
type Websocket,
|
|
18
|
-
} from '@dacely/hyper-express';
|
|
19
|
-
|
|
20
|
-
const DEFAULT_MAX_BODY_LENGTH = 1024 * 1024 * 8;
|
|
21
|
-
const MAX_BODY_BUFFER = 1024 * 32;
|
|
22
|
-
const HTTP_IDLE_TIMEOUT = 60;
|
|
23
|
-
const HTTP_RESPONSE_TIMEOUT = 120;
|
|
24
|
-
|
|
25
|
-
const WS_MAX_PAYLOAD_LENGTH = 1024 * 1024;
|
|
26
|
-
const WS_IDLE_TIMEOUT = 120;
|
|
27
|
-
const WS_MAX_BACKPRESSURE = 1024 * 1024 * 2;
|
|
28
|
-
|
|
29
|
-
const CORS_METHODS = 'GET, POST, OPTIONS, PUT, PATCH, DELETE';
|
|
30
|
-
const CORS_HEADERS = 'X-Requested-With, content-type';
|
|
31
|
-
|
|
32
|
-
/** Options for {@link startBackend}. */
|
|
33
|
-
export interface BackendOptions {
|
|
34
|
-
/** Directory to serve (the built client `outDir`, e.g. `dist`). */
|
|
35
|
-
readonly root: string;
|
|
36
|
-
/** Listening port. Default `3000`. */
|
|
37
|
-
readonly port?: number;
|
|
38
|
-
/**
|
|
39
|
-
* Bind host. Default `127.0.0.1` (loopback only). Pass `0.0.0.0` (or a specific interface) to
|
|
40
|
-
* expose the server on the network; do so deliberately, since the WebSocket channel relays
|
|
41
|
-
* messages between all connected clients.
|
|
42
|
-
*/
|
|
43
|
-
readonly host?: string;
|
|
44
|
-
/**
|
|
45
|
-
* Extra origins allowed to open the WebSocket channel, in addition to the server's own origin.
|
|
46
|
-
* Cross-origin WebSocket handshakes from other origins are rejected (prevents cross-site
|
|
47
|
-
* WebSocket hijacking). Example: `['https://app.example.com']`.
|
|
48
|
-
*/
|
|
49
|
-
readonly allowedOrigins?: readonly string[];
|
|
50
|
-
/** WebSocket channel path. Default `/_toil`. */
|
|
51
|
-
readonly wsPath?: string;
|
|
52
|
-
/** Send permissive CORS headers + handle preflight. Default `true`. */
|
|
53
|
-
readonly cors?: boolean;
|
|
54
|
-
/** Max request body length in bytes. Default 8 MB. */
|
|
55
|
-
readonly maxBodyLength?: number;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** A running backend instance. */
|
|
59
|
-
export interface RunningBackend {
|
|
60
|
-
readonly port: number;
|
|
61
|
-
readonly host: string;
|
|
62
|
-
readonly wsPath: string;
|
|
63
|
-
/** Sends a message to every connected WebSocket client. */
|
|
64
|
-
broadcast(message: string): void;
|
|
65
|
-
/** Number of currently-connected WebSocket clients. */
|
|
66
|
-
clientCount(): number;
|
|
67
|
-
/** Gracefully shuts the server down. */
|
|
68
|
-
close(): Promise<void>;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Whether a WebSocket upgrade from `origin` may connect. A missing `Origin` (non-browser clients
|
|
73
|
-
* like curl or server-to-server) is allowed; a browser `Origin` must match the host the server was
|
|
74
|
-
* reached at (same-origin) or be in the explicit allowlist. This blocks cross-site WebSocket
|
|
75
|
-
* hijacking, where a page the victim visits opens a socket to this server from their browser.
|
|
76
|
-
*/
|
|
77
|
-
function isWsOriginAllowed(
|
|
78
|
-
origin: string | undefined,
|
|
79
|
-
hostHeader: string | undefined,
|
|
80
|
-
allowed: readonly string[] | undefined,
|
|
81
|
-
): boolean {
|
|
82
|
-
if (!origin) return true;
|
|
83
|
-
if (allowed?.includes(origin)) return true;
|
|
84
|
-
try {
|
|
85
|
-
return new URL(origin).host === hostHeader;
|
|
86
|
-
} catch {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Resolves a request path to a file inside `root`, guarding against path traversal. */
|
|
92
|
-
function resolveStaticFile(root: string, requestPath: string): string | null {
|
|
93
|
-
const decoded = decodeURIComponent(requestPath);
|
|
94
|
-
const resolved = path.join(root, decoded);
|
|
95
|
-
if (resolved !== root && !resolved.startsWith(root + path.sep)) return null;
|
|
96
|
-
if (decoded === '/' || decoded === '') return null;
|
|
97
|
-
if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) return resolved;
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Starts the hyper-express server serving `root` with an SPA fallback to `index.html`,
|
|
103
|
-
* plus a WebSocket channel at `wsPath`. Resolves once the server is listening.
|
|
104
|
-
*/
|
|
105
|
-
export async function startBackend(options: BackendOptions): Promise<RunningBackend> {
|
|
106
|
-
const port = options.port ?? 3000;
|
|
107
|
-
const host = options.host ?? '127.0.0.1';
|
|
108
|
-
const wsPath = options.wsPath ?? '/_toil';
|
|
109
|
-
const cors = options.cors ?? true;
|
|
110
|
-
const root = path.resolve(options.root);
|
|
111
|
-
const indexHtml = path.join(root, 'index.html');
|
|
112
|
-
|
|
113
|
-
const app = new Server({
|
|
114
|
-
max_body_length: options.maxBodyLength ?? DEFAULT_MAX_BODY_LENGTH,
|
|
115
|
-
max_body_buffer: MAX_BODY_BUFFER,
|
|
116
|
-
fast_abort: true,
|
|
117
|
-
idle_timeout: HTTP_IDLE_TIMEOUT,
|
|
118
|
-
response_timeout: HTTP_RESPONSE_TIMEOUT,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const clients = new Set<Websocket>();
|
|
122
|
-
|
|
123
|
-
app.set_error_handler((_request: Request, response: Response, _error: Error) => {
|
|
124
|
-
if (response.completed) return;
|
|
125
|
-
response.atomic(() => {
|
|
126
|
-
response.status(500).json({ error: 'Internal server error.' });
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
if (cors) {
|
|
131
|
-
app.use((request: Request, response: Response, next: MiddlewareNext) => {
|
|
132
|
-
if (request.method !== 'OPTIONS') {
|
|
133
|
-
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
134
|
-
response.setHeader('Access-Control-Allow-Methods', CORS_METHODS);
|
|
135
|
-
response.setHeader('Access-Control-Allow-Headers', CORS_HEADERS);
|
|
136
|
-
}
|
|
137
|
-
response.removeHeader('uWebSockets');
|
|
138
|
-
next();
|
|
139
|
-
});
|
|
140
|
-
app.options('/*', (_request: Request, response: Response) => {
|
|
141
|
-
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
142
|
-
response.setHeader('Access-Control-Allow-Methods', CORS_METHODS);
|
|
143
|
-
response.setHeader('Access-Control-Allow-Headers', CORS_HEADERS);
|
|
144
|
-
response.setHeader('Access-Control-Max-Age', '86400');
|
|
145
|
-
response.status(204).send();
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
app.ws(
|
|
150
|
-
wsPath,
|
|
151
|
-
{
|
|
152
|
-
message_type: 'String',
|
|
153
|
-
max_payload_length: WS_MAX_PAYLOAD_LENGTH,
|
|
154
|
-
idle_timeout: WS_IDLE_TIMEOUT,
|
|
155
|
-
max_backpressure: WS_MAX_BACKPRESSURE,
|
|
156
|
-
},
|
|
157
|
-
(ws) => {
|
|
158
|
-
clients.add(ws);
|
|
159
|
-
ws.send(JSON.stringify({ type: 'connected', clients: clients.size }));
|
|
160
|
-
ws.on('message', (message: string) => {
|
|
161
|
-
for (const client of clients) client.send(message);
|
|
162
|
-
});
|
|
163
|
-
ws.on('drain', () => {});
|
|
164
|
-
ws.on('close', () => {
|
|
165
|
-
clients.delete(ws);
|
|
166
|
-
});
|
|
167
|
-
},
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// Gate the WebSocket upgrade on the request Origin, so a cross-origin page in a victim's browser
|
|
171
|
-
// cannot hijack the channel (CSWSH). Registered AFTER `app.ws` so it overrides that route's
|
|
172
|
-
// default upgrade handler (hyper-express links it to the companion ws route). Same-origin and
|
|
173
|
-
// non-browser clients pass; others get 403.
|
|
174
|
-
app.upgrade(wsPath, (request: Request, response: Response) => {
|
|
175
|
-
if (
|
|
176
|
-
!isWsOriginAllowed(request.headers.origin, request.headers.host, options.allowedOrigins)
|
|
177
|
-
) {
|
|
178
|
-
response.status(403).send();
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
response.upgrade({});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
app.get('/*', (request: Request, response: Response) => {
|
|
185
|
-
if (response.completed) return;
|
|
186
|
-
const file = resolveStaticFile(root, request.path);
|
|
187
|
-
response.sendFile(file ?? indexHtml);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
await app.listen(port, host);
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
port,
|
|
194
|
-
host,
|
|
195
|
-
wsPath,
|
|
196
|
-
broadcast: (message: string): void => {
|
|
197
|
-
for (const client of clients) client.send(message);
|
|
198
|
-
},
|
|
199
|
-
clientCount: (): number => clients.size,
|
|
200
|
-
close: async (): Promise<void> => {
|
|
201
|
-
await app.shutdown();
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
}
|