@ultimat3/cli 29.3.2 → 30.0.0

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 (448) hide show
  1. package/CLAUDE.md +64 -40
  2. package/README.md +61 -11
  3. package/package.json +42 -28
  4. package/src/affected-tests.ts +97 -0
  5. package/src/affected-unowned.ts +43 -0
  6. package/src/affected.ts +25 -6
  7. package/src/app-boundaries.ts +29 -1
  8. package/src/app-evals.ts +12 -4
  9. package/src/app-load-unresolved.ts +62 -0
  10. package/src/app-load.ts +30 -37
  11. package/src/app-mcp.ts +14 -2
  12. package/src/app-module-graph.ts +232 -0
  13. package/src/app-reload-graph.ts +2 -2
  14. package/src/app-root.ts +37 -1
  15. package/src/bin.ts +1 -0
  16. package/src/boundary-findings.ts +6 -0
  17. package/src/budgets-freshness.ts +32 -0
  18. package/src/budgets.ts +20 -1
  19. package/src/build-capture-env.ts +9 -0
  20. package/src/cdp-shot-a11y.ts +2 -39
  21. package/src/ci-tested.ts +243 -0
  22. package/src/client-emit-customer-runtime.ts +139 -0
  23. package/src/client-emit-customer.ts +108 -0
  24. package/src/client-emit-drift.ts +72 -0
  25. package/src/client-emit-header.ts +67 -0
  26. package/src/client-emit-schemas.ts +175 -0
  27. package/src/client-emit-types.ts +121 -0
  28. package/src/client-emit-vendor.ts +146 -0
  29. package/src/client-errors-routes.ts +229 -0
  30. package/src/client-errors-script.ts +74 -0
  31. package/src/client-vitals-route.ts +197 -0
  32. package/src/client-vitals-script.ts +56 -0
  33. package/src/cmd-affected.ts +23 -0
  34. package/src/cmd-build-spec.ts +7 -1
  35. package/src/cmd-build.ts +35 -9
  36. package/src/cmd-ci-spec.ts +42 -6
  37. package/src/cmd-ci.ts +99 -2
  38. package/src/cmd-client-customer.ts +134 -0
  39. package/src/cmd-client-source.ts +81 -0
  40. package/src/cmd-client-spec.ts +48 -0
  41. package/src/cmd-client-write.ts +41 -0
  42. package/src/cmd-client.ts +111 -0
  43. package/src/cmd-db-recount.ts +120 -0
  44. package/src/cmd-db-spec.ts +36 -6
  45. package/src/cmd-db.ts +13 -3
  46. package/src/cmd-deploy-compose.ts +61 -3
  47. package/src/cmd-deploy-drain.ts +10 -2
  48. package/src/cmd-deploy-helm.ts +11 -0
  49. package/src/cmd-deploy-preflight.ts +118 -0
  50. package/src/cmd-deploy-spec.ts +58 -6
  51. package/src/cmd-deploy-verify.ts +380 -0
  52. package/src/cmd-deploy.ts +42 -9
  53. package/src/cmd-docs.ts +34 -4
  54. package/src/cmd-doctor.ts +66 -9
  55. package/src/cmd-drill-spec.ts +25 -0
  56. package/src/cmd-drill.ts +176 -0
  57. package/src/cmd-env-init.ts +102 -0
  58. package/src/cmd-env-spec.ts +10 -3
  59. package/src/cmd-env.ts +15 -10
  60. package/src/cmd-errors-recent.ts +60 -0
  61. package/src/cmd-errors-spec.ts +9 -1
  62. package/src/cmd-errors.ts +4 -0
  63. package/src/cmd-exercise-spec.ts +19 -0
  64. package/src/cmd-exercise.ts +129 -0
  65. package/src/cmd-generate-spec.ts +9 -1
  66. package/src/cmd-generate.ts +32 -27
  67. package/src/cmd-graph-load.ts +97 -0
  68. package/src/cmd-graph-spec.ts +16 -0
  69. package/src/cmd-graph.ts +73 -0
  70. package/src/cmd-i18n.ts +4 -1
  71. package/src/cmd-impact-spec.ts +13 -0
  72. package/src/cmd-impact.ts +64 -0
  73. package/src/cmd-jobs-resolve.ts +76 -0
  74. package/src/cmd-jobs-spec.ts +21 -2
  75. package/src/cmd-jobs.ts +10 -6
  76. package/src/cmd-mail-spec.ts +28 -0
  77. package/src/cmd-mail.ts +70 -0
  78. package/src/cmd-mcp-lock.ts +69 -0
  79. package/src/cmd-mcp-spec.ts +23 -4
  80. package/src/cmd-mcp.ts +17 -3
  81. package/src/cmd-new-spec.ts +8 -1
  82. package/src/cmd-new.ts +61 -9
  83. package/src/cmd-planned.ts +0 -6
  84. package/src/cmd-repro-spec.ts +31 -0
  85. package/src/cmd-repro.ts +174 -0
  86. package/src/cmd-stop-spec.ts +21 -0
  87. package/src/cmd-stop.ts +166 -0
  88. package/src/cmd-test-spec.ts +17 -4
  89. package/src/cmd-test.ts +70 -14
  90. package/src/cmd-upgrade-spec.ts +26 -0
  91. package/src/cmd-upgrade.ts +274 -0
  92. package/src/cmd-verify-spec.ts +3 -3
  93. package/src/db-backfill.ts +6 -0
  94. package/src/db-diagnostics.ts +106 -0
  95. package/src/db-generate.ts +28 -8
  96. package/src/db-reset-template.ts +1 -1
  97. package/src/db-rolling.ts +45 -0
  98. package/src/db-seed-generate.ts +188 -0
  99. package/src/deploy-verify-findings.ts +98 -0
  100. package/src/deploy-verify-flags.ts +109 -0
  101. package/src/deploy-verify-grade.ts +185 -0
  102. package/src/deploy-verify-queues.ts +53 -0
  103. package/src/dev-boot.ts +35 -15
  104. package/src/dev-child-watch.ts +16 -4
  105. package/src/dev-dashboard-guard.ts +8 -1
  106. package/src/dev-dashboard.ts +7 -0
  107. package/src/dev-lock-identity.ts +61 -0
  108. package/src/dev-lock-port-holder.ts +80 -0
  109. package/src/dev-lock-steal.ts +124 -0
  110. package/src/dev-lock.ts +92 -120
  111. package/src/dev-mail-templates.ts +51 -0
  112. package/src/dev-rebuild.ts +26 -2
  113. package/src/dev-route-table.ts +17 -0
  114. package/src/dev-supervisor.ts +5 -2
  115. package/src/dev-watch-tree.ts +11 -10
  116. package/src/dev-watch.ts +1 -1
  117. package/src/dispatch.ts +29 -6
  118. package/src/docs-section.ts +69 -0
  119. package/src/docs-sources.ts +100 -0
  120. package/src/doctor-auth.ts +11 -2
  121. package/src/doctor-chart-alerts.ts +55 -0
  122. package/src/doctor-pooler.ts +107 -0
  123. package/src/doctor-rolling.ts +35 -0
  124. package/src/doctor-row-security.ts +40 -0
  125. package/src/document-options.ts +13 -0
  126. package/src/drill-sync-lane.ts +25 -0
  127. package/src/drill-targets.ts +39 -0
  128. package/src/duplicate-packages.ts +1 -1
  129. package/src/env-production.ts +93 -0
  130. package/src/error-codes-shot.ts +59 -0
  131. package/src/error-codes-tooling.ts +29 -0
  132. package/src/error-codes.ts +100 -54
  133. package/src/error-contract.ts +2 -2
  134. package/src/errors.ts +22 -3
  135. package/src/exercise-database.ts +99 -0
  136. package/src/exercise-findings.ts +159 -0
  137. package/src/exercise-overrides.ts +42 -0
  138. package/src/exercise-registration.ts +58 -0
  139. package/src/exercise-static.ts +88 -0
  140. package/src/exercise-subject.ts +69 -0
  141. package/src/failure-capture-boot.ts +30 -0
  142. package/src/framework-env.ts +7 -1
  143. package/src/framework-schema.ts +72 -5
  144. package/src/generate-catalog-locales.ts +19 -0
  145. package/src/generate-chart-alerts.ts +156 -0
  146. package/src/generate-feature-required.ts +65 -0
  147. package/src/generate-feature.ts +83 -6
  148. package/src/generate-files.ts +24 -0
  149. package/src/generate-format-written.ts +32 -0
  150. package/src/generate-kinds.ts +6 -0
  151. package/src/generate-sign-in-config.ts +100 -0
  152. package/src/generate-sign-in-manifest.ts +28 -0
  153. package/src/generate-sign-in-mode.ts +101 -0
  154. package/src/generate-sign-in.ts +250 -0
  155. package/src/generate-write.ts +44 -7
  156. package/src/hand-rolled-socket.ts +72 -0
  157. package/src/i18n-app-key-shapes.ts +29 -0
  158. package/src/i18n-audit.ts +10 -3
  159. package/src/i18n-registration.ts +18 -3
  160. package/src/index.ts +2 -0
  161. package/src/island-bundle.ts +4 -3
  162. package/src/island-disposer.ts +116 -0
  163. package/src/island-realtime-state.ts +34 -6
  164. package/src/island-realtime.ts +6 -2
  165. package/src/island-routes.ts +3 -2
  166. package/src/isr-query-advice.ts +6 -11
  167. package/src/isr-query-guard.ts +4 -3
  168. package/src/job-budget.ts +95 -0
  169. package/src/jobs-json.ts +11 -0
  170. package/src/lint-findings.ts +104 -0
  171. package/src/local-cli.ts +34 -4
  172. package/src/loopback-host.ts +2 -2
  173. package/src/mail-preview-findings.ts +24 -0
  174. package/src/mcp-errors.ts +82 -2
  175. package/src/mcp-host.ts +11 -1
  176. package/src/mcp-lock-findings.ts +57 -0
  177. package/src/measure-paths.ts +12 -4
  178. package/src/measure-scope.ts +27 -13
  179. package/src/messages-client.ts +23 -0
  180. package/src/messages-db.ts +56 -0
  181. package/src/messages-gate.ts +107 -0
  182. package/src/messages-repro.ts +11 -0
  183. package/src/messages-shot.ts +51 -0
  184. package/src/messages-upgrade.ts +45 -0
  185. package/src/messages.ts +53 -178
  186. package/src/metrics-endpoint.ts +23 -5
  187. package/src/new-shapes-generate.ts +71 -0
  188. package/src/new-shapes.ts +63 -0
  189. package/src/openapi-compile-ir.ts +68 -0
  190. package/src/openapi-compile-schema.ts +284 -0
  191. package/src/openapi-compile.ts +273 -0
  192. package/src/openapi-refuse.ts +45 -0
  193. package/src/otlp-export.ts +2 -1
  194. package/src/output-findings.ts +123 -0
  195. package/src/output.ts +32 -8
  196. package/src/package-boundaries.ts +110 -0
  197. package/src/page-pins.ts +23 -0
  198. package/src/plain-routes.ts +13 -0
  199. package/src/prerender-entries.ts +59 -0
  200. package/src/prerender-heaviest.ts +27 -0
  201. package/src/prerender-preload.ts +6 -0
  202. package/src/prerender-report.ts +12 -0
  203. package/src/prerender-seo-reads.ts +38 -0
  204. package/src/prerender-served.ts +22 -7
  205. package/src/prerender-vitals.ts +42 -0
  206. package/src/prerender.ts +61 -32
  207. package/src/pwa-artifacts.ts +4 -3
  208. package/src/pwa-manifest-locales.ts +1 -1
  209. package/src/registry.ts +18 -0
  210. package/src/repro-bundle.ts +65 -0
  211. package/src/repro-errors.ts +47 -0
  212. package/src/repro-job.ts +72 -0
  213. package/src/repro-literal.ts +41 -0
  214. package/src/repro-request.ts +48 -0
  215. package/src/repro-write.ts +100 -0
  216. package/src/retain-sweep.ts +27 -0
  217. package/src/retained-assets-boot.ts +113 -0
  218. package/src/retained-assets-layout.ts +110 -0
  219. package/src/retained-assets-read.ts +142 -0
  220. package/src/retained-assets-sweep.ts +70 -0
  221. package/src/retained-assets.ts +171 -0
  222. package/src/revalidated-response.ts +8 -5
  223. package/src/role-replicator.ts +7 -1
  224. package/src/role-start-devices.ts +119 -0
  225. package/src/role-start-types.ts +6 -0
  226. package/src/role-start.ts +22 -5
  227. package/src/role-sync.ts +7 -1
  228. package/src/route-islands.ts +42 -0
  229. package/src/route-table.ts +2 -1
  230. package/src/row-security-boot.ts +58 -0
  231. package/src/row-security-wanted.ts +32 -0
  232. package/src/runtime-admin.ts +0 -1
  233. package/src/runtime-assets-site-cache.ts +55 -0
  234. package/src/runtime-assets-site.ts +136 -0
  235. package/src/runtime-assets.ts +99 -55
  236. package/src/runtime-bindings.ts +19 -3
  237. package/src/runtime-cache.ts +23 -14
  238. package/src/runtime-isr-outcome.ts +114 -60
  239. package/src/runtime-purge.ts +14 -2
  240. package/src/runtime-queue.ts +31 -4
  241. package/src/runtime-render-document.ts +204 -0
  242. package/src/runtime-render.ts +48 -198
  243. package/src/runtime-replica-position.ts +62 -0
  244. package/src/runtime-replica.ts +18 -3
  245. package/src/runtime-s3-path-style.ts +17 -0
  246. package/src/runtime-services.ts +19 -6
  247. package/src/runtime-statement-bound.ts +22 -0
  248. package/src/runtime-storage-upload.ts +9 -0
  249. package/src/runtime-storage.ts +42 -138
  250. package/src/schema-diff-views.ts +54 -0
  251. package/src/schema-diff.ts +21 -3
  252. package/src/schema-drift.ts +28 -9
  253. package/src/schema-dump-drift.ts +91 -7
  254. package/src/script-csp.ts +1 -1
  255. package/src/seo-cards.ts +26 -0
  256. package/src/seo-routes.ts +22 -8
  257. package/src/serve-boot.ts +54 -21
  258. package/src/serve-drain.ts +1 -0
  259. package/src/serve-env.ts +26 -8
  260. package/src/serve-retire.ts +12 -6
  261. package/src/serve-web.ts +42 -4
  262. package/src/serve.ts +9 -7
  263. package/src/site-asset-routes.ts +17 -27
  264. package/src/site-config.ts +4 -0
  265. package/src/site-llms-pages.ts +106 -0
  266. package/src/site-llms.ts +64 -0
  267. package/src/site-seo-write.ts +4 -2
  268. package/src/site-seo.ts +55 -6
  269. package/src/source-mtime.ts +50 -0
  270. package/src/static-capture-errors.ts +41 -0
  271. package/src/static-capture.ts +56 -0
  272. package/src/static-cards-serve.ts +39 -0
  273. package/src/static-cards.ts +93 -0
  274. package/src/static-document.ts +14 -13
  275. package/src/static-images.ts +115 -0
  276. package/src/stream-hole-scope.ts +55 -0
  277. package/src/style-bundle.ts +1 -1
  278. package/src/style-routes.ts +3 -2
  279. package/src/sw-precache-plan.ts +2 -2
  280. package/src/sw-routes.ts +2 -2
  281. package/src/templates/action-tests.ts +306 -0
  282. package/src/templates/action.ts +81 -269
  283. package/src/templates/backfill.ts +1 -1
  284. package/src/templates/entity.ts +21 -5
  285. package/src/templates/github/ci.yml.ts +8 -9
  286. package/src/templates/guard-bare-error.ts +2 -2
  287. package/src/templates/guard-error-declared.ts +263 -0
  288. package/src/templates/guard-native-confirm.ts +132 -0
  289. package/src/templates/guard-raw-fetch.ts +151 -0
  290. package/src/templates/guard-raw-interval.ts +121 -0
  291. package/src/templates/guard-repo-raw-sql.ts +71 -2
  292. package/src/templates/job.ts +1 -1
  293. package/src/templates/naming.ts +5 -0
  294. package/src/templates/pinned/docker-compose.yml +6 -0
  295. package/src/templates/pinned-images.ts +8 -0
  296. package/src/templates/policy.ts +85 -2
  297. package/src/templates/query.ts +70 -13
  298. package/src/templates/resource-create.ts +3 -1
  299. package/src/templates/resource-form-island.ts +15 -4
  300. package/src/templates/resource-page-db.ts +83 -0
  301. package/src/templates/resource-page.ts +161 -52
  302. package/src/templates/route.ts +4 -6
  303. package/src/templates/scaffold-app.ts +3 -0
  304. package/src/templates/scaffold-auth.ts +2 -1
  305. package/src/templates/scaffold-bin.ts +5 -52
  306. package/src/templates/scaffold-claude-agents.ts +19 -19
  307. package/src/templates/scaffold-claude-commands.ts +28 -31
  308. package/src/templates/scaffold-claude.ts +26 -23
  309. package/src/templates/scaffold-container-compose.ts +6 -1
  310. package/src/templates/scaffold-container.ts +24 -4
  311. package/src/templates/scaffold-dashboard-bare.ts +3 -3
  312. package/src/templates/scaffold-dashboard-example-tests.ts +49 -9
  313. package/src/templates/scaffold-dashboard-example-view.ts +156 -0
  314. package/src/templates/scaffold-dashboard-example.ts +43 -131
  315. package/src/templates/scaffold-dashboard-shared.ts +3 -7
  316. package/src/templates/scaffold-db-client.ts +20 -6
  317. package/src/templates/scaffold-db-package.ts +5 -14
  318. package/src/templates/scaffold-docs.ts +73 -57
  319. package/src/templates/scaffold-domain-package.ts +18 -14
  320. package/src/templates/scaffold-entries.ts +7 -2
  321. package/src/templates/scaffold-exercise.ts +37 -0
  322. package/src/templates/scaffold-guards.ts +13 -1
  323. package/src/templates/scaffold-helm-alerts.ts +234 -0
  324. package/src/templates/scaffold-helm-templates.ts +7 -5
  325. package/src/templates/scaffold-helm.ts +46 -12
  326. package/src/templates/scaffold-i18n.ts +6 -2
  327. package/src/templates/scaffold-mcp-endpoint.ts +182 -0
  328. package/src/templates/scaffold-mcp-json.ts +31 -0
  329. package/src/templates/scaffold-mcp-package.ts +59 -56
  330. package/src/templates/scaffold-repo.ts +7 -2
  331. package/src/templates/scaffold-shell.ts +45 -79
  332. package/src/templates/scaffold-test-services.ts +13 -5
  333. package/src/templates/shapes/api-keys.ts +170 -0
  334. package/src/templates/shapes/api.ts +101 -0
  335. package/src/templates/shapes/blog-site.ts +340 -0
  336. package/src/templates/shapes/blog-slice.ts +265 -0
  337. package/src/templates/shapes/blog.ts +114 -0
  338. package/src/templates/shapes/dashboard-detail.ts +242 -0
  339. package/src/templates/shapes/dashboard.ts +53 -0
  340. package/src/templates/shapes/index.ts +17 -0
  341. package/src/templates/shapes/overlay.ts +99 -0
  342. package/src/templates/shapes/shared-edits.ts +46 -0
  343. package/src/templates/sign-in-action-tests.ts +237 -0
  344. package/src/templates/sign-in-authenticator.ts +112 -0
  345. package/src/templates/sign-in-oidc.ts +189 -0
  346. package/src/templates/sign-in-pages.ts +148 -0
  347. package/src/templates/sign-in-password.ts +216 -0
  348. package/src/templates/sign-in-shared.ts +268 -0
  349. package/src/templates/slice-foundation.ts +70 -15
  350. package/src/test-batches.ts +1 -1
  351. package/src/test-database.ts +1 -1
  352. package/src/test-shards.ts +7 -1
  353. package/src/test-workers.ts +18 -12
  354. package/src/tool-gate-names.ts +34 -0
  355. package/src/upgrade-agents-link.ts +49 -0
  356. package/src/upgrade-bun-floor.ts +225 -0
  357. package/src/upgrade-gate.ts +228 -0
  358. package/src/upgrade-pins.ts +63 -0
  359. package/src/upgrade-plan.ts +214 -0
  360. package/src/upgrade-registry.ts +53 -0
  361. package/src/upgrade-report.ts +139 -0
  362. package/src/upgrade-root.ts +74 -0
  363. package/src/verify-budgets-build.ts +118 -0
  364. package/src/verify-checks.ts +67 -24
  365. package/src/verify-coverage-run.ts +1 -1
  366. package/src/verify-e2e-lane.ts +41 -0
  367. package/src/verify-exercise.ts +64 -0
  368. package/src/verify-floor.ts +41 -0
  369. package/src/verify-merge.ts +19 -4
  370. package/src/verify-role-load.ts +2 -0
  371. package/src/verify-run.ts +28 -8
  372. package/src/verify-shard.ts +12 -2
  373. package/src/verify-skip-ceiling.ts +87 -0
  374. package/src/verify-step.ts +5 -0
  375. package/src/verify-tests.ts +25 -5
  376. package/src/view-drift.ts +55 -0
  377. package/src/worker-bundle.ts +3 -2
  378. package/src/workspace-graph.ts +38 -19
  379. package/src/write-generated.ts +43 -0
  380. package/src/zone-boot.ts +20 -0
  381. package/wiki/Actions.md +342 -0
  382. package/wiki/Admin-Dashboard.md +432 -0
  383. package/wiki/Agents.md +958 -0
  384. package/wiki/Auth.md +195 -0
  385. package/wiki/Bare-VM.md +111 -0
  386. package/wiki/Batching-And-Preloading.md +136 -0
  387. package/wiki/Building-Your-Own-Base.md +203 -0
  388. package/wiki/CI-Parallel-Gate.md +220 -0
  389. package/wiki/CLI-Reference.md +1894 -0
  390. package/wiki/Caching-And-Invalidation.md +255 -0
  391. package/wiki/Client-Data.md +194 -0
  392. package/wiki/Client-Navigation.md +513 -0
  393. package/wiki/Compared.md +93 -0
  394. package/wiki/Configuration.md +868 -0
  395. package/wiki/Contributing.md +12 -0
  396. package/wiki/Deploy-Recipes.md +122 -0
  397. package/wiki/Deployment.md +508 -0
  398. package/wiki/Entities-And-Migrations.md +1116 -0
  399. package/wiki/FAQ.md +175 -0
  400. package/wiki/Feature-Flags.md +65 -0
  401. package/wiki/Getting-Started.md +221 -0
  402. package/wiki/Home.md +160 -0
  403. package/wiki/I18n.md +239 -0
  404. package/wiki/Installation.md +206 -0
  405. package/wiki/Interface-Rules.md +339 -0
  406. package/wiki/Jobs-And-Workflows.md +783 -0
  407. package/wiki/Known-Gaps.md +185 -0
  408. package/wiki/MCP-And-AI.md +753 -0
  409. package/wiki/Mail.md +138 -0
  410. package/wiki/Migrating-An-Existing-App.md +594 -0
  411. package/wiki/Migrating-From-Django.md +54 -0
  412. package/wiki/Migrating-From-Laravel.md +53 -0
  413. package/wiki/Migrating-From-Node-Drizzle.md +53 -0
  414. package/wiki/Migrating-From-Rails.md +53 -0
  415. package/wiki/Migrations-And-Backfills.md +552 -0
  416. package/wiki/Money.md +338 -0
  417. package/wiki/N-Plus-One-Detection.md +197 -0
  418. package/wiki/Notify.md +208 -0
  419. package/wiki/Observability.md +245 -0
  420. package/wiki/Outbound-HTTP.md +109 -0
  421. package/wiki/PWA-And-Offline.md +343 -0
  422. package/wiki/Policies-And-Authz.md +251 -0
  423. package/wiki/Project-Layout.md +150 -0
  424. package/wiki/Queries-And-Live-Queries.md +429 -0
  425. package/wiki/Realtime.md +734 -0
  426. package/wiki/Resource-Management.md +121 -0
  427. package/wiki/Routes-And-Render-Modes.md +521 -0
  428. package/wiki/SEO.md +288 -0
  429. package/wiki/Scheduled-Tasks.md +175 -0
  430. package/wiki/Scraping.md +493 -0
  431. package/wiki/Shapes.md +63 -0
  432. package/wiki/Stability.md +97 -0
  433. package/wiki/Static-Assets.md +166 -0
  434. package/wiki/Storage-And-Uploads.md +404 -0
  435. package/wiki/Testing.md +1313 -0
  436. package/wiki/The-Eight-Primitives.md +247 -0
  437. package/wiki/Theming.md +362 -0
  438. package/wiki/Timezones-And-Dates.md +194 -0
  439. package/wiki/Troubleshooting.md +163 -0
  440. package/wiki/Tutorial-01-First-App.md +289 -0
  441. package/wiki/Tutorial-02-First-Feature.md +339 -0
  442. package/wiki/Tutorial-03-Auth-And-Admin.md +379 -0
  443. package/wiki/Tutorial-04-Jobs-And-Realtime.md +263 -0
  444. package/wiki/Tutorial-05-Deploy-Free.md +231 -0
  445. package/wiki/Tutorial-06-Growing-Up.md +158 -0
  446. package/wiki/UI-Components.md +156 -0
  447. package/wiki/Upgrading.md +3667 -0
  448. package/src/stored-object-headers.ts +0 -43
@@ -3,81 +3,59 @@
3
3
  // document (head + the route's component, the surface's CSS LINKED, `style-bundle.ts`), it never
4
4
  // decides what a mode means or what headers it earns.
5
5
 
6
- import { actionPathStyle } from '@ultimat3/action';
7
6
  import { clientScopeOf } from '@ultimat3/auth';
8
7
  import type { Actor, Ctx } from '@ultimat3/core';
9
- import { CLIENT_SCOPE_HEADER, escapeHtml, singleFlight } from '@ultimat3/core';
8
+ import { CLIENT_SCOPE_HEADER, singleFlight } from '@ultimat3/core';
10
9
  import type {
11
10
  RouteMeta as HttpRouteMeta,
12
11
  RedirectIntent,
13
12
  RequestContext,
14
13
  Route,
15
- RouteParams,
16
14
  UltimateRequest,
17
15
  } from '@ultimat3/http';
18
- import { asCtx, html, NO_STORE, redirect, stream, takeRedirect } from '@ultimat3/http';
19
- import { currentLocale } from '@ultimat3/i18n';
20
- import type { IslandCollector, RenderResult, RouteData, RouteEntry } from '@ultimat3/render';
21
16
  import {
22
- clientPathStyleTags,
23
- clientPersistTags,
24
- clientScopeTag,
25
- clientSyncTags,
17
+ asCtx,
18
+ html,
19
+ NO_STORE,
20
+ redirect,
21
+ stream,
22
+ takeRedirect,
23
+ withAnonymousRequest,
24
+ } from '@ultimat3/http';
25
+ import type { RenderResult, RouteData, RouteEntry } from '@ultimat3/render';
26
+ import {
26
27
  documentCarriesScope,
27
- headFromMeta,
28
- hydrateRuntime,
29
- metaContextFor,
30
- renderHead,
31
28
  routeDataFor,
32
29
  routeEntries,
33
30
  routeFor,
34
31
  routeStatusOf,
35
- seoRenderers,
36
32
  } from '@ultimat3/render';
37
33
  import type { IsrController } from '@ultimat3/render/server';
38
34
  import {
39
35
  contentHash,
40
- ROOT_ELEMENT_ID,
41
- renderComponent,
36
+ holeSink,
42
37
  renderSsr,
43
38
  ssrHeaders,
44
39
  staticHeaders,
45
40
  streamResult,
46
41
  } from '@ultimat3/render/server';
47
- import { loadAppConfig } from './app-config-load';
48
- import type { DocumentOptions } from './document-options';
49
- import { routeQuery } from './isr-query-guard';
50
42
  import {
51
43
  type NavigationDocumentHead,
52
44
  navigationMetaOf,
53
- navigationTagsOf,
54
45
  principalRelocation,
55
46
  } from './page-navigation';
56
- import { bootScript, collectorFor } from './route-islands';
47
+ import { collectorFor, streamScripts } from './route-islands';
57
48
  import { addressedUrl } from './runtime-address';
58
49
  import { attachedIsr } from './runtime-isr';
59
50
  import { isrOutcome } from './runtime-isr-outcome';
60
- import { siteAssetTable } from './site-assets';
51
+ import type { DevRouteData, RenderOptions } from './runtime-render-document';
52
+ import { documentFrom, headFor, lang, routeBody, stylesFor } from './runtime-render-document';
61
53
  import type { StaticResult } from './static-document';
62
54
  import { createStaticMemo, staticMemoKey, staticResponse } from './static-document';
63
- import { styleBundle } from './style-bundle';
55
+ import { enterHole } from './stream-hole-scope';
64
56
 
65
57
  export type { DocumentOptions, IslandResolver } from './document-options';
66
58
 
67
- /**
68
- * The app-wide favicon head, from `site.favicon`: the content-hashed URL `asset('assets/….svg')`
69
- * returns and `/assets/*` serves, never the raw path. Per render, never frozen at boot.
70
- */
71
- export async function appFaviconHead(root: string): Promise<string | undefined> {
72
- const favicon = (await loadAppConfig(root))?.site.favicon;
73
- if (favicon === undefined) return undefined;
74
- const url = siteAssetTable(root).resolve(favicon).url;
75
- return `<link rel="icon" type="image/svg+xml" href="${escapeHtml(url)}">`;
76
- }
77
-
78
- /** `DocumentOptions`, plus the app root the app's own favicon is resolved against per render. */
79
- export type RenderOptions = DocumentOptions & { readonly root?: string };
80
-
81
59
  export interface DevRenderOptions extends RenderOptions {
82
60
  readonly buildId: string;
83
61
  /**
@@ -92,130 +70,6 @@ export interface DevRenderOptions extends RenderOptions {
92
70
  */
93
71
  readonly memoStatic?: boolean;
94
72
  }
95
- /** What a route's `meta(data)` is given. `url` is a string because that is what `ld.*` embeds. */
96
- export interface DevRouteData extends Record<string, unknown> {
97
- readonly url: string;
98
- readonly params: RouteParams;
99
- }
100
- /**
101
- * `<html lang>` is the request's own locale, never a constant: the `locale` stage negotiated it
102
- * one stage before this handler and published it on the context, so a hardcoded `'en'` shipped
103
- * every document mislabelled — wrong for a screen reader, wrong for `hreflang`. Outside a request
104
- * (`x build`'s prerender) it is the app's own configured fallback.
105
- */
106
- const lang = (): string => currentLocale();
107
-
108
- /**
109
- * `scope` is present only on a PRIVATE document (a gated `ssr` page, every `stream`): the page's
110
- * client scope (`@ultimat3/auth`'s `clientScopeOf`), which core's `pageClient()` reads to fence its
111
- * one store per principal. A shareable document carries NO scope tag — absent means "not rendered
112
- * for anyone", a different answer from `''`, the anonymous page.
113
- */
114
- const headFor = async (
115
- entry: RouteEntry,
116
- ctx: DevRouteData,
117
- data: RouteData,
118
- options: RenderOptions,
119
- scope?: string,
120
- ): Promise<string> => {
121
- const meta = metaContextFor(ctx, data);
122
- const url = new URL(ctx.url);
123
- const router = navigationTagsOf(options.navigation, entry, meta.t);
124
- return (
125
- renderHead(
126
- headFromMeta(
127
- await entry.config.meta(meta),
128
- seoRenderers({
129
- path: url.pathname,
130
- baseUrl: options.origin ?? url.origin,
131
- // The default is the cluster's own head (`alternates` is default-first by contract), so
132
- // `x-default` comes from the list the hreflang tags do — never a second locale read.
133
- localization: {
134
- locale: meta.locale,
135
- defaultLocale: meta.alternates[0]?.locale ?? meta.locale,
136
- alternates: meta.alternates,
137
- },
138
- }),
139
- [
140
- ...(options.sync === undefined ? [] : clientSyncTags(options.sync)),
141
- // Every document, shareable ones included: the style is the app's, not a visitor's, and
142
- // it is the only way an island's `rpc()` learns it. Read per render — `defineApi` may run
143
- // after these routes are built. `'resource'`, the default, is no tag.
144
- ...clientPathStyleTags(actionPathStyle()),
145
- ...router,
146
- // The page boot rides the scope tag: its whole job — restoring a principal's persisted
147
- // records and replaying its queued writes — is per principal, and a shareable document
148
- // (no scope tag) has neither.
149
- ...(scope === undefined
150
- ? []
151
- : [clientScopeTag(scope), ...clientPersistTags(options.persisted?.() ?? [])]),
152
- ],
153
- ),
154
- ) +
155
- (options.themeHead ?? '') +
156
- // One mechanism per document: the router prefetches for itself, the browser for the rest.
157
- (router.length === 0 ? (options.speculationHead ?? '') : '') +
158
- // The app's own favicon: one link, every document, the URL the table resolves NOW.
159
- ((options.root === undefined ? options.faviconHead : await appFaviconHead(options.root)) ??
160
- '') +
161
- // Per locale: each links its own locale's manifest (`PwaArtifacts.headFor`).
162
- (typeof options.pwaHead === 'function' ? options.pwaHead(meta.locale) : (options.pwaHead ?? ''))
163
- );
164
- };
165
-
166
- /**
167
- * `<link rel="stylesheet">` for the surface's own stylesheets, or nothing at all when the surface
168
- * imports none.
169
- *
170
- * In `<head>`, which is what keeps this a swap and not a regression: a `<link rel="stylesheet">`
171
- * there is render-blocking in every browser, exactly as the inline block was, so there is no window
172
- * in which the document paints unstyled. Moving it to the body, or deferring it, would introduce a
173
- * flash — never do that here.
174
- *
175
- * Read through `styleBundle()` rather than passed in through `DocumentOptions`: the registry it
176
- * derives from is process-global (importing the app IS what fills it), so a caller that forgot to
177
- * thread a resolver would serve a document with no CSS at all. One reader, and it is the same one
178
- * `styleRoutes` serves from.
179
- */
180
- const styleTag = (entry: RouteEntry): string => {
181
- const href = styleBundle().hrefFor(entry.surface);
182
- return href === undefined ? '' : `<link rel="stylesheet" href="${href}">`;
183
- };
184
-
185
- /** The surface stylesheet, then the app's brand — the order that lets the brand win the cascade. */
186
- const stylesFor = (entry: RouteEntry, options: DocumentOptions): string =>
187
- styleTag(entry) + (options.brandHead ?? '');
188
-
189
- /**
190
- * The route's rendered body, inside the hydration root. Every mode goes through here — an empty
191
- * root is what a module exporting no component renders, and nothing else: `spa` was the one mode
192
- * that never reached this function, so every `spa` route ever declared served an empty root.
193
- */
194
- export async function routeBody(
195
- entry: RouteEntry,
196
- ctx: DevRouteData,
197
- data: RouteData,
198
- islands: IslandCollector,
199
- ): Promise<string> {
200
- if (entry.component === undefined) return `<div id="${ROOT_ELEMENT_ID}"></div>`;
201
- const url = new URL(ctx.url);
202
- const html = await renderComponent(
203
- entry.component,
204
- // `data` is the route's own `load` result and is what `meta` was just given — the same object,
205
- // never a second resolution. `query` is supplied because a page that reads `props.query.x`
206
- // otherwise dereferences undefined and takes the whole render down.
207
- {
208
- data,
209
- params: ctx.params,
210
- url: ctx.url,
211
- query: routeQuery(entry, url),
212
- },
213
- entry.file,
214
- { islands },
215
- );
216
- return `<div id="${ROOT_ELEMENT_ID}">${html}</div>`;
217
- }
218
-
219
73
  /**
220
74
  * Head + body for one route render — the document `x dev` serves, which the build's prerenderer
221
75
  * must emit too.
@@ -262,33 +116,6 @@ function documentScope(entry: RouteEntry, buildId: string, actor: Actor): string
262
116
  }
263
117
  }
264
118
 
265
- /**
266
- * The document from data ALREADY resolved. Split from `routeDocument` so one request resolves
267
- * `load` exactly once: `stream` renders head and body separately, and resolving in each would let
268
- * a `<title>` describe content the body does not contain.
269
- *
270
- * The hydration runtime is appended after the body and only after it: what strategies a page needs
271
- * is a fact about the islands the walk just recorded, so emitting it earlier would guess or ship
272
- * the whole runtime to a page with no island.
273
- */
274
- async function documentFrom(
275
- entry: RouteEntry,
276
- ctx: DevRouteData,
277
- data: RouteData,
278
- options: RenderOptions,
279
- scope?: string,
280
- ): Promise<string> {
281
- const islands = collectorFor(entry, options, scope);
282
- const [head, body] = await Promise.all([
283
- headFor(entry, ctx, data, options, scope),
284
- routeBody(entry, ctx, data, islands),
285
- ]);
286
- return (
287
- `<!doctype html><html lang="${lang()}"><head>${head}${stylesFor(entry, options)}</head>` +
288
- `<body>${body}${bootScript(entry, islands, options, scope)}${hydrateRuntime(islands.directives)}</body></html>`
289
- );
290
- }
291
-
292
119
  /** Every mode but `isr`, which `renderIsr` answers: its `load` runs only when the store misses. */
293
120
  async function resultFor(
294
121
  entry: RouteEntry,
@@ -309,17 +136,18 @@ async function resultFor(
309
136
  return { status, headers: staticHeaders(contentHash(body), options.buildId), body };
310
137
  }
311
138
  case 'stream': {
312
- // The shell IS the component: nothing can yet mark a subtree as a hole, and Solid's
313
- // `Suspense` cannot be the missing piece — it calls `getContextId()`, which throws outside a
314
- // Solid renderer. Until a hole marker of the framework's own exists, the first flush carries
315
- // the whole body — correct output, no streaming benefit.
139
+ // Everything outside a `<Hole>` is the first flush; every hole fills after it, in the
140
+ // request's context, in completion order. `status` and any redirect were decided by `load`
141
+ // above: a hole starts after the headers left, so its failure is its own error markup.
316
142
  // A stream is always `private, no-store` (`streamResult`), so it always carries the scope.
317
143
  const scope = documentScope(entry, options.buildId, ctx.actor);
318
144
  const islands = collectorFor(entry, options, scope);
145
+ const sink = holeSink({ enter: enterHole });
319
146
  const [head, shell] = await Promise.all([
320
147
  headFor(entry, request, data, options, scope),
321
- routeBody(entry, request, data, islands),
148
+ routeBody(entry, request, data, islands, sink),
322
149
  ]);
150
+ const scripts = streamScripts(entry, islands, options, scope, sink.holes.length > 0);
323
151
  return withScope(
324
152
  streamResult(
325
153
  {
@@ -327,8 +155,8 @@ async function resultFor(
327
155
  // The runtime rides the first flush, with the shell it boots. A later chunk would leave
328
156
  // the window between flush one and the close with inert islands and no listeners on
329
157
  // them — the first-click-lost failure `interaction` replay exists for.
330
- shell: `${shell}${bootScript(entry, islands, options, scope)}${hydrateRuntime(islands.directives)}`,
331
- holes: [],
158
+ shell: `${shell}${scripts.first}`,
159
+ holes: sink.holes.map((hole) => ({ ...hole, after: scripts.after })),
332
160
  },
333
161
  { buildId: options.buildId },
334
162
  status,
@@ -428,7 +256,7 @@ export function appRoutes(options: DevRenderOptions): readonly Route[] {
428
256
  // A static page already rendered by this process is answered without `load` or a render.
429
257
  const key =
430
258
  options.memoStatic === true
431
- ? staticMemoKey(entry, addressedUrl(request.url, ctx), asCtx(ctx).locale)
259
+ ? staticMemoKey(entry, addressedUrl(request.url, ctx), asCtx(ctx).locale, options.origin)
432
260
  : undefined;
433
261
  const kept = key === undefined ? undefined : memo.get(key);
434
262
  if (kept !== undefined) return staticResponse(request, kept);
@@ -468,6 +296,21 @@ async function renderEntry(
468
296
  const at = entry.config.render === 'static' ? addressedUrl(request.url, ctx) : request.url;
469
297
  const data: DevRouteData = { url: at.href, params: ctx.params };
470
298
  if (entry.config.render === 'isr') return renderIsr(entry, data, ctx, options, isr);
299
+ // A `static` document is kept and answered to every later visitor, so it is rendered as nobody,
300
+ // `load` and document both: the memo and a CDN would otherwise hold this visitor's answer.
301
+ if (entry.config.render === 'static') {
302
+ return withAnonymousRequest(ctx, at, (scope) => loadAndRender(entry, data, scope, options));
303
+ }
304
+ return loadAndRender(entry, data, ctx, options);
305
+ }
306
+
307
+ /** Every mode but `isr`, whose `load` is inside its producer (`runtime-isr-outcome.ts`). */
308
+ async function loadAndRender(
309
+ entry: RouteEntry,
310
+ data: DevRouteData,
311
+ ctx: RequestContext,
312
+ options: DevRenderOptions,
313
+ ): Promise<Rendered> {
471
314
  // ONCE per request, before the mode is chosen: every branch of `resultFor` reads this same
472
315
  // object, so a route's `load` runs exactly once however its mode splits head from body.
473
316
  const loaded = await routeDataFor(entry.config, data);
@@ -494,7 +337,14 @@ async function renderIsr(
494
337
  ): Promise<Rendered> {
495
338
  const document = (loaded: RouteData, at: DevRouteData): Promise<string> =>
496
339
  documentFrom(entry, at, loaded, options);
497
- const outcome = await isrOutcome({ entry, data, ctx, isr, document });
340
+ const outcome = await isrOutcome({
341
+ entry,
342
+ data,
343
+ ctx,
344
+ isr,
345
+ document,
346
+ origin: options.origin,
347
+ });
498
348
  if (outcome.kind === 'redirect') return { respond: () => loadRedirect(entry, outcome.to) };
499
349
  return { respond: () => responseOf(outcome.result) };
500
350
  }
@@ -0,0 +1,62 @@
1
+ // Single responsibility: the replica scope's request frame, and the position it carries. A request
2
+ // that wrote answers with the primary's WAL position (`__Host-x-pos` and `x-ultimate-position`);
3
+ // a later request that brings it back reads a replica only once that replica has replayed it.
4
+
5
+ import { logger, renderThrowable } from '@ultimat3/core';
6
+ import { db, formatPosition, parsePosition, primaryPosition, withReplicaReads } from '@ultimat3/db';
7
+ import {
8
+ type Middleware,
9
+ type RequestContext,
10
+ setCookie,
11
+ type UltimateRequest,
12
+ } from '@ultimat3/http';
13
+
14
+ export const POSITION_COOKIE = '__Host-x-pos';
15
+ export const POSITION_HEADER = 'x-ultimate-position';
16
+ /** How long a write's position is carried: past it, a replica is assumed to have caught up. */
17
+ export const POSITION_STICKY_MS = 30_000;
18
+
19
+ /**
20
+ * The position this request carries, parsed — the header an API client echoes, else the cookie a
21
+ * browser does. Never the raw value: a forged one costs the sender a primary read, nothing more.
22
+ */
23
+ export function carriedPosition(request: UltimateRequest): bigint | undefined {
24
+ return (
25
+ parsePosition(request.header(POSITION_HEADER)) ?? parsePosition(request.cookie(POSITION_COOKIE))
26
+ );
27
+ }
28
+
29
+ /**
30
+ * Stamps the position on the request's response-header bag, which the `response` stage lays onto
31
+ * whatever Response the handler built — a `Response.redirect`, the write-then-redirect this exists
32
+ * for, included. The cookie through `setCookie`, so its attributes are core's one serializer's.
33
+ */
34
+ function stamp(ctx: RequestContext, position: bigint, stickyMs: number): void {
35
+ const text = formatPosition(position);
36
+ ctx.headers.set(POSITION_HEADER, text);
37
+ setCookie(POSITION_COOKIE, text, { maxAge: Math.ceil(stickyMs / 1000) });
38
+ }
39
+
40
+ /**
41
+ * Opens the scope with the carried position, runs the request, and — only when it wrote — reads the
42
+ * primary's position ONCE after every transaction in it committed. Inside the scope, so the read
43
+ * itself is the primary's: a scope that wrote routes nothing to the replica.
44
+ */
45
+ export function positionStage(stickyMs: number = POSITION_STICKY_MS): Middleware {
46
+ return (request, ctx, next) =>
47
+ withReplicaReads(
48
+ async (scope) => {
49
+ const response = await next(request, ctx);
50
+ if (!scope.wrote) return response;
51
+ const position = await primaryPosition(db()).catch((error: unknown) => {
52
+ // The write committed and the answer is ready: a lost position costs the NEXT request a
53
+ // possibly stale replica read within the sticky window, never this request its answer.
54
+ logger.warn('db.replica_position_unread', { error: renderThrowable(error) });
55
+ return undefined;
56
+ });
57
+ if (position !== undefined) stamp(ctx, position, stickyMs);
58
+ return response;
59
+ },
60
+ { minPosition: carriedPosition(request) },
61
+ );
62
+ }
@@ -17,10 +17,11 @@
17
17
  // what a database is.
18
18
 
19
19
  import type { DbClient, PoolProfile, PostgresClient } from '@ultimat3/db';
20
- import { postgresClient, REPLICA_URL_ENV, replicatedClient, withReplicaReads } from '@ultimat3/db';
20
+ import { POOLER_ENV, postgresClient, REPLICA_URL_ENV, replicatedClient } from '@ultimat3/db';
21
21
  import type { Middleware } from '@ultimat3/http';
22
22
  import type { ServiceBinding } from './runtime-bindings';
23
23
  import type { RuntimeOverrides } from './runtime-overrides';
24
+ import { positionStage } from './runtime-replica-position';
24
25
 
25
26
  export type ReplicaEnv = Readonly<Record<string, string | undefined>>;
26
27
 
@@ -44,6 +45,9 @@ export interface ReplicaAttachment {
44
45
  readonly replica: PostgresClient | undefined;
45
46
  }
46
47
 
48
+ const isPool = (client: DbClient): client is PostgresClient =>
49
+ 'profile' in client && 'pooler' in client;
50
+
47
51
  /**
48
52
  * The primary as it is, or the primary with a standby behind it. Composed here rather than by
49
53
  * calling `defaultClient()`, because that function builds its own primary from `DATABASE_URL` and
@@ -57,7 +61,14 @@ export function attachReplica(
57
61
  profile: Partial<PoolProfile> = {},
58
62
  ): ReplicaAttachment {
59
63
  if (replicaUrl === undefined) return { client: primary, replica: undefined };
60
- const replica = postgresClient({ url: replicaUrl, applicationName: 'ultimate-replica', profile });
64
+ const replica = postgresClient({
65
+ url: replicaUrl,
66
+ applicationName: 'ultimate-replica',
67
+ // The primary's answers, never a second read of the environment: one process, one role's
68
+ // numbers and one pooler mode. An embedded primary has neither, and the defaults stand.
69
+ profile: isPool(primary) ? { ...primary.profile, ...profile } : profile,
70
+ env: { [POOLER_ENV]: isPool(primary) ? primary.pooler : undefined },
71
+ });
61
72
  return { client: replicatedClient(primary, replica), replica };
62
73
  }
63
74
 
@@ -66,13 +77,17 @@ export function attachReplica(
66
77
  * every later read in it to the primary, which is what read-your-writes means. `compose()` wraps
67
78
  * each route handler, and every read and write a request makes happens inside one.
68
79
  *
80
+ * The same frame carries read-your-writes ACROSS requests: the position a write produced goes back
81
+ * as a cookie and a header, and the next request's reads wait for a replica that has replayed it
82
+ * (`runtime-replica-position.ts`).
83
+ *
69
84
  * EMPTY when no replica is configured, and that is the whole cost argument: a homework app
70
85
  * installs no middleware, allocates no scope object and enters no `AsyncLocalStorage` run per
71
86
  * request. With one configured, the list is a single frame.
72
87
  */
73
88
  export function replicaMiddleware(binding: ServiceBinding, env: ReplicaEnv): readonly Middleware[] {
74
89
  if (replicaUrlFor(binding, env) === undefined) return [];
75
- return [(request, ctx, next) => withReplicaReads(() => next(request, ctx))];
90
+ return [positionStage()];
76
91
  }
77
92
 
78
93
  /**
@@ -0,0 +1,17 @@
1
+ // Single responsibility: the one reader of `S3_FORCE_PATH_STYLE`. A gateway addressed by host and port
2
+ // needs path style and R2 and AWS do not, so it is declared, never sniffed from the endpoint. It read
3
+ // only `'1'`, so `true` silently selected virtual-host style and every request missed the bucket.
4
+
5
+ import { ConfigInvalidError, renderCauseValue } from '@ultimat3/core';
6
+ import type { Env } from './runtime-bindings';
7
+
8
+ /** `1` is on; `0`, unset and blank are off, as a blank `S3_*` is unset everywhere else on the disk. */
9
+ export function s3PathStyleOf(env: Env): boolean {
10
+ const value = env['S3_FORCE_PATH_STYLE'];
11
+ if (value === undefined || value === '' || value === '0') return false;
12
+ if (value === '1') return true;
13
+ throw new ConfigInvalidError({
14
+ cause: `S3_FORCE_PATH_STYLE is ${renderCauseValue(value)}, and only 0 and 1 say which addressing the disk uses`,
15
+ fix: 'S3_FORCE_PATH_STYLE=1 # a gateway addressed by host and port; 0 for AWS and R2',
16
+ });
17
+ }
@@ -4,7 +4,14 @@
4
4
  // boot installs, only backed by embedded drivers.
5
5
 
6
6
  import { mkdirSync } from 'node:fs';
7
- import { configureAuthLimiters, postgresAuthLimiter, resetAuthLimiters } from '@ultimat3/auth';
7
+ import {
8
+ configureAuthLimiters,
9
+ configureTotpReplay,
10
+ postgresAuthLimiter,
11
+ postgresTotpReplayGuard,
12
+ resetAuthLimiters,
13
+ resetTotpReplay,
14
+ } from '@ultimat3/auth';
8
15
  import type { PurgeDriver } from '@ultimat3/cache';
9
16
  import { isNoopPurgeDriver, selectPurgeDriver } from '@ultimat3/cache';
10
17
  import {
@@ -43,9 +50,10 @@ import { inboxRetentionOf } from './runtime-notify-retention';
43
50
  import type { RuntimeOverrides } from './runtime-overrides';
44
51
  import { installRetentionSweep } from './runtime-purge';
45
52
  import { installAppWebPush, selectWebPush } from './runtime-push';
46
- import type { DevDbClient, SchemaMode } from './runtime-queue';
47
- import { startQueue } from './runtime-queue';
53
+ import { type DevDbClient, type SchemaMode, startQueue } from './runtime-queue';
48
54
  import { realtimeConfigOf } from './runtime-realtime';
55
+ import { s3PathStyleOf } from './runtime-s3-path-style';
56
+ import { reportStatementBound } from './runtime-statement-bound';
49
57
 
50
58
  export interface RunningServices {
51
59
  readonly services: DevServices;
@@ -194,9 +202,7 @@ export function startStorage(services: DevServices, env: Env, override?: Storage
194
202
  bucket,
195
203
  endpoint: binding.url,
196
204
  ...(env['S3_REGION'] === undefined ? {} : { region: env['S3_REGION'] }),
197
- // A gateway addressed by host and port needs path style; R2 and AWS do not. Declared,
198
- // never sniffed from the endpoint.
199
- forcePathStyle: env['S3_FORCE_PATH_STYLE'] === '1',
205
+ forcePathStyle: s3PathStyleOf(env),
200
206
  }),
201
207
  },
202
208
  default: 'object',
@@ -361,6 +367,7 @@ export async function startServices(
361
367
  // a different answer than the middleware that routes to it.
362
368
  const queue = await startQueue(services, overrides, env, schema);
363
369
  const { db, jobs, outbox, events } = queue;
370
+ await reportStatementBound(db);
364
371
  // The same executor the jobs driver, the outbox, the event bus and the idempotency store run
365
372
  // on — one pool, one `Bun.sql` that does NOT satisfy `PgExecutor` (`Bun.sql.query` is
366
373
  // `undefined`), one wrapper.
@@ -388,6 +395,11 @@ export async function startServices(
388
395
  postgresAuthLimiter({ executor, clock: systemClock, policy }),
389
396
  );
390
397
  started.push(() => resetAuthLimiters());
398
+ // Where spent TOTP steps are kept, for the same reason: a code one replica accepted must be
399
+ // refused by the rest, and `defineAuth` runs when the app's modules import.
400
+ const totpReplay = postgresTotpReplayGuard({ executor });
401
+ configureTotpReplay(() => totpReplay);
402
+ started.push(() => resetTotpReplay());
391
403
  // Where an action's or a query's declared `rateLimit:` is counted — on every surface, so a
392
404
  // task or a one-off command spends from the fleet's table too, not one process' memory. Before
393
405
  // `loadApp` for the auth limiters' reason; `startRoles` stacks a deployment's override over it.
@@ -403,6 +415,7 @@ export async function startServices(
403
415
  installRetentionSweep({
404
416
  idempotency: queue.idempotency,
405
417
  rateLimit: rateLimitStore,
418
+ totpReplay,
406
419
  events,
407
420
  ...jobsRetentionTargets(executor, config),
408
421
  inboxRetention: inboxRetentionOf(config),
@@ -0,0 +1,22 @@
1
+ // Single responsibility: the one boot line for a statement bound that is not the role's. A report,
2
+ // never a refusal: a fleet already running on a pooler that drops the bound must not stop on upgrade.
3
+
4
+ import { logger, renderThrowable, resolveRole } from '@ultimat3/core';
5
+ import type { DbClient, PostgresClient } from '@ultimat3/db';
6
+ import { lostStatementBound, statementBound } from '@ultimat3/db';
7
+
8
+ /** PGlite has no profile and no `options`, and a replica's bound is read by nothing at boot. */
9
+ const isPool = (db: DbClient): db is PostgresClient => 'profile' in db && 'pooler' in db;
10
+
11
+ /** Total: a boot that got past `db.ping()` does not fail on a report about itself. */
12
+ export async function reportStatementBound(db: DbClient): Promise<void> {
13
+ if (!isPool(db)) return;
14
+ try {
15
+ const lost = lostStatementBound(await statementBound(db), resolveRole(), db.pooler);
16
+ if (lost !== undefined) {
17
+ logger.error(lost.format(), { code: lost.code, ...lost.meta });
18
+ }
19
+ } catch (error) {
20
+ logger.debug('db.statement_bound_unread', { error: renderThrowable(error) });
21
+ }
22
+ }
@@ -8,6 +8,7 @@ import type { RequestContext, Route, UltimateRequest } from '@ultimat3/http';
8
8
  import { jsonResponse, NO_STORE } from '@ultimat3/http';
9
9
  import type { Storage } from '@ultimat3/storage';
10
10
  import {
11
+ acceptSignedPart,
11
12
  acceptSignedUpload,
12
13
  DEFAULT_MAX_UPLOAD_BYTES,
13
14
  DEFAULT_SIGNED_URL_BASE,
@@ -61,6 +62,14 @@ async function acceptUpload(
61
62
  const read = await readWithinLimit(request.raw.body, ceiling);
62
63
  if ('over' in read) throw tooLarge(constraints.key, read.over, ceiling);
63
64
 
65
+ // A part URL (`signPart` on a local disk) writes one part into its open session — never the
66
+ // object, which `completeUpload` assembles. The signature says which, so the one PUT route
67
+ // serves both: a second route on the same path could only shadow this one.
68
+ if (constraints.part !== undefined) {
69
+ const part = await acceptSignedPart({ url, disk, orgId, bytes: read.bytes });
70
+ return jsonResponse(part, { status: 200 });
71
+ }
72
+
64
73
  const stored = await acceptSignedUpload({
65
74
  url,
66
75
  disk,