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
package/src/compiler/extract.ts
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import { parseSync } from "oxc-parser";
|
|
10
10
|
|
|
11
|
+
import {
|
|
12
|
+
AI_NATIVE_DRIVER_IDS,
|
|
13
|
+
formatAiProviderTier2Warn,
|
|
14
|
+
getAiProviderEntry,
|
|
15
|
+
} from "../elements/ai/providers.ts";
|
|
16
|
+
import { buildCronExpression, type CronField } from "../elements/clock/cron-fields.ts";
|
|
17
|
+
import { SearchConfigError } from "../elements/store/search-errors.ts";
|
|
18
|
+
|
|
11
19
|
import type {
|
|
12
20
|
Ai,
|
|
13
21
|
AiAgent,
|
|
@@ -230,7 +238,21 @@ export async function extractManifest(options: ExtractManifestOptions = {}): Pro
|
|
|
230
238
|
if (Object.keys(journeys).length > 0) manifest.journeys = journeys;
|
|
231
239
|
if (Object.keys(drivers).length > 0) manifest.drivers = drivers;
|
|
232
240
|
if (scope.tenancy) manifest.tenancy = scope.tenancy;
|
|
233
|
-
if (scope.store
|
|
241
|
+
if (scope.store) {
|
|
242
|
+
const storeManifest: NonNullable<Manifest["store"]> = {};
|
|
243
|
+
if (scope.store.live === true) storeManifest.live = true;
|
|
244
|
+
if (scope.store.search?.embed) {
|
|
245
|
+
storeManifest.search = {
|
|
246
|
+
embed: {
|
|
247
|
+
model: scope.store.search.embed.model,
|
|
248
|
+
dims: scope.store.search.embed.dims,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
if (storeManifest.live === true || storeManifest.search?.embed) {
|
|
253
|
+
manifest.store = storeManifest;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
234
256
|
if (scope.i18n) manifest.i18n = scope.i18n;
|
|
235
257
|
if (scope.topology) manifest.topology = scope.topology;
|
|
236
258
|
if (scope.images) manifest.images = scope.images;
|
|
@@ -238,6 +260,8 @@ export async function extractManifest(options: ExtractManifestOptions = {}): Pro
|
|
|
238
260
|
applyTenancyDefaults(manifest);
|
|
239
261
|
assertTenantSafeSchema(manifest);
|
|
240
262
|
applyLiveDefaults(manifest, scope);
|
|
263
|
+
resolveSearchEmbedDefaults(manifest);
|
|
264
|
+
assertSearchConfig(manifest);
|
|
241
265
|
|
|
242
266
|
return manifest;
|
|
243
267
|
}
|
|
@@ -295,6 +319,7 @@ function collectDeclarations(filePath: string, program: AstNode, scope: ProjectS
|
|
|
295
319
|
if (name) scope.app = name;
|
|
296
320
|
applyOkeTenantConfig(opts, scope);
|
|
297
321
|
applyOkeStoreLiveConfig(opts, scope);
|
|
322
|
+
applyOkeStoreSearchConfig(opts, scope);
|
|
298
323
|
});
|
|
299
324
|
}
|
|
300
325
|
|
|
@@ -495,16 +520,35 @@ function tableFromDeclared(t: DeclaredSchemaTable): {
|
|
|
495
520
|
policies?: Record<string, TablePolicy>;
|
|
496
521
|
tenantScoped?: boolean;
|
|
497
522
|
live?: boolean;
|
|
523
|
+
search?: { engines: Array<"bm25" | "lsh"> };
|
|
498
524
|
} {
|
|
525
|
+
const engines = searchEnginesFromColumns(t.columns);
|
|
499
526
|
return {
|
|
500
527
|
columns: t.columns,
|
|
501
528
|
...(t.rls ? { rls: true } : {}),
|
|
502
529
|
...(t.policies ? { policies: t.policies } : {}),
|
|
503
530
|
...(t.tenantScoped === false ? { tenantScoped: false } : {}),
|
|
504
531
|
...(t.live === false ? { live: false } : {}),
|
|
532
|
+
...(engines ? { search: { engines } } : {}),
|
|
505
533
|
};
|
|
506
534
|
}
|
|
507
535
|
|
|
536
|
+
function searchEnginesFromColumns(
|
|
537
|
+
columns: Record<string, DeclaredColumn>,
|
|
538
|
+
): Array<"bm25" | "lsh"> | undefined {
|
|
539
|
+
let bm25 = false;
|
|
540
|
+
let lsh = false;
|
|
541
|
+
for (const col of Object.values(columns)) {
|
|
542
|
+
if (col.searchable) bm25 = true;
|
|
543
|
+
if (col.embed) lsh = true;
|
|
544
|
+
}
|
|
545
|
+
if (!bm25 && !lsh) return undefined;
|
|
546
|
+
const engines: Array<"bm25" | "lsh"> = [];
|
|
547
|
+
if (bm25) engines.push("bm25");
|
|
548
|
+
if (lsh) engines.push("lsh");
|
|
549
|
+
return engines;
|
|
550
|
+
}
|
|
551
|
+
|
|
508
552
|
function parseSchemaTableExtras(
|
|
509
553
|
node: AstNode | undefined,
|
|
510
554
|
): Pick<DeclaredSchemaTable, "rls" | "policies" | "tenantScoped" | "live"> {
|
|
@@ -678,11 +722,19 @@ const FIELD_SQL_TYPES: ReadonlySet<string> = new Set([
|
|
|
678
722
|
"cidr",
|
|
679
723
|
"macaddr",
|
|
680
724
|
"macaddr8",
|
|
725
|
+
// Prepared text-id factories (`field.id()` / `field.okid()` ≡ text + defaultFn).
|
|
726
|
+
"id",
|
|
727
|
+
"okid",
|
|
681
728
|
]);
|
|
682
729
|
|
|
730
|
+
/** Prepared text-id heads that imply a dynamic default (same as `.okid()` / `.defaultFn(id)`). */
|
|
731
|
+
const FIELD_ID_HEADS: ReadonlySet<string> = new Set(["id", "okid"]);
|
|
732
|
+
|
|
683
733
|
/** Manifest primitive for a field head (`decimal` collapses into `numeric`). */
|
|
684
734
|
function manifestFieldType(head: string): DeclaredColumn["type"] {
|
|
685
|
-
|
|
735
|
+
if (head === "decimal") return "numeric";
|
|
736
|
+
if (FIELD_ID_HEADS.has(head)) return "text";
|
|
737
|
+
return head as DeclaredColumn["type"];
|
|
686
738
|
}
|
|
687
739
|
|
|
688
740
|
/**
|
|
@@ -719,7 +771,7 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
|
|
|
719
771
|
if (method === "describe") {
|
|
720
772
|
description = stringArg(call.arguments[0]) ?? description;
|
|
721
773
|
}
|
|
722
|
-
if (method === "defaultFn" || method === "okid" || method === "now") {
|
|
774
|
+
if (method === "defaultFn" || method === "okid" || method === "now" || method === "id") {
|
|
723
775
|
hasDefault = true;
|
|
724
776
|
defaultValue = defaultValue ?? null;
|
|
725
777
|
}
|
|
@@ -755,11 +807,17 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
|
|
|
755
807
|
const member = cur as AstNode & { object: AstNode; property: AstNode };
|
|
756
808
|
if (identifierName(member.object) === "field") {
|
|
757
809
|
const t = identifierName(member.property);
|
|
758
|
-
if (t && FIELD_SQL_TYPES.has(t))
|
|
810
|
+
if (t && FIELD_SQL_TYPES.has(t)) {
|
|
811
|
+
sqlType = manifestFieldType(t);
|
|
812
|
+
if (FIELD_ID_HEADS.has(t)) {
|
|
813
|
+
hasDefault = true;
|
|
814
|
+
defaultValue = defaultValue ?? null;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
759
817
|
}
|
|
760
818
|
}
|
|
761
819
|
|
|
762
|
-
// Also: field.text() ends as CallExpression with callee MemberExpression
|
|
820
|
+
// Also: field.text() / field.id() ends as CallExpression with callee MemberExpression
|
|
763
821
|
if (!sqlType && node.type === "CallExpression") {
|
|
764
822
|
let probe: AstNode | undefined = node;
|
|
765
823
|
while (probe && probe.type === "CallExpression") {
|
|
@@ -770,6 +828,10 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
|
|
|
770
828
|
const head = identifierName(member.property);
|
|
771
829
|
if (identifierName(member.object) === "field" && head && FIELD_SQL_TYPES.has(head)) {
|
|
772
830
|
sqlType = manifestFieldType(head);
|
|
831
|
+
if (FIELD_ID_HEADS.has(head)) {
|
|
832
|
+
hasDefault = true;
|
|
833
|
+
defaultValue = defaultValue ?? null;
|
|
834
|
+
}
|
|
773
835
|
const arg0 = call.arguments[0];
|
|
774
836
|
if (arg0?.type === "ObjectExpression") optionsNode = arg0;
|
|
775
837
|
break;
|
|
@@ -796,6 +858,86 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
|
|
|
796
858
|
...(methods.has("sensitive") ? { sensitive: true } : {}),
|
|
797
859
|
...parseFieldOptions(optionsNode),
|
|
798
860
|
};
|
|
861
|
+
if (methods.has("searchable")) {
|
|
862
|
+
let weight = 1;
|
|
863
|
+
let probe: AstNode | undefined = node;
|
|
864
|
+
while (probe && probe.type === "CallExpression") {
|
|
865
|
+
const call = probe as CallExpression;
|
|
866
|
+
const callee = call.callee;
|
|
867
|
+
if (callee.type === "MemberExpression") {
|
|
868
|
+
const member = callee as AstNode & { object: AstNode; property: AstNode };
|
|
869
|
+
if (identifierName(member.property) === "searchable") {
|
|
870
|
+
const arg0 = call.arguments[0];
|
|
871
|
+
if (arg0?.type === "ObjectExpression") {
|
|
872
|
+
const w = numberProp(arg0, "weight");
|
|
873
|
+
if (w !== undefined) weight = w;
|
|
874
|
+
}
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
probe = member.object;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
col.searchable = { weight };
|
|
883
|
+
}
|
|
884
|
+
if (methods.has("embed")) {
|
|
885
|
+
if (!methods.has("searchable")) {
|
|
886
|
+
throw new Error(
|
|
887
|
+
`extract: .embed() on column "${key}" requires a prior .searchable() — weight is free SQL math; embed is an async AI pipeline`,
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
let dims: number | undefined;
|
|
891
|
+
let model: string | undefined;
|
|
892
|
+
let probe: AstNode | undefined = node;
|
|
893
|
+
while (probe && probe.type === "CallExpression") {
|
|
894
|
+
const call = probe as CallExpression;
|
|
895
|
+
const callee = call.callee;
|
|
896
|
+
if (callee.type === "MemberExpression") {
|
|
897
|
+
const member = callee as AstNode & { object: AstNode; property: AstNode };
|
|
898
|
+
if (identifierName(member.property) === "embed") {
|
|
899
|
+
const arg0 = call.arguments[0];
|
|
900
|
+
if (arg0?.type === "ObjectExpression") {
|
|
901
|
+
const d = numberProp(arg0, "dims");
|
|
902
|
+
if (d !== undefined) dims = d;
|
|
903
|
+
const modelNode = objectProp(arg0, "model");
|
|
904
|
+
if (modelNode) {
|
|
905
|
+
const lit = stringArg(modelNode);
|
|
906
|
+
if (lit) model = lit;
|
|
907
|
+
else {
|
|
908
|
+
const bindingName = identifierName(modelNode);
|
|
909
|
+
if (bindingName) model = bindingName;
|
|
910
|
+
else if (modelNode.type === "MemberExpression") {
|
|
911
|
+
const m = modelNode as AstNode & { property: AstNode };
|
|
912
|
+
const prop = identifierName(m.property);
|
|
913
|
+
if (prop === "name") {
|
|
914
|
+
const obj = (modelNode as AstNode & { object: AstNode }).object;
|
|
915
|
+
model = identifierName(obj) ?? model;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
probe = member.object;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
if (dims !== undefined && !(Number.isInteger(dims) && dims > 0)) {
|
|
929
|
+
throw new Error(
|
|
930
|
+
`extract: .embed() on column "${key}" has invalid dims (need positive integer)`,
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
// Stamp partial embed; resolveSearchEmbedDefaults fills model/dims from
|
|
934
|
+
// oke({ store: { search: { embed } } }) before assertSearchConfig.
|
|
935
|
+
// Cast: Manifest DeclaredColumn requires concrete dims after resolution.
|
|
936
|
+
(col as { embed?: { model?: string; dims?: number } }).embed = {
|
|
937
|
+
...(dims !== undefined ? { dims } : {}),
|
|
938
|
+
...(model !== undefined ? { model } : {}),
|
|
939
|
+
};
|
|
940
|
+
}
|
|
799
941
|
if (methods.has("retain")) {
|
|
800
942
|
// retain("7y") — find the call in the original chain by re-walk
|
|
801
943
|
let probe: AstNode | undefined = node;
|
|
@@ -1013,7 +1155,7 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1013
1155
|
const name = stringArg(call.arguments[0]);
|
|
1014
1156
|
const opts = objectArg(call.arguments[1]);
|
|
1015
1157
|
if (name) {
|
|
1016
|
-
scope
|
|
1158
|
+
recordClockDecl(scope, program, call, name, {
|
|
1017
1159
|
...(stringProp(opts, "every") ? { every: stringProp(opts, "every") } : {}),
|
|
1018
1160
|
...(stringProp(opts, "cron") ? { cron: stringProp(opts, "cron") } : {}),
|
|
1019
1161
|
...(boolProp(opts, "overridable") !== undefined
|
|
@@ -1023,7 +1165,61 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1023
1165
|
? { description: stringProp(opts, "description") }
|
|
1024
1166
|
: {}),
|
|
1025
1167
|
perTenant: true,
|
|
1026
|
-
};
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// clock.every("name", "30s", opts?)
|
|
1173
|
+
if (obj === "clock" && prop === "every") {
|
|
1174
|
+
const name = stringArg(call.arguments[0]);
|
|
1175
|
+
const duration = stringArg(call.arguments[1]);
|
|
1176
|
+
const opts = objectArg(call.arguments[2]);
|
|
1177
|
+
if (name && duration) {
|
|
1178
|
+
recordClockDecl(scope, program, call, name, {
|
|
1179
|
+
every: duration,
|
|
1180
|
+
...clockHelperOptsFromAst(opts),
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// clock.cron("name", "0 6 * * *" | fields, opts?)
|
|
1186
|
+
if (obj === "clock" && prop === "cron") {
|
|
1187
|
+
const name = stringArg(call.arguments[0]);
|
|
1188
|
+
if (name) {
|
|
1189
|
+
const expr = stringArg(call.arguments[1]);
|
|
1190
|
+
const fieldsObj = objectArg(call.arguments[1]);
|
|
1191
|
+
if (expr) {
|
|
1192
|
+
recordClockDecl(scope, program, call, name, {
|
|
1193
|
+
cron: expr,
|
|
1194
|
+
...clockHelperOptsFromAst(objectArg(call.arguments[2])),
|
|
1195
|
+
});
|
|
1196
|
+
} else if (fieldsObj) {
|
|
1197
|
+
const built = cronExpressionFromFieldsAst(fieldsObj);
|
|
1198
|
+
if (built) {
|
|
1199
|
+
recordClockDecl(scope, program, call, name, {
|
|
1200
|
+
cron: built,
|
|
1201
|
+
...clockHelperOptsFromAst(fieldsObj),
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// clock.daily / hourly / weekly / monthly
|
|
1209
|
+
if (
|
|
1210
|
+
obj === "clock" &&
|
|
1211
|
+
(prop === "daily" || prop === "hourly" || prop === "weekly" || prop === "monthly")
|
|
1212
|
+
) {
|
|
1213
|
+
const name = stringArg(call.arguments[0]);
|
|
1214
|
+
const opts = objectArg(call.arguments[1]);
|
|
1215
|
+
if (name) {
|
|
1216
|
+
const cron = cronFromPresetHelper(prop, opts);
|
|
1217
|
+
if (cron) {
|
|
1218
|
+
recordClockDecl(scope, program, call, name, {
|
|
1219
|
+
cron,
|
|
1220
|
+
...clockHelperOptsFromAst(opts),
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1027
1223
|
}
|
|
1028
1224
|
}
|
|
1029
1225
|
|
|
@@ -1143,13 +1339,23 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1143
1339
|
const modelName = stringArg(call.arguments[0]);
|
|
1144
1340
|
const opts = objectArg(call.arguments[1]);
|
|
1145
1341
|
if (modelName) {
|
|
1342
|
+
const provider = stringProp(opts, "provider");
|
|
1343
|
+
const driverId = stringProp(opts, "driverId");
|
|
1344
|
+
const baseUrl = stringProp(opts, "baseUrl");
|
|
1146
1345
|
const model: AiModel = {
|
|
1147
|
-
...(
|
|
1346
|
+
...(provider ? { provider } : {}),
|
|
1148
1347
|
...(stringProp(opts, "tier") ? { tier: stringProp(opts, "tier") } : {}),
|
|
1149
|
-
...(
|
|
1348
|
+
...(driverId ? { driverId } : {}),
|
|
1150
1349
|
};
|
|
1151
1350
|
scope.ai.models = scope.ai.models ?? {};
|
|
1152
1351
|
scope.ai.models[modelName] = model;
|
|
1352
|
+
// Limited OpenAI-compat caveat — same honesty as declare-time warn.
|
|
1353
|
+
if (provider && !baseUrl && !(driverId && AI_NATIVE_DRIVER_IDS.has(driverId))) {
|
|
1354
|
+
const entry = getAiProviderEntry(provider);
|
|
1355
|
+
if (entry?.tier === 2 && entry.caveat) {
|
|
1356
|
+
console.warn(formatAiProviderTier2Warn(provider, entry.caveat, "[oke extract] warn"));
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1153
1359
|
const bindingName = enclosingConstName(call, program);
|
|
1154
1360
|
if (bindingName) {
|
|
1155
1361
|
scope.bindings.set(bindingName, {
|
|
@@ -1265,15 +1471,13 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1265
1471
|
}
|
|
1266
1472
|
}
|
|
1267
1473
|
}
|
|
1268
|
-
}
|
|
1269
1474
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
if (delivery) {
|
|
1475
|
+
// signal.once("name", opts?) / .broadcast / .live
|
|
1476
|
+
if (obj === "signal" && (prop === "once" || prop === "broadcast" || prop === "live")) {
|
|
1477
|
+
const signalName = stringArg(call.arguments[0]);
|
|
1478
|
+
const opts = objectArg(call.arguments[1]);
|
|
1479
|
+
if (signalName) {
|
|
1480
|
+
const delivery = prop as SignalDelivery;
|
|
1277
1481
|
const retentionObj = objectProp(opts, "retention");
|
|
1278
1482
|
const maxAge = retentionObj ? stringProp(retentionObj, "maxAge") : undefined;
|
|
1279
1483
|
const maxCount = retentionObj ? numberProp(retentionObj, "maxCount") : undefined;
|
|
@@ -1314,7 +1518,7 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1314
1518
|
const name = stringArg(call.arguments[0]);
|
|
1315
1519
|
const opts = objectArg(call.arguments[1]);
|
|
1316
1520
|
if (name && opts) {
|
|
1317
|
-
scope
|
|
1521
|
+
recordClockDecl(scope, program, call, name, {
|
|
1318
1522
|
...(stringProp(opts, "every") ? { every: stringProp(opts, "every") } : {}),
|
|
1319
1523
|
...(stringProp(opts, "cron") ? { cron: stringProp(opts, "cron") } : {}),
|
|
1320
1524
|
...(boolProp(opts, "overridable") !== undefined
|
|
@@ -1324,7 +1528,7 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
1324
1528
|
? { description: stringProp(opts, "description") }
|
|
1325
1529
|
: {}),
|
|
1326
1530
|
...(boolProp(opts, "perTenant") === true ? { perTenant: true } : {}),
|
|
1327
|
-
};
|
|
1531
|
+
});
|
|
1328
1532
|
}
|
|
1329
1533
|
}
|
|
1330
1534
|
|
|
@@ -1582,8 +1786,8 @@ function collectFlows(file: SourceFile, program: AstNode, scope: ProjectScope):
|
|
|
1582
1786
|
return;
|
|
1583
1787
|
}
|
|
1584
1788
|
|
|
1585
|
-
if (callee === "flow") {
|
|
1586
|
-
// Bare flow — skip when this call is the second arg of an on().
|
|
1789
|
+
if (callee === "flow" || callee === "call") {
|
|
1790
|
+
// Bare flow / call — skip when this call is the second arg of an on().
|
|
1587
1791
|
if (isOnFlowArgument(call, program)) return;
|
|
1588
1792
|
const exportName = enclosingConstName(call, program);
|
|
1589
1793
|
registerFlow({
|
|
@@ -1621,12 +1825,9 @@ function isOnFlowArgument(flowCall: CallExpression, program: AstNode): boolean {
|
|
|
1621
1825
|
}
|
|
1622
1826
|
|
|
1623
1827
|
function unwrapFlowCall(node: AstNode): CallExpression | undefined {
|
|
1624
|
-
if (
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
) {
|
|
1628
|
-
return node as CallExpression;
|
|
1629
|
-
}
|
|
1828
|
+
if (node.type !== "CallExpression") return undefined;
|
|
1829
|
+
const name = identifierName((node as CallExpression).callee);
|
|
1830
|
+
if (name === "flow" || name === "call") return node as CallExpression;
|
|
1630
1831
|
return undefined;
|
|
1631
1832
|
}
|
|
1632
1833
|
|
|
@@ -1709,12 +1910,17 @@ function registerFlow(args: {
|
|
|
1709
1910
|
}
|
|
1710
1911
|
if (gates && gates.length > 0) flow.gates = gates;
|
|
1711
1912
|
|
|
1712
|
-
const inSchema =
|
|
1913
|
+
const inSchema =
|
|
1914
|
+
schemaProp(opts, "in", args.scope, args.file.path) ??
|
|
1915
|
+
exposureSchemaProp(trigger?.contract, "in", args.scope, args.file.path);
|
|
1713
1916
|
if (inSchema !== undefined) flow.in = inSchema;
|
|
1714
|
-
const outSchema =
|
|
1917
|
+
const outSchema =
|
|
1918
|
+
schemaProp(opts, "out", args.scope, args.file.path) ??
|
|
1919
|
+
exposureSchemaProp(trigger?.contract, "out", args.scope, args.file.path);
|
|
1715
1920
|
if (outSchema !== undefined) flow.out = outSchema;
|
|
1716
1921
|
|
|
1717
|
-
const errors =
|
|
1922
|
+
const errors =
|
|
1923
|
+
parseErrors(objectProp(opts, "errors")) ?? parseErrors(objectProp(trigger?.contract, "errors"));
|
|
1718
1924
|
if (errors) flow.errors = errors;
|
|
1719
1925
|
|
|
1720
1926
|
if (effects) flow.effects = effects;
|
|
@@ -1738,7 +1944,7 @@ function registerFlow(args: {
|
|
|
1738
1944
|
};
|
|
1739
1945
|
}
|
|
1740
1946
|
}
|
|
1741
|
-
if (boolProp(opts, "breaking")) flow.breaking = true;
|
|
1947
|
+
if (boolProp(opts, "breaking") || boolProp(trigger?.contract, "breaking")) flow.breaking = true;
|
|
1742
1948
|
|
|
1743
1949
|
const slo = parseSlo(objectProp(opts, "slo"));
|
|
1744
1950
|
if (slo) flow.slo = slo;
|
|
@@ -2146,6 +2352,8 @@ interface ParsedTrigger {
|
|
|
2146
2352
|
live?: string;
|
|
2147
2353
|
/** Physical table name when `.live(tableBinding)` — manual live surface. */
|
|
2148
2354
|
liveTable?: string;
|
|
2355
|
+
/** Invoke contract bag authored on `http.*` / `mcp.tool`. */
|
|
2356
|
+
contract?: AstNode;
|
|
2149
2357
|
}
|
|
2150
2358
|
|
|
2151
2359
|
function parseTrigger(
|
|
@@ -2153,13 +2361,22 @@ function parseTrigger(
|
|
|
2153
2361
|
scope: ProjectScope,
|
|
2154
2362
|
filePath?: string,
|
|
2155
2363
|
): ParsedTrigger | undefined {
|
|
2156
|
-
// every("10m")
|
|
2157
2364
|
if (node.type === "CallExpression") {
|
|
2158
2365
|
const call = node as CallExpression;
|
|
2159
2366
|
const name = identifierName(call.callee);
|
|
2160
|
-
if (name === "
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2367
|
+
if (name === "clock") {
|
|
2368
|
+
const clockName = stringArg(call.arguments[0]);
|
|
2369
|
+
const opts = objectArg(call.arguments[1]);
|
|
2370
|
+
const cron = stringProp(opts, "cron");
|
|
2371
|
+
const every = stringProp(opts, "every");
|
|
2372
|
+
if (cron) return { trigger: { cron } };
|
|
2373
|
+
if (every) return { trigger: { every } };
|
|
2374
|
+
if (clockName) {
|
|
2375
|
+
const clockDef = scope.clocks[clockName];
|
|
2376
|
+
if (clockDef?.cron) return { trigger: { cron: clockDef.cron } };
|
|
2377
|
+
if (clockDef?.every) return { trigger: { every: clockDef.every } };
|
|
2378
|
+
return { trigger: { cron: clockName } };
|
|
2379
|
+
}
|
|
2163
2380
|
}
|
|
2164
2381
|
if (name === "internal") {
|
|
2165
2382
|
return { trigger: {} };
|
|
@@ -2173,8 +2390,8 @@ function parseTrigger(
|
|
|
2173
2390
|
const http = parseHttpTrigger(call, scope, filePath);
|
|
2174
2391
|
if (http) return http;
|
|
2175
2392
|
|
|
2176
|
-
// table(
|
|
2177
|
-
const cdc = parseCdcTrigger(call);
|
|
2393
|
+
// db.table(orders).changed(...) / <storeName>.table(orders).changed(...)
|
|
2394
|
+
const cdc = parseCdcTrigger(call, scope);
|
|
2178
2395
|
if (cdc) return { trigger: { cdc } };
|
|
2179
2396
|
|
|
2180
2397
|
// on(signalHandle, …) — Identifier referring to a signal binding
|
|
@@ -2191,6 +2408,18 @@ function parseTrigger(
|
|
|
2191
2408
|
const id = (node as Identifier).name;
|
|
2192
2409
|
if (id === "internal") return { trigger: {} };
|
|
2193
2410
|
const binding = scope.bindings.get(id);
|
|
2411
|
+
if (binding?.kind === "clock") {
|
|
2412
|
+
const clockDef = scope.clocks[binding.ref];
|
|
2413
|
+
if (clockDef?.cron) return { trigger: { cron: clockDef.cron } };
|
|
2414
|
+
if (clockDef?.every) return { trigger: { every: clockDef.every } };
|
|
2415
|
+
return { trigger: { cron: binding.ref } };
|
|
2416
|
+
}
|
|
2417
|
+
if (scope.clocks[id]) {
|
|
2418
|
+
const clockDef = scope.clocks[id];
|
|
2419
|
+
if (clockDef?.cron) return { trigger: { cron: clockDef.cron } };
|
|
2420
|
+
if (clockDef?.every) return { trigger: { every: clockDef.every } };
|
|
2421
|
+
return { trigger: { cron: id } };
|
|
2422
|
+
}
|
|
2194
2423
|
if (binding?.kind === "signal") {
|
|
2195
2424
|
return { trigger: { signal: binding.ref } };
|
|
2196
2425
|
}
|
|
@@ -2237,6 +2466,7 @@ function parseHttpTrigger(
|
|
|
2237
2466
|
let live: string | undefined;
|
|
2238
2467
|
/** Table name when `.live(tableBinding)` — a manual live query surface. */
|
|
2239
2468
|
let liveTable: string | undefined;
|
|
2469
|
+
let contract: AstNode | undefined;
|
|
2240
2470
|
const gateNames: string[] = [];
|
|
2241
2471
|
|
|
2242
2472
|
for (const node of chain) {
|
|
@@ -2269,7 +2499,14 @@ function parseHttpTrigger(
|
|
|
2269
2499
|
continue;
|
|
2270
2500
|
}
|
|
2271
2501
|
method = prop.toUpperCase();
|
|
2272
|
-
|
|
2502
|
+
const pathArg = c.arguments[0];
|
|
2503
|
+
const contractArg = c.arguments[1];
|
|
2504
|
+
if (stringArg(pathArg) !== undefined) {
|
|
2505
|
+
path = stringArg(pathArg);
|
|
2506
|
+
if (isBoundaryContractObject(contractArg)) contract = contractArg;
|
|
2507
|
+
} else if (isBoundaryContractObject(pathArg)) {
|
|
2508
|
+
contract = pathArg;
|
|
2509
|
+
}
|
|
2273
2510
|
continue;
|
|
2274
2511
|
}
|
|
2275
2512
|
|
|
@@ -2323,10 +2560,11 @@ function parseHttpTrigger(
|
|
|
2323
2560
|
gates: gateNames.length > 0 ? gateNames : undefined,
|
|
2324
2561
|
...(live !== undefined ? { live } : {}),
|
|
2325
2562
|
...(liveTable !== undefined ? { liveTable } : {}),
|
|
2563
|
+
...(contract !== undefined ? { contract } : {}),
|
|
2326
2564
|
};
|
|
2327
2565
|
}
|
|
2328
2566
|
|
|
2329
|
-
function parseCdcTrigger(call: CallExpression): Trigger["cdc"] | undefined {
|
|
2567
|
+
function parseCdcTrigger(call: CallExpression, scope?: ProjectScope): Trigger["cdc"] | undefined {
|
|
2330
2568
|
// *.changed("col") or *.changed()
|
|
2331
2569
|
const callee = call.callee;
|
|
2332
2570
|
if (callee.type !== "MemberExpression") return undefined;
|
|
@@ -2334,21 +2572,22 @@ function parseCdcTrigger(call: CallExpression): Trigger["cdc"] | undefined {
|
|
|
2334
2572
|
if (identifierName(member.property) !== "changed") return undefined;
|
|
2335
2573
|
|
|
2336
2574
|
const column = stringArg(call.arguments[0]);
|
|
2337
|
-
// object is
|
|
2575
|
+
// object is db.table(orders) or <storeName>.table(orders)
|
|
2338
2576
|
const obj = member.object;
|
|
2339
2577
|
if (obj.type === "CallExpression") {
|
|
2340
2578
|
const inner = obj as CallExpression;
|
|
2341
2579
|
const innerCallee = inner.callee;
|
|
2342
|
-
if (identifierName(innerCallee) === "table") {
|
|
2343
|
-
const tableName = stringArg(inner.arguments[0]) ?? identifierName(inner.arguments[0]);
|
|
2344
|
-
if (!tableName) return undefined;
|
|
2345
|
-
return column ? { table: tableName, column } : { table: tableName };
|
|
2346
|
-
}
|
|
2347
2580
|
if (
|
|
2348
2581
|
innerCallee.type === "MemberExpression" &&
|
|
2349
2582
|
identifierName((innerCallee as AstNode & { property: AstNode }).property) === "table"
|
|
2350
2583
|
) {
|
|
2351
|
-
const
|
|
2584
|
+
const argId = identifierName(inner.arguments[0]);
|
|
2585
|
+
const resolvedName = argId
|
|
2586
|
+
? scope?.bindings.get(argId)?.kind === "table"
|
|
2587
|
+
? scope.bindings.get(argId)?.ref
|
|
2588
|
+
: argId
|
|
2589
|
+
: undefined;
|
|
2590
|
+
const tableName = stringArg(inner.arguments[0]) ?? resolvedName;
|
|
2352
2591
|
if (!tableName) return undefined;
|
|
2353
2592
|
return column ? { table: tableName, column } : { table: tableName };
|
|
2354
2593
|
}
|
|
@@ -2368,6 +2607,7 @@ function stripTriggerExtras(trigger: Trigger): Trigger {
|
|
|
2368
2607
|
function parseMcpTrigger(leaf: AstNode, scope: ProjectScope): ParsedTrigger | undefined {
|
|
2369
2608
|
const chain = flattenMemberCallChain(leaf);
|
|
2370
2609
|
let toolName: string | undefined;
|
|
2610
|
+
let contract: AstNode | undefined;
|
|
2371
2611
|
const gateNames: string[] = [];
|
|
2372
2612
|
|
|
2373
2613
|
for (const node of chain) {
|
|
@@ -2383,6 +2623,7 @@ function parseMcpTrigger(leaf: AstNode, scope: ProjectScope): ParsedTrigger | un
|
|
|
2383
2623
|
(member.object as Identifier).name === "mcp"
|
|
2384
2624
|
) {
|
|
2385
2625
|
toolName = stringArg(c.arguments[0]);
|
|
2626
|
+
if (isBoundaryContractObject(c.arguments[1])) contract = c.arguments[1];
|
|
2386
2627
|
continue;
|
|
2387
2628
|
}
|
|
2388
2629
|
if (prop === "gate") {
|
|
@@ -2396,6 +2637,7 @@ function parseMcpTrigger(leaf: AstNode, scope: ProjectScope): ParsedTrigger | un
|
|
|
2396
2637
|
return {
|
|
2397
2638
|
trigger: { mcp: { name: toolName } },
|
|
2398
2639
|
gates: gateNames.length > 0 ? gateNames : undefined,
|
|
2640
|
+
...(contract !== undefined ? { contract } : {}),
|
|
2399
2641
|
};
|
|
2400
2642
|
}
|
|
2401
2643
|
|
|
@@ -2407,15 +2649,19 @@ function parseEffectsObject(node: AstNode | undefined): Effects | undefined {
|
|
|
2407
2649
|
const emits = stringArrayProp(node, "emits");
|
|
2408
2650
|
const sends = stringArrayProp(node, "sends");
|
|
2409
2651
|
const asks = stringArrayProp(node, "asks");
|
|
2652
|
+
const embeds = stringArrayProp(node, "embeds");
|
|
2410
2653
|
const secrets = stringArrayProp(node, "secrets");
|
|
2411
2654
|
const calls = stringArrayProp(node, "calls");
|
|
2655
|
+
const fetches = stringArrayProp(node, "fetches");
|
|
2412
2656
|
if (reads) effects.reads = reads as Effects["reads"];
|
|
2413
2657
|
if (writes) effects.writes = writes as Effects["writes"];
|
|
2414
2658
|
if (emits) effects.emits = emits;
|
|
2415
2659
|
if (sends) effects.sends = sends;
|
|
2416
2660
|
if (asks) effects.asks = asks;
|
|
2661
|
+
if (embeds) effects.embeds = embeds;
|
|
2417
2662
|
if (secrets) effects.secrets = secrets;
|
|
2418
2663
|
if (calls) effects.calls = calls;
|
|
2664
|
+
if (fetches) effects.fetches = fetches;
|
|
2419
2665
|
return effects;
|
|
2420
2666
|
}
|
|
2421
2667
|
|
|
@@ -2526,6 +2772,26 @@ function schemaProp(
|
|
|
2526
2772
|
return "…";
|
|
2527
2773
|
}
|
|
2528
2774
|
|
|
2775
|
+
/** Read invoke contract fields from an exposure bag (`http.*` / `mcp.tool`). */
|
|
2776
|
+
function exposureSchemaProp(
|
|
2777
|
+
contract: AstNode | undefined,
|
|
2778
|
+
key: string,
|
|
2779
|
+
scope: ProjectScope,
|
|
2780
|
+
filePath: string,
|
|
2781
|
+
): JsonSchema | undefined {
|
|
2782
|
+
return schemaProp(contract, key, scope, filePath);
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
/** True when an AST node looks like a {@link BoundaryContract} bag (not a path). */
|
|
2786
|
+
function isBoundaryContractObject(node: AstNode | undefined): node is AstNode {
|
|
2787
|
+
if (!node || node.type !== "ObjectExpression") return false;
|
|
2788
|
+
for (const prop of objectProperties(node)) {
|
|
2789
|
+
const key = propKey(prop);
|
|
2790
|
+
if (key === "in" || key === "out" || key === "errors" || key === "breaking") return true;
|
|
2791
|
+
}
|
|
2792
|
+
return false;
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2529
2795
|
// ── AST helpers ────────────────────────────────────────────────────────────
|
|
2530
2796
|
|
|
2531
2797
|
/** Leaf-to-root walk of `http.get("/x").public().live(signal)`. */
|
|
@@ -2648,10 +2914,148 @@ function objectArg(node: AstNode | undefined): AstNode | undefined {
|
|
|
2648
2914
|
return undefined;
|
|
2649
2915
|
}
|
|
2650
2916
|
|
|
2917
|
+
/** Shared helper options (`timezone` is runtime-only — Manifest Clock has no zone field). */
|
|
2918
|
+
function clockHelperOptsFromAst(opts: AstNode | undefined): {
|
|
2919
|
+
readonly overridable?: boolean;
|
|
2920
|
+
readonly description?: string;
|
|
2921
|
+
readonly perTenant?: boolean;
|
|
2922
|
+
} {
|
|
2923
|
+
return {
|
|
2924
|
+
...(boolProp(opts, "overridable") !== undefined
|
|
2925
|
+
? { overridable: boolProp(opts, "overridable") }
|
|
2926
|
+
: {}),
|
|
2927
|
+
...(stringProp(opts, "description") ? { description: stringProp(opts, "description") } : {}),
|
|
2928
|
+
...(boolProp(opts, "perTenant") === true ? { perTenant: true } : {}),
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
/**
|
|
2933
|
+
* Record a clock into the extract scope + binding map.
|
|
2934
|
+
*
|
|
2935
|
+
* @param scope - Project scope
|
|
2936
|
+
* @param program - AST program
|
|
2937
|
+
* @param call - Declaration call
|
|
2938
|
+
* @param name - Clock name
|
|
2939
|
+
* @param entry - Partial Manifest clock
|
|
2940
|
+
*/
|
|
2941
|
+
function recordClockDecl(
|
|
2942
|
+
scope: ProjectScope,
|
|
2943
|
+
program: AstNode,
|
|
2944
|
+
call: CallExpression,
|
|
2945
|
+
name: string,
|
|
2946
|
+
entry: Clock,
|
|
2947
|
+
): void {
|
|
2948
|
+
scope.clocks[name] = entry;
|
|
2949
|
+
const bindingName = enclosingConstName(call, program);
|
|
2950
|
+
if (bindingName) {
|
|
2951
|
+
scope.bindings.set(bindingName, {
|
|
2952
|
+
kind: "clock",
|
|
2953
|
+
ref: name,
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
/**
|
|
2959
|
+
* Read a structured cron field from an AST node (literal or array of literals).
|
|
2960
|
+
*
|
|
2961
|
+
* @param node - Property value
|
|
2962
|
+
*/
|
|
2963
|
+
function cronFieldFromAst(node: AstNode | undefined): CronField | undefined {
|
|
2964
|
+
if (!node) return undefined;
|
|
2965
|
+
if (node.type === "Literal") {
|
|
2966
|
+
const v = (node as Literal).value;
|
|
2967
|
+
if (typeof v === "number" || typeof v === "string") return v;
|
|
2968
|
+
return undefined;
|
|
2969
|
+
}
|
|
2970
|
+
if (node.type === "ArrayExpression") {
|
|
2971
|
+
const els = (node as AstNode & { elements?: AstNode[] }).elements ?? [];
|
|
2972
|
+
const out: Array<number | string> = [];
|
|
2973
|
+
for (const el of els) {
|
|
2974
|
+
if (!el || el.type !== "Literal") return undefined;
|
|
2975
|
+
const v = (el as Literal).value;
|
|
2976
|
+
if (typeof v !== "number" && typeof v !== "string") return undefined;
|
|
2977
|
+
out.push(v);
|
|
2978
|
+
}
|
|
2979
|
+
return out;
|
|
2980
|
+
}
|
|
2981
|
+
return undefined;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
/**
|
|
2985
|
+
* Build a cron expression from a structured fields object literal.
|
|
2986
|
+
*
|
|
2987
|
+
* @param obj - ObjectExpression with CronFields keys
|
|
2988
|
+
*/
|
|
2989
|
+
function cronExpressionFromFieldsAst(obj: AstNode): string | undefined {
|
|
2990
|
+
const fields: {
|
|
2991
|
+
minute?: CronField;
|
|
2992
|
+
hour?: CronField;
|
|
2993
|
+
dayOfMonth?: CronField;
|
|
2994
|
+
month?: CronField;
|
|
2995
|
+
dayOfWeek?: CronField;
|
|
2996
|
+
at?: string;
|
|
2997
|
+
} = {};
|
|
2998
|
+
const minute = cronFieldFromAst(objectProp(obj, "minute"));
|
|
2999
|
+
const hour = cronFieldFromAst(objectProp(obj, "hour"));
|
|
3000
|
+
const dayOfMonth = cronFieldFromAst(objectProp(obj, "dayOfMonth"));
|
|
3001
|
+
const month = cronFieldFromAst(objectProp(obj, "month"));
|
|
3002
|
+
const dayOfWeek = cronFieldFromAst(objectProp(obj, "dayOfWeek"));
|
|
3003
|
+
const at = stringProp(obj, "at");
|
|
3004
|
+
if (minute !== undefined) fields.minute = minute;
|
|
3005
|
+
if (hour !== undefined) fields.hour = hour;
|
|
3006
|
+
if (dayOfMonth !== undefined) fields.dayOfMonth = dayOfMonth;
|
|
3007
|
+
if (month !== undefined) fields.month = month;
|
|
3008
|
+
if (dayOfWeek !== undefined) fields.dayOfWeek = dayOfWeek;
|
|
3009
|
+
if (at !== undefined) fields.at = at;
|
|
3010
|
+
try {
|
|
3011
|
+
return buildCronExpression(fields);
|
|
3012
|
+
} catch {
|
|
3013
|
+
return undefined;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
/**
|
|
3018
|
+
* Compile a preset helper (`daily` / `hourly` / `weekly` / `monthly`) to cron.
|
|
3019
|
+
*
|
|
3020
|
+
* @param prop - Helper name
|
|
3021
|
+
* @param opts - Options object literal
|
|
3022
|
+
*/
|
|
3023
|
+
function cronFromPresetHelper(
|
|
3024
|
+
prop: "daily" | "hourly" | "weekly" | "monthly",
|
|
3025
|
+
opts: AstNode | undefined,
|
|
3026
|
+
): string | undefined {
|
|
3027
|
+
try {
|
|
3028
|
+
if (prop === "daily") {
|
|
3029
|
+
return buildCronExpression({ at: stringProp(opts, "at") ?? "00:00" });
|
|
3030
|
+
}
|
|
3031
|
+
if (prop === "hourly") {
|
|
3032
|
+
const minute = numberProp(opts, "minute") ?? 0;
|
|
3033
|
+
return buildCronExpression({ minute });
|
|
3034
|
+
}
|
|
3035
|
+
if (prop === "weekly") {
|
|
3036
|
+
const on = cronFieldFromAst(objectProp(opts, "on"));
|
|
3037
|
+
if (on === undefined) return undefined;
|
|
3038
|
+
return buildCronExpression({
|
|
3039
|
+
at: stringProp(opts, "at") ?? "00:00",
|
|
3040
|
+
dayOfWeek: on,
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
const on = cronFieldFromAst(objectProp(opts, "on"));
|
|
3044
|
+
if (on === undefined) return undefined;
|
|
3045
|
+
return buildCronExpression({
|
|
3046
|
+
at: stringProp(opts, "at") ?? "00:00",
|
|
3047
|
+
dayOfMonth: on,
|
|
3048
|
+
});
|
|
3049
|
+
} catch {
|
|
3050
|
+
return undefined;
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
|
|
2651
3054
|
/**
|
|
2652
|
-
* `flow("name", { do })`
|
|
3055
|
+
* `flow("name", { do })` / `call("name", { do })` options live in arg1;
|
|
3056
|
+
* `flow({ do })` in arg0.
|
|
2653
3057
|
*
|
|
2654
|
-
* @param flowCall - `flow(...)` AST node
|
|
3058
|
+
* @param flowCall - `flow(...)` / `call(...)` AST node
|
|
2655
3059
|
*/
|
|
2656
3060
|
function flowOptionsArg(flowCall: CallExpression): AstNode | undefined {
|
|
2657
3061
|
const named = objectArg(flowCall.arguments[1]);
|
|
@@ -2787,6 +3191,54 @@ function applyOkeStoreLiveConfig(opts: AstNode | undefined, scope: ProjectScope)
|
|
|
2787
3191
|
scope.store = { ...scope.store, live: true };
|
|
2788
3192
|
}
|
|
2789
3193
|
|
|
3194
|
+
/**
|
|
3195
|
+
* Stamp project default for field `.embed()` from
|
|
3196
|
+
* `oke({ store: { search: { embed: { model, dims } } } })`.
|
|
3197
|
+
*
|
|
3198
|
+
* @param opts - `oke({…})` options object
|
|
3199
|
+
* @param scope - Project scope
|
|
3200
|
+
*/
|
|
3201
|
+
function applyOkeStoreSearchConfig(opts: AstNode | undefined, scope: ProjectScope): void {
|
|
3202
|
+
const store = objectProp(opts, "store");
|
|
3203
|
+
const search = objectProp(store, "search");
|
|
3204
|
+
const embed = objectProp(search, "embed");
|
|
3205
|
+
if (!embed) return;
|
|
3206
|
+
const dims = numberProp(embed, "dims");
|
|
3207
|
+
if (dims === undefined || !(Number.isInteger(dims) && dims > 0)) {
|
|
3208
|
+
throw new Error(
|
|
3209
|
+
`extract: oke({ store: { search: { embed } } }) requires dims: positive integer`,
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
const modelNode = objectProp(embed, "model");
|
|
3213
|
+
let model: string | undefined = stringArg(modelNode);
|
|
3214
|
+
if (!model && modelNode) {
|
|
3215
|
+
const bindingName = identifierName(modelNode);
|
|
3216
|
+
if (bindingName) {
|
|
3217
|
+
const binding = scope.bindings.get(bindingName);
|
|
3218
|
+
model = binding?.ref ?? bindingName;
|
|
3219
|
+
} else if (modelNode.type === "MemberExpression") {
|
|
3220
|
+
const prop = identifierName((modelNode as AstNode & { property: AstNode }).property);
|
|
3221
|
+
if (prop === "name") {
|
|
3222
|
+
const obj = (modelNode as AstNode & { object: AstNode }).object;
|
|
3223
|
+
const objName = identifierName(obj);
|
|
3224
|
+
if (objName) {
|
|
3225
|
+
const binding = scope.bindings.get(objName);
|
|
3226
|
+
model = binding?.ref ?? objName;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
if (!model) {
|
|
3232
|
+
throw new Error(
|
|
3233
|
+
`extract: oke({ store: { search: { embed } } }) requires model (ai.model handle or name string)`,
|
|
3234
|
+
);
|
|
3235
|
+
}
|
|
3236
|
+
scope.store = {
|
|
3237
|
+
...scope.store,
|
|
3238
|
+
search: { embed: { model, dims } },
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
|
|
2790
3242
|
/**
|
|
2791
3243
|
* Default every SQL table to live when `oke({ store: { live: true } })` is
|
|
2792
3244
|
* on, unless the table explicitly opted out with `store.schema.live(false)`.
|
|
@@ -2881,6 +3333,72 @@ function assertTenantSafeSchema(manifest: Manifest): void {
|
|
|
2881
3333
|
}
|
|
2882
3334
|
}
|
|
2883
3335
|
|
|
3336
|
+
/**
|
|
3337
|
+
* Inherit `oke({ store: { search: { embed } } })` onto every column that
|
|
3338
|
+
* called `.embed()`, then stamp concrete `{ model, dims }` on the Manifest.
|
|
3339
|
+
* Per-field values win when set.
|
|
3340
|
+
*/
|
|
3341
|
+
function resolveSearchEmbedDefaults(manifest: Manifest): void {
|
|
3342
|
+
const project = manifest.store?.search?.embed;
|
|
3343
|
+
for (const store of Object.values(manifest.stores ?? {})) {
|
|
3344
|
+
if (store.facet !== "sql" || !store.tables) continue;
|
|
3345
|
+
for (const [tableName, table] of Object.entries(store.tables)) {
|
|
3346
|
+
for (const [colName, col] of Object.entries(table.columns ?? {})) {
|
|
3347
|
+
if (!col || typeof col !== "object") continue;
|
|
3348
|
+
const declared = col as DeclaredColumn;
|
|
3349
|
+
// `embed: {}` is stamped when `.embed()` was called with no options.
|
|
3350
|
+
if (declared.embed === undefined) continue;
|
|
3351
|
+
const model = declared.embed.model ?? project?.model;
|
|
3352
|
+
const dims = declared.embed.dims ?? project?.dims;
|
|
3353
|
+
if (!model || dims === undefined || !(Number.isInteger(dims) && dims > 0)) {
|
|
3354
|
+
throw new SearchConfigError(
|
|
3355
|
+
tableName,
|
|
3356
|
+
colName,
|
|
3357
|
+
`.embed() needs model and dims — set oke({ store: { search: { embed: { model, dims } } } }) or pass them on .embed({ model, dims })`,
|
|
3358
|
+
);
|
|
3359
|
+
}
|
|
3360
|
+
declared.embed = { model, dims };
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
/**
|
|
3367
|
+
* Fail-loud hybrid-search config: `.embed()` requires an `ai` element;
|
|
3368
|
+
* `.embed()` without `searchable` is already rejected in parseFieldChain.
|
|
3369
|
+
* Call after {@link resolveSearchEmbedDefaults} so columns carry concrete dims.
|
|
3370
|
+
*/
|
|
3371
|
+
function assertSearchConfig(manifest: Manifest): void {
|
|
3372
|
+
const hasAi =
|
|
3373
|
+
manifest.ai !== undefined &&
|
|
3374
|
+
(Object.keys(manifest.ai.models ?? {}).length > 0 ||
|
|
3375
|
+
Object.keys(manifest.ai.prompts ?? {}).length > 0 ||
|
|
3376
|
+
Object.keys(manifest.ai.agents ?? {}).length > 0);
|
|
3377
|
+
for (const store of Object.values(manifest.stores ?? {})) {
|
|
3378
|
+
if (store.facet !== "sql" || !store.tables) continue;
|
|
3379
|
+
for (const [tableName, table] of Object.entries(store.tables)) {
|
|
3380
|
+
for (const [colName, col] of Object.entries(table.columns ?? {})) {
|
|
3381
|
+
if (!col || typeof col !== "object") continue;
|
|
3382
|
+
const declared = col as DeclaredColumn;
|
|
3383
|
+
if (declared.embed && !hasAi) {
|
|
3384
|
+
throw new SearchConfigError(
|
|
3385
|
+
tableName,
|
|
3386
|
+
colName,
|
|
3387
|
+
`.embed() requires a configured ai element (ai.model / ai.embed). Remove .embed() for BM25-only search, or declare an embedding model.`,
|
|
3388
|
+
);
|
|
3389
|
+
}
|
|
3390
|
+
if (declared.embed && !declared.searchable) {
|
|
3391
|
+
throw new SearchConfigError(
|
|
3392
|
+
tableName,
|
|
3393
|
+
colName,
|
|
3394
|
+
`.embed() requires a prior .searchable() on the same field`,
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
|
|
2884
3402
|
function sortRecord<T>(record: Record<string, T>): Record<string, T> {
|
|
2885
3403
|
const out: Record<string, T> = {};
|
|
2886
3404
|
for (const key of Object.keys(record).sort()) {
|