pyric-tools 0.1.0-alpha.7
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/README.md +125 -0
- package/dist/auth/domains/handler.d.ts +6 -0
- package/dist/auth/domains/handler.d.ts.map +1 -0
- package/dist/auth/domains/handler.js +75 -0
- package/dist/auth/domains/handler.js.map +1 -0
- package/dist/auth/domains/spec.d.ts +25 -0
- package/dist/auth/domains/spec.d.ts.map +1 -0
- package/dist/auth/domains/spec.js +6 -0
- package/dist/auth/domains/spec.js.map +1 -0
- package/dist/auth/index.d.ts +34 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +30 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/mapper.d.ts +5 -0
- package/dist/auth/mapper.d.ts.map +1 -0
- package/dist/auth/mapper.js +49 -0
- package/dist/auth/mapper.js.map +1 -0
- package/dist/auth/provider/handler.d.ts +9 -0
- package/dist/auth/provider/handler.d.ts.map +1 -0
- package/dist/auth/provider/handler.js +128 -0
- package/dist/auth/provider/handler.js.map +1 -0
- package/dist/auth/provider/spec.d.ts +29 -0
- package/dist/auth/provider/spec.d.ts.map +1 -0
- package/dist/auth/provider/spec.js +12 -0
- package/dist/auth/provider/spec.js.map +1 -0
- package/dist/auth/resolver.d.ts +16 -0
- package/dist/auth/resolver.d.ts.map +1 -0
- package/dist/auth/resolver.js +32 -0
- package/dist/auth/resolver.js.map +1 -0
- package/dist/auth/tools.d.ts +23 -0
- package/dist/auth/tools.d.ts.map +1 -0
- package/dist/auth/tools.js +71 -0
- package/dist/auth/tools.js.map +1 -0
- package/dist/auth/types.d.ts +37 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +17 -0
- package/dist/auth/types.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 +430 -0
- package/dist/bridge/client/bridge.js.map +1 -0
- package/dist/bridge/client/dispatch.d.ts +49 -0
- package/dist/bridge/client/dispatch.d.ts.map +1 -0
- package/dist/bridge/client/dispatch.js +103 -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 +228 -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 +165 -0
- package/dist/bridge/server/bridge.d.ts.map +1 -0
- package/dist/bridge/server/bridge.js +410 -0
- package/dist/bridge/server/bridge.js.map +1 -0
- package/dist/bridge/server/confirm-policy.d.ts +56 -0
- package/dist/bridge/server/confirm-policy.d.ts.map +1 -0
- package/dist/bridge/server/confirm-policy.js +107 -0
- package/dist/bridge/server/confirm-policy.js.map +1 -0
- package/dist/bridge/server/confirm-prompt.d.ts +73 -0
- package/dist/bridge/server/confirm-prompt.d.ts.map +1 -0
- package/dist/bridge/server/confirm-prompt.js +199 -0
- package/dist/bridge/server/confirm-prompt.js.map +1 -0
- package/dist/bridge/server/confirm.d.ts +92 -0
- package/dist/bridge/server/confirm.d.ts.map +1 -0
- package/dist/bridge/server/confirm.js +210 -0
- package/dist/bridge/server/confirm.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 +132 -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 +42 -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.d.ts +33 -0
- package/dist/bridge/server/mcp.d.ts.map +1 -0
- package/dist/bridge/server/mcp.js +159 -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 +102 -0
- package/dist/bridge/server/standalone.d.ts.map +1 -0
- package/dist/bridge/server/standalone.js +352 -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 +79 -0
- package/dist/bridge/server/tool-metadata.js.map +1 -0
- package/dist/bridge/server.d.ts +25 -0
- package/dist/bridge/server.d.ts.map +1 -0
- package/dist/bridge/server.js +23 -0
- package/dist/bridge/server.js.map +1 -0
- package/dist/cli/auth.d.ts +37 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +119 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/database-rules.d.ts +25 -0
- package/dist/cli/database-rules.d.ts.map +1 -0
- package/dist/cli/database-rules.js +215 -0
- package/dist/cli/database-rules.js.map +1 -0
- package/dist/cli/deploy-progress.d.ts +35 -0
- package/dist/cli/deploy-progress.d.ts.map +1 -0
- package/dist/cli/deploy-progress.js +69 -0
- package/dist/cli/deploy-progress.js.map +1 -0
- package/dist/cli/deploy.d.ts +81 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/deploy.js +413 -0
- package/dist/cli/deploy.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 +227 -0
- package/dist/cli/dev-runner.js.map +1 -0
- package/dist/cli/discover.d.ts +47 -0
- package/dist/cli/discover.d.ts.map +1 -0
- package/dist/cli/discover.js +77 -0
- package/dist/cli/discover.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/index.d.ts +55 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +500 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-templates.d.ts +37 -0
- package/dist/cli/init-templates.d.ts.map +1 -0
- package/dist/cli/init-templates.js +638 -0
- package/dist/cli/init-templates.js.map +1 -0
- package/dist/cli/init.d.ts +124 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +423 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/login.d.ts +18 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +59 -0
- package/dist/cli/login.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 +126 -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 +69 -0
- package/dist/cli/parse-args.js.map +1 -0
- package/dist/cli/rules.d.ts +40 -0
- package/dist/cli/rules.d.ts.map +1 -0
- package/dist/cli/rules.js +162 -0
- package/dist/cli/rules.js.map +1 -0
- package/dist/cli/scope.d.ts +38 -0
- package/dist/cli/scope.d.ts.map +1 -0
- package/dist/cli/scope.js +63 -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 +685 -0
- package/dist/cli/serve.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/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/credentials/core/authorize.d.ts +22 -0
- package/dist/credentials/core/authorize.d.ts.map +1 -0
- package/dist/credentials/core/authorize.js +39 -0
- package/dist/credentials/core/authorize.js.map +1 -0
- package/dist/credentials/core/client.d.ts +14 -0
- package/dist/credentials/core/client.d.ts.map +1 -0
- package/dist/credentials/core/client.js +13 -0
- package/dist/credentials/core/client.js.map +1 -0
- package/dist/credentials/core/exchange.d.ts +24 -0
- package/dist/credentials/core/exchange.d.ts.map +1 -0
- package/dist/credentials/core/exchange.js +46 -0
- package/dist/credentials/core/exchange.js.map +1 -0
- package/dist/credentials/core/flow.d.ts +15 -0
- package/dist/credentials/core/flow.d.ts.map +1 -0
- package/dist/credentials/core/flow.js +40 -0
- package/dist/credentials/core/flow.js.map +1 -0
- package/dist/credentials/core/from-user-credential.d.ts +3 -0
- package/dist/credentials/core/from-user-credential.d.ts.map +1 -0
- package/dist/credentials/core/from-user-credential.js +19 -0
- package/dist/credentials/core/from-user-credential.js.map +1 -0
- package/dist/credentials/core/scopes.d.ts +26 -0
- package/dist/credentials/core/scopes.d.ts.map +1 -0
- package/dist/credentials/core/scopes.js +32 -0
- package/dist/credentials/core/scopes.js.map +1 -0
- package/dist/credentials/core/types.d.ts +72 -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/index.d.ts +14 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +13 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/node/ensure-scope.d.ts +29 -0
- package/dist/credentials/node/ensure-scope.d.ts.map +1 -0
- package/dist/credentials/node/ensure-scope.js +42 -0
- package/dist/credentials/node/ensure-scope.js.map +1 -0
- package/dist/credentials/node/file-store.d.ts +4 -0
- package/dist/credentials/node/file-store.d.ts.map +1 -0
- package/dist/credentials/node/file-store.js +52 -0
- package/dist/credentials/node/file-store.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 +60 -0
- package/dist/credentials/node/from-adc.js.map +1 -0
- package/dist/credentials/node/index.d.ts +10 -0
- package/dist/credentials/node/index.d.ts.map +1 -0
- package/dist/credentials/node/index.js +10 -0
- package/dist/credentials/node/index.js.map +1 -0
- package/dist/credentials/node/loopback-authorizer.d.ts +12 -0
- package/dist/credentials/node/loopback-authorizer.d.ts.map +1 -0
- package/dist/credentials/node/loopback-authorizer.js +61 -0
- package/dist/credentials/node/loopback-authorizer.js.map +1 -0
- package/dist/credentials/node/resolve-local-token.d.ts +20 -0
- package/dist/credentials/node/resolve-local-token.d.ts.map +1 -0
- package/dist/credentials/node/resolve-local-token.js +97 -0
- package/dist/credentials/node/resolve-local-token.js.map +1 -0
- package/dist/credentials/server/bff.d.ts +44 -0
- package/dist/credentials/server/bff.d.ts.map +1 -0
- package/dist/credentials/server/bff.js +43 -0
- package/dist/credentials/server/bff.js.map +1 -0
- package/dist/deploy/api-enablement.d.ts +45 -0
- package/dist/deploy/api-enablement.d.ts.map +1 -0
- package/dist/deploy/api-enablement.js +125 -0
- package/dist/deploy/api-enablement.js.map +1 -0
- package/dist/deploy/firestore/databases.d.ts +41 -0
- package/dist/deploy/firestore/databases.d.ts.map +1 -0
- package/dist/deploy/firestore/databases.js +63 -0
- package/dist/deploy/firestore/databases.js.map +1 -0
- package/dist/deploy/firestore/indexes.d.ts +120 -0
- package/dist/deploy/firestore/indexes.d.ts.map +1 -0
- package/dist/deploy/firestore/indexes.js +243 -0
- package/dist/deploy/firestore/indexes.js.map +1 -0
- package/dist/deploy/firestore/recipes.d.ts +21 -0
- package/dist/deploy/firestore/recipes.d.ts.map +1 -0
- package/dist/deploy/firestore/recipes.js +34 -0
- package/dist/deploy/firestore/recipes.js.map +1 -0
- package/dist/deploy/firestore/rules.d.ts +76 -0
- package/dist/deploy/firestore/rules.d.ts.map +1 -0
- package/dist/deploy/firestore/rules.js +180 -0
- package/dist/deploy/firestore/rules.js.map +1 -0
- package/dist/deploy/from-admin-app.d.ts +20 -0
- package/dist/deploy/from-admin-app.d.ts.map +1 -0
- package/dist/deploy/from-admin-app.js +18 -0
- package/dist/deploy/from-admin-app.js.map +1 -0
- package/dist/deploy/from-service-account.d.ts +29 -0
- package/dist/deploy/from-service-account.d.ts.map +1 -0
- package/dist/deploy/from-service-account.js +115 -0
- package/dist/deploy/from-service-account.js.map +1 -0
- package/dist/deploy/functions/bundle.d.ts +37 -0
- package/dist/deploy/functions/bundle.d.ts.map +1 -0
- package/dist/deploy/functions/bundle.js +181 -0
- package/dist/deploy/functions/bundle.js.map +1 -0
- package/dist/deploy/functions/core.d.ts +18 -0
- package/dist/deploy/functions/core.d.ts.map +1 -0
- package/dist/deploy/functions/core.js +211 -0
- package/dist/deploy/functions/core.js.map +1 -0
- package/dist/deploy/functions/iam.d.ts +17 -0
- package/dist/deploy/functions/iam.d.ts.map +1 -0
- package/dist/deploy/functions/iam.js +40 -0
- package/dist/deploy/functions/iam.js.map +1 -0
- package/dist/deploy/functions/operation.d.ts +39 -0
- package/dist/deploy/functions/operation.d.ts.map +1 -0
- package/dist/deploy/functions/operation.js +37 -0
- package/dist/deploy/functions/operation.js.map +1 -0
- package/dist/deploy/functions/spec.d.ts +69 -0
- package/dist/deploy/functions/spec.d.ts.map +1 -0
- package/dist/deploy/functions/spec.js +7 -0
- package/dist/deploy/functions/spec.js.map +1 -0
- package/dist/deploy/functions/types.d.ts +50 -0
- package/dist/deploy/functions/types.d.ts.map +1 -0
- package/dist/deploy/functions/types.js +3 -0
- package/dist/deploy/functions/types.js.map +1 -0
- package/dist/deploy/hosting/channels.d.ts +93 -0
- package/dist/deploy/hosting/channels.d.ts.map +1 -0
- package/dist/deploy/hosting/channels.js +134 -0
- package/dist/deploy/hosting/channels.js.map +1 -0
- package/dist/deploy/hosting/config.d.ts +28 -0
- package/dist/deploy/hosting/config.d.ts.map +1 -0
- package/dist/deploy/hosting/config.js +261 -0
- package/dist/deploy/hosting/config.js.map +1 -0
- package/dist/deploy/hosting/core.d.ts +38 -0
- package/dist/deploy/hosting/core.d.ts.map +1 -0
- package/dist/deploy/hosting/core.js +311 -0
- package/dist/deploy/hosting/core.js.map +1 -0
- package/dist/deploy/hosting/gzip.d.ts +11 -0
- package/dist/deploy/hosting/gzip.d.ts.map +1 -0
- package/dist/deploy/hosting/gzip.js +20 -0
- package/dist/deploy/hosting/gzip.js.map +1 -0
- package/dist/deploy/hosting/hash.d.ts +6 -0
- package/dist/deploy/hosting/hash.d.ts.map +1 -0
- package/dist/deploy/hosting/hash.js +17 -0
- package/dist/deploy/hosting/hash.js.map +1 -0
- package/dist/deploy/hosting/sites.d.ts +77 -0
- package/dist/deploy/hosting/sites.d.ts.map +1 -0
- package/dist/deploy/hosting/sites.js +73 -0
- package/dist/deploy/hosting/sites.js.map +1 -0
- package/dist/deploy/hosting/spec.d.ts +140 -0
- package/dist/deploy/hosting/spec.d.ts.map +1 -0
- package/dist/deploy/hosting/spec.js +8 -0
- package/dist/deploy/hosting/spec.js.map +1 -0
- package/dist/deploy/hosting/types.d.ts +71 -0
- package/dist/deploy/hosting/types.d.ts.map +1 -0
- package/dist/deploy/hosting/types.js +3 -0
- package/dist/deploy/hosting/types.js.map +1 -0
- package/dist/deploy/hosting/walk.d.ts +31 -0
- package/dist/deploy/hosting/walk.d.ts.map +1 -0
- package/dist/deploy/hosting/walk.js +167 -0
- package/dist/deploy/hosting/walk.js.map +1 -0
- package/dist/deploy/index.d.ts +51 -0
- package/dist/deploy/index.d.ts.map +1 -0
- package/dist/deploy/index.js +50 -0
- package/dist/deploy/index.js.map +1 -0
- package/dist/deploy/memoize-ttl.d.ts +58 -0
- package/dist/deploy/memoize-ttl.d.ts.map +1 -0
- package/dist/deploy/memoize-ttl.js +88 -0
- package/dist/deploy/memoize-ttl.js.map +1 -0
- package/dist/deploy/namespaces.d.ts +119 -0
- package/dist/deploy/namespaces.d.ts.map +1 -0
- package/dist/deploy/namespaces.js +192 -0
- package/dist/deploy/namespaces.js.map +1 -0
- package/dist/deploy/preflight.d.ts +119 -0
- package/dist/deploy/preflight.d.ts.map +1 -0
- package/dist/deploy/preflight.js +440 -0
- package/dist/deploy/preflight.js.map +1 -0
- package/dist/deploy/provider.d.ts +130 -0
- package/dist/deploy/provider.d.ts.map +1 -0
- package/dist/deploy/provider.js +21 -0
- package/dist/deploy/provider.js.map +1 -0
- package/dist/deploy/providers/database.d.ts +8 -0
- package/dist/deploy/providers/database.d.ts.map +1 -0
- package/dist/deploy/providers/database.js +37 -0
- package/dist/deploy/providers/database.js.map +1 -0
- package/dist/deploy/providers/firestore.d.ts +11 -0
- package/dist/deploy/providers/firestore.d.ts.map +1 -0
- package/dist/deploy/providers/firestore.js +43 -0
- package/dist/deploy/providers/firestore.js.map +1 -0
- package/dist/deploy/providers/functions.d.ts +9 -0
- package/dist/deploy/providers/functions.d.ts.map +1 -0
- package/dist/deploy/providers/functions.js +49 -0
- package/dist/deploy/providers/functions.js.map +1 -0
- package/dist/deploy/providers/hosting.d.ts +13 -0
- package/dist/deploy/providers/hosting.d.ts.map +1 -0
- package/dist/deploy/providers/hosting.js +149 -0
- package/dist/deploy/providers/hosting.js.map +1 -0
- package/dist/deploy/providers/storage.d.ts +9 -0
- package/dist/deploy/providers/storage.d.ts.map +1 -0
- package/dist/deploy/providers/storage.js +46 -0
- package/dist/deploy/providers/storage.js.map +1 -0
- package/dist/deploy/registry.d.ts +17 -0
- package/dist/deploy/registry.d.ts.map +1 -0
- package/dist/deploy/registry.js +15 -0
- package/dist/deploy/registry.js.map +1 -0
- package/dist/deploy/rtdb/rules.d.ts +21 -0
- package/dist/deploy/rtdb/rules.d.ts.map +1 -0
- package/dist/deploy/rtdb/rules.js +104 -0
- package/dist/deploy/rtdb/rules.js.map +1 -0
- package/dist/deploy/scope.d.ts +81 -0
- package/dist/deploy/scope.d.ts.map +1 -0
- package/dist/deploy/scope.js +44 -0
- package/dist/deploy/scope.js.map +1 -0
- package/dist/deploy/tools.d.ts +55 -0
- package/dist/deploy/tools.d.ts.map +1 -0
- package/dist/deploy/tools.js +329 -0
- package/dist/deploy/tools.js.map +1 -0
- package/dist/deploy/with-resolved-scope.d.ts +31 -0
- package/dist/deploy/with-resolved-scope.d.ts.map +1 -0
- package/dist/deploy/with-resolved-scope.js +73 -0
- package/dist/deploy/with-resolved-scope.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 +49 -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 +333 -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/findCollectionGroup.d.ts +80 -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/index.d.ts +15 -0
- package/dist/discover/index.d.ts.map +1 -0
- package/dist/discover/index.js +24 -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/rest-crawler-firestore.d.ts +51 -0
- package/dist/discover/rest-crawler-firestore.d.ts.map +1 -0
- package/dist/discover/rest-crawler-firestore.js +154 -0
- package/dist/discover/rest-crawler-firestore.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 +59 -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 +191 -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 +81 -0
- package/dist/discover/wire.d.ts.map +1 -0
- package/dist/discover/wire.js +308 -0
- package/dist/discover/wire.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/exempt.d.ts +11 -0
- package/dist/register/exempt.d.ts.map +1 -0
- package/dist/register/exempt.js +74 -0
- package/dist/register/exempt.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 +126 -0
- package/dist/register/index.js.map +1 -0
- package/dist/register/mapping.d.ts +17 -0
- package/dist/register/mapping.d.ts.map +1 -0
- package/dist/register/mapping.js +31 -0
- package/dist/register/mapping.js.map +1 -0
- package/dist/registry/admin-deps.d.ts +17 -0
- package/dist/registry/admin-deps.d.ts.map +1 -0
- package/dist/registry/admin-deps.js +68 -0
- package/dist/registry/admin-deps.js.map +1 -0
- package/dist/registry/compose.d.ts +68 -0
- package/dist/registry/compose.d.ts.map +1 -0
- package/dist/registry/compose.js +113 -0
- package/dist/registry/compose.js.map +1 -0
- package/dist/registry/index.d.ts +11 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +11 -0
- package/dist/registry/index.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 +719 -0
- package/dist/remote/index.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 +38 -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 +134 -0
- package/dist/serve/bundler.d.ts.map +1 -0
- package/dist/serve/bundler.js +452 -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_.BsN3bPok.css +1 -0
- package/dist/serve/docs-ui/docs/_rhythm/index.html +99 -0
- package/dist/serve/docs-ui/docs/_rhythm.md +101 -0
- package/dist/serve/docs-ui/docs/index.html +1 -0
- package/dist/serve/docs-ui/docs/index.json +9914 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore/index.html +38 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-error-translation/index.html +57 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-error-translation.md +87 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-per-call-delegate/index.html +58 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-per-call-delegate.md +86 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-why-mirror-admin-shape/index.html +59 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-why-mirror-admin-shape.md +82 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-run-a-transaction/index.html +53 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-run-a-transaction.md +70 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-seed-and-set-rules/index.html +62 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-seed-and-set-rules.md +86 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-translate-denials/index.html +80 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-translate-denials.md +92 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-use-onsnapshot/index.html +57 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-use-onsnapshot.md +78 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-write-a-batch/index.html +50 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-write-a-batch.md +67 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +53 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api.md +73 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-onsnapshot/index.html +68 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-onsnapshot.md +75 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-re-exported-types/index.html +76 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-re-exported-types.md +95 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-sandbox-firestore/index.html +40 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-sandbox-firestore.md +73 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-tutorials-01-first-admin-session/index.html +118 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-tutorials-01-first-admin-session.md +146 -0
- package/dist/serve/docs-ui/docs/pyric-admin-firestore.md +49 -0
- package/dist/serve/docs-ui/docs/pyric-auth/index.html +64 -0
- package/dist/serve/docs-ui/docs/pyric-auth-compat/index.html +874 -0
- package/dist/serve/docs-ui/docs/pyric-auth-compat.md +265 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +112 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api.md +129 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-feature-matrix/index.html +781 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-feature-matrix.md +198 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-sandbox-test-driver/index.html +80 -0
- package/dist/serve/docs-ui/docs/pyric-auth-reference-sandbox-test-driver.md +107 -0
- package/dist/serve/docs-ui/docs/pyric-auth.md +84 -0
- package/dist/serve/docs-ui/docs/pyric-database/index.html +36 -0
- package/dist/serve/docs-ui/docs/pyric-database-compat/index.html +2428 -0
- package/dist/serve/docs-ui/docs/pyric-database-compat.md +850 -0
- package/dist/serve/docs-ui/docs/pyric-database-explanation-rules-authoring-and-deploy-are-separate/index.html +23 -0
- package/dist/serve/docs-ui/docs/pyric-database-explanation-rules-authoring-and-deploy-are-separate.md +29 -0
- package/dist/serve/docs-ui/docs/pyric-database-reference-rules-tooling/index.html +263 -0
- package/dist/serve/docs-ui/docs/pyric-database-reference-rules-tooling.md +288 -0
- package/dist/serve/docs-ui/docs/pyric-database-tutorials-01-author-rtdb-rules-with-constraints/index.html +116 -0
- package/dist/serve/docs-ui/docs/pyric-database-tutorials-01-author-rtdb-rules-with-constraints.md +142 -0
- package/dist/serve/docs-ui/docs/pyric-database.md +18 -0
- package/dist/serve/docs-ui/docs/pyric-firestore/index.html +54 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-compat/index.html +1345 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-compat.md +366 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-explanation-rules-tooling-is-separate/index.html +79 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-explanation-rules-tooling-is-separate.md +62 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-explanation-target-symbol-opacity/index.html +59 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-explanation-target-symbol-opacity.md +85 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-explanation-two-backends-one-surface/index.html +61 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-explanation-two-backends-one-surface.md +87 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-build-queries/index.html +105 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-build-queries.md +138 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-migrate-from-firebase-firestore/index.html +65 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-migrate-from-firebase-firestore.md +93 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-pick-a-backend/index.html +112 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-pick-a-backend.md +100 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-run-a-transaction/index.html +42 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-run-a-transaction.md +55 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-onsnapshot/index.html +61 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-onsnapshot.md +83 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-sandbox-ops/index.html +52 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-sandbox-ops.md +73 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +132 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api.md +160 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-feature-matrix/index.html +793 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-feature-matrix.md +239 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-getfirestore/index.html +55 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-getfirestore.md +76 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-query-constraints/index.html +78 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-query-constraints.md +115 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-sandbox-ops/index.html +53 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-sandbox-ops.md +76 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-tool-factories/index.html +51 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-tool-factories.md +65 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-01-write-a-sandbox-demo/index.html +127 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-01-write-a-sandbox-demo.md +157 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-02-swap-to-prod-backend/index.html +85 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-02-swap-to-prod-backend.md +122 -0
- package/dist/serve/docs-ui/docs/pyric-firestore.md +69 -0
- package/dist/serve/docs-ui/docs/pyric-rules/index.html +93 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-agent-failure-modes/index.html +50 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-agent-failure-modes.md +74 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-lint-vs-validate-vs-simulate-vs-test/index.html +79 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-lint-vs-validate-vs-simulate-vs-test.md +69 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-runtime-budget-and-shared-gates/index.html +67 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-runtime-budget-and-shared-gates.md +71 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-sentinel-expression-engine/index.html +49 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-sentinel-expression-engine.md +70 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-simulator-vs-rules-test-api/index.html +96 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-simulator-vs-rules-test-api.md +76 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-the-2-plus-modules-extension/index.html +58 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-the-2-plus-modules-extension.md +75 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-value-wrappers-design/index.html +62 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-value-wrappers-design.md +80 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-why-this-package-exists/index.html +34 -0
- package/dist/serve/docs-ui/docs/pyric-rules-explanation-why-this-package-exists.md +43 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-compare-rulesets-for-weakening/index.html +54 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-compare-rulesets-for-weakening.md +69 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-inspect-rules-via-the-ast/index.html +89 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-inspect-rules-via-the-ast.md +113 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-lint-rules-source/index.html +50 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-lint-rules-source.md +73 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-pin-request-time/index.html +47 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-pin-request-time.md +58 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-register-tools-with-an-agent/index.html +61 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-register-tools-with-an-agent.md +78 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-resolve-module-imports/index.html +92 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-resolve-module-imports.md +93 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-simulate-rules-locally/index.html +89 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-simulate-rules-locally.md +117 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-test-rules-against-firebase/index.html +70 -0
- package/dist/serve/docs-ui/docs/pyric-rules-how-to-test-rules-against-firebase.md +93 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +129 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api.md +176 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-ast/index.html +85 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-ast.md +105 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-errors/index.html +63 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-errors.md +89 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-lint-rules/index.html +148 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-lint-rules.md +159 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-simulator-context/index.html +66 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-simulator-context.md +98 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-stdlib-modules/index.html +239 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-stdlib-modules.md +135 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-test-case-schema/index.html +93 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-test-case-schema.md +109 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-validator-findings/index.html +86 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-validator-findings.md +113 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-value-wrappers/index.html +101 -0
- package/dist/serve/docs-ui/docs/pyric-rules-reference-value-wrappers.md +134 -0
- package/dist/serve/docs-ui/docs/pyric-rules-tutorials-01-lint-your-first-rules-file/index.html +107 -0
- package/dist/serve/docs-ui/docs/pyric-rules-tutorials-01-lint-your-first-rules-file.md +142 -0
- package/dist/serve/docs-ui/docs/pyric-rules-tutorials-02-write-a-test-suite-for-your-rules/index.html +151 -0
- package/dist/serve/docs-ui/docs/pyric-rules-tutorials-02-write-a-test-suite-for-your-rules.md +194 -0
- package/dist/serve/docs-ui/docs/pyric-rules.md +88 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox/index.html +67 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-every-op-is-a-request/index.html +141 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-every-op-is-a-request.md +157 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-identity-is-a-context/index.html +66 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-identity-is-a-context.md +98 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-internal-adapter-protocol/index.html +71 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-internal-adapter-protocol.md +105 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-listener-re-evaluation/index.html +56 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-listener-re-evaluation.md +80 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-local-backend-vs-firestore-offline/index.html +64 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-local-backend-vs-firestore-offline.md +56 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-adapters-are-siblings/index.html +88 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-adapters-are-siblings.md +87 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-this-package-exists/index.html +69 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-this-package-exists.md +57 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-multiple-isolated-sandboxes/index.html +63 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-multiple-isolated-sandboxes.md +83 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-observe-events/index.html +159 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-observe-events.md +154 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-pick-an-adapter/index.html +79 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-pick-an-adapter.md +69 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-replay-events/index.html +89 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-replay-events.md +87 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-reset-between-tests/index.html +68 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-reset-between-tests.md +86 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-seed-data-and-rules/index.html +91 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-seed-data-and-rules.md +112 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-switch-users/index.html +50 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-switch-users.md +70 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-use-admin-reads/index.html +54 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-use-admin-reads.md +73 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +77 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +117 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-divergences/index.html +125 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-divergences.md +137 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-error-codes/index.html +110 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-error-codes.md +114 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-internal-protocol/index.html +72 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-internal-protocol.md +99 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-and-context/index.html +90 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-and-context.md +128 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-event/index.html +146 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-event.md +188 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-snapshot-and-admin/index.html +53 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-snapshot-and-admin.md +77 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-01-your-first-sandbox-session/index.html +115 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-01-your-first-sandbox-session.md +154 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-02-use-the-sandbox-in-a-test-harness/index.html +191 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-02-use-the-sandbox-in-a-test-harness.md +222 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-03-build-a-traffic-monitor/index.html +163 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-03-build-a-traffic-monitor.md +192 -0
- package/dist/serve/docs-ui/docs/pyric-sandbox.md +63 -0
- package/dist/serve/docs-ui/docs/pyric-storage/index.html +65 -0
- package/dist/serve/docs-ui/docs/pyric-storage-compat/index.html +1076 -0
- package/dist/serve/docs-ui/docs/pyric-storage-compat.md +337 -0
- package/dist/serve/docs-ui/docs/pyric-storage-explanation-implementation-scope/index.html +63 -0
- package/dist/serve/docs-ui/docs/pyric-storage-explanation-implementation-scope.md +104 -0
- package/dist/serve/docs-ui/docs/pyric-storage-explanation-session-archive-use-case/index.html +58 -0
- package/dist/serve/docs-ui/docs/pyric-storage-explanation-session-archive-use-case.md +80 -0
- package/dist/serve/docs-ui/docs/pyric-storage-explanation-why-indexeddb/index.html +50 -0
- package/dist/serve/docs-ui/docs/pyric-storage-explanation-why-indexeddb.md +82 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-enforce-rules/index.html +104 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-enforce-rules.md +111 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-list-and-delete/index.html +53 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-list-and-delete.md +76 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-round-trip-metadata/index.html +77 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-round-trip-metadata.md +100 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-switch-backends/index.html +65 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-switch-backends.md +82 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-test-rule-expressions/index.html +70 -0
- package/dist/serve/docs-ui/docs/pyric-storage-how-to-test-rule-expressions.md +82 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +92 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api.md +152 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-error-codes/index.html +46 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-error-codes.md +66 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-rules-subset/index.html +73 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-rules-subset.md +90 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-storage-options/index.html +45 -0
- package/dist/serve/docs-ui/docs/pyric-storage-reference-storage-options.md +65 -0
- package/dist/serve/docs-ui/docs/pyric-storage-tutorials-01-upload-and-download/index.html +114 -0
- package/dist/serve/docs-ui/docs/pyric-storage-tutorials-01-upload-and-download.md +141 -0
- package/dist/serve/docs-ui/docs/pyric-storage.md +63 -0
- package/dist/serve/docs-ui/docs/pyric-tools/index.html +45 -0
- package/dist/serve/docs-ui/docs/pyric-tools-bridge/index.html +71 -0
- package/dist/serve/docs-ui/docs/pyric-tools-bridge.md +90 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy/index.html +59 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-primitives-vs-orchestrators/index.html +63 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-primitives-vs-orchestrators.md +83 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-token-caching/index.html +47 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-token-caching.md +70 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-no-firebase-cli/index.html +36 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-no-firebase-cli.md +50 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-this-package-exists/index.html +47 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-this-package-exists.md +60 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-firebase-auth/index.html +55 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-firebase-auth.md +68 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-service-account/index.html +35 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-service-account.md +52 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-bundle-and-deploy-a-function/index.html +81 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-bundle-and-deploy-a-function.md +98 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-indexes/index.html +71 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-indexes.md +91 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-rules/index.html +100 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-rules.md +105 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-hosting-rewrites/index.html +86 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-hosting-rewrites.md +92 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-realtime-database-rules/index.html +132 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-realtime-database-rules.md +165 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-to-a-preview-channel/index.html +46 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-to-a-preview-channel.md +62 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-handle-errors-and-outcomes/index.html +75 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-handle-errors-and-outcomes.md +94 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-provision-a-firestore-database/index.html +48 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-provision-a-firestore-database.md +65 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-register-tools-with-an-agent/index.html +65 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-register-tools-with-an-agent.md +82 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-api/index.html +155 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-api.md +190 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-cli-agent-io/index.html +84 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-cli-agent-io.md +66 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-error-codes/index.html +71 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-error-codes.md +102 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-firestore-namespace/index.html +138 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-firestore-namespace.md +175 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-functions-namespace/index.html +105 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-functions-namespace.md +133 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-config/index.html +194 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-config.md +95 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-namespace/index.html +99 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-namespace.md +126 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-rtdb-namespace/index.html +110 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-rtdb-namespace.md +133 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-scope-and-outcome/index.html +68 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-scope-and-outcome.md +88 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-tool-factories/index.html +160 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-tool-factories.md +104 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-tutorials-01-deploy-a-cloud-function/index.html +88 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy-tutorials-01-deploy-a-cloud-function.md +119 -0
- package/dist/serve/docs-ui/docs/pyric-tools-deploy.md +52 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-build-a-standalone-binary/index.html +92 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-build-a-standalone-binary.md +95 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-configure-auth-providers-and-domains/index.html +101 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-configure-auth-providers-and-domains.md +126 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-discover-a-schema-from-firestore/index.html +95 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-discover-a-schema-from-firestore.md +119 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-promote-sandbox-state-to-a-fixture/index.html +52 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-promote-sandbox-state-to-a-fixture.md +83 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-serve-persistence-and-multi-tab/index.html +232 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-serve-persistence-and-multi-tab.md +178 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-use-the-vite-plugin/index.html +340 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-use-the-vite-plugin.md +288 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-verify-against-a-captured-session/index.html +128 -0
- package/dist/serve/docs-ui/docs/pyric-tools-how-to-verify-against-a-captured-session.md +173 -0
- package/dist/serve/docs-ui/docs/pyric-tools-reference-cli/index.html +433 -0
- package/dist/serve/docs-ui/docs/pyric-tools-reference-cli.md +252 -0
- package/dist/serve/docs-ui/docs/pyric-tools-reference-verify/index.html +81 -0
- package/dist/serve/docs-ui/docs/pyric-tools-reference-verify.md +98 -0
- package/dist/serve/docs-ui/docs/pyric-tools-tutorials-getting-started/index.html +92 -0
- package/dist/serve/docs-ui/docs/pyric-tools-tutorials-getting-started.md +117 -0
- package/dist/serve/docs-ui/docs/pyric-tools-tutorials-server-adoption/index.html +221 -0
- package/dist/serve/docs-ui/docs/pyric-tools-tutorials-server-adoption.md +239 -0
- package/dist/serve/docs-ui/docs/pyric-tools-tutorials-wire-claude-code/index.html +205 -0
- package/dist/serve/docs-ui/docs/pyric-tools-tutorials-wire-claude-code.md +250 -0
- package/dist/serve/docs-ui/docs/pyric-tools.md +47 -0
- package/dist/serve/docs-ui/docs/ui/index.html +76 -0
- package/dist/serve/docs-ui/docs/ui-auth-authsigninhelper/index.html +194 -0
- package/dist/serve/docs-ui/docs/ui-auth-authsigninhelper.md +108 -0
- package/dist/serve/docs-ui/docs/ui-auth-authusers/index.html +258 -0
- package/dist/serve/docs-ui/docs/ui-auth-authusers.md +174 -0
- package/dist/serve/docs-ui/docs/ui-firestore-collectionlist/index.html +92 -0
- package/dist/serve/docs-ui/docs/ui-firestore-collectionlist.md +71 -0
- package/dist/serve/docs-ui/docs/ui-firestore-deletewithconfirm/index.html +128 -0
- package/dist/serve/docs-ui/docs/ui-firestore-deletewithconfirm.md +94 -0
- package/dist/serve/docs-ui/docs/ui-firestore-documenteditor/index.html +154 -0
- package/dist/serve/docs-ui/docs/ui-firestore-documenteditor.md +107 -0
- package/dist/serve/docs-ui/docs/ui-firestore-documentlist/index.html +132 -0
- package/dist/serve/docs-ui/docs/ui-firestore-documentlist.md +86 -0
- package/dist/serve/docs-ui/docs/ui-firestore-documentpreview/index.html +159 -0
- package/dist/serve/docs-ui/docs/ui-firestore-documentpreview.md +78 -0
- package/dist/serve/docs-ui/docs/ui-firestore-querybuilder/index.html +104 -0
- package/dist/serve/docs-ui/docs/ui-firestore-querybuilder.md +95 -0
- package/dist/serve/docs-ui/docs/ui-firestore-referencepicker/index.html +113 -0
- package/dist/serve/docs-ui/docs/ui-firestore-referencepicker.md +85 -0
- package/dist/serve/docs-ui/docs/ui-primitives-badge/index.html +56 -0
- package/dist/serve/docs-ui/docs/ui-primitives-badge.md +34 -0
- package/dist/serve/docs-ui/docs/ui-primitives-confirmdialog/index.html +140 -0
- package/dist/serve/docs-ui/docs/ui-primitives-confirmdialog.md +98 -0
- package/dist/serve/docs-ui/docs/ui-primitives-copybutton/index.html +58 -0
- package/dist/serve/docs-ui/docs/ui-primitives-copybutton.md +31 -0
- package/dist/serve/docs-ui/docs/ui-primitives-jsonview/index.html +58 -0
- package/dist/serve/docs-ui/docs/ui-primitives-jsonview.md +41 -0
- package/dist/serve/docs-ui/docs/ui-primitives-segmentedcontrol/index.html +70 -0
- package/dist/serve/docs-ui/docs/ui-primitives-segmentedcontrol.md +44 -0
- package/dist/serve/docs-ui/docs/ui-primitives-toast/index.html +87 -0
- package/dist/serve/docs-ui/docs/ui-primitives-toast.md +74 -0
- package/dist/serve/docs-ui/docs/ui-primitives-virtuallist/index.html +85 -0
- package/dist/serve/docs-ui/docs/ui-primitives-virtuallist.md +48 -0
- package/dist/serve/docs-ui/docs/ui-storage/index.html +154 -0
- package/dist/serve/docs-ui/docs/ui-storage-deleteselectionwithconfirm/index.html +127 -0
- package/dist/serve/docs-ui/docs/ui-storage-deleteselectionwithconfirm.md +83 -0
- package/dist/serve/docs-ui/docs/ui-storage-objectbrowser/index.html +137 -0
- package/dist/serve/docs-ui/docs/ui-storage-objectbrowser.md +84 -0
- package/dist/serve/docs-ui/docs/ui-storage-objectinspector/index.html +139 -0
- package/dist/serve/docs-ui/docs/ui-storage-objectinspector.md +87 -0
- package/dist/serve/docs-ui/docs/ui-storage-pathbreadcrumb/index.html +72 -0
- package/dist/serve/docs-ui/docs/ui-storage-pathbreadcrumb.md +51 -0
- package/dist/serve/docs-ui/docs/ui-storage-rules-aware-affordances/index.html +65 -0
- package/dist/serve/docs-ui/docs/ui-storage-rules-aware-affordances.md +53 -0
- package/dist/serve/docs-ui/docs/ui-storage-uploaddropzone/index.html +93 -0
- package/dist/serve/docs-ui/docs/ui-storage-uploaddropzone.md +72 -0
- package/dist/serve/docs-ui/docs/ui-storage-usestoragerulesgate/index.html +153 -0
- package/dist/serve/docs-ui/docs/ui-storage-usestoragerulesgate.md +105 -0
- package/dist/serve/docs-ui/docs/ui-storage.md +165 -0
- package/dist/serve/docs-ui/docs/ui-traffic/index.html +88 -0
- package/dist/serve/docs-ui/docs/ui-traffic-ruleheatmap/index.html +79 -0
- package/dist/serve/docs-ui/docs/ui-traffic-ruleheatmap.md +54 -0
- package/dist/serve/docs-ui/docs/ui-traffic-trafficdetail/index.html +78 -0
- package/dist/serve/docs-ui/docs/ui-traffic-trafficdetail.md +53 -0
- package/dist/serve/docs-ui/docs/ui-traffic-trafficlog/index.html +118 -0
- package/dist/serve/docs-ui/docs/ui-traffic-trafficlog.md +63 -0
- package/dist/serve/docs-ui/docs/ui-traffic-trafficstats/index.html +51 -0
- package/dist/serve/docs-ui/docs/ui-traffic-trafficstats.md +40 -0
- package/dist/serve/docs-ui/docs/ui-traffic.md +96 -0
- package/dist/serve/docs-ui/docs/ui.md +88 -0
- package/dist/serve/docs-ui/index.html +1 -0
- package/dist/serve/docs-ui/llms.txt +221 -0
- package/dist/serve/entries/app.d.ts +26 -0
- package/dist/serve/entries/app.d.ts.map +1 -0
- package/dist/serve/entries/app.js +51 -0
- package/dist/serve/entries/app.js.map +1 -0
- package/dist/serve/entries/auth-helper-core.d.ts +47 -0
- package/dist/serve/entries/auth-helper-core.d.ts.map +1 -0
- package/dist/serve/entries/auth-helper-core.js +121 -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.d.ts +62 -0
- package/dist/serve/entries/auth.d.ts.map +1 -0
- package/dist/serve/entries/auth.js +128 -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 +34 -0
- package/dist/serve/entries/database.d.ts.map +1 -0
- package/dist/serve/entries/database.js +42 -0
- package/dist/serve/entries/database.js.map +1 -0
- package/dist/serve/entries/firestore.d.ts +68 -0
- package/dist/serve/entries/firestore.d.ts.map +1 -0
- package/dist/serve/entries/firestore.js +115 -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 +14 -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/runtime.d.ts +60 -0
- package/dist/serve/entries/runtime.d.ts.map +1 -0
- package/dist/serve/entries/runtime.js +555 -0
- package/dist/serve/entries/runtime.js.map +1 -0
- package/dist/serve/entries/session-store.d.ts +45 -0
- package/dist/serve/entries/session-store.d.ts.map +1 -0
- package/dist/serve/entries/session-store.js +71 -0
- package/dist/serve/entries/session-store.js.map +1 -0
- package/dist/serve/entries/storage.d.ts +23 -0
- package/dist/serve/entries/storage.d.ts.map +1 -0
- package/dist/serve/entries/storage.js +49 -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/namespace.d.ts +93 -0
- package/dist/serve/namespace.d.ts.map +1 -0
- package/dist/serve/namespace.js +367 -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/playground-ui/_astro/HomePage.BFOjZGZp.js +2 -0
- package/dist/serve/playground-ui/_astro/PlaygroundPage.3jki8il0.js +3300 -0
- package/dist/serve/playground-ui/_astro/PlaygroundPage.MhP_vFvK.css +32 -0
- package/dist/serve/playground-ui/_astro/TopBar.ChifCjLD.js +5025 -0
- package/dist/serve/playground-ui/_astro/client.D61DpmEZ.js +274 -0
- package/dist/serve/playground-ui/_astro/client.GCK88uRs.js +1 -0
- package/dist/serve/playground-ui/_astro/esbuild.BHljloGq.wasm +0 -0
- package/dist/serve/playground-ui/_astro/index.B9aLOeXL.js +29 -0
- package/dist/serve/playground-ui/_astro/index.BTKE7uRF.js +1227 -0
- package/dist/serve/playground-ui/_astro/index.BfCthstx.css +1 -0
- package/dist/serve/playground-ui/_astro/index.Mr0Dtjih.js +342 -0
- package/dist/serve/playground-ui/_astro/ort-wasm-simd-threaded.asyncify.DMmc6YqF.wasm +0 -0
- package/dist/serve/playground-ui/favicon.svg +4 -0
- package/dist/serve/playground-ui/index.html +2 -0
- package/dist/serve/playground-ui/playground/index.html +2 -0
- package/dist/serve/rules.d.ts +45 -0
- package/dist/serve/rules.d.ts.map +1 -0
- package/dist/serve/rules.js +139 -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 +32 -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 +66 -0
- package/dist/serve/standalone-assets.d.ts.map +1 -0
- package/dist/serve/standalone-assets.js +144 -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-Bncteb8z.js +23 -0
- package/dist/serve/studio-ui/assets/index-CmkJxFcG.js +4577 -0
- package/dist/serve/studio-ui/assets/index-DNK39P6C.css +1 -0
- package/dist/serve/studio-ui/assets/seed-Bab4ArXQ.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 +687 -0
- package/dist/serve/vite-plugin.js.map +1 -0
- package/dist/serve/worker/client.d.ts +668 -0
- package/dist/serve/worker/client.d.ts.map +1 -0
- package/dist/serve/worker/client.js +1635 -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 +122 -0
- package/dist/serve/worker/entry.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 +434 -0
- package/dist/serve/worker/host-auth.js.map +1 -0
- package/dist/serve/worker/host-context.d.ts +180 -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.d.ts +81 -0
- package/dist/serve/worker/host.d.ts.map +1 -0
- package/dist/serve/worker/host.js +1605 -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/protocol.d.ts +955 -0
- package/dist/serve/worker/protocol.d.ts.map +1 -0
- package/dist/serve/worker/protocol.js +197 -0
- package/dist/serve/worker/protocol.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts +192 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -0
- package/dist/serve/worker/serve-init.js +555 -0
- package/dist/serve/worker/serve-init.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 +79 -0
- package/dist/verify/fixture.d.ts.map +1 -0
- package/dist/verify/fixture.js +114 -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/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 +127 -0
|
@@ -0,0 +1,1605 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWorker host — op handlers and subscription registry.
|
|
3
|
+
*
|
|
4
|
+
* WHY INJECTED DEPS
|
|
5
|
+
* -----------------
|
|
6
|
+
* The host is deliberately decoupled from `SharedWorkerGlobalScope` so
|
|
7
|
+
* unit tests can drive it with a REAL pyric sandbox + fake MessagePort
|
|
8
|
+
* objects — no browser or SharedWorker runtime required. The entry point
|
|
9
|
+
* (`entry.ts`) creates the real sandbox + db and wires the connecting ports
|
|
10
|
+
* to this module.
|
|
11
|
+
*
|
|
12
|
+
* ARCHITECTURE
|
|
13
|
+
* ------------
|
|
14
|
+
* One `HostCtx` is shared across ALL connected ports. It holds:
|
|
15
|
+
* - `db` — the single modular Firestore handle (from pyric/firestore's
|
|
16
|
+
* `getFirestore(sandbox)` — sandbox-live mode so auth changes propagate).
|
|
17
|
+
* - `sandbox` — the underlying Sandbox, needed for `setRules` and for
|
|
18
|
+
* constructing sentinels via FieldValue.
|
|
19
|
+
* - `subs` — per-port subscription registry: Map<PortLike, Map<subId, unsub>>
|
|
20
|
+
*
|
|
21
|
+
* Each connecting port calls `handleMessage(ctx, port, msg)`. The function
|
|
22
|
+
* is exported so tests can call it directly.
|
|
23
|
+
*
|
|
24
|
+
* SENTINEL RESOLUTION
|
|
25
|
+
* -------------------
|
|
26
|
+
* Write data crossing the port may contain `SentinelMarker` objects
|
|
27
|
+
* (`{ __sentinel: 'serverTimestamp' }` etc.). Before passing data to the
|
|
28
|
+
* sandbox we walk the payload and replace each marker with the real
|
|
29
|
+
* FieldValue object from `pyric/firestore`'s sentinel factories. The
|
|
30
|
+
* sandbox's value-resolver then executes them as usual.
|
|
31
|
+
*
|
|
32
|
+
* SUBSCRIPTION FAN-OUT
|
|
33
|
+
* --------------------
|
|
34
|
+
* Because all ports share ONE sandbox, an onSnapshot listener registered
|
|
35
|
+
* via the sandbox automatically fires for writes from ANY port. We just
|
|
36
|
+
* need to forward the snapshot to the correct originating port(s).
|
|
37
|
+
*
|
|
38
|
+
* TRANSACTIONS + READ-SET VALIDATION
|
|
39
|
+
* ------------------------------------
|
|
40
|
+
* `runTransaction` on the worker is the full-fidelity path: the host calls
|
|
41
|
+
* the sandbox's `runTransaction`, which runs the update function, handles
|
|
42
|
+
* optimistic-concurrency retries, and commits atomically. The client now
|
|
43
|
+
* sends a `reads` array alongside `writes`; the worker re-reads each doc
|
|
44
|
+
* inside the sandbox transaction and validates that no concurrent write
|
|
45
|
+
* changed any of them between the client's read and this commit. A mismatch
|
|
46
|
+
* signals `{ code: 'aborted' }` on the wire so the client can re-run
|
|
47
|
+
* `updateFn` — see `txnCommit` handler for full details.
|
|
48
|
+
*/
|
|
49
|
+
import { getFirestore as pyricGetFirestore, getAdminFirestore as pyricGetAdminFirestore, doc as pyricDoc, collection as pyricCollection, collectionGroup as pyricCollectionGroup, query as pyricQuery, where as pyricWhere, and as pyricAnd, or as pyricOr, orderBy as pyricOrderBy, limit as pyricLimit, limitToLast as pyricLimitToLast, startAt as pyricStartAt, startAfter as pyricStartAfter, endAt as pyricEndAt, endBefore as pyricEndBefore, getDoc, getDocs, setDoc, updateDoc, deleteDoc, addDoc, onSnapshot, getCountFromServer, getAggregateFromServer, runTransaction, writeBatch, serverTimestamp, increment, arrayUnion, arrayRemove, deleteField, } from 'pyric/firestore';
|
|
50
|
+
// The canonical value codec (same module the protocol's read path uses) —
|
|
51
|
+
// write payloads rehydrate host-side so marker-shaped scalars from the JSON
|
|
52
|
+
// relay legs are STORED as real wrapper instances (see prepareWriteData).
|
|
53
|
+
import { rehydrateDocValue } from 'pyric/firestore-values';
|
|
54
|
+
import { serializeToBuckets, bundleRecords, parseBundle, deserializeFromBuckets } from 'pyric/sandbox';
|
|
55
|
+
import { sandbox as sandboxOps } from 'pyric/firestore';
|
|
56
|
+
import { getInternalEnv } from 'pyric/sandbox/internal';
|
|
57
|
+
import { initializeApp } from 'pyric/app';
|
|
58
|
+
import { getStorage, getStorageSandbox, ref as storageRef, listAll as storageListAll, getMetadata as storageGetMetadata, getBlob as storageGetBlob, getBytes as storageGetBytes, uploadBytes as storageUploadBytes, deleteObject as storageDeleteObject, } from 'pyric/storage';
|
|
59
|
+
// Host-only rules-bypass admin plane — the storage mirror of
|
|
60
|
+
// `getAdminFirestore`/`getAdminDatabase`, resolved for `actAs: { mode: 'admin' }`.
|
|
61
|
+
import { getAdminStorageSandbox } from 'pyric/storage/internal';
|
|
62
|
+
import { getDatabase as pyricGetDatabase, getAdminDatabase as pyricGetAdminDatabase, ref as rtdbRef, get as rtdbGet, set as rtdbSet, update as rtdbUpdate, remove as rtdbRemove, onValue as rtdbOnValue, serverTimestamp as rtdbServerTimestamp, sandbox as rtdbSandbox, } from 'pyric/database/modular';
|
|
63
|
+
import { serializeError, serializeDocData, isSentinelMarker, isAuthSub, isEventSub, isRtdbSub, bytesToBase64, base64ToBytes, storagePayloadTooLarge, MAX_STORAGE_OP_BYTES, MAX_STORAGE_OP_B64_LENGTH, } from './protocol.js';
|
|
64
|
+
// The canonical agent tool dispatcher — reused on the worker so a bridged agent
|
|
65
|
+
// executes against THIS sandbox (one backend for app + Studio + agent), instead
|
|
66
|
+
// of a separate in-page sandbox.
|
|
67
|
+
import { buildSandboxDispatcher } from '../../bridge/client/dispatch.js';
|
|
68
|
+
import { post, ok, fail, bestEffortFlush } from './host-context.js';
|
|
69
|
+
import { authSubsFor, isAuthOp, handleAuthOp, handleAuthSub, handleAuthUnsub, portSession, cleanupPortSession, } from './host-auth.js';
|
|
70
|
+
import { eventSubsFor, handleEventSub, handleEventUnsub, } from './host-events.js';
|
|
71
|
+
// Re-export so host.ts's public surface is unchanged after the decomposition.
|
|
72
|
+
export { ensureAuth, portSession } from './host-auth.js';
|
|
73
|
+
/**
|
|
74
|
+
* Per-SharedWorker instance id — generated once and persisted to the RAW idb
|
|
75
|
+
* (local-only, like the session record above; it must NEVER reach the
|
|
76
|
+
* committable server file). Because IndexedDB is per (origin + browser profile),
|
|
77
|
+
* two profiles on the same `localhost:<port>` get two distinct ids — which is
|
|
78
|
+
* exactly how the UI tells same-port-different-profile sandboxes apart.
|
|
79
|
+
*/
|
|
80
|
+
export const INSTANCE_ID_KEY = 'pyric:worker:instance';
|
|
81
|
+
/**
|
|
82
|
+
* `crypto.randomUUID()` is secure-context-only (https or localhost), so it is
|
|
83
|
+
* `undefined` over plain http on a non-localhost host (a Tailscale or LAN
|
|
84
|
+
* hostname). `crypto.getRandomValues` is NOT gated, so build a v4 UUID from it as
|
|
85
|
+
* the fallback. Without this the worker throws on init over Tailscale and the
|
|
86
|
+
* whole sandbox (auth, firestore, bridge) silently fails to come up.
|
|
87
|
+
*/
|
|
88
|
+
export function randomUuid() {
|
|
89
|
+
if (typeof crypto.randomUUID === 'function')
|
|
90
|
+
return crypto.randomUUID();
|
|
91
|
+
const b = crypto.getRandomValues(new Uint8Array(16));
|
|
92
|
+
b[6] = (b[6] & 0x0f) | 0x40;
|
|
93
|
+
b[8] = (b[8] & 0x3f) | 0x80;
|
|
94
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, '0'));
|
|
95
|
+
return `${h.slice(0, 4).join('')}-${h.slice(4, 6).join('')}-${h.slice(6, 8).join('')}-${h.slice(8, 10).join('')}-${h.slice(10, 16).join('')}`;
|
|
96
|
+
}
|
|
97
|
+
export async function getOrCreateInstanceId(idb) {
|
|
98
|
+
const rec = (await idb.getRecord(INSTANCE_ID_KEY, 'id'));
|
|
99
|
+
if (rec && typeof rec.value === 'string')
|
|
100
|
+
return rec.value;
|
|
101
|
+
const id = randomUuid();
|
|
102
|
+
await idb.putRecords(INSTANCE_ID_KEY, new Map([['id', { value: id }]]));
|
|
103
|
+
return id;
|
|
104
|
+
}
|
|
105
|
+
// ── Phase 3: named branches ─────────────────────────────────────────────────
|
|
106
|
+
// A branch is a named saved state bundle in the RAW idb (local-only, like the
|
|
107
|
+
// instance id + session; it must NEVER reach the committable server file). They
|
|
108
|
+
// let one instance keep several named states it can switch between
|
|
109
|
+
// (switchBranch = loadSnapshot the bundle, a clobber). A registry record holds
|
|
110
|
+
// the ordered name list, since the backend lists records WITHIN a key, not keys.
|
|
111
|
+
export const BRANCH_PREFIX = 'pyric:worker:branch:';
|
|
112
|
+
export const BRANCH_REGISTRY_KEY = 'pyric:worker:branches';
|
|
113
|
+
export async function listBranchNames(idb) {
|
|
114
|
+
if (!idb)
|
|
115
|
+
return [];
|
|
116
|
+
const rec = (await idb.getRecord(BRANCH_REGISTRY_KEY, 'names'));
|
|
117
|
+
return Array.isArray(rec?.value) ? rec.value : [];
|
|
118
|
+
}
|
|
119
|
+
async function writeBranchRegistry(idb, names) {
|
|
120
|
+
await idb.putRecords(BRANCH_REGISTRY_KEY, new Map([['names', { value: names }]]));
|
|
121
|
+
}
|
|
122
|
+
// ─── Descriptor → live ref resolution ───────────────────────────────────
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a TargetDescriptor into a live pyric ref or query.
|
|
125
|
+
*
|
|
126
|
+
* WHY WE RESOLVE ON THE WORKER
|
|
127
|
+
* The client never holds live pyric refs — it holds plain `DocRef`/
|
|
128
|
+
* `CollRef`/`GroupRef`/`QueryDescriptor` objects that serialize cleanly
|
|
129
|
+
* across the MessagePort. On every op the worker rebuilds the live ref
|
|
130
|
+
* from these descriptors. This keeps the client completely stateless with
|
|
131
|
+
* respect to sandbox internals and makes retries trivially correct.
|
|
132
|
+
*/
|
|
133
|
+
function resolveTarget(db, target) {
|
|
134
|
+
if (target.__ref === 'doc') {
|
|
135
|
+
return pyricDoc(db, target.path);
|
|
136
|
+
}
|
|
137
|
+
if (target.__ref === 'collection') {
|
|
138
|
+
return pyricCollection(db, target.path);
|
|
139
|
+
}
|
|
140
|
+
if (target.__ref === 'group') {
|
|
141
|
+
return pyricCollectionGroup(db, target.collectionId);
|
|
142
|
+
}
|
|
143
|
+
// query descriptor
|
|
144
|
+
const source = resolveTarget(db, target.source);
|
|
145
|
+
const constraints = target.constraints.map((c) => resolveConstraint(c));
|
|
146
|
+
return pyricQuery(source, ...constraints);
|
|
147
|
+
}
|
|
148
|
+
function resolveConstraint(c) {
|
|
149
|
+
switch (c.kind) {
|
|
150
|
+
case 'where':
|
|
151
|
+
// Rehydrate the comparison value (same rationale as prepareWriteData,
|
|
152
|
+
// spike gap 4, applied to READ inputs): over the JSON relay legs a
|
|
153
|
+
// Node-side Timestamp/Bytes/GeoPoint arrives as its marker shape;
|
|
154
|
+
// without rehydration the comparison would see a plain map and the
|
|
155
|
+
// filter would silently mismatch typed stored values.
|
|
156
|
+
return pyricWhere(c.field, c.op, rehydrateDocValue(c.value));
|
|
157
|
+
// Composite filters rebuild through the modular `and`/`or` factories,
|
|
158
|
+
// which validate operands: an empty composite or a nested non-filter
|
|
159
|
+
// throws the same TypeError the in-page SDK raises (surfaces as an
|
|
160
|
+
// error res / snap-error, never a crash).
|
|
161
|
+
case 'and':
|
|
162
|
+
return pyricAnd(...c.filters.map(resolveConstraint));
|
|
163
|
+
case 'or':
|
|
164
|
+
return pyricOr(...c.filters.map(resolveConstraint));
|
|
165
|
+
case 'orderBy':
|
|
166
|
+
return pyricOrderBy(c.field, c.direction);
|
|
167
|
+
case 'limit':
|
|
168
|
+
return pyricLimit(c.n);
|
|
169
|
+
case 'limitToLast':
|
|
170
|
+
return pyricLimitToLast(c.n);
|
|
171
|
+
// Cursor values rehydrate for the same reason as `where` values —
|
|
172
|
+
// `startAfter(<timestamp>)` must position against real Timestamps.
|
|
173
|
+
case 'startAt':
|
|
174
|
+
return pyricStartAt(...c.values.map(rehydrateDocValue));
|
|
175
|
+
case 'startAfter':
|
|
176
|
+
return pyricStartAfter(...c.values.map(rehydrateDocValue));
|
|
177
|
+
case 'endAt':
|
|
178
|
+
return pyricEndAt(...c.values.map(rehydrateDocValue));
|
|
179
|
+
case 'endBefore':
|
|
180
|
+
return pyricEndBefore(...c.values.map(rehydrateDocValue));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ─── Sentinel resolution ──────────────────────────────────────────────────
|
|
184
|
+
/**
|
|
185
|
+
* Walk a write payload and replace every `SentinelMarker` with the
|
|
186
|
+
* corresponding pyric/firestore FieldValue sentinel object.
|
|
187
|
+
*
|
|
188
|
+
* WHY: FieldValue class instances don't survive structured clone — they
|
|
189
|
+
* arrive as plain objects and lose their prototype, breaking sandbox
|
|
190
|
+
* sentinel detection. The client sends `{ __sentinel: 'serverTimestamp' }`
|
|
191
|
+
* etc. and we reconstruct the real FieldValue here before the sandbox sees
|
|
192
|
+
* the data.
|
|
193
|
+
*
|
|
194
|
+
* The sandbox's sentinel-capture code (sentinel-capture.ts) recognizes
|
|
195
|
+
* FieldValue objects by their internal `__type` property, which the
|
|
196
|
+
* factories produce correctly.
|
|
197
|
+
*/
|
|
198
|
+
function resolveSentinels(value) {
|
|
199
|
+
if (isSentinelMarker(value)) {
|
|
200
|
+
return resolveSentinel(value);
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
return value.map(resolveSentinels);
|
|
204
|
+
}
|
|
205
|
+
if (value !== null && typeof value === 'object') {
|
|
206
|
+
// Leave CLASS instances intact (Timestamp/Bytes/LatLng rehydrated by
|
|
207
|
+
// prepareWriteData below, or FieldValue objects): walking their entries
|
|
208
|
+
// into a plain object would strip the prototype the sandbox keys on.
|
|
209
|
+
// Sentinel markers only ever live in plain JSON containers.
|
|
210
|
+
const proto = Object.getPrototypeOf(value);
|
|
211
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
const obj = value;
|
|
215
|
+
const out = {};
|
|
216
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
217
|
+
out[k] = resolveSentinels(v);
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Prepare an incoming WRITE payload for the sandbox: rehydrate marker-shaped
|
|
225
|
+
* scalars into REAL wrapper instances, then rebuild FieldValue sentinels.
|
|
226
|
+
*
|
|
227
|
+
* WHY REHYDRATE WRITES (spike gap 4): over the JSON relay legs a Node-side
|
|
228
|
+
* `Timestamp`/`Bytes`/`GeoPoint` arrives as its `toJSON()` marker
|
|
229
|
+
* (`{ type: 'firestore/timestamp/1.0', … }` or `{ __type: 'timestamp', … }`).
|
|
230
|
+
* Without rehydration the worker STORES the marker as a plain map — reads
|
|
231
|
+
* mask the bug (the read path rehydrates), but in-worker rules comparisons
|
|
232
|
+
* and `orderBy` over that field see a map, not a timestamp. `rehydrateDocValue`
|
|
233
|
+
* is the same canonical codec the read path / persistence uses, so the wire,
|
|
234
|
+
* store, and IDB formats stay one format.
|
|
235
|
+
*
|
|
236
|
+
* Order matters: rehydration first (it passes `__sentinel` markers through
|
|
237
|
+
* as plain objects, rehydrating any marker-shaped values nested inside
|
|
238
|
+
* arrayUnion/arrayRemove), then sentinel resolution (which now skips the
|
|
239
|
+
* freshly rehydrated class instances — see resolveSentinels).
|
|
240
|
+
*/
|
|
241
|
+
function prepareWriteData(value) {
|
|
242
|
+
return resolveSentinels(rehydrateDocValue(value));
|
|
243
|
+
}
|
|
244
|
+
function resolveRtdbSentinels(value) {
|
|
245
|
+
if (value && typeof value === 'object') {
|
|
246
|
+
const marker = value;
|
|
247
|
+
if (marker.__rtdbSentinel === 'serverTimestamp')
|
|
248
|
+
return rtdbServerTimestamp();
|
|
249
|
+
if (Array.isArray(value))
|
|
250
|
+
return value.map(resolveRtdbSentinels);
|
|
251
|
+
const out = {};
|
|
252
|
+
for (const [k, v] of Object.entries(value))
|
|
253
|
+
out[k] = resolveRtdbSentinels(v);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function resolveSentinel(marker) {
|
|
259
|
+
switch (marker.__sentinel) {
|
|
260
|
+
case 'serverTimestamp': return serverTimestamp();
|
|
261
|
+
case 'increment': return increment(marker.n);
|
|
262
|
+
case 'arrayUnion': return arrayUnion(...marker.values);
|
|
263
|
+
case 'arrayRemove': return arrayRemove(...marker.values);
|
|
264
|
+
case 'deleteField': return deleteField();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// ─── Snapshot serialization ───────────────────────────────────────────────
|
|
268
|
+
/**
|
|
269
|
+
* Rebuild a JSON-flattened `Uint8Array` (an index-keyed `{ "0": n, … }`
|
|
270
|
+
* map) and encode it as base64url without padding — the exact form
|
|
271
|
+
* `pyric/rules`' `Bytes.toBase64()` emits. Backs the transaction read-set
|
|
272
|
+
* canonicalizer's handling of prototype-stripped `Bytes` clones.
|
|
273
|
+
*/
|
|
274
|
+
/** Timestamp/Duration stripped-clone key set (they share field names). */
|
|
275
|
+
const TS_CLONE_KEYS = ['typeName', 'seconds', 'nanos'];
|
|
276
|
+
/** Does `o` carry EXACTLY these own enumerable keys (no more, no fewer)?
|
|
277
|
+
* Backs the transaction canonicalizer's strict clone-shape matching. */
|
|
278
|
+
function hasExactKeys(o, keys) {
|
|
279
|
+
const own = Object.keys(o);
|
|
280
|
+
return own.length === keys.length && keys.every((k) => k in o);
|
|
281
|
+
}
|
|
282
|
+
function indexMapToBase64Url(data) {
|
|
283
|
+
const map = (data ?? {});
|
|
284
|
+
const keys = Object.keys(map);
|
|
285
|
+
const bytes = new Uint8Array(keys.length);
|
|
286
|
+
for (let i = 0; i < keys.length; i++)
|
|
287
|
+
bytes[i] = map[String(i)] ?? 0;
|
|
288
|
+
let binary = '';
|
|
289
|
+
for (let i = 0; i < bytes.length; i++)
|
|
290
|
+
binary += String.fromCharCode(bytes[i]);
|
|
291
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Serialize a document snapshot to cross-port form.
|
|
295
|
+
* Uses JSON-via-serializeDocData so Timestamp/Bytes/LatLng survive.
|
|
296
|
+
*/
|
|
297
|
+
function serializeDocSnap(snap) {
|
|
298
|
+
const existsBool = typeof snap.exists === 'function' ? snap.exists() : snap.exists;
|
|
299
|
+
const data = existsBool ? snap.data() : undefined;
|
|
300
|
+
return {
|
|
301
|
+
id: snap.id,
|
|
302
|
+
// Doc snapshots carry a top-level `path`; QUERY doc snapshots carry it on
|
|
303
|
+
// `.ref.path`. Read both so query rows serialize their FULL path (Studio's
|
|
304
|
+
// browse resolves the document detail from it).
|
|
305
|
+
path: snap.path ?? snap.ref?.path,
|
|
306
|
+
exists: existsBool,
|
|
307
|
+
data: data ? serializeDocData(data) : undefined,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
// ─── Auth lens (Pyric Studio) ─────────────────────────────────────────────
|
|
311
|
+
/**
|
|
312
|
+
* Resolve the Firestore handle an op runs against, given its `actAs` lens
|
|
313
|
+
* (Pyric Studio auth lens, T2 — implements the C2 protocol seam).
|
|
314
|
+
*
|
|
315
|
+
* Resolution by `actAs.mode` (absent ⇒ the app's session):
|
|
316
|
+
*
|
|
317
|
+
* - `app-session` (and `actAs` absent): `ctx.db` — the shared sandbox-live
|
|
318
|
+
* handle. Reads `sandbox.currentUser` per op, so rules evaluate under
|
|
319
|
+
* whoever the served app is signed in as. This is the unchanged default
|
|
320
|
+
* and the ONLY lens the app itself ever uses.
|
|
321
|
+
*
|
|
322
|
+
* - `{ mode: 'as', uid }` (impersonation — the rules-debugging primitive):
|
|
323
|
+
* a FROZEN-identity `getFirestore(sandbox.withAuth({ uid }))` handle.
|
|
324
|
+
* Security rules APPLY and evaluate as that user — `request.auth.uid`
|
|
325
|
+
* resolves to `uid`. This lets Studio "re-run this denied op as the user
|
|
326
|
+
* who attempted it" (Wave-2 rules-debugging, F4). Handles are cached
|
|
327
|
+
* per-uid on `ctx.lensHandles`.
|
|
328
|
+
*
|
|
329
|
+
* - `{ mode: 'admin' }` (rule bypass): a modular `getAdminFirestore(sandbox)`
|
|
330
|
+
* handle (pyric Gap #2) whose ops skip security-rule evaluation while still
|
|
331
|
+
* reading/writing the same store and emitting events. This is Studio's
|
|
332
|
+
* "edit anything as admin" surface (F2). Cached on `ctx.adminDb`.
|
|
333
|
+
*
|
|
334
|
+
* - `{ mode: 'anon' }` (explicitly unauthenticated): a frozen
|
|
335
|
+
* `getFirestore(sandbox.withAuth(null))` handle — rules apply with
|
|
336
|
+
* `request.auth == null`. The remote arm's `withAuth(null)`; distinct from
|
|
337
|
+
* an ABSENT lens, which resolves to the port's session. Cached on
|
|
338
|
+
* `ctx.anonDb`.
|
|
339
|
+
*
|
|
340
|
+
* WRITE-IMPERSONATION GATING (open micro-decision #1, honoured): the resolver
|
|
341
|
+
* itself is symmetric — an `{ mode: 'as', uid }` lens applies to BOTH reads and
|
|
342
|
+
* writes (a write-as-user is denied/allowed exactly as that user's rules say).
|
|
343
|
+
* The POLICY decision — "read-as-user always; write-as-user only on an explicit
|
|
344
|
+
* reproduce path" — is enforced at the CALLER (the Studio client / UI chooses
|
|
345
|
+
* when to attach a write op's `actAs`), NOT here. Default behaviour stays admin/
|
|
346
|
+
* app-session unless a caller sets `actAs`; no UI is added by T2.
|
|
347
|
+
*/
|
|
348
|
+
function lensDb(ctx, actAs) {
|
|
349
|
+
// Absent / app-session → the app's live session handle.
|
|
350
|
+
if (!actAs || actAs.mode === 'app-session') {
|
|
351
|
+
return ctx.db;
|
|
352
|
+
}
|
|
353
|
+
// { mode: 'admin' } → a modular rules-bypass handle (cached per ctx).
|
|
354
|
+
if (actAs.mode === 'admin') {
|
|
355
|
+
return (ctx.adminDb ??= pyricGetAdminFirestore(ctx.sandbox));
|
|
356
|
+
}
|
|
357
|
+
// { mode: 'anon' } → a genuinely UNAUTHENTICATED handle
|
|
358
|
+
// (`withAuth(null)` — `request.auth == null` in rules). NOT the same as an
|
|
359
|
+
// absent lens, which resolves to the PORT'S SESSION: a relayed op that
|
|
360
|
+
// means "no auth" must pin this lens or it silently runs as whoever the
|
|
361
|
+
// browser tab is signed in as.
|
|
362
|
+
if (actAs.mode === 'anon') {
|
|
363
|
+
return (ctx.anonDb ??= pyricGetFirestore(ctx.sandbox.withAuth(null)));
|
|
364
|
+
}
|
|
365
|
+
// { mode: 'as', uid } → a frozen-identity handle; rules evaluate as `uid`.
|
|
366
|
+
const handles = (ctx.lensHandles ??= new Map());
|
|
367
|
+
const key = lensCacheKey(actAs);
|
|
368
|
+
let handle = handles.get(key);
|
|
369
|
+
if (!handle) {
|
|
370
|
+
handle = pyricGetFirestore(ctx.sandbox.withAuth(authStateForLens(actAs)));
|
|
371
|
+
handles.set(key, handle);
|
|
372
|
+
}
|
|
373
|
+
return handle;
|
|
374
|
+
}
|
|
375
|
+
function authStateForLens(actAs) {
|
|
376
|
+
return actAs.token === undefined
|
|
377
|
+
? { uid: actAs.uid }
|
|
378
|
+
: { uid: actAs.uid, token: actAs.token };
|
|
379
|
+
}
|
|
380
|
+
function lensCacheKey(actAs) {
|
|
381
|
+
return actAs.token === undefined
|
|
382
|
+
? actAs.uid
|
|
383
|
+
: `${actAs.uid}:${JSON.stringify(actAs.token)}`;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Normalise a per-op `actAs` lens to the {@link AuthLens} provenance shape that
|
|
387
|
+
* the unified sandbox event stream stamps on each event's `authLens` field.
|
|
388
|
+
*
|
|
389
|
+
* Absent ⇒ `{ mode: 'app-session' }` (the app's own session) — matching how
|
|
390
|
+
* `EventProvenance.authLens` reads when omitted. C1 added the field to events;
|
|
391
|
+
* T1 owns the EMIT path that actually writes it. T2's job is to thread the lens
|
|
392
|
+
* THROUGH the host so T1 has the value at emit time: see the call in `handleOp`
|
|
393
|
+
* where the resolved lens is passed to the sandbox via `withLens` when that
|
|
394
|
+
* emit seam exists. Today the sandbox event emitters infer `authLens` from the
|
|
395
|
+
* acting identity (the impersonation handle's frozen `auth` is `{ uid }`, so a
|
|
396
|
+
* rules eval already carries that uid); this helper exists so the host has a
|
|
397
|
+
* single canonical normaliser when the explicit emit-time stamp lands.
|
|
398
|
+
*/
|
|
399
|
+
function lensProvenance(actAs) {
|
|
400
|
+
return actAs ?? { mode: 'app-session' };
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Resolve the data handle for an op/sub carrying NO explicit lens: the
|
|
404
|
+
* PORT'S SESSION (#754). A signed-in port gets a per-uid cached
|
|
405
|
+
* `getFirestore(sandbox.withAuth(session.state))` handle — rules evaluate
|
|
406
|
+
* under its uid + custom claims, exactly like a globally signed-in user. A
|
|
407
|
+
* signed-out port falls back to `ctx.db` (sandbox-live; `currentUser` is
|
|
408
|
+
* never set in served mode, so that is the unauthenticated view).
|
|
409
|
+
*
|
|
410
|
+
* This is NOT the impersonation lens: the session was minted by validated
|
|
411
|
+
* sign-in on this port (`sandbox.mintSession`), so both reads and writes
|
|
412
|
+
* legitimately run as that user — the write-impersonation gate on the
|
|
413
|
+
* Studio `as` lens does not apply to a port's own session.
|
|
414
|
+
*
|
|
415
|
+
* Known staleness (same class as `lensHandles`): a claims change via
|
|
416
|
+
* `auth.adminUpdateUser` is not reflected in an existing session's handle
|
|
417
|
+
* until that user session is re-established (real Firebase behaves the same
|
|
418
|
+
* until token refresh).
|
|
419
|
+
*/
|
|
420
|
+
function sessionDb(ctx, port) {
|
|
421
|
+
const session = portSession(ctx, port);
|
|
422
|
+
if (!session)
|
|
423
|
+
return ctx.db;
|
|
424
|
+
const cache = (ctx.sessionDbs ??= new Map());
|
|
425
|
+
let handle = cache.get(session.user.uid);
|
|
426
|
+
if (!handle) {
|
|
427
|
+
handle = pyricGetFirestore(ctx.sandbox.withAuth(session.state));
|
|
428
|
+
cache.set(session.user.uid, handle);
|
|
429
|
+
}
|
|
430
|
+
return handle;
|
|
431
|
+
}
|
|
432
|
+
function sessionRtdb(ctx, port) {
|
|
433
|
+
const session = portSession(ctx, port);
|
|
434
|
+
if (!session)
|
|
435
|
+
return ensureRtdb(ctx);
|
|
436
|
+
const cache = (ctx.sessionRtdbs ??= new Map());
|
|
437
|
+
let handle = cache.get(session.user.uid);
|
|
438
|
+
if (!handle) {
|
|
439
|
+
handle = pyricGetDatabase(ctx.sandbox.withAuth(session.state));
|
|
440
|
+
cache.set(session.user.uid, handle);
|
|
441
|
+
}
|
|
442
|
+
return handle;
|
|
443
|
+
}
|
|
444
|
+
function lensRtdb(ctx, actAs, port) {
|
|
445
|
+
if (!actAs || actAs.mode === 'app-session') {
|
|
446
|
+
return sessionRtdb(ctx, port);
|
|
447
|
+
}
|
|
448
|
+
if (actAs.mode === 'admin') {
|
|
449
|
+
return (ctx.adminRtdb ??= pyricGetAdminDatabase(ctx.sandbox));
|
|
450
|
+
}
|
|
451
|
+
// Genuinely unauthenticated — see the `anon` note on lensDb.
|
|
452
|
+
if (actAs.mode === 'anon') {
|
|
453
|
+
return (ctx.anonRtdb ??= pyricGetDatabase(ctx.sandbox.withAuth(null)));
|
|
454
|
+
}
|
|
455
|
+
const handles = (ctx.lensRtdbs ??= new Map());
|
|
456
|
+
const key = lensCacheKey(actAs);
|
|
457
|
+
let handle = handles.get(key);
|
|
458
|
+
if (!handle) {
|
|
459
|
+
handle = pyricGetDatabase(ctx.sandbox.withAuth(authStateForLens(actAs)));
|
|
460
|
+
handles.set(key, handle);
|
|
461
|
+
}
|
|
462
|
+
return handle;
|
|
463
|
+
}
|
|
464
|
+
// ─── Op handlers ──────────────────────────────────────────────────────────
|
|
465
|
+
/** The shared Storage handle, lazily created (Pyric Studio data browse): one per
|
|
466
|
+
* worker, over an app bound to the shared sandbox. The high-level
|
|
467
|
+
* `pyric/storage` ops enforce rules, so the host reads through them. */
|
|
468
|
+
function ensureStorage(ctx) {
|
|
469
|
+
return (ctx.storage ??= getStorage(initializeApp({ sandbox: ctx.sandbox })));
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Resolve the Storage handle a storage op runs against, given its `actAs`
|
|
473
|
+
* lens — the storage mirror of {@link lensRtdb}:
|
|
474
|
+
*
|
|
475
|
+
* - absent / `app-session` → the shared anonymous page handle
|
|
476
|
+
* ({@link ensureStorage}). Storage rules apply only when the HOST
|
|
477
|
+
* configured them on this sandbox's storage service (first call per
|
|
478
|
+
* sandbox wins) — the SERVED worker currently configures none
|
|
479
|
+
* (`setRules`/`setDatabaseRules` cover Firestore/RTDB only), so
|
|
480
|
+
* worker-mode storage is effectively open today and all lenses behave
|
|
481
|
+
* alike there. The lens split matters for embedding/test hosts that
|
|
482
|
+
* pre-open the service with rules. (Storage also has no per-port
|
|
483
|
+
* session plumbing — reads always ran anonymous; writes keep that.)
|
|
484
|
+
* - `{ mode: 'admin' }` → the rules-BYPASS handle from
|
|
485
|
+
* `pyric/storage/internal`'s admin plane — same per-sandbox store +
|
|
486
|
+
* ruleset, rule evaluation skipped (firebase-admin semantics for the
|
|
487
|
+
* `pyric-admin` remote arm / Studio admin lens).
|
|
488
|
+
* - `{ mode: 'as', uid }` → a frozen-identity `getStorageSandbox(
|
|
489
|
+
* sandbox.withAuth({ uid, token? }))` handle; rules evaluate AS that
|
|
490
|
+
* user. Cached per uid/token key on `ctx.lensStorages`.
|
|
491
|
+
*/
|
|
492
|
+
function lensStorage(ctx, actAs) {
|
|
493
|
+
if (!actAs || actAs.mode === 'app-session') {
|
|
494
|
+
return ensureStorage(ctx);
|
|
495
|
+
}
|
|
496
|
+
if (actAs.mode === 'admin') {
|
|
497
|
+
return (ctx.adminStorage ??= getAdminStorageSandbox(ctx.sandbox));
|
|
498
|
+
}
|
|
499
|
+
// Genuinely unauthenticated — see the `anon` note on lensDb. Distinct from
|
|
500
|
+
// the shared page handle only when the host configured storage rules, but
|
|
501
|
+
// pinning it keeps remote `withAuth(null)` semantics uniform across services.
|
|
502
|
+
if (actAs.mode === 'anon') {
|
|
503
|
+
return (ctx.anonStorage ??= getStorageSandbox(ctx.sandbox.withAuth(null)));
|
|
504
|
+
}
|
|
505
|
+
const handles = (ctx.lensStorages ??= new Map());
|
|
506
|
+
const key = lensCacheKey(actAs);
|
|
507
|
+
let handle = handles.get(key);
|
|
508
|
+
if (!handle) {
|
|
509
|
+
handle = getStorageSandbox(ctx.sandbox.withAuth(authStateForLens(actAs)));
|
|
510
|
+
handles.set(key, handle);
|
|
511
|
+
}
|
|
512
|
+
return handle;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Map a wire `storage.putBytes` payload to `pyric/storage`'s
|
|
516
|
+
* `SettableMetadata`. The explicit `contentType` field wins; recognized
|
|
517
|
+
* settable fields are lifted from `metadata`; a GCS-style nested custom map
|
|
518
|
+
* (`metadata.metadata`, as `@google-cloud/storage`'s `save` spells it) or a
|
|
519
|
+
* pyric-style `metadata.customMetadata` becomes `customMetadata` with
|
|
520
|
+
* values coerced to strings (the storage-rules `metadata` model).
|
|
521
|
+
*/
|
|
522
|
+
function toSettableMetadata(msg) {
|
|
523
|
+
const md = msg.metadata ?? {};
|
|
524
|
+
const str = (key) => typeof md[key] === 'string' ? md[key] : undefined;
|
|
525
|
+
const customSource = md['customMetadata'] ?? md['metadata'];
|
|
526
|
+
let customMetadata;
|
|
527
|
+
if (customSource !== null && typeof customSource === 'object' && !Array.isArray(customSource)) {
|
|
528
|
+
customMetadata = {};
|
|
529
|
+
for (const [k, v] of Object.entries(customSource)) {
|
|
530
|
+
customMetadata[k] = String(v);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const settable = {};
|
|
534
|
+
const contentType = msg.contentType ?? str('contentType');
|
|
535
|
+
if (contentType !== undefined)
|
|
536
|
+
settable.contentType = contentType;
|
|
537
|
+
const cacheControl = str('cacheControl');
|
|
538
|
+
if (cacheControl !== undefined)
|
|
539
|
+
settable.cacheControl = cacheControl;
|
|
540
|
+
const contentDisposition = str('contentDisposition');
|
|
541
|
+
if (contentDisposition !== undefined)
|
|
542
|
+
settable.contentDisposition = contentDisposition;
|
|
543
|
+
const contentEncoding = str('contentEncoding');
|
|
544
|
+
if (contentEncoding !== undefined)
|
|
545
|
+
settable.contentEncoding = contentEncoding;
|
|
546
|
+
const contentLanguage = str('contentLanguage');
|
|
547
|
+
if (contentLanguage !== undefined)
|
|
548
|
+
settable.contentLanguage = contentLanguage;
|
|
549
|
+
if (customMetadata !== undefined)
|
|
550
|
+
settable.customMetadata = customMetadata;
|
|
551
|
+
return settable;
|
|
552
|
+
}
|
|
553
|
+
function ensureRtdb(ctx) {
|
|
554
|
+
return (ctx.rtdb ??= pyricGetDatabase(ctx.sandbox));
|
|
555
|
+
}
|
|
556
|
+
function rtdbSnapToWire(snap) {
|
|
557
|
+
return {
|
|
558
|
+
key: snap.key,
|
|
559
|
+
exists: snap.exists(),
|
|
560
|
+
value: snap.val(),
|
|
561
|
+
size: snap.size,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function normalizeDatabaseRules(source) {
|
|
565
|
+
if (source === null)
|
|
566
|
+
return null;
|
|
567
|
+
if (typeof source === 'string') {
|
|
568
|
+
return JSON.parse(source);
|
|
569
|
+
}
|
|
570
|
+
if (typeof source === 'object' && source !== null) {
|
|
571
|
+
return source;
|
|
572
|
+
}
|
|
573
|
+
throw new Error('RTDB rules must be a rules JSON object or JSON string.');
|
|
574
|
+
}
|
|
575
|
+
function firestoreRuleMessages(result) {
|
|
576
|
+
const messages = [];
|
|
577
|
+
if (result.parseError) {
|
|
578
|
+
messages.push({
|
|
579
|
+
severity: 'error',
|
|
580
|
+
text: `PARSE ERROR: expected ${String(result.parseError.expected ?? 'valid rules')}`,
|
|
581
|
+
line: result.parseError.line,
|
|
582
|
+
column: result.parseError.column,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
for (const warning of result.warnings ?? []) {
|
|
586
|
+
messages.push({
|
|
587
|
+
severity: warning.severity === 'error' ? 'error' : warning.severity === 'warning' ? 'warn' : 'info',
|
|
588
|
+
text: String(warning.message ?? warning),
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
return messages;
|
|
592
|
+
}
|
|
593
|
+
async function handleOp(ctx, port, msg) {
|
|
594
|
+
const { sandbox } = ctx;
|
|
595
|
+
// Explicit lens (Studio admin / as / app-session) → lensDb; no lens → the
|
|
596
|
+
// PORT'S SESSION (#754), so app ops run as whoever this tab signed in as.
|
|
597
|
+
const db = msg.actAs ? lensDb(ctx, msg.actAs) : sessionDb(ctx, port);
|
|
598
|
+
// Provenance the op runs under. Stamped onto the unified event stream's
|
|
599
|
+
// `authLens` by the emit path (C1 field / T1 emit). For `{ mode: 'as', uid }`
|
|
600
|
+
// the resolved `db` already carries `auth: { uid }`, so a rules eval emits
|
|
601
|
+
// under that identity; `lens` is the canonical normalised value the host
|
|
602
|
+
// hands forward when the explicit emit-time stamp seam exists (see lensProvenance).
|
|
603
|
+
const lens = lensProvenance(msg.actAs);
|
|
604
|
+
void lens;
|
|
605
|
+
switch (msg.method) {
|
|
606
|
+
case 'getDoc': {
|
|
607
|
+
try {
|
|
608
|
+
const ref = pyricDoc(db, msg.path);
|
|
609
|
+
const snap = await getDoc(ref);
|
|
610
|
+
ok(port, msg.id, serializeDocSnap(snap));
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
fail(port, msg.id, e);
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
case 'getDocs': {
|
|
618
|
+
try {
|
|
619
|
+
const source = resolveTarget(db, msg.source);
|
|
620
|
+
// CollectionReference is always also queryable — getDocs accepts Query<T>
|
|
621
|
+
// and CollectionReference is structurally compatible at runtime even though
|
|
622
|
+
// the type system doesn't know that (CollectionReference has no `_isQuery`
|
|
623
|
+
// brand). Cast through Query to satisfy the type checker.
|
|
624
|
+
const snap = await getDocs(source);
|
|
625
|
+
const docs = snap.docs.map((d) => serializeDocSnap(d));
|
|
626
|
+
ok(port, msg.id, { docs });
|
|
627
|
+
}
|
|
628
|
+
catch (e) {
|
|
629
|
+
fail(port, msg.id, e);
|
|
630
|
+
}
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
case 'setDoc': {
|
|
634
|
+
try {
|
|
635
|
+
const ref = pyricDoc(db, msg.path);
|
|
636
|
+
const data = prepareWriteData(msg.data);
|
|
637
|
+
await setDoc(ref, data, msg.options);
|
|
638
|
+
await bestEffortFlush(ctx);
|
|
639
|
+
ok(port, msg.id, null);
|
|
640
|
+
}
|
|
641
|
+
catch (e) {
|
|
642
|
+
fail(port, msg.id, e);
|
|
643
|
+
}
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
case 'updateDoc': {
|
|
647
|
+
try {
|
|
648
|
+
const ref = pyricDoc(db, msg.path);
|
|
649
|
+
const data = prepareWriteData(msg.data);
|
|
650
|
+
await updateDoc(ref, data);
|
|
651
|
+
await bestEffortFlush(ctx);
|
|
652
|
+
ok(port, msg.id, null);
|
|
653
|
+
}
|
|
654
|
+
catch (e) {
|
|
655
|
+
fail(port, msg.id, e);
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
case 'deleteDoc': {
|
|
660
|
+
try {
|
|
661
|
+
const ref = pyricDoc(db, msg.path);
|
|
662
|
+
await deleteDoc(ref);
|
|
663
|
+
await bestEffortFlush(ctx);
|
|
664
|
+
ok(port, msg.id, null);
|
|
665
|
+
}
|
|
666
|
+
catch (e) {
|
|
667
|
+
fail(port, msg.id, e);
|
|
668
|
+
}
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
case 'addDoc': {
|
|
672
|
+
try {
|
|
673
|
+
const coll = pyricCollection(db, msg.collectionPath);
|
|
674
|
+
const data = prepareWriteData(msg.data);
|
|
675
|
+
const ref = await addDoc(coll, data);
|
|
676
|
+
await bestEffortFlush(ctx);
|
|
677
|
+
ok(port, msg.id, { id: ref.id, path: ref.path });
|
|
678
|
+
}
|
|
679
|
+
catch (e) {
|
|
680
|
+
fail(port, msg.id, e);
|
|
681
|
+
}
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
case 'count': {
|
|
685
|
+
try {
|
|
686
|
+
const source = resolveTarget(db, msg.source);
|
|
687
|
+
const snap = await getCountFromServer(source);
|
|
688
|
+
ok(port, msg.id, { count: snap.data().count });
|
|
689
|
+
}
|
|
690
|
+
catch (e) {
|
|
691
|
+
fail(port, msg.id, e);
|
|
692
|
+
}
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
case 'aggregate': {
|
|
696
|
+
// Multi-field aggregates (count/sum/average). The wire spec is
|
|
697
|
+
// structurally pyric/firestore's AggregateSpec, so it passes straight
|
|
698
|
+
// through; the reply data is plain numbers / null (empty-input average).
|
|
699
|
+
try {
|
|
700
|
+
const source = resolveTarget(db, msg.source);
|
|
701
|
+
const snap = await getAggregateFromServer(source, msg.spec);
|
|
702
|
+
ok(port, msg.id, { data: snap.data() });
|
|
703
|
+
}
|
|
704
|
+
catch (e) {
|
|
705
|
+
fail(port, msg.id, e);
|
|
706
|
+
}
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case 'listRootCollections': {
|
|
710
|
+
// Keyspace enumeration (Pyric Studio data browse). Lens-independent: it
|
|
711
|
+
// lists the collection ids present, not rule-gated reads.
|
|
712
|
+
try {
|
|
713
|
+
ok(port, msg.id, { ids: getInternalEnv(sandbox).listRootCollections() });
|
|
714
|
+
}
|
|
715
|
+
catch (e) {
|
|
716
|
+
fail(port, msg.id, e);
|
|
717
|
+
}
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
case 'listSubcollections': {
|
|
721
|
+
try {
|
|
722
|
+
ok(port, msg.id, { ids: getInternalEnv(sandbox).listSubcollections(msg.docPath) });
|
|
723
|
+
}
|
|
724
|
+
catch (e) {
|
|
725
|
+
fail(port, msg.id, e);
|
|
726
|
+
}
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
case 'batchCommit': {
|
|
730
|
+
/**
|
|
731
|
+
* WHY: writeBatch in pyric/firestore buffers set/update/delete calls
|
|
732
|
+
* and commits them as one unit through the sandbox backend. We
|
|
733
|
+
* reconstruct the batch here from the wire write descriptors.
|
|
734
|
+
* Atomicity guarantee: the sandbox backend applies all writes or none
|
|
735
|
+
* (per-collection lock semantics of LocalEnvironment.batch()).
|
|
736
|
+
*/
|
|
737
|
+
try {
|
|
738
|
+
const batch = writeBatch(db);
|
|
739
|
+
for (const w of msg.writes) {
|
|
740
|
+
applyWriteToBatch(db, batch, w);
|
|
741
|
+
}
|
|
742
|
+
await batch.commit();
|
|
743
|
+
await bestEffortFlush(ctx);
|
|
744
|
+
ok(port, msg.id, null);
|
|
745
|
+
}
|
|
746
|
+
catch (e) {
|
|
747
|
+
fail(port, msg.id, e);
|
|
748
|
+
}
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
case 'txnCommit': {
|
|
752
|
+
/**
|
|
753
|
+
* READ-SET VALIDATION FOR MULTI-TAB CORRECTNESS
|
|
754
|
+
* -----------------------------------------------
|
|
755
|
+
* The client sends `{ method: 'txnCommit', reads, writes }` after its
|
|
756
|
+
* `updateFn` callback has resolved. `reads` is the set of docs the
|
|
757
|
+
* client read during `updateFn` (each entry is `{ path, data }` where
|
|
758
|
+
* `data` is the serialized snapshot the client saw, or null if the doc
|
|
759
|
+
* was missing).
|
|
760
|
+
*
|
|
761
|
+
* WHY READ-SET VALIDATION IS NECESSARY
|
|
762
|
+
* -------------------------------------
|
|
763
|
+
* A transaction spans two messages: the client's `txn.get` RPC (sent
|
|
764
|
+
* during `updateFn`) and this `txnCommit` RPC. Between those messages
|
|
765
|
+
* the event loop is free — another tab's write to any of the read docs
|
|
766
|
+
* can land in that gap via a separate `setDoc` message. Without
|
|
767
|
+
* validation that would be a silent lost update: the client computed
|
|
768
|
+
* its writes using stale data, committed them, and the other tab's
|
|
769
|
+
* changes were silently overwritten.
|
|
770
|
+
*
|
|
771
|
+
* HOW WE VALIDATE (no per-doc version/updateTime in the sandbox)
|
|
772
|
+
* ---------------------------------------------------------------
|
|
773
|
+
* Inside a real `runTransaction(db, callback)`, the callback re-reads
|
|
774
|
+
* every doc from the read-set via `tx.get(ref)`, serializes its current
|
|
775
|
+
* data to the same JSON form the client produced (using `serializeDocData`
|
|
776
|
+
* on both sides), and compares the JSON strings. If any doc's current
|
|
777
|
+
* serialized form differs from what the client recorded, another tab
|
|
778
|
+
* wrote it between the client's read and this commit — we throw an abort
|
|
779
|
+
* sentinel so the sandbox rolls back the transaction, and we return
|
|
780
|
+
* `{ ok: false, error: { code: 'aborted' } }` on the wire. The client's
|
|
781
|
+
* retry loop then re-runs `updateFn` with fresh reads.
|
|
782
|
+
*
|
|
783
|
+
* SERIALIZED-FORM EQUALITY — VIA THE CANONICAL CODEC
|
|
784
|
+
* ---------------------------------------------------
|
|
785
|
+
* Both JSON strings are CANONICALIZED before comparison:
|
|
786
|
+
* `JSON.stringify(rehydrateDocValue(JSON.parse(json)))`. Raw string
|
|
787
|
+
* equality is NOT safe here even within one process, because the two
|
|
788
|
+
* read paths yield DIFFERENT wrapper classes for the same stored
|
|
789
|
+
* value: `getDoc` (what the client's read-set echoes) returns
|
|
790
|
+
* `firebase/firestore` classes whose `Timestamp.toJSON()` emits
|
|
791
|
+
* `{ seconds, nanoseconds, type }`, while the transaction's
|
|
792
|
+
* validation re-read comes through the admin-compat wrapper whose
|
|
793
|
+
* `Timestamp.toJSON()` emits `{ type, seconds, nanoseconds }` — same
|
|
794
|
+
* value, different key order, different string. Rehydrating both
|
|
795
|
+
* sides through the ONE shared codec (`pyric/firestore-values`)
|
|
796
|
+
* collapses every marker family into the same wrapper classes with
|
|
797
|
+
* deterministic `toJSON()` key order, so string equality ↔ value
|
|
798
|
+
* equality again — an unmodified doc can never phantom-abort (which
|
|
799
|
+
* would livelock the client's retry loop), while a real concurrent
|
|
800
|
+
* write still mismatches. Plain-map key order is preserved by both
|
|
801
|
+
* paths from the same stored object, so it stays comparable.
|
|
802
|
+
*
|
|
803
|
+
* NOTE ON tx.set/update/delete SIGNATURE
|
|
804
|
+
* ---------------------------------------
|
|
805
|
+
* The modular pyric/firestore Transaction (wrapping the admin-compat
|
|
806
|
+
* TransactionWrapper) takes `tx.set(docRef, data)` where `docRef` is a
|
|
807
|
+
* real `DocumentReference` — NOT a path string. We build refs via
|
|
808
|
+
* `pyricDoc(db, path)` for each write.
|
|
809
|
+
*/
|
|
810
|
+
/** Sentinel thrown inside the transaction callback to signal an abort. */
|
|
811
|
+
const TXN_ABORT = Symbol('txn-abort');
|
|
812
|
+
/**
|
|
813
|
+
* Canonicalize a serialized doc-data JSON string (see
|
|
814
|
+
* SERIALIZED-FORM EQUALITY above). Two normalization passes:
|
|
815
|
+
*
|
|
816
|
+
* 1. `rehydrateDocValue` collapses the marker families (`__type`
|
|
817
|
+
* persistence markers and `firebase/firestore` `toJSON()`
|
|
818
|
+
* markers) into the one set of wrapper classes, whose
|
|
819
|
+
* `toJSON()` re-emits a single deterministic form.
|
|
820
|
+
* 2. The stringify replacer additionally normalizes PROTOTYPE-
|
|
821
|
+
* STRIPPED wrapper clones — the sandbox transaction's
|
|
822
|
+
* capture-by-value `structuredClone` turns a stored rules
|
|
823
|
+
* wrapper into a plain `{ typeName, … }` object that neither
|
|
824
|
+
* marker family matches, so without this pass an unmodified
|
|
825
|
+
* typed doc would never compare equal to the client's getDoc
|
|
826
|
+
* echo (a guaranteed phantom abort → retry livelock).
|
|
827
|
+
*/
|
|
828
|
+
const canonicalDocJson = (json) => JSON.stringify(rehydrateDocValue(JSON.parse(json)), (_key, v) => {
|
|
829
|
+
if (v === null || typeof v !== 'object' || Array.isArray(v))
|
|
830
|
+
return v;
|
|
831
|
+
const o = v;
|
|
832
|
+
if (typeof o.typeName !== 'string' || o.__type !== undefined)
|
|
833
|
+
return v;
|
|
834
|
+
// Re-shape a stripped rules-wrapper clone into the wrapper's own
|
|
835
|
+
// canonical toJSON marker form (kept in sync with pyric/rules'
|
|
836
|
+
// simulator/wrappers/* instance fields + toJSON()) — but ONLY
|
|
837
|
+
// when the key set EXACTLY matches that wrapper's own-field
|
|
838
|
+
// shape. A looser match would silently DROP extra keys from
|
|
839
|
+
// user data that merely resembles a clone, collapsing two
|
|
840
|
+
// genuinely different docs into one canonical form and letting
|
|
841
|
+
// a concurrent write commit undetected (false equality). A
|
|
842
|
+
// near-miss map passes through unchanged — worst case is a
|
|
843
|
+
// spurious abort + retry, never a lost update.
|
|
844
|
+
switch (o.typeName) {
|
|
845
|
+
case 'timestamp':
|
|
846
|
+
return hasExactKeys(o, TS_CLONE_KEYS)
|
|
847
|
+
? { __type: 'timestamp', seconds: o.seconds, nanos: o.nanos }
|
|
848
|
+
: v;
|
|
849
|
+
case 'duration':
|
|
850
|
+
return hasExactKeys(o, TS_CLONE_KEYS)
|
|
851
|
+
? { __type: 'duration', seconds: o.seconds, nanos: o.nanos }
|
|
852
|
+
: v;
|
|
853
|
+
case 'latlng':
|
|
854
|
+
return hasExactKeys(o, ['typeName', 'lat', 'lng'])
|
|
855
|
+
? { __type: 'latlng', lat: o.lat, lng: o.lng }
|
|
856
|
+
: v;
|
|
857
|
+
case 'reference':
|
|
858
|
+
return hasExactKeys(o, ['typeName', 'path'])
|
|
859
|
+
? { __type: 'reference', path: o.path }
|
|
860
|
+
: v;
|
|
861
|
+
case 'path':
|
|
862
|
+
return hasExactKeys(o, ['typeName', 'segments', 'bindings'])
|
|
863
|
+
? { __type: 'path', segments: o.segments }
|
|
864
|
+
: v;
|
|
865
|
+
case 'bytes':
|
|
866
|
+
// The Uint8Array field serialized as an index-keyed map;
|
|
867
|
+
// rebuild and emit Bytes.toJSON()'s base64url form.
|
|
868
|
+
return hasExactKeys(o, ['typeName', 'data'])
|
|
869
|
+
? { __type: 'bytes', base64: indexMapToBase64Url(o.data) }
|
|
870
|
+
: v;
|
|
871
|
+
default:
|
|
872
|
+
return v;
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
try {
|
|
876
|
+
await runTransaction(db, async (tx) => {
|
|
877
|
+
// ── Step 1: validate the read-set ──────────────────────────────
|
|
878
|
+
// Re-read each doc the client touched and compare its current
|
|
879
|
+
// serialized form against what the client recorded at read time.
|
|
880
|
+
const modularTx = tx;
|
|
881
|
+
for (const r of msg.reads) {
|
|
882
|
+
const ref = pyricDoc(db, r.path);
|
|
883
|
+
const currentSnap = await modularTx.get(ref);
|
|
884
|
+
const existsBool = typeof currentSnap.exists === 'function'
|
|
885
|
+
? currentSnap.exists()
|
|
886
|
+
: currentSnap.exists;
|
|
887
|
+
// Compute the serialized form of the current doc state.
|
|
888
|
+
const currentSerialized = existsBool && currentSnap.data()
|
|
889
|
+
? serializeDocData(currentSnap.data())
|
|
890
|
+
: null;
|
|
891
|
+
// Compare against what the client recorded:
|
|
892
|
+
// both null → ok (doc still doesn't exist)
|
|
893
|
+
// both present + same JSON → ok
|
|
894
|
+
// anything else → conflict
|
|
895
|
+
const clientHadNull = r.data === null;
|
|
896
|
+
const workerHasNull = currentSerialized === null;
|
|
897
|
+
if (clientHadNull !== workerHasNull) {
|
|
898
|
+
// Existence changed (created or deleted by another tab).
|
|
899
|
+
throw TXN_ABORT;
|
|
900
|
+
}
|
|
901
|
+
if (!clientHadNull &&
|
|
902
|
+
!workerHasNull &&
|
|
903
|
+
canonicalDocJson(r.data.json) !== canonicalDocJson(currentSerialized.json)) {
|
|
904
|
+
// Data changed by another tab.
|
|
905
|
+
throw TXN_ABORT;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// ── Step 2: apply the queued writes ────────────────────────────
|
|
909
|
+
for (const w of msg.writes) {
|
|
910
|
+
const ref = pyricDoc(db, w.path);
|
|
911
|
+
if (w.method === 'set') {
|
|
912
|
+
const data = prepareWriteData(w.data);
|
|
913
|
+
modularTx.set(ref, data, w.options);
|
|
914
|
+
}
|
|
915
|
+
else if (w.method === 'update') {
|
|
916
|
+
const data = prepareWriteData(w.data);
|
|
917
|
+
modularTx.update(ref, data);
|
|
918
|
+
}
|
|
919
|
+
else if (w.method === 'delete') {
|
|
920
|
+
modularTx.delete(ref);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
await bestEffortFlush(ctx);
|
|
925
|
+
ok(port, msg.id, null);
|
|
926
|
+
}
|
|
927
|
+
catch (e) {
|
|
928
|
+
if (e === TXN_ABORT) {
|
|
929
|
+
// Read-set conflict — signal the client to retry its updateFn.
|
|
930
|
+
const abortErr = { code: 'aborted', message: 'Transaction read-set conflict: a concurrent write invalidated the read snapshot.' };
|
|
931
|
+
post(port, { t: 'res', id: msg.id, ok: false, error: abortErr });
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
fail(port, msg.id, e);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
case 'setRules':
|
|
940
|
+
case 'setFirestoreRules': {
|
|
941
|
+
try {
|
|
942
|
+
const result = sandboxOps.setRules(db, msg.source);
|
|
943
|
+
const messages = firestoreRuleMessages(result);
|
|
944
|
+
const okDeploy = !messages.some((m) => m.severity === 'error');
|
|
945
|
+
ctx.activeRules ??= {};
|
|
946
|
+
const previous = ctx.activeRules.firestore?.status === 'active'
|
|
947
|
+
? ctx.activeRules.firestore.source
|
|
948
|
+
: ctx.activeRules.firestore?.lastKnownGood;
|
|
949
|
+
ctx.activeRules.firestore = {
|
|
950
|
+
source: okDeploy ? msg.source : ctx.activeRules.firestore?.source ?? msg.source,
|
|
951
|
+
updatedAt: Date.now(),
|
|
952
|
+
status: okDeploy ? 'active' : 'error',
|
|
953
|
+
messages,
|
|
954
|
+
...(previous ? { lastKnownGood: previous } : {}),
|
|
955
|
+
};
|
|
956
|
+
ok(port, msg.id, { warnings: result.warnings, messages, ok: okDeploy });
|
|
957
|
+
}
|
|
958
|
+
catch (e) {
|
|
959
|
+
fail(port, msg.id, e);
|
|
960
|
+
}
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
case 'setDatabaseRules': {
|
|
964
|
+
try {
|
|
965
|
+
const db = ensureRtdb(ctx);
|
|
966
|
+
const rules = normalizeDatabaseRules(msg.source);
|
|
967
|
+
const previous = ctx.activeRules?.database?.status === 'active'
|
|
968
|
+
? ctx.activeRules.database.source
|
|
969
|
+
: ctx.activeRules?.database?.lastKnownGood;
|
|
970
|
+
rtdbSandbox.setRules(db, rules);
|
|
971
|
+
ctx.activeRules ??= {};
|
|
972
|
+
ctx.activeRules.database = {
|
|
973
|
+
source: rules,
|
|
974
|
+
updatedAt: Date.now(),
|
|
975
|
+
status: 'active',
|
|
976
|
+
messages: [],
|
|
977
|
+
...(previous ? { lastKnownGood: previous } : {}),
|
|
978
|
+
};
|
|
979
|
+
ok(port, msg.id, { ok: true, messages: [] });
|
|
980
|
+
}
|
|
981
|
+
catch (e) {
|
|
982
|
+
fail(port, msg.id, e);
|
|
983
|
+
}
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
case 'getActiveRules': {
|
|
987
|
+
ok(port, msg.id, msg.service ? ctx.activeRules?.[msg.service] ?? null : ctx.activeRules ?? {});
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
case 'getRulesStatus': {
|
|
991
|
+
ok(port, msg.id, msg.service ? ctx.activeRules?.[msg.service] ?? null : ctx.activeRules ?? {});
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
case 'admin.getDocument': {
|
|
995
|
+
try {
|
|
996
|
+
ok(port, msg.id, ctx.sandbox.admin.getDocument(msg.path));
|
|
997
|
+
}
|
|
998
|
+
catch (e) {
|
|
999
|
+
fail(port, msg.id, e);
|
|
1000
|
+
}
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
case 'admin.listDocuments': {
|
|
1004
|
+
try {
|
|
1005
|
+
ok(port, msg.id, ctx.sandbox.admin.listDocuments(msg.path));
|
|
1006
|
+
}
|
|
1007
|
+
catch (e) {
|
|
1008
|
+
fail(port, msg.id, e);
|
|
1009
|
+
}
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
case 'admin.setDocument': {
|
|
1013
|
+
try {
|
|
1014
|
+
ctx.sandbox.admin.setDocument(msg.path, msg.data);
|
|
1015
|
+
await bestEffortFlush(ctx);
|
|
1016
|
+
ok(port, msg.id, null);
|
|
1017
|
+
}
|
|
1018
|
+
catch (e) {
|
|
1019
|
+
fail(port, msg.id, e);
|
|
1020
|
+
}
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
case 'admin.deleteDocument': {
|
|
1024
|
+
try {
|
|
1025
|
+
const deleted = ctx.sandbox.admin.deleteDocument(msg.path);
|
|
1026
|
+
await bestEffortFlush(ctx);
|
|
1027
|
+
ok(port, msg.id, deleted);
|
|
1028
|
+
}
|
|
1029
|
+
catch (e) {
|
|
1030
|
+
fail(port, msg.id, e);
|
|
1031
|
+
}
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
case 'admin.readState': {
|
|
1035
|
+
try {
|
|
1036
|
+
const snap = sandboxOps.snapshotState(ctx.adminDb ?? lensDb(ctx, { mode: 'admin' }));
|
|
1037
|
+
const out = {};
|
|
1038
|
+
const prefix = msg.path ?? '';
|
|
1039
|
+
for (const [path, data] of Object.entries(snap)) {
|
|
1040
|
+
if (prefix && !path.startsWith(prefix))
|
|
1041
|
+
continue;
|
|
1042
|
+
if (msg.maxDepth !== undefined && path.split('/').length > msg.maxDepth)
|
|
1043
|
+
continue;
|
|
1044
|
+
out[path] = data;
|
|
1045
|
+
}
|
|
1046
|
+
ok(port, msg.id, out);
|
|
1047
|
+
}
|
|
1048
|
+
catch (e) {
|
|
1049
|
+
fail(port, msg.id, e);
|
|
1050
|
+
}
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
case 'rtdb.get': {
|
|
1054
|
+
try {
|
|
1055
|
+
const db = lensRtdb(ctx, msg.actAs, port);
|
|
1056
|
+
ok(port, msg.id, rtdbSnapToWire(await rtdbGet(rtdbRef(db, msg.path))));
|
|
1057
|
+
}
|
|
1058
|
+
catch (e) {
|
|
1059
|
+
fail(port, msg.id, e);
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
case 'rtdb.set': {
|
|
1064
|
+
try {
|
|
1065
|
+
const db = lensRtdb(ctx, msg.actAs, port);
|
|
1066
|
+
const value = resolveRtdbSentinels(msg.value);
|
|
1067
|
+
await rtdbSet(rtdbRef(db, msg.path), value);
|
|
1068
|
+
await bestEffortFlush(ctx);
|
|
1069
|
+
ok(port, msg.id, null);
|
|
1070
|
+
}
|
|
1071
|
+
catch (e) {
|
|
1072
|
+
fail(port, msg.id, e);
|
|
1073
|
+
}
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
case 'rtdb.update': {
|
|
1077
|
+
try {
|
|
1078
|
+
const db = lensRtdb(ctx, msg.actAs, port);
|
|
1079
|
+
await rtdbUpdate(rtdbRef(db, msg.path), resolveRtdbSentinels(msg.values));
|
|
1080
|
+
await bestEffortFlush(ctx);
|
|
1081
|
+
ok(port, msg.id, null);
|
|
1082
|
+
}
|
|
1083
|
+
catch (e) {
|
|
1084
|
+
fail(port, msg.id, e);
|
|
1085
|
+
}
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
case 'rtdb.remove': {
|
|
1089
|
+
try {
|
|
1090
|
+
const db = lensRtdb(ctx, msg.actAs, port);
|
|
1091
|
+
await rtdbRemove(rtdbRef(db, msg.path));
|
|
1092
|
+
await bestEffortFlush(ctx);
|
|
1093
|
+
ok(port, msg.id, null);
|
|
1094
|
+
}
|
|
1095
|
+
catch (e) {
|
|
1096
|
+
fail(port, msg.id, e);
|
|
1097
|
+
}
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
case 'rtdb.push': {
|
|
1101
|
+
try {
|
|
1102
|
+
const db = lensRtdb(ctx, msg.actAs, port);
|
|
1103
|
+
const childPath = `${msg.path}/${msg.key}`;
|
|
1104
|
+
if (msg.value !== undefined) {
|
|
1105
|
+
await rtdbSet(rtdbRef(db, childPath), resolveRtdbSentinels(msg.value));
|
|
1106
|
+
await bestEffortFlush(ctx);
|
|
1107
|
+
}
|
|
1108
|
+
const normalizedPath = `/${childPath.split('/').filter(Boolean).join('/')}`;
|
|
1109
|
+
ok(port, msg.id, { key: msg.key, path: normalizedPath });
|
|
1110
|
+
}
|
|
1111
|
+
catch (e) {
|
|
1112
|
+
fail(port, msg.id, e);
|
|
1113
|
+
}
|
|
1114
|
+
break;
|
|
1115
|
+
}
|
|
1116
|
+
case 'rtdb.adminSnapshot': {
|
|
1117
|
+
try {
|
|
1118
|
+
ok(port, msg.id, rtdbSandbox.snapshotState(lensRtdb(ctx, { mode: 'admin' }, port)));
|
|
1119
|
+
}
|
|
1120
|
+
catch (e) {
|
|
1121
|
+
fail(port, msg.id, e);
|
|
1122
|
+
}
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
case 'getVersion': {
|
|
1126
|
+
// The build hash is injected by the bundler (esbuild `define`). `typeof`
|
|
1127
|
+
// guards the non-bundled path (tests import the compiled host directly,
|
|
1128
|
+
// where the global is undefined) → reports 'dev'.
|
|
1129
|
+
ok(port, msg.id, {
|
|
1130
|
+
version: typeof __PYRIC_WORKER_VERSION__ !== 'undefined' ? __PYRIC_WORKER_VERSION__ : 'dev',
|
|
1131
|
+
instanceId: ctx.instanceId,
|
|
1132
|
+
});
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
case 'exportState': {
|
|
1136
|
+
// Phase 2 (transfer): serialize the FULL sandbox state to a portable bundle
|
|
1137
|
+
// string using the SAME chunk format the persist layer uses, so wrapper
|
|
1138
|
+
// types (Timestamp / Bytes / GeoPoint / VectorValue) round-trip. The string
|
|
1139
|
+
// crosses the MessagePort cleanly (unlike the raw snapshot object).
|
|
1140
|
+
const snap = ctx.sandbox.snapshot();
|
|
1141
|
+
ok(port, msg.id, { bundle: bundleRecords(serializeToBuckets(snap.firestore, snap.services, 0)) });
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
case 'importState': {
|
|
1145
|
+
// Phase 2 (clobber): replace this sandbox's ENTIRE state with the imported
|
|
1146
|
+
// bundle via the public loadSnapshot() (reset + rebuild firestore + restore
|
|
1147
|
+
// services; listeners re-evaluate, persist re-flushes).
|
|
1148
|
+
ctx.sandbox.loadSnapshot(deserializeFromBuckets(parseBundle(msg.bundle)));
|
|
1149
|
+
ok(port, msg.id, { ok: true });
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
case 'saveBranch': {
|
|
1153
|
+
// Phase 3: snapshot the live sandbox into a named branch bundle (raw idb).
|
|
1154
|
+
if (!ctx.sessionBackend) {
|
|
1155
|
+
ok(port, msg.id, { ok: false, error: 'no persistence backend' });
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
const snap = ctx.sandbox.snapshot();
|
|
1159
|
+
const bundle = bundleRecords(serializeToBuckets(snap.firestore, snap.services, 0));
|
|
1160
|
+
await ctx.sessionBackend.putRecords(BRANCH_PREFIX + msg.name, new Map([['bundle', { value: bundle }]]));
|
|
1161
|
+
const names = await listBranchNames(ctx.sessionBackend);
|
|
1162
|
+
if (!names.includes(msg.name))
|
|
1163
|
+
await writeBranchRegistry(ctx.sessionBackend, [...names, msg.name]);
|
|
1164
|
+
ok(port, msg.id, { ok: true });
|
|
1165
|
+
break;
|
|
1166
|
+
}
|
|
1167
|
+
case 'listBranches': {
|
|
1168
|
+
ok(port, msg.id, { branches: await listBranchNames(ctx.sessionBackend) });
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
case 'switchBranch': {
|
|
1172
|
+
// Phase 3: loadSnapshot the named branch bundle (a clobber).
|
|
1173
|
+
const rec = ctx.sessionBackend
|
|
1174
|
+
? (await ctx.sessionBackend.getRecord(BRANCH_PREFIX + msg.name, 'bundle'))
|
|
1175
|
+
: undefined;
|
|
1176
|
+
if (!rec?.value) {
|
|
1177
|
+
ok(port, msg.id, { ok: false, error: `no such branch: ${msg.name}` });
|
|
1178
|
+
break;
|
|
1179
|
+
}
|
|
1180
|
+
ctx.sandbox.loadSnapshot(deserializeFromBuckets(parseBundle(rec.value)));
|
|
1181
|
+
ok(port, msg.id, { ok: true });
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
case 'deleteBranch': {
|
|
1185
|
+
if (ctx.sessionBackend) {
|
|
1186
|
+
await ctx.sessionBackend.clear(BRANCH_PREFIX + msg.name);
|
|
1187
|
+
await writeBranchRegistry(ctx.sessionBackend, (await listBranchNames(ctx.sessionBackend)).filter((n) => n !== msg.name));
|
|
1188
|
+
}
|
|
1189
|
+
ok(port, msg.id, { ok: true });
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
case 'setPolicy': {
|
|
1193
|
+
// Store the dial's PolicyRequest as the worker-side runtime governance
|
|
1194
|
+
// (Pyric Studio F3). This is the source of truth Studio reflects + a
|
|
1195
|
+
// future in-worker agent runtime consults. It does NOT push into a
|
|
1196
|
+
// running bridge process — see the limitation note on `ctx.policy` /
|
|
1197
|
+
// `PolicyRequest`. Additive + idempotent: last write wins.
|
|
1198
|
+
ctx.policy = msg.policy;
|
|
1199
|
+
ok(port, msg.id, null);
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
case 'getPolicy': {
|
|
1203
|
+
// Read back the active runtime policy (null until the dial set one), so
|
|
1204
|
+
// Studio can reflect persisted state across reconnects within a worker
|
|
1205
|
+
// lifetime and a freshly-connecting port can hydrate the dial.
|
|
1206
|
+
ok(port, msg.id, ctx.policy ?? null);
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
case 'getSnapshot': {
|
|
1210
|
+
// Export the current sandbox snapshot (Pyric Studio rules re-run): Studio
|
|
1211
|
+
// forks it locally to test edited rules / re-issue as the user on a branch.
|
|
1212
|
+
ok(port, msg.id, ctx.sandbox.snapshot());
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
case 'storage.listAll': {
|
|
1216
|
+
// Object browse. `listAll` enforces `read` rules on the scanned prefix
|
|
1217
|
+
// under the op's lens (admin lens bypasses — see lensStorage).
|
|
1218
|
+
try {
|
|
1219
|
+
const storage = lensStorage(ctx, msg.actAs);
|
|
1220
|
+
const result = await storageListAll(storageRef(storage, msg.path));
|
|
1221
|
+
ok(port, msg.id, {
|
|
1222
|
+
items: result.items.map((r) => ({ fullPath: r.fullPath, name: r.name })),
|
|
1223
|
+
prefixes: result.prefixes.map((r) => ({ fullPath: r.fullPath, name: r.name })),
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
catch (e) {
|
|
1227
|
+
fail(port, msg.id, e);
|
|
1228
|
+
}
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
case 'storage.getMetadata': {
|
|
1232
|
+
try {
|
|
1233
|
+
const storage = lensStorage(ctx, msg.actAs);
|
|
1234
|
+
// FullMetadata is plain JSON (bucket/fullPath/name/size/contentType/...).
|
|
1235
|
+
ok(port, msg.id, await storageGetMetadata(storageRef(storage, msg.path)));
|
|
1236
|
+
}
|
|
1237
|
+
catch (e) {
|
|
1238
|
+
fail(port, msg.id, e);
|
|
1239
|
+
}
|
|
1240
|
+
break;
|
|
1241
|
+
}
|
|
1242
|
+
case 'storage.getBlob': {
|
|
1243
|
+
// MessagePort-ONLY: the Blob structured-clones to in-page callers
|
|
1244
|
+
// (Studio previews) but silently corrupts under the JSON WS relay — the
|
|
1245
|
+
// bridge client rejects relaying it (binary-payload guard). Remote
|
|
1246
|
+
// callers use `storage.getBytes` (base64) instead.
|
|
1247
|
+
try {
|
|
1248
|
+
const storage = lensStorage(ctx, msg.actAs);
|
|
1249
|
+
ok(port, msg.id, await storageGetBlob(storageRef(storage, msg.path)));
|
|
1250
|
+
}
|
|
1251
|
+
catch (e) {
|
|
1252
|
+
fail(port, msg.id, e);
|
|
1253
|
+
}
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
case 'storage.putBytes': {
|
|
1257
|
+
// Byte upload (remote sandbox, slice 2). Decode-end size cap: reject an
|
|
1258
|
+
// oversized base64 string BEFORE materializing its bytes, then re-check
|
|
1259
|
+
// the exact decoded length. Rules enforce under the op's lens; the
|
|
1260
|
+
// upload emits `service_mutation` events, so server writes get Studio
|
|
1261
|
+
// provenance for free.
|
|
1262
|
+
try {
|
|
1263
|
+
if (msg.dataB64.length > MAX_STORAGE_OP_B64_LENGTH) {
|
|
1264
|
+
throw storagePayloadTooLarge(Math.floor(msg.dataB64.length * 0.75), `storage.putBytes payload for '${msg.path}'`);
|
|
1265
|
+
}
|
|
1266
|
+
const bytes = base64ToBytes(msg.dataB64);
|
|
1267
|
+
if (bytes.byteLength > MAX_STORAGE_OP_BYTES) {
|
|
1268
|
+
throw storagePayloadTooLarge(bytes.byteLength, `storage.putBytes payload for '${msg.path}'`);
|
|
1269
|
+
}
|
|
1270
|
+
const storage = lensStorage(ctx, msg.actAs);
|
|
1271
|
+
const result = await storageUploadBytes(storageRef(storage, msg.path), bytes, toSettableMetadata(msg));
|
|
1272
|
+
// FullMetadata — plain JSON, relay-safe.
|
|
1273
|
+
ok(port, msg.id, result.metadata);
|
|
1274
|
+
}
|
|
1275
|
+
catch (e) {
|
|
1276
|
+
fail(port, msg.id, e);
|
|
1277
|
+
}
|
|
1278
|
+
break;
|
|
1279
|
+
}
|
|
1280
|
+
case 'storage.getBytes': {
|
|
1281
|
+
// JSON-safe byte download (remote sandbox, slice 2): base64 in the
|
|
1282
|
+
// result. Encode-end size cap so a big browser-side object can't blow
|
|
1283
|
+
// up the relay. Metadata is read alongside for contentType (both reads
|
|
1284
|
+
// run under the same lens; rule-eval order keeps `unauthorized`
|
|
1285
|
+
// superseding `not-found`, matching pyric/storage).
|
|
1286
|
+
try {
|
|
1287
|
+
const storage = lensStorage(ctx, msg.actAs);
|
|
1288
|
+
const r = storageRef(storage, msg.path);
|
|
1289
|
+
const meta = await storageGetMetadata(r);
|
|
1290
|
+
if (meta.size > MAX_STORAGE_OP_BYTES) {
|
|
1291
|
+
throw storagePayloadTooLarge(meta.size, `object '${msg.path}'`);
|
|
1292
|
+
}
|
|
1293
|
+
const buf = await storageGetBytes(r);
|
|
1294
|
+
if (buf.byteLength > MAX_STORAGE_OP_BYTES) {
|
|
1295
|
+
throw storagePayloadTooLarge(buf.byteLength, `object '${msg.path}'`);
|
|
1296
|
+
}
|
|
1297
|
+
ok(port, msg.id, {
|
|
1298
|
+
dataB64: bytesToBase64(new Uint8Array(buf)),
|
|
1299
|
+
contentType: meta.contentType,
|
|
1300
|
+
size: buf.byteLength,
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
catch (e) {
|
|
1304
|
+
fail(port, msg.id, e);
|
|
1305
|
+
}
|
|
1306
|
+
break;
|
|
1307
|
+
}
|
|
1308
|
+
case 'storage.deleteObject': {
|
|
1309
|
+
// `pyric/storage`'s sandbox delete is idempotent (no-op on missing) —
|
|
1310
|
+
// matching the pyric-admin local arm's delete semantics. Rules enforce
|
|
1311
|
+
// `write` under the op's lens; deletes emit `service_mutation` events.
|
|
1312
|
+
try {
|
|
1313
|
+
const storage = lensStorage(ctx, msg.actAs);
|
|
1314
|
+
await storageDeleteObject(storageRef(storage, msg.path));
|
|
1315
|
+
ok(port, msg.id, null);
|
|
1316
|
+
}
|
|
1317
|
+
catch (e) {
|
|
1318
|
+
fail(port, msg.id, e);
|
|
1319
|
+
}
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
default: {
|
|
1323
|
+
// Auth ops (`auth.*`) are routed to handleAuthOp by handleMessage before
|
|
1324
|
+
// reaching here, so any method landing in this default is genuinely
|
|
1325
|
+
// unknown. (We can't use a `never` exhaustiveness check anymore because
|
|
1326
|
+
// OpMessage now includes the auth variants this switch deliberately skips.)
|
|
1327
|
+
fail(port, msg.id, new Error(`Unknown method: ${String(msg.method)}`));
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function applyWriteToBatch(db, batch, w) {
|
|
1332
|
+
const b = batch;
|
|
1333
|
+
const ref = pyricDoc(db, w.path);
|
|
1334
|
+
if (w.method === 'set') {
|
|
1335
|
+
const data = prepareWriteData(w.data);
|
|
1336
|
+
b.set(ref, data, w.options);
|
|
1337
|
+
}
|
|
1338
|
+
else if (w.method === 'update') {
|
|
1339
|
+
const data = prepareWriteData(w.data);
|
|
1340
|
+
b.update(ref, data);
|
|
1341
|
+
}
|
|
1342
|
+
else if (w.method === 'delete') {
|
|
1343
|
+
b.delete(ref);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
const _sessionSubs = new WeakMap();
|
|
1347
|
+
function sessionSubsFor(ctx, port) {
|
|
1348
|
+
let byPort = _sessionSubs.get(ctx);
|
|
1349
|
+
if (!byPort) {
|
|
1350
|
+
byPort = new Map();
|
|
1351
|
+
_sessionSubs.set(ctx, byPort);
|
|
1352
|
+
}
|
|
1353
|
+
let bySubId = byPort.get(port);
|
|
1354
|
+
if (!bySubId) {
|
|
1355
|
+
bySubId = new Map();
|
|
1356
|
+
byPort.set(port, bySubId);
|
|
1357
|
+
}
|
|
1358
|
+
return bySubId;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Re-establish a port's session-bound listeners under its CURRENT session —
|
|
1362
|
+
* invoked (via the ctx hook) on every port session change. Mirrors prod's
|
|
1363
|
+
* stream re-establishment on auth transitions: each listener is torn down and
|
|
1364
|
+
* re-registered through `sessionDb`, so the fresh evaluation either delivers
|
|
1365
|
+
* a snapshot (allowed) or a `permission-denied` snap-error (revoked). A
|
|
1366
|
+
* signed-out page no longer keeps receiving auth-gated data.
|
|
1367
|
+
*/
|
|
1368
|
+
function resubscribeSessionSubs(ctx, port) {
|
|
1369
|
+
const bound = _sessionSubs.get(ctx)?.get(port);
|
|
1370
|
+
if (!bound || bound.size === 0)
|
|
1371
|
+
return;
|
|
1372
|
+
const portSubs = ctx.subs.get(port);
|
|
1373
|
+
for (const [subId, msg] of [...bound]) {
|
|
1374
|
+
const unsub = portSubs?.get(subId);
|
|
1375
|
+
if (unsub)
|
|
1376
|
+
unsub();
|
|
1377
|
+
portSubs?.delete(subId);
|
|
1378
|
+
bound.delete(subId); // handleSub/handleRtdbSub re-records it
|
|
1379
|
+
if (isRtdbSub(msg)) {
|
|
1380
|
+
handleRtdbSub(ctx, port, msg);
|
|
1381
|
+
}
|
|
1382
|
+
else {
|
|
1383
|
+
handleSub(ctx, port, msg);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function handleSub(ctx, port, msg) {
|
|
1388
|
+
// Resolve the listener's data handle through the SAME lens path ops use
|
|
1389
|
+
// (Pyric Studio F4 "watch as user"): `{ mode: 'as', uid }` registers the
|
|
1390
|
+
// listener as that user so its rule evals impersonate, `{ mode: 'admin' }`
|
|
1391
|
+
// bypasses rules. Absent ⇒ the PORT'S SESSION (#754), so an app listener
|
|
1392
|
+
// evaluates rules as whoever this tab signed in as.
|
|
1393
|
+
const db = msg.actAs ? lensDb(ctx, msg.actAs) : sessionDb(ctx, port);
|
|
1394
|
+
ensurePortSubs(ctx, port);
|
|
1395
|
+
const portSubs = ctx.subs.get(port);
|
|
1396
|
+
if (portSubs.has(msg.subId))
|
|
1397
|
+
return; // idempotent
|
|
1398
|
+
// Session-bound listeners re-establish on this port's auth transitions.
|
|
1399
|
+
if (!msg.actAs) {
|
|
1400
|
+
ctx.resubscribePortSubs ??= (p) => resubscribeSessionSubs(ctx, p);
|
|
1401
|
+
sessionSubsFor(ctx, port).set(msg.subId, msg);
|
|
1402
|
+
}
|
|
1403
|
+
let target;
|
|
1404
|
+
let unsub;
|
|
1405
|
+
try {
|
|
1406
|
+
target = resolveTarget(db, msg.target);
|
|
1407
|
+
unsub = registerListener(ctx, port, msg, target);
|
|
1408
|
+
}
|
|
1409
|
+
catch (e) {
|
|
1410
|
+
// resolveTarget / onSnapshot can throw synchronously (e.g. an invalid
|
|
1411
|
+
// query or a rules-rejected target). Deliver it to the client's onSnapshot
|
|
1412
|
+
// error callback as a snap-error instead of letting it escape handleMessage
|
|
1413
|
+
// as an unhandled rejection (which would silently deliver NOTHING).
|
|
1414
|
+
post(port, { t: 'snap', subId: msg.subId, value: { __error: serializeError(e) } });
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
portSubs.set(msg.subId, unsub);
|
|
1418
|
+
}
|
|
1419
|
+
function handleRtdbSub(ctx, port, msg) {
|
|
1420
|
+
ensurePortSubs(ctx, port);
|
|
1421
|
+
const portSubs = ctx.subs.get(port);
|
|
1422
|
+
if (portSubs.has(msg.subId))
|
|
1423
|
+
return;
|
|
1424
|
+
if (!msg.actAs) {
|
|
1425
|
+
ctx.resubscribePortSubs ??= (p) => resubscribeSessionSubs(ctx, p);
|
|
1426
|
+
sessionSubsFor(ctx, port).set(msg.subId, msg);
|
|
1427
|
+
}
|
|
1428
|
+
try {
|
|
1429
|
+
const ref = rtdbRef(lensRtdb(ctx, msg.actAs, port), msg.target.path);
|
|
1430
|
+
const unsub = rtdbOnValue(ref, (snap) => post(port, { t: 'snap', subId: msg.subId, value: rtdbSnapToWire(snap) }));
|
|
1431
|
+
portSubs.set(msg.subId, unsub);
|
|
1432
|
+
}
|
|
1433
|
+
catch (e) {
|
|
1434
|
+
post(port, { t: 'snap', subId: msg.subId, value: { __error: serializeError(e) } });
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
/** Register the real sandbox listener for a resolved target; returns its unsub.
|
|
1438
|
+
* Split out of handleSub so the throwing surface (resolveTarget + onSnapshot)
|
|
1439
|
+
* is inside handleSub's try/catch. */
|
|
1440
|
+
function registerListener(_ctx, port, msg, target) {
|
|
1441
|
+
return onSnapshot(target, (snap) => {
|
|
1442
|
+
// Detect doc vs query snapshot by shape.
|
|
1443
|
+
const snapAny = snap;
|
|
1444
|
+
if (Array.isArray(snapAny.docs)) {
|
|
1445
|
+
// Query snapshot
|
|
1446
|
+
const docs = snapAny.docs.map((d) => serializeDocSnap(d));
|
|
1447
|
+
post(port, { t: 'snap', subId: msg.subId, value: { docs } });
|
|
1448
|
+
}
|
|
1449
|
+
else if (snapAny.id !== undefined) {
|
|
1450
|
+
// Doc snapshot
|
|
1451
|
+
post(port, {
|
|
1452
|
+
t: 'snap',
|
|
1453
|
+
subId: msg.subId,
|
|
1454
|
+
value: serializeDocSnap(snapAny),
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
}, (err) => {
|
|
1458
|
+
// Snapshot listener error (e.g. rules changed to deny).
|
|
1459
|
+
// We forward as a snap with an __error field so the client can
|
|
1460
|
+
// surface it to the original onSnapshot error callback.
|
|
1461
|
+
post(port, { t: 'snap', subId: msg.subId, value: { __error: serializeError(err) } });
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
function handleUnsub(ctx, port, msg) {
|
|
1465
|
+
// Drop the session-bound record first — even when the live listener never
|
|
1466
|
+
// registered (it errored at sub time), the record must not resurrect the
|
|
1467
|
+
// sub on a later session change.
|
|
1468
|
+
_sessionSubs.get(ctx)?.get(port)?.delete(msg.subId);
|
|
1469
|
+
const portSubs = ctx.subs.get(port);
|
|
1470
|
+
if (!portSubs)
|
|
1471
|
+
return;
|
|
1472
|
+
const unsub = portSubs.get(msg.subId);
|
|
1473
|
+
if (!unsub)
|
|
1474
|
+
return;
|
|
1475
|
+
unsub();
|
|
1476
|
+
portSubs.delete(msg.subId);
|
|
1477
|
+
}
|
|
1478
|
+
// ─── Main dispatch ────────────────────────────────────────────────────────
|
|
1479
|
+
/**
|
|
1480
|
+
* Handle one inbound message from a port.
|
|
1481
|
+
*
|
|
1482
|
+
* This is the primary unit-testable seam. Tests create a real `HostCtx`
|
|
1483
|
+
* backed by an in-memory pyric sandbox and call this function directly
|
|
1484
|
+
* with fake port objects, exercising the full op+subscription lifecycle
|
|
1485
|
+
* without a real SharedWorker.
|
|
1486
|
+
*/
|
|
1487
|
+
/**
|
|
1488
|
+
* Agent tool-call dispatch. The bridge peer forwards `tool` messages so the
|
|
1489
|
+
* agent runs the canonical sandbox tool set against THIS worker's sandbox (the
|
|
1490
|
+
* same instance the app + Studio use) instead of a separate in-page backend.
|
|
1491
|
+
* Replies with a `res` whose value is the `{ ok, summary, data }` result.
|
|
1492
|
+
*/
|
|
1493
|
+
async function handleTool(ctx, port, msg) {
|
|
1494
|
+
try {
|
|
1495
|
+
ctx.toolDispatch ??= buildSandboxDispatcher(ctx.sandbox);
|
|
1496
|
+
const result = await ctx.toolDispatch(msg.name, msg.args ?? {});
|
|
1497
|
+
// Pre-serialize via JSON BEFORE the structured-clone hop over the port. Read
|
|
1498
|
+
// results carry real firebase wrapper instances (Timestamp/GeoPoint/Bytes/
|
|
1499
|
+
// VectorValue) whose toJSON() produces the canonical agent-facing shapes.
|
|
1500
|
+
// structuredClone would strip those prototypes (losing toJSON) and post
|
|
1501
|
+
// mangled internals, and would throw DataCloneError on any non-cloneable
|
|
1502
|
+
// field. JSON.stringify here runs toJSON() while the instances are intact and
|
|
1503
|
+
// yields a plain, clone-safe object — matching the in-page path, which
|
|
1504
|
+
// JSON-stringified at the bridge. A serialization error lands in the catch
|
|
1505
|
+
// below, never in postMessage.
|
|
1506
|
+
ok(port, msg.id, JSON.parse(JSON.stringify(result)));
|
|
1507
|
+
}
|
|
1508
|
+
catch (e) {
|
|
1509
|
+
fail(port, msg.id, e);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
export async function handleMessage(ctx, port, msg) {
|
|
1513
|
+
// Op provenance, stamped MECHANICALLY at dispatch:
|
|
1514
|
+
// - `actor`: a client that DECLARED itself the issuer (`issuer: 'studio'`,
|
|
1515
|
+
// stamped by Studio's worker client — see `setOpIssuer` in client.ts)
|
|
1516
|
+
// gets `actor: { kind: 'studio' }`. Bridge-relayed frames are forwarded
|
|
1517
|
+
// verbatim without the stamp (client.ts `relayWorkerOp`), so remote
|
|
1518
|
+
// admin/agent traffic is never mislabeled as Studio's.
|
|
1519
|
+
// - `authLens`: the op's EFFECTIVE lens (`msg.actAs`) is stamped whenever
|
|
1520
|
+
// present — independent of issuer declaration, because admin is admin
|
|
1521
|
+
// regardless of who asked (an agent tool relay's admin op must classify
|
|
1522
|
+
// as a rules BYPASS in Traffic, not as a rules allow). Without this,
|
|
1523
|
+
// Firestore admin-lens ops carried no lens on their events and
|
|
1524
|
+
// `verdictFor` mislabeled the bypass as ALLOW (the RTDB/Firestore
|
|
1525
|
+
// asymmetry the traffic-metrics work flagged).
|
|
1526
|
+
// Events an emitter already stamped win over the window (stampProvenance
|
|
1527
|
+
// semantics: the event's own field always beats the ambient default).
|
|
1528
|
+
const issuer = msg.issuer;
|
|
1529
|
+
const actAs = msg.actAs;
|
|
1530
|
+
if ((issuer === 'studio' || actAs) && ctx.sandbox.runWithProvenance) {
|
|
1531
|
+
return ctx.sandbox.runWithProvenance({
|
|
1532
|
+
...(issuer === 'studio' ? { actor: { kind: 'studio' } } : {}),
|
|
1533
|
+
...(actAs ? { authLens: actAs } : {}),
|
|
1534
|
+
}, () => dispatchMessage(ctx, port, msg));
|
|
1535
|
+
}
|
|
1536
|
+
return dispatchMessage(ctx, port, msg);
|
|
1537
|
+
}
|
|
1538
|
+
async function dispatchMessage(ctx, port, msg) {
|
|
1539
|
+
if (msg.t === 'op') {
|
|
1540
|
+
if (isAuthOp(msg.method)) {
|
|
1541
|
+
await handleAuthOp(ctx, port, msg);
|
|
1542
|
+
}
|
|
1543
|
+
else {
|
|
1544
|
+
await handleOp(ctx, port, msg);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
else if (msg.t === 'sub') {
|
|
1548
|
+
if (isAuthSub(msg)) {
|
|
1549
|
+
handleAuthSub(ctx, port, msg);
|
|
1550
|
+
}
|
|
1551
|
+
else if (isEventSub(msg)) {
|
|
1552
|
+
handleEventSub(ctx, port, msg);
|
|
1553
|
+
}
|
|
1554
|
+
else if (isRtdbSub(msg)) {
|
|
1555
|
+
handleRtdbSub(ctx, port, msg);
|
|
1556
|
+
}
|
|
1557
|
+
else {
|
|
1558
|
+
handleSub(ctx, port, msg);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
else if (msg.t === 'unsub') {
|
|
1562
|
+
// An unsub may target an auth sub, an event-stream sub, or a Firestore
|
|
1563
|
+
// listener — try the cheap routing registries first, then fall through to
|
|
1564
|
+
// the Firestore listener teardown.
|
|
1565
|
+
if (!handleAuthUnsub(ctx, port, msg.subId) &&
|
|
1566
|
+
!handleEventUnsub(ctx, port, msg.subId)) {
|
|
1567
|
+
handleUnsub(ctx, port, msg);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
else if (msg.t === 'tool') {
|
|
1571
|
+
await handleTool(ctx, port, msg);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
// ─── Port cleanup ─────────────────────────────────────────────────────────
|
|
1575
|
+
/**
|
|
1576
|
+
* Tear down all subscriptions for a disconnected port.
|
|
1577
|
+
* Called when a port's `close` event fires (browser best-effort) or
|
|
1578
|
+
* when the entry point explicitly cleans up a port.
|
|
1579
|
+
*/
|
|
1580
|
+
export function cleanupPort(ctx, port) {
|
|
1581
|
+
// Drop the port's auth subscriptions (routing entries — no real listener
|
|
1582
|
+
// to tear down), its per-port session, and its session-bound sub records
|
|
1583
|
+
// (#754).
|
|
1584
|
+
authSubsFor(ctx).delete(port);
|
|
1585
|
+
cleanupPortSession(ctx, port);
|
|
1586
|
+
_sessionSubs.get(ctx)?.delete(port);
|
|
1587
|
+
// Drop the port's event-stream subscriptions too (also routing entries off
|
|
1588
|
+
// the single shared `sandbox.onEvent` subscription — nothing to unsubscribe,
|
|
1589
|
+
// just stop fanning out to a dead port).
|
|
1590
|
+
eventSubsFor(ctx).delete(port);
|
|
1591
|
+
const portSubs = ctx.subs.get(port);
|
|
1592
|
+
if (!portSubs)
|
|
1593
|
+
return;
|
|
1594
|
+
for (const unsub of portSubs.values()) {
|
|
1595
|
+
unsub();
|
|
1596
|
+
}
|
|
1597
|
+
ctx.subs.delete(port);
|
|
1598
|
+
}
|
|
1599
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────
|
|
1600
|
+
function ensurePortSubs(ctx, port) {
|
|
1601
|
+
if (!ctx.subs.has(port)) {
|
|
1602
|
+
ctx.subs.set(port, new Map());
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
//# sourceMappingURL=host.js.map
|