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.
Files changed (326) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +16 -7
  3. package/TYPESCRIPT_LAW.md +1946 -2073
  4. package/build/backend/.tsbuildinfo +1 -1
  5. package/build/cli/.tsbuildinfo +1 -1
  6. package/build/cli/index.js +2375 -258
  7. package/build/client/.tsbuildinfo +1 -1
  8. package/build/client/dev/devtools.js +1 -1
  9. package/build/client/dev/error-overlay.js +1 -1
  10. package/build/client/routing/mount.js +1 -1
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/auth-emails.js +7 -3
  13. package/build/compiler/emails.js +1 -1
  14. package/build/compiler/index.d.ts +1 -0
  15. package/build/compiler/index.js +25 -4
  16. package/build/compiler/pages.js +2 -3
  17. package/build/compiler/prerender.d.ts +3 -8
  18. package/build/compiler/prerender.js +88 -127
  19. package/build/compiler/ssg.js +3 -4
  20. package/build/compiler/template-build.js +3 -4
  21. package/build/compiler/toil-docs.generated.js +6 -5
  22. package/build/compiler/vite.js +3 -6
  23. package/build/devserver/.tsbuildinfo +1 -1
  24. package/build/devserver/db/database.js +7 -3
  25. package/build/devserver/db/types.js +1 -1
  26. package/build/devserver/production.js +4 -3
  27. package/build/devserver/runtime/crypto.d.ts +1 -1
  28. package/build/io/.tsbuildinfo +1 -1
  29. package/build/logger/.tsbuildinfo +1 -1
  30. package/build/shared/.tsbuildinfo +1 -1
  31. package/examples/basic/client/routes/analytics.tsx +7 -5
  32. package/examples/basic/server/tsconfig.json +1 -3
  33. package/package.json +45 -44
  34. package/presets/no-uint8array-tostring.js +120 -153
  35. package/presets/oxlint.d.ts +5 -0
  36. package/presets/oxlint.js +224 -0
  37. package/server/runtime/tsconfig.json +1 -3
  38. package/tsconfig.json +2 -11
  39. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  40. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  41. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  42. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  43. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  44. package/.github/changelog-config.json +0 -45
  45. package/.github/dependabot.yml +0 -27
  46. package/.github/workflows/ci.yml +0 -222
  47. package/.vscode/settings.json +0 -10
  48. package/docs/README.md +0 -107
  49. package/docs/auth/README.md +0 -104
  50. package/docs/auth/configuration.md +0 -105
  51. package/docs/auth/emails.md +0 -92
  52. package/docs/auth/extending.md +0 -202
  53. package/docs/auth/how-it-works.md +0 -138
  54. package/docs/auth/usage.md +0 -188
  55. package/docs/backend/README.md +0 -143
  56. package/docs/backend/data.md +0 -351
  57. package/docs/backend/rest.md +0 -402
  58. package/docs/backend/rpc.md +0 -226
  59. package/docs/background/README.md +0 -114
  60. package/docs/background/daemons.md +0 -276
  61. package/docs/background/derive.md +0 -179
  62. package/docs/cli/README.md +0 -379
  63. package/docs/concepts/ai-guide.md +0 -132
  64. package/docs/concepts/config.md +0 -416
  65. package/docs/concepts/decorators.md +0 -127
  66. package/docs/concepts/security.md +0 -108
  67. package/docs/concepts/tiers.md +0 -166
  68. package/docs/concepts/types.md +0 -216
  69. package/docs/database/README.md +0 -143
  70. package/docs/database/capacity.md +0 -350
  71. package/docs/database/counters.md +0 -174
  72. package/docs/database/documents.md +0 -277
  73. package/docs/database/events.md +0 -307
  74. package/docs/database/membership.md +0 -246
  75. package/docs/database/setup.md +0 -155
  76. package/docs/database/unique.md +0 -216
  77. package/docs/database/views.md +0 -246
  78. package/docs/frontend/README.md +0 -104
  79. package/docs/frontend/components.md +0 -286
  80. package/docs/frontend/data-fetching.md +0 -260
  81. package/docs/frontend/images.md +0 -148
  82. package/docs/frontend/metadata.md +0 -344
  83. package/docs/frontend/navigation.md +0 -296
  84. package/docs/frontend/rendering.md +0 -236
  85. package/docs/frontend/routing.md +0 -346
  86. package/docs/frontend/scripts.md +0 -118
  87. package/docs/frontend/search.md +0 -191
  88. package/docs/frontend/styling.md +0 -147
  89. package/docs/frontend/toil-global.md +0 -216
  90. package/docs/getting-started/README.md +0 -81
  91. package/docs/getting-started/create-project.md +0 -131
  92. package/docs/getting-started/deploy.md +0 -101
  93. package/docs/getting-started/first-app.md +0 -215
  94. package/docs/getting-started/installation.md +0 -124
  95. package/docs/getting-started/migrating.md +0 -125
  96. package/docs/getting-started/project-structure.md +0 -163
  97. package/docs/introduction/README.md +0 -66
  98. package/docs/introduction/design-principles.md +0 -81
  99. package/docs/introduction/distributed.md +0 -91
  100. package/docs/introduction/how-it-works.md +0 -121
  101. package/docs/introduction/hyperscale.md +0 -70
  102. package/docs/introduction/modern-stack.md +0 -85
  103. package/docs/introduction/vs-other-frameworks.md +0 -64
  104. package/docs/introduction/why-toil.md +0 -99
  105. package/docs/llms.txt +0 -95
  106. package/docs/realtime/README.md +0 -102
  107. package/docs/realtime/channels.md +0 -211
  108. package/docs/realtime/streams.md +0 -369
  109. package/docs/services/README.md +0 -60
  110. package/docs/services/analytics.md +0 -279
  111. package/docs/services/caching.md +0 -175
  112. package/docs/services/cookies.md +0 -235
  113. package/docs/services/crypto.md +0 -209
  114. package/docs/services/email.md +0 -289
  115. package/docs/services/environment.md +0 -141
  116. package/docs/services/ratelimit.md +0 -174
  117. package/docs/services/time.md +0 -85
  118. package/eslint.config.js +0 -57
  119. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  120. package/presets/eslint.js +0 -88
  121. package/src/backend/index.ts +0 -204
  122. package/src/cli/configure.ts +0 -363
  123. package/src/cli/create.ts +0 -924
  124. package/src/cli/db.ts +0 -158
  125. package/src/cli/diagnostics.ts +0 -665
  126. package/src/cli/doctor.ts +0 -982
  127. package/src/cli/features.ts +0 -170
  128. package/src/cli/index.ts +0 -326
  129. package/src/cli/notify.ts +0 -120
  130. package/src/cli/proc.ts +0 -56
  131. package/src/cli/ui.ts +0 -169
  132. package/src/cli/update.ts +0 -248
  133. package/src/cli/updates.ts +0 -92
  134. package/src/cli/validate.ts +0 -31
  135. package/src/cli/version-check.ts +0 -125
  136. package/src/client/auth.ts +0 -869
  137. package/src/client/channel/channel.ts +0 -146
  138. package/src/client/components/Form.tsx +0 -65
  139. package/src/client/components/Image.tsx +0 -160
  140. package/src/client/components/Script.tsx +0 -113
  141. package/src/client/components/Slot.tsx +0 -21
  142. package/src/client/dev/devtools.tsx +0 -1089
  143. package/src/client/dev/error-overlay.tsx +0 -225
  144. package/src/client/errors.ts +0 -11
  145. package/src/client/head/head.ts +0 -182
  146. package/src/client/head/metadata.ts +0 -110
  147. package/src/client/index.ts +0 -130
  148. package/src/client/navigation/Link.tsx +0 -108
  149. package/src/client/navigation/NavLink.tsx +0 -86
  150. package/src/client/navigation/navigation.ts +0 -235
  151. package/src/client/navigation/prefetch.ts +0 -169
  152. package/src/client/navigation/scroll.ts +0 -53
  153. package/src/client/routing/Router.tsx +0 -216
  154. package/src/client/routing/action.ts +0 -122
  155. package/src/client/routing/error-boundary.tsx +0 -43
  156. package/src/client/routing/hooks.ts +0 -140
  157. package/src/client/routing/lazy.ts +0 -77
  158. package/src/client/routing/loader.ts +0 -381
  159. package/src/client/routing/match.ts +0 -47
  160. package/src/client/routing/mount.tsx +0 -90
  161. package/src/client/routing/params-context.ts +0 -10
  162. package/src/client/routing/slot-context.ts +0 -7
  163. package/src/client/rpc.ts +0 -97
  164. package/src/client/search/search.ts +0 -189
  165. package/src/client/search/use-page-search.ts +0 -73
  166. package/src/client/ssr/markers.tsx +0 -172
  167. package/src/client/stream/client.ts +0 -237
  168. package/src/client/types.ts +0 -73
  169. package/src/compiler/auth-emails.ts +0 -312
  170. package/src/compiler/config.ts +0 -369
  171. package/src/compiler/docs.ts +0 -127
  172. package/src/compiler/email-preview.ts +0 -314
  173. package/src/compiler/emails.ts +0 -404
  174. package/src/compiler/fonts.ts +0 -89
  175. package/src/compiler/generate.ts +0 -471
  176. package/src/compiler/image-blur.ts +0 -42
  177. package/src/compiler/image-report.ts +0 -88
  178. package/src/compiler/index.ts +0 -1362
  179. package/src/compiler/pages.ts +0 -49
  180. package/src/compiler/plugin.ts +0 -391
  181. package/src/compiler/prerender.ts +0 -263
  182. package/src/compiler/routes.ts +0 -170
  183. package/src/compiler/seo.ts +0 -415
  184. package/src/compiler/sri.ts +0 -144
  185. package/src/compiler/ssg.ts +0 -177
  186. package/src/compiler/ssr-codegen.ts +0 -85
  187. package/src/compiler/template-build.ts +0 -676
  188. package/src/compiler/template.ts +0 -262
  189. package/src/compiler/toil-docs.generated.ts +0 -77
  190. package/src/compiler/vite.ts +0 -245
  191. package/src/devserver/analytics/index.ts +0 -317
  192. package/src/devserver/config/dotenv.ts +0 -102
  193. package/src/devserver/config/env.ts +0 -30
  194. package/src/devserver/config/ratelimit.ts +0 -123
  195. package/src/devserver/daemon/catalog.ts +0 -120
  196. package/src/devserver/daemon/cron.ts +0 -87
  197. package/src/devserver/daemon/host.ts +0 -134
  198. package/src/devserver/daemon/index.ts +0 -369
  199. package/src/devserver/daemon/runtime.ts +0 -48
  200. package/src/devserver/db/catalog.ts +0 -109
  201. package/src/devserver/db/database.ts +0 -1834
  202. package/src/devserver/db/derives.ts +0 -121
  203. package/src/devserver/db/index.ts +0 -20
  204. package/src/devserver/db/routeKinds.ts +0 -191
  205. package/src/devserver/db/types.ts +0 -168
  206. package/src/devserver/email/caps.ts +0 -0
  207. package/src/devserver/email/config.ts +0 -141
  208. package/src/devserver/email/console.ts +0 -348
  209. package/src/devserver/email/index.ts +0 -155
  210. package/src/devserver/email/providers.ts +0 -131
  211. package/src/devserver/email/status.ts +0 -23
  212. package/src/devserver/email/validate.ts +0 -37
  213. package/src/devserver/email/wire.ts +0 -55
  214. package/src/devserver/http/cache.ts +0 -0
  215. package/src/devserver/http/envelope.ts +0 -157
  216. package/src/devserver/http/proxy.ts +0 -172
  217. package/src/devserver/http/runtime.ts +0 -387
  218. package/src/devserver/index.ts +0 -43
  219. package/src/devserver/production-ipc.ts +0 -63
  220. package/src/devserver/production-worker.ts +0 -83
  221. package/src/devserver/production.ts +0 -718
  222. package/src/devserver/runtime/crypto.ts +0 -587
  223. package/src/devserver/runtime/host.ts +0 -427
  224. package/src/devserver/runtime/module.ts +0 -442
  225. package/src/devserver/server.ts +0 -251
  226. package/src/devserver/ssr.ts +0 -208
  227. package/src/devserver/stream/catalog.ts +0 -106
  228. package/src/devserver/stream/host.ts +0 -42
  229. package/src/devserver/stream/index.ts +0 -301
  230. package/src/devserver/stream/manager.ts +0 -167
  231. package/src/devserver/stream/router.ts +0 -101
  232. package/src/devserver/stream/wire.ts +0 -58
  233. package/src/devserver/stream/ws.ts +0 -78
  234. package/src/devserver/wasm/sections.ts +0 -59
  235. package/src/devserver/wasm/surface.ts +0 -115
  236. package/src/io/FastMap.ts +0 -151
  237. package/src/io/FastSet.ts +0 -113
  238. package/src/io/codec.ts +0 -362
  239. package/src/io/index.ts +0 -10
  240. package/src/io/lengths.ts +0 -14
  241. package/src/io/types.ts +0 -19
  242. package/src/logger/index.ts +0 -22
  243. package/src/shared/index.ts +0 -46
  244. package/test/analytics-dev.test.ts +0 -113
  245. package/test/assembly/aspect-shim.ts +0 -33
  246. package/test/assembly/cookie.spec.ts +0 -303
  247. package/test/assembly/example.spec.ts +0 -22
  248. package/test/assembly/ssr.spec.ts +0 -95
  249. package/test/assembly.test.ts +0 -59
  250. package/test/auth-emails.test.ts +0 -118
  251. package/test/built-ssr.test.ts +0 -148
  252. package/test/channel.test.ts +0 -21
  253. package/test/config-host-port.test.ts +0 -59
  254. package/test/configure.test.ts +0 -94
  255. package/test/daemon-build.test.ts +0 -278
  256. package/test/daemon-catalog.test.ts +0 -349
  257. package/test/daemon-emulation.test.ts +0 -256
  258. package/test/db.test.ts +0 -0
  259. package/test/devserver-database.test.ts +0 -1102
  260. package/test/devserver-email.test.ts +0 -306
  261. package/test/devserver-pqauth.test.ts +0 -93
  262. package/test/devserver-secrets.test.ts +0 -63
  263. package/test/devserver.test.ts +0 -259
  264. package/test/doctor.test.ts +0 -298
  265. package/test/dom/Image.test.tsx +0 -144
  266. package/test/dom/Link.test.tsx +0 -67
  267. package/test/dom/NavLink.test.tsx +0 -37
  268. package/test/dom/Script.test.tsx +0 -48
  269. package/test/dom/action.test.tsx +0 -146
  270. package/test/dom/error-overlay.test.tsx +0 -44
  271. package/test/dom/loader.test.tsx +0 -121
  272. package/test/dom/navigation.test.ts +0 -59
  273. package/test/dom/revalidate.test.tsx +0 -38
  274. package/test/dom/route-head.test.tsx +0 -56
  275. package/test/dom/router-loading.test.tsx +0 -44
  276. package/test/dom/scroll.test.ts +0 -56
  277. package/test/dom/slot.test.tsx +0 -131
  278. package/test/dom/use-metadata.test.tsx +0 -58
  279. package/test/dom/view-transitions.test.tsx +0 -53
  280. package/test/dynamic-routing.test.ts +0 -132
  281. package/test/email-console.test.ts +0 -86
  282. package/test/email-preview.test.ts +0 -73
  283. package/test/emails.test.ts +0 -58
  284. package/test/errors.test.ts +0 -21
  285. package/test/example-guestbook.test.ts +0 -123
  286. package/test/features.test.ts +0 -149
  287. package/test/fixtures/bignum-wire/spec.ts +0 -25
  288. package/test/fixtures/daemon-app.ts +0 -65
  289. package/test/fixtures/stream-echo.ts +0 -26
  290. package/test/fixtures/stream-gate.ts +0 -28
  291. package/test/fixtures/stream-trap.ts +0 -18
  292. package/test/fixtures/stream-typed.ts +0 -41
  293. package/test/fonts.test.ts +0 -28
  294. package/test/global-setup.ts +0 -17
  295. package/test/head.test.ts +0 -42
  296. package/test/image-blur.test.ts +0 -50
  297. package/test/io.test.ts +0 -117
  298. package/test/metadata.test.ts +0 -40
  299. package/test/navlink.test.ts +0 -28
  300. package/test/pages.test.ts +0 -105
  301. package/test/placeholder.test.ts +0 -9
  302. package/test/pqauth-e2e.test.ts +0 -216
  303. package/test/pqauth-email.test.ts +0 -801
  304. package/test/prerender.test.ts +0 -116
  305. package/test/prettier-plugin.test.ts +0 -46
  306. package/test/routes.test.ts +0 -76
  307. package/test/rpc-bignum-wire.test.ts +0 -164
  308. package/test/rpc-dispatch.test.ts +0 -132
  309. package/test/rpc-kinds.test.ts +0 -18
  310. package/test/rpc.test.ts +0 -66
  311. package/test/search.test.ts +0 -114
  312. package/test/seo.test.ts +0 -175
  313. package/test/slot-layouts.test.ts +0 -69
  314. package/test/sri.test.ts +0 -129
  315. package/test/ssg.test.ts +0 -36
  316. package/test/ssr-hydration.test.tsx +0 -122
  317. package/test/ssr-render.test.ts +0 -197
  318. package/test/ssr-template.test.tsx +0 -482
  319. package/test/stream-emulation.test.ts +0 -479
  320. package/test/ui.test.ts +0 -40
  321. package/test/update.test.ts +0 -58
  322. package/test/validate.test.ts +0 -42
  323. package/test/version-check.test.ts +0 -130
  324. package/tests/data-parity/generated-parity.ts +0 -99
  325. package/tests/data-parity/parity.ts +0 -80
  326. package/tests/data-parity/spec.ts +0 -46
@@ -1,118 +0,0 @@
1
- # Scripts
2
-
3
- `Toil.Script` loads an external or inline `<script>` for you, with control over *when* it runs and a guarantee it runs only *once* across your whole app. Use it instead of a hand-written `<script>` tag for third-party snippets (analytics, chat widgets, embeds). It is the toiljs analog of Next.js `next/script`.
4
-
5
- ## Why not just write a `<script>` tag?
6
-
7
- Dropping a raw `<script>` into your JSX is unreliable in a single-page app (an app where the browser loads one HTML shell and JavaScript swaps pages in place, with no full reload). Two problems:
8
-
9
- 1. **It may not execute.** When React inserts a `<script>` element into the page, the browser does not always run it the way it runs scripts present in the original HTML.
10
- 2. **It runs too often.** As the user navigates between routes that both render that script, React can mount it more than once, so an analytics library or a widget initialises twice.
11
-
12
- `Toil.Script` fixes both: it injects a real `<script>` into `<head>` so the browser runs it, and it **deduplicates** by a key so a given script executes at most once for the whole life of the app, even across client-side navigations. It renders nothing into your layout.
13
-
14
- ## The simplest usage: an external script
15
-
16
- Give it a `src`. By default it loads once the app is interactive, which is right for most third-party scripts:
17
-
18
- ```tsx
19
- // client/layout.tsx
20
- import { type ReactNode } from 'react';
21
-
22
- export default function Layout({ children }: { children?: ReactNode }) {
23
- return (
24
- <div className="app">
25
- <Toil.Script src="https://cdn.example-analytics.com/analytics.js" />
26
- {children}
27
- </div>
28
- );
29
- }
30
- ```
31
-
32
- Putting it in the root layout means it loads once for the whole app and stays loaded as the user navigates. For an external script, the dedup key defaults to its `src`, so you never need an `id`.
33
-
34
- ## Load strategies
35
-
36
- The `strategy` prop decides *when* the script is injected, relative to your app becoming interactive:
37
-
38
- | Strategy | When it runs | Use it for |
39
- | --- | --- | --- |
40
- | `afterInteractive` (default) | On mount, once the app is running. | Analytics, chat widgets, most third-party scripts. |
41
- | `lazyOnload` | Deferred until the browser is idle, after the page's `load` event. | Low-priority extras (a feedback button, a social embed) that should not compete with the initial render. |
42
- | `beforeInteractive` | As early as possible: injected immediately on first mount. | A script other code depends on immediately. |
43
-
44
- One honest caveat about `beforeInteractive`: a toiljs frontend is a client-only single-page app, so there is no server-rendered `<script>` to run before hydration. `beforeInteractive` therefore still runs *after* hydration, just as early and eagerly as possible on the first mount. It is a priority hint, not a true "before the page is interactive" guarantee.
45
-
46
- ```tsx
47
- <Toil.Script
48
- src="https://widget.example.com/embed.js"
49
- strategy="lazyOnload"
50
- />
51
- ```
52
-
53
- ## Inline scripts
54
-
55
- To run a snippet of code instead of loading a URL, put the code in `children` (as a **string**) and give the script an `id`. An inline script has no `src`, so the `id` is what identifies it for dedup, and it is required:
56
-
57
- ```tsx
58
- <Toil.Script id="init-theme" strategy="beforeInteractive">
59
- {`document.documentElement.dataset.theme =
60
- localStorage.getItem('theme') ?? 'light';`}
61
- </Toil.Script>
62
- ```
63
-
64
- Without an `id` (and no `src`), an inline script has nothing to dedup on, so `Toil.Script` does nothing at all. This is a deliberate no-op, not an error, so remember the `id`.
65
-
66
- ## Reacting to load
67
-
68
- Three optional callbacks let you run code around the script's lifecycle:
69
-
70
- ```tsx
71
- <Toil.Script
72
- src="https://widget.example.com/embed.js"
73
- strategy="lazyOnload"
74
- onReady={() => {
75
- // The global the script defines is now available.
76
- window.MyWidget?.init();
77
- }}
78
- onError={(err) => {
79
- console.warn('widget failed to load', err);
80
- }}
81
- />
82
- ```
83
-
84
- - `onLoad` fires **once**, when an external script finishes loading (or an inline script is inserted).
85
- - `onReady` fires after load **and on every later mount** once the script is already loaded. So if a route that renders the `Toil.Script` is left and revisited, `onReady` runs again, which is the right place to re-initialise a widget.
86
- - `onError` fires if an **external** script fails to load. After an error the script's key is cleared, so a later remount retries the load.
87
-
88
- ## All props
89
-
90
- Read them straight from the source (`src/client/components/Script.tsx`):
91
-
92
- | Prop | Type | Default | What it does |
93
- | --- | --- | --- | --- |
94
- | `src` | `string` | (none) | URL of an external script. Omit it when you provide an inline body via `children`. |
95
- | `children` | `string` | (none) | Inline script body. Mutually exclusive with `src`. |
96
- | `strategy` | `'beforeInteractive' \| 'afterInteractive' \| 'lazyOnload'` | `'afterInteractive'` | When to inject the script (see above). |
97
- | `id` | `string` | `src` for external scripts | Stable identity for dedup. **Required** for inline scripts. |
98
- | `type` | `string` | (none) | The `type` attribute, e.g. `'module'` or `'application/json'`. |
99
- | `onLoad` | `() => void` | (none) | Fired once the script has loaded (external) or been inserted (inline). |
100
- | `onReady` | `() => void` | (none) | Fired after load, and on every later mount once the script is already loaded. |
101
- | `onError` | `(error: unknown) => void` | (none) | Fired if an external script fails to load. |
102
-
103
- External scripts are injected with `async` set, so they never block other work while downloading.
104
-
105
- ## Gotchas
106
-
107
- - **Inline scripts need an `id`.** No `id` and no `src` means nothing to dedup on, so the component quietly does nothing.
108
- - **Dedup is app-wide and survives navigation.** The same `Toil.Script` rendered on two different routes runs a total of once, keyed by `id` (or `src`). This is the point, but it means you cannot use two copies of the same key to run something twice.
109
- - **Props are read at injection time, and the script re-injects only if its key or strategy changes.** Changing a handler or an inline body *without* changing the `id` will not re-run the script. If you truly need to re-inject a changed inline script, change its `id`.
110
- - **`onReady` versus `onLoad`.** Use `onLoad` for one-time setup that must happen exactly once; use `onReady` for setup that should also run each time a component remounts against an already-loaded script.
111
- - **`beforeInteractive` is not truly before hydration** in a client-only app (see the caveat above). Do not rely on it running before your React tree mounts.
112
- - **`Toil.Script` renders nothing.** It returns `null`, so you can place it anywhere in your component tree; a layout is the usual home for app-wide scripts.
113
-
114
- ## Related
115
-
116
- - [Rendering and SSR](./rendering.md): how the client-only app and its first paint fit together.
117
- - [Metadata and SEO](./metadata.md): for `<head>` tags (title, meta, Open Graph), which is a different job from loading scripts.
118
- - [Frontend overview](./README.md): the `Toil` global and the rest of the client API.
@@ -1,191 +0,0 @@
1
- # Page search
2
-
3
- toiljs ships a built-in search over your own pages: at build time it indexes every route's metadata (title, description, keywords, Open Graph), and at runtime you query that index to build a search box or a command palette that jumps straight to a page. It is entirely static (no server, no search service), and there is no equivalent in Next.js, so this page explains it from scratch.
4
-
5
- ## What it is
6
-
7
- Every route in your app can export `metadata` (its title, description, and so on, see [Metadata and SEO](./metadata.md)). The toiljs compiler reads that metadata from all your routes at build time and bakes a small **page index** into your bundle: a list of `{ path, title, description, keywords, ... }` for every page. When your app starts, that index is registered in the browser. You can then search it instantly, offline, with zero network calls, and turn a match into a navigation.
8
-
9
- This is what powers a "jump to any page" box or a `Cmd+K` command palette without you maintaining a list of pages or standing up a search backend.
10
-
11
- ```mermaid
12
- flowchart TD
13
- A["Your routes<br/>each with export const metadata"] -->|build: scan + extract| B["Static page index<br/>{ path, dynamic, metadata } per page"]
14
- B -->|baked into the bundle| C["App startup: Toil.registerPages(index)"]
15
- C --> D["Browser: usePageSearch / searchPages query the index"]
16
- D --> E["Ranked results -> goTo() navigates"]
17
- ```
18
-
19
- Only **statically-known** metadata is indexed. A route whose `<head>` comes from a dynamic `generateMetadata` (a per-request title) has nothing to index by default; to include it anyway, export `searchHints` (see below).
20
-
21
- ## The `usePageSearch` hook
22
-
23
- `usePageSearch` is the React way in. Give it the current query string, and it hands back ranked results plus a helper to navigate:
24
-
25
- ```tsx
26
- const { results, pages, goTo } = Toil.usePageSearch(query);
27
- ```
28
-
29
- It returns an object with three fields:
30
-
31
- | Field | Type | What it is |
32
- | --- | --- | --- |
33
- | `results` | `readonly PageSearchResult[]` | The ranked matches for `query`, best first. Empty when the query is blank. |
34
- | `pages` | `readonly PageMeta[]` | The full page index, handy for showing an "all pages" listing. |
35
- | `goTo` | `(target, options?) => void` | Navigates to a result, a page, or a raw path string. A stable reference (safe to pass to a child or destructure). |
36
-
37
- Each item in `results` is a `PageSearchResult`:
38
-
39
- - `page`: the matched `PageMeta`, which is `{ path, dynamic, metadata }`. `path` is the route URL (`'/about'`), `dynamic` says whether it has `:param` segments, and `metadata` is the indexed title/description/etc.
40
- - `score`: a relevance number (higher is better; always above zero for a returned result).
41
- - `matches`: which fields matched, for example `['title', 'keywords']`.
42
-
43
- The results are memoised, so they recompute only when the query (or options) change, not on every render.
44
-
45
- ### A full search box
46
-
47
- Here is a complete search page: an input, a ranked result list, and a click that navigates to the chosen page.
48
-
49
- ```tsx
50
- // client/routes/search.tsx
51
- import { useState } from 'react';
52
-
53
- export const metadata: Toil.Metadata = {
54
- title: 'Search',
55
- description: 'Find any page and jump straight to it.',
56
- keywords: ['search', 'find', 'pages'],
57
- };
58
-
59
- export default function SearchPage() {
60
- const [query, setQuery] = useState('');
61
- const { results, pages, goTo } = Toil.usePageSearch(query);
62
-
63
- return (
64
- <main>
65
- <h1>Search</h1>
66
- <input
67
- type="search"
68
- value={query}
69
- onChange={(e) => {
70
- setQuery(e.target.value);
71
- }}
72
- placeholder={`Search ${pages.length} pages...`}
73
- aria-label="Search pages"
74
- autoFocus
75
- />
76
-
77
- {query.trim() !== '' && (
78
- <ul>
79
- {results.length === 0 && <li>No pages match "{query}".</li>}
80
- {results.map((r) => (
81
- <li key={r.page.path}>
82
- <button
83
- type="button"
84
- onClick={() => {
85
- goTo(r);
86
- }}
87
- >
88
- <strong>{r.page.metadata.title ?? r.page.path}</strong>{' '}
89
- <code>{r.page.path}</code>
90
- {r.page.metadata.description !== undefined && (
91
- <p>{r.page.metadata.description}</p>
92
- )}
93
- </button>
94
- </li>
95
- ))}
96
- </ul>
97
- )}
98
- </main>
99
- );
100
- }
101
- ```
102
-
103
- `goTo` accepts a result (as above), a `PageMeta`, or a plain path string, and takes the same options as `navigate` (see [Routing](./routing.md)). Passing a result is the common case.
104
-
105
- ### Options
106
-
107
- `usePageSearch` takes a second argument to tune the search:
108
-
109
- ```tsx
110
- const { results } = Toil.usePageSearch(query, {
111
- limit: 8, // cap the number of results (after ranking)
112
- includeDynamic: true, // include :param routes (see below); default false
113
- fields: ['title', 'keywords'], // only match these fields; default all fields
114
- });
115
- ```
116
-
117
- | Option | Type | Default | Effect |
118
- | --- | --- | --- | --- |
119
- | `limit` | `number` | no cap | Keep only the top N results after ranking. |
120
- | `includeDynamic` | `boolean` | `false` | Include dynamic (`:param` / `*catch-all`) routes. Off by default because you cannot navigate to them without filling in the params. |
121
- | `fields` | array of field names | all fields | Restrict matching to a subset of `'title'`, `'description'`, `'keywords'`, `'path'`, `'openGraph'`. |
122
-
123
- ## Making dynamic routes searchable with `searchHints`
124
-
125
- A dynamic route like `client/routes/blog/[id].tsx` usually produces its `<head>` with a per-post `generateMetadata`, so there is nothing static to index, and the route is missing from search. To surface it anyway, export `searchHints`: a small static object the compiler merges into the index for that route (winning ties against any static `metadata`).
126
-
127
- ```tsx
128
- // client/routes/blog/[id].tsx
129
-
130
- // The per-post <title> is dynamic, so it cannot be indexed. These static hints
131
- // put the blog into the search index so it shows up when someone searches "blog".
132
- export const searchHints: Toil.SearchHints = {
133
- title: 'Blog',
134
- description: 'Articles and updates.',
135
- keywords: ['blog', 'posts', 'articles'],
136
- };
137
- ```
138
-
139
- `SearchHints` has three optional fields: `title`, `description`, and `keywords` (a string or an array of strings).
140
-
141
- Two things to keep in mind. First, `searchHints` only affects the index; the route is still dynamic, so it appears in results only when you pass `includeDynamic: true`. Second, you cannot `goTo` a dynamic page directly (it needs concrete params), so `goTo` is a no-op for one unless you hand it a real, filled-in path string. In practice you use `searchHints` to make a *section* discoverable (typing "blog" surfaces the blog), and point the user at a concrete landing URL.
142
-
143
- ## The lower-level API
144
-
145
- The hook is a thin wrapper over a small, framework-agnostic core you can use directly (outside React, in tests, or to build your own UI). All of these are on the `Toil` global:
146
-
147
- - `Toil.searchPages(query, options?)`: the pure ranking function behind the hook. Returns `PageSearchResult[]`. Same options as the hook.
148
- - `Toil.getPages()`: the full registered index (`readonly PageMeta[]`), including dynamic routes. Empty before registration.
149
- - `Toil.pagePath(target)`: normalises a result, a page, or a raw string down to its path string.
150
- - `Toil.registerPages(pages)`: replaces the live index. toiljs calls this for you at startup with the compiler-built index, so you rarely call it yourself. It exists for tests and advanced setups that build a custom index.
151
-
152
- ```tsx
153
- import { searchPages } from 'toiljs/client';
154
-
155
- // Outside a component: the top 5 title/keyword matches for "billing".
156
- const hits = searchPages('billing', { limit: 5, fields: ['title', 'keywords'] });
157
- ```
158
-
159
- ## How ranking works
160
-
161
- The matcher is simple and predictable:
162
-
163
- - The query is lower-cased and split on whitespace into terms. **Every** term must match somewhere (AND semantics), or the page is dropped. A blank query returns nothing.
164
- - Matching is case-insensitive and substring-based, with bonuses: an exact field match ranks highest, then a match at the start of the field, then a match at the start of a word inside the field, then a plain mid-word substring.
165
- - Each field carries a weight, so a hit in the title counts for much more than a hit in the description:
166
-
167
- | Field | Weight |
168
- | --- | --- |
169
- | `title` | 10 |
170
- | `path` | 6 |
171
- | `keywords` | 5 |
172
- | `description` | 3 |
173
- | `openGraph` | 2 |
174
-
175
- - The `path` field is made word-searchable: `/get-started` matches "get" or "started".
176
- - Results are sorted by score (highest first), ties broken alphabetically by path for a stable order, then cut to `limit` if you set one.
177
-
178
- ## Gotchas
179
-
180
- - **Only static metadata is indexed.** A route whose title comes from `generateMetadata` is invisible to search until you add `searchHints`.
181
- - **Dynamic routes are excluded by default.** They need `includeDynamic: true` to appear, and even then `goTo` cannot navigate to one without concrete params.
182
- - **The index is built, not live.** It reflects the metadata at build time. Add a route or change a title and you must rebuild for search to see it.
183
- - **A blank query returns no results**, not every page. If you want an "all pages" listing for an empty box, render `pages` yourself.
184
- - **The type names** `PageSearchResult`, `PageSearchOptions`, and `SearchField` are importable from `'toiljs/client'` if you need to annotate them; `PageMeta` and `SearchHints` are also available as `Toil.PageMeta` / `Toil.SearchHints`.
185
-
186
- ## Related
187
-
188
- - [Metadata and SEO](./metadata.md): the `metadata` the search index is built from.
189
- - [Routing](./routing.md): route paths, dynamic segments, and `navigate` (what `goTo` calls).
190
- - [Rendering and SSR](./rendering.md): where `searchHints` fits alongside `generateStaticParams` for dynamic routes.
191
- - [Frontend overview](./README.md): the `Toil` global and the rest of the client API.
@@ -1,147 +0,0 @@
1
- # Styling
2
-
3
- toiljs does not force a styling system on you. It builds with Vite, so you style your app the way you would any Vite + React project: plain CSS, CSS Modules, a preprocessor like Sass, or Tailwind. This page shows the practical options.
4
-
5
- ## The one import that matters
6
-
7
- Your app pulls in global styles from the entry file, `client/toil.tsx`:
8
-
9
- ```tsx
10
- // client/toil.tsx
11
- import { routes, layout, notFound, globalError, slots } from 'toiljs/routes';
12
- import './styles/main.css'; // <- your global stylesheet
13
-
14
- Toil.mount(routes, layout, notFound, globalError, slots);
15
- ```
16
-
17
- That one import is where your global CSS (resets, CSS variables, base element styles) lives. A typical `client/styles/main.css` sets up theme variables and base styles:
18
-
19
- ```css
20
- /* client/styles/main.css */
21
- :root {
22
- --accent: #2563ff;
23
- --bg: #080d11;
24
- --text: #f5f6fa;
25
- }
26
-
27
- *, *::before, *::after {
28
- box-sizing: border-box;
29
- }
30
-
31
- body {
32
- margin: 0;
33
- background: var(--bg);
34
- color: var(--text);
35
- font-family: system-ui, -apple-system, sans-serif;
36
- line-height: 1.6;
37
- }
38
- ```
39
-
40
- ## Importing CSS anywhere
41
-
42
- You are not limited to the one global sheet. Any component can import its own CSS, and Vite bundles it in:
43
-
44
- ```tsx
45
- // client/components/Card.tsx
46
- import './card.css';
47
-
48
- export default function Card() {
49
- return <div className="card">...</div>;
50
- }
51
- ```
52
-
53
- The class names in a plain `.css` file are global (they apply everywhere), so name them carefully to avoid clashes, or reach for CSS Modules.
54
-
55
- ## CSS Modules (scoped class names)
56
-
57
- If you name a file `*.module.css`, Vite treats it as a **CSS Module**: the class names are locally scoped to the component that imports them, so two components can both use `.title` without colliding. You import the generated names as an object:
58
-
59
- ```tsx
60
- // client/components/Card.tsx
61
- import styles from './card.module.css';
62
-
63
- export default function Card() {
64
- return <div className={styles.card}>...</div>;
65
- }
66
- ```
67
-
68
- ```css
69
- /* client/components/card.module.css */
70
- .card {
71
- padding: 1rem;
72
- border: 1px solid var(--border);
73
- }
74
- ```
75
-
76
- This is the simplest way to get component-scoped styles with no extra tooling.
77
-
78
- ## Preprocessors: Sass, Less, Stylus
79
-
80
- When you create a project (`toiljs create`) you can pick a CSS preprocessor, or add one later to an existing project with the `configure` command:
81
-
82
- ```sh
83
- toiljs configure # interactive: choose preprocessor + Tailwind
84
- toiljs configure --style sass # switch the preprocessor to Sass
85
- ```
86
-
87
- `configure` installs the right packages and rewrites your style imports for you. After that, `.scss` / `.sass` / `.less` / `.styl` files import exactly like `.css` files, and Vite compiles them.
88
-
89
- ## Tailwind
90
-
91
- To use Tailwind (a utility-class CSS framework), turn it on at create time or add it later:
92
-
93
- ```sh
94
- toiljs configure --tailwind
95
- ```
96
-
97
- Tailwind lives in its own stylesheet, `client/styles/tailwind.css`, which is just:
98
-
99
- ```css
100
- @import "tailwindcss";
101
- ```
102
-
103
- `configure` wires that import in for you. From then on you use Tailwind's utility classes directly in your JSX:
104
-
105
- ```tsx
106
- export default function Cta() {
107
- return (
108
- <button className="rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white">
109
- Get started
110
- </button>
111
- );
112
- }
113
- ```
114
-
115
- ## Inline styles and the `style` prop
116
-
117
- Standard React inline styles work as always, and are handy for one-off, dynamic values:
118
-
119
- ```tsx
120
- <div style={{ padding: 24, background: 'var(--surface)' }}>...</div>
121
- ```
122
-
123
- Prefer classes for anything reused; keep inline styles for the truly per-instance case (a computed width, a dynamic color).
124
-
125
- ## How types work for style and asset imports
126
-
127
- toiljs generates a `toil-env.d.ts` in your project that declares the import types, so TypeScript is happy importing stylesheets and images. It covers `.css`, `.scss`, `.sass`, `.less`, `.styl` (and friends), plus image formats (`.svg`, `.png`, `.jpg`, `.webp`, and so on), which import as a URL string:
128
-
129
- ```tsx
130
- import logoUrl from './logo.svg'; // logoUrl is a string (a hashed URL)
131
-
132
- <img src={logoUrl} alt="Logo" />
133
- ```
134
-
135
- For images specifically, prefer the `Toil.Image` component and its `?toil` import, which also handle layout shift and blur placeholders (see [Images](./images.md)).
136
-
137
- ## Gotchas
138
-
139
- - **Global CSS is global.** A plain `.css` import puts its class names in one shared namespace. If two files both define `.button`, the last one loaded wins. Use `*.module.css` (CSS Modules) or unique prefixes to scope styles.
140
- - **Do not hand-edit `toil-env.d.ts`.** It is generated. If a new file type is not recognized, re-run the build so it regenerates, rather than editing it.
141
- - **Preprocessor packages are managed by `configure`.** Add or switch preprocessors through `toiljs configure` so the packages and imports stay in sync; do not install them by hand.
142
-
143
- ## Related
144
-
145
- - [Images](./images.md): the `Toil.Image` component and image imports.
146
- - [The CLI](../cli/README.md): `toiljs configure` and every flag.
147
- - [Frontend overview](./README.md): where styles fit in the `client/` folder.