toiljs 0.0.116 → 0.0.118

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 (337) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +16 -7
  3. package/build/backend/.tsbuildinfo +1 -1
  4. package/build/cli/.tsbuildinfo +1 -1
  5. package/build/cli/index.js +2375 -258
  6. package/build/client/.tsbuildinfo +1 -1
  7. package/build/client/auth.d.ts +3 -0
  8. package/build/client/auth.js +19 -10
  9. package/build/client/dev/devtools.js +1 -1
  10. package/build/client/dev/error-overlay.js +1 -1
  11. package/build/client/index.d.ts +1 -1
  12. package/build/client/index.js +1 -1
  13. package/build/client/navigation/navigation.js +28 -3
  14. package/build/client/routing/Router.js +5 -2
  15. package/build/client/routing/mount.js +1 -1
  16. package/build/compiler/.tsbuildinfo +1 -1
  17. package/build/compiler/auth-emails.js +7 -3
  18. package/build/compiler/emails.js +1 -1
  19. package/build/compiler/index.d.ts +1 -0
  20. package/build/compiler/index.js +26 -5
  21. package/build/compiler/pages.js +2 -3
  22. package/build/compiler/prerender.d.ts +3 -8
  23. package/build/compiler/prerender.js +88 -127
  24. package/build/compiler/ssg.js +3 -4
  25. package/build/compiler/template-build.js +3 -4
  26. package/build/compiler/toil-docs.generated.js +8 -7
  27. package/build/compiler/vite.js +4 -6
  28. package/build/devserver/.tsbuildinfo +1 -1
  29. package/build/devserver/db/database.js +7 -3
  30. package/build/devserver/db/types.js +1 -1
  31. package/build/devserver/production.js +4 -3
  32. package/build/devserver/runtime/crypto.d.ts +1 -1
  33. package/build/io/.tsbuildinfo +1 -1
  34. package/build/logger/.tsbuildinfo +1 -1
  35. package/build/shared/.tsbuildinfo +1 -1
  36. package/examples/basic/client/routes/analytics.tsx +7 -5
  37. package/examples/basic/client/routes/pq.tsx +2 -1
  38. package/examples/basic/server/tsconfig.json +1 -3
  39. package/package.json +45 -44
  40. package/presets/no-uint8array-tostring.js +120 -153
  41. package/presets/oxlint.d.ts +5 -0
  42. package/presets/oxlint.js +224 -0
  43. package/server/auth/AuthController.ts +53 -14
  44. package/server/auth/FactorSettings.ts +41 -0
  45. package/server/auth/SessionEpochs.ts +58 -0
  46. package/server/auth/SessionRevocations.ts +13 -0
  47. package/server/globals/auth.ts +27 -2
  48. package/server/runtime/tsconfig.json +1 -3
  49. package/tsconfig.json +2 -11
  50. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  51. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  52. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  53. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  54. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  55. package/.github/changelog-config.json +0 -45
  56. package/.github/dependabot.yml +0 -27
  57. package/.github/workflows/ci.yml +0 -222
  58. package/.vscode/settings.json +0 -10
  59. package/docs/README.md +0 -107
  60. package/docs/auth/README.md +0 -104
  61. package/docs/auth/configuration.md +0 -105
  62. package/docs/auth/emails.md +0 -92
  63. package/docs/auth/extending.md +0 -202
  64. package/docs/auth/how-it-works.md +0 -138
  65. package/docs/auth/usage.md +0 -188
  66. package/docs/backend/README.md +0 -143
  67. package/docs/backend/data.md +0 -351
  68. package/docs/backend/rest.md +0 -402
  69. package/docs/backend/rpc.md +0 -226
  70. package/docs/background/README.md +0 -114
  71. package/docs/background/daemons.md +0 -276
  72. package/docs/background/derive.md +0 -179
  73. package/docs/cli/README.md +0 -379
  74. package/docs/concepts/ai-guide.md +0 -132
  75. package/docs/concepts/config.md +0 -416
  76. package/docs/concepts/decorators.md +0 -127
  77. package/docs/concepts/security.md +0 -108
  78. package/docs/concepts/tiers.md +0 -166
  79. package/docs/concepts/types.md +0 -216
  80. package/docs/database/README.md +0 -143
  81. package/docs/database/capacity.md +0 -350
  82. package/docs/database/counters.md +0 -174
  83. package/docs/database/documents.md +0 -277
  84. package/docs/database/events.md +0 -307
  85. package/docs/database/membership.md +0 -246
  86. package/docs/database/setup.md +0 -155
  87. package/docs/database/unique.md +0 -216
  88. package/docs/database/views.md +0 -246
  89. package/docs/frontend/README.md +0 -104
  90. package/docs/frontend/components.md +0 -286
  91. package/docs/frontend/data-fetching.md +0 -260
  92. package/docs/frontend/images.md +0 -148
  93. package/docs/frontend/metadata.md +0 -344
  94. package/docs/frontend/navigation.md +0 -296
  95. package/docs/frontend/rendering.md +0 -236
  96. package/docs/frontend/routing.md +0 -346
  97. package/docs/frontend/scripts.md +0 -118
  98. package/docs/frontend/search.md +0 -191
  99. package/docs/frontend/styling.md +0 -147
  100. package/docs/frontend/toil-global.md +0 -216
  101. package/docs/getting-started/README.md +0 -81
  102. package/docs/getting-started/create-project.md +0 -131
  103. package/docs/getting-started/deploy.md +0 -101
  104. package/docs/getting-started/first-app.md +0 -215
  105. package/docs/getting-started/installation.md +0 -124
  106. package/docs/getting-started/migrating.md +0 -125
  107. package/docs/getting-started/project-structure.md +0 -163
  108. package/docs/introduction/README.md +0 -66
  109. package/docs/introduction/design-principles.md +0 -81
  110. package/docs/introduction/distributed.md +0 -91
  111. package/docs/introduction/how-it-works.md +0 -121
  112. package/docs/introduction/hyperscale.md +0 -70
  113. package/docs/introduction/modern-stack.md +0 -85
  114. package/docs/introduction/vs-other-frameworks.md +0 -64
  115. package/docs/introduction/why-toil.md +0 -99
  116. package/docs/llms.txt +0 -95
  117. package/docs/realtime/README.md +0 -102
  118. package/docs/realtime/channels.md +0 -211
  119. package/docs/realtime/streams.md +0 -369
  120. package/docs/services/README.md +0 -60
  121. package/docs/services/analytics.md +0 -279
  122. package/docs/services/caching.md +0 -175
  123. package/docs/services/cookies.md +0 -235
  124. package/docs/services/crypto.md +0 -209
  125. package/docs/services/email.md +0 -289
  126. package/docs/services/environment.md +0 -141
  127. package/docs/services/ratelimit.md +0 -174
  128. package/docs/services/time.md +0 -85
  129. package/eslint.config.js +0 -57
  130. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  131. package/presets/eslint.js +0 -88
  132. package/src/backend/index.ts +0 -204
  133. package/src/cli/configure.ts +0 -363
  134. package/src/cli/create.ts +0 -924
  135. package/src/cli/db.ts +0 -158
  136. package/src/cli/diagnostics.ts +0 -665
  137. package/src/cli/doctor.ts +0 -982
  138. package/src/cli/features.ts +0 -170
  139. package/src/cli/index.ts +0 -326
  140. package/src/cli/notify.ts +0 -120
  141. package/src/cli/proc.ts +0 -56
  142. package/src/cli/ui.ts +0 -169
  143. package/src/cli/update.ts +0 -248
  144. package/src/cli/updates.ts +0 -92
  145. package/src/cli/validate.ts +0 -31
  146. package/src/cli/version-check.ts +0 -125
  147. package/src/client/auth.ts +0 -869
  148. package/src/client/channel/channel.ts +0 -146
  149. package/src/client/components/Form.tsx +0 -65
  150. package/src/client/components/Image.tsx +0 -160
  151. package/src/client/components/Script.tsx +0 -113
  152. package/src/client/components/Slot.tsx +0 -21
  153. package/src/client/dev/devtools.tsx +0 -1089
  154. package/src/client/dev/error-overlay.tsx +0 -225
  155. package/src/client/errors.ts +0 -11
  156. package/src/client/head/head.ts +0 -182
  157. package/src/client/head/metadata.ts +0 -110
  158. package/src/client/index.ts +0 -130
  159. package/src/client/navigation/Link.tsx +0 -108
  160. package/src/client/navigation/NavLink.tsx +0 -86
  161. package/src/client/navigation/navigation.ts +0 -235
  162. package/src/client/navigation/prefetch.ts +0 -169
  163. package/src/client/navigation/scroll.ts +0 -53
  164. package/src/client/routing/Router.tsx +0 -216
  165. package/src/client/routing/action.ts +0 -122
  166. package/src/client/routing/error-boundary.tsx +0 -43
  167. package/src/client/routing/hooks.ts +0 -140
  168. package/src/client/routing/lazy.ts +0 -77
  169. package/src/client/routing/loader.ts +0 -381
  170. package/src/client/routing/match.ts +0 -47
  171. package/src/client/routing/mount.tsx +0 -90
  172. package/src/client/routing/params-context.ts +0 -10
  173. package/src/client/routing/slot-context.ts +0 -7
  174. package/src/client/rpc.ts +0 -97
  175. package/src/client/search/search.ts +0 -189
  176. package/src/client/search/use-page-search.ts +0 -73
  177. package/src/client/ssr/markers.tsx +0 -172
  178. package/src/client/stream/client.ts +0 -237
  179. package/src/client/types.ts +0 -73
  180. package/src/compiler/auth-emails.ts +0 -312
  181. package/src/compiler/config.ts +0 -369
  182. package/src/compiler/docs.ts +0 -127
  183. package/src/compiler/email-preview.ts +0 -314
  184. package/src/compiler/emails.ts +0 -404
  185. package/src/compiler/fonts.ts +0 -89
  186. package/src/compiler/generate.ts +0 -471
  187. package/src/compiler/image-blur.ts +0 -42
  188. package/src/compiler/image-report.ts +0 -88
  189. package/src/compiler/index.ts +0 -1362
  190. package/src/compiler/pages.ts +0 -49
  191. package/src/compiler/plugin.ts +0 -391
  192. package/src/compiler/prerender.ts +0 -263
  193. package/src/compiler/routes.ts +0 -170
  194. package/src/compiler/seo.ts +0 -415
  195. package/src/compiler/sri.ts +0 -144
  196. package/src/compiler/ssg.ts +0 -177
  197. package/src/compiler/ssr-codegen.ts +0 -85
  198. package/src/compiler/template-build.ts +0 -676
  199. package/src/compiler/template.ts +0 -262
  200. package/src/compiler/toil-docs.generated.ts +0 -77
  201. package/src/compiler/vite.ts +0 -245
  202. package/src/devserver/analytics/index.ts +0 -317
  203. package/src/devserver/config/dotenv.ts +0 -102
  204. package/src/devserver/config/env.ts +0 -30
  205. package/src/devserver/config/ratelimit.ts +0 -123
  206. package/src/devserver/daemon/catalog.ts +0 -120
  207. package/src/devserver/daemon/cron.ts +0 -87
  208. package/src/devserver/daemon/host.ts +0 -134
  209. package/src/devserver/daemon/index.ts +0 -369
  210. package/src/devserver/daemon/runtime.ts +0 -48
  211. package/src/devserver/db/catalog.ts +0 -109
  212. package/src/devserver/db/database.ts +0 -1834
  213. package/src/devserver/db/derives.ts +0 -121
  214. package/src/devserver/db/index.ts +0 -20
  215. package/src/devserver/db/routeKinds.ts +0 -191
  216. package/src/devserver/db/types.ts +0 -168
  217. package/src/devserver/email/caps.ts +0 -0
  218. package/src/devserver/email/config.ts +0 -141
  219. package/src/devserver/email/console.ts +0 -348
  220. package/src/devserver/email/index.ts +0 -155
  221. package/src/devserver/email/providers.ts +0 -131
  222. package/src/devserver/email/status.ts +0 -23
  223. package/src/devserver/email/validate.ts +0 -37
  224. package/src/devserver/email/wire.ts +0 -55
  225. package/src/devserver/http/cache.ts +0 -0
  226. package/src/devserver/http/envelope.ts +0 -157
  227. package/src/devserver/http/proxy.ts +0 -172
  228. package/src/devserver/http/runtime.ts +0 -387
  229. package/src/devserver/index.ts +0 -43
  230. package/src/devserver/production-ipc.ts +0 -63
  231. package/src/devserver/production-worker.ts +0 -83
  232. package/src/devserver/production.ts +0 -718
  233. package/src/devserver/runtime/crypto.ts +0 -587
  234. package/src/devserver/runtime/host.ts +0 -427
  235. package/src/devserver/runtime/module.ts +0 -442
  236. package/src/devserver/server.ts +0 -251
  237. package/src/devserver/ssr.ts +0 -208
  238. package/src/devserver/stream/catalog.ts +0 -106
  239. package/src/devserver/stream/host.ts +0 -42
  240. package/src/devserver/stream/index.ts +0 -301
  241. package/src/devserver/stream/manager.ts +0 -167
  242. package/src/devserver/stream/router.ts +0 -101
  243. package/src/devserver/stream/wire.ts +0 -58
  244. package/src/devserver/stream/ws.ts +0 -78
  245. package/src/devserver/wasm/sections.ts +0 -59
  246. package/src/devserver/wasm/surface.ts +0 -115
  247. package/src/io/FastMap.ts +0 -151
  248. package/src/io/FastSet.ts +0 -113
  249. package/src/io/codec.ts +0 -362
  250. package/src/io/index.ts +0 -10
  251. package/src/io/lengths.ts +0 -14
  252. package/src/io/types.ts +0 -19
  253. package/src/logger/index.ts +0 -22
  254. package/src/shared/index.ts +0 -46
  255. package/test/analytics-dev.test.ts +0 -113
  256. package/test/assembly/aspect-shim.ts +0 -33
  257. package/test/assembly/cookie.spec.ts +0 -303
  258. package/test/assembly/example.spec.ts +0 -22
  259. package/test/assembly/ssr.spec.ts +0 -95
  260. package/test/assembly.test.ts +0 -59
  261. package/test/auth-emails.test.ts +0 -118
  262. package/test/built-ssr.test.ts +0 -148
  263. package/test/channel.test.ts +0 -21
  264. package/test/config-host-port.test.ts +0 -59
  265. package/test/configure.test.ts +0 -94
  266. package/test/daemon-build.test.ts +0 -278
  267. package/test/daemon-catalog.test.ts +0 -349
  268. package/test/daemon-emulation.test.ts +0 -256
  269. package/test/db.test.ts +0 -0
  270. package/test/devserver-database.test.ts +0 -1102
  271. package/test/devserver-email.test.ts +0 -306
  272. package/test/devserver-pqauth.test.ts +0 -93
  273. package/test/devserver-secrets.test.ts +0 -63
  274. package/test/devserver.test.ts +0 -259
  275. package/test/doctor.test.ts +0 -298
  276. package/test/dom/Image.test.tsx +0 -144
  277. package/test/dom/Link.test.tsx +0 -67
  278. package/test/dom/NavLink.test.tsx +0 -37
  279. package/test/dom/Script.test.tsx +0 -48
  280. package/test/dom/action.test.tsx +0 -146
  281. package/test/dom/error-overlay.test.tsx +0 -44
  282. package/test/dom/loader.test.tsx +0 -121
  283. package/test/dom/navigation.test.ts +0 -59
  284. package/test/dom/revalidate.test.tsx +0 -38
  285. package/test/dom/route-head.test.tsx +0 -56
  286. package/test/dom/router-loading.test.tsx +0 -44
  287. package/test/dom/scroll.test.ts +0 -56
  288. package/test/dom/slot.test.tsx +0 -131
  289. package/test/dom/use-metadata.test.tsx +0 -58
  290. package/test/dom/view-transitions.test.tsx +0 -53
  291. package/test/dynamic-routing.test.ts +0 -132
  292. package/test/email-console.test.ts +0 -86
  293. package/test/email-preview.test.ts +0 -73
  294. package/test/emails.test.ts +0 -58
  295. package/test/errors.test.ts +0 -21
  296. package/test/example-guestbook.test.ts +0 -123
  297. package/test/features.test.ts +0 -149
  298. package/test/fixtures/bignum-wire/spec.ts +0 -25
  299. package/test/fixtures/daemon-app.ts +0 -65
  300. package/test/fixtures/stream-echo.ts +0 -26
  301. package/test/fixtures/stream-gate.ts +0 -28
  302. package/test/fixtures/stream-trap.ts +0 -18
  303. package/test/fixtures/stream-typed.ts +0 -41
  304. package/test/fonts.test.ts +0 -28
  305. package/test/global-setup.ts +0 -17
  306. package/test/head.test.ts +0 -42
  307. package/test/image-blur.test.ts +0 -50
  308. package/test/io.test.ts +0 -117
  309. package/test/metadata.test.ts +0 -40
  310. package/test/navlink.test.ts +0 -28
  311. package/test/pages.test.ts +0 -105
  312. package/test/placeholder.test.ts +0 -9
  313. package/test/pqauth-e2e.test.ts +0 -216
  314. package/test/pqauth-email.test.ts +0 -801
  315. package/test/prerender.test.ts +0 -116
  316. package/test/prettier-plugin.test.ts +0 -46
  317. package/test/routes.test.ts +0 -76
  318. package/test/rpc-bignum-wire.test.ts +0 -164
  319. package/test/rpc-dispatch.test.ts +0 -132
  320. package/test/rpc-kinds.test.ts +0 -18
  321. package/test/rpc.test.ts +0 -66
  322. package/test/search.test.ts +0 -114
  323. package/test/seo.test.ts +0 -175
  324. package/test/slot-layouts.test.ts +0 -69
  325. package/test/sri.test.ts +0 -129
  326. package/test/ssg.test.ts +0 -36
  327. package/test/ssr-hydration.test.tsx +0 -122
  328. package/test/ssr-render.test.ts +0 -197
  329. package/test/ssr-template.test.tsx +0 -482
  330. package/test/stream-emulation.test.ts +0 -479
  331. package/test/ui.test.ts +0 -40
  332. package/test/update.test.ts +0 -58
  333. package/test/validate.test.ts +0 -42
  334. package/test/version-check.test.ts +0 -130
  335. package/tests/data-parity/generated-parity.ts +0 -99
  336. package/tests/data-parity/parity.ts +0 -80
  337. package/tests/data-parity/spec.ts +0 -46
@@ -1,245 +0,0 @@
1
- import fs from 'node:fs';
2
- import { createRequire } from 'node:module';
3
- import path from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
-
6
- import pc from 'picocolors';
7
- import react from '@vitejs/plugin-react';
8
- import { imagetools } from 'vite-imagetools';
9
- import { nodePolyfills } from 'vite-plugin-node-polyfills';
10
- import { createLogger, type InlineConfig, type Logger, mergeConfig, type PluginOption } from 'vite';
11
-
12
- import { type ResolvedToilConfig } from './config.js';
13
- import { fontPreloadPlugin } from './fonts.js';
14
- import { imageReportPlugin } from './image-report.js';
15
- import { imageBlurPlugin } from './image-blur.js';
16
- import { toilPlugin } from './plugin.js';
17
- import { prerenderPlugin } from './prerender.js';
18
- import { sriPlugin } from './sri.js';
19
-
20
- // `vite-plugin-node-polyfills` rewrites react/react-dom to import its `vite-plugin-node-polyfills/
21
- // shims/*` modules. When a consumer links toiljs by symlink (`file:`/workspace), that package lives
22
- // only in toiljs's own node_modules, so resolving those bare specifiers from the consumer root
23
- // fails ("Failed to resolve import vite-plugin-node-polyfills/shims/process"). Alias them to
24
- // absolute paths resolved from toiljs's location so the build works however toiljs was installed.
25
- const polyfillPkgRoot = path.dirname(
26
- path.dirname(createRequire(import.meta.url).resolve('vite-plugin-node-polyfills')),
27
- );
28
- const polyfillShimAliases: Record<string, string> = {
29
- 'vite-plugin-node-polyfills/shims/buffer': path.join(
30
- polyfillPkgRoot,
31
- 'shims/buffer/dist/index.js',
32
- ),
33
- 'vite-plugin-node-polyfills/shims/global': path.join(
34
- polyfillPkgRoot,
35
- 'shims/global/dist/index.js',
36
- ),
37
- 'vite-plugin-node-polyfills/shims/process': path.join(
38
- polyfillPkgRoot,
39
- 'shims/process/dist/index.js',
40
- ),
41
- };
42
-
43
- /** Image extensions routed to `images/` in the build output. */
44
- const IMAGE_EXT = /^(png|jpe?g|svg|gif|tiff|bmp|ico|webp|avif)$/i;
45
- /** Font extensions routed to `fonts/`. */
46
- const FONT_EXT = /^(woff|woff2|eot|ttf|otf)$/i;
47
-
48
- /**
49
- * Resolves bare `shared/*` imports to the project's `shared/` folder (replacing a plain alias so it
50
- * can run early), and fails `shared/server` with an actionable message when the module has not been
51
- * generated yet (it comes from the server build, which must run first), instead of Vite's opaque
52
- * `UNLOADABLE_DEPENDENCY`.
53
- */
54
- function sharedResolverPlugin(cfg: ResolvedToilConfig): PluginOption {
55
- const sharedDir = path.join(cfg.root, 'shared');
56
- const serverModule = path.join(sharedDir, 'server.ts');
57
- // Posix-normalized: Vite hands `transform` forward-slash module ids while path.join is OS-native, so a
58
- // raw `===` would silently never match on Windows (the surface would regress to "client not loaded").
59
- const routesModule = path.join(cfg.toilDir, 'routes.ts').replace(/\\/g, '/');
60
- return {
61
- name: 'toiljs:shared-resolver',
62
- enforce: 'pre',
63
- resolveId(source: string) {
64
- if (source !== 'shared' && !source.startsWith('shared/')) return null;
65
- const rel = source === 'shared' ? 'index' : source.slice('shared/'.length);
66
- for (const ext of ['', '.ts', '.tsx', '.js', '.jsx', '.mjs']) {
67
- const candidate = path.join(sharedDir, rel + ext);
68
- if (fs.existsSync(candidate)) return candidate;
69
- }
70
- if (source === 'shared/server') {
71
- throw new Error(
72
- 'toiljs: "shared/server" is generated by the server build but is missing. ' +
73
- 'Run the server build first (it emits shared/server.ts from your @data/@remote code): ' +
74
- '`npm run build:server` (toilscript --target release --rpcModule shared/server.ts).',
75
- );
76
- }
77
- return null;
78
- },
79
- transform(code: string, id: string) {
80
- // Guarantee the generated client surface (shared/server.ts: __toilRest/__toilRpc/__toilStream
81
- // + globalThis.Server) is EVALUATED. Otherwise it is dead unless some route happens to
82
- // value-import a @data type - a type-only import is erased and never attaches, so
83
- // `Server.<rpc>()` / `Server.REST` / `Server.Stream` would throw "client not loaded". Inject a
84
- // side-effect import from the always-loaded route manifest; idempotent and a no-op for a
85
- // client-only project (no shared/server.ts).
86
- if ((id.split('?')[0] ?? id).replace(/\\/g, '/') === routesModule && fs.existsSync(serverModule)) {
87
- return { code: `import 'shared/server';\n${code}`, map: null };
88
- }
89
- return null;
90
- },
91
- };
92
- }
93
-
94
- /** Routes a built asset to a typed sub-folder (`images/`, `fonts/`, `css/`, else `assets/`). CSS is
95
- * content-hashed (`[name]-[hash]`) because we put Subresource Integrity on `<link rel=stylesheet>`:
96
- * a stable URL whose bytes change across deploys would make a client holding stale HTML fail the
97
- * integrity check and BLOCK the stylesheet. Hashing pins the URL to its bytes, so the baked integrity
98
- * can never falsely mismatch (identical to how JS chunks are hashed). Images/fonts stay stable-named:
99
- * they carry no SRI and are commonly referenced by fixed path. */
100
- function assetFileName(name: string): string {
101
- const ext = name.split('.').pop() ?? '';
102
- if (IMAGE_EXT.test(ext)) return 'images/[name][extname]';
103
- if (FONT_EXT.test(ext)) return 'fonts/[name][extname]';
104
- if (/^css$/i.test(ext)) return 'css/[name]-[hash][extname]';
105
- return 'assets/[name][extname]';
106
- }
107
-
108
- /**
109
- * Loads the Tailwind v4 Vite plugin if the project has `@tailwindcss/vite` installed (added by
110
- * `toiljs create`/`configure` when Tailwind is enabled). Resolved from the project root so it picks
111
- * up the project's copy; returns `undefined` when Tailwind is off, so the plugin simply isn't added.
112
- */
113
- async function tailwindPlugin(root: string): Promise<PluginOption | undefined> {
114
- let resolved: string;
115
- try {
116
- resolved = createRequire(path.join(root, 'package.json')).resolve('@tailwindcss/vite');
117
- } catch {
118
- return undefined;
119
- }
120
- const mod = (await import(pathToFileURL(resolved).href)) as { default?: () => PluginOption };
121
- return mod.default?.();
122
- }
123
-
124
- /** Splits React's runtime into its own long-lived chunk for better caching. */
125
- function manualChunks(id: string): string | undefined {
126
- if (!id.includes('node_modules')) return undefined;
127
- if (
128
- id.includes('node_modules/react-dom') ||
129
- id.includes('node_modules/react/') ||
130
- id.includes('node_modules/scheduler')
131
- ) {
132
- return 'react';
133
- }
134
- return undefined;
135
- }
136
-
137
- /**
138
- * Builds the framework-owned Vite config. Vite's `root` is the generated `.toil` dir so its
139
- * `index.html` (built from the project's `public/index.html` template) emits at the output root
140
- * with assets resolving correctly; static `public/` assets are mirrored to `.toil/public` and
141
- * picked up via Vite's default publicDir. `fs.allow` opens the project (for `client/`) and the
142
- * framework runtime. The opinionated default, Node polyfills
143
- * (Buffer/global/process), React plugin, toil route plugin, typed asset folders, React chunk
144
- * splitting and tuned build options, is applied here; `toiljs/client` is aliased to the
145
- * runtime, and the user's `client.vite` overrides deep-merge on top.
146
- */
147
- /**
148
- * Vite's `(ssr)` environment label is dark blue — nearly invisible on a black
149
- * terminal. Wrap the default logger to recolor `(ssr)` magenta (purple) and
150
- * `(client)` cyan, so the dev reload logs are readable.
151
- */
152
- function brandedLogger(): Logger {
153
- const logger = createLogger();
154
- const recolor = (msg: string): string =>
155
- msg
156
- .replace(/(?:\[[0-9;]*m)*\(ssr\)(?:\[[0-9;]*m)*/g, pc.magenta('(ssr)'))
157
- .replace(/(?:\[[0-9;]*m)*\(client\)(?:\[[0-9;]*m)*/g, pc.cyan('(client)'));
158
- const info = logger.info.bind(logger);
159
- const warn = logger.warn.bind(logger);
160
- logger.info = (msg, opts) => info(recolor(msg), opts);
161
- logger.warn = (msg, opts) => warn(recolor(msg), opts);
162
- return logger;
163
- }
164
-
165
- export async function createViteConfig(cfg: ResolvedToilConfig): Promise<InlineConfig> {
166
- const frameworkRoot = path.resolve(path.dirname(cfg.runtimePath), '..', '..');
167
- const tailwind = await tailwindPlugin(cfg.root);
168
-
169
- const base: InlineConfig = {
170
- root: cfg.toilDir,
171
- base: cfg.base,
172
- configFile: false,
173
- customLogger: brandedLogger(),
174
- plugins: [
175
- tailwind,
176
- // Auto-generate a tiny blurred base64 LQIP for `?toil` image imports (consumed by
177
- // `Toil.Image` placeholder="blur"). Runs before imagetools (enforce:'pre') so it owns the
178
- // `?toil` query; the bare re-imported src still flows through imagetools below.
179
- cfg.images ? imageBlurPlugin() : undefined,
180
- // Build-time image resize/optimization. Every *imported* raster image is compressed to
181
- // webp by default (so a plain `<img src={imported}>` is optimized too, not just
182
- // `Toil.Image`); add `?w=400;800&format=…` to resize or pick a format. `public/` assets
183
- // referenced by string path are served as-is. Disabled by `client.images: false`.
184
- cfg.images
185
- ? imagetools({
186
- defaultDirectives: () =>
187
- new URLSearchParams({ format: 'webp', quality: '80' }),
188
- })
189
- : undefined,
190
- cfg.images ? imageReportPlugin(cfg.root, cfg.toilDir) : undefined,
191
- // Subresource Integrity (build only): adds integrity="sha384-…" + crossorigin to every
192
- // local script/stylesheet/modulepreload in the shell. MUST precede prerenderPlugin so the
193
- // per-route/bracket/SSR pages baked from the shell inherit it (and the SSR .tmpl coherence
194
- // hash covers it).
195
- sriPlugin(cfg),
196
- // Static per-route SEO prerender (build only): bakes each route's metadata into its HTML.
197
- cfg.seo ? prerenderPlugin(cfg) : undefined,
198
- // Preload bundled fonts (build only). Disabled by `client.fonts: false`.
199
- cfg.fonts ? fontPreloadPlugin(cfg) : undefined,
200
- nodePolyfills({ globals: { Buffer: true, global: true, process: true } }),
201
- react(),
202
- sharedResolverPlugin(cfg),
203
- toilPlugin(cfg),
204
- ],
205
- resolve: {
206
- alias: {
207
- 'toiljs/client': cfg.runtimePath,
208
- 'toiljs/routes': path.join(cfg.toilDir, 'routes.ts'),
209
- // `client/*` -> the project's client source dir, so anything (pages, and notably
210
- // `emails/*.tsx`) can reuse existing client assets, e.g. `import 'client/styles/x.css'`.
211
- // Vite's string alias matches only `client` or `client/...`, never `toiljs/client`.
212
- client: cfg.clientAbsDir,
213
- // `shared/*` is resolved by sharedResolverPlugin (above) so a missing generated
214
- // shared/server.ts gives an actionable error instead of an opaque load failure.
215
- ...polyfillShimAliases,
216
- },
217
- dedupe: ['react', 'react-dom'],
218
- },
219
- server: {
220
- port: cfg.port,
221
- fs: { allow: [cfg.root, frameworkRoot] },
222
- },
223
- build: {
224
- outDir: path.resolve(cfg.root, cfg.outDir),
225
- emptyOutDir: true,
226
- target: 'es2020',
227
- modulePreload: false,
228
- cssCodeSplit: false,
229
- assetsInlineLimit: 10000,
230
- chunkSizeWarningLimit: 3000,
231
- commonjsOptions: {
232
- strictRequires: true,
233
- transformMixedEsModules: true,
234
- },
235
- rollupOptions: {
236
- output: {
237
- chunkFileNames: 'assets/[name]-[hash].js',
238
- assetFileNames: (assetInfo) => assetFileName(assetInfo.names[0] ?? ''),
239
- manualChunks,
240
- },
241
- },
242
- },
243
- };
244
- return mergeConfig(base, cfg.vite);
245
- }
@@ -1,317 +0,0 @@
1
- /**
2
- * Dev-server stand-in for the edge `env.analytics_read` host import
3
- * (toil-backend `src/wasm/host/import_functions/analytics.rs`). It returns a fixed
4
- * sample `TenantStats` frame so a tenant's wasm exercises the toilscript `Analytics`
5
- * API under `toiljs dev`. The frame is stashed into the SAME per-request result
6
- * buffer the `data.*` ops use (`db.lastResult`), so the guest drains it with the
7
- * existing `data.take_result`.
8
- *
9
- * Dev is intentionally PERMISSIVE: it returns sample data for ANY domain (own or
10
- * named) so a dashboard can be built locally. The real `dacely.com`-only
11
- * authorization is enforced host-side at the edge; the dev server has no
12
- * multi-tenant host resolution to authorize against.
13
- */
14
- import type { DbDevState } from '../db/index.js';
15
- import type { MemoryRef } from '../runtime/host.js';
16
-
17
- /** Frame version + counter count, kept in lockstep with the edge (`analytics.rs` / `metric_id.rs`). */
18
- const FRAME_VERSION = 3;
19
- const METRIC_COUNTERS = 42;
20
-
21
- /** The sustained-rate window: requests are metered over a 3-minute ROLLING window. A plan's
22
- * `sustained_rps` (X) allows `X * BURST_WINDOW_SECS` requests across it; instantaneous spikes above
23
- * that are NOT capped per-tier (only the box `firewall.global_pps` DDoS backstop applies). */
24
- export const BURST_WINDOW_SECS = 180;
25
-
26
- /** The plan's sustained rate in requests/second, derived from the rolling window's allowance.
27
- * `0` means unmetered (no sustained cap), and must NOT be divided into a rate. */
28
- export function sustainedRpsCap(reqBurstCap: number): number {
29
- return reqBurstCap === 0 ? 0 : reqBurstCap / BURST_WINDOW_SECS;
30
- }
31
- /** MetricId indices we seed with sample data (others default 0). Mirrors the AUTHORITATIVE edge wire
32
- * contract EXACTLY (`toil-backend/src/analytics/metric_id.rs`): counter ids 0..=41, gauge ids
33
- * ConnectedStreamsAvg=42, CommittedMemoryAvg=44. There is NO WasmDispatches (removed + reindexed); the
34
- * edge/guest are 42, so this must be 42 with matching indices or the dashboard mislabels every metric. */
35
- const MID = {
36
- Requests: 0,
37
- BytesOutL1: 1,
38
- BytesInL1: 2,
39
- Status2xx: 3,
40
- Status4xx: 5,
41
- StaticHits: 7,
42
- GasUsed: 11,
43
- DbOps: 12,
44
- DbReads: 13,
45
- DbWrites: 14,
46
- StreamBytesIn: 24,
47
- StreamBytesOut: 25,
48
- MemGrownBytes: 38,
49
- CacheHits: 40,
50
- CacheMisses: 41,
51
- } as const;
52
-
53
- interface DevTenantStats {
54
- life: bigint[]; // length METRIC_COUNTERS, indexed by MetricId
55
- connectedStreams: number;
56
- committedMemory: number;
57
- // Fleet-global requests in the trailing 3-minute ROLLING window, and its allowance
58
- // (= sustained_rps * BURST_WINDOW_SECS; 0 = unmetered). Spikes are not tier-capped.
59
- reqBurstUsed: number;
60
- reqBurstCap: number;
61
- // Fleet-global requests in the current 30-day tumbling bucket, and the 30-day quota (0 = unmetered).
62
- req30dUsed: number;
63
- req30dCap: number;
64
- // Derived (NOT part of the wire frame): the plan's sustained request rate, `reqBurstCap /
65
- // BURST_WINDOW_SECS` (0 = unmetered). Surfaced so a dev dashboard can show "X sustained rps".
66
- sustainedRpsCap: number;
67
- // LIVE per-second rates (f64), appended after the windows. Mirrors the edge `encode_stats`.
68
- rps: number;
69
- bytesInPerSec: number;
70
- bytesOutPerSec: number;
71
- streamBytesInPerSec: number;
72
- streamBytesOutPerSec: number;
73
- dbOpsPerSec: number;
74
- gasPerSec: number;
75
- nowMs: number;
76
- }
77
-
78
- /** A plausible sample so a guest sees non-zero analytics under `toiljs dev`. */
79
- function devStats(): DevTenantStats {
80
- const life = new Array<bigint>(METRIC_COUNTERS).fill(0n);
81
- life[MID.Requests] = 42n;
82
- life[MID.BytesOutL1] = 12345n;
83
- life[MID.BytesInL1] = 4096n;
84
- life[MID.Status2xx] = 40n;
85
- life[MID.Status4xx] = 2n;
86
- life[MID.StaticHits] = 8n;
87
- life[MID.GasUsed] = 1_000_000n;
88
- life[MID.DbOps] = 17n;
89
- life[MID.DbReads] = 12n;
90
- life[MID.DbWrites] = 5n;
91
- life[MID.StreamBytesIn] = 2048n;
92
- life[MID.StreamBytesOut] = 8192n;
93
- life[MID.MemGrownBytes] = 262144n;
94
- life[MID.CacheHits] = 900n;
95
- life[MID.CacheMisses] = 100n;
96
- // Free tier: sustained 1000 rps -> a 3-minute burst cap of 1000 * 180 = 180_000, plus a 30-day
97
- // quota of 10_000_000. `used` values are small so the dashboard shows plenty of headroom.
98
- const reqBurstCap: number = 180_000;
99
- return {
100
- life,
101
- connectedStreams: 3,
102
- committedMemory: 65536,
103
- reqBurstUsed: 1_200,
104
- reqBurstCap,
105
- req30dUsed: 250_000,
106
- req30dCap: 10_000_000,
107
- sustainedRpsCap: sustainedRpsCap(reqBurstCap),
108
- // Plausible non-zero live rates so a dev dashboard shows moving gauges.
109
- rps: 0.7,
110
- bytesInPerSec: 68.2,
111
- bytesOutPerSec: 205.75,
112
- streamBytesInPerSec: 34.1,
113
- streamBytesOutPerSec: 136.5,
114
- dbOpsPerSec: 0.28,
115
- gasPerSec: 16_666.67,
116
- nowMs: 1_700_000_000_000,
117
- };
118
- }
119
-
120
- /**
121
- * Encode the v3 snapshot frame BYTE-FOR-BYTE like the edge (`analytics.rs` `encode_stats`), FIXED
122
- * layout (no strings). The v2 -> v3 bump renamed two u64 pairs (req_minute_* -> req_burst_*,
123
- * req_day_* -> req_30d_*); the BYTE LAYOUT is UNCHANGED (same field count, order, and widths).
124
- * Counters/levels are u64; the 7 trailing LIVE per-second rates are f64:
125
- * u16 version | u64 now_ms | u32 count | u64 × count | u64 connectedStreams | u64 committedMemory |
126
- * u64 reqBurstUsed | u64 reqBurstCap | u64 req30dUsed | u64 req30dCap |
127
- * f64 rps | f64 bytesInPerSec | f64 bytesOutPerSec | f64 streamBytesInPerSec |
128
- * f64 streamBytesOutPerSec | f64 dbOpsPerSec | f64 gasPerSec
129
- * The rates are APPENDED (version-gated by length): the guest's bounds-checked reader yields 0.0 for a
130
- * pre-rates frame, so an older edge stays decodable.
131
- */
132
- function encodeStats(s: DevTenantStats): Buffer {
133
- const head = Buffer.alloc(2 + 8 + 4);
134
- head.writeUInt16LE(FRAME_VERSION, 0);
135
- head.writeBigUInt64LE(BigInt(s.nowMs), 2);
136
- head.writeUInt32LE(s.life.length, 10);
137
- // 6 window u64s (48 bytes) + 7 rate f64s (56 bytes).
138
- const body = Buffer.alloc(s.life.length * 8 + 48 + 56);
139
- let o = 0;
140
- for (const v of s.life) {
141
- body.writeBigUInt64LE(v, o);
142
- o += 8;
143
- }
144
- body.writeBigUInt64LE(BigInt(s.connectedStreams), o); o += 8;
145
- body.writeBigUInt64LE(BigInt(s.committedMemory), o); o += 8;
146
- body.writeBigUInt64LE(BigInt(s.reqBurstUsed), o); o += 8;
147
- body.writeBigUInt64LE(BigInt(s.reqBurstCap), o); o += 8;
148
- body.writeBigUInt64LE(BigInt(s.req30dUsed), o); o += 8;
149
- body.writeBigUInt64LE(BigInt(s.req30dCap), o); o += 8;
150
- body.writeDoubleLE(s.rps, o); o += 8;
151
- body.writeDoubleLE(s.bytesInPerSec, o); o += 8;
152
- body.writeDoubleLE(s.bytesOutPerSec, o); o += 8;
153
- body.writeDoubleLE(s.streamBytesInPerSec, o); o += 8;
154
- body.writeDoubleLE(s.streamBytesOutPerSec, o); o += 8;
155
- body.writeDoubleLE(s.dbOpsPerSec, o); o += 8;
156
- body.writeDoubleLE(s.gasPerSec, o);
157
- return Buffer.concat([head, body]);
158
- }
159
-
160
- /** The metric ids carried in the per-minute ring (mirrors the edge `MINUTE_RING_METRICS` in
161
- * `toil-backend/src/analytics/ring.rs` EXACTLY). Only these get minute resolution on the 1h/6h ranges;
162
- * every other metric falls back to the hour ring there. These are the AUTHORITATIVE edge ids
163
- * (Requests=0, BytesInL1=2, BytesOutL1=1, GasUsed=11, DbOps=12, StreamBytesIn=24, StreamBytesOut=25,
164
- * MemGrownBytes=38, ConnectedStreamsAvg=42, CommittedMemoryAvg=44) -- must match the reindexed layout or
165
- * the dev 1h/6h graphs pick the wrong ring for these metrics. */
166
- const MINUTE_RING_METRICS = new Set<number>([0, 1, 2, 11, 12, 24, 25, 38, 42, 44]);
167
-
168
- /**
169
- * Range id -> (bucketCount, bucketSecs), matching the edge `Range` + minute-ring fallback: 1h/6h are
170
- * per-minute ONLY for a minute-ring metric; otherwise they fall back to the hour ring (1h/6h buckets).
171
- */
172
- function rangeShape(range: number, metricId: number): { count: number; bucketSecs: number } {
173
- const isMinute = (range === 0 || range === 1) && MINUTE_RING_METRICS.has(metricId);
174
- if (isMinute) return range === 0 ? { count: 60, bucketSecs: 60 } : { count: 360, bucketSecs: 60 };
175
- switch (range) {
176
- case 0: return { count: 1, bucketSecs: 3600 }; // 1h on the hour ring
177
- case 1: return { count: 6, bucketSecs: 3600 }; // 6h on the hour ring
178
- case 2: return { count: 12, bucketSecs: 3600 };
179
- case 3: return { count: 24, bucketSecs: 3600 };
180
- case 4: return { count: 72, bucketSecs: 3600 };
181
- case 5: return { count: 168, bucketSecs: 3600 };
182
- case 6: return { count: 336, bucketSecs: 3600 };
183
- case 7: return { count: 720, bucketSecs: 3600 };
184
- case 8: return { count: 60, bucketSecs: 86_400 }; // D60 on the DAY ring
185
- case 9: return { count: 90, bucketSecs: 86_400 }; // D90 on the DAY ring
186
- default: return { count: 24, bucketSecs: 3600 };
187
- }
188
- }
189
-
190
- /**
191
- * Encode the v3 series frame BYTE-FOR-BYTE like the edge `encode_series` (points are u64 now):
192
- * u16 version | u16 metricId | u32 bucketSecs | u64 headMs | u32 count | u64 × count
193
- * A synthetic gentle ramp so a dev dashboard draws a non-flat line.
194
- */
195
- function encodeSeries(metricId: number, range: number): Buffer {
196
- const { count, bucketSecs } = rangeShape(range, metricId);
197
- const nowMs = 1_700_000_000_000;
198
- const head = Buffer.alloc(2 + 2 + 4 + 8 + 4);
199
- head.writeUInt16LE(FRAME_VERSION, 0);
200
- head.writeUInt16LE(metricId & 0xffff, 2);
201
- head.writeUInt32LE(bucketSecs, 4);
202
- head.writeBigUInt64LE(BigInt(nowMs), 8);
203
- head.writeUInt32LE(count, 16);
204
- const body = Buffer.alloc(count * 8);
205
- for (let i = 0; i < count; i++) {
206
- // A smooth ramp with a little variation, deterministic per (metric, index). Non-negative -> u64.
207
- const v = BigInt(((i * 7 + metricId) % 50) + i);
208
- body.writeBigUInt64LE(v, i * 8);
209
- }
210
- return Buffer.concat([head, body]);
211
- }
212
-
213
- /**
214
- * Encode a site page BYTE-FOR-BYTE like the edge (analytics.rs `encode_site_list`):
215
- * count u32 | (u32 nameLen, name bytes)* | has_more u8, all little-endian.
216
- */
217
- function encodeSiteList(names: string[], hasMore: boolean): Buffer {
218
- const parts: Buffer[] = [];
219
- const head = Buffer.alloc(4);
220
- head.writeUInt32LE(names.length, 0);
221
- parts.push(head);
222
- for (const name of names) {
223
- const nb = Buffer.from(name, 'utf8');
224
- const nl = Buffer.alloc(4);
225
- nl.writeUInt32LE(nb.length, 0);
226
- parts.push(nl, nb);
227
- }
228
- parts.push(Buffer.from([hasMore ? 1 : 0]));
229
- return Buffer.concat(parts);
230
- }
231
-
232
- // Mirror the edge ABI bounds (analytics.rs) so dev exercises the SAME negative-status paths as prod.
233
- const MAX_DOMAIN_LEN = 256;
234
- const MAX_CURSOR_LEN = 256;
235
- const ABSENT = -2; // the edge's ABSENT sentinel; the guest maps a negative status to empty stats/list.
236
- // Pre-sorted so cursor pagination is deterministic and matches the edge's sorted enumeration.
237
- const SITE_SAMPLE = ['demo.dacely.com', 'example.com', 'shop.test'];
238
-
239
- /** The `env.analytics_read` + `env.analytics_list_sites` dev imports. Mirrors `buildDatabaseImports`. */
240
- export function buildAnalyticsImports(
241
- ref: MemoryRef,
242
- db: DbDevState,
243
- ): Record<string, (...args: number[]) => number> {
244
- return {
245
- analytics_read: (domainPtr: number, domainLen: number): number => {
246
- // Over-long domain -> ABSENT, mirroring the edge (which returns a negative status, not a
247
- // trap); the guest maps it to empty stats. domainLen 0 = the caller's own stats.
248
- if (domainLen > MAX_DOMAIN_LEN) return ABSENT;
249
- if (domainLen > 0) {
250
- if (!ref.memory) throw new Error('analytics_read called before memory was bound');
251
- const m = Buffer.from(ref.memory.buffer);
252
- if (domainPtr < 0 || domainPtr + domainLen > m.length) {
253
- throw new Error('analytics_read: domain out of bounds');
254
- }
255
- }
256
- const frame = encodeStats(devStats());
257
- db.lastResult = frame;
258
- db.lastResultVersion = -1;
259
- return frame.length;
260
- },
261
-
262
- // `env.analytics_series`: one metric's time-series for a range. Dev returns a synthetic ramp
263
- // (real ring reads are edge-side). Mirrors the edge bounds + negative-status paths.
264
- analytics_series: (
265
- domainPtr: number,
266
- domainLen: number,
267
- metricId: number,
268
- range: number,
269
- ): number => {
270
- if (domainLen > MAX_DOMAIN_LEN) return ABSENT;
271
- // Valid metric ids are 0..=45 (counters 0..=41 + the 4 gauge avg/peak series 42..=45), matching
272
- // the edge `MetricId::from_u16` (v < N_METRICS=46); ranges 0..=9 (H1..D30 + D60/D90).
273
- if (metricId < 0 || metricId > 45 || range < 0 || range > 9) return ABSENT;
274
- if (domainLen > 0) {
275
- if (!ref.memory) throw new Error('analytics_series called before memory was bound');
276
- const m = Buffer.from(ref.memory.buffer);
277
- if (domainPtr < 0 || domainPtr + domainLen > m.length) {
278
- throw new Error('analytics_series: domain out of bounds');
279
- }
280
- }
281
- const frame = encodeSeries(metricId, range);
282
- db.lastResult = frame;
283
- db.lastResultVersion = -1;
284
- return frame.length;
285
- },
286
-
287
- // The dacely dashboard enumerate-all-sites stub. Dev returns a fixed SORTED sample for any
288
- // caller (the real dacely.com-only authz is the edge), but mirrors the edge ABI exactly:
289
- // over-long/non-utf8 cursor -> ABSENT, cursor = strictly-after, limit cap, and a REAL has_more.
290
- analytics_list_sites: (cursorPtr: number, cursorLen: number, limit: number): number => {
291
- if (cursorLen > MAX_CURSOR_LEN) return ABSENT;
292
- let cursor = '';
293
- if (cursorLen > 0) {
294
- if (!ref.memory) throw new Error('analytics_list_sites called before memory was bound');
295
- const m = Buffer.from(ref.memory.buffer);
296
- if (cursorPtr < 0 || cursorPtr + cursorLen > m.length) {
297
- throw new Error('analytics_list_sites: cursor out of bounds');
298
- }
299
- const cb = m.subarray(cursorPtr, cursorPtr + cursorLen);
300
- try {
301
- cursor = new TextDecoder('utf-8', { fatal: true }).decode(cb);
302
- } catch {
303
- return ABSENT; // non-utf8 cursor -> empty page, mirroring the edge
304
- }
305
- }
306
- const start = SITE_SAMPLE.findIndex((n) => n > cursor); // strictly after the cursor
307
- const from = start < 0 ? SITE_SAMPLE.length : start;
308
- const lim = limit > 0 ? Math.min(limit, 256) : 256;
309
- const page = SITE_SAMPLE.slice(from, from + lim);
310
- const hasMore = from + page.length < SITE_SAMPLE.length;
311
- const frame = encodeSiteList(page, hasMore);
312
- db.lastResult = frame;
313
- db.lastResultVersion = -1;
314
- return frame.length;
315
- },
316
- };
317
- }
@@ -1,102 +0,0 @@
1
- /**
2
- * Shared dotenv loader for the dev server (and the future Node self-host): reads
3
- * a project's `.env` (plain vars) + `.env.secrets` (secrets) and splits out the
4
- * framework-reserved `TOIL_*` keys (host-only), mirroring the edge's `env_store`.
5
- *
6
- * Vite/devserver-free on purpose — both the `Environment.get/getSecure` dev
7
- * source (`./env.ts`) and the email backend config (`./email/config.ts`) consume
8
- * this one loader, and the self-host will too.
9
- */
10
- import fs from 'node:fs';
11
- import path from 'node:path';
12
-
13
- /** Keys with this prefix are framework-reserved/host-only (never a tenant var/secret). */
14
- export const RESERVED_PREFIX = 'TOIL_';
15
-
16
- export interface LoadedEnv {
17
- /** Non-reserved `.env` entries + `process.env` overlay → `Environment.get`. */
18
- readonly vars: Map<string, string>;
19
- /** Non-reserved `.env.secrets` entries → `Environment.getSecure`. */
20
- readonly secrets: Map<string, string>;
21
- /** Reserved `TOIL_*` entries from either file (+ `process.env`) → host-only config. */
22
- readonly reserved: Map<string, string>;
23
- }
24
-
25
- const cache = new Map<string, LoadedEnv>();
26
-
27
- /** Parse one dotenv value: take inside matching quotes, else cut an inline ` #`. */
28
- function parseValue(rest: string): string {
29
- const q = rest[0];
30
- if (q === '"' || q === "'") {
31
- const end = rest.indexOf(q, 1);
32
- return end < 0 ? rest.slice(1) : rest.slice(1, end);
33
- }
34
- const hash = rest.indexOf(' #');
35
- return (hash < 0 ? rest : rest.slice(0, hash)).trimEnd();
36
- }
37
-
38
- /**
39
- * Parse dotenv text into `plain` (non-reserved) and `reserved` (`TOIL_*`):
40
- * `KEY=value`, `#` comments, optional `export`, optional surrounding quotes.
41
- */
42
- function parseDotenv(
43
- text: string,
44
- plain: Map<string, string>,
45
- reserved: Map<string, string>,
46
- ): void {
47
- for (const raw of text.split('\n')) {
48
- let line = raw.trim();
49
- if (line.length === 0 || line.startsWith('#')) continue;
50
- if (line.startsWith('export ')) line = line.slice('export '.length);
51
- const eq = line.indexOf('=');
52
- if (eq < 0) continue;
53
- const key = line.slice(0, eq).trim();
54
- if (key.length === 0) continue;
55
- const val = parseValue(line.slice(eq + 1).trim());
56
- (key.startsWith(RESERVED_PREFIX) ? reserved : plain).set(key, val);
57
- }
58
- }
59
-
60
- function readFileInto(
61
- file: string,
62
- plain: Map<string, string>,
63
- reserved: Map<string, string>,
64
- ): void {
65
- try {
66
- parseDotenv(fs.readFileSync(file, 'utf8'), plain, reserved);
67
- } catch {
68
- /* file absent: skip */
69
- }
70
- }
71
-
72
- /**
73
- * Load `<root>/.env` + `<root>/.env.secrets`, cached by resolved root. `process.env`
74
- * overlays first (non-reserved → vars, `TOIL_*` → reserved), then the files take
75
- * precedence. Secrets come only from `.env.secrets`.
76
- */
77
- export function loadEnvFiles(root: string): LoadedEnv {
78
- const key = path.resolve(root);
79
- const hit = cache.get(key);
80
- if (hit) return hit;
81
-
82
- const vars = new Map<string, string>();
83
- const secrets = new Map<string, string>();
84
- const reserved = new Map<string, string>();
85
-
86
- // process.env overlay: non-reserved as plain vars, TOIL_* as reserved config.
87
- for (const [k, v] of Object.entries(process.env)) {
88
- if (typeof v !== 'string') continue;
89
- (k.startsWith(RESERVED_PREFIX) ? reserved : vars).set(k, v);
90
- }
91
- readFileInto(path.join(key, '.env'), vars, reserved);
92
- readFileInto(path.join(key, '.env.secrets'), secrets, reserved);
93
-
94
- const out: LoadedEnv = { vars, secrets, reserved };
95
- cache.set(key, out);
96
- return out;
97
- }
98
-
99
- /** Drop the cache (tests). */
100
- export function clearEnvCache(): void {
101
- cache.clear();
102
- }