@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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/dist/cli/serve.js +4 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +2 -2
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/serve/bundler.d.ts +13 -8
- package/dist/serve/bundler.d.ts.map +1 -1
- package/dist/serve/bundler.js +31 -36
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
- package/dist/serve/docs-ui/docs/api-reference.md +1 -1
- package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
- package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
- package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
- package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
- package/dist/serve/docs-ui/docs/index.json +349 -29
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/docs-ui/docs/overview/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
- package/dist/serve/docs-ui/docs/tutorial.md +701 -0
- package/dist/serve/docs-ui/llms.txt +3 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +3 -0
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +20 -5
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/worker-runtime.d.ts +2 -2
- package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
- package/dist/serve/entries/worker-runtime.js +45 -10
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts +19 -0
- package/dist/serve/runtime/chip-config.d.ts.map +1 -0
- package/dist/serve/runtime/chip-config.js +19 -0
- package/dist/serve/runtime/chip-config.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +10 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -0
- package/dist/serve/runtime/chip-install.js +15 -0
- package/dist/serve/runtime/chip-install.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +17 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -0
- package/dist/serve/runtime/chip.js +257 -0
- package/dist/serve/runtime/chip.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +22 -0
- package/dist/serve/runtime/manifest.d.ts.map +1 -0
- package/dist/serve/runtime/manifest.js +20 -0
- package/dist/serve/runtime/manifest.js.map +1 -0
- package/dist/serve/runtime/status.d.ts +49 -0
- package/dist/serve/runtime/status.d.ts.map +1 -0
- package/dist/serve/runtime/status.js +164 -0
- package/dist/serve/runtime/status.js.map +1 -0
- package/dist/serve/runtime/worker-connection.d.ts +4 -0
- package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
- package/dist/serve/runtime/worker-connection.js +11 -0
- package/dist/serve/runtime/worker-connection.js.map +1 -0
- package/dist/serve/runtime/worker-generation.d.ts +14 -0
- package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
- package/dist/serve/runtime/worker-generation.js +56 -0
- package/dist/serve/runtime/worker-generation.js.map +1 -0
- package/dist/serve/runtime/worker-replacement.d.ts +18 -0
- package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
- package/dist/serve/runtime/worker-replacement.js +29 -0
- package/dist/serve/runtime/worker-replacement.js.map +1 -0
- package/dist/serve/standalone-assets.d.ts +2 -2
- package/dist/serve/standalone-assets.d.ts.map +1 -1
- package/dist/serve/standalone-assets.js +1 -1
- package/dist/serve/standalone-assets.js.map +1 -1
- package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
- package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
- package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
- package/dist/serve/studio-ui/index.html +1 -1
- package/dist/serve/vite-ai-config.d.ts +25 -0
- package/dist/serve/vite-ai-config.d.ts.map +1 -0
- package/dist/serve/vite-ai-config.js +45 -0
- package/dist/serve/vite-ai-config.js.map +1 -0
- package/dist/serve/vite-plugin.d.ts +17 -36
- package/dist/serve/vite-plugin.d.ts.map +1 -1
- package/dist/serve/vite-plugin.js +38 -57
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-rules-source.d.ts +8 -0
- package/dist/serve/vite-rules-source.d.ts.map +1 -0
- package/dist/serve/vite-rules-source.js +23 -0
- package/dist/serve/vite-rules-source.js.map +1 -0
- package/dist/serve/vite-worker-runtime.d.ts +20 -0
- package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
- package/dist/serve/vite-worker-runtime.js +25 -0
- package/dist/serve/vite-worker-runtime.js.map +1 -0
- package/dist/serve/worker/client/connection.d.ts +7 -2
- package/dist/serve/worker/client/connection.d.ts.map +1 -1
- package/dist/serve/worker/client/connection.js +13 -16
- package/dist/serve/worker/client/connection.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +7 -0
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +28 -0
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/runtime-control.d.ts +8 -0
- package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
- package/dist/serve/worker/client/runtime-control.js +11 -0
- package/dist/serve/worker/client/runtime-control.js.map +1 -0
- package/dist/serve/worker/entry.js +44 -2
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.js +1 -1
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
- package/dist/serve/worker/host/firestore-writes.js +3 -2
- package/dist/serve/worker/host/firestore-writes.js.map +1 -1
- package/dist/serve/worker/host/studio.js +1 -1
- package/dist/serve/worker/host/studio.js.map +1 -1
- package/dist/serve/worker/host-context.d.ts +1 -1
- package/dist/serve/worker/host-context.d.ts.map +1 -1
- package/dist/serve/worker/index.d.ts +2 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -1
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/protocol.d.ts +15 -1
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js +2 -2
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/retirement.d.ts +21 -0
- package/dist/serve/worker/retirement.d.ts.map +1 -0
- package/dist/serve/worker/retirement.js +97 -0
- package/dist/serve/worker/retirement.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +5 -6
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/package.json +6 -4
- package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
- package/src/assurance/attachment.ts +211 -0
- package/src/assurance/browser.ts +80 -0
- package/src/assurance/campaign.ts +496 -0
- package/src/assurance/capabilities.ts +446 -0
- package/src/assurance/cases.ts +43 -0
- package/src/assurance/index.ts +75 -0
- package/src/assurance/runner.ts +885 -0
- package/src/assurance/tool-names.ts +15 -0
- package/src/assurance/tools.ts +830 -0
- package/src/assurance/types.ts +352 -0
- package/src/assurance/validation.ts +434 -0
- package/src/bridge/client/bridge.ts +550 -0
- package/src/bridge/client/dispatch.ts +137 -0
- package/src/bridge/client.ts +45 -0
- package/src/bridge/protocol.ts +351 -0
- package/src/bridge/server/audit.ts +53 -0
- package/src/bridge/server/bridge.ts +597 -0
- package/src/bridge/server/headless.ts +160 -0
- package/src/bridge/server/json-schema-to-zod.ts +108 -0
- package/src/bridge/server/local-bridge.ts +66 -0
- package/src/bridge/server/logger.ts +50 -0
- package/src/bridge/server/mcp-contract.ts +93 -0
- package/src/bridge/server/mcp.ts +129 -0
- package/src/bridge/server/peer.ts +232 -0
- package/src/bridge/server/standalone.ts +347 -0
- package/src/bridge/server/tool-metadata.ts +99 -0
- package/src/bridge/server.ts +28 -0
- package/src/cli/can-i-use.ts +50 -0
- package/src/cli/cli.test.ts +493 -0
- package/src/cli/database-rules.ts +329 -0
- package/src/cli/dev-runner.ts +282 -0
- package/src/cli/firebase-json.ts +100 -0
- package/src/cli/firestore-indexes.ts +70 -0
- package/src/cli/index.ts +425 -0
- package/src/cli/init.ts +260 -0
- package/src/cli/mcp-proxy.ts +196 -0
- package/src/cli/parse-args.ts +80 -0
- package/src/cli/rules.ts +259 -0
- package/src/cli/scope.ts +67 -0
- package/src/cli/serve.ts +996 -0
- package/src/cli/service-commands.ts +73 -0
- package/src/cli/snapshot.ts +164 -0
- package/src/cli/storage-rules.ts +172 -0
- package/src/cli/verify.ts +416 -0
- package/src/conformance/.generated/can-i-use-browser.ts +113 -0
- package/src/conformance/.generated/can-i-use.ts +117 -0
- package/src/conformance/.generated/conformance-docs.ts +14 -0
- package/src/conformance/browser.ts +24 -0
- package/src/conformance/can-i-use-tool.ts +51 -0
- package/src/conformance/can-i-use.ts +18 -0
- package/src/conformance/docs.ts +8 -0
- package/src/conformance/index.ts +20 -0
- package/src/conformance/tools.ts +16 -0
- package/src/credentials/core/memoize-ttl.ts +142 -0
- package/src/credentials/core/types.ts +10 -0
- package/src/credentials/node/from-adc.ts +96 -0
- package/src/credentials/node/from-service-account.ts +133 -0
- package/src/credentials/node/index.ts +6 -0
- package/src/discover/concurrency.ts +127 -0
- package/src/discover/crawler-adapter.ts +142 -0
- package/src/discover/crawler.ts +1127 -0
- package/src/discover/credential-free.ts +27 -0
- package/src/discover/findCollectionGroup.ts +131 -0
- package/src/discover/firestore-source.ts +59 -0
- package/src/discover/index.ts +5 -0
- package/src/discover/merge.ts +523 -0
- package/src/discover/session.ts +402 -0
- package/src/discover/tools.ts +201 -0
- package/src/discover/types.ts +187 -0
- package/src/discover/wire.ts +324 -0
- package/src/functions-rtdb/child.ts +382 -0
- package/src/functions-rtdb/delivery.ts +7 -0
- package/src/functions-rtdb/discovery.ts +136 -0
- package/src/functions-rtdb/event.ts +64 -0
- package/src/functions-rtdb/execution.ts +111 -0
- package/src/functions-rtdb/in-memory-delivery.ts +40 -0
- package/src/functions-rtdb/project.ts +74 -0
- package/src/functions-rtdb/projection.ts +104 -0
- package/src/functions-rtdb/reference-pattern.ts +20 -0
- package/src/functions-rtdb/remote-delivery.ts +23 -0
- package/src/pkg-version.ts +49 -0
- package/src/register/esm-exports.ts +63 -0
- package/src/register/hooks.ts +30 -0
- package/src/register/index.ts +150 -0
- package/src/register/mapping.ts +33 -0
- package/src/remote/index.ts +1050 -0
- package/src/rtdb/crawl-snapshot.ts +118 -0
- package/src/rtdb/inspection.ts +137 -0
- package/src/rtdb/load-rules-document.ts +56 -0
- package/src/rtdb/rules-generation-tool.ts +38 -0
- package/src/rtdb/rules-json.ts +29 -0
- package/src/serve/activity-guard.ts +29 -0
- package/src/serve/activity-route.ts +164 -0
- package/src/serve/activity-warning.ts +26 -0
- package/src/serve/bridge-mount.ts +200 -0
- package/src/serve/bundler.ts +519 -0
- package/src/serve/capture-store.ts +65 -0
- package/src/serve/discovery.ts +199 -0
- package/src/serve/entries/ai.ts +292 -0
- package/src/serve/entries/app-backend.ts +4 -0
- package/src/serve/entries/app-client.ts +26 -0
- package/src/serve/entries/app-session-store.ts +47 -0
- package/src/serve/entries/app.ts +11 -0
- package/src/serve/entries/auth-helper-core.ts +181 -0
- package/src/serve/entries/auth-helper-dom.ts +134 -0
- package/src/serve/entries/auth-helper-runtime.ts +20 -0
- package/src/serve/entries/auth.ts +255 -0
- package/src/serve/entries/bridge-url.ts +31 -0
- package/src/serve/entries/database.ts +129 -0
- package/src/serve/entries/firestore.ts +232 -0
- package/src/serve/entries/init.ts +49 -0
- package/src/serve/entries/keepalive.ts +46 -0
- package/src/serve/entries/messaging-sw.ts +75 -0
- package/src/serve/entries/messaging.ts +96 -0
- package/src/serve/entries/runtime.ts +556 -0
- package/src/serve/entries/session-store.ts +97 -0
- package/src/serve/entries/storage.ts +92 -0
- package/src/serve/entries/tab-sync-wiring.ts +274 -0
- package/src/serve/entries/worker-runtime.ts +135 -0
- package/src/serve/init-payload.ts +42 -0
- package/src/serve/namespace.ts +675 -0
- package/src/serve/open-browser.ts +68 -0
- package/src/serve/rules.ts +272 -0
- package/src/serve/runtime/chip-config.ts +30 -0
- package/src/serve/runtime/chip-install.ts +27 -0
- package/src/serve/runtime/chip.ts +282 -0
- package/src/serve/runtime/manifest.ts +37 -0
- package/src/serve/runtime/status.ts +216 -0
- package/src/serve/runtime/worker-connection.ts +14 -0
- package/src/serve/runtime/worker-generation.ts +75 -0
- package/src/serve/runtime/worker-replacement.ts +42 -0
- package/src/serve/sandbox-marker.ts +31 -0
- package/src/serve/server.ts +428 -0
- package/src/serve/standalone-assets.ts +180 -0
- package/src/serve/state-store.ts +166 -0
- package/src/serve/studio/disk-project-store.ts +185 -0
- package/src/serve/studio/disk-workspace.ts +162 -0
- package/src/serve/studio/index.ts +20 -0
- package/src/serve/studio/routes.ts +237 -0
- package/src/serve/studio/store-types.ts +48 -0
- package/src/serve/studio/studio-storage.test.ts +289 -0
- package/src/serve/vite-ai-config.ts +77 -0
- package/src/serve/vite-plugin.ts +1075 -0
- package/src/serve/vite-rules-source.ts +28 -0
- package/src/serve/vite-worker-runtime.ts +51 -0
- package/src/serve/worker/activity-bootstrap.ts +24 -0
- package/src/serve/worker/client/admin-firestore.ts +38 -0
- package/src/serve/worker/client/ai.ts +124 -0
- package/src/serve/worker/client/auth.ts +489 -0
- package/src/serve/worker/client/connection.ts +227 -0
- package/src/serve/worker/client/core.ts +384 -0
- package/src/serve/worker/client/disconnect.ts +45 -0
- package/src/serve/worker/client/firestore-reads.ts +181 -0
- package/src/serve/worker/client/firestore-refs.ts +226 -0
- package/src/serve/worker/client/firestore-writes.ts +232 -0
- package/src/serve/worker/client/handles.ts +91 -0
- package/src/serve/worker/client/messaging.ts +119 -0
- package/src/serve/worker/client/presence.ts +174 -0
- package/src/serve/worker/client/rtdb.ts +311 -0
- package/src/serve/worker/client/rules.ts +63 -0
- package/src/serve/worker/client/runtime-control.ts +22 -0
- package/src/serve/worker/client/service-worker-connection.ts +59 -0
- package/src/serve/worker/client/snapshots.ts +86 -0
- package/src/serve/worker/client/storage.ts +195 -0
- package/src/serve/worker/client/studio.ts +88 -0
- package/src/serve/worker/client.ts +57 -0
- package/src/serve/worker/durable-persistence.ts +137 -0
- package/src/serve/worker/entry.ts +223 -0
- package/src/serve/worker/host/admin-firestore.ts +88 -0
- package/src/serve/worker/host/connection.ts +179 -0
- package/src/serve/worker/host/core.ts +387 -0
- package/src/serve/worker/host/dispatch.ts +316 -0
- package/src/serve/worker/host/firestore-reads.ts +117 -0
- package/src/serve/worker/host/firestore-writes.ts +456 -0
- package/src/serve/worker/host/presence.ts +312 -0
- package/src/serve/worker/host/rtdb.ts +136 -0
- package/src/serve/worker/host/rules.ts +128 -0
- package/src/serve/worker/host/storage.ts +312 -0
- package/src/serve/worker/host/studio.ts +75 -0
- package/src/serve/worker/host/subscriptions.ts +238 -0
- package/src/serve/worker/host-ai.ts +165 -0
- package/src/serve/worker/host-auth.ts +469 -0
- package/src/serve/worker/host-context.ts +273 -0
- package/src/serve/worker/host-events.ts +94 -0
- package/src/serve/worker/host-messaging.ts +240 -0
- package/src/serve/worker/host.ts +47 -0
- package/src/serve/worker/index.ts +196 -0
- package/src/serve/worker/presence-timing.ts +15 -0
- package/src/serve/worker/protocol.ts +1124 -0
- package/src/serve/worker/retirement.ts +125 -0
- package/src/serve/worker/serve-init.ts +605 -0
- package/src/serve/worker/service-worker-channel.ts +33 -0
- package/src/serve/worker/service-worker-relay.ts +86 -0
- package/src/serve/writer-lock.ts +53 -0
- package/src/verify/cases.ts +233 -0
- package/src/verify/fixture.ts +258 -0
- package/src/verify/index.ts +519 -0
- package/src/verify/tools.ts +108 -0
- package/src/version/compat-target.ts +17 -0
- package/src/vite.ts +18 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWorker host — shared foundation (context + reply helpers).
|
|
3
|
+
*
|
|
4
|
+
* The `HostCtx` is the single per-worker context shared across ALL connected
|
|
5
|
+
* ports and across the auth + event-stream subsystems. `PortLike`, `post`,
|
|
6
|
+
* `ok`, and `fail` are the minimal port + reply primitives every subsystem
|
|
7
|
+
* uses. This module imports NOTHING from the sibling `host-*` files, so it is
|
|
8
|
+
* the dependency root (no circular imports).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Firestore } from 'pyric/firestore';
|
|
12
|
+
import type { Database } from 'pyric/database';
|
|
13
|
+
import type { LocalSandbox, PersistenceBackend } from 'pyric/sandbox';
|
|
14
|
+
import type { Auth, MintedSession } from 'pyric/auth';
|
|
15
|
+
import type { FirebaseStorage } from 'pyric/storage';
|
|
16
|
+
import {
|
|
17
|
+
serializeError,
|
|
18
|
+
type OutboundMessage,
|
|
19
|
+
type AuthPersistenceMode,
|
|
20
|
+
type AiEngineConfigWire,
|
|
21
|
+
} from './protocol.js';
|
|
22
|
+
|
|
23
|
+
// ─── AI broker (structural view) ──────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The worker host's view of `pyric/ai`'s `AiBroker` — the in-process
|
|
27
|
+
* Gemini-wire answer seam (cdd-deltas #98.1: the broker lives with the
|
|
28
|
+
* sandbox, i.e. HERE in the serve worker host). Structural because the
|
|
29
|
+
* `AiBroker` class is not exported from `pyric/ai`'s public surface; the
|
|
30
|
+
* host recovers the instance from a `getAI(sandbox)` handle via
|
|
31
|
+
* `TARGET_SYMBOL` (see host-ai.ts). Requests/responses are the plain
|
|
32
|
+
* Gemini-wire JSON the protocol's ai ops carry.
|
|
33
|
+
*/
|
|
34
|
+
export interface AiBrokerLike {
|
|
35
|
+
generateContent(req: Record<string, unknown>, model: string): Promise<Record<string, unknown>>;
|
|
36
|
+
streamGenerateContent(req: Record<string, unknown>, model: string): AsyncIterable<Record<string, unknown>>;
|
|
37
|
+
countTokens(req: Record<string, unknown>, model: string): Promise<Record<string, unknown>>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Port interface ───────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/** Minimal postMessage interface. Fake ports in tests implement this. */
|
|
43
|
+
export interface PortLike {
|
|
44
|
+
postMessage(msg: OutboundMessage): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Stable worker-lifetime activity identity for an app page/port. */
|
|
48
|
+
export function activityJourneyId(ctx: HostCtx, port: PortLike): string {
|
|
49
|
+
const ids = (ctx.activityJourneyIds ??= new WeakMap());
|
|
50
|
+
const existing = ids.get(port);
|
|
51
|
+
if (existing) return existing;
|
|
52
|
+
const id = `page-${(ctx.activityJourneySequence = (ctx.activityJourneySequence ?? 0) + 1)}`;
|
|
53
|
+
ids.set(port, id);
|
|
54
|
+
return id;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Host context ─────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export interface HostCtx {
|
|
60
|
+
/** The single shared Firestore handle (sandbox-live — reads currentUser per op). */
|
|
61
|
+
db: Firestore;
|
|
62
|
+
/** The underlying Sandbox, for setRules + direct sandbox ops. */
|
|
63
|
+
sandbox: LocalSandbox;
|
|
64
|
+
/** Stable worker-lifetime activity identity for each page/app port. */
|
|
65
|
+
activityJourneyIds?: WeakMap<PortLike, string>;
|
|
66
|
+
activityJourneySequence?: number;
|
|
67
|
+
/** Stable per-SharedWorker instance id (see {@link INSTANCE_ID_KEY}). Reported
|
|
68
|
+
* via `getVersion` so the UI can identify which sandbox instance this is. */
|
|
69
|
+
instanceId: string;
|
|
70
|
+
/** The shared Storage handle (Pyric Studio data browse). Lazily created on
|
|
71
|
+
* the first storage op via `getStorageSandbox(sandbox)`. */
|
|
72
|
+
storage?: FirebaseStorage;
|
|
73
|
+
/** Shared RTDB handle. Lazily created for the playground shared-runtime path. */
|
|
74
|
+
rtdb?: Database;
|
|
75
|
+
/** Cached admin (rules-bypass) RTDB handle for Studio/Playground data inspection. */
|
|
76
|
+
adminRtdb?: Database;
|
|
77
|
+
/** Cached admin (rules-bypass) Storage handle for the `{ mode: 'admin' }`
|
|
78
|
+
* lens — `pyric/storage/internal`'s `getAdminStorageSandbox`, the handle
|
|
79
|
+
* the pyric-admin remote arm's storage ops resolve to. */
|
|
80
|
+
adminStorage?: FirebaseStorage;
|
|
81
|
+
/** Per-uid/token Storage handles for the impersonation lens (rules evaluate
|
|
82
|
+
* as that user). Mirrors {@link HostCtx.lensRtdbs}. */
|
|
83
|
+
lensStorages?: Map<string, FirebaseStorage>;
|
|
84
|
+
/** Per-session Storage handles so app-session rules see the initiating
|
|
85
|
+
* port's authenticated identity. */
|
|
86
|
+
sessionStorages?: Map<string, FirebaseStorage>;
|
|
87
|
+
/** Cached genuinely-UNAUTHENTICATED Firestore handle for the
|
|
88
|
+
* `{ mode: 'anon' }` lens — `getFirestore(sandbox.withAuth(null))`, so
|
|
89
|
+
* rules evaluate with `request.auth == null`. The remote arm's
|
|
90
|
+
* `withAuth(null)`; distinct from an absent lens (the port's session). */
|
|
91
|
+
anonDb?: Firestore;
|
|
92
|
+
/** Cached unauthenticated RTDB handle for the `{ mode: 'anon' }` lens. */
|
|
93
|
+
anonRtdb?: Database;
|
|
94
|
+
/** Cached unauthenticated Storage handle for the `{ mode: 'anon' }` lens. */
|
|
95
|
+
anonStorage?: FirebaseStorage;
|
|
96
|
+
/** Per-uid RTDB handles carrying a port session's real identity. */
|
|
97
|
+
sessionRtdbs?: Map<string, Database>;
|
|
98
|
+
/** Per-uid/token RTDB handles for the Studio impersonation lens. */
|
|
99
|
+
lensRtdbs?: Map<string, Database>;
|
|
100
|
+
/** Current active rules metadata for shared-runtime diagnostics and revert. */
|
|
101
|
+
activeRules?: {
|
|
102
|
+
firestore?: ActiveRulesState;
|
|
103
|
+
database?: ActiveRulesState;
|
|
104
|
+
};
|
|
105
|
+
/** Per-port subscription registry. Map<port, Map<subId, unsub>>. */
|
|
106
|
+
subs: Map<PortLike, Map<string, () => void>>;
|
|
107
|
+
/** Ports whose app containers completed explicit deletion. */
|
|
108
|
+
disconnectedPorts?: WeakSet<PortLike>;
|
|
109
|
+
/** Firebase options accepted by the first app-owned port. All named apps
|
|
110
|
+
* and tabs share this one backend, so later app ports must match it. */
|
|
111
|
+
appOptions?: Record<string, unknown>;
|
|
112
|
+
/** App ports that attempted to attach a different Firebase configuration. */
|
|
113
|
+
rejectedConfigPorts?: WeakSet<PortLike>;
|
|
114
|
+
/**
|
|
115
|
+
* The ONE shared auth handle for the worker — the USER POOL (who exists)
|
|
116
|
+
* and the admin surface. Lazily created on first auth op or auth
|
|
117
|
+
* subscription. Sessions (who a connection IS) are per-port: see
|
|
118
|
+
* {@link HostCtx.portSessions} (#754). The handle's own `currentUser`
|
|
119
|
+
* stays null in served mode.
|
|
120
|
+
*/
|
|
121
|
+
auth?: Auth;
|
|
122
|
+
/**
|
|
123
|
+
* Per-connection identity (#754): each port's signed-in session, minted
|
|
124
|
+
* via `sandbox.mintSession` (an authentic session — validated
|
|
125
|
+
* credentials / a really-minted identity, NOT the impersonation lens).
|
|
126
|
+
* A port absent from the map, or mapped to null, is signed out. The
|
|
127
|
+
* user pool + data + rules stay shared across ports.
|
|
128
|
+
*/
|
|
129
|
+
portSessions?: Map<PortLike, MintedSession | null>;
|
|
130
|
+
/**
|
|
131
|
+
* Re-establish a port's session-bound Firestore listeners under its NEW
|
|
132
|
+
* session (#754) — set lazily by host.ts (which owns subs) and invoked by
|
|
133
|
+
* host-auth.ts on every port session change, mirroring prod's stream
|
|
134
|
+
* re-establishment on auth transitions: a sign-out re-evaluates live
|
|
135
|
+
* listeners so an auth-gated stream loses access instead of leaking the
|
|
136
|
+
* previous user's data. (A ctx hook, not an import — host-auth must not
|
|
137
|
+
* import host.ts.)
|
|
138
|
+
*/
|
|
139
|
+
resubscribePortSubs?: (port: PortLike) => void;
|
|
140
|
+
/**
|
|
141
|
+
* Per-session-token Firestore handles carrying a PORT SESSION's identity —
|
|
142
|
+
* `getFirestore(sandbox.withAuth(session.state))`, so rules see the
|
|
143
|
+
* session's uid AND its custom claims on `request.auth.token`. Keyed by
|
|
144
|
+
* uid + token so distinct claim snapshots never share a handle; forced
|
|
145
|
+
* refresh also clears the cache. Distinct from
|
|
146
|
+
* {@link HostCtx.lensHandles}, which is the Studio debugging lens
|
|
147
|
+
* (uid-only, caller-gated writes).
|
|
148
|
+
*/
|
|
149
|
+
sessionDbs?: Map<string, Firestore>;
|
|
150
|
+
/**
|
|
151
|
+
* The backend used for named state branches (save/switch). Session
|
|
152
|
+
* persistence is NOT here anymore: per-tab sessions persist client-side
|
|
153
|
+
* in web storage (the page restores via `auth.restorePortSession`),
|
|
154
|
+
* mirroring how the real SDK owns persistence in the client (#754).
|
|
155
|
+
*/
|
|
156
|
+
sessionBackend?: PersistenceBackend;
|
|
157
|
+
/**
|
|
158
|
+
* Accepted `auth.setPersistence` mode, kept for surface parity. The
|
|
159
|
+
* worker itself no longer writes a session record — the CLIENT's
|
|
160
|
+
* SessionStore honors the mode (which web storage slot, or none).
|
|
161
|
+
*/
|
|
162
|
+
sessionMode?: AuthPersistenceMode;
|
|
163
|
+
/**
|
|
164
|
+
* Force an IMMEDIATE session-capture flush, bypassing the debounce — set
|
|
165
|
+
* by `applyServeInit` when `--capture` is on (see the capture block in
|
|
166
|
+
* serve-init.ts). The `resetAll` op invokes it so the server-persisted
|
|
167
|
+
* capture (`.pyric/last-session.json`) records the post-reset (empty)
|
|
168
|
+
* event history instead of the wiped session's traffic, which a rebooting
|
|
169
|
+
* worker would otherwise re-prime via `hydrateEventHistory` (issue #359
|
|
170
|
+
* extension). Absent when capture is off.
|
|
171
|
+
*/
|
|
172
|
+
captureFlush?: () => Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* Per-uid impersonation Firestore handles (Pyric Studio auth lens, T2).
|
|
175
|
+
* Keyed by the impersonated uid. Each is a FROZEN-identity
|
|
176
|
+
* `getFirestore(sandbox.withAuth({ uid }))` handle whose ops evaluate
|
|
177
|
+
* security rules AS that user — the rules-debugging primitive. Cached so
|
|
178
|
+
* repeated `{ mode: 'as', uid }` ops reuse one handle (and so the modular
|
|
179
|
+
* layer's per-ctx ref cache stays warm). Lazily populated by `lensDb`.
|
|
180
|
+
*/
|
|
181
|
+
lensHandles?: Map<string, Firestore>;
|
|
182
|
+
/**
|
|
183
|
+
* Cached admin (rules-bypass) Firestore handle for the `{ mode: 'admin' }`
|
|
184
|
+
* lens (Pyric Studio, Gap #2). A modular `getAdminFirestore(sandbox)` handle
|
|
185
|
+
* whose ops skip security-rule evaluation — Studio's "edit anything as admin"
|
|
186
|
+
* (F2). Lazily populated by `lensDb`.
|
|
187
|
+
*/
|
|
188
|
+
adminDb?: Firestore;
|
|
189
|
+
/**
|
|
190
|
+
* The sandbox's AiBroker (pyric/ai), lazily created on the first ai op /
|
|
191
|
+
* ai stream sub via `getAI(ctx.sandbox, …)` — so the worker's broker IS the
|
|
192
|
+
* mirror's per-sandbox broker (one instance, events land on the shared
|
|
193
|
+
* sandbox's unified stream). See `ensureAiBroker` in host-ai.ts.
|
|
194
|
+
*/
|
|
195
|
+
aiBroker?: AiBrokerLike;
|
|
196
|
+
/**
|
|
197
|
+
* Host-construction engine config for the AI broker (cdd-deltas #98.4).
|
|
198
|
+
* When set (embedding/test hosts, or serve wiring), it WINS over any
|
|
199
|
+
* op-carried `engine` field; absent, the first ai op's `engine` field is
|
|
200
|
+
* honored; both absent ⇒ the zero-config scripted default.
|
|
201
|
+
*/
|
|
202
|
+
aiEngine?: AiEngineConfigWire;
|
|
203
|
+
/**
|
|
204
|
+
* Lazily-built agent tool dispatcher (the canonical sandbox tool set) bound to
|
|
205
|
+
* THIS worker's sandbox. The bridge peer forwards `tool` messages here so the
|
|
206
|
+
* agent executes against the one shared sandbox, not a separate in-page
|
|
207
|
+
* backend. Cached so the handler array is built once.
|
|
208
|
+
*/
|
|
209
|
+
toolDispatch?: (
|
|
210
|
+
name: string,
|
|
211
|
+
args: Record<string, unknown>,
|
|
212
|
+
) => Promise<{ ok: boolean; summary: string; data?: unknown }>;
|
|
213
|
+
/**
|
|
214
|
+
* Messaging host capability. Normal serve producers enable it because
|
|
215
|
+
* Messaging is in the canonical SDK swap. Absent/false answers
|
|
216
|
+
* `messaging/disabled` for standalone host construction and focused tests.
|
|
217
|
+
*/
|
|
218
|
+
messagingEnabled?: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Per-port broker client ids (the worker-host seam's visibility mapping):
|
|
221
|
+
* a port that reports `messaging.setVisibility` becomes ONE window client
|
|
222
|
+
* in the broker, so the captured routing rule — foreground iff ANY visible
|
|
223
|
+
* client — spans tabs. `cleanupPort` removes the client so a closed tab
|
|
224
|
+
* stops counting as visible. Lazily populated by host-messaging.ts.
|
|
225
|
+
*/
|
|
226
|
+
messagingClients?: Map<PortLike, string>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface ActiveRulesState {
|
|
230
|
+
source: unknown;
|
|
231
|
+
updatedAt: number;
|
|
232
|
+
status: 'active' | 'error';
|
|
233
|
+
messages: Array<{ severity: 'info' | 'warn' | 'error'; text: string; line?: number; column?: number }>;
|
|
234
|
+
lastKnownGood?: unknown;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Utility: post a typed reply to a port ───────────────────────────────
|
|
238
|
+
|
|
239
|
+
export function post(port: PortLike, msg: OutboundMessage): void {
|
|
240
|
+
port.postMessage(msg);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function ok(port: PortLike, id: string, value: unknown): void {
|
|
244
|
+
post(port, { t: 'res', id, ok: true, value });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function fail(port: PortLike, id: string, err: unknown): void {
|
|
248
|
+
// The serialized error carries `denialContext` when present (spike gap 6) —
|
|
249
|
+
// post it whole so the structured denial frame reaches remote consumers.
|
|
250
|
+
post(port, { t: 'res', id, ok: false, error: serializeError(err) });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Await a persistence flush BEFORE acking a mutating op, so an acknowledged
|
|
255
|
+
* write is durable in IndexedDB when the reply reaches the client. This is
|
|
256
|
+
* the worker's ONLY durability mechanism: there is no beforeterminate-style
|
|
257
|
+
* event for a SharedWorker, so a teardown on last-tab close/reload can kill
|
|
258
|
+
* the worker inside the controller's debounce window — an un-flushed write
|
|
259
|
+
* would be silently lost (the reload-durability bug).
|
|
260
|
+
*
|
|
261
|
+
* "Best-effort" refers to the REPLY, not the flush: a flush failure (e.g.
|
|
262
|
+
* storage quota) must not turn a successful in-memory mutation into a wire
|
|
263
|
+
* error, but it IS logged — a silently swallowed flush failure is exactly
|
|
264
|
+
* how a broken flush contract stays invisible.
|
|
265
|
+
*/
|
|
266
|
+
export async function bestEffortFlush(ctx: HostCtx): Promise<void> {
|
|
267
|
+
try {
|
|
268
|
+
await ctx.sandbox.flush?.();
|
|
269
|
+
} catch (e) {
|
|
270
|
+
// eslint-disable-next-line no-console
|
|
271
|
+
console.warn('[pyric worker] persistence flush after acked write failed:', e);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWorker host — event-stream subsystem (Pyric Studio keystone).
|
|
3
|
+
*
|
|
4
|
+
* Surfaces the sandbox's unified cross-service `onEvent`/`history()` stream over
|
|
5
|
+
* the port so Studio's Action Center / traffic / rules-debug denial feed see
|
|
6
|
+
* real activity. Imports only from `./host-context.js` + external packages (no
|
|
7
|
+
* circular imports).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type HostCtx, type PortLike, post } from './host-context.js';
|
|
11
|
+
import type { SandboxEvent } from 'pyric/sandbox';
|
|
12
|
+
import type { EventSubMessage } from './protocol.js';
|
|
13
|
+
|
|
14
|
+
// ─── Event-stream subscription (Pyric Studio keystone) ─────────────────────
|
|
15
|
+
//
|
|
16
|
+
// Surfaces the sandbox's unified cross-service `onEvent`/`history()` stream over
|
|
17
|
+
// the port so Studio's Action Center / traffic / rules-debug denial feed see
|
|
18
|
+
// real activity. Architecture mirrors the AUTH fan-out: ONE real
|
|
19
|
+
// `sandbox.onEvent` subscription per worker (lazily wired in {@link ensureEventStream}),
|
|
20
|
+
// and a per-port routing registry telling the host which ports want events. On
|
|
21
|
+
// any event the single sandbox subscription fires once and we fan it out to
|
|
22
|
+
// EVERY subscribed port — so multiple Studio/app tabs share the one backend
|
|
23
|
+
// stream. Events are plain JSON (no codec round-trip; see EventSubMessage).
|
|
24
|
+
|
|
25
|
+
/** Per-port event-stream subscription registry. Map<port, Set<subId>>. The
|
|
26
|
+
* WeakMap is keyed by ctx so multiple in-process hosts (tests) don't collide. */
|
|
27
|
+
const _eventSubs = new WeakMap<HostCtx, Map<PortLike, Set<string>>>();
|
|
28
|
+
|
|
29
|
+
/** Tracks whether the single real `sandbox.onEvent` subscription is wired. */
|
|
30
|
+
const _eventStreamWired = new WeakSet<HostCtx>();
|
|
31
|
+
|
|
32
|
+
export function eventSubsFor(ctx: HostCtx): Map<PortLike, Set<string>> {
|
|
33
|
+
let m = _eventSubs.get(ctx);
|
|
34
|
+
if (!m) {
|
|
35
|
+
m = new Map();
|
|
36
|
+
_eventSubs.set(ctx, m);
|
|
37
|
+
}
|
|
38
|
+
return m;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lazily wire the ONE real `sandbox.onEvent` subscription for this ctx. On every
|
|
43
|
+
* sandbox event it fans the event out (as a single-element batch) to every port
|
|
44
|
+
* subscribed to the stream. Idempotent — only the first event sub wires it; the
|
|
45
|
+
* subscription then lives for the worker's lifetime (cheap, observational).
|
|
46
|
+
*/
|
|
47
|
+
function ensureEventStream(ctx: HostCtx): void {
|
|
48
|
+
if (_eventStreamWired.has(ctx)) return;
|
|
49
|
+
_eventStreamWired.add(ctx);
|
|
50
|
+
ctx.sandbox.onEvent((event) => {
|
|
51
|
+
broadcastEvents(ctx, [event]);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Fan a batch of events out to every port subscribed to the event stream. */
|
|
56
|
+
function broadcastEvents(ctx: HostCtx, events: readonly SandboxEvent[]): void {
|
|
57
|
+
const subs = eventSubsFor(ctx);
|
|
58
|
+
for (const [port, subIds] of subs) {
|
|
59
|
+
for (const subId of subIds) {
|
|
60
|
+
post(port, { t: 'event', subId, events });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register an event-stream subscription for a port (Pyric Studio keystone).
|
|
67
|
+
* Records the (port, subId) so live events reach it, ensures the single real
|
|
68
|
+
* sandbox subscription is wired, and immediately delivers `sandbox.history()`
|
|
69
|
+
* (every event so far) as the initial batch — so a late subscriber folds the
|
|
70
|
+
* full backlog then streams uniformly (mirrors `onEvent` + `history`).
|
|
71
|
+
*/
|
|
72
|
+
export function handleEventSub(ctx: HostCtx, port: PortLike, msg: EventSubMessage): void {
|
|
73
|
+
ensureEventStream(ctx);
|
|
74
|
+
const subs = eventSubsFor(ctx);
|
|
75
|
+
let subIds = subs.get(port);
|
|
76
|
+
if (!subIds) {
|
|
77
|
+
subIds = new Set();
|
|
78
|
+
subs.set(port, subIds);
|
|
79
|
+
}
|
|
80
|
+
if (subIds.has(msg.subId)) return; // idempotent
|
|
81
|
+
subIds.add(msg.subId);
|
|
82
|
+
|
|
83
|
+
// Initial fire: the full history snapshot (a defensive copy from the sandbox).
|
|
84
|
+
post(port, { t: 'event', subId: msg.subId, events: ctx.sandbox.history() });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function handleEventUnsub(ctx: HostCtx, port: PortLike, subId: string): boolean {
|
|
88
|
+
const subs = eventSubsFor(ctx);
|
|
89
|
+
const subIds = subs.get(port);
|
|
90
|
+
if (!subIds || !subIds.has(subId)) return false;
|
|
91
|
+
subIds.delete(subId);
|
|
92
|
+
if (subIds.size === 0) subs.delete(port);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWorker host — messaging subsystem: the broker's documented
|
|
3
|
+
* worker-host seam, wired (see `pyric/src/messaging/broker/broker.ts`
|
|
4
|
+
* header — each public broker method is one `messaging.*` op here).
|
|
5
|
+
*
|
|
6
|
+
* ONE broker per sandbox (`getMessagingBroker`), shared by every port —
|
|
7
|
+
* production's one-service-worker-per-origin model. Two seams cross the
|
|
8
|
+
* transport:
|
|
9
|
+
*
|
|
10
|
+
* OPS token lifecycle (`getToken`/`deleteToken`), the send plane
|
|
11
|
+
* (`send`, topic management), the delivery driver (`deliver`), and
|
|
12
|
+
* visibility (`setVisibility`). Send-plane rejections carry the
|
|
13
|
+
* broker's captured google.rpc envelope VERBATIM on
|
|
14
|
+
* `error.envelope` (plain JSON, structured-clone-safe).
|
|
15
|
+
*
|
|
16
|
+
* SUBS `messaging.foreground` / `messaging.background` register real
|
|
17
|
+
* broker handlers whose payloads forward to the subscribing port as
|
|
18
|
+
* snaps. Unsubs ride the ordinary `ctx.subs` registry, so
|
|
19
|
+
* `cleanupPort` tears them down like Firestore listeners.
|
|
20
|
+
*
|
|
21
|
+
* PER-PORT VISIBILITY — the captured routing rule crossing the transport:
|
|
22
|
+
* each port that reports `messaging.setVisibility` is ONE window client in
|
|
23
|
+
* the broker (`setClientVisibility(portId, state)` on the page's
|
|
24
|
+
* `visibilitychange`). A hidden tab's port marks its client not-visible;
|
|
25
|
+
* routing is foreground iff ANY visible client (oracle:
|
|
26
|
+
* `messaging-web-visibility-routing` — visibility, never focus). Port close
|
|
27
|
+
* removes the client so a dead tab cannot pin foreground routing.
|
|
28
|
+
*
|
|
29
|
+
* HOST CAPABILITY GATE: ops exist only when the ctx was built with
|
|
30
|
+
* `messagingEnabled: true`. Both serve producers enable it because Messaging
|
|
31
|
+
* is part of the canonical Firebase SDK swap. The disabled path remains for
|
|
32
|
+
* standalone host construction and focused tests.
|
|
33
|
+
*
|
|
34
|
+
* Imports only from `./host-context.js` + external packages (host-auth
|
|
35
|
+
* precedent — no circular imports).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { getMessagingBroker, BrokerSendError } from 'pyric/messaging/internal';
|
|
39
|
+
import type { MessagingBroker } from 'pyric/messaging/internal';
|
|
40
|
+
|
|
41
|
+
import { type HostCtx, type PortLike, post, ok, fail } from './host-context.js';
|
|
42
|
+
import type { OpMessage, MessagingSubMessage } from './protocol.js';
|
|
43
|
+
|
|
44
|
+
/** Registration id used when a `messaging.getToken`/`deleteToken` op names
|
|
45
|
+
* none — the transport twin of the mirrors' module-default registration. */
|
|
46
|
+
export const DEFAULT_WIRE_REGISTRATION_ID = 'swreg-port-default';
|
|
47
|
+
|
|
48
|
+
const MESSAGING_METHODS = new Set([
|
|
49
|
+
'messaging.getToken',
|
|
50
|
+
'messaging.deleteToken',
|
|
51
|
+
'messaging.send',
|
|
52
|
+
'messaging.subscribeToTopic',
|
|
53
|
+
'messaging.unsubscribeFromTopic',
|
|
54
|
+
'messaging.deliver',
|
|
55
|
+
'messaging.setVisibility',
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
/** Is this op method part of the messaging surface? (Routing predicate for
|
|
59
|
+
* handleMessage — the auth-op precedent.) */
|
|
60
|
+
export function isMessagingOp(method: string): boolean {
|
|
61
|
+
return MESSAGING_METHODS.has(method);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The error every messaging op/sub answers on a host without the capability. */
|
|
65
|
+
function disabledError(): { code: string; message: string } {
|
|
66
|
+
return {
|
|
67
|
+
code: 'messaging/disabled',
|
|
68
|
+
message:
|
|
69
|
+
'pyric worker: this host was created without Messaging enabled. ' +
|
|
70
|
+
'Build the worker host ctx with `messagingEnabled: true`.',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function broker(ctx: HostCtx): MessagingBroker {
|
|
75
|
+
return getMessagingBroker(ctx.sandbox);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Module-level mint so client ids never collide across delete/re-add on one
|
|
79
|
+
// broker (a ctx-map-size mint would reuse ids after removal).
|
|
80
|
+
let clientSeq = 0;
|
|
81
|
+
|
|
82
|
+
/** The port's broker client id, minted on first visibility report. */
|
|
83
|
+
function clientIdFor(ctx: HostCtx, port: PortLike): string {
|
|
84
|
+
const clients = (ctx.messagingClients ??= new Map());
|
|
85
|
+
let id = clients.get(port);
|
|
86
|
+
if (id === undefined) {
|
|
87
|
+
id = `port-client-${++clientSeq}`;
|
|
88
|
+
clients.set(port, id);
|
|
89
|
+
}
|
|
90
|
+
return id;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fail a messaging op. A `BrokerSendError` crosses the wire as its captured
|
|
95
|
+
* envelope (the seam contract): `code` is the envelope's google.rpc status
|
|
96
|
+
* (`INVALID_ARGUMENT`, `NOT_FOUND`, …), `envelope` the full pinned value.
|
|
97
|
+
*/
|
|
98
|
+
function failMessaging(port: PortLike, id: string, err: unknown): void {
|
|
99
|
+
if (err instanceof BrokerSendError) {
|
|
100
|
+
post(port, {
|
|
101
|
+
t: 'res',
|
|
102
|
+
id,
|
|
103
|
+
ok: false,
|
|
104
|
+
error: {
|
|
105
|
+
code: err.envelope.error.status,
|
|
106
|
+
message: err.envelope.error.message,
|
|
107
|
+
envelope: err.envelope,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
fail(port, id, err);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function handleMessagingOp(
|
|
116
|
+
ctx: HostCtx,
|
|
117
|
+
port: PortLike,
|
|
118
|
+
msg: OpMessage,
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
if (ctx.messagingEnabled !== true) {
|
|
121
|
+
post(port, { t: 'res', id: msg.id, ok: false, error: disabledError() });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
switch (msg.method) {
|
|
126
|
+
case 'messaging.getToken': {
|
|
127
|
+
// Stable per registration (oracle: `messaging-web-token-stability`).
|
|
128
|
+
try {
|
|
129
|
+
const token = broker(ctx).getTokenFor(msg.registrationId ?? DEFAULT_WIRE_REGISTRATION_ID);
|
|
130
|
+
ok(port, msg.id, { token });
|
|
131
|
+
} catch (e) { failMessaging(port, msg.id, e); }
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case 'messaging.deleteToken': {
|
|
136
|
+
// Resolves truthy either way (oracle: `messaging-web-deletetoken-unregistered`).
|
|
137
|
+
try {
|
|
138
|
+
ok(port, msg.id, broker(ctx).deleteTokenFor(msg.registrationId ?? DEFAULT_WIRE_REGISTRATION_ID));
|
|
139
|
+
} catch (e) { failMessaging(port, msg.id, e); }
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case 'messaging.send': {
|
|
144
|
+
// AcceptedSend is plain JSON — replies verbatim. Rejections carry the
|
|
145
|
+
// captured envelope (see failMessaging).
|
|
146
|
+
try {
|
|
147
|
+
const accepted = broker(ctx).send(msg.message, {
|
|
148
|
+
...(msg.validateOnly !== undefined ? { validateOnly: msg.validateOnly } : {}),
|
|
149
|
+
});
|
|
150
|
+
ok(port, msg.id, accepted);
|
|
151
|
+
} catch (e) { failMessaging(port, msg.id, e); }
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case 'messaging.subscribeToTopic': {
|
|
156
|
+
try {
|
|
157
|
+
ok(port, msg.id, broker(ctx).subscribeToTopic(msg.tokens, msg.topic));
|
|
158
|
+
} catch (e) { failMessaging(port, msg.id, e); }
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'messaging.unsubscribeFromTopic': {
|
|
163
|
+
try {
|
|
164
|
+
ok(port, msg.id, broker(ctx).unsubscribeFromTopic(msg.tokens, msg.topic));
|
|
165
|
+
} catch (e) { failMessaging(port, msg.id, e); }
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'messaging.deliver': {
|
|
170
|
+
// Test/Studio driver — DeliveryResult { route, handlerCount, payload }
|
|
171
|
+
// is plain JSON. `visibilityState` (the in-page driver's twin) sets THIS
|
|
172
|
+
// port's client visibility before routing, so `pyric/messaging`'s
|
|
173
|
+
// `sandbox.deliver(spec)` picks foreground/background over the transport
|
|
174
|
+
// exactly as in-page: visible → onMessage, hidden → onBackgroundMessage.
|
|
175
|
+
try {
|
|
176
|
+
const { visibilityState, ...payload } = msg.spec;
|
|
177
|
+
if (visibilityState !== undefined) {
|
|
178
|
+
broker(ctx).setClientVisibility(clientIdFor(ctx, port), visibilityState);
|
|
179
|
+
}
|
|
180
|
+
ok(port, msg.id, broker(ctx).deliver(payload));
|
|
181
|
+
} catch (e) { failMessaging(port, msg.id, e); }
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
case 'messaging.setVisibility': {
|
|
186
|
+
// THE routing input crossing the transport: this port's tab is one
|
|
187
|
+
// window client; its visibility report updates the broker state the
|
|
188
|
+
// captured rule routes on.
|
|
189
|
+
try {
|
|
190
|
+
broker(ctx).setClientVisibility(clientIdFor(ctx, port), msg.state);
|
|
191
|
+
ok(port, msg.id, null);
|
|
192
|
+
} catch (e) { failMessaging(port, msg.id, e); }
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
default:
|
|
197
|
+
fail(port, msg.id, new Error(`Unknown messaging method: ${String(msg.method)}`));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Register a foreground/background delivery listener for this port. The
|
|
203
|
+
* broker handler's unsub goes into the ordinary `ctx.subs` registry, so the
|
|
204
|
+
* generic unsub path and `cleanupPort` both tear it down. Payloads are plain
|
|
205
|
+
* JSON (`DeliveredPayload`) — no codec round-trip.
|
|
206
|
+
*/
|
|
207
|
+
export function handleMessagingSub(ctx: HostCtx, port: PortLike, msg: MessagingSubMessage): void {
|
|
208
|
+
if (ctx.messagingEnabled !== true) {
|
|
209
|
+
// Mirror handleSub's error path: deliver the gate as a snap-error so the
|
|
210
|
+
// client's error callback fires instead of the sub silently dying.
|
|
211
|
+
post(port, { t: 'snap', subId: msg.subId, value: { __error: disabledError() } });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!ctx.subs.has(port)) ctx.subs.set(port, new Map());
|
|
216
|
+
const portSubs = ctx.subs.get(port)!;
|
|
217
|
+
if (portSubs.has(msg.subId)) return; // idempotent
|
|
218
|
+
|
|
219
|
+
const b = broker(ctx);
|
|
220
|
+
const forward = (payload: unknown): void =>
|
|
221
|
+
post(port, { t: 'snap', subId: msg.subId, value: payload });
|
|
222
|
+
const unsub =
|
|
223
|
+
msg.target === 'messaging.foreground'
|
|
224
|
+
? b.onForegroundMessage(forward)
|
|
225
|
+
: b.onBackgroundMessage(forward);
|
|
226
|
+
portSubs.set(msg.subId, unsub);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Drop a disconnected port's broker client so its last-reported visibility
|
|
231
|
+
* stops feeding the routing rule (a closed visible tab must not pin
|
|
232
|
+
* foreground routing forever). Handler subs are NOT touched here — they
|
|
233
|
+
* live in `ctx.subs`, which `cleanupPort` already tears down.
|
|
234
|
+
*/
|
|
235
|
+
export function cleanupPortMessaging(ctx: HostCtx, port: PortLike): void {
|
|
236
|
+
const id = ctx.messagingClients?.get(port);
|
|
237
|
+
if (id === undefined) return;
|
|
238
|
+
ctx.messagingClients!.delete(port);
|
|
239
|
+
broker(ctx).removeClient(id);
|
|
240
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWorker host — public barrel over the per-family modules under `host/`.
|
|
3
|
+
*
|
|
4
|
+
* The host is the SharedWorker's data plane: one `HostCtx` shared across ALL
|
|
5
|
+
* connected ports, driven per message by `handleMessage(ctx, port, msg)`. It is
|
|
6
|
+
* deliberately decoupled from `SharedWorkerGlobalScope` so unit tests can drive
|
|
7
|
+
* it with a REAL pyric sandbox + fake MessagePort objects — no browser or
|
|
8
|
+
* SharedWorker runtime required. The entry point (`entry.ts`) creates the real
|
|
9
|
+
* sandbox + db and wires the connecting ports to this module.
|
|
10
|
+
*
|
|
11
|
+
* This file re-exports only the host's PUBLIC surface; the implementation lives
|
|
12
|
+
* in the per-family modules (mirroring the client split under `client/`):
|
|
13
|
+
* - `host/core` shared lens/handle resolution, descriptor → ref
|
|
14
|
+
* resolution, snapshot serialization, op provenance
|
|
15
|
+
* - `host/dispatch` `handleMessage` seam + family routing + `cleanupPort`
|
|
16
|
+
* - `host/firestore-reads` getDoc/getDocs/count/aggregate/keyspace ops
|
|
17
|
+
* - `host/firestore-writes` set/update/delete/add/batch/txn ops (+ sentinels)
|
|
18
|
+
* - `host/rules` firestore/rtdb rules deploy + status
|
|
19
|
+
* - `host/admin-firestore` admin-lens document ops (rules bypass)
|
|
20
|
+
* - `host/rtdb` RTDB modular ops + snapshot→wire shaping
|
|
21
|
+
* - `host/storage` worker-backed Cloud Storage ops + lens resolver
|
|
22
|
+
* - `host/connection` version / state transfer / branches + instance id
|
|
23
|
+
* - `host/studio` runtime confirm-policy + sandbox snapshot
|
|
24
|
+
* - `host/subscriptions` Firestore/RTDB value-subscription registry (#754)
|
|
25
|
+
*
|
|
26
|
+
* The already-extracted auth / AI / events / messaging families keep their
|
|
27
|
+
* sibling `host-*.ts` modules; `host/dispatch` routes to them.
|
|
28
|
+
*
|
|
29
|
+
* `HostCtx` + the shared reply/port primitives live in `host-context.ts` (the
|
|
30
|
+
* engine-free dependency root).
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// Auth surface used by serve-init (ensureAuth) + the per-port session accessor.
|
|
34
|
+
export { ensureAuth, portSession } from './host-auth.js';
|
|
35
|
+
export type { HostCtx, PortLike } from './host-context.js';
|
|
36
|
+
// Instance-id + named-branch surface (part of the host's public shape:
|
|
37
|
+
// serve-init imports getOrCreateInstanceId; tests import the rest).
|
|
38
|
+
export {
|
|
39
|
+
INSTANCE_ID_KEY,
|
|
40
|
+
randomUuid,
|
|
41
|
+
getOrCreateInstanceId,
|
|
42
|
+
BRANCH_PREFIX,
|
|
43
|
+
BRANCH_REGISTRY_KEY,
|
|
44
|
+
listBranchNames,
|
|
45
|
+
} from './host/connection.js';
|
|
46
|
+
// The message dispatcher seam (entry.ts + tests) and per-port teardown.
|
|
47
|
+
export { handleMessage, cleanupPort } from './host/dispatch.js';
|