@pyric/cli 0.1.0-alpha.10
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/LICENSE +216 -0
- package/README.md +180 -0
- package/README.md.orig +141 -0
- package/dist/assurance/.generated/conformance-verdicts.d.ts +1071 -0
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -0
- package/dist/assurance/.generated/conformance-verdicts.js +1070 -0
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -0
- package/dist/assurance/attachment.d.ts +24 -0
- package/dist/assurance/attachment.d.ts.map +1 -0
- package/dist/assurance/attachment.js +138 -0
- package/dist/assurance/attachment.js.map +1 -0
- package/dist/assurance/browser.d.ts +7 -0
- package/dist/assurance/browser.d.ts.map +1 -0
- package/dist/assurance/browser.js +68 -0
- package/dist/assurance/browser.js.map +1 -0
- package/dist/assurance/campaign.d.ts +41 -0
- package/dist/assurance/campaign.d.ts.map +1 -0
- package/dist/assurance/campaign.js +364 -0
- package/dist/assurance/campaign.js.map +1 -0
- package/dist/assurance/capabilities.d.ts +11 -0
- package/dist/assurance/capabilities.d.ts.map +1 -0
- package/dist/assurance/capabilities.js +282 -0
- package/dist/assurance/capabilities.js.map +1 -0
- package/dist/assurance/cases.d.ts +14 -0
- package/dist/assurance/cases.d.ts.map +1 -0
- package/dist/assurance/cases.js +27 -0
- package/dist/assurance/cases.js.map +1 -0
- package/dist/assurance/index.d.ts +10 -0
- package/dist/assurance/index.d.ts.map +1 -0
- package/dist/assurance/index.js +9 -0
- package/dist/assurance/index.js.map +1 -0
- package/dist/assurance/runner.d.ts +3 -0
- package/dist/assurance/runner.d.ts.map +1 -0
- package/dist/assurance/runner.js +633 -0
- package/dist/assurance/runner.js.map +1 -0
- package/dist/assurance/tool-names.d.ts +4 -0
- package/dist/assurance/tool-names.d.ts.map +1 -0
- package/dist/assurance/tool-names.js +14 -0
- package/dist/assurance/tool-names.js.map +1 -0
- package/dist/assurance/tools.d.ts +19 -0
- package/dist/assurance/tools.d.ts.map +1 -0
- package/dist/assurance/tools.js +737 -0
- package/dist/assurance/tools.js.map +1 -0
- package/dist/assurance/types.d.ts +347 -0
- package/dist/assurance/types.d.ts.map +1 -0
- package/dist/assurance/types.js +7 -0
- package/dist/assurance/types.js.map +1 -0
- package/dist/assurance/validation.d.ts +8 -0
- package/dist/assurance/validation.d.ts.map +1 -0
- package/dist/assurance/validation.js +269 -0
- package/dist/assurance/validation.js.map +1 -0
- package/dist/bridge/client/bridge.d.ts +133 -0
- package/dist/bridge/client/bridge.d.ts.map +1 -0
- package/dist/bridge/client/bridge.js +429 -0
- package/dist/bridge/client/bridge.js.map +1 -0
- package/dist/bridge/client/dispatch.d.ts +41 -0
- package/dist/bridge/client/dispatch.d.ts.map +1 -0
- package/dist/bridge/client/dispatch.js +111 -0
- package/dist/bridge/client/dispatch.js.map +1 -0
- package/dist/bridge/client.d.ts +24 -0
- package/dist/bridge/client.d.ts.map +1 -0
- package/dist/bridge/client.js +26 -0
- package/dist/bridge/client.js.map +1 -0
- package/dist/bridge/protocol.d.ts +227 -0
- package/dist/bridge/protocol.d.ts.map +1 -0
- package/dist/bridge/protocol.js +131 -0
- package/dist/bridge/protocol.js.map +1 -0
- package/dist/bridge/server/audit.d.ts +19 -0
- package/dist/bridge/server/audit.d.ts.map +1 -0
- package/dist/bridge/server/audit.js +46 -0
- package/dist/bridge/server/audit.js.map +1 -0
- package/dist/bridge/server/bridge.d.ts +126 -0
- package/dist/bridge/server/bridge.d.ts.map +1 -0
- package/dist/bridge/server/bridge.js +383 -0
- package/dist/bridge/server/bridge.js.map +1 -0
- package/dist/bridge/server/headless.d.ts +36 -0
- package/dist/bridge/server/headless.d.ts.map +1 -0
- package/dist/bridge/server/headless.js +142 -0
- package/dist/bridge/server/headless.js.map +1 -0
- package/dist/bridge/server/json-schema-to-zod.d.ts +37 -0
- package/dist/bridge/server/json-schema-to-zod.d.ts.map +1 -0
- package/dist/bridge/server/json-schema-to-zod.js +93 -0
- package/dist/bridge/server/json-schema-to-zod.js.map +1 -0
- package/dist/bridge/server/local-bridge.d.ts +33 -0
- package/dist/bridge/server/local-bridge.d.ts.map +1 -0
- package/dist/bridge/server/local-bridge.js +41 -0
- package/dist/bridge/server/local-bridge.js.map +1 -0
- package/dist/bridge/server/logger.d.ts +28 -0
- package/dist/bridge/server/logger.d.ts.map +1 -0
- package/dist/bridge/server/logger.js +42 -0
- package/dist/bridge/server/logger.js.map +1 -0
- package/dist/bridge/server/mcp-contract.d.ts +21 -0
- package/dist/bridge/server/mcp-contract.d.ts.map +1 -0
- package/dist/bridge/server/mcp-contract.js +63 -0
- package/dist/bridge/server/mcp-contract.js.map +1 -0
- package/dist/bridge/server/mcp.d.ts +26 -0
- package/dist/bridge/server/mcp.d.ts.map +1 -0
- package/dist/bridge/server/mcp.js +102 -0
- package/dist/bridge/server/mcp.js.map +1 -0
- package/dist/bridge/server/peer.d.ts +39 -0
- package/dist/bridge/server/peer.d.ts.map +1 -0
- package/dist/bridge/server/peer.js +189 -0
- package/dist/bridge/server/peer.js.map +1 -0
- package/dist/bridge/server/standalone.d.ts +49 -0
- package/dist/bridge/server/standalone.d.ts.map +1 -0
- package/dist/bridge/server/standalone.js +267 -0
- package/dist/bridge/server/standalone.js.map +1 -0
- package/dist/bridge/server/tool-metadata.d.ts +38 -0
- package/dist/bridge/server/tool-metadata.d.ts.map +1 -0
- package/dist/bridge/server/tool-metadata.js +84 -0
- package/dist/bridge/server/tool-metadata.js.map +1 -0
- package/dist/bridge/server.d.ts +21 -0
- package/dist/bridge/server.d.ts.map +1 -0
- package/dist/bridge/server.js +18 -0
- package/dist/bridge/server.js.map +1 -0
- package/dist/cli/can-i-use.d.ts +5 -0
- package/dist/cli/can-i-use.d.ts.map +1 -0
- package/dist/cli/can-i-use.js +49 -0
- package/dist/cli/can-i-use.js.map +1 -0
- package/dist/cli/database-rules.d.ts +43 -0
- package/dist/cli/database-rules.d.ts.map +1 -0
- package/dist/cli/database-rules.js +261 -0
- package/dist/cli/database-rules.js.map +1 -0
- package/dist/cli/dev-runner.d.ts +112 -0
- package/dist/cli/dev-runner.d.ts.map +1 -0
- package/dist/cli/dev-runner.js +239 -0
- package/dist/cli/dev-runner.js.map +1 -0
- package/dist/cli/firebase-json.d.ts +66 -0
- package/dist/cli/firebase-json.d.ts.map +1 -0
- package/dist/cli/firebase-json.js +60 -0
- package/dist/cli/firebase-json.js.map +1 -0
- package/dist/cli/firestore-indexes.d.ts +21 -0
- package/dist/cli/firestore-indexes.d.ts.map +1 -0
- package/dist/cli/firestore-indexes.js +39 -0
- package/dist/cli/firestore-indexes.js.map +1 -0
- package/dist/cli/index.d.ts +56 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +387 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +50 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +182 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp-proxy.d.ts +10 -0
- package/dist/cli/mcp-proxy.d.ts.map +1 -0
- package/dist/cli/mcp-proxy.js +129 -0
- package/dist/cli/mcp-proxy.js.map +1 -0
- package/dist/cli/parse-args.d.ts +20 -0
- package/dist/cli/parse-args.d.ts.map +1 -0
- package/dist/cli/parse-args.js +74 -0
- package/dist/cli/parse-args.js.map +1 -0
- package/dist/cli/rules.d.ts +55 -0
- package/dist/cli/rules.d.ts.map +1 -0
- package/dist/cli/rules.js +199 -0
- package/dist/cli/rules.js.map +1 -0
- package/dist/cli/scope.d.ts +12 -0
- package/dist/cli/scope.d.ts.map +1 -0
- package/dist/cli/scope.js +44 -0
- package/dist/cli/scope.js.map +1 -0
- package/dist/cli/serve.d.ts +122 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +844 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/service-commands.d.ts +8 -0
- package/dist/cli/service-commands.d.ts.map +1 -0
- package/dist/cli/service-commands.js +58 -0
- package/dist/cli/service-commands.js.map +1 -0
- package/dist/cli/snapshot.d.ts +22 -0
- package/dist/cli/snapshot.d.ts.map +1 -0
- package/dist/cli/snapshot.js +139 -0
- package/dist/cli/snapshot.js.map +1 -0
- package/dist/cli/storage-rules.d.ts +21 -0
- package/dist/cli/storage-rules.d.ts.map +1 -0
- package/dist/cli/storage-rules.js +121 -0
- package/dist/cli/storage-rules.js.map +1 -0
- package/dist/cli/verify.d.ts +23 -0
- package/dist/cli/verify.d.ts.map +1 -0
- package/dist/cli/verify.js +349 -0
- package/dist/cli/verify.js.map +1 -0
- package/dist/conformance/.generated/can-i-use-browser.d.ts +44 -0
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -0
- package/dist/conformance/.generated/can-i-use-browser.js +67 -0
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -0
- package/dist/conformance/.generated/can-i-use.d.ts +61 -0
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -0
- package/dist/conformance/.generated/can-i-use.js +68 -0
- package/dist/conformance/.generated/can-i-use.js.map +1 -0
- package/dist/conformance/browser.d.ts +7 -0
- package/dist/conformance/browser.d.ts.map +1 -0
- package/dist/conformance/browser.js +8 -0
- package/dist/conformance/browser.js.map +1 -0
- package/dist/conformance/can-i-use-tool.d.ts +21 -0
- package/dist/conformance/can-i-use-tool.d.ts.map +1 -0
- package/dist/conformance/can-i-use-tool.js +40 -0
- package/dist/conformance/can-i-use-tool.js.map +1 -0
- package/dist/conformance/can-i-use.d.ts +6 -0
- package/dist/conformance/can-i-use.d.ts.map +1 -0
- package/dist/conformance/can-i-use.js +10 -0
- package/dist/conformance/can-i-use.js.map +1 -0
- package/dist/conformance/index.d.ts +8 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +2 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/tools.d.ts +4 -0
- package/dist/conformance/tools.d.ts.map +1 -0
- package/dist/conformance/tools.js +14 -0
- package/dist/conformance/tools.js.map +1 -0
- package/dist/credentials/core/memoize-ttl.d.ts +58 -0
- package/dist/credentials/core/memoize-ttl.d.ts.map +1 -0
- package/dist/credentials/core/memoize-ttl.js +88 -0
- package/dist/credentials/core/memoize-ttl.js.map +1 -0
- package/dist/credentials/core/types.d.ts +11 -0
- package/dist/credentials/core/types.d.ts.map +1 -0
- package/dist/credentials/core/types.js +2 -0
- package/dist/credentials/core/types.js.map +1 -0
- package/dist/credentials/node/from-adc.d.ts +10 -0
- package/dist/credentials/node/from-adc.d.ts.map +1 -0
- package/dist/credentials/node/from-adc.js +76 -0
- package/dist/credentials/node/from-adc.js.map +1 -0
- package/dist/credentials/node/from-service-account.d.ts +24 -0
- package/dist/credentials/node/from-service-account.d.ts.map +1 -0
- package/dist/credentials/node/from-service-account.js +108 -0
- package/dist/credentials/node/from-service-account.js.map +1 -0
- package/dist/credentials/node/index.d.ts +7 -0
- package/dist/credentials/node/index.d.ts.map +1 -0
- package/dist/credentials/node/index.js +7 -0
- package/dist/credentials/node/index.js.map +1 -0
- package/dist/discover/concurrency.d.ts +42 -0
- package/dist/discover/concurrency.d.ts.map +1 -0
- package/dist/discover/concurrency.js +113 -0
- package/dist/discover/concurrency.js.map +1 -0
- package/dist/discover/crawler-adapter.d.ts +48 -0
- package/dist/discover/crawler-adapter.d.ts.map +1 -0
- package/dist/discover/crawler-adapter.js +92 -0
- package/dist/discover/crawler-adapter.js.map +1 -0
- package/dist/discover/crawler.d.ts +294 -0
- package/dist/discover/crawler.d.ts.map +1 -0
- package/dist/discover/crawler.js +764 -0
- package/dist/discover/crawler.js.map +1 -0
- package/dist/discover/credential-free.d.ts +19 -0
- package/dist/discover/credential-free.d.ts.map +1 -0
- package/dist/discover/credential-free.js +18 -0
- package/dist/discover/credential-free.js.map +1 -0
- package/dist/discover/findCollectionGroup.d.ts +44 -0
- package/dist/discover/findCollectionGroup.d.ts.map +1 -0
- package/dist/discover/findCollectionGroup.js +81 -0
- package/dist/discover/findCollectionGroup.js.map +1 -0
- package/dist/discover/firestore-source.d.ts +60 -0
- package/dist/discover/firestore-source.d.ts.map +1 -0
- package/dist/discover/firestore-source.js +11 -0
- package/dist/discover/firestore-source.js.map +1 -0
- package/dist/discover/index.d.ts +3 -0
- package/dist/discover/index.d.ts.map +1 -0
- package/dist/discover/index.js +5 -0
- package/dist/discover/index.js.map +1 -0
- package/dist/discover/merge.d.ts +108 -0
- package/dist/discover/merge.d.ts.map +1 -0
- package/dist/discover/merge.js +394 -0
- package/dist/discover/merge.js.map +1 -0
- package/dist/discover/session.d.ts +116 -0
- package/dist/discover/session.d.ts.map +1 -0
- package/dist/discover/session.js +331 -0
- package/dist/discover/session.js.map +1 -0
- package/dist/discover/tools.d.ts +58 -0
- package/dist/discover/tools.d.ts.map +1 -0
- package/dist/discover/tools.js +115 -0
- package/dist/discover/tools.js.map +1 -0
- package/dist/discover/types.d.ts +190 -0
- package/dist/discover/types.d.ts.map +1 -0
- package/dist/discover/types.js +12 -0
- package/dist/discover/types.js.map +1 -0
- package/dist/discover/wire.d.ts +70 -0
- package/dist/discover/wire.d.ts.map +1 -0
- package/dist/discover/wire.js +309 -0
- package/dist/discover/wire.js.map +1 -0
- package/dist/functions-rtdb/child.d.ts +52 -0
- package/dist/functions-rtdb/child.d.ts.map +1 -0
- package/dist/functions-rtdb/child.js +283 -0
- package/dist/functions-rtdb/child.js.map +1 -0
- package/dist/functions-rtdb/delivery.d.ts +4 -0
- package/dist/functions-rtdb/delivery.d.ts.map +1 -0
- package/dist/functions-rtdb/delivery.js +2 -0
- package/dist/functions-rtdb/delivery.js.map +1 -0
- package/dist/functions-rtdb/discovery.d.ts +41 -0
- package/dist/functions-rtdb/discovery.d.ts.map +1 -0
- package/dist/functions-rtdb/discovery.js +86 -0
- package/dist/functions-rtdb/discovery.js.map +1 -0
- package/dist/functions-rtdb/event.d.ts +20 -0
- package/dist/functions-rtdb/event.d.ts.map +1 -0
- package/dist/functions-rtdb/event.js +45 -0
- package/dist/functions-rtdb/event.js.map +1 -0
- package/dist/functions-rtdb/execution.d.ts +20 -0
- package/dist/functions-rtdb/execution.d.ts.map +1 -0
- package/dist/functions-rtdb/execution.js +70 -0
- package/dist/functions-rtdb/execution.js.map +1 -0
- package/dist/functions-rtdb/in-memory-delivery.d.ts +9 -0
- package/dist/functions-rtdb/in-memory-delivery.d.ts.map +1 -0
- package/dist/functions-rtdb/in-memory-delivery.js +36 -0
- package/dist/functions-rtdb/in-memory-delivery.js.map +1 -0
- package/dist/functions-rtdb/project.d.ts +7 -0
- package/dist/functions-rtdb/project.d.ts.map +1 -0
- package/dist/functions-rtdb/project.js +54 -0
- package/dist/functions-rtdb/project.js.map +1 -0
- package/dist/functions-rtdb/projection.d.ts +14 -0
- package/dist/functions-rtdb/projection.d.ts.map +1 -0
- package/dist/functions-rtdb/projection.js +65 -0
- package/dist/functions-rtdb/projection.js.map +1 -0
- package/dist/functions-rtdb/reference-pattern.d.ts +5 -0
- package/dist/functions-rtdb/reference-pattern.d.ts.map +1 -0
- package/dist/functions-rtdb/reference-pattern.js +14 -0
- package/dist/functions-rtdb/reference-pattern.js.map +1 -0
- package/dist/functions-rtdb/remote-delivery.d.ts +9 -0
- package/dist/functions-rtdb/remote-delivery.d.ts.map +1 -0
- package/dist/functions-rtdb/remote-delivery.js +11 -0
- package/dist/functions-rtdb/remote-delivery.js.map +1 -0
- package/dist/pkg-version.d.ts +2 -0
- package/dist/pkg-version.d.ts.map +1 -0
- package/dist/pkg-version.js +47 -0
- package/dist/pkg-version.js.map +1 -0
- package/dist/register/esm-exports.d.ts +26 -0
- package/dist/register/esm-exports.d.ts.map +1 -0
- package/dist/register/esm-exports.js +61 -0
- package/dist/register/esm-exports.js.map +1 -0
- package/dist/register/hooks.d.ts +13 -0
- package/dist/register/hooks.d.ts.map +1 -0
- package/dist/register/hooks.js +15 -0
- package/dist/register/hooks.js.map +1 -0
- package/dist/register/index.d.ts +5 -0
- package/dist/register/index.d.ts.map +1 -0
- package/dist/register/index.js +124 -0
- package/dist/register/index.js.map +1 -0
- package/dist/register/mapping.d.ts +19 -0
- package/dist/register/mapping.d.ts.map +1 -0
- package/dist/register/mapping.js +35 -0
- package/dist/register/mapping.js.map +1 -0
- package/dist/remote/index.d.ts +232 -0
- package/dist/remote/index.d.ts.map +1 -0
- package/dist/remote/index.js +720 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/rtdb/crawl-snapshot.d.ts +17 -0
- package/dist/rtdb/crawl-snapshot.d.ts.map +1 -0
- package/dist/rtdb/crawl-snapshot.js +72 -0
- package/dist/rtdb/crawl-snapshot.js.map +1 -0
- package/dist/rtdb/inspection.d.ts +8 -0
- package/dist/rtdb/inspection.d.ts.map +1 -0
- package/dist/rtdb/inspection.js +111 -0
- package/dist/rtdb/inspection.js.map +1 -0
- package/dist/rtdb/load-rules-document.d.ts +25 -0
- package/dist/rtdb/load-rules-document.d.ts.map +1 -0
- package/dist/rtdb/load-rules-document.js +38 -0
- package/dist/rtdb/load-rules-document.js.map +1 -0
- package/dist/rtdb/rules-generation-tool.d.ts +4 -0
- package/dist/rtdb/rules-generation-tool.d.ts.map +1 -0
- package/dist/rtdb/rules-generation-tool.js +35 -0
- package/dist/rtdb/rules-generation-tool.js.map +1 -0
- package/dist/rtdb/rules-json.d.ts +8 -0
- package/dist/rtdb/rules-json.d.ts.map +1 -0
- package/dist/rtdb/rules-json.js +17 -0
- package/dist/rtdb/rules-json.js.map +1 -0
- package/dist/serve/bridge-mount.d.ts +36 -0
- package/dist/serve/bridge-mount.d.ts.map +1 -0
- package/dist/serve/bridge-mount.js +162 -0
- package/dist/serve/bridge-mount.js.map +1 -0
- package/dist/serve/bundler.d.ts +110 -0
- package/dist/serve/bundler.d.ts.map +1 -0
- package/dist/serve/bundler.js +432 -0
- package/dist/serve/bundler.js.map +1 -0
- package/dist/serve/capture-store.d.ts +27 -0
- package/dist/serve/capture-store.d.ts.map +1 -0
- package/dist/serve/capture-store.js +47 -0
- package/dist/serve/capture-store.js.map +1 -0
- package/dist/serve/discovery.d.ts +71 -0
- package/dist/serve/discovery.d.ts.map +1 -0
- package/dist/serve/discovery.js +158 -0
- package/dist/serve/discovery.js.map +1 -0
- package/dist/serve/docs-ui/_astro/_slug_.DPUwShm2.css +1 -0
- package/dist/serve/docs-ui/docs/_rhythm/index.html +99 -0
- package/dist/serve/docs-ui/docs/_rhythm.md +97 -0
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +42 -0
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent.md +72 -0
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +25 -0
- package/dist/serve/docs-ui/docs/agent/watch-and-review.md +38 -0
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +69 -0
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent.md +74 -0
- package/dist/serve/docs-ui/docs/ai-compat/index.html +423 -0
- package/dist/serve/docs-ui/docs/ai-compat.md +456 -0
- package/dist/serve/docs-ui/docs/api-reference/index.html +66 -0
- package/dist/serve/docs-ui/docs/api-reference.md +69 -0
- package/dist/serve/docs-ui/docs/app-compat/index.html +159 -0
- package/dist/serve/docs-ui/docs/app-compat.md +178 -0
- package/dist/serve/docs-ui/docs/auth-compat/index.html +644 -0
- package/dist/serve/docs-ui/docs/auth-compat.md +701 -0
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +27 -0
- package/dist/serve/docs-ui/docs/build/ai-logic.md +39 -0
- package/dist/serve/docs-ui/docs/build/authentication/index.html +65 -0
- package/dist/serve/docs-ui/docs/build/authentication.md +95 -0
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +69 -0
- package/dist/serve/docs-ui/docs/build/cloud-firestore.md +93 -0
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +27 -0
- package/dist/serve/docs-ui/docs/build/cloud-messaging.md +37 -0
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +65 -0
- package/dist/serve/docs-ui/docs/build/cloud-storage.md +86 -0
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +49 -0
- package/dist/serve/docs-ui/docs/build/realtime-database.md +67 -0
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +49 -0
- package/dist/serve/docs-ui/docs/conformance-scores.md +53 -0
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +498 -0
- package/dist/serve/docs-ui/docs/create-pyric-reference-api.md +345 -0
- package/dist/serve/docs-ui/docs/database-compat/index.html +1037 -0
- package/dist/serve/docs-ui/docs/database-compat.md +1099 -0
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +823 -0
- package/dist/serve/docs-ui/docs/firestore-compat.md +881 -0
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +72 -0
- package/dist/serve/docs-ui/docs/functions-rtdb-compat.md +83 -0
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +27 -0
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +50 -0
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +31 -0
- package/dist/serve/docs-ui/docs/get-started/start-building.md +53 -0
- package/dist/serve/docs-ui/docs/index.html +2 -0
- package/dist/serve/docs-ui/docs/index.json +11719 -0
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +264 -0
- package/dist/serve/docs-ui/docs/messaging-compat.md +285 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +73 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening.md +69 -0
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +44 -0
- package/dist/serve/docs-ui/docs/observe/shape-your-data.md +74 -0
- package/dist/serve/docs-ui/docs/overview/index.html +16 -0
- package/dist/serve/docs-ui/docs/overview.md +31 -0
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +185 -0
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api.md +206 -0
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +617 -0
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api.md +441 -0
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +583 -0
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api.md +597 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +32 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api.md +30 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +317 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api.md +285 -0
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +5830 -0
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api.md +5439 -0
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +159 -0
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api.md +122 -0
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +420 -0
- package/dist/serve/docs-ui/docs/pyric-app-reference-api.md +326 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +5452 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api.md +5271 -0
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +98 -0
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api.md +88 -0
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +2263 -0
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api.md +1477 -0
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +546 -0
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api.md +485 -0
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +506 -0
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api.md +419 -0
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +257 -0
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api.md +197 -0
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +307 -0
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api.md +254 -0
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +67 -0
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api.md +59 -0
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +2355 -0
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api.md +2036 -0
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +10 -0
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api.md +16 -0
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1044 -0
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api.md +952 -0
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +5041 -0
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +4739 -0
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +926 -0
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api.md +586 -0
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +102 -0
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +76 -0
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1851 -0
- package/dist/serve/docs-ui/docs/pyric-database-reference-api.md +1842 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +3623 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api.md +3399 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +30 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +30 -0
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +508 -0
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api.md +374 -0
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +482 -0
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api.md +348 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +2629 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api.md +2213 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +120 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api.md +110 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +202 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api.md +134 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +5019 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +4060 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +2120 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api.md +1687 -0
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +638 -0
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api.md +430 -0
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +813 -0
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api.md +634 -0
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1708 -0
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api.md +1300 -0
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +177 -0
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api.md +111 -0
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1542 -0
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api.md +903 -0
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1024 -0
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api.md +640 -0
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +2753 -0
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api.md +2171 -0
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1005 -0
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api.md +693 -0
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1388 -0
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api.md +1340 -0
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +131 -0
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api.md +76 -0
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +648 -0
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api.md +508 -0
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1457 -0
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api.md +1029 -0
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +2476 -0
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api.md +1919 -0
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1241 -0
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api.md +817 -0
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +2289 -0
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api.md +1489 -0
- package/dist/serve/docs-ui/docs/rules-compat/index.html +272 -0
- package/dist/serve/docs-ui/docs/rules-compat.md +287 -0
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +34 -0
- package/dist/serve/docs-ui/docs/secure/audit-your-rules.md +67 -0
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +105 -0
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits.md +152 -0
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +40 -0
- package/dist/serve/docs-ui/docs/secure/read-a-denial.md +58 -0
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +66 -0
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript.md +89 -0
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +158 -0
- package/dist/serve/docs-ui/docs/secure/rules-standard-library.md +116 -0
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +46 -0
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules.md +51 -0
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +94 -0
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint.md +86 -0
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +81 -0
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite.md +105 -0
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +36 -0
- package/dist/serve/docs-ui/docs/ship/ship-to-production.md +64 -0
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +74 -0
- package/dist/serve/docs-ui/docs/ship/test-in-node.md +94 -0
- package/dist/serve/docs-ui/docs/storage-compat/index.html +532 -0
- package/dist/serve/docs-ui/docs/storage-compat.md +577 -0
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +131 -0
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase.md +155 -0
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +13 -0
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility.md +21 -0
- package/dist/serve/docs-ui/index.html +1 -0
- package/dist/serve/docs-ui/llms.txt +123 -0
- package/dist/serve/entries/ai.d.ts +4 -0
- package/dist/serve/entries/ai.d.ts.map +1 -0
- package/dist/serve/entries/ai.js +184 -0
- package/dist/serve/entries/ai.js.map +1 -0
- package/dist/serve/entries/app-backend.d.ts +2 -0
- package/dist/serve/entries/app-backend.d.ts.map +1 -0
- package/dist/serve/entries/app-backend.js +4 -0
- package/dist/serve/entries/app-backend.js.map +1 -0
- package/dist/serve/entries/app-client.d.ts +5 -0
- package/dist/serve/entries/app-client.d.ts.map +1 -0
- package/dist/serve/entries/app-client.js +23 -0
- package/dist/serve/entries/app-client.js.map +1 -0
- package/dist/serve/entries/app-session-store.d.ts +14 -0
- package/dist/serve/entries/app-session-store.d.ts.map +1 -0
- package/dist/serve/entries/app-session-store.js +37 -0
- package/dist/serve/entries/app-session-store.js.map +1 -0
- package/dist/serve/entries/app.d.ts +2 -0
- package/dist/serve/entries/app.d.ts.map +1 -0
- package/dist/serve/entries/app.js +10 -0
- package/dist/serve/entries/app.js.map +1 -0
- package/dist/serve/entries/auth-helper-core.d.ts +52 -0
- package/dist/serve/entries/auth-helper-core.d.ts.map +1 -0
- package/dist/serve/entries/auth-helper-core.js +118 -0
- package/dist/serve/entries/auth-helper-core.js.map +1 -0
- package/dist/serve/entries/auth-helper-dom.d.ts +14 -0
- package/dist/serve/entries/auth-helper-dom.d.ts.map +1 -0
- package/dist/serve/entries/auth-helper-dom.js +116 -0
- package/dist/serve/entries/auth-helper-dom.js.map +1 -0
- package/dist/serve/entries/auth-helper-runtime.d.ts +5 -0
- package/dist/serve/entries/auth-helper-runtime.d.ts.map +1 -0
- package/dist/serve/entries/auth-helper-runtime.js +11 -0
- package/dist/serve/entries/auth-helper-runtime.js.map +1 -0
- package/dist/serve/entries/auth.d.ts +77 -0
- package/dist/serve/entries/auth.d.ts.map +1 -0
- package/dist/serve/entries/auth.js +201 -0
- package/dist/serve/entries/auth.js.map +1 -0
- package/dist/serve/entries/bridge-url.d.ts +25 -0
- package/dist/serve/entries/bridge-url.d.ts.map +1 -0
- package/dist/serve/entries/bridge-url.js +30 -0
- package/dist/serve/entries/bridge-url.js.map +1 -0
- package/dist/serve/entries/database.d.ts +46 -0
- package/dist/serve/entries/database.d.ts.map +1 -0
- package/dist/serve/entries/database.js +80 -0
- package/dist/serve/entries/database.js.map +1 -0
- package/dist/serve/entries/firestore.d.ts +104 -0
- package/dist/serve/entries/firestore.d.ts.map +1 -0
- package/dist/serve/entries/firestore.js +202 -0
- package/dist/serve/entries/firestore.js.map +1 -0
- package/dist/serve/entries/init.d.ts +2 -0
- package/dist/serve/entries/init.d.ts.map +1 -0
- package/dist/serve/entries/init.js +40 -0
- package/dist/serve/entries/init.js.map +1 -0
- package/dist/serve/entries/keepalive.d.ts +20 -0
- package/dist/serve/entries/keepalive.d.ts.map +1 -0
- package/dist/serve/entries/keepalive.js +38 -0
- package/dist/serve/entries/keepalive.js.map +1 -0
- package/dist/serve/entries/messaging-sw.d.ts +10 -0
- package/dist/serve/entries/messaging-sw.d.ts.map +1 -0
- package/dist/serve/entries/messaging-sw.js +36 -0
- package/dist/serve/entries/messaging-sw.js.map +1 -0
- package/dist/serve/entries/messaging.d.ts +11 -0
- package/dist/serve/entries/messaging.d.ts.map +1 -0
- package/dist/serve/entries/messaging.js +53 -0
- package/dist/serve/entries/messaging.js.map +1 -0
- package/dist/serve/entries/runtime.d.ts +46 -0
- package/dist/serve/entries/runtime.d.ts.map +1 -0
- package/dist/serve/entries/runtime.js +435 -0
- package/dist/serve/entries/runtime.js.map +1 -0
- package/dist/serve/entries/session-store.d.ts +48 -0
- package/dist/serve/entries/session-store.d.ts.map +1 -0
- package/dist/serve/entries/session-store.js +82 -0
- package/dist/serve/entries/session-store.js.map +1 -0
- package/dist/serve/entries/storage.d.ts +25 -0
- package/dist/serve/entries/storage.d.ts.map +1 -0
- package/dist/serve/entries/storage.js +61 -0
- package/dist/serve/entries/storage.js.map +1 -0
- package/dist/serve/entries/tab-sync-wiring.d.ts +88 -0
- package/dist/serve/entries/tab-sync-wiring.d.ts.map +1 -0
- package/dist/serve/entries/tab-sync-wiring.js +217 -0
- package/dist/serve/entries/tab-sync-wiring.js.map +1 -0
- package/dist/serve/entries/worker-runtime.d.ts +10 -0
- package/dist/serve/entries/worker-runtime.d.ts.map +1 -0
- package/dist/serve/entries/worker-runtime.js +59 -0
- package/dist/serve/entries/worker-runtime.js.map +1 -0
- package/dist/serve/init-payload.d.ts +21 -0
- package/dist/serve/init-payload.d.ts.map +1 -0
- package/dist/serve/init-payload.js +2 -0
- package/dist/serve/init-payload.js.map +1 -0
- package/dist/serve/namespace.d.ts +73 -0
- package/dist/serve/namespace.d.ts.map +1 -0
- package/dist/serve/namespace.js +470 -0
- package/dist/serve/namespace.js.map +1 -0
- package/dist/serve/open-browser.d.ts +23 -0
- package/dist/serve/open-browser.d.ts.map +1 -0
- package/dist/serve/open-browser.js +58 -0
- package/dist/serve/open-browser.js.map +1 -0
- package/dist/serve/rules.d.ts +77 -0
- package/dist/serve/rules.d.ts.map +1 -0
- package/dist/serve/rules.js +202 -0
- package/dist/serve/rules.js.map +1 -0
- package/dist/serve/sandbox-marker.d.ts +9 -0
- package/dist/serve/sandbox-marker.d.ts.map +1 -0
- package/dist/serve/sandbox-marker.js +31 -0
- package/dist/serve/sandbox-marker.js.map +1 -0
- package/dist/serve/server.d.ts +135 -0
- package/dist/serve/server.d.ts.map +1 -0
- package/dist/serve/server.js +328 -0
- package/dist/serve/server.js.map +1 -0
- package/dist/serve/standalone-assets.d.ts +67 -0
- package/dist/serve/standalone-assets.d.ts.map +1 -0
- package/dist/serve/standalone-assets.js +134 -0
- package/dist/serve/standalone-assets.js.map +1 -0
- package/dist/serve/state-store.d.ts +45 -0
- package/dist/serve/state-store.d.ts.map +1 -0
- package/dist/serve/state-store.js +120 -0
- package/dist/serve/state-store.js.map +1 -0
- package/dist/serve/studio/disk-project-store.d.ts +9 -0
- package/dist/serve/studio/disk-project-store.d.ts.map +1 -0
- package/dist/serve/studio/disk-project-store.js +173 -0
- package/dist/serve/studio/disk-project-store.js.map +1 -0
- package/dist/serve/studio/disk-workspace.d.ts +14 -0
- package/dist/serve/studio/disk-workspace.d.ts.map +1 -0
- package/dist/serve/studio/disk-workspace.js +152 -0
- package/dist/serve/studio/disk-workspace.js.map +1 -0
- package/dist/serve/studio/index.d.ts +10 -0
- package/dist/serve/studio/index.d.ts.map +1 -0
- package/dist/serve/studio/index.js +9 -0
- package/dist/serve/studio/index.js.map +1 -0
- package/dist/serve/studio/routes.d.ts +40 -0
- package/dist/serve/studio/routes.d.ts.map +1 -0
- package/dist/serve/studio/routes.js +182 -0
- package/dist/serve/studio/routes.js.map +1 -0
- package/dist/serve/studio/store-types.d.ts +45 -0
- package/dist/serve/studio/store-types.d.ts.map +1 -0
- package/dist/serve/studio/store-types.js +11 -0
- package/dist/serve/studio/store-types.js.map +1 -0
- package/dist/serve/studio-ui/assets/RulesCodeEditor-CXdSLKhO.js +23 -0
- package/dist/serve/studio-ui/assets/index-BlSGru_r.css +1 -0
- package/dist/serve/studio-ui/assets/index-BvtQIpGn.js +1211 -0
- package/dist/serve/studio-ui/assets/seed-3DMedLL3.js +2 -0
- package/dist/serve/studio-ui/index.html +32 -0
- package/dist/serve/studio-ui/pyric-logo.svg +76 -0
- package/dist/serve/vite-plugin.d.ts +57 -0
- package/dist/serve/vite-plugin.d.ts.map +1 -0
- package/dist/serve/vite-plugin.js +664 -0
- package/dist/serve/vite-plugin.js.map +1 -0
- package/dist/serve/worker/client/admin-firestore.d.ts +18 -0
- package/dist/serve/worker/client/admin-firestore.d.ts.map +1 -0
- package/dist/serve/worker/client/admin-firestore.js +26 -0
- package/dist/serve/worker/client/admin-firestore.js.map +1 -0
- package/dist/serve/worker/client/ai.d.ts +31 -0
- package/dist/serve/worker/client/ai.d.ts.map +1 -0
- package/dist/serve/worker/client/ai.js +89 -0
- package/dist/serve/worker/client/ai.js.map +1 -0
- package/dist/serve/worker/client/auth.d.ts +168 -0
- package/dist/serve/worker/client/auth.d.ts.map +1 -0
- package/dist/serve/worker/client/auth.js +322 -0
- package/dist/serve/worker/client/auth.js.map +1 -0
- package/dist/serve/worker/client/connection.d.ts +80 -0
- package/dist/serve/worker/client/connection.d.ts.map +1 -0
- package/dist/serve/worker/client/connection.js +148 -0
- package/dist/serve/worker/client/connection.js.map +1 -0
- package/dist/serve/worker/client/core.d.ts +109 -0
- package/dist/serve/worker/client/core.d.ts.map +1 -0
- package/dist/serve/worker/client/core.js +248 -0
- package/dist/serve/worker/client/core.js.map +1 -0
- package/dist/serve/worker/client/disconnect.d.ts +7 -0
- package/dist/serve/worker/client/disconnect.d.ts.map +1 -0
- package/dist/serve/worker/client/disconnect.js +26 -0
- package/dist/serve/worker/client/disconnect.js.map +1 -0
- package/dist/serve/worker/client/firestore-reads.d.ts +49 -0
- package/dist/serve/worker/client/firestore-reads.d.ts.map +1 -0
- package/dist/serve/worker/client/firestore-reads.js +139 -0
- package/dist/serve/worker/client/firestore-reads.js.map +1 -0
- package/dist/serve/worker/client/firestore-refs.d.ts +54 -0
- package/dist/serve/worker/client/firestore-refs.d.ts.map +1 -0
- package/dist/serve/worker/client/firestore-refs.js +167 -0
- package/dist/serve/worker/client/firestore-refs.js.map +1 -0
- package/dist/serve/worker/client/firestore-writes.d.ts +70 -0
- package/dist/serve/worker/client/firestore-writes.d.ts.map +1 -0
- package/dist/serve/worker/client/firestore-writes.js +169 -0
- package/dist/serve/worker/client/firestore-writes.js.map +1 -0
- package/dist/serve/worker/client/handles.d.ts +73 -0
- package/dist/serve/worker/client/handles.d.ts.map +1 -0
- package/dist/serve/worker/client/handles.js +4 -0
- package/dist/serve/worker/client/handles.js.map +1 -0
- package/dist/serve/worker/client/messaging.d.ts +16 -0
- package/dist/serve/worker/client/messaging.d.ts.map +1 -0
- package/dist/serve/worker/client/messaging.js +60 -0
- package/dist/serve/worker/client/messaging.js.map +1 -0
- package/dist/serve/worker/client/presence.d.ts +43 -0
- package/dist/serve/worker/client/presence.d.ts.map +1 -0
- package/dist/serve/worker/client/presence.js +132 -0
- package/dist/serve/worker/client/presence.js.map +1 -0
- package/dist/serve/worker/client/rtdb.d.ts +36 -0
- package/dist/serve/worker/client/rtdb.d.ts.map +1 -0
- package/dist/serve/worker/client/rtdb.js +241 -0
- package/dist/serve/worker/client/rtdb.js.map +1 -0
- package/dist/serve/worker/client/rules.d.ts +24 -0
- package/dist/serve/worker/client/rules.d.ts.map +1 -0
- package/dist/serve/worker/client/rules.js +44 -0
- package/dist/serve/worker/client/rules.js.map +1 -0
- package/dist/serve/worker/client/service-worker-connection.d.ts +3 -0
- package/dist/serve/worker/client/service-worker-connection.d.ts.map +1 -0
- package/dist/serve/worker/client/service-worker-connection.js +53 -0
- package/dist/serve/worker/client/service-worker-connection.js.map +1 -0
- package/dist/serve/worker/client/snapshots.d.ts +32 -0
- package/dist/serve/worker/client/snapshots.d.ts.map +1 -0
- package/dist/serve/worker/client/snapshots.js +46 -0
- package/dist/serve/worker/client/snapshots.js.map +1 -0
- package/dist/serve/worker/client/storage.d.ts +64 -0
- package/dist/serve/worker/client/storage.d.ts.map +1 -0
- package/dist/serve/worker/client/storage.js +125 -0
- package/dist/serve/worker/client/storage.js.map +1 -0
- package/dist/serve/worker/client/studio.d.ts +32 -0
- package/dist/serve/worker/client/studio.d.ts.map +1 -0
- package/dist/serve/worker/client/studio.js +61 -0
- package/dist/serve/worker/client/studio.js.map +1 -0
- package/dist/serve/worker/client.d.ts +43 -0
- package/dist/serve/worker/client.d.ts.map +1 -0
- package/dist/serve/worker/client.js +44 -0
- package/dist/serve/worker/client.js.map +1 -0
- package/dist/serve/worker/entry.d.ts +40 -0
- package/dist/serve/worker/entry.d.ts.map +1 -0
- package/dist/serve/worker/entry.js +144 -0
- package/dist/serve/worker/entry.js.map +1 -0
- package/dist/serve/worker/host/admin-firestore.d.ts +17 -0
- package/dist/serve/worker/host/admin-firestore.d.ts.map +1 -0
- package/dist/serve/worker/host/admin-firestore.js +92 -0
- package/dist/serve/worker/host/admin-firestore.js.map +1 -0
- package/dist/serve/worker/host/connection.d.ts +40 -0
- package/dist/serve/worker/host/connection.d.ts.map +1 -0
- package/dist/serve/worker/host/connection.js +153 -0
- package/dist/serve/worker/host/connection.js.map +1 -0
- package/dist/serve/worker/host/core.d.ts +159 -0
- package/dist/serve/worker/host/core.d.ts.map +1 -0
- package/dist/serve/worker/host/core.js +296 -0
- package/dist/serve/worker/host/core.js.map +1 -0
- package/dist/serve/worker/host/dispatch.d.ts +34 -0
- package/dist/serve/worker/host/dispatch.d.ts.map +1 -0
- package/dist/serve/worker/host/dispatch.js +267 -0
- package/dist/serve/worker/host/dispatch.js.map +1 -0
- package/dist/serve/worker/host/firestore-reads.d.ts +17 -0
- package/dist/serve/worker/host/firestore-reads.d.ts.map +1 -0
- package/dist/serve/worker/host/firestore-reads.js +108 -0
- package/dist/serve/worker/host/firestore-reads.js.map +1 -0
- package/dist/serve/worker/host/firestore-writes.d.ts +37 -0
- package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -0
- package/dist/serve/worker/host/firestore-writes.js +409 -0
- package/dist/serve/worker/host/firestore-writes.js.map +1 -0
- package/dist/serve/worker/host/presence.d.ts +38 -0
- package/dist/serve/worker/host/presence.d.ts.map +1 -0
- package/dist/serve/worker/host/presence.js +280 -0
- package/dist/serve/worker/host/presence.js.map +1 -0
- package/dist/serve/worker/host/rtdb.d.ts +18 -0
- package/dist/serve/worker/host/rtdb.d.ts.map +1 -0
- package/dist/serve/worker/host/rtdb.js +128 -0
- package/dist/serve/worker/host/rtdb.js.map +1 -0
- package/dist/serve/worker/host/rules.d.ts +17 -0
- package/dist/serve/worker/host/rules.d.ts.map +1 -0
- package/dist/serve/worker/host/rules.js +118 -0
- package/dist/serve/worker/host/rules.js.map +1 -0
- package/dist/serve/worker/host/storage.d.ts +19 -0
- package/dist/serve/worker/host/storage.d.ts.map +1 -0
- package/dist/serve/worker/host/storage.js +260 -0
- package/dist/serve/worker/host/storage.js.map +1 -0
- package/dist/serve/worker/host/studio.d.ts +13 -0
- package/dist/serve/worker/host/studio.d.ts.map +1 -0
- package/dist/serve/worker/host/studio.js +30 -0
- package/dist/serve/worker/host/studio.js.map +1 -0
- package/dist/serve/worker/host/subscriptions.d.ts +21 -0
- package/dist/serve/worker/host/subscriptions.d.ts.map +1 -0
- package/dist/serve/worker/host/subscriptions.js +162 -0
- package/dist/serve/worker/host/subscriptions.js.map +1 -0
- package/dist/serve/worker/host-ai.d.ts +59 -0
- package/dist/serve/worker/host-ai.d.ts.map +1 -0
- package/dist/serve/worker/host-ai.js +158 -0
- package/dist/serve/worker/host-ai.js.map +1 -0
- package/dist/serve/worker/host-auth.d.ts +47 -0
- package/dist/serve/worker/host-auth.d.ts.map +1 -0
- package/dist/serve/worker/host-auth.js +464 -0
- package/dist/serve/worker/host-auth.js.map +1 -0
- package/dist/serve/worker/host-context.d.ts +220 -0
- package/dist/serve/worker/host-context.d.ts.map +1 -0
- package/dist/serve/worker/host-context.js +45 -0
- package/dist/serve/worker/host-context.js.map +1 -0
- package/dist/serve/worker/host-events.d.ts +21 -0
- package/dist/serve/worker/host-events.d.ts.map +1 -0
- package/dist/serve/worker/host-events.js +87 -0
- package/dist/serve/worker/host-events.js.map +1 -0
- package/dist/serve/worker/host-messaging.d.ts +60 -0
- package/dist/serve/worker/host-messaging.d.ts.map +1 -0
- package/dist/serve/worker/host-messaging.js +226 -0
- package/dist/serve/worker/host-messaging.js.map +1 -0
- package/dist/serve/worker/host.d.ts +36 -0
- package/dist/serve/worker/host.d.ts.map +1 -0
- package/dist/serve/worker/host.js +39 -0
- package/dist/serve/worker/host.js.map +1 -0
- package/dist/serve/worker/index.d.ts +22 -0
- package/dist/serve/worker/index.d.ts.map +1 -0
- package/dist/serve/worker/index.js +61 -0
- package/dist/serve/worker/index.js.map +1 -0
- package/dist/serve/worker/presence-timing.d.ts +14 -0
- package/dist/serve/worker/presence-timing.d.ts.map +1 -0
- package/dist/serve/worker/presence-timing.js +14 -0
- package/dist/serve/worker/presence-timing.js.map +1 -0
- package/dist/serve/worker/protocol.d.ts +1150 -0
- package/dist/serve/worker/protocol.d.ts.map +1 -0
- package/dist/serve/worker/protocol.js +228 -0
- package/dist/serve/worker/protocol.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts +201 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -0
- package/dist/serve/worker/serve-init.js +579 -0
- package/dist/serve/worker/serve-init.js.map +1 -0
- package/dist/serve/worker/service-worker-channel.d.ts +23 -0
- package/dist/serve/worker/service-worker-channel.d.ts.map +1 -0
- package/dist/serve/worker/service-worker-channel.js +9 -0
- package/dist/serve/worker/service-worker-channel.js.map +1 -0
- package/dist/serve/worker/service-worker-relay.d.ts +22 -0
- package/dist/serve/worker/service-worker-relay.d.ts.map +1 -0
- package/dist/serve/worker/service-worker-relay.js +63 -0
- package/dist/serve/worker/service-worker-relay.js.map +1 -0
- package/dist/serve/writer-lock.d.ts +32 -0
- package/dist/serve/writer-lock.d.ts.map +1 -0
- package/dist/serve/writer-lock.js +43 -0
- package/dist/serve/writer-lock.js.map +1 -0
- package/dist/verify/cases.d.ts +37 -0
- package/dist/verify/cases.d.ts.map +1 -0
- package/dist/verify/cases.js +173 -0
- package/dist/verify/cases.js.map +1 -0
- package/dist/verify/fixture.d.ts +100 -0
- package/dist/verify/fixture.d.ts.map +1 -0
- package/dist/verify/fixture.js +156 -0
- package/dist/verify/fixture.js.map +1 -0
- package/dist/verify/index.d.ts +102 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +318 -0
- package/dist/verify/index.js.map +1 -0
- package/dist/verify/tools.d.ts +7 -0
- package/dist/verify/tools.d.ts.map +1 -0
- package/dist/verify/tools.js +83 -0
- package/dist/verify/tools.js.map +1 -0
- package/dist/version/compat-target.d.ts +18 -0
- package/dist/version/compat-target.d.ts.map +1 -0
- package/dist/version/compat-target.js +18 -0
- package/dist/version/compat-target.js.map +1 -0
- package/dist/vite.d.ts +12 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +16 -0
- package/dist/vite.js.map +1 -0
- package/package.json +144 -0
|
@@ -0,0 +1,4060 @@
|
|
|
1
|
+
|
|
2
|
+
<!-- Generated from published package declarations via TypeDoc. Do not edit by hand; run bun run docs:api:generate. -->
|
|
3
|
+
|
|
4
|
+
## Classes
|
|
5
|
+
|
|
6
|
+
<a id="persistenceschemaerror"></a>
|
|
7
|
+
|
|
8
|
+
### PersistenceSchemaError
|
|
9
|
+
|
|
10
|
+
#### Extends
|
|
11
|
+
|
|
12
|
+
- `Error`
|
|
13
|
+
|
|
14
|
+
#### Constructors
|
|
15
|
+
|
|
16
|
+
<a id="constructor"></a>
|
|
17
|
+
|
|
18
|
+
##### Constructor
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
new PersistenceSchemaError(message: string): PersistenceSchemaError;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
###### Parameters
|
|
25
|
+
|
|
26
|
+
| Parameter | Type |
|
|
27
|
+
| :------ | :------ |
|
|
28
|
+
| `message` | `string` |
|
|
29
|
+
|
|
30
|
+
###### Returns
|
|
31
|
+
|
|
32
|
+
[`PersistenceSchemaError`](#persistenceschemaerror)
|
|
33
|
+
|
|
34
|
+
###### Overrides
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
Error.constructor
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
***
|
|
41
|
+
|
|
42
|
+
<a id="sandboxcontextimpl"></a>
|
|
43
|
+
|
|
44
|
+
### SandboxContextImpl
|
|
45
|
+
|
|
46
|
+
Identity-bearing handle on a [Sandbox](#sandbox-3). A
|
|
47
|
+
`(sandbox, auth, operationContext)`
|
|
48
|
+
tuple — cheap to create, immutable, freely shareable. Service
|
|
49
|
+
factories require a `SandboxContext`; bare `Sandbox` is a type
|
|
50
|
+
error so every call site states identity explicitly.
|
|
51
|
+
|
|
52
|
+
Constructed via `Sandbox.withAuth(auth)` or chained via
|
|
53
|
+
`SandboxContext.withAuth(auth)`. The concrete class is exported
|
|
54
|
+
from `pyric/sandbox` for `instanceof` routing in service
|
|
55
|
+
factories; consumers don't construct it directly.
|
|
56
|
+
|
|
57
|
+
#### Implements
|
|
58
|
+
|
|
59
|
+
- [`SandboxContext`](#sandboxcontext)
|
|
60
|
+
|
|
61
|
+
#### Constructors
|
|
62
|
+
|
|
63
|
+
<a id="constructor-1"></a>
|
|
64
|
+
|
|
65
|
+
##### Constructor
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
new SandboxContextImpl(
|
|
69
|
+
sandbox: Sandbox,
|
|
70
|
+
auth: {
|
|
71
|
+
token?: Record<string, unknown>;
|
|
72
|
+
uid: string;
|
|
73
|
+
},
|
|
74
|
+
operationContext?: OperationContext): SandboxContextImpl;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
###### Parameters
|
|
78
|
+
|
|
79
|
+
| Parameter | Type |
|
|
80
|
+
| :------ | :------ |
|
|
81
|
+
| `sandbox` | [`Sandbox`](#sandbox-3) |
|
|
82
|
+
| `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
83
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
84
|
+
| `auth.uid?` | `string` |
|
|
85
|
+
| `operationContext?` | [`OperationContext`](#operationcontext-2) |
|
|
86
|
+
|
|
87
|
+
###### Returns
|
|
88
|
+
|
|
89
|
+
[`SandboxContextImpl`](#sandboxcontextimpl)
|
|
90
|
+
|
|
91
|
+
#### Properties
|
|
92
|
+
|
|
93
|
+
| Property | Modifier | Type | Description |
|
|
94
|
+
| :------ | :------ | :------ | :------ |
|
|
95
|
+
| <a id="auth"></a> `auth` | `readonly` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | The identity rules evaluate under for operations through this context. |
|
|
96
|
+
| `auth.token?` | `public` | `Record`\<`string`, `unknown`\> | - |
|
|
97
|
+
| `auth.uid` | `public` | `string` | - |
|
|
98
|
+
| <a id="operationcontext"></a> `operationContext` | `readonly` | [`OperationContext`](#operationcontext-2) | Immutable provenance bound to every operation issued through this handle. |
|
|
99
|
+
| <a id="sandbox"></a> `sandbox` | `readonly` | [`Sandbox`](#sandbox-3) | The data foundation this context operates against. |
|
|
100
|
+
|
|
101
|
+
#### Methods
|
|
102
|
+
|
|
103
|
+
<a id="withauth"></a>
|
|
104
|
+
|
|
105
|
+
##### withAuth()
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
withAuth(auth: {
|
|
109
|
+
token?: Record<string, unknown>;
|
|
110
|
+
uid: string;
|
|
111
|
+
}): SandboxContext;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Derive a sibling context on the same sandbox with different auth.
|
|
115
|
+
Replaces auth and its lens while preserving the operation source and
|
|
116
|
+
optional plan identity.
|
|
117
|
+
|
|
118
|
+
###### Parameters
|
|
119
|
+
|
|
120
|
+
| Parameter | Type |
|
|
121
|
+
| :------ | :------ |
|
|
122
|
+
| `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
123
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
124
|
+
| `auth.uid` | `string` |
|
|
125
|
+
|
|
126
|
+
###### Returns
|
|
127
|
+
|
|
128
|
+
[`SandboxContext`](#sandboxcontext)
|
|
129
|
+
|
|
130
|
+
###### Implementation of
|
|
131
|
+
|
|
132
|
+
[`SandboxContext`](#sandboxcontext).[`withAuth`](#withauth-8)
|
|
133
|
+
|
|
134
|
+
***
|
|
135
|
+
|
|
136
|
+
<a id="sandboxerror"></a>
|
|
137
|
+
|
|
138
|
+
### SandboxError
|
|
139
|
+
|
|
140
|
+
Sandbox-layer error. Catch with `instanceof SandboxError` and switch
|
|
141
|
+
on `code`. `denialContext` is populated for `permission-denied` only
|
|
142
|
+
(and only after Slice 4 wires it through).
|
|
143
|
+
|
|
144
|
+
Two construction forms are supported:
|
|
145
|
+
- Positional: `new SandboxError(code, message, denialContext?)` —
|
|
146
|
+
the original signature, kept for backward compatibility with
|
|
147
|
+
existing internal call sites.
|
|
148
|
+
- Options bag: `new SandboxError({ code, message, remediation? })` —
|
|
149
|
+
used when attaching remediation guidance.
|
|
150
|
+
|
|
151
|
+
#### Extends
|
|
152
|
+
|
|
153
|
+
- `Error`
|
|
154
|
+
|
|
155
|
+
#### Constructors
|
|
156
|
+
|
|
157
|
+
<a id="constructor-2"></a>
|
|
158
|
+
|
|
159
|
+
##### Constructor
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
new SandboxError(
|
|
163
|
+
code: SandboxErrorCode,
|
|
164
|
+
message: string,
|
|
165
|
+
denialContext?: DenialContext): SandboxError;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
###### Parameters
|
|
169
|
+
|
|
170
|
+
| Parameter | Type |
|
|
171
|
+
| :------ | :------ |
|
|
172
|
+
| `code` | [`SandboxErrorCode`](#sandboxerrorcode-1) |
|
|
173
|
+
| `message` | `string` |
|
|
174
|
+
| `denialContext?` | [`DenialContext`](#denialcontext-1) |
|
|
175
|
+
|
|
176
|
+
###### Returns
|
|
177
|
+
|
|
178
|
+
[`SandboxError`](#sandboxerror)
|
|
179
|
+
|
|
180
|
+
###### Overrides
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
Error.constructor
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
##### Constructor
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
new SandboxError(options: SandboxErrorOptions): SandboxError;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
###### Parameters
|
|
193
|
+
|
|
194
|
+
| Parameter | Type |
|
|
195
|
+
| :------ | :------ |
|
|
196
|
+
| `options` | `SandboxErrorOptions` |
|
|
197
|
+
|
|
198
|
+
###### Returns
|
|
199
|
+
|
|
200
|
+
[`SandboxError`](#sandboxerror)
|
|
201
|
+
|
|
202
|
+
###### Overrides
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
Error.constructor
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### Properties
|
|
209
|
+
|
|
210
|
+
| Property | Modifier | Type |
|
|
211
|
+
| :------ | :------ | :------ |
|
|
212
|
+
| <a id="code"></a> `code` | `readonly` | [`SandboxErrorCode`](#sandboxerrorcode-1) |
|
|
213
|
+
| <a id="denialcontext"></a> `denialContext?` | `readonly` | [`DenialContext`](#denialcontext-1) |
|
|
214
|
+
| <a id="remediation"></a> `remediation?` | `readonly` | `string` |
|
|
215
|
+
|
|
216
|
+
## Interfaces
|
|
217
|
+
|
|
218
|
+
<a id="branch"></a>
|
|
219
|
+
|
|
220
|
+
### Branch
|
|
221
|
+
|
|
222
|
+
An isolated, in-memory experiment seeded from a [SandboxSnapshot](#sandboxsnapshot-2).
|
|
223
|
+
|
|
224
|
+
A branch owns its own [LocalSandbox](#localsandbox) (fully isolated from the source —
|
|
225
|
+
separate `LocalEnvironment`, separate event history) plus the
|
|
226
|
+
accumulated [SandboxEvent](#sandboxevent)s applied to it via [apply](#apply). The
|
|
227
|
+
applied events are what [promote](#promote) replays onto the target.
|
|
228
|
+
|
|
229
|
+
#### Properties
|
|
230
|
+
|
|
231
|
+
| Property | Modifier | Type | Description |
|
|
232
|
+
| :------ | :------ | :------ | :------ |
|
|
233
|
+
| <a id="base"></a> `base` | `readonly` | [`SandboxSnapshot`](#sandboxsnapshot-2) | The snapshot this branch was forked from. Retained so [diff](#diff) and [promote](#promote) can reason about the baseline. |
|
|
234
|
+
| <a id="discarded"></a> `discarded` | `public` | `boolean` | Flipped by [discard](#discard); subsequent [apply](#apply)/[promote](#promote) calls throw. |
|
|
235
|
+
| <a id="events"></a> `events` | `readonly` | [`SandboxEvent`](#sandboxevent)[] | Write/op events applied to this branch since fork, in order. These are replayed onto the target by [promote](#promote). |
|
|
236
|
+
| <a id="rules"></a> `rules` | `readonly` | `string` | Rules the branch was forked with — carried so [promote](#promote) can re-seed a replay target identically. |
|
|
237
|
+
| <a id="sandbox-1"></a> `sandbox` | `readonly` | [`LocalSandbox`](#localsandbox) | The branch's own sandbox. Inspect it directly (`branch.sandbox.snapshot()`) or read docs via `branch.sandbox.admin.getDocument(path)`. |
|
|
238
|
+
|
|
239
|
+
***
|
|
240
|
+
|
|
241
|
+
<a id="broadcastchannellike"></a>
|
|
242
|
+
|
|
243
|
+
### BroadcastChannelLike
|
|
244
|
+
|
|
245
|
+
Minimal interface that `BroadcastChannel` satisfies. Provided as an
|
|
246
|
+
injectable seam so tests can run without a real browser channel.
|
|
247
|
+
|
|
248
|
+
The real `BroadcastChannel` global satisfies this interface out of the
|
|
249
|
+
box — pass it directly:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
sandbox.enableTabSync({
|
|
253
|
+
channel: new BroadcastChannel('pyric:tabsync'),
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
For SSR / Node environments, the default channel construction is guarded
|
|
258
|
+
(`typeof BroadcastChannel !== 'undefined'`) so the call site doesn't need
|
|
259
|
+
to branch — a missing global just means no sync, which is fine for server
|
|
260
|
+
renders that only care about initial data.
|
|
261
|
+
|
|
262
|
+
#### Methods
|
|
263
|
+
|
|
264
|
+
<a id="addeventlistener"></a>
|
|
265
|
+
|
|
266
|
+
##### addEventListener()
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
addEventListener(type: "message", listener: (ev: {
|
|
270
|
+
data: unknown;
|
|
271
|
+
}) => void): void;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
###### Parameters
|
|
275
|
+
|
|
276
|
+
| Parameter | Type |
|
|
277
|
+
| :------ | :------ |
|
|
278
|
+
| `type` | `"message"` |
|
|
279
|
+
| `listener` | (`ev`: \{ `data`: `unknown`; \}) => `void` |
|
|
280
|
+
|
|
281
|
+
###### Returns
|
|
282
|
+
|
|
283
|
+
`void`
|
|
284
|
+
|
|
285
|
+
<a id="close"></a>
|
|
286
|
+
|
|
287
|
+
##### close()
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
close(): void;
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
###### Returns
|
|
294
|
+
|
|
295
|
+
`void`
|
|
296
|
+
|
|
297
|
+
<a id="postmessage"></a>
|
|
298
|
+
|
|
299
|
+
##### postMessage()
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
postMessage(message: unknown): void;
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
###### Parameters
|
|
306
|
+
|
|
307
|
+
| Parameter | Type |
|
|
308
|
+
| :------ | :------ |
|
|
309
|
+
| `message` | `unknown` |
|
|
310
|
+
|
|
311
|
+
###### Returns
|
|
312
|
+
|
|
313
|
+
`void`
|
|
314
|
+
|
|
315
|
+
<a id="removeeventlistener"></a>
|
|
316
|
+
|
|
317
|
+
##### removeEventListener()
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
removeEventListener(type: "message", listener: (ev: {
|
|
321
|
+
data: unknown;
|
|
322
|
+
}) => void): void;
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
###### Parameters
|
|
326
|
+
|
|
327
|
+
| Parameter | Type |
|
|
328
|
+
| :------ | :------ |
|
|
329
|
+
| `type` | `"message"` |
|
|
330
|
+
| `listener` | (`ev`: \{ `data`: `unknown`; \}) => `void` |
|
|
331
|
+
|
|
332
|
+
###### Returns
|
|
333
|
+
|
|
334
|
+
`void`
|
|
335
|
+
|
|
336
|
+
***
|
|
337
|
+
|
|
338
|
+
<a id="denialcontext-1"></a>
|
|
339
|
+
|
|
340
|
+
### DenialContext
|
|
341
|
+
|
|
342
|
+
Structured denial context emitted alongside a `permission-denied`
|
|
343
|
+
error. Real Firebase strips this server-side for security; the
|
|
344
|
+
sandbox can expose it because it's a development tool.
|
|
345
|
+
|
|
346
|
+
`auth` and `reasons` are populated whenever the sandbox raises a
|
|
347
|
+
`permission-denied` error. `rule` (line + expression) requires
|
|
348
|
+
source-position tracking in the rules AST and is deferred — see
|
|
349
|
+
design rationale "Open questions" for the follow-up.
|
|
350
|
+
`failedFields` will be filled in once the evaluator surfaces field-
|
|
351
|
+
reference traces.
|
|
352
|
+
|
|
353
|
+
#### Properties
|
|
354
|
+
|
|
355
|
+
| Property | Type | Description |
|
|
356
|
+
| :------ | :------ | :------ |
|
|
357
|
+
| <a id="auth-1"></a> `auth?` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | Auth identity that was active when the denial fired. |
|
|
358
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
359
|
+
| `auth.uid` | `string` | - |
|
|
360
|
+
| <a id="failedfields"></a> `failedFields?` | `string`[] | Field paths in `request.resource.data` that the rule referenced and that failed. |
|
|
361
|
+
| <a id="reasons"></a> `reasons?` | `string`[] | Raw simulator reasoning lines (the underlying engine's `debugMessages`). Always present on `permission-denied`. Stable enough for log surfacing; not stable as machine-parseable data. |
|
|
362
|
+
| <a id="request"></a> `request?` | \{ `method`: `"get"` \| `"list"` \| `"create"` \| `"update"` \| `"delete"`; `path`: `string`; `resourceData?`: `Record`\<`string`, `unknown`\>; \} | Eval-time request shape — what the rule saw on `request.*`. Lets callers render a "why did this deny" frame (auth, method, path, `request.resource.data` with sentinels resolved) without re-deriving any of it from out-of-band state. |
|
|
363
|
+
| `request.method` | `"get"` \| `"list"` \| `"create"` \| `"update"` \| `"delete"` | - |
|
|
364
|
+
| `request.path` | `string` | - |
|
|
365
|
+
| `request.resourceData?` | `Record`\<`string`, `unknown`\> | The user's proposed `request.resource.data` — pre-resolution. `FieldValue.*` sentinels are preserved as their marker shapes (`{ __type: 'serverTimestamp' }`, etc.). The rule engine evaluated against the resolved form; what surfaces here is the caller's INTENT so consumers see what they tried to write. Absent for reads (no proposed write) and for `delete` (no payload). |
|
|
366
|
+
| <a id="resource"></a> `resource?` | \{ `data`: `Record`\<`string`, `unknown`\>; `exists`: `boolean`; \} | Eval-time existing-document snapshot — what the rule saw on `resource.data`. `null` data with `exists: false` mirrors how the rule sees an absent doc. Absent for collection ops (`list`). |
|
|
367
|
+
| `resource.data` | `Record`\<`string`, `unknown`\> | - |
|
|
368
|
+
| `resource.exists` | `boolean` | - |
|
|
369
|
+
| <a id="rule"></a> `rule?` | \{ `expression`: `string`; `line`: `number`; \} | The rule whose evaluation produced the denial. Best effort; may be absent until source positions land in the AST. |
|
|
370
|
+
| `rule.expression` | `string` | - |
|
|
371
|
+
| `rule.line` | `number` | - |
|
|
372
|
+
|
|
373
|
+
***
|
|
374
|
+
|
|
375
|
+
<a id="denialevent"></a>
|
|
376
|
+
|
|
377
|
+
### DenialEvent
|
|
378
|
+
|
|
379
|
+
Eval-time payload emitted to Sandbox.onDenial subscribers.
|
|
380
|
+
|
|
381
|
+
Mirrors the structured fields [DenialContext](#denialcontext-1) carries (`request`
|
|
382
|
+
+ `resource` + `reasons` + `auth`) so a host environment that wants
|
|
383
|
+
to surface denials independent of try/catch behavior gets the same
|
|
384
|
+
frame either way.
|
|
385
|
+
|
|
386
|
+
#### Properties
|
|
387
|
+
|
|
388
|
+
| Property | Type |
|
|
389
|
+
| :------ | :------ |
|
|
390
|
+
| <a id="auth-2"></a> `auth?` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
391
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
392
|
+
| `auth.uid` | `string` |
|
|
393
|
+
| <a id="code-1"></a> `code` | `"permission-denied"` |
|
|
394
|
+
| <a id="message"></a> `message` | `string` |
|
|
395
|
+
| <a id="reasons-1"></a> `reasons?` | `string`[] |
|
|
396
|
+
| <a id="request-1"></a> `request?` | \{ `method`: `"get"` \| `"list"` \| `"create"` \| `"update"` \| `"delete"`; `path`: `string`; `resourceData?`: `Record`\<`string`, `unknown`\>; \} |
|
|
397
|
+
| `request.method` | `"get"` \| `"list"` \| `"create"` \| `"update"` \| `"delete"` |
|
|
398
|
+
| `request.path` | `string` |
|
|
399
|
+
| `request.resourceData?` | `Record`\<`string`, `unknown`\> |
|
|
400
|
+
| <a id="resource-1"></a> `resource?` | \{ `data`: `Record`\<`string`, `unknown`\>; `exists`: `boolean`; \} |
|
|
401
|
+
| `resource.data` | `Record`\<`string`, `unknown`\> |
|
|
402
|
+
| `resource.exists` | `boolean` |
|
|
403
|
+
|
|
404
|
+
***
|
|
405
|
+
|
|
406
|
+
<a id="eventprovenance"></a>
|
|
407
|
+
|
|
408
|
+
### EventProvenance
|
|
409
|
+
|
|
410
|
+
Compatibility provenance carried by sandbox events while producers and
|
|
411
|
+
consumers migrate to the canonical `operationContext`.
|
|
412
|
+
|
|
413
|
+
#### Properties
|
|
414
|
+
|
|
415
|
+
| Property | Type | Description |
|
|
416
|
+
| :------ | :------ | :------ |
|
|
417
|
+
| <a id="actor"></a> `actor?` | [`EventActor`](#eventactor) | - |
|
|
418
|
+
| <a id="authlens"></a> `authLens?` | [`AuthLens`](#authlens-2) | - |
|
|
419
|
+
| <a id="operationcontext-1"></a> `operationContext?` | [`OperationContext`](#operationcontext-2) | - |
|
|
420
|
+
| <a id="planid"></a> `planId?` | `string` | Set when the op is part of an agent plan. |
|
|
421
|
+
| <a id="service"></a> `service?` | [`EventService`](#eventservice) | - |
|
|
422
|
+
|
|
423
|
+
***
|
|
424
|
+
|
|
425
|
+
<a id="listenerlifecycleevent"></a>
|
|
426
|
+
|
|
427
|
+
### ListenerLifecycleEvent
|
|
428
|
+
|
|
429
|
+
Listener lifecycle event — attach, detach, or errored. Errored
|
|
430
|
+
supersedes the prior `onSnapshotError` channel; `error` is populated
|
|
431
|
+
on the errored phase only.
|
|
432
|
+
|
|
433
|
+
#### Properties
|
|
434
|
+
|
|
435
|
+
| Property | Type | Description |
|
|
436
|
+
| :------ | :------ | :------ |
|
|
437
|
+
| <a id="at"></a> `at` | `number` | - |
|
|
438
|
+
| <a id="auth-3"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | - |
|
|
439
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
440
|
+
| `auth.uid` | `string` | - |
|
|
441
|
+
| <a id="error"></a> `error?` | \{ `code`: `"permission-denied"`; `message`: `string`; `reasons?`: `string`[]; \} | Populated on `listener_errored` only. |
|
|
442
|
+
| `error.code` | `"permission-denied"` | - |
|
|
443
|
+
| `error.message` | `string` | - |
|
|
444
|
+
| `error.reasons?` | `string`[] | - |
|
|
445
|
+
| <a id="id"></a> `id` | `string` | - |
|
|
446
|
+
| <a id="kind"></a> `kind` | `"listener_attach"` \| `"listener_detach"` \| `"listener_errored"` | - |
|
|
447
|
+
| <a id="listenerid"></a> `listenerId` | `string` | - |
|
|
448
|
+
| <a id="target"></a> `target` | \| \{ `kind`: `"doc"`; `path`: `string`; \} \| \{ `collection`: `string`; `kind`: `"query"`; \} | - |
|
|
449
|
+
|
|
450
|
+
***
|
|
451
|
+
|
|
452
|
+
<a id="localsandbox"></a>
|
|
453
|
+
|
|
454
|
+
### LocalSandbox
|
|
455
|
+
|
|
456
|
+
An in-process sandbox created by [initializeSandbox](#initializesandbox).
|
|
457
|
+
|
|
458
|
+
Service controls whose implementation requires synchronous access to local
|
|
459
|
+
state accept this type. Remote worker handles remain [Sandbox](#sandbox-3)s, but
|
|
460
|
+
are deliberately not assignable to this local-only interface.
|
|
461
|
+
|
|
462
|
+
#### Extends
|
|
463
|
+
|
|
464
|
+
- [`Sandbox`](#sandbox-3)
|
|
465
|
+
|
|
466
|
+
#### Properties
|
|
467
|
+
|
|
468
|
+
| Property | Modifier | Type | Description |
|
|
469
|
+
| :------ | :------ | :------ | :------ |
|
|
470
|
+
| <a id="local_sandbox"></a> `[LOCAL_SANDBOX]` | `readonly` | `true` | - |
|
|
471
|
+
| <a id="admin"></a> `admin` | `readonly` | `SandboxAdmin` | Admin-plane access (rule-bypass reads). Identity-agnostic by design — admin reads aren't gated on auth, so they live on the sandbox, not on a context. See SandboxAdmin. |
|
|
472
|
+
| <a id="currentuser"></a> `currentUser` | `public` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | Current authenticated user across the sandbox. Mutated by `pyric/auth`'s `signInAnonymously` / `signInWithEmailAndPassword` / `signOut` / `sandbox.setUser`. Read per-call by service factories (e.g. a future `getFirestore(sandbox)` overload) so they see auth state changes without re-binding handles. Defaults to `null` (anonymous / signed out). **Independent of `withAuth({uid})`** — `withAuth` still produces a frozen [SandboxContext](#sandboxcontext) that carries its own identity for the runner's test code (the existing pattern: explicit identity per service call). `currentUser` exists for the `pyric/auth` mirror, where consumer app code drives identity through a stateful `Auth` handle rather than naming it per call. |
|
|
473
|
+
| `currentUser.token?` | `public` | `Record`\<`string`, `unknown`\> | - |
|
|
474
|
+
| `currentUser.uid` | `public` | `string` | - |
|
|
475
|
+
|
|
476
|
+
#### Methods
|
|
477
|
+
|
|
478
|
+
<a id="clearpersistence"></a>
|
|
479
|
+
|
|
480
|
+
##### clearPersistence()
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
clearPersistence(): Promise<void>;
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Wipe the persisted blob for this sandbox's `key`. In-memory state
|
|
487
|
+
is left intact — call `reset()` if you want both. Useful for
|
|
488
|
+
"sign out and forget" flows.
|
|
489
|
+
|
|
490
|
+
No-op when persistence is not enabled.
|
|
491
|
+
|
|
492
|
+
###### Returns
|
|
493
|
+
|
|
494
|
+
`Promise`\<`void`\>
|
|
495
|
+
|
|
496
|
+
###### Inherited from
|
|
497
|
+
|
|
498
|
+
[`Sandbox`](#sandbox-3).[`clearPersistence`](#clearpersistence-4)
|
|
499
|
+
|
|
500
|
+
<a id="dispose"></a>
|
|
501
|
+
|
|
502
|
+
##### dispose()
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
dispose(): void;
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Tear down listener registries on this sandbox's environment without
|
|
509
|
+
replacing it. Use this when you're about to discard the sandbox
|
|
510
|
+
itself (e.g. `runner.reseed()` builds a fresh sandbox rather than
|
|
511
|
+
calling `reset()`) and want to drop callback references on the
|
|
512
|
+
outgoing instance defensively. Idempotent. Does not touch data.
|
|
513
|
+
|
|
514
|
+
###### Returns
|
|
515
|
+
|
|
516
|
+
`void`
|
|
517
|
+
|
|
518
|
+
###### Inherited from
|
|
519
|
+
|
|
520
|
+
[`Sandbox`](#sandbox-3).[`dispose`](#dispose-6)
|
|
521
|
+
|
|
522
|
+
<a id="enablepersistence"></a>
|
|
523
|
+
|
|
524
|
+
##### enablePersistence()
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
enablePersistence(options: SandboxPersistenceOptions): Promise<void>;
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Persist the sandbox's data to a backend and restore it on next
|
|
531
|
+
`enablePersistence` call. The default `'indexedDB'` backend turns
|
|
532
|
+
the sandbox into the host page's local Firestore — writes flush
|
|
533
|
+
automatically and a fresh `initializeSandbox()` rehydrates from
|
|
534
|
+
the prior session.
|
|
535
|
+
|
|
536
|
+
Restoration happens before the promise resolves; awaiting this
|
|
537
|
+
call is sufficient to guarantee in-memory state matches the
|
|
538
|
+
persisted blob.
|
|
539
|
+
|
|
540
|
+
Idempotent across the same `key` — calling twice in one process
|
|
541
|
+
is a no-op on the second call. Different keys are rejected as an
|
|
542
|
+
error (a sandbox can persist to at most one backend at a time).
|
|
543
|
+
|
|
544
|
+
Listener semantics: every write event the sandbox emits triggers
|
|
545
|
+
a debounced flush (default 250ms). Browser hosts additionally
|
|
546
|
+
flush on `beforeunload` so a page navigation doesn't lose the
|
|
547
|
+
tail of the debounce window.
|
|
548
|
+
|
|
549
|
+
See [SandboxPersistenceOptions](#sandboxpersistenceoptions) for backend selection and
|
|
550
|
+
tuning.
|
|
551
|
+
|
|
552
|
+
###### Parameters
|
|
553
|
+
|
|
554
|
+
| Parameter | Type |
|
|
555
|
+
| :------ | :------ |
|
|
556
|
+
| `options` | [`SandboxPersistenceOptions`](#sandboxpersistenceoptions) |
|
|
557
|
+
|
|
558
|
+
###### Returns
|
|
559
|
+
|
|
560
|
+
`Promise`\<`void`\>
|
|
561
|
+
|
|
562
|
+
###### Inherited from
|
|
563
|
+
|
|
564
|
+
[`Sandbox`](#sandbox-3).[`enablePersistence`](#enablepersistence-4)
|
|
565
|
+
|
|
566
|
+
<a id="enabletabsync"></a>
|
|
567
|
+
|
|
568
|
+
##### enableTabSync()
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
enableTabSync(options?: TabSyncOptions): () => void;
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Enable cross-tab realtime sync via `BroadcastChannel`. A write in
|
|
575
|
+
this tab will propagate to every OTHER tab of the same origin that
|
|
576
|
+
also called `enableTabSync`, causing their `onSnapshot` listeners to
|
|
577
|
+
re-evaluate — restoring production's cross-client realtime behavior.
|
|
578
|
+
|
|
579
|
+
**Opt-in, OFF by default.** Firestore only (RTDB is a follow-on).
|
|
580
|
+
|
|
581
|
+
Returns a disable function. Calling it removes the `onEvent`
|
|
582
|
+
subscription, the channel message listener, and closes the channel
|
|
583
|
+
(when it was created internally). After disable, no further propagation
|
|
584
|
+
occurs in either direction.
|
|
585
|
+
|
|
586
|
+
**Multi-writer note:** concurrent writes from two tabs to the same doc
|
|
587
|
+
produce last-write-wins divergence — there is no conflict resolution.
|
|
588
|
+
The intended model is one active writer (one user, one tab) with
|
|
589
|
+
observers in other tabs; this covers the overwhelming majority of
|
|
590
|
+
local development scenarios.
|
|
591
|
+
|
|
592
|
+
###### Parameters
|
|
593
|
+
|
|
594
|
+
| Parameter | Type |
|
|
595
|
+
| :------ | :------ |
|
|
596
|
+
| `options?` | [`TabSyncOptions`](#tabsyncoptions) |
|
|
597
|
+
|
|
598
|
+
###### Returns
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
(): void;
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
###### Returns
|
|
605
|
+
|
|
606
|
+
`void`
|
|
607
|
+
|
|
608
|
+
###### See
|
|
609
|
+
|
|
610
|
+
[TabSyncOptions](#tabsyncoptions) for channel injection (tests) and originId.
|
|
611
|
+
|
|
612
|
+
###### Example
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
// In every tab that should participate in realtime:
|
|
616
|
+
const sandbox = initializeSandbox();
|
|
617
|
+
const disableSync = sandbox.enableTabSync();
|
|
618
|
+
// Later, to stop syncing:
|
|
619
|
+
disableSync();
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
###### Inherited from
|
|
623
|
+
|
|
624
|
+
[`Sandbox`](#sandbox-3).[`enableTabSync`](#enabletabsync-4)
|
|
625
|
+
|
|
626
|
+
<a id="flush"></a>
|
|
627
|
+
|
|
628
|
+
##### flush()
|
|
629
|
+
|
|
630
|
+
```ts
|
|
631
|
+
flush(): Promise<void>;
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Force a snapshot to the configured persistence backend right now.
|
|
635
|
+
Useful before a manual navigation, or in tests that need
|
|
636
|
+
deterministic ordering against the debounce window. Resolves once
|
|
637
|
+
the write hits the backend.
|
|
638
|
+
|
|
639
|
+
Throws if persistence is not enabled.
|
|
640
|
+
|
|
641
|
+
###### Returns
|
|
642
|
+
|
|
643
|
+
`Promise`\<`void`\>
|
|
644
|
+
|
|
645
|
+
###### Inherited from
|
|
646
|
+
|
|
647
|
+
[`Sandbox`](#sandbox-3).[`flush`](#flush-6)
|
|
648
|
+
|
|
649
|
+
<a id="history"></a>
|
|
650
|
+
|
|
651
|
+
##### history()
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
history(): SandboxEvent[];
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Every [SandboxEvent](#sandboxevent) this sandbox has emitted since init or
|
|
658
|
+
the last `reset()`. Returns a defensive copy.
|
|
659
|
+
|
|
660
|
+
Use this for replay: hand the array to `replay(events, rules)`
|
|
661
|
+
from `pyric/sandbox` and the engine re-issues every
|
|
662
|
+
captured write against a fresh sandbox.
|
|
663
|
+
|
|
664
|
+
Unlike [onEvent](#onevent-4) (live stream from the moment of subscribe),
|
|
665
|
+
`history()` returns *every* event the sandbox has seen — useful
|
|
666
|
+
for consumers that attach late (e.g., loading a saved session
|
|
667
|
+
before subscribing) or that need a snapshot at a particular moment.
|
|
668
|
+
|
|
669
|
+
`reset()` and `dispose()` each append a closing `session_boundary`
|
|
670
|
+
event; `reset()` then clears the history. Consumers that took a
|
|
671
|
+
snapshot *before* reset retain the boundary in their copy.
|
|
672
|
+
|
|
673
|
+
###### Returns
|
|
674
|
+
|
|
675
|
+
[`SandboxEvent`](#sandboxevent)[]
|
|
676
|
+
|
|
677
|
+
###### Inherited from
|
|
678
|
+
|
|
679
|
+
[`Sandbox`](#sandbox-3).[`history`](#history-4)
|
|
680
|
+
|
|
681
|
+
<a id="loadsnapshot"></a>
|
|
682
|
+
|
|
683
|
+
##### loadSnapshot()
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
loadSnapshot(data: SandboxSnapshot): void;
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
CLOBBER-restore the sandbox's entire state from a prior [snapshot](#snapshot-6):
|
|
690
|
+
`reset()` (clears firestore + the signed-in session), then rebuild firestore
|
|
691
|
+
from `data` and restore each registered service. This is a TOTAL replace —
|
|
692
|
+
documents absent from `data` do NOT survive — and is the counterpart to
|
|
693
|
+
[snapshot](#snapshot-6). It is what makes "transfer (clobber) one instance's data
|
|
694
|
+
into another" and named-branch switching possible.
|
|
695
|
+
|
|
696
|
+
Fires a `session_boundary` (reset phase), re-evaluates live listeners against
|
|
697
|
+
the loaded state, and the next persistence flush writes the loaded state.
|
|
698
|
+
Services present in `data` but not currently registered are skipped (a
|
|
699
|
+
snapshot taken via [snapshot](#snapshot-6) always includes every registered
|
|
700
|
+
service, so this only affects cross-instance imports from a sandbox that had
|
|
701
|
+
a service this one lacks).
|
|
702
|
+
|
|
703
|
+
###### Parameters
|
|
704
|
+
|
|
705
|
+
| Parameter | Type |
|
|
706
|
+
| :------ | :------ |
|
|
707
|
+
| `data` | [`SandboxSnapshot`](#sandboxsnapshot-2) |
|
|
708
|
+
|
|
709
|
+
###### Returns
|
|
710
|
+
|
|
711
|
+
`void`
|
|
712
|
+
|
|
713
|
+
###### Inherited from
|
|
714
|
+
|
|
715
|
+
[`Sandbox`](#sandbox-3).[`loadSnapshot`](#loadsnapshot-4)
|
|
716
|
+
|
|
717
|
+
<a id="oncurrentuserchanged"></a>
|
|
718
|
+
|
|
719
|
+
##### onCurrentUserChanged()
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
onCurrentUserChanged(cb: (user: {
|
|
723
|
+
token?: Record<string, unknown>;
|
|
724
|
+
uid: string;
|
|
725
|
+
}) => void): () => void;
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
Subscribe to `currentUser` changes. Fires on every mutation —
|
|
729
|
+
sign-in, sign-out, user swap. Does NOT fire on subscribe.
|
|
730
|
+
|
|
731
|
+
Survives `reset()` and `dispose()` only as a no-op: a disposed
|
|
732
|
+
sandbox emits nothing further; a reset sandbox clears
|
|
733
|
+
`currentUser` to `null` (and fires the change) before swapping
|
|
734
|
+
the env.
|
|
735
|
+
|
|
736
|
+
Returns an unsubscribe function. Listener errors are swallowed —
|
|
737
|
+
subscribers are observational, the sandbox does not propagate
|
|
738
|
+
their errors.
|
|
739
|
+
|
|
740
|
+
###### Parameters
|
|
741
|
+
|
|
742
|
+
| Parameter | Type |
|
|
743
|
+
| :------ | :------ |
|
|
744
|
+
| `cb` | (`user`: \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \}) => `void` |
|
|
745
|
+
|
|
746
|
+
###### Returns
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
(): void;
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
###### Returns
|
|
753
|
+
|
|
754
|
+
`void`
|
|
755
|
+
|
|
756
|
+
###### Inherited from
|
|
757
|
+
|
|
758
|
+
[`Sandbox`](#sandbox-3).[`onCurrentUserChanged`](#oncurrentuserchanged-4)
|
|
759
|
+
|
|
760
|
+
<a id="onevent"></a>
|
|
761
|
+
|
|
762
|
+
##### onEvent()
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
onEvent(cb: (event: SandboxEvent) => void): () => void;
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
Subscribe to every event the sandbox emits — see [SandboxEvent](#sandboxevent)
|
|
769
|
+
for the discriminated-union shape. One subscription covers
|
|
770
|
+
request/denial/snapshot-error/listener-lifecycle/session-boundary;
|
|
771
|
+
filter on `event.kind` to recover individual streams.
|
|
772
|
+
|
|
773
|
+
Replaces the prior three-channel surface (`onRequest` / `onDenial`
|
|
774
|
+
/ `onSnapshotError`) — see issue #307. Filter cookbook:
|
|
775
|
+
- All denials: `event.kind === 'request' && event.result === 'deny'`
|
|
776
|
+
- Stream errors: `event.kind === 'listener_errored'`
|
|
777
|
+
- Per-op traffic: `event.kind === 'request'`
|
|
778
|
+
|
|
779
|
+
Survives `sandbox.reset()` — the subscription is held on the
|
|
780
|
+
sandbox, not on the underlying environment. A `session_boundary`
|
|
781
|
+
event with `phase: 'reset'` fires before the env swap so consumers
|
|
782
|
+
can segment their stream.
|
|
783
|
+
|
|
784
|
+
Returns an unsubscribe function. Listener errors are swallowed so a
|
|
785
|
+
faulty subscriber can't change rule semantics or hide other events.
|
|
786
|
+
Both synchronous throws and rejected Promises from async callbacks
|
|
787
|
+
are silently discarded — subscribers are **observational**, the
|
|
788
|
+
sandbox doesn't await them and doesn't propagate their errors.
|
|
789
|
+
|
|
790
|
+
###### Parameters
|
|
791
|
+
|
|
792
|
+
| Parameter | Type |
|
|
793
|
+
| :------ | :------ |
|
|
794
|
+
| `cb` | (`event`: [`SandboxEvent`](#sandboxevent)) => `void` |
|
|
795
|
+
|
|
796
|
+
###### Returns
|
|
797
|
+
|
|
798
|
+
```ts
|
|
799
|
+
(): void;
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
###### Returns
|
|
803
|
+
|
|
804
|
+
`void`
|
|
805
|
+
|
|
806
|
+
###### Inherited from
|
|
807
|
+
|
|
808
|
+
[`Sandbox`](#sandbox-3).[`onEvent`](#onevent-4)
|
|
809
|
+
|
|
810
|
+
<a id="registerpersistableservice"></a>
|
|
811
|
+
|
|
812
|
+
##### registerPersistableService()
|
|
813
|
+
|
|
814
|
+
```ts
|
|
815
|
+
registerPersistableService(name: string, hooks: PersistableService): () => void;
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
Register a service (auth, storage, …) as a persistence participant.
|
|
819
|
+
The sandbox calls `hooks.snapshot()` on every flush and
|
|
820
|
+
`hooks.restore(data)` on restore. If `hooks.subscribe` is provided,
|
|
821
|
+
the persistence controller subscribes and schedules a debounced
|
|
822
|
+
flush on each change — so auth-user edits flush promptly, not only
|
|
823
|
+
on the next Firestore write.
|
|
824
|
+
|
|
825
|
+
Returns an unregister function — call it if the service is torn
|
|
826
|
+
down before the sandbox is disposed (uncommon in practice; the
|
|
827
|
+
sandbox's `dispose()` clears the registry anyway).
|
|
828
|
+
|
|
829
|
+
Throws `failed-precondition` when a service with the same `name` is
|
|
830
|
+
already registered — the auth package registers `'auth'` once when
|
|
831
|
+
`getAuth(sandbox)` first creates a backend, so accidental double-
|
|
832
|
+
registration is a caller bug, not a no-op.
|
|
833
|
+
|
|
834
|
+
**Advanced / internal API.** Service packages (auth, storage) call
|
|
835
|
+
this when they first attach to a sandbox. Consumer app code should
|
|
836
|
+
not need to call this directly.
|
|
837
|
+
|
|
838
|
+
###### Parameters
|
|
839
|
+
|
|
840
|
+
| Parameter | Type |
|
|
841
|
+
| :------ | :------ |
|
|
842
|
+
| `name` | `string` |
|
|
843
|
+
| `hooks` | [`PersistableService`](#persistableservice) |
|
|
844
|
+
|
|
845
|
+
###### Returns
|
|
846
|
+
|
|
847
|
+
```ts
|
|
848
|
+
(): void;
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
###### Returns
|
|
852
|
+
|
|
853
|
+
`void`
|
|
854
|
+
|
|
855
|
+
###### Inherited from
|
|
856
|
+
|
|
857
|
+
[`Sandbox`](#sandbox-3).[`registerPersistableService`](#registerpersistableservice-4)
|
|
858
|
+
|
|
859
|
+
<a id="reset"></a>
|
|
860
|
+
|
|
861
|
+
##### reset()
|
|
862
|
+
|
|
863
|
+
```ts
|
|
864
|
+
reset(): void;
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
Reset the underlying environment to a fresh state — wipes data,
|
|
868
|
+
rules, and any service-specific configuration.
|
|
869
|
+
|
|
870
|
+
Snapshot listeners attached to the OLD environment are dropped at
|
|
871
|
+
the swap — they can't survive because their target docs have been
|
|
872
|
+
wiped. `onEvent` subscribers DO survive — the registry lives on
|
|
873
|
+
the sandbox, and a `session_boundary` event with `phase: 'reset'`
|
|
874
|
+
fires before the swap so subscribers know the rollover happened.
|
|
875
|
+
Existing [SandboxContext](#sandboxcontext)s continue to work — their sandbox
|
|
876
|
+
reference is stable; subsequent operations resolve to the new env.
|
|
877
|
+
|
|
878
|
+
###### Returns
|
|
879
|
+
|
|
880
|
+
`void`
|
|
881
|
+
|
|
882
|
+
###### Inherited from
|
|
883
|
+
|
|
884
|
+
[`Sandbox`](#sandbox-3).[`reset`](#reset-4)
|
|
885
|
+
|
|
886
|
+
<a id="runwithprovenance"></a>
|
|
887
|
+
|
|
888
|
+
##### runWithProvenance()?
|
|
889
|
+
|
|
890
|
+
```ts
|
|
891
|
+
optional runWithProvenance<T>(provenance: EventProvenance, fn: () => T): T;
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
Run `fn` with ambient [EventProvenance](#eventprovenance) defaults: every event
|
|
895
|
+
emitted SYNCHRONOUSLY during `fn` that doesn't already carry a
|
|
896
|
+
provenance field (on the event itself or via an explicit per-emit
|
|
897
|
+
override) is stamped with these values instead of the global
|
|
898
|
+
defaults. This is the mechanical "who issued this op" seam the
|
|
899
|
+
serve worker uses to tag Studio-issued ops (`actor: { kind:
|
|
900
|
+
'studio' }`) and to stamp the auth lens an op ran under
|
|
901
|
+
(`authLens`) — declared by the caller that issues the op, never
|
|
902
|
+
inferred from the op's shape.
|
|
903
|
+
|
|
904
|
+
SYNCHRONOUS WINDOW: the ambient values apply only until `fn`
|
|
905
|
+
returns (for an async `fn`, its synchronous prefix — which covers
|
|
906
|
+
the local environment's rules eval + event emission, since those
|
|
907
|
+
run before the op's promise is handed back). Work an op DEFERS
|
|
908
|
+
(snapshot-listener deliveries and re-evals drain on a microtask,
|
|
909
|
+
off-stack) is intentionally OUTSIDE the window: a listener re-eval
|
|
910
|
+
belongs to the listener's owner, not to whoever's write triggered
|
|
911
|
+
it. Nested calls stack — the innermost window wins per field, and
|
|
912
|
+
each window restores the previous one on exit (including on throw).
|
|
913
|
+
|
|
914
|
+
OPTIONAL because remote sandbox proxies can't provide an ambient
|
|
915
|
+
emit window (events are emitted in the worker they front). Callers
|
|
916
|
+
spell `sandbox.runWithProvenance?.(prov, fn) ?? fn()`.
|
|
917
|
+
|
|
918
|
+
###### Type Parameters
|
|
919
|
+
|
|
920
|
+
| Type Parameter |
|
|
921
|
+
| :------ |
|
|
922
|
+
| `T` |
|
|
923
|
+
|
|
924
|
+
###### Parameters
|
|
925
|
+
|
|
926
|
+
| Parameter | Type |
|
|
927
|
+
| :------ | :------ |
|
|
928
|
+
| `provenance` | [`EventProvenance`](#eventprovenance) |
|
|
929
|
+
| `fn` | () => `T` |
|
|
930
|
+
|
|
931
|
+
###### Returns
|
|
932
|
+
|
|
933
|
+
`T`
|
|
934
|
+
|
|
935
|
+
###### Inherited from
|
|
936
|
+
|
|
937
|
+
[`Sandbox`](#sandbox-3).[`runWithProvenance`](#runwithprovenance-4)
|
|
938
|
+
|
|
939
|
+
<a id="snapshot"></a>
|
|
940
|
+
|
|
941
|
+
##### snapshot()
|
|
942
|
+
|
|
943
|
+
```ts
|
|
944
|
+
snapshot(): SandboxSnapshot;
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
Capture a snapshot of every service's state. For v1 with only
|
|
948
|
+
Firestore, the return value carries a `firestore` key mapping doc
|
|
949
|
+
paths to data. Future services will add their own keys.
|
|
950
|
+
|
|
951
|
+
###### Returns
|
|
952
|
+
|
|
953
|
+
[`SandboxSnapshot`](#sandboxsnapshot-2)
|
|
954
|
+
|
|
955
|
+
###### Inherited from
|
|
956
|
+
|
|
957
|
+
[`Sandbox`](#sandbox-3).[`snapshot`](#snapshot-6)
|
|
958
|
+
|
|
959
|
+
<a id="withauth-2"></a>
|
|
960
|
+
|
|
961
|
+
##### withAuth()
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
withAuth(auth: {
|
|
965
|
+
token?: Record<string, unknown>;
|
|
966
|
+
uid: string;
|
|
967
|
+
}): SandboxContext;
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
Derive a context bound to this sandbox under the given auth
|
|
971
|
+
identity. Operations through services attached to the returned
|
|
972
|
+
context evaluate rules under that identity. Many contexts can
|
|
973
|
+
coexist for one sandbox; data is shared.
|
|
974
|
+
|
|
975
|
+
`null` is anonymous; an `AuthState` object names the user (and
|
|
976
|
+
optional custom claims). Passing `undefined` is a deliberate
|
|
977
|
+
error — say `withAuth(null)` for anonymous so the call site is
|
|
978
|
+
unambiguous.
|
|
979
|
+
|
|
980
|
+
###### Parameters
|
|
981
|
+
|
|
982
|
+
| Parameter | Type |
|
|
983
|
+
| :------ | :------ |
|
|
984
|
+
| `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
985
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
986
|
+
| `auth.uid` | `string` |
|
|
987
|
+
|
|
988
|
+
###### Returns
|
|
989
|
+
|
|
990
|
+
[`SandboxContext`](#sandboxcontext)
|
|
991
|
+
|
|
992
|
+
###### Example
|
|
993
|
+
|
|
994
|
+
```ts
|
|
995
|
+
const sandbox = initializeSandbox();
|
|
996
|
+
const dbAlice = getFirestore(sandbox.withAuth({ uid: 'alice' }));
|
|
997
|
+
const dbAnon = getFirestore(sandbox.withAuth(null));
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
###### Inherited from
|
|
1001
|
+
|
|
1002
|
+
[`Sandbox`](#sandbox-3).[`withAuth`](#withauth-6)
|
|
1003
|
+
|
|
1004
|
+
***
|
|
1005
|
+
|
|
1006
|
+
<a id="operationcontext-2"></a>
|
|
1007
|
+
|
|
1008
|
+
### OperationContext
|
|
1009
|
+
|
|
1010
|
+
Immutable operation provenance, bound where an operation is issued.
|
|
1011
|
+
Source and auth lens are deliberately orthogonal: Studio may evaluate rules
|
|
1012
|
+
as a user, while an app or agent may use an admin lens.
|
|
1013
|
+
|
|
1014
|
+
#### Properties
|
|
1015
|
+
|
|
1016
|
+
| Property | Modifier | Type |
|
|
1017
|
+
| :------ | :------ | :------ |
|
|
1018
|
+
| <a id="authlens-1"></a> `authLens` | `readonly` | [`AuthLens`](#authlens-2) |
|
|
1019
|
+
| <a id="planid-1"></a> `planId?` | `readonly` | `string` |
|
|
1020
|
+
| <a id="source"></a> `source` | `readonly` | [`EventActor`](#eventactor) |
|
|
1021
|
+
|
|
1022
|
+
***
|
|
1023
|
+
|
|
1024
|
+
<a id="operationrecord"></a>
|
|
1025
|
+
|
|
1026
|
+
### OperationRecord
|
|
1027
|
+
|
|
1028
|
+
#### Properties
|
|
1029
|
+
|
|
1030
|
+
| Property | Modifier | Type |
|
|
1031
|
+
| :------ | :------ | :------ |
|
|
1032
|
+
| <a id="at-1"></a> `at` | `readonly` | `number` |
|
|
1033
|
+
| <a id="auth-4"></a> `auth` | `readonly` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
1034
|
+
| `auth.token?` | `public` | `Record`\<`string`, `unknown`\> |
|
|
1035
|
+
| `auth.uid` | `public` | `string` |
|
|
1036
|
+
| <a id="context"></a> `context` | `readonly` | [`OperationContext`](#operationcontext-2) |
|
|
1037
|
+
| <a id="eventkind"></a> `eventKind` | `readonly` | `"request"` \| `"operation"` |
|
|
1038
|
+
| <a id="id-1"></a> `id` | `readonly` | `string` |
|
|
1039
|
+
| <a id="method"></a> `method` | `readonly` | `string` |
|
|
1040
|
+
| <a id="path"></a> `path?` | `readonly` | `string` |
|
|
1041
|
+
| <a id="result"></a> `result` | `readonly` | `"allow"` \| `"deny"` \| `"unsupported"` \| `"error"` \| `"not-applicable"` |
|
|
1042
|
+
| <a id="rules-1"></a> `rules` | `readonly` | [`RulesDisposition`](#rulesdisposition-2) |
|
|
1043
|
+
| <a id="service-1"></a> `service` | `readonly` | [`EventService`](#eventservice) |
|
|
1044
|
+
|
|
1045
|
+
***
|
|
1046
|
+
|
|
1047
|
+
<a id="persistableservice"></a>
|
|
1048
|
+
|
|
1049
|
+
### PersistableService
|
|
1050
|
+
|
|
1051
|
+
Contract for a service that can contribute its state to the sandbox
|
|
1052
|
+
persistence layer. Services (auth, storage, database) register
|
|
1053
|
+
themselves via [Sandbox.registerPersistableService](#registerpersistableservice-4) so the
|
|
1054
|
+
sandbox core stays service-agnostic — the sandbox doesn't know what
|
|
1055
|
+
auth or storage look like; it just calls `snapshot()` / `restore()`.
|
|
1056
|
+
|
|
1057
|
+
`subscribe` is optional but strongly recommended: without it, a
|
|
1058
|
+
service's changes (e.g. new users created via auth) only reach the
|
|
1059
|
+
persisted blob on the next Firestore write. With `subscribe`, the
|
|
1060
|
+
controller debounces a flush on every user-DB change — same latency
|
|
1061
|
+
as Firestore writes.
|
|
1062
|
+
|
|
1063
|
+
#### Properties
|
|
1064
|
+
|
|
1065
|
+
| Property | Type | Description |
|
|
1066
|
+
| :------ | :------ | :------ |
|
|
1067
|
+
| <a id="session"></a> `session?` | \{ `currentUid`: `string`; `mode`: `"LOCAL"` \| `"SESSION"` \| `"NONE"`; `restore`: `void`; `subscribe`: () => `void`; \} | Optional: session-level persistence hooks. When provided, the persistence controller uses these to save and restore the CURRENTLY SIGNED-IN user (not the user database — that's `snapshot`/`restore`). The controller calls `session.subscribe` so it hears every sign-in / sign-out, then writes the uid to the appropriate web-storage slot (determined by `session.mode()`). On init, the controller reads the stored uid and its storage-derived mode, then calls `session.restore(uid, mode)` to re-establish both before firing `onAuthStateChanged` as if the user just signed in. Only active when `SandboxPersistenceOptions.sessionStorage` is provided; omitting `sessionStorage` causes the controller to skip session persistence entirely (no fake durability). Auth is the only service that provides session hooks today. The field is on the generic interface so the controller stays service-agnostic — if a second service ever needs session-style semantics it can add its own hooks without changing the controller. |
|
|
1068
|
+
| `session.currentUid` | `string` | - |
|
|
1069
|
+
| `session.mode` | `"LOCAL"` \| `"SESSION"` \| `"NONE"` | - |
|
|
1070
|
+
| `session.restore` | `void` | - |
|
|
1071
|
+
| `session.subscribe` | () => `void` | - |
|
|
1072
|
+
| <a id="subscribe"></a> `subscribe?` | (`onChange`: () => `void`) => () => `void` | Optional: subscribe to changes in this service's state. When provided, the persistence controller hooks it up and schedules a debounced flush on each change — ensuring auth-user edits reach the backend promptly, not only on the next Firestore write. Must return an unsubscribe function. The controller unsubscribes on `dispose()`. |
|
|
1073
|
+
|
|
1074
|
+
#### Methods
|
|
1075
|
+
|
|
1076
|
+
<a id="restore"></a>
|
|
1077
|
+
|
|
1078
|
+
##### restore()
|
|
1079
|
+
|
|
1080
|
+
```ts
|
|
1081
|
+
restore(data: unknown): void;
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
Restore previously snapshotted state. Called once during
|
|
1085
|
+
`enablePersistence`, AFTER Firestore docs have been restored (so
|
|
1086
|
+
any service that needs Firestore to be hydrated first can rely on
|
|
1087
|
+
that ordering). Guard against bad data — the blob came from disk
|
|
1088
|
+
and may be stale or from a schema migration.
|
|
1089
|
+
|
|
1090
|
+
###### Parameters
|
|
1091
|
+
|
|
1092
|
+
| Parameter | Type |
|
|
1093
|
+
| :------ | :------ |
|
|
1094
|
+
| `data` | `unknown` |
|
|
1095
|
+
|
|
1096
|
+
###### Returns
|
|
1097
|
+
|
|
1098
|
+
`void`
|
|
1099
|
+
|
|
1100
|
+
<a id="snapshot-2"></a>
|
|
1101
|
+
|
|
1102
|
+
##### snapshot()
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
snapshot(): unknown;
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
Return a plain-JSON-serializable snapshot of this service's state.
|
|
1109
|
+
Called by the persistence controller on every flush. The return
|
|
1110
|
+
value is stored under the service's registered name in the
|
|
1111
|
+
`services` map of the persisted blob.
|
|
1112
|
+
|
|
1113
|
+
###### Returns
|
|
1114
|
+
|
|
1115
|
+
`unknown`
|
|
1116
|
+
|
|
1117
|
+
***
|
|
1118
|
+
|
|
1119
|
+
<a id="persistencebackend"></a>
|
|
1120
|
+
|
|
1121
|
+
### PersistenceBackend
|
|
1122
|
+
|
|
1123
|
+
Backend contract: read/write/list/delete RECORDS under a key. The controller
|
|
1124
|
+
partitions a snapshot into structured-clone bucket records (chunk-format.ts) so
|
|
1125
|
+
the backend stores many small records natively, never one keyspace-sized blob.
|
|
1126
|
+
Record values are structured-clone-safe objects; the backend never interprets
|
|
1127
|
+
them. (v2 and earlier used a single string blob; v3 is record-shaped.)
|
|
1128
|
+
|
|
1129
|
+
#### Methods
|
|
1130
|
+
|
|
1131
|
+
<a id="clear"></a>
|
|
1132
|
+
|
|
1133
|
+
##### clear()
|
|
1134
|
+
|
|
1135
|
+
```ts
|
|
1136
|
+
clear(key: string): Promise<void>;
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
Remove ALL records under `key`. No-op if none exist.
|
|
1140
|
+
|
|
1141
|
+
###### Parameters
|
|
1142
|
+
|
|
1143
|
+
| Parameter | Type |
|
|
1144
|
+
| :------ | :------ |
|
|
1145
|
+
| `key` | `string` |
|
|
1146
|
+
|
|
1147
|
+
###### Returns
|
|
1148
|
+
|
|
1149
|
+
`Promise`\<`void`\>
|
|
1150
|
+
|
|
1151
|
+
<a id="deleterecords"></a>
|
|
1152
|
+
|
|
1153
|
+
##### deleteRecords()
|
|
1154
|
+
|
|
1155
|
+
```ts
|
|
1156
|
+
deleteRecords(key: string, recordIds: readonly string[]): Promise<void>;
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
Delete the given record ids under `key`. No-op for ids that don't exist.
|
|
1160
|
+
|
|
1161
|
+
###### Parameters
|
|
1162
|
+
|
|
1163
|
+
| Parameter | Type |
|
|
1164
|
+
| :------ | :------ |
|
|
1165
|
+
| `key` | `string` |
|
|
1166
|
+
| `recordIds` | readonly `string`[] |
|
|
1167
|
+
|
|
1168
|
+
###### Returns
|
|
1169
|
+
|
|
1170
|
+
`Promise`\<`void`\>
|
|
1171
|
+
|
|
1172
|
+
<a id="estimate"></a>
|
|
1173
|
+
|
|
1174
|
+
##### estimate()?
|
|
1175
|
+
|
|
1176
|
+
```ts
|
|
1177
|
+
optional estimate(): Promise<{
|
|
1178
|
+
quota: number;
|
|
1179
|
+
usage: number;
|
|
1180
|
+
}>;
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
Best-effort storage usage estimate (bytes used + the quota ceiling), or
|
|
1184
|
+
`null` when the backend can't report it. Surfaced by the metadata API so a
|
|
1185
|
+
host can show how close the sandbox is to its storage limit. Optional: a
|
|
1186
|
+
backend that can't estimate simply omits it.
|
|
1187
|
+
|
|
1188
|
+
###### Returns
|
|
1189
|
+
|
|
1190
|
+
`Promise`\<\{
|
|
1191
|
+
`quota`: `number`;
|
|
1192
|
+
`usage`: `number`;
|
|
1193
|
+
\}\>
|
|
1194
|
+
|
|
1195
|
+
<a id="getrecord"></a>
|
|
1196
|
+
|
|
1197
|
+
##### getRecord()
|
|
1198
|
+
|
|
1199
|
+
```ts
|
|
1200
|
+
getRecord(key: string, recordId: string): Promise<unknown>;
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
Read one record by id under `key`. Resolves `null` when absent.
|
|
1204
|
+
|
|
1205
|
+
###### Parameters
|
|
1206
|
+
|
|
1207
|
+
| Parameter | Type |
|
|
1208
|
+
| :------ | :------ |
|
|
1209
|
+
| `key` | `string` |
|
|
1210
|
+
| `recordId` | `string` |
|
|
1211
|
+
|
|
1212
|
+
###### Returns
|
|
1213
|
+
|
|
1214
|
+
`Promise`\<`unknown`\>
|
|
1215
|
+
|
|
1216
|
+
<a id="listrecords"></a>
|
|
1217
|
+
|
|
1218
|
+
##### listRecords()
|
|
1219
|
+
|
|
1220
|
+
```ts
|
|
1221
|
+
listRecords(key: string): Promise<string[]>;
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
List all record ids under `key`, any order.
|
|
1225
|
+
|
|
1226
|
+
###### Parameters
|
|
1227
|
+
|
|
1228
|
+
| Parameter | Type |
|
|
1229
|
+
| :------ | :------ |
|
|
1230
|
+
| `key` | `string` |
|
|
1231
|
+
|
|
1232
|
+
###### Returns
|
|
1233
|
+
|
|
1234
|
+
`Promise`\<`string`[]\>
|
|
1235
|
+
|
|
1236
|
+
<a id="putrecords"></a>
|
|
1237
|
+
|
|
1238
|
+
##### putRecords()
|
|
1239
|
+
|
|
1240
|
+
```ts
|
|
1241
|
+
putRecords(key: string, records: ReadonlyMap<string, unknown>): Promise<void>;
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
Write each `[recordId, value]` under `key`, replacing any prior value.
|
|
1245
|
+
|
|
1246
|
+
###### Parameters
|
|
1247
|
+
|
|
1248
|
+
| Parameter | Type |
|
|
1249
|
+
| :------ | :------ |
|
|
1250
|
+
| `key` | `string` |
|
|
1251
|
+
| `records` | `ReadonlyMap`\<`string`, `unknown`\> |
|
|
1252
|
+
|
|
1253
|
+
###### Returns
|
|
1254
|
+
|
|
1255
|
+
`Promise`\<`void`\>
|
|
1256
|
+
|
|
1257
|
+
***
|
|
1258
|
+
|
|
1259
|
+
<a id="persistencecontroller"></a>
|
|
1260
|
+
|
|
1261
|
+
### PersistenceController
|
|
1262
|
+
|
|
1263
|
+
#### Properties
|
|
1264
|
+
|
|
1265
|
+
| Property | Modifier | Type |
|
|
1266
|
+
| :------ | :------ | :------ |
|
|
1267
|
+
| <a id="options"></a> `options` | `readonly` | `Readonly`\<[`SandboxPersistenceOptions`](#sandboxpersistenceoptions)\> |
|
|
1268
|
+
|
|
1269
|
+
#### Methods
|
|
1270
|
+
|
|
1271
|
+
<a id="clear-2"></a>
|
|
1272
|
+
|
|
1273
|
+
##### clear()
|
|
1274
|
+
|
|
1275
|
+
```ts
|
|
1276
|
+
clear(): Promise<void>;
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
Wipe persisted state. In-memory state is untouched.
|
|
1280
|
+
|
|
1281
|
+
###### Returns
|
|
1282
|
+
|
|
1283
|
+
`Promise`\<`void`\>
|
|
1284
|
+
|
|
1285
|
+
<a id="dispose-2"></a>
|
|
1286
|
+
|
|
1287
|
+
##### dispose()
|
|
1288
|
+
|
|
1289
|
+
```ts
|
|
1290
|
+
dispose(): void;
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
Detach event subscription + beforeunload listener.
|
|
1294
|
+
|
|
1295
|
+
###### Returns
|
|
1296
|
+
|
|
1297
|
+
`void`
|
|
1298
|
+
|
|
1299
|
+
<a id="flush-2"></a>
|
|
1300
|
+
|
|
1301
|
+
##### flush()
|
|
1302
|
+
|
|
1303
|
+
```ts
|
|
1304
|
+
flush(): Promise<void>;
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
Force a flush of the current sandbox state to the backend.
|
|
1308
|
+
|
|
1309
|
+
###### Returns
|
|
1310
|
+
|
|
1311
|
+
`Promise`\<`void`\>
|
|
1312
|
+
|
|
1313
|
+
***
|
|
1314
|
+
|
|
1315
|
+
<a id="remotesandbox"></a>
|
|
1316
|
+
|
|
1317
|
+
### RemoteSandbox
|
|
1318
|
+
|
|
1319
|
+
A branded remote sandbox handle. Structurally a [Sandbox](#sandbox-3) — it can
|
|
1320
|
+
be passed anywhere a `Sandbox` is accepted (notably
|
|
1321
|
+
`pyric-admin/app`'s `initializeApp({ sandbox })`) — but sync-only members
|
|
1322
|
+
that cannot be mirrored over the wire (`admin`, `snapshot()`,
|
|
1323
|
+
`history()`, …) throw a remediating error. Consumers with a remote arm
|
|
1324
|
+
dispatch on [isRemoteSandbox](#isremotesandbox) and use [channel](#channel) instead.
|
|
1325
|
+
|
|
1326
|
+
#### Extends
|
|
1327
|
+
|
|
1328
|
+
- [`Sandbox`](#sandbox-3)
|
|
1329
|
+
|
|
1330
|
+
#### Properties
|
|
1331
|
+
|
|
1332
|
+
| Property | Modifier | Type | Description |
|
|
1333
|
+
| :------ | :------ | :------ | :------ |
|
|
1334
|
+
| <a id="remote_sandbox"></a> `[REMOTE_SANDBOX]` | `readonly` | `true` | - |
|
|
1335
|
+
| <a id="admin-1"></a> `admin` | `readonly` | `SandboxAdmin` | Admin-plane access (rule-bypass reads). Identity-agnostic by design — admin reads aren't gated on auth, so they live on the sandbox, not on a context. See SandboxAdmin. |
|
|
1336
|
+
| <a id="channel"></a> `channel` | `readonly` | [`RemoteSandboxChannel`](#remotesandboxchannel-1) | The raw worker op/sub relay channel. |
|
|
1337
|
+
| <a id="currentuser-1"></a> `currentUser` | `public` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | Current authenticated user across the sandbox. Mutated by `pyric/auth`'s `signInAnonymously` / `signInWithEmailAndPassword` / `signOut` / `sandbox.setUser`. Read per-call by service factories (e.g. a future `getFirestore(sandbox)` overload) so they see auth state changes without re-binding handles. Defaults to `null` (anonymous / signed out). **Independent of `withAuth({uid})`** — `withAuth` still produces a frozen [SandboxContext](#sandboxcontext) that carries its own identity for the runner's test code (the existing pattern: explicit identity per service call). `currentUser` exists for the `pyric/auth` mirror, where consumer app code drives identity through a stateful `Auth` handle rather than naming it per call. |
|
|
1338
|
+
| `currentUser.token?` | `public` | `Record`\<`string`, `unknown`\> | - |
|
|
1339
|
+
| `currentUser.uid` | `public` | `string` | - |
|
|
1340
|
+
| <a id="serveurl"></a> `serveUrl` | `readonly` | `string` | Base URL of the `pyric dev` this handle is attached to (used in error guidance: "open <serveUrl> in a browser and retry"). |
|
|
1341
|
+
|
|
1342
|
+
#### Methods
|
|
1343
|
+
|
|
1344
|
+
<a id="clearpersistence-2"></a>
|
|
1345
|
+
|
|
1346
|
+
##### clearPersistence()
|
|
1347
|
+
|
|
1348
|
+
```ts
|
|
1349
|
+
clearPersistence(): Promise<void>;
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
Wipe the persisted blob for this sandbox's `key`. In-memory state
|
|
1353
|
+
is left intact — call `reset()` if you want both. Useful for
|
|
1354
|
+
"sign out and forget" flows.
|
|
1355
|
+
|
|
1356
|
+
No-op when persistence is not enabled.
|
|
1357
|
+
|
|
1358
|
+
###### Returns
|
|
1359
|
+
|
|
1360
|
+
`Promise`\<`void`\>
|
|
1361
|
+
|
|
1362
|
+
###### Inherited from
|
|
1363
|
+
|
|
1364
|
+
[`Sandbox`](#sandbox-3).[`clearPersistence`](#clearpersistence-4)
|
|
1365
|
+
|
|
1366
|
+
<a id="dispose-4"></a>
|
|
1367
|
+
|
|
1368
|
+
##### dispose()
|
|
1369
|
+
|
|
1370
|
+
```ts
|
|
1371
|
+
dispose(): void;
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
Tear down listener registries on this sandbox's environment without
|
|
1375
|
+
replacing it. Use this when you're about to discard the sandbox
|
|
1376
|
+
itself (e.g. `runner.reseed()` builds a fresh sandbox rather than
|
|
1377
|
+
calling `reset()`) and want to drop callback references on the
|
|
1378
|
+
outgoing instance defensively. Idempotent. Does not touch data.
|
|
1379
|
+
|
|
1380
|
+
###### Returns
|
|
1381
|
+
|
|
1382
|
+
`void`
|
|
1383
|
+
|
|
1384
|
+
###### Inherited from
|
|
1385
|
+
|
|
1386
|
+
[`Sandbox`](#sandbox-3).[`dispose`](#dispose-6)
|
|
1387
|
+
|
|
1388
|
+
<a id="enablepersistence-2"></a>
|
|
1389
|
+
|
|
1390
|
+
##### enablePersistence()
|
|
1391
|
+
|
|
1392
|
+
```ts
|
|
1393
|
+
enablePersistence(options: SandboxPersistenceOptions): Promise<void>;
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
Persist the sandbox's data to a backend and restore it on next
|
|
1397
|
+
`enablePersistence` call. The default `'indexedDB'` backend turns
|
|
1398
|
+
the sandbox into the host page's local Firestore — writes flush
|
|
1399
|
+
automatically and a fresh `initializeSandbox()` rehydrates from
|
|
1400
|
+
the prior session.
|
|
1401
|
+
|
|
1402
|
+
Restoration happens before the promise resolves; awaiting this
|
|
1403
|
+
call is sufficient to guarantee in-memory state matches the
|
|
1404
|
+
persisted blob.
|
|
1405
|
+
|
|
1406
|
+
Idempotent across the same `key` — calling twice in one process
|
|
1407
|
+
is a no-op on the second call. Different keys are rejected as an
|
|
1408
|
+
error (a sandbox can persist to at most one backend at a time).
|
|
1409
|
+
|
|
1410
|
+
Listener semantics: every write event the sandbox emits triggers
|
|
1411
|
+
a debounced flush (default 250ms). Browser hosts additionally
|
|
1412
|
+
flush on `beforeunload` so a page navigation doesn't lose the
|
|
1413
|
+
tail of the debounce window.
|
|
1414
|
+
|
|
1415
|
+
See [SandboxPersistenceOptions](#sandboxpersistenceoptions) for backend selection and
|
|
1416
|
+
tuning.
|
|
1417
|
+
|
|
1418
|
+
###### Parameters
|
|
1419
|
+
|
|
1420
|
+
| Parameter | Type |
|
|
1421
|
+
| :------ | :------ |
|
|
1422
|
+
| `options` | [`SandboxPersistenceOptions`](#sandboxpersistenceoptions) |
|
|
1423
|
+
|
|
1424
|
+
###### Returns
|
|
1425
|
+
|
|
1426
|
+
`Promise`\<`void`\>
|
|
1427
|
+
|
|
1428
|
+
###### Inherited from
|
|
1429
|
+
|
|
1430
|
+
[`Sandbox`](#sandbox-3).[`enablePersistence`](#enablepersistence-4)
|
|
1431
|
+
|
|
1432
|
+
<a id="enabletabsync-2"></a>
|
|
1433
|
+
|
|
1434
|
+
##### enableTabSync()
|
|
1435
|
+
|
|
1436
|
+
```ts
|
|
1437
|
+
enableTabSync(options?: TabSyncOptions): () => void;
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
Enable cross-tab realtime sync via `BroadcastChannel`. A write in
|
|
1441
|
+
this tab will propagate to every OTHER tab of the same origin that
|
|
1442
|
+
also called `enableTabSync`, causing their `onSnapshot` listeners to
|
|
1443
|
+
re-evaluate — restoring production's cross-client realtime behavior.
|
|
1444
|
+
|
|
1445
|
+
**Opt-in, OFF by default.** Firestore only (RTDB is a follow-on).
|
|
1446
|
+
|
|
1447
|
+
Returns a disable function. Calling it removes the `onEvent`
|
|
1448
|
+
subscription, the channel message listener, and closes the channel
|
|
1449
|
+
(when it was created internally). After disable, no further propagation
|
|
1450
|
+
occurs in either direction.
|
|
1451
|
+
|
|
1452
|
+
**Multi-writer note:** concurrent writes from two tabs to the same doc
|
|
1453
|
+
produce last-write-wins divergence — there is no conflict resolution.
|
|
1454
|
+
The intended model is one active writer (one user, one tab) with
|
|
1455
|
+
observers in other tabs; this covers the overwhelming majority of
|
|
1456
|
+
local development scenarios.
|
|
1457
|
+
|
|
1458
|
+
###### Parameters
|
|
1459
|
+
|
|
1460
|
+
| Parameter | Type |
|
|
1461
|
+
| :------ | :------ |
|
|
1462
|
+
| `options?` | [`TabSyncOptions`](#tabsyncoptions) |
|
|
1463
|
+
|
|
1464
|
+
###### Returns
|
|
1465
|
+
|
|
1466
|
+
```ts
|
|
1467
|
+
(): void;
|
|
1468
|
+
```
|
|
1469
|
+
|
|
1470
|
+
###### Returns
|
|
1471
|
+
|
|
1472
|
+
`void`
|
|
1473
|
+
|
|
1474
|
+
###### See
|
|
1475
|
+
|
|
1476
|
+
[TabSyncOptions](#tabsyncoptions) for channel injection (tests) and originId.
|
|
1477
|
+
|
|
1478
|
+
###### Example
|
|
1479
|
+
|
|
1480
|
+
```ts
|
|
1481
|
+
// In every tab that should participate in realtime:
|
|
1482
|
+
const sandbox = initializeSandbox();
|
|
1483
|
+
const disableSync = sandbox.enableTabSync();
|
|
1484
|
+
// Later, to stop syncing:
|
|
1485
|
+
disableSync();
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
###### Inherited from
|
|
1489
|
+
|
|
1490
|
+
[`Sandbox`](#sandbox-3).[`enableTabSync`](#enabletabsync-4)
|
|
1491
|
+
|
|
1492
|
+
<a id="flush-4"></a>
|
|
1493
|
+
|
|
1494
|
+
##### flush()
|
|
1495
|
+
|
|
1496
|
+
```ts
|
|
1497
|
+
flush(): Promise<void>;
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
Force a snapshot to the configured persistence backend right now.
|
|
1501
|
+
Useful before a manual navigation, or in tests that need
|
|
1502
|
+
deterministic ordering against the debounce window. Resolves once
|
|
1503
|
+
the write hits the backend.
|
|
1504
|
+
|
|
1505
|
+
Throws if persistence is not enabled.
|
|
1506
|
+
|
|
1507
|
+
###### Returns
|
|
1508
|
+
|
|
1509
|
+
`Promise`\<`void`\>
|
|
1510
|
+
|
|
1511
|
+
###### Inherited from
|
|
1512
|
+
|
|
1513
|
+
[`Sandbox`](#sandbox-3).[`flush`](#flush-6)
|
|
1514
|
+
|
|
1515
|
+
<a id="history-2"></a>
|
|
1516
|
+
|
|
1517
|
+
##### history()
|
|
1518
|
+
|
|
1519
|
+
```ts
|
|
1520
|
+
history(): SandboxEvent[];
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
Every [SandboxEvent](#sandboxevent) this sandbox has emitted since init or
|
|
1524
|
+
the last `reset()`. Returns a defensive copy.
|
|
1525
|
+
|
|
1526
|
+
Use this for replay: hand the array to `replay(events, rules)`
|
|
1527
|
+
from `pyric/sandbox` and the engine re-issues every
|
|
1528
|
+
captured write against a fresh sandbox.
|
|
1529
|
+
|
|
1530
|
+
Unlike [onEvent](#onevent-4) (live stream from the moment of subscribe),
|
|
1531
|
+
`history()` returns *every* event the sandbox has seen — useful
|
|
1532
|
+
for consumers that attach late (e.g., loading a saved session
|
|
1533
|
+
before subscribing) or that need a snapshot at a particular moment.
|
|
1534
|
+
|
|
1535
|
+
`reset()` and `dispose()` each append a closing `session_boundary`
|
|
1536
|
+
event; `reset()` then clears the history. Consumers that took a
|
|
1537
|
+
snapshot *before* reset retain the boundary in their copy.
|
|
1538
|
+
|
|
1539
|
+
###### Returns
|
|
1540
|
+
|
|
1541
|
+
[`SandboxEvent`](#sandboxevent)[]
|
|
1542
|
+
|
|
1543
|
+
###### Inherited from
|
|
1544
|
+
|
|
1545
|
+
[`Sandbox`](#sandbox-3).[`history`](#history-4)
|
|
1546
|
+
|
|
1547
|
+
<a id="loadsnapshot-2"></a>
|
|
1548
|
+
|
|
1549
|
+
##### loadSnapshot()
|
|
1550
|
+
|
|
1551
|
+
```ts
|
|
1552
|
+
loadSnapshot(data: SandboxSnapshot): void;
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
CLOBBER-restore the sandbox's entire state from a prior [snapshot](#snapshot-6):
|
|
1556
|
+
`reset()` (clears firestore + the signed-in session), then rebuild firestore
|
|
1557
|
+
from `data` and restore each registered service. This is a TOTAL replace —
|
|
1558
|
+
documents absent from `data` do NOT survive — and is the counterpart to
|
|
1559
|
+
[snapshot](#snapshot-6). It is what makes "transfer (clobber) one instance's data
|
|
1560
|
+
into another" and named-branch switching possible.
|
|
1561
|
+
|
|
1562
|
+
Fires a `session_boundary` (reset phase), re-evaluates live listeners against
|
|
1563
|
+
the loaded state, and the next persistence flush writes the loaded state.
|
|
1564
|
+
Services present in `data` but not currently registered are skipped (a
|
|
1565
|
+
snapshot taken via [snapshot](#snapshot-6) always includes every registered
|
|
1566
|
+
service, so this only affects cross-instance imports from a sandbox that had
|
|
1567
|
+
a service this one lacks).
|
|
1568
|
+
|
|
1569
|
+
###### Parameters
|
|
1570
|
+
|
|
1571
|
+
| Parameter | Type |
|
|
1572
|
+
| :------ | :------ |
|
|
1573
|
+
| `data` | [`SandboxSnapshot`](#sandboxsnapshot-2) |
|
|
1574
|
+
|
|
1575
|
+
###### Returns
|
|
1576
|
+
|
|
1577
|
+
`void`
|
|
1578
|
+
|
|
1579
|
+
###### Inherited from
|
|
1580
|
+
|
|
1581
|
+
[`Sandbox`](#sandbox-3).[`loadSnapshot`](#loadsnapshot-4)
|
|
1582
|
+
|
|
1583
|
+
<a id="oncurrentuserchanged-2"></a>
|
|
1584
|
+
|
|
1585
|
+
##### onCurrentUserChanged()
|
|
1586
|
+
|
|
1587
|
+
```ts
|
|
1588
|
+
onCurrentUserChanged(cb: (user: {
|
|
1589
|
+
token?: Record<string, unknown>;
|
|
1590
|
+
uid: string;
|
|
1591
|
+
}) => void): () => void;
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
Subscribe to `currentUser` changes. Fires on every mutation —
|
|
1595
|
+
sign-in, sign-out, user swap. Does NOT fire on subscribe.
|
|
1596
|
+
|
|
1597
|
+
Survives `reset()` and `dispose()` only as a no-op: a disposed
|
|
1598
|
+
sandbox emits nothing further; a reset sandbox clears
|
|
1599
|
+
`currentUser` to `null` (and fires the change) before swapping
|
|
1600
|
+
the env.
|
|
1601
|
+
|
|
1602
|
+
Returns an unsubscribe function. Listener errors are swallowed —
|
|
1603
|
+
subscribers are observational, the sandbox does not propagate
|
|
1604
|
+
their errors.
|
|
1605
|
+
|
|
1606
|
+
###### Parameters
|
|
1607
|
+
|
|
1608
|
+
| Parameter | Type |
|
|
1609
|
+
| :------ | :------ |
|
|
1610
|
+
| `cb` | (`user`: \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \}) => `void` |
|
|
1611
|
+
|
|
1612
|
+
###### Returns
|
|
1613
|
+
|
|
1614
|
+
```ts
|
|
1615
|
+
(): void;
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
###### Returns
|
|
1619
|
+
|
|
1620
|
+
`void`
|
|
1621
|
+
|
|
1622
|
+
###### Inherited from
|
|
1623
|
+
|
|
1624
|
+
[`Sandbox`](#sandbox-3).[`onCurrentUserChanged`](#oncurrentuserchanged-4)
|
|
1625
|
+
|
|
1626
|
+
<a id="onevent-2"></a>
|
|
1627
|
+
|
|
1628
|
+
##### onEvent()
|
|
1629
|
+
|
|
1630
|
+
```ts
|
|
1631
|
+
onEvent(cb: (event: SandboxEvent) => void): () => void;
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
Subscribe to every event the sandbox emits — see [SandboxEvent](#sandboxevent)
|
|
1635
|
+
for the discriminated-union shape. One subscription covers
|
|
1636
|
+
request/denial/snapshot-error/listener-lifecycle/session-boundary;
|
|
1637
|
+
filter on `event.kind` to recover individual streams.
|
|
1638
|
+
|
|
1639
|
+
Replaces the prior three-channel surface (`onRequest` / `onDenial`
|
|
1640
|
+
/ `onSnapshotError`) — see issue #307. Filter cookbook:
|
|
1641
|
+
- All denials: `event.kind === 'request' && event.result === 'deny'`
|
|
1642
|
+
- Stream errors: `event.kind === 'listener_errored'`
|
|
1643
|
+
- Per-op traffic: `event.kind === 'request'`
|
|
1644
|
+
|
|
1645
|
+
Survives `sandbox.reset()` — the subscription is held on the
|
|
1646
|
+
sandbox, not on the underlying environment. A `session_boundary`
|
|
1647
|
+
event with `phase: 'reset'` fires before the env swap so consumers
|
|
1648
|
+
can segment their stream.
|
|
1649
|
+
|
|
1650
|
+
Returns an unsubscribe function. Listener errors are swallowed so a
|
|
1651
|
+
faulty subscriber can't change rule semantics or hide other events.
|
|
1652
|
+
Both synchronous throws and rejected Promises from async callbacks
|
|
1653
|
+
are silently discarded — subscribers are **observational**, the
|
|
1654
|
+
sandbox doesn't await them and doesn't propagate their errors.
|
|
1655
|
+
|
|
1656
|
+
###### Parameters
|
|
1657
|
+
|
|
1658
|
+
| Parameter | Type |
|
|
1659
|
+
| :------ | :------ |
|
|
1660
|
+
| `cb` | (`event`: [`SandboxEvent`](#sandboxevent)) => `void` |
|
|
1661
|
+
|
|
1662
|
+
###### Returns
|
|
1663
|
+
|
|
1664
|
+
```ts
|
|
1665
|
+
(): void;
|
|
1666
|
+
```
|
|
1667
|
+
|
|
1668
|
+
###### Returns
|
|
1669
|
+
|
|
1670
|
+
`void`
|
|
1671
|
+
|
|
1672
|
+
###### Inherited from
|
|
1673
|
+
|
|
1674
|
+
[`Sandbox`](#sandbox-3).[`onEvent`](#onevent-4)
|
|
1675
|
+
|
|
1676
|
+
<a id="registerpersistableservice-2"></a>
|
|
1677
|
+
|
|
1678
|
+
##### registerPersistableService()
|
|
1679
|
+
|
|
1680
|
+
```ts
|
|
1681
|
+
registerPersistableService(name: string, hooks: PersistableService): () => void;
|
|
1682
|
+
```
|
|
1683
|
+
|
|
1684
|
+
Register a service (auth, storage, …) as a persistence participant.
|
|
1685
|
+
The sandbox calls `hooks.snapshot()` on every flush and
|
|
1686
|
+
`hooks.restore(data)` on restore. If `hooks.subscribe` is provided,
|
|
1687
|
+
the persistence controller subscribes and schedules a debounced
|
|
1688
|
+
flush on each change — so auth-user edits flush promptly, not only
|
|
1689
|
+
on the next Firestore write.
|
|
1690
|
+
|
|
1691
|
+
Returns an unregister function — call it if the service is torn
|
|
1692
|
+
down before the sandbox is disposed (uncommon in practice; the
|
|
1693
|
+
sandbox's `dispose()` clears the registry anyway).
|
|
1694
|
+
|
|
1695
|
+
Throws `failed-precondition` when a service with the same `name` is
|
|
1696
|
+
already registered — the auth package registers `'auth'` once when
|
|
1697
|
+
`getAuth(sandbox)` first creates a backend, so accidental double-
|
|
1698
|
+
registration is a caller bug, not a no-op.
|
|
1699
|
+
|
|
1700
|
+
**Advanced / internal API.** Service packages (auth, storage) call
|
|
1701
|
+
this when they first attach to a sandbox. Consumer app code should
|
|
1702
|
+
not need to call this directly.
|
|
1703
|
+
|
|
1704
|
+
###### Parameters
|
|
1705
|
+
|
|
1706
|
+
| Parameter | Type |
|
|
1707
|
+
| :------ | :------ |
|
|
1708
|
+
| `name` | `string` |
|
|
1709
|
+
| `hooks` | [`PersistableService`](#persistableservice) |
|
|
1710
|
+
|
|
1711
|
+
###### Returns
|
|
1712
|
+
|
|
1713
|
+
```ts
|
|
1714
|
+
(): void;
|
|
1715
|
+
```
|
|
1716
|
+
|
|
1717
|
+
###### Returns
|
|
1718
|
+
|
|
1719
|
+
`void`
|
|
1720
|
+
|
|
1721
|
+
###### Inherited from
|
|
1722
|
+
|
|
1723
|
+
[`Sandbox`](#sandbox-3).[`registerPersistableService`](#registerpersistableservice-4)
|
|
1724
|
+
|
|
1725
|
+
<a id="reset-2"></a>
|
|
1726
|
+
|
|
1727
|
+
##### reset()
|
|
1728
|
+
|
|
1729
|
+
```ts
|
|
1730
|
+
reset(): void;
|
|
1731
|
+
```
|
|
1732
|
+
|
|
1733
|
+
Reset the underlying environment to a fresh state — wipes data,
|
|
1734
|
+
rules, and any service-specific configuration.
|
|
1735
|
+
|
|
1736
|
+
Snapshot listeners attached to the OLD environment are dropped at
|
|
1737
|
+
the swap — they can't survive because their target docs have been
|
|
1738
|
+
wiped. `onEvent` subscribers DO survive — the registry lives on
|
|
1739
|
+
the sandbox, and a `session_boundary` event with `phase: 'reset'`
|
|
1740
|
+
fires before the swap so subscribers know the rollover happened.
|
|
1741
|
+
Existing [SandboxContext](#sandboxcontext)s continue to work — their sandbox
|
|
1742
|
+
reference is stable; subsequent operations resolve to the new env.
|
|
1743
|
+
|
|
1744
|
+
###### Returns
|
|
1745
|
+
|
|
1746
|
+
`void`
|
|
1747
|
+
|
|
1748
|
+
###### Inherited from
|
|
1749
|
+
|
|
1750
|
+
[`Sandbox`](#sandbox-3).[`reset`](#reset-4)
|
|
1751
|
+
|
|
1752
|
+
<a id="runwithprovenance-2"></a>
|
|
1753
|
+
|
|
1754
|
+
##### runWithProvenance()?
|
|
1755
|
+
|
|
1756
|
+
```ts
|
|
1757
|
+
optional runWithProvenance<T>(provenance: EventProvenance, fn: () => T): T;
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
Run `fn` with ambient [EventProvenance](#eventprovenance) defaults: every event
|
|
1761
|
+
emitted SYNCHRONOUSLY during `fn` that doesn't already carry a
|
|
1762
|
+
provenance field (on the event itself or via an explicit per-emit
|
|
1763
|
+
override) is stamped with these values instead of the global
|
|
1764
|
+
defaults. This is the mechanical "who issued this op" seam the
|
|
1765
|
+
serve worker uses to tag Studio-issued ops (`actor: { kind:
|
|
1766
|
+
'studio' }`) and to stamp the auth lens an op ran under
|
|
1767
|
+
(`authLens`) — declared by the caller that issues the op, never
|
|
1768
|
+
inferred from the op's shape.
|
|
1769
|
+
|
|
1770
|
+
SYNCHRONOUS WINDOW: the ambient values apply only until `fn`
|
|
1771
|
+
returns (for an async `fn`, its synchronous prefix — which covers
|
|
1772
|
+
the local environment's rules eval + event emission, since those
|
|
1773
|
+
run before the op's promise is handed back). Work an op DEFERS
|
|
1774
|
+
(snapshot-listener deliveries and re-evals drain on a microtask,
|
|
1775
|
+
off-stack) is intentionally OUTSIDE the window: a listener re-eval
|
|
1776
|
+
belongs to the listener's owner, not to whoever's write triggered
|
|
1777
|
+
it. Nested calls stack — the innermost window wins per field, and
|
|
1778
|
+
each window restores the previous one on exit (including on throw).
|
|
1779
|
+
|
|
1780
|
+
OPTIONAL because remote sandbox proxies can't provide an ambient
|
|
1781
|
+
emit window (events are emitted in the worker they front). Callers
|
|
1782
|
+
spell `sandbox.runWithProvenance?.(prov, fn) ?? fn()`.
|
|
1783
|
+
|
|
1784
|
+
###### Type Parameters
|
|
1785
|
+
|
|
1786
|
+
| Type Parameter |
|
|
1787
|
+
| :------ |
|
|
1788
|
+
| `T` |
|
|
1789
|
+
|
|
1790
|
+
###### Parameters
|
|
1791
|
+
|
|
1792
|
+
| Parameter | Type |
|
|
1793
|
+
| :------ | :------ |
|
|
1794
|
+
| `provenance` | [`EventProvenance`](#eventprovenance) |
|
|
1795
|
+
| `fn` | () => `T` |
|
|
1796
|
+
|
|
1797
|
+
###### Returns
|
|
1798
|
+
|
|
1799
|
+
`T`
|
|
1800
|
+
|
|
1801
|
+
###### Inherited from
|
|
1802
|
+
|
|
1803
|
+
[`Sandbox`](#sandbox-3).[`runWithProvenance`](#runwithprovenance-4)
|
|
1804
|
+
|
|
1805
|
+
<a id="snapshot-4"></a>
|
|
1806
|
+
|
|
1807
|
+
##### snapshot()
|
|
1808
|
+
|
|
1809
|
+
```ts
|
|
1810
|
+
snapshot(): SandboxSnapshot;
|
|
1811
|
+
```
|
|
1812
|
+
|
|
1813
|
+
Capture a snapshot of every service's state. For v1 with only
|
|
1814
|
+
Firestore, the return value carries a `firestore` key mapping doc
|
|
1815
|
+
paths to data. Future services will add their own keys.
|
|
1816
|
+
|
|
1817
|
+
###### Returns
|
|
1818
|
+
|
|
1819
|
+
[`SandboxSnapshot`](#sandboxsnapshot-2)
|
|
1820
|
+
|
|
1821
|
+
###### Inherited from
|
|
1822
|
+
|
|
1823
|
+
[`Sandbox`](#sandbox-3).[`snapshot`](#snapshot-6)
|
|
1824
|
+
|
|
1825
|
+
<a id="withauth-4"></a>
|
|
1826
|
+
|
|
1827
|
+
##### withAuth()
|
|
1828
|
+
|
|
1829
|
+
```ts
|
|
1830
|
+
withAuth(auth: {
|
|
1831
|
+
token?: Record<string, unknown>;
|
|
1832
|
+
uid: string;
|
|
1833
|
+
}): SandboxContext;
|
|
1834
|
+
```
|
|
1835
|
+
|
|
1836
|
+
Derive a context bound to this sandbox under the given auth
|
|
1837
|
+
identity. Operations through services attached to the returned
|
|
1838
|
+
context evaluate rules under that identity. Many contexts can
|
|
1839
|
+
coexist for one sandbox; data is shared.
|
|
1840
|
+
|
|
1841
|
+
`null` is anonymous; an `AuthState` object names the user (and
|
|
1842
|
+
optional custom claims). Passing `undefined` is a deliberate
|
|
1843
|
+
error — say `withAuth(null)` for anonymous so the call site is
|
|
1844
|
+
unambiguous.
|
|
1845
|
+
|
|
1846
|
+
###### Parameters
|
|
1847
|
+
|
|
1848
|
+
| Parameter | Type |
|
|
1849
|
+
| :------ | :------ |
|
|
1850
|
+
| `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
1851
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
1852
|
+
| `auth.uid` | `string` |
|
|
1853
|
+
|
|
1854
|
+
###### Returns
|
|
1855
|
+
|
|
1856
|
+
[`SandboxContext`](#sandboxcontext)
|
|
1857
|
+
|
|
1858
|
+
###### Example
|
|
1859
|
+
|
|
1860
|
+
```ts
|
|
1861
|
+
const sandbox = initializeSandbox();
|
|
1862
|
+
const dbAlice = getFirestore(sandbox.withAuth({ uid: 'alice' }));
|
|
1863
|
+
const dbAnon = getFirestore(sandbox.withAuth(null));
|
|
1864
|
+
```
|
|
1865
|
+
|
|
1866
|
+
###### Inherited from
|
|
1867
|
+
|
|
1868
|
+
[`Sandbox`](#sandbox-3).[`withAuth`](#withauth-6)
|
|
1869
|
+
|
|
1870
|
+
***
|
|
1871
|
+
|
|
1872
|
+
<a id="remotesandboxchannel-1"></a>
|
|
1873
|
+
|
|
1874
|
+
### RemoteSandboxChannel
|
|
1875
|
+
|
|
1876
|
+
The minimal worker-relay channel a remote sandbox handle carries.
|
|
1877
|
+
|
|
1878
|
+
Structural mirror of `@pyric/cli/remote`'s `RemoteSandboxChannel`: one
|
|
1879
|
+
method to dispatch any SharedWorker-protocol op, one to register a
|
|
1880
|
+
snap-delivering subscription. Payloads are typed openly here (the real
|
|
1881
|
+
discriminated unions live in `@pyric/cli`' worker protocol); callers in
|
|
1882
|
+
`pyric-admin` spell the concrete op objects (`rtdb.set`, `auth.listUsers`,
|
|
1883
|
+
…) and pin their own `actAs` lens — nothing is pinned by the channel.
|
|
1884
|
+
|
|
1885
|
+
#### Methods
|
|
1886
|
+
|
|
1887
|
+
<a id="op"></a>
|
|
1888
|
+
|
|
1889
|
+
##### op()
|
|
1890
|
+
|
|
1891
|
+
```ts
|
|
1892
|
+
op(op: {
|
|
1893
|
+
method: string;
|
|
1894
|
+
} & Record<string, unknown>): Promise<unknown>;
|
|
1895
|
+
```
|
|
1896
|
+
|
|
1897
|
+
Dispatch one worker op. Resolves with the worker's result value;
|
|
1898
|
+
rejects with an `Error` carrying a `.code` (including the fail-fast
|
|
1899
|
+
"no browser tab is connected — open <serve url>" guidance when no
|
|
1900
|
+
peer is registered).
|
|
1901
|
+
|
|
1902
|
+
###### Parameters
|
|
1903
|
+
|
|
1904
|
+
| Parameter | Type |
|
|
1905
|
+
| :------ | :------ |
|
|
1906
|
+
| `op` | \{ `method`: `string`; \} & `Record`\<`string`, `unknown`\> |
|
|
1907
|
+
|
|
1908
|
+
###### Returns
|
|
1909
|
+
|
|
1910
|
+
`Promise`\<`unknown`\>
|
|
1911
|
+
|
|
1912
|
+
<a id="subscribe-1"></a>
|
|
1913
|
+
|
|
1914
|
+
##### subscribe()
|
|
1915
|
+
|
|
1916
|
+
```ts
|
|
1917
|
+
subscribe(
|
|
1918
|
+
sub: {
|
|
1919
|
+
target: unknown;
|
|
1920
|
+
} & Record<string, unknown>,
|
|
1921
|
+
onSnap: (value: unknown) => void,
|
|
1922
|
+
onError?: (err: Error & {
|
|
1923
|
+
code: string;
|
|
1924
|
+
}) => void): () => void;
|
|
1925
|
+
```
|
|
1926
|
+
|
|
1927
|
+
Register a worker subscription (e.g. an RTDB value listener:
|
|
1928
|
+
`{ target: { service: 'rtdb', path } }`). `onSnap` receives every snap
|
|
1929
|
+
value (initial + updates — and re-delivered fresh after peer
|
|
1930
|
+
replacement); an establishment failure routes to `onError` instead.
|
|
1931
|
+
Returns the unsubscribe function.
|
|
1932
|
+
|
|
1933
|
+
###### Parameters
|
|
1934
|
+
|
|
1935
|
+
| Parameter | Type |
|
|
1936
|
+
| :------ | :------ |
|
|
1937
|
+
| `sub` | \{ `target`: `unknown`; \} & `Record`\<`string`, `unknown`\> |
|
|
1938
|
+
| `onSnap` | (`value`: `unknown`) => `void` |
|
|
1939
|
+
| `onError?` | (`err`: `Error` & \{ `code`: `string`; \}) => `void` |
|
|
1940
|
+
|
|
1941
|
+
###### Returns
|
|
1942
|
+
|
|
1943
|
+
```ts
|
|
1944
|
+
(): void;
|
|
1945
|
+
```
|
|
1946
|
+
|
|
1947
|
+
###### Returns
|
|
1948
|
+
|
|
1949
|
+
`void`
|
|
1950
|
+
|
|
1951
|
+
***
|
|
1952
|
+
|
|
1953
|
+
<a id="remotesandboxfactoryoptions"></a>
|
|
1954
|
+
|
|
1955
|
+
### RemoteSandboxFactoryOptions
|
|
1956
|
+
|
|
1957
|
+
Options accepted by the ambient remote-sandbox factory.
|
|
1958
|
+
|
|
1959
|
+
#### Properties
|
|
1960
|
+
|
|
1961
|
+
| Property | Type | Description |
|
|
1962
|
+
| :------ | :------ | :------ |
|
|
1963
|
+
| <a id="url"></a> `url?` | `string` | Explicit `pyric dev` base URL (from `PYRIC_SANDBOX=remote:<url>`). When omitted the factory discovers the running host itself (the `.pyric/serve.json` locator protocol). |
|
|
1964
|
+
|
|
1965
|
+
***
|
|
1966
|
+
|
|
1967
|
+
<a id="replayoptions"></a>
|
|
1968
|
+
|
|
1969
|
+
### ReplayOptions
|
|
1970
|
+
|
|
1971
|
+
#### Properties
|
|
1972
|
+
|
|
1973
|
+
| Property | Type | Description |
|
|
1974
|
+
| :------ | :------ | :------ |
|
|
1975
|
+
| <a id="pinrequesttime"></a> `pinRequestTime?` | `boolean` | When true (default), re-issue the captured `requestTime` so `serverTimestamp()` sentinels resolve to the same value as capture and `request.time`-gated rules evaluate identically. |
|
|
1976
|
+
|
|
1977
|
+
***
|
|
1978
|
+
|
|
1979
|
+
<a id="replayresult"></a>
|
|
1980
|
+
|
|
1981
|
+
### ReplayResult
|
|
1982
|
+
|
|
1983
|
+
#### Properties
|
|
1984
|
+
|
|
1985
|
+
| Property | Type | Description |
|
|
1986
|
+
| :------ | :------ | :------ |
|
|
1987
|
+
| <a id="divergences"></a> `divergences` | [`Divergence`](#divergence)[] | Field- and path-level differences between original and replayed state, classified. |
|
|
1988
|
+
| <a id="pathaliases"></a> `pathAliases` | `Map`\<`string`, `string`\> | Maps captured auto-id paths → freshly-minted replay paths. The diff classifier uses this to skip `autoid-alias` paths when computing field-level differences. |
|
|
1989
|
+
| <a id="sandbox-2"></a> `sandbox` | [`LocalSandbox`](#localsandbox) | Fresh sandbox with the captured writes re-applied. |
|
|
1990
|
+
|
|
1991
|
+
***
|
|
1992
|
+
|
|
1993
|
+
<a id="requestevent"></a>
|
|
1994
|
+
|
|
1995
|
+
### RequestEvent
|
|
1996
|
+
|
|
1997
|
+
Eval-time payload emitted to Sandbox.onRequest subscribers —
|
|
1998
|
+
one per evaluated op, regardless of outcome.
|
|
1999
|
+
|
|
2000
|
+
Issue #307: the playground today only renders denials, but every op
|
|
2001
|
+
the simulator evaluates is a request worth seeing. This event is the
|
|
2002
|
+
source of truth; [DenialEvent](#denialevent) is a filtered projection over
|
|
2003
|
+
the `result === 'deny'` subset.
|
|
2004
|
+
|
|
2005
|
+
Origin tells the consumer who initiated the eval:
|
|
2006
|
+
- `user` single op via the data-plane adapter (admin / firestore).
|
|
2007
|
+
- `batch` part of a multi-op batch — shares `groupId` with siblings.
|
|
2008
|
+
- `transaction` part of a transaction commit — shares `groupId`.
|
|
2009
|
+
- `listener` a write or `deployRules` triggered a snapshot listener
|
|
2010
|
+
to re-evaluate. Carries `triggeredBy` naming the
|
|
2011
|
+
originating user op (when knowable).
|
|
2012
|
+
|
|
2013
|
+
`evalMs` measures the wall-clock duration of the simulator's
|
|
2014
|
+
`simulate(...)` call. Sub-millisecond is normal for simple rules;
|
|
2015
|
+
rule-engine-heavy rules (deep boolean chains, many get() calls) can
|
|
2016
|
+
reach tens of milliseconds; a traffic-monitor validation probe measured
|
|
2017
|
+
connect-four rules at ~95ms p99. Surface this in your UI when it matters.
|
|
2018
|
+
|
|
2019
|
+
Listener throws are swallowed by the dispatcher so a faulty
|
|
2020
|
+
subscriber can't change rule semantics or hide other events.
|
|
2021
|
+
|
|
2022
|
+
#### See
|
|
2023
|
+
|
|
2024
|
+
traffic-monitor-decision.md for the field-by-field rationale.
|
|
2025
|
+
|
|
2026
|
+
#### Properties
|
|
2027
|
+
|
|
2028
|
+
| Property | Type | Description |
|
|
2029
|
+
| :------ | :------ | :------ |
|
|
2030
|
+
| <a id="at-2"></a> `at` | `number` | Wall-clock at op start, ms since epoch. |
|
|
2031
|
+
| <a id="auth-5"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | - |
|
|
2032
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
2033
|
+
| `auth.uid` | `string` | - |
|
|
2034
|
+
| <a id="detail"></a> `detail?` | \{ `admin?`: `boolean`; \} & `Record`\<`string`, `unknown`\> | Free-form operation metadata. `admin: true` marks a rules-bypassing setup/admin operation so fixture tooling can exclude it from protected behavior while still preserving it as replay context. |
|
|
2035
|
+
| <a id="evalms"></a> `evalMs` | `number` | Wall-clock duration of the simulator.simulate(...) call, in ms. |
|
|
2036
|
+
| <a id="evaluatedrule"></a> `evaluatedRule?` | `EvaluatedRuleInfo` | The DECIDING rule's verdict + 1-indexed source line + full sub-expression trace, projected from the simulator's structured `RuleEvaluation` (additive: present on `result: 'allow' | 'deny'` Firestore events when the simulator produced a per-rule trace — the allowing rule on an allow, the denying rule on a deny). Studio's rules inspector reads this to mark the deciding line and render the evaluation step-through ("show the work"). Absent on an implicit deny (no rule evaluated), a simulator-error deny, and unsupported results. |
|
|
2037
|
+
| <a id="groupid"></a> `groupId?` | `string` | Shared across ops in one batch or transaction. Opaque to consumers. |
|
|
2038
|
+
| <a id="groupkind"></a> `groupKind?` | `"transaction"` \| `"batch"` | Disambiguates `origin: 'transaction' | 'batch'` cases when consumers need to tell them apart without inspecting `origin` directly. |
|
|
2039
|
+
| <a id="id-2"></a> `id` | `string` | Unique within a sandbox process. Useful for React list keys. |
|
|
2040
|
+
| <a id="kind-1"></a> `kind` | `"request"` | Discriminator. |
|
|
2041
|
+
| <a id="matchedrule"></a> `matchedRule?` | \{ `operations`: `string`[]; `ruleIndex`: `number`; \} | Parsed from the simulator's "Rule #N → …" debug line. Absent when no rule matched (e.g. no allow rules at the path — implicit deny). |
|
|
2042
|
+
| `matchedRule.operations` | `string`[] | - |
|
|
2043
|
+
| `matchedRule.ruleIndex` | `number` | - |
|
|
2044
|
+
| <a id="method-1"></a> `method` | `"get"` \| `"list"` \| `"create"` \| `"update"` \| `"delete"` \| `"set"` | - |
|
|
2045
|
+
| <a id="origin"></a> `origin` | `"user"` \| `"listener"` \| `"transaction"` \| `"batch"` | - |
|
|
2046
|
+
| <a id="path-1"></a> `path` | `string` | - |
|
|
2047
|
+
| <a id="reasons-2"></a> `reasons` | `string`[] | Simulator debug messages — the per-rule trace (`Rule #0 (read) → ALLOW`). Same shape as `DenialEvent.reasons` so consumer code can share rendering. |
|
|
2048
|
+
| <a id="request-2"></a> `request?` | \{ `resourceData?`: `Record`\<`string`, `unknown`\>; \} | Proposed write payload, for create/update/set. Absent on reads + delete. Pre-resolution: `FieldValue.*` sentinels are preserved as their marker shapes (`{ __type: 'serverTimestamp' }`, etc.) so the replay engine can re-resolve them. The rule engine evaluated against the resolved form internally; that resolved form lives on [WriteSandboxEvent.nextState](#nextstate-1), not here. |
|
|
2049
|
+
| `request.resourceData?` | `Record`\<`string`, `unknown`\> | - |
|
|
2050
|
+
| <a id="resourceafter"></a> `resourceAfter?` | \{ `data`: `Record`\<`string`, `unknown`\>; `exists`: `boolean`; \} | Projected document state after the write. Absent on reads. |
|
|
2051
|
+
| `resourceAfter.data` | `Record`\<`string`, `unknown`\> | - |
|
|
2052
|
+
| `resourceAfter.exists` | `boolean` | - |
|
|
2053
|
+
| <a id="resourcebefore"></a> `resourceBefore?` | \{ `data`: `Record`\<`string`, `unknown`\>; `exists`: `boolean`; \} | Existing document state before the write (or read target for get). |
|
|
2054
|
+
| `resourceBefore.data` | `Record`\<`string`, `unknown`\> | - |
|
|
2055
|
+
| `resourceBefore.exists` | `boolean` | - |
|
|
2056
|
+
| <a id="result-1"></a> `result` | `"allow"` \| `"deny"` \| `"unsupported"` | `'unsupported'` fires when the simulator hit an unmodelled feature and the sandbox upgraded it (today: thrown as SimulatorUnsupportedError, surfaced here as a discrete result so the panel can show it distinctly from a real denial). |
|
|
2057
|
+
| <a id="rulesdisposition"></a> `rulesDisposition?` | [`RulesDisposition`](#rulesdisposition-2) | Canonical statement of whether Security Rules evaluated this request. Added by the sandbox event recorder when an older emitter omits it. |
|
|
2058
|
+
| <a id="triggeredby"></a> `triggeredBy?` | \{ `method`: `string`; `path`: `string`; \} | For listener re-evals: the originating user op that triggered this re-evaluation. Absent on the initial-snapshot fire. |
|
|
2059
|
+
| `triggeredBy.method` | `string` | - |
|
|
2060
|
+
| `triggeredBy.path` | `string` | - |
|
|
2061
|
+
|
|
2062
|
+
***
|
|
2063
|
+
|
|
2064
|
+
<a id="sandbox-3"></a>
|
|
2065
|
+
|
|
2066
|
+
### Sandbox
|
|
2067
|
+
|
|
2068
|
+
A Firebase sandbox — an isolated environment with one auth identity.
|
|
2069
|
+
|
|
2070
|
+
Created via `initializeSandbox(config)`. Use `fork({ auth })` to
|
|
2071
|
+
derive a new sandbox with a different identity that shares the
|
|
2072
|
+
underlying environment (rules, data, state). Fork is the only
|
|
2073
|
+
identity-switching mechanism — there are no per-op auth overrides
|
|
2074
|
+
and no in-place mutation.
|
|
2075
|
+
|
|
2076
|
+
#### Extended by
|
|
2077
|
+
|
|
2078
|
+
- [`LocalSandbox`](#localsandbox)
|
|
2079
|
+
- [`RemoteSandbox`](#remotesandbox)
|
|
2080
|
+
|
|
2081
|
+
#### Properties
|
|
2082
|
+
|
|
2083
|
+
| Property | Modifier | Type | Description |
|
|
2084
|
+
| :------ | :------ | :------ | :------ |
|
|
2085
|
+
| <a id="admin-2"></a> `admin` | `readonly` | `SandboxAdmin` | Admin-plane access (rule-bypass reads). Identity-agnostic by design — admin reads aren't gated on auth, so they live on the sandbox, not on a context. See SandboxAdmin. |
|
|
2086
|
+
| <a id="currentuser-2"></a> `currentUser` | `public` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | Current authenticated user across the sandbox. Mutated by `pyric/auth`'s `signInAnonymously` / `signInWithEmailAndPassword` / `signOut` / `sandbox.setUser`. Read per-call by service factories (e.g. a future `getFirestore(sandbox)` overload) so they see auth state changes without re-binding handles. Defaults to `null` (anonymous / signed out). **Independent of `withAuth({uid})`** — `withAuth` still produces a frozen [SandboxContext](#sandboxcontext) that carries its own identity for the runner's test code (the existing pattern: explicit identity per service call). `currentUser` exists for the `pyric/auth` mirror, where consumer app code drives identity through a stateful `Auth` handle rather than naming it per call. |
|
|
2087
|
+
| `currentUser.token?` | `public` | `Record`\<`string`, `unknown`\> | - |
|
|
2088
|
+
| `currentUser.uid` | `public` | `string` | - |
|
|
2089
|
+
|
|
2090
|
+
#### Methods
|
|
2091
|
+
|
|
2092
|
+
<a id="clearpersistence-4"></a>
|
|
2093
|
+
|
|
2094
|
+
##### clearPersistence()
|
|
2095
|
+
|
|
2096
|
+
```ts
|
|
2097
|
+
clearPersistence(): Promise<void>;
|
|
2098
|
+
```
|
|
2099
|
+
|
|
2100
|
+
Wipe the persisted blob for this sandbox's `key`. In-memory state
|
|
2101
|
+
is left intact — call `reset()` if you want both. Useful for
|
|
2102
|
+
"sign out and forget" flows.
|
|
2103
|
+
|
|
2104
|
+
No-op when persistence is not enabled.
|
|
2105
|
+
|
|
2106
|
+
###### Returns
|
|
2107
|
+
|
|
2108
|
+
`Promise`\<`void`\>
|
|
2109
|
+
|
|
2110
|
+
<a id="dispose-6"></a>
|
|
2111
|
+
|
|
2112
|
+
##### dispose()
|
|
2113
|
+
|
|
2114
|
+
```ts
|
|
2115
|
+
dispose(): void;
|
|
2116
|
+
```
|
|
2117
|
+
|
|
2118
|
+
Tear down listener registries on this sandbox's environment without
|
|
2119
|
+
replacing it. Use this when you're about to discard the sandbox
|
|
2120
|
+
itself (e.g. `runner.reseed()` builds a fresh sandbox rather than
|
|
2121
|
+
calling `reset()`) and want to drop callback references on the
|
|
2122
|
+
outgoing instance defensively. Idempotent. Does not touch data.
|
|
2123
|
+
|
|
2124
|
+
###### Returns
|
|
2125
|
+
|
|
2126
|
+
`void`
|
|
2127
|
+
|
|
2128
|
+
<a id="enablepersistence-4"></a>
|
|
2129
|
+
|
|
2130
|
+
##### enablePersistence()
|
|
2131
|
+
|
|
2132
|
+
```ts
|
|
2133
|
+
enablePersistence(options: SandboxPersistenceOptions): Promise<void>;
|
|
2134
|
+
```
|
|
2135
|
+
|
|
2136
|
+
Persist the sandbox's data to a backend and restore it on next
|
|
2137
|
+
`enablePersistence` call. The default `'indexedDB'` backend turns
|
|
2138
|
+
the sandbox into the host page's local Firestore — writes flush
|
|
2139
|
+
automatically and a fresh `initializeSandbox()` rehydrates from
|
|
2140
|
+
the prior session.
|
|
2141
|
+
|
|
2142
|
+
Restoration happens before the promise resolves; awaiting this
|
|
2143
|
+
call is sufficient to guarantee in-memory state matches the
|
|
2144
|
+
persisted blob.
|
|
2145
|
+
|
|
2146
|
+
Idempotent across the same `key` — calling twice in one process
|
|
2147
|
+
is a no-op on the second call. Different keys are rejected as an
|
|
2148
|
+
error (a sandbox can persist to at most one backend at a time).
|
|
2149
|
+
|
|
2150
|
+
Listener semantics: every write event the sandbox emits triggers
|
|
2151
|
+
a debounced flush (default 250ms). Browser hosts additionally
|
|
2152
|
+
flush on `beforeunload` so a page navigation doesn't lose the
|
|
2153
|
+
tail of the debounce window.
|
|
2154
|
+
|
|
2155
|
+
See [SandboxPersistenceOptions](#sandboxpersistenceoptions) for backend selection and
|
|
2156
|
+
tuning.
|
|
2157
|
+
|
|
2158
|
+
###### Parameters
|
|
2159
|
+
|
|
2160
|
+
| Parameter | Type |
|
|
2161
|
+
| :------ | :------ |
|
|
2162
|
+
| `options` | [`SandboxPersistenceOptions`](#sandboxpersistenceoptions) |
|
|
2163
|
+
|
|
2164
|
+
###### Returns
|
|
2165
|
+
|
|
2166
|
+
`Promise`\<`void`\>
|
|
2167
|
+
|
|
2168
|
+
<a id="enabletabsync-4"></a>
|
|
2169
|
+
|
|
2170
|
+
##### enableTabSync()
|
|
2171
|
+
|
|
2172
|
+
```ts
|
|
2173
|
+
enableTabSync(options?: TabSyncOptions): () => void;
|
|
2174
|
+
```
|
|
2175
|
+
|
|
2176
|
+
Enable cross-tab realtime sync via `BroadcastChannel`. A write in
|
|
2177
|
+
this tab will propagate to every OTHER tab of the same origin that
|
|
2178
|
+
also called `enableTabSync`, causing their `onSnapshot` listeners to
|
|
2179
|
+
re-evaluate — restoring production's cross-client realtime behavior.
|
|
2180
|
+
|
|
2181
|
+
**Opt-in, OFF by default.** Firestore only (RTDB is a follow-on).
|
|
2182
|
+
|
|
2183
|
+
Returns a disable function. Calling it removes the `onEvent`
|
|
2184
|
+
subscription, the channel message listener, and closes the channel
|
|
2185
|
+
(when it was created internally). After disable, no further propagation
|
|
2186
|
+
occurs in either direction.
|
|
2187
|
+
|
|
2188
|
+
**Multi-writer note:** concurrent writes from two tabs to the same doc
|
|
2189
|
+
produce last-write-wins divergence — there is no conflict resolution.
|
|
2190
|
+
The intended model is one active writer (one user, one tab) with
|
|
2191
|
+
observers in other tabs; this covers the overwhelming majority of
|
|
2192
|
+
local development scenarios.
|
|
2193
|
+
|
|
2194
|
+
###### Parameters
|
|
2195
|
+
|
|
2196
|
+
| Parameter | Type |
|
|
2197
|
+
| :------ | :------ |
|
|
2198
|
+
| `options?` | [`TabSyncOptions`](#tabsyncoptions) |
|
|
2199
|
+
|
|
2200
|
+
###### Returns
|
|
2201
|
+
|
|
2202
|
+
```ts
|
|
2203
|
+
(): void;
|
|
2204
|
+
```
|
|
2205
|
+
|
|
2206
|
+
###### Returns
|
|
2207
|
+
|
|
2208
|
+
`void`
|
|
2209
|
+
|
|
2210
|
+
###### See
|
|
2211
|
+
|
|
2212
|
+
[TabSyncOptions](#tabsyncoptions) for channel injection (tests) and originId.
|
|
2213
|
+
|
|
2214
|
+
###### Example
|
|
2215
|
+
|
|
2216
|
+
```ts
|
|
2217
|
+
// In every tab that should participate in realtime:
|
|
2218
|
+
const sandbox = initializeSandbox();
|
|
2219
|
+
const disableSync = sandbox.enableTabSync();
|
|
2220
|
+
// Later, to stop syncing:
|
|
2221
|
+
disableSync();
|
|
2222
|
+
```
|
|
2223
|
+
|
|
2224
|
+
<a id="flush-6"></a>
|
|
2225
|
+
|
|
2226
|
+
##### flush()
|
|
2227
|
+
|
|
2228
|
+
```ts
|
|
2229
|
+
flush(): Promise<void>;
|
|
2230
|
+
```
|
|
2231
|
+
|
|
2232
|
+
Force a snapshot to the configured persistence backend right now.
|
|
2233
|
+
Useful before a manual navigation, or in tests that need
|
|
2234
|
+
deterministic ordering against the debounce window. Resolves once
|
|
2235
|
+
the write hits the backend.
|
|
2236
|
+
|
|
2237
|
+
Throws if persistence is not enabled.
|
|
2238
|
+
|
|
2239
|
+
###### Returns
|
|
2240
|
+
|
|
2241
|
+
`Promise`\<`void`\>
|
|
2242
|
+
|
|
2243
|
+
<a id="history-4"></a>
|
|
2244
|
+
|
|
2245
|
+
##### history()
|
|
2246
|
+
|
|
2247
|
+
```ts
|
|
2248
|
+
history(): SandboxEvent[];
|
|
2249
|
+
```
|
|
2250
|
+
|
|
2251
|
+
Every [SandboxEvent](#sandboxevent) this sandbox has emitted since init or
|
|
2252
|
+
the last `reset()`. Returns a defensive copy.
|
|
2253
|
+
|
|
2254
|
+
Use this for replay: hand the array to `replay(events, rules)`
|
|
2255
|
+
from `pyric/sandbox` and the engine re-issues every
|
|
2256
|
+
captured write against a fresh sandbox.
|
|
2257
|
+
|
|
2258
|
+
Unlike [onEvent](#onevent-4) (live stream from the moment of subscribe),
|
|
2259
|
+
`history()` returns *every* event the sandbox has seen — useful
|
|
2260
|
+
for consumers that attach late (e.g., loading a saved session
|
|
2261
|
+
before subscribing) or that need a snapshot at a particular moment.
|
|
2262
|
+
|
|
2263
|
+
`reset()` and `dispose()` each append a closing `session_boundary`
|
|
2264
|
+
event; `reset()` then clears the history. Consumers that took a
|
|
2265
|
+
snapshot *before* reset retain the boundary in their copy.
|
|
2266
|
+
|
|
2267
|
+
###### Returns
|
|
2268
|
+
|
|
2269
|
+
[`SandboxEvent`](#sandboxevent)[]
|
|
2270
|
+
|
|
2271
|
+
<a id="loadsnapshot-4"></a>
|
|
2272
|
+
|
|
2273
|
+
##### loadSnapshot()
|
|
2274
|
+
|
|
2275
|
+
```ts
|
|
2276
|
+
loadSnapshot(data: SandboxSnapshot): void;
|
|
2277
|
+
```
|
|
2278
|
+
|
|
2279
|
+
CLOBBER-restore the sandbox's entire state from a prior [snapshot](#snapshot-6):
|
|
2280
|
+
`reset()` (clears firestore + the signed-in session), then rebuild firestore
|
|
2281
|
+
from `data` and restore each registered service. This is a TOTAL replace —
|
|
2282
|
+
documents absent from `data` do NOT survive — and is the counterpart to
|
|
2283
|
+
[snapshot](#snapshot-6). It is what makes "transfer (clobber) one instance's data
|
|
2284
|
+
into another" and named-branch switching possible.
|
|
2285
|
+
|
|
2286
|
+
Fires a `session_boundary` (reset phase), re-evaluates live listeners against
|
|
2287
|
+
the loaded state, and the next persistence flush writes the loaded state.
|
|
2288
|
+
Services present in `data` but not currently registered are skipped (a
|
|
2289
|
+
snapshot taken via [snapshot](#snapshot-6) always includes every registered
|
|
2290
|
+
service, so this only affects cross-instance imports from a sandbox that had
|
|
2291
|
+
a service this one lacks).
|
|
2292
|
+
|
|
2293
|
+
###### Parameters
|
|
2294
|
+
|
|
2295
|
+
| Parameter | Type |
|
|
2296
|
+
| :------ | :------ |
|
|
2297
|
+
| `data` | [`SandboxSnapshot`](#sandboxsnapshot-2) |
|
|
2298
|
+
|
|
2299
|
+
###### Returns
|
|
2300
|
+
|
|
2301
|
+
`void`
|
|
2302
|
+
|
|
2303
|
+
<a id="oncurrentuserchanged-4"></a>
|
|
2304
|
+
|
|
2305
|
+
##### onCurrentUserChanged()
|
|
2306
|
+
|
|
2307
|
+
```ts
|
|
2308
|
+
onCurrentUserChanged(cb: (user: {
|
|
2309
|
+
token?: Record<string, unknown>;
|
|
2310
|
+
uid: string;
|
|
2311
|
+
}) => void): () => void;
|
|
2312
|
+
```
|
|
2313
|
+
|
|
2314
|
+
Subscribe to `currentUser` changes. Fires on every mutation —
|
|
2315
|
+
sign-in, sign-out, user swap. Does NOT fire on subscribe.
|
|
2316
|
+
|
|
2317
|
+
Survives `reset()` and `dispose()` only as a no-op: a disposed
|
|
2318
|
+
sandbox emits nothing further; a reset sandbox clears
|
|
2319
|
+
`currentUser` to `null` (and fires the change) before swapping
|
|
2320
|
+
the env.
|
|
2321
|
+
|
|
2322
|
+
Returns an unsubscribe function. Listener errors are swallowed —
|
|
2323
|
+
subscribers are observational, the sandbox does not propagate
|
|
2324
|
+
their errors.
|
|
2325
|
+
|
|
2326
|
+
###### Parameters
|
|
2327
|
+
|
|
2328
|
+
| Parameter | Type |
|
|
2329
|
+
| :------ | :------ |
|
|
2330
|
+
| `cb` | (`user`: \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \}) => `void` |
|
|
2331
|
+
|
|
2332
|
+
###### Returns
|
|
2333
|
+
|
|
2334
|
+
```ts
|
|
2335
|
+
(): void;
|
|
2336
|
+
```
|
|
2337
|
+
|
|
2338
|
+
###### Returns
|
|
2339
|
+
|
|
2340
|
+
`void`
|
|
2341
|
+
|
|
2342
|
+
<a id="onevent-4"></a>
|
|
2343
|
+
|
|
2344
|
+
##### onEvent()
|
|
2345
|
+
|
|
2346
|
+
```ts
|
|
2347
|
+
onEvent(cb: (event: SandboxEvent) => void): () => void;
|
|
2348
|
+
```
|
|
2349
|
+
|
|
2350
|
+
Subscribe to every event the sandbox emits — see [SandboxEvent](#sandboxevent)
|
|
2351
|
+
for the discriminated-union shape. One subscription covers
|
|
2352
|
+
request/denial/snapshot-error/listener-lifecycle/session-boundary;
|
|
2353
|
+
filter on `event.kind` to recover individual streams.
|
|
2354
|
+
|
|
2355
|
+
Replaces the prior three-channel surface (`onRequest` / `onDenial`
|
|
2356
|
+
/ `onSnapshotError`) — see issue #307. Filter cookbook:
|
|
2357
|
+
- All denials: `event.kind === 'request' && event.result === 'deny'`
|
|
2358
|
+
- Stream errors: `event.kind === 'listener_errored'`
|
|
2359
|
+
- Per-op traffic: `event.kind === 'request'`
|
|
2360
|
+
|
|
2361
|
+
Survives `sandbox.reset()` — the subscription is held on the
|
|
2362
|
+
sandbox, not on the underlying environment. A `session_boundary`
|
|
2363
|
+
event with `phase: 'reset'` fires before the env swap so consumers
|
|
2364
|
+
can segment their stream.
|
|
2365
|
+
|
|
2366
|
+
Returns an unsubscribe function. Listener errors are swallowed so a
|
|
2367
|
+
faulty subscriber can't change rule semantics or hide other events.
|
|
2368
|
+
Both synchronous throws and rejected Promises from async callbacks
|
|
2369
|
+
are silently discarded — subscribers are **observational**, the
|
|
2370
|
+
sandbox doesn't await them and doesn't propagate their errors.
|
|
2371
|
+
|
|
2372
|
+
###### Parameters
|
|
2373
|
+
|
|
2374
|
+
| Parameter | Type |
|
|
2375
|
+
| :------ | :------ |
|
|
2376
|
+
| `cb` | (`event`: [`SandboxEvent`](#sandboxevent)) => `void` |
|
|
2377
|
+
|
|
2378
|
+
###### Returns
|
|
2379
|
+
|
|
2380
|
+
```ts
|
|
2381
|
+
(): void;
|
|
2382
|
+
```
|
|
2383
|
+
|
|
2384
|
+
###### Returns
|
|
2385
|
+
|
|
2386
|
+
`void`
|
|
2387
|
+
|
|
2388
|
+
<a id="registerpersistableservice-4"></a>
|
|
2389
|
+
|
|
2390
|
+
##### registerPersistableService()
|
|
2391
|
+
|
|
2392
|
+
```ts
|
|
2393
|
+
registerPersistableService(name: string, hooks: PersistableService): () => void;
|
|
2394
|
+
```
|
|
2395
|
+
|
|
2396
|
+
Register a service (auth, storage, …) as a persistence participant.
|
|
2397
|
+
The sandbox calls `hooks.snapshot()` on every flush and
|
|
2398
|
+
`hooks.restore(data)` on restore. If `hooks.subscribe` is provided,
|
|
2399
|
+
the persistence controller subscribes and schedules a debounced
|
|
2400
|
+
flush on each change — so auth-user edits flush promptly, not only
|
|
2401
|
+
on the next Firestore write.
|
|
2402
|
+
|
|
2403
|
+
Returns an unregister function — call it if the service is torn
|
|
2404
|
+
down before the sandbox is disposed (uncommon in practice; the
|
|
2405
|
+
sandbox's `dispose()` clears the registry anyway).
|
|
2406
|
+
|
|
2407
|
+
Throws `failed-precondition` when a service with the same `name` is
|
|
2408
|
+
already registered — the auth package registers `'auth'` once when
|
|
2409
|
+
`getAuth(sandbox)` first creates a backend, so accidental double-
|
|
2410
|
+
registration is a caller bug, not a no-op.
|
|
2411
|
+
|
|
2412
|
+
**Advanced / internal API.** Service packages (auth, storage) call
|
|
2413
|
+
this when they first attach to a sandbox. Consumer app code should
|
|
2414
|
+
not need to call this directly.
|
|
2415
|
+
|
|
2416
|
+
###### Parameters
|
|
2417
|
+
|
|
2418
|
+
| Parameter | Type |
|
|
2419
|
+
| :------ | :------ |
|
|
2420
|
+
| `name` | `string` |
|
|
2421
|
+
| `hooks` | [`PersistableService`](#persistableservice) |
|
|
2422
|
+
|
|
2423
|
+
###### Returns
|
|
2424
|
+
|
|
2425
|
+
```ts
|
|
2426
|
+
(): void;
|
|
2427
|
+
```
|
|
2428
|
+
|
|
2429
|
+
###### Returns
|
|
2430
|
+
|
|
2431
|
+
`void`
|
|
2432
|
+
|
|
2433
|
+
<a id="reset-4"></a>
|
|
2434
|
+
|
|
2435
|
+
##### reset()
|
|
2436
|
+
|
|
2437
|
+
```ts
|
|
2438
|
+
reset(): void;
|
|
2439
|
+
```
|
|
2440
|
+
|
|
2441
|
+
Reset the underlying environment to a fresh state — wipes data,
|
|
2442
|
+
rules, and any service-specific configuration.
|
|
2443
|
+
|
|
2444
|
+
Snapshot listeners attached to the OLD environment are dropped at
|
|
2445
|
+
the swap — they can't survive because their target docs have been
|
|
2446
|
+
wiped. `onEvent` subscribers DO survive — the registry lives on
|
|
2447
|
+
the sandbox, and a `session_boundary` event with `phase: 'reset'`
|
|
2448
|
+
fires before the swap so subscribers know the rollover happened.
|
|
2449
|
+
Existing [SandboxContext](#sandboxcontext)s continue to work — their sandbox
|
|
2450
|
+
reference is stable; subsequent operations resolve to the new env.
|
|
2451
|
+
|
|
2452
|
+
###### Returns
|
|
2453
|
+
|
|
2454
|
+
`void`
|
|
2455
|
+
|
|
2456
|
+
<a id="runwithprovenance-4"></a>
|
|
2457
|
+
|
|
2458
|
+
##### runWithProvenance()?
|
|
2459
|
+
|
|
2460
|
+
```ts
|
|
2461
|
+
optional runWithProvenance<T>(provenance: EventProvenance, fn: () => T): T;
|
|
2462
|
+
```
|
|
2463
|
+
|
|
2464
|
+
Run `fn` with ambient [EventProvenance](#eventprovenance) defaults: every event
|
|
2465
|
+
emitted SYNCHRONOUSLY during `fn` that doesn't already carry a
|
|
2466
|
+
provenance field (on the event itself or via an explicit per-emit
|
|
2467
|
+
override) is stamped with these values instead of the global
|
|
2468
|
+
defaults. This is the mechanical "who issued this op" seam the
|
|
2469
|
+
serve worker uses to tag Studio-issued ops (`actor: { kind:
|
|
2470
|
+
'studio' }`) and to stamp the auth lens an op ran under
|
|
2471
|
+
(`authLens`) — declared by the caller that issues the op, never
|
|
2472
|
+
inferred from the op's shape.
|
|
2473
|
+
|
|
2474
|
+
SYNCHRONOUS WINDOW: the ambient values apply only until `fn`
|
|
2475
|
+
returns (for an async `fn`, its synchronous prefix — which covers
|
|
2476
|
+
the local environment's rules eval + event emission, since those
|
|
2477
|
+
run before the op's promise is handed back). Work an op DEFERS
|
|
2478
|
+
(snapshot-listener deliveries and re-evals drain on a microtask,
|
|
2479
|
+
off-stack) is intentionally OUTSIDE the window: a listener re-eval
|
|
2480
|
+
belongs to the listener's owner, not to whoever's write triggered
|
|
2481
|
+
it. Nested calls stack — the innermost window wins per field, and
|
|
2482
|
+
each window restores the previous one on exit (including on throw).
|
|
2483
|
+
|
|
2484
|
+
OPTIONAL because remote sandbox proxies can't provide an ambient
|
|
2485
|
+
emit window (events are emitted in the worker they front). Callers
|
|
2486
|
+
spell `sandbox.runWithProvenance?.(prov, fn) ?? fn()`.
|
|
2487
|
+
|
|
2488
|
+
###### Type Parameters
|
|
2489
|
+
|
|
2490
|
+
| Type Parameter |
|
|
2491
|
+
| :------ |
|
|
2492
|
+
| `T` |
|
|
2493
|
+
|
|
2494
|
+
###### Parameters
|
|
2495
|
+
|
|
2496
|
+
| Parameter | Type |
|
|
2497
|
+
| :------ | :------ |
|
|
2498
|
+
| `provenance` | [`EventProvenance`](#eventprovenance) |
|
|
2499
|
+
| `fn` | () => `T` |
|
|
2500
|
+
|
|
2501
|
+
###### Returns
|
|
2502
|
+
|
|
2503
|
+
`T`
|
|
2504
|
+
|
|
2505
|
+
<a id="snapshot-6"></a>
|
|
2506
|
+
|
|
2507
|
+
##### snapshot()
|
|
2508
|
+
|
|
2509
|
+
```ts
|
|
2510
|
+
snapshot(): SandboxSnapshot;
|
|
2511
|
+
```
|
|
2512
|
+
|
|
2513
|
+
Capture a snapshot of every service's state. For v1 with only
|
|
2514
|
+
Firestore, the return value carries a `firestore` key mapping doc
|
|
2515
|
+
paths to data. Future services will add their own keys.
|
|
2516
|
+
|
|
2517
|
+
###### Returns
|
|
2518
|
+
|
|
2519
|
+
[`SandboxSnapshot`](#sandboxsnapshot-2)
|
|
2520
|
+
|
|
2521
|
+
<a id="withauth-6"></a>
|
|
2522
|
+
|
|
2523
|
+
##### withAuth()
|
|
2524
|
+
|
|
2525
|
+
```ts
|
|
2526
|
+
withAuth(auth: {
|
|
2527
|
+
token?: Record<string, unknown>;
|
|
2528
|
+
uid: string;
|
|
2529
|
+
}): SandboxContext;
|
|
2530
|
+
```
|
|
2531
|
+
|
|
2532
|
+
Derive a context bound to this sandbox under the given auth
|
|
2533
|
+
identity. Operations through services attached to the returned
|
|
2534
|
+
context evaluate rules under that identity. Many contexts can
|
|
2535
|
+
coexist for one sandbox; data is shared.
|
|
2536
|
+
|
|
2537
|
+
`null` is anonymous; an `AuthState` object names the user (and
|
|
2538
|
+
optional custom claims). Passing `undefined` is a deliberate
|
|
2539
|
+
error — say `withAuth(null)` for anonymous so the call site is
|
|
2540
|
+
unambiguous.
|
|
2541
|
+
|
|
2542
|
+
###### Parameters
|
|
2543
|
+
|
|
2544
|
+
| Parameter | Type |
|
|
2545
|
+
| :------ | :------ |
|
|
2546
|
+
| `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
2547
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
2548
|
+
| `auth.uid` | `string` |
|
|
2549
|
+
|
|
2550
|
+
###### Returns
|
|
2551
|
+
|
|
2552
|
+
[`SandboxContext`](#sandboxcontext)
|
|
2553
|
+
|
|
2554
|
+
###### Example
|
|
2555
|
+
|
|
2556
|
+
```ts
|
|
2557
|
+
const sandbox = initializeSandbox();
|
|
2558
|
+
const dbAlice = getFirestore(sandbox.withAuth({ uid: 'alice' }));
|
|
2559
|
+
const dbAnon = getFirestore(sandbox.withAuth(null));
|
|
2560
|
+
```
|
|
2561
|
+
|
|
2562
|
+
***
|
|
2563
|
+
|
|
2564
|
+
<a id="sandboxcommitevent"></a>
|
|
2565
|
+
|
|
2566
|
+
### SandboxCommitEvent
|
|
2567
|
+
|
|
2568
|
+
Canonical committed mutation event. Unlike `operation`, this fires only when
|
|
2569
|
+
state actually changed. Replay and branch tooling should eventually consume
|
|
2570
|
+
these service adapters instead of filtering Firestore-only `write` events.
|
|
2571
|
+
|
|
2572
|
+
#### Properties
|
|
2573
|
+
|
|
2574
|
+
| Property | Type |
|
|
2575
|
+
| :------ | :------ |
|
|
2576
|
+
| <a id="at-3"></a> `at` | `number` |
|
|
2577
|
+
| <a id="auth-6"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
2578
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
2579
|
+
| `auth.uid` | `string` |
|
|
2580
|
+
| <a id="data"></a> `data?` | `unknown` |
|
|
2581
|
+
| <a id="detail-1"></a> `detail?` | `Record`\<`string`, `unknown`\> |
|
|
2582
|
+
| <a id="groupid-1"></a> `groupId?` | `string` |
|
|
2583
|
+
| <a id="groupkind-1"></a> `groupKind?` | `"transaction"` \| `"batch"` |
|
|
2584
|
+
| <a id="id-3"></a> `id` | `string` |
|
|
2585
|
+
| <a id="kind-2"></a> `kind` | `"commit"` |
|
|
2586
|
+
| <a id="method-2"></a> `method` | `string` |
|
|
2587
|
+
| <a id="nextstate"></a> `nextState?` | `unknown` |
|
|
2588
|
+
| <a id="path-2"></a> `path?` | `string` |
|
|
2589
|
+
| <a id="priorstate"></a> `priorState?` | `unknown` |
|
|
2590
|
+
| <a id="replay"></a> `replay?` | \{ `autoId?`: `string`; `requestTime?`: `number`; `sentinels?`: \{ `field`: `string`; `kind`: `string`; \}[]; \} |
|
|
2591
|
+
| `replay.autoId?` | `string` |
|
|
2592
|
+
| `replay.requestTime?` | `number` |
|
|
2593
|
+
| `replay.sentinels?` | \{ `field`: `string`; `kind`: `string`; \}[] |
|
|
2594
|
+
| <a id="service-2"></a> `service` | [`EventService`](#eventservice) |
|
|
2595
|
+
|
|
2596
|
+
***
|
|
2597
|
+
|
|
2598
|
+
<a id="sandboxconfig"></a>
|
|
2599
|
+
|
|
2600
|
+
### SandboxConfig
|
|
2601
|
+
|
|
2602
|
+
Initialization config for a sandbox. All fields are optional; an
|
|
2603
|
+
empty config produces a sandbox with no rules and no seeded data.
|
|
2604
|
+
|
|
2605
|
+
**No `auth` field.** Identity belongs to [SandboxContext](#sandboxcontext), not
|
|
2606
|
+
the sandbox. Service handles always require an explicit context.
|
|
2607
|
+
|
|
2608
|
+
***
|
|
2609
|
+
|
|
2610
|
+
<a id="sandboxcontext"></a>
|
|
2611
|
+
|
|
2612
|
+
### SandboxContext
|
|
2613
|
+
|
|
2614
|
+
Identity-bearing handle on a [Sandbox](#sandbox-3). A
|
|
2615
|
+
`(sandbox, auth, operationContext)`
|
|
2616
|
+
tuple — cheap to create, immutable, freely shareable. Service
|
|
2617
|
+
factories require a `SandboxContext`; bare `Sandbox` is a type
|
|
2618
|
+
error so every call site states identity explicitly.
|
|
2619
|
+
|
|
2620
|
+
Constructed via `Sandbox.withAuth(auth)` or chained via
|
|
2621
|
+
`SandboxContext.withAuth(auth)`. The concrete class is exported
|
|
2622
|
+
from `pyric/sandbox` for `instanceof` routing in service
|
|
2623
|
+
factories; consumers don't construct it directly.
|
|
2624
|
+
|
|
2625
|
+
#### Properties
|
|
2626
|
+
|
|
2627
|
+
| Property | Modifier | Type | Description |
|
|
2628
|
+
| :------ | :------ | :------ | :------ |
|
|
2629
|
+
| <a id="auth-7"></a> `auth` | `readonly` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | The identity rules evaluate under for operations through this context. |
|
|
2630
|
+
| `auth.token?` | `public` | `Record`\<`string`, `unknown`\> | - |
|
|
2631
|
+
| `auth.uid` | `public` | `string` | - |
|
|
2632
|
+
| <a id="operationcontext-3"></a> `operationContext` | `readonly` | [`OperationContext`](#operationcontext-2) | Immutable provenance bound to every operation issued through this handle. |
|
|
2633
|
+
| <a id="sandbox-4"></a> `sandbox` | `readonly` | [`Sandbox`](#sandbox-3) | The data foundation this context operates against. |
|
|
2634
|
+
|
|
2635
|
+
#### Methods
|
|
2636
|
+
|
|
2637
|
+
<a id="withauth-8"></a>
|
|
2638
|
+
|
|
2639
|
+
##### withAuth()
|
|
2640
|
+
|
|
2641
|
+
```ts
|
|
2642
|
+
withAuth(auth: {
|
|
2643
|
+
token?: Record<string, unknown>;
|
|
2644
|
+
uid: string;
|
|
2645
|
+
}): SandboxContext;
|
|
2646
|
+
```
|
|
2647
|
+
|
|
2648
|
+
Derive a sibling context on the same sandbox with different auth.
|
|
2649
|
+
Replaces auth and its lens while preserving the operation source and
|
|
2650
|
+
optional plan identity.
|
|
2651
|
+
|
|
2652
|
+
###### Parameters
|
|
2653
|
+
|
|
2654
|
+
| Parameter | Type |
|
|
2655
|
+
| :------ | :------ |
|
|
2656
|
+
| `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
2657
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
2658
|
+
| `auth.uid` | `string` |
|
|
2659
|
+
|
|
2660
|
+
###### Returns
|
|
2661
|
+
|
|
2662
|
+
[`SandboxContext`](#sandboxcontext)
|
|
2663
|
+
|
|
2664
|
+
***
|
|
2665
|
+
|
|
2666
|
+
<a id="sandboxlistenerevent"></a>
|
|
2667
|
+
|
|
2668
|
+
### SandboxListenerEvent
|
|
2669
|
+
|
|
2670
|
+
Canonical listener lifecycle/delivery event. Firestore's existing snapshot
|
|
2671
|
+
delivery/lifecycle variants are preserved; this shape gives RTDB and future
|
|
2672
|
+
service listeners the same debuggable surface.
|
|
2673
|
+
|
|
2674
|
+
#### Properties
|
|
2675
|
+
|
|
2676
|
+
| Property | Type |
|
|
2677
|
+
| :------ | :------ |
|
|
2678
|
+
| <a id="at-4"></a> `at` | `number` |
|
|
2679
|
+
| <a id="auth-8"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
2680
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
2681
|
+
| `auth.uid` | `string` |
|
|
2682
|
+
| <a id="detail-2"></a> `detail?` | `Record`\<`string`, `unknown`\> |
|
|
2683
|
+
| <a id="error-1"></a> `error?` | \{ `code?`: `string`; `message`: `string`; `reasons?`: `string`[]; \} |
|
|
2684
|
+
| `error.code?` | `string` |
|
|
2685
|
+
| `error.message` | `string` |
|
|
2686
|
+
| `error.reasons?` | `string`[] |
|
|
2687
|
+
| <a id="id-4"></a> `id` | `string` |
|
|
2688
|
+
| <a id="kind-3"></a> `kind` | `"listener"` |
|
|
2689
|
+
| <a id="listenerid-1"></a> `listenerId` | `string` |
|
|
2690
|
+
| <a id="phase"></a> `phase` | `"attach"` \| `"detach"` \| `"delivery"` \| `"suppressed"` \| `"errored"` |
|
|
2691
|
+
| <a id="reason"></a> `reason?` | `string` |
|
|
2692
|
+
| <a id="result-2"></a> `result?` | `"allow"` \| `"deny"` \| `"unsupported"` \| `"error"` |
|
|
2693
|
+
| <a id="sample"></a> `sample?` | `unknown` |
|
|
2694
|
+
| <a id="service-3"></a> `service` | [`EventService`](#eventservice) |
|
|
2695
|
+
| <a id="size"></a> `size?` | `number` |
|
|
2696
|
+
| <a id="target-1"></a> `target` | \{ `kind`: `string`; `path?`: `string`; `query?`: `unknown`; \} |
|
|
2697
|
+
| `target.kind` | `string` |
|
|
2698
|
+
| `target.path?` | `string` |
|
|
2699
|
+
| `target.query?` | `unknown` |
|
|
2700
|
+
| <a id="triggeredby-1"></a> `triggeredBy?` | \{ `method`: `string`; `path?`: `string`; \} |
|
|
2701
|
+
| `triggeredBy.method` | `string` |
|
|
2702
|
+
| `triggeredBy.path?` | `string` |
|
|
2703
|
+
|
|
2704
|
+
***
|
|
2705
|
+
|
|
2706
|
+
<a id="sandboxoperationevent"></a>
|
|
2707
|
+
|
|
2708
|
+
### SandboxOperationEvent
|
|
2709
|
+
|
|
2710
|
+
Canonical service operation event. This is the service-neutral successor to
|
|
2711
|
+
Firestore's `request` traffic shape: every user-visible operation can be
|
|
2712
|
+
represented here, whether it is backed by security rules (Firestore/RTDB/
|
|
2713
|
+
Storage) or by a service control plane (Auth).
|
|
2714
|
+
|
|
2715
|
+
Existing Firestore `request` events remain for compatibility. New cross-
|
|
2716
|
+
service consumers should prefer `operation` because it carries an explicit
|
|
2717
|
+
`service` discriminator and does not require RTDB/Storage/Auth to pretend
|
|
2718
|
+
their state is a Firestore document.
|
|
2719
|
+
|
|
2720
|
+
#### Properties
|
|
2721
|
+
|
|
2722
|
+
| Property | Type | Description |
|
|
2723
|
+
| :------ | :------ | :------ |
|
|
2724
|
+
| <a id="at-5"></a> `at` | `number` | - |
|
|
2725
|
+
| <a id="auth-9"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | - |
|
|
2726
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
2727
|
+
| `auth.uid` | `string` | - |
|
|
2728
|
+
| <a id="detail-3"></a> `detail?` | `Record`\<`string`, `unknown`\> | - |
|
|
2729
|
+
| <a id="durationms"></a> `durationMs?` | `number` | - |
|
|
2730
|
+
| <a id="groupid-2"></a> `groupId?` | `string` | - |
|
|
2731
|
+
| <a id="groupkind-2"></a> `groupKind?` | `"transaction"` \| `"batch"` | - |
|
|
2732
|
+
| <a id="id-5"></a> `id` | `string` | - |
|
|
2733
|
+
| <a id="kind-4"></a> `kind` | `"operation"` | - |
|
|
2734
|
+
| <a id="method-3"></a> `method` | `string` | - |
|
|
2735
|
+
| <a id="origin-1"></a> `origin` | `"admin"` \| `"user"` \| `"listener"` \| `"transaction"` \| `"batch"` \| `"system"` | - |
|
|
2736
|
+
| <a id="path-3"></a> `path?` | `string` | - |
|
|
2737
|
+
| <a id="reasons-3"></a> `reasons?` | `string`[] | - |
|
|
2738
|
+
| <a id="request-3"></a> `request?` | \{ `data?`: `unknown`; `query?`: `unknown`; `resourceData?`: `unknown`; \} | - |
|
|
2739
|
+
| `request.data?` | `unknown` | - |
|
|
2740
|
+
| `request.query?` | `unknown` | - |
|
|
2741
|
+
| `request.resourceData?` | `unknown` | - |
|
|
2742
|
+
| <a id="resourceafter-1"></a> `resourceAfter?` | \{ `data`: `unknown`; `exists`: `boolean`; \} | - |
|
|
2743
|
+
| `resourceAfter.data` | `unknown` | - |
|
|
2744
|
+
| `resourceAfter.exists` | `boolean` | - |
|
|
2745
|
+
| <a id="resourcebefore-1"></a> `resourceBefore?` | \{ `data`: `unknown`; `exists`: `boolean`; \} | - |
|
|
2746
|
+
| `resourceBefore.data` | `unknown` | - |
|
|
2747
|
+
| `resourceBefore.exists` | `boolean` | - |
|
|
2748
|
+
| <a id="result-3"></a> `result` | `"allow"` \| `"deny"` \| `"unsupported"` \| `"error"` \| `"not-applicable"` | - |
|
|
2749
|
+
| <a id="rules-2"></a> `rules?` | \{ `engine`: `"firestore"` \| `"storage"` \| `"rtdb"`; `errorCode?`: `string`; `matchedPath?`: `string`; `matchedRule?`: `string`; `operations?`: `string`[]; `pathVariableBindings?`: `Record`\<`string`, `string`\>; `reason?`: `string`; `ruleIndex?`: `number`; \} | - |
|
|
2750
|
+
| `rules.engine` | `"firestore"` \| `"storage"` \| `"rtdb"` | - |
|
|
2751
|
+
| `rules.errorCode?` | `string` | - |
|
|
2752
|
+
| `rules.matchedPath?` | `string` | - |
|
|
2753
|
+
| `rules.matchedRule?` | `string` | - |
|
|
2754
|
+
| `rules.operations?` | `string`[] | - |
|
|
2755
|
+
| `rules.pathVariableBindings?` | `Record`\<`string`, `string`\> | - |
|
|
2756
|
+
| `rules.reason?` | `string` | - |
|
|
2757
|
+
| `rules.ruleIndex?` | `number` | - |
|
|
2758
|
+
| <a id="rulesdisposition-1"></a> `rulesDisposition?` | [`RulesDisposition`](#rulesdisposition-2) | Canonical statement of whether Security Rules evaluated this operation. Service emitters may provide it directly; the recorder normalizes legacy operation shapes at the unified stream seam. |
|
|
2759
|
+
| <a id="service-4"></a> `service` | [`EventService`](#eventservice) | - |
|
|
2760
|
+
| <a id="triggeredby-2"></a> `triggeredBy?` | \{ `method`: `string`; `path?`: `string`; \} | - |
|
|
2761
|
+
| `triggeredBy.method` | `string` | - |
|
|
2762
|
+
| `triggeredBy.path?` | `string` | - |
|
|
2763
|
+
|
|
2764
|
+
***
|
|
2765
|
+
|
|
2766
|
+
<a id="sandboxpersistenceoptions"></a>
|
|
2767
|
+
|
|
2768
|
+
### SandboxPersistenceOptions
|
|
2769
|
+
|
|
2770
|
+
Controller options. See [Sandbox.enablePersistence](#enablepersistence-4).
|
|
2771
|
+
|
|
2772
|
+
#### Properties
|
|
2773
|
+
|
|
2774
|
+
| Property | Type | Description |
|
|
2775
|
+
| :------ | :------ | :------ |
|
|
2776
|
+
| <a id="backend"></a> `backend?` | `"indexedDB"` \| `"memory"` | Storage backend. `indexedDB` requires a browser environment; in non-browser hosts (Bun, Node, tests) the controller falls back to `memory` automatically unless an `injectedBackend` is supplied. Default: `indexedDB`. |
|
|
2777
|
+
| <a id="flushintervalms"></a> `flushIntervalMs?` | `number` | Debounce window before write events are flushed to the backend. Buffers rapid bursts (e.g., a batch of seed writes) into one flush. Default: 250ms. |
|
|
2778
|
+
| <a id="injectedbackend"></a> `injectedBackend?` | [`PersistenceBackend`](#persistencebackend) | Override the backend with an injected implementation. Used by tests and hosts that have their own storage adapter. When set, `backend` is ignored. |
|
|
2779
|
+
| <a id="key"></a> `key` | `string` | IndexedDB database name (or generic bucket key for other backends). Different keys persist to different storage locations — use one key per logical sandbox if you run several in parallel. |
|
|
2780
|
+
| <a id="sessionstorage"></a> `sessionStorage?` | \{ `local`: [`WebStorageLike`](#webstoragelike); `session`: [`WebStorageLike`](#webstoragelike); \} | Optional web-storage pair for current-session persistence. When provided, the controller reads and writes the signed-in uid here (honoring the auth `setPersistence` mode) so page reloads restore the signed-in user — exactly like `browserLocalPersistence` in prod Firebase. When omitted, the user DATABASE still persists (Phase 1), but the CURRENT SESSION is not restored on reload. This is the honest no-fake-durability choice for environments where web storage isn't available (Bun tests, Node servers, etc.). `local` maps to `localStorage` semantics (survives reload + restart); `session` maps to `sessionStorage` semantics (survives reload, cleared on tab close). The controller picks which store to write based on the auth `setPersistence` mode recorded on the backend: LOCAL → local (default; matches Firebase's default) SESSION → session NONE → neither (uid is not stored) Both storages are read on restore (mode-agnostic — a prior session may have used a different mode). Exactly one store holds the uid at any time; a mode change migrates the uid to the new store. |
|
|
2781
|
+
| `sessionStorage.local` | [`WebStorageLike`](#webstoragelike) | - |
|
|
2782
|
+
| `sessionStorage.session` | [`WebStorageLike`](#webstoragelike) | - |
|
|
2783
|
+
|
|
2784
|
+
***
|
|
2785
|
+
|
|
2786
|
+
<a id="sandboxruntimeerrorevent"></a>
|
|
2787
|
+
|
|
2788
|
+
### SandboxRuntimeErrorEvent
|
|
2789
|
+
|
|
2790
|
+
Canonical non-rules operational failure.
|
|
2791
|
+
|
|
2792
|
+
#### Properties
|
|
2793
|
+
|
|
2794
|
+
| Property | Type |
|
|
2795
|
+
| :------ | :------ |
|
|
2796
|
+
| <a id="at-6"></a> `at` | `number` |
|
|
2797
|
+
| <a id="auth-10"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
2798
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
2799
|
+
| `auth.uid` | `string` |
|
|
2800
|
+
| <a id="detail-4"></a> `detail?` | `Record`\<`string`, `unknown`\> |
|
|
2801
|
+
| <a id="error-2"></a> `error` | \{ `code?`: `string`; `message`: `string`; \} |
|
|
2802
|
+
| `error.code?` | `string` |
|
|
2803
|
+
| `error.message` | `string` |
|
|
2804
|
+
| <a id="id-6"></a> `id` | `string` |
|
|
2805
|
+
| <a id="kind-5"></a> `kind` | `"runtime_error"` |
|
|
2806
|
+
| <a id="method-4"></a> `method` | `string` |
|
|
2807
|
+
| <a id="path-4"></a> `path?` | `string` |
|
|
2808
|
+
| <a id="service-5"></a> `service` | [`EventService`](#eventservice) |
|
|
2809
|
+
|
|
2810
|
+
***
|
|
2811
|
+
|
|
2812
|
+
<a id="sandboxsnapshot-2"></a>
|
|
2813
|
+
|
|
2814
|
+
### SandboxSnapshot
|
|
2815
|
+
|
|
2816
|
+
Sandbox-level snapshot — a coarse capture of every service's state
|
|
2817
|
+
keyed by service name. The `firestore` key is always present; the
|
|
2818
|
+
`services` map holds one entry per registered persistable service
|
|
2819
|
+
(auth users, future storage objects, etc.). Service-specific
|
|
2820
|
+
snapshot types live in their service modules; `/app` keeps the index
|
|
2821
|
+
structural so it stays decoupled from service implementations.
|
|
2822
|
+
|
|
2823
|
+
v2 shape — `services` was added when the persistable-service registry
|
|
2824
|
+
landed. Prior `{ firestore }` v1 blobs are treated as having an empty
|
|
2825
|
+
`services` map on restore.
|
|
2826
|
+
|
|
2827
|
+
#### Properties
|
|
2828
|
+
|
|
2829
|
+
| Property | Type | Description |
|
|
2830
|
+
| :------ | :------ | :------ |
|
|
2831
|
+
| <a id="firestore"></a> `firestore` | `Record`\<`string`, `Record`\<`string`, `unknown`\>\> | Firestore documents, keyed by full path. Always present — empty `{}` for a fresh or just-reset sandbox. Per-document values are the post-resolution state the keyspace stored. |
|
|
2832
|
+
| <a id="services"></a> `services` | `Record`\<`string`, `unknown`\> | Per-service opaque state, keyed by service name (e.g. `'auth'`). Each entry is whatever the service's `PersistableService.snapshot()` returned. May be `{}` when no services are registered. |
|
|
2833
|
+
|
|
2834
|
+
***
|
|
2835
|
+
|
|
2836
|
+
<a id="servicemutationevent"></a>
|
|
2837
|
+
|
|
2838
|
+
### ServiceMutationEvent
|
|
2839
|
+
|
|
2840
|
+
Cross-service mutation event — the unified envelope the NON-Firestore
|
|
2841
|
+
services (`auth` / `storage` / `rtdb`) emit into the single
|
|
2842
|
+
`onEvent`/`history()` stream (Pyric Studio keystone, track T1).
|
|
2843
|
+
|
|
2844
|
+
**Why a new variant rather than reusing `request`/`write`.** Firestore's
|
|
2845
|
+
existing kinds are tightly coupled to the rules-simulator: `RequestEvent`
|
|
2846
|
+
carries `result: 'allow'|'deny'`, `evalMs`, the simulator's `reasons[]`,
|
|
2847
|
+
and `matchedRule`; `WriteSandboxEvent` carries Firestore-specific
|
|
2848
|
+
`sentinels`, `autoId`, and a Firestore `requestTime` Timestamp. Auth user-
|
|
2849
|
+
DB mutations (no path, no rule eval), Storage object puts, and RTDB tree
|
|
2850
|
+
writes don't have those concepts, and bending them into the Firestore
|
|
2851
|
+
shapes would either lie (synthesize a fake `result`/`requestTime`) or
|
|
2852
|
+
pollute the Firestore consumer contract. So this is ONE small, additive
|
|
2853
|
+
variant the three services share — Firestore consumers filter on their
|
|
2854
|
+
existing `kind`s and never see it. See the design rationale.
|
|
2855
|
+
|
|
2856
|
+
It is intentionally generic: `op` is a free-ish string discriminated by
|
|
2857
|
+
`service`, and `before`/`after` are best-effort serializable snapshots
|
|
2858
|
+
(omitted when not meaningful — e.g. a sign-out has no `after`). Studio's
|
|
2859
|
+
data grids / Action Center render `service` + `op` + `path` directly and
|
|
2860
|
+
diff `before`→`after` when both are present.
|
|
2861
|
+
|
|
2862
|
+
#### Properties
|
|
2863
|
+
|
|
2864
|
+
| Property | Type | Description |
|
|
2865
|
+
| :------ | :------ | :------ |
|
|
2866
|
+
| <a id="after"></a> `after?` | `unknown` | Best-effort serializable snapshot of the state AFTER the mutation. Absent on deletes / sign-outs (nothing remains). |
|
|
2867
|
+
| <a id="at-7"></a> `at` | `number` | - |
|
|
2868
|
+
| <a id="auth-11"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | Identity in effect when the op ran (the service's `request.auth` equivalent). `null` for admin/anonymous-driven mutations (e.g. `sandbox.createUser`, an unauthenticated RTDB write). |
|
|
2869
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
2870
|
+
| `auth.uid` | `string` | - |
|
|
2871
|
+
| <a id="before"></a> `before?` | `unknown` | Best-effort serializable snapshot of the state BEFORE the mutation. Absent when there was no prior state (a create) or it isn't cheap to capture. |
|
|
2872
|
+
| <a id="detail-5"></a> `detail?` | `Record`\<`string`, `unknown`\> | Free-form, service-specific extras a consumer may surface without re-deriving (e.g. storage `{ size, contentType }`, rtdb `{ committed }` for a transaction). Kept loose on purpose — it's a display hint, not a contract. |
|
|
2873
|
+
| <a id="id-7"></a> `id` | `string` | - |
|
|
2874
|
+
| <a id="kind-6"></a> `kind` | `"service_mutation"` | - |
|
|
2875
|
+
| <a id="op-2"></a> `op` | `string` | Service-scoped operation name. Stable, lowercase, snake/kebab-free: - auth: `user_create` | `user_update` | `user_delete` | `users_clear` | `sign_in` | `sign_out` - storage: `object_put` | `object_delete` | `metadata_update` - rtdb: `set` | `update` | `remove` | `transaction` - ai: `generate_content` | `stream_generate_content` | `count_tokens` | `request_rejected` New ops can be added without a breaking change (consumers switch with a default branch). |
|
|
2876
|
+
| <a id="path-5"></a> `path?` | `string` | The thing mutated, in the service's own addressing scheme: - auth: the user `uid` (or `'*'` for a clear-all). Absent for a sign-out with no prior user. - storage: the object `fullPath` (e.g. `avatars/alice.png`). - rtdb: the database path (e.g. `/rooms/r1/messages`), or for a multi-path `update` the ref path the call targeted. |
|
|
2877
|
+
| <a id="service-6"></a> `service` | `"auth"` \| `"storage"` \| `"rtdb"` \| `"messaging"` \| `"ai"` | Which service performed the mutation. Always one of the non-Firestore services — Firestore rides its own `request`/`write` path. (The provenance `service` field on the stamped event mirrors this; it is set redundantly here so a consumer matching purely on `kind` still gets the discriminator without reaching into provenance.) |
|
|
2878
|
+
|
|
2879
|
+
***
|
|
2880
|
+
|
|
2881
|
+
<a id="sessionboundaryevent"></a>
|
|
2882
|
+
|
|
2883
|
+
### SessionBoundaryEvent
|
|
2884
|
+
|
|
2885
|
+
Session boundary — emitted before `sandbox.reset()` swaps the env,
|
|
2886
|
+
and before `sandbox.dispose()` tears it down. Lets consumers segment
|
|
2887
|
+
a persisted event stream into "session N pre-reset" / "session N+1
|
|
2888
|
+
post-reset" runs.
|
|
2889
|
+
|
|
2890
|
+
#### Properties
|
|
2891
|
+
|
|
2892
|
+
| Property | Type | Description |
|
|
2893
|
+
| :------ | :------ | :------ |
|
|
2894
|
+
| <a id="at-8"></a> `at` | `number` | - |
|
|
2895
|
+
| <a id="id-8"></a> `id` | `string` | - |
|
|
2896
|
+
| <a id="kind-7"></a> `kind` | `"session_boundary"` | - |
|
|
2897
|
+
| <a id="phase-1"></a> `phase` | `"reset"` \| `"dispose"` | - |
|
|
2898
|
+
| <a id="prioropcount"></a> `priorOpCount` | `number` | Total events emitted on this sandbox before the boundary. |
|
|
2899
|
+
|
|
2900
|
+
***
|
|
2901
|
+
|
|
2902
|
+
<a id="snapshotdeliveryevent"></a>
|
|
2903
|
+
|
|
2904
|
+
### SnapshotDeliveryEvent
|
|
2905
|
+
|
|
2906
|
+
Snapshot delivered to a `onSnapshot` listener's user callback.
|
|
2907
|
+
|
|
2908
|
+
Fires AFTER the no-op suppression check — every `snapshot_delivery`
|
|
2909
|
+
event corresponds to an actual user-callback invocation. Listener
|
|
2910
|
+
re-evals that resolved to no-ops emit [SnapshotSuppressedEvent](#snapshotsuppressedevent)
|
|
2911
|
+
instead.
|
|
2912
|
+
|
|
2913
|
+
`sample` carries best-effort serializable views of the docs the
|
|
2914
|
+
callback received; consumers truncate before persisting if the
|
|
2915
|
+
scenario produces large snapshots.
|
|
2916
|
+
|
|
2917
|
+
#### Properties
|
|
2918
|
+
|
|
2919
|
+
| Property | Type | Description |
|
|
2920
|
+
| :------ | :------ | :------ |
|
|
2921
|
+
| <a id="addedcount"></a> `addedCount` | `number` | - |
|
|
2922
|
+
| <a id="at-9"></a> `at` | `number` | - |
|
|
2923
|
+
| <a id="auth-12"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | - |
|
|
2924
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
2925
|
+
| `auth.uid` | `string` | - |
|
|
2926
|
+
| <a id="id-9"></a> `id` | `string` | - |
|
|
2927
|
+
| <a id="kind-8"></a> `kind` | `"snapshot_delivery"` | - |
|
|
2928
|
+
| <a id="listenerid-2"></a> `listenerId` | `string` | Opaque listener id assigned at attach time. |
|
|
2929
|
+
| <a id="modifiedcount"></a> `modifiedCount` | `number` | - |
|
|
2930
|
+
| <a id="removedcount"></a> `removedCount` | `number` | - |
|
|
2931
|
+
| <a id="sample-1"></a> `sample?` | \{ `docs`: \{ `data`: `Record`\<`string`, `unknown`\>; `path`: `string`; \}[]; \} | Doc payloads, in the order the user callback saw them. |
|
|
2932
|
+
| `sample.docs` | \{ `data`: `Record`\<`string`, `unknown`\>; `path`: `string`; \}[] | - |
|
|
2933
|
+
| <a id="size-1"></a> `size` | `number` | `1` for doc-kind (exists) / `0` (deleted), `n` for query-kind. |
|
|
2934
|
+
| <a id="target-2"></a> `target` | \| \{ `kind`: `"doc"`; `path`: `string`; \} \| \{ `collection`: `string`; `kind`: `"query"`; \} | - |
|
|
2935
|
+
| <a id="triggeredby-3"></a> `triggeredBy?` | \{ `method`: `string`; `path`: `string`; \} | The user op that triggered this re-eval. Absent on initial fire and on `deployRules`-driven re-evals. |
|
|
2936
|
+
| `triggeredBy.method` | `string` | - |
|
|
2937
|
+
| `triggeredBy.path` | `string` | - |
|
|
2938
|
+
|
|
2939
|
+
***
|
|
2940
|
+
|
|
2941
|
+
<a id="snapshoterrorevent"></a>
|
|
2942
|
+
|
|
2943
|
+
### SnapshotErrorEvent
|
|
2944
|
+
|
|
2945
|
+
Eval-time payload emitted to Sandbox.onSnapshotError subscribers.
|
|
2946
|
+
|
|
2947
|
+
Stream-level error from a Firestore `onSnapshot` listener — the
|
|
2948
|
+
listener has been silently terminated and will deliver no further
|
|
2949
|
+
snapshots (matches production: a stream error is once-per-stream and
|
|
2950
|
+
the listener stays "subscribed" from the consumer's perspective but
|
|
2951
|
+
receives nothing further). Carries the `target` so the host UI can
|
|
2952
|
+
attribute the error to a specific watch.
|
|
2953
|
+
|
|
2954
|
+
Currently `permission-denied` is the only code the sandbox produces
|
|
2955
|
+
(production also emits `unavailable`, `aborted`, `resource-exhausted`
|
|
2956
|
+
— none of which have a sandbox analog: no network stream to drop, no
|
|
2957
|
+
quota, no concurrent transactions to conflict). Documented divergence
|
|
2958
|
+
from production; new codes can be added if a sandbox-specific
|
|
2959
|
+
scenario surfaces them.
|
|
2960
|
+
|
|
2961
|
+
#### Properties
|
|
2962
|
+
|
|
2963
|
+
| Property | Type |
|
|
2964
|
+
| :------ | :------ |
|
|
2965
|
+
| <a id="auth-13"></a> `auth?` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} |
|
|
2966
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> |
|
|
2967
|
+
| `auth.uid` | `string` |
|
|
2968
|
+
| <a id="code-2"></a> `code` | `"permission-denied"` |
|
|
2969
|
+
| <a id="message-1"></a> `message` | `string` |
|
|
2970
|
+
| <a id="reasons-4"></a> `reasons?` | `string`[] |
|
|
2971
|
+
| <a id="request-4"></a> `request?` | \{ `method`: `"get"` \| `"list"` \| `"create"` \| `"update"` \| `"delete"`; `path`: `string`; `resourceData?`: `Record`\<`string`, `unknown`\>; \} |
|
|
2972
|
+
| `request.method` | `"get"` \| `"list"` \| `"create"` \| `"update"` \| `"delete"` |
|
|
2973
|
+
| `request.path` | `string` |
|
|
2974
|
+
| `request.resourceData?` | `Record`\<`string`, `unknown`\> |
|
|
2975
|
+
| <a id="resource-2"></a> `resource?` | \{ `data`: `Record`\<`string`, `unknown`\>; `exists`: `boolean`; \} |
|
|
2976
|
+
| `resource.data` | `Record`\<`string`, `unknown`\> |
|
|
2977
|
+
| `resource.exists` | `boolean` |
|
|
2978
|
+
| <a id="target-3"></a> `target` | \| \{ `kind`: `"doc"`; `path`: `string`; \} \| \{ `collection`: `string`; `kind`: `"query"`; \} |
|
|
2979
|
+
|
|
2980
|
+
***
|
|
2981
|
+
|
|
2982
|
+
<a id="snapshotsuppressedevent"></a>
|
|
2983
|
+
|
|
2984
|
+
### SnapshotSuppressedEvent
|
|
2985
|
+
|
|
2986
|
+
Listener re-eval that was suppressed before delivery — the re-eval
|
|
2987
|
+
ran but produced no observable change vs the prior snapshot, so the
|
|
2988
|
+
user callback wasn't invoked.
|
|
2989
|
+
|
|
2990
|
+
Useful for "why didn't my listener fire" debugging. Default UIs
|
|
2991
|
+
should filter these out; only the inspector-style consumer needs
|
|
2992
|
+
them.
|
|
2993
|
+
|
|
2994
|
+
#### Properties
|
|
2995
|
+
|
|
2996
|
+
| Property | Type | Description |
|
|
2997
|
+
| :------ | :------ | :------ |
|
|
2998
|
+
| <a id="at-10"></a> `at` | `number` | - |
|
|
2999
|
+
| <a id="auth-14"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | - |
|
|
3000
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
3001
|
+
| `auth.uid` | `string` | - |
|
|
3002
|
+
| <a id="id-10"></a> `id` | `string` | - |
|
|
3003
|
+
| <a id="kind-9"></a> `kind` | `"snapshot_suppressed"` | - |
|
|
3004
|
+
| <a id="listenerid-3"></a> `listenerId` | `string` | - |
|
|
3005
|
+
| <a id="reason-1"></a> `reason` | `"no-op"` | Why this re-eval was suppressed. v1 only emits `'no-op'`. |
|
|
3006
|
+
| <a id="target-4"></a> `target` | \| \{ `kind`: `"doc"`; `path`: `string`; \} \| \{ `collection`: `string`; `kind`: `"query"`; \} | - |
|
|
3007
|
+
| <a id="triggeredby-4"></a> `triggeredBy?` | \{ `method`: `string`; `path`: `string`; \} | - |
|
|
3008
|
+
| `triggeredBy.method` | `string` | - |
|
|
3009
|
+
| `triggeredBy.path` | `string` | - |
|
|
3010
|
+
|
|
3011
|
+
***
|
|
3012
|
+
|
|
3013
|
+
<a id="tabsyncoptions"></a>
|
|
3014
|
+
|
|
3015
|
+
### TabSyncOptions
|
|
3016
|
+
|
|
3017
|
+
Options for `sandbox.enableTabSync(options?)`. All fields are optional;
|
|
3018
|
+
the defaults provide a ready-to-use configuration for browser environments.
|
|
3019
|
+
|
|
3020
|
+
#### Properties
|
|
3021
|
+
|
|
3022
|
+
| Property | Type | Description |
|
|
3023
|
+
| :------ | :------ | :------ |
|
|
3024
|
+
| <a id="channel-1"></a> `channel?` | [`BroadcastChannelLike`](#broadcastchannellike) | The broadcast channel to use. Defaults to `new BroadcastChannel('pyric:tabsync')` when omitted and `BroadcastChannel` is available in the global scope. Pass a custom implementation for tests or Node environments. |
|
|
3025
|
+
| <a id="originid"></a> `originId?` | `string` | A string that uniquely identifies this tab's sandbox instance. Used for echo suppression (messages with `origin === originId` are silently dropped) and for directing `state` replies to the requesting tab. Defaults to `crypto.randomUUID()` when available, otherwise a counter + process-uptime string (no `Date.now()` or `Math.random()` — those change every call and can collide in fast tests). |
|
|
3026
|
+
|
|
3027
|
+
***
|
|
3028
|
+
|
|
3029
|
+
<a id="webstoragelike"></a>
|
|
3030
|
+
|
|
3031
|
+
### WebStorageLike
|
|
3032
|
+
|
|
3033
|
+
Minimal web-storage-like contract the session persistence controller
|
|
3034
|
+
reads/writes. Matches the `localStorage` / `sessionStorage` browser
|
|
3035
|
+
API subset that `pyric dev`'s `SessionStore` already uses, so
|
|
3036
|
+
browsers pass real storages and tests pass in-memory Map-backed fakes.
|
|
3037
|
+
|
|
3038
|
+
Why the minimal subset (get/set/remove) instead of the full
|
|
3039
|
+
`Storage` interface: this library targets multiple environments
|
|
3040
|
+
(browser, Bun, Node) and the full `Storage` interface carries
|
|
3041
|
+
length + key() + clear() that aren't needed here — narrowing the
|
|
3042
|
+
contract keeps tests simple and Node/Bun hosts from having to
|
|
3043
|
+
implement a complete polyfill.
|
|
3044
|
+
|
|
3045
|
+
#### Methods
|
|
3046
|
+
|
|
3047
|
+
<a id="getitem"></a>
|
|
3048
|
+
|
|
3049
|
+
##### getItem()
|
|
3050
|
+
|
|
3051
|
+
```ts
|
|
3052
|
+
getItem(key: string): string;
|
|
3053
|
+
```
|
|
3054
|
+
|
|
3055
|
+
###### Parameters
|
|
3056
|
+
|
|
3057
|
+
| Parameter | Type |
|
|
3058
|
+
| :------ | :------ |
|
|
3059
|
+
| `key` | `string` |
|
|
3060
|
+
|
|
3061
|
+
###### Returns
|
|
3062
|
+
|
|
3063
|
+
`string`
|
|
3064
|
+
|
|
3065
|
+
<a id="removeitem"></a>
|
|
3066
|
+
|
|
3067
|
+
##### removeItem()
|
|
3068
|
+
|
|
3069
|
+
```ts
|
|
3070
|
+
removeItem(key: string): void;
|
|
3071
|
+
```
|
|
3072
|
+
|
|
3073
|
+
###### Parameters
|
|
3074
|
+
|
|
3075
|
+
| Parameter | Type |
|
|
3076
|
+
| :------ | :------ |
|
|
3077
|
+
| `key` | `string` |
|
|
3078
|
+
|
|
3079
|
+
###### Returns
|
|
3080
|
+
|
|
3081
|
+
`void`
|
|
3082
|
+
|
|
3083
|
+
<a id="setitem"></a>
|
|
3084
|
+
|
|
3085
|
+
##### setItem()
|
|
3086
|
+
|
|
3087
|
+
```ts
|
|
3088
|
+
setItem(key: string, value: string): void;
|
|
3089
|
+
```
|
|
3090
|
+
|
|
3091
|
+
###### Parameters
|
|
3092
|
+
|
|
3093
|
+
| Parameter | Type |
|
|
3094
|
+
| :------ | :------ |
|
|
3095
|
+
| `key` | `string` |
|
|
3096
|
+
| `value` | `string` |
|
|
3097
|
+
|
|
3098
|
+
###### Returns
|
|
3099
|
+
|
|
3100
|
+
`void`
|
|
3101
|
+
|
|
3102
|
+
***
|
|
3103
|
+
|
|
3104
|
+
<a id="writesandboxevent"></a>
|
|
3105
|
+
|
|
3106
|
+
### WriteSandboxEvent
|
|
3107
|
+
|
|
3108
|
+
Committed write — a `create`/`update`/`set`/`delete` that the rule
|
|
3109
|
+
engine allowed AND that the keyspace successfully applied. Includes
|
|
3110
|
+
pre- and post-state so consumers can render diffs and (in a future
|
|
3111
|
+
`sandbox.history()` API) reconstruct state by replay.
|
|
3112
|
+
|
|
3113
|
+
Fires AFTER the corresponding `kind: 'request'` event for the same
|
|
3114
|
+
op. A denied or rolled-back write surfaces as a request-deny only;
|
|
3115
|
+
`write` events only fire for committed writes.
|
|
3116
|
+
|
|
3117
|
+
`sentinels` and `autoId` are placeholders for the eventual replay
|
|
3118
|
+
engine — v1 of the unified channel leaves them undefined. The shape
|
|
3119
|
+
is locked so consumers can build against it without churn when
|
|
3120
|
+
sentinel/auto-id capture lands.
|
|
3121
|
+
|
|
3122
|
+
#### Properties
|
|
3123
|
+
|
|
3124
|
+
| Property | Type | Description |
|
|
3125
|
+
| :------ | :------ | :------ |
|
|
3126
|
+
| <a id="at-11"></a> `at` | `number` | - |
|
|
3127
|
+
| <a id="auth-15"></a> `auth` | \{ `token?`: `Record`\<`string`, `unknown`\>; `uid`: `string`; \} | - |
|
|
3128
|
+
| `auth.token?` | `Record`\<`string`, `unknown`\> | - |
|
|
3129
|
+
| `auth.uid` | `string` | - |
|
|
3130
|
+
| <a id="autoid"></a> `autoId?` | `string` | Minted document ID when this write came from `collection.add()` / `LocalEnvironment.createWithAutoId`. The replay engine aliases the path's last segment to a fresh mint on replay (rather than preserving the original auto-ID). |
|
|
3131
|
+
| <a id="data-1"></a> `data?` | `Record`\<`string`, `unknown`\> | Pre-resolution write payload — `FieldValue.*` sentinels preserved as marker shapes (`{ __type: 'serverTimestamp' }`, etc.) so the replay engine can re-resolve them. The rule engine evaluated against the resolved form internally; the resolved form lives on [nextState](#nextstate-1). Absent on `delete`. |
|
|
3132
|
+
| <a id="detail-6"></a> `detail?` | \{ `admin?`: `boolean`; \} & `Record`\<`string`, `unknown`\> | Free-form write metadata. `admin: true` marks a rules-bypassing setup/admin commit so replay can apply it as context without asking candidate rules to permit it. |
|
|
3133
|
+
| <a id="groupid-3"></a> `groupId?` | `string` | - |
|
|
3134
|
+
| <a id="groupkind-3"></a> `groupKind?` | `"transaction"` \| `"batch"` | - |
|
|
3135
|
+
| <a id="id-11"></a> `id` | `string` | - |
|
|
3136
|
+
| <a id="kind-10"></a> `kind` | `"write"` | - |
|
|
3137
|
+
| <a id="method-5"></a> `method` | `"create"` \| `"update"` \| `"delete"` \| `"set"` | - |
|
|
3138
|
+
| <a id="nextstate-1"></a> `nextState` | `Record`\<`string`, `unknown`\> | State AFTER this write. `null` on `delete`. |
|
|
3139
|
+
| <a id="path-6"></a> `path` | `string` | - |
|
|
3140
|
+
| <a id="priorstate-1"></a> `priorState` | `Record`\<`string`, `unknown`\> | State BEFORE this write. `null` for a non-existent doc. |
|
|
3141
|
+
| <a id="requesttime"></a> `requestTime` | \{ `nanoseconds`: `number`; `seconds`: `number`; \} | Server time at which the rule engine evaluated this write — pinned per op (or shared across sub-ops in a batch / transaction). The replay engine re-issues this exact value when re-resolving `serverTimestamp()` sentinels so resolved fields are bit-identical on replay. Shape mirrors the Firestore Web SDK Timestamp (`{ seconds, nanoseconds }`). |
|
|
3142
|
+
| `requestTime.nanoseconds` | `number` | - |
|
|
3143
|
+
| `requestTime.seconds` | `number` | - |
|
|
3144
|
+
| <a id="sentinels"></a> `sentinels?` | \{ `field`: `string`; `kind`: \| `"delete"` \| `"serverTimestamp"` \| `"increment"` \| `"arrayUnion"` \| `"arrayRemove"`; \}[] | FieldValue sentinels (serverTimestamp / increment / arrayUnion / arrayRemove / deleteField → 'delete') extracted from the pre-resolution write payload. The replay engine consumes this to re-issue the same sentinels at replay time without consulting resolved values that would have drifted. Path syntax: dotted with bracket-indices ('a.b[0].c'). Absent when the write contained no sentinels. |
|
|
3145
|
+
|
|
3146
|
+
## Type Aliases
|
|
3147
|
+
|
|
3148
|
+
<a id="authlens-2"></a>
|
|
3149
|
+
|
|
3150
|
+
### AuthLens
|
|
3151
|
+
|
|
3152
|
+
```ts
|
|
3153
|
+
type AuthLens =
|
|
3154
|
+
| {
|
|
3155
|
+
mode: "admin";
|
|
3156
|
+
}
|
|
3157
|
+
| {
|
|
3158
|
+
mode: "as";
|
|
3159
|
+
token?: Record<string, unknown>;
|
|
3160
|
+
uid: string;
|
|
3161
|
+
}
|
|
3162
|
+
| {
|
|
3163
|
+
mode: "app-session";
|
|
3164
|
+
}
|
|
3165
|
+
| {
|
|
3166
|
+
mode: "anon";
|
|
3167
|
+
};
|
|
3168
|
+
```
|
|
3169
|
+
|
|
3170
|
+
The identity/rules lens an operation actually ran under.
|
|
3171
|
+
|
|
3172
|
+
***
|
|
3173
|
+
|
|
3174
|
+
<a id="authstate"></a>
|
|
3175
|
+
|
|
3176
|
+
### AuthState
|
|
3177
|
+
|
|
3178
|
+
```ts
|
|
3179
|
+
type AuthState =
|
|
3180
|
+
| {
|
|
3181
|
+
token?: Record<string, unknown>;
|
|
3182
|
+
uid: string;
|
|
3183
|
+
}
|
|
3184
|
+
| null;
|
|
3185
|
+
```
|
|
3186
|
+
|
|
3187
|
+
A signed-in identity for sandbox operations. `null` is anonymous.
|
|
3188
|
+
|
|
3189
|
+
`token` is the Firebase Auth token claims map (custom claims plus
|
|
3190
|
+
standard ones). It surfaces the same way it does in production rules
|
|
3191
|
+
via `request.auth.token.*`. Omit it for plain UID-only auth.
|
|
3192
|
+
|
|
3193
|
+
Renamed from `AuthContext` (pre-multi-context) so the data type
|
|
3194
|
+
doesn't visually collide with `SandboxContext` (the identity-bearing
|
|
3195
|
+
handle). They sit at different layers — payload vs. handle — and the
|
|
3196
|
+
names should reflect that.
|
|
3197
|
+
|
|
3198
|
+
***
|
|
3199
|
+
|
|
3200
|
+
<a id="difftarget"></a>
|
|
3201
|
+
|
|
3202
|
+
### DiffTarget
|
|
3203
|
+
|
|
3204
|
+
```ts
|
|
3205
|
+
type DiffTarget = LocalSandbox | SandboxSnapshot;
|
|
3206
|
+
```
|
|
3207
|
+
|
|
3208
|
+
A reference to diff a branch against: a live sandbox or a bare snapshot.
|
|
3209
|
+
|
|
3210
|
+
***
|
|
3211
|
+
|
|
3212
|
+
<a id="divergence"></a>
|
|
3213
|
+
|
|
3214
|
+
### Divergence
|
|
3215
|
+
|
|
3216
|
+
```ts
|
|
3217
|
+
type Divergence =
|
|
3218
|
+
| {
|
|
3219
|
+
after: unknown;
|
|
3220
|
+
before: unknown;
|
|
3221
|
+
field: string;
|
|
3222
|
+
kind: "sentinel-drift";
|
|
3223
|
+
path: string;
|
|
3224
|
+
sentinelKind: | "serverTimestamp"
|
|
3225
|
+
| "increment"
|
|
3226
|
+
| "arrayUnion"
|
|
3227
|
+
| "arrayRemove"
|
|
3228
|
+
| "delete";
|
|
3229
|
+
}
|
|
3230
|
+
| {
|
|
3231
|
+
kind: "autoid-alias";
|
|
3232
|
+
originalPath: string;
|
|
3233
|
+
replayedPath: string;
|
|
3234
|
+
}
|
|
3235
|
+
| {
|
|
3236
|
+
after: unknown;
|
|
3237
|
+
before: unknown;
|
|
3238
|
+
field: string;
|
|
3239
|
+
kind: "time-drift";
|
|
3240
|
+
path: string;
|
|
3241
|
+
}
|
|
3242
|
+
| {
|
|
3243
|
+
after: unknown;
|
|
3244
|
+
before: unknown;
|
|
3245
|
+
field?: string;
|
|
3246
|
+
kind: "real-divergence";
|
|
3247
|
+
path: string;
|
|
3248
|
+
};
|
|
3249
|
+
```
|
|
3250
|
+
|
|
3251
|
+
***
|
|
3252
|
+
|
|
3253
|
+
<a id="eventactor"></a>
|
|
3254
|
+
|
|
3255
|
+
### EventActor
|
|
3256
|
+
|
|
3257
|
+
```ts
|
|
3258
|
+
type EventActor =
|
|
3259
|
+
| {
|
|
3260
|
+
kind: "app";
|
|
3261
|
+
}
|
|
3262
|
+
| {
|
|
3263
|
+
kind: "studio";
|
|
3264
|
+
}
|
|
3265
|
+
| {
|
|
3266
|
+
kind: "agent";
|
|
3267
|
+
name: string;
|
|
3268
|
+
}
|
|
3269
|
+
| {
|
|
3270
|
+
kind: "app-builder";
|
|
3271
|
+
}
|
|
3272
|
+
| {
|
|
3273
|
+
kind: "unattributed";
|
|
3274
|
+
};
|
|
3275
|
+
```
|
|
3276
|
+
|
|
3277
|
+
Who initiated the operation behind an event. Missing source is represented
|
|
3278
|
+
explicitly as `unattributed`; it is never silently promoted to app traffic.
|
|
3279
|
+
|
|
3280
|
+
***
|
|
3281
|
+
|
|
3282
|
+
<a id="eventservice"></a>
|
|
3283
|
+
|
|
3284
|
+
### EventService
|
|
3285
|
+
|
|
3286
|
+
```ts
|
|
3287
|
+
type EventService = "firestore" | "auth" | "storage" | "rtdb" | "messaging" | "ai";
|
|
3288
|
+
```
|
|
3289
|
+
|
|
3290
|
+
Which sandbox service emitted an event.
|
|
3291
|
+
|
|
3292
|
+
***
|
|
3293
|
+
|
|
3294
|
+
<a id="remotesandboxfactory"></a>
|
|
3295
|
+
|
|
3296
|
+
### RemoteSandboxFactory()
|
|
3297
|
+
|
|
3298
|
+
```ts
|
|
3299
|
+
type RemoteSandboxFactory = (opts?: RemoteSandboxFactoryOptions) => RemoteSandbox;
|
|
3300
|
+
```
|
|
3301
|
+
|
|
3302
|
+
The factory `@pyric/cli/register` installs at
|
|
3303
|
+
`globalThis[`[REMOTE\_SANDBOX\_FACTORY](#remote_sandbox_factory)`]`. SYNCHRONOUS by contract:
|
|
3304
|
+
`initializeApp()` is sync in firebase-admin, so the factory must return
|
|
3305
|
+
the branded handle without awaiting (connection establishment may be
|
|
3306
|
+
lazy inside the handle's channel).
|
|
3307
|
+
|
|
3308
|
+
#### Parameters
|
|
3309
|
+
|
|
3310
|
+
| Parameter | Type |
|
|
3311
|
+
| :------ | :------ |
|
|
3312
|
+
| `opts?` | [`RemoteSandboxFactoryOptions`](#remotesandboxfactoryoptions) |
|
|
3313
|
+
|
|
3314
|
+
#### Returns
|
|
3315
|
+
|
|
3316
|
+
[`RemoteSandbox`](#remotesandbox)
|
|
3317
|
+
|
|
3318
|
+
***
|
|
3319
|
+
|
|
3320
|
+
<a id="rulesdisposition-2"></a>
|
|
3321
|
+
|
|
3322
|
+
### RulesDisposition
|
|
3323
|
+
|
|
3324
|
+
```ts
|
|
3325
|
+
type RulesDisposition =
|
|
3326
|
+
| {
|
|
3327
|
+
kind: "evaluated";
|
|
3328
|
+
verdict: "allow" | "deny";
|
|
3329
|
+
}
|
|
3330
|
+
| {
|
|
3331
|
+
kind: "bypassed";
|
|
3332
|
+
reason: "admin";
|
|
3333
|
+
}
|
|
3334
|
+
| {
|
|
3335
|
+
kind: "not-evaluated";
|
|
3336
|
+
reason: "no-rules" | "unsupported" | "not-a-rules-operation" | "runtime-error";
|
|
3337
|
+
};
|
|
3338
|
+
```
|
|
3339
|
+
|
|
3340
|
+
What happened at the Security Rules seam. Admin is a lens; `bypassed` is
|
|
3341
|
+
the rules disposition.
|
|
3342
|
+
|
|
3343
|
+
***
|
|
3344
|
+
|
|
3345
|
+
<a id="sandboxerrorcode-1"></a>
|
|
3346
|
+
|
|
3347
|
+
### SandboxErrorCode
|
|
3348
|
+
|
|
3349
|
+
```ts
|
|
3350
|
+
type SandboxErrorCode =
|
|
3351
|
+
| "invalid-argument"
|
|
3352
|
+
| "permission-denied"
|
|
3353
|
+
| "not-found"
|
|
3354
|
+
| "already-exists"
|
|
3355
|
+
| "failed-precondition"
|
|
3356
|
+
| "aborted"
|
|
3357
|
+
| "unavailable"
|
|
3358
|
+
| "unimplemented"
|
|
3359
|
+
| "not-seeded"
|
|
3360
|
+
| "rules-not-loaded";
|
|
3361
|
+
```
|
|
3362
|
+
|
|
3363
|
+
Error codes raised by the sandbox layer.
|
|
3364
|
+
|
|
3365
|
+
The first batch matches Firebase / gRPC conventions so existing
|
|
3366
|
+
`if (e.code === 'permission-denied')` code from production paths
|
|
3367
|
+
keeps working. The second batch is sandbox-specific and exists so
|
|
3368
|
+
agents can distinguish "sandbox doesn't simulate this" from "your
|
|
3369
|
+
code is wrong" without parsing message strings.
|
|
3370
|
+
|
|
3371
|
+
***
|
|
3372
|
+
|
|
3373
|
+
<a id="sandboxevent"></a>
|
|
3374
|
+
|
|
3375
|
+
### SandboxEvent
|
|
3376
|
+
|
|
3377
|
+
```ts
|
|
3378
|
+
type SandboxEvent =
|
|
3379
|
+
| RequestEvent
|
|
3380
|
+
| WriteSandboxEvent
|
|
3381
|
+
| SnapshotDeliveryEvent
|
|
3382
|
+
| SnapshotSuppressedEvent
|
|
3383
|
+
| ListenerLifecycleEvent
|
|
3384
|
+
| SessionBoundaryEvent
|
|
3385
|
+
| ServiceMutationEvent
|
|
3386
|
+
| SandboxOperationEvent
|
|
3387
|
+
| SandboxCommitEvent
|
|
3388
|
+
| SandboxListenerEvent
|
|
3389
|
+
| SandboxRuntimeErrorEvent & EventProvenance;
|
|
3390
|
+
```
|
|
3391
|
+
|
|
3392
|
+
Discriminated union of every event the sandbox emits to
|
|
3393
|
+
[Sandbox.onEvent](#onevent-4) subscribers.
|
|
3394
|
+
|
|
3395
|
+
Issue #307 — replaces the prior three-channel surface
|
|
3396
|
+
(`onRequest` / `onDenial` / `onSnapshotError`). Filter on `kind`
|
|
3397
|
+
to recover the subset each old channel covered:
|
|
3398
|
+
- request: `kind === 'request'`
|
|
3399
|
+
- denial: `kind === 'request' && result === 'deny'`
|
|
3400
|
+
- snapshotError: `kind === 'listener_errored'`
|
|
3401
|
+
|
|
3402
|
+
See the design rationale for the
|
|
3403
|
+
field-by-field rationale.
|
|
3404
|
+
|
|
3405
|
+
## Variables
|
|
3406
|
+
|
|
3407
|
+
<a id="remote_sandbox-1"></a>
|
|
3408
|
+
|
|
3409
|
+
### REMOTE\_SANDBOX
|
|
3410
|
+
|
|
3411
|
+
```ts
|
|
3412
|
+
const REMOTE_SANDBOX: unique symbol;
|
|
3413
|
+
```
|
|
3414
|
+
|
|
3415
|
+
Brand stamped (value `true`) on every remote sandbox handle.
|
|
3416
|
+
`Symbol.for` — registered globally so `pyric-admin`'s check matches the
|
|
3417
|
+
stamp even if two copies of `pyric` end up in one process.
|
|
3418
|
+
|
|
3419
|
+
***
|
|
3420
|
+
|
|
3421
|
+
<a id="remote_sandbox_factory"></a>
|
|
3422
|
+
|
|
3423
|
+
### REMOTE\_SANDBOX\_FACTORY
|
|
3424
|
+
|
|
3425
|
+
```ts
|
|
3426
|
+
const REMOTE_SANDBOX_FACTORY: unique symbol;
|
|
3427
|
+
```
|
|
3428
|
+
|
|
3429
|
+
Well-known global key under which `@pyric/cli/register` installs the
|
|
3430
|
+
remote-sandbox factory: `globalThis[REMOTE_SANDBOX_FACTORY]`.
|
|
3431
|
+
|
|
3432
|
+
This is the AMBIENT-INIT seam (adoption experience, layer 3): when
|
|
3433
|
+
`pyric-admin/app`'s bare `initializeApp()` sees `PYRIC_SANDBOX=remote[:url]`
|
|
3434
|
+
it reads this global and calls the installed [RemoteSandboxFactory](#remotesandboxfactory)
|
|
3435
|
+
to obtain the branded handle — without importing `@pyric/cli` (which is
|
|
3436
|
+
a devDependency of the app, not of `pyric-admin`). `Symbol.for` so the
|
|
3437
|
+
installer and the reader agree even across duplicated copies of `pyric`.
|
|
3438
|
+
|
|
3439
|
+
## Functions
|
|
3440
|
+
|
|
3441
|
+
<a id="apply"></a>
|
|
3442
|
+
|
|
3443
|
+
### apply()
|
|
3444
|
+
|
|
3445
|
+
```ts
|
|
3446
|
+
function apply(branch: Branch, events: readonly SandboxEvent[]): Branch;
|
|
3447
|
+
```
|
|
3448
|
+
|
|
3449
|
+
Apply a stream of events to a branch by re-issuing their writes against
|
|
3450
|
+
the branch's CURRENT state.
|
|
3451
|
+
|
|
3452
|
+
This is the same per-write re-issue logic `replay()` runs (filter to
|
|
3453
|
+
`kind: 'write'`, honour `autoId` / pinned `requestTime`, prefer the
|
|
3454
|
+
pre-resolution `request.resourceData` so sentinels re-resolve), but
|
|
3455
|
+
applied *incrementally* on the branch's existing env rather than on a
|
|
3456
|
+
fresh empty sandbox — so it composes over base docs (e.g. an `update`
|
|
3457
|
+
lands on a doc the snapshot seeded) and accumulates across multiple
|
|
3458
|
+
`apply` calls. The applied events are folded into `branch.events` so
|
|
3459
|
+
[promote](#promote) can reproduce the same sequence on the target.
|
|
3460
|
+
|
|
3461
|
+
#### Parameters
|
|
3462
|
+
|
|
3463
|
+
| Parameter | Type |
|
|
3464
|
+
| :------ | :------ |
|
|
3465
|
+
| `branch` | [`Branch`](#branch) |
|
|
3466
|
+
| `events` | readonly [`SandboxEvent`](#sandboxevent)[] |
|
|
3467
|
+
|
|
3468
|
+
#### Returns
|
|
3469
|
+
|
|
3470
|
+
[`Branch`](#branch)
|
|
3471
|
+
|
|
3472
|
+
the same branch (mutated in place) for chaining.
|
|
3473
|
+
|
|
3474
|
+
***
|
|
3475
|
+
|
|
3476
|
+
<a id="attachpersistence"></a>
|
|
3477
|
+
|
|
3478
|
+
### attachPersistence()
|
|
3479
|
+
|
|
3480
|
+
```ts
|
|
3481
|
+
function attachPersistence(sandbox: Sandbox, rawOptions: SandboxPersistenceOptions): Promise<PersistenceController>;
|
|
3482
|
+
```
|
|
3483
|
+
|
|
3484
|
+
Construct a controller, restore any prior snapshot, and wire the
|
|
3485
|
+
auto-flush subscription. Returns once restore has completed (callers
|
|
3486
|
+
can `await sandbox.enablePersistence(...)` and be sure the in-memory
|
|
3487
|
+
state reflects the persisted blob).
|
|
3488
|
+
|
|
3489
|
+
Late service registration: services (e.g. auth) may register with the
|
|
3490
|
+
sandbox AFTER this call returns (the user calls `enablePersistence`
|
|
3491
|
+
then later `getAuth(sandbox)` which triggers `registerPersistableService`).
|
|
3492
|
+
We handle this in two parts:
|
|
3493
|
+
1. `restore()` returns the raw `services` blob map so the controller
|
|
3494
|
+
can apply it to late-arriving services.
|
|
3495
|
+
2. `setServiceRegistrationHook` fires on each registration — we
|
|
3496
|
+
immediately apply the saved blob data (if any) AND subscribe the
|
|
3497
|
+
service's change notifier for future flushes.
|
|
3498
|
+
|
|
3499
|
+
#### Parameters
|
|
3500
|
+
|
|
3501
|
+
| Parameter | Type |
|
|
3502
|
+
| :------ | :------ |
|
|
3503
|
+
| `sandbox` | [`Sandbox`](#sandbox-3) |
|
|
3504
|
+
| `rawOptions` | [`SandboxPersistenceOptions`](#sandboxpersistenceoptions) |
|
|
3505
|
+
|
|
3506
|
+
#### Returns
|
|
3507
|
+
|
|
3508
|
+
`Promise`\<[`PersistenceController`](#persistencecontroller)\>
|
|
3509
|
+
|
|
3510
|
+
***
|
|
3511
|
+
|
|
3512
|
+
<a id="attachtabsync"></a>
|
|
3513
|
+
|
|
3514
|
+
### attachTabSync()
|
|
3515
|
+
|
|
3516
|
+
```ts
|
|
3517
|
+
function attachTabSync(sandbox: LocalSandbox, options?: TabSyncOptions): () => void;
|
|
3518
|
+
```
|
|
3519
|
+
|
|
3520
|
+
Attach cross-tab sync to a sandbox. Called by `SandboxImpl.enableTabSync`;
|
|
3521
|
+
kept in a separate module so `sandbox-impl.ts` stays thin.
|
|
3522
|
+
|
|
3523
|
+
Returns a `disable` function: calling it unsubscribes the onEvent listener,
|
|
3524
|
+
removes the channel message listener, and closes the channel.
|
|
3525
|
+
|
|
3526
|
+
#### Parameters
|
|
3527
|
+
|
|
3528
|
+
| Parameter | Type | Description |
|
|
3529
|
+
| :------ | :------ | :------ |
|
|
3530
|
+
| `sandbox` | [`LocalSandbox`](#localsandbox) | The sandbox to sync. Must expose `onEvent`, `admin`, and `snapshot`. |
|
|
3531
|
+
| `options?` | [`TabSyncOptions`](#tabsyncoptions) | Optional channel and origin override. |
|
|
3532
|
+
|
|
3533
|
+
#### Returns
|
|
3534
|
+
|
|
3535
|
+
```ts
|
|
3536
|
+
(): void;
|
|
3537
|
+
```
|
|
3538
|
+
|
|
3539
|
+
##### Returns
|
|
3540
|
+
|
|
3541
|
+
`void`
|
|
3542
|
+
|
|
3543
|
+
***
|
|
3544
|
+
|
|
3545
|
+
<a id="bundlerecords"></a>
|
|
3546
|
+
|
|
3547
|
+
### bundleRecords()
|
|
3548
|
+
|
|
3549
|
+
```ts
|
|
3550
|
+
function bundleRecords(records: ReadonlyMap<string, unknown>): string;
|
|
3551
|
+
```
|
|
3552
|
+
|
|
3553
|
+
Bundle v3 records into one committable JSON string, for single-blob stores
|
|
3554
|
+
(serve's exportable `.pyric/state` file, an HTTP state endpoint). Inverse of
|
|
3555
|
+
[parseBundle](#parsebundle). This deliberately collapses the chunking into one blob:
|
|
3556
|
+
it is the single-artifact EXPORT shape, not the scale path (that is the
|
|
3557
|
+
record-shaped backend).
|
|
3558
|
+
|
|
3559
|
+
#### Parameters
|
|
3560
|
+
|
|
3561
|
+
| Parameter | Type |
|
|
3562
|
+
| :------ | :------ |
|
|
3563
|
+
| `records` | `ReadonlyMap`\<`string`, `unknown`\> |
|
|
3564
|
+
|
|
3565
|
+
#### Returns
|
|
3566
|
+
|
|
3567
|
+
`string`
|
|
3568
|
+
|
|
3569
|
+
***
|
|
3570
|
+
|
|
3571
|
+
<a id="createindexeddbbackend"></a>
|
|
3572
|
+
|
|
3573
|
+
### createIndexedDBBackend()
|
|
3574
|
+
|
|
3575
|
+
```ts
|
|
3576
|
+
function createIndexedDBBackend(): PersistenceBackend;
|
|
3577
|
+
```
|
|
3578
|
+
|
|
3579
|
+
Build an IndexedDB-backed `PersistenceBackend`. Throws synchronously when
|
|
3580
|
+
called outside a browser (no `indexedDB` global) so callers can detect the
|
|
3581
|
+
absence and fall back to memory.
|
|
3582
|
+
|
|
3583
|
+
#### Returns
|
|
3584
|
+
|
|
3585
|
+
[`PersistenceBackend`](#persistencebackend)
|
|
3586
|
+
|
|
3587
|
+
***
|
|
3588
|
+
|
|
3589
|
+
<a id="creatememorybackend"></a>
|
|
3590
|
+
|
|
3591
|
+
### createMemoryBackend()
|
|
3592
|
+
|
|
3593
|
+
```ts
|
|
3594
|
+
function createMemoryBackend(): PersistenceBackend;
|
|
3595
|
+
```
|
|
3596
|
+
|
|
3597
|
+
#### Returns
|
|
3598
|
+
|
|
3599
|
+
[`PersistenceBackend`](#persistencebackend)
|
|
3600
|
+
|
|
3601
|
+
***
|
|
3602
|
+
|
|
3603
|
+
<a id="deserializefrombuckets"></a>
|
|
3604
|
+
|
|
3605
|
+
### deserializeFromBuckets()
|
|
3606
|
+
|
|
3607
|
+
```ts
|
|
3608
|
+
function deserializeFromBuckets(records: Iterable<[string, unknown]>): {
|
|
3609
|
+
firestore: Record<string, Record<string, unknown>>;
|
|
3610
|
+
services: Record<string, unknown>;
|
|
3611
|
+
};
|
|
3612
|
+
```
|
|
3613
|
+
|
|
3614
|
+
Reassemble a firestore snapshot + services from v3 records (any iterable of
|
|
3615
|
+
[recordId, record] pairs). Rehydrates wrapper types from their markers.
|
|
3616
|
+
|
|
3617
|
+
#### Parameters
|
|
3618
|
+
|
|
3619
|
+
| Parameter | Type |
|
|
3620
|
+
| :------ | :------ |
|
|
3621
|
+
| `records` | `Iterable`\<\[`string`, `unknown`\]\> |
|
|
3622
|
+
|
|
3623
|
+
#### Returns
|
|
3624
|
+
|
|
3625
|
+
```ts
|
|
3626
|
+
{
|
|
3627
|
+
firestore: Record<string, Record<string, unknown>>;
|
|
3628
|
+
services: Record<string, unknown>;
|
|
3629
|
+
}
|
|
3630
|
+
```
|
|
3631
|
+
|
|
3632
|
+
##### firestore
|
|
3633
|
+
|
|
3634
|
+
```ts
|
|
3635
|
+
firestore: Record<string, Record<string, unknown>>;
|
|
3636
|
+
```
|
|
3637
|
+
|
|
3638
|
+
##### services
|
|
3639
|
+
|
|
3640
|
+
```ts
|
|
3641
|
+
services: Record<string, unknown>;
|
|
3642
|
+
```
|
|
3643
|
+
|
|
3644
|
+
***
|
|
3645
|
+
|
|
3646
|
+
<a id="diff"></a>
|
|
3647
|
+
|
|
3648
|
+
### diff()
|
|
3649
|
+
|
|
3650
|
+
```ts
|
|
3651
|
+
function diff(branch: Branch, target: DiffTarget): Divergence[];
|
|
3652
|
+
```
|
|
3653
|
+
|
|
3654
|
+
Structural diff of a branch's current state against a reference.
|
|
3655
|
+
|
|
3656
|
+
Reuses the replay engine's `Divergence` result type and mirrors its
|
|
3657
|
+
doc-level + field-level walk (see diffDocSets). With no captured
|
|
3658
|
+
write metadata in play, differences surface as `real-divergence`
|
|
3659
|
+
(field/doc changed) — the honest classification for "branch vs live".
|
|
3660
|
+
Added/removed docs surface as presence divergences (one side
|
|
3661
|
+
`undefined`).
|
|
3662
|
+
|
|
3663
|
+
#### Parameters
|
|
3664
|
+
|
|
3665
|
+
| Parameter | Type | Description |
|
|
3666
|
+
| :------ | :------ | :------ |
|
|
3667
|
+
| `branch` | [`Branch`](#branch) | The experiment. |
|
|
3668
|
+
| `target` | [`DiffTarget`](#difftarget) | Live sandbox or a snapshot to compare against. |
|
|
3669
|
+
|
|
3670
|
+
#### Returns
|
|
3671
|
+
|
|
3672
|
+
[`Divergence`](#divergence)[]
|
|
3673
|
+
|
|
3674
|
+
***
|
|
3675
|
+
|
|
3676
|
+
<a id="discard"></a>
|
|
3677
|
+
|
|
3678
|
+
### discard()
|
|
3679
|
+
|
|
3680
|
+
```ts
|
|
3681
|
+
function discard(branch: Branch): void;
|
|
3682
|
+
```
|
|
3683
|
+
|
|
3684
|
+
Discard a branch: drop its sandbox and mark it spent. The target is
|
|
3685
|
+
never touched (nothing was promoted). Idempotent.
|
|
3686
|
+
|
|
3687
|
+
#### Parameters
|
|
3688
|
+
|
|
3689
|
+
| Parameter | Type |
|
|
3690
|
+
| :------ | :------ |
|
|
3691
|
+
| `branch` | [`Branch`](#branch) |
|
|
3692
|
+
|
|
3693
|
+
#### Returns
|
|
3694
|
+
|
|
3695
|
+
`void`
|
|
3696
|
+
|
|
3697
|
+
***
|
|
3698
|
+
|
|
3699
|
+
<a id="fork"></a>
|
|
3700
|
+
|
|
3701
|
+
### fork()
|
|
3702
|
+
|
|
3703
|
+
```ts
|
|
3704
|
+
function fork(snapshot: SandboxSnapshot, rules?: string): Branch;
|
|
3705
|
+
```
|
|
3706
|
+
|
|
3707
|
+
Fork a new branch from a snapshot.
|
|
3708
|
+
|
|
3709
|
+
Seeds a fresh sandbox with `rules` and the snapshot's Firestore docs
|
|
3710
|
+
(the same `seed({ rules, documents })` path replay uses to stand up a
|
|
3711
|
+
clean environment). The branch is fully isolated: writes on it never
|
|
3712
|
+
touch the source sandbox.
|
|
3713
|
+
|
|
3714
|
+
#### Parameters
|
|
3715
|
+
|
|
3716
|
+
| Parameter | Type | Description |
|
|
3717
|
+
| :------ | :------ | :------ |
|
|
3718
|
+
| `snapshot` | [`SandboxSnapshot`](#sandboxsnapshot-2) | Baseline state — typically `liveSandbox.snapshot()`. |
|
|
3719
|
+
| `rules?` | `string` | Rules source for the branch. Pass the live rules to reproduce production behaviour, or an *edited* ruleset to test a rules change in isolation (Studio F4). |
|
|
3720
|
+
|
|
3721
|
+
#### Returns
|
|
3722
|
+
|
|
3723
|
+
[`Branch`](#branch)
|
|
3724
|
+
|
|
3725
|
+
***
|
|
3726
|
+
|
|
3727
|
+
<a id="initializesandbox"></a>
|
|
3728
|
+
|
|
3729
|
+
### initializeSandbox()
|
|
3730
|
+
|
|
3731
|
+
```ts
|
|
3732
|
+
function initializeSandbox(_config?: SandboxConfig): LocalSandbox;
|
|
3733
|
+
```
|
|
3734
|
+
|
|
3735
|
+
Create a sandbox.
|
|
3736
|
+
|
|
3737
|
+
Identity is **not** part of init — call `sandbox.withAuth(...)` to
|
|
3738
|
+
derive a [SandboxContext](#sandboxcontext) for service operations. Service-
|
|
3739
|
+
specific configuration (rules, seed data) happens through service-specific
|
|
3740
|
+
sandbox controls — for example, `setRules(sandbox, source)` from
|
|
3741
|
+
`pyric/sandbox/firestore`.
|
|
3742
|
+
|
|
3743
|
+
#### Parameters
|
|
3744
|
+
|
|
3745
|
+
| Parameter | Type |
|
|
3746
|
+
| :------ | :------ |
|
|
3747
|
+
| `_config?` | [`SandboxConfig`](#sandboxconfig) |
|
|
3748
|
+
|
|
3749
|
+
#### Returns
|
|
3750
|
+
|
|
3751
|
+
[`LocalSandbox`](#localsandbox)
|
|
3752
|
+
|
|
3753
|
+
#### Example
|
|
3754
|
+
|
|
3755
|
+
```ts
|
|
3756
|
+
import { initializeSandbox } from 'pyric/sandbox';
|
|
3757
|
+
import { getFirestore } from 'pyric-admin/firestore';
|
|
3758
|
+
|
|
3759
|
+
const sandbox = initializeSandbox();
|
|
3760
|
+
const dbAlice = getFirestore(sandbox.withAuth({ uid: 'alice' }));
|
|
3761
|
+
const dbAnon = getFirestore(sandbox.withAuth(null));
|
|
3762
|
+
```
|
|
3763
|
+
|
|
3764
|
+
***
|
|
3765
|
+
|
|
3766
|
+
<a id="isoperationevent"></a>
|
|
3767
|
+
|
|
3768
|
+
### isOperationEvent()
|
|
3769
|
+
|
|
3770
|
+
```ts
|
|
3771
|
+
function isOperationEvent(event: SandboxEvent): event is OperationEvent;
|
|
3772
|
+
```
|
|
3773
|
+
|
|
3774
|
+
#### Parameters
|
|
3775
|
+
|
|
3776
|
+
| Parameter | Type |
|
|
3777
|
+
| :------ | :------ |
|
|
3778
|
+
| `event` | [`SandboxEvent`](#sandboxevent) |
|
|
3779
|
+
|
|
3780
|
+
#### Returns
|
|
3781
|
+
|
|
3782
|
+
`event is OperationEvent`
|
|
3783
|
+
|
|
3784
|
+
***
|
|
3785
|
+
|
|
3786
|
+
<a id="isremotesandbox"></a>
|
|
3787
|
+
|
|
3788
|
+
### isRemoteSandbox()
|
|
3789
|
+
|
|
3790
|
+
```ts
|
|
3791
|
+
function isRemoteSandbox(sandbox: Sandbox): sandbox is RemoteSandbox;
|
|
3792
|
+
```
|
|
3793
|
+
|
|
3794
|
+
Is this sandbox a remote handle? Backend dispatch guard for consumers
|
|
3795
|
+
(e.g. `pyric-admin`'s RTDB/Auth sandbox backends) that must route a
|
|
3796
|
+
remote sandbox's operations through [RemoteSandbox.channel](#channel) rather
|
|
3797
|
+
than into process-local state.
|
|
3798
|
+
|
|
3799
|
+
#### Parameters
|
|
3800
|
+
|
|
3801
|
+
| Parameter | Type |
|
|
3802
|
+
| :------ | :------ |
|
|
3803
|
+
| `sandbox` | [`Sandbox`](#sandbox-3) |
|
|
3804
|
+
|
|
3805
|
+
#### Returns
|
|
3806
|
+
|
|
3807
|
+
`sandbox is RemoteSandbox`
|
|
3808
|
+
|
|
3809
|
+
***
|
|
3810
|
+
|
|
3811
|
+
<a id="operationcontextfor"></a>
|
|
3812
|
+
|
|
3813
|
+
### operationContextFor()
|
|
3814
|
+
|
|
3815
|
+
```ts
|
|
3816
|
+
function operationContextFor(event: Pick<EventProvenance, "operationContext" | "actor" | "authLens" | "planId">): OperationContext;
|
|
3817
|
+
```
|
|
3818
|
+
|
|
3819
|
+
The canonical context on a recorded event. Old/pre-context events are
|
|
3820
|
+
explicitly unattributed rather than silently asserted to be app traffic.
|
|
3821
|
+
|
|
3822
|
+
#### Parameters
|
|
3823
|
+
|
|
3824
|
+
| Parameter | Type |
|
|
3825
|
+
| :------ | :------ |
|
|
3826
|
+
| `event` | `Pick`\<[`EventProvenance`](#eventprovenance), `"operationContext"` \| `"actor"` \| `"authLens"` \| `"planId"`\> |
|
|
3827
|
+
|
|
3828
|
+
#### Returns
|
|
3829
|
+
|
|
3830
|
+
[`OperationContext`](#operationcontext-2)
|
|
3831
|
+
|
|
3832
|
+
***
|
|
3833
|
+
|
|
3834
|
+
<a id="parsebundle"></a>
|
|
3835
|
+
|
|
3836
|
+
### parseBundle()
|
|
3837
|
+
|
|
3838
|
+
```ts
|
|
3839
|
+
function parseBundle(blob: string): Map<string, unknown>;
|
|
3840
|
+
```
|
|
3841
|
+
|
|
3842
|
+
Parse a v3 bundle blob back into records. Returns an empty map for an
|
|
3843
|
+
unrecognized blob (e.g. a legacy v2 single-blob snapshot); migrate-on-open (a
|
|
3844
|
+
later commit) handles converting a v2 blob to v3 records.
|
|
3845
|
+
|
|
3846
|
+
#### Parameters
|
|
3847
|
+
|
|
3848
|
+
| Parameter | Type |
|
|
3849
|
+
| :------ | :------ |
|
|
3850
|
+
| `blob` | `string` |
|
|
3851
|
+
|
|
3852
|
+
#### Returns
|
|
3853
|
+
|
|
3854
|
+
`Map`\<`string`, `unknown`\>
|
|
3855
|
+
|
|
3856
|
+
***
|
|
3857
|
+
|
|
3858
|
+
<a id="promote"></a>
|
|
3859
|
+
|
|
3860
|
+
### promote()
|
|
3861
|
+
|
|
3862
|
+
```ts
|
|
3863
|
+
function promote(branch: Branch, target: LocalSandbox): void;
|
|
3864
|
+
```
|
|
3865
|
+
|
|
3866
|
+
Promote a branch's mutations onto a target (live) sandbox.
|
|
3867
|
+
|
|
3868
|
+
"Honest promote": it computes the doc-level delta between the branch's
|
|
3869
|
+
BASE snapshot and its current state — i.e. exactly what the applied
|
|
3870
|
+
events changed — and lands only those mutations on the target through
|
|
3871
|
+
the admin plane:
|
|
3872
|
+
- docs added/changed on the branch → `admin.setDocument`
|
|
3873
|
+
- docs deleted on the branch → `admin.deleteDocument`
|
|
3874
|
+
- docs the branch never touched → left untouched on the target
|
|
3875
|
+
|
|
3876
|
+
Admin-plane application fires the target's listeners (matching how
|
|
3877
|
+
persistence restore lands docs), so live UI/handles see the promotion.
|
|
3878
|
+
|
|
3879
|
+
The branch is marked discarded afterward — a promoted branch is spent.
|
|
3880
|
+
|
|
3881
|
+
#### Parameters
|
|
3882
|
+
|
|
3883
|
+
| Parameter | Type | Description |
|
|
3884
|
+
| :------ | :------ | :------ |
|
|
3885
|
+
| `branch` | [`Branch`](#branch) | The experiment to land. |
|
|
3886
|
+
| `target` | [`LocalSandbox`](#localsandbox) | The live sandbox to land it on. |
|
|
3887
|
+
|
|
3888
|
+
#### Returns
|
|
3889
|
+
|
|
3890
|
+
`void`
|
|
3891
|
+
|
|
3892
|
+
***
|
|
3893
|
+
|
|
3894
|
+
<a id="recordbackendoverblob"></a>
|
|
3895
|
+
|
|
3896
|
+
### recordBackendOverBlob()
|
|
3897
|
+
|
|
3898
|
+
```ts
|
|
3899
|
+
function recordBackendOverBlob(io: {
|
|
3900
|
+
clear: Promise<void>;
|
|
3901
|
+
read: Promise<string>;
|
|
3902
|
+
write: Promise<void>;
|
|
3903
|
+
}): PersistenceBackend;
|
|
3904
|
+
```
|
|
3905
|
+
|
|
3906
|
+
A record-shaped backend over a single-blob store (read/write/clear ONE blob).
|
|
3907
|
+
The whole record set is bundled into that blob, for serve's committable export
|
|
3908
|
+
file / HTTP state endpoint and any single-key store. It loses chunking's scale
|
|
3909
|
+
benefit by design (the blob IS the single artifact). The blob is loaded once and
|
|
3910
|
+
cached, so a restore (list + per-record get) costs one read, not one per record.
|
|
3911
|
+
|
|
3912
|
+
#### Parameters
|
|
3913
|
+
|
|
3914
|
+
| Parameter | Type |
|
|
3915
|
+
| :------ | :------ |
|
|
3916
|
+
| `io` | \{ `clear`: `Promise`\<`void`\>; `read`: `Promise`\<`string`\>; `write`: `Promise`\<`void`\>; \} |
|
|
3917
|
+
| `io.clear` |
|
|
3918
|
+
| `io.read` |
|
|
3919
|
+
| `io.write` |
|
|
3920
|
+
|
|
3921
|
+
#### Returns
|
|
3922
|
+
|
|
3923
|
+
[`PersistenceBackend`](#persistencebackend)
|
|
3924
|
+
|
|
3925
|
+
***
|
|
3926
|
+
|
|
3927
|
+
<a id="rehydratedocvalue"></a>
|
|
3928
|
+
|
|
3929
|
+
### rehydrateDocValue()
|
|
3930
|
+
|
|
3931
|
+
```ts
|
|
3932
|
+
function rehydrateDocValue(value: unknown): unknown;
|
|
3933
|
+
```
|
|
3934
|
+
|
|
3935
|
+
Walk a parsed JSON tree and re-wrap any marker shape back into its real
|
|
3936
|
+
wrapper-class instance. Visits arrays and plain objects recursively. Plain
|
|
3937
|
+
values (and plain objects without a recognized discriminator) pass through.
|
|
3938
|
+
|
|
3939
|
+
This is the canonical rehydrate used by BOTH the sandbox persistence
|
|
3940
|
+
serializer and the SharedWorker wire protocol, so the IDB format and the
|
|
3941
|
+
MessagePort wire format are guaranteed identical.
|
|
3942
|
+
|
|
3943
|
+
#### Parameters
|
|
3944
|
+
|
|
3945
|
+
| Parameter | Type |
|
|
3946
|
+
| :------ | :------ |
|
|
3947
|
+
| `value` | `unknown` |
|
|
3948
|
+
|
|
3949
|
+
#### Returns
|
|
3950
|
+
|
|
3951
|
+
`unknown`
|
|
3952
|
+
|
|
3953
|
+
***
|
|
3954
|
+
|
|
3955
|
+
<a id="replay-1"></a>
|
|
3956
|
+
|
|
3957
|
+
### replay()
|
|
3958
|
+
|
|
3959
|
+
```ts
|
|
3960
|
+
function replay(
|
|
3961
|
+
events: readonly SandboxEvent[],
|
|
3962
|
+
rules: string,
|
|
3963
|
+
options?: ReplayOptions,
|
|
3964
|
+
originalState?: Record<string, DocData>): ReplayResult;
|
|
3965
|
+
```
|
|
3966
|
+
|
|
3967
|
+
Replay a captured SandboxEvent stream on a fresh sandbox.
|
|
3968
|
+
|
|
3969
|
+
The `originalState` snapshot is optional — when provided, the engine
|
|
3970
|
+
diffs the replayed sandbox's final state against it and returns
|
|
3971
|
+
classified divergences. When omitted, divergences is `[]` (you still
|
|
3972
|
+
get the replayed sandbox; you can inspect its state manually).
|
|
3973
|
+
|
|
3974
|
+
#### Parameters
|
|
3975
|
+
|
|
3976
|
+
| Parameter | Type |
|
|
3977
|
+
| :------ | :------ |
|
|
3978
|
+
| `events` | readonly [`SandboxEvent`](#sandboxevent)[] |
|
|
3979
|
+
| `rules` | `string` |
|
|
3980
|
+
| `options?` | [`ReplayOptions`](#replayoptions) |
|
|
3981
|
+
| `originalState?` | `Record`\<`string`, `DocData`\> |
|
|
3982
|
+
|
|
3983
|
+
#### Returns
|
|
3984
|
+
|
|
3985
|
+
[`ReplayResult`](#replayresult)
|
|
3986
|
+
|
|
3987
|
+
***
|
|
3988
|
+
|
|
3989
|
+
<a id="rulesdispositionfor"></a>
|
|
3990
|
+
|
|
3991
|
+
### rulesDispositionFor()
|
|
3992
|
+
|
|
3993
|
+
```ts
|
|
3994
|
+
function rulesDispositionFor(event: OperationEvent): RulesDisposition;
|
|
3995
|
+
```
|
|
3996
|
+
|
|
3997
|
+
Normalize the legacy per-service markers exactly once, at the sandbox
|
|
3998
|
+
stream seam. Consumers must not inspect `detail.admin`, `origin`, or the
|
|
3999
|
+
presence of a trace themselves.
|
|
4000
|
+
|
|
4001
|
+
#### Parameters
|
|
4002
|
+
|
|
4003
|
+
| Parameter | Type |
|
|
4004
|
+
| :------ | :------ |
|
|
4005
|
+
| `event` | `OperationEvent` |
|
|
4006
|
+
|
|
4007
|
+
#### Returns
|
|
4008
|
+
|
|
4009
|
+
[`RulesDisposition`](#rulesdisposition-2)
|
|
4010
|
+
|
|
4011
|
+
***
|
|
4012
|
+
|
|
4013
|
+
<a id="serializetobuckets"></a>
|
|
4014
|
+
|
|
4015
|
+
### serializeToBuckets()
|
|
4016
|
+
|
|
4017
|
+
```ts
|
|
4018
|
+
function serializeToBuckets(
|
|
4019
|
+
firestore: Record<string, Record<string, unknown>>,
|
|
4020
|
+
services: Record<string, unknown>,
|
|
4021
|
+
savedAt: number): Map<string, BucketRecord | MetaRecord>;
|
|
4022
|
+
```
|
|
4023
|
+
|
|
4024
|
+
Partition a firestore doc map into v3 records: one bucket record per occupied
|
|
4025
|
+
bucket plus the `meta` record. The returned map's keys are record ids; values
|
|
4026
|
+
are structured-clone-safe.
|
|
4027
|
+
|
|
4028
|
+
#### Parameters
|
|
4029
|
+
|
|
4030
|
+
| Parameter | Type |
|
|
4031
|
+
| :------ | :------ |
|
|
4032
|
+
| `firestore` | `Record`\<`string`, `Record`\<`string`, `unknown`\>\> |
|
|
4033
|
+
| `services` | `Record`\<`string`, `unknown`\> |
|
|
4034
|
+
| `savedAt` | `number` |
|
|
4035
|
+
|
|
4036
|
+
#### Returns
|
|
4037
|
+
|
|
4038
|
+
`Map`\<`string`, `BucketRecord` \| `MetaRecord`\>
|
|
4039
|
+
|
|
4040
|
+
***
|
|
4041
|
+
|
|
4042
|
+
<a id="tooperationrecord"></a>
|
|
4043
|
+
|
|
4044
|
+
### toOperationRecord()
|
|
4045
|
+
|
|
4046
|
+
```ts
|
|
4047
|
+
function toOperationRecord(event: SandboxEvent): OperationRecord;
|
|
4048
|
+
```
|
|
4049
|
+
|
|
4050
|
+
Project either traffic event family into the canonical record.
|
|
4051
|
+
|
|
4052
|
+
#### Parameters
|
|
4053
|
+
|
|
4054
|
+
| Parameter | Type |
|
|
4055
|
+
| :------ | :------ |
|
|
4056
|
+
| `event` | [`SandboxEvent`](#sandboxevent) |
|
|
4057
|
+
|
|
4058
|
+
#### Returns
|
|
4059
|
+
|
|
4060
|
+
[`OperationRecord`](#operationrecord)
|