okengine 0.14.1 → 0.16.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.
- package/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +9 -0
- package/site/content/docs/elements/gate.mdx +80 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +84 -27
- package/site/content/docs/elements/vault.mdx +4 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +28 -21
- package/site/content/docs/reference/fx.mdx +29 -18
- package/site/content/docs/reference/security.md +2 -2
- package/src/auth/api-key-sql.test.ts +47 -0
- package/src/auth/api-key-sql.ts +254 -0
- package/src/auth/api-keys.ts +273 -14
- package/src/auth/auth.test.ts +56 -0
- package/src/auth/bindings.ts +14 -5
- package/src/auth/config.ts +5 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/auth/index.ts +25 -1
- package/src/auth/schema.ts +8 -0
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/attach-host-console.test.ts +40 -2
- package/src/cli/attach-host-console.ts +9 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +17 -2
- package/src/compiler/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/access.test.ts +116 -8
- package/src/console/server/access.ts +70 -23
- package/src/console/server/app.ts +1 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/dev-identities.test.ts +127 -0
- package/src/console/server/dev-identities.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +21 -4
- package/src/console/server/flows.ts +233 -98
- package/src/console/server/gates.test.ts +24 -0
- package/src/console/server/gates.ts +8 -21
- package/src/console/server/index.ts +1 -0
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/live.test.ts +26 -1
- package/src/console/server/live.ts +4 -2
- package/src/console/server/operator-db.ts +2 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +37 -55
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-Awq7gYdu.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
- package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
- package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
- package/src/console/ui-next/dist/assets/{skeleton-CL_X0GCj.js → skeleton-D-czQJT6.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-uk4WVboC.js} +1 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
- package/src/console/ui-next/dist/index.html +12 -10
- package/src/console/ui-next/index.html +3 -3
- package/src/console/ui-next/src/client.ts +202 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
- package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
- package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
- package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
- package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
- package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
- package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
- package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
- package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
- package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
- package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
- package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
- package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
- package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
- package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
- package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
- package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
- package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
- package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
- package/src/console/ui-next/src/lib/document-meta.ts +2 -1
- package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
- package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
- package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
- package/src/console/ui-next/src/lib/shortcut.ts +3 -0
- package/src/console/ui-next/src/router.tsx +10 -1
- package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
- package/src/console/ui-next/ui-next-seed-store.ts +43 -15
- package/src/console/xss.gate.test.ts +1 -0
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +38 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +18 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +260 -41
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-select.types.test.ts +71 -0
- package/src/elements/store/sql-session.ts +135 -35
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +17 -0
- package/src/http.ts +20 -3
- package/src/index.ts +11 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/api-key-host-persist.test.ts +143 -0
- package/src/kernel/app.ts +145 -16
- package/src/kernel/auth-resolve.ts +10 -5
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +5 -5
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-auth-keys.test.ts +88 -0
- package/src/kernel/fx-auth-keys.ts +198 -0
- package/src/kernel/fx.test.ts +17 -1
- package/src/kernel/fx.ts +59 -18
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +4 -4
- package/src/kernel/pipeline-api-key.test.ts +192 -7
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/pipeline.ts +4 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/types.ts +22 -3
- package/src/release/build-lib.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +8 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-BanhLsEY.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-bqkxMrJH.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DDCRmG62.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-HjTnVQoR.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/link-DF7SZ9Ek.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-BEZDzyYh.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
|
@@ -244,7 +244,9 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
|
-
* Write AI model declarations into `src/core.ts`
|
|
247
|
+
* Write AI model declarations into `src/core/ai.ts` when that split exists
|
|
248
|
+
* (so a thin `src/core.ts` barrel stays a re-export), else `src/core.ts`,
|
|
249
|
+
* else legacy `src/core/index.ts` + sidecar.
|
|
248
250
|
*
|
|
249
251
|
* @param cwd - Project root
|
|
250
252
|
* @param input - Setup choices
|
|
@@ -252,9 +254,25 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
252
254
|
*/
|
|
253
255
|
function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
|
|
254
256
|
const rendered = renderAiTs(input);
|
|
257
|
+
const coreAiPath = join(cwd, "src", "core", "ai.ts");
|
|
255
258
|
const coreTsPath = join(cwd, "src", "core.ts");
|
|
256
259
|
const legacyIndex = join(cwd, "src", "core", "index.ts");
|
|
257
260
|
|
|
261
|
+
if (existsSync(coreAiPath)) {
|
|
262
|
+
const existing = readFileSync(coreAiPath, "utf8");
|
|
263
|
+
if (hasAiModels(existing)) {
|
|
264
|
+
const withPrompt = ensureSummarizeNotePrompt(existing);
|
|
265
|
+
if (withPrompt !== existing) {
|
|
266
|
+
writeFileSync(coreAiPath, withPrompt, "utf8");
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
writeFileSync(coreAiPath, mergeAiIntoCore(existing, rendered), "utf8");
|
|
270
|
+
}
|
|
271
|
+
ensureCoreBarrelExportsAi(cwd);
|
|
272
|
+
ensureCoreImported(cwd);
|
|
273
|
+
return coreAiPath;
|
|
274
|
+
}
|
|
275
|
+
|
|
258
276
|
// Folder layout still in the wild — keep writing a sidecar.
|
|
259
277
|
if (!existsSync(coreTsPath) && existsSync(legacyIndex)) {
|
|
260
278
|
const aiTsPath = join(cwd, "src", "core", "ai.ts");
|
|
@@ -364,6 +382,19 @@ export function ensureNamedOkengineImport(source: string, name: string): string
|
|
|
364
382
|
return source.replace(re, `import { ${sorted.join(", ")} } from "okengine";`);
|
|
365
383
|
}
|
|
366
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Keep a `src/core.ts` barrel re-exporting `./core/ai.ts` after a split write.
|
|
387
|
+
*
|
|
388
|
+
* @param cwd - Project root
|
|
389
|
+
*/
|
|
390
|
+
function ensureCoreBarrelExportsAi(cwd: string): void {
|
|
391
|
+
const coreTsPath = join(cwd, "src", "core.ts");
|
|
392
|
+
if (!existsSync(coreTsPath)) return;
|
|
393
|
+
const src = readFileSync(coreTsPath, "utf8");
|
|
394
|
+
if (/from\s+["']\.\/core\/ai/.test(src)) return;
|
|
395
|
+
writeFileSync(coreTsPath, `${src.trimEnd()}\nexport * from "./core/ai.ts";\n`, "utf8");
|
|
396
|
+
}
|
|
397
|
+
|
|
367
398
|
/**
|
|
368
399
|
* Ensure `src/app.ts` loads `@/core` / `./core` so merged AI registers.
|
|
369
400
|
*
|
|
@@ -28,7 +28,7 @@ describe("attachHostToConsole", () => {
|
|
|
28
28
|
import { oke, on, flow, http, gate, store } from ${JSON.stringify(OKE)};
|
|
29
29
|
const db = store.sql("db");
|
|
30
30
|
export const ping = on(
|
|
31
|
-
http.get("/ping").
|
|
31
|
+
http.get("/ping").public(),
|
|
32
32
|
flow("main.ping", { do: () => ({ ok: true }) }),
|
|
33
33
|
);
|
|
34
34
|
export const app = oke({
|
|
@@ -69,6 +69,7 @@ export const app = oke({
|
|
|
69
69
|
expect(ingested.some((row) => row.event?.flow === "main.ping")).toBe(true);
|
|
70
70
|
await attached!.stop();
|
|
71
71
|
expect(state.invokeUserFlow).toBeNull();
|
|
72
|
+
expect(state.storeRuntime).toBeNull();
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
test("binds host aiRuntime so Console projection sees a journaled ask", async () => {
|
|
@@ -81,7 +82,7 @@ import { oke, on, flow, http, gate, ai } from ${JSON.stringify(OKE)};
|
|
|
81
82
|
const smart = ai.model("smart", { provider: "mock" });
|
|
82
83
|
smart.prompt("ticket-triage", { version: 2 });
|
|
83
84
|
export const ping = on(
|
|
84
|
-
http.get("/ping").
|
|
85
|
+
http.get("/ping").public(),
|
|
85
86
|
flow("main.ping", {
|
|
86
87
|
effects: { asks: ["ticket-triage"] },
|
|
87
88
|
do: (_input, fx) => fx.ask("ticket-triage", { subject: "hello" }),
|
|
@@ -122,6 +123,43 @@ export const app = oke({
|
|
|
122
123
|
await attached!.stop();
|
|
123
124
|
});
|
|
124
125
|
|
|
126
|
+
test("Console Access uses the host apiKeyStore instance (not Console SQL)", async () => {
|
|
127
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-attach-keys-"));
|
|
128
|
+
const entry = join(dir, "app.ts");
|
|
129
|
+
await writeFile(
|
|
130
|
+
entry,
|
|
131
|
+
`
|
|
132
|
+
import { oke, on, flow, http, gate, store } from ${JSON.stringify(OKE)};
|
|
133
|
+
const db = store.sql("db");
|
|
134
|
+
const member = gate.policy("member", ({ auth }) => !!auth.verified);
|
|
135
|
+
export const ping = on(
|
|
136
|
+
http.get("/ping").gate(member),
|
|
137
|
+
flow("main.ping", { do: () => ({ ok: true }) }),
|
|
138
|
+
);
|
|
139
|
+
export const app = oke({
|
|
140
|
+
name: "attach-keys",
|
|
141
|
+
stores: [db],
|
|
142
|
+
gate: {
|
|
143
|
+
auth: { secret: "attach-host-key-pepper", http: false },
|
|
144
|
+
policies: [member],
|
|
145
|
+
},
|
|
146
|
+
}).adopt({ ping });
|
|
147
|
+
`,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const consoleKeys = { keys: new Map(), persist: async () => {} };
|
|
151
|
+
const state = {
|
|
152
|
+
invokeUserFlow: null,
|
|
153
|
+
storeRuntime: null,
|
|
154
|
+
apiKeys: consoleKeys,
|
|
155
|
+
} as unknown as ConsoleState;
|
|
156
|
+
const attached = await attachHostToConsole({ entry, cwd: dir, state });
|
|
157
|
+
expect(attached).not.toBeNull();
|
|
158
|
+
expect(state.apiKeys).not.toBe(consoleKeys);
|
|
159
|
+
expect(state.apiKeys.persist).not.toBe(consoleKeys.persist);
|
|
160
|
+
await attached!.stop();
|
|
161
|
+
});
|
|
162
|
+
|
|
125
163
|
test("returns null when the entry is not a bootable app", async () => {
|
|
126
164
|
const dir = await mkdtemp(join(tmpdir(), "oke-attach-empty-"));
|
|
127
165
|
const entry = join(dir, "app.ts");
|
|
@@ -69,10 +69,19 @@ export async function attachHostToConsole(
|
|
|
69
69
|
if (vault) options.state.vaultRuntime = vault;
|
|
70
70
|
const ai = host.elements?.ai ?? host.bootResult?.ai;
|
|
71
71
|
if (ai) bindAiRuntime(options.state, ai);
|
|
72
|
+
const hostKeys = host.apiKeys;
|
|
73
|
+
if (hostKeys) {
|
|
74
|
+
// Host `gate.auth.apiKeyStore` is the durable store (`oke_api_keys` on
|
|
75
|
+
// `store.sql()`). Console displays that instance — never the reverse.
|
|
76
|
+
options.state.apiKeys = hostKeys;
|
|
77
|
+
}
|
|
72
78
|
|
|
73
79
|
return {
|
|
74
80
|
stop: async () => {
|
|
75
81
|
options.state.invokeUserFlow = null;
|
|
82
|
+
options.state.storeRuntime = null;
|
|
83
|
+
options.state.vaultRuntime = null;
|
|
84
|
+
options.state.aiRuntime = null;
|
|
76
85
|
await host.stop();
|
|
77
86
|
},
|
|
78
87
|
};
|
package/src/cli/build.test.ts
CHANGED
|
@@ -32,8 +32,10 @@ describe("oke build — .adopt() barrel regeneration", () => {
|
|
|
32
32
|
expect(code).toBe(0);
|
|
33
33
|
|
|
34
34
|
const after = await readFile(generatedPath, "utf8");
|
|
35
|
-
expect(after).toContain('
|
|
36
|
-
expect(after).toContain('
|
|
35
|
+
expect(after).toContain('import * as main from "./main/index.ts";');
|
|
36
|
+
expect(after).toContain('import * as notes from "./notes/index.ts";');
|
|
37
|
+
expect(after).toContain("export { main }");
|
|
38
|
+
expect(after).toContain("export { notes }");
|
|
37
39
|
} finally {
|
|
38
40
|
await rm(root, { recursive: true, force: true });
|
|
39
41
|
}
|
package/src/cli/db-seed.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `oke db seed` — load `defineSeed`, boot the app store, run essential +
|
|
3
|
-
* env-selected category (`dev` | `prod`).
|
|
3
|
+
* env-selected category (`dev` | `prod`). Tallies SQL upserts and `kv.set`.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
|
-
import { isCancel, text } from "@clack/prompts";
|
|
7
|
+
import { intro, isCancel, outro, text } from "@clack/prompts";
|
|
8
8
|
import type { ConfigEnv, OkeConfig } from "../config/index.ts";
|
|
9
9
|
import {
|
|
10
10
|
normalizeSeedFns,
|
|
@@ -25,11 +25,13 @@ import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "./exit.ts";
|
|
|
25
25
|
import { loadOkeConfig } from "./load-config.ts";
|
|
26
26
|
import { resolveDevSqlEnv } from "./resolve-dev-sql-env.ts";
|
|
27
27
|
|
|
28
|
-
/** Per-function upsert outcome counts. */
|
|
28
|
+
/** Per-function upsert / KV-set outcome counts. */
|
|
29
29
|
export interface SeedTally {
|
|
30
30
|
upserted: number;
|
|
31
31
|
changed: number;
|
|
32
32
|
alreadyExisted: number;
|
|
33
|
+
/** `fx.store(kv).set` calls (cache or durable). */
|
|
34
|
+
keys: number;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/** Options for {@link runSeed}. */
|
|
@@ -56,6 +58,11 @@ export interface SeedOptions {
|
|
|
56
58
|
*/
|
|
57
59
|
readonly confirmEnv?: (env: ConfigEnv, target: string) => Promise<boolean>;
|
|
58
60
|
readonly stdinIsTTY?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Clack `intro` / `outro` for the standalone `oke db seed` path.
|
|
63
|
+
* Default: on when stdin is a TTY.
|
|
64
|
+
*/
|
|
65
|
+
readonly intro?: boolean;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
/**
|
|
@@ -127,12 +134,13 @@ export async function runSeedFns(
|
|
|
127
134
|
): Promise<void> {
|
|
128
135
|
for (let i = 0; i < fns.length; i++) {
|
|
129
136
|
const fn = fns[i]!;
|
|
130
|
-
const tally: SeedTally = { upserted: 0, changed: 0, alreadyExisted: 0 };
|
|
137
|
+
const tally: SeedTally = { upserted: 0, changed: 0, alreadyExisted: 0, keys: 0 };
|
|
131
138
|
const instrumented = instrumentFxUpserts(fx, tally);
|
|
132
139
|
await fn(instrumented);
|
|
133
140
|
const label = fn.name.length > 0 ? fn.name : String(i);
|
|
141
|
+
const kv = tally.keys > 0 ? ` · keys ${tally.keys}` : "";
|
|
134
142
|
write(
|
|
135
|
-
`oke db seed: ${category} ${label} — upserted ${tally.upserted} · changed ${tally.changed} · already-existed ${tally.alreadyExisted}\n`,
|
|
143
|
+
`oke db seed: ${category} ${label} — upserted ${tally.upserted} · changed ${tally.changed} · already-existed ${tally.alreadyExisted}${kv}\n`,
|
|
136
144
|
);
|
|
137
145
|
}
|
|
138
146
|
}
|
|
@@ -233,7 +241,13 @@ export async function bootSeedFx(
|
|
|
233
241
|
if (!app) {
|
|
234
242
|
throw new Error(`oke db seed: no oke() app export in ${entryAbs}`);
|
|
235
243
|
}
|
|
236
|
-
await app.boot({
|
|
244
|
+
await app.boot({
|
|
245
|
+
env,
|
|
246
|
+
startScheduler: false,
|
|
247
|
+
config: config ?? undefined,
|
|
248
|
+
rootDir: cwd,
|
|
249
|
+
docker: process.env.OKE_DOCKER === "1",
|
|
250
|
+
});
|
|
237
251
|
const storeRuntime = app.elements?.store ?? app.bootResult?.store;
|
|
238
252
|
if (!storeRuntime) {
|
|
239
253
|
throw new Error(
|
|
@@ -243,6 +257,7 @@ export async function bootSeedFx(
|
|
|
243
257
|
const fx = createFx({
|
|
244
258
|
flow: "oke.db.seed",
|
|
245
259
|
storeRuntime,
|
|
260
|
+
rlsBypass: true,
|
|
246
261
|
});
|
|
247
262
|
return {
|
|
248
263
|
fx,
|
|
@@ -262,6 +277,14 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
262
277
|
const cwd = options.cwd ?? process.cwd();
|
|
263
278
|
const loaded = await loadOkeConfig(cwd).catch(() => null);
|
|
264
279
|
const env = options.env ?? (await resolveDevSqlEnv(cwd));
|
|
280
|
+
const tty = options.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
281
|
+
const useIntro = options.intro !== false && tty;
|
|
282
|
+
if (useIntro) intro("oke db seed");
|
|
283
|
+
|
|
284
|
+
const finish = (code: number, message: string): number => {
|
|
285
|
+
if (useIntro) outro(message);
|
|
286
|
+
return code;
|
|
287
|
+
};
|
|
265
288
|
|
|
266
289
|
let targetLabel = String(env);
|
|
267
290
|
try {
|
|
@@ -274,7 +297,6 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
274
297
|
|
|
275
298
|
if (env === "dev" || env === "prod") {
|
|
276
299
|
if (options.force !== true) {
|
|
277
|
-
const tty = options.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
278
300
|
const confirm =
|
|
279
301
|
options.confirmEnv ??
|
|
280
302
|
(tty
|
|
@@ -288,7 +310,7 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
288
310
|
const ok = await confirm(env, targetLabel);
|
|
289
311
|
if (!ok) {
|
|
290
312
|
write("oke db seed: cancelled\n");
|
|
291
|
-
return EXIT_USAGE;
|
|
313
|
+
return finish(EXIT_USAGE, "Cancelled.");
|
|
292
314
|
}
|
|
293
315
|
}
|
|
294
316
|
}
|
|
@@ -308,10 +330,10 @@ export async function runSeed(options: SeedOptions = {}): Promise<number> {
|
|
|
308
330
|
stop = session.stop;
|
|
309
331
|
await executeSeedDef(def, env, session.fx, write);
|
|
310
332
|
write("oke db seed: ok\n");
|
|
311
|
-
return EXIT_OK;
|
|
333
|
+
return finish(EXIT_OK, "oke db seed: ok");
|
|
312
334
|
} catch (err) {
|
|
313
335
|
write(`oke db seed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
314
|
-
return EXIT_RUNTIME;
|
|
336
|
+
return finish(EXIT_RUNTIME, "oke db seed: failed");
|
|
315
337
|
} finally {
|
|
316
338
|
if (stop) await stop().catch(() => {});
|
|
317
339
|
}
|
|
@@ -374,14 +396,18 @@ function instrumentFxUpserts(fx: Fx, tally: SeedTally): Fx {
|
|
|
374
396
|
|
|
375
397
|
function instrumentUpsertHandle<T>(handle: T, tally: SeedTally): T {
|
|
376
398
|
if (!handle || typeof handle !== "object") return handle;
|
|
377
|
-
const h = handle as {
|
|
378
|
-
|
|
379
|
-
|
|
399
|
+
const h = handle as {
|
|
400
|
+
upsert?: (...args: unknown[]) => Promise<{ status: UpsertStatus }>;
|
|
401
|
+
set?: (...args: unknown[]) => Promise<unknown>;
|
|
402
|
+
};
|
|
403
|
+
const origUpsert = typeof h.upsert === "function" ? h.upsert.bind(h) : undefined;
|
|
404
|
+
const origSet = typeof h.set === "function" ? h.set.bind(h) : undefined;
|
|
405
|
+
if (!origUpsert && !origSet) return handle;
|
|
380
406
|
return new Proxy(handle as object, {
|
|
381
407
|
get(target, prop, receiver) {
|
|
382
|
-
if (prop === "upsert") {
|
|
408
|
+
if (prop === "upsert" && origUpsert) {
|
|
383
409
|
return async (...args: unknown[]) => {
|
|
384
|
-
const result = await
|
|
410
|
+
const result = await origUpsert(...args);
|
|
385
411
|
const status =
|
|
386
412
|
result && typeof result === "object" && "status" in result
|
|
387
413
|
? (result as { status: UpsertStatus }).status
|
|
@@ -392,6 +418,13 @@ function instrumentUpsertHandle<T>(handle: T, tally: SeedTally): T {
|
|
|
392
418
|
return result;
|
|
393
419
|
};
|
|
394
420
|
}
|
|
421
|
+
if (prop === "set" && origSet) {
|
|
422
|
+
return async (...args: unknown[]) => {
|
|
423
|
+
const result = await origSet(...args);
|
|
424
|
+
tally.keys += 1;
|
|
425
|
+
return result;
|
|
426
|
+
};
|
|
427
|
+
}
|
|
395
428
|
return Reflect.get(target, prop, receiver);
|
|
396
429
|
},
|
|
397
430
|
}) as T;
|
package/src/cli/db.test.ts
CHANGED
|
@@ -11,9 +11,11 @@ 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";
|
|
13
13
|
import {
|
|
14
|
+
createHintsForRenameOrCreate,
|
|
14
15
|
dbCli,
|
|
15
16
|
emitAbstractSchemaPrestep,
|
|
16
17
|
executeSeedDef,
|
|
18
|
+
formatKitError,
|
|
17
19
|
runSeedFns,
|
|
18
20
|
runDb,
|
|
19
21
|
runGenerate,
|
|
@@ -116,6 +118,13 @@ describe("oke db", () => {
|
|
|
116
118
|
expect(out.join("")).toContain("0001_init.sql");
|
|
117
119
|
});
|
|
118
120
|
|
|
121
|
+
test("formatKitError joins code message and sql", () => {
|
|
122
|
+
expect(formatKitError({ code: "query_error", message: "boom", sql: "CREATE TABLE t" })).toBe(
|
|
123
|
+
" — query_error — boom — CREATE TABLE t",
|
|
124
|
+
);
|
|
125
|
+
expect(formatKitError(undefined)).toBe("");
|
|
126
|
+
});
|
|
127
|
+
|
|
119
128
|
test("missing_hints exits runtime", async () => {
|
|
120
129
|
const out: string[] = [];
|
|
121
130
|
const code = await runDb("push", {
|
|
@@ -131,6 +140,75 @@ describe("oke db", () => {
|
|
|
131
140
|
expect(out.join("")).toContain("DROP TABLE leftover");
|
|
132
141
|
});
|
|
133
142
|
|
|
143
|
+
test("createHintsForRenameOrCreate maps rename_or_create only", () => {
|
|
144
|
+
expect(
|
|
145
|
+
createHintsForRenameOrCreate([
|
|
146
|
+
{ type: "rename_or_create", kind: "table", entity: ["public", "activity"] },
|
|
147
|
+
{
|
|
148
|
+
type: "confirm_data_loss",
|
|
149
|
+
kind: "table",
|
|
150
|
+
entity: ["public", "old"],
|
|
151
|
+
reason: "non_empty",
|
|
152
|
+
},
|
|
153
|
+
]),
|
|
154
|
+
).toEqual([{ type: "create", kind: "table", entity: ["public", "activity"] }]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("push retries rename_or_create as create", async () => {
|
|
158
|
+
const hints: unknown[] = [];
|
|
159
|
+
const out: string[] = [];
|
|
160
|
+
const code = await runPush("/tmp/drizzle.config.ts", (t) => out.push(t), {
|
|
161
|
+
skipEmit: true,
|
|
162
|
+
pushFn: async (opts) => {
|
|
163
|
+
hints.push(opts.hints);
|
|
164
|
+
if (!opts.hints) {
|
|
165
|
+
return {
|
|
166
|
+
status: "missing_hints",
|
|
167
|
+
unresolved: [{ type: "rename_or_create", kind: "table", entity: ["public", "notes"] }],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return { status: "ok" };
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
expect(code).toBe(EXIT_OK);
|
|
174
|
+
expect(hints[1]).toEqual([{ type: "create", kind: "table", entity: ["public", "notes"] }]);
|
|
175
|
+
expect(out.join("")).toContain("creating 1 new object");
|
|
176
|
+
expect(out.join("")).toContain("oke db push: ok");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("push does not retry confirm_data_loss-only missing_hints", async () => {
|
|
180
|
+
let calls = 0;
|
|
181
|
+
const code = await runPush("/tmp/drizzle.config.ts", () => {}, {
|
|
182
|
+
skipEmit: true,
|
|
183
|
+
pushFn: async () => {
|
|
184
|
+
calls += 1;
|
|
185
|
+
return {
|
|
186
|
+
status: "missing_hints",
|
|
187
|
+
unresolved: [{ type: "confirm_data_loss", kind: "table", entity: ["public", "notes"] }],
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
expect(code).toBe(EXIT_RUNTIME);
|
|
192
|
+
expect(calls).toBe(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("explain does not auto-create on missing_hints", async () => {
|
|
196
|
+
let calls = 0;
|
|
197
|
+
const code = await runPush("/tmp/drizzle.config.ts", () => {}, {
|
|
198
|
+
skipEmit: true,
|
|
199
|
+
explain: true,
|
|
200
|
+
pushFn: async () => {
|
|
201
|
+
calls += 1;
|
|
202
|
+
return {
|
|
203
|
+
status: "missing_hints",
|
|
204
|
+
unresolved: [{ type: "rename_or_create", kind: "table", entity: ["public", "notes"] }],
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
expect(code).toBe(EXIT_RUNTIME);
|
|
209
|
+
expect(calls).toBe(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
134
212
|
test("resolveDrizzleConfigPath uses db.config from oke.config", async () => {
|
|
135
213
|
const dir = await mkdtemp(join(tmpdir(), "oke-db-"));
|
|
136
214
|
await writeFile(
|
|
@@ -551,6 +629,32 @@ describe("oke db seed", () => {
|
|
|
551
629
|
expect(out.join("")).toContain("dev volume — upserted 0 · changed 1 · already-existed 0");
|
|
552
630
|
});
|
|
553
631
|
|
|
632
|
+
test("runSeedFns tallies kv.set as keys", async () => {
|
|
633
|
+
const out: string[] = [];
|
|
634
|
+
const fx = {
|
|
635
|
+
store: () => ({
|
|
636
|
+
set: async () => undefined,
|
|
637
|
+
}),
|
|
638
|
+
} as unknown as ReturnType<typeof createFx>;
|
|
639
|
+
await runSeedFns(
|
|
640
|
+
"dev",
|
|
641
|
+
[
|
|
642
|
+
async function volume(seedFx) {
|
|
643
|
+
const kv = seedFx.store("kv" as never) as unknown as {
|
|
644
|
+
set: (key: string, value: unknown) => Promise<void>;
|
|
645
|
+
};
|
|
646
|
+
await kv.set("ENG-12", { title: "SSO" });
|
|
647
|
+
await kv.set("ENG-14", { title: "Polish" });
|
|
648
|
+
},
|
|
649
|
+
],
|
|
650
|
+
fx,
|
|
651
|
+
(t) => out.push(t),
|
|
652
|
+
);
|
|
653
|
+
expect(out.join("")).toContain(
|
|
654
|
+
"dev volume — upserted 0 · changed 0 · already-existed 0 · keys 2",
|
|
655
|
+
);
|
|
656
|
+
});
|
|
657
|
+
|
|
554
658
|
test("executeSeedDef is the category source of truth", async () => {
|
|
555
659
|
const order: string[] = [];
|
|
556
660
|
const out: string[] = [];
|
package/src/cli/db.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
|
+
import type { Hint as DrizzleKitHint } from "drizzle-kit/cli";
|
|
11
12
|
import {
|
|
12
13
|
dialectFromDriverId,
|
|
13
14
|
maybeEmitDomainSchema,
|
|
@@ -17,6 +18,7 @@ import { loadPluginTablesFromAppEntry } from "../elements/store/load-plugin-tabl
|
|
|
17
18
|
import { resolveDriverId, type ConfigEnv, type OkeConfig } from "../config/index.ts";
|
|
18
19
|
import type { TableContribution } from "../kernel/plugin.ts";
|
|
19
20
|
import { runSeed, type SeedOptions } from "./db-seed.ts";
|
|
21
|
+
import { installOkeRlsHelpers as runOkeRlsHelperStatements } from "../drivers/pg-rls.ts";
|
|
20
22
|
import { resolveDrizzleKitEnv } from "./drizzle-env.ts";
|
|
21
23
|
import { resolveDevSqlEnv } from "./resolve-dev-sql-env.ts";
|
|
22
24
|
import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "./exit.ts";
|
|
@@ -90,7 +92,10 @@ export interface DbOptions {
|
|
|
90
92
|
readonly config?: string;
|
|
91
93
|
readonly write?: (text: string) => void;
|
|
92
94
|
/** Injectable push (tests). */
|
|
93
|
-
readonly pushFn?: (opts: {
|
|
95
|
+
readonly pushFn?: (opts: {
|
|
96
|
+
config: string;
|
|
97
|
+
hints?: readonly DbKitHint[];
|
|
98
|
+
}) => Promise<DbKitResult>;
|
|
94
99
|
/** Injectable generate (tests). */
|
|
95
100
|
readonly generateFn?: (opts: {
|
|
96
101
|
config: string;
|
|
@@ -129,13 +134,61 @@ export interface DbOptions {
|
|
|
129
134
|
readonly stdinIsTTY?: boolean;
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
/** drizzle-kit SDK hint — create vs rename vs confirm data loss. */
|
|
138
|
+
export type DbKitHint =
|
|
139
|
+
| {
|
|
140
|
+
readonly type: "create";
|
|
141
|
+
readonly kind: string;
|
|
142
|
+
readonly entity: readonly string[];
|
|
143
|
+
}
|
|
144
|
+
| {
|
|
145
|
+
readonly type: "rename";
|
|
146
|
+
readonly kind: string;
|
|
147
|
+
readonly from: readonly string[];
|
|
148
|
+
readonly to: readonly string[];
|
|
149
|
+
}
|
|
150
|
+
| {
|
|
151
|
+
readonly type: "confirm_data_loss";
|
|
152
|
+
readonly kind: string;
|
|
153
|
+
readonly entity: readonly string[];
|
|
154
|
+
};
|
|
155
|
+
|
|
132
156
|
/** Minimal drizzle-kit envelope we handle. */
|
|
133
157
|
export interface DbKitResult {
|
|
134
158
|
readonly status: "ok" | "no_changes" | "missing_hints" | "error" | string;
|
|
135
|
-
readonly error?: {
|
|
159
|
+
readonly error?: {
|
|
160
|
+
readonly code?: string;
|
|
161
|
+
readonly message?: string;
|
|
162
|
+
readonly sql?: string;
|
|
163
|
+
};
|
|
136
164
|
readonly unresolved?: readonly unknown[];
|
|
137
165
|
}
|
|
138
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Auto-resolve drizzle-kit `rename_or_create` as create (dev push).
|
|
169
|
+
* Leaves `confirm_data_loss` for the caller to fail on.
|
|
170
|
+
*
|
|
171
|
+
* @param unresolved - Kit `missing_hints` payload
|
|
172
|
+
*/
|
|
173
|
+
export function createHintsForRenameOrCreate(
|
|
174
|
+
unresolved: readonly unknown[] | undefined,
|
|
175
|
+
): DbKitHint[] {
|
|
176
|
+
const hints: DbKitHint[] = [];
|
|
177
|
+
if (!unresolved) return hints;
|
|
178
|
+
for (const item of unresolved) {
|
|
179
|
+
if (!item || typeof item !== "object") continue;
|
|
180
|
+
const rec = item as Record<string, unknown>;
|
|
181
|
+
if (rec.type !== "rename_or_create") continue;
|
|
182
|
+
if (typeof rec.kind !== "string" || !Array.isArray(rec.entity)) continue;
|
|
183
|
+
hints.push({
|
|
184
|
+
type: "create",
|
|
185
|
+
kind: rec.kind,
|
|
186
|
+
entity: rec.entity.map((part) => String(part)),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return hints;
|
|
190
|
+
}
|
|
191
|
+
|
|
139
192
|
/**
|
|
140
193
|
* Resolve the drizzle config path for a project.
|
|
141
194
|
*
|
|
@@ -295,19 +348,30 @@ export async function runPush(
|
|
|
295
348
|
|
|
296
349
|
const pushFn =
|
|
297
350
|
options.pushFn ??
|
|
298
|
-
(async (opts: { config: string }) => {
|
|
351
|
+
(async (opts: { config: string; hints?: readonly DbKitHint[] }) => {
|
|
299
352
|
const { push } = await import("drizzle-kit/cli");
|
|
300
353
|
return (await push({
|
|
301
354
|
config: opts.config,
|
|
302
355
|
...(options.explain ? { explain: true } : {}),
|
|
356
|
+
...(opts.hints && opts.hints.length > 0
|
|
357
|
+
? { hints: [...opts.hints] as DrizzleKitHint[] }
|
|
358
|
+
: {}),
|
|
303
359
|
})) as DbKitResult;
|
|
304
360
|
});
|
|
305
361
|
|
|
306
362
|
let result: DbKitResult;
|
|
307
363
|
try {
|
|
308
|
-
result = await withDrizzleKitEnv(cwd, loaded?.config, env, () =>
|
|
309
|
-
|
|
310
|
-
|
|
364
|
+
result = await withDrizzleKitEnv(cwd, loaded?.config, env, async () => {
|
|
365
|
+
await installOkeRlsHelpers(write);
|
|
366
|
+
const first = await pushFn({ config: configPath });
|
|
367
|
+
if (options.explain || first.status !== "missing_hints") return first;
|
|
368
|
+
const createHints = createHintsForRenameOrCreate(first.unresolved);
|
|
369
|
+
if (createHints.length === 0) return first;
|
|
370
|
+
write(
|
|
371
|
+
`oke db push: creating ${createHints.length} new ${createHints.length === 1 ? "object" : "objects"}\n`,
|
|
372
|
+
);
|
|
373
|
+
return pushFn({ config: configPath, hints: createHints });
|
|
374
|
+
});
|
|
311
375
|
} catch (err) {
|
|
312
376
|
write(`oke db push: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
313
377
|
write(" → install drizzle-kit and ensure drizzle.config.ts exists\n");
|
|
@@ -402,7 +466,9 @@ export async function runMigrate(
|
|
|
402
466
|
});
|
|
403
467
|
|
|
404
468
|
const { overlay } = await resolveDrizzleKitEnv(cwd, loaded?.config, env);
|
|
469
|
+
const restore = applyDrizzleEnvOverlay(overlay);
|
|
405
470
|
try {
|
|
471
|
+
await installOkeRlsHelpers(write);
|
|
406
472
|
const code = await migrateFn({
|
|
407
473
|
config: configPath,
|
|
408
474
|
cwd,
|
|
@@ -417,6 +483,8 @@ export async function runMigrate(
|
|
|
417
483
|
} catch (err) {
|
|
418
484
|
write(`oke db migrate: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
419
485
|
return EXIT_RUNTIME;
|
|
486
|
+
} finally {
|
|
487
|
+
restore();
|
|
420
488
|
}
|
|
421
489
|
}
|
|
422
490
|
|
|
@@ -523,7 +591,7 @@ Not the same as \`oke schema generate\` (core/plugin stub tables).
|
|
|
523
591
|
push Apply schema to the live local DB (dev; no migration files)
|
|
524
592
|
generate Write versioned SQL under drizzle/ for review
|
|
525
593
|
migrate Apply generated migrations (explicit; never automatic in prod)
|
|
526
|
-
seed Run defineSeed (essential + env category)
|
|
594
|
+
seed Run defineSeed (essential + env category) — standard CLI path
|
|
527
595
|
studio Open drizzle-kit Studio (long-running)
|
|
528
596
|
|
|
529
597
|
--env Override config env (dev|test|prod)
|
|
@@ -571,6 +639,39 @@ Not the same as \`oke schema generate\` (core/plugin stub tables).
|
|
|
571
639
|
return runDb(sub, { config, env, force, entry });
|
|
572
640
|
}
|
|
573
641
|
|
|
642
|
+
/**
|
|
643
|
+
* Install `oke.gate()` / `oke.user()` / `oke.has_scope()` before drizzle-kit
|
|
644
|
+
* so `CREATE POLICY … oke.gate()` succeeds.
|
|
645
|
+
*
|
|
646
|
+
* @param write - Output
|
|
647
|
+
*/
|
|
648
|
+
async function installOkeRlsHelpers(write: (text: string) => void): Promise<void> {
|
|
649
|
+
const url = process.env.DATABASE_URL ?? process.env.OKE_STORE_SQL_URL;
|
|
650
|
+
const pgliteUrl = process.env.OKE_PGLITE_URL;
|
|
651
|
+
try {
|
|
652
|
+
if (pgliteUrl && !url) {
|
|
653
|
+
const { connectPglite } = await import("../drivers/pglite.ts");
|
|
654
|
+
const conn = await connectPglite({ url: pgliteUrl });
|
|
655
|
+
try {
|
|
656
|
+
await runOkeRlsHelperStatements((sql) => conn.exec(sql));
|
|
657
|
+
} finally {
|
|
658
|
+
await conn.close();
|
|
659
|
+
}
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (!url) return;
|
|
663
|
+
const { connectPostgres } = await import("../drivers/postgres.ts");
|
|
664
|
+
const conn = await connectPostgres({ url });
|
|
665
|
+
try {
|
|
666
|
+
await runOkeRlsHelperStatements((sql) => conn.exec(sql));
|
|
667
|
+
} finally {
|
|
668
|
+
await conn.close();
|
|
669
|
+
}
|
|
670
|
+
} catch (err) {
|
|
671
|
+
write(`oke db: oke.* helpers skipped — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
574
675
|
function parseConfigEnv(value: string | undefined): ConfigEnv | undefined {
|
|
575
676
|
if (value === "dev" || value === "test" || value === "prod") return value;
|
|
576
677
|
// Soft-compat for older CLI flags / docs.
|
|
@@ -616,8 +717,19 @@ function reportKitResult(verb: string, result: DbKitResult, write: (text: string
|
|
|
616
717
|
if (detail) write(detail);
|
|
617
718
|
return EXIT_RUNTIME;
|
|
618
719
|
}
|
|
619
|
-
write(
|
|
620
|
-
`oke db ${verb}: ${result.status}${result.error?.message ? ` — ${result.error.message}` : ""}\n`,
|
|
621
|
-
);
|
|
720
|
+
write(`oke db ${verb}: ${result.status}${formatKitError(result.error)}\n`);
|
|
622
721
|
return EXIT_RUNTIME;
|
|
623
722
|
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Format a drizzle-kit `error` object for the CLI line.
|
|
726
|
+
*
|
|
727
|
+
* @param error - Kit envelope error
|
|
728
|
+
*/
|
|
729
|
+
export function formatKitError(error: DbKitResult["error"] | undefined): string {
|
|
730
|
+
if (!error) return "";
|
|
731
|
+
const parts = [error.code, error.message, error.sql].filter(
|
|
732
|
+
(part): part is string => typeof part === "string" && part.length > 0,
|
|
733
|
+
);
|
|
734
|
+
return parts.length > 0 ? ` — ${parts.join(" — ")}` : "";
|
|
735
|
+
}
|