okengine 0.18.4 → 0.19.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/AGENTS.md +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/index.mdx +25 -6
- package/site/content/docs/ai/llms-txt.mdx +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- package/site/content/docs/ai/try-it.mdx +58 -0
- package/site/content/docs/client/auth.mdx +208 -0
- package/site/content/docs/client/calling.mdx +451 -0
- package/site/content/docs/client/index.mdx +209 -0
- package/site/content/docs/client/live.mdx +234 -0
- package/site/content/docs/client/meta.json +5 -0
- package/site/content/docs/client/react.mdx +249 -0
- package/site/content/docs/elements/ai/agents.mdx +232 -0
- package/site/content/docs/elements/ai/index.mdx +341 -0
- package/site/content/docs/elements/ai/mcp.mdx +275 -0
- package/site/content/docs/elements/ai/meta.json +5 -0
- package/site/content/docs/elements/ai/models.mdx +283 -0
- package/site/content/docs/elements/ai/prompts.mdx +256 -0
- package/site/content/docs/elements/channel/email.mdx +266 -0
- package/site/content/docs/elements/channel/index.mdx +344 -0
- package/site/content/docs/elements/channel/meta.json +5 -0
- package/site/content/docs/elements/channel/push.mdx +218 -0
- package/site/content/docs/elements/channel/receipts.mdx +206 -0
- package/site/content/docs/elements/channel/sms.mdx +264 -0
- package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
- package/site/content/docs/elements/clock/index.mdx +300 -0
- package/site/content/docs/elements/clock/meta.json +5 -0
- package/site/content/docs/elements/clock/schedules.mdx +508 -0
- package/site/content/docs/elements/clock/sleep.mdx +445 -0
- package/site/content/docs/elements/flow/consumers.mdx +707 -0
- package/site/content/docs/elements/flow/http.mdx +1080 -0
- package/site/content/docs/elements/flow/index.mdx +618 -0
- package/site/content/docs/elements/flow/meta.json +5 -0
- package/site/content/docs/elements/flow/routing.mdx +559 -0
- package/site/content/docs/elements/flow/workflows.mdx +640 -0
- package/site/content/docs/elements/gate/auth.mdx +371 -0
- package/site/content/docs/elements/gate/authorization.mdx +298 -0
- package/site/content/docs/elements/gate/index.mdx +418 -0
- package/site/content/docs/elements/gate/meta.json +5 -0
- package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
- package/site/content/docs/elements/gate/rls.mdx +303 -0
- package/site/content/docs/elements/gate/tenancy.mdx +372 -0
- package/site/content/docs/elements/index.mdx +52 -11
- package/site/content/docs/elements/signal/broadcast.mdx +517 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +590 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +596 -0
- package/site/content/docs/elements/store/files.mdx +659 -0
- package/site/content/docs/elements/store/index.mdx +310 -0
- package/site/content/docs/elements/store/kv.mdx +629 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +946 -0
- package/site/content/docs/elements/store/sql.mdx +937 -0
- package/site/content/docs/elements/vault/config.mdx +256 -0
- package/site/content/docs/elements/vault/index.mdx +356 -0
- package/site/content/docs/elements/vault/meta.json +5 -0
- package/site/content/docs/elements/vault/rotation.mdx +273 -0
- package/site/content/docs/elements/vault/secrets.mdx +285 -0
- package/site/content/docs/index.mdx +78 -30
- package/site/content/docs/meta.json +11 -8
- package/site/content/docs/plugins/cors.mdx +2 -1
- package/site/content/docs/plugins/csrf.mdx +4 -1
- package/site/content/docs/plugins/headers.mdx +2 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
- package/site/content/docs/plugins/magic-link.mdx +6 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
- package/site/content/docs/plugins/oauth.mdx +1 -1
- package/site/content/docs/plugins/otp.mdx +12 -1
- package/site/content/docs/plugins/passkey.mdx +39 -15
- package/site/content/docs/plugins/two-factor.mdx +91 -34
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +3 -3
- package/site/content/docs/recipes/index.mdx +18 -28
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +3 -3
- package/site/content/docs/recipes/meta.json +1 -4
- package/site/content/docs/recipes/nginx.mdx +2 -2
- package/site/content/docs/recipes/openrouter.mdx +283 -0
- package/site/content/docs/recipes/pgdog.mdx +3 -3
- package/site/content/docs/recipes/postgres.mdx +1 -2
- package/site/content/docs/recipes/rustfs.mdx +2 -2
- package/site/content/docs/recipes/traefik.mdx +3 -3
- package/site/content/docs/reference/cli.mdx +231 -0
- package/site/content/docs/reference/configuration.mdx +30 -10
- package/site/content/docs/reference/environment-variables.mdx +93 -20
- package/site/content/docs/reference/errors.mdx +132 -29
- package/site/content/docs/reference/fx.mdx +137 -73
- package/site/content/docs/reference/index.mdx +13 -21
- package/site/content/docs/reference/meta.json +4 -5
- package/site/content/docs/reference/okid.mdx +42 -17
- package/site/content/docs/reference/plugins.mdx +4 -3
- package/site/content/docs/reference/security.mdx +197 -0
- package/site/content/docs/understand/meta.json +5 -0
- package/site/content/docs/understand/the-anatomy.mdx +132 -0
- package/site/content/docs/understand/the-model.mdx +32 -0
- package/site/content/docs/understand/the-problem.mdx +74 -0
- package/site/content/docs/understand/the-vocabulary.mdx +26 -0
- package/src/auth/api-keys.ts +2 -1
- package/src/auth/bindings.ts +64 -16
- package/src/auth/gate-auth.test.ts +6 -1
- package/src/auth/identity.ts +152 -5
- package/src/auth/index.ts +29 -0
- package/src/auth/invites.ts +2 -1
- package/src/auth/method-context.ts +41 -0
- package/src/auth/oauth-as/crypto.ts +2 -1
- package/src/auth/oauth-as/stores.ts +2 -1
- package/src/auth/operator.ts +2 -1
- package/src/auth/sessions-jwt.test.ts +77 -0
- package/src/auth/sessions.ts +20 -4
- package/src/auth/tenants.ts +3 -2
- package/src/auth/two-factor-challenge.test.ts +61 -0
- package/src/auth/two-factor-challenge.ts +284 -0
- package/src/auth/verification.ts +5 -0
- package/src/bench/REPORT.md +49 -0
- package/src/bench/g03-signal-once.bench.ts +1 -1
- package/src/bench/g10-observability-contention.bench.ts +1 -1
- package/src/bench/g17-hybrid-search.bench.ts +572 -0
- package/src/bench/load-app.ts +2 -10
- package/src/cli/ai-setup/ai-setup.test.ts +331 -42
- package/src/cli/ai-setup/apply.ts +294 -52
- package/src/cli/ai-setup/catalog.ts +238 -1342
- package/src/cli/ai-setup/index.ts +14 -99
- package/src/cli/ai-setup/prompts.ts +60 -443
- package/src/cli/ask-seed.test.ts +96 -0
- package/src/cli/ask-seed.ts +82 -0
- package/src/cli/ask-vault-gaps.test.ts +84 -0
- package/src/cli/ask-vault-gaps.ts +146 -0
- package/src/cli/build.ts +2 -2
- package/src/cli/client-add.test.ts +26 -1
- package/src/cli/client-add.ts +106 -27
- package/src/cli/competitor-mention-removal.test.ts +1 -1
- package/src/cli/db-seed.ts +2 -0
- package/src/cli/db.ts +150 -11
- package/src/cli/dev.ts +119 -211
- package/src/cli/docker-clean.ts +2 -2
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/doctor-diff.ts +4 -2
- package/src/cli/doctor-pii.test.ts +1 -1
- package/src/cli/doctor.ts +14 -1
- package/src/cli/hero-meta.test.ts +4 -2
- package/src/cli/load-config.images.test.ts +8 -10
- package/src/cli/load-config.ts +1 -1
- package/src/cli/registry.ts +22 -10
- package/src/cli/replay.ts +3 -1
- package/src/cli/start.ts +1 -1
- package/src/cli/tui/keys.ts +2 -3
- package/src/client/auth/cookies.ts +74 -0
- package/src/client/auth/create-auth-client.ts +636 -0
- package/src/client/auth/denials.ts +99 -0
- package/src/client/auth/session.ts +283 -0
- package/src/client/auth.test.ts +251 -0
- package/src/client/auth.ts +39 -114
- package/src/client/create-with-session.ts +245 -0
- package/src/client/create.ts +51 -5
- package/src/client/index.ts +11 -1
- package/src/client/live.ts +11 -79
- package/src/client/notes-contract.test.ts +5 -10
- package/src/client/sse.ts +137 -0
- package/src/client/stream.ts +147 -0
- package/src/client/transport.test.ts +32 -0
- package/src/client/transport.ts +123 -26
- package/src/client/types.ts +118 -14
- package/src/client-react/index.ts +185 -24
- package/src/client-react/use-live-query.ts +13 -3
- package/src/compiler/aot.test.ts +7 -4
- package/src/compiler/effects-embed.test.ts +56 -0
- package/src/compiler/effects-fetch.test.ts +43 -0
- package/src/compiler/effects-infer.ts +58 -2
- package/src/compiler/extract.test.ts +258 -44
- package/src/compiler/extract.ts +566 -48
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
- package/src/compiler/fixtures/skyport.expected.json +4 -4
- package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
- package/src/compiler/response.ts +2 -2
- package/src/compiler/search-writer-isolation.test.ts +40 -0
- package/src/config/index.ts +11 -0
- package/src/console/server/app.ts +17 -23
- package/src/console/server/bind.ts +4 -0
- package/src/console/server/console.test.ts +2 -2
- package/src/console/server/flows-invoke.test.ts +50 -34
- package/src/console/server/flows.ts +436 -255
- package/src/console/server/runs-ingest.test.ts +6 -3
- package/src/console/server/serve.ts +1 -1
- package/src/console/server/signals.test.ts +1 -5
- package/src/console/server/signals.ts +8 -6
- package/src/console/server/state.ts +6 -1
- package/src/console/server/store.ts +1 -1
- package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
- package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
- package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
- package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
- package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
- package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
- package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
- package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
- package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
- package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
- package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
- package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
- package/src/console/ui-next/dist/index.html +6 -13
- package/src/console/ui-next/seed-invoke-host.ts +21 -18
- package/src/console/ui-next/src/client.ts +7 -2
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
- package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
- package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
- package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
- package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
- package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
- package/src/docker/ai-model-status.test.ts +1 -12
- package/src/docker/ai-model-status.ts +10 -38
- package/src/docker/compose-up.test.ts +100 -0
- package/src/docker/compose-up.ts +292 -0
- package/src/docker/compose.ts +4 -14
- package/src/docker/derive.ts +9 -34
- package/src/docker/docker.test.ts +16 -261
- package/src/docker/images-config.test.ts +13 -25
- package/src/docker/index.ts +1 -24
- package/src/docker/recipes/index.ts +0 -23
- package/src/docker/recipes/pgdog.ts +6 -4
- package/src/docker/stack-id.test.ts +2 -2
- package/src/docker/stack-id.ts +0 -1
- package/src/docker/types.ts +2 -2
- package/src/drivers/ai-anthropic.ts +10 -0
- package/src/drivers/ai-openai-compatible.ts +34 -7
- package/src/drivers/ai-providers.test.ts +0 -107
- package/src/drivers/ai-stream.test.ts +0 -34
- package/src/drivers/ai-types.ts +17 -8
- package/src/drivers/channel-fcm.ts +6 -1
- package/src/drivers/channel-msegat.ts +6 -1
- package/src/drivers/channel-resend.ts +5 -1
- package/src/drivers/channel-smtp.ts +5 -1
- package/src/drivers/channel-sndr.ts +10 -1
- package/src/drivers/channel-taqnyat-mail.ts +5 -1
- package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
- package/src/drivers/channel-taqnyat.ts +6 -1
- package/src/drivers/channel-types.ts +10 -0
- package/src/drivers/channel-unifonic.ts +6 -1
- package/src/drivers/channel-wa-cloud.ts +6 -1
- package/src/drivers/channel-webpush.ts +6 -1
- package/src/drivers/external.ts +33 -0
- package/src/drivers/index.ts +0 -10
- package/src/drivers/meilisearch.ts +6 -0
- package/src/drivers/oauth-types.ts +4 -0
- package/src/drivers/postgres.test.ts +74 -3
- package/src/drivers/postgres.ts +175 -6
- package/src/drivers/signal-types.ts +5 -5
- package/src/drivers/types.ts +7 -0
- package/src/drivers/vault-types.ts +5 -0
- package/src/elements/ai/declare.ts +17 -4
- package/src/elements/ai/eval.ts +1 -1
- package/src/elements/ai/mcp-http.ts +1 -1
- package/src/elements/ai/pii.ts +1 -1
- package/src/elements/ai/providers.test.ts +289 -0
- package/src/elements/ai/providers.ts +169 -0
- package/src/elements/ai/runtime.ts +68 -5
- package/src/elements/ai/schema.ts +2 -2
- package/src/elements/ai.test.ts +7 -7
- package/src/elements/ai.ts +17 -1
- package/src/elements/channel/runtime.ts +105 -12
- package/src/elements/clock/cron-fields.test.ts +144 -0
- package/src/elements/clock/cron-fields.ts +185 -0
- package/src/elements/clock/declare.ts +247 -3
- package/src/elements/clock.ts +21 -1
- package/src/elements/index.ts +15 -0
- package/src/elements/signal/chaos-child.ts +1 -2
- package/src/elements/signal/declare.ts +65 -37
- package/src/elements/signal/delivery-modes.test.ts +11 -31
- package/src/elements/signal/dry-run-replay.test.ts +1 -7
- package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
- package/src/elements/signal/key-ordering.test.ts +6 -26
- package/src/elements/signal/lease-reclaim.test.ts +2 -6
- package/src/elements/signal/optional-emit.test.ts +6 -9
- package/src/elements/signal/order-lifecycle.test.ts +4 -17
- package/src/elements/signal/orphan-messages.test.ts +4 -15
- package/src/elements/signal/reconcile.test.ts +2 -2
- package/src/elements/signal/runtime.ts +1 -1
- package/src/elements/signal/schema-emit.test.ts +4 -8
- package/src/elements/signal.test.ts +24 -49
- package/src/elements/signal.ts +9 -2
- package/src/elements/store/domain-ddl.test.ts +1 -1
- package/src/elements/store/live-http.test.ts +9 -4
- package/src/elements/store/prepare-row.test.ts +123 -0
- package/src/elements/store/resource.ts +43 -12
- package/src/elements/store/schema-decl.ts +117 -0
- package/src/elements/store/search-backfill.ts +205 -0
- package/src/elements/store/search-bind.ts +66 -0
- package/src/elements/store/search-bm25.ts +78 -0
- package/src/elements/store/search-ddl.ts +156 -0
- package/src/elements/store/search-embed-flow.ts +141 -0
- package/src/elements/store/search-errors.ts +34 -0
- package/src/elements/store/search-fusion.ts +112 -0
- package/src/elements/store/search-lsh.ts +179 -0
- package/src/elements/store/search-runtime.ts +300 -0
- package/src/elements/store/search.test.ts +144 -0
- package/src/elements/store/sql-session.test.ts +1 -0
- package/src/elements/store/sql-session.ts +83 -3
- package/src/elements/store/table.ts +23 -2
- package/src/elements/store/upsert-app.test.ts +1 -3
- package/src/elements/store.ts +50 -0
- package/src/full.ts +4 -2
- package/src/http.ts +4 -1
- package/src/i18n/catalogs/ar.ts +14 -6
- package/src/i18n/catalogs/en.ts +14 -6
- package/src/index.ts +6 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
- package/src/kernel/adopt-routes.ts +9 -1
- package/src/kernel/app-auth.ts +5 -0
- package/src/kernel/app.ts +102 -9
- package/src/kernel/auto-cache.test.ts +6 -12
- package/src/kernel/auto-registry.test.ts +3 -3
- package/src/kernel/boot-bind/ai.test.ts +22 -19
- package/src/kernel/boot-bind/ai.ts +5 -43
- package/src/kernel/boot-bind/clock.ts +23 -5
- package/src/kernel/boot-bind/honor-config.test.ts +4 -4
- package/src/kernel/boot.test.ts +11 -6
- package/src/kernel/boot.ts +66 -16
- package/src/kernel/boundary-contract.ts +93 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/call.ts +89 -0
- package/src/kernel/capability.ts +6 -0
- package/src/kernel/client-descriptor.ts +123 -0
- package/src/kernel/clock-timezone.test.ts +80 -0
- package/src/kernel/correlation.test.ts +1 -1
- package/src/kernel/dry-run.ts +11 -8
- package/src/kernel/effects-stamping.test.ts +42 -9
- package/src/kernel/effects.test.ts +4 -2
- package/src/kernel/effects.ts +51 -9
- package/src/kernel/errors-channel.ts +17 -0
- package/src/kernel/errors-live-resume.ts +3 -2
- package/src/kernel/errors-tenant.ts +7 -4
- package/src/kernel/errors.registry-helpers.ts +96 -0
- package/src/kernel/errors.registry.test.ts +139 -25
- package/src/kernel/errors.ts +102 -23
- package/src/kernel/external-effects.test.ts +196 -0
- package/src/kernel/flow.test.ts +13 -4
- package/src/kernel/flow.ts +69 -71
- package/src/kernel/fx-ask-telemetry.test.ts +2 -2
- package/src/kernel/fx-dead-letters.test.ts +3 -3
- package/src/kernel/fx-fetch.ts +55 -0
- package/src/kernel/fx-live-stream.ts +2 -2
- package/src/kernel/fx-live.test.ts +8 -8
- package/src/kernel/fx.test.ts +17 -11
- package/src/kernel/fx.ts +180 -57
- package/src/kernel/horizontal-child.ts +1 -1
- package/src/kernel/http-query.test.ts +3 -6
- package/src/kernel/index.ts +13 -3
- package/src/kernel/instance-id.ts +1 -1
- package/src/kernel/instances.test.ts +40 -0
- package/src/kernel/instances.ts +47 -9
- package/src/kernel/live-http.test.ts +6 -7
- package/src/kernel/live-http.ts +6 -2
- package/src/kernel/live-resume.test.ts +1 -1
- package/src/kernel/mcp-tool.test.ts +3 -3
- package/src/kernel/on.ts +98 -4
- package/src/kernel/pipeline.test.ts +12 -15
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/stamp-http.test.ts +3 -3
- package/src/kernel/triggers.ts +210 -95
- package/src/kernel-entry.ts +0 -1
- package/src/manifest/diff.ts +11 -1
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/types.ts +46 -2
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +1 -1
- package/src/okid.test.ts +56 -0
- package/src/okid.ts +55 -11
- package/src/plugins/anonymous.ts +8 -4
- package/src/plugins/auth/shared.ts +27 -3
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +561 -23
- package/src/plugins/config-source.ts +1 -1
- package/src/plugins/csrf.test.ts +65 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +36 -16
- package/src/plugins/oauth/shared.ts +3 -1
- package/src/plugins/oauth.ts +22 -14
- package/src/plugins/otp.ts +37 -21
- package/src/plugins/passkey-webauthn.ts +9 -6
- package/src/plugins/passkey.ts +98 -26
- package/src/plugins/pre-account-hijack.test.ts +223 -0
- package/src/plugins/two-factor.ts +466 -37
- package/src/plugins/username.ts +19 -7
- package/src/release/build-lib.ts +1 -0
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +1 -1
- package/src/runs/collect.test.ts +0 -2
- package/src/runtime/json-code-block.test.ts +250 -20
- package/src/runtime/json-code-block.ts +1261 -41
- package/src/term.test.ts +1 -1
- package/src/test/create-test-app.test.ts +12 -12
- package/src/test/live-signals.test.ts +6 -6
- package/src/test/provisions.integration.test.ts +10 -14
- package/src/test/reset-element-registries.ts +1 -1
- package/src/test/tenant-isolation.test.ts +12 -6
- package/site/content/docs/deployment/docker-swarm.mdx +0 -164
- package/site/content/docs/deployment/docker.mdx +0 -227
- package/site/content/docs/deployment/index.mdx +0 -83
- package/site/content/docs/deployment/kubernetes.mdx +0 -176
- package/site/content/docs/deployment/meta.json +0 -5
- package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
- package/site/content/docs/elements/ai.mdx +0 -385
- package/site/content/docs/elements/channel.mdx +0 -346
- package/site/content/docs/elements/clock.mdx +0 -244
- package/site/content/docs/elements/flow.mdx +0 -420
- package/site/content/docs/elements/gate.mdx +0 -436
- package/site/content/docs/elements/signal.mdx +0 -380
- package/site/content/docs/elements/store.mdx +0 -1099
- package/site/content/docs/elements/vault.mdx +0 -405
- package/site/content/docs/get-started/basic-usage.mdx +0 -173
- package/site/content/docs/get-started/index.mdx +0 -43
- package/site/content/docs/get-started/installation.mdx +0 -220
- package/site/content/docs/get-started/introduction.mdx +0 -132
- package/site/content/docs/get-started/meta.json +0 -13
- package/site/content/docs/get-started/project-structure.mdx +0 -925
- package/site/content/docs/get-started/testing.mdx +0 -328
- package/site/content/docs/get-started/why.mdx +0 -162
- package/site/content/docs/recipes/llama-cpp.mdx +0 -151
- package/site/content/docs/recipes/ollama.mdx +0 -142
- package/site/content/docs/recipes/sglang.mdx +0 -105
- package/site/content/docs/recipes/vllm.mdx +0 -106
- package/site/content/docs/reference/cli.md +0 -232
- package/site/content/docs/reference/client.mdx +0 -469
- package/site/content/docs/reference/security.md +0 -74
- package/src/cli/ai-setup/detect-ollama.ts +0 -213
- package/src/cli/ai-setup/recommend.test.ts +0 -225
- package/src/cli/ai-setup/recommend.ts +0 -225
- package/src/cli/dev-controls.test.ts +0 -122
- package/src/cli/dev-controls.ts +0 -164
- package/src/cli/tui/DevLive.tsx +0 -126
- package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
- package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
- package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
- package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
- package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
- package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
- package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
- package/src/docker/ollama-pull.ts +0 -232
- package/src/docker/recipes/llama-cpp.ts +0 -298
- package/src/docker/recipes/ollama.ts +0 -44
- package/src/docker/recipes/sglang.ts +0 -55
- package/src/docker/recipes/vllm.ts +0 -44
- package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
- package/src/drivers/ai-ollama.integration.test.ts +0 -184
- package/src/drivers/ai-ollama.ts +0 -389
- package/src/drivers/ollama.ts +0 -14
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "KV"
|
|
3
|
+
description: "Namespaced key-value storage with duration TTL — sessions, caches, and short-lived locks via fx.store."
|
|
4
|
+
icon: "Key"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The KV facet is a typed namespace for sessions, cache entries, OTPs, and other short-lived keys.
|
|
9
|
+
Declare with `store.kv(name)`, then read and write through `fx.store(decl)` inside Flows.
|
|
10
|
+
|
|
11
|
+
For developers caching or locking on okengine — one get/set surface; Redis in Docker, memory in tests.
|
|
12
|
+
|
|
13
|
+
<Callout title="The one rule">
|
|
14
|
+
Pass the **declaration** into `fx.store(sessions)`. There is no `fx.store.kv`
|
|
15
|
+
namespace. TTL is a duration **string** (`"15m"`), not `{ ttl: "15m" }`.
|
|
16
|
+
</Callout>
|
|
17
|
+
|
|
18
|
+
## Smallest Example
|
|
19
|
+
|
|
20
|
+
<Steps>
|
|
21
|
+
|
|
22
|
+
<Step>
|
|
23
|
+
### Declare a namespace
|
|
24
|
+
|
|
25
|
+
```typescript title="src/core.ts"
|
|
26
|
+
import { store } from "okengine";
|
|
27
|
+
|
|
28
|
+
export const sessions = store.kv("sessions");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
</Step>
|
|
32
|
+
|
|
33
|
+
<Step>
|
|
34
|
+
### Set and get in a Flow
|
|
35
|
+
|
|
36
|
+
```typescript title="src/flows/sessions/create.ts"
|
|
37
|
+
import { on, flow, http } from "okengine";
|
|
38
|
+
import { z } from "zod";
|
|
39
|
+
import { sessions } from "@/core";
|
|
40
|
+
|
|
41
|
+
export const create = on(
|
|
42
|
+
http.post(),
|
|
43
|
+
flow({
|
|
44
|
+
in: z.object({ token: z.string(), userId: z.string() }),
|
|
45
|
+
do: async ({ token, userId }, fx) => {
|
|
46
|
+
await fx.store(sessions).set(`session:${token}`, userId, "1h");
|
|
47
|
+
const cached = await fx.store(sessions).get(`session:${token}`);
|
|
48
|
+
return { userId: cached };
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
</Step>
|
|
55
|
+
|
|
56
|
+
<Step>
|
|
57
|
+
### Call the endpoint
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
curl -X POST http://localhost:6530/sessions \
|
|
61
|
+
-H "content-type: application/json" \
|
|
62
|
+
-d '{"token":"abc","userId":"u1"}'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Response:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"data": { "userId": "u1" },
|
|
70
|
+
"error": null
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
</Step>
|
|
75
|
+
|
|
76
|
+
</Steps>
|
|
77
|
+
|
|
78
|
+
<Callout title="Import the declaration">
|
|
79
|
+
`store.kv(…)` must run when the app loads — export it from a module Flows import (starters:
|
|
80
|
+
`@/core`). A Flow that only types the name as a string never opens a namespace; the handle needs
|
|
81
|
+
the decl object.
|
|
82
|
+
</Callout>
|
|
83
|
+
|
|
84
|
+
## Progressive Patterns
|
|
85
|
+
|
|
86
|
+
From a plain set to TTL, list/delete, and durable SQL-backed namespaces:
|
|
87
|
+
|
|
88
|
+
<Tabs items={["Set / get", "TTL", "List / delete", "Durable"]}>
|
|
89
|
+
|
|
90
|
+
<Tab value="Set / get">
|
|
91
|
+
|
|
92
|
+
Values may be strings, numbers, or JSON-serializable objects:
|
|
93
|
+
|
|
94
|
+
```typescript title="src/flows/prefs/[userId]/get.ts"
|
|
95
|
+
import { on, flow, http } from "okengine";
|
|
96
|
+
import { z } from "zod";
|
|
97
|
+
import { sessions } from "@/core";
|
|
98
|
+
|
|
99
|
+
export const get = on(
|
|
100
|
+
http.get(),
|
|
101
|
+
flow({
|
|
102
|
+
in: z.object({ userId: z.string() }),
|
|
103
|
+
do: async ({ userId }, fx) => {
|
|
104
|
+
const prefs = await fx.store(sessions).get(`user:${userId}:prefs`);
|
|
105
|
+
return { prefs: prefs ?? null };
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Missing keys return `undefined` / driver nullish — treat as a cache miss.
|
|
112
|
+
|
|
113
|
+
</Tab>
|
|
114
|
+
|
|
115
|
+
<Tab value="TTL">
|
|
116
|
+
|
|
117
|
+
Third argument to `set` is optional. Format: `^(\d+)(ms|s|m|h|d)$`:
|
|
118
|
+
|
|
119
|
+
```typescript title="src/flows/otp/create.ts"
|
|
120
|
+
import { on, flow, http } from "okengine";
|
|
121
|
+
import { z } from "zod";
|
|
122
|
+
import { sessions } from "@/core";
|
|
123
|
+
|
|
124
|
+
export const create = on(
|
|
125
|
+
http.post(),
|
|
126
|
+
flow({
|
|
127
|
+
in: z.object({ userId: z.string() }),
|
|
128
|
+
do: async ({ userId }, fx) => {
|
|
129
|
+
const code = "482193";
|
|
130
|
+
await fx.store(sessions).set(`otp:${userId}`, code, "5m");
|
|
131
|
+
const remaining = await fx.store(sessions).ttlMs(`otp:${userId}`);
|
|
132
|
+
return { code, ttlMs: remaining };
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Consequence:** redis honors TTL; the `memory` driver records expiry for
|
|
139
|
+
`ttlMs` but **does not delete** expired keys on `get` — see [TTL Physics](#ttl-physics).
|
|
140
|
+
|
|
141
|
+
</Tab>
|
|
142
|
+
|
|
143
|
+
<Tab value="List / delete">
|
|
144
|
+
|
|
145
|
+
```typescript title="src/flows/sessions/route.ts"
|
|
146
|
+
import { on, flow, http } from "okengine";
|
|
147
|
+
import { sessions } from "@/core";
|
|
148
|
+
|
|
149
|
+
export const clear = on(
|
|
150
|
+
http.delete(),
|
|
151
|
+
flow({
|
|
152
|
+
do: async (_, fx) => {
|
|
153
|
+
const keys = await fx.store(sessions).list("session:");
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
await fx.store(sessions).delete(key);
|
|
156
|
+
}
|
|
157
|
+
return { removed: keys.length };
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`list` is for Console browsing and admin Flows — not a substitute for a SQL
|
|
164
|
+
index when you need relational queries.
|
|
165
|
+
|
|
166
|
+
</Tab>
|
|
167
|
+
|
|
168
|
+
<Tab value="Durable">
|
|
169
|
+
|
|
170
|
+
`durable: true` persists the namespace in SQL (`oke_kv` on `DATABASE_URL`) —
|
|
171
|
+
not Redis. Distinct from Flow `durable: true`:
|
|
172
|
+
|
|
173
|
+
```typescript title="src/core.ts"
|
|
174
|
+
import { store } from "okengine";
|
|
175
|
+
|
|
176
|
+
export const featureFlags = store.kv("feature-flags", {
|
|
177
|
+
durable: true,
|
|
178
|
+
description: "Flags that must survive Redis flushes",
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Requires a configured SQL driver. Durable namespaces do **not** support
|
|
183
|
+
driver-level `eval` (Gate rate Lua stays on Redis). See
|
|
184
|
+
[Durable Namespaces](#durable-namespaces).
|
|
185
|
+
|
|
186
|
+
</Tab>
|
|
187
|
+
|
|
188
|
+
</Tabs>
|
|
189
|
+
|
|
190
|
+
## Method Reference
|
|
191
|
+
|
|
192
|
+
`fx.store(kvDecl)`:
|
|
193
|
+
|
|
194
|
+
| Method | Signature | Purpose | Side effect |
|
|
195
|
+
| -------- | ----------------------- | ------------------------------- | ----------- |
|
|
196
|
+
| `get` | `get(key)` | Read value (or miss) | Read |
|
|
197
|
+
| `set` | `set(key, value, ttl?)` | Write; optional duration string | Write |
|
|
198
|
+
| `delete` | `delete(key)` | Remove; returns whether deleted | Write |
|
|
199
|
+
| `list` | `list(prefix?)` | List keys (optional prefix) | Read |
|
|
200
|
+
| `ttlMs` | `ttlMs(key)` | Remaining ms, or `null` | Read |
|
|
201
|
+
|
|
202
|
+
Handle also exposes `ref` (`kv:name`) and `driverId`
|
|
203
|
+
(`memory` · `redis` · `postgres` · `pglite` when durable).
|
|
204
|
+
|
|
205
|
+
There is **no** public `incr`, `setNx`, `del` alias, or `eval` on the fx handle.
|
|
206
|
+
Gate rate strategies use Redis `eval` internally — not application Flows.
|
|
207
|
+
|
|
208
|
+
## Namespaces
|
|
209
|
+
|
|
210
|
+
Every `store.kv(name)` registers a Manifest resource `kv:name`. The string you
|
|
211
|
+
pass is the namespace id — keys inside it are relative to that namespace.
|
|
212
|
+
|
|
213
|
+
**Cache namespace** (default) — opens on `drivers.store.kv` (`redis` / `memory`):
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
export const sessions = store.kv("sessions");
|
|
217
|
+
// ref → "kv:sessions"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Durable namespace** — opens on the shared SQL connection (`oke_kv` table):
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
export const drafts = store.kv("drafts", {
|
|
224
|
+
durable: true,
|
|
225
|
+
description: "Compose drafts that survive Redis recreate",
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Global under tenancy** — skip the `{tenantId}:` prefix when `gate.auth.tenant`
|
|
230
|
+
is on:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
export const sharedFlags = store.kv("shared-flags", {
|
|
234
|
+
tenantScoped: false,
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Methods
|
|
239
|
+
|
|
240
|
+
Each verb binds through `fx.store(decl)` inside `flow({ do })`:
|
|
241
|
+
|
|
242
|
+
<Tabs items={["get", "set", "delete", "list", "ttlMs"]}>
|
|
243
|
+
|
|
244
|
+
<Tab value="get">
|
|
245
|
+
|
|
246
|
+
Read a key. Misses return `undefined` (or driver nullish):
|
|
247
|
+
|
|
248
|
+
```typescript title="src/flows/sessions/[token]/get.ts"
|
|
249
|
+
import { on, flow, http } from "okengine";
|
|
250
|
+
import { z } from "zod";
|
|
251
|
+
import { sessions } from "@/core";
|
|
252
|
+
|
|
253
|
+
export const get = on(
|
|
254
|
+
http.get(),
|
|
255
|
+
flow({
|
|
256
|
+
in: z.object({ token: z.string() }),
|
|
257
|
+
errors: { NotFound: z.object({ token: z.string() }) },
|
|
258
|
+
do: async ({ token }, fx) => {
|
|
259
|
+
const userId = await fx.store(sessions).get(`session:${token}`);
|
|
260
|
+
if (userId === undefined || userId === null) {
|
|
261
|
+
return fx.fail("NotFound", { token });
|
|
262
|
+
}
|
|
263
|
+
return { userId };
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
</Tab>
|
|
270
|
+
|
|
271
|
+
<Tab value="set">
|
|
272
|
+
|
|
273
|
+
Write a value. Pass a duration string as the third argument for TTL:
|
|
274
|
+
|
|
275
|
+
```typescript title="src/flows/sessions/create.ts"
|
|
276
|
+
import { on, flow, http } from "okengine";
|
|
277
|
+
import { z } from "zod";
|
|
278
|
+
import { sessions } from "@/core";
|
|
279
|
+
|
|
280
|
+
export const create = on(
|
|
281
|
+
http.post(),
|
|
282
|
+
flow({
|
|
283
|
+
in: z.object({ token: z.string(), userId: z.string() }),
|
|
284
|
+
do: async ({ token, userId }, fx) => {
|
|
285
|
+
await fx.store(sessions).set(`session:${token}`, userId, "1h");
|
|
286
|
+
return { ok: true as const };
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Omit the third argument for a key with no expiry. Redis JSON-stringifies
|
|
293
|
+
objects; memory keeps the value as-is.
|
|
294
|
+
|
|
295
|
+
</Tab>
|
|
296
|
+
|
|
297
|
+
<Tab value="delete">
|
|
298
|
+
|
|
299
|
+
Remove a key. Returns `true` when a key was deleted:
|
|
300
|
+
|
|
301
|
+
```typescript title="src/flows/sessions/[token]/remove.ts"
|
|
302
|
+
import { on, flow, http } from "okengine";
|
|
303
|
+
import { z } from "zod";
|
|
304
|
+
import { sessions } from "@/core";
|
|
305
|
+
|
|
306
|
+
export const remove = on(
|
|
307
|
+
http.delete(),
|
|
308
|
+
flow({
|
|
309
|
+
in: z.object({ token: z.string() }),
|
|
310
|
+
do: async ({ token }, fx) => {
|
|
311
|
+
const removed = await fx.store(sessions).delete(`session:${token}`);
|
|
312
|
+
return { removed };
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
315
|
+
);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
</Tab>
|
|
319
|
+
|
|
320
|
+
<Tab value="list">
|
|
321
|
+
|
|
322
|
+
List keys, optionally filtered by prefix. Results are sorted; the returned
|
|
323
|
+
strings are the **logical** keys (no driver prefix, no tenant prefix):
|
|
324
|
+
|
|
325
|
+
```typescript title="src/flows/drafts/list.ts"
|
|
326
|
+
import { on, flow, http } from "okengine";
|
|
327
|
+
import { drafts } from "@/core";
|
|
328
|
+
|
|
329
|
+
export const list = on(
|
|
330
|
+
http.get(),
|
|
331
|
+
flow({
|
|
332
|
+
do: async (_, fx) => {
|
|
333
|
+
const keys = await fx.store(drafts).list();
|
|
334
|
+
const items = [];
|
|
335
|
+
for (const key of keys) {
|
|
336
|
+
const value = await fx.store(drafts).get(key);
|
|
337
|
+
items.push({ id: key, value });
|
|
338
|
+
}
|
|
339
|
+
return items;
|
|
340
|
+
},
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
On Redis, `list` uses `SCAN` — never `KEYS *`. A client without SCAN throws
|
|
346
|
+
rather than scanning the whole instance.
|
|
347
|
+
|
|
348
|
+
</Tab>
|
|
349
|
+
|
|
350
|
+
<Tab value="ttlMs">
|
|
351
|
+
|
|
352
|
+
Remaining lifetime in milliseconds, or `null` when the key has no expiry
|
|
353
|
+
(or the backend cannot report one). Does not create or delete the key:
|
|
354
|
+
|
|
355
|
+
```typescript title="src/flows/otp/[userId]/ttl.ts"
|
|
356
|
+
import { on, flow, http } from "okengine";
|
|
357
|
+
import { z } from "zod";
|
|
358
|
+
import { sessions } from "@/core";
|
|
359
|
+
|
|
360
|
+
export const ttl = on(
|
|
361
|
+
http.get(),
|
|
362
|
+
flow({
|
|
363
|
+
in: z.object({ userId: z.string() }),
|
|
364
|
+
do: async ({ userId }, fx) => {
|
|
365
|
+
const ttlMs = await fx.store(sessions).ttlMs(`otp:${userId}`);
|
|
366
|
+
return { ttlMs };
|
|
367
|
+
},
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
</Tab>
|
|
373
|
+
|
|
374
|
+
</Tabs>
|
|
375
|
+
|
|
376
|
+
## Declare Options
|
|
377
|
+
|
|
378
|
+
Second argument to `store.kv(name, options)`:
|
|
379
|
+
|
|
380
|
+
| Option | Type | Default | Meaning |
|
|
381
|
+
| -------------- | --------- | ------------------------- | ----------------------------------------------------------------- |
|
|
382
|
+
| `description` | `string` | omitted | Console / Manifest label |
|
|
383
|
+
| `durable` | `true` | omitted | Persist in SQL `oke_kv` (not Redis) |
|
|
384
|
+
| `tenantScoped` | `boolean` | `true` when tenancy is on | Prefix keys with `{tenantId}:`; set `false` for global namespaces |
|
|
385
|
+
|
|
386
|
+
## Durable Namespaces
|
|
387
|
+
|
|
388
|
+
<Callout title="Detailed section">
|
|
389
|
+
If you only need a Redis/memory cache, skip this section. `durable: true` is a different backend —
|
|
390
|
+
SQL table `oke_kv` on the shared store.sql connection — not a Redis persistence mode.
|
|
391
|
+
</Callout>
|
|
392
|
+
|
|
393
|
+
Use durable KV when values must survive Redis flushes or Compose recreate
|
|
394
|
+
(feature flags, compose drafts, webhook registrations). Cache namespaces stay
|
|
395
|
+
on `redis` / `memory`.
|
|
396
|
+
|
|
397
|
+
```typescript title="src/core.ts"
|
|
398
|
+
import { store } from "okengine";
|
|
399
|
+
|
|
400
|
+
export const drafts = store.kv("drafts", {
|
|
401
|
+
durable: true,
|
|
402
|
+
description: "Compose drafts",
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
```typescript title="src/flows/drafts/[id]/save.ts"
|
|
407
|
+
import { on, flow, http } from "okengine";
|
|
408
|
+
import { z } from "zod";
|
|
409
|
+
import { drafts } from "@/core";
|
|
410
|
+
|
|
411
|
+
export const save = on(
|
|
412
|
+
http.put("/drafts/:id"),
|
|
413
|
+
flow({
|
|
414
|
+
in: z.object({
|
|
415
|
+
id: z.string().min(1),
|
|
416
|
+
title: z.string().min(1),
|
|
417
|
+
body: z.string().optional(),
|
|
418
|
+
}),
|
|
419
|
+
do: async ({ id, title, body }, fx) => {
|
|
420
|
+
await fx.store(drafts).set(id, { title, body: body ?? "" }, "7d");
|
|
421
|
+
return { id };
|
|
422
|
+
},
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
<Accordions>
|
|
428
|
+
|
|
429
|
+
<Accordion title="Storage model">
|
|
430
|
+
Rows live in engine-owned table `oke_kv` (`namespace`, `key`, `value` JSONB,
|
|
431
|
+
`expires_at`, `updated_at`). Schema is ensured at open — not part of
|
|
432
|
+
`oke db push` domain migrations.
|
|
433
|
+
|
|
434
|
+
| Fact | Detail |
|
|
435
|
+
| ------------------------ | ------------------------------------------------------------------------------------- |
|
|
436
|
+
| Connection | Shared primary SQL URL (`DATABASE_URL` / project SQL) |
|
|
437
|
+
| `driverId` on the handle | `postgres` or `pglite` (the SQL driver), not `redis` |
|
|
438
|
+
| TTL | `expires_at` filtered on `get` / `list` / `ttlMs`; expired rows purged on write paths |
|
|
439
|
+
| Distinct from | Flow `durable: true` (step journaling) |
|
|
440
|
+
|
|
441
|
+
</Accordion>
|
|
442
|
+
|
|
443
|
+
<Accordion title="What durable does not do">
|
|
444
|
+
Durable namespaces reject Lua/`eval`. Gate rate buckets and any Redis-only
|
|
445
|
+
atomic scripts stay on a cache (`redis` / `memory`) namespace.
|
|
446
|
+
|
|
447
|
+
```text
|
|
448
|
+
oke store: durable store.kv does not support eval
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Without a SQL driver configured:
|
|
452
|
+
|
|
453
|
+
```text
|
|
454
|
+
oke store: durable store.kv needs a configured sql driver
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
</Accordion>
|
|
458
|
+
|
|
459
|
+
<Accordion title="When to prefer durable vs redis">
|
|
460
|
+
|
|
461
|
+
| Need | Prefer |
|
|
462
|
+
| ----------------------------------------------- | ---------------------------------------------- |
|
|
463
|
+
| Sessions, OTP, short locks, Gate rates | `redis` / `memory` cache namespace |
|
|
464
|
+
| Flags / drafts that must outlive Redis recreate | `durable: true` |
|
|
465
|
+
| Relational queries / joins | [SQL](/docs/elements/store/sql), not KV `list` |
|
|
466
|
+
|
|
467
|
+
</Accordion>
|
|
468
|
+
|
|
469
|
+
</Accordions>
|
|
470
|
+
|
|
471
|
+
## TTL Physics
|
|
472
|
+
|
|
473
|
+
<Callout title="Detailed section">
|
|
474
|
+
TTL is best-effort and driver-dependent. The same `set(key, value, "30m")` call does not mean
|
|
475
|
+
identical expiry semantics on every backend.
|
|
476
|
+
</Callout>
|
|
477
|
+
|
|
478
|
+
<StoreKvTtl />
|
|
479
|
+
|
|
480
|
+
| Driver | TTL on `set(…, "30m")` |
|
|
481
|
+
| ---------------------- | -------------------------------------------------------------------- |
|
|
482
|
+
| `redis` | Applied (`SET … EX`); key expires |
|
|
483
|
+
| `memory` | `ttlMs` may report remaining time; **`get` does not expire** the key |
|
|
484
|
+
| Durable SQL (`oke_kv`) | Applied on read (`expires_at` filter) |
|
|
485
|
+
|
|
486
|
+
Valid units match `^(\d+)(ms|s|m|h|d)$`. Invalid strings parse to `0` (no
|
|
487
|
+
useful expiry). Prefer explicit units: `"30s"`, `"15m"`, `"1h"`, `"1d"`.
|
|
488
|
+
|
|
489
|
+
Redis converts the duration to whole seconds (`EX`); sub-second `"ms"` values
|
|
490
|
+
ceil to at least `1` second when TTL is set.
|
|
491
|
+
|
|
492
|
+
## Tenant Scoping
|
|
493
|
+
|
|
494
|
+
When [`gate.auth.tenant`](/docs/elements/gate/tenancy) is on, KV namespaces
|
|
495
|
+
default to tenant-prefixed keys. Application code still uses logical keys —
|
|
496
|
+
the runtime rewrites:
|
|
497
|
+
|
|
498
|
+
| Op | Physical key (when scoped) |
|
|
499
|
+
| ---------------------------------- | --------------------------------------------------------- |
|
|
500
|
+
| `get` / `set` / `delete` / `ttlMs` | `{tenantId}:{key}` |
|
|
501
|
+
| `list(prefix?)` | scans `{tenantId}:{prefix…}`; strips the prefix on return |
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// With tenancy on and fx.tenant.id = "acme":
|
|
505
|
+
await fx.store(sessions).set("session:abc", "u1", "1h");
|
|
506
|
+
// Physical redis key: oke:kv:sessions:acme:session:abc
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Opt out for genuinely global namespaces:
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
export const catalog = store.kv("catalog", { tenantScoped: false });
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Missing tenant on a scoped op throws **OKE1810** (`TENANT_REQUIRED`):
|
|
516
|
+
|
|
517
|
+
```text
|
|
518
|
+
This operation needs a tenant, but none is resolved for this request.
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Fix: switch tenant (`fx.auth.switchTenant`), send a signed `tid` claim, or pass
|
|
522
|
+
the tenant header — see [Tenancy](/docs/elements/gate/tenancy).
|
|
523
|
+
|
|
524
|
+
## Key Prefixes & Effects
|
|
525
|
+
|
|
526
|
+
Two prefix layers sit under the logical key you pass to `fx.store`:
|
|
527
|
+
|
|
528
|
+
| Layer | Shape | Who sees it |
|
|
529
|
+
| -------------------- | ---------------------------------------------------------------------- | ---------------------------- |
|
|
530
|
+
| Tenant (when scoped) | `{tenantId}:` | Stripped from `list` results |
|
|
531
|
+
| Driver | Redis `oke:kv:{ns}:` · memory `{ns}:` · durable SQL `namespace` column | Console / Redis tools |
|
|
532
|
+
|
|
533
|
+
Compiler effects stamp `kv:name` on reads (`get` · `list` · `ttlMs`) and
|
|
534
|
+
writes (`set` · `delete`). Declare the same refs when you hand-write
|
|
535
|
+
`effects` on a Flow.
|
|
536
|
+
|
|
537
|
+
## Drivers
|
|
538
|
+
|
|
539
|
+
| Driver | Runs as | Best for |
|
|
540
|
+
| -------------------------------------------- | -------------------------------- | ---------------------------------- |
|
|
541
|
+
| `redis` | Docker Redis / Valkey-compatible | Dev + prod default |
|
|
542
|
+
| `memory` | Process map | Test default |
|
|
543
|
+
| Durable (`postgres` / `pglite` via `oke_kv`) | Shared SQL URL | Namespaces that must outlive Redis |
|
|
544
|
+
|
|
545
|
+
Defaults: `redis` / `memory` / `redis` (dev / test / prod). Pin overrides in
|
|
546
|
+
`oke.config.ts`; image pins stay under `images.store.kv` — the driver id stays
|
|
547
|
+
`redis` for Valkey / Dragonfly / Upstash wire-compatible servers.
|
|
548
|
+
|
|
549
|
+
Gate rate strategies inherit `drivers.store.kv` (no separate `drivers.gate`).
|
|
550
|
+
|
|
551
|
+
## Troubleshooting
|
|
552
|
+
|
|
553
|
+
<Accordions>
|
|
554
|
+
|
|
555
|
+
<Accordion title="No kv driver configured">
|
|
556
|
+
Boot needs `drivers.store.kv` (or `DRIVER_DEFAULTS`). Check `REDIS_URL` when the id is `redis`.
|
|
557
|
+
</Accordion>
|
|
558
|
+
|
|
559
|
+
<Accordion title="oke store: durable store.kv needs a configured sql driver">
|
|
560
|
+
`durable: true` opens `oke_kv` on the SQL connection. Configure `drivers.store.sql` and
|
|
561
|
+
`DATABASE_URL` (or the project SQL URL).
|
|
562
|
+
</Accordion>
|
|
563
|
+
|
|
564
|
+
<Accordion title="oke store: durable store.kv does not support eval">
|
|
565
|
+
Durable namespaces reject Lua/`eval`. Keep rate-limit buckets on a Redis namespace; use durable KV
|
|
566
|
+
for flags and similar durable maps.
|
|
567
|
+
</Accordion>
|
|
568
|
+
|
|
569
|
+
<Accordion title="Unknown kv ref">
|
|
570
|
+
Cause: `Unknown kv ref: …`. The declaration was never imported. Export `store.kv(…)` from a module
|
|
571
|
+
the app loads before Flows run (starters: `@/core`).
|
|
572
|
+
</Accordion>
|
|
573
|
+
|
|
574
|
+
<Accordion title="TTL set but key still readable (memory)">
|
|
575
|
+
Expected on the `memory` driver — see [TTL Physics](#ttl-physics). Use `redis` in Docker when
|
|
576
|
+
expiry must be enforced on read.
|
|
577
|
+
</Accordion>
|
|
578
|
+
|
|
579
|
+
<Accordion title='I passed { ttl: "15m" } and it did not typecheck'>
|
|
580
|
+
Third argument is a bare string: `set(key, value, "15m")`. There is no options bag on `set`.
|
|
581
|
+
</Accordion>
|
|
582
|
+
|
|
583
|
+
<Accordion title="OKE1810 — TENANT_REQUIRED on kv ops">
|
|
584
|
+
Cause: `This operation needs a tenant, but none is resolved for this request.` Tenancy is on and
|
|
585
|
+
the namespace is tenant-scoped. Resolve `fx.tenant.id`, or set `tenantScoped: false` for global
|
|
586
|
+
keys.
|
|
587
|
+
</Accordion>
|
|
588
|
+
|
|
589
|
+
<Accordion title="redis kv.list: client lacks SCAN">
|
|
590
|
+
Browse refused rather than `KEYS *`. Use a Redis client that supports `SCAN` (Bun.RedisClient
|
|
591
|
+
does). Prefer prefix filters in admin Flows.
|
|
592
|
+
</Accordion>
|
|
593
|
+
|
|
594
|
+
<Accordion title="I expected incr / setNx / fx.store.kv">
|
|
595
|
+
Those helpers are not on the public handle. Use `get` / `set` / `delete` / `list` / `ttlMs` via
|
|
596
|
+
`fx.store(decl)`. Gate rates use Redis Lua internally.
|
|
597
|
+
</Accordion>
|
|
598
|
+
|
|
599
|
+
</Accordions>
|
|
600
|
+
|
|
601
|
+
## Learn more
|
|
602
|
+
|
|
603
|
+
- [Store](/docs/elements/store) — four facets; driver defaults
|
|
604
|
+
- [SQL](/docs/elements/store/sql) — durable KV shares the SQL driver
|
|
605
|
+
- [Gate · Tenancy](/docs/elements/gate/tenancy) — `tenantScoped` and `fx.tenant.id`
|
|
606
|
+
- [Gate](/docs/elements/gate) — rate buckets use Redis internally
|
|
607
|
+
- [fx](/docs/reference/fx) — `fx.store(decl)`
|
|
608
|
+
- [Configuration](/docs/reference/configuration) — `drivers.store.kv`
|
|
609
|
+
- [Errors](/docs/reference/errors) — OKE1810
|
|
610
|
+
|
|
611
|
+
## Next
|
|
612
|
+
|
|
613
|
+
<Cards>
|
|
614
|
+
<Card
|
|
615
|
+
title="Files"
|
|
616
|
+
description="Object buckets, putImage variants, and image pipelines."
|
|
617
|
+
href="/docs/elements/store/files"
|
|
618
|
+
/>
|
|
619
|
+
<Card
|
|
620
|
+
title="SQL"
|
|
621
|
+
description="Relational tables and store.resource."
|
|
622
|
+
href="/docs/elements/store/sql"
|
|
623
|
+
/>
|
|
624
|
+
<Card
|
|
625
|
+
title="Store Overview"
|
|
626
|
+
description="SQL · KV · files · index — one handle."
|
|
627
|
+
href="/docs/elements/store"
|
|
628
|
+
/>
|
|
629
|
+
</Cards>
|