@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,86 @@
|
|
|
1
|
+
/** Session-aware Service Worker relay over the authoritative SharedWorker host. */
|
|
2
|
+
import {
|
|
3
|
+
cleanupPort,
|
|
4
|
+
handleMessage,
|
|
5
|
+
type HostCtx,
|
|
6
|
+
type PortLike,
|
|
7
|
+
} from './host.js';
|
|
8
|
+
import type { ServiceWorkerChannelMessage } from './service-worker-channel.js';
|
|
9
|
+
|
|
10
|
+
type HostEnvelope = Extract<ServiceWorkerChannelMessage, { direction: 'host' }>;
|
|
11
|
+
|
|
12
|
+
interface RelayState {
|
|
13
|
+
readonly sessionId: string;
|
|
14
|
+
readonly port: PortLike;
|
|
15
|
+
queue: Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ServiceWorkerRelay {
|
|
19
|
+
handle(envelope: HostEnvelope): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Keep one virtual host port per logical app while treating each Service Worker
|
|
24
|
+
* realm as a new session. An attach from a replacement realm first tears down
|
|
25
|
+
* the previous port's subscriptions; frames from the superseded realm are then
|
|
26
|
+
* ignored instead of being attributed to the replacement.
|
|
27
|
+
*/
|
|
28
|
+
export function createServiceWorkerRelay(options: {
|
|
29
|
+
getCtx(): Promise<HostCtx>;
|
|
30
|
+
send(message: ServiceWorkerChannelMessage): void;
|
|
31
|
+
onError?(error: unknown, envelope: HostEnvelope): void;
|
|
32
|
+
}): ServiceWorkerRelay {
|
|
33
|
+
const ports = new Map<string, RelayState>();
|
|
34
|
+
|
|
35
|
+
const makePort = (clientId: string, sessionId: string): PortLike => ({
|
|
36
|
+
postMessage(message) {
|
|
37
|
+
options.send({ direction: 'client', clientId, sessionId, message });
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
async handle(envelope) {
|
|
43
|
+
if (envelope.phase === 'attach') {
|
|
44
|
+
const previous = ports.get(envelope.clientId);
|
|
45
|
+
if (previous?.sessionId === envelope.sessionId) {
|
|
46
|
+
await previous.queue;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const state: RelayState = {
|
|
50
|
+
sessionId: envelope.sessionId,
|
|
51
|
+
port: makePort(envelope.clientId, envelope.sessionId),
|
|
52
|
+
queue: (previous?.queue ?? Promise.resolve()).then(async () => {
|
|
53
|
+
if (!previous) return;
|
|
54
|
+
try {
|
|
55
|
+
const ctx = await options.getCtx();
|
|
56
|
+
cleanupPort(ctx, previous.port);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
options.onError?.(error, envelope);
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
ports.set(envelope.clientId, state);
|
|
63
|
+
await state.queue;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const state = ports.get(envelope.clientId);
|
|
68
|
+
if (!state || state.sessionId !== envelope.sessionId) return;
|
|
69
|
+
state.queue = state.queue.then(async () => {
|
|
70
|
+
try {
|
|
71
|
+
const ctx = await options.getCtx();
|
|
72
|
+
await handleMessage(ctx, state.port, envelope.message);
|
|
73
|
+
if (
|
|
74
|
+
envelope.message.t === 'disconnect'
|
|
75
|
+
&& ports.get(envelope.clientId) === state
|
|
76
|
+
) {
|
|
77
|
+
ports.delete(envelope.clientId);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
options.onError?.(error, envelope);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
await state.queue;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-writer lock for `--persist` (pre-mortem #3).
|
|
3
|
+
*
|
|
4
|
+
* Two tabs = two sandboxes, each flushing its WHOLE world view — so the
|
|
5
|
+
* second tab's flush doesn't merge, it ERASES everything the first wrote.
|
|
6
|
+
* "last-writer-wins" undersold it: last-tab's-universe wins, visibly, on the
|
|
7
|
+
* loser's next reload.
|
|
8
|
+
*
|
|
9
|
+
* The conflict only exists at WRITE time, so the lock is resolved there: the
|
|
10
|
+
* first page to flush claims the writer role (carried in the
|
|
11
|
+
* `x-pyric-writer` header); a different page's flush is refused (409) and
|
|
12
|
+
* that page drops to read-only persistence + a loud console line. A reader
|
|
13
|
+
* that never mutates never conflicts.
|
|
14
|
+
*
|
|
15
|
+
* Liveness: the holder refreshes `lastSeen` on every accepted write and via
|
|
16
|
+
* a periodic heartbeat. A holder that goes quiet for `staleMs` (crash, or an
|
|
17
|
+
* idle tab whose heartbeat also stopped) is considered gone, so a fresh page
|
|
18
|
+
* can claim the role — no permanent wedge. The small overlap window where a
|
|
19
|
+
* stale-but-alive holder and a new claimer both think they're the writer is
|
|
20
|
+
* an accepted dev-tool race (documented), kept rare by a generous staleMs.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface WriterLock {
|
|
24
|
+
/** Claim/refresh for `id`. Granted when free, already held by `id`, or the
|
|
25
|
+
* current holder is stale. Returns whether `id` now holds the lock. */
|
|
26
|
+
claim(id: string, now: number): boolean;
|
|
27
|
+
/** Release if `id` is the holder (no-op otherwise). */
|
|
28
|
+
release(id: string): void;
|
|
29
|
+
/** Current holder, or null. */
|
|
30
|
+
holder(): string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createWriterLock(staleMs = 60_000): WriterLock {
|
|
34
|
+
let holder: string | null = null;
|
|
35
|
+
let lastSeen = 0;
|
|
36
|
+
return {
|
|
37
|
+
claim(id, now) {
|
|
38
|
+
if (holder === null || holder === id || now - lastSeen > staleMs) {
|
|
39
|
+
holder = id;
|
|
40
|
+
lastSeen = now;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
},
|
|
45
|
+
release(id) {
|
|
46
|
+
if (holder === id) {
|
|
47
|
+
holder = null;
|
|
48
|
+
lastSeen = 0;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
holder: () => holder,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { RequestEvent } from 'pyric/sandbox';
|
|
2
|
+
import type { FirestoreMethod, FunctionMock, TestCase } from 'pyric/rules/internal';
|
|
3
|
+
import {
|
|
4
|
+
parseVerifyFixture,
|
|
5
|
+
type PyricVerifyFixture,
|
|
6
|
+
} from './fixture.js';
|
|
7
|
+
|
|
8
|
+
export type VerifyFixtureWarning = {
|
|
9
|
+
service: 'firestore';
|
|
10
|
+
eventId?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type VerifyUnsupportedEvent = {
|
|
18
|
+
service: 'firestore';
|
|
19
|
+
eventId?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
method?: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface DeriveRulesTestCasesOptions {
|
|
26
|
+
service?: 'firestore';
|
|
27
|
+
includeAllowed?: boolean;
|
|
28
|
+
includeDenied?: boolean;
|
|
29
|
+
mockReads?: 'strict' | 'omit';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DerivedRulesTestCase {
|
|
33
|
+
eventId: string;
|
|
34
|
+
testCase: TestCase;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DeriveRulesTestCasesResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
service: 'firestore';
|
|
40
|
+
testCases: TestCase[];
|
|
41
|
+
derived: DerivedRulesTestCase[];
|
|
42
|
+
warnings: VerifyFixtureWarning[];
|
|
43
|
+
unsupportedEvents: VerifyUnsupportedEvent[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function deriveRulesTestCases(
|
|
47
|
+
fixtureInput: PyricVerifyFixture | unknown,
|
|
48
|
+
opts: DeriveRulesTestCasesOptions = {},
|
|
49
|
+
): DeriveRulesTestCasesResult {
|
|
50
|
+
const fixture = parseVerifyFixture(fixtureInput);
|
|
51
|
+
const includeAllowed = opts.includeAllowed ?? true;
|
|
52
|
+
const includeDenied = opts.includeDenied ?? true;
|
|
53
|
+
const mockReads = opts.mockReads ?? 'strict';
|
|
54
|
+
const derived: DerivedRulesTestCase[] = [];
|
|
55
|
+
const warnings: VerifyFixtureWarning[] = [];
|
|
56
|
+
const unsupportedEvents: VerifyUnsupportedEvent[] = [];
|
|
57
|
+
|
|
58
|
+
for (const event of fixture.events) {
|
|
59
|
+
if (!isFirestoreRequestEvent(event)) continue;
|
|
60
|
+
if (event.origin === 'listener') continue;
|
|
61
|
+
if (event.detail?.admin === true) continue;
|
|
62
|
+
if (event.result === 'allow' && !includeAllowed) continue;
|
|
63
|
+
if (event.result === 'deny' && !includeDenied) continue;
|
|
64
|
+
|
|
65
|
+
if (event.result === 'unsupported') {
|
|
66
|
+
unsupportedEvents.push({
|
|
67
|
+
service: 'firestore',
|
|
68
|
+
eventId: event.id,
|
|
69
|
+
path: event.path,
|
|
70
|
+
method: event.method,
|
|
71
|
+
reason: event.reasons.join('\n') || 'captured Firestore request was unsupported locally.',
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const method = lowerFirestoreMethod(event);
|
|
77
|
+
if (!method) {
|
|
78
|
+
unsupportedEvents.push({
|
|
79
|
+
service: 'firestore',
|
|
80
|
+
eventId: event.id,
|
|
81
|
+
path: event.path,
|
|
82
|
+
method: event.method,
|
|
83
|
+
reason: `cannot derive a Rules Test API case for Firestore method '${event.method}'.`,
|
|
84
|
+
});
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const functionMocks = extractFunctionMocks(event);
|
|
89
|
+
if (mockReads === 'strict' && functionMocks === null) {
|
|
90
|
+
unsupportedEvents.push({
|
|
91
|
+
service: 'firestore',
|
|
92
|
+
eventId: event.id,
|
|
93
|
+
path: event.path,
|
|
94
|
+
method: event.method,
|
|
95
|
+
reason:
|
|
96
|
+
'captured request appears to depend on get()/exists(), but the fixture does not contain exact rule-read mocks.',
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (mockReads === 'omit' && eventMayNeedFunctionMocks(event)) {
|
|
101
|
+
warnings.push({
|
|
102
|
+
service: 'firestore',
|
|
103
|
+
eventId: event.id,
|
|
104
|
+
path: event.path,
|
|
105
|
+
method: event.method,
|
|
106
|
+
code: 'READ_MOCKS_OMITTED',
|
|
107
|
+
message:
|
|
108
|
+
'captured request may depend on get()/exists(); deriving the case without functionMocks may change hosted Rules Test API behavior.',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
derived.push({
|
|
113
|
+
eventId: event.id,
|
|
114
|
+
testCase: buildTestCase(event, method, functionMocks ?? []),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
ok: unsupportedEvents.length === 0,
|
|
120
|
+
service: 'firestore',
|
|
121
|
+
testCases: derived.map((item) => item.testCase),
|
|
122
|
+
derived,
|
|
123
|
+
warnings,
|
|
124
|
+
unsupportedEvents,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isFirestoreRequestEvent(event: unknown): event is RequestEvent {
|
|
129
|
+
if (!isCapturedRequestEvent(event) || event.kind !== 'request') return false;
|
|
130
|
+
const service = event.service;
|
|
131
|
+
return service === undefined || service === 'firestore';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function lowerFirestoreMethod(event: RequestEvent): FirestoreMethod | null {
|
|
135
|
+
if (event.method === 'get' || event.method === 'list' || event.method === 'create' || event.method === 'update' || event.method === 'delete') {
|
|
136
|
+
return event.method;
|
|
137
|
+
}
|
|
138
|
+
if (event.method === 'set') {
|
|
139
|
+
return event.resourceBefore?.exists ? 'update' : 'create';
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildTestCase(
|
|
145
|
+
event: RequestEvent,
|
|
146
|
+
method: FirestoreMethod,
|
|
147
|
+
functionMocks: FunctionMock[],
|
|
148
|
+
): TestCase {
|
|
149
|
+
const testCase: TestCase = {
|
|
150
|
+
description: `${event.result === 'allow' ? 'allow' : 'deny'} ${method} ${event.path}`,
|
|
151
|
+
expectation: event.result === 'allow' ? 'ALLOW' : 'DENY',
|
|
152
|
+
method,
|
|
153
|
+
path: event.path,
|
|
154
|
+
auth: event.auth,
|
|
155
|
+
requestTime: new Date(event.at).toISOString(),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (event.resourceBefore?.exists && event.resourceBefore.data !== null) {
|
|
159
|
+
testCase.resource = event.resourceBefore.data;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isWriteMethod(method)) {
|
|
163
|
+
if (method !== 'delete') {
|
|
164
|
+
const data = event.resourceAfter?.data ?? event.request?.resourceData;
|
|
165
|
+
if (isFirestoreWriteData(data)) testCase.data = data;
|
|
166
|
+
}
|
|
167
|
+
testCase.writeMode = writeModeFor(method);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (functionMocks.length > 0) {
|
|
171
|
+
testCase.functionMocks = functionMocks;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const query = (event.detail as { query?: unknown } | undefined)?.query;
|
|
175
|
+
if (isListQuery(query)) {
|
|
176
|
+
testCase.query = query;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return testCase;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function writeModeFor(method: FirestoreMethod): TestCase['writeMode'] | undefined {
|
|
183
|
+
if (method === 'create') return { kind: 'create' };
|
|
184
|
+
if (method === 'update') return { kind: 'update' };
|
|
185
|
+
if (method === 'delete') return { kind: 'delete' };
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isWriteMethod(method: FirestoreMethod): boolean {
|
|
190
|
+
return method === 'create' || method === 'update' || method === 'delete';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function extractFunctionMocks(event: RequestEvent): FunctionMock[] | null {
|
|
194
|
+
const detail = event.detail as { functionMocks?: unknown } | undefined;
|
|
195
|
+
if (Array.isArray(detail?.functionMocks)) {
|
|
196
|
+
return detail.functionMocks.filter(isFunctionMock);
|
|
197
|
+
}
|
|
198
|
+
return eventMayNeedFunctionMocks(event) ? null : [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isFunctionMock(value: unknown): value is FunctionMock {
|
|
202
|
+
if (!isFunctionMockCandidate(value)) return false;
|
|
203
|
+
if (value.function !== 'get' && value.function !== 'exists') return false;
|
|
204
|
+
return typeof value.path === 'string' && 'result' in value;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isListQuery(value: unknown): value is NonNullable<TestCase['query']> {
|
|
208
|
+
if (!isListQueryCandidate(value)) return false;
|
|
209
|
+
if ('limit' in value && value.limit !== undefined && typeof value.limit !== 'number') return false;
|
|
210
|
+
if ('offset' in value && value.offset !== undefined && typeof value.offset !== 'number') return false;
|
|
211
|
+
if ('orderBy' in value && value.orderBy !== undefined && typeof value.orderBy !== 'string') return false;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function eventMayNeedFunctionMocks(event: RequestEvent): boolean {
|
|
216
|
+
return event.reasons.some((reason) => /\b(get|exists)\s*\(/.test(reason));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isCapturedRequestEvent(value: unknown): value is Record<string, unknown> {
|
|
220
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isFirestoreWriteData(value: unknown): value is Record<string, unknown> {
|
|
224
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isFunctionMockCandidate(value: unknown): value is Record<string, unknown> {
|
|
228
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isListQueryCandidate(value: unknown): value is Record<string, unknown> {
|
|
232
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
233
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { EventService, Sandbox, SandboxEvent } from 'pyric/sandbox';
|
|
2
|
+
import { getStorageSandbox } from 'pyric/storage';
|
|
3
|
+
import { isRtdbRulesJson } from '../rtdb/rules-json.js';
|
|
4
|
+
|
|
5
|
+
export const VERIFY_FIXTURE_SCHEMA = 'pyric.verify.fixture.v1' as const;
|
|
6
|
+
|
|
7
|
+
export interface VerifyFirestoreRulesBlock {
|
|
8
|
+
format: 'firestore.rules';
|
|
9
|
+
source: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface VerifyRtdbRulesBlock {
|
|
13
|
+
format: 'rtdb.rules.json';
|
|
14
|
+
json: { rules: Record<string, unknown> };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface VerifyStorageRulesBlock {
|
|
18
|
+
format: 'storage.rules';
|
|
19
|
+
source: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PyricVerifyFixture {
|
|
23
|
+
schema: typeof VERIFY_FIXTURE_SCHEMA;
|
|
24
|
+
description?: string;
|
|
25
|
+
createdAt?: string;
|
|
26
|
+
/** Opaque id of the sandbox instance that produced this capture (the served
|
|
27
|
+
* SharedWorker's `instanceId`). Purely additive: `pyric verify` ignores it.
|
|
28
|
+
* Present only on captures written by the worker's capture flush; used by
|
|
29
|
+
* boot-time event hydration to SKIP priming a capture that belongs to a
|
|
30
|
+
* DIFFERENT instance (e.g. another browser profile sharing one `pyric dev`),
|
|
31
|
+
* so someone else's session never shows up as yours. Absent on older /
|
|
32
|
+
* standalone captures → hydration primes best-effort. */
|
|
33
|
+
capturedBy?: string;
|
|
34
|
+
events: SandboxEvent[];
|
|
35
|
+
services: {
|
|
36
|
+
firestore?: {
|
|
37
|
+
rules: VerifyFirestoreRulesBlock;
|
|
38
|
+
state: { documents: Record<string, Record<string, unknown>> };
|
|
39
|
+
};
|
|
40
|
+
rtdb?: {
|
|
41
|
+
rules: VerifyRtdbRulesBlock;
|
|
42
|
+
state: { tree: unknown };
|
|
43
|
+
databaseUrl?: string;
|
|
44
|
+
};
|
|
45
|
+
auth?: {
|
|
46
|
+
state: {
|
|
47
|
+
users?: unknown[];
|
|
48
|
+
currentUser?: unknown;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
storage?: {
|
|
52
|
+
/** RULES TEXT ONLY — captured object state is a separate, larger
|
|
53
|
+
* redesign (persistence.ts's IDB blob store) and is deliberately
|
|
54
|
+
* left untouched here. `state` stays `null` until that lands. */
|
|
55
|
+
rules: VerifyStorageRulesBlock;
|
|
56
|
+
state: unknown;
|
|
57
|
+
};
|
|
58
|
+
[service: string]: unknown;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface BuildVerifyFixtureInput {
|
|
63
|
+
sandbox: Pick<Sandbox, 'history' | 'snapshot'> & { currentUser?: unknown };
|
|
64
|
+
description?: string;
|
|
65
|
+
firestoreRules?: string | null;
|
|
66
|
+
rtdbRules?: { rules: Record<string, unknown> } | null;
|
|
67
|
+
rtdbState?: unknown;
|
|
68
|
+
rtdbDatabaseUrl?: string | null;
|
|
69
|
+
/** Currently-deployed storage rules text. RULES ONLY — there is no
|
|
70
|
+
* `storageState` input; captured storage OBJECTS are a separate,
|
|
71
|
+
* larger redesign left untouched by this fixture. */
|
|
72
|
+
storageRules?: string | null;
|
|
73
|
+
authState?: {
|
|
74
|
+
users?: unknown[];
|
|
75
|
+
currentUser?: unknown;
|
|
76
|
+
} | null;
|
|
77
|
+
createdAt?: string;
|
|
78
|
+
/** Stamped into the fixture as `capturedBy` (identity for boot-time event
|
|
79
|
+
* hydration). Omit for `pyric verify` builds — they have no instance. */
|
|
80
|
+
capturedBy?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildVerifyFixture(input: BuildVerifyFixtureInput): PyricVerifyFixture {
|
|
84
|
+
const events = input.sandbox.history();
|
|
85
|
+
const snapshot = input.sandbox.snapshot();
|
|
86
|
+
const firestoreDocuments = snapshot.firestore;
|
|
87
|
+
const services: PyricVerifyFixture['services'] = {};
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
input.firestoreRules != null ||
|
|
91
|
+
Object.keys(firestoreDocuments).length > 0 ||
|
|
92
|
+
events.some((event) => eventService(event) === 'firestore')
|
|
93
|
+
) {
|
|
94
|
+
services.firestore = {
|
|
95
|
+
rules: {
|
|
96
|
+
format: 'firestore.rules',
|
|
97
|
+
source: input.firestoreRules ?? '',
|
|
98
|
+
},
|
|
99
|
+
state: { documents: firestoreDocuments },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
input.rtdbRules != null ||
|
|
105
|
+
input.rtdbState !== undefined ||
|
|
106
|
+
events.some((event) => eventService(event) === 'rtdb')
|
|
107
|
+
) {
|
|
108
|
+
services.rtdb = {
|
|
109
|
+
rules: {
|
|
110
|
+
format: 'rtdb.rules.json',
|
|
111
|
+
json: input.rtdbRules ?? { rules: {} },
|
|
112
|
+
},
|
|
113
|
+
state: { tree: input.rtdbState ?? null },
|
|
114
|
+
...(input.rtdbDatabaseUrl ? { databaseUrl: input.rtdbDatabaseUrl } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (
|
|
119
|
+
input.storageRules != null ||
|
|
120
|
+
events.some((event) => eventService(event) === 'storage')
|
|
121
|
+
) {
|
|
122
|
+
services.storage = {
|
|
123
|
+
rules: {
|
|
124
|
+
format: 'storage.rules',
|
|
125
|
+
source: input.storageRules ?? '',
|
|
126
|
+
},
|
|
127
|
+
// RULES TEXT ONLY (scope note above) — objects are never captured.
|
|
128
|
+
state: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const authUsers = input.authState?.users;
|
|
133
|
+
const currentUser = input.authState?.currentUser ?? input.sandbox.currentUser;
|
|
134
|
+
if ((authUsers && authUsers.length > 0) || currentUser != null) {
|
|
135
|
+
services.auth = {
|
|
136
|
+
state: {
|
|
137
|
+
...(authUsers ? { users: authUsers } : {}),
|
|
138
|
+
...(currentUser != null ? { currentUser } : {}),
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
schema: VERIFY_FIXTURE_SCHEMA,
|
|
145
|
+
...(input.description ? { description: input.description } : {}),
|
|
146
|
+
createdAt: input.createdAt ?? new Date().toISOString(),
|
|
147
|
+
...(input.capturedBy ? { capturedBy: input.capturedBy } : {}),
|
|
148
|
+
events,
|
|
149
|
+
services,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function parseVerifyFixture(value: unknown): PyricVerifyFixture {
|
|
154
|
+
if (!isVerifyFixtureObject(value)) {
|
|
155
|
+
throw new Error('fixture must be a JSON object.');
|
|
156
|
+
}
|
|
157
|
+
if (value.schema !== VERIFY_FIXTURE_SCHEMA) {
|
|
158
|
+
throw new Error(`fixture schema must be '${VERIFY_FIXTURE_SCHEMA}'.`);
|
|
159
|
+
}
|
|
160
|
+
if (!Array.isArray(value.events)) {
|
|
161
|
+
throw new Error('fixture.events must be an array.');
|
|
162
|
+
}
|
|
163
|
+
if (!isVerifyFixtureObject(value.services)) {
|
|
164
|
+
throw new Error('fixture.services must be an object.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const services = value.services;
|
|
168
|
+
if (services.firestore !== undefined) {
|
|
169
|
+
assertFirestoreService(services.firestore);
|
|
170
|
+
}
|
|
171
|
+
if (services.rtdb !== undefined) {
|
|
172
|
+
assertRtdbService(services.rtdb);
|
|
173
|
+
}
|
|
174
|
+
if (services.storage !== undefined) {
|
|
175
|
+
assertStorageService(services.storage);
|
|
176
|
+
}
|
|
177
|
+
if (services.auth !== undefined && !isVerifyFixtureObject(services.auth)) {
|
|
178
|
+
throw new Error('fixture.services.auth must be an object.');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return value as unknown as PyricVerifyFixture;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function fixtureVerifiableServices(
|
|
185
|
+
fixture: PyricVerifyFixture,
|
|
186
|
+
): Array<'firestore' | 'rtdb'> {
|
|
187
|
+
const services: Array<'firestore' | 'rtdb'> = [];
|
|
188
|
+
if (fixture.services.firestore) services.push('firestore');
|
|
189
|
+
if (fixture.services.rtdb) services.push('rtdb');
|
|
190
|
+
return services;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function assertFirestoreService(value: unknown): void {
|
|
194
|
+
if (!isVerifyFixtureObject(value)) throw new Error('fixture.services.firestore must be an object.');
|
|
195
|
+
if (!isVerifyFixtureObject(value.rules) || value.rules.format !== 'firestore.rules' || typeof value.rules.source !== 'string') {
|
|
196
|
+
throw new Error('fixture.services.firestore.rules must contain firestore.rules source.');
|
|
197
|
+
}
|
|
198
|
+
if (!isVerifyFixtureObject(value.state) || !isVerifyFixtureObject(value.state.documents)) {
|
|
199
|
+
throw new Error('fixture.services.firestore.state.documents must be an object.');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function assertRtdbService(value: unknown): void {
|
|
204
|
+
if (!isVerifyFixtureObject(value)) throw new Error('fixture.services.rtdb must be an object.');
|
|
205
|
+
if (
|
|
206
|
+
!isVerifyFixtureObject(value.rules) ||
|
|
207
|
+
value.rules.format !== 'rtdb.rules.json' ||
|
|
208
|
+
!isRtdbRulesJson(value.rules.json)
|
|
209
|
+
) {
|
|
210
|
+
throw new Error('fixture.services.rtdb.rules must contain RTDB rules JSON.');
|
|
211
|
+
}
|
|
212
|
+
if (!isVerifyFixtureObject(value.state) || !('tree' in value.state)) {
|
|
213
|
+
throw new Error('fixture.services.rtdb.state.tree is required.');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function assertStorageService(value: unknown): void {
|
|
218
|
+
if (!isVerifyFixtureObject(value)) throw new Error('fixture.services.storage must be an object.');
|
|
219
|
+
if (
|
|
220
|
+
!isVerifyFixtureObject(value.rules) ||
|
|
221
|
+
value.rules.format !== 'storage.rules' ||
|
|
222
|
+
typeof value.rules.source !== 'string'
|
|
223
|
+
) {
|
|
224
|
+
throw new Error('fixture.services.storage.rules must contain storage.rules source.');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Re-deploy a fixture's captured storage rules into a sandbox's storage
|
|
230
|
+
* evaluator. RULES TEXT ONLY, mirroring the capture-side scope note: this
|
|
231
|
+
* never touches storage OBJECTS (persistence.ts's IDB blob store) — only
|
|
232
|
+
* `fixture.services.storage.rules.source` is applied.
|
|
233
|
+
*
|
|
234
|
+
* Storage rules are honored only on the FIRST `getStorageSandbox` call per
|
|
235
|
+
* `Sandbox` (see `storage/service.ts`), so this must run before any other
|
|
236
|
+
* code opens the storage service on `sandbox` — exactly the same ordering
|
|
237
|
+
* constraint firestore/rtdb rules already have at restore time. A no-op
|
|
238
|
+
* when the fixture carries no storage block.
|
|
239
|
+
*/
|
|
240
|
+
export function restoreStorageRulesFromFixture(
|
|
241
|
+
fixture: PyricVerifyFixture,
|
|
242
|
+
sandbox: Sandbox,
|
|
243
|
+
): void {
|
|
244
|
+
const source = fixture.services.storage?.rules?.source;
|
|
245
|
+
if (source === undefined) return;
|
|
246
|
+
getStorageSandbox(sandbox, { rules: source });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function eventService(event: SandboxEvent): EventService {
|
|
250
|
+
if ('service' in event && typeof event.service === 'string') {
|
|
251
|
+
return event.service as EventService;
|
|
252
|
+
}
|
|
253
|
+
return 'firestore';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isVerifyFixtureObject(value: unknown): value is Record<string, unknown> {
|
|
257
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
258
|
+
}
|