okengine 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/index.mdx +25 -6
- package/site/content/docs/ai/llms-txt.mdx +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- package/site/content/docs/ai/try-it.mdx +58 -0
- package/site/content/docs/client/auth.mdx +208 -0
- package/site/content/docs/client/calling.mdx +451 -0
- package/site/content/docs/client/index.mdx +209 -0
- package/site/content/docs/client/live.mdx +234 -0
- package/site/content/docs/client/meta.json +5 -0
- package/site/content/docs/client/react.mdx +249 -0
- package/site/content/docs/elements/ai/agents.mdx +232 -0
- package/site/content/docs/elements/ai/index.mdx +341 -0
- package/site/content/docs/elements/ai/mcp.mdx +275 -0
- package/site/content/docs/elements/ai/meta.json +5 -0
- package/site/content/docs/elements/ai/models.mdx +283 -0
- package/site/content/docs/elements/ai/prompts.mdx +256 -0
- package/site/content/docs/elements/channel/email.mdx +266 -0
- package/site/content/docs/elements/channel/index.mdx +344 -0
- package/site/content/docs/elements/channel/meta.json +5 -0
- package/site/content/docs/elements/channel/push.mdx +218 -0
- package/site/content/docs/elements/channel/receipts.mdx +206 -0
- package/site/content/docs/elements/channel/sms.mdx +264 -0
- package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
- package/site/content/docs/elements/clock/index.mdx +300 -0
- package/site/content/docs/elements/clock/meta.json +5 -0
- package/site/content/docs/elements/clock/schedules.mdx +508 -0
- package/site/content/docs/elements/clock/sleep.mdx +445 -0
- package/site/content/docs/elements/flow/consumers.mdx +707 -0
- package/site/content/docs/elements/flow/http.mdx +1080 -0
- package/site/content/docs/elements/flow/index.mdx +618 -0
- package/site/content/docs/elements/flow/meta.json +5 -0
- package/site/content/docs/elements/flow/routing.mdx +559 -0
- package/site/content/docs/elements/flow/workflows.mdx +640 -0
- package/site/content/docs/elements/gate/auth.mdx +371 -0
- package/site/content/docs/elements/gate/authorization.mdx +298 -0
- package/site/content/docs/elements/gate/index.mdx +418 -0
- package/site/content/docs/elements/gate/meta.json +5 -0
- package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
- package/site/content/docs/elements/gate/rls.mdx +303 -0
- package/site/content/docs/elements/gate/tenancy.mdx +372 -0
- package/site/content/docs/elements/index.mdx +52 -11
- package/site/content/docs/elements/signal/broadcast.mdx +517 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +590 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +596 -0
- package/site/content/docs/elements/store/files.mdx +659 -0
- package/site/content/docs/elements/store/index.mdx +310 -0
- package/site/content/docs/elements/store/kv.mdx +629 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +946 -0
- package/site/content/docs/elements/store/sql.mdx +937 -0
- package/site/content/docs/elements/vault/config.mdx +256 -0
- package/site/content/docs/elements/vault/index.mdx +356 -0
- package/site/content/docs/elements/vault/meta.json +5 -0
- package/site/content/docs/elements/vault/rotation.mdx +273 -0
- package/site/content/docs/elements/vault/secrets.mdx +285 -0
- package/site/content/docs/index.mdx +78 -30
- package/site/content/docs/meta.json +11 -8
- package/site/content/docs/plugins/cors.mdx +2 -1
- package/site/content/docs/plugins/csrf.mdx +4 -1
- package/site/content/docs/plugins/headers.mdx +2 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
- package/site/content/docs/plugins/magic-link.mdx +6 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
- package/site/content/docs/plugins/oauth.mdx +1 -1
- package/site/content/docs/plugins/otp.mdx +12 -1
- package/site/content/docs/plugins/passkey.mdx +39 -15
- package/site/content/docs/plugins/two-factor.mdx +91 -34
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +3 -3
- package/site/content/docs/recipes/index.mdx +18 -28
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +3 -3
- package/site/content/docs/recipes/meta.json +1 -4
- package/site/content/docs/recipes/nginx.mdx +2 -2
- package/site/content/docs/recipes/openrouter.mdx +283 -0
- package/site/content/docs/recipes/pgdog.mdx +3 -3
- package/site/content/docs/recipes/postgres.mdx +1 -2
- package/site/content/docs/recipes/rustfs.mdx +2 -2
- package/site/content/docs/recipes/traefik.mdx +3 -3
- package/site/content/docs/reference/cli.mdx +231 -0
- package/site/content/docs/reference/configuration.mdx +30 -10
- package/site/content/docs/reference/environment-variables.mdx +93 -20
- package/site/content/docs/reference/errors.mdx +132 -29
- package/site/content/docs/reference/fx.mdx +137 -73
- package/site/content/docs/reference/index.mdx +13 -21
- package/site/content/docs/reference/meta.json +4 -5
- package/site/content/docs/reference/okid.mdx +42 -17
- package/site/content/docs/reference/plugins.mdx +4 -3
- package/site/content/docs/reference/security.mdx +197 -0
- package/site/content/docs/understand/meta.json +5 -0
- package/site/content/docs/understand/the-anatomy.mdx +132 -0
- package/site/content/docs/understand/the-model.mdx +32 -0
- package/site/content/docs/understand/the-problem.mdx +74 -0
- package/site/content/docs/understand/the-vocabulary.mdx +26 -0
- package/src/auth/api-keys.ts +2 -1
- package/src/auth/bindings.ts +64 -16
- package/src/auth/gate-auth.test.ts +6 -1
- package/src/auth/identity.ts +152 -5
- package/src/auth/index.ts +29 -0
- package/src/auth/invites.ts +2 -1
- package/src/auth/method-context.ts +41 -0
- package/src/auth/oauth-as/crypto.ts +2 -1
- package/src/auth/oauth-as/stores.ts +2 -1
- package/src/auth/operator.ts +2 -1
- package/src/auth/sessions-jwt.test.ts +77 -0
- package/src/auth/sessions.ts +20 -4
- package/src/auth/tenants.ts +3 -2
- package/src/auth/two-factor-challenge.test.ts +61 -0
- package/src/auth/two-factor-challenge.ts +284 -0
- package/src/auth/verification.ts +5 -0
- package/src/bench/REPORT.md +49 -0
- package/src/bench/g03-signal-once.bench.ts +1 -1
- package/src/bench/g10-observability-contention.bench.ts +1 -1
- package/src/bench/g17-hybrid-search.bench.ts +572 -0
- package/src/bench/load-app.ts +2 -10
- package/src/cli/ai-setup/ai-setup.test.ts +331 -42
- package/src/cli/ai-setup/apply.ts +294 -52
- package/src/cli/ai-setup/catalog.ts +238 -1342
- package/src/cli/ai-setup/index.ts +14 -99
- package/src/cli/ai-setup/prompts.ts +60 -443
- package/src/cli/ask-seed.test.ts +96 -0
- package/src/cli/ask-seed.ts +82 -0
- package/src/cli/ask-vault-gaps.test.ts +84 -0
- package/src/cli/ask-vault-gaps.ts +146 -0
- package/src/cli/build.ts +2 -2
- package/src/cli/client-add.test.ts +26 -1
- package/src/cli/client-add.ts +106 -27
- package/src/cli/competitor-mention-removal.test.ts +1 -1
- package/src/cli/db-seed.ts +2 -0
- package/src/cli/db.ts +150 -11
- package/src/cli/dev.ts +119 -211
- package/src/cli/docker-clean.ts +2 -2
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/doctor-diff.ts +4 -2
- package/src/cli/doctor-pii.test.ts +1 -1
- package/src/cli/doctor.ts +14 -1
- package/src/cli/hero-meta.test.ts +4 -2
- package/src/cli/load-config.images.test.ts +8 -10
- package/src/cli/load-config.ts +1 -1
- package/src/cli/registry.ts +22 -10
- package/src/cli/replay.ts +3 -1
- package/src/cli/start.ts +1 -1
- package/src/cli/tui/keys.ts +2 -3
- package/src/client/auth/cookies.ts +74 -0
- package/src/client/auth/create-auth-client.ts +636 -0
- package/src/client/auth/denials.ts +99 -0
- package/src/client/auth/session.ts +283 -0
- package/src/client/auth.test.ts +251 -0
- package/src/client/auth.ts +39 -114
- package/src/client/create-with-session.ts +245 -0
- package/src/client/create.ts +51 -5
- package/src/client/index.ts +11 -1
- package/src/client/live.ts +11 -79
- package/src/client/notes-contract.test.ts +5 -10
- package/src/client/sse.ts +137 -0
- package/src/client/stream.ts +147 -0
- package/src/client/transport.test.ts +32 -0
- package/src/client/transport.ts +123 -26
- package/src/client/types.ts +118 -14
- package/src/client-react/index.ts +185 -24
- package/src/client-react/use-live-query.ts +13 -3
- package/src/compiler/aot.test.ts +7 -4
- package/src/compiler/effects-embed.test.ts +56 -0
- package/src/compiler/effects-fetch.test.ts +43 -0
- package/src/compiler/effects-infer.ts +58 -2
- package/src/compiler/extract.test.ts +258 -44
- package/src/compiler/extract.ts +566 -48
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
- package/src/compiler/fixtures/skyport.expected.json +4 -4
- package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
- package/src/compiler/response.ts +2 -2
- package/src/compiler/search-writer-isolation.test.ts +40 -0
- package/src/config/index.ts +11 -0
- package/src/console/server/app.ts +17 -23
- package/src/console/server/bind.ts +4 -0
- package/src/console/server/console.test.ts +2 -2
- package/src/console/server/flows-invoke.test.ts +50 -34
- package/src/console/server/flows.ts +436 -255
- package/src/console/server/runs-ingest.test.ts +6 -3
- package/src/console/server/serve.ts +1 -1
- package/src/console/server/signals.test.ts +1 -5
- package/src/console/server/signals.ts +8 -6
- package/src/console/server/state.ts +6 -1
- package/src/console/server/store.ts +1 -1
- package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
- package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
- package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
- package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
- package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
- package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
- package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
- package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
- package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
- package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
- package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
- package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
- package/src/console/ui-next/dist/index.html +6 -13
- package/src/console/ui-next/seed-invoke-host.ts +21 -18
- package/src/console/ui-next/src/client.ts +7 -2
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
- package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
- package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
- package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
- package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
- package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
- package/src/docker/ai-model-status.test.ts +1 -12
- package/src/docker/ai-model-status.ts +10 -38
- package/src/docker/compose-up.test.ts +100 -0
- package/src/docker/compose-up.ts +292 -0
- package/src/docker/compose.ts +4 -14
- package/src/docker/derive.ts +9 -34
- package/src/docker/docker.test.ts +16 -261
- package/src/docker/images-config.test.ts +13 -25
- package/src/docker/index.ts +1 -24
- package/src/docker/recipes/index.ts +0 -23
- package/src/docker/recipes/pgdog.ts +6 -4
- package/src/docker/stack-id.test.ts +2 -2
- package/src/docker/stack-id.ts +0 -1
- package/src/docker/types.ts +2 -2
- package/src/drivers/ai-anthropic.ts +10 -0
- package/src/drivers/ai-openai-compatible.ts +34 -7
- package/src/drivers/ai-providers.test.ts +0 -107
- package/src/drivers/ai-stream.test.ts +0 -34
- package/src/drivers/ai-types.ts +17 -8
- package/src/drivers/channel-fcm.ts +6 -1
- package/src/drivers/channel-msegat.ts +6 -1
- package/src/drivers/channel-resend.ts +5 -1
- package/src/drivers/channel-smtp.ts +5 -1
- package/src/drivers/channel-sndr.ts +10 -1
- package/src/drivers/channel-taqnyat-mail.ts +5 -1
- package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
- package/src/drivers/channel-taqnyat.ts +6 -1
- package/src/drivers/channel-types.ts +10 -0
- package/src/drivers/channel-unifonic.ts +6 -1
- package/src/drivers/channel-wa-cloud.ts +6 -1
- package/src/drivers/channel-webpush.ts +6 -1
- package/src/drivers/external.ts +33 -0
- package/src/drivers/index.ts +0 -10
- package/src/drivers/meilisearch.ts +6 -0
- package/src/drivers/oauth-types.ts +4 -0
- package/src/drivers/postgres.test.ts +74 -3
- package/src/drivers/postgres.ts +175 -6
- package/src/drivers/signal-types.ts +5 -5
- package/src/drivers/types.ts +7 -0
- package/src/drivers/vault-types.ts +5 -0
- package/src/elements/ai/declare.ts +17 -4
- package/src/elements/ai/eval.ts +1 -1
- package/src/elements/ai/mcp-http.ts +1 -1
- package/src/elements/ai/pii.ts +1 -1
- package/src/elements/ai/providers.test.ts +289 -0
- package/src/elements/ai/providers.ts +169 -0
- package/src/elements/ai/runtime.ts +68 -5
- package/src/elements/ai/schema.ts +2 -2
- package/src/elements/ai.test.ts +7 -7
- package/src/elements/ai.ts +17 -1
- package/src/elements/channel/runtime.ts +105 -12
- package/src/elements/clock/cron-fields.test.ts +144 -0
- package/src/elements/clock/cron-fields.ts +185 -0
- package/src/elements/clock/declare.ts +247 -3
- package/src/elements/clock.ts +21 -1
- package/src/elements/index.ts +15 -0
- package/src/elements/signal/chaos-child.ts +1 -2
- package/src/elements/signal/declare.ts +65 -37
- package/src/elements/signal/delivery-modes.test.ts +11 -31
- package/src/elements/signal/dry-run-replay.test.ts +1 -7
- package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
- package/src/elements/signal/key-ordering.test.ts +6 -26
- package/src/elements/signal/lease-reclaim.test.ts +2 -6
- package/src/elements/signal/optional-emit.test.ts +6 -9
- package/src/elements/signal/order-lifecycle.test.ts +4 -17
- package/src/elements/signal/orphan-messages.test.ts +4 -15
- package/src/elements/signal/reconcile.test.ts +2 -2
- package/src/elements/signal/runtime.ts +1 -1
- package/src/elements/signal/schema-emit.test.ts +4 -8
- package/src/elements/signal.test.ts +24 -49
- package/src/elements/signal.ts +9 -2
- package/src/elements/store/domain-ddl.test.ts +1 -1
- package/src/elements/store/live-http.test.ts +9 -4
- package/src/elements/store/prepare-row.test.ts +123 -0
- package/src/elements/store/resource.ts +43 -12
- package/src/elements/store/schema-decl.ts +117 -0
- package/src/elements/store/search-backfill.ts +205 -0
- package/src/elements/store/search-bind.ts +66 -0
- package/src/elements/store/search-bm25.ts +78 -0
- package/src/elements/store/search-ddl.ts +156 -0
- package/src/elements/store/search-embed-flow.ts +141 -0
- package/src/elements/store/search-errors.ts +34 -0
- package/src/elements/store/search-fusion.ts +112 -0
- package/src/elements/store/search-lsh.ts +179 -0
- package/src/elements/store/search-runtime.ts +300 -0
- package/src/elements/store/search.test.ts +144 -0
- package/src/elements/store/sql-session.test.ts +1 -0
- package/src/elements/store/sql-session.ts +83 -3
- package/src/elements/store/table.ts +23 -2
- package/src/elements/store/upsert-app.test.ts +1 -3
- package/src/elements/store.ts +50 -0
- package/src/full.ts +4 -2
- package/src/http.ts +4 -1
- package/src/i18n/catalogs/ar.ts +14 -6
- package/src/i18n/catalogs/en.ts +14 -6
- package/src/index.ts +6 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
- package/src/kernel/adopt-routes.ts +9 -1
- package/src/kernel/app-auth.ts +5 -0
- package/src/kernel/app.ts +102 -9
- package/src/kernel/auto-cache.test.ts +6 -12
- package/src/kernel/auto-registry.test.ts +3 -3
- package/src/kernel/boot-bind/ai.test.ts +22 -19
- package/src/kernel/boot-bind/ai.ts +5 -43
- package/src/kernel/boot-bind/clock.ts +23 -5
- package/src/kernel/boot-bind/honor-config.test.ts +4 -4
- package/src/kernel/boot.test.ts +11 -6
- package/src/kernel/boot.ts +66 -16
- package/src/kernel/boundary-contract.ts +93 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/call.ts +89 -0
- package/src/kernel/capability.ts +6 -0
- package/src/kernel/client-descriptor.ts +123 -0
- package/src/kernel/clock-timezone.test.ts +80 -0
- package/src/kernel/correlation.test.ts +1 -1
- package/src/kernel/dry-run.ts +11 -8
- package/src/kernel/effects-stamping.test.ts +42 -9
- package/src/kernel/effects.test.ts +4 -2
- package/src/kernel/effects.ts +51 -9
- package/src/kernel/errors-channel.ts +17 -0
- package/src/kernel/errors-live-resume.ts +3 -2
- package/src/kernel/errors-tenant.ts +7 -4
- package/src/kernel/errors.registry-helpers.ts +96 -0
- package/src/kernel/errors.registry.test.ts +139 -25
- package/src/kernel/errors.ts +102 -23
- package/src/kernel/external-effects.test.ts +196 -0
- package/src/kernel/flow.test.ts +13 -4
- package/src/kernel/flow.ts +69 -71
- package/src/kernel/fx-ask-telemetry.test.ts +2 -2
- package/src/kernel/fx-dead-letters.test.ts +3 -3
- package/src/kernel/fx-fetch.ts +55 -0
- package/src/kernel/fx-live-stream.ts +2 -2
- package/src/kernel/fx-live.test.ts +8 -8
- package/src/kernel/fx.test.ts +17 -11
- package/src/kernel/fx.ts +180 -57
- package/src/kernel/horizontal-child.ts +1 -1
- package/src/kernel/http-query.test.ts +3 -6
- package/src/kernel/index.ts +13 -3
- package/src/kernel/instance-id.ts +1 -1
- package/src/kernel/instances.test.ts +40 -0
- package/src/kernel/instances.ts +47 -9
- package/src/kernel/live-http.test.ts +6 -7
- package/src/kernel/live-http.ts +6 -2
- package/src/kernel/live-resume.test.ts +1 -1
- package/src/kernel/mcp-tool.test.ts +3 -3
- package/src/kernel/on.ts +98 -4
- package/src/kernel/pipeline.test.ts +12 -15
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/stamp-http.test.ts +3 -3
- package/src/kernel/triggers.ts +210 -95
- package/src/kernel-entry.ts +0 -1
- package/src/manifest/diff.ts +11 -1
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/types.ts +46 -2
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +1 -1
- package/src/okid.test.ts +56 -0
- package/src/okid.ts +55 -11
- package/src/plugins/anonymous.ts +8 -4
- package/src/plugins/auth/shared.ts +27 -3
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +561 -23
- package/src/plugins/config-source.ts +1 -1
- package/src/plugins/csrf.test.ts +65 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +36 -16
- package/src/plugins/oauth/shared.ts +3 -1
- package/src/plugins/oauth.ts +22 -14
- package/src/plugins/otp.ts +37 -21
- package/src/plugins/passkey-webauthn.ts +9 -6
- package/src/plugins/passkey.ts +98 -26
- package/src/plugins/pre-account-hijack.test.ts +223 -0
- package/src/plugins/two-factor.ts +466 -37
- package/src/plugins/username.ts +19 -7
- package/src/release/build-lib.ts +1 -0
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +1 -1
- package/src/runs/collect.test.ts +0 -2
- package/src/runtime/json-code-block.test.ts +250 -20
- package/src/runtime/json-code-block.ts +1261 -41
- package/src/term.test.ts +1 -1
- package/src/test/create-test-app.test.ts +12 -12
- package/src/test/live-signals.test.ts +6 -6
- package/src/test/provisions.integration.test.ts +10 -14
- package/src/test/reset-element-registries.ts +1 -1
- package/src/test/tenant-isolation.test.ts +12 -6
- package/site/content/docs/deployment/docker-swarm.mdx +0 -164
- package/site/content/docs/deployment/docker.mdx +0 -227
- package/site/content/docs/deployment/index.mdx +0 -83
- package/site/content/docs/deployment/kubernetes.mdx +0 -176
- package/site/content/docs/deployment/meta.json +0 -5
- package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
- package/site/content/docs/elements/ai.mdx +0 -385
- package/site/content/docs/elements/channel.mdx +0 -346
- package/site/content/docs/elements/clock.mdx +0 -244
- package/site/content/docs/elements/flow.mdx +0 -420
- package/site/content/docs/elements/gate.mdx +0 -436
- package/site/content/docs/elements/signal.mdx +0 -380
- package/site/content/docs/elements/store.mdx +0 -1099
- package/site/content/docs/elements/vault.mdx +0 -405
- package/site/content/docs/get-started/basic-usage.mdx +0 -173
- package/site/content/docs/get-started/index.mdx +0 -43
- package/site/content/docs/get-started/installation.mdx +0 -220
- package/site/content/docs/get-started/introduction.mdx +0 -132
- package/site/content/docs/get-started/meta.json +0 -13
- package/site/content/docs/get-started/project-structure.mdx +0 -925
- package/site/content/docs/get-started/testing.mdx +0 -328
- package/site/content/docs/get-started/why.mdx +0 -162
- package/site/content/docs/recipes/llama-cpp.mdx +0 -151
- package/site/content/docs/recipes/ollama.mdx +0 -142
- package/site/content/docs/recipes/sglang.mdx +0 -105
- package/site/content/docs/recipes/vllm.mdx +0 -106
- package/site/content/docs/reference/cli.md +0 -232
- package/site/content/docs/reference/client.mdx +0 -469
- package/site/content/docs/reference/security.md +0 -74
- package/src/cli/ai-setup/detect-ollama.ts +0 -213
- package/src/cli/ai-setup/recommend.test.ts +0 -225
- package/src/cli/ai-setup/recommend.ts +0 -225
- package/src/cli/dev-controls.test.ts +0 -122
- package/src/cli/dev-controls.ts +0 -164
- package/src/cli/tui/DevLive.tsx +0 -126
- package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
- package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
- package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
- package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
- package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
- package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
- package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
- package/src/docker/ollama-pull.ts +0 -232
- package/src/docker/recipes/llama-cpp.ts +0 -298
- package/src/docker/recipes/ollama.ts +0 -44
- package/src/docker/recipes/sglang.ts +0 -55
- package/src/docker/recipes/vllm.ts +0 -44
- package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
- package/src/drivers/ai-ollama.integration.test.ts +0 -184
- package/src/drivers/ai-ollama.ts +0 -389
- package/src/drivers/ollama.ts +0 -14
|
@@ -31,11 +31,7 @@ afterEach(async () => {
|
|
|
31
31
|
|
|
32
32
|
describe("signal key ordering · memory", () => {
|
|
33
33
|
test("same key: never concurrent; complete in emission order", async () => {
|
|
34
|
-
const once = signal("order-events", {
|
|
35
|
-
delivery: "once",
|
|
36
|
-
retries: 3,
|
|
37
|
-
deadLetter: true,
|
|
38
|
-
});
|
|
34
|
+
const once = signal.once("order-events", { retries: 3, deadLetter: true });
|
|
39
35
|
const bus = await memorySignalDriver.open({
|
|
40
36
|
signals: new Map([[once.name, once]]),
|
|
41
37
|
});
|
|
@@ -75,11 +71,7 @@ describe("signal key ordering · memory", () => {
|
|
|
75
71
|
});
|
|
76
72
|
|
|
77
73
|
test("different keys: may process concurrently", async () => {
|
|
78
|
-
const once = signal("order-events", {
|
|
79
|
-
delivery: "once",
|
|
80
|
-
retries: 3,
|
|
81
|
-
deadLetter: true,
|
|
82
|
-
});
|
|
74
|
+
const once = signal.once("order-events", { retries: 3, deadLetter: true });
|
|
83
75
|
const bus = await memorySignalDriver.open({
|
|
84
76
|
signals: new Map([[once.name, once]]),
|
|
85
77
|
});
|
|
@@ -110,11 +102,7 @@ describe("signal key ordering · memory", () => {
|
|
|
110
102
|
});
|
|
111
103
|
|
|
112
104
|
test("no key: unchanged competing-consumer (both may run concurrent)", async () => {
|
|
113
|
-
const once = signal("order-events", {
|
|
114
|
-
delivery: "once",
|
|
115
|
-
retries: 3,
|
|
116
|
-
deadLetter: true,
|
|
117
|
-
});
|
|
105
|
+
const once = signal.once("order-events", { retries: 3, deadLetter: true });
|
|
118
106
|
const bus = await memorySignalDriver.open({
|
|
119
107
|
signals: new Map([[once.name, once]]),
|
|
120
108
|
});
|
|
@@ -148,7 +136,7 @@ describe("signal key ordering · memory", () => {
|
|
|
148
136
|
describe("signal key ordering · postgres", () => {
|
|
149
137
|
test("same-key pending blocked while sibling holds unexpired lease", async () => {
|
|
150
138
|
let t = 10_000;
|
|
151
|
-
const once = signal("order-events", {
|
|
139
|
+
const once = signal.once("order-events", { retries: 3 });
|
|
152
140
|
const sql = createPostgresSignalFake({ now: () => t });
|
|
153
141
|
const runtime = createSignalRuntime({
|
|
154
142
|
driver: postgresSignalDriver,
|
|
@@ -213,11 +201,7 @@ describe("signal key ordering · postgres", () => {
|
|
|
213
201
|
});
|
|
214
202
|
|
|
215
203
|
test("emit with key then drain preserves emission order", async () => {
|
|
216
|
-
const once = signal("order-events", {
|
|
217
|
-
delivery: "once",
|
|
218
|
-
retries: 3,
|
|
219
|
-
deadLetter: true,
|
|
220
|
-
});
|
|
204
|
+
const once = signal.once("order-events", { retries: 3, deadLetter: true });
|
|
221
205
|
const bus = await postgresSignalDriver.open({
|
|
222
206
|
signals: new Map([[once.name, once]]),
|
|
223
207
|
sql: createPostgresSignalFake(),
|
|
@@ -246,11 +230,7 @@ describe("signal key ordering · lease reclaim", () => {
|
|
|
246
230
|
const marker = join(dir, "claimed");
|
|
247
231
|
try {
|
|
248
232
|
let t = 5_000;
|
|
249
|
-
const once = signal("order-events", {
|
|
250
|
-
delivery: "once",
|
|
251
|
-
retries: 3,
|
|
252
|
-
deadLetter: true,
|
|
253
|
-
});
|
|
233
|
+
const once = signal.once("order-events", { retries: 3, deadLetter: true });
|
|
254
234
|
const r1 = createSignalRuntime({
|
|
255
235
|
driver: memorySignalDriver,
|
|
256
236
|
durablePath,
|
|
@@ -30,11 +30,7 @@ describe("signal lease reclaim", () => {
|
|
|
30
30
|
const marker = join(dir, "claimed");
|
|
31
31
|
try {
|
|
32
32
|
let t = 5_000;
|
|
33
|
-
const once = signal("order-placed", {
|
|
34
|
-
delivery: "once",
|
|
35
|
-
retries: 3,
|
|
36
|
-
deadLetter: true,
|
|
37
|
-
});
|
|
33
|
+
const once = signal.once("order-placed", { retries: 3, deadLetter: true });
|
|
38
34
|
const r1 = createSignalRuntime({
|
|
39
35
|
driver: memorySignalDriver,
|
|
40
36
|
durablePath,
|
|
@@ -83,7 +79,7 @@ describe("signal lease reclaim", () => {
|
|
|
83
79
|
|
|
84
80
|
test("postgres: expired inflight reclaimed in the same claim query", async () => {
|
|
85
81
|
let t = 10_000;
|
|
86
|
-
const once = signal("order-placed", {
|
|
82
|
+
const once = signal.once("order-placed", { retries: 3 });
|
|
87
83
|
const sql = createPostgresSignalFake({ now: () => t });
|
|
88
84
|
const runtime = createSignalRuntime({
|
|
89
85
|
driver: postgresSignalDriver,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Zero-subscriber emit —
|
|
2
|
+
* Zero-subscriber emit — OKE1240 when optional is unset/false; allow when true.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
@@ -44,8 +44,8 @@ for (const { label, driver, setup } of [
|
|
|
44
44
|
},
|
|
45
45
|
] as const) {
|
|
46
46
|
describe(`signal optional emit · ${label}`, () => {
|
|
47
|
-
test("optional unset/false: emit with zero subscribers throws
|
|
48
|
-
const once = signal("order-placed"
|
|
47
|
+
test("optional unset/false: emit with zero subscribers throws OKE1240", async () => {
|
|
48
|
+
const once = signal.once("order-placed");
|
|
49
49
|
expect(once.optional).toBe(false);
|
|
50
50
|
const bus = await openBus(driver, [once], setup());
|
|
51
51
|
|
|
@@ -56,16 +56,13 @@ for (const { label, driver, setup } of [
|
|
|
56
56
|
err = e;
|
|
57
57
|
}
|
|
58
58
|
expect(err).toBeInstanceOf(OkeError);
|
|
59
|
-
expect((err as OkeError).code).toBe(
|
|
60
|
-
expect((err as Error).message).toContain("
|
|
59
|
+
expect((err as OkeError).code).toBe(1240);
|
|
60
|
+
expect((err as Error).message).toContain("OKE1240");
|
|
61
61
|
expect((err as Error).message).toContain("no subscriber");
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test("optional: true allows emit with zero subscribers", async () => {
|
|
65
|
-
const once = signal("hook", {
|
|
66
|
-
delivery: "once",
|
|
67
|
-
optional: true,
|
|
68
|
-
});
|
|
65
|
+
const once = signal.once("hook", { optional: true });
|
|
69
66
|
const bus = await openBus(driver, [once], setup());
|
|
70
67
|
await bus.emit("hook", { ok: true });
|
|
71
68
|
await bus.drain();
|
|
@@ -42,20 +42,11 @@ afterEach(async () => {
|
|
|
42
42
|
describe("signal order lifecycle · once + broadcast + live", () => {
|
|
43
43
|
test("one placed order: exclusive fulfill, fan-out side effects, live status replay", async () => {
|
|
44
44
|
// once — queued job: exactly one competing consumer fulfills
|
|
45
|
-
const orderPlaced = signal("order-placed", {
|
|
46
|
-
delivery: "once",
|
|
47
|
-
retries: 2,
|
|
48
|
-
deadLetter: true,
|
|
49
|
-
});
|
|
45
|
+
const orderPlaced = signal.once("order-placed", { retries: 2, deadLetter: true });
|
|
50
46
|
// broadcast — same domain event fan-out: every subscriber gets a copy
|
|
51
|
-
const orderChanged = signal("order-changed"
|
|
52
|
-
delivery: "broadcast",
|
|
53
|
-
});
|
|
47
|
+
const orderChanged = signal.broadcast("order-changed");
|
|
54
48
|
// live — client-visible status feed (bus.live; createClient has no subscribe yet)
|
|
55
|
-
const orderStatus = signal("order-status", {
|
|
56
|
-
delivery: "live",
|
|
57
|
-
optional: true,
|
|
58
|
-
});
|
|
49
|
+
const orderStatus = signal.live("order-status", { optional: true });
|
|
59
50
|
|
|
60
51
|
const bus = await memorySignalDriver.open({
|
|
61
52
|
signals: new Map([
|
|
@@ -118,11 +109,7 @@ describe("signal order lifecycle · once + broadcast + live", () => {
|
|
|
118
109
|
|
|
119
110
|
test("once retries then DLQ still apply inside the same domain", async () => {
|
|
120
111
|
// Light reuse of once retry/DLQ — full matrix lives in delivery-modes.test.ts.
|
|
121
|
-
const orderPlaced = signal("order-placed", {
|
|
122
|
-
delivery: "once",
|
|
123
|
-
retries: 1,
|
|
124
|
-
deadLetter: true,
|
|
125
|
-
});
|
|
112
|
+
const orderPlaced = signal.once("order-placed", { retries: 1, deadLetter: true });
|
|
126
113
|
const bus = await memorySignalDriver.open({
|
|
127
114
|
signals: new Map([[orderPlaced.name, orderPlaced]]),
|
|
128
115
|
});
|
|
@@ -28,11 +28,7 @@ describe("orphan signal messages", () => {
|
|
|
28
28
|
const dir = await mkdtemp(join(tmpdir(), "oke-signal-orphan-"));
|
|
29
29
|
const durablePath = join(dir, "bus.json");
|
|
30
30
|
try {
|
|
31
|
-
const legacy = signal("legacy-shipped", {
|
|
32
|
-
delivery: "once",
|
|
33
|
-
retries: 0,
|
|
34
|
-
deadLetter: true,
|
|
35
|
-
});
|
|
31
|
+
const legacy = signal.once("legacy-shipped", { retries: 0, deadLetter: true });
|
|
36
32
|
const runtime = createSignalRuntime({
|
|
37
33
|
driver: memorySignalDriver,
|
|
38
34
|
durablePath,
|
|
@@ -75,11 +71,7 @@ describe("orphan signal messages", () => {
|
|
|
75
71
|
openRuntimes.pop();
|
|
76
72
|
|
|
77
73
|
// Re-declare → DLQ processable again via replay.
|
|
78
|
-
const restored = signal("legacy-shipped", {
|
|
79
|
-
delivery: "once",
|
|
80
|
-
retries: 0,
|
|
81
|
-
deadLetter: true,
|
|
82
|
-
});
|
|
74
|
+
const restored = signal.once("legacy-shipped", { retries: 0, deadLetter: true });
|
|
83
75
|
const again = await reconcileSignals([restored], store);
|
|
84
76
|
expect(again.active).toEqual(["legacy-shipped"]);
|
|
85
77
|
expect(again.orphaned).toEqual([]);
|
|
@@ -111,10 +103,7 @@ describe("orphan signal messages", () => {
|
|
|
111
103
|
const dir = await mkdtemp(join(tmpdir(), "oke-signal-orphan-pend-"));
|
|
112
104
|
const durablePath = join(dir, "bus.json");
|
|
113
105
|
try {
|
|
114
|
-
const once = signal("legacy-shipped", {
|
|
115
|
-
delivery: "once",
|
|
116
|
-
optional: true,
|
|
117
|
-
});
|
|
106
|
+
const once = signal.once("legacy-shipped", { optional: true });
|
|
118
107
|
const runtime = createSignalRuntime({
|
|
119
108
|
driver: memorySignalDriver,
|
|
120
109
|
durablePath,
|
|
@@ -126,7 +115,7 @@ describe("orphan signal messages", () => {
|
|
|
126
115
|
await runtime.close();
|
|
127
116
|
openRuntimes.pop();
|
|
128
117
|
|
|
129
|
-
const other = signal("order-placed"
|
|
118
|
+
const other = signal.once("order-placed");
|
|
130
119
|
const next = createSignalRuntime({
|
|
131
120
|
driver: memorySignalDriver,
|
|
132
121
|
durablePath,
|
|
@@ -5,8 +5,8 @@ import { createMemorySignalConfigStore, reconcileSignals } from "./reconcile.ts"
|
|
|
5
5
|
describe("reconcileSignals", () => {
|
|
6
6
|
test("marks a removed signal orphaned without deleting it", async () => {
|
|
7
7
|
const store = createMemorySignalConfigStore();
|
|
8
|
-
const a = signal("order-placed"
|
|
9
|
-
const b = signal("legacy-shipped"
|
|
8
|
+
const a = signal.once("order-placed");
|
|
9
|
+
const b = signal.once("legacy-shipped");
|
|
10
10
|
|
|
11
11
|
await reconcileSignals([a, b], store);
|
|
12
12
|
const second = await reconcileSignals([a], store);
|
|
@@ -68,7 +68,7 @@ export interface SignalRuntime {
|
|
|
68
68
|
*/
|
|
69
69
|
live(name: string, opts?: { readonly afterId?: string }): AsyncIterable<LiveEvent>;
|
|
70
70
|
/**
|
|
71
|
-
* Validate a live resume cursor (auto-starts). Throws
|
|
71
|
+
* Validate a live resume cursor (auto-starts). Throws OKE1210 when missing.
|
|
72
72
|
*
|
|
73
73
|
* @param name - Signal name
|
|
74
74
|
* @param afterId - SSE cursor
|
|
@@ -49,9 +49,8 @@ const drivers: Array<{
|
|
|
49
49
|
|
|
50
50
|
for (const { label, driver, setup } of drivers) {
|
|
51
51
|
describe(`signal schema emit · ${label}`, () => {
|
|
52
|
-
test("invalid payload rejected at emit with
|
|
53
|
-
const orderPlaced = signal("order-placed", {
|
|
54
|
-
delivery: "once",
|
|
52
|
+
test("invalid payload rejected at emit with OKE1250; valid succeeds", async () => {
|
|
53
|
+
const orderPlaced = signal.once("order-placed", {
|
|
55
54
|
schema: z.object({
|
|
56
55
|
orderId: z.string(),
|
|
57
56
|
total: z.number(),
|
|
@@ -74,7 +73,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
74
73
|
expect(rejected).toBeInstanceOf(OkeError);
|
|
75
74
|
const oke = rejected as OkeError;
|
|
76
75
|
expect(oke.code).toBe(OKE_ERRORS.SIGNAL_SCHEMA.code);
|
|
77
|
-
expect(oke.message).toContain("
|
|
76
|
+
expect(oke.message).toContain("OKE1250");
|
|
78
77
|
expect(oke.message).toContain("order-placed");
|
|
79
78
|
|
|
80
79
|
// Nothing staged while invalid.
|
|
@@ -90,10 +89,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
90
89
|
});
|
|
91
90
|
|
|
92
91
|
test("no schema: any payload still accepted", async () => {
|
|
93
|
-
const loose = signal("hook", {
|
|
94
|
-
delivery: "once",
|
|
95
|
-
optional: true,
|
|
96
|
-
});
|
|
92
|
+
const loose = signal.once("hook", { optional: true });
|
|
97
93
|
const bus = await openBus(driver, [loose], setup?.() ?? {});
|
|
98
94
|
const got: unknown[] = [];
|
|
99
95
|
await bus.subscribe("hook", "c1", async (m) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Signal element acceptance:
|
|
3
|
-
* - delivery
|
|
3
|
+
* - delivery helpers: signal.once / broadcast / live
|
|
4
4
|
* - rolled-back transaction emits nothing
|
|
5
5
|
* - crash between write and emit loses nothing (transactional outbox)
|
|
6
6
|
* - competing consumers under once receive each message once
|
|
@@ -76,25 +76,20 @@ async function openBus(
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
describe("signal declaration", () => {
|
|
79
|
-
test("
|
|
80
|
-
expect(()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
).
|
|
84
|
-
|
|
85
|
-
const s = signal("order-placed", { delivery: "once" });
|
|
86
|
-
expect(s.delivery).toBe("once");
|
|
87
|
-
expect(s.name).toBe("order-placed");
|
|
79
|
+
test("helpers stamp delivery physics", () => {
|
|
80
|
+
expect(signal.once("order-placed").delivery).toBe("once");
|
|
81
|
+
expect(signal.broadcast("news").delivery).toBe("broadcast");
|
|
82
|
+
expect(signal.live("seat-feed").delivery).toBe("live");
|
|
83
|
+
expect(signal.once("order-placed").name).toBe("order-placed");
|
|
88
84
|
});
|
|
89
85
|
|
|
90
86
|
test("retention is live-only", () => {
|
|
91
87
|
expect(() =>
|
|
92
88
|
// @ts-expect-error retention is live-only
|
|
93
|
-
signal("order-placed", {
|
|
89
|
+
signal.once("order-placed", { retention: { maxCount: 2 } }),
|
|
94
90
|
).toThrow(/retention is only valid/);
|
|
95
91
|
|
|
96
|
-
const live = signal("order-status", {
|
|
97
|
-
delivery: "live",
|
|
92
|
+
const live = signal.live("order-status", {
|
|
98
93
|
optional: true,
|
|
99
94
|
retention: { maxAge: "24h", maxCount: 500 },
|
|
100
95
|
});
|
|
@@ -105,7 +100,7 @@ describe("signal declaration", () => {
|
|
|
105
100
|
for (const { label, driver, setup } of drivers) {
|
|
106
101
|
describe(`signal driver · ${label}`, () => {
|
|
107
102
|
test("rolled-back transaction emits nothing", async () => {
|
|
108
|
-
const once = signal("order-placed"
|
|
103
|
+
const once = signal.once("order-placed");
|
|
109
104
|
const bus = await openBus(driver, [once], setup?.() ?? {});
|
|
110
105
|
const seen: unknown[] = [];
|
|
111
106
|
await bus.subscribe("order-placed", "c1", async (m) => {
|
|
@@ -123,7 +118,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
123
118
|
});
|
|
124
119
|
|
|
125
120
|
test("commit makes write + emit visible together", async () => {
|
|
126
|
-
const once = signal("order-placed"
|
|
121
|
+
const once = signal.once("order-placed");
|
|
127
122
|
const bus = await openBus(driver, [once], setup?.() ?? {});
|
|
128
123
|
const seen: unknown[] = [];
|
|
129
124
|
await bus.subscribe("order-placed", "c1", async (m) => {
|
|
@@ -141,7 +136,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
141
136
|
});
|
|
142
137
|
|
|
143
138
|
test("competing consumers under once receive each message exactly once", async () => {
|
|
144
|
-
const once = signal("order-placed", {
|
|
139
|
+
const once = signal.once("order-placed", { retries: 2 });
|
|
145
140
|
const bus = await openBus(driver, [once], setup?.() ?? {});
|
|
146
141
|
const got: string[] = [];
|
|
147
142
|
|
|
@@ -163,7 +158,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
163
158
|
});
|
|
164
159
|
|
|
165
160
|
test("once: a single emit is received by exactly one of two consumers", async () => {
|
|
166
|
-
const once = signal("order-placed", {
|
|
161
|
+
const once = signal.once("order-placed", { retries: 2 });
|
|
167
162
|
const bus = await openBus(driver, [once], setup?.() ?? {});
|
|
168
163
|
const got: string[] = [];
|
|
169
164
|
await bus.subscribe("order-placed", "worker-a", async () => {
|
|
@@ -179,7 +174,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
179
174
|
});
|
|
180
175
|
|
|
181
176
|
test("broadcast reaches every subscriber", async () => {
|
|
182
|
-
const bcast = signal("news"
|
|
177
|
+
const bcast = signal.broadcast("news");
|
|
183
178
|
const bus = await openBus(driver, [bcast], setup?.() ?? {});
|
|
184
179
|
const a: unknown[] = [];
|
|
185
180
|
const b: unknown[] = [];
|
|
@@ -198,7 +193,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
198
193
|
});
|
|
199
194
|
|
|
200
195
|
test("live is client-subscribable", async () => {
|
|
201
|
-
const live = signal("seat-feed", {
|
|
196
|
+
const live = signal.live("seat-feed", { optional: true });
|
|
202
197
|
const bus = await openBus(driver, [live], setup?.() ?? {});
|
|
203
198
|
const it = bus.live("seat-feed")[Symbol.asyncIterator]();
|
|
204
199
|
const pending = it.next();
|
|
@@ -211,11 +206,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
211
206
|
});
|
|
212
207
|
|
|
213
208
|
test("DLQ preserves typed failure reasons per attempt", async () => {
|
|
214
|
-
const once = signal("flaky", {
|
|
215
|
-
delivery: "once",
|
|
216
|
-
retries: 2,
|
|
217
|
-
deadLetter: true,
|
|
218
|
-
});
|
|
209
|
+
const once = signal.once("flaky", { retries: 2, deadLetter: true });
|
|
219
210
|
const bus = await openBus(driver, [once], setup?.() ?? {});
|
|
220
211
|
let attempts = 0;
|
|
221
212
|
await bus.subscribe("flaky", "c1", async () => {
|
|
@@ -241,7 +232,7 @@ for (const { label, driver, setup } of drivers) {
|
|
|
241
232
|
|
|
242
233
|
describe("postgres transactional emit (dual-write fix)", () => {
|
|
243
234
|
test("fx.emit enrols in the caller's transaction via runtime", async () => {
|
|
244
|
-
const orderPlaced = signal("order-placed"
|
|
235
|
+
const orderPlaced = signal.once("order-placed");
|
|
245
236
|
const runtime = createSignalRuntime({
|
|
246
237
|
driver: postgresSignalDriver,
|
|
247
238
|
sql: createPostgresSignalFake(),
|
|
@@ -268,7 +259,7 @@ describe("postgres transactional emit (dual-write fix)", () => {
|
|
|
268
259
|
|
|
269
260
|
test("LISTEN/NOTIFY wakes drain after commit", async () => {
|
|
270
261
|
const sql = createPostgresSignalFake();
|
|
271
|
-
const once = signal("order-placed"
|
|
262
|
+
const once = signal.once("order-placed");
|
|
272
263
|
const bus = await openBus(postgresSignalDriver, [once], { sql });
|
|
273
264
|
const seen: unknown[] = [];
|
|
274
265
|
await bus.subscribe("order-placed", "c1", async (m) => {
|
|
@@ -291,7 +282,7 @@ describe("postgres transactional emit (dual-write fix)", () => {
|
|
|
291
282
|
describe("redis / nats outbox relay", () => {
|
|
292
283
|
test("redis relay publishes after commit, not on rollback", async () => {
|
|
293
284
|
const redis = createSignalRedisFake();
|
|
294
|
-
const once = signal("order-placed", {
|
|
285
|
+
const once = signal.once("order-placed", { optional: true });
|
|
295
286
|
const bus = await openBus(redisSignalDriver, [once], { redis });
|
|
296
287
|
|
|
297
288
|
const tx = await bus.begin();
|
|
@@ -309,7 +300,7 @@ describe("redis / nats outbox relay", () => {
|
|
|
309
300
|
|
|
310
301
|
test("nats relay publishes after commit, not on rollback", async () => {
|
|
311
302
|
const nats = createSignalNatsFake();
|
|
312
|
-
const once = signal("order-placed", {
|
|
303
|
+
const once = signal.once("order-placed", { optional: true });
|
|
313
304
|
const bus = await openBus(natsSignalDriver, [once], { nats });
|
|
314
305
|
|
|
315
306
|
const tx = await bus.begin();
|
|
@@ -338,11 +329,7 @@ describe("chaos — kill process mid-transaction", () => {
|
|
|
338
329
|
expect(code).toBe(99);
|
|
339
330
|
|
|
340
331
|
// Recover: new process opens the same durable path.
|
|
341
|
-
const orderPlaced = signal("order-placed", {
|
|
342
|
-
delivery: "once",
|
|
343
|
-
retries: 3,
|
|
344
|
-
deadLetter: true,
|
|
345
|
-
});
|
|
332
|
+
const orderPlaced = signal.once("order-placed", { retries: 3, deadLetter: true });
|
|
346
333
|
const runtime = createSignalRuntime({
|
|
347
334
|
driver: memorySignalDriver,
|
|
348
335
|
durablePath,
|
|
@@ -376,7 +363,7 @@ describe("chaos — kill process mid-transaction", () => {
|
|
|
376
363
|
import { memorySignalDriver } from ${JSON.stringify(join(import.meta.dir, "../drivers/signal-memory.ts"))};
|
|
377
364
|
import { signal } from ${JSON.stringify(join(import.meta.dir, "signal/declare.ts"))};
|
|
378
365
|
import { createSignalRuntime } from ${JSON.stringify(join(import.meta.dir, "signal/runtime.ts"))};
|
|
379
|
-
const orderPlaced = signal("order-placed", {
|
|
366
|
+
const orderPlaced = signal.once("order-placed", { retries: 3, deadLetter: true, optional: true });
|
|
380
367
|
const runtime = createSignalRuntime({ driver: memorySignalDriver, durablePath: ${JSON.stringify(durablePath)} });
|
|
381
368
|
runtime.register(orderPlaced);
|
|
382
369
|
const bus = await runtime.start();
|
|
@@ -396,11 +383,7 @@ describe("chaos — kill process mid-transaction", () => {
|
|
|
396
383
|
child.kill(9);
|
|
397
384
|
await child.exited;
|
|
398
385
|
|
|
399
|
-
const orderPlaced = signal("order-placed", {
|
|
400
|
-
delivery: "once",
|
|
401
|
-
retries: 3,
|
|
402
|
-
deadLetter: true,
|
|
403
|
-
});
|
|
386
|
+
const orderPlaced = signal.once("order-placed", { retries: 3, deadLetter: true });
|
|
404
387
|
const runtime = createSignalRuntime({
|
|
405
388
|
driver: memorySignalDriver,
|
|
406
389
|
durablePath,
|
|
@@ -434,11 +417,7 @@ describe("chaos — kill process mid-transaction", () => {
|
|
|
434
417
|
});
|
|
435
418
|
expect(await child.exited).toBe(0);
|
|
436
419
|
|
|
437
|
-
const orderPlaced = signal("order-placed", {
|
|
438
|
-
delivery: "once",
|
|
439
|
-
retries: 3,
|
|
440
|
-
deadLetter: true,
|
|
441
|
-
});
|
|
420
|
+
const orderPlaced = signal.once("order-placed", { retries: 3, deadLetter: true });
|
|
442
421
|
const runtime = createSignalRuntime({
|
|
443
422
|
driver: memorySignalDriver,
|
|
444
423
|
durablePath,
|
|
@@ -501,11 +480,7 @@ describe("chaos — kill process mid-transaction", () => {
|
|
|
501
480
|
await Bun.sleep(leaseMs + 40);
|
|
502
481
|
|
|
503
482
|
let clock = Date.now() + leaseMs + 1_000;
|
|
504
|
-
const orderPlaced = signal("order-placed", {
|
|
505
|
-
delivery: "once",
|
|
506
|
-
retries: 3,
|
|
507
|
-
deadLetter: true,
|
|
508
|
-
});
|
|
483
|
+
const orderPlaced = signal.once("order-placed", { retries: 3, deadLetter: true });
|
|
509
484
|
const runtime = createSignalRuntime({
|
|
510
485
|
driver: memorySignalDriver,
|
|
511
486
|
durablePath,
|
package/src/elements/signal.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Signal element — data in motion.
|
|
3
3
|
*
|
|
4
|
-
* Delivery physics: `once` · `broadcast` · `live
|
|
4
|
+
* Delivery physics via helpers: `signal.once` · `signal.broadcast` · `signal.live`.
|
|
5
5
|
* Drivers: `memory` · `postgres` (default / transactional) · `redis` · `nats`.
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export { signal } from "./signal/declare.ts";
|
|
10
|
-
export type {
|
|
10
|
+
export type {
|
|
11
|
+
SignalDecl,
|
|
12
|
+
SignalLiveOptions,
|
|
13
|
+
SignalNamespace,
|
|
14
|
+
SignalOptions,
|
|
15
|
+
SignalRetention,
|
|
16
|
+
SignalSharedOptions,
|
|
17
|
+
} from "./signal/declare.ts";
|
|
11
18
|
export type { DeadLetter, SignalFailureReason } from "../drivers/signal-types.ts";
|
|
12
19
|
|
|
13
20
|
export { createSignalRuntime } from "./signal/runtime.ts";
|
|
@@ -79,9 +79,11 @@ describe("manual live flow (.live(table) + liveQuery) — real boot", () => {
|
|
|
79
79
|
const db = store.sql("app", { schema: { tasks, activity } });
|
|
80
80
|
|
|
81
81
|
const tasksLive = on(
|
|
82
|
-
http
|
|
82
|
+
http
|
|
83
|
+
.get("/tasks/live", { in: { unknown: true } })
|
|
84
|
+
.public()
|
|
85
|
+
.live(tasks),
|
|
83
86
|
flow("tasks.live", {
|
|
84
|
-
in: { unknown: true },
|
|
85
87
|
effects: { reads: ["sql:app"] },
|
|
86
88
|
do: async (input, fx) =>
|
|
87
89
|
liveQuery(fx, tasks, input, {
|
|
@@ -93,9 +95,12 @@ describe("manual live flow (.live(table) + liveQuery) — real boot", () => {
|
|
|
93
95
|
);
|
|
94
96
|
// A plain flow writing to BOTH tables — the keel `writeActivity` shape.
|
|
95
97
|
const createTask = on(
|
|
96
|
-
http
|
|
98
|
+
http
|
|
99
|
+
.post("/tasks", {
|
|
100
|
+
in: { title: { type: "string" }, status: { type: "string" } },
|
|
101
|
+
})
|
|
102
|
+
.public(),
|
|
97
103
|
flow("tasks.create", {
|
|
98
|
-
in: { title: { type: "string" }, status: { type: "string" } },
|
|
99
104
|
effects: { writes: ["sql:app"] },
|
|
100
105
|
do: async (input, fx) => {
|
|
101
106
|
const s = fx.store(db);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prepareInsertRow / prepareUpdateRow — temporal bind coercion.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { timestamp, text, pgTable, integer } from "drizzle-orm/pg-core";
|
|
7
|
+
import { pgliteDriver } from "../../drivers/pglite.ts";
|
|
8
|
+
import type { SqlConnection } from "../../drivers/types.ts";
|
|
9
|
+
import { field, store } from "../store.ts";
|
|
10
|
+
import { createSqlStoreHandle, type SqlStoreHandle } from "./sql-session.ts";
|
|
11
|
+
import { coerceTemporalBindValue, prepareInsertRow, prepareUpdateRow } from "./table.ts";
|
|
12
|
+
|
|
13
|
+
describe("coerceTemporalBindValue", () => {
|
|
14
|
+
test("epoch-ms number → Date for TIMESTAMP and DATE", () => {
|
|
15
|
+
const at = coerceTemporalBindValue("TIMESTAMP", 1_700_000_000_000);
|
|
16
|
+
expect(at).toBeInstanceOf(Date);
|
|
17
|
+
expect((at as Date).getTime()).toBe(1_700_000_000_000);
|
|
18
|
+
|
|
19
|
+
const day = coerceTemporalBindValue("DATE", 0);
|
|
20
|
+
expect(day).toBeInstanceOf(Date);
|
|
21
|
+
expect((day as Date).getTime()).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("leaves non-temporal and non-number values alone", () => {
|
|
25
|
+
expect(coerceTemporalBindValue("INTEGER", 42)).toBe(42);
|
|
26
|
+
expect(coerceTemporalBindValue("TIMESTAMP", "2024-01-01T00:00:00.000Z")).toBe(
|
|
27
|
+
"2024-01-01T00:00:00.000Z",
|
|
28
|
+
);
|
|
29
|
+
const d = new Date(5);
|
|
30
|
+
expect(coerceTemporalBindValue("TIMESTAMP", d)).toBe(d);
|
|
31
|
+
expect(coerceTemporalBindValue("TIMESTAMP", null)).toBe(null);
|
|
32
|
+
expect(coerceTemporalBindValue("TIMESTAMP", Number.NaN)).toBe(Number.NaN);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("prepareInsertRow / prepareUpdateRow — timestamp columns", () => {
|
|
37
|
+
const notes = store.schema.table("notes", {
|
|
38
|
+
id: field.id().primaryKey(),
|
|
39
|
+
createdAt: field.timestamp().notNull(),
|
|
40
|
+
archivedAt: field.timestamp(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("abstract schema: insert coerces epoch-ms on timestamp columns", () => {
|
|
44
|
+
const prepared = prepareInsertRow(notes, {
|
|
45
|
+
id: "welcome",
|
|
46
|
+
createdAt: 1,
|
|
47
|
+
archivedAt: null,
|
|
48
|
+
});
|
|
49
|
+
expect(prepared.created_at).toBeInstanceOf(Date);
|
|
50
|
+
expect((prepared.created_at as Date).getTime()).toBe(1);
|
|
51
|
+
expect(prepared.archived_at).toBe(null);
|
|
52
|
+
expect(prepared.id).toBe("welcome");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("abstract schema: update coerces epoch-ms on timestamp columns", () => {
|
|
56
|
+
const prepared = prepareUpdateRow(notes, { archivedAt: 99 });
|
|
57
|
+
expect(prepared.archived_at).toBeInstanceOf(Date);
|
|
58
|
+
expect((prepared.archived_at as Date).getTime()).toBe(99);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("drizzle pgTable timestamp: insert coerces epoch-ms", () => {
|
|
62
|
+
const posts = pgTable("posts", {
|
|
63
|
+
id: text("id").primaryKey(),
|
|
64
|
+
createdAt: timestamp("created_at", { mode: "date" }).notNull(),
|
|
65
|
+
hits: integer("hits").notNull(),
|
|
66
|
+
});
|
|
67
|
+
const prepared = prepareInsertRow(posts, {
|
|
68
|
+
id: "p1",
|
|
69
|
+
createdAt: 42,
|
|
70
|
+
hits: 7,
|
|
71
|
+
});
|
|
72
|
+
expect(prepared.created_at).toBeInstanceOf(Date);
|
|
73
|
+
expect((prepared.created_at as Date).getTime()).toBe(42);
|
|
74
|
+
expect(prepared.hits).toBe(7);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("SqlStoreHandle upsert — epoch-ms into timestamp (Postgres)", () => {
|
|
79
|
+
const notesTs = pgTable("notes_ts", {
|
|
80
|
+
id: text("id").primaryKey(),
|
|
81
|
+
title: text("title").notNull(),
|
|
82
|
+
createdAt: timestamp("created_at", { mode: "date" }).notNull(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
let conn: SqlConnection;
|
|
86
|
+
let handle: SqlStoreHandle;
|
|
87
|
+
|
|
88
|
+
beforeAll(async () => {
|
|
89
|
+
conn = await pgliteDriver.connect({
|
|
90
|
+
url: "memory://prepare-row-ts",
|
|
91
|
+
role: "primary",
|
|
92
|
+
});
|
|
93
|
+
handle = createSqlStoreHandle("sql:app", {
|
|
94
|
+
connection: conn,
|
|
95
|
+
classifications: new Map(),
|
|
96
|
+
routedRole: "primary",
|
|
97
|
+
domainDdl: "ensure",
|
|
98
|
+
});
|
|
99
|
+
await handle.insert(notesTs).values({ id: "_warmup", title: "x", createdAt: new Date(0) });
|
|
100
|
+
await conn.exec(`TRUNCATE "notes_ts" RESTART IDENTITY CASCADE`);
|
|
101
|
+
}, 15_000);
|
|
102
|
+
|
|
103
|
+
afterAll(async () => {
|
|
104
|
+
await conn.close();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
beforeEach(async () => {
|
|
108
|
+
await conn.exec(`TRUNCATE "notes_ts" RESTART IDENTITY CASCADE`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("upsert accepts fx.clock.now()-style epoch-ms without Postgres type error", async () => {
|
|
112
|
+
const first = await handle.upsert(
|
|
113
|
+
notesTs,
|
|
114
|
+
{ id: "welcome" },
|
|
115
|
+
{ id: "welcome", title: "Welcome", createdAt: 1 },
|
|
116
|
+
);
|
|
117
|
+
expect(first.status).toBe("upserted");
|
|
118
|
+
|
|
119
|
+
const rows = await handle.select().from(notesTs);
|
|
120
|
+
expect(rows).toHaveLength(1);
|
|
121
|
+
expect(Number(rows[0]!.createdAt)).toBe(1);
|
|
122
|
+
});
|
|
123
|
+
});
|