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
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { flow, type AnyFlowDef, type FlowDef, type FlowErrorMap } from "../../kernel/flow.ts";
|
|
16
|
+
import { applyBoundaryContract, stampBoundaryContract } from "../../kernel/boundary-contract.ts";
|
|
16
17
|
import { fail } from "../../kernel/errors.ts";
|
|
17
18
|
import type { Fx } from "../../kernel/fx.ts";
|
|
18
19
|
import {
|
|
@@ -260,11 +261,9 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
const listFlow = flow("list", {
|
|
263
|
-
...(breaking ? { breaking: true as const } : {}),
|
|
264
264
|
// Loose record so the HTTP AoT infers `query` and lets every list URL
|
|
265
265
|
// key through; real validation happens in parseListQuery (PostgREST
|
|
266
266
|
// grammar).
|
|
267
|
-
in: z.record(z.string(), z.unknown()) as never,
|
|
268
267
|
effects: { reads: [db.ref] },
|
|
269
268
|
do: async (input, fx) => {
|
|
270
269
|
const result = await runList(input, fx);
|
|
@@ -272,10 +271,15 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
272
271
|
return fx.json.with(result);
|
|
273
272
|
},
|
|
274
273
|
});
|
|
274
|
+
applyBoundaryContract(
|
|
275
|
+
listFlow,
|
|
276
|
+
stampBoundaryContract({
|
|
277
|
+
in: z.record(z.string(), z.unknown()) as never,
|
|
278
|
+
...(breaking ? { breaking: true } : {}),
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
275
281
|
|
|
276
282
|
const createFlow = flow("create", {
|
|
277
|
-
...(breaking ? { breaking: true as const } : {}),
|
|
278
|
-
in: options.in as never,
|
|
279
283
|
effects: { writes: [db.ref] },
|
|
280
284
|
do: async (input, fx) => {
|
|
281
285
|
const store = fx.store(db) as {
|
|
@@ -288,10 +292,16 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
288
292
|
return fx.json.create(row);
|
|
289
293
|
},
|
|
290
294
|
});
|
|
295
|
+
applyBoundaryContract(
|
|
296
|
+
createFlow,
|
|
297
|
+
stampBoundaryContract({
|
|
298
|
+
in: options.in as never,
|
|
299
|
+
out: options.out as never,
|
|
300
|
+
...(breaking ? { breaking: true } : {}),
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
291
303
|
|
|
292
304
|
const getFlow = flow("get", {
|
|
293
|
-
...(breaking ? { breaking: true as const } : {}),
|
|
294
|
-
errors,
|
|
295
305
|
effects: { reads: [db.ref] },
|
|
296
306
|
do: async (input, fx) => {
|
|
297
307
|
const id = (input as Record<string, unknown>)[idKey];
|
|
@@ -303,6 +313,14 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
303
313
|
return row;
|
|
304
314
|
},
|
|
305
315
|
});
|
|
316
|
+
applyBoundaryContract(
|
|
317
|
+
getFlow,
|
|
318
|
+
stampBoundaryContract({
|
|
319
|
+
out: options.out as never,
|
|
320
|
+
errors,
|
|
321
|
+
...(breaking ? { breaking: true } : {}),
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
306
324
|
|
|
307
325
|
// Wire update body is `{ id, ...patch }`. The patch schema (`update`,
|
|
308
326
|
// default `in`) describes the mutable fields; the path id rides along and
|
|
@@ -315,9 +333,6 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
315
333
|
? patchSchema.extend({ [idKey]: z.string() })
|
|
316
334
|
: patchSchema;
|
|
317
335
|
const updateFlow = flow("update", {
|
|
318
|
-
...(breaking ? { breaking: true as const } : {}),
|
|
319
|
-
in: updateIn as never,
|
|
320
|
-
errors,
|
|
321
336
|
effects: { reads: [db.ref], writes: [db.ref] },
|
|
322
337
|
do: async (input, fx) => {
|
|
323
338
|
const { [idKey]: id, ...patch } = input as Record<string, unknown>;
|
|
@@ -338,10 +353,17 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
338
353
|
return row;
|
|
339
354
|
},
|
|
340
355
|
});
|
|
356
|
+
applyBoundaryContract(
|
|
357
|
+
updateFlow,
|
|
358
|
+
stampBoundaryContract({
|
|
359
|
+
in: updateIn as never,
|
|
360
|
+
out: options.out as never,
|
|
361
|
+
errors,
|
|
362
|
+
...(breaking ? { breaking: true } : {}),
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
341
365
|
|
|
342
366
|
const removeFlow = flow("remove", {
|
|
343
|
-
...(breaking ? { breaking: true as const } : {}),
|
|
344
|
-
errors,
|
|
345
367
|
effects: { writes: [db.ref] },
|
|
346
368
|
do: async (input, fx) => {
|
|
347
369
|
const id = (input as Record<string, unknown>)[idKey];
|
|
@@ -353,6 +375,13 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
353
375
|
return fx.json.empty();
|
|
354
376
|
},
|
|
355
377
|
});
|
|
378
|
+
applyBoundaryContract(
|
|
379
|
+
removeFlow,
|
|
380
|
+
stampBoundaryContract({
|
|
381
|
+
errors,
|
|
382
|
+
...(breaking ? { breaking: true } : {}),
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
356
385
|
|
|
357
386
|
const defs: ResourceFlowDefs = {
|
|
358
387
|
list: listFlow as FlowDef<any, any, any>,
|
|
@@ -389,7 +418,6 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
389
418
|
if (kind !== undefined) columnKinds[c.key] = kind;
|
|
390
419
|
}
|
|
391
420
|
const liveFlow = flow(`_live_${tableName}`, {
|
|
392
|
-
...(options.out !== undefined ? { out: options.out } : {}),
|
|
393
421
|
effects: { reads: [db.ref, `signal:${signalName}`] },
|
|
394
422
|
do: async (input: unknown, fx: Fx) => {
|
|
395
423
|
const bridge = realtimeBridgeRuntime();
|
|
@@ -428,6 +456,9 @@ export function resource(db: SqlStoreDecl, table: unknown, options: ResourceOpti
|
|
|
428
456
|
return fx.json.stream(stream.chunks);
|
|
429
457
|
},
|
|
430
458
|
}) as unknown as AnyFlowDef;
|
|
459
|
+
if (options.out !== undefined) {
|
|
460
|
+
applyBoundaryContract(liveFlow, stampBoundaryContract({ out: options.out as never }));
|
|
461
|
+
}
|
|
431
462
|
return { signal: signalName, flow: liveFlow };
|
|
432
463
|
};
|
|
433
464
|
|
|
@@ -167,6 +167,8 @@ export interface SchemaColumnDecl<
|
|
|
167
167
|
readonly typeOptions?: FieldTypeOptions;
|
|
168
168
|
/** Optional human description for Console / docs (falls back to the JS key). */
|
|
169
169
|
readonly description?: string;
|
|
170
|
+
/** Hybrid search (BM25 ± LSH) when `.searchable()` / `.embed()` was chained. */
|
|
171
|
+
readonly search?: ColumnSearchDecl;
|
|
170
172
|
/**
|
|
171
173
|
* Drizzle `SQLWrapper` structural match — never invoked at runtime.
|
|
172
174
|
*
|
|
@@ -215,6 +217,45 @@ export interface SchemaLiveDecl {
|
|
|
215
217
|
readonly live: false;
|
|
216
218
|
}
|
|
217
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Options for {@link FieldBuilder.searchable}.
|
|
222
|
+
*
|
|
223
|
+
* `weight` is a BM25F field weight applied to term frequency **before**
|
|
224
|
+
* saturation / IDF — pure SQL math, zero AI cost. Default `1`.
|
|
225
|
+
*/
|
|
226
|
+
export interface SearchableOptions {
|
|
227
|
+
readonly weight?: number;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Options for {@link FieldBuilder.embed} — separate from {@link SearchableOptions}
|
|
232
|
+
* because embedding is an async, costed AI pipeline.
|
|
233
|
+
*
|
|
234
|
+
* `model` / `dims` may be omitted when the project supplies
|
|
235
|
+
* `oke({ store: { search: { embed: { model, dims } } } })`. Per-field values
|
|
236
|
+
* override the project default.
|
|
237
|
+
*/
|
|
238
|
+
export interface EmbedFieldOptions {
|
|
239
|
+
/** Embedding model handle or name (inherits project default when omitted). */
|
|
240
|
+
readonly model?: { readonly name: string } | string;
|
|
241
|
+
/**
|
|
242
|
+
* Vector dimensionality (inherits project default when omitted).
|
|
243
|
+
* Mismatch with a computed embedding fails loud at write time.
|
|
244
|
+
*/
|
|
245
|
+
readonly dims?: number;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Column-level hybrid search declaration stamped on {@link SchemaColumnDecl}. */
|
|
249
|
+
export interface ColumnSearchDecl {
|
|
250
|
+
readonly searchable: true;
|
|
251
|
+
readonly weight: number;
|
|
252
|
+
readonly embed?: {
|
|
253
|
+
readonly model?: string;
|
|
254
|
+
/** Present after extract resolution; may be deferred to project default at declare time. */
|
|
255
|
+
readonly dims?: number;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
218
259
|
/** Third-arg extra for {@link schemaTable}. */
|
|
219
260
|
export type SchemaTableExtra =
|
|
220
261
|
| SchemaPolicyDecl
|
|
@@ -299,6 +340,23 @@ export interface FieldBuilder<TData = unknown, TNotNull extends boolean = false>
|
|
|
299
340
|
pii(): FieldBuilder<TData, TNotNull>;
|
|
300
341
|
sensitive(): FieldBuilder<TData, TNotNull>;
|
|
301
342
|
retain(duration: string): FieldBuilder<TData, TNotNull>;
|
|
343
|
+
/**
|
|
344
|
+
* Participate in built-in BM25 full-text ranking on the primary SQL table.
|
|
345
|
+
* Zero AI dependency. Optional {@link SearchableOptions.weight} is BM25F
|
|
346
|
+
* field weight (default `1`).
|
|
347
|
+
*
|
|
348
|
+
* Chain `.embed()` separately when semantic LSH is also required.
|
|
349
|
+
*/
|
|
350
|
+
searchable(options?: SearchableOptions): FieldBuilder<TData, TNotNull>;
|
|
351
|
+
/**
|
|
352
|
+
* Opt into async embedding + LSH for this searchable field.
|
|
353
|
+
* Requires a prior `.searchable()` and a configured `ai` element.
|
|
354
|
+
* Never a boolean flag inside searchable options — cost/mechanism differ.
|
|
355
|
+
*
|
|
356
|
+
* Omit `model` / `dims` to inherit `oke({ store: { search: { embed } } })`.
|
|
357
|
+
* Pass either to override the project default for this field only.
|
|
358
|
+
*/
|
|
359
|
+
embed(options?: EmbedFieldOptions): FieldBuilder<TData, TNotNull>;
|
|
302
360
|
/** Override snake_case SQL name. */
|
|
303
361
|
as(sqlName: string): FieldBuilder<TData, TNotNull>;
|
|
304
362
|
/** Optional human description for Console / docs (falls back to the JS key). */
|
|
@@ -342,8 +400,11 @@ interface FieldState {
|
|
|
342
400
|
readonly description?: string;
|
|
343
401
|
readonly references?: ColumnReference;
|
|
344
402
|
readonly typeOptions?: FieldTypeOptions;
|
|
403
|
+
readonly search?: ColumnSearchDecl;
|
|
345
404
|
}
|
|
346
405
|
|
|
406
|
+
const TEXT_SEARCHABLE_TYPES = new Set<FieldSqlType>(["text", "varchar", "char"]);
|
|
407
|
+
|
|
347
408
|
function camelToSnake(key: string): string {
|
|
348
409
|
return key
|
|
349
410
|
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
@@ -428,6 +489,61 @@ function createBuilder<TData, TNotNull extends boolean>(
|
|
|
428
489
|
next<TNotNull>({
|
|
429
490
|
classification: mergeClassification(state.classification, { retain: duration }),
|
|
430
491
|
}),
|
|
492
|
+
searchable: (options) => {
|
|
493
|
+
if (!TEXT_SEARCHABLE_TYPES.has(state.sqlType)) {
|
|
494
|
+
throw new Error(
|
|
495
|
+
`field.${state.sqlType}().searchable() is only valid on text / varchar / char columns`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
const weight = options?.weight ?? 1;
|
|
499
|
+
if (!(typeof weight === "number" && Number.isFinite(weight) && weight > 0)) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`searchable({ weight }) must be a finite number > 0 (got ${String(weight)})`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
return next<TNotNull>({
|
|
505
|
+
search: {
|
|
506
|
+
searchable: true,
|
|
507
|
+
weight,
|
|
508
|
+
...(state.search?.embed ? { embed: state.search.embed } : {}),
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
embed: (options) => {
|
|
513
|
+
if (!state.search?.searchable) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`.embed() requires a prior .searchable() on the same field — weight is free SQL math; embed is an async AI pipeline`,
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
if (!TEXT_SEARCHABLE_TYPES.has(state.sqlType)) {
|
|
519
|
+
throw new Error(
|
|
520
|
+
`field.${state.sqlType}().embed() is only valid on text / varchar / char columns`,
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
const dims = options?.dims;
|
|
524
|
+
if (dims !== undefined && !(typeof dims === "number" && Number.isInteger(dims) && dims > 0)) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
`.embed({ dims }) requires a positive integer when set (got ${String(dims)})`,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const modelRaw = options?.model;
|
|
530
|
+
const model =
|
|
531
|
+
typeof modelRaw === "string"
|
|
532
|
+
? modelRaw
|
|
533
|
+
: modelRaw && typeof modelRaw === "object" && "name" in modelRaw
|
|
534
|
+
? String(modelRaw.name)
|
|
535
|
+
: undefined;
|
|
536
|
+
return next<TNotNull>({
|
|
537
|
+
search: {
|
|
538
|
+
searchable: true,
|
|
539
|
+
weight: state.search.weight,
|
|
540
|
+
embed: {
|
|
541
|
+
...(dims !== undefined ? { dims } : {}),
|
|
542
|
+
...(model !== undefined ? { model } : {}),
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
},
|
|
431
547
|
as: (sqlName) => next<TNotNull>({ sqlName }),
|
|
432
548
|
describe: (description) => next<TNotNull>({ description }),
|
|
433
549
|
type: () => createBuilder<unknown, TNotNull>(state) as FieldBuilder<never, TNotNull> as never,
|
|
@@ -450,6 +566,7 @@ function createBuilder<TData, TNotNull extends boolean>(
|
|
|
450
566
|
...(state.defaultFnKind ? { defaultFnKind: state.defaultFnKind } : {}),
|
|
451
567
|
...(state.classification ? { classification: state.classification } : {}),
|
|
452
568
|
...(state.description !== undefined ? { description: state.description } : {}),
|
|
569
|
+
...(state.search ? { search: state.search } : {}),
|
|
453
570
|
...(state.references ? { references: state.references } : {}),
|
|
454
571
|
...(state.typeOptions !== undefined && Object.keys(state.typeOptions).length > 0
|
|
455
572
|
? { typeOptions: state.typeOptions }
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `oke db search-backfill` — explicit, resumable corpus + embedding backfill.
|
|
3
|
+
* Never runs as a side effect of `oke db push`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SqlConnection } from "../../drivers/types.ts";
|
|
7
|
+
import type { DeclaredColumn, Manifest } from "../../manifest/types.ts";
|
|
8
|
+
import { SEARCH_LOW_CORPUS_WARN_N } from "./search-errors.ts";
|
|
9
|
+
import {
|
|
10
|
+
ensureHyperplaneInserts,
|
|
11
|
+
OKE_SEARCH_DF,
|
|
12
|
+
OKE_SEARCH_STATS,
|
|
13
|
+
OKE_TSV_COL,
|
|
14
|
+
searchDdlForTable,
|
|
15
|
+
} from "./search-ddl.ts";
|
|
16
|
+
import { tokenize } from "./search-bm25.ts";
|
|
17
|
+
import { embColumn, lshColumn, OKE_SEARCH_PLANES } from "./search-ddl.ts";
|
|
18
|
+
import { deserializePlanes, lshBucket, lshBucketToSql } from "./search-lsh.ts";
|
|
19
|
+
import { LSH_DEFAULT_K, SearchConfigError } from "./search-errors.ts";
|
|
20
|
+
|
|
21
|
+
export interface SearchBackfillOptions {
|
|
22
|
+
readonly table: string;
|
|
23
|
+
readonly batchSize?: number;
|
|
24
|
+
/** Sleep between embed batches (ms) — rate-limit pacing. */
|
|
25
|
+
readonly embedPauseMs?: number;
|
|
26
|
+
readonly embed?: (model: string, text: string, dims: number) => Promise<readonly number[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional abort — mid-backfill kill. Re-running the same command is safe
|
|
29
|
+
* (idempotent UPDATEs + DF/stats rebuild) and completes the remaining work.
|
|
30
|
+
*/
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SearchBackfillResult {
|
|
35
|
+
readonly table: string;
|
|
36
|
+
readonly rows: number;
|
|
37
|
+
readonly embedded: number;
|
|
38
|
+
readonly warnedLowCorpus: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Apply search DDL + rebuild stats/DF + optional embeddings for one table.
|
|
43
|
+
* Cursor-paginated; caller may wrap steps in Journal for resumability.
|
|
44
|
+
*
|
|
45
|
+
* @param conn - SQL connection
|
|
46
|
+
* @param manifest - Project manifest
|
|
47
|
+
* @param options - Table + batching
|
|
48
|
+
*/
|
|
49
|
+
export async function runSearchBackfill(
|
|
50
|
+
conn: SqlConnection,
|
|
51
|
+
manifest: Manifest,
|
|
52
|
+
options: SearchBackfillOptions,
|
|
53
|
+
): Promise<SearchBackfillResult> {
|
|
54
|
+
const tableMeta = findTable(manifest, options.table);
|
|
55
|
+
if (!tableMeta) {
|
|
56
|
+
throw new Error(`search-backfill: table "${options.table}" not found in Manifest`);
|
|
57
|
+
}
|
|
58
|
+
const { columns, pk } = tableMeta;
|
|
59
|
+
const batchSize = options.batchSize ?? 32;
|
|
60
|
+
const pauseMs = options.embedPauseMs ?? 100;
|
|
61
|
+
|
|
62
|
+
for (const stmt of searchDdlForTable(options.table, columns)) {
|
|
63
|
+
await conn.exec(stmt);
|
|
64
|
+
}
|
|
65
|
+
for (const ins of ensureHyperplaneInserts(options.table, columns)) {
|
|
66
|
+
await conn.exec(ins.sql, ins.params);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Rebuild DF + stats from full scan (pg_textsearch lesson: after data load).
|
|
70
|
+
await conn.exec(`DELETE FROM ${OKE_SEARCH_DF} WHERE table_name = ?`, [options.table]);
|
|
71
|
+
const df = new Map<string, number>();
|
|
72
|
+
let n = 0;
|
|
73
|
+
let totalLen = 0;
|
|
74
|
+
let cursor: string | null = null;
|
|
75
|
+
let embedded = 0;
|
|
76
|
+
|
|
77
|
+
const embedCols = Object.entries(columns)
|
|
78
|
+
.filter(([, c]) => c.embed)
|
|
79
|
+
.map(([key, c]) => ({
|
|
80
|
+
sqlName: c.sqlName ?? key,
|
|
81
|
+
dims: c.embed!.dims,
|
|
82
|
+
model: c.embed!.model ?? "default",
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
for (;;) {
|
|
86
|
+
if (options.signal?.aborted) {
|
|
87
|
+
throw new DOMException("search-backfill aborted", "AbortError");
|
|
88
|
+
}
|
|
89
|
+
const params: unknown[] = [];
|
|
90
|
+
let sql = `SELECT * FROM ${options.table}`;
|
|
91
|
+
if (cursor !== null) {
|
|
92
|
+
sql += ` WHERE ${pk} > ?`;
|
|
93
|
+
params.push(cursor);
|
|
94
|
+
}
|
|
95
|
+
sql += ` ORDER BY ${pk} ASC LIMIT ${batchSize}`;
|
|
96
|
+
const rows = await conn.query(sql, params);
|
|
97
|
+
if (rows.length === 0) break;
|
|
98
|
+
|
|
99
|
+
for (const row of rows) {
|
|
100
|
+
if (options.signal?.aborted) {
|
|
101
|
+
throw new DOMException("search-backfill aborted", "AbortError");
|
|
102
|
+
}
|
|
103
|
+
n += 1;
|
|
104
|
+
const seenTerms = new Set<string>();
|
|
105
|
+
let docLen = 0;
|
|
106
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
107
|
+
if (!col.searchable) continue;
|
|
108
|
+
const sqlName = col.sqlName ?? key;
|
|
109
|
+
const tokens = tokenize(String(row[sqlName] ?? ""));
|
|
110
|
+
docLen += tokens.length;
|
|
111
|
+
for (const t of new Set(tokens)) {
|
|
112
|
+
if (!seenTerms.has(t)) {
|
|
113
|
+
seenTerms.add(t);
|
|
114
|
+
df.set(t, (df.get(t) ?? 0) + 1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
totalLen += docLen;
|
|
119
|
+
|
|
120
|
+
if (embedCols.length > 0 && options.embed) {
|
|
121
|
+
const id = String(row[pk] ?? "");
|
|
122
|
+
for (const col of embedCols) {
|
|
123
|
+
const text = String(row[col.sqlName] ?? "");
|
|
124
|
+
const vector = await options.embed(col.model, text, col.dims);
|
|
125
|
+
if (vector.length !== col.dims) {
|
|
126
|
+
throw new SearchConfigError(
|
|
127
|
+
options.table,
|
|
128
|
+
col.sqlName,
|
|
129
|
+
`embedding length ${vector.length} !== declared dims ${col.dims}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const planesRows = await conn.query(
|
|
133
|
+
`SELECT k, planes FROM ${OKE_SEARCH_PLANES} WHERE table_name = ? AND column_name = ?`,
|
|
134
|
+
[options.table, col.sqlName],
|
|
135
|
+
);
|
|
136
|
+
const prow = planesRows[0];
|
|
137
|
+
if (!prow) {
|
|
138
|
+
throw new SearchConfigError(options.table, col.sqlName, "missing hyperplanes");
|
|
139
|
+
}
|
|
140
|
+
const k = Number(prow["k"] ?? LSH_DEFAULT_K);
|
|
141
|
+
const planes = deserializePlanes(Buffer.from(prow["planes"] as Buffer), k);
|
|
142
|
+
const bucket = lshBucket(vector, planes);
|
|
143
|
+
await conn.exec(
|
|
144
|
+
`UPDATE ${options.table} SET ${embColumn(col.sqlName)} = ?::real[], ${lshColumn(col.sqlName)} = ? WHERE ${pk} = ?`,
|
|
145
|
+
[`{${Array.from(vector).join(",")}}`, lshBucketToSql(bucket), id],
|
|
146
|
+
);
|
|
147
|
+
embedded += 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
cursor = String(row[pk] ?? cursor);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (embedCols.length > 0 && options.embed && pauseMs > 0) {
|
|
154
|
+
await new Promise((r) => setTimeout(r, pauseMs));
|
|
155
|
+
}
|
|
156
|
+
if (rows.length < batchSize) break;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const avgdl = n > 0 ? totalLen / n : 0;
|
|
160
|
+
await conn.exec(
|
|
161
|
+
`INSERT INTO ${OKE_SEARCH_STATS} (table_name, n, avgdl, updated_at)
|
|
162
|
+
VALUES (?, ?, ?, now())
|
|
163
|
+
ON CONFLICT (table_name) DO UPDATE SET n = EXCLUDED.n, avgdl = EXCLUDED.avgdl, updated_at = now()`,
|
|
164
|
+
[options.table, n, avgdl],
|
|
165
|
+
);
|
|
166
|
+
for (const [term, count] of df) {
|
|
167
|
+
await conn.exec(
|
|
168
|
+
`INSERT INTO ${OKE_SEARCH_DF} (table_name, term, df) VALUES (?, ?, ?)
|
|
169
|
+
ON CONFLICT (table_name, term) DO UPDATE SET df = EXCLUDED.df`,
|
|
170
|
+
[options.table, term, count],
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const warnedLowCorpus = n < SEARCH_LOW_CORPUS_WARN_N;
|
|
175
|
+
if (warnedLowCorpus) {
|
|
176
|
+
console.warn(
|
|
177
|
+
`[oke db search-backfill] warn: table "${options.table}" has only ${n} rows — IDF/BM25 corpus statistics are not meaningful yet (threshold ${SEARCH_LOW_CORPUS_WARN_N})`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Touch generated tsv column by no-op update when needed — GENERATED ALWAYS is maintained by PG.
|
|
182
|
+
void OKE_TSV_COL;
|
|
183
|
+
|
|
184
|
+
return { table: options.table, rows: n, embedded, warnedLowCorpus };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function findTable(
|
|
188
|
+
manifest: Manifest,
|
|
189
|
+
tableName: string,
|
|
190
|
+
): { columns: Record<string, DeclaredColumn>; pk: string } | undefined {
|
|
191
|
+
for (const store of Object.values(manifest.stores ?? {})) {
|
|
192
|
+
if (store.facet !== "sql" || !store.tables?.[tableName]) continue;
|
|
193
|
+
const table = store.tables[tableName]!;
|
|
194
|
+
const columns: Record<string, DeclaredColumn> = {};
|
|
195
|
+
let pk = "id";
|
|
196
|
+
for (const [key, col] of Object.entries(table.columns ?? {})) {
|
|
197
|
+
if (!col || typeof col !== "object") continue;
|
|
198
|
+
const c = col as DeclaredColumn;
|
|
199
|
+
columns[key] = c;
|
|
200
|
+
if (c.primaryKey) pk = c.sqlName ?? key;
|
|
201
|
+
}
|
|
202
|
+
return { columns, pk };
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot helper — register system-owned durable embed CDC consumers for every
|
|
3
|
+
* `.embed()` column in the Manifest. Writer flows stay free of `effects.embeds`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Manifest } from "../../manifest/types.ts";
|
|
7
|
+
import type { Binding } from "../../kernel/on.ts";
|
|
8
|
+
import { flow } from "../../kernel/flow.ts";
|
|
9
|
+
import {
|
|
10
|
+
applySearchEmbedCdc,
|
|
11
|
+
searchEmbedFlowName,
|
|
12
|
+
tablesNeedingSearchEmbed,
|
|
13
|
+
} from "./search-embed-flow.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Adopt `_oke_search_embed_<table>` durable CDC bindings for each embed-declared
|
|
17
|
+
* table. Idempotent on flow name — safe to call once at boot after Manifest is
|
|
18
|
+
* known. Prefer {@link adopt} with real CDC {@link Binding}s so
|
|
19
|
+
* `dispatchCdc` runs them (bare `app.adopt([flow])` only registers the flow).
|
|
20
|
+
*
|
|
21
|
+
* @param adopt - App binding adopter (`adoptBinding` inside `oke()`)
|
|
22
|
+
* @param manifest - Project manifest
|
|
23
|
+
*/
|
|
24
|
+
export function bindSearchEmbedFlows(
|
|
25
|
+
adopt: (binding: Binding) => void,
|
|
26
|
+
manifest: Manifest,
|
|
27
|
+
): readonly string[] {
|
|
28
|
+
const tables = tablesNeedingSearchEmbed(manifest);
|
|
29
|
+
const names: string[] = [];
|
|
30
|
+
for (const t of tables) {
|
|
31
|
+
const name = searchEmbedFlowName(t.table);
|
|
32
|
+
names.push(name);
|
|
33
|
+
const models = [...new Set(t.columns.map((c) => c.model ?? "default"))];
|
|
34
|
+
const sqlRef = `sql:${t.store}` as const;
|
|
35
|
+
const embedFlow = flow(name, {
|
|
36
|
+
plane: "operator",
|
|
37
|
+
durable: true,
|
|
38
|
+
effects: {
|
|
39
|
+
reads: [sqlRef],
|
|
40
|
+
writes: [sqlRef],
|
|
41
|
+
embeds: models,
|
|
42
|
+
},
|
|
43
|
+
do: async (payload: { before: unknown; after: unknown }, fx) => {
|
|
44
|
+
await fx.step(`embed:${t.table}`, async () => {
|
|
45
|
+
await applySearchEmbedCdc(
|
|
46
|
+
fx,
|
|
47
|
+
t.table,
|
|
48
|
+
t.pk,
|
|
49
|
+
t.columns,
|
|
50
|
+
{
|
|
51
|
+
before: (payload.before ?? null) as Record<string, unknown> | null,
|
|
52
|
+
after: (payload.after ?? null) as Record<string, unknown> | null,
|
|
53
|
+
},
|
|
54
|
+
sqlRef,
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const binding: Binding = {
|
|
60
|
+
trigger: { kind: "cdc", table: t.table, store: t.store },
|
|
61
|
+
flow: embedFlow,
|
|
62
|
+
};
|
|
63
|
+
adopt(binding);
|
|
64
|
+
}
|
|
65
|
+
return names;
|
|
66
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BM25F scoring — Robertson–Zaragoza with field weights applied to TF
|
|
3
|
+
* **before** saturation (not post-hoc score summing).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BM25_B, BM25_K1 } from "./search-errors.ts";
|
|
7
|
+
|
|
8
|
+
/** One field's contribution to a document. */
|
|
9
|
+
export interface Bm25FieldTerms {
|
|
10
|
+
readonly weight: number;
|
|
11
|
+
/** term → raw term frequency in this field */
|
|
12
|
+
readonly tf: ReadonlyMap<string, number>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* BM25F score for one document given query terms and corpus stats.
|
|
17
|
+
*
|
|
18
|
+
* @param queryTerms - Tokenized query (unique terms scored once)
|
|
19
|
+
* @param fields - Per-field TF maps with BM25F weights
|
|
20
|
+
* @param docLen - Total document length (sum of field token counts)
|
|
21
|
+
* @param avgdl - Corpus average document length
|
|
22
|
+
* @param N - Corpus size
|
|
23
|
+
* @param df - Document frequency per term
|
|
24
|
+
*/
|
|
25
|
+
export function bm25fScore(
|
|
26
|
+
queryTerms: readonly string[],
|
|
27
|
+
fields: readonly Bm25FieldTerms[],
|
|
28
|
+
docLen: number,
|
|
29
|
+
avgdl: number,
|
|
30
|
+
N: number,
|
|
31
|
+
df: ReadonlyMap<string, number>,
|
|
32
|
+
): number {
|
|
33
|
+
if (N <= 0 || avgdl <= 0) return 0;
|
|
34
|
+
let score = 0;
|
|
35
|
+
const seen = new Set<string>();
|
|
36
|
+
for (const term of queryTerms) {
|
|
37
|
+
if (seen.has(term)) continue;
|
|
38
|
+
seen.add(term);
|
|
39
|
+
let weightedTf = 0;
|
|
40
|
+
for (const field of fields) {
|
|
41
|
+
const tf = field.tf.get(term) ?? 0;
|
|
42
|
+
if (tf > 0) weightedTf += field.weight * tf;
|
|
43
|
+
}
|
|
44
|
+
if (weightedTf <= 0) continue;
|
|
45
|
+
const n = df.get(term) ?? 0;
|
|
46
|
+
const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
|
|
47
|
+
const denom = weightedTf + BM25_K1 * (1 - BM25_B + BM25_B * (docLen / avgdl));
|
|
48
|
+
score += idf * ((weightedTf * (BM25_K1 + 1)) / denom);
|
|
49
|
+
}
|
|
50
|
+
return score;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Simple whitespace + lower-case tokenizer for BM25 (English-oriented).
|
|
55
|
+
* Used for in-process scoring and tests; SQL path uses to_tsvector terms.
|
|
56
|
+
*
|
|
57
|
+
* @param text - Raw text
|
|
58
|
+
*/
|
|
59
|
+
export function tokenize(text: string): string[] {
|
|
60
|
+
return text
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.replace(/[^\p{L}\p{N}\s]+/gu, " ")
|
|
63
|
+
.split(/\s+/)
|
|
64
|
+
.filter((t) => t.length > 0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build a TF map from tokens.
|
|
69
|
+
*
|
|
70
|
+
* @param tokens - Token list
|
|
71
|
+
*/
|
|
72
|
+
export function termFrequencies(tokens: readonly string[]): Map<string, number> {
|
|
73
|
+
const tf = new Map<string, number>();
|
|
74
|
+
for (const t of tokens) {
|
|
75
|
+
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
return tf;
|
|
78
|
+
}
|