okengine 0.10.3 → 0.11.1

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 (323) hide show
  1. package/package.json +76 -10
  2. package/site/content/docs/deployment/docker-swarm.mdx +48 -111
  3. package/site/content/docs/deployment/docker.mdx +43 -36
  4. package/site/content/docs/deployment/index.mdx +9 -9
  5. package/site/content/docs/deployment/kubernetes.mdx +4 -4
  6. package/site/content/docs/elements/ai.mdx +7 -7
  7. package/site/content/docs/elements/channel.mdx +9 -7
  8. package/site/content/docs/elements/clock.mdx +8 -8
  9. package/site/content/docs/elements/flow.mdx +46 -47
  10. package/site/content/docs/elements/gate.mdx +6 -5
  11. package/site/content/docs/elements/signal.mdx +2 -2
  12. package/site/content/docs/elements/store.mdx +71 -78
  13. package/site/content/docs/elements/vault.mdx +89 -70
  14. package/site/content/docs/get-started/basic-usage.mdx +8 -8
  15. package/site/content/docs/get-started/installation.mdx +36 -36
  16. package/site/content/docs/get-started/introduction.mdx +11 -0
  17. package/site/content/docs/get-started/why.mdx +2 -2
  18. package/site/content/docs/plugins/anonymous.mdx +1 -1
  19. package/site/content/docs/plugins/magic-link.mdx +11 -11
  20. package/site/content/docs/plugins/otp.mdx +2 -2
  21. package/site/content/docs/plugins/passkey.mdx +1 -1
  22. package/site/content/docs/plugins/two-factor.mdx +1 -1
  23. package/site/content/docs/plugins/username.mdx +1 -1
  24. package/site/content/docs/recipes/caddy.mdx +1 -1
  25. package/site/content/docs/recipes/cockroachdb.mdx +2 -2
  26. package/site/content/docs/recipes/dragonfly.mdx +1 -1
  27. package/site/content/docs/recipes/index.mdx +1 -2
  28. package/site/content/docs/recipes/llama-cpp.mdx +21 -23
  29. package/site/content/docs/recipes/mailpit.mdx +11 -11
  30. package/site/content/docs/recipes/meilisearch.mdx +10 -10
  31. package/site/content/docs/recipes/meta.json +0 -1
  32. package/site/content/docs/recipes/ollama.mdx +1 -1
  33. package/site/content/docs/recipes/pgdog.mdx +12 -9
  34. package/site/content/docs/recipes/postgres.mdx +3 -3
  35. package/site/content/docs/recipes/redis.mdx +2 -2
  36. package/site/content/docs/recipes/rustfs.mdx +10 -7
  37. package/site/content/docs/recipes/sglang.mdx +1 -1
  38. package/site/content/docs/recipes/supabase-docker.mdx +1 -1
  39. package/site/content/docs/recipes/timescale.mdx +2 -2
  40. package/site/content/docs/recipes/valkey.mdx +1 -1
  41. package/site/content/docs/recipes/vllm.mdx +1 -1
  42. package/site/content/docs/recipes/yugabytedb.mdx +2 -2
  43. package/site/content/docs/reference/cli.md +61 -38
  44. package/site/content/docs/reference/configuration.mdx +145 -70
  45. package/site/content/docs/reference/environment-variables.mdx +36 -28
  46. package/site/content/docs/reference/errors.mdx +28 -29
  47. package/site/content/docs/reference/fx.mdx +11 -4
  48. package/site/content/docs/reference/i18n.mdx +17 -6
  49. package/site/content/docs/reference/index.mdx +5 -0
  50. package/site/content/docs/reference/meta.json +1 -0
  51. package/site/content/docs/reference/migrating-environments.mdx +158 -0
  52. package/site/content/docs/reference/plugins.mdx +2 -2
  53. package/src/auth/gate-auth.test.ts +12 -10
  54. package/src/cli/ai-setup/ai-setup.test.ts +27 -15
  55. package/src/cli/ai-setup/apply.ts +111 -15
  56. package/src/cli/ai-setup/catalog.ts +3 -3
  57. package/src/cli/ai-setup/index.ts +1 -1
  58. package/src/cli/ai-setup/prompts.ts +1 -1
  59. package/src/cli/ai-setup/recommend.test.ts +1 -1
  60. package/src/cli/ask-seed.test.ts +3 -3
  61. package/src/cli/build.ts +1 -1
  62. package/src/cli/db-auto-push.test.ts +6 -3
  63. package/src/cli/db-auto-push.ts +7 -5
  64. package/src/cli/db-seed.ts +7 -8
  65. package/src/cli/db.test.ts +70 -71
  66. package/src/cli/db.ts +86 -12
  67. package/src/cli/dev-controller.test.ts +35 -0
  68. package/src/cli/dev-controller.ts +298 -0
  69. package/src/cli/dev-controls.ts +11 -65
  70. package/src/cli/dev-db-push.test.ts +47 -15
  71. package/src/cli/dev-schema-sync.test.ts +11 -11
  72. package/src/cli/dev-schema-sync.ts +10 -10
  73. package/src/cli/dev-session-lock.test.ts +106 -0
  74. package/src/cli/dev-session-lock.ts +209 -0
  75. package/src/cli/dev.test.ts +166 -121
  76. package/src/cli/dev.ts +138 -192
  77. package/src/cli/doc-staleness.test.ts +3 -6
  78. package/src/cli/docker-clean.test.ts +33 -1
  79. package/src/cli/docker-clean.ts +28 -33
  80. package/src/cli/docker-cli.test.ts +12 -11
  81. package/src/cli/docker.ts +95 -20
  82. package/src/cli/doctor.test.ts +40 -0
  83. package/src/cli/doctor.ts +55 -15
  84. package/src/cli/drizzle-env.test.ts +11 -13
  85. package/src/cli/drizzle-env.ts +18 -11
  86. package/src/cli/ensure-drizzle-config.ts +11 -14
  87. package/src/cli/hero-meta.test.ts +15 -15
  88. package/src/cli/hero-meta.ts +10 -9
  89. package/src/cli/index.ts +36 -6
  90. package/src/cli/load-config.images.test.ts +16 -12
  91. package/src/cli/load-config.source.test.ts +50 -0
  92. package/src/cli/load-config.ts +34 -28
  93. package/src/cli/meilisearch-local.ts +1 -2
  94. package/src/cli/mode.ts +7 -58
  95. package/src/cli/registry.ts +175 -19
  96. package/src/cli/resolve-dev-sql-env.test.ts +8 -16
  97. package/src/cli/resolve-dev-sql-env.ts +13 -15
  98. package/src/cli/safe-defaults.test.ts +28 -10
  99. package/src/cli/schema.ts +65 -16
  100. package/src/cli/test.ts +58 -0
  101. package/src/cli/tui/App.tsx +252 -0
  102. package/src/cli/tui/DevLive.tsx +116 -0
  103. package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
  104. package/src/cli/tui/components/HeaderBar.tsx +67 -0
  105. package/src/cli/tui/components/LogStream.tsx +47 -0
  106. package/src/cli/tui/components/PanelCard.tsx +39 -0
  107. package/src/cli/tui/components/SelectRow.tsx +37 -0
  108. package/src/cli/tui/components/SlashPalette.tsx +172 -0
  109. package/src/cli/tui/components/Spinner.tsx +32 -0
  110. package/src/cli/tui/components/StatusBar.tsx +51 -0
  111. package/src/cli/tui/components/StatusDot.tsx +37 -0
  112. package/src/cli/tui/components/TabBar.tsx +49 -0
  113. package/src/cli/tui/components/Wordmark.tsx +43 -0
  114. package/src/cli/tui/format.ts +38 -0
  115. package/src/cli/tui/keys.test.ts +18 -0
  116. package/src/cli/tui/keys.ts +26 -0
  117. package/src/cli/tui/launch.test.ts +18 -0
  118. package/src/cli/tui/launch.ts +57 -0
  119. package/src/cli/tui/panels/Dashboard.tsx +201 -0
  120. package/src/cli/tui/panels/Database.tsx +198 -0
  121. package/src/cli/tui/panels/Dev.tsx +257 -0
  122. package/src/cli/tui/panels/Docker.tsx +357 -0
  123. package/src/cli/tui/panels/Navigator.test.ts +22 -0
  124. package/src/cli/tui/panels/Navigator.tsx +213 -0
  125. package/src/cli/tui/slash-catalog.test.ts +31 -0
  126. package/src/cli/tui/slash-catalog.ts +184 -0
  127. package/src/cli/tui/slash-run.ts +54 -0
  128. package/src/cli/tui/state/ports.ts +95 -0
  129. package/src/cli/tui/theme.ts +24 -0
  130. package/src/cli/vault-cmd.test.ts +402 -0
  131. package/src/cli/vault-cmd.ts +727 -69
  132. package/src/cli/vault-secure-input.test.ts +56 -0
  133. package/src/cli/vault-secure-input.ts +124 -0
  134. package/src/compiler/effects-infer.ts +7 -0
  135. package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
  136. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
  137. package/src/compiler/fixtures/skyport.expected.json +1 -1
  138. package/src/compiler-entry.ts +7 -0
  139. package/src/config/define-config.test.ts +111 -47
  140. package/src/config/driver-defaults.test.ts +16 -23
  141. package/src/config/driver-defaults.ts +33 -63
  142. package/src/config/index.ts +169 -144
  143. package/src/config/resolve-driver.test.ts +14 -19
  144. package/src/console/server/app.ts +4 -2
  145. package/src/console/server/console.test.ts +58 -1
  146. package/src/console/server/flows.ts +22 -2
  147. package/src/console/server/index.ts +3 -0
  148. package/src/console/server/operator-db.test.ts +55 -13
  149. package/src/console/server/operator-db.ts +305 -263
  150. package/src/console/server/serve.ts +16 -7
  151. package/src/console/server/state.ts +31 -9
  152. package/src/console/server/vault.test.ts +120 -5
  153. package/src/console/server/vault.ts +208 -12
  154. package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
  155. package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
  156. package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
  157. package/src/console/ui/dist/index.html +2 -2
  158. package/src/console/ui/shell/client.ts +16 -0
  159. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
  160. package/src/console/ui/vault/VaultA11yView.tsx +33 -1
  161. package/src/console/ui/vault/backend.test.ts +75 -0
  162. package/src/console/ui/vault/backend.ts +117 -0
  163. package/src/console/ui/vault/fixture.ts +21 -2
  164. package/src/console/ui/vault/index.ts +11 -1
  165. package/src/console/ui/vault/types.ts +26 -0
  166. package/src/docker/compose.ts +164 -61
  167. package/src/docker/derive.ts +106 -12
  168. package/src/docker/docker.test.ts +156 -110
  169. package/src/docker/helpers.ts +98 -0
  170. package/src/docker/images-config.test.ts +3 -8
  171. package/src/docker/index.ts +30 -5
  172. package/src/docker/recipes/index.ts +0 -3
  173. package/src/docker/recipes/llama-cpp.ts +4 -4
  174. package/src/docker/recipes/pgdog.ts +9 -2
  175. package/src/docker/resources.test.ts +71 -0
  176. package/src/docker/resources.ts +176 -0
  177. package/src/docker/stack-id.test.ts +11 -4
  178. package/src/docker/stack-id.ts +42 -32
  179. package/src/docker/stack.integration.test.ts +4 -15
  180. package/src/docker/types.ts +35 -3
  181. package/src/drivers/ai-ollama.integration.test.ts +2 -2
  182. package/src/drivers/conformance.test.ts +11 -12
  183. package/src/drivers/drizzle-dialect.test.ts +0 -4
  184. package/src/drivers/drizzle-dialect.ts +7 -13
  185. package/src/drivers/index.ts +24 -7
  186. package/src/drivers/journal-postgres.test.ts +3 -1
  187. package/src/drivers/journal-postgres.ts +4 -2
  188. package/src/drivers/meilisearch.ts +1 -1
  189. package/src/drivers/pglite.ts +28 -5
  190. package/src/drivers/types.ts +9 -11
  191. package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
  192. package/src/drivers/vault-aws-secrets-manager.ts +333 -0
  193. package/src/drivers/vault-builtin.test.ts +110 -0
  194. package/src/drivers/vault-builtin.ts +184 -0
  195. package/src/drivers/vault-driver-removal.test.ts +13 -0
  196. package/src/drivers/vault-managed.test.ts +95 -0
  197. package/src/drivers/vault-managed.ts +101 -25
  198. package/src/drivers/vault-types.ts +25 -5
  199. package/src/elements/ai/runtime.ts +1 -1
  200. package/src/elements/clock/chaos.test.ts +4 -1
  201. package/src/elements/clock/durable.ts +57 -4
  202. package/src/elements/clock.test.ts +13 -14
  203. package/src/elements/gate/config.ts +13 -3
  204. package/src/elements/gate/constants.ts +18 -0
  205. package/src/elements/gate/declare.ts +1 -1
  206. package/src/elements/gate/strategies.ts +2 -12
  207. package/src/elements/gate.test.ts +1 -1
  208. package/src/elements/gate.ts +2 -2
  209. package/src/elements/store/domain-ddl.test.ts +5 -12
  210. package/src/elements/store/emit-drizzle.ts +44 -19
  211. package/src/elements/store/index-boot.test.ts +15 -116
  212. package/src/elements/store/load-plugin-tables.ts +1 -1
  213. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  214. package/src/elements/store/resource-list-docs.test.ts +64 -44
  215. package/src/elements/store/resource.test.ts +17 -10
  216. package/src/elements/store/runtime.ts +9 -12
  217. package/src/elements/store/schema-decl.test.ts +16 -32
  218. package/src/elements/store/seed.test.ts +1 -2
  219. package/src/elements/store/seed.ts +7 -8
  220. package/src/elements/store/sql-condition.test.ts +2 -2
  221. package/src/elements/store/sql-session.test.ts +8 -10
  222. package/src/elements/store/sql-session.ts +10 -2
  223. package/src/elements/store.test.ts +13 -13
  224. package/src/elements/vault/audit.ts +233 -0
  225. package/src/elements/vault/boot-chain.test.ts +78 -0
  226. package/src/elements/vault/boot-chain.ts +43 -39
  227. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  228. package/src/elements/vault/builtin-adapter.ts +1184 -0
  229. package/src/elements/vault/crypto.test.ts +237 -0
  230. package/src/elements/vault/crypto.ts +431 -0
  231. package/src/elements/vault/declare.ts +164 -3
  232. package/src/elements/vault/errors.ts +56 -0
  233. package/src/elements/vault/kms-unseal.test.ts +76 -0
  234. package/src/elements/vault/kms-unseal.ts +181 -0
  235. package/src/elements/vault/path.ts +90 -0
  236. package/src/elements/vault/resilience.test.ts +102 -0
  237. package/src/elements/vault/resilience.ts +107 -0
  238. package/src/elements/vault/runtime.ts +36 -4
  239. package/src/elements/vault/security-checklist.test.ts +130 -0
  240. package/src/elements/vault/storage.ts +448 -0
  241. package/src/elements/vault/types.ts +288 -0
  242. package/src/elements/vault/unseal.ts +150 -0
  243. package/src/elements/vault.test.ts +72 -4
  244. package/src/elements/vault.ts +91 -2
  245. package/src/full.ts +248 -0
  246. package/src/http.ts +41 -0
  247. package/src/i18n/interpolate-simple.ts +37 -0
  248. package/src/i18n/messages.ts +51 -4
  249. package/src/i18n-entry.ts +7 -0
  250. package/src/index.ts +35 -145
  251. package/src/journal-entry.ts +20 -0
  252. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  253. package/src/kernel/app-auth.ts +98 -0
  254. package/src/kernel/app.ts +77 -110
  255. package/src/kernel/auto-registry.test.ts +2 -2
  256. package/src/kernel/boot-bind/ai.test.ts +5 -5
  257. package/src/kernel/boot-bind/ai.ts +2 -1
  258. package/src/kernel/boot-bind/channel.test.ts +6 -6
  259. package/src/kernel/boot-bind/channel.ts +3 -10
  260. package/src/kernel/boot-bind/clock.ts +2 -2
  261. package/src/kernel/boot-bind/gate.ts +5 -9
  262. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  263. package/src/kernel/boot-bind/journal.ts +2 -2
  264. package/src/kernel/boot-bind/store.test.ts +43 -44
  265. package/src/kernel/boot-bind/store.ts +16 -41
  266. package/src/kernel/boot-bind/vault.ts +16 -1
  267. package/src/kernel/boot.test.ts +13 -19
  268. package/src/kernel/boot.ts +31 -19
  269. package/src/kernel/budget-entry.ts +1 -1
  270. package/src/kernel/compensate.test.ts +288 -1
  271. package/src/kernel/compensate.ts +126 -0
  272. package/src/kernel/concurrency.test.ts +10 -9
  273. package/src/kernel/effects-stamping.test.ts +27 -2
  274. package/src/kernel/element-registries.ts +5 -3
  275. package/src/kernel/flow.ts +4 -2
  276. package/src/kernel/fx.test.ts +119 -3
  277. package/src/kernel/fx.ts +215 -40
  278. package/src/kernel/index.ts +14 -0
  279. package/src/kernel/journal.ts +130 -7
  280. package/src/kernel/pipeline.test.ts +20 -6
  281. package/src/kernel/redacted.test.ts +35 -0
  282. package/src/kernel/redacted.ts +24 -2
  283. package/src/kernel/router/create-default.ts +16 -0
  284. package/src/kernel/router/create-edge.ts +18 -0
  285. package/src/kernel/router/create.ts +23 -0
  286. package/src/kernel/router/index.ts +23 -0
  287. package/src/kernel/router/linear.ts +88 -0
  288. package/src/kernel/router/regexp.ts +188 -0
  289. package/src/kernel/router/smart.ts +120 -0
  290. package/src/kernel/router/trie.ts +108 -0
  291. package/src/kernel/router/types.ts +56 -0
  292. package/src/kernel/router.ts +18 -532
  293. package/src/kernel-entry.ts +43 -0
  294. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  295. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  296. package/src/plugins/config-source.test.ts +7 -7
  297. package/src/release/build-lib.ts +61 -0
  298. package/src/release/exports.test.ts +4 -2
  299. package/src/release/exports.ts +30 -4
  300. package/src/release/measure.ts +101 -22
  301. package/src/runs/duckdb.ts +7 -2
  302. package/src/shared/lazy-src.ts +79 -0
  303. package/src/shared/optional-peer.ts +41 -0
  304. package/src/term.test.ts +3 -44
  305. package/src/test/create-test-app.ts +12 -0
  306. package/src/test/provisions.integration.test.ts +1 -1
  307. package/src/test/reset-element-registries.ts +2 -0
  308. package/src/upgrade/codemods.test.ts +7 -20
  309. package/src/upgrade/codemods.ts +93 -60
  310. package/site/content/docs/recipes/openbao.mdx +0 -141
  311. package/src/cli/ask-dev-mode.ts +0 -40
  312. package/src/cli/dev-mode.test.ts +0 -45
  313. package/src/cli/dev-mode.ts +0 -78
  314. package/src/cli/openbao-bootstrap.test.ts +0 -150
  315. package/src/cli/openbao-bootstrap.ts +0 -321
  316. package/src/cli/openbao-restart.integration.test.ts +0 -145
  317. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  318. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  319. package/src/docker/recipes/openbao.ts +0 -47
  320. package/src/drivers/libsql.ts +0 -179
  321. package/src/drivers/sqlite.ts +0 -44
  322. package/src/drivers/vault-openbao.test.ts +0 -97
  323. package/src/drivers/vault-openbao.ts +0 -143
@@ -3,10 +3,10 @@
3
3
  */
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
- import { Database } from "bun:sqlite";
7
6
  import { mkdir, mkdtemp, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
8
7
  import { tmpdir } from "node:os";
9
8
  import { join, resolve } from "node:path";
9
+ import { connectPglite } from "../drivers/pglite.ts";
10
10
  import { createFx } from "../kernel/fx.ts";
11
11
  import { defineSeed, type SeedFn } from "../elements/store/seed.ts";
12
12
  import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "./exit.ts";
@@ -151,7 +151,7 @@ describe("oke db emit — live plugged plugin tables", () => {
151
151
  join(dir, "oke.config.ts"),
152
152
  `import { defineConfig } from ${JSON.stringify(CONFIG_MOD)};
153
153
  export default defineConfig({
154
- drivers: { store: { sql: { local: "sqlite", test: "memory", prod: "postgres" } } },
154
+ drivers: { store: { sql: { dev: "postgres", test: "pglite", prod: "postgres" } } },
155
155
  db: { declare: "src/schema.decl.ts", generated: "src/schema.generated.ts" },
156
156
  });
157
157
  `,
@@ -185,10 +185,10 @@ export const app = oke({ name: "plugin-emit" }).plug(audit);
185
185
  join(dir, "drizzle.config.ts"),
186
186
  `import { defineConfig } from "drizzle-kit";
187
187
  export default defineConfig({
188
- dialect: "sqlite",
188
+ dialect: "postgresql",
189
189
  schema: "./src/schema.generated.ts",
190
190
  out: "./drizzle",
191
- dbCredentials: { url: "file:./local.db" },
191
+ dbCredentials: { url: "postgres://127.0.0.1:5432/oke" },
192
192
  });
193
193
  `,
194
194
  );
@@ -205,8 +205,8 @@ export default defineConfig({
205
205
  expect(out.join("")).toMatch(/emitted.*schema\.generated\.ts/);
206
206
 
207
207
  const generated = await readFile(join(dir, "src", "schema.generated.ts"), "utf8");
208
- expect(generated).toContain('sqliteTable("notes"');
209
- expect(generated).toContain('sqliteTable("audit_events"');
208
+ expect(generated).toContain('pgTable("notes"');
209
+ expect(generated).toContain('pgTable("audit_events"');
210
210
  expect(generated).toContain('text("actor_id")');
211
211
  expect(generated).toContain('text("action")');
212
212
  expect(generated).toContain(".notNull()");
@@ -220,7 +220,7 @@ export default defineConfig({
220
220
  join(dir, "oke.config.ts"),
221
221
  `import { defineConfig } from ${JSON.stringify(CONFIG_MOD)};
222
222
  export default defineConfig({
223
- drivers: { store: { sql: { local: "sqlite" } } },
223
+ drivers: { store: { sql: { test: "pglite" } } },
224
224
  });
225
225
  `,
226
226
  );
@@ -239,12 +239,13 @@ export const app = oke({ name: "plugin-only" }).plug(
239
239
 
240
240
  const { loadOkeConfig } = await import("./load-config.ts");
241
241
  const loaded = await loadOkeConfig(dir);
242
- const emitted = await emitAbstractSchemaPrestep(dir, loaded.config, () => {}, "local");
242
+ const emitted = await emitAbstractSchemaPrestep(dir, loaded.config, () => {}, "test");
243
243
  expect(emitted).toBe(true);
244
244
 
245
- const generated = await readFile(join(dir, "src", "db", "schema.generated.ts"), "utf8");
246
- expect(generated).toContain('sqliteTable("metrics_daily"');
247
- expect(generated).toContain('integer("hits")');
245
+ const generated = await readFile(join(dir, "src", "db", "schema.drizzle.ts"), "utf8");
246
+ expect(generated).toContain('pgTable("metrics_daily"');
247
+ // Postgres dialect maps abstract integer → bigint (32-bit INTEGER is too narrow).
248
+ expect(generated).toContain('bigint("hits", { mode: "number" })');
248
249
  });
249
250
  });
250
251
 
@@ -252,17 +253,49 @@ describe("oke db multi-migration catch-up", () => {
252
253
  /**
253
254
  * Real drizzle-kit generate/migrate through `oke db` — proves multi-file
254
255
  * versioned migrations and that re-migrate / environment lag only apply
255
- * previously-unapplied files (drizzle-kit `__drizzle_migrations` history).
256
- * The CLI wrapper must not interfere with that bookkeeping.
256
+ * previously-unapplied files (drizzle-kit `drizzle.__drizzle_migrations`
257
+ * history). The CLI wrapper must not interfere with that bookkeeping.
257
258
  */
258
259
  test("second migrate skips applied files; lag DB catches up with only the new one", async () => {
259
260
  const dir = await mkdtemp(join(import.meta.dir, ".tmp-db-mig-"));
261
+ const pgdata = join(dir, "app.pglite");
262
+ async function migrationNames(): Promise<string[]> {
263
+ const conn = await connectPglite({ url: pgdata });
264
+ try {
265
+ const rows = await conn.query(`SELECT name FROM drizzle.__drizzle_migrations ORDER BY id`);
266
+ return rows.map((r) => String(r.name));
267
+ } finally {
268
+ await conn.close();
269
+ }
270
+ }
271
+ async function noteColumns(): Promise<string[]> {
272
+ const conn = await connectPglite({ url: pgdata });
273
+ try {
274
+ const rows = await conn.query(
275
+ `SELECT column_name AS name FROM information_schema.columns
276
+ WHERE table_schema = 'public' AND table_name = 'notes'`,
277
+ );
278
+ return rows.map((r) => String(r.name));
279
+ } finally {
280
+ await conn.close();
281
+ }
282
+ }
283
+ async function notesTableExists(): Promise<boolean> {
284
+ const conn = await connectPglite({ url: pgdata });
285
+ try {
286
+ const rows = await conn.query(`SELECT to_regclass('public.notes') AS reg`);
287
+ return rows[0]?.reg != null;
288
+ } finally {
289
+ await conn.close();
290
+ }
291
+ }
292
+
260
293
  try {
261
294
  await mkdir(join(dir, "src"), { recursive: true });
262
295
  await writeFile(
263
296
  join(dir, "src", "schema.ts"),
264
- `import { sqliteTable, text } from "drizzle-orm/sqlite-core";
265
- export const notes = sqliteTable("notes", {
297
+ `import { pgTable, text } from "drizzle-orm/pg-core";
298
+ export const notes = pgTable("notes", {
266
299
  id: text("id").primaryKey(),
267
300
  title: text("title").notNull(),
268
301
  });
@@ -272,10 +305,11 @@ export const notes = sqliteTable("notes", {
272
305
  join(dir, "drizzle.config.ts"),
273
306
  `import { defineConfig } from "drizzle-kit";
274
307
  export default defineConfig({
275
- dialect: "sqlite",
308
+ dialect: "postgresql",
309
+ driver: "pglite",
276
310
  schema: "./src/schema.ts",
277
311
  out: "./drizzle",
278
- dbCredentials: { url: "file:./app.db" },
312
+ dbCredentials: { url: ${JSON.stringify(pgdata)} },
279
313
  });
280
314
  `,
281
315
  );
@@ -293,20 +327,13 @@ export default defineConfig({
293
327
  expect(mig1.code).toBe(EXIT_OK);
294
328
  expect(mig1.out).toContain("oke db migrate: applied");
295
329
 
296
- let db = new Database(join(dir, "app.db"));
297
- expect(
298
- db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'").get(),
299
- ).toBeTruthy();
300
- let history = db.query("SELECT name FROM __drizzle_migrations ORDER BY id").all() as Array<{
301
- name: string;
302
- }>;
303
- expect(history.map((r) => r.name)).toEqual([initialMig]);
304
- db.close();
330
+ expect(await notesTableExists()).toBe(true);
331
+ expect(await migrationNames()).toEqual([initialMig]);
305
332
 
306
333
  await writeFile(
307
334
  join(dir, "src", "schema.ts"),
308
- `import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
309
- export const notes = sqliteTable("notes", {
335
+ `import { integer, pgTable, text } from "drizzle-orm/pg-core";
336
+ export const notes = pgTable("notes", {
310
337
  id: text("id").primaryKey(),
311
338
  title: text("title").notNull(),
312
339
  pinned: integer("pinned").notNull().default(0),
@@ -333,54 +360,31 @@ export const notes = sqliteTable("notes", {
333
360
  const mig2 = await runDbFresh("migrate", dir);
334
361
  expect(mig2.code).toBe(EXIT_OK);
335
362
 
336
- db = new Database(join(dir, "app.db"));
337
- history = db.query("SELECT name FROM __drizzle_migrations ORDER BY id").all() as Array<{
338
- name: string;
339
- }>;
340
- expect(history.map((r) => r.name)).toEqual([firstMig, secondMig]);
341
- const cols = db.query("PRAGMA table_info(notes)").all() as Array<{ name: string }>;
342
- expect(cols.map((c) => c.name)).toContain("pinned");
343
- db.close();
363
+ expect(await migrationNames()).toEqual([firstMig, secondMig]);
364
+ expect(await noteColumns()).toContain("pinned");
344
365
 
345
366
  // Idempotent re-migrate: history unchanged, no error.
346
367
  const migNoop = await runDbFresh("migrate", dir);
347
368
  expect(migNoop.code).toBe(EXIT_OK);
348
- db = new Database(join(dir, "app.db"));
349
- const historyNoop = db
350
- .query("SELECT name FROM __drizzle_migrations ORDER BY id")
351
- .all() as Array<{ name: string }>;
352
- expect(historyNoop.map((r) => r.name)).toEqual([firstMig, secondMig]);
353
- db.close();
369
+ expect(await migrationNames()).toEqual([firstMig, secondMig]);
354
370
 
355
371
  // Staging/prod lag: fresh DB with only the first migration applied,
356
372
  // then both files present — catch up applies only the second.
357
- await rm(join(dir, "app.db"), { force: true });
373
+ await rm(pgdata, { recursive: true, force: true });
358
374
  const stash = join(dir, "_stash_second");
359
375
  await rm(stash, { recursive: true, force: true });
360
376
  await rename(join(dir, "drizzle", secondMig), stash);
361
377
 
362
378
  const lagFirst = await runDbFresh("migrate", dir);
363
379
  expect(lagFirst.code).toBe(EXIT_OK);
364
- db = new Database(join(dir, "app.db"));
365
- const lagHist1 = db
366
- .query("SELECT name FROM __drizzle_migrations ORDER BY id")
367
- .all() as Array<{ name: string }>;
368
- expect(lagHist1.map((r) => r.name)).toEqual([firstMig]);
369
- const lagCols1 = db.query("PRAGMA table_info(notes)").all() as Array<{ name: string }>;
370
- expect(lagCols1.map((c) => c.name)).not.toContain("pinned");
371
- db.close();
380
+ expect(await migrationNames()).toEqual([firstMig]);
381
+ expect(await noteColumns()).not.toContain("pinned");
372
382
 
373
383
  await rename(stash, join(dir, "drizzle", secondMig));
374
384
  const lagCatchUp = await runDbFresh("migrate", dir);
375
385
  expect(lagCatchUp.code).toBe(EXIT_OK);
376
- db = new Database(join(dir, "app.db"));
377
- const lagHist2 = db
378
- .query("SELECT name FROM __drizzle_migrations ORDER BY id")
379
- .all() as Array<{ name: string }>;
380
- expect(lagHist2.map((r) => r.name)).toEqual([firstMig, secondMig]);
381
- const lagCols2 = db.query("PRAGMA table_info(notes)").all() as Array<{ name: string }>;
382
- expect(lagCols2.map((c) => c.name)).toContain("pinned");
383
- db.close();
386
+ expect(await migrationNames()).toEqual([firstMig, secondMig]);
387
+ expect(await noteColumns()).toContain("pinned");
384
388
  } finally {
385
389
  await rm(dir, { recursive: true, force: true });
386
390
  }
@@ -409,7 +413,7 @@ describe("oke db seed", () => {
409
413
  }
410
414
 
411
415
  async function seedWith(
412
- env: "local" | "docker" | "test" | "prod",
416
+ env: "dev" | "test" | "prod",
413
417
  extras: Parameters<typeof runSeed>[0] = {},
414
418
  ) {
415
419
  const order: string[] = [];
@@ -429,20 +433,15 @@ describe("oke db seed", () => {
429
433
  }
430
434
 
431
435
  test("essential array order is execution order", async () => {
432
- const { code, order, out } = await seedWith("local");
436
+ const { code, order, out } = await seedWith("dev");
433
437
  expect(code).toBe(EXIT_OK);
434
438
  expect(order.slice(0, 2)).toEqual(["a", "b"]);
435
439
  expect(out).toContain("oke db seed: essential a");
436
440
  expect(out).toContain("oke db seed: ok");
437
441
  });
438
442
 
439
- test("local runs essential + dev, never prod", async () => {
440
- const { order } = await seedWith("local");
441
- expect(order).toEqual(["a", "b", "dev"]);
442
- });
443
-
444
- test("docker runs essential + dev, never prod", async () => {
445
- const { order } = await seedWith("docker");
443
+ test("dev runs essential + dev, never prod", async () => {
444
+ const { order } = await seedWith("dev");
446
445
  expect(order).toEqual(["a", "b", "dev"]);
447
446
  });
448
447
 
@@ -493,11 +492,11 @@ describe("oke db seed", () => {
493
492
  expect(code).toBe(EXIT_OK);
494
493
  });
495
494
 
496
- test("docker confirm proceeds when confirmEnv returns true", async () => {
495
+ test("dev confirm proceeds when confirmEnv returns true", async () => {
497
496
  const out: string[] = [];
498
497
  const order: string[] = [];
499
498
  const code = await runSeed({
500
- env: "docker",
499
+ env: "dev",
501
500
  force: false,
502
501
  stdinIsTTY: true,
503
502
  seedDef: trackingDef(order),
@@ -506,7 +505,7 @@ describe("oke db seed", () => {
506
505
  stop: async () => {},
507
506
  }),
508
507
  confirmEnv: async (env, target) => {
509
- expect(env).toBe("docker");
508
+ expect(env).toBe("dev");
510
509
  expect(target.length).toBeGreaterThan(0);
511
510
  return true;
512
511
  },
package/src/cli/db.ts CHANGED
@@ -81,7 +81,7 @@ export async function withDrizzleKitEnv<T>(
81
81
  }
82
82
 
83
83
  /** Subcommands under `oke db`. */
84
- export type DbSubcommand = "push" | "generate" | "migrate" | "seed";
84
+ export type DbSubcommand = "push" | "generate" | "migrate" | "seed" | "studio";
85
85
 
86
86
  /** Options for {@link runDb}. */
87
87
  export interface DbOptions {
@@ -167,6 +167,10 @@ export async function runDb(sub: DbSubcommand, options: DbOptions = {}): Promise
167
167
  const loaded = await loadOkeConfig(cwd).catch(() => null);
168
168
  const env = options.env ?? (await resolveDevSqlEnv(cwd));
169
169
 
170
+ if (sub === "studio") {
171
+ return runStudio(cwd, options, write, env, loaded?.config ?? null);
172
+ }
173
+
170
174
  if (sub === "seed") {
171
175
  return runSeed({
172
176
  cwd,
@@ -208,7 +212,7 @@ export async function runDb(sub: DbSubcommand, options: DbOptions = {}): Promise
208
212
  }
209
213
 
210
214
  /**
211
- * Emit `schema.generated.ts` from abstract decls + live plugged plugin tables.
215
+ * Emit `schema.drizzle.ts` from abstract decls + live plugged plugin tables.
212
216
  *
213
217
  * @param cwd - Project root
214
218
  * @param config - Loaded oke config
@@ -220,12 +224,16 @@ export async function emitAbstractSchemaPrestep(
220
224
  cwd: string,
221
225
  config: OkeConfig | null | undefined,
222
226
  write: (text: string) => void = () => {},
223
- env: ConfigEnv = "local",
227
+ env: ConfigEnv = "test",
224
228
  options: {
225
229
  readonly pluginTables?: readonly TableContribution[];
226
230
  readonly entry?: string;
227
231
  } = {},
228
232
  ): Promise<boolean> {
233
+ // System stubs (core / auth / Manifest stores) — separate from domain Drizzle.
234
+ // Re-run here so plugins added later refresh `.oke/schema/oke.ts` on db push.
235
+ await refreshSystemSchema(cwd, write);
236
+
229
237
  const driverId = resolveDriverId(config?.drivers?.store?.sql, env);
230
238
  const dialect: SqlDialect = dialectFromDriverId(driverId);
231
239
  const pluginTables =
@@ -245,6 +253,24 @@ export async function emitAbstractSchemaPrestep(
245
253
  return result.emitted;
246
254
  }
247
255
 
256
+ /**
257
+ * Quietly refresh `.oke/schema/oke.ts` (never fails the domain db path).
258
+ *
259
+ * @param cwd - Project root
260
+ * @param write - Optional log sink
261
+ */
262
+ async function refreshSystemSchema(
263
+ cwd: string,
264
+ write: (text: string) => void = () => {},
265
+ ): Promise<void> {
266
+ try {
267
+ const { runSchemaGenerate } = await import("./schema.ts");
268
+ await runSchemaGenerate({ cwd, write: () => {} });
269
+ } catch (err) {
270
+ write(`oke schema generate: skipped — ${err instanceof Error ? err.message : String(err)}\n`);
271
+ }
272
+ }
273
+
248
274
  /**
249
275
  * Push domain schema to the live database (dev).
250
276
  *
@@ -438,18 +464,58 @@ export async function detectDbDrift(options: DbOptions = {}): Promise<{
438
464
  }
439
465
 
440
466
  /**
441
- * CLI entry: parse `oke db <push|generate|migrate|seed> […]`.
467
+ * Open drizzle-kit Studio against the project config / env overlay.
468
+ *
469
+ * @param cwd - Project root
470
+ * @param options - Paths / injectables
471
+ * @param write - stdout writer
472
+ * @param env - Config env
473
+ * @param config - Loaded oke config
474
+ */
475
+ export async function runStudio(
476
+ cwd: string,
477
+ options: DbOptions,
478
+ write: (text: string) => void = (t) => process.stdout.write(t),
479
+ env?: ConfigEnv,
480
+ config?: OkeConfig | null,
481
+ ): Promise<number> {
482
+ const activeEnv = env ?? (await resolveDevSqlEnv(cwd));
483
+ const loaded = config ?? (await loadOkeConfig(cwd).catch(() => null))?.config ?? null;
484
+ const configPath = await resolveDrizzleConfigPath(cwd, {
485
+ config: options.config,
486
+ loadedConfig: loaded,
487
+ });
488
+ if (!(await Bun.file(configPath).exists())) {
489
+ write(`oke db studio: missing ${configPath}\n`);
490
+ return EXIT_RUNTIME;
491
+ }
492
+ write(`oke db studio: opening drizzle-kit studio (${configPath})\n`);
493
+ return withDrizzleKitEnv(cwd, loaded, activeEnv, async () => {
494
+ const proc = Bun.spawn(["bunx", "drizzle-kit", "studio", "--config", configPath], {
495
+ cwd,
496
+ stdout: "inherit",
497
+ stderr: "inherit",
498
+ stdin: "inherit",
499
+ env: process.env,
500
+ });
501
+ const code = await proc.exited;
502
+ return code === 0 ? EXIT_OK : EXIT_RUNTIME;
503
+ });
504
+ }
505
+
506
+ /**
507
+ * CLI entry: parse `oke db <push|generate|migrate|seed|studio> […]`.
442
508
  *
443
509
  * @param args - Args after `db`
444
510
  */
445
511
  export async function dbCli(args: readonly string[]): Promise<number> {
446
512
  const sub = args[0];
447
513
  if (!sub || sub === "--help" || sub === "-h") {
448
- console.log(`oke db push|generate|migrate|seed [--config|-c] [--env name] [--force]
514
+ console.log(`oke db push|generate|migrate|seed|studio [--config|-c] [--env name] [--force]
449
515
 
450
516
  Domain schema sync via drizzle-kit, plus explicit seed.
451
517
  When src/db/schema.decl.ts and/or a plugged app entry exists, emits
452
- schema.generated.ts from store.schema.table + live plugin .table()
518
+ schema.drizzle.ts from store.schema.table + live plugin .table()
453
519
  contributions — then runs drizzle-kit.
454
520
  Hand-written src/schema.ts remains supported (emit skipped if nothing to emit).
455
521
  Not the same as \`oke schema generate\` (core/plugin stub tables).
@@ -458,14 +524,21 @@ Not the same as \`oke schema generate\` (core/plugin stub tables).
458
524
  generate Write versioned SQL under drizzle/ for review
459
525
  migrate Apply generated migrations (explicit; never automatic in prod)
460
526
  seed Run defineSeed (essential + env category); never at boot
527
+ studio Open drizzle-kit Studio (long-running)
461
528
 
462
- --env Override config env (local|docker|test|prod)
529
+ --env Override config env (dev|test|prod)
463
530
  --force Skip docker/prod confirmation prompt (CI)
464
531
  --entry App entry for seed boot / plugin table discovery
465
532
  `);
466
533
  return sub ? EXIT_OK : EXIT_USAGE;
467
534
  }
468
- if (sub !== "push" && sub !== "generate" && sub !== "migrate" && sub !== "seed") {
535
+ if (
536
+ sub !== "push" &&
537
+ sub !== "generate" &&
538
+ sub !== "migrate" &&
539
+ sub !== "seed" &&
540
+ sub !== "studio"
541
+ ) {
469
542
  console.error(`oke db: unknown subcommand "${sub}"`);
470
543
  return EXIT_USAGE;
471
544
  }
@@ -481,7 +554,7 @@ Not the same as \`oke schema generate\` (core/plugin stub tables).
481
554
  const value = args[++i];
482
555
  const parsed = parseConfigEnv(value);
483
556
  if (!parsed) {
484
- console.error(`oke db: invalid --env ${JSON.stringify(value)} (local|docker|test|prod)`);
557
+ console.error(`oke db: invalid --env ${JSON.stringify(value)} (dev|test|prod)`);
485
558
  return EXIT_USAGE;
486
559
  }
487
560
  env = parsed;
@@ -499,9 +572,10 @@ Not the same as \`oke schema generate\` (core/plugin stub tables).
499
572
  }
500
573
 
501
574
  function parseConfigEnv(value: string | undefined): ConfigEnv | undefined {
502
- if (value === "local" || value === "docker" || value === "test" || value === "prod") {
503
- return value;
504
- }
575
+ if (value === "dev" || value === "test" || value === "prod") return value;
576
+ // Soft-compat for older CLI flags / docs.
577
+ if (value === "local") return "test";
578
+ if (value === "docker") return "dev";
505
579
  return undefined;
506
580
  }
507
581
 
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { DevController } from "./dev-controller.ts";
3
+
4
+ describe("DevController", () => {
5
+ test("refuses to stop an external session", async () => {
6
+ const ctrl = new DevController();
7
+ const errors: string[] = [];
8
+ ctrl.on((e) => {
9
+ if (e.type === "error") errors.push(e.message);
10
+ });
11
+ // Force external via attach with busy ports inject — attach uses real probe.
12
+ // Simulate by calling stop after manually setting ownership through attach
13
+ // against a free project with no lock: stop should be a no-op (stopped).
14
+ await ctrl.attach(process.cwd());
15
+ if (ctrl.getOwnership() === "stopped") {
16
+ await ctrl.stop();
17
+ expect(ctrl.getOwnership()).toBe("stopped");
18
+ }
19
+ // Direct external refusal path:
20
+ (ctrl as unknown as { ownership: string }).ownership = "external";
21
+ await ctrl.stop();
22
+ expect(errors.some((m) => m.includes("refusing to stop external"))).toBe(true);
23
+ });
24
+
25
+ test("emit delivers events to listeners", () => {
26
+ const ctrl = new DevController();
27
+ const types: string[] = [];
28
+ const off = ctrl.on((e) => types.push(e.type));
29
+ ctrl.emit({ type: "boot", phase: "x" });
30
+ ctrl.emit({ type: "log", text: "hi" });
31
+ off();
32
+ ctrl.emit({ type: "exit", code: 0 });
33
+ expect(types).toEqual(["boot", "log"]);
34
+ });
35
+ });