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,640 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Workflows"
|
|
3
|
+
description: "Journaled multi-step Flows — fx.step replay, LIFO undo, durable sleep, and crash resume."
|
|
4
|
+
icon: "GitFork"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
A durable Flow is an ordinary Flow with `durable: true`. Named `fx.step` calls journal
|
|
9
|
+
their results so a crash resumes without re-running completed work — checkout, onboarding,
|
|
10
|
+
anything that must not double-charge.
|
|
11
|
+
|
|
12
|
+
For developers writing multi-step work on okengine — set `durable: true`, wrap side
|
|
13
|
+
effects in `fx.step`, keep time on `fx.clock`.
|
|
14
|
+
|
|
15
|
+
<Callout title="The one rule">
|
|
16
|
+
Wrap every side effect in a uniquely named `fx.step`. Replay returns the journaled value and never
|
|
17
|
+
re-runs the body. Register `{undo}` on steps that must reverse; sleep only with
|
|
18
|
+
`fx.clock.sleep(label, duration)` on a durable Flow.
|
|
19
|
+
</Callout>
|
|
20
|
+
|
|
21
|
+
<FlowDurable />
|
|
22
|
+
|
|
23
|
+
## Smallest Example
|
|
24
|
+
|
|
25
|
+
<Steps>
|
|
26
|
+
|
|
27
|
+
<Step>
|
|
28
|
+
### Define a durable Flow
|
|
29
|
+
|
|
30
|
+
```typescript title="src/flows/orders/checkout.ts"
|
|
31
|
+
import { on, flow, http } from "okengine";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
import { db, charges, orders } from "@/schema";
|
|
34
|
+
|
|
35
|
+
export const checkout = on(
|
|
36
|
+
http.post({
|
|
37
|
+
in: z.object({ userId: z.string(), sku: z.string() }),
|
|
38
|
+
out: z.object({ orderId: z.string() }),
|
|
39
|
+
}),
|
|
40
|
+
flow({
|
|
41
|
+
durable: true,
|
|
42
|
+
do: async ({ userId, sku }, fx) => {
|
|
43
|
+
const charge = await fx.step("charge", async () => {
|
|
44
|
+
const id = fx.id();
|
|
45
|
+
await fx.store(db).insert(charges).values({ id, userId, amount: 50 });
|
|
46
|
+
return { id };
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const orderId = await fx.step("create-order", async () => {
|
|
50
|
+
const id = fx.id();
|
|
51
|
+
await fx.store(db).insert(orders).values({
|
|
52
|
+
id,
|
|
53
|
+
userId,
|
|
54
|
+
sku,
|
|
55
|
+
chargeId: charge.id,
|
|
56
|
+
});
|
|
57
|
+
return id;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return { orderId };
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
</Step>
|
|
67
|
+
|
|
68
|
+
<Step>
|
|
69
|
+
### Call the endpoint
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
curl -X POST http://localhost:6530/orders/checkout \
|
|
73
|
+
-H "content-type: application/json" \
|
|
74
|
+
-d '{"userId":"usr_1","sku":"sku_42"}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Response:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"data": { "orderId": "ord_1" },
|
|
82
|
+
"error": null
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Kill the process after `charge` persists and before `create-order` finishes.
|
|
87
|
+
Boot again — `charge` replays from the journal; the card is not charged twice.
|
|
88
|
+
|
|
89
|
+
</Step>
|
|
90
|
+
|
|
91
|
+
</Steps>
|
|
92
|
+
|
|
93
|
+
<Callout title="Not a separate species">
|
|
94
|
+
There is no workflow engine API. `durable: true` is a Flow option — HTTP, Signal, Clock, CDC, and
|
|
95
|
+
call-only Flows all journal the same way. See [Consumers](/docs/elements/flow/consumers).
|
|
96
|
+
</Callout>
|
|
97
|
+
|
|
98
|
+
## Progressive Patterns
|
|
99
|
+
|
|
100
|
+
Explore durable Flows from a two-step journal to undo, sleep, and flow-level compensation:
|
|
101
|
+
|
|
102
|
+
<Tabs items={["Minimal", "Undo", "Sleep", "Compensate"]}>
|
|
103
|
+
|
|
104
|
+
<Tab value="Minimal">
|
|
105
|
+
|
|
106
|
+
Two named steps. `fx.id()` lives **inside** the step so resume reuses the journaled id:
|
|
107
|
+
|
|
108
|
+
```typescript title="src/flows/billing/charge.ts"
|
|
109
|
+
import { on, flow, http } from "okengine";
|
|
110
|
+
import { z } from "zod";
|
|
111
|
+
import { db, payments } from "@/schema";
|
|
112
|
+
|
|
113
|
+
export const charge = on(
|
|
114
|
+
http.post({
|
|
115
|
+
in: z.object({ userId: z.string(), amount: z.number() }),
|
|
116
|
+
out: z.object({ paymentId: z.string() }),
|
|
117
|
+
}),
|
|
118
|
+
flow({
|
|
119
|
+
durable: true,
|
|
120
|
+
do: async ({ userId, amount }, fx) => {
|
|
121
|
+
const paymentId = await fx.step("create-intent", async () => {
|
|
122
|
+
const id = fx.id();
|
|
123
|
+
await fx.store(db).insert(payments).values({ id, userId, amount });
|
|
124
|
+
return id;
|
|
125
|
+
});
|
|
126
|
+
await fx.step("confirm", async () => {
|
|
127
|
+
await fx.call(capturePayment, { paymentId });
|
|
128
|
+
});
|
|
129
|
+
return { paymentId };
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
</Tab>
|
|
136
|
+
|
|
137
|
+
<Tab value="Undo">
|
|
138
|
+
|
|
139
|
+
`{ undo }` receives the journaled return value. On terminal failure, completed
|
|
140
|
+
undos run last-in first-out; the failed step does **not** undo:
|
|
141
|
+
|
|
142
|
+
```typescript title="src/flows/orders/checkout.ts"
|
|
143
|
+
import { on, flow, http } from "okengine";
|
|
144
|
+
import { z } from "zod";
|
|
145
|
+
import { eq } from "drizzle-orm";
|
|
146
|
+
import { db, charges } from "@/schema";
|
|
147
|
+
|
|
148
|
+
export const checkout = on(
|
|
149
|
+
http.post({ in: z.object({ userId: z.string() }) }),
|
|
150
|
+
flow({
|
|
151
|
+
durable: true,
|
|
152
|
+
do: async ({ userId }, fx) => {
|
|
153
|
+
const charge = await fx.step(
|
|
154
|
+
"charge",
|
|
155
|
+
async () => {
|
|
156
|
+
const id = fx.id();
|
|
157
|
+
await fx.store(db).insert(charges).values({ id, userId, amount: 50 });
|
|
158
|
+
return { id };
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
undo: async (res) => {
|
|
162
|
+
await fx.store(db).delete(charges).where(eq(charges.id, res.id));
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
await fx.step("fulfill", async () => {
|
|
167
|
+
await fx.call(fulfillOrder, { chargeId: charge.id });
|
|
168
|
+
});
|
|
169
|
+
return { chargeId: charge.id };
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
</Tab>
|
|
176
|
+
|
|
177
|
+
<Tab value="Sleep">
|
|
178
|
+
|
|
179
|
+
`fx.clock.sleep(label, duration)` parks the run and releases the worker. HTTP
|
|
180
|
+
returns **`204 No Content`** immediately — the caller is not waiting at wake:
|
|
181
|
+
|
|
182
|
+
```typescript title="src/flows/trials/reminder.ts"
|
|
183
|
+
import { on, flow, http } from "okengine";
|
|
184
|
+
import { z } from "zod";
|
|
185
|
+
import { trialExpiringEmail } from "@/channels/trial";
|
|
186
|
+
|
|
187
|
+
export const reminder = on(
|
|
188
|
+
http.post({ in: z.object({ email: z.string().email() }) }),
|
|
189
|
+
flow({
|
|
190
|
+
durable: true,
|
|
191
|
+
do: async ({ email }, fx) => {
|
|
192
|
+
await fx.step("mark-trial", async () => {
|
|
193
|
+
await fx.call(startTrial, { email });
|
|
194
|
+
});
|
|
195
|
+
await fx.clock.sleep("expiry-window", "3d");
|
|
196
|
+
await fx.step("notify", async () => {
|
|
197
|
+
await fx.send(trialExpiringEmail, { to: email });
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Without `durable: true`, `sleep` resolves immediately and does not park.
|
|
205
|
+
|
|
206
|
+
</Tab>
|
|
207
|
+
|
|
208
|
+
<Tab value="Compensate">
|
|
209
|
+
|
|
210
|
+
Per-step `{ undo }` runs first (LIFO), then optional `compensate` for
|
|
211
|
+
cross-cutting cleanup. Manual undo work uses distinct `undo:…` step names:
|
|
212
|
+
|
|
213
|
+
```typescript title="src/flows/orders/checkout.ts"
|
|
214
|
+
import { on, flow, http } from "okengine";
|
|
215
|
+
import { z } from "zod";
|
|
216
|
+
|
|
217
|
+
export const checkout = on(
|
|
218
|
+
http.post({ in: z.object({ userId: z.string(), sku: z.string() }) }),
|
|
219
|
+
flow({
|
|
220
|
+
durable: true,
|
|
221
|
+
do: async ({ userId, sku }, fx) => {
|
|
222
|
+
await fx.step("reserve", () => fx.call(reserveStock, { sku }), {
|
|
223
|
+
undo: () => fx.call(releaseStock, { sku }),
|
|
224
|
+
});
|
|
225
|
+
await fx.step("charge", () => fx.call(chargeCard, { userId }));
|
|
226
|
+
return { ok: true as const };
|
|
227
|
+
},
|
|
228
|
+
compensate: async (ctx, fx) => {
|
|
229
|
+
await fx.step("undo:alert", async () => {
|
|
230
|
+
await fx.send(opsAlert, {
|
|
231
|
+
to: "oncall@example.com",
|
|
232
|
+
data: {
|
|
233
|
+
steps: ctx.completedSteps.join(", "),
|
|
234
|
+
error: String(ctx.error),
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`compensate` does **not** run on success, between `retry` attempts, or on sleep park.
|
|
244
|
+
|
|
245
|
+
</Tab>
|
|
246
|
+
|
|
247
|
+
</Tabs>
|
|
248
|
+
|
|
249
|
+
## Options Reference
|
|
250
|
+
|
|
251
|
+
| Option / call | Type | Default | Meaning |
|
|
252
|
+
| --------------------------------- | ---------------------- | --------- | --------------------------------------------------------- |
|
|
253
|
+
| `durable` | `boolean` | `false` | Journal `fx.step`, `fx.clock.sleep`, and gated `fx` calls |
|
|
254
|
+
| `compensate` | `(ctx, fx) => unknown` | omitted | After LIFO undos, before the run commits `failed` |
|
|
255
|
+
| `retry` | `FxRetryOptions` | omitted | Whole-`do` retry on the **same** journal session |
|
|
256
|
+
| `fx.step(name, fn, opts?)` | step | — | Named checkpoint; `{ undo }` is optional |
|
|
257
|
+
| `fx.clock.sleep(label, duration)` | park | — | Durable pause; two arguments (label then duration) |
|
|
258
|
+
| `fx.retry(fn, opts?)` | inner retry | see Retry | Put **inside** a step so a completed charge never re-runs |
|
|
259
|
+
|
|
260
|
+
`compensate` context: `{ input, error, completedSteps }` — `completedSteps` are
|
|
261
|
+
forward names only (`undo:…` entries are excluded).
|
|
262
|
+
|
|
263
|
+
**Consequence:** `durable: true` disables automatic read-cache for that Flow.
|
|
264
|
+
|
|
265
|
+
## Steps
|
|
266
|
+
|
|
267
|
+
<Callout title="Detailed section">
|
|
268
|
+
If you only need a named checkpoint, jump to Replay below. Step names must be unique per run. The
|
|
269
|
+
prefix `undo:` is reserved for compensation — a forward step with that prefix throws `journal:
|
|
270
|
+
step name "…" uses reserved prefix "undo:"`.
|
|
271
|
+
</Callout>
|
|
272
|
+
|
|
273
|
+
Every `fx.step` persists `{ name, value }` before the next line runs. On resume the
|
|
274
|
+
engine matches by name, returns the stored value, and skips the function.
|
|
275
|
+
|
|
276
|
+
<Tabs items={["Replay", "At-least-once", "Inner retry"]}>
|
|
277
|
+
|
|
278
|
+
<Tab value="Replay">
|
|
279
|
+
|
|
280
|
+
Completed steps are skipped. Generate ids and call providers **inside** the step:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const intent = await fx.step("create-intent", async () => {
|
|
284
|
+
const id = fx.id();
|
|
285
|
+
await fx.call(createPaymentIntent, { id, amount: input.total });
|
|
286
|
+
return { id };
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`fx.id()` outside a step mints a new id on every resume. Gated `fx` calls
|
|
291
|
+
(`store`, `emit`, `send`, `ask`, `call`, `vault`) are also journaled in call
|
|
292
|
+
order — named steps are the stable checkpoint when the sequence might branch.
|
|
293
|
+
|
|
294
|
+
</Tab>
|
|
295
|
+
|
|
296
|
+
<Tab value="At-least-once">
|
|
297
|
+
|
|
298
|
+
A crash **during** a step (before persist) re-runs that function. Completed
|
|
299
|
+
neighbors never re-run. Make the in-flight body safe to repeat, or persist at
|
|
300
|
+
the provider first and journal only the id (the create-intent pattern).
|
|
301
|
+
|
|
302
|
+
**Consequence:** two replicas will not double-run a **journaled** step; they
|
|
303
|
+
may double-run the step that was in flight when the holder died.
|
|
304
|
+
|
|
305
|
+
</Tab>
|
|
306
|
+
|
|
307
|
+
<Tab value="Inner retry">
|
|
308
|
+
|
|
309
|
+
`fx.retry` inside `fx.step` retries the provider without committing a step
|
|
310
|
+
until success. Flow-level `retry` re-enters `do` on the same journal — completed
|
|
311
|
+
steps still replay:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const charge = await fx.step("charge", () =>
|
|
315
|
+
fx.retry(() => fx.call(stripeCharge, { amount: input.total }), {
|
|
316
|
+
retries: 3,
|
|
317
|
+
delay: "100ms",
|
|
318
|
+
backoff: 2,
|
|
319
|
+
jitter: true,
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Do **not** put `fx.retry` around the whole `do` by hand — use `flow({ retry })`.
|
|
325
|
+
|
|
326
|
+
</Tab>
|
|
327
|
+
|
|
328
|
+
</Tabs>
|
|
329
|
+
|
|
330
|
+
<Accordions>
|
|
331
|
+
|
|
332
|
+
<Accordion title="Step Options">
|
|
333
|
+
Third argument to `fx.step(name, fn, options)`.
|
|
334
|
+
|
|
335
|
+
| Option | Type | Default | Meaning |
|
|
336
|
+
| ------ | -------------------- | ------- | -------------------------------------------------------- |
|
|
337
|
+
| `undo` | `(value) => unknown` | omitted | Runs on terminal failure with the journaled value (LIFO) |
|
|
338
|
+
|
|
339
|
+
`undo` closures are re-bound on resume by re-entering `do` without new forward
|
|
340
|
+
work. Nested `{ undo }` on an `undo:…` step throws
|
|
341
|
+
`journal: undo steps cannot register nested undo`.
|
|
342
|
+
|
|
343
|
+
</Accordion>
|
|
344
|
+
|
|
345
|
+
<Accordion title="Duplicate names">
|
|
346
|
+
A second forward step with the same name throws
|
|
347
|
+
`journal: duplicate step name "charge"`. Pick a new name (`charge-tax`) or
|
|
348
|
+
fold the work into the first step.
|
|
349
|
+
|
|
350
|
+
Sleep matches by **label** from the cursor — use a distinct label per pause.
|
|
351
|
+
|
|
352
|
+
</Accordion>
|
|
353
|
+
|
|
354
|
+
<Accordion title="Manifest steps">
|
|
355
|
+
The compiler records `fx.step("…")` string names on the Flow as `steps`.
|
|
356
|
+
Removing a name is a Manifest contract change (`oke doctor --diff`). Adding
|
|
357
|
+
a name is recorded the same way. Names only — bodies are not in the Manifest.
|
|
358
|
+
|
|
359
|
+
</Accordion>
|
|
360
|
+
|
|
361
|
+
<Accordion title="fx.using is not journaled">
|
|
362
|
+
`fx.using(acquire, release, use)` is same-attempt cleanup. Do not hold a
|
|
363
|
+
connection or file handle across `fx.clock.sleep` — acquire again after wake.
|
|
364
|
+
|
|
365
|
+
</Accordion>
|
|
366
|
+
|
|
367
|
+
</Accordions>
|
|
368
|
+
|
|
369
|
+
## Compensation
|
|
370
|
+
|
|
371
|
+
<Callout title="Detailed section">
|
|
372
|
+
If you only need per-step refunds, jump to the table below. Compensation runs on throw **and**
|
|
373
|
+
`fx.fail` — after retries are exhausted, never on sleep park.
|
|
374
|
+
</Callout>
|
|
375
|
+
|
|
376
|
+
When a durable run fails terminally, status becomes `compensating`, then `failed`.
|
|
377
|
+
|
|
378
|
+
| Step | State | Action when `fulfill` throws |
|
|
379
|
+
| --------------------------- | ---------------------- | ------------------------------- |
|
|
380
|
+
| 1. `charge` with `{ undo }` | Succeeded | `undo(journaledValue)` — refund |
|
|
381
|
+
| 2. `fulfill` | Failed (not persisted) | No undo for this step |
|
|
382
|
+
| 3. later work | Not started | Never executed |
|
|
383
|
+
|
|
384
|
+
Order: reverse `{ undo }` frames, then `flow.compensate`, then commit `failed`.
|
|
385
|
+
If an undo or `compensate` throws, the journal error is `compensate:{code}`.
|
|
386
|
+
|
|
387
|
+
<Accordions>
|
|
388
|
+
|
|
389
|
+
<Accordion title="compensate context">
|
|
390
|
+
|
|
391
|
+
| Field | Meaning |
|
|
392
|
+
| ---------------- | --------------------------------------- |
|
|
393
|
+
| `input` | Original validated Flow input |
|
|
394
|
+
| `error` | Thrown value or `fx.fail` result |
|
|
395
|
+
| `completedSteps` | Forward step names (no `undo:` entries) |
|
|
396
|
+
|
|
397
|
+
Use `compensate` for alerts and cross-cutting cleanup. Prefer `{ undo }` for
|
|
398
|
+
the reverse of one step. Manual bodies must call `fx.step("undo:…", …)` — never
|
|
399
|
+
reuse a forward name.
|
|
400
|
+
|
|
401
|
+
</Accordion>
|
|
402
|
+
|
|
403
|
+
<Accordion title="Orphan mid-undo">
|
|
404
|
+
A crash during compensation resumes in `compensating`. Already-journaled
|
|
405
|
+
`undo:charge` is skipped; remaining undos continue — forward `do` does not
|
|
406
|
+
re-enter. Failed/completed runs refuse resume (`journal: run is already failed`).
|
|
407
|
+
|
|
408
|
+
</Accordion>
|
|
409
|
+
|
|
410
|
+
<Accordion title="Retry vs undo">
|
|
411
|
+
`flow({ retry })` does **not** undo between attempts. Undos run once, after
|
|
412
|
+
the last extra attempt still throws or `fx.fail`s.
|
|
413
|
+
|
|
414
|
+
</Accordion>
|
|
415
|
+
|
|
416
|
+
</Accordions>
|
|
417
|
+
|
|
418
|
+
## Durable Sleep
|
|
419
|
+
|
|
420
|
+
<Callout title="Detailed section">
|
|
421
|
+
If you only need a pause, jump to the example below. Signature is `fx.clock.sleep(label,
|
|
422
|
+
duration)` — a single duration string is the **label**, not the wait. Durations: `"200ms"` ·
|
|
423
|
+
`"30s"` · `"2m"` · `"1h"` · `"7d"`.
|
|
424
|
+
</Callout>
|
|
425
|
+
|
|
426
|
+
Sleep writes a wake time, sets status `sleeping`, and **releases the run lease**
|
|
427
|
+
so a parked flow does not hold a 30s lock for days. Any instance may claim the
|
|
428
|
+
row when `wakeAt` is due.
|
|
429
|
+
|
|
430
|
+
```typescript title="src/flows/onboarding/welcome.ts"
|
|
431
|
+
import { on, flow } from "okengine";
|
|
432
|
+
import { userSignedUp } from "@/signals";
|
|
433
|
+
import { welcomeEmail } from "@/channels/welcome";
|
|
434
|
+
|
|
435
|
+
export const sendWelcome = on(
|
|
436
|
+
userSignedUp,
|
|
437
|
+
flow("onboarding.welcome", {
|
|
438
|
+
durable: true,
|
|
439
|
+
do: async ({ email }, fx) => {
|
|
440
|
+
await fx.step("provision", async () => {
|
|
441
|
+
await fx.call(createWorkspace, { email });
|
|
442
|
+
});
|
|
443
|
+
await fx.clock.sleep("morning-window", "8h");
|
|
444
|
+
await fx.step("notify", async () => {
|
|
445
|
+
await fx.send(welcomeEmail, { to: email });
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
HTTP + sleep: the request returns `204` with an empty body. Resume is a
|
|
453
|
+
scheduler job, not a second response to that client.
|
|
454
|
+
|
|
455
|
+
<Accordions>
|
|
456
|
+
|
|
457
|
+
<Accordion title="Duration strings">
|
|
458
|
+
Integer + unit only — no weeks. `"d"` is 86_400_000 ms, not a calendar day.
|
|
459
|
+
Unknown strings parse as `0` (wake immediately). Same grammar as
|
|
460
|
+
`fx.clock.ago` / `fromNow`.
|
|
461
|
+
|
|
462
|
+
</Accordion>
|
|
463
|
+
|
|
464
|
+
<Accordion title="Do not fx.call a sleeper">
|
|
465
|
+
`fx.call` waits for the callee to return. If the callee parks, the caller
|
|
466
|
+
receives `undefined` and continues; the child wakes later as its own run.
|
|
467
|
+
Sleep on the **root** durable Flow, or split with `fx.emit` to a consumer.
|
|
468
|
+
|
|
469
|
+
</Accordion>
|
|
470
|
+
|
|
471
|
+
<Accordion title="Non-durable sleep">
|
|
472
|
+
Without a journal, `fx.clock.sleep` resolves immediately (tests and sync
|
|
473
|
+
Flows). There is no thread sleep and no `setTimeout`.
|
|
474
|
+
|
|
475
|
+
</Accordion>
|
|
476
|
+
|
|
477
|
+
</Accordions>
|
|
478
|
+
|
|
479
|
+
## Retry
|
|
480
|
+
|
|
481
|
+
Two layers — do not mix them up.
|
|
482
|
+
|
|
483
|
+
| Layer | Where | Journal | Undo |
|
|
484
|
+
| --------------------------- | ----------------- | -------------------------------- | ---------------------------- |
|
|
485
|
+
| `fx.retry` inside `fx.step` | One provider call | Step commits once, after success | No |
|
|
486
|
+
| `flow({ retry })` | Whole `do` | Same session; rewind + replay | After the last attempt fails |
|
|
487
|
+
|
|
488
|
+
| `retry` option | Default | Meaning |
|
|
489
|
+
| -------------- | ---------------------- | --------------------------------------------- |
|
|
490
|
+
| `retries` | `0` | Extra attempts after the first |
|
|
491
|
+
| `delay` | `50` (ms) or `"100ms"` | Initial backoff |
|
|
492
|
+
| `backoff` | `2` | Multiplier after each retry |
|
|
493
|
+
| `jitter` | `true` | Full jitter (thundering-herd) |
|
|
494
|
+
| `when` | thrown errors | Skips abort and sleep park (`JournalSuspend`) |
|
|
495
|
+
|
|
496
|
+
**Consequence:** put provider retries inside the step; use Flow `retry` for
|
|
497
|
+
transient failures **after** a step (network to your own `fx.call`).
|
|
498
|
+
|
|
499
|
+
## Journal
|
|
500
|
+
|
|
501
|
+
<Callout title="Detailed section">
|
|
502
|
+
If you only need defaults, jump to the table. `drivers.journal` is `postgres` in `dev`/`prod` and
|
|
503
|
+
`memory` in `test`. Pin `file` for a single host without Postgres.
|
|
504
|
+
</Callout>
|
|
505
|
+
|
|
506
|
+
The journal is a driver, not an element. Runs are rows: `running` · `sleeping` ·
|
|
507
|
+
`compensating` · `completed` · `failed`.
|
|
508
|
+
|
|
509
|
+
| Driver | Default env | Best for |
|
|
510
|
+
| ---------- | -------------- | ---------------------------------------------------- |
|
|
511
|
+
| `postgres` | `dev`, `prod` | Shared durable runs across replicas (`DATABASE_URL`) |
|
|
512
|
+
| `memory` | `test` | Process-local; lost on exit |
|
|
513
|
+
| `file` | pin explicitly | One machine — `.oke/journal.json` |
|
|
514
|
+
|
|
515
|
+
Unknown ids throw `oke boot: unknown journal driver "…" (expected memory · file · postgres)`.
|
|
516
|
+
Postgres without a URL throws `oke boot: journal driver "postgres" needs DATABASE_URL`.
|
|
517
|
+
|
|
518
|
+
<Accordions>
|
|
519
|
+
|
|
520
|
+
<Accordion title="Leases">
|
|
521
|
+
Default lease is **30s** (same as Signal claims). A live holder renews on
|
|
522
|
+
every journal write. Sleep and terminal commit **release** the lease.
|
|
523
|
+
|
|
524
|
+
Resume that loses the race throws `journal: run "{id}" is leased by another instance`
|
|
525
|
+
(`JournalLeaseBusy`). The other holder continues; this instance skips.
|
|
526
|
+
|
|
527
|
+
</Accordion>
|
|
528
|
+
|
|
529
|
+
<Accordion title="Orphans & ready">
|
|
530
|
+
Boot resumes `running` / `sleeping` / `compensating` rows with no live lease;
|
|
531
|
+
future sleeps stay scheduled until `wakeAt`. `GET /_/ready` stays
|
|
532
|
+
`503 { ready: false, reason: "orphan_scan" }` until that scan finishes.
|
|
533
|
+
|
|
534
|
+
</Accordion>
|
|
535
|
+
|
|
536
|
+
<Accordion title="Statuses">
|
|
537
|
+
|
|
538
|
+
| Status | Meaning |
|
|
539
|
+
| -------------- | ------------------------------------- |
|
|
540
|
+
| `running` | In-flight attempt; lease held |
|
|
541
|
+
| `sleeping` | Parked; lease released; `wakeAt` set |
|
|
542
|
+
| `compensating` | LIFO undos / `compensate` in progress |
|
|
543
|
+
| `completed` | Terminal success — resume refused |
|
|
544
|
+
| `failed` | Terminal failure — resume refused |
|
|
545
|
+
|
|
546
|
+
</Accordion>
|
|
547
|
+
|
|
548
|
+
</Accordions>
|
|
549
|
+
|
|
550
|
+
## Troubleshooting
|
|
551
|
+
|
|
552
|
+
<Accordions>
|
|
553
|
+
|
|
554
|
+
<Accordion title='journal: duplicate step name "charge"'>
|
|
555
|
+
Two forward `fx.step("charge", …)` calls in one run. Rename one, or combine the work. Compensation
|
|
556
|
+
uses `undo:charge` automatically — do not declare a second forward step with that name.
|
|
557
|
+
</Accordion>
|
|
558
|
+
|
|
559
|
+
<Accordion title='journal: step name "undo:x" uses reserved prefix "undo:"'>
|
|
560
|
+
`undo:` is for the compensation phase. Forward work needs a plain name (`refund`). Inside
|
|
561
|
+
`compensate`, `fx.step("undo:alert", …)` is the intended form.
|
|
562
|
+
</Accordion>
|
|
563
|
+
|
|
564
|
+
<Accordion title="HTTP 204 with empty body after POST">
|
|
565
|
+
The Flow parked on `fx.clock.sleep` — success, not a missing handler. The original client is done;
|
|
566
|
+
wake continues on a worker. Return a body **before** sleep if the caller must see an id, or emit
|
|
567
|
+
to a Signal consumer for the rest.
|
|
568
|
+
</Accordion>
|
|
569
|
+
|
|
570
|
+
<Accordion title="Sleep returns immediately / work runs twice after wait">
|
|
571
|
+
Missing `durable: true`, or `fx.clock.sleep("8h")` with one argument — `"8h"` is the label,
|
|
572
|
+
duration is missing. Use `fx.clock.sleep("label", "8h")`. Non-durable sleep is a no-op.
|
|
573
|
+
</Accordion>
|
|
574
|
+
|
|
575
|
+
<Accordion title="Card charged twice after a crash">
|
|
576
|
+
The provider call was outside `fx.step`, or the crash was mid-step (at-least-once). Move
|
|
577
|
+
create-intent into a step and make confirm idempotent. `Date.now()` / `fetch` bypass the journal —
|
|
578
|
+
use `fx.clock.now()` and `fx.step`.
|
|
579
|
+
</Accordion>
|
|
580
|
+
|
|
581
|
+
<Accordion title="journal: run is leased by another instance">
|
|
582
|
+
Two instances claimed the same run. This is skip-not-fail: the holder continues. Shared `postgres`
|
|
583
|
+
(or `file` on one host) is required — `memory` does not coordinate across processes.
|
|
584
|
+
</Accordion>
|
|
585
|
+
|
|
586
|
+
<Accordion title="oke boot: journal driver postgres needs DATABASE_URL">
|
|
587
|
+
Dev/prod default is `postgres`. Set `DATABASE_URL`, or pin `drivers.journal.test` / a non-Postgres
|
|
588
|
+
map in `oke.config.ts` for local experiments without SQL.
|
|
589
|
+
</Accordion>
|
|
590
|
+
|
|
591
|
+
<Accordion title="compensate:{code} on the failed run">
|
|
592
|
+
An `{undo}` or `compensate` body threw. Fix the reverse path; the forward error is already
|
|
593
|
+
recorded. Forward `do` will not re-run on that run id.
|
|
594
|
+
</Accordion>
|
|
595
|
+
|
|
596
|
+
<Accordion title="fx.call of a durable sleeper returned undefined">
|
|
597
|
+
The callee parked. Sleep on the root Flow, or emit to a durable consumer instead of calling a
|
|
598
|
+
sleeper inline.
|
|
599
|
+
</Accordion>
|
|
600
|
+
|
|
601
|
+
<Accordion title="GET /_/ready is 503 reason orphan_scan">
|
|
602
|
+
Boot is resuming durable orphans. Wait — do not point a liveness probe at `/_/ready`. Use a
|
|
603
|
+
separate liveness check; readiness may stay 503 until the orphan scan finishes.
|
|
604
|
+
</Accordion>
|
|
605
|
+
|
|
606
|
+
</Accordions>
|
|
607
|
+
|
|
608
|
+
## Learn more
|
|
609
|
+
|
|
610
|
+
- [Flow](/docs/elements/flow) — `durable`, `retry`, `compensate` on the Flow options table
|
|
611
|
+
- [Consumers](/docs/elements/flow/consumers) — Signal / Clock / CDC as the same species
|
|
612
|
+
- [HTTP](/docs/elements/flow/http) — request envelope; `204` from `undefined`
|
|
613
|
+
- [Clock · Durable Sleep](/docs/elements/clock/sleep) — pause physics
|
|
614
|
+
- [fx](/docs/reference/fx) — `fx.step`, `fx.retry`, `fx.clock.sleep`, `fx.using`
|
|
615
|
+
- [Configuration](/docs/reference/configuration) — `drivers.journal`
|
|
616
|
+
|
|
617
|
+
## Next
|
|
618
|
+
|
|
619
|
+
<Cards>
|
|
620
|
+
<Card
|
|
621
|
+
title="Durable Sleep"
|
|
622
|
+
description="Process-safe pauses that resume across reboots."
|
|
623
|
+
href="/docs/elements/clock/sleep"
|
|
624
|
+
/>
|
|
625
|
+
<Card
|
|
626
|
+
title="Consumers"
|
|
627
|
+
description="Signal workers, named Clock jobs, and SQL CDC — one Flow species."
|
|
628
|
+
href="/docs/elements/flow/consumers"
|
|
629
|
+
/>
|
|
630
|
+
<Card
|
|
631
|
+
title="HTTP"
|
|
632
|
+
description="Synchronous REST, QUERY, resources, and live SSE."
|
|
633
|
+
href="/docs/elements/flow/http"
|
|
634
|
+
/>
|
|
635
|
+
<Card
|
|
636
|
+
title="Flow Overview"
|
|
637
|
+
description="One shape for every kind of backend behavior."
|
|
638
|
+
href="/docs/elements/flow"
|
|
639
|
+
/>
|
|
640
|
+
</Cards>
|