okengine 0.10.2 → 0.11.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 (343) hide show
  1. package/package.json +76 -10
  2. package/site/content/docs/ai/mcp.mdx +0 -1
  3. package/site/content/docs/deployment/docker-swarm.mdx +48 -111
  4. package/site/content/docs/deployment/docker.mdx +43 -36
  5. package/site/content/docs/deployment/index.mdx +9 -9
  6. package/site/content/docs/deployment/kubernetes.mdx +4 -4
  7. package/site/content/docs/elements/ai.mdx +7 -13
  8. package/site/content/docs/elements/channel.mdx +9 -13
  9. package/site/content/docs/elements/clock.mdx +8 -14
  10. package/site/content/docs/elements/flow.mdx +46 -54
  11. package/site/content/docs/elements/gate.mdx +6 -11
  12. package/site/content/docs/elements/signal.mdx +2 -8
  13. package/site/content/docs/elements/store.mdx +79 -93
  14. package/site/content/docs/elements/vault.mdx +89 -76
  15. package/site/content/docs/get-started/basic-usage.mdx +8 -14
  16. package/site/content/docs/get-started/installation.mdx +36 -36
  17. package/site/content/docs/get-started/introduction.mdx +11 -0
  18. package/site/content/docs/get-started/why.mdx +3 -4
  19. package/site/content/docs/index.mdx +0 -1
  20. package/site/content/docs/meta.json +0 -1
  21. package/site/content/docs/plugins/anonymous.mdx +1 -1
  22. package/site/content/docs/plugins/magic-link.mdx +11 -11
  23. package/site/content/docs/plugins/otp.mdx +2 -2
  24. package/site/content/docs/plugins/passkey.mdx +1 -1
  25. package/site/content/docs/plugins/two-factor.mdx +1 -1
  26. package/site/content/docs/plugins/username.mdx +1 -1
  27. package/site/content/docs/recipes/caddy.mdx +1 -1
  28. package/site/content/docs/recipes/cockroachdb.mdx +2 -2
  29. package/site/content/docs/recipes/dragonfly.mdx +1 -1
  30. package/site/content/docs/recipes/index.mdx +1 -2
  31. package/site/content/docs/recipes/llama-cpp.mdx +21 -23
  32. package/site/content/docs/recipes/mailpit.mdx +11 -11
  33. package/site/content/docs/recipes/meilisearch.mdx +10 -10
  34. package/site/content/docs/recipes/meta.json +0 -1
  35. package/site/content/docs/recipes/ollama.mdx +1 -1
  36. package/site/content/docs/recipes/pgdog.mdx +12 -9
  37. package/site/content/docs/recipes/postgres.mdx +3 -3
  38. package/site/content/docs/recipes/redis.mdx +2 -2
  39. package/site/content/docs/recipes/rustfs.mdx +10 -7
  40. package/site/content/docs/recipes/sglang.mdx +1 -1
  41. package/site/content/docs/recipes/supabase-docker.mdx +1 -1
  42. package/site/content/docs/recipes/timescale.mdx +2 -2
  43. package/site/content/docs/recipes/valkey.mdx +1 -1
  44. package/site/content/docs/recipes/vllm.mdx +1 -1
  45. package/site/content/docs/recipes/yugabytedb.mdx +2 -2
  46. package/site/content/docs/reference/cli.md +61 -38
  47. package/site/content/docs/reference/configuration.mdx +145 -70
  48. package/site/content/docs/reference/environment-variables.mdx +36 -28
  49. package/site/content/docs/reference/errors.mdx +28 -29
  50. package/site/content/docs/reference/fx.mdx +14 -7
  51. package/site/content/docs/reference/i18n.mdx +17 -6
  52. package/site/content/docs/reference/index.mdx +5 -0
  53. package/site/content/docs/reference/meta.json +1 -0
  54. package/site/content/docs/reference/migrating-environments.mdx +158 -0
  55. package/site/content/docs/reference/plugins.mdx +2 -2
  56. package/src/auth/gate-auth.test.ts +12 -10
  57. package/src/cli/ai-setup/ai-setup.test.ts +30 -16
  58. package/src/cli/ai-setup/apply.ts +111 -15
  59. package/src/cli/ai-setup/catalog.ts +3 -3
  60. package/src/cli/ai-setup/index.ts +1 -1
  61. package/src/cli/ai-setup/prompts.ts +1 -1
  62. package/src/cli/ai-setup/recommend.test.ts +1 -1
  63. package/src/cli/ask-seed.test.ts +3 -3
  64. package/src/cli/build.ts +8 -9
  65. package/src/cli/db-auto-push.test.ts +6 -3
  66. package/src/cli/db-auto-push.ts +7 -5
  67. package/src/cli/db-seed.ts +7 -8
  68. package/src/cli/db.test.ts +70 -71
  69. package/src/cli/db.ts +86 -12
  70. package/src/cli/dev-controller.test.ts +35 -0
  71. package/src/cli/dev-controller.ts +298 -0
  72. package/src/cli/dev-controls.ts +11 -65
  73. package/src/cli/dev-db-push.test.ts +47 -15
  74. package/src/cli/dev-schema-sync.test.ts +11 -11
  75. package/src/cli/dev-schema-sync.ts +10 -10
  76. package/src/cli/dev-session-lock.test.ts +106 -0
  77. package/src/cli/dev-session-lock.ts +209 -0
  78. package/src/cli/dev.test.ts +246 -114
  79. package/src/cli/dev.ts +153 -205
  80. package/src/cli/doc-staleness.test.ts +3 -6
  81. package/src/cli/docker-clean.test.ts +33 -1
  82. package/src/cli/docker-clean.ts +28 -33
  83. package/src/cli/docker-cli.test.ts +12 -11
  84. package/src/cli/docker.ts +95 -20
  85. package/src/cli/doctor.test.ts +40 -0
  86. package/src/cli/doctor.ts +142 -9
  87. package/src/cli/drizzle-env.test.ts +11 -13
  88. package/src/cli/drizzle-env.ts +18 -11
  89. package/src/cli/ensure-drizzle-config.ts +11 -14
  90. package/src/cli/hero-meta.test.ts +15 -15
  91. package/src/cli/hero-meta.ts +10 -9
  92. package/src/cli/index.ts +36 -6
  93. package/src/cli/load-config.images.test.ts +44 -10
  94. package/src/cli/load-config.source.test.ts +50 -0
  95. package/src/cli/load-config.ts +35 -29
  96. package/src/cli/meilisearch-local.ts +1 -2
  97. package/src/cli/mode.ts +7 -58
  98. package/src/cli/registry.ts +175 -19
  99. package/src/cli/resolve-dev-sql-env.test.ts +8 -16
  100. package/src/cli/resolve-dev-sql-env.ts +13 -15
  101. package/src/cli/safe-defaults.test.ts +29 -11
  102. package/src/cli/schema.ts +65 -16
  103. package/src/cli/test.ts +58 -0
  104. package/src/cli/tui/App.tsx +252 -0
  105. package/src/cli/tui/DevLive.tsx +116 -0
  106. package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
  107. package/src/cli/tui/components/HeaderBar.tsx +67 -0
  108. package/src/cli/tui/components/LogStream.tsx +47 -0
  109. package/src/cli/tui/components/PanelCard.tsx +39 -0
  110. package/src/cli/tui/components/SelectRow.tsx +37 -0
  111. package/src/cli/tui/components/SlashPalette.tsx +172 -0
  112. package/src/cli/tui/components/Spinner.tsx +32 -0
  113. package/src/cli/tui/components/StatusBar.tsx +51 -0
  114. package/src/cli/tui/components/StatusDot.tsx +37 -0
  115. package/src/cli/tui/components/TabBar.tsx +49 -0
  116. package/src/cli/tui/components/Wordmark.tsx +43 -0
  117. package/src/cli/tui/format.ts +38 -0
  118. package/src/cli/tui/keys.test.ts +18 -0
  119. package/src/cli/tui/keys.ts +26 -0
  120. package/src/cli/tui/launch.test.ts +18 -0
  121. package/src/cli/tui/launch.ts +57 -0
  122. package/src/cli/tui/panels/Dashboard.tsx +201 -0
  123. package/src/cli/tui/panels/Database.tsx +198 -0
  124. package/src/cli/tui/panels/Dev.tsx +257 -0
  125. package/src/cli/tui/panels/Docker.tsx +357 -0
  126. package/src/cli/tui/panels/Navigator.test.ts +22 -0
  127. package/src/cli/tui/panels/Navigator.tsx +213 -0
  128. package/src/cli/tui/slash-catalog.test.ts +31 -0
  129. package/src/cli/tui/slash-catalog.ts +184 -0
  130. package/src/cli/tui/slash-run.ts +54 -0
  131. package/src/cli/tui/state/ports.ts +95 -0
  132. package/src/cli/tui/theme.ts +24 -0
  133. package/src/cli/vault-cmd.test.ts +402 -0
  134. package/src/cli/vault-cmd.ts +727 -69
  135. package/src/cli/vault-secure-input.test.ts +56 -0
  136. package/src/cli/vault-secure-input.ts +124 -0
  137. package/src/compiler/effects-infer.ts +7 -0
  138. package/src/compiler/extract.ts +12 -0
  139. package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
  140. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
  141. package/src/compiler/fixtures/skyport.expected.json +1 -1
  142. package/src/compiler/generate-adopt.ts +27 -1
  143. package/src/compiler-entry.ts +7 -0
  144. package/src/config/define-config.test.ts +111 -47
  145. package/src/config/driver-defaults.test.ts +115 -0
  146. package/src/config/driver-defaults.ts +156 -0
  147. package/src/config/index.ts +246 -131
  148. package/src/config/resolve-driver.test.ts +14 -19
  149. package/src/console/server/app.ts +4 -2
  150. package/src/console/server/console.test.ts +58 -1
  151. package/src/console/server/flows.ts +22 -2
  152. package/src/console/server/index.ts +3 -0
  153. package/src/console/server/operator-db.test.ts +55 -13
  154. package/src/console/server/operator-db.ts +305 -263
  155. package/src/console/server/serve.ts +16 -7
  156. package/src/console/server/state.ts +31 -9
  157. package/src/console/server/vault.test.ts +120 -5
  158. package/src/console/server/vault.ts +208 -12
  159. package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
  160. package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
  161. package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
  162. package/src/console/ui/dist/index.html +2 -2
  163. package/src/console/ui/shell/client.ts +16 -0
  164. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
  165. package/src/console/ui/vault/VaultA11yView.tsx +33 -1
  166. package/src/console/ui/vault/backend.test.ts +75 -0
  167. package/src/console/ui/vault/backend.ts +117 -0
  168. package/src/console/ui/vault/fixture.ts +21 -2
  169. package/src/console/ui/vault/index.ts +11 -1
  170. package/src/console/ui/vault/types.ts +26 -0
  171. package/src/docker/compose.ts +164 -61
  172. package/src/docker/derive.ts +106 -12
  173. package/src/docker/docker.test.ts +156 -110
  174. package/src/docker/helpers.ts +98 -0
  175. package/src/docker/images-config.test.ts +163 -0
  176. package/src/docker/index.ts +30 -5
  177. package/src/docker/recipes/index.ts +0 -3
  178. package/src/docker/recipes/llama-cpp.ts +4 -4
  179. package/src/docker/recipes/pgdog.ts +9 -2
  180. package/src/docker/resources.test.ts +71 -0
  181. package/src/docker/resources.ts +176 -0
  182. package/src/docker/stack-id.test.ts +11 -4
  183. package/src/docker/stack-id.ts +42 -32
  184. package/src/docker/stack.integration.test.ts +4 -15
  185. package/src/docker/types.ts +35 -3
  186. package/src/drivers/ai-ollama.integration.test.ts +2 -2
  187. package/src/drivers/conformance.test.ts +11 -12
  188. package/src/drivers/drizzle-dialect.test.ts +0 -4
  189. package/src/drivers/drizzle-dialect.ts +7 -13
  190. package/src/drivers/index.ts +24 -7
  191. package/src/drivers/journal-postgres.test.ts +3 -1
  192. package/src/drivers/journal-postgres.ts +4 -2
  193. package/src/drivers/meilisearch.ts +1 -1
  194. package/src/drivers/pglite.ts +28 -5
  195. package/src/drivers/types.ts +9 -11
  196. package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
  197. package/src/drivers/vault-aws-secrets-manager.ts +333 -0
  198. package/src/drivers/vault-builtin.test.ts +110 -0
  199. package/src/drivers/vault-builtin.ts +184 -0
  200. package/src/drivers/vault-driver-removal.test.ts +13 -0
  201. package/src/drivers/vault-managed.test.ts +95 -0
  202. package/src/drivers/vault-managed.ts +101 -25
  203. package/src/drivers/vault-types.ts +25 -5
  204. package/src/elements/ai/runtime.ts +1 -1
  205. package/src/elements/clock/chaos.test.ts +4 -1
  206. package/src/elements/clock/durable.ts +57 -4
  207. package/src/elements/clock.test.ts +13 -14
  208. package/src/elements/gate/constants.ts +18 -0
  209. package/src/elements/gate.test.ts +1 -1
  210. package/src/elements/gate.ts +2 -2
  211. package/src/elements/store/domain-ddl.test.ts +5 -12
  212. package/src/elements/store/emit-drizzle.ts +44 -19
  213. package/src/elements/store/index-boot.test.ts +15 -116
  214. package/src/elements/store/load-plugin-tables.ts +1 -1
  215. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  216. package/src/elements/store/resource-list-docs.test.ts +64 -44
  217. package/src/elements/store/resource.test.ts +17 -10
  218. package/src/elements/store/runtime.ts +9 -12
  219. package/src/elements/store/schema-decl.test.ts +16 -32
  220. package/src/elements/store/seed.test.ts +1 -2
  221. package/src/elements/store/seed.ts +7 -8
  222. package/src/elements/store/sql-condition.test.ts +2 -2
  223. package/src/elements/store/sql-session.test.ts +8 -10
  224. package/src/elements/store/sql-session.ts +10 -2
  225. package/src/elements/store.test.ts +13 -13
  226. package/src/elements/vault/audit.ts +233 -0
  227. package/src/elements/vault/boot-chain.test.ts +78 -0
  228. package/src/elements/vault/boot-chain.ts +43 -39
  229. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  230. package/src/elements/vault/builtin-adapter.ts +1184 -0
  231. package/src/elements/vault/crypto.test.ts +237 -0
  232. package/src/elements/vault/crypto.ts +431 -0
  233. package/src/elements/vault/declare.ts +164 -3
  234. package/src/elements/vault/errors.ts +56 -0
  235. package/src/elements/vault/kms-unseal.test.ts +76 -0
  236. package/src/elements/vault/kms-unseal.ts +181 -0
  237. package/src/elements/vault/path.ts +90 -0
  238. package/src/elements/vault/resilience.test.ts +102 -0
  239. package/src/elements/vault/resilience.ts +107 -0
  240. package/src/elements/vault/runtime.ts +36 -4
  241. package/src/elements/vault/security-checklist.test.ts +130 -0
  242. package/src/elements/vault/storage.ts +448 -0
  243. package/src/elements/vault/types.ts +288 -0
  244. package/src/elements/vault/unseal.ts +150 -0
  245. package/src/elements/vault.test.ts +72 -4
  246. package/src/elements/vault.ts +91 -2
  247. package/src/full.ts +248 -0
  248. package/src/http.ts +41 -0
  249. package/src/i18n/interpolate-simple.ts +37 -0
  250. package/src/i18n/messages.ts +51 -4
  251. package/src/i18n-entry.ts +7 -0
  252. package/src/index.ts +35 -145
  253. package/src/journal-entry.ts +20 -0
  254. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  255. package/src/kernel/app.ts +40 -42
  256. package/src/kernel/auto-registry.test.ts +2 -2
  257. package/src/kernel/boot-bind/ai.test.ts +5 -5
  258. package/src/kernel/boot-bind/ai.ts +2 -1
  259. package/src/kernel/boot-bind/channel.test.ts +6 -6
  260. package/src/kernel/boot-bind/channel.ts +5 -4
  261. package/src/kernel/boot-bind/clock.ts +5 -5
  262. package/src/kernel/boot-bind/gate.ts +6 -7
  263. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  264. package/src/kernel/boot-bind/journal.ts +5 -5
  265. package/src/kernel/boot-bind/signal.ts +3 -1
  266. package/src/kernel/boot-bind/store.test.ts +55 -37
  267. package/src/kernel/boot-bind/store.ts +24 -34
  268. package/src/kernel/boot-bind/vault.ts +19 -4
  269. package/src/kernel/boot.test.ts +12 -4
  270. package/src/kernel/boot.ts +31 -19
  271. package/src/kernel/budget-entry.ts +1 -1
  272. package/src/kernel/compensate.test.ts +288 -1
  273. package/src/kernel/compensate.ts +126 -0
  274. package/src/kernel/concurrency.test.ts +10 -9
  275. package/src/kernel/effects-stamping.test.ts +27 -2
  276. package/src/kernel/element-registries.ts +5 -3
  277. package/src/kernel/flow.ts +4 -2
  278. package/src/kernel/fx.test.ts +119 -3
  279. package/src/kernel/fx.ts +215 -40
  280. package/src/kernel/index.ts +14 -0
  281. package/src/kernel/journal.ts +130 -7
  282. package/src/kernel/pipeline.test.ts +20 -6
  283. package/src/kernel/redacted.test.ts +35 -0
  284. package/src/kernel/redacted.ts +24 -2
  285. package/src/kernel/router/create-default.ts +16 -0
  286. package/src/kernel/router/create-edge.ts +18 -0
  287. package/src/kernel/router/create.ts +23 -0
  288. package/src/kernel/router/index.ts +23 -0
  289. package/src/kernel/router/linear.ts +88 -0
  290. package/src/kernel/router/regexp.ts +188 -0
  291. package/src/kernel/router/smart.ts +120 -0
  292. package/src/kernel/router/trie.ts +108 -0
  293. package/src/kernel/router/types.ts +56 -0
  294. package/src/kernel/router.ts +18 -532
  295. package/src/kernel-entry.ts +43 -0
  296. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  297. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  298. package/src/plugins/config-source.test.ts +7 -7
  299. package/src/release/build-lib.ts +58 -0
  300. package/src/release/exports.test.ts +4 -2
  301. package/src/release/exports.ts +30 -4
  302. package/src/release/measure.ts +101 -22
  303. package/src/runs/duckdb.ts +7 -2
  304. package/src/shared/optional-peer.ts +41 -0
  305. package/src/term.test.ts +3 -44
  306. package/src/test/create-test-app.ts +12 -0
  307. package/src/test/provisions.integration.test.ts +1 -1
  308. package/src/test/reset-element-registries.ts +2 -0
  309. package/src/upgrade/codemods.test.ts +7 -20
  310. package/src/upgrade/codemods.ts +93 -60
  311. package/site/content/docs/console/access.mdx +0 -29
  312. package/site/content/docs/console/ai.mdx +0 -35
  313. package/site/content/docs/console/architecture.mdx +0 -35
  314. package/site/content/docs/console/channels.mdx +0 -37
  315. package/site/content/docs/console/clock.mdx +0 -51
  316. package/site/content/docs/console/flows.mdx +0 -31
  317. package/site/content/docs/console/gates.mdx +0 -73
  318. package/site/content/docs/console/index.mdx +0 -54
  319. package/site/content/docs/console/manifest-diff.mdx +0 -34
  320. package/site/content/docs/console/meta.json +0 -24
  321. package/site/content/docs/console/overview.mdx +0 -40
  322. package/site/content/docs/console/plugins.mdx +0 -41
  323. package/site/content/docs/console/privacy.mdx +0 -32
  324. package/site/content/docs/console/runs.mdx +0 -50
  325. package/site/content/docs/console/signals.mdx +0 -40
  326. package/site/content/docs/console/store.mdx +0 -32
  327. package/site/content/docs/console/tenancy.mdx +0 -32
  328. package/site/content/docs/console/traces.mdx +0 -38
  329. package/site/content/docs/console/vault.mdx +0 -41
  330. package/site/content/docs/recipes/openbao.mdx +0 -141
  331. package/src/cli/ask-dev-mode.ts +0 -40
  332. package/src/cli/dev-mode.test.ts +0 -45
  333. package/src/cli/dev-mode.ts +0 -78
  334. package/src/cli/openbao-bootstrap.test.ts +0 -150
  335. package/src/cli/openbao-bootstrap.ts +0 -321
  336. package/src/cli/openbao-restart.integration.test.ts +0 -145
  337. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  338. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  339. package/src/docker/recipes/openbao.ts +0 -47
  340. package/src/drivers/libsql.ts +0 -179
  341. package/src/drivers/sqlite.ts +0 -44
  342. package/src/drivers/vault-openbao.test.ts +0 -97
  343. package/src/drivers/vault-openbao.ts +0 -143
@@ -14,7 +14,49 @@ import { JournalSuspend } from "./journal-suspend.ts";
14
14
  export { JournalSuspend, isJournalSuspend } from "./journal-suspend.ts";
15
15
 
16
16
  /** Status of a durable run. */
17
- export type JournalRunStatus = "running" | "sleeping" | "completed" | "failed";
17
+ export type JournalRunStatus = "running" | "sleeping" | "compensating" | "completed" | "failed";
18
+
19
+ /** Reserved prefix for compensation undo steps — never use for forward work. */
20
+ export const JOURNAL_UNDO_PREFIX = "undo:";
21
+
22
+ /**
23
+ * Options for {@link JournalSession.step}.
24
+ *
25
+ * @typeParam T - Step result type
26
+ */
27
+ export interface JournalStepOptions<T> {
28
+ /**
29
+ * Durable undo for this step — registered on persist and replay.
30
+ * Invoked with the journaled value on terminal failure (LIFO).
31
+ */
32
+ readonly undo?: (value: T) => unknown | Promise<unknown>;
33
+ }
34
+
35
+ /** One registered per-step undo frame (in-memory; closures re-bound on resume). */
36
+ export interface JournalUndoFrame {
37
+ /** Forward step name (without {@link JOURNAL_UNDO_PREFIX}). */
38
+ readonly name: string;
39
+ /** Journaled step value passed to {@link undo}. */
40
+ readonly value: unknown;
41
+ /** Undo body. */
42
+ readonly undo: (value: unknown) => unknown | Promise<unknown>;
43
+ }
44
+
45
+ /**
46
+ * Thrown during a compensating registration pass when `do` would execute
47
+ * new forward work — stops re-entry without continuing the happy path.
48
+ */
49
+ export class JournalRegistrationComplete extends Error {
50
+ constructor() {
51
+ super("journal: registration pass complete");
52
+ this.name = "JournalRegistrationComplete";
53
+ }
54
+ }
55
+
56
+ /** Type guard for {@link JournalRegistrationComplete}. */
57
+ export function isJournalRegistrationComplete(err: unknown): err is JournalRegistrationComplete {
58
+ return err instanceof JournalRegistrationComplete;
59
+ }
18
60
 
19
61
  /** A completed named step. */
20
62
  export interface JournalStepEntry {
@@ -98,8 +140,8 @@ export interface JournalLeaseStore {
98
140
  */
99
141
  claimDueSleep(instanceId: string, now: number, leaseMs: number): Promise<JournalRun | undefined>;
100
142
  /**
101
- * Boot-time orphan discovery: `running` / `sleeping` runs with no live lease
102
- * (crashed holder or never claimed). Rows are never deleted.
143
+ * Boot-time orphan discovery: `running` / `sleeping` / `compensating` runs
144
+ * with no live lease (crashed holder or never claimed). Rows are never deleted.
103
145
  *
104
146
  * @param now - Epoch-ms
105
147
  */
@@ -212,7 +254,11 @@ function leaseMethods(
212
254
  async listOrphans(now) {
213
255
  const map = await load();
214
256
  return [...map.values()]
215
- .filter((r) => (r.status === "running" || r.status === "sleeping") && !hasLiveLease(r, now))
257
+ .filter(
258
+ (r) =>
259
+ (r.status === "running" || r.status === "sleeping" || r.status === "compensating") &&
260
+ !hasLiveLease(r, now),
261
+ )
216
262
  .map(cloneRun);
217
263
  },
218
264
  };
@@ -325,8 +371,9 @@ export interface JournalSession {
325
371
  *
326
372
  * @param name - Step name
327
373
  * @param fn - Step body
374
+ * @param opts - Optional per-step undo registration
328
375
  */
329
- step<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
376
+ step<T>(name: string, fn: () => T | Promise<T>, opts?: JournalStepOptions<T>): Promise<T>;
330
377
  /**
331
378
  * Durable sleep — journals wake time; suspends until elapsed.
332
379
  *
@@ -346,9 +393,25 @@ export interface JournalSession {
346
393
  /**
347
394
  * Rewind the replay cursor to the start of the entry list.
348
395
  * Used by flow-level retry so a re-entered `do` replays completed steps
349
- * instead of treating the cursor as past them.
396
+ * instead of treating the cursor as past them. Clears the in-memory undo stack
397
+ * so frames re-bind on the next walk.
350
398
  */
351
399
  rewind(): void;
400
+ /** Registered per-step undos in persist/replay order (LIFO compensate). */
401
+ undoStack(): readonly JournalUndoFrame[];
402
+ /**
403
+ * Enter registration-only mode — replay + re-bind undos; new forward work
404
+ * throws {@link JournalRegistrationComplete}.
405
+ */
406
+ beginRegistrationPass(): void;
407
+ /** Exit registration-only mode. */
408
+ endRegistrationPass(): void;
409
+ /**
410
+ * Allow appending `undo:*` steps (compensation phase only).
411
+ *
412
+ * @param allowed - Whether undo-prefix steps may append
413
+ */
414
+ setUndoExecution(allowed: boolean): void;
352
415
  /** Persist current run status / output. */
353
416
  commit(
354
417
  status: JournalRunStatus,
@@ -389,6 +452,9 @@ export function createJournal(options: CreateJournalOptions): Journal {
389
452
  /** Next entry index to consume on replay. */
390
453
  let cursor = 0;
391
454
  let leaseHeld = leased;
455
+ let registrationPass = false;
456
+ let undoExecution = false;
457
+ const undos: JournalUndoFrame[] = [];
392
458
 
393
459
  async function persist(): Promise<void> {
394
460
  run.updatedAt = now();
@@ -407,18 +473,53 @@ export function createJournal(options: CreateJournalOptions): Journal {
407
473
  delete run.leaseExpiresAt;
408
474
  }
409
475
 
476
+ function registerUndo<T>(name: string, value: T, opts?: JournalStepOptions<T>): void {
477
+ if (!opts?.undo) return;
478
+ const undo = opts.undo as (value: unknown) => unknown | Promise<unknown>;
479
+ const idx = undos.findIndex((f) => f.name === name);
480
+ const frame: JournalUndoFrame = { name, value, undo };
481
+ if (idx >= 0) undos[idx] = frame;
482
+ else undos.push(frame);
483
+ }
484
+
485
+ function assertCanAppendStep(name: string): void {
486
+ if (registrationPass) {
487
+ throw new JournalRegistrationComplete();
488
+ }
489
+ const isUndoStep = name.startsWith(JOURNAL_UNDO_PREFIX);
490
+ if (isUndoStep && !undoExecution) {
491
+ throw new Error(
492
+ `journal: step name "${name}" uses reserved prefix "${JOURNAL_UNDO_PREFIX}"`,
493
+ );
494
+ }
495
+ if (!isUndoStep && run.entries.some((e) => e.kind === "step" && e.name === name)) {
496
+ throw new Error(`journal: duplicate step name "${name}"`);
497
+ }
498
+ }
499
+
410
500
  return {
411
501
  runId: run.id,
412
502
  run,
413
- async step<T>(name: string, fn: () => T | Promise<T>): Promise<T> {
503
+ async step<T>(
504
+ name: string,
505
+ fn: () => T | Promise<T>,
506
+ opts?: JournalStepOptions<T>,
507
+ ): Promise<T> {
508
+ if (name.startsWith(JOURNAL_UNDO_PREFIX) && opts?.undo) {
509
+ throw new Error("journal: undo steps cannot register nested undo");
510
+ }
414
511
  // Prefer name match among remaining entries (resume after crash).
415
512
  for (let i = cursor; i < run.entries.length; i++) {
416
513
  const e = run.entries[i]!;
417
514
  if (e.kind === "step" && e.name === name) {
418
515
  cursor = i + 1;
516
+ if (!name.startsWith(JOURNAL_UNDO_PREFIX)) {
517
+ registerUndo(name, e.value as T, opts);
518
+ }
419
519
  return e.value as T;
420
520
  }
421
521
  }
522
+ assertCanAppendStep(name);
422
523
  const value = await fn();
423
524
  const entry: JournalStepEntry = {
424
525
  kind: "step",
@@ -429,6 +530,9 @@ export function createJournal(options: CreateJournalOptions): Journal {
429
530
  run.entries.push(entry);
430
531
  cursor = run.entries.length;
431
532
  await persist();
533
+ if (!name.startsWith(JOURNAL_UNDO_PREFIX)) {
534
+ registerUndo(name, value, opts);
535
+ }
432
536
  return value;
433
537
  },
434
538
  async sleep(label, duration, parseMs) {
@@ -446,6 +550,9 @@ export function createJournal(options: CreateJournalOptions): Journal {
446
550
  return;
447
551
  }
448
552
  }
553
+ if (registrationPass) {
554
+ throw new JournalRegistrationComplete();
555
+ }
449
556
  const wakeAt = now() + parseMs(duration);
450
557
  const entry: JournalSleepEntry = {
451
558
  kind: "sleep",
@@ -477,6 +584,9 @@ export function createJournal(options: CreateJournalOptions): Journal {
477
584
  return e.value as T;
478
585
  }
479
586
  }
587
+ if (registrationPass) {
588
+ throw new JournalRegistrationComplete();
589
+ }
480
590
  const value = await execute();
481
591
  const entry: JournalEffectEntry = {
482
592
  kind: "effect",
@@ -492,6 +602,19 @@ export function createJournal(options: CreateJournalOptions): Journal {
492
602
  },
493
603
  rewind() {
494
604
  cursor = 0;
605
+ undos.length = 0;
606
+ },
607
+ undoStack() {
608
+ return undos;
609
+ },
610
+ beginRegistrationPass() {
611
+ registrationPass = true;
612
+ },
613
+ endRegistrationPass() {
614
+ registrationPass = false;
615
+ },
616
+ setUndoExecution(allowed) {
617
+ undoExecution = allowed;
495
618
  },
496
619
  async commit(status, patch) {
497
620
  run.status = status;
@@ -212,10 +212,10 @@ describe("pipeline — Bearer cryptographic verification", () => {
212
212
  auth: { secret: "hmac-secret-for-tests", sessions, http: false },
213
213
  policies: [member],
214
214
  },
215
- env: "local",
215
+ env: "test",
216
216
  startScheduler: false,
217
217
  });
218
- await app.boot({ env: "local", gates: [member], startScheduler: false });
218
+ await app.boot({ env: "test", gates: [member], startScheduler: false });
219
219
 
220
220
  const res = await app.fetch(
221
221
  new Request("http://localhost/secure", {
@@ -267,12 +267,12 @@ describe("pipeline — Bearer cryptographic verification", () => {
267
267
  auth: { secret, sessions, now: () => nowMs, http: false },
268
268
  policies: [member],
269
269
  },
270
- env: "local",
270
+ env: "test",
271
271
  elements: { clock: clockRt },
272
272
  startScheduler: false,
273
273
  });
274
274
  await app.boot({
275
- env: "local",
275
+ env: "test",
276
276
  gates: [member],
277
277
  startScheduler: false,
278
278
  elements: { clock: clockRt },
@@ -317,10 +317,24 @@ describe("pipeline — Bearer cryptographic verification", () => {
317
317
  const app = oke({
318
318
  name: "no-inject",
319
319
  gate: { policies: [member] },
320
- env: "local",
320
+ env: "dev",
321
+ config: {
322
+ drivers: {
323
+ store: { kv: { dev: "memory", test: "memory", prod: "memory" } },
324
+ signal: { dev: "memory", test: "memory", prod: "memory" },
325
+ clock: { dev: "memory", test: "frozen", prod: "memory" },
326
+ journal: { dev: "memory", test: "memory", prod: "memory" },
327
+ channel: { email: { dev: "console", test: "console", prod: "console" } },
328
+ },
329
+ },
330
+ startScheduler: false,
331
+ });
332
+ await app.boot({
333
+ env: "dev",
334
+ gates: [member],
321
335
  startScheduler: false,
336
+ config: app.$options.config,
322
337
  });
323
- await app.boot({ env: "local", gates: [member], startScheduler: false });
324
338
 
325
339
  const result = await app.execute(
326
340
  app.flow("orders.inject")!,
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Unit tests for {@link Redacted} serialization / inspect hygiene.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { inspect } from "node:util";
7
+ import { REDACTED_PLACEHOLDER, Redacted } from "./redacted.ts";
8
+
9
+ describe("Redacted", () => {
10
+ test("does not leak in JSON.stringify", () => {
11
+ const r = Redacted.of("secret");
12
+ expect(JSON.stringify({ r })).toBe(`{"r":"${REDACTED_PLACEHOLDER}"}`);
13
+ });
14
+
15
+ test("does not leak in string coercion", () => {
16
+ const r = Redacted.of("secret");
17
+ expect(r.toString()).toBe(REDACTED_PLACEHOLDER);
18
+ expect(r.valueOf()).toBe(REDACTED_PLACEHOLDER);
19
+ expect(String(r)).toBe(REDACTED_PLACEHOLDER);
20
+ expect(`${r}`).toBe(REDACTED_PLACEHOLDER);
21
+ });
22
+
23
+ test("does not leak in util.inspect", () => {
24
+ const r = Redacted.of("secret");
25
+ const inspected = inspect(r);
26
+ expect(inspected).toContain("Redacted");
27
+ expect(inspected).not.toContain("secret");
28
+ });
29
+
30
+ test("reveal and map keep the cleartext only behind reveal", () => {
31
+ const r = Redacted.of("secret").map((v) => v.toUpperCase());
32
+ expect(r.reveal()).toBe("SECRET");
33
+ expect(JSON.stringify(r)).toBe(`"${REDACTED_PLACEHOLDER}"`);
34
+ });
35
+ });
@@ -20,11 +20,29 @@ export class Redacted<T> {
20
20
  this.#value = value;
21
21
  }
22
22
 
23
+ /**
24
+ * Construct a {@link Redacted} without calling `new`.
25
+ *
26
+ * @param value - Wrapped secret
27
+ */
28
+ static of<T>(value: T): Redacted<T> {
29
+ return new Redacted(value);
30
+ }
31
+
23
32
  /** The one explicit way to get the real value. */
24
33
  reveal(): T {
25
34
  return this.#value;
26
35
  }
27
36
 
37
+ /**
38
+ * Transform the wrapped value without exposing it to callers.
39
+ *
40
+ * @param fn - Mapper over the cleartext
41
+ */
42
+ map<U>(fn: (value: T) => U): Redacted<U> {
43
+ return Redacted.of(fn(this.#value));
44
+ }
45
+
28
46
  toString(): string {
29
47
  return REDACTED_PLACEHOLDER;
30
48
  }
@@ -33,13 +51,17 @@ export class Redacted<T> {
33
51
  return REDACTED_PLACEHOLDER;
34
52
  }
35
53
 
54
+ valueOf(): string {
55
+ return REDACTED_PLACEHOLDER;
56
+ }
57
+
36
58
  [Symbol.toPrimitive](): string {
37
59
  return REDACTED_PLACEHOLDER;
38
60
  }
39
61
 
40
- /** `util.inspect` / `console.log` — never the real value. */
62
+ /** `util.inspect` / `console.log` / Bun — never the real value. */
41
63
  [Symbol.for("nodejs.util.inspect.custom")](): string {
42
- return REDACTED_PLACEHOLDER;
64
+ return `Redacted<${typeof this.#value}>`;
43
65
  }
44
66
  }
45
67
 
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Default long-lived server router preset — RegExp → Trie.
3
+ */
4
+
5
+ import { RegExpRouter } from "./regexp.ts";
6
+ import { SmartRouter } from "./smart.ts";
7
+ import { TrieRouter } from "./trie.ts";
8
+
9
+ /**
10
+ * Create the default SmartRouter (RegExp → Trie) for long-lived servers.
11
+ */
12
+ export function createDefaultRouter<T>(): SmartRouter<T> {
13
+ return new SmartRouter<T>({
14
+ factories: [() => new RegExpRouter<T>(), () => new TrieRouter<T>()],
15
+ });
16
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Edge / cold-start router preset — Linear → Trie (no RegExp compile).
3
+ */
4
+
5
+ import { LinearRouter } from "./linear.ts";
6
+ import { SmartRouter } from "./smart.ts";
7
+ import { TrieRouter } from "./trie.ts";
8
+
9
+ /**
10
+ * Create an edge-oriented SmartRouter (Linear → Trie).
11
+ *
12
+ * Does not import `regexp.ts`, so edge bundles can tree-shake RegExpRouter.
13
+ */
14
+ export function createEdgeRouter<T>(): SmartRouter<T> {
15
+ return new SmartRouter<T>({
16
+ factories: [() => new LinearRouter<T>(), () => new TrieRouter<T>()],
17
+ });
18
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Preset switch for {@link createRouter}.
3
+ */
4
+
5
+ import { createDefaultRouter } from "./create-default.ts";
6
+ import { createEdgeRouter } from "./create-edge.ts";
7
+ import type { SmartRouter } from "./smart.ts";
8
+ import type { RouterPreset } from "./types.ts";
9
+
10
+ /**
11
+ * Create a router for the given preset.
12
+ *
13
+ * - `default` — SmartRouter(RegExp → Trie), long-lived servers
14
+ * - `edge` — SmartRouter(Linear → Trie), cold-start / one-shot isolates
15
+ *
16
+ * @param preset - Preset name
17
+ */
18
+ export function createRouter<T>(preset: RouterPreset = "default"): SmartRouter<T> {
19
+ if (preset === "edge") {
20
+ return createEdgeRouter<T>();
21
+ }
22
+ return createDefaultRouter<T>();
23
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * HTTP router — compiled RegExp matching with a Trie fallback, plus a linear
3
+ * preset for cold-start edge builds.
4
+ *
5
+ * Chosen at build/startup: try RegExp first; on unsupported patterns fall
6
+ * back to Trie. The `edge` preset uses LinearRouter for fast registration.
7
+ */
8
+
9
+ export { createRouter } from "./create.ts";
10
+ export { createDefaultRouter } from "./create-default.ts";
11
+ export { createEdgeRouter } from "./create-edge.ts";
12
+ export { LinearRouter } from "./linear.ts";
13
+ export { RegExpRouter } from "./regexp.ts";
14
+ export { SmartRouter } from "./smart.ts";
15
+ export { TrieRouter } from "./trie.ts";
16
+ export {
17
+ UnsupportedPathError,
18
+ isUnsupportedByRegExp,
19
+ type RouteMatch,
20
+ type Router,
21
+ type RouterFactory,
22
+ type RouterPreset,
23
+ } from "./types.ts";
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Linear router — fast registration, O(n) string-scan match.
3
+ * Edge / cold-start preset (no compile phase).
4
+ */
5
+
6
+ import type { RouteMatch, Router } from "./types.ts";
7
+
8
+ interface LinearRoute<T> {
9
+ readonly method: string;
10
+ readonly path: string;
11
+ readonly value: T;
12
+ readonly parts: readonly string[];
13
+ }
14
+
15
+ /**
16
+ * Linear router — fast registration, O(n) string-scan match.
17
+ * Edge / cold-start preset (no compile phase).
18
+ */
19
+ export class LinearRouter<T> implements Router<T> {
20
+ readonly name = "LinearRouter";
21
+ readonly #routes: LinearRoute<T>[] = [];
22
+
23
+ /**
24
+ * @param method - HTTP method
25
+ * @param path - Path pattern
26
+ * @param value - Route value
27
+ */
28
+ add(method: string, path: string, value: T): void {
29
+ const parts = path.startsWith("/") ? path.split("/").slice(1) : path.split("/");
30
+ this.#routes.push({ method, path, value, parts });
31
+ }
32
+
33
+ /**
34
+ * @param method - HTTP method
35
+ * @param path - Pathname
36
+ */
37
+ match(method: string, path: string): RouteMatch<T> | undefined {
38
+ const pathParts = path.startsWith("/") ? path.split("/").slice(1) : path.split("/");
39
+ if (pathParts.length > 0 && pathParts[pathParts.length - 1] === "") {
40
+ pathParts.pop();
41
+ }
42
+
43
+ for (const route of this.#routes) {
44
+ if (route.method !== method) continue;
45
+
46
+ if (!route.path.includes(":") && !route.path.includes("*")) {
47
+ if (route.path === path || `${route.path}/` === path) {
48
+ return { value: route.value, params: {} };
49
+ }
50
+ continue;
51
+ }
52
+
53
+ const params: Record<string, string> = {};
54
+ let pi = 0;
55
+ let matched = true;
56
+
57
+ for (let ri = 0; ri < route.parts.length; ri++) {
58
+ const part = route.parts[ri] ?? "";
59
+ if (part === "*") {
60
+ params["*"] = pathParts.slice(pi).join("/");
61
+ pi = pathParts.length;
62
+ break;
63
+ }
64
+ if (pi >= pathParts.length) {
65
+ matched = false;
66
+ break;
67
+ }
68
+ const got = pathParts[pi] ?? "";
69
+ if (part.startsWith(":") && part.length > 1) {
70
+ const name = part.endsWith("?") ? part.slice(1, -1) : part.slice(1);
71
+ params[name] = got;
72
+ pi += 1;
73
+ continue;
74
+ }
75
+ if (part !== got) {
76
+ matched = false;
77
+ break;
78
+ }
79
+ pi += 1;
80
+ }
81
+
82
+ if (matched && pi === pathParts.length) {
83
+ return { value: route.value, params };
84
+ }
85
+ }
86
+ return undefined;
87
+ }
88
+ }