okengine 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/index.mdx +25 -6
- package/site/content/docs/ai/llms-txt.mdx +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- package/site/content/docs/ai/try-it.mdx +58 -0
- package/site/content/docs/client/auth.mdx +208 -0
- package/site/content/docs/client/calling.mdx +451 -0
- package/site/content/docs/client/index.mdx +209 -0
- package/site/content/docs/client/live.mdx +234 -0
- package/site/content/docs/client/meta.json +5 -0
- package/site/content/docs/client/react.mdx +249 -0
- package/site/content/docs/elements/ai/agents.mdx +232 -0
- package/site/content/docs/elements/ai/index.mdx +341 -0
- package/site/content/docs/elements/ai/mcp.mdx +275 -0
- package/site/content/docs/elements/ai/meta.json +5 -0
- package/site/content/docs/elements/ai/models.mdx +283 -0
- package/site/content/docs/elements/ai/prompts.mdx +256 -0
- package/site/content/docs/elements/channel/email.mdx +266 -0
- package/site/content/docs/elements/channel/index.mdx +344 -0
- package/site/content/docs/elements/channel/meta.json +5 -0
- package/site/content/docs/elements/channel/push.mdx +218 -0
- package/site/content/docs/elements/channel/receipts.mdx +206 -0
- package/site/content/docs/elements/channel/sms.mdx +264 -0
- package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
- package/site/content/docs/elements/clock/index.mdx +300 -0
- package/site/content/docs/elements/clock/meta.json +5 -0
- package/site/content/docs/elements/clock/schedules.mdx +508 -0
- package/site/content/docs/elements/clock/sleep.mdx +445 -0
- package/site/content/docs/elements/flow/consumers.mdx +707 -0
- package/site/content/docs/elements/flow/http.mdx +1080 -0
- package/site/content/docs/elements/flow/index.mdx +618 -0
- package/site/content/docs/elements/flow/meta.json +5 -0
- package/site/content/docs/elements/flow/routing.mdx +559 -0
- package/site/content/docs/elements/flow/workflows.mdx +640 -0
- package/site/content/docs/elements/gate/auth.mdx +371 -0
- package/site/content/docs/elements/gate/authorization.mdx +298 -0
- package/site/content/docs/elements/gate/index.mdx +418 -0
- package/site/content/docs/elements/gate/meta.json +5 -0
- package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
- package/site/content/docs/elements/gate/rls.mdx +303 -0
- package/site/content/docs/elements/gate/tenancy.mdx +372 -0
- package/site/content/docs/elements/index.mdx +52 -11
- package/site/content/docs/elements/signal/broadcast.mdx +517 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +590 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +596 -0
- package/site/content/docs/elements/store/files.mdx +659 -0
- package/site/content/docs/elements/store/index.mdx +310 -0
- package/site/content/docs/elements/store/kv.mdx +629 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +946 -0
- package/site/content/docs/elements/store/sql.mdx +937 -0
- package/site/content/docs/elements/vault/config.mdx +256 -0
- package/site/content/docs/elements/vault/index.mdx +356 -0
- package/site/content/docs/elements/vault/meta.json +5 -0
- package/site/content/docs/elements/vault/rotation.mdx +273 -0
- package/site/content/docs/elements/vault/secrets.mdx +285 -0
- package/site/content/docs/index.mdx +78 -30
- package/site/content/docs/meta.json +11 -8
- package/site/content/docs/plugins/cors.mdx +2 -1
- package/site/content/docs/plugins/csrf.mdx +4 -1
- package/site/content/docs/plugins/headers.mdx +2 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
- package/site/content/docs/plugins/magic-link.mdx +6 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
- package/site/content/docs/plugins/oauth.mdx +1 -1
- package/site/content/docs/plugins/otp.mdx +12 -1
- package/site/content/docs/plugins/passkey.mdx +39 -15
- package/site/content/docs/plugins/two-factor.mdx +91 -34
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +3 -3
- package/site/content/docs/recipes/index.mdx +18 -28
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +3 -3
- package/site/content/docs/recipes/meta.json +1 -4
- package/site/content/docs/recipes/nginx.mdx +2 -2
- package/site/content/docs/recipes/openrouter.mdx +283 -0
- package/site/content/docs/recipes/pgdog.mdx +3 -3
- package/site/content/docs/recipes/postgres.mdx +1 -2
- package/site/content/docs/recipes/rustfs.mdx +2 -2
- package/site/content/docs/recipes/traefik.mdx +3 -3
- package/site/content/docs/reference/cli.mdx +231 -0
- package/site/content/docs/reference/configuration.mdx +30 -10
- package/site/content/docs/reference/environment-variables.mdx +93 -20
- package/site/content/docs/reference/errors.mdx +132 -29
- package/site/content/docs/reference/fx.mdx +137 -73
- package/site/content/docs/reference/index.mdx +13 -21
- package/site/content/docs/reference/meta.json +4 -5
- package/site/content/docs/reference/okid.mdx +42 -17
- package/site/content/docs/reference/plugins.mdx +4 -3
- package/site/content/docs/reference/security.mdx +197 -0
- package/site/content/docs/understand/meta.json +5 -0
- package/site/content/docs/understand/the-anatomy.mdx +132 -0
- package/site/content/docs/understand/the-model.mdx +32 -0
- package/site/content/docs/understand/the-problem.mdx +74 -0
- package/site/content/docs/understand/the-vocabulary.mdx +26 -0
- package/src/auth/api-keys.ts +2 -1
- package/src/auth/bindings.ts +64 -16
- package/src/auth/gate-auth.test.ts +6 -1
- package/src/auth/identity.ts +152 -5
- package/src/auth/index.ts +29 -0
- package/src/auth/invites.ts +2 -1
- package/src/auth/method-context.ts +41 -0
- package/src/auth/oauth-as/crypto.ts +2 -1
- package/src/auth/oauth-as/stores.ts +2 -1
- package/src/auth/operator.ts +2 -1
- package/src/auth/sessions-jwt.test.ts +77 -0
- package/src/auth/sessions.ts +20 -4
- package/src/auth/tenants.ts +3 -2
- package/src/auth/two-factor-challenge.test.ts +61 -0
- package/src/auth/two-factor-challenge.ts +284 -0
- package/src/auth/verification.ts +5 -0
- package/src/bench/REPORT.md +49 -0
- package/src/bench/g03-signal-once.bench.ts +1 -1
- package/src/bench/g10-observability-contention.bench.ts +1 -1
- package/src/bench/g17-hybrid-search.bench.ts +572 -0
- package/src/bench/load-app.ts +2 -10
- package/src/cli/ai-setup/ai-setup.test.ts +331 -42
- package/src/cli/ai-setup/apply.ts +294 -52
- package/src/cli/ai-setup/catalog.ts +238 -1342
- package/src/cli/ai-setup/index.ts +14 -99
- package/src/cli/ai-setup/prompts.ts +60 -443
- package/src/cli/ask-seed.test.ts +96 -0
- package/src/cli/ask-seed.ts +82 -0
- package/src/cli/ask-vault-gaps.test.ts +84 -0
- package/src/cli/ask-vault-gaps.ts +146 -0
- package/src/cli/build.ts +2 -2
- package/src/cli/client-add.test.ts +26 -1
- package/src/cli/client-add.ts +106 -27
- package/src/cli/competitor-mention-removal.test.ts +1 -1
- package/src/cli/db-seed.ts +2 -0
- package/src/cli/db.ts +150 -11
- package/src/cli/dev.ts +119 -211
- package/src/cli/docker-clean.ts +2 -2
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/doctor-diff.ts +4 -2
- package/src/cli/doctor-pii.test.ts +1 -1
- package/src/cli/doctor.ts +14 -1
- package/src/cli/hero-meta.test.ts +4 -2
- package/src/cli/load-config.images.test.ts +8 -10
- package/src/cli/load-config.ts +1 -1
- package/src/cli/registry.ts +22 -10
- package/src/cli/replay.ts +3 -1
- package/src/cli/start.ts +1 -1
- package/src/cli/tui/keys.ts +2 -3
- package/src/client/auth/cookies.ts +74 -0
- package/src/client/auth/create-auth-client.ts +636 -0
- package/src/client/auth/denials.ts +99 -0
- package/src/client/auth/session.ts +283 -0
- package/src/client/auth.test.ts +251 -0
- package/src/client/auth.ts +39 -114
- package/src/client/create-with-session.ts +245 -0
- package/src/client/create.ts +51 -5
- package/src/client/index.ts +11 -1
- package/src/client/live.ts +11 -79
- package/src/client/notes-contract.test.ts +5 -10
- package/src/client/sse.ts +137 -0
- package/src/client/stream.ts +147 -0
- package/src/client/transport.test.ts +32 -0
- package/src/client/transport.ts +123 -26
- package/src/client/types.ts +118 -14
- package/src/client-react/index.ts +185 -24
- package/src/client-react/use-live-query.ts +13 -3
- package/src/compiler/aot.test.ts +7 -4
- package/src/compiler/effects-embed.test.ts +56 -0
- package/src/compiler/effects-fetch.test.ts +43 -0
- package/src/compiler/effects-infer.ts +58 -2
- package/src/compiler/extract.test.ts +258 -44
- package/src/compiler/extract.ts +566 -48
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
- package/src/compiler/fixtures/skyport.expected.json +4 -4
- package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
- package/src/compiler/response.ts +2 -2
- package/src/compiler/search-writer-isolation.test.ts +40 -0
- package/src/config/index.ts +11 -0
- package/src/console/server/app.ts +17 -23
- package/src/console/server/bind.ts +4 -0
- package/src/console/server/console.test.ts +2 -2
- package/src/console/server/flows-invoke.test.ts +50 -34
- package/src/console/server/flows.ts +436 -255
- package/src/console/server/runs-ingest.test.ts +6 -3
- package/src/console/server/serve.ts +1 -1
- package/src/console/server/signals.test.ts +1 -5
- package/src/console/server/signals.ts +8 -6
- package/src/console/server/state.ts +6 -1
- package/src/console/server/store.ts +1 -1
- package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
- package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
- package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
- package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
- package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
- package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
- package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
- package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
- package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
- package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
- package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
- package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
- package/src/console/ui-next/dist/index.html +6 -13
- package/src/console/ui-next/seed-invoke-host.ts +21 -18
- package/src/console/ui-next/src/client.ts +7 -2
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
- package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
- package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
- package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
- package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
- package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
- package/src/docker/ai-model-status.test.ts +1 -12
- package/src/docker/ai-model-status.ts +10 -38
- package/src/docker/compose-up.test.ts +100 -0
- package/src/docker/compose-up.ts +292 -0
- package/src/docker/compose.ts +4 -14
- package/src/docker/derive.ts +9 -34
- package/src/docker/docker.test.ts +16 -261
- package/src/docker/images-config.test.ts +13 -25
- package/src/docker/index.ts +1 -24
- package/src/docker/recipes/index.ts +0 -23
- package/src/docker/recipes/pgdog.ts +6 -4
- package/src/docker/stack-id.test.ts +2 -2
- package/src/docker/stack-id.ts +0 -1
- package/src/docker/types.ts +2 -2
- package/src/drivers/ai-anthropic.ts +10 -0
- package/src/drivers/ai-openai-compatible.ts +34 -7
- package/src/drivers/ai-providers.test.ts +0 -107
- package/src/drivers/ai-stream.test.ts +0 -34
- package/src/drivers/ai-types.ts +17 -8
- package/src/drivers/channel-fcm.ts +6 -1
- package/src/drivers/channel-msegat.ts +6 -1
- package/src/drivers/channel-resend.ts +5 -1
- package/src/drivers/channel-smtp.ts +5 -1
- package/src/drivers/channel-sndr.ts +10 -1
- package/src/drivers/channel-taqnyat-mail.ts +5 -1
- package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
- package/src/drivers/channel-taqnyat.ts +6 -1
- package/src/drivers/channel-types.ts +10 -0
- package/src/drivers/channel-unifonic.ts +6 -1
- package/src/drivers/channel-wa-cloud.ts +6 -1
- package/src/drivers/channel-webpush.ts +6 -1
- package/src/drivers/external.ts +33 -0
- package/src/drivers/index.ts +0 -10
- package/src/drivers/meilisearch.ts +6 -0
- package/src/drivers/oauth-types.ts +4 -0
- package/src/drivers/postgres.test.ts +74 -3
- package/src/drivers/postgres.ts +175 -6
- package/src/drivers/signal-types.ts +5 -5
- package/src/drivers/types.ts +7 -0
- package/src/drivers/vault-types.ts +5 -0
- package/src/elements/ai/declare.ts +17 -4
- package/src/elements/ai/eval.ts +1 -1
- package/src/elements/ai/mcp-http.ts +1 -1
- package/src/elements/ai/pii.ts +1 -1
- package/src/elements/ai/providers.test.ts +289 -0
- package/src/elements/ai/providers.ts +169 -0
- package/src/elements/ai/runtime.ts +68 -5
- package/src/elements/ai/schema.ts +2 -2
- package/src/elements/ai.test.ts +7 -7
- package/src/elements/ai.ts +17 -1
- package/src/elements/channel/runtime.ts +105 -12
- package/src/elements/clock/cron-fields.test.ts +144 -0
- package/src/elements/clock/cron-fields.ts +185 -0
- package/src/elements/clock/declare.ts +247 -3
- package/src/elements/clock.ts +21 -1
- package/src/elements/index.ts +15 -0
- package/src/elements/signal/chaos-child.ts +1 -2
- package/src/elements/signal/declare.ts +65 -37
- package/src/elements/signal/delivery-modes.test.ts +11 -31
- package/src/elements/signal/dry-run-replay.test.ts +1 -7
- package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
- package/src/elements/signal/key-ordering.test.ts +6 -26
- package/src/elements/signal/lease-reclaim.test.ts +2 -6
- package/src/elements/signal/optional-emit.test.ts +6 -9
- package/src/elements/signal/order-lifecycle.test.ts +4 -17
- package/src/elements/signal/orphan-messages.test.ts +4 -15
- package/src/elements/signal/reconcile.test.ts +2 -2
- package/src/elements/signal/runtime.ts +1 -1
- package/src/elements/signal/schema-emit.test.ts +4 -8
- package/src/elements/signal.test.ts +24 -49
- package/src/elements/signal.ts +9 -2
- package/src/elements/store/domain-ddl.test.ts +1 -1
- package/src/elements/store/live-http.test.ts +9 -4
- package/src/elements/store/prepare-row.test.ts +123 -0
- package/src/elements/store/resource.ts +43 -12
- package/src/elements/store/schema-decl.ts +117 -0
- package/src/elements/store/search-backfill.ts +205 -0
- package/src/elements/store/search-bind.ts +66 -0
- package/src/elements/store/search-bm25.ts +78 -0
- package/src/elements/store/search-ddl.ts +156 -0
- package/src/elements/store/search-embed-flow.ts +141 -0
- package/src/elements/store/search-errors.ts +34 -0
- package/src/elements/store/search-fusion.ts +112 -0
- package/src/elements/store/search-lsh.ts +179 -0
- package/src/elements/store/search-runtime.ts +300 -0
- package/src/elements/store/search.test.ts +144 -0
- package/src/elements/store/sql-session.test.ts +1 -0
- package/src/elements/store/sql-session.ts +83 -3
- package/src/elements/store/table.ts +23 -2
- package/src/elements/store/upsert-app.test.ts +1 -3
- package/src/elements/store.ts +50 -0
- package/src/full.ts +4 -2
- package/src/http.ts +4 -1
- package/src/i18n/catalogs/ar.ts +14 -6
- package/src/i18n/catalogs/en.ts +14 -6
- package/src/index.ts +6 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
- package/src/kernel/adopt-routes.ts +9 -1
- package/src/kernel/app-auth.ts +5 -0
- package/src/kernel/app.ts +102 -9
- package/src/kernel/auto-cache.test.ts +6 -12
- package/src/kernel/auto-registry.test.ts +3 -3
- package/src/kernel/boot-bind/ai.test.ts +22 -19
- package/src/kernel/boot-bind/ai.ts +5 -43
- package/src/kernel/boot-bind/clock.ts +23 -5
- package/src/kernel/boot-bind/honor-config.test.ts +4 -4
- package/src/kernel/boot.test.ts +11 -6
- package/src/kernel/boot.ts +66 -16
- package/src/kernel/boundary-contract.ts +93 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/call.ts +89 -0
- package/src/kernel/capability.ts +6 -0
- package/src/kernel/client-descriptor.ts +123 -0
- package/src/kernel/clock-timezone.test.ts +80 -0
- package/src/kernel/correlation.test.ts +1 -1
- package/src/kernel/dry-run.ts +11 -8
- package/src/kernel/effects-stamping.test.ts +42 -9
- package/src/kernel/effects.test.ts +4 -2
- package/src/kernel/effects.ts +51 -9
- package/src/kernel/errors-channel.ts +17 -0
- package/src/kernel/errors-live-resume.ts +3 -2
- package/src/kernel/errors-tenant.ts +7 -4
- package/src/kernel/errors.registry-helpers.ts +96 -0
- package/src/kernel/errors.registry.test.ts +139 -25
- package/src/kernel/errors.ts +102 -23
- package/src/kernel/external-effects.test.ts +196 -0
- package/src/kernel/flow.test.ts +13 -4
- package/src/kernel/flow.ts +69 -71
- package/src/kernel/fx-ask-telemetry.test.ts +2 -2
- package/src/kernel/fx-dead-letters.test.ts +3 -3
- package/src/kernel/fx-fetch.ts +55 -0
- package/src/kernel/fx-live-stream.ts +2 -2
- package/src/kernel/fx-live.test.ts +8 -8
- package/src/kernel/fx.test.ts +17 -11
- package/src/kernel/fx.ts +180 -57
- package/src/kernel/horizontal-child.ts +1 -1
- package/src/kernel/http-query.test.ts +3 -6
- package/src/kernel/index.ts +13 -3
- package/src/kernel/instance-id.ts +1 -1
- package/src/kernel/instances.test.ts +40 -0
- package/src/kernel/instances.ts +47 -9
- package/src/kernel/live-http.test.ts +6 -7
- package/src/kernel/live-http.ts +6 -2
- package/src/kernel/live-resume.test.ts +1 -1
- package/src/kernel/mcp-tool.test.ts +3 -3
- package/src/kernel/on.ts +98 -4
- package/src/kernel/pipeline.test.ts +12 -15
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/stamp-http.test.ts +3 -3
- package/src/kernel/triggers.ts +210 -95
- package/src/kernel-entry.ts +0 -1
- package/src/manifest/diff.ts +11 -1
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/types.ts +46 -2
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +1 -1
- package/src/okid.test.ts +56 -0
- package/src/okid.ts +55 -11
- package/src/plugins/anonymous.ts +8 -4
- package/src/plugins/auth/shared.ts +27 -3
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +561 -23
- package/src/plugins/config-source.ts +1 -1
- package/src/plugins/csrf.test.ts +65 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +36 -16
- package/src/plugins/oauth/shared.ts +3 -1
- package/src/plugins/oauth.ts +22 -14
- package/src/plugins/otp.ts +37 -21
- package/src/plugins/passkey-webauthn.ts +9 -6
- package/src/plugins/passkey.ts +98 -26
- package/src/plugins/pre-account-hijack.test.ts +223 -0
- package/src/plugins/two-factor.ts +466 -37
- package/src/plugins/username.ts +19 -7
- package/src/release/build-lib.ts +1 -0
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +1 -1
- package/src/runs/collect.test.ts +0 -2
- package/src/runtime/json-code-block.test.ts +250 -20
- package/src/runtime/json-code-block.ts +1261 -41
- package/src/term.test.ts +1 -1
- package/src/test/create-test-app.test.ts +12 -12
- package/src/test/live-signals.test.ts +6 -6
- package/src/test/provisions.integration.test.ts +10 -14
- package/src/test/reset-element-registries.ts +1 -1
- package/src/test/tenant-isolation.test.ts +12 -6
- package/site/content/docs/deployment/docker-swarm.mdx +0 -164
- package/site/content/docs/deployment/docker.mdx +0 -227
- package/site/content/docs/deployment/index.mdx +0 -83
- package/site/content/docs/deployment/kubernetes.mdx +0 -176
- package/site/content/docs/deployment/meta.json +0 -5
- package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
- package/site/content/docs/elements/ai.mdx +0 -385
- package/site/content/docs/elements/channel.mdx +0 -346
- package/site/content/docs/elements/clock.mdx +0 -244
- package/site/content/docs/elements/flow.mdx +0 -420
- package/site/content/docs/elements/gate.mdx +0 -436
- package/site/content/docs/elements/signal.mdx +0 -380
- package/site/content/docs/elements/store.mdx +0 -1099
- package/site/content/docs/elements/vault.mdx +0 -405
- package/site/content/docs/get-started/basic-usage.mdx +0 -173
- package/site/content/docs/get-started/index.mdx +0 -43
- package/site/content/docs/get-started/installation.mdx +0 -220
- package/site/content/docs/get-started/introduction.mdx +0 -132
- package/site/content/docs/get-started/meta.json +0 -13
- package/site/content/docs/get-started/project-structure.mdx +0 -925
- package/site/content/docs/get-started/testing.mdx +0 -328
- package/site/content/docs/get-started/why.mdx +0 -162
- package/site/content/docs/recipes/llama-cpp.mdx +0 -151
- package/site/content/docs/recipes/ollama.mdx +0 -142
- package/site/content/docs/recipes/sglang.mdx +0 -105
- package/site/content/docs/recipes/vllm.mdx +0 -106
- package/site/content/docs/reference/cli.md +0 -232
- package/site/content/docs/reference/client.mdx +0 -469
- package/site/content/docs/reference/security.md +0 -74
- package/src/cli/ai-setup/detect-ollama.ts +0 -213
- package/src/cli/ai-setup/recommend.test.ts +0 -225
- package/src/cli/ai-setup/recommend.ts +0 -225
- package/src/cli/dev-controls.test.ts +0 -122
- package/src/cli/dev-controls.ts +0 -164
- package/src/cli/tui/DevLive.tsx +0 -126
- package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
- package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
- package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
- package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
- package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
- package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
- package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
- package/src/docker/ollama-pull.ts +0 -232
- package/src/docker/recipes/llama-cpp.ts +0 -298
- package/src/docker/recipes/ollama.ts +0 -44
- package/src/docker/recipes/sglang.ts +0 -55
- package/src/docker/recipes/vllm.ts +0 -44
- package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
- package/src/drivers/ai-ollama.integration.test.ts +0 -184
- package/src/drivers/ai-ollama.ts +0 -389
- package/src/drivers/ollama.ts +0 -14
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-SQL DDL for built-in hybrid search (PostgreSQL 15+, zero extensions).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DeclaredColumn } from "../../manifest/types.ts";
|
|
6
|
+
import { LSH_DEFAULT_K } from "./search-errors.ts";
|
|
7
|
+
import { generateHyperplanes, hyperplaneSeed, serializePlanes } from "./search-lsh.ts";
|
|
8
|
+
|
|
9
|
+
/** Shadow / system column and index names. */
|
|
10
|
+
export const OKE_TSV_COL = "__oke_tsv";
|
|
11
|
+
export const OKE_SEARCH_STATS = "oke_search_stats";
|
|
12
|
+
export const OKE_SEARCH_DF = "oke_search_df";
|
|
13
|
+
export const OKE_SEARCH_PLANES = "oke_search_planes";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param sqlName - Column SQL name
|
|
17
|
+
*/
|
|
18
|
+
export function embColumn(sqlName: string): string {
|
|
19
|
+
return `__oke_emb_${sqlName}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param sqlName - Column SQL name
|
|
24
|
+
*/
|
|
25
|
+
export function lshColumn(sqlName: string): string {
|
|
26
|
+
return `__oke_lsh_${sqlName}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Emit CREATE TABLE / ALTER / INDEX statements for searchable columns.
|
|
31
|
+
*
|
|
32
|
+
* @param table - SQL table name
|
|
33
|
+
* @param columns - Manifest columns map
|
|
34
|
+
*/
|
|
35
|
+
export function searchDdlForTable(
|
|
36
|
+
table: string,
|
|
37
|
+
columns: Record<string, DeclaredColumn>,
|
|
38
|
+
): string[] {
|
|
39
|
+
const searchable: Array<{ key: string; sqlName: string; weight: number }> = [];
|
|
40
|
+
const embedCols: Array<{ key: string; sqlName: string; dims: number; model?: string }> = [];
|
|
41
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
42
|
+
if (!col.searchable) continue;
|
|
43
|
+
const sqlName = col.sqlName ?? key;
|
|
44
|
+
searchable.push({ key, sqlName, weight: col.searchable.weight });
|
|
45
|
+
if (col.embed) {
|
|
46
|
+
embedCols.push({
|
|
47
|
+
key,
|
|
48
|
+
sqlName,
|
|
49
|
+
dims: col.embed.dims,
|
|
50
|
+
...(col.embed.model ? { model: col.embed.model } : {}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (searchable.length === 0) return [];
|
|
55
|
+
|
|
56
|
+
const stmts: string[] = [
|
|
57
|
+
`CREATE TABLE IF NOT EXISTS ${OKE_SEARCH_STATS} (
|
|
58
|
+
table_name text PRIMARY KEY,
|
|
59
|
+
n bigint NOT NULL DEFAULT 0,
|
|
60
|
+
avgdl double precision NOT NULL DEFAULT 0,
|
|
61
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
62
|
+
)`,
|
|
63
|
+
`CREATE TABLE IF NOT EXISTS ${OKE_SEARCH_DF} (
|
|
64
|
+
table_name text NOT NULL,
|
|
65
|
+
term text NOT NULL,
|
|
66
|
+
df bigint NOT NULL DEFAULT 0,
|
|
67
|
+
PRIMARY KEY (table_name, term)
|
|
68
|
+
)`,
|
|
69
|
+
`CREATE TABLE IF NOT EXISTS ${OKE_SEARCH_PLANES} (
|
|
70
|
+
table_name text NOT NULL,
|
|
71
|
+
column_name text NOT NULL,
|
|
72
|
+
dims integer NOT NULL,
|
|
73
|
+
k integer NOT NULL,
|
|
74
|
+
seed text NOT NULL,
|
|
75
|
+
planes bytea NOT NULL,
|
|
76
|
+
PRIMARY KEY (table_name, column_name)
|
|
77
|
+
)`,
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// Weighted tsvector expression for GIN candidate retrieval.
|
|
81
|
+
const tsvParts = searchable.map(
|
|
82
|
+
(c) => `coalesce(to_tsvector('english', ${quoteIdent(c.sqlName)}), '')`,
|
|
83
|
+
);
|
|
84
|
+
const tsvExpr = tsvParts.join(" || ");
|
|
85
|
+
stmts.push(
|
|
86
|
+
`ALTER TABLE ${quoteIdent(table)} ADD COLUMN IF NOT EXISTS ${OKE_TSV_COL} tsvector GENERATED ALWAYS AS (${tsvExpr}) STORED`,
|
|
87
|
+
);
|
|
88
|
+
stmts.push(
|
|
89
|
+
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${table}_${OKE_TSV_COL}_gin`)} ON ${quoteIdent(table)} USING gin (${OKE_TSV_COL})`,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
for (const c of embedCols) {
|
|
93
|
+
stmts.push(
|
|
94
|
+
`ALTER TABLE ${quoteIdent(table)} ADD COLUMN IF NOT EXISTS ${quoteIdent(embColumn(c.sqlName))} real[]`,
|
|
95
|
+
);
|
|
96
|
+
stmts.push(
|
|
97
|
+
`ALTER TABLE ${quoteIdent(table)} ADD COLUMN IF NOT EXISTS ${quoteIdent(lshColumn(c.sqlName))} bigint`,
|
|
98
|
+
);
|
|
99
|
+
stmts.push(
|
|
100
|
+
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${table}_${lshColumn(c.sqlName)}_btree`)} ON ${quoteIdent(table)} (${quoteIdent(lshColumn(c.sqlName))})`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return stmts;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Insert-once hyperplane rows for embed columns (never regenerate).
|
|
109
|
+
*
|
|
110
|
+
* @param table - SQL table
|
|
111
|
+
* @param columns - Manifest columns
|
|
112
|
+
*/
|
|
113
|
+
export function ensureHyperplaneInserts(
|
|
114
|
+
table: string,
|
|
115
|
+
columns: Record<string, DeclaredColumn>,
|
|
116
|
+
): Array<{ sql: string; params: unknown[] }> {
|
|
117
|
+
const out: Array<{ sql: string; params: unknown[] }> = [];
|
|
118
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
119
|
+
if (!col.embed) continue;
|
|
120
|
+
const sqlName = col.sqlName ?? key;
|
|
121
|
+
const dims = col.embed.dims;
|
|
122
|
+
const k = LSH_DEFAULT_K;
|
|
123
|
+
const seed = hyperplaneSeed(table, sqlName, dims, k);
|
|
124
|
+
const planes = generateHyperplanes(seed, dims, k);
|
|
125
|
+
const blob = serializePlanes(planes);
|
|
126
|
+
const hex = blob.toString("hex");
|
|
127
|
+
out.push({
|
|
128
|
+
sql: `INSERT INTO ${OKE_SEARCH_PLANES} (table_name, column_name, dims, k, seed, planes)
|
|
129
|
+
VALUES (?, ?, ?, ?, ?, decode(?, 'hex'))
|
|
130
|
+
ON CONFLICT (table_name, column_name) DO NOTHING`,
|
|
131
|
+
params: [table, sqlName, dims, k, seed, hex],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Minimum PostgreSQL major version for hybrid search DDL.
|
|
139
|
+
*/
|
|
140
|
+
export const SEARCH_PG_MIN_MAJOR = 15;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param versionString - `SELECT version()` / `server_version` text
|
|
144
|
+
*/
|
|
145
|
+
export function parsePgMajor(versionString: string): number | undefined {
|
|
146
|
+
const m = /(\d+)\.\d+/.exec(versionString);
|
|
147
|
+
if (!m) return undefined;
|
|
148
|
+
return Number(m[1]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function quoteIdent(name: string): string {
|
|
152
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(name)) {
|
|
153
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
154
|
+
}
|
|
155
|
+
return name;
|
|
156
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-owned durable CDC flow that embeds `.embed()` columns asynchronously.
|
|
3
|
+
* Writer flows never carry `effects.embeds` — only this flow does.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DeclaredColumn, Manifest } from "../../manifest/types.ts";
|
|
7
|
+
import type { Fx } from "../../kernel/fx.ts";
|
|
8
|
+
import { LSH_DEFAULT_K, SearchConfigError } from "./search-errors.ts";
|
|
9
|
+
import { embColumn, lshColumn, OKE_SEARCH_PLANES } from "./search-ddl.ts";
|
|
10
|
+
import { deserializePlanes, lshBucket, lshBucketToSql } from "./search-lsh.ts";
|
|
11
|
+
|
|
12
|
+
/** CDC payload shape from bindRealtimeBridge / dispatchCdc. */
|
|
13
|
+
export interface SearchEmbedCdcPayload {
|
|
14
|
+
readonly before: Record<string, unknown> | null;
|
|
15
|
+
readonly after: Record<string, unknown> | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* System flow name for a searchable-embed table.
|
|
20
|
+
*
|
|
21
|
+
* @param table - SQL table name
|
|
22
|
+
*/
|
|
23
|
+
export function searchEmbedFlowName(table: string): string {
|
|
24
|
+
return `_oke_search_embed_${table}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Collect tables that need the system embed CDC flow.
|
|
29
|
+
*
|
|
30
|
+
* @param manifest - Project manifest
|
|
31
|
+
*/
|
|
32
|
+
export function tablesNeedingSearchEmbed(manifest: Manifest): Array<{
|
|
33
|
+
readonly table: string;
|
|
34
|
+
readonly store: string;
|
|
35
|
+
readonly columns: Array<{ sqlName: string; dims: number; model?: string }>;
|
|
36
|
+
readonly pk: string;
|
|
37
|
+
}> {
|
|
38
|
+
const out: Array<{
|
|
39
|
+
readonly table: string;
|
|
40
|
+
readonly store: string;
|
|
41
|
+
readonly columns: Array<{ sqlName: string; dims: number; model?: string }>;
|
|
42
|
+
readonly pk: string;
|
|
43
|
+
}> = [];
|
|
44
|
+
for (const [storeName, store] of Object.entries(manifest.stores ?? {})) {
|
|
45
|
+
if (store.facet !== "sql" || !store.tables) continue;
|
|
46
|
+
for (const [tableName, table] of Object.entries(store.tables)) {
|
|
47
|
+
const embedCols: Array<{ sqlName: string; dims: number; model?: string }> = [];
|
|
48
|
+
let pk = "id";
|
|
49
|
+
for (const [key, col] of Object.entries(table.columns ?? {})) {
|
|
50
|
+
if (!col || typeof col !== "object") continue;
|
|
51
|
+
const c = col as DeclaredColumn;
|
|
52
|
+
if (c.primaryKey) pk = c.sqlName ?? key;
|
|
53
|
+
if (c.embed) {
|
|
54
|
+
embedCols.push({
|
|
55
|
+
sqlName: c.sqlName ?? key,
|
|
56
|
+
dims: c.embed.dims,
|
|
57
|
+
...(c.embed.model ? { model: c.embed.model } : {}),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (embedCols.length > 0) {
|
|
62
|
+
out.push({ table: tableName, store: storeName, columns: embedCols, pk });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run one embed step for a CDC event (call inside `fx.step`).
|
|
71
|
+
*
|
|
72
|
+
* @param fx - Flow fx
|
|
73
|
+
* @param table - Table name
|
|
74
|
+
* @param pk - Primary key SQL name
|
|
75
|
+
* @param columns - Embed columns
|
|
76
|
+
* @param payload - CDC before/after
|
|
77
|
+
* @param sqlRef - Resource ref for raw SQL (`sql:app`)
|
|
78
|
+
*/
|
|
79
|
+
export async function applySearchEmbedCdc(
|
|
80
|
+
fx: Fx,
|
|
81
|
+
table: string,
|
|
82
|
+
pk: string,
|
|
83
|
+
columns: Array<{ sqlName: string; dims: number; model?: string }>,
|
|
84
|
+
payload: SearchEmbedCdcPayload,
|
|
85
|
+
sqlRef: `sql:${string}`,
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
const store = fx.store({
|
|
88
|
+
kind: "sql",
|
|
89
|
+
name: sqlRef.slice(4),
|
|
90
|
+
ref: sqlRef,
|
|
91
|
+
facet: "sql",
|
|
92
|
+
} as never);
|
|
93
|
+
|
|
94
|
+
if (!payload.after) {
|
|
95
|
+
// Delete — embeddings cleared with the row; nothing to do.
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const id = String(payload.after[pk] ?? "");
|
|
100
|
+
if (!id) return;
|
|
101
|
+
|
|
102
|
+
for (const col of columns) {
|
|
103
|
+
const text = String(payload.after[col.sqlName] ?? "");
|
|
104
|
+
const beforeText =
|
|
105
|
+
payload.before && typeof payload.before[col.sqlName] === "string"
|
|
106
|
+
? String(payload.before[col.sqlName])
|
|
107
|
+
: undefined;
|
|
108
|
+
if (beforeText !== undefined && beforeText === text) continue;
|
|
109
|
+
|
|
110
|
+
const model = col.model ?? "default";
|
|
111
|
+
const vector = await fx.embed(model, text);
|
|
112
|
+
if (vector.length !== col.dims) {
|
|
113
|
+
throw new SearchConfigError(
|
|
114
|
+
table,
|
|
115
|
+
col.sqlName,
|
|
116
|
+
`embedding length ${vector.length} !== declared dims ${col.dims}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const planesRows = await store.raw(
|
|
121
|
+
`SELECT k, planes FROM ${OKE_SEARCH_PLANES} WHERE table_name = ? AND column_name = ?`,
|
|
122
|
+
[table, col.sqlName],
|
|
123
|
+
);
|
|
124
|
+
const prow = planesRows[0];
|
|
125
|
+
if (!prow) {
|
|
126
|
+
throw new SearchConfigError(
|
|
127
|
+
table,
|
|
128
|
+
col.sqlName,
|
|
129
|
+
"missing hyperplanes — ensure search DDL / search-backfill ran",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const k = Number(prow["k"] ?? LSH_DEFAULT_K);
|
|
133
|
+
const planes = deserializePlanes(Buffer.from(prow["planes"] as Buffer), k);
|
|
134
|
+
const bucket = lshBucket(vector, planes);
|
|
135
|
+
|
|
136
|
+
await store.raw(
|
|
137
|
+
`UPDATE ${table} SET ${embColumn(col.sqlName)} = ?::real[], ${lshColumn(col.sqlName)} = ? WHERE ${pk} = ?`,
|
|
138
|
+
[`{${Array.from(vector).join(",")}}`, lshBucketToSql(bucket), id],
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-loud errors for built-in hybrid SQL search.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Thrown when `.embed()` / search config is invalid or AI is missing. */
|
|
6
|
+
export class SearchConfigError extends Error {
|
|
7
|
+
override readonly name = "SearchConfigError";
|
|
8
|
+
readonly table: string;
|
|
9
|
+
readonly column: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param table - SQL table name
|
|
13
|
+
* @param column - JS or SQL column key
|
|
14
|
+
* @param message - Human-readable fix
|
|
15
|
+
*/
|
|
16
|
+
constructor(table: string, column: string, message: string) {
|
|
17
|
+
super(`SearchConfigError: ${table}.${column}: ${message}`);
|
|
18
|
+
this.table = table;
|
|
19
|
+
this.column = column;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Corpus size below which IDF / BM25 stats are not meaningful. */
|
|
24
|
+
export const SEARCH_LOW_CORPUS_WARN_N = 100;
|
|
25
|
+
|
|
26
|
+
/** Default RRF damping constant (Cormack, Clarke, Büttcher — SIGIR 2009). */
|
|
27
|
+
export const RRF_DEFAULT_K = 60;
|
|
28
|
+
|
|
29
|
+
/** Fixed LSH hyperplane count (fits bigint bit packing). */
|
|
30
|
+
export const LSH_DEFAULT_K = 64;
|
|
31
|
+
|
|
32
|
+
/** BM25 saturation / length-norm defaults (Robertson–Zaragoza). */
|
|
33
|
+
export const BM25_K1 = 1.2;
|
|
34
|
+
export const BM25_B = 0.75;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rank fusion — RRF (default) and weighted combination (opt-in).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { RRF_DEFAULT_K } from "./search-errors.ts";
|
|
6
|
+
|
|
7
|
+
/** One ranked list entry (1-based rank preferred for RRF). */
|
|
8
|
+
export interface RankedHit {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly score: number;
|
|
11
|
+
readonly rank: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type FuseStrategy = "rrf" | "weighted";
|
|
15
|
+
|
|
16
|
+
export interface FuseOptions {
|
|
17
|
+
readonly strategy?: FuseStrategy;
|
|
18
|
+
/** RRF damping constant — default {@link RRF_DEFAULT_K} (Cormack et al. 2009). */
|
|
19
|
+
readonly k?: number;
|
|
20
|
+
/** Weighted fusion only. */
|
|
21
|
+
readonly weights?: { readonly bm25?: number; readonly vector?: number };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Reciprocal Rank Fusion: Σ 1/(k + rank).
|
|
26
|
+
*
|
|
27
|
+
* @param lists - Named ranked lists (e.g. bm25, vector)
|
|
28
|
+
* @param k - Damping constant
|
|
29
|
+
*/
|
|
30
|
+
export function fuseRrf(
|
|
31
|
+
lists: ReadonlyMap<string, readonly RankedHit[]>,
|
|
32
|
+
k: number = RRF_DEFAULT_K,
|
|
33
|
+
): RankedHit[] {
|
|
34
|
+
const scores = new Map<string, number>();
|
|
35
|
+
for (const list of lists.values()) {
|
|
36
|
+
for (const hit of list) {
|
|
37
|
+
const contrib = 1 / (k + hit.rank);
|
|
38
|
+
scores.set(hit.id, (scores.get(hit.id) ?? 0) + contrib);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return toRanked(scores);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Weighted linear combination of raw scores (min-max normalized per list).
|
|
46
|
+
*
|
|
47
|
+
* @param lists - Named scored lists
|
|
48
|
+
* @param weights - Per-engine weights
|
|
49
|
+
*/
|
|
50
|
+
export function fuseWeighted(
|
|
51
|
+
lists: ReadonlyMap<string, readonly RankedHit[]>,
|
|
52
|
+
weights: { readonly bm25?: number; readonly vector?: number } = {},
|
|
53
|
+
): RankedHit[] {
|
|
54
|
+
const wBm25 = weights.bm25 ?? 0.5;
|
|
55
|
+
const wVec = weights.vector ?? 0.5;
|
|
56
|
+
const norm = normalizeLists(lists);
|
|
57
|
+
const scores = new Map<string, number>();
|
|
58
|
+
for (const [name, list] of norm) {
|
|
59
|
+
const w = name === "vector" ? wVec : name === "bm25" ? wBm25 : 1;
|
|
60
|
+
for (const hit of list) {
|
|
61
|
+
scores.set(hit.id, (scores.get(hit.id) ?? 0) + w * hit.score);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return toRanked(scores);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Apply {@link FuseOptions} — omitted / rrf → RRF with k=60.
|
|
69
|
+
*
|
|
70
|
+
* @param lists - Ranked lists
|
|
71
|
+
* @param fuse - Options
|
|
72
|
+
*/
|
|
73
|
+
export function fuseLists(
|
|
74
|
+
lists: ReadonlyMap<string, readonly RankedHit[]>,
|
|
75
|
+
fuse?: FuseOptions,
|
|
76
|
+
): { hits: RankedHit[]; strategy: FuseStrategy; k?: number } {
|
|
77
|
+
const strategy = fuse?.strategy ?? "rrf";
|
|
78
|
+
if (strategy === "weighted") {
|
|
79
|
+
return { hits: fuseWeighted(lists, fuse?.weights), strategy };
|
|
80
|
+
}
|
|
81
|
+
const k = fuse?.k ?? RRF_DEFAULT_K;
|
|
82
|
+
return { hits: fuseRrf(lists, k), strategy, k };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeLists(
|
|
86
|
+
lists: ReadonlyMap<string, readonly RankedHit[]>,
|
|
87
|
+
): Map<string, RankedHit[]> {
|
|
88
|
+
const out = new Map<string, RankedHit[]>();
|
|
89
|
+
for (const [name, list] of lists) {
|
|
90
|
+
let min = Infinity;
|
|
91
|
+
let max = -Infinity;
|
|
92
|
+
for (const h of list) {
|
|
93
|
+
if (h.score < min) min = h.score;
|
|
94
|
+
if (h.score > max) max = h.score;
|
|
95
|
+
}
|
|
96
|
+
const span = max - min || 1;
|
|
97
|
+
out.set(
|
|
98
|
+
name,
|
|
99
|
+
list.map((h) => ({
|
|
100
|
+
id: h.id,
|
|
101
|
+
rank: h.rank,
|
|
102
|
+
score: (h.score - min) / span,
|
|
103
|
+
})),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function toRanked(scores: Map<string, number>): RankedHit[] {
|
|
110
|
+
const sorted = [...scores.entries()].sort((a, b) => b[1]! - a[1]!);
|
|
111
|
+
return sorted.map(([id, score], i) => ({ id, score, rank: i + 1 }));
|
|
112
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Random-hyperplane LSH — K fixed planes generated once per field lifetime.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { LSH_DEFAULT_K } from "./search-errors.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stable seed string for a field's hyperplanes.
|
|
10
|
+
*
|
|
11
|
+
* @param table - SQL table
|
|
12
|
+
* @param column - SQL column
|
|
13
|
+
* @param dims - Embedding dimensionality
|
|
14
|
+
* @param k - Number of planes
|
|
15
|
+
*/
|
|
16
|
+
export function hyperplaneSeed(table: string, column: string, dims: number, k: number): string {
|
|
17
|
+
return `${table}|${column}|${dims}|${k}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate K unit-ish random hyperplanes in `dims` dimensions from a seed.
|
|
22
|
+
* Deterministic across hosts for the same seed.
|
|
23
|
+
*
|
|
24
|
+
* @param seed - Stable seed string
|
|
25
|
+
* @param dims - Vector dimensionality
|
|
26
|
+
* @param k - Plane count (default {@link LSH_DEFAULT_K})
|
|
27
|
+
*/
|
|
28
|
+
export function generateHyperplanes(
|
|
29
|
+
seed: string,
|
|
30
|
+
dims: number,
|
|
31
|
+
k: number = LSH_DEFAULT_K,
|
|
32
|
+
): Float32Array[] {
|
|
33
|
+
const planes: Float32Array[] = [];
|
|
34
|
+
let counter = 0;
|
|
35
|
+
for (let i = 0; i < k; i++) {
|
|
36
|
+
const plane = new Float32Array(dims);
|
|
37
|
+
let filled = 0;
|
|
38
|
+
while (filled < dims) {
|
|
39
|
+
const h = createHash("sha256").update(`${seed}\0${counter++}`).digest();
|
|
40
|
+
for (let b = 0; b + 3 < h.length && filled < dims; b += 4) {
|
|
41
|
+
const u = h.readUInt32BE(b) / 0xffff_ffff;
|
|
42
|
+
plane[filled++] = u * 2 - 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// L2 normalize
|
|
46
|
+
let norm = 0;
|
|
47
|
+
for (let j = 0; j < dims; j++) norm += plane[j]! * plane[j]!;
|
|
48
|
+
norm = Math.sqrt(norm) || 1;
|
|
49
|
+
for (let j = 0; j < dims; j++) plane[j]! /= norm;
|
|
50
|
+
planes.push(plane);
|
|
51
|
+
}
|
|
52
|
+
return planes;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Pack LSH bucket bits into a bigint (K ≤ 64).
|
|
57
|
+
*
|
|
58
|
+
* @param vector - Embedding
|
|
59
|
+
* @param planes - Fixed hyperplanes
|
|
60
|
+
*/
|
|
61
|
+
export function lshBucket(vector: readonly number[], planes: readonly Float32Array[]): bigint {
|
|
62
|
+
if (planes.length > 64) {
|
|
63
|
+
throw new Error(`lshBucket: K=${planes.length} exceeds bigint packing (max 64)`);
|
|
64
|
+
}
|
|
65
|
+
let bits = 0n;
|
|
66
|
+
for (let i = 0; i < planes.length; i++) {
|
|
67
|
+
const plane = planes[i]!;
|
|
68
|
+
if (vector.length !== plane.length) {
|
|
69
|
+
throw new Error(`lshBucket: vector length ${vector.length} !== plane dims ${plane.length}`);
|
|
70
|
+
}
|
|
71
|
+
let dot = 0;
|
|
72
|
+
for (let j = 0; j < plane.length; j++) {
|
|
73
|
+
dot += vector[j]! * plane[j]!;
|
|
74
|
+
}
|
|
75
|
+
if (dot >= 0) bits |= 1n << BigInt(i);
|
|
76
|
+
}
|
|
77
|
+
return bits;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Encode an LSH bucket for PostgreSQL `bigint` (signed int64).
|
|
82
|
+
* K=64 bit patterns with the high bit set exceed unsigned range when passed
|
|
83
|
+
* as a decimal string — reinterpret as two's-complement signed.
|
|
84
|
+
*
|
|
85
|
+
* @param bucket - Unsigned bit pack from {@link lshBucket}
|
|
86
|
+
*/
|
|
87
|
+
export function lshBucketToSql(bucket: bigint): string {
|
|
88
|
+
const masked = bucket & 0xffff_ffff_ffff_ffffn;
|
|
89
|
+
if (masked >= 0x8000_0000_0000_0000n) {
|
|
90
|
+
return (masked - 0x1_0000_0000_0000_0000n).toString();
|
|
91
|
+
}
|
|
92
|
+
return masked.toString();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Decode a PostgreSQL `bigint` LSH value back to an unsigned bit pack.
|
|
97
|
+
*
|
|
98
|
+
* @param value - Driver value (string | number | bigint)
|
|
99
|
+
*/
|
|
100
|
+
export function lshBucketFromSql(value: unknown): bigint {
|
|
101
|
+
const n = typeof value === "bigint" ? value : BigInt(String(value));
|
|
102
|
+
return n < 0n ? n + 0x1_0000_0000_0000_0000n : n;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Cosine similarity in [−1, 1].
|
|
107
|
+
*
|
|
108
|
+
* @param a - Vector a
|
|
109
|
+
* @param b - Vector b
|
|
110
|
+
*/
|
|
111
|
+
export function cosineSimilarity(a: readonly number[], b: readonly number[]): number {
|
|
112
|
+
if (a.length !== b.length) {
|
|
113
|
+
throw new Error(`cosineSimilarity: length mismatch ${a.length} vs ${b.length}`);
|
|
114
|
+
}
|
|
115
|
+
let dot = 0;
|
|
116
|
+
let na = 0;
|
|
117
|
+
let nb = 0;
|
|
118
|
+
for (let i = 0; i < a.length; i++) {
|
|
119
|
+
dot += a[i]! * b[i]!;
|
|
120
|
+
na += a[i]! * a[i]!;
|
|
121
|
+
nb += b[i]! * b[i]!;
|
|
122
|
+
}
|
|
123
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
124
|
+
return denom === 0 ? 0 : dot / denom;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Candidate buckets: exact match plus Hamming distance 1 flips.
|
|
129
|
+
*
|
|
130
|
+
* @param bucket - Query bucket
|
|
131
|
+
* @param k - Bit width
|
|
132
|
+
*/
|
|
133
|
+
export function neighborBuckets(bucket: bigint, k: number): bigint[] {
|
|
134
|
+
const out: bigint[] = [bucket];
|
|
135
|
+
for (let i = 0; i < k; i++) {
|
|
136
|
+
out.push(bucket ^ (1n << BigInt(i)));
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Serialize planes for `oke_search_planes.planes` (Float32 little-endian).
|
|
143
|
+
*
|
|
144
|
+
* @param planes - Hyperplanes
|
|
145
|
+
*/
|
|
146
|
+
export function serializePlanes(planes: readonly Float32Array[]): Buffer {
|
|
147
|
+
const dims = planes[0]?.length ?? 0;
|
|
148
|
+
const buf = Buffer.allocUnsafe(4 + planes.length * dims * 4);
|
|
149
|
+
buf.writeUInt32LE(dims, 0);
|
|
150
|
+
let offset = 4;
|
|
151
|
+
for (const plane of planes) {
|
|
152
|
+
for (let i = 0; i < dims; i++) {
|
|
153
|
+
buf.writeFloatLE(plane[i]!, offset);
|
|
154
|
+
offset += 4;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return buf;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Deserialize planes from storage.
|
|
162
|
+
*
|
|
163
|
+
* @param buf - Stored bytes
|
|
164
|
+
* @param k - Expected plane count
|
|
165
|
+
*/
|
|
166
|
+
export function deserializePlanes(buf: Buffer, k: number): Float32Array[] {
|
|
167
|
+
const dims = buf.readUInt32LE(0);
|
|
168
|
+
const planes: Float32Array[] = [];
|
|
169
|
+
let offset = 4;
|
|
170
|
+
for (let i = 0; i < k; i++) {
|
|
171
|
+
const plane = new Float32Array(dims);
|
|
172
|
+
for (let j = 0; j < dims; j++) {
|
|
173
|
+
plane[j] = buf.readFloatLE(offset);
|
|
174
|
+
offset += 4;
|
|
175
|
+
}
|
|
176
|
+
planes.push(plane);
|
|
177
|
+
}
|
|
178
|
+
return planes;
|
|
179
|
+
}
|