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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -174,10 +174,9 @@
|
|
|
174
174
|
"d3-array": "^3.2.4",
|
|
175
175
|
"d3-shape": "^3.2.0",
|
|
176
176
|
"date-fns": "^4.1.0",
|
|
177
|
-
"drizzle-kit": "1.0.0-rc.
|
|
178
|
-
"drizzle-orm": "1.0.0-rc.
|
|
177
|
+
"drizzle-kit": "1.0.0-rc.5-ab785fc",
|
|
178
|
+
"drizzle-orm": "1.0.0-rc.5-169397b",
|
|
179
179
|
"drizzle-seed": "^0.3.1",
|
|
180
|
-
"drizzle-zod": "^0.8.3",
|
|
181
180
|
"gflows": "^1.2.1",
|
|
182
181
|
"happy-dom": "^20.11.1",
|
|
183
182
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
@@ -182,7 +182,7 @@ Return those chunks on HTTP with `fx.json.stream(...)`. The kernel answers `text
|
|
|
182
182
|
|
|
183
183
|
```typescript
|
|
184
184
|
on(
|
|
185
|
-
http.post("/complete").
|
|
185
|
+
http.post("/complete").public(),
|
|
186
186
|
flow("chat.complete", {
|
|
187
187
|
do: (input, fx) => fx.json.stream(fx.stream(smart, { prompt: input.prompt })),
|
|
188
188
|
}),
|
|
@@ -364,6 +364,7 @@ A hang-up is `AbortError` — it does not retry or advance `via`.
|
|
|
364
364
|
|
|
365
365
|
## Learn more
|
|
366
366
|
|
|
367
|
+
- [Project structure](/docs/get-started/project-structure) — agent `tools` are Flow names (`notes.get`), not URLs
|
|
367
368
|
- [Flow](/docs/elements/flow) — `fx.ask` and `fx.search` inside `do`
|
|
368
369
|
- [Store](/docs/elements/store) — `store.index`, the home of embeddings
|
|
369
370
|
- [MCP](/docs/ai/mcp) — serve the app to agents, or consume external servers as tools
|
|
@@ -327,6 +327,7 @@ Confirm a visible OS notification titled `okengine webpush interop`.
|
|
|
327
327
|
|
|
328
328
|
## Learn more
|
|
329
329
|
|
|
330
|
+
- [Project structure](/docs/get-started/project-structure) — `flow({…})` names as `unit.export`; explicit `flow("unit.action")` still wins
|
|
330
331
|
- [Flow](/docs/elements/flow) — `fx.send` inside `do`
|
|
331
332
|
- [Environment Variables](/docs/reference/environment-variables) — Channel boot credentials
|
|
332
333
|
- [Configuration](/docs/reference/configuration) — `drivers.channel.*` maps
|
|
@@ -52,6 +52,8 @@ export const dailyReport = clock("daily-report", {
|
|
|
52
52
|
});
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
Import the module before `oke()` — named clocks auto-register. No `oke({ clocks })` list.
|
|
56
|
+
|
|
55
57
|
```typescript title="src/flows/reports/daily.ts"
|
|
56
58
|
export const sendDaily = on(
|
|
57
59
|
dailyReport,
|
|
@@ -218,6 +220,7 @@ The registry is off in `test` and when there is no shared SQL URL. `dev`/`prod`
|
|
|
218
220
|
|
|
219
221
|
## Learn more
|
|
220
222
|
|
|
223
|
+
- [Project structure](/docs/get-started/project-structure) — clock flows in the tree get a name, never a URL
|
|
221
224
|
- [Flow](/docs/elements/flow) — `on(trigger, flow)` and the `fx` surface
|
|
222
225
|
- [fx · Runs](/docs/reference/fx#runs-observability-read) — native SLO checkers via `on(every(…))` + `fx.runs`
|
|
223
226
|
- [Signal](/docs/elements/signal) — reacting to events instead of time
|
|
@@ -104,6 +104,12 @@ Built-in codes attach a localized `message` ([Errors](/docs/reference/errors));
|
|
|
104
104
|
[Store](/docs/elements/store#handwritten-http-lists).
|
|
105
105
|
</Callout>
|
|
106
106
|
|
|
107
|
+
<Callout title="Helpers take the real Fx">
|
|
108
|
+
Type a helper's `fx` as the exported `Fx` (`import type { Fx } from
|
|
109
|
+
"okengine"`). A narrower `{ store: … }` will not match `store()` overloads
|
|
110
|
+
— that is the `as never` trap. See [fx](/docs/reference/fx).
|
|
111
|
+
</Callout>
|
|
112
|
+
|
|
107
113
|
## The five triggers
|
|
108
114
|
|
|
109
115
|
<FlowTriggers />
|
|
@@ -112,6 +118,8 @@ Built-in codes attach a localized `message` ([Errors](/docs/reference/errors));
|
|
|
112
118
|
|
|
113
119
|
`http.get` · `http.post` · `http.put` · `http.patch` · `http.delete` · `http.head` · `http.options` · `http.query`. QUERY (RFC 10008) is a safe, idempotent read with a JSON body.
|
|
114
120
|
|
|
121
|
+
`http.get()` with no path is filled from the file tree (`flows/notes/[id]/get.ts` → `/notes/:id`). `flow({ do })` is named `unit.export`. Explicit `http.get("/me/tasks")` and `flow("notes.get")` still win. Folder conventions live on [Project structure](/docs/get-started/project-structure).
|
|
122
|
+
|
|
115
123
|
`Content-Type: application/json` is required — missing → **400** `InvalidQuery`, anything else → **415** `UnsupportedMediaType` with `Accept-Query: "application/json"`. Invalid JSON is **400** (no sniffing). Responses advertise `Accept-Query`. okengine does not assign `Location` / `Content-Location` result URIs.
|
|
116
124
|
|
|
117
125
|
A browser `GET` (`Accept` prefers `text/html`) paints the `{ data, error }` envelope in traces chrome — status, latency, and cache, Routes rail (static GET links), copy, line numbers, Pretty / Raw, Console on **6533**. `curl`, `Accept: application/json`, and `?format=json` stay JSON.
|
|
@@ -369,6 +377,7 @@ a **hit**. A matching write turns the next read into a miss.
|
|
|
369
377
|
|
|
370
378
|
## Learn more
|
|
371
379
|
|
|
380
|
+
- [Project structure](/docs/get-started/project-structure) — folders are the URL; `unit.export` is the name
|
|
372
381
|
- [fx](/docs/reference/fx) — full `fx` surface, including ICU `fx.t` / `fx.locale`
|
|
373
382
|
- [Signal](/docs/elements/signal) — delivery physics (`once` · `broadcast` · `live`)
|
|
374
383
|
- [Clock](/docs/elements/clock) — schedules and durable sleep
|
|
@@ -10,7 +10,7 @@ burned its minute quota? Attach gates to the HTTP trigger — the pipeline check
|
|
|
10
10
|
store write, emit, or channel send runs.
|
|
11
11
|
|
|
12
12
|
<Callout title="The one rule">
|
|
13
|
-
Every HTTP trigger declares posture: attach a real gate, or attach `.
|
|
13
|
+
Every HTTP trigger declares posture: attach a real gate, or attach `.public()`. Omitting both
|
|
14
14
|
fails boot. Denial is a typed error value — never a thrown exception mid-`do`.
|
|
15
15
|
</Callout>
|
|
16
16
|
|
|
@@ -19,10 +19,10 @@ store write, emit, or channel send runs.
|
|
|
19
19
|
<Steps>
|
|
20
20
|
|
|
21
21
|
<Step>
|
|
22
|
-
### Declare policies
|
|
22
|
+
### Declare policies
|
|
23
23
|
|
|
24
|
-
Policies are named predicates; rate limits are declarative budgets.
|
|
25
|
-
`oke({ gate: { policies } })
|
|
24
|
+
Policies are named predicates; rate limits are declarative budgets. Import the module before
|
|
25
|
+
`oke()` — they auto-register. `oke({ gate: { policies } })` is still valid and additive.
|
|
26
26
|
|
|
27
27
|
```typescript title="src/gates.ts"
|
|
28
28
|
import { gate } from "okengine";
|
|
@@ -43,14 +43,10 @@ export const fair = gate.rate({
|
|
|
43
43
|
the scope checked.
|
|
44
44
|
|
|
45
45
|
```typescript title="src/app.ts"
|
|
46
|
+
import "./gates";
|
|
46
47
|
import { oke } from "okengine";
|
|
47
|
-
import { member, canBook, fair } from "./gates";
|
|
48
48
|
|
|
49
|
-
export const app = oke({
|
|
50
|
-
name: "shop",
|
|
51
|
-
env: "dev",
|
|
52
|
-
gate: { policies: [member, canBook, fair] },
|
|
53
|
-
});
|
|
49
|
+
export const app = oke({ name: "shop", env: "dev" });
|
|
54
50
|
```
|
|
55
51
|
|
|
56
52
|
</Step>
|
|
@@ -58,14 +54,14 @@ export const app = oke({
|
|
|
58
54
|
<Step>
|
|
59
55
|
### Attach them to triggers
|
|
60
56
|
|
|
61
|
-
`.gate(...)` composes left to right — first denial wins. Public routes must say so explicitly
|
|
57
|
+
`.gate(...)` composes left to right — first denial wins. Public routes must say so explicitly. Path and name can come from the file tree — see [Project structure](/docs/get-started/project-structure).
|
|
62
58
|
|
|
63
59
|
```typescript title="src/flows/links/shorten.ts"
|
|
64
60
|
import { on, flow, http, gate } from "okengine";
|
|
65
61
|
import { member, canBook, fair } from "../../gates";
|
|
66
62
|
|
|
67
63
|
export const health = on(
|
|
68
|
-
http.get("/health").
|
|
64
|
+
http.get("/health").public(),
|
|
69
65
|
flow("health.check", {
|
|
70
66
|
out: HealthOut,
|
|
71
67
|
do: () => ({ ok: true as const }),
|
|
@@ -88,8 +84,8 @@ export const shorten = on(
|
|
|
88
84
|
```
|
|
89
85
|
|
|
90
86
|
A single gate stays `.gate(member)`. Reuse two or more with `gate.all` — `.gate(write)`
|
|
91
|
-
flattens. `http.resource(path, ops).gate(...)` stamps every verb.
|
|
92
|
-
`gate.public` in `gate.policies
|
|
87
|
+
flattens. `http.resource(path, ops).gate(...)` stamps every verb. Public routes use
|
|
88
|
+
`.public()` — do **not** put `gate.public` in `gate.policies`.
|
|
93
89
|
|
|
94
90
|
</Step>
|
|
95
91
|
|
|
@@ -131,16 +127,16 @@ gates are skipped. `do` runs only when every gate passed.
|
|
|
131
127
|
| ------------------------------------- | ------------------------------------------- | ---------------------------------- |
|
|
132
128
|
| `gate.policy(name, check \| options)` | Is this principal allowed? (ABAC) | auth / operator / request metadata |
|
|
133
129
|
| `gate.scope(name)` | Does `auth.scopes` contain this exact name? | `auth.scopes` |
|
|
134
|
-
| `gate.public` / `.
|
|
130
|
+
| `gate.public` / `.public()` | Intentionally unauthenticated surface | always allows |
|
|
135
131
|
| `gate.rate(options)` | Is there budget left for this subject? | an atomic counter on the kv driver |
|
|
136
132
|
| `gate.all(...members)` | Reuse this chain on many triggers | flattens; each member is evaluated |
|
|
137
133
|
|
|
138
134
|
Pass `{ check, description }` on `gate.policy` when you want a human title in the Console (falls
|
|
139
|
-
back to the policy name). The name `"public"` is reserved —
|
|
140
|
-
`gate.policy("public", …)`.
|
|
135
|
+
back to the policy name). The name `"public"` is reserved — mark the trigger with
|
|
136
|
+
`.public()`, not `gate.policy("public", …)`.
|
|
141
137
|
|
|
142
138
|
`gate.all()` with no members throws. Nested `all` handles flatten.
|
|
143
|
-
`oke({ gate: { policies: [write] } })`
|
|
139
|
+
Members auto-register when declared; `oke({ gate: { policies: [write] } })` is additive.
|
|
144
140
|
|
|
145
141
|
### Policy context
|
|
146
142
|
|
|
@@ -178,11 +174,11 @@ driver in tests. Boot opens a dedicated `oke:gates` namespace on **`drivers.stor
|
|
|
178
174
|
|
|
179
175
|
## Auth posture at boot
|
|
180
176
|
|
|
181
|
-
Every HTTP trigger must carry a gate or `.
|
|
177
|
+
Every HTTP trigger must carry a gate or `.public()`. Omitting both fails boot with
|
|
182
178
|
`GateBootError` — message starts with `gate boot failed —` and lists every gap.
|
|
183
179
|
|
|
184
180
|
```typescript
|
|
185
|
-
on(http.get("/health").
|
|
181
|
+
on(http.get("/health").public(), flow("health.check", {/* … */}));
|
|
186
182
|
on(http.post("/bookings").gate(member, canBook), flow("bookings.create", {/* … */}));
|
|
187
183
|
```
|
|
188
184
|
|
|
@@ -191,7 +187,7 @@ on(http.post("/bookings").gate(member, canBook), flow("bookings.create", {/* …
|
|
|
191
187
|
posture but does **not** authenticate.
|
|
192
188
|
|
|
193
189
|
`gate: { unguardedHttp: "allow" }` skips the audit **only** when `env === "test"`.
|
|
194
|
-
In `dev` / `prod` migrate with per-trigger `.
|
|
190
|
+
In `dev` / `prod` migrate with per-trigger `.public()`.
|
|
195
191
|
|
|
196
192
|
## Built-in auth (`gate.auth`)
|
|
197
193
|
|
|
@@ -260,6 +256,38 @@ Customize tables, then `oke schema generate` (`.oke/schema/oke.ts`; `--check` in
|
|
|
260
256
|
sign-in shapes: [Plugins](/docs/plugins). Call auth from
|
|
261
257
|
[createClient](/docs/reference/client) — helpers in `okengine/client/auth`.
|
|
262
258
|
|
|
259
|
+
## API keys
|
|
260
|
+
|
|
261
|
+
A key is the **issuer with fewer gates** — not a second permission system.
|
|
262
|
+
Bearer secrets authenticate as the creator; `fx.auth.scopes` shrink to the key.
|
|
263
|
+
|
|
264
|
+
| Field | Session | API key |
|
|
265
|
+
| ----------------------------------- | -------------- | ----------------------- |
|
|
266
|
+
| `fx.auth.userId` / `fx.operator.id` | subject | creator (`creatorId`) |
|
|
267
|
+
| `fx.auth.scopes` | session scopes | key scopes (attenuated) |
|
|
268
|
+
| `fx.auth.apiKeyId` | `null` | key id |
|
|
269
|
+
| WideEvent `principal` | subject | issuer |
|
|
270
|
+
| `dimensions.api_key` | absent | key id |
|
|
271
|
+
|
|
272
|
+
`gate.auth` always creates a shared key store (HMAC-SHA-256 with `gate.auth.secret`).
|
|
273
|
+
Do not pass `apiKeyStore` unless you are injecting a test Map.
|
|
274
|
+
|
|
275
|
+
<Callout title="Keys live on the app database">
|
|
276
|
+
With `store.sql()`, boot hydrates public `oke_api_keys` on that connection (`DATABASE_URL`) and
|
|
277
|
+
write-through persists create / rotate / revoke. Console **Access** attaches this host store — it
|
|
278
|
+
does not keep a second key database.
|
|
279
|
+
</Callout>
|
|
280
|
+
|
|
281
|
+
Session Flows manage keys via [fx](/docs/reference/fx) (`createApiKey`, `listApiKeys`,
|
|
282
|
+
`revokeApiKey`, `rotateApiKey`, `updateApiKey`). A key-authenticated caller cannot.
|
|
283
|
+
|
|
284
|
+
Declare `effects.reads: ["auth:api-keys"]` / `effects.writes: ["auth:api-keys"]` — never
|
|
285
|
+
`sql:oke_api_keys`. Console **Access** (`:6533` → `/access`) mints operator keys as the
|
|
286
|
+
operator, and user-plane keys only as a picked issuer (`creatorUserId`).
|
|
287
|
+
|
|
288
|
+
Verify rejects expired, revoked, allowlist-miss, and over-rate secrets with `401`
|
|
289
|
+
and no `api_key` dimension.
|
|
290
|
+
|
|
263
291
|
## Identity and decisions
|
|
264
292
|
|
|
265
293
|
Gates decide on principals that already exist. `gate.auth` issues hybrid sessions onto
|
|
@@ -270,12 +298,18 @@ Pass **and** deny land on the run's `gates` dimension. Console (`:6533` → `/fl
|
|
|
270
298
|
chain on the flow contract. Rate `overridable` is a catalog flag only — Clock schedules are what the Console can
|
|
271
299
|
edit live today.
|
|
272
300
|
|
|
301
|
+
<Callout title="RLS reads the stamped principal">
|
|
302
|
+
After HTTP Gate passes, user-plane `fx.store` stamps `oke.gate()`, `oke.user()`, and
|
|
303
|
+
`oke.has_scope()`. Rate, cron, CDC, and signal stay unstamped. Helpers:
|
|
304
|
+
[Store](/docs/elements/store).
|
|
305
|
+
</Callout>
|
|
306
|
+
|
|
273
307
|
## Troubleshooting
|
|
274
308
|
|
|
275
309
|
<Accordions>
|
|
276
310
|
<Accordion title="Boot: gate boot failed — N HTTP trigger(s) missing auth posture">
|
|
277
311
|
|
|
278
|
-
Empty `.gate(...)` chain. Attach a policy/rate gate, or `.
|
|
312
|
+
Empty `.gate(...)` chain. Attach a policy/rate gate, or `.public()`. First `fetch`
|
|
279
313
|
auto-boots — do not rely on `gate.unguardedHttp: "allow"` outside `env: "test"`.
|
|
280
314
|
|
|
281
315
|
</Accordion>
|
|
@@ -308,11 +342,35 @@ character classes.
|
|
|
308
342
|
`"ip"` for public surfaces (sign-up, password reset), `"user"` for authenticated quotas.
|
|
309
343
|
Keying an authenticated endpoint by IP punishes shared NAT.
|
|
310
344
|
|
|
345
|
+
</Accordion>
|
|
346
|
+
<Accordion title="API key Bearer returns 401">
|
|
347
|
+
|
|
348
|
+
Expired, revoked, allowlist miss, or over the key's `rateLimit`. Check
|
|
349
|
+
`ipAllowlist` against the same X-Forwarded-For hop as `ip-allowlist`.
|
|
350
|
+
Entries are IPs or hostnames — a host resolves at verify time and must
|
|
351
|
+
include the client IP (lookup failure is closed). Rotate if the secret
|
|
352
|
+
was shown once and then lost.
|
|
353
|
+
|
|
354
|
+
</Accordion>
|
|
355
|
+
<Accordion title="Keys vanish after an app restart">
|
|
356
|
+
|
|
357
|
+
No SQL store, or a new `gate.auth.secret`. Declare `store.sql()`, keep
|
|
358
|
+
`DATABASE_URL` (or the same PGlite datadir) and the auth secret stable.
|
|
359
|
+
Console does not persist keys in `oke_console`.
|
|
360
|
+
|
|
361
|
+
</Accordion>
|
|
362
|
+
<Accordion title="Key can call a route the issuer cannot">
|
|
363
|
+
|
|
364
|
+
That is a bug in the mint. `createApiKey` / `fx.auth.createApiKey` re-run
|
|
365
|
+
`assertAttenuated` against the issuer's live scopes. Custom `gate.policy`
|
|
366
|
+
predicates that ignore scopes still see `verified: true`.
|
|
367
|
+
|
|
311
368
|
</Accordion>
|
|
312
369
|
</Accordions>
|
|
313
370
|
|
|
314
371
|
## Learn more
|
|
315
372
|
|
|
373
|
+
- [Project structure](/docs/get-started/project-structure) — `http.get()` and `flow({…})` fill path and name from the file tree
|
|
316
374
|
- [Flow](/docs/elements/flow) — the trigger pipeline gates plug into
|
|
317
375
|
- [fx](/docs/reference/fx) — `fx.auth`, `fx.operator`, `fx.principal`
|
|
318
376
|
- [Client](/docs/reference/client) — Bearer `createClient` + `okengine/client/auth`
|
|
@@ -131,7 +131,7 @@ A Flow **writes** all three tapes. `once` / `broadcast` **listen** with `on(sign
|
|
|
131
131
|
|
|
132
132
|
```typescript title="src/flows/orders/place.ts"
|
|
133
133
|
export const placeOrder = on(
|
|
134
|
-
http.post("/orders").
|
|
134
|
+
http.post("/orders").public(),
|
|
135
135
|
flow("orders.place", {
|
|
136
136
|
in: z.object({ id: z.string(), total: z.number() }),
|
|
137
137
|
do: async (input, fx) => {
|
|
@@ -256,9 +256,8 @@ Committed `once` messages survive process death. A claim sets `lockedBy` and a v
|
|
|
256
256
|
|
|
257
257
|
<Callout title="Client subscription is not shipped yet">
|
|
258
258
|
`delivery: "live"` means the **driver** retains and replays. Listen with `bus.live()` on the
|
|
259
|
-
server — not `on(signal, flow)`. `createClient` has no SSE
|
|
260
|
-
|
|
261
|
-
queries](/docs/reference/client#signal-and-live-queries).
|
|
259
|
+
server — not `on(signal, flow)`. `createClient` has no SSE yet — poll HTTP. See
|
|
260
|
+
[Client](/docs/reference/client#signal-and-live-queries).
|
|
262
261
|
</Callout>
|
|
263
262
|
|
|
264
263
|
## Orphaned signal config
|
|
@@ -335,6 +334,7 @@ At-least-once: crash-after-claim reclaims when the lease expires, and a handler
|
|
|
335
334
|
|
|
336
335
|
## Learn more
|
|
337
336
|
|
|
337
|
+
- [Project structure](/docs/get-started/project-structure) — signal files get a name, never a URL; `http.get("/…")` still wins
|
|
338
338
|
- [Flow](/docs/elements/flow) — `on(trigger, flow)` and `fx.emit`
|
|
339
339
|
- [fx](/docs/reference/fx) — `fx.emit` and `fx.deadLetters`
|
|
340
340
|
- [Clock](/docs/elements/clock) — scheduled and delayed work
|
|
@@ -19,6 +19,9 @@ only the driver changes.
|
|
|
19
19
|
Every facet accepts optional `description` — a human title in the Console (falls back to the store
|
|
20
20
|
name).
|
|
21
21
|
|
|
22
|
+
`store.sql` / `kv` / `files` / `index` auto-register when the declaring module is imported before
|
|
23
|
+
`oke()`. `oke({ stores })` is still valid and additive.
|
|
24
|
+
|
|
22
25
|
## SQL
|
|
23
26
|
|
|
24
27
|
<StoreFacetMark facet="sql" />
|
|
@@ -39,9 +42,9 @@ Manifest tables, columns, and keywords.
|
|
|
39
42
|
Classified PII stays masked unless the toolbar **PII** control is on
|
|
40
43
|
(`revealPii: true`, audited).
|
|
41
44
|
|
|
42
|
-
**Gate** views
|
|
43
|
-
|
|
44
|
-
`
|
|
45
|
+
**Gate** views as Operator (bypass), public, a user, or a policy. SQL,
|
|
46
|
+
browse, and grid edit send `{ asGate, asUserId }`. postgres / pglite stamp
|
|
47
|
+
`oke.gate()` / `oke.user()` / `oke.has_scope()`; memory is catalog-only.
|
|
45
48
|
|
|
46
49
|
The SQL band **Schema** control opens a visualizer of Manifest tables and
|
|
47
50
|
relations — declared `.references()` plus inferred `*_id` columns — not the
|
|
@@ -88,17 +91,21 @@ Install reviews the `CREATE EXTENSION` SQL; **Advanced** can set `SCHEMA`,
|
|
|
88
91
|
When a newer packaged version exists, the name chip shows **Upgrade**
|
|
89
92
|
(`ALTER EXTENSION … UPDATE`). Names link to the project or Postgres docs.
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
RLS on create flips
|
|
94
|
+
SQL table rows and the resource header show RLS (emerald / muted shield;
|
|
95
|
+
**N RLS** when policies exist) before **PII**. KV, files, and index have
|
|
96
|
+
no badge — enabling RLS on create flips the shield.
|
|
97
|
+
|
|
98
|
+
**RLS Policies** lists `pg_policies` plus Manifest `declared` / `live`.
|
|
99
|
+
Two-finger click a row for its sheet, or empty grid to create. Create
|
|
100
|
+
pins SQL | Code (`store.schema.policy` + `pgPolicy`); drag to resize.
|
|
94
101
|
|
|
95
|
-
**
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
**Templates** use `oke.user()` / `oke.gate()` / `oke.has_scope()`.
|
|
103
|
+
Owner cards pick a **Column** (guess `creator_email` / `owner_email`)
|
|
104
|
+
with PK / FK marks. Create stays blocked until a column is picked.
|
|
98
105
|
|
|
99
106
|
Create policy picks a Gate posture (Read / Write / Both), or **Policy &
|
|
100
|
-
scope
|
|
101
|
-
|
|
107
|
+
scope**. Those picks fill `USING` / `WITH CHECK`. `TO` stays `public` —
|
|
108
|
+
Gate names are not Postgres roles.
|
|
102
109
|
|
|
103
110
|
### Quick start
|
|
104
111
|
|
|
@@ -192,7 +199,7 @@ const notesR = store.resource(db, notes, {
|
|
|
192
199
|
},
|
|
193
200
|
});
|
|
194
201
|
|
|
195
|
-
const mounted = on(http.resource("/notes", notesR.all()).
|
|
202
|
+
const mounted = on(http.resource("/notes", notesR.all()).public().live());
|
|
196
203
|
```
|
|
197
204
|
|
|
198
205
|
`.gate(...)` / `.live()` chain like `http.get` — gates on every verb, live on list and get.
|
|
@@ -240,7 +247,7 @@ list: { mode: "offset", filter: "none", limit: 20 },
|
|
|
240
247
|
|
|
241
248
|
```typescript
|
|
242
249
|
export const list = on(
|
|
243
|
-
http.get("/notes").
|
|
250
|
+
http.get("/notes").public(),
|
|
244
251
|
flow("notes.list", {
|
|
245
252
|
out: z.array(Note),
|
|
246
253
|
do: async (input, fx) => {
|
|
@@ -251,6 +258,8 @@ export const list = on(
|
|
|
251
258
|
);
|
|
252
259
|
```
|
|
253
260
|
|
|
261
|
+
`list.ts` + `http.get()` fills `GET /notes` named `notes.list` — explicit strings still win. See [Project structure](/docs/get-started/project-structure).
|
|
262
|
+
|
|
254
263
|
Zero-config: `?q=` searches every string field, extra keys auto-eq except path `id`, and PostgREST `?col=op.value` / `?order=` / `?select=` are open. Nested lists filter the parent in the load — `input.id` is not a column eq.
|
|
255
264
|
|
|
256
265
|
<Callout title="Preferred, not required">
|
|
@@ -282,6 +291,9 @@ await fx.store(db).update(notes).set({ title: input.title }).where(eq(notes.id,
|
|
|
282
291
|
await fx.store(db).delete(notes).where(lt(notes.createdAt, cutoff));
|
|
283
292
|
```
|
|
284
293
|
|
|
294
|
+
`select().from(notes)` infers the declared row (`string` / `number` / `null` from
|
|
295
|
+
`field.*`). A `select({ title: notes.title })` projection stays untyped.
|
|
296
|
+
|
|
285
297
|
<Callout title="One table per call — no relational with:">
|
|
286
298
|
`fx.store` is deliberately **single-table**: Drizzle's relational `findMany({ with: … })` is not
|
|
287
299
|
available through `fx`. Compose joins as separate single-table reads (or `fx.call`) so every
|
|
@@ -292,15 +304,44 @@ await fx.store(db).delete(notes).where(lt(notes.createdAt, cutoff));
|
|
|
292
304
|
|
|
293
305
|
The recommended path: declare tables ORM-agnostically, then let `oke db` emit real Drizzle (`pgTable` for Postgres / PGLite) into `src/db/schema.drizzle.ts`.
|
|
294
306
|
|
|
295
|
-
| Field API | Meaning
|
|
296
|
-
| -------------------------------------------- |
|
|
297
|
-
| `field.text()` / `field.integer()` | v1 column primitives
|
|
298
|
-
| `.primaryKey()` · `.notNull()` · `.unique()` | constraints
|
|
299
|
-
| `.default(v)` · `.defaultFn(id \| now)` | defaults
|
|
300
|
-
| `.pii()` · `.sensitive()` · `.retain("30d")` | privacy classification
|
|
301
|
-
| `.as("sql_name")` | override the automatic `camelCase → snake_case`
|
|
302
|
-
| `.describe("…")` | human title in the Console (falls back to key)
|
|
303
|
-
| `.references(() => col, { onDelete })` | foreign key
|
|
307
|
+
| Field API | Meaning |
|
|
308
|
+
| -------------------------------------------- | ---------------------------------------------------------------- |
|
|
309
|
+
| `field.text()` / `field.integer()` | v1 column primitives |
|
|
310
|
+
| `.primaryKey()` · `.notNull()` · `.unique()` | constraints |
|
|
311
|
+
| `.default(v)` · `.defaultFn(id \| now)` | defaults |
|
|
312
|
+
| `.pii()` · `.sensitive()` · `.retain("30d")` | privacy classification |
|
|
313
|
+
| `.as("sql_name")` | override the automatic `camelCase → snake_case` |
|
|
314
|
+
| `.describe("…")` | human title in the Console (falls back to key) |
|
|
315
|
+
| `.references(() => col, { onDelete })` | foreign key |
|
|
316
|
+
| `store.schema.rls()` | `pgTable.withRLS` when there are no policies |
|
|
317
|
+
| `store.schema.policy.gate/owner/scope` | happy-path RLS (`oke.gate()` / `oke.user()` / `oke.has_scope()`) |
|
|
318
|
+
|
|
319
|
+
Third argument on `store.schema.table` is Drizzle-shaped extras:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
export const bookings = store.schema.table(
|
|
323
|
+
"bookings",
|
|
324
|
+
{
|
|
325
|
+
id: field.text().primaryKey(),
|
|
326
|
+
owner: field.text().notNull(),
|
|
327
|
+
},
|
|
328
|
+
[
|
|
329
|
+
store.schema.policy.gate("member", { for: "select" }),
|
|
330
|
+
store.schema.policy.owner("owner", { for: "all" }),
|
|
331
|
+
store.schema.policy.scope("booking:create", { for: "insert" }),
|
|
332
|
+
],
|
|
333
|
+
);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Helpers emit stable names (`gate_member_select`). Raw
|
|
337
|
+
`store.schema.policy("name", { as, to, for, using, withCheck })` is the
|
|
338
|
+
escape hatch — predicates use `oke.*`, never `current_setting`.
|
|
339
|
+
|
|
340
|
+
<Callout title="User-plane fx.store applies RLS">
|
|
341
|
+
HTTP / resource flows stamp Gate identity onto every postgres / pglite statement (`SET LOCAL ROLE
|
|
342
|
+
oke_app` + `set_config` in one pinned transaction). Table-owner bypass ends there; operator / cron
|
|
343
|
+
/ CDC / signal / catalog stay unstamped.
|
|
344
|
+
</Callout>
|
|
304
345
|
|
|
305
346
|
#### Foreign keys and relations
|
|
306
347
|
|
|
@@ -356,9 +397,9 @@ Staging/prod accumulate versioned SQL under `drizzle/` (`oke db generate`).
|
|
|
356
397
|
| Staging / prod | `oke db generate` → review files → `oke db migrate` on that DB |
|
|
357
398
|
|
|
358
399
|
<Callout title="Connection pooling is infrastructure, not app code" type="info">
|
|
359
|
-
Bun.SQL
|
|
360
|
-
|
|
361
|
-
|
|
400
|
+
One shared Bun.SQL pool (**8** connections) per URL. `dev`/`prod` put **PgDog** in front
|
|
401
|
+
(`DATABASE_URL` → port `6432`, `pool_size` 20). RLS stamps `reserve` one slot, then `SET LOCAL` in
|
|
402
|
+
that transaction — a leaked `BEGIN` exhausts checkout.
|
|
362
403
|
</Callout>
|
|
363
404
|
|
|
364
405
|
**Why PgDog (not PgBouncer as the default).** Transaction pooling fixes the
|
|
@@ -371,8 +412,9 @@ Not wired this round — readiness only.
|
|
|
371
412
|
|
|
372
413
|
#### Seeding
|
|
373
414
|
|
|
374
|
-
`oke db seed` loads `defineSeed` from `src/db/seed/index.ts` —
|
|
375
|
-
|
|
415
|
+
`oke db seed` loads `defineSeed` from `src/db/seed/index.ts` — a separate
|
|
416
|
+
command, never at boot. Not every project has a seed. In `oke dev`, `s` runs
|
|
417
|
+
the same command. `name` is this app's seed (template or example).
|
|
376
418
|
|
|
377
419
|
<StoreSeeding />
|
|
378
420
|
|
|
@@ -642,8 +684,12 @@ Default `store.kv("sessions")` is cache-shaped — a Redis recreate drops keys.
|
|
|
642
684
|
```typescript
|
|
643
685
|
export const sessions = store.kv("sessions", { description: "Session cache" });
|
|
644
686
|
export const ledger = store.kv("ledger", { durable: true, description: "Idempotency keys" });
|
|
687
|
+
export const drafts = store.kv("drafts", { durable: true, description: "Compose drafts" });
|
|
645
688
|
```
|
|
646
689
|
|
|
690
|
+
**Consequence:** `oke db seed` into cache Redis looks fine until compose recreates the
|
|
691
|
+
container — Console Store then shows **No rows.** Seeded namespaces need `{ durable: true }`.
|
|
692
|
+
|
|
647
693
|
Gate rates and Signal stay on `REDIS_URL`. Missing `DATABASE_URL` with the postgres driver
|
|
648
694
|
fails boot: `oke boot: durable store.kv needs DATABASE_URL`.
|
|
649
695
|
|
|
@@ -1014,6 +1060,11 @@ Inserts and selects now resolve it from the declaration. Keep the `name` column.
|
|
|
1014
1060
|
|
|
1015
1061
|
Schema DDL never runs automatically in `prod`. Under `oke dev`, `db.autoPush` (default `true`) runs `oke db push`. For production use `oke db generate` + `oke db migrate`.
|
|
1016
1062
|
|
|
1063
|
+
</Accordion>
|
|
1064
|
+
<Accordion title="oke db push: missing_hints">
|
|
1065
|
+
|
|
1066
|
+
New tables are created automatically. Push only manages `public` — it will not drop schema `oke` (RLS helpers) or `oke_console`. Remaining `missing_hints` are destructive (drop a non-empty public table, type change). Review with `oke db generate`.
|
|
1067
|
+
|
|
1017
1068
|
</Accordion>
|
|
1018
1069
|
<Accordion title="I need a join — with: is not supported">
|
|
1019
1070
|
|
|
@@ -1064,11 +1115,17 @@ Unlike KV/Files, unset `drivers.store.index` does **not** promote to `pgvector`
|
|
|
1064
1115
|
|
|
1065
1116
|
`.oke/state.json` stores `seededAt` plus this app's `defineSeed({ name })` — notes vs keel are different ids. A new name re-asks. Clear those keys to prompt again.
|
|
1066
1117
|
|
|
1118
|
+
</Accordion>
|
|
1119
|
+
<Accordion title="Console KV shows No rows after oke db seed">
|
|
1120
|
+
|
|
1121
|
+
Default `store.kv` is cache Redis with no AOF — a recreate drops keys. SQL seed still looks full. Mark seeded namespaces `{ durable: true }` so they live in `oke_kv`, then restart and seed again.
|
|
1122
|
+
|
|
1067
1123
|
</Accordion>
|
|
1068
1124
|
</Accordions>
|
|
1069
1125
|
|
|
1070
1126
|
## Learn more
|
|
1071
1127
|
|
|
1128
|
+
- [Project structure](/docs/get-started/project-structure) — `list.ts` + `http.get()` is `GET /notes` named `notes.list`
|
|
1072
1129
|
- [Flow](/docs/elements/flow) — the `fx.store` session inside `do`
|
|
1073
1130
|
- [AI](/docs/elements/ai) — `ai.embed` into a vector `store.index`, searched via `fx.search`
|
|
1074
1131
|
- [Gate](/docs/elements/gate) — `pii:reveal` and other permissions on flows
|
|
@@ -201,6 +201,9 @@ The **Console** (`:6533`) **Vault** page (`/vault`) groups **Secrets** and
|
|
|
201
201
|
**Config**. Secrets are write-only: a salted fingerprint (`sha256:…`), never
|
|
202
202
|
the cleartext. Config rows show the value — that is `vault.config()`.
|
|
203
203
|
|
|
204
|
+
API keys are principals — mint them on **Access** (`/access`), not Vault.
|
|
205
|
+
See [Gate](/docs/elements/gate#api-keys).
|
|
206
|
+
|
|
204
207
|
Add (`+`) creates a contract from Console as well as from source. Those rows
|
|
205
208
|
show `console` until you declare the same name in the Manifest.
|
|
206
209
|
|
|
@@ -363,6 +366,7 @@ Schedule the live command with cron when you want automatic cleanup.
|
|
|
363
366
|
|
|
364
367
|
## Learn more
|
|
365
368
|
|
|
369
|
+
- [Project structure](/docs/get-started/project-structure) — `flow({…})` names as `unit.export`; `flow("billing.charge")` still wins
|
|
366
370
|
- [Flow](/docs/elements/flow) — how `fx.vault.get` reads secrets inside `do`
|
|
367
371
|
- [CLI Reference](/docs/reference/cli) — `oke vault set` · `list` · `import`
|
|
368
372
|
|
|
@@ -45,19 +45,21 @@ lists HTTP routes — static GET paths are links. `curl` still gets `{ data, err
|
|
|
45
45
|
|
|
46
46
|
The standard starter already exports a named health Flow:
|
|
47
47
|
|
|
48
|
-
```typescript title="flows/main"
|
|
49
|
-
import { on, flow, http
|
|
48
|
+
```typescript title="flows/main/health.ts"
|
|
49
|
+
import { on, flow, http } from "okengine";
|
|
50
50
|
import { z } from "zod";
|
|
51
51
|
|
|
52
52
|
export const health = on(
|
|
53
|
-
http.get(
|
|
54
|
-
flow(
|
|
53
|
+
http.get().public(),
|
|
54
|
+
flow({
|
|
55
55
|
out: z.object({ ok: z.literal(true) }),
|
|
56
56
|
do: () => ({ ok: true as const }),
|
|
57
57
|
}),
|
|
58
58
|
);
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
`main` omits the URL prefix, so this file is `GET /health` named `main.health`. See [Project structure](/docs/get-started/project-structure).
|
|
62
|
+
|
|
61
63
|
Named flows show up as `main.health` (not `flow_1`) in `oke dev` logs, with the run id after the timestamp.
|
|
62
64
|
Change the route or output and save — App and Console update together from the
|
|
63
65
|
same Manifest.
|
|
@@ -67,20 +69,19 @@ same Manifest.
|
|
|
67
69
|
<Step>
|
|
68
70
|
### Call it from a typed client
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
Generated units become namespaces on the client:
|
|
71
73
|
|
|
72
74
|
```typescript title="app"
|
|
73
75
|
import "@/core";
|
|
74
|
-
|
|
76
|
+
import "@/flows/generated";
|
|
75
77
|
import { oke } from "okengine/http";
|
|
76
|
-
import * as routes from "@/flows/generated";
|
|
77
78
|
|
|
78
|
-
export const app = oke({ name: "notes" })
|
|
79
|
+
export const app = oke({ name: "notes" });
|
|
79
80
|
export type App = typeof app;
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
`import "@/core"` loads stores, secrets, channels, and
|
|
83
|
-
(via `locales/index.ts`) so they auto-register before `oke()`.
|
|
83
|
+
`import "@/core"` loads stores, secrets, gates, clocks, channels, and
|
|
84
|
+
message catalogs (via `locales/index.ts`) so they auto-register before `oke()`.
|
|
84
85
|
|
|
85
86
|
```typescript title="client"
|
|
86
87
|
import { createClient } from "okengine/client";
|
|
@@ -107,7 +108,7 @@ Every backend behavior has one shape:
|
|
|
107
108
|
|
|
108
109
|
## From Flow to client
|
|
109
110
|
|
|
110
|
-
Export →
|
|
111
|
+
Export → generated barrel → call or test. Same App type — no separate codegen project.
|
|
111
112
|
|
|
112
113
|
<ClientLoop />
|
|
113
114
|
|
|
@@ -137,8 +138,10 @@ bun test
|
|
|
137
138
|
<Accordions>
|
|
138
139
|
|
|
139
140
|
<Accordion title="api.main.health is not a function / type error">
|
|
140
|
-
Confirm the Flow is `export`ed from
|
|
141
|
-
|
|
141
|
+
Confirm the Flow is `export`ed from a generated unit (`import "@/flows/generated"` then `oke({ name })`), or from a module you still `.adopt({ main })`.
|
|
142
|
+
|
|
143
|
+
Type `createClient` with your `App` type. Restart `oke dev` after renaming exports so the Manifest refreshes.
|
|
144
|
+
|
|
142
145
|
</Accordion>
|
|
143
146
|
|
|
144
147
|
<Accordion title="createTestApp fails to boot">
|
|
@@ -151,6 +154,7 @@ bun test
|
|
|
151
154
|
|
|
152
155
|
## Learn more
|
|
153
156
|
|
|
157
|
+
- [Project structure](/docs/get-started/project-structure) — folders are the URL
|
|
154
158
|
- [Flow](/docs/elements/flow) — triggers, contracts, effects, and composition
|
|
155
159
|
- [Store](/docs/elements/store) — SQL, KV, files, index, and the preferred list envelope
|
|
156
160
|
- [fx](/docs/reference/fx) — the full effect surface
|
|
@@ -158,6 +162,11 @@ bun test
|
|
|
158
162
|
## Next
|
|
159
163
|
|
|
160
164
|
<Cards>
|
|
165
|
+
<Card
|
|
166
|
+
title="Project structure"
|
|
167
|
+
description="Folders are the URL; unit plus export is the name."
|
|
168
|
+
href="/docs/get-started/project-structure"
|
|
169
|
+
/>
|
|
161
170
|
<Card
|
|
162
171
|
title="Flow"
|
|
163
172
|
description="Triggers, contracts, effects, and composition."
|