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,590 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Live"
|
|
3
|
+
description: "Retained live event tapes streamed to clients over HTTP SSE with Last-Event-ID resume."
|
|
4
|
+
icon: "Activity"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`signal.live` keeps a retained event tape and exposes it over Server-Sent Events. Emit with
|
|
9
|
+
`fx.emit`, and mount the feed with `http.live` (or a gated GET).
|
|
10
|
+
|
|
11
|
+
For developers shipping status feeds and progress UIs — declare the Signal, expose SSE, subscribe
|
|
12
|
+
from the typed client.
|
|
13
|
+
|
|
14
|
+
<Callout title="The one rule">
|
|
15
|
+
`signal.live` is an HTTP SSE tape — not a competing worker. Bind with
|
|
16
|
+
[`http.live`](/docs/elements/flow/http#live-streams). Do not use `on(liveSignal, flow)` as a
|
|
17
|
+
queue consumer. Prefer `{ optional: true }` so emit succeeds when no client is connected yet.
|
|
18
|
+
</Callout>
|
|
19
|
+
|
|
20
|
+
<SignalLiveReplay />
|
|
21
|
+
|
|
22
|
+
## Smallest Example
|
|
23
|
+
|
|
24
|
+
<Steps>
|
|
25
|
+
|
|
26
|
+
<Step>
|
|
27
|
+
### Declare the live signal
|
|
28
|
+
|
|
29
|
+
```typescript title="src/signals/orders.ts"
|
|
30
|
+
import { signal } from "okengine";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
|
|
33
|
+
export const orderStatus = signal.live("order-status", {
|
|
34
|
+
optional: true,
|
|
35
|
+
schema: z.object({
|
|
36
|
+
orderId: z.string(),
|
|
37
|
+
status: z.enum(["placed", "fulfilling", "shipped"]),
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
</Step>
|
|
43
|
+
|
|
44
|
+
<Step>
|
|
45
|
+
### Expose SSE
|
|
46
|
+
|
|
47
|
+
```typescript title="src/flows/orders/firehose.ts"
|
|
48
|
+
import { on, http } from "okengine";
|
|
49
|
+
import { member } from "@/core/gate";
|
|
50
|
+
import { orderStatus } from "@/signals/orders";
|
|
51
|
+
|
|
52
|
+
export const firehose = on(http.live(orderStatus).gate(member));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
</Step>
|
|
56
|
+
|
|
57
|
+
<Step>
|
|
58
|
+
### Emit and subscribe
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Inside any Flow:
|
|
62
|
+
await fx.emit(orderStatus, { orderId: "ord_1", status: "shipped" });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
curl -N http://localhost:6530/_oke/live/order-status \
|
|
67
|
+
-H "accept: text/event-stream" \
|
|
68
|
+
-H "authorization: Bearer …"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Response `Content-Type` is `text/event-stream`. Frames are JSON `data:` lines
|
|
72
|
+
(optional `id:` for resume), then `data: [DONE]`.
|
|
73
|
+
|
|
74
|
+
</Step>
|
|
75
|
+
|
|
76
|
+
</Steps>
|
|
77
|
+
|
|
78
|
+
<Callout title="Pathless firehose">
|
|
79
|
+
`on(http.live(signal))` always mounts `GET /_oke/live/{name}` — there is no pathless file-tree
|
|
80
|
+
stamp for live. Custom paths use `http.get(path).live(signal)`. See [Exposure](#exposure).
|
|
81
|
+
</Callout>
|
|
82
|
+
|
|
83
|
+
## Progressive Patterns
|
|
84
|
+
|
|
85
|
+
From a default firehose to filtered paths, retention, and the typed client:
|
|
86
|
+
|
|
87
|
+
<Tabs items={["Firehose", "Filtered", "Retention", "Client"]}>
|
|
88
|
+
|
|
89
|
+
<Tab value="Firehose">
|
|
90
|
+
|
|
91
|
+
`on(http.live(signal))` mounts `GET /_oke/live/{name}`. Chain `.gate(...)` like any GET:
|
|
92
|
+
|
|
93
|
+
```typescript title="src/flows/orders/firehose.ts"
|
|
94
|
+
import { on, http } from "okengine";
|
|
95
|
+
import { member } from "@/core/gate";
|
|
96
|
+
import { orderStatus } from "@/signals/orders";
|
|
97
|
+
|
|
98
|
+
export const firehose = on(http.live(orderStatus).gate(member));
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Signal names in the path are `encodeURIComponent`'d (`chat.message` stays readable).
|
|
102
|
+
|
|
103
|
+
</Tab>
|
|
104
|
+
|
|
105
|
+
<Tab value="Filtered">
|
|
106
|
+
|
|
107
|
+
Path params become a filter: an event forwards when each `:param` that **exists on the payload**
|
|
108
|
+
equals the request value. Params missing from the payload are skipped:
|
|
109
|
+
|
|
110
|
+
```typescript title="src/flows/orders/events.ts"
|
|
111
|
+
import { on, http } from "okengine";
|
|
112
|
+
import { member } from "@/core/gate";
|
|
113
|
+
import { orderStatus } from "@/signals/orders";
|
|
114
|
+
|
|
115
|
+
export const events = on(http.get("/orders/:orderId/events").gate(member).live(orderStatus));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`GET /orders/ord_1/events` receives `{ orderId: "ord_1", status: "shipped" }` and drops other
|
|
119
|
+
orders.
|
|
120
|
+
|
|
121
|
+
</Tab>
|
|
122
|
+
|
|
123
|
+
<Tab value="Retention">
|
|
124
|
+
|
|
125
|
+
Cap the tape with `retention` (live-only). Omit for unbounded history:
|
|
126
|
+
|
|
127
|
+
```typescript title="src/signals/chat.ts"
|
|
128
|
+
import { signal } from "okengine";
|
|
129
|
+
import { z } from "zod";
|
|
130
|
+
|
|
131
|
+
export const chatMessage = signal.live("chat.message", {
|
|
132
|
+
optional: true,
|
|
133
|
+
retention: { maxAge: "7d", maxCount: 10_000 },
|
|
134
|
+
schema: z.object({
|
|
135
|
+
room: z.string(),
|
|
136
|
+
text: z.string(),
|
|
137
|
+
author: z.string(),
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Invalid `maxAge` / `maxCount` throw at declare — see [Retention](#retention).
|
|
143
|
+
|
|
144
|
+
</Tab>
|
|
145
|
+
|
|
146
|
+
<Tab value="Client">
|
|
147
|
+
|
|
148
|
+
Browsers use the typed client callback — not raw `EventSource` on an invented path:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const stop = api.live(
|
|
152
|
+
orderStatus,
|
|
153
|
+
{ orderId: "ord_1" },
|
|
154
|
+
{
|
|
155
|
+
onEvent: (event) => {
|
|
156
|
+
/* { orderId, status } */
|
|
157
|
+
},
|
|
158
|
+
onError: (err) => {
|
|
159
|
+
/* 4xx, envelope, or drop */
|
|
160
|
+
},
|
|
161
|
+
autoResubscribe: false,
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
stop();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Reconnects send `Last-Event-ID` from the last `id:` received. See
|
|
168
|
+
[Client subscription](#client-subscription).
|
|
169
|
+
|
|
170
|
+
</Tab>
|
|
171
|
+
|
|
172
|
+
</Tabs>
|
|
173
|
+
|
|
174
|
+
## Options Reference
|
|
175
|
+
|
|
176
|
+
Optional second argument to `signal.live(name, options?)`. Delivery is the helper name — not an
|
|
177
|
+
option.
|
|
178
|
+
|
|
179
|
+
| Option | Type | Default | Meaning |
|
|
180
|
+
| ------------- | ------------------------ | --------- | -------------------------------------------------------- |
|
|
181
|
+
| `schema` | Standard Schema | omitted | **Emit** contract — validated at `fx.emit` (**OKE1250**) |
|
|
182
|
+
| `optional` | `boolean` | `false` | Allow emit with zero SSE clients / subscribers |
|
|
183
|
+
| `retention` | `{ maxAge?, maxCount? }` | unbounded | Prune the tape (live-only) |
|
|
184
|
+
| `description` | `string` | the name | Console / docs blurb |
|
|
185
|
+
| `retries` | `number` | `3` | Declared; live uses the tape, not once DLQ |
|
|
186
|
+
| `deadLetter` | `boolean` | `true` | Declared; live does not use once DLQ |
|
|
187
|
+
|
|
188
|
+
**Consequence:** `retention` on `signal.once` / `signal.broadcast` is a type error — switch to
|
|
189
|
+
`signal.live` or drop the option.
|
|
190
|
+
|
|
191
|
+
## Exposure
|
|
192
|
+
|
|
193
|
+
<Callout title="Detailed section">
|
|
194
|
+
If you only need the default firehose, jump to the example below. `.live(…)` is GET-only —
|
|
195
|
+
`on(http.post("/x").live(signal))` throws `on(http.*.live(signal)): live exposure must be GET`.
|
|
196
|
+
</Callout>
|
|
197
|
+
|
|
198
|
+
`http.live(signal)` is one-arg `on()` — the engine synthesizes the stream Flow
|
|
199
|
+
(`fx.live` + `effects.reads: ["signal:<name>"]`). Chain `.gate(...)` like any GET.
|
|
200
|
+
|
|
201
|
+
```typescript title="src/flows/orders/firehose.ts"
|
|
202
|
+
import { on, http, signal } from "okengine";
|
|
203
|
+
import { z } from "zod";
|
|
204
|
+
import { member } from "@/core/gate";
|
|
205
|
+
|
|
206
|
+
export const orderStatus = signal.live("order-status", {
|
|
207
|
+
optional: true,
|
|
208
|
+
schema: z.object({
|
|
209
|
+
orderId: z.string(),
|
|
210
|
+
status: z.enum(["placed", "fulfilling", "shipped"]),
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
export const firehose = on(http.live(orderStatus).gate(member));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
curl -N http://localhost:6530/_oke/live/order-status \
|
|
219
|
+
-H "accept: text/event-stream" \
|
|
220
|
+
-H "authorization: Bearer …"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Three GET shapes expose a live SSE body. Pick the physics first, then the path.
|
|
224
|
+
|
|
225
|
+
| Declaration | Path | Physics |
|
|
226
|
+
| -------------------------------------------------- | ----------------------- | ------------------------------------- |
|
|
227
|
+
| `on(http.live(signal))` | `GET /_oke/live/{name}` | Signal tape — every event |
|
|
228
|
+
| `on(http.get(path).live(signal))` | Your path | Signal tape — auto-match on `:params` |
|
|
229
|
+
| `on(http.get(path).live(table), flow)` | Your path | Live **query** — classified CDC rows |
|
|
230
|
+
| `store.resource({ live: true })` + `http.resource` | `GET <path>/live` | Same live-query physics |
|
|
231
|
+
|
|
232
|
+
Signal firehoses and resource live queries are different physics — see Live Queries below.
|
|
233
|
+
|
|
234
|
+
<Accordions>
|
|
235
|
+
|
|
236
|
+
<Accordion title="Filtered Paths">
|
|
237
|
+
Path params become a filter: an event is forwarded when each `:param` that
|
|
238
|
+
**exists on the payload** equals the request value. Params missing from the
|
|
239
|
+
payload are skipped (the event still flows). No params = firehose.
|
|
240
|
+
|
|
241
|
+
```typescript title="src/flows/orders/events.ts"
|
|
242
|
+
import { on, http } from "okengine";
|
|
243
|
+
import { member } from "@/core/gate";
|
|
244
|
+
import { orderStatus } from "@/signals/orders";
|
|
245
|
+
|
|
246
|
+
export const events = on(http.get("/orders/:orderId/events").gate(member).live(orderStatus));
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`GET /orders/ord_1/events` receives `{ orderId: "ord_1", status: "shipped" }`
|
|
250
|
+
and drops events for other orders.
|
|
251
|
+
|
|
252
|
+
</Accordion>
|
|
253
|
+
|
|
254
|
+
<Accordion title="Custom Match">
|
|
255
|
+
Pass your own Flow as the second argument to `on()` when auto-match is not
|
|
256
|
+
enough. Return `fx.live(signal, { match })` from `do` — do not wrap it with
|
|
257
|
+
`fx.json.stream`.
|
|
258
|
+
|
|
259
|
+
```typescript title="src/flows/orders/vip-feed.ts"
|
|
260
|
+
import { on, flow, http } from "okengine";
|
|
261
|
+
import { member } from "@/core/gate";
|
|
262
|
+
import { orderStatus } from "@/signals/orders";
|
|
263
|
+
|
|
264
|
+
export const vipFeed = on(
|
|
265
|
+
http.get("/orders/vip/events").gate(member).live(orderStatus),
|
|
266
|
+
flow({
|
|
267
|
+
do: (_input, fx) =>
|
|
268
|
+
fx.live(orderStatus, {
|
|
269
|
+
match: (payload) => payload.status === "shipped",
|
|
270
|
+
}),
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Consequence:** a custom Flow stamps a distinct match key, so it can coexist
|
|
276
|
+
with the auto-match route for the same signal (different path). Two synthesized
|
|
277
|
+
firehoses that share signal **and** gates fail uniqueness — see Uniqueness.
|
|
278
|
+
|
|
279
|
+
</Accordion>
|
|
280
|
+
|
|
281
|
+
<Accordion title="Live Queries">
|
|
282
|
+
Resource / table live is **not** a signal tape. Each subscriber gets classified
|
|
283
|
+
row events (RLS + list filters). Prefer `http.resource` + `{ live: true }` when
|
|
284
|
+
you already mount the five CRUD ops.
|
|
285
|
+
|
|
286
|
+
| `live` on the resource | Result |
|
|
287
|
+
| ---------------------- | ---------------------------------------------- |
|
|
288
|
+
| `{ live: true }` | Mount `GET <path>/live` now |
|
|
289
|
+
| omitted | Mount only if `oke({ store: { live: true } })` |
|
|
290
|
+
| `{ live: false }` | Never mount live for this resource |
|
|
291
|
+
|
|
292
|
+
Wire events (consumed with `useLiveQuery` on the [typed client](/docs/client/react)):
|
|
293
|
+
|
|
294
|
+
| `kind` | Meaning |
|
|
295
|
+
| --------- | ----------------------------------------------------------- |
|
|
296
|
+
| `upsert` | Row visible under stamp + query — merge by primary key |
|
|
297
|
+
| `revoked` | Row left visibility (`reason: "rls"` or `"query"`) — remove |
|
|
298
|
+
| `delete` | Row deleted — remove |
|
|
299
|
+
|
|
300
|
+
For a handwritten list, bind the table on GET and open the window with
|
|
301
|
+
`liveQuery` — full detail under [HTTP · Live Streams](/docs/elements/flow/http#live-streams).
|
|
302
|
+
|
|
303
|
+
</Accordion>
|
|
304
|
+
|
|
305
|
+
<Accordion title="Uniqueness">
|
|
306
|
+
Boot keys each live HTTP route as `(signal, gates, match)`. Match is the
|
|
307
|
+
sorted path-param names, or `custom:<flow>` when you passed a Flow, or
|
|
308
|
+
`(firehose)` when there are no params.
|
|
309
|
+
|
|
310
|
+
| Pair | Boots? |
|
|
311
|
+
| --------------------------------------- | ----------------------------------------- |
|
|
312
|
+
| Member `:orderId` + admin firehose | Yes — gates and match differ |
|
|
313
|
+
| Same params, different gates | Yes — the client disambiguates with `via` |
|
|
314
|
+
| Two member firehoses on different paths | No — **OKE1050** |
|
|
315
|
+
| Same method + path twice | No — **OKE1041** first |
|
|
316
|
+
|
|
317
|
+
**OKE1050** cause: `Live signal "{signal}" is exposed twice with the same gates ({gates}) and match ({match}).`
|
|
318
|
+
Fix: a different gate, a path-param filter, or drop the extra route.
|
|
319
|
+
|
|
320
|
+
</Accordion>
|
|
321
|
+
|
|
322
|
+
</Accordions>
|
|
323
|
+
|
|
324
|
+
## Emit through fx
|
|
325
|
+
|
|
326
|
+
<Callout title="Detailed section">
|
|
327
|
+
If you only need `fx.emit(signal, payload)`, jump to the table. Emit appends to the retained tape
|
|
328
|
+
when the call resolves. The producer run id is stamped as `parentRunId` for Console trace chains.
|
|
329
|
+
</Callout>
|
|
330
|
+
|
|
331
|
+
| Call | Records | Use |
|
|
332
|
+
| ----------------------------- | ----------------------- | ------------------------------- |
|
|
333
|
+
| `fx.emit(signal, payload?)` | `emits` | Append to the live tape |
|
|
334
|
+
| `fx.live(signal, { match? })` | `reads` `signal:<name>` | Server SSE body for a live tape |
|
|
335
|
+
|
|
336
|
+
Invalid `schema` payloads fail at emit with **OKE1250** (`"{resource}": {detail}`) before any
|
|
337
|
+
client receives the frame. Cross-signal `fx.live` without a declared read throws **OKE1001**.
|
|
338
|
+
|
|
339
|
+
```typescript title="src/flows/orders/[id]/ship.ts"
|
|
340
|
+
import { on, flow, http } from "okengine";
|
|
341
|
+
import { z } from "zod";
|
|
342
|
+
import { orderStatus } from "@/signals/orders";
|
|
343
|
+
|
|
344
|
+
export const ship = on(
|
|
345
|
+
http.post(),
|
|
346
|
+
flow({
|
|
347
|
+
in: z.object({ id: z.string() }),
|
|
348
|
+
do: async ({ id }, fx) => {
|
|
349
|
+
await fx.emit(orderStatus, { orderId: id, status: "shipped" });
|
|
350
|
+
return { id, status: "shipped" };
|
|
351
|
+
},
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Zero SSE clients + `optional: false` → **OKE1240**. Cause:
|
|
357
|
+
`Flow "{flow}" emits signal "{resource}" with no subscriber.`
|
|
358
|
+
Fix: mount `http.live` (or a path `.live`) before emitting, or set `{ optional: true }`.
|
|
359
|
+
|
|
360
|
+
## Retention
|
|
361
|
+
|
|
362
|
+
<Callout title="Detailed section">
|
|
363
|
+
If you only need an unbounded tape, skip this section. `retention` is live-only — omit both fields
|
|
364
|
+
(or omit `retention`) for unlimited history.
|
|
365
|
+
</Callout>
|
|
366
|
+
|
|
367
|
+
| Field | Type | Meaning |
|
|
368
|
+
| ---------- | ------------------------------------- | ----------------------------- |
|
|
369
|
+
| `maxAge` | duration string (`"24h"`, `"30s"`, …) | Drop events older than this |
|
|
370
|
+
| `maxCount` | integer ≥ 1 | Keep only the newest N events |
|
|
371
|
+
|
|
372
|
+
```typescript title="src/signals/chat.ts"
|
|
373
|
+
import { signal } from "okengine";
|
|
374
|
+
import { z } from "zod";
|
|
375
|
+
|
|
376
|
+
export const chatMessage = signal.live("chat.message", {
|
|
377
|
+
optional: true,
|
|
378
|
+
retention: { maxAge: "7d", maxCount: 10_000 },
|
|
379
|
+
schema: z.object({
|
|
380
|
+
room: z.string(),
|
|
381
|
+
text: z.string(),
|
|
382
|
+
author: z.string(),
|
|
383
|
+
}),
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Invalid values throw at declare:
|
|
388
|
+
|
|
389
|
+
```text
|
|
390
|
+
signal.live("…"): retention.maxAge must be a duration like "24h" or "30s"
|
|
391
|
+
signal.live("…"): retention.maxCount must be an integer ≥ 1
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Consequence:** a pruned `id:` becomes a resume gap — reconnects that still send that
|
|
395
|
+
`Last-Event-ID` hit **OKE1210** / 410 `LiveResumeGap`. Prefer `autoResubscribe: true` on flaky
|
|
396
|
+
networks, or raise `maxCount` / `maxAge` if clients need longer catch-up.
|
|
397
|
+
|
|
398
|
+
## Resume and gaps
|
|
399
|
+
|
|
400
|
+
<Callout title="Detailed section">
|
|
401
|
+
Resume is exclusive: replay events **after** `Last-Event-ID`, then continue live. Unknown or
|
|
402
|
+
pruned ids throw **OKE1210** before the SSE body — HTTP maps that to **410** `LiveResumeGap`.
|
|
403
|
+
</Callout>
|
|
404
|
+
|
|
405
|
+
| Symptom | Meaning | Fix |
|
|
406
|
+
| ----------------------------------- | ------------------------------- | -------------------------------------- |
|
|
407
|
+
| **OKE1210** / 410 `LiveResumeGap` | Cursor gone from the tape | Drop `Last-Event-ID`; replay remaining |
|
|
408
|
+
| `autoResubscribe: true` | Client clears gap after backoff | Prefer for flaky networks |
|
|
409
|
+
| Custom `fx.live(signal, { match })` | Server-side filter | Do not wrap with `fx.json.stream` |
|
|
410
|
+
|
|
411
|
+
```text
|
|
412
|
+
Cursor "{afterId}" missing on "{signal}".
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
SSE frames carry optional `id:` lines. Clients that reconnect send
|
|
416
|
+
`Last-Event-ID` from the last `id:` they actually received. A **410** means that cursor is
|
|
417
|
+
gone — drop it and replay the remaining tape.
|
|
418
|
+
|
|
419
|
+
## Client subscription
|
|
420
|
+
|
|
421
|
+
<Callout title="Detailed section">
|
|
422
|
+
`signal.live` is HTTP SSE. `for await` stays on the server; the browser uses a callback. Prefer
|
|
423
|
+
`api.live` or `useLive` — not raw `EventSource` on an invented path.
|
|
424
|
+
</Callout>
|
|
425
|
+
|
|
426
|
+
The client picks the unique exposure whose `matchKey` fields are a subset of the input,
|
|
427
|
+
preferring the largest match (`{ orderId }` beats firehose). A tie needs `via: "unit.flow"`.
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const stop = api.live(
|
|
431
|
+
orderStatus,
|
|
432
|
+
{ orderId: "ord_1" },
|
|
433
|
+
{
|
|
434
|
+
onEvent: (event) => {
|
|
435
|
+
/* { orderId, status } */
|
|
436
|
+
},
|
|
437
|
+
onError: (err) => {
|
|
438
|
+
/* 4xx, envelope, or drop */
|
|
439
|
+
},
|
|
440
|
+
onOpen: () => {
|
|
441
|
+
/* HTTP 200, including reconnects */
|
|
442
|
+
},
|
|
443
|
+
autoResubscribe: false,
|
|
444
|
+
},
|
|
445
|
+
);
|
|
446
|
+
stop();
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
`api.orders.events({ orderId }, { onEvent })` is the same shape on the exposing Flow.
|
|
450
|
+
Reconnects send `Last-Event-ID` from the last `id:` received.
|
|
451
|
+
|
|
452
|
+
| Option | Default | Meaning |
|
|
453
|
+
| ----------------- | ------------ | --------------------------------------------------- |
|
|
454
|
+
| `onEvent` | _(required)_ | Each JSON frame from the tape |
|
|
455
|
+
| `onError` | omitted | 4xx, envelope errors, or network drop |
|
|
456
|
+
| `onOpen` | omitted | After a successful SSE open (incl. reconnects) |
|
|
457
|
+
| `autoResubscribe` | `false` | Re-open after a drop (500ms…30s backoff) |
|
|
458
|
+
| `via` | omitted | Disambiguate when two exposures share a match shape |
|
|
459
|
+
| `signal` | omitted | `AbortSignal` to cancel the subscribe |
|
|
460
|
+
|
|
461
|
+
React:
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import { useLive } from "okengine/client-react";
|
|
465
|
+
|
|
466
|
+
const { events, latest, error, isConnected } = useLive(
|
|
467
|
+
api,
|
|
468
|
+
orderStatus,
|
|
469
|
+
{ orderId: "ord_1" },
|
|
470
|
+
{ autoResubscribe: true },
|
|
471
|
+
);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Resource live queries use `useLiveQuery` (snapshot + classified events), not `api.live`.
|
|
475
|
+
See [Client · Live](/docs/client/live).
|
|
476
|
+
|
|
477
|
+
## What live is not
|
|
478
|
+
|
|
479
|
+
<Callout title="Detailed section">
|
|
480
|
+
Pick physics from the guarantee you need. Live is the wrong tool when work must be claimed once,
|
|
481
|
+
or when every in-process listener should react with no retained history.
|
|
482
|
+
</Callout>
|
|
483
|
+
|
|
484
|
+
| Need | Use instead |
|
|
485
|
+
| -------------------------------------- | --------------------------------------------------------------------------- |
|
|
486
|
+
| Exactly one worker processes the job | [`once`](/docs/elements/signal/once) |
|
|
487
|
+
| Every active Flow gets a copy, no tape | [`broadcast`](/docs/elements/signal/broadcast) |
|
|
488
|
+
| Classified CDC rows for a list window | [`http.resource` live](/docs/elements/flow/http#resources) / `useLiveQuery` |
|
|
489
|
+
| Durable multi-step work with journal | [Durable Workflows](/docs/elements/flow/workflows) |
|
|
490
|
+
|
|
491
|
+
Live does **not** use the `once` visibility lease or dead-letter queue. `retries` /
|
|
492
|
+
`deadLetter` may appear on the declare options bag, but the live path is the retained tape +
|
|
493
|
+
SSE resume — not competing-consumer physics.
|
|
494
|
+
|
|
495
|
+
## Troubleshooting
|
|
496
|
+
|
|
497
|
+
<Accordions>
|
|
498
|
+
|
|
499
|
+
<Accordion title="404 on /_oke/live/…">
|
|
500
|
+
Confirm `on(http.live(signal))` (or a path `.live(signal)`) is adopted. Names in the default path
|
|
501
|
+
are URI-encoded (`encodeURIComponent`). A bare `404` with body `Not Found` means the router found
|
|
502
|
+
no match.
|
|
503
|
+
</Accordion>
|
|
504
|
+
|
|
505
|
+
<Accordion title="TypeError: live exposure must be GET">
|
|
506
|
+
`.live(signal)` only attaches to `http.get` / `http.live`. Other verbs reject live synthesis:
|
|
507
|
+
`on(http.*.live(signal)): live exposure must be GET`.
|
|
508
|
+
</Accordion>
|
|
509
|
+
|
|
510
|
+
<Accordion title="OKE1050 — live signal exposed twice">
|
|
511
|
+
Cause: `Live signal "{signal}" is exposed twice with the same gates ({gates}) and match ({match}
|
|
512
|
+
).` Two firehoses (`http.live` or param-less `.live`) that share the signal and gates cannot boot.
|
|
513
|
+
Change the gate, add a path-param filter, or remove a route.
|
|
514
|
+
</Accordion>
|
|
515
|
+
|
|
516
|
+
<Accordion title="OKE1041 — method + path bound twice">
|
|
517
|
+
Cause: `{method} {path} is bound twice (flow "{flow}").` Two mounts collide on the same method +
|
|
518
|
+
path (for example two `http.live` firehoses that resolve to the same URL). Drop one binding.
|
|
519
|
+
</Accordion>
|
|
520
|
+
|
|
521
|
+
<Accordion title="OKE1210 — 410 LiveResumeGap">
|
|
522
|
+
Cause: `Cursor "{afterId}" missing on "{signal}".` That `Last-Event-ID` was pruned or never
|
|
523
|
+
existed. Reconnect without it; remaining events replay. `autoResubscribe: true` does this after
|
|
524
|
+
backoff.
|
|
525
|
+
</Accordion>
|
|
526
|
+
|
|
527
|
+
<Accordion title="OKE1240 when emitting live">
|
|
528
|
+
No subscriber / exposure counted. Set `{ optional: true }` (usual for firehoses) or mount
|
|
529
|
+
`http.live` before emitting in tests.
|
|
530
|
+
</Accordion>
|
|
531
|
+
|
|
532
|
+
<Accordion title="OKE1250 on emit">
|
|
533
|
+
Cause: `"{resource}": {detail}` from the Standard Schema issues. Align the payload with `schema` —
|
|
534
|
+
no client receives a frame.
|
|
535
|
+
</Accordion>
|
|
536
|
+
|
|
537
|
+
<Accordion title="Bound on(liveSignal, flow) as a worker">
|
|
538
|
+
Live is not competing-consumer physics. Use `signal.once` for workers, or expose SSE with
|
|
539
|
+
`http.live`.
|
|
540
|
+
</Accordion>
|
|
541
|
+
|
|
542
|
+
<Accordion title="Raw EventSource on /api/signals/…">
|
|
543
|
+
That path is not the engine firehose. Use `GET /_oke/live/{name}`, a gated `.live` route, or
|
|
544
|
+
`api.live` / `useLive` from the typed client.
|
|
545
|
+
</Accordion>
|
|
546
|
+
|
|
547
|
+
<Accordion title="Multiple live exposures (client)">
|
|
548
|
+
Two routes share the same match shape. Pass `via: "unit.flow"` or call the exposing Flow
|
|
549
|
+
(`api.orders.events({orderId}, {onEvent})`) instead of root `api.live`.
|
|
550
|
+
</Accordion>
|
|
551
|
+
|
|
552
|
+
<Accordion title="TypeError: retention is only valid with signal.live">
|
|
553
|
+
`retention: { maxAge, maxCount }` is live-only. Drop it on queue / pub-sub Signals, or switch
|
|
554
|
+
to `signal.live`.
|
|
555
|
+
</Accordion>
|
|
556
|
+
|
|
557
|
+
<Accordion title="live query requires a primary key / RLS driver">
|
|
558
|
+
Extract: `live: true on table "…" requires a primary key column`. Runtime needs an RLS-capable SQL
|
|
559
|
+
driver (`postgres` / `pglite`) and a gated identity. That path is resource live — not
|
|
560
|
+
`signal.live`. Attach `.gate(...)` and declare a PK.
|
|
561
|
+
</Accordion>
|
|
562
|
+
|
|
563
|
+
</Accordions>
|
|
564
|
+
|
|
565
|
+
## Learn more
|
|
566
|
+
|
|
567
|
+
- [HTTP · Live Streams](/docs/elements/flow/http#live-streams) — exposure, uniqueness, match
|
|
568
|
+
- [Signal Overview](/docs/elements/signal) — delivery matrix and drivers
|
|
569
|
+
- [Once](/docs/elements/signal/once) — when you need competing workers instead
|
|
570
|
+
- [Broadcast](/docs/elements/signal/broadcast) — ephemeral fan-out without a tape
|
|
571
|
+
- [Client](/docs/client/live) — `api.live`, `useLive`, `useLiveQuery`
|
|
572
|
+
- [fx](/docs/reference/fx) — `fx.emit`, `fx.live`
|
|
573
|
+
- [Gate](/docs/elements/gate) — `.gate(...)` / `.public()` on triggers
|
|
574
|
+
- [Errors](/docs/reference/errors) — OKE1050 · OKE1210 · OKE1240 · OKE1250
|
|
575
|
+
|
|
576
|
+
## Next
|
|
577
|
+
|
|
578
|
+
<Cards>
|
|
579
|
+
<Card
|
|
580
|
+
title="HTTP · Live Streams"
|
|
581
|
+
description="Firehose paths, filters, uniqueness, and client subscribe."
|
|
582
|
+
href="/docs/elements/flow/http#live-streams"
|
|
583
|
+
/>
|
|
584
|
+
<Card title="Client" description="api.live and useLive for browsers." href="/docs/client/live" />
|
|
585
|
+
<Card
|
|
586
|
+
title="Signal Overview"
|
|
587
|
+
description="once / broadcast / live in one place."
|
|
588
|
+
href="/docs/elements/signal"
|
|
589
|
+
/>
|
|
590
|
+
</Cards>
|