okengine 0.18.5 → 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/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 +20 -10
- 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 +63 -36
- package/site/content/docs/meta.json +8 -7
- 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/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 +257 -43
- 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 -144
- 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 -114
- 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-3-EFj-2G.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-cVFnA4HH.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-CMIgUbD0.js +0 -66
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.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-l8FeKfnP.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-CL-d_mLE.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,596 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Once"
|
|
3
|
+
description: "Competing workers, visibility leases, retries, dead letters, and per-key ordering for signal.once."
|
|
4
|
+
icon: "ListOrdered"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`signal.once` processes background work with competing consumers — exactly one worker claims each
|
|
9
|
+
message; failed attempts retry, then dead-letter.
|
|
10
|
+
|
|
11
|
+
For developers shipping jobs on okengine — declare the Signal, bind `on(signal, flow)`, emit with
|
|
12
|
+
`fx.emit`.
|
|
13
|
+
|
|
14
|
+
<Callout title="The one rule">
|
|
15
|
+
Physics live on the Signal (`retries`, `deadLetter`, `optional`). The Flow is the subscriber.
|
|
16
|
+
Visibility lease defaults to **30s** — an inflight worker that dies is reclaimed on the next
|
|
17
|
+
claim.
|
|
18
|
+
</Callout>
|
|
19
|
+
|
|
20
|
+
<SignalOnceLease />
|
|
21
|
+
|
|
22
|
+
## Smallest Example
|
|
23
|
+
|
|
24
|
+
<Steps>
|
|
25
|
+
|
|
26
|
+
<Step>
|
|
27
|
+
### Define the once signal
|
|
28
|
+
|
|
29
|
+
```typescript title="src/signals/email.ts"
|
|
30
|
+
import { signal } from "okengine";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
|
|
33
|
+
export const emailTask = signal.once("tasks.email", {
|
|
34
|
+
schema: z.object({ to: z.string().email(), body: z.string() }),
|
|
35
|
+
retries: 3,
|
|
36
|
+
deadLetter: true,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
</Step>
|
|
41
|
+
|
|
42
|
+
<Step>
|
|
43
|
+
### Attach a worker and emit
|
|
44
|
+
|
|
45
|
+
```typescript title="src/flows/workers/email.ts"
|
|
46
|
+
import { on, flow } from "okengine";
|
|
47
|
+
import { emailTask } from "@/signals/email";
|
|
48
|
+
import { rawEmail } from "@/channels/email";
|
|
49
|
+
|
|
50
|
+
export const processEmail = on(
|
|
51
|
+
emailTask,
|
|
52
|
+
flow("workers.email", {
|
|
53
|
+
do: async ({ to, body }, fx) => {
|
|
54
|
+
await fx.send(rawEmail, { to, body });
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
await fx.emit(emailTask, { to: "alice@example.com", body: "Welcome" });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The emit resolves when the outbox commits. The worker runs asynchronously — the producer does not
|
|
65
|
+
wait for `fx.send` to finish.
|
|
66
|
+
|
|
67
|
+
</Step>
|
|
68
|
+
|
|
69
|
+
</Steps>
|
|
70
|
+
|
|
71
|
+
<Callout title="Competing, not fan-out">
|
|
72
|
+
Two Flows bound to the same `once` Signal compete — exactly one claims each message. For every
|
|
73
|
+
subscriber to run, use [`broadcast`](/docs/elements/signal/broadcast).
|
|
74
|
+
</Callout>
|
|
75
|
+
|
|
76
|
+
## Progressive Patterns
|
|
77
|
+
|
|
78
|
+
From a minimal job to ordered partitions and DLQ inspection:
|
|
79
|
+
|
|
80
|
+
<Tabs items={["Minimal", "Retries", "Ordered", "Dead letters"]}>
|
|
81
|
+
|
|
82
|
+
<Tab value="Minimal">
|
|
83
|
+
|
|
84
|
+
Defaults are `retries: 3`, `deadLetter: true`, `optional: false`:
|
|
85
|
+
|
|
86
|
+
```typescript title="src/signals/orders.ts"
|
|
87
|
+
import { signal } from "okengine";
|
|
88
|
+
import { z } from "zod";
|
|
89
|
+
|
|
90
|
+
export const orderPlaced = signal.once("orders.placed", {
|
|
91
|
+
schema: z.object({
|
|
92
|
+
orderId: z.string(),
|
|
93
|
+
amount: z.number(),
|
|
94
|
+
userId: z.string(),
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```typescript title="src/flows/orders/fulfill.ts"
|
|
100
|
+
import { on, flow } from "okengine";
|
|
101
|
+
import { orderPlaced } from "@/signals/orders";
|
|
102
|
+
|
|
103
|
+
export const fulfill = on(
|
|
104
|
+
orderPlaced,
|
|
105
|
+
flow("orders.fulfill", {
|
|
106
|
+
do: async ({ orderId }, fx) => {
|
|
107
|
+
await fx.call(chargeAndShip, { orderId });
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
</Tab>
|
|
114
|
+
|
|
115
|
+
<Tab value="Retries">
|
|
116
|
+
|
|
117
|
+
`retries` is extra attempts after the first — total handler invocations are `retries + 1`.
|
|
118
|
+
Failed attempts requeue immediately for another claim (no delay between attempts).
|
|
119
|
+
Make consumers idempotent; at-least-once delivery can re-run after lease reclaim:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
export const syncPayment = signal.once("payments.sync", {
|
|
123
|
+
retries: 5,
|
|
124
|
+
deadLetter: true,
|
|
125
|
+
schema: z.object({ chargeId: z.string() }),
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
</Tab>
|
|
130
|
+
|
|
131
|
+
<Tab value="Ordered">
|
|
132
|
+
|
|
133
|
+
Pass `{ key }` on emit. No two `once` messages sharing `(signal, key)` are claimed at once —
|
|
134
|
+
the in-flight visibility lease is the lock:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
await fx.emit(emailTask, payload, { key: user.id });
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Same key → FIFO. Different keys run in parallel. Omit `key` for a pure competing pool.
|
|
141
|
+
|
|
142
|
+
</Tab>
|
|
143
|
+
|
|
144
|
+
<Tab value="Dead letters">
|
|
145
|
+
|
|
146
|
+
After `retries + 1` failures with `deadLetter: true`, the message is `dead`. Inspect with
|
|
147
|
+
`fx.deadLetters` (records `reads` on `signal:<name>`):
|
|
148
|
+
|
|
149
|
+
```typescript title="src/flows/ops/email-dlq.ts"
|
|
150
|
+
import { on, flow, http } from "okengine";
|
|
151
|
+
import { emailTask } from "@/signals/email";
|
|
152
|
+
|
|
153
|
+
export const list = on(
|
|
154
|
+
http.get(),
|
|
155
|
+
flow({
|
|
156
|
+
effects: { reads: ["signal:tasks.email"] },
|
|
157
|
+
do: async (_, fx) => {
|
|
158
|
+
return await fx.deadLetters(emailTask);
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`deadLetter: false` marks the message delivered after the last attempt — nothing lands in the DLQ.
|
|
165
|
+
|
|
166
|
+
</Tab>
|
|
167
|
+
|
|
168
|
+
</Tabs>
|
|
169
|
+
|
|
170
|
+
## Delivery Reference
|
|
171
|
+
|
|
172
|
+
| Surface | Signature | Purpose |
|
|
173
|
+
| ----------- | ------------------------------------- | --------------------------------------------------- |
|
|
174
|
+
| Declare | `signal.once(name, options?)` | Competing queue — one claim per message |
|
|
175
|
+
| Bind | `on(signalHandle, flow)` | Worker Flow; payload is `do`'s input |
|
|
176
|
+
| Emit | `fx.emit(signal, payload?, { key? })` | Enrol in the outbox; records `emits` |
|
|
177
|
+
| Inspect DLQ | `fx.deadLetters(signal)` | Exhausted messages; records `reads` `signal:<name>` |
|
|
178
|
+
|
|
179
|
+
| Need | Use instead |
|
|
180
|
+
| -------------------------------------- | -------------------------------------------------- |
|
|
181
|
+
| Every active subscriber gets a copy | [`broadcast`](/docs/elements/signal/broadcast) |
|
|
182
|
+
| Browser / SSE resume after disconnect | [`live`](/docs/elements/signal/live) |
|
|
183
|
+
| Durable multi-step work with a journal | [Durable Workflows](/docs/elements/flow/workflows) |
|
|
184
|
+
|
|
185
|
+
## Options for once
|
|
186
|
+
|
|
187
|
+
Optional second argument to `signal.once`. Delivery is the helper name — not an option.
|
|
188
|
+
|
|
189
|
+
| Option | Type | Default | Meaning |
|
|
190
|
+
| ------------- | --------------- | -------- | ----------------------------------------------------------------------------------------- |
|
|
191
|
+
| `schema` | Standard Schema | omitted | **Emit** contract — validated at `fx.emit` (**OKE1250**). Workers inherit payload typing. |
|
|
192
|
+
| `retries` | `number` | `3` | Extra attempts after the first (`retries + 1` total) |
|
|
193
|
+
| `deadLetter` | `boolean` | `true` | Keep exhausted messages; not a queue name |
|
|
194
|
+
| `optional` | `boolean` | `false` | Allow emit with zero subscribers |
|
|
195
|
+
| `description` | `string` | the name | Console / docs blurb |
|
|
196
|
+
|
|
197
|
+
`retention` is a type error on `signal.once` — that option is live-only:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
signal.once("…"): retention is only valid with signal.live
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Consequence:** `deadLetter` is a boolean flag, not a DLQ signal name string.
|
|
204
|
+
|
|
205
|
+
## Binding Workers
|
|
206
|
+
|
|
207
|
+
Each worker binds with `on(signalHandle, flow(...))`. The Signal carries delivery physics; the
|
|
208
|
+
Flow is only the handler.
|
|
209
|
+
|
|
210
|
+
<Tabs items={["Declare", "Bind", "Competing", "Optional"]}>
|
|
211
|
+
|
|
212
|
+
<Tab value="Declare">
|
|
213
|
+
|
|
214
|
+
Name the Signal and attach retry / DLQ / schema policy:
|
|
215
|
+
|
|
216
|
+
```typescript title="src/signals/email.ts"
|
|
217
|
+
import { signal } from "okengine";
|
|
218
|
+
import { z } from "zod";
|
|
219
|
+
|
|
220
|
+
export const emailTask = signal.once("tasks.email", {
|
|
221
|
+
schema: z.object({ to: z.string().email(), body: z.string() }),
|
|
222
|
+
retries: 3,
|
|
223
|
+
deadLetter: true,
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
</Tab>
|
|
228
|
+
|
|
229
|
+
<Tab value="Bind">
|
|
230
|
+
|
|
231
|
+
Subscribe with the same handle. Payload fields destructure in `do`:
|
|
232
|
+
|
|
233
|
+
```typescript title="src/flows/workers/email.ts"
|
|
234
|
+
import { on, flow } from "okengine";
|
|
235
|
+
import { emailTask } from "@/signals/email";
|
|
236
|
+
import { rawEmail } from "@/channels/email";
|
|
237
|
+
|
|
238
|
+
export const processEmail = on(
|
|
239
|
+
emailTask,
|
|
240
|
+
flow("workers.email", {
|
|
241
|
+
do: async ({ to, body }, fx) => {
|
|
242
|
+
await fx.send(rawEmail, { to, body });
|
|
243
|
+
},
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The compiler records `emits: ["tasks.email"]` on producers that call `fx.emit(emailTask, …)`.
|
|
249
|
+
|
|
250
|
+
</Tab>
|
|
251
|
+
|
|
252
|
+
<Tab value="Competing">
|
|
253
|
+
|
|
254
|
+
Two Flows on the same `once` Signal share the pool — exactly one claims each message:
|
|
255
|
+
|
|
256
|
+
```typescript title="src/flows/orders/fulfill-a.ts"
|
|
257
|
+
import { on, flow } from "okengine";
|
|
258
|
+
import { orderPlaced } from "@/signals/orders";
|
|
259
|
+
|
|
260
|
+
export const fulfillA = on(
|
|
261
|
+
orderPlaced,
|
|
262
|
+
flow("orders.fulfillA", {
|
|
263
|
+
do: async ({ orderId }, fx) => {
|
|
264
|
+
await fx.call(chargeAndShip, { orderId });
|
|
265
|
+
},
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```typescript title="src/flows/orders/fulfill-b.ts"
|
|
271
|
+
import { on, flow } from "okengine";
|
|
272
|
+
import { orderPlaced } from "@/signals/orders";
|
|
273
|
+
|
|
274
|
+
export const fulfillB = on(
|
|
275
|
+
orderPlaced,
|
|
276
|
+
flow("orders.fulfillB", {
|
|
277
|
+
do: async ({ orderId }, fx) => {
|
|
278
|
+
await fx.call(chargeAndShip, { orderId });
|
|
279
|
+
},
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Consequence:** this is not fan-out. For every Flow to run, use `signal.broadcast`.
|
|
285
|
+
|
|
286
|
+
</Tab>
|
|
287
|
+
|
|
288
|
+
<Tab value="Optional">
|
|
289
|
+
|
|
290
|
+
Hooks that may have no worker yet need `optional: true` or emit throws **OKE1240**:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
export const webhook = signal.once("hooks.inbound", {
|
|
294
|
+
optional: true,
|
|
295
|
+
schema: z.object({ id: z.string() }),
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
</Tab>
|
|
300
|
+
|
|
301
|
+
</Tabs>
|
|
302
|
+
|
|
303
|
+
## Emit
|
|
304
|
+
|
|
305
|
+
Before any worker runs, `fx.emit` validates `schema` (when set) and enrols the message in the
|
|
306
|
+
outbox. The call resolves on commit — not when the handler finishes.
|
|
307
|
+
|
|
308
|
+
```typescript title="src/flows/orders/create.ts"
|
|
309
|
+
import { on, flow, http } from "okengine";
|
|
310
|
+
import { z } from "zod";
|
|
311
|
+
import { orderPlaced } from "@/signals/orders";
|
|
312
|
+
|
|
313
|
+
export const create = on(
|
|
314
|
+
http.post(),
|
|
315
|
+
flow({
|
|
316
|
+
in: z.object({ userId: z.string(), amount: z.number() }),
|
|
317
|
+
do: async ({ userId, amount }, fx) => {
|
|
318
|
+
const orderId = fx.id();
|
|
319
|
+
await fx.emit(orderPlaced, { orderId, amount, userId }, { key: userId });
|
|
320
|
+
return { orderId };
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
| Call | Records | Meaning |
|
|
327
|
+
| ----------------------------------- | ----------------------- | ------------------------------------- |
|
|
328
|
+
| `fx.emit(signal, payload?)` | `emits` | Competing pool — no ordering |
|
|
329
|
+
| `fx.emit(signal, payload, { key })` | `emits` | Per-key FIFO for that `(signal, key)` |
|
|
330
|
+
| `fx.deadLetters(signal)` | `reads` `signal:<name>` | Inspect `dead` messages |
|
|
331
|
+
|
|
332
|
+
`parentRunId` is stamped automatically from the producer run for Console trace chains — you do
|
|
333
|
+
not set it by hand in app code.
|
|
334
|
+
|
|
335
|
+
## Lease and reclaim
|
|
336
|
+
|
|
337
|
+
<Callout title="Detailed section">
|
|
338
|
+
If you only need the default 30s lease, jump to Ordering. Claims set `lockedBy` +
|
|
339
|
+
`leaseExpiresAt`. There is no background sweeper — reclaim happens lazily on the next claim after
|
|
340
|
+
expiry.
|
|
341
|
+
</Callout>
|
|
342
|
+
|
|
343
|
+
| Concept | Default | Meaning |
|
|
344
|
+
| ---------------- | ---------------------------------------------- | -------------------------------------------- |
|
|
345
|
+
| Visibility lease | `30_000` ms | How long an inflight claim holds the message |
|
|
346
|
+
| Reclaim | next claim after expiry | Another worker may take the same message |
|
|
347
|
+
| Status path | `pending` → `inflight` → `delivered` \| `dead` | Operator inspect / Console |
|
|
348
|
+
|
|
349
|
+
**Consequence:** treat every `once` handler as at-least-once. Prefer idempotent `do` bodies
|
|
350
|
+
(or durable steps for side effects that must not double-fire).
|
|
351
|
+
|
|
352
|
+
<Accordions>
|
|
353
|
+
|
|
354
|
+
<Accordion title="Claim">
|
|
355
|
+
Eligible messages are `pending` unlocked, or `inflight` whose `leaseExpiresAt` has passed. Claim
|
|
356
|
+
sets status to `inflight`, stamps `lockedBy` + `leaseExpiresAt`, and increments `attempts` before
|
|
357
|
+
the handler runs — so a crash mid-handler leaves a reclaimable row.
|
|
358
|
+
</Accordion>
|
|
359
|
+
|
|
360
|
+
<Accordion title="Reclaim">
|
|
361
|
+
There is no timeout daemon. After the lease expires, the next drain/claim may hand the same
|
|
362
|
+
message to another (or the same) worker. Slow handlers that outlive the lease can overlap with a
|
|
363
|
+
reclaim — keep side effects short or journal them.
|
|
364
|
+
</Accordion>
|
|
365
|
+
|
|
366
|
+
<Accordion title="Status path">
|
|
367
|
+
Successful `do` → `delivered`. Exhausted retries with `deadLetter: true` → `dead`. With
|
|
368
|
+
`deadLetter: false` → `delivered` and nothing in the DLQ.
|
|
369
|
+
</Accordion>
|
|
370
|
+
|
|
371
|
+
<Accordion title="Lease is not a declare option">
|
|
372
|
+
The 30s default lives on the signal bus open options. App authors do not pass `leaseMs` on
|
|
373
|
+
`signal.once(…)`. Tests and custom runtimes may override it when opening the bus.
|
|
374
|
+
</Accordion>
|
|
375
|
+
|
|
376
|
+
</Accordions>
|
|
377
|
+
|
|
378
|
+
## Ordering
|
|
379
|
+
|
|
380
|
+
<Callout title="Detailed section">
|
|
381
|
+
Partition with `{key}` only when you need per-tenant / per-user FIFO. Unkeyed messages stay a
|
|
382
|
+
competing pool and may run concurrently.
|
|
383
|
+
</Callout>
|
|
384
|
+
|
|
385
|
+
```typescript title="src/flows/orders/ship.ts"
|
|
386
|
+
import { on, flow } from "okengine";
|
|
387
|
+
import { orderPlaced } from "@/signals/orders";
|
|
388
|
+
|
|
389
|
+
export const shipOrder = on(
|
|
390
|
+
orderPlaced,
|
|
391
|
+
flow("orders.ship", {
|
|
392
|
+
do: async ({ orderId }, fx) => {
|
|
393
|
+
await fx.call(fulfillOrder, { orderId });
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
await fx.emit(orderPlaced, { orderId: "ord_1", userId: "usr_1" }, { key: "usr_1" });
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
| Emit | Concurrency | Order |
|
|
404
|
+
| -------------- | ----------------------- | ----------------- |
|
|
405
|
+
| No `key` | Competing — may overlap | None |
|
|
406
|
+
| Same `key` | Serialized by lease | FIFO for that key |
|
|
407
|
+
| Different keys | May overlap | Independent |
|
|
408
|
+
|
|
409
|
+
<Accordions>
|
|
410
|
+
|
|
411
|
+
<Accordion title="Lease is the lock">
|
|
412
|
+
No two messages sharing `(signal, key)` are claimed while one holds an unexpired lease. When the
|
|
413
|
+
first completes (or its lease expires and is reclaimed), the next same-key message becomes
|
|
414
|
+
eligible — emission order is preserved for that key.
|
|
415
|
+
</Accordion>
|
|
416
|
+
|
|
417
|
+
<Accordion title="Unkeyed pool">
|
|
418
|
+
Omit `key` for maximum parallelism across workers. There is no global FIFO across unkeyed messages
|
|
419
|
+
— only competing claim exclusivity per message.
|
|
420
|
+
</Accordion>
|
|
421
|
+
|
|
422
|
+
</Accordions>
|
|
423
|
+
|
|
424
|
+
## Retries and dead letters
|
|
425
|
+
|
|
426
|
+
<Callout title="Detailed section">
|
|
427
|
+
If you only need defaults (`retries: 3`, `deadLetter: true`), jump to Idempotency. Retries requeue
|
|
428
|
+
immediately — there is no delay backoff between attempts.
|
|
429
|
+
</Callout>
|
|
430
|
+
|
|
431
|
+
<Accordions>
|
|
432
|
+
|
|
433
|
+
<Accordion title="Attempt budget">
|
|
434
|
+
Handler invocations = `retries + 1`. On each failure the bus records a typed
|
|
435
|
+
`{ code, message, at, attempt }` reason. When `attempts` exceeds `retries` and
|
|
436
|
+
`deadLetter: true`, status becomes `dead`.
|
|
437
|
+
</Accordion>
|
|
438
|
+
|
|
439
|
+
<Accordion title="Failure reasons">
|
|
440
|
+
Each failed attempt appends a `SignalFailureReason`:
|
|
441
|
+
|
|
442
|
+
| Field | Meaning |
|
|
443
|
+
| --------- | ---------------------------------- |
|
|
444
|
+
| `code` | Machine-readable failure code |
|
|
445
|
+
| `message` | Human-readable detail |
|
|
446
|
+
| `at` | Epoch-ms when the attempt failed |
|
|
447
|
+
| `attempt` | 1-based attempt number that failed |
|
|
448
|
+
|
|
449
|
+
The full history survives on the dead-letter entry for operator inspect.
|
|
450
|
+
|
|
451
|
+
</Accordion>
|
|
452
|
+
|
|
453
|
+
<Accordion title="deadLetter: false">
|
|
454
|
+
Exhausted messages are marked `delivered` instead of entering the DLQ. Use when dropping is
|
|
455
|
+
acceptable and you do not want operator replay.
|
|
456
|
+
</Accordion>
|
|
457
|
+
|
|
458
|
+
<Accordion title="fx.deadLetters">
|
|
459
|
+
Requires a bound signal runtime and `effects.reads` including `signal:<name>`.
|
|
460
|
+
Cross-signal reads throw **OKE1001**. Without a runtime:
|
|
461
|
+
`fx.deadLetters requires a bound signal runtime`.
|
|
462
|
+
|
|
463
|
+
Returned entries include `payload`, `attempts`, `failures`, `key`, `createdAt`, and
|
|
464
|
+
`status: "dead"`.
|
|
465
|
+
|
|
466
|
+
</Accordion>
|
|
467
|
+
|
|
468
|
+
<Accordion title="Schema at emit">
|
|
469
|
+
Invalid payloads fail at `fx.emit` with **OKE1250** before any worker runs. The Signal's
|
|
470
|
+
`schema` is an **emit** contract (like Channel `schema` at `fx.send`) — distinct from HTTP
|
|
471
|
+
invoke contracts on `http.*` / `call` / `mcp.tool`.
|
|
472
|
+
|
|
473
|
+
Cause: `"{resource}": {detail}`.
|
|
474
|
+
|
|
475
|
+
</Accordion>
|
|
476
|
+
|
|
477
|
+
<Accordion title="Orphan emit">
|
|
478
|
+
Zero subscribers + `optional: false` → **OKE1240**.
|
|
479
|
+
Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
|
|
480
|
+
Fix: add `on(signal, …)` or mark `{ optional: true }`.
|
|
481
|
+
</Accordion>
|
|
482
|
+
|
|
483
|
+
</Accordions>
|
|
484
|
+
|
|
485
|
+
## Idempotency
|
|
486
|
+
|
|
487
|
+
Lease reclaim and retries mean a handler can run more than once for the same message.
|
|
488
|
+
|
|
489
|
+
| Approach | When |
|
|
490
|
+
| --------------------------- | -------------------------------------------------------- |
|
|
491
|
+
| Idempotent `do` | Side effects are safe to repeat (upsert, set-once flags) |
|
|
492
|
+
| `durable: true` + `fx.step` | Multi-step work that must not double-fire |
|
|
493
|
+
| Short handlers | Finish before the 30s lease so reclaim does not overlap |
|
|
494
|
+
|
|
495
|
+
```typescript title="src/flows/payments/sync.ts"
|
|
496
|
+
import { on, flow } from "okengine";
|
|
497
|
+
import { syncPayment } from "@/signals/payments";
|
|
498
|
+
|
|
499
|
+
export const runSync = on(
|
|
500
|
+
syncPayment,
|
|
501
|
+
flow("payments.sync", {
|
|
502
|
+
durable: true,
|
|
503
|
+
do: async ({ chargeId }, fx) => {
|
|
504
|
+
await fx.step("charge", async () => {
|
|
505
|
+
await fx.call(applyCharge, { chargeId });
|
|
506
|
+
});
|
|
507
|
+
await fx.step("receipt", async () => {
|
|
508
|
+
await fx.call(sendReceipt, { chargeId });
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
See [Durable Workflows](/docs/elements/flow/workflows).
|
|
516
|
+
|
|
517
|
+
## Troubleshooting
|
|
518
|
+
|
|
519
|
+
<Accordions>
|
|
520
|
+
|
|
521
|
+
<Accordion title="Worker never runs after emit">
|
|
522
|
+
Confirm `on(signalHandle, flow)` uses the same declared handle (or the same name) and is adopted
|
|
523
|
+
into the app. Check **OKE1240** if the emit itself threw. Live Signals are not workers — use Once
|
|
524
|
+
or Broadcast.
|
|
525
|
+
</Accordion>
|
|
526
|
+
|
|
527
|
+
<Accordion title="Same message processed twice">
|
|
528
|
+
Lease reclaim after a crash or slow handler is expected at-least-once physics. Make `do`
|
|
529
|
+
idempotent, or journal side effects with `durable: true` + `fx.step`.
|
|
530
|
+
</Accordion>
|
|
531
|
+
|
|
532
|
+
<Accordion title="Both of two Flows ran on one once message">
|
|
533
|
+
That is broadcast physics, not once. Confirm both files import the same `signal.once` handle —
|
|
534
|
+
competing workers share one claim. If you need fan-out, switch the declaration to
|
|
535
|
+
`signal.broadcast`.
|
|
536
|
+
</Accordion>
|
|
537
|
+
|
|
538
|
+
<Accordion title="Messages stuck inflight">
|
|
539
|
+
Wait for the 30s lease and the next drain/claim. There is no separate timeout daemon. A handler
|
|
540
|
+
still running past the lease can overlap with a reclaim — shorten the work or journal it.
|
|
541
|
+
</Accordion>
|
|
542
|
+
|
|
543
|
+
<Accordion title="OKE1250 on emit">
|
|
544
|
+
Cause: `"{resource}": {detail}`. Align the payload with `schema` — the worker never started.
|
|
545
|
+
</Accordion>
|
|
546
|
+
|
|
547
|
+
<Accordion title="OKE1240 on emit">
|
|
548
|
+
Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
|
|
549
|
+
Add a subscriber or set `{ optional: true }` on the Signal.
|
|
550
|
+
</Accordion>
|
|
551
|
+
|
|
552
|
+
<Accordion title="OKE1001 on fx.deadLetters">
|
|
553
|
+
Cause: `Flow "…" reads "signal:…" without declaring it.`
|
|
554
|
+
Add `effects.reads: ["signal:<name>"]` (or the matching `signalReadRef`) on that Flow.
|
|
555
|
+
</Accordion>
|
|
556
|
+
|
|
557
|
+
<Accordion title="deadLetter as a string name">
|
|
558
|
+
`deadLetter` is `boolean` (default `true`). There is no separate named DLQ signal string — inspect
|
|
559
|
+
with `fx.deadLetters(signal)`.
|
|
560
|
+
</Accordion>
|
|
561
|
+
|
|
562
|
+
<Accordion title="TypeError: retention is only valid with signal.live">
|
|
563
|
+
Drop `retention` on `signal.once`, or switch the helper to `signal.live`.
|
|
564
|
+
</Accordion>
|
|
565
|
+
|
|
566
|
+
</Accordions>
|
|
567
|
+
|
|
568
|
+
## Learn more
|
|
569
|
+
|
|
570
|
+
- [Signal Overview](/docs/elements/signal) — delivery matrix and drivers
|
|
571
|
+
- [Broadcast](/docs/elements/signal/broadcast) — fan-out without leases
|
|
572
|
+
- [Live](/docs/elements/signal/live) — retained SSE tapes
|
|
573
|
+
- [Consumers](/docs/elements/flow/consumers) — `on(signal)` next to Clock / CDC
|
|
574
|
+
- [Workflows](/docs/elements/flow/workflows) — `durable` + `fx.step` for idempotent side effects
|
|
575
|
+
- [fx](/docs/reference/fx) — `fx.emit`, `fx.deadLetters`
|
|
576
|
+
- [Errors](/docs/reference/errors) — OKE1240 · OKE1250 · OKE1001
|
|
577
|
+
|
|
578
|
+
## Next
|
|
579
|
+
|
|
580
|
+
<Cards>
|
|
581
|
+
<Card
|
|
582
|
+
title="Broadcast"
|
|
583
|
+
description="Ephemeral fan-out across subscribed Flows."
|
|
584
|
+
href="/docs/elements/signal/broadcast"
|
|
585
|
+
/>
|
|
586
|
+
<Card
|
|
587
|
+
title="Live"
|
|
588
|
+
description="Retained SSE tapes for browsers."
|
|
589
|
+
href="/docs/elements/signal/live"
|
|
590
|
+
/>
|
|
591
|
+
<Card
|
|
592
|
+
title="Consumers"
|
|
593
|
+
description="Signal workers, Clock jobs, and SQL CDC."
|
|
594
|
+
href="/docs/elements/flow/consumers"
|
|
595
|
+
/>
|
|
596
|
+
</Cards>
|