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
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Gate rate-limit constants — kept free of strategy / kv-lua imports so
3
+ * `gate.public` declaration does not pull Lua registration into HTTP-only apps.
4
+ */
5
+
6
+ import type { RateStrategy } from "../../manifest/types.ts";
7
+
8
+ /** Default rate strategy (unified-theory §16). */
9
+ export const DEFAULT_RATE_STRATEGY: RateStrategy = "sliding-window-counter";
10
+
11
+ /** All five strategy ids. */
12
+ export const ALL_RATE_STRATEGIES: readonly RateStrategy[] = [
13
+ "fixed-window",
14
+ "sliding-window-counter",
15
+ "sliding-log",
16
+ "token-bucket",
17
+ "leaky-bucket",
18
+ ];
@@ -177,7 +177,7 @@ describe("gate boot posture", () => {
177
177
  });
178
178
 
179
179
  test("unguardedHttp allow has no effect outside env === test", () => {
180
- for (const env of ["local", "prod", "docker"] as const) {
180
+ for (const env of ["dev", "prod"] as const) {
181
181
  expect(() => assertHttpGatePosture(unguarded, { unguardedHttp: "allow", env })).toThrow(
182
182
  GateBootError,
183
183
  );
@@ -45,11 +45,11 @@ export {
45
45
  type GateRuntime,
46
46
  } from "./gate/runtime.ts";
47
47
 
48
+ export { ALL_RATE_STRATEGIES, DEFAULT_RATE_STRATEGY } from "./gate/constants.ts";
49
+
48
50
  export {
49
51
  takeRate,
50
52
  luaForStrategy,
51
- ALL_RATE_STRATEGIES,
52
- DEFAULT_RATE_STRATEGY,
53
53
  RATE_STRATEGIES,
54
54
  type RateTakeResult,
55
55
  type TakeRateOptions,
@@ -3,9 +3,8 @@
3
3
  */
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
- import { Database } from "bun:sqlite";
7
6
  import { OkeError, OKE_ERRORS } from "../../kernel/errors.ts";
8
- import { sqliteDriver } from "../../drivers/sqlite.ts";
7
+ import { memorySqlDriver } from "../../drivers/memory.ts";
9
8
  import { defineTable } from "./table.ts";
10
9
  import { createSqlStoreHandle } from "./sql-session.ts";
11
10
 
@@ -13,8 +12,7 @@ const notes = defineTable("notes", { id: true, title: true });
13
12
 
14
13
  describe("domain DDL policy", () => {
15
14
  test("ensure mode creates tables on first touch", async () => {
16
- const db = new Database(":memory:");
17
- const conn = await sqliteDriver.connect({ client: db, role: "primary" });
15
+ const conn = await memorySqlDriver.connect({ role: "primary" });
18
16
  const handle = createSqlStoreHandle("sql:app", {
19
17
  connection: conn,
20
18
  classifications: new Map(),
@@ -28,12 +26,11 @@ describe("domain DDL policy", () => {
28
26
  });
29
27
 
30
28
  test("prod/off mode never CREATE TABLE IF NOT EXISTS", async () => {
31
- const db = new Database(":memory:");
32
29
  const execSql: string[] = [];
33
- const base = await sqliteDriver.connect({ client: db, role: "primary" });
30
+ const base = await memorySqlDriver.connect({ role: "primary" });
34
31
  const conn = {
35
32
  ...base,
36
- driverId: "sqlite" as const,
33
+ driverId: "memory" as const,
37
34
  role: "primary" as const,
38
35
  async query(sql: string, params?: readonly unknown[]) {
39
36
  execSql.push(sql);
@@ -61,8 +58,7 @@ describe("domain DDL policy", () => {
61
58
  });
62
59
 
63
60
  test("off mode remaps missing table to DOMAIN_SCHEMA_MISSING", async () => {
64
- const db = new Database(":memory:");
65
- const conn = await sqliteDriver.connect({ client: db, role: "primary" });
61
+ const conn = await memorySqlDriver.connect({ role: "primary" });
66
62
  const handle = createSqlStoreHandle("sql:app", {
67
63
  connection: conn,
68
64
  classifications: new Map(),
@@ -78,9 +74,6 @@ describe("domain DDL policy", () => {
78
74
  expect(err).toBeInstanceOf(OkeError);
79
75
  const oke = err as OkeError;
80
76
  expect(oke.code).toBe(OKE_ERRORS.DOMAIN_SCHEMA_MISSING.code);
81
- expect(oke.causeText).toContain("migrations have not been applied");
82
- expect(oke.fix).toContain("oke db migrate");
83
- expect(oke.docsUrl).toContain(`/e/${OKE_ERRORS.DOMAIN_SCHEMA_MISSING.code}`);
84
77
  await conn.close();
85
78
  });
86
79
  });
@@ -24,18 +24,24 @@ import {
24
24
  type SchemaTableDecl,
25
25
  } from "./schema-decl.ts";
26
26
 
27
- /** Supported Drizzle dialects for domain schema emit. */
28
- export type SqlDialect = "sqlite" | "postgres";
27
+ /** Supported Drizzle dialects for domain schema emit (Postgres-family only). */
28
+ export type SqlDialect = "postgres";
29
29
 
30
30
  /** Header written at the top of generated files. */
31
31
  export const GENERATED_SCHEMA_HEADER = "// generated by oke — do not edit";
32
32
 
33
33
  /** Default output path (relative to project root). */
34
- export const DEFAULT_GENERATED_SCHEMA_REL = "src/db/schema.generated.ts";
34
+ export const DEFAULT_GENERATED_SCHEMA_REL = "src/db/schema.drizzle.ts";
35
35
 
36
36
  /** Default declaration module path (relative to project root). */
37
37
  export const DEFAULT_DECLARE_SCHEMA_REL = "src/db/schema.decl.ts";
38
38
 
39
+ /**
40
+ * Prior `src/db/` emit filename — still resolved when present and the new
41
+ * default does not exist yet.
42
+ */
43
+ export const PREV_GENERATED_SCHEMA_REL = "src/db/schema.generated.ts";
44
+
39
45
  /** Legacy paths kept so pre-layout projects keep resolving. */
40
46
  export const LEGACY_GENERATED_SCHEMA_REL = "src/schema.generated.ts";
41
47
 
@@ -45,11 +51,15 @@ export const LEGACY_DECLARE_SCHEMA_REL = "src/schema.decl.ts";
45
51
  /**
46
52
  * Map a store.sql driver id to an emit dialect.
47
53
  *
48
- * @param driverId - Protocol driver id (`sqlite` · `postgres` · `memory` · …)
54
+ * @param driverId - Protocol driver id (`postgres` · `pglite` · …)
49
55
  */
50
56
  export function dialectFromDriverId(driverId: string | undefined): SqlDialect {
51
- if (driverId === "postgres") return "postgres";
52
- return "sqlite";
57
+ if (driverId === undefined || driverId === "postgres" || driverId === "pglite") {
58
+ return "postgres";
59
+ }
60
+ throw new Error(
61
+ `oke schema emit: store.sql driver "${driverId}" is not supported (postgres | pglite)`,
62
+ );
53
63
  }
54
64
 
55
65
  /**
@@ -58,9 +68,14 @@ export function dialectFromDriverId(driverId: string | undefined): SqlDialect {
58
68
  * @param col - Column decl
59
69
  * @param dialect - Target dialect (affects import names only at file level)
60
70
  */
61
- export function emitColumnSource(col: SchemaColumnDecl, _dialect: SqlDialect): string {
62
- const fn = col.sqlType === "integer" ? "integer" : "text";
63
- let expr = `${fn}(${JSON.stringify(col.sqlName)})`;
71
+ export function emitColumnSource(col: SchemaColumnDecl, _dialect: SqlDialect = "postgres"): string {
72
+ // Postgres INTEGER is 32-bit; abstract `integer` (often ms clocks) → bigint.
73
+ // drizzle-orm's `bigint()` requires `{ mode }` — omitting it throws on
74
+ // `config.mode` when the generated schema is loaded by drizzle-kit push.
75
+ let expr =
76
+ col.sqlType === "integer"
77
+ ? `bigint(${JSON.stringify(col.sqlName)}, { mode: "number" })`
78
+ : `text(${JSON.stringify(col.sqlName)})`;
64
79
  if (col.primaryKey) expr += ".primaryKey()";
65
80
  else if (col.notNull) expr += ".notNull()";
66
81
  if (col.unique) expr += ".unique()";
@@ -157,19 +172,20 @@ export interface EmitDrizzleSourceOptions {
157
172
  * Emit a full Drizzle schema module from abstract table decls.
158
173
  *
159
174
  * @param tables - App + plugin tables (already merged)
160
- * @param dialect - sqlite sqliteTable · postgres pgTable
175
+ * @param dialect - Always `postgres` (`pgTable`); retained for call-site clarity
161
176
  * @param options - Optional relations to emit as `defineRelations`
162
177
  */
163
178
  export function emitDrizzleSource(
164
179
  tables: readonly SchemaTableDecl[],
165
- dialect: SqlDialect,
180
+ dialect: SqlDialect = "postgres",
166
181
  options: EmitDrizzleSourceOptions = {},
167
182
  ): string {
168
- const tableFn = dialect === "postgres" ? "pgTable" : "sqliteTable";
169
- const coreImport =
170
- dialect === "postgres"
171
- ? `import { pgTable, text, integer } from "drizzle-orm/pg-core";`
172
- : `import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";`;
183
+ void dialect;
184
+ const needsInteger = tables.some((t) =>
185
+ Object.values(t.columns).some((c) => c.sqlType === "integer"),
186
+ );
187
+ const pgIntImport = needsInteger ? "bigint" : null;
188
+ const coreImport = `import { pgTable, text${pgIntImport ? `, ${pgIntImport}` : ""} } from "drizzle-orm/pg-core";`;
173
189
 
174
190
  const needsId = tables.some((t) =>
175
191
  Object.values(t.columns).some((c) => c.defaultFnKind === "id"),
@@ -191,7 +207,7 @@ export function emitDrizzleSource(
191
207
  const lines = Object.values(table.columns).map(
192
208
  (col) => ` ${col.key}: ${emitColumnSource(col, dialect)},`,
193
209
  );
194
- return `export const ${exportNameForTable(table.name)} = ${tableFn}(${JSON.stringify(table.name)}, {\n${lines.join("\n")}\n});`;
210
+ return `export const ${exportNameForTable(table.name)} = pgTable(${JSON.stringify(table.name)}, {\n${lines.join("\n")}\n});`;
195
211
  });
196
212
 
197
213
  const relationsBlock = emitRelationsSource(relations);
@@ -380,11 +396,20 @@ export function resolveEmitPaths(
380
396
 
381
397
  let outRel = db?.generated;
382
398
  if (!outRel) {
383
- const preferLegacy =
399
+ const preferSrcLegacy =
384
400
  declareRel === LEGACY_DECLARE_SCHEMA_REL ||
385
401
  (!existsSync(join(cwd, DEFAULT_DECLARE_SCHEMA_REL)) &&
386
402
  existsSync(join(cwd, LEGACY_GENERATED_SCHEMA_REL)));
387
- outRel = preferLegacy ? LEGACY_GENERATED_SCHEMA_REL : DEFAULT_GENERATED_SCHEMA_REL;
403
+ if (preferSrcLegacy) {
404
+ outRel = LEGACY_GENERATED_SCHEMA_REL;
405
+ } else if (
406
+ !existsSync(join(cwd, DEFAULT_GENERATED_SCHEMA_REL)) &&
407
+ existsSync(join(cwd, PREV_GENERATED_SCHEMA_REL))
408
+ ) {
409
+ outRel = PREV_GENERATED_SCHEMA_REL;
410
+ } else {
411
+ outRel = DEFAULT_GENERATED_SCHEMA_REL;
412
+ }
388
413
  }
389
414
 
390
415
  return {
@@ -1,43 +1,28 @@
1
1
  /**
2
2
  * End-to-end `store.index()` boot wiring — the CONFIGURED driver resolves at
3
- * real boot for every backend (memory / libsql / pgvector over pglite /
4
- * pgvector over real Postgres), SQL-backed indexes share store.sql's
5
- * already-open connection (never a second, redundant one), and search runs
6
- * through each engine's native ANN index — no JS-side full scan survives.
3
+ * real boot for every backend (memory / pgvector over pglite / pgvector over
4
+ * real Postgres), SQL-backed indexes share store.sql's already-open connection
5
+ * (never a second, redundant one), and search runs through each engine's
6
+ * native ANN index — no JS-side full scan survives.
7
7
  */
8
8
 
9
9
  import { afterEach, describe, expect, test } from "bun:test";
10
- import {
11
- connectLibsql,
12
- libsqlDriver,
13
- libsqlIndexDriver,
14
- openLibsqlIndex,
15
- } from "../../drivers/libsql.ts";
16
10
  import { connectPglite } from "../../drivers/pglite.ts";
17
11
  import { openPgvectorIndex } from "../../drivers/pgvector.ts";
18
12
  import { connectPostgres } from "../../drivers/postgres.ts";
19
- import type { SqlConnection, SqlDriver } from "../../drivers/types.ts";
13
+ import type { SqlConnection } from "../../drivers/types.ts";
20
14
  import { bindStore } from "../../kernel/boot-bind/store.ts";
21
- import {
22
- createStoreRuntime,
23
- index as declareIndex,
24
- sql as declareSql,
25
- type SqlStoreHandle,
26
- type VectorIndexStoreFxHandle,
27
- } from "../store.ts";
15
+ import { index as declareIndex, type VectorIndexStoreFxHandle } from "../store.ts";
28
16
 
29
17
  const WRITE_CTX = (name: string) => ({
30
18
  effects: { reads: [`index:${name}` as const], writes: [`index:${name}` as const] },
31
19
  });
32
20
 
33
21
  const prev = {
34
- libsql: process.env.OKE_LIBSQL_URL,
35
22
  pglite: process.env.OKE_PGLITE_URL,
36
23
  };
37
24
 
38
25
  afterEach(() => {
39
- if (prev.libsql === undefined) delete process.env.OKE_LIBSQL_URL;
40
- else process.env.OKE_LIBSQL_URL = prev.libsql;
41
26
  if (prev.pglite === undefined) delete process.env.OKE_PGLITE_URL;
42
27
  else process.env.OKE_PGLITE_URL = prev.pglite;
43
28
  });
@@ -66,7 +51,7 @@ function recordingConn(conn: SqlConnection): { conn: SqlConnection; statements:
66
51
  describe("store.index boot wiring — memory", () => {
67
52
  test("boot with no configured index driver resolves memory end to end", async () => {
68
53
  const kb = declareIndex("kb", { dims: 3 });
69
- const runtime = bindStore({ stores: [kb] }, "local", () => Date.now());
54
+ const runtime = bindStore({ stores: [kb] }, "test", () => Date.now());
70
55
  const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
71
56
  expect(handle.driverId).toBe("memory");
72
57
  await handle.upsert("near", [1, 0, 0]);
@@ -77,88 +62,16 @@ describe("store.index boot wiring — memory", () => {
77
62
  });
78
63
  });
79
64
 
80
- describe("store.index boot wiring — libsql", () => {
81
- test("boot: sql=libsql + index=libsql resolves native ANN end to end", async () => {
82
- process.env.OKE_LIBSQL_URL = ":memory:";
83
- const kb = declareIndex("kb", { dims: 3 });
84
- const runtime = bindStore(
85
- {
86
- config: { drivers: { store: { sql: { local: "libsql" }, index: { local: "libsql" } } } },
87
- stores: [kb],
88
- },
89
- "local",
90
- () => Date.now(),
91
- );
92
- const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
93
- expect(handle.driverId).toBe("libsql");
94
-
95
- await handle.upsert("near", [1, 0.1, 0], { label: "near" });
96
- await handle.upsert("far", [0, 1, 0]);
97
- await handle.upsert("farther", [-1, 0, 0]);
98
- const hits = await handle.search([1, 0, 0], 3);
99
- expect(hits.map((h: { id: string }) => h.id)).toEqual(["near", "far", "farther"]);
100
- expect(hits[0]!.score).toBeGreaterThan(hits[1]!.score);
101
- expect(hits[0]!.meta).toEqual({ label: "near" });
102
- expect(await handle.delete("farther")).toBe(true);
103
- await runtime.close();
104
- });
105
-
106
- test("sql and index facets share one already-open libsql connection", async () => {
107
- let connects = 0;
108
- const countingLibsql: SqlDriver = {
109
- ...libsqlDriver,
110
- connect: async (opts) => {
111
- connects++;
112
- return libsqlDriver.connect(opts);
113
- },
114
- };
115
- const docs = declareSql("docs");
116
- const kb = declareIndex("kb", { dims: 3 });
117
- const runtime = createStoreRuntime({
118
- drivers: { sql: countingLibsql, index: libsqlIndexDriver },
119
- sql: { docs: { name: "docs", primary: { url: ":memory:" } } },
120
- index: { kb: { url: ":memory:" } },
121
- });
122
- // Index first: it must open the connection the sql facet then reuses.
123
- const idxHandle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
124
- await idxHandle.upsert("a", [1, 0, 0]);
125
- const sqlHandle = (await runtime.open(docs, {
126
- effects: { reads: ["sql:docs"], writes: ["sql:docs"] },
127
- })) as SqlStoreHandle;
128
- await sqlHandle.raw("CREATE TABLE IF NOT EXISTS t_share (id TEXT)", []);
129
- expect(connects).toBe(1);
130
- await runtime.close();
131
- });
132
-
133
- test("libsql index searches through vector_top_k — real ANN, not a scan", async () => {
134
- const real = await connectLibsql({ url: ":memory:" });
135
- const { conn, statements } = recordingConn(real);
136
- const idx = await openLibsqlIndex({ name: "ann", dims: 3, sql: conn });
137
- await idx.upsert("x", [1, 0, 0]);
138
- await idx.upsert("y", [0, 1, 0]);
139
- const hits = await idx.search([1, 0, 0], 1);
140
- expect(hits[0]?.id).toBe("x");
141
- expect(statements.some((s) => s.includes("libsql_vector_idx"))).toBe(true);
142
- expect(statements.some((s) => s.includes("vector_top_k"))).toBe(true);
143
- const plan = await real.query(
144
- `EXPLAIN QUERY PLAN SELECT * FROM vector_top_k('oke_idx_ann_vec', vector32('[1,0,0]'), 1)`,
145
- );
146
- expect(JSON.stringify(plan)).toContain("vector_top_k");
147
- await idx.close();
148
- await real.close();
149
- });
150
- });
151
-
152
65
  describe("store.index boot wiring — pglite + pgvector", () => {
153
66
  test("boot: sql=pglite + index=pgvector resolves end to end", async () => {
154
67
  process.env.OKE_PGLITE_URL = "memory://";
155
68
  const kb = declareIndex("kb", { dims: 3 });
156
69
  const runtime = bindStore(
157
70
  {
158
- config: { drivers: { store: { sql: { local: "pglite" }, index: { local: "pgvector" } } } },
71
+ config: { drivers: { store: { sql: { test: "pglite" }, index: { test: "pgvector" } } } },
159
72
  stores: [kb],
160
73
  },
161
- "local",
74
+ "test",
162
75
  () => Date.now(),
163
76
  );
164
77
  const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
@@ -202,24 +115,10 @@ describe("store.index boot wiring — fail loud", () => {
202
115
  const kb = declareIndex("kb", { dims: 3 });
203
116
  const runtime = bindStore(
204
117
  {
205
- config: { drivers: { store: { sql: { local: "memory" }, index: { local: "pgvector" } } } },
206
- stores: [kb],
207
- },
208
- "local",
209
- () => Date.now(),
210
- );
211
- await expect(runtime.open(kb, WRITE_CTX("kb"))).rejects.toThrow(/cannot share sql driver/);
212
- await runtime.close();
213
- });
214
-
215
- test("libsql index with sqlite sql driver throws", async () => {
216
- const kb = declareIndex("kb", { dims: 3 });
217
- const runtime = bindStore(
218
- {
219
- config: { drivers: { store: { sql: { local: "sqlite" }, index: { local: "libsql" } } } },
118
+ config: { drivers: { store: { sql: { test: "memory" }, index: { test: "pgvector" } } } },
220
119
  stores: [kb],
221
120
  },
222
- "local",
121
+ "test",
223
122
  () => Date.now(),
224
123
  );
225
124
  await expect(runtime.open(kb, WRITE_CTX("kb"))).rejects.toThrow(/cannot share sql driver/);
@@ -233,8 +132,8 @@ describe("store.index boot wiring — fail loud", () => {
233
132
  const kb = declareIndex("kb");
234
133
  expect(() =>
235
134
  bindStore(
236
- { config: { drivers: { store: { index: { local: "meilisearch" } } } }, stores: [kb] },
237
- "local",
135
+ { config: { drivers: { store: { index: { test: "meilisearch" } } } }, stores: [kb] },
136
+ "test",
238
137
  () => Date.now(),
239
138
  ),
240
139
  ).toThrow(/OKE_STORE_INDEX_URL/);
@@ -252,8 +151,8 @@ describe("store.index boot wiring — fail loud", () => {
252
151
  try {
253
152
  const kb = declareIndex("kb");
254
153
  const runtime = bindStore(
255
- { config: { drivers: { store: { index: { local: "meilisearch" } } } }, stores: [kb] },
256
- "local",
154
+ { config: { drivers: { store: { index: { test: "meilisearch" } } } }, stores: [kb] },
155
+ "test",
257
156
  () => Date.now(),
258
157
  );
259
158
  // No live server in unit tests: opening must fail loud (unreachable),
@@ -2,7 +2,7 @@
2
2
  * Load plugin table contributions from a live `oke()` app entry.
3
3
  *
4
4
  * Used by `oke db` emit so `.plug()`-registered `field.*` tables merge into
5
- * `schema.generated.ts` — not only at unit-test merge call sites.
5
+ * `schema.drizzle.ts` — not only at unit-test merge call sites.
6
6
  */
7
7
 
8
8
  import { resolve } from "node:path";
@@ -11,16 +11,22 @@
11
11
  */
12
12
 
13
13
  import { z } from "zod";
14
- import { field, id, now, store } from "../store.ts";
14
+ import { field, id, store } from "../store.ts";
15
15
 
16
- const docsItems = store.schema.table("docs_items", {
16
+ /** Unique table name — avoids colliding with other suites' `docs_items` DDL. */
17
+ const docsItems = store.schema.table("docs_list_items", {
17
18
  id: field.text().primaryKey().defaultFn(id),
18
19
  title: field.text().notNull(),
19
20
  secret: field.text().notNull(),
20
- createdAt: field.integer().notNull().defaultFn(now),
21
+ // Small default — docs fixtures only need a stable integer clock column.
22
+ // (ms `now()` exceeds Postgres INTEGER; ensure DDL maps to BIGINT for apps.)
23
+ createdAt: field.integer().notNull().default(1),
21
24
  });
22
25
 
23
- const db = store.sql("docs-items", { schema: { docs_items: docsItems } });
26
+ /** Shared SQL store tests must pass this to `oke({ stores })` after registry consume. */
27
+ export const docsListItemsStore = store.sql("docs-list-items", {
28
+ schema: { docs_list_items: docsItems },
29
+ });
24
30
 
25
31
  const inSchema = z.object({ title: z.string().min(1), secret: z.string().min(1) });
26
32
  const outSchema = z.object({
@@ -35,7 +41,7 @@ const outSchema = z.object({
35
41
  * `count: "exact"` (offset default) and point to `count: "none"` as the escape
36
42
  * hatch when COUNT(*) is too expensive.
37
43
  */
38
- export const docsAdminTableResource = store.resource(db, docsItems, {
44
+ export const docsAdminTableResource = store.resource(docsListItemsStore, docsItems, {
39
45
  in: inSchema,
40
46
  out: outSchema,
41
47
  list: { mode: "offset", count: "exact", limit: 20 },
@@ -47,7 +53,7 @@ export const docsAdminTableResource = store.resource(db, docsItems, {
47
53
  * Public restricted endpoint — filters off entirely. Any `?col=op.value`
48
54
  * query key that is not a reserved list param fails validation (422).
49
55
  */
50
- export const docsRestrictedResource = store.resource(db, docsItems, {
56
+ export const docsRestrictedResource = store.resource(docsListItemsStore, docsItems, {
51
57
  in: inSchema,
52
58
  out: outSchema,
53
59
  list: { mode: "offset", filter: "none", limit: 20 },
@@ -15,7 +15,11 @@ import { resetFlowSeq } from "../../kernel/flow.ts";
15
15
  import { on, resetBindings } from "../../kernel/on.ts";
16
16
  import { http } from "../../kernel/triggers.ts";
17
17
  import { createTestApp } from "../../test/create-test-app.ts";
18
- import { docsAdminTableResource, docsRestrictedResource } from "./resource-list-docs.fixture.ts";
18
+ import {
19
+ docsAdminTableResource,
20
+ docsListItemsStore,
21
+ docsRestrictedResource,
22
+ } from "./resource-list-docs.fixture.ts";
19
23
 
20
24
  afterEach(() => {
21
25
  resetBindings();
@@ -23,57 +27,73 @@ afterEach(() => {
23
27
  });
24
28
 
25
29
  describe("resource-list-docs fixtures — documented behaviors stay real", () => {
26
- test("filter: \"none\" forbidden filter key 422 ValidationError 'unknown list param'", async () => {
27
- resetBindings();
28
- resetFlowSeq();
30
+ // PGLite first-boot under full-suite load can exceed the default 5s.
31
+ test(
32
+ "filter: \"none\" — forbidden filter key → 422 ValidationError 'unknown list param'",
33
+ async () => {
34
+ resetBindings();
35
+ resetFlowSeq();
29
36
 
30
- const mounted = on(http.resource("/docs-restricted", docsRestrictedResource.all()));
31
- const app = oke({ name: "docs-restricted-test" }).adopt({ docs: mounted });
32
- Object.assign(app.$options, { env: "test" });
37
+ const mounted = on(http.resource("/docs-restricted", docsRestrictedResource.all()));
38
+ const app = oke({ name: "docs-restricted-test" }).adopt({ docs: mounted });
39
+ // Explicit stores — module-level `store.sql` is drained by the first `oke()`
40
+ // (and cleared by the global afterEach registry reset).
41
+ Object.assign(app.$options, { env: "test", stores: [docsListItemsStore] });
33
42
 
34
- const t = await createTestApp(app);
43
+ const t = await createTestApp(app);
35
44
 
36
- const bad = await app.fetch(new Request("http://localhost/docs-restricted?secret=eq.x"));
37
- expect(bad.status).toBe(422);
38
- const badBody = (await bad.json()) as {
39
- data: null;
40
- error: { code: string; data: { issues: Array<{ message: string; path: string[] }> } };
41
- };
42
- expect(badBody.error.code).toBe("ValidationError");
43
- expect(badBody.error.data.issues[0]!.message).toBe('unknown list param "secret"');
44
- expect(badBody.error.data.issues[0]!.path).toEqual(["secret"]);
45
+ const bad = await app.fetch(new Request("http://localhost/docs-restricted?secret=eq.x"));
46
+ expect(bad.status).toBe(422);
47
+ const badBody = (await bad.json()) as {
48
+ data: null;
49
+ error: { code: string; data: { issues: Array<{ message: string; path: string[] }> } };
50
+ };
51
+ expect(badBody.error.code).toBe("ValidationError");
52
+ expect(badBody.error.data.issues[0]!.message).toBe('unknown list param "secret"');
53
+ expect(badBody.error.data.issues[0]!.path).toEqual(["secret"]);
45
54
 
46
- const ok = await app.fetch(new Request("http://localhost/docs-restricted?limit=20"));
47
- expect(ok.status).toBe(200);
55
+ const ok = await app.fetch(new Request("http://localhost/docs-restricted?limit=20"));
56
+ expect(ok.status).toBe(200);
48
57
 
49
- await t.close();
50
- });
58
+ await t.close();
59
+ },
60
+ { timeout: 30_000 },
61
+ );
51
62
 
52
- test('offset + count: "exact" answers meta.total; "none" drops it', async () => {
53
- resetBindings();
54
- resetFlowSeq();
63
+ test(
64
+ 'offset + count: "exact" answers meta.total; "none" drops it',
65
+ async () => {
66
+ resetBindings();
67
+ resetFlowSeq();
55
68
 
56
- const exactMounted = on(http.resource("/docs-admin", docsAdminTableResource.all()));
57
- const app = oke({ name: "docs-admin-test" }).adopt({ docs: exactMounted });
58
- Object.assign(app.$options, { env: "test" });
69
+ const exactMounted = on(http.resource("/docs-admin", docsAdminTableResource.all()));
70
+ const app = oke({ name: "docs-admin-test" }).adopt({ docs: exactMounted });
71
+ Object.assign(app.$options, { env: "test", stores: [docsListItemsStore] });
59
72
 
60
- const t = await createTestApp(app);
61
- await app.fetch(
62
- new Request("http://localhost/docs-admin", {
63
- method: "POST",
64
- headers: { "content-type": "application/json" },
65
- body: JSON.stringify({ title: "one", secret: "s" }),
66
- }),
67
- );
73
+ const t = await createTestApp(app);
74
+ const created = await app.fetch(
75
+ new Request("http://localhost/docs-admin", {
76
+ method: "POST",
77
+ headers: { "content-type": "application/json" },
78
+ body: JSON.stringify({ title: "one", secret: "s" }),
79
+ }),
80
+ );
81
+ expect(created.status).toBe(201);
68
82
 
69
- const exact = await app.fetch(new Request("http://localhost/docs-admin?limit=20"));
70
- expect(exact.status).toBe(200);
71
- const exactBody = (await exact.json()) as {
72
- meta: Record<string, unknown>;
73
- };
74
- expect(exactBody.meta.total).toBe(1);
75
- expect(exactBody.meta.offset).toBe(0);
83
+ const exact = await app.fetch(new Request("http://localhost/docs-admin?limit=20"));
84
+ expect(exact.status).toBe(200);
85
+ const exactBody = (await exact.json()) as {
86
+ data: Array<{ title?: string }>;
87
+ meta: Record<string, unknown>;
88
+ };
89
+ // `count: "exact"` must answer meta.total (COUNT(*)); offset mode keeps meta.offset.
90
+ expect(typeof exactBody.meta.total).toBe("number");
91
+ expect(exactBody.meta.total).toBeGreaterThanOrEqual(1);
92
+ expect(exactBody.meta.offset).toBe(0);
93
+ expect(exactBody.data.some((row) => row.title === "one")).toBe(true);
76
94
 
77
- await t.close();
78
- });
95
+ await t.close();
96
+ },
97
+ { timeout: 30_000 },
98
+ );
79
99
  });