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,189 +0,0 @@
1
- /**
2
- * Site-wide page search over route {@link Metadata}. The compiler bakes a static index of every
3
- * page's title/description/keywords/OpenGraph (extracted from each route's `export const metadata`)
4
- * into the generated bundle and registers it with {@link registerPages} at startup. User code then
5
- * queries it with {@link searchPages} (pure, framework-agnostic) or the {@link usePageSearch} hook,
6
- * getting back ranked pages with their `path` ready to feed to `Link` / `navigate`.
7
- *
8
- * Only statically-analyzable metadata is indexed: a route's `generateMetadata` (dynamic, per-request)
9
- * and computed values can't be known at build time. A dynamic route can still be made discoverable
10
- * by exporting static {@link SearchHints} (`export const searchHints = { title, keywords, … }`),
11
- * which the compiler merges over the route's static `metadata` when building the index.
12
- */
13
- import type { Metadata } from '../head/metadata.js';
14
-
15
- /**
16
- * Static search hints a route can `export const searchHints` to seed the search index, useful when
17
- * the route's real `<head>` is produced by a dynamic `generateMetadata` (so nothing else is
18
- * statically indexable). Merged over the route's static `metadata`, winning ties.
19
- */
20
- export interface SearchHints {
21
- /** Indexed as the page title (highest-weighted field). */
22
- readonly title?: string;
23
- /** Indexed as the page description. */
24
- readonly description?: string;
25
- /** Indexed keywords (string or array). */
26
- readonly keywords?: string | readonly string[];
27
- }
28
-
29
- /** A searchable page: its route pattern plus the statically-known metadata baked at build time. */
30
- export interface PageMeta {
31
- /** Route URL pattern, e.g. `'/'`, `'/about'`, `'/blog/:id'`. */
32
- readonly path: string;
33
- /** Whether `path` has dynamic (`:param` / `*catch-all`) segments, not navigable without params. */
34
- readonly dynamic: boolean;
35
- /** The page's statically-extracted metadata (empty object when the route declares none). */
36
- readonly metadata: Metadata;
37
- }
38
-
39
- /** A metadata field that {@link searchPages} can match against. */
40
- export type SearchField = 'title' | 'description' | 'keywords' | 'path' | 'openGraph';
41
-
42
- /** Options for {@link searchPages}. */
43
- export interface PageSearchOptions {
44
- /** Cap the number of results returned (after ranking). Default: no cap. */
45
- readonly limit?: number;
46
- /** Include dynamic (`:param` / `*`) routes, which can't be navigated to as-is. Default: `false`. */
47
- readonly includeDynamic?: boolean;
48
- /** Restrict matching to these fields. Default: every searchable field. */
49
- readonly fields?: readonly SearchField[];
50
- }
51
-
52
- /** A page that matched a query, with its relevance {@link score} and the fields that matched. */
53
- export interface PageSearchResult {
54
- readonly page: PageMeta;
55
- /** Relevance score (higher = better); always `> 0` for a returned result. */
56
- readonly score: number;
57
- /** The metadata fields that contributed to the match, e.g. `['title', 'keywords']`. */
58
- readonly matches: readonly SearchField[];
59
- }
60
-
61
- /** Relative weight of each field, title is the strongest signal, OpenGraph the weakest. */
62
- const FIELD_WEIGHT: Record<SearchField, number> = {
63
- title: 10,
64
- path: 6,
65
- keywords: 5,
66
- description: 3,
67
- openGraph: 2,
68
- };
69
-
70
- const ALL_FIELDS: readonly SearchField[] = [
71
- 'title',
72
- 'description',
73
- 'keywords',
74
- 'path',
75
- 'openGraph',
76
- ];
77
-
78
- /** The live page index, populated by {@link registerPages} from the compiler-generated bundle. */
79
- let registry: readonly PageMeta[] = [];
80
-
81
- /**
82
- * Registers the project's page index. Called once at startup by the generated `globals` module
83
- * (`Toil.registerPages(pages)`); replaces any previous registration. Rarely called by user code,
84
- * but exposed for tests and advanced setups that build their own index.
85
- */
86
- export function registerPages(pages: readonly PageMeta[]): void {
87
- registry = pages;
88
- }
89
-
90
- /** The registered page index (every page, including dynamic ones). Empty before registration. */
91
- export function getPages(): readonly PageMeta[] {
92
- return registry;
93
- }
94
-
95
- /** Normalizes a search target (a result, a page, or a raw path) to its route path string. */
96
- export function pagePath(target: string | PageMeta | PageSearchResult): string {
97
- if (typeof target === 'string') return target;
98
- return 'page' in target ? target.page.path : target.path;
99
- }
100
-
101
- /** Joins a page's keyword list (string or array) into one searchable string. */
102
- function keywordsText(keywords: Metadata['keywords']): string {
103
- if (keywords === undefined) return '';
104
- return typeof keywords === 'string' ? keywords : keywords.join(' ');
105
- }
106
-
107
- /** The searchable text for one field of a page (empty string when the field is unset). */
108
- function fieldText(page: PageMeta, field: SearchField): string {
109
- const m = page.metadata;
110
- switch (field) {
111
- case 'title':
112
- return m.title ?? '';
113
- case 'description':
114
- return m.description ?? '';
115
- case 'keywords':
116
- return keywordsText(m.keywords);
117
- case 'path':
118
- // Make slugs word-searchable: '/get-started' → 'get started', '/blog/:id' → 'blog id'.
119
- return page.path.replace(/[/:*\-_]+/g, ' ').trim();
120
- case 'openGraph': {
121
- const og = m.openGraph;
122
- if (!og) return '';
123
- return [og.title, og.description, og.siteName, og.type].filter(Boolean).join(' ');
124
- }
125
- }
126
- }
127
-
128
- /** Whether the character before `index` is a word boundary (start of string or non-alphanumeric). */
129
- function isWordStart(text: string, index: number): boolean {
130
- return index === 0 || !/[a-z0-9]/i.test(text[index - 1]);
131
- }
132
-
133
- /**
134
- * Scores a single field against one query term, returning `0` for no match. Substring matches count;
135
- * a whole-field exact match, a prefix match, and a word-boundary match each rank progressively higher.
136
- */
137
- function scoreTerm(text: string, term: string, weight: number): number {
138
- const index = text.indexOf(term);
139
- if (index === -1) return 0;
140
- if (text === term) return weight * 3; // the field IS the term
141
- if (index === 0) return weight * 1.6; // prefix of the field
142
- if (isWordStart(text, index)) return weight * 1.2; // start of a word within the field
143
- return weight; // mid-word substring
144
- }
145
-
146
- /**
147
- * Searches the registered page index for `query`, returning pages ranked by relevance (best first).
148
- * Matching is case-insensitive; the query is split on whitespace and every term must match somewhere
149
- * (AND semantics) for a page to be included. An empty query returns no results. Dynamic routes are
150
- * excluded unless {@link PageSearchOptions.includeDynamic} is set, since they need params to navigate.
151
- */
152
- export function searchPages(query: string, options: PageSearchOptions = {}): PageSearchResult[] {
153
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
154
- if (terms.length === 0) return [];
155
- const fields = options.fields ?? ALL_FIELDS;
156
-
157
- const results: PageSearchResult[] = [];
158
- for (const page of registry) {
159
- if (page.dynamic && !options.includeDynamic) continue;
160
-
161
- const texts = fields.map((field) => ({
162
- field,
163
- text: fieldText(page, field).toLowerCase(),
164
- }));
165
- const matched = new Set<SearchField>();
166
- let score = 0;
167
- // AND semantics: every term must hit at least one field, or the page is dropped.
168
- const allTermsMatch = terms.every((term) => {
169
- let termScore = 0;
170
- for (const { field, text } of texts) {
171
- if (!text) continue;
172
- const s = scoreTerm(text, term, FIELD_WEIGHT[field]);
173
- if (s > 0) {
174
- termScore += s;
175
- matched.add(field);
176
- }
177
- }
178
- score += termScore;
179
- return termScore > 0;
180
- });
181
- if (allTermsMatch && score > 0) {
182
- results.push({ page, score, matches: [...matched] });
183
- }
184
- }
185
-
186
- // Best score first; ties broken by path for a stable, deterministic order.
187
- results.sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path));
188
- return options.limit !== undefined ? results.slice(0, options.limit) : results;
189
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * React binding for the page-metadata {@link searchPages search}. Gives a route component reactive,
3
- * memoized search results plus a `goTo` helper that navigates straight to a matched page, a drop-in
4
- * for a site-wide "jump to page" / command-palette style search box.
5
- */
6
- import { useMemo } from 'react';
7
-
8
- import { navigate, type NavigateOptions } from '../navigation/navigation.js';
9
- import type { Href } from '../types.js';
10
- import {
11
- getPages,
12
- type PageMeta,
13
- pagePath,
14
- type PageSearchOptions,
15
- type PageSearchResult,
16
- searchPages,
17
- } from './search.js';
18
-
19
- /** What {@link usePageSearch} returns. */
20
- export interface PageSearch {
21
- /** Ranked matches for the current query (best first); empty when the query is blank. */
22
- readonly results: readonly PageSearchResult[];
23
- /** The full registered page index (handy for rendering an "all pages" listing). */
24
- readonly pages: readonly PageMeta[];
25
- /**
26
- * Navigates to a result / page / raw path. A dynamic (`:param`) page can't be navigated to
27
- * as-is, so passing one (or its result) is a no-op unless you pass a concrete path string with
28
- * the params already filled in. A stable reference, safe to destructure.
29
- */
30
- readonly goTo: (
31
- target: string | PageMeta | PageSearchResult,
32
- options?: NavigateOptions,
33
- ) => void;
34
- }
35
-
36
- /** Whether a path can be navigated to directly (no unfilled dynamic segments). */
37
- function isNavigable(path: string): boolean {
38
- return !/[:*]/.test(path);
39
- }
40
-
41
- /**
42
- * Searches the project's pages by `query` and returns ranked {@link PageSearchResult}s, recomputed
43
- * only when the query or options change. Use the returned `goTo` to redirect to a match:
44
- *
45
- * ```tsx
46
- * const { results, goTo } = usePageSearch(query);
47
- * return results.map((r) => (
48
- * <button key={r.page.path} onClick={() => { goTo(r); }}>{r.page.metadata.title ?? r.page.path}</button>
49
- * ));
50
- * ```
51
- */
52
- export function usePageSearch(query: string, options: PageSearchOptions = {}): PageSearch {
53
- const { limit, includeDynamic, fields } = options;
54
- const fieldsKey = fields?.join(',');
55
- const results = useMemo(
56
- () => searchPages(query, { limit, includeDynamic, fields }),
57
- // `fields` is compared by content (fieldsKey) so a fresh array literal each render is fine.
58
- [query, limit, includeDynamic, fieldsKey],
59
- );
60
-
61
- return useMemo<PageSearch>(
62
- () => ({
63
- results,
64
- pages: getPages(),
65
- goTo(target, navOptions) {
66
- const path = pagePath(target);
67
- if (typeof target !== 'string' && !isNavigable(path)) return;
68
- navigate(path as Href, navOptions);
69
- },
70
- }),
71
- [results],
72
- );
73
- }
@@ -1,172 +0,0 @@
1
- /**
2
- * Edge-SSR hole markers. A route opts into server rendering by wrapping its
3
- * dynamic bits in these components; the compiler's template extractor finds
4
- * them deterministically (rather than guessing which `{expr}` is dynamic).
5
- *
6
- * The markers are TRANSPARENT at runtime: in the browser `<Hole>` renders its
7
- * children, `<Repeat>` renders `each.map(...)`, `<RawHtml>` renders a
8
- * `dangerouslySetInnerHTML` wrapper, `<Island>` renders its children. So the
9
- * client's React tree is exactly the normal app.
10
- *
11
- * Under the BUILD extractor (which calls {@link __setSsrBuild}(true) before
12
- * `renderToStaticMarkup`), each marker instead emits a unique PUA sentinel
13
- * token marking an insertion point. The extractor strips the tokens, records
14
- * their byte offsets, and emits the `.tmpl` + `.slots`. PUA codepoints
15
- * (U+E000..) never occur in real HTML and React serialises them verbatim, so
16
- * the tokens are collision-proof and strip to zero bytes.
17
- *
18
- * Because the static scaffold around every hole is React's OWN
19
- * `renderToStaticMarkup` output, the browser's `hydrateRoot` sees byte-
20
- * identical markup as long as the guest escapes hole values the same way React
21
- * does (it does: see `server/runtime/ssr/escape.ts`).
22
- */
23
-
24
- import { createElement, Fragment, type ReactNode, useEffect, useState } from 'react';
25
-
26
- /** Token framing codepoints (Unicode Private Use Area). */
27
- export const SENTINEL_START = String.fromCharCode(0xe000);
28
- export const SENTINEL_SEP = String.fromCharCode(0xe001);
29
- export const SENTINEL_END = String.fromCharCode(0xe002);
30
-
31
- /** Kind chars embedded in a sentinel token. Lowercase letters; `R`/`r` open and
32
- * close a repeat region. */
33
- export const enum HoleKindChar {
34
- Text = 't',
35
- Raw = 'h',
36
- Attr = 'a',
37
- RepeatOpen = 'R',
38
- RepeatClose = 'r',
39
- }
40
-
41
- let ssrBuild = false;
42
-
43
- /** Build-extractor switch. Flipped on around a `renderToStaticMarkup` pass so
44
- * the markers emit sentinels; left `false` in the browser bundle so they are
45
- * transparent. */
46
- export function __setSsrBuild(on: boolean): void {
47
- ssrBuild = on;
48
- }
49
-
50
- /** `true` while the extractor is rendering (test/diagnostic use). */
51
- export function __isSsrBuild(): boolean {
52
- return ssrBuild;
53
- }
54
-
55
- /** A self-closing insertion-point token, e.g. `␀t<id>␂`. */
56
- function token(kind: HoleKindChar, id: string): string {
57
- return SENTINEL_START + kind + id + SENTINEL_END;
58
- }
59
-
60
- /** Wrap a string so a component always returns a `ReactElement` (a Fragment
61
- * with one text child renders the string verbatim). */
62
- function textNode(s: string): ReactNode {
63
- return createElement(Fragment, null, s);
64
- }
65
-
66
- export interface HoleProps {
67
- /** Stable hole name; the extractor maps it to a numeric slot id. */
68
- id: string;
69
- children?: ReactNode;
70
- }
71
-
72
- /** A scalar text hole. Client: renders `children`. Build: a text insertion
73
- * point the guest fills with the React-escaped value. */
74
- export function Hole(props: HoleProps): ReactNode {
75
- if (ssrBuild) return textNode(token(HoleKindChar.Text, props.id));
76
- return createElement(Fragment, null, props.children);
77
- }
78
-
79
- /**
80
- * An attribute-value hole. Unlike the child markers, an attribute is not a child
81
- * node, so this is a *function* used in attribute position rather than a JSX
82
- * element:
83
- *
84
- * ```tsx
85
- * <a href={attr('profileUrl', d.url)} class={attr('cls', d.cls)}>…</a>
86
- * ```
87
- *
88
- * Browser: returns `value` unchanged, so the attribute renders normally. Build:
89
- * returns a PUA sentinel token, so the extractor records an `attr` slot at the
90
- * attribute's byte offset (the token sits between the quotes and strips to zero
91
- * bytes). The guest fills it per request with the React-escaped value (kind
92
- * `attr`), which the host splices at that offset — exactly like a text hole, but
93
- * inside an attribute. It composes with surrounding literal text
94
- * (`` `btn ${attr('x', v)}` ``), and `setAttr`/`HtmlBuilder.attr` produce the
95
- * value on the server side.
96
- */
97
- export function attr(id: string, value: string): string {
98
- return ssrBuild ? token(HoleKindChar.Attr, id) : value;
99
- }
100
-
101
- export interface RawHtmlProps {
102
- id: string;
103
- /** Raw HTML string. The author owns sanitisation (same as React
104
- * `dangerouslySetInnerHTML`). */
105
- html: string;
106
- /** Wrapper element tag (raw HTML needs a host element to live in so server
107
- * and client DOM match). Defaults to `div`. */
108
- as?: keyof React.JSX.IntrinsicElements;
109
- }
110
-
111
- /** A raw-HTML block hole. Client: `<as dangerouslySetInnerHTML>`. Build:
112
- * `<as>SENTINEL</as>` so the `.tmpl` carries the wrapper and the guest fills
113
- * its inner HTML. */
114
- export function RawHtml(props: RawHtmlProps): ReactNode {
115
- const tag = props.as ?? 'div';
116
- if (ssrBuild) {
117
- return createElement(tag, null, token(HoleKindChar.Raw, props.id));
118
- }
119
- return createElement(tag, { dangerouslySetInnerHTML: { __html: props.html } });
120
- }
121
-
122
- export interface RepeatProps<T> {
123
- id: string;
124
- /** The data rows. The build extractor requires a representative sample with
125
- * at least one row to capture the row sub-template. */
126
- each: readonly T[];
127
- children: (item: T, index: number) => ReactNode;
128
- }
129
-
130
- /** A repeat region. Client: `each.map(children)`. Build: a region wrapping
131
- * exactly ONE representative row (so the extractor captures the row sub-
132
- * template + its nested holes); the host inserts the guest's pre-stamped,
133
- * concatenated rows at the region offset. */
134
- export function Repeat<T>(props: RepeatProps<T>): ReactNode {
135
- if (ssrBuild) {
136
- const sample = props.each.length > 0 ? props.each[0] : undefined;
137
- return createElement(
138
- Fragment,
139
- null,
140
- token(HoleKindChar.RepeatOpen, props.id),
141
- sample !== undefined ? props.children(sample, 0) : null,
142
- token(HoleKindChar.RepeatClose, props.id),
143
- );
144
- }
145
- return createElement(
146
- Fragment,
147
- null,
148
- // Each row is wrapped in a keyed Fragment so React has a stable list key (the
149
- // row markup itself need not carry one). Index keys are fine here: an SSR'd
150
- // region hydrates 1:1 against the host's pre-stamped rows and does not reorder.
151
- props.each.map((item, i) => createElement(Fragment, { key: i }, props.children(item, i))),
152
- );
153
- }
154
-
155
- export interface IslandProps {
156
- children?: ReactNode;
157
- }
158
-
159
- /** A client-only escape hatch for content outside the server-template subset.
160
- * Build: renders nothing (empty in the server HTML). Client: ALSO renders nothing
161
- * on the first (hydration) render, so it matches the empty server markup, then a
162
- * mount effect reveals `children` on the next commit. Rendering the children
163
- * during hydration instead would diverge from the server's empty markup and trip
164
- * a hydration mismatch. So an island gets no first-paint / SEO, by design. */
165
- export function Island(props: IslandProps): ReactNode {
166
- const [hydrated, setHydrated] = useState(false);
167
- useEffect(() => {
168
- setHydrated(true);
169
- }, []);
170
- if (ssrBuild || !hydrated) return null;
171
- return createElement(Fragment, null, props.children);
172
- }
@@ -1,237 +0,0 @@
1
- /**
2
- * Client runtime for the typed `@stream` channel (doc 08 section 8.2). `Server.Stream.<Class>.connect(path?)`
3
- * opens a browser `WebTransport` session to the class's `route` on the production edge (an `https://`
4
- * stream-tier origin), or a `WebSocket` against the dev server (a `ws(s)://` origin), and returns a channel:
5
- * `onMessage` / `send` / `onClose` / `close`. A raw `@stream` channel sends `Uint8Array`; a typed
6
- * `@stream({ message: T })` channel sends the `@data` class and encodes it on send (the per-class
7
- * encoder the generated module passes). The inbound reply is always raw bytes. The generated
8
- * `shared/server.ts` (toilscript hot pass) attaches `makeStreamClient(routes, undefined, encoders)` to
9
- * `globalThis.__toilStream`, and `Server.Stream`
10
- * (`rpc.ts`) surfaces it - the same wiring the REST client uses via `globalThis.__toilRest`.
11
- */
12
-
13
- /** A live `@stream` connection. `TSend` is the outbound message type: `Uint8Array` for a raw `@stream`,
14
- * or the `@data` class for a typed `@stream({ message: T })` (the channel encodes it before sending).
15
- * The inbound reply is ALWAYS raw bytes - the server's `StreamOutbound` is raw (doc 03 2.5). */
16
- export interface StreamChannel<TSend = Uint8Array> {
17
- /** Register the inbound-frame handler (one call per server reply frame). */
18
- onMessage(cb: (data: Uint8Array) => void): void;
19
- /** Send one outbound message (one `@message` on the server); a typed channel encodes it first. */
20
- send(data: TSend): void;
21
- /** Register the close handler (`code` is the `0x02xx` stream close code, or the WS code). */
22
- onClose(cb: (code: number) => void): void;
23
- /** Close the connection. */
24
- close(): void;
25
- }
26
-
27
- /** The connect factory for one `@stream` class. `path` is the `@connect` path (default `''`). */
28
- export interface StreamConnectable<TSend = Uint8Array> {
29
- connect(path?: string): Promise<StreamChannel<TSend>>;
30
- }
31
-
32
- /** `Server.Stream`: one `connect()` factory per `@stream` class name. */
33
- export type StreamClient = Record<string, StreamConnectable>;
34
-
35
- /** Open a WebSocket to `url` and resolve a channel once the upgrade completes; reject if the socket
36
- * closes/errors BEFORE it opens (a 421 redirect / wrong-node / unreachable). A server close AFTER
37
- * open (e.g. a `@connect` reject or a guest reject) surfaces through `onClose(code)`. */
38
- function connectStream<TSend = Uint8Array>(
39
- url: string,
40
- encode?: (msg: never) => Uint8Array,
41
- ): Promise<StreamChannel<TSend>> {
42
- return new Promise((resolve, reject) => {
43
- const ws = new WebSocket(url);
44
- ws.binaryType = 'arraybuffer';
45
- let opened = false;
46
- let messageCb: ((data: Uint8Array) => void) | undefined;
47
- let closeCb: ((code: number) => void) | undefined;
48
- const pending: Uint8Array[] = [];
49
-
50
- const channel: StreamChannel<TSend> = {
51
- onMessage: (cb): void => {
52
- messageCb = cb;
53
- for (const m of pending) cb(m);
54
- pending.length = 0;
55
- },
56
- onClose: (cb): void => {
57
- closeCb = cb;
58
- },
59
- send: (data): void => {
60
- if (ws.readyState !== WebSocket.OPEN) return;
61
- // A typed channel encodes the @data message; a raw channel sends the bytes as-is.
62
- const bytes = encode ? encode(data as never) : (data as unknown as Uint8Array);
63
- try {
64
- ws.send(bytes as BufferSource);
65
- } catch {
66
- /* socket transitioned OPEN -> CLOSING between the readyState check and send */
67
- }
68
- },
69
- close: (): void => {
70
- ws.close();
71
- },
72
- };
73
-
74
- ws.addEventListener('open', () => {
75
- opened = true;
76
- resolve(channel);
77
- });
78
- ws.addEventListener('message', (event: MessageEvent) => {
79
- if (!(event.data instanceof ArrayBuffer)) return;
80
- const bytes = new Uint8Array(event.data);
81
- // Buffer a reply that arrives before onMessage() registers (mirror the WebTransport path),
82
- // so an eager server reply is not silently dropped.
83
- if (messageCb) messageCb(bytes);
84
- else pending.push(bytes);
85
- });
86
- ws.addEventListener('close', (event: CloseEvent) => {
87
- if (!opened) reject(new Error(`stream connect failed (closed ${String(event.code)})`));
88
- else closeCb?.(event.code);
89
- });
90
- ws.addEventListener('error', () => {
91
- if (!opened) reject(new Error('stream connect error'));
92
- });
93
- });
94
- }
95
-
96
- /** Open a browser `WebTransport` session to `url` (an `https://` stream-tier origin) and resolve a
97
- * channel once the session is ready. The browser drives the QUIC handshake, H3 Extended-CONNECT, and
98
- * the RFC 9297 Quarter-Stream-ID datagram framing, so `send`/`onMessage` deal in raw bytes (no manual
99
- * prefix). Rejects if the session fails to open (wrong node / unreachable / cert); a server close AFTER
100
- * open surfaces via `onClose(code)`. This is the PRODUCTION transport - the L2/L3 edge is
101
- * WebTransport-only; the dev server uses the `connectStream` WebSocket above. */
102
- function connectStreamWT<TSend = Uint8Array>(
103
- url: string,
104
- encode?: (msg: never) => Uint8Array,
105
- ): Promise<StreamChannel<TSend>> {
106
- return new Promise((resolve, reject) => {
107
- if (!('WebTransport' in globalThis)) {
108
- reject(new Error('WebTransport is not available in this browser'));
109
- return;
110
- }
111
- let transport: WebTransport;
112
- try {
113
- transport = new WebTransport(url);
114
- } catch (e) {
115
- reject(e instanceof Error ? e : new Error(String(e)));
116
- return;
117
- }
118
- let messageCb: ((data: Uint8Array) => void) | undefined;
119
- let closeCb: ((code: number) => void) | undefined;
120
- let writer: WritableStreamDefaultWriter<Uint8Array> | undefined;
121
- let opened = false;
122
- const pending: Uint8Array[] = [];
123
-
124
- const channel: StreamChannel<TSend> = {
125
- onMessage: (cb): void => {
126
- messageCb = cb;
127
- for (const m of pending) cb(m);
128
- pending.length = 0;
129
- },
130
- onClose: (cb): void => {
131
- closeCb = cb;
132
- },
133
- send: (data): void => {
134
- if (!writer) return;
135
- const bytes = encode ? encode(data as never) : (data as unknown as Uint8Array);
136
- // Fire-and-forget; a write that rejects (transport closing / backpressure) is surfaced
137
- // via onClose, so swallow it here to avoid an unhandled promise rejection.
138
- void writer.write(bytes).catch(() => {});
139
- },
140
- close: (): void => {
141
- try {
142
- transport.close();
143
- } catch {
144
- /* already closing */
145
- }
146
- },
147
- };
148
-
149
- // A close AFTER open (guest reject, idle sweep, server shutdown) reports through onClose; a
150
- // failure BEFORE open rejects the connect() promise via the `ready` catch below.
151
- transport.closed
152
- .then((info) => {
153
- if (opened) closeCb?.(info?.closeCode ?? 0);
154
- })
155
- .catch(() => {
156
- if (opened) closeCb?.(1);
157
- });
158
-
159
- transport.ready
160
- .then(() => {
161
- opened = true;
162
- writer = transport.datagrams.writable.getWriter();
163
- const reader = transport.datagrams.readable.getReader();
164
- void (async (): Promise<void> => {
165
- try {
166
- for (;;) {
167
- const { value, done } = await reader.read();
168
- if (done) break;
169
- const bytes =
170
- value instanceof Uint8Array
171
- ? value
172
- : new Uint8Array(value as ArrayBufferLike);
173
- if (messageCb) messageCb(bytes);
174
- else pending.push(bytes);
175
- }
176
- } catch {
177
- /* read loop ended on session close */
178
- }
179
- })();
180
- resolve(channel);
181
- })
182
- .catch((e) => reject(e instanceof Error ? e : new Error(String(e))));
183
- });
184
- }
185
-
186
- /** Pick the transport by origin scheme: `https://` -> WebTransport (the production L2/L3 edge),
187
- * `ws(s)://` -> WebSocket (the dev server). The generated `shared/server.ts` sets the scheme per build
188
- * (edge: `https://wt.<tenant>`; dev: same-origin `wss://`). */
189
- function openChannel<TSend = Uint8Array>(
190
- url: string,
191
- encode?: (msg: never) => Uint8Array,
192
- ): Promise<StreamChannel<TSend>> {
193
- return url.startsWith('https://')
194
- ? connectStreamWT<TSend>(url, encode)
195
- : connectStream<TSend>(url, encode);
196
- }
197
-
198
- /** The same-origin WebSocket base (`ws://` / `wss://` per the page protocol). */
199
- function defaultOrigin(): string {
200
- const loc = globalThis.location;
201
- return `${loc.protocol === 'https:' ? 'wss:' : 'ws:'}//${loc.host}`;
202
- }
203
-
204
- /** Resolve the stream-tier origin when the generated client passes none. A deploy/runtime override -
205
- * `globalThis.__TOIL_STREAM_ORIGIN__` (e.g. `"https://wt.dacely.com"`, the production L2/L3 edge) -
206
- * wins; otherwise fall back to the same-origin WebSocket base (the dev server). The edge override is
207
- * `https://` so `openChannel` selects WebTransport; the dev fallback is `ws(s)://` so it selects
208
- * WebSocket. This is how a deployed app points at its `wt.<tenant>` tier without the build knowing it. */
209
- function resolveStreamOrigin(): string {
210
- const override = (globalThis as { __TOIL_STREAM_ORIGIN__?: unknown }).__TOIL_STREAM_ORIGIN__;
211
- if (typeof override === 'string' && override.length > 0) return override;
212
- return defaultOrigin();
213
- }
214
-
215
- /**
216
- * Build the `Server.Stream` client from the generated route map (`{ ClassName: route }`). `origin`
217
- * defaults to the page origin. `encoders` carries one `@data` encoder per typed `@stream({ message: T })`
218
- * class (the generated `(m) => m.encode()`); a class with no entry is a raw byte channel. The generated
219
- * `shared/server.ts` calls this and assigns the result to `globalThis.__toilStream`.
220
- */
221
- export function makeStreamClient(
222
- routes: Record<string, string>,
223
- origin?: string,
224
- encoders?: Record<string, (msg: never) => Uint8Array>,
225
- ): StreamClient {
226
- const client: StreamClient = {};
227
- for (const [name, route] of Object.entries(routes)) {
228
- const encode = encoders?.[name];
229
- client[name] = {
230
- // Resolve the origin LAZILY, per connect() - so a deploy/app that sets
231
- // `__TOIL_STREAM_ORIGIN__` after this module loads is still honoured.
232
- connect: (path = ''): Promise<StreamChannel> =>
233
- openChannel(`${origin ?? resolveStreamOrigin()}${route}${path}`, encode),
234
- };
235
- }
236
- return client;
237
- }