@pyric/cli 0.1.0-alpha.20 → 0.1.0-alpha.22
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 +64 -89
- package/dist/assurance/.generated/conformance-verdicts.d.ts +238 -1
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
- package/dist/assurance/.generated/conformance-verdicts.js +238 -1
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
- package/dist/bridge/client/dispatch.d.ts +25 -19
- package/dist/bridge/client/dispatch.d.ts.map +1 -1
- package/dist/bridge/client/dispatch.js +41 -48
- package/dist/bridge/client/dispatch.js.map +1 -1
- package/dist/bridge/client/tool-family-factories.d.ts +26 -0
- package/dist/bridge/client/tool-family-factories.d.ts.map +1 -0
- package/dist/bridge/client/tool-family-factories.js +10 -0
- package/dist/bridge/client/tool-family-factories.js.map +1 -0
- package/dist/bridge/server/mcp-contract.d.ts +7 -6
- package/dist/bridge/server/mcp-contract.d.ts.map +1 -1
- package/dist/bridge/server/mcp-contract.js +9 -48
- package/dist/bridge/server/mcp-contract.js.map +1 -1
- package/dist/bridge/server/peer.d.ts +11 -1
- package/dist/bridge/server/peer.d.ts.map +1 -1
- package/dist/bridge/server/peer.js +22 -1
- package/dist/bridge/server/peer.js.map +1 -1
- package/dist/bridge/server/tool-family-factories.d.ts +30 -0
- package/dist/bridge/server/tool-family-factories.d.ts.map +1 -0
- package/dist/bridge/server/tool-family-factories.js +22 -0
- package/dist/bridge/server/tool-family-factories.js.map +1 -0
- package/dist/bridge/server/tool-metadata.d.ts +17 -18
- package/dist/bridge/server/tool-metadata.d.ts.map +1 -1
- package/dist/bridge/server/tool-metadata.js +24 -59
- package/dist/bridge/server/tool-metadata.js.map +1 -1
- package/dist/bridge/tool-families.d.ts +48 -0
- package/dist/bridge/tool-families.d.ts.map +1 -0
- package/dist/bridge/tool-families.generated.d.ts +32 -0
- package/dist/bridge/tool-families.generated.d.ts.map +1 -0
- package/dist/bridge/tool-families.generated.js +15 -0
- package/dist/bridge/tool-families.generated.js.map +1 -0
- package/dist/bridge/tool-families.js +51 -0
- package/dist/bridge/tool-families.js.map +1 -0
- package/dist/bridge/tool-family-records/conformance.d.ts +7 -0
- package/dist/bridge/tool-family-records/conformance.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/conformance.js +6 -0
- package/dist/bridge/tool-family-records/conformance.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-data.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-data.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-data.js +15 -0
- package/dist/bridge/tool-family-records/firestore-data.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-inspect.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-inspect.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-inspect.js +6 -0
- package/dist/bridge/tool-family-records/firestore-inspect.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-rules.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-rules.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-rules.js +15 -0
- package/dist/bridge/tool-family-records/firestore-rules.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-simulator.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-simulator.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-simulator.js +16 -0
- package/dist/bridge/tool-family-records/firestore-simulator.js.map +1 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.d.ts +7 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.js +6 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.js.map +1 -0
- package/dist/bundler/externals.d.ts +37 -0
- package/dist/bundler/externals.d.ts.map +1 -0
- package/dist/bundler/externals.js +52 -0
- package/dist/bundler/externals.js.map +1 -0
- package/dist/bundler/index.d.ts +17 -0
- package/dist/bundler/index.d.ts.map +1 -0
- package/dist/bundler/index.js +17 -0
- package/dist/bundler/index.js.map +1 -0
- package/dist/cli/inlined-sdk-scanner.d.ts +6 -0
- package/dist/cli/inlined-sdk-scanner.d.ts.map +1 -0
- package/dist/cli/inlined-sdk-scanner.js +82 -0
- package/dist/cli/inlined-sdk-scanner.js.map +1 -0
- package/dist/cli/sandbox-interlock.d.ts +97 -0
- package/dist/cli/sandbox-interlock.d.ts.map +1 -0
- package/dist/cli/sandbox-interlock.js +112 -0
- package/dist/cli/sandbox-interlock.js.map +1 -0
- package/dist/cli/sandbox-runner.d.ts +20 -12
- package/dist/cli/sandbox-runner.d.ts.map +1 -1
- package/dist/cli/sandbox-runner.js +24 -21
- package/dist/cli/sandbox-runner.js.map +1 -1
- package/dist/cli/serve.d.ts +6 -10
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +57 -77
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/unsupported-runtime.d.ts +27 -0
- package/dist/cli/unsupported-runtime.d.ts.map +1 -0
- package/dist/cli/unsupported-runtime.js +63 -0
- package/dist/cli/unsupported-runtime.js.map +1 -0
- package/dist/conformance/.generated/can-i-use-browser.d.ts +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +1 -1
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
- package/dist/conformance/.generated/conformance-docs.js +1 -1
- package/dist/conformance/.generated/conformance-docs.js.map +1 -1
- package/dist/functions-rtdb/child.d.ts +37 -0
- package/dist/functions-rtdb/child.d.ts.map +1 -1
- package/dist/functions-rtdb/child.js +90 -21
- package/dist/functions-rtdb/child.js.map +1 -1
- package/dist/functions-rtdb/development-runtime.d.ts +3 -0
- package/dist/functions-rtdb/development-runtime.d.ts.map +1 -1
- package/dist/functions-rtdb/development-runtime.js +1 -0
- package/dist/functions-rtdb/development-runtime.js.map +1 -1
- package/dist/google-endpoints.d.ts +67 -0
- package/dist/google-endpoints.d.ts.map +1 -0
- package/dist/google-endpoints.js +119 -0
- package/dist/google-endpoints.js.map +1 -0
- package/dist/next/runtime-env.d.ts.map +1 -1
- package/dist/next/runtime-env.js +5 -0
- package/dist/next/runtime-env.js.map +1 -1
- package/dist/next/with-pyric.d.ts +1 -1
- package/dist/next/with-pyric.js +1 -1
- package/dist/register/beacon.d.ts +69 -0
- package/dist/register/beacon.d.ts.map +1 -0
- package/dist/register/beacon.js +199 -0
- package/dist/register/beacon.js.map +1 -0
- package/dist/register/index.d.ts.map +1 -1
- package/dist/register/index.js +65 -6
- package/dist/register/index.js.map +1 -1
- package/dist/register/net-guard.d.ts +110 -0
- package/dist/register/net-guard.d.ts.map +1 -0
- package/dist/register/net-guard.js +462 -0
- package/dist/register/net-guard.js.map +1 -0
- package/dist/serve/ai-diagnostics-relay.d.ts +63 -0
- package/dist/serve/ai-diagnostics-relay.d.ts.map +1 -0
- package/dist/serve/ai-diagnostics-relay.js +125 -0
- package/dist/serve/ai-diagnostics-relay.js.map +1 -0
- package/dist/serve/ai-proxy.d.ts +92 -0
- package/dist/serve/ai-proxy.d.ts.map +1 -0
- package/dist/serve/ai-proxy.js +269 -0
- package/dist/serve/ai-proxy.js.map +1 -0
- package/dist/serve/ai-status.d.ts +44 -0
- package/dist/serve/ai-status.d.ts.map +1 -0
- package/dist/serve/ai-status.js +77 -0
- package/dist/serve/ai-status.js.map +1 -0
- package/dist/serve/ai-terminal-blocks.d.ts +97 -0
- package/dist/serve/ai-terminal-blocks.d.ts.map +1 -0
- package/dist/serve/ai-terminal-blocks.js +128 -0
- package/dist/serve/ai-terminal-blocks.js.map +1 -0
- package/dist/serve/ai-terminal-text.d.ts +36 -0
- package/dist/serve/ai-terminal-text.d.ts.map +1 -0
- package/dist/serve/ai-terminal-text.js +48 -0
- package/dist/serve/ai-terminal-text.js.map +1 -0
- package/dist/serve/beacon-route.d.ts +53 -0
- package/dist/serve/beacon-route.d.ts.map +1 -0
- package/dist/serve/beacon-route.js +101 -0
- package/dist/serve/beacon-route.js.map +1 -0
- package/dist/serve/entries/active-auth-registry.d.ts +16 -0
- package/dist/serve/entries/active-auth-registry.d.ts.map +1 -0
- package/dist/serve/entries/active-auth-registry.js +48 -0
- package/dist/serve/entries/active-auth-registry.js.map +1 -0
- package/dist/serve/entries/active-auth.d.ts +18 -0
- package/dist/serve/entries/active-auth.d.ts.map +1 -0
- package/dist/serve/entries/active-auth.js +84 -0
- package/dist/serve/entries/active-auth.js.map +1 -0
- package/dist/serve/entries/auth-helper-core.d.ts +1 -0
- package/dist/serve/entries/auth-helper-core.d.ts.map +1 -1
- package/dist/serve/entries/auth-helper-core.js +4 -0
- package/dist/serve/entries/auth-helper-core.js.map +1 -1
- package/dist/serve/entries/auth-helper-dom.d.ts.map +1 -1
- package/dist/serve/entries/auth-helper-dom.js +28 -2
- package/dist/serve/entries/auth-helper-dom.js.map +1 -1
- package/dist/serve/entries/auth.d.ts.map +1 -1
- package/dist/serve/entries/auth.js +8 -0
- package/dist/serve/entries/auth.js.map +1 -1
- package/dist/serve/entries/bridge-url.d.ts +1 -1
- package/dist/serve/entries/bridge-url.js +1 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +70 -4
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +9 -0
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/namespace.d.ts +14 -5
- package/dist/serve/namespace.d.ts.map +1 -1
- package/dist/serve/namespace.js +57 -124
- package/dist/serve/namespace.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts.map +1 -1
- package/dist/serve/runtime/chip-config.js +16 -2
- package/dist/serve/runtime/chip-config.js.map +1 -1
- package/dist/serve/runtime/chip-dialog.d.ts +18 -0
- package/dist/serve/runtime/chip-dialog.d.ts.map +1 -0
- package/dist/serve/runtime/chip-dialog.js +315 -0
- package/dist/serve/runtime/chip-dialog.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +2 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -1
- package/dist/serve/runtime/chip-install.js +9 -4
- package/dist/serve/runtime/chip-install.js.map +1 -1
- package/dist/serve/runtime/chip-user-search.d.ts +16 -0
- package/dist/serve/runtime/chip-user-search.d.ts.map +1 -0
- package/dist/serve/runtime/chip-user-search.js +354 -0
- package/dist/serve/runtime/chip-user-search.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +10 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -1
- package/dist/serve/runtime/chip.js +118 -15
- package/dist/serve/runtime/chip.js.map +1 -1
- package/dist/serve/runtime/identity.d.ts +17 -0
- package/dist/serve/runtime/identity.d.ts.map +1 -0
- package/dist/serve/runtime/identity.js +10 -0
- package/dist/serve/runtime/identity.js.map +1 -0
- package/dist/serve/sandbox-session.d.ts +7 -0
- package/dist/serve/sandbox-session.d.ts.map +1 -1
- package/dist/serve/sandbox-session.js +2 -0
- package/dist/serve/sandbox-session.js.map +1 -1
- package/dist/serve/site-ui/_astro/{RulesCodeEditor.XtDmHOlN.js → RulesCodeEditor.DsTAsKIa.js} +1 -1
- package/dist/serve/site-ui/_astro/app.CMmTxyhL.js +1 -0
- package/dist/serve/site-ui/_astro/app.DhALsVii.js +115 -0
- package/dist/serve/site-ui/_astro/{chess-showcase.B72XYeoh.js → chess-showcase.Bn_pP6UJ.js} +2 -2
- package/dist/serve/site-ui/_astro/{example-runtime.C25_lx6Z.js → example-runtime.CXopTGv7.js} +1 -1
- package/dist/serve/site-ui/_astro/sandbox-controls.rOuZTOUG.js +510 -0
- package/dist/serve/site-ui/_astro/{seed.Co0yUJY5.js → seed.lywYbXZ0.js} +2 -2
- package/dist/serve/site-ui/_astro/writes.BsPBG3dp.js +653 -0
- package/dist/serve/site-ui/auth/index.html +1 -1
- package/dist/serve/site-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/site-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/api-reference/index.html +8 -1
- package/dist/serve/site-ui/docs/api-reference.md +7 -0
- package/dist/serve/site-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/auth-compat/index.html +3 -3
- package/dist/serve/site-ui/docs/auth-compat.md +2 -2
- package/dist/serve/site-ui/docs/build/ai-logic/index.html +6 -1
- package/dist/serve/site-ui/docs/build/ai-logic.md +6 -0
- package/dist/serve/site-ui/docs/build/authentication/index.html +2 -2
- package/dist/serve/site-ui/docs/build/authentication.md +1 -1
- package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +4 -2
- package/dist/serve/site-ui/docs/build/cloud-firestore.md +4 -0
- package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/site-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/site-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/site-ui/docs/conformance-scores/index.html +14 -2
- package/dist/serve/site-ui/docs/conformance-scores.md +13 -1
- package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/database-compat/index.html +44 -44
- package/dist/serve/site-ui/docs/database-compat.md +43 -43
- package/dist/serve/site-ui/docs/examples/chess/index.html +1 -1
- package/dist/serve/site-ui/docs/examples/index.html +1 -1
- package/dist/serve/site-ui/docs/firestore-compat/index.html +4 -4
- package/dist/serve/site-ui/docs/firestore-compat.md +3 -3
- package/dist/serve/site-ui/docs/firestore-flutter-compat/index.html +319 -0
- package/dist/serve/site-ui/docs/firestore-flutter-compat.md +344 -0
- package/dist/serve/site-ui/docs/firestore-kotlin-compat/index.html +319 -0
- package/dist/serve/site-ui/docs/firestore-kotlin-compat.md +344 -0
- package/dist/serve/site-ui/docs/firestore-swift-compat/index.html +490 -0
- package/dist/serve/site-ui/docs/firestore-swift-compat.md +517 -0
- package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/backend-bundlers/index.html +105 -0
- package/dist/serve/site-ui/docs/get-started/backend-bundlers.md +165 -0
- package/dist/serve/site-ui/docs/get-started/flutter/index.html +98 -0
- package/dist/serve/site-ui/docs/get-started/flutter.md +105 -0
- package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/nextjs/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/start-building/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/vite/index.html +1 -1
- package/dist/serve/site-ui/docs/index.json +3211 -1471
- package/dist/serve/site-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +4 -3
- package/dist/serve/site-ui/docs/observe/resolve-runtime-status.md +3 -1
- package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/site-ui/docs/observe/switch-and-impersonate-identities/index.html +110 -0
- package/dist/serve/site-ui/docs/observe/switch-and-impersonate-identities.md +134 -0
- package/dist/serve/site-ui/docs/overview/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-analytics-reference-api/index.html +124 -0
- package/dist/serve/site-ui/docs/pyric-analytics-reference-api.md +287 -0
- package/dist/serve/site-ui/docs/pyric-app-check-reference-api/index.html +87 -0
- package/dist/serve/site-ui/docs/pyric-app-check-reference-api.md +199 -0
- package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +6 -2
- package/dist/serve/site-ui/docs/pyric-auth-reference-api.md +2 -1
- package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api/index.html +56 -0
- package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api.md +72 -0
- package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +4 -1
- package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api.md +3 -0
- package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +4 -1
- package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api.md +3 -0
- package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-cli-next-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +34 -9
- package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api.md +16 -8
- package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +133 -93
- package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api.md +61 -1
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api.md +2 -2
- package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-database-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api/index.html +559 -0
- package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api.md +1240 -0
- package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +40 -11
- package/dist/serve/site-ui/docs/pyric-firestore-reference-api.md +18 -8
- package/dist/serve/site-ui/docs/pyric-functions-reference-api/index.html +67 -0
- package/dist/serve/site-ui/docs/pyric-functions-reference-api.md +149 -0
- package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-performance-reference-api/index.html +35 -0
- package/dist/serve/site-ui/docs/pyric-performance-reference-api.md +69 -0
- package/dist/serve/site-ui/docs/pyric-remote-config-reference-api/index.html +132 -0
- package/dist/serve/site-ui/docs/pyric-remote-config-reference-api.md +307 -0
- package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +52 -17
- package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api.md +48 -9
- package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +239 -49
- package/dist/serve/site-ui/docs/pyric-sandbox-reference-api.md +148 -31
- package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +22 -4
- package/dist/serve/site-ui/docs/pyric-storage-reference-api.md +25 -3
- package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +26 -96
- package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api.md +20 -51
- package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +124 -26
- package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api.md +58 -5
- package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +4 -5
- package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api.md +3 -4
- package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/reference/cli/index.html +6 -3
- package/dist/serve/site-ui/docs/reference/cli.md +8 -2
- package/dist/serve/site-ui/docs/rules-compat/index.html +10 -2
- package/dist/serve/site-ui/docs/rules-compat.md +9 -1
- package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/site-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/site-ui/docs/storage-compat/index.html +13 -13
- package/dist/serve/site-ui/docs/storage-compat.md +12 -12
- package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +1 -1
- package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/site-ui/docs/tutorial/index.html +1 -1
- package/dist/serve/site-ui/examples/chess/index.html +1 -1
- package/dist/serve/site-ui/firestore/index.html +1 -1
- package/dist/serve/site-ui/llms.txt +13 -0
- package/dist/serve/site-ui/rtdb/index.html +1 -1
- package/dist/serve/site-ui/settings/index.html +1 -1
- package/dist/serve/site-ui/storage/index.html +1 -1
- package/dist/serve/site-ui/studio/index.html +1 -1
- package/dist/serve/site-ui/traffic/index.html +1 -1
- package/dist/serve/studio/disk-workspace.d.ts.map +1 -1
- package/dist/serve/studio/disk-workspace.js +64 -2
- package/dist/serve/studio/disk-workspace.js.map +1 -1
- package/dist/serve/vite-plugin.d.ts +7 -7
- package/dist/serve/vite-plugin.js +1 -1
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
- package/dist/serve/vite-sandbox-generation.js +12 -0
- package/dist/serve/vite-sandbox-generation.js.map +1 -1
- package/dist/serve/worker/client/auth.js +1 -2
- package/dist/serve/worker/client/auth.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +21 -1
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +79 -3
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client.d.ts +1 -1
- package/dist/serve/worker/client.d.ts.map +1 -1
- package/dist/serve/worker/client.js +1 -1
- package/dist/serve/worker/client.js.map +1 -1
- package/dist/serve/worker/context-resolver.d.ts +30 -0
- package/dist/serve/worker/context-resolver.d.ts.map +1 -0
- package/dist/serve/worker/context-resolver.js +44 -0
- package/dist/serve/worker/context-resolver.js.map +1 -0
- package/dist/serve/worker/entry.js +23 -19
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.d.ts +1 -0
- package/dist/serve/worker/host/core.d.ts.map +1 -1
- package/dist/serve/worker/host/core.js +12 -6
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/storage.js +2 -2
- package/dist/serve/worker/host-auth.js +1 -2
- package/dist/serve/worker/host-auth.js.map +1 -1
- package/dist/serve/worker/index.d.ts +1 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -2
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/port-lifecycle.d.ts +18 -0
- package/dist/serve/worker/port-lifecycle.d.ts.map +1 -0
- package/dist/serve/worker/port-lifecycle.js +28 -0
- package/dist/serve/worker/port-lifecycle.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +8 -2
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/verify/fixture.d.ts +1 -1
- package/package.json +16 -5
- package/src/assurance/.generated/conformance-verdicts.ts +238 -1
- package/src/bridge/client/dispatch.ts +52 -56
- package/src/bridge/client/tool-family-factories.ts +34 -0
- package/src/bridge/server/mcp-contract.ts +15 -59
- package/src/bridge/server/peer.ts +24 -1
- package/src/bridge/server/tool-family-factories.ts +40 -0
- package/src/bridge/server/tool-metadata.ts +29 -61
- package/src/bridge/tool-families.generated.ts +17 -0
- package/src/bridge/tool-families.ts +87 -0
- package/src/bridge/tool-family-records/conformance.ts +6 -0
- package/src/bridge/tool-family-records/firestore-data.ts +15 -0
- package/src/bridge/tool-family-records/firestore-inspect.ts +6 -0
- package/src/bridge/tool-family-records/firestore-rules.ts +15 -0
- package/src/bridge/tool-family-records/firestore-simulator.ts +16 -0
- package/src/bridge/tool-family-records/rtdb-inspection.ts +6 -0
- package/src/bundler/externals.ts +55 -0
- package/src/bundler/index.ts +21 -0
- package/src/cli/cli.test.ts +0 -44
- package/src/cli/inlined-sdk-scanner.ts +77 -0
- package/src/cli/sandbox-interlock.ts +167 -0
- package/src/cli/sandbox-runner.ts +37 -26
- package/src/cli/serve.ts +68 -69
- package/src/cli/unsupported-runtime.ts +68 -0
- package/src/conformance/.generated/can-i-use-browser.ts +2 -2
- package/src/conformance/.generated/can-i-use.ts +2 -2
- package/src/conformance/.generated/conformance-docs.ts +1 -1
- package/src/functions-rtdb/child.ts +115 -21
- package/src/functions-rtdb/development-runtime.ts +4 -0
- package/src/google-endpoints.ts +155 -0
- package/src/next/runtime-env.ts +5 -0
- package/src/next/with-pyric.ts +1 -1
- package/src/register/beacon.ts +232 -0
- package/src/register/index.ts +68 -6
- package/src/register/net-guard.ts +567 -0
- package/src/serve/ai-diagnostics-relay.ts +243 -0
- package/src/serve/ai-proxy.ts +327 -0
- package/src/serve/ai-status.ts +109 -0
- package/src/serve/ai-terminal-blocks.ts +210 -0
- package/src/serve/ai-terminal-text.ts +52 -0
- package/src/serve/beacon-route.ts +141 -0
- package/src/serve/entries/active-auth-registry.ts +56 -0
- package/src/serve/entries/active-auth.ts +100 -0
- package/src/serve/entries/auth-helper-core.ts +5 -0
- package/src/serve/entries/auth-helper-dom.ts +26 -2
- package/src/serve/entries/auth.ts +8 -0
- package/src/serve/entries/bridge-url.ts +1 -1
- package/src/serve/entries/init.ts +78 -3
- package/src/serve/entries/runtime.ts +10 -0
- package/src/serve/namespace.ts +73 -140
- package/src/serve/runtime/chip-config.ts +16 -2
- package/src/serve/runtime/chip-dialog.ts +366 -0
- package/src/serve/runtime/chip-install.ts +10 -4
- package/src/serve/runtime/chip-user-search.ts +391 -0
- package/src/serve/runtime/chip.ts +140 -15
- package/src/serve/runtime/identity.ts +26 -0
- package/src/serve/sandbox-session.ts +9 -0
- package/src/serve/studio/disk-workspace.ts +76 -2
- package/src/serve/vite-plugin.ts +8 -8
- package/src/serve/vite-sandbox-generation.ts +15 -0
- package/src/serve/worker/client/auth.ts +1 -1
- package/src/serve/worker/client/core.ts +80 -3
- package/src/serve/worker/client.ts +1 -1
- package/src/serve/worker/context-resolver.ts +60 -0
- package/src/serve/worker/entry.ts +22 -18
- package/src/serve/worker/host/core.ts +9 -7
- package/src/serve/worker/host/storage.ts +2 -2
- package/src/serve/worker/host-auth.ts +1 -1
- package/src/serve/worker/index.ts +4 -1
- package/src/serve/worker/port-lifecycle.ts +45 -0
- package/src/serve/worker/serve-init.ts +9 -2
- package/src/verify/fixture.ts +1 -1
- package/dist/serve/site-ui/_astro/app.B52xr-DK.js +0 -1
- package/dist/serve/site-ui/_astro/app.DWBraPUw.js +0 -12
- package/dist/serve/site-ui/_astro/sandbox-controls.DEpnKg-O.js +0 -1169
- package/dist/serve/site-ui/_astro/writes.rpjrFzmu.js +0 -102
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import{P as st,c as it,Q as oe,U as J,V as at,W as T,X as ot,Y as f,_ as ut,$ as ct,a0 as dt,a1 as H,a2 as S,a3 as lt,a4 as ht,v as ue,a5 as q,a6 as Fe,a7 as ft,a8 as mt,B as Ae,M as p,K as O,T as ye,y as pt,a9 as yt,aa as gt,ab as bt,C as ce,ac as wt,ad as vt,ae as xt,af as St,ag as Ct,S as kt,ah as qt}from"./sandbox-controls.rOuZTOUG.js";const Et={atomic:`// @pyric-services cloud.firestore
|
|
2
|
+
// Atomic module — cross-document integrity for BATCH writes, via the
|
|
3
|
+
// get()/getAfter() pair. Firestore evaluates every write in a batch
|
|
4
|
+
// against rules, and getAfter() returns another document's POST-BATCH
|
|
5
|
+
// state — so a rule can require "this write is only valid if a
|
|
6
|
+
// companion write happened in the same atomic batch".
|
|
7
|
+
//
|
|
8
|
+
// All semantics live-verified against a production database: companion
|
|
9
|
+
// visibility, solo-write denial, wrong-delta denial, single-use consumption
|
|
10
|
+
// incl. replay denial, and out-of-batch getAfter() falling back to current
|
|
11
|
+
// state.
|
|
12
|
+
//
|
|
13
|
+
// Explicit-param design: pass BOTH states of the companion doc —
|
|
14
|
+
// \`get(path).data\` (pre-batch) and \`getAfter(path).data\` (post-batch).
|
|
15
|
+
//
|
|
16
|
+
// import { companionChangedBy, consumedFlag } from 'atomic';
|
|
17
|
+
//
|
|
18
|
+
// // Denormalized counter: creating a task REQUIRES the team's
|
|
19
|
+
// // taskCount to have been incremented in the same batch:
|
|
20
|
+
// match /teams/{teamId}/tasks/{taskId} {
|
|
21
|
+
// function teamBefore() {
|
|
22
|
+
// return get(/databases/$(database)/documents/teams/$(teamId)).data;
|
|
23
|
+
// }
|
|
24
|
+
// function teamAfter() {
|
|
25
|
+
// return getAfter(/databases/$(database)/documents/teams/$(teamId)).data;
|
|
26
|
+
// }
|
|
27
|
+
// allow create: if companionChangedBy(teamBefore(), teamAfter(), 'taskCount', 1);
|
|
28
|
+
// }
|
|
29
|
+
//
|
|
30
|
+
// // Single-use invite: the join is valid only when the invite was
|
|
31
|
+
// // consumed (false -> true) IN THIS BATCH — replays deny because
|
|
32
|
+
// // the pre-batch state is already true:
|
|
33
|
+
// allow update: if consumedFlag(inviteBefore(), inviteAfter(), 'used');
|
|
34
|
+
//
|
|
35
|
+
// NOTE (simulator): in-batch companion semantics cannot be expressed
|
|
36
|
+
// in the simulator or the Rules Test API mock surface — the simulator
|
|
37
|
+
// treats cross-doc getAfter() as get(). These function BODIES are
|
|
38
|
+
// simulator-tested with explicit values; the getAfter() WIRING is the
|
|
39
|
+
// live v1 scope's job. Companion writes need their own allow rules too —
|
|
40
|
+
// every write in a batch is evaluated.
|
|
41
|
+
|
|
42
|
+
// The companion doc's field changed by EXACTLY n in this batch.
|
|
43
|
+
// before/after are the SAME doc's get()/getAfter() data.
|
|
44
|
+
export function companionChangedBy(before, after, field, n) {
|
|
45
|
+
return after[field] == before[field] + n;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// A single-use flag was consumed IN THIS BATCH: pre-batch false,
|
|
49
|
+
// post-batch true. Replays deny (pre-batch is already true), and a
|
|
50
|
+
// solo write denies (getAfter == get, so false != true).
|
|
51
|
+
export function consumedFlag(before, after, flagField) {
|
|
52
|
+
return before[flagField] == false && after[flagField] == true;
|
|
53
|
+
}
|
|
54
|
+
`,auth:`// @pyric-services cloud.firestore,firebase.storage
|
|
55
|
+
// @pyric-evidence storage-rules#125,firestore-rules#189
|
|
56
|
+
export function isAuthenticated() {
|
|
57
|
+
return request.auth != null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isOwner(userId) {
|
|
61
|
+
return isAuthenticated() && request.auth.uid == userId;
|
|
62
|
+
}
|
|
63
|
+
`,content:`// @pyric-services cloud.firestore
|
|
64
|
+
// Content module for author-owned documents — the most common
|
|
65
|
+
// Firebase app shape (posts, notes, docs, comments, tasks).
|
|
66
|
+
//
|
|
67
|
+
// Convention: documents carry an author field (a UID string) and
|
|
68
|
+
// usually a status field ('draft' | 'published' | ...). Field names
|
|
69
|
+
// are parameters, not conventions — pass yours.
|
|
70
|
+
//
|
|
71
|
+
// Usage:
|
|
72
|
+
// import { validAuthorCreate, isAuthor, canReadContent, notDeleted } from 'content';
|
|
73
|
+
// import { onlyFieldsChanged } from 'lifecycle';
|
|
74
|
+
//
|
|
75
|
+
// match /posts/{postId} {
|
|
76
|
+
// // Create: author is the signed-in user.
|
|
77
|
+
// allow create: if validAuthorCreate('author');
|
|
78
|
+
// // Read: published to everyone, drafts to the author only.
|
|
79
|
+
// allow read: if canReadContent('status', 'author') && notDeleted();
|
|
80
|
+
// // Update: author edits content fields; authorship immutable.
|
|
81
|
+
// allow update: if isAuthor('author') && onlyFieldsChanged(['title', 'body', 'status']);
|
|
82
|
+
// allow delete: if isAuthor('author');
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
// Create guard: signed in, and the incoming doc's author field is the
|
|
86
|
+
// caller. Use on \`create\` (reads request.resource).
|
|
87
|
+
export function validAuthorCreate(authorField) {
|
|
88
|
+
return request.auth != null
|
|
89
|
+
&& request.resource.data[authorField] == request.auth.uid;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The caller is the EXISTING document's author. Use on update/delete
|
|
93
|
+
// (reads resource).
|
|
94
|
+
export function isAuthor(authorField) {
|
|
95
|
+
return request.auth != null
|
|
96
|
+
&& resource.data[authorField] == request.auth.uid;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Read visibility: published content is public; anything else is
|
|
100
|
+
// visible to its author only. Works for get; for list queries the
|
|
101
|
+
// client must filter (rules are not filters — a bare collection query
|
|
102
|
+
// will be denied unless it proves status == 'published').
|
|
103
|
+
export function canReadContent(statusField, authorField) {
|
|
104
|
+
return resource.data[statusField] == 'published'
|
|
105
|
+
|| (request.auth != null && resource.data[authorField] == request.auth.uid);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Soft-delete guard: the document is not marked deleted. Reads the
|
|
109
|
+
// EXISTING doc; a doc without the field passes — bracket access is
|
|
110
|
+
// the null-on-miss idiom (dotted access of a missing key ERRORS).
|
|
111
|
+
export function notDeleted() {
|
|
112
|
+
return resource.data['deleted'] != true;
|
|
113
|
+
}
|
|
114
|
+
`,counters:`// @pyric-services cloud.firestore
|
|
115
|
+
// Counters module for denormalized numeric integrity.
|
|
116
|
+
//
|
|
117
|
+
// The recurring shape: a client-maintained count (likes, votes, moves,
|
|
118
|
+
// quantities) that rules must keep honest — it may only change by a
|
|
119
|
+
// known step, or stay within known bounds. Generalizes the
|
|
120
|
+
// state-module's moveIncremented() (hardcoded to moveCount) to any
|
|
121
|
+
// field.
|
|
122
|
+
//
|
|
123
|
+
// Usage:
|
|
124
|
+
// import { incrementedBy, changedBy, boundedNumber } from 'counters';
|
|
125
|
+
//
|
|
126
|
+
// // A like toggle: likeCount moves by exactly ±1:
|
|
127
|
+
// allow update: if changedBy('likeCount', -1, 1);
|
|
128
|
+
//
|
|
129
|
+
// // A move counter: strictly +1 per move:
|
|
130
|
+
// allow update: if incrementedBy('moveCount', 1);
|
|
131
|
+
//
|
|
132
|
+
// // A rating: any write keeps it in [1, 5]:
|
|
133
|
+
// allow write: if boundedNumber('rating', 1, 5);
|
|
134
|
+
|
|
135
|
+
// The field changed by EXACTLY n relative to the existing document.
|
|
136
|
+
// n may be negative (decrement). Update rules only (needs resource).
|
|
137
|
+
export function incrementedBy(field, n) {
|
|
138
|
+
return request.resource.data[field] == resource.data[field] + n;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The field's delta is within [min, max] (inclusive) — and it is NOT
|
|
142
|
+
// required to change (delta 0 passes when min <= 0 <= max).
|
|
143
|
+
export function changedBy(field, min, max) {
|
|
144
|
+
return request.resource.data[field] - resource.data[field] >= min
|
|
145
|
+
&& request.resource.data[field] - resource.data[field] <= max;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The incoming value is a number within [min, max] (inclusive).
|
|
149
|
+
// Works on create and update. Missing field reads null (dynamic
|
|
150
|
+
// access) and fails the type check rather than erroring.
|
|
151
|
+
export function boundedNumber(field, min, max) {
|
|
152
|
+
return (request.resource.data[field] is int || request.resource.data[field] is float)
|
|
153
|
+
&& request.resource.data[field] >= min
|
|
154
|
+
&& request.resource.data[field] <= max;
|
|
155
|
+
}
|
|
156
|
+
`,geometry:`// @pyric-services cloud.firestore
|
|
157
|
+
// Geometry module for movement game validation via config document lookup.
|
|
158
|
+
//
|
|
159
|
+
// Caller must pass the config document data as an explicit parameter.
|
|
160
|
+
// The config doc is read via get() and cached per request — define a
|
|
161
|
+
// config() function in your rules and pass its result to these functions.
|
|
162
|
+
//
|
|
163
|
+
// Config doc schema:
|
|
164
|
+
// moves[pieceType][fromCell][toCell] = true
|
|
165
|
+
// jumps[pieceType][fromCell][toCell] = capturedCellName
|
|
166
|
+
//
|
|
167
|
+
// Usage:
|
|
168
|
+
// import { validSimpleMove, validJumpMove } from 'geometry';
|
|
169
|
+
//
|
|
170
|
+
// function config() {
|
|
171
|
+
// return get(/databases/$(database)/documents/gameConfig/checkers).data;
|
|
172
|
+
// }
|
|
173
|
+
//
|
|
174
|
+
// allow update: if validSimpleMove(config())
|
|
175
|
+
// && piecePlaced() && moveIntegrity();
|
|
176
|
+
//
|
|
177
|
+
// allow update: if validJumpMove(config())
|
|
178
|
+
// && captureValid() && captureDecrement() && moveIntegrity();
|
|
179
|
+
|
|
180
|
+
// Validate a simple (non-capture) move via config document lookup.
|
|
181
|
+
// Uses 3-level dynamic nesting: cfg.moves[piece][from][to].
|
|
182
|
+
// piece comes from resource.data (pre-write board) so client can't fake it.
|
|
183
|
+
export function validSimpleMove(cfg) {
|
|
184
|
+
let mf = request.resource.data.moveFrom;
|
|
185
|
+
let mt = request.resource.data.moveTo;
|
|
186
|
+
let piece = resource.data[mf];
|
|
187
|
+
return cfg.moves[piece][mf][mt] == true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Validate a jump (capture) move via config document lookup.
|
|
191
|
+
// cfg.jumps[piece][from][to] returns the expected captured cell name.
|
|
192
|
+
// Caller must separately verify the captured piece is an opponent piece.
|
|
193
|
+
export function validJumpMove(cfg) {
|
|
194
|
+
let mf = request.resource.data.moveFrom;
|
|
195
|
+
let mt = request.resource.data.moveTo;
|
|
196
|
+
let cap = request.resource.data.captured;
|
|
197
|
+
let piece = resource.data[mf];
|
|
198
|
+
return cfg.jumps[piece][mf][mt] == cap;
|
|
199
|
+
}
|
|
200
|
+
`,joining:`// @pyric-services cloud.firestore
|
|
201
|
+
// Joining module — how membership CHANGES, safely. The connective
|
|
202
|
+
// tissue between content/spaces: spaces gates children behind a
|
|
203
|
+
// members field; joining lets users enter and leave that field
|
|
204
|
+
// WITHOUT an admin backend, with no privilege escalation.
|
|
205
|
+
//
|
|
206
|
+
// Convention: a MAP-shaped members field (uid -> role) on the parent
|
|
207
|
+
// doc. Compose with lifecycle's onlyFieldsChanged so a join/leave
|
|
208
|
+
// write can't touch anything else:
|
|
209
|
+
//
|
|
210
|
+
// import { onlyAddedSelf, onlyRemovedSelf } from 'joining';
|
|
211
|
+
// import { onlyFieldsChanged } from 'lifecycle';
|
|
212
|
+
// import { isSpaceMember, hasSpaceRole } from 'spaces';
|
|
213
|
+
//
|
|
214
|
+
// match /teams/{teamId} {
|
|
215
|
+
// allow read: if isSpaceMember(resource.data);
|
|
216
|
+
//
|
|
217
|
+
// // Open self-service join (public team) at a fixed role, and
|
|
218
|
+
// // self-service leave — nothing else about the doc may change:
|
|
219
|
+
// allow update: if onlyFieldsChanged(['members'])
|
|
220
|
+
// && (onlyAddedSelf('members', 'editor') || onlyRemovedSelf('members'));
|
|
221
|
+
// // Admins manage the doc freely:
|
|
222
|
+
// allow update: if hasSpaceRole(resource.data, 'admin');
|
|
223
|
+
// }
|
|
224
|
+
//
|
|
225
|
+
// Every guarantee here is production-verified against the live Rules Test API:
|
|
226
|
+
// the field-level map diff is reliable; the empty no-op diff DENIES (set
|
|
227
|
+
// equality, not hasOnly — hasOnly on an empty set passes); role escalation via
|
|
228
|
+
// CHANGING an existing entry denies; adding or removing anyone else denies.
|
|
229
|
+
|
|
230
|
+
// The write adds EXACTLY the caller to the members map, at EXACTLY
|
|
231
|
+
// \`role\`, changing and removing nobody. Update rules only.
|
|
232
|
+
export function onlyAddedSelf(membersField, role) {
|
|
233
|
+
let diff = request.resource.data[membersField].diff(resource.data[membersField]);
|
|
234
|
+
return request.auth != null
|
|
235
|
+
&& diff.addedKeys() == [request.auth.uid].toSet()
|
|
236
|
+
&& diff.changedKeys().size() == 0
|
|
237
|
+
&& diff.removedKeys().size() == 0
|
|
238
|
+
&& request.resource.data[membersField][request.auth.uid] == role;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// The write removes EXACTLY the caller from the members map, adding
|
|
242
|
+
// and changing nobody. Self-service leave. Update rules only.
|
|
243
|
+
export function onlyRemovedSelf(membersField) {
|
|
244
|
+
let diff = request.resource.data[membersField].diff(resource.data[membersField]);
|
|
245
|
+
return request.auth != null
|
|
246
|
+
&& diff.removedKeys() == [request.auth.uid].toSet()
|
|
247
|
+
&& diff.addedKeys().size() == 0
|
|
248
|
+
&& diff.changedKeys().size() == 0;
|
|
249
|
+
}
|
|
250
|
+
`,lifecycle:`// @pyric-services cloud.firestore
|
|
251
|
+
// Lifecycle module for field immutability and timestamp enforcement.
|
|
252
|
+
//
|
|
253
|
+
// Common pattern: certain fields must never change after document creation
|
|
254
|
+
// (createdBy, createdAt, authorId).
|
|
255
|
+
//
|
|
256
|
+
// Usage:
|
|
257
|
+
// import { fieldUnchanged, immutableFields, isServerTimestamp } from 'lifecycle';
|
|
258
|
+
//
|
|
259
|
+
// // Single field:
|
|
260
|
+
// allow update: if fieldUnchanged('createdBy');
|
|
261
|
+
//
|
|
262
|
+
// // Multiple fields (replaces chained fieldUnchanged calls):
|
|
263
|
+
// allow update: if immutableFields(['createdBy', 'createdAt', 'authorId']);
|
|
264
|
+
//
|
|
265
|
+
// // The dual: users may edit title/body but NOTHING else:
|
|
266
|
+
// allow update: if onlyFieldsChanged(['title', 'body']);
|
|
267
|
+
//
|
|
268
|
+
// allow create: if isServerTimestamp('createdAt');
|
|
269
|
+
|
|
270
|
+
// Ensure a field's value is identical between existing and incoming document.
|
|
271
|
+
// Use on update rules to enforce immutability.
|
|
272
|
+
export function fieldUnchanged(field) {
|
|
273
|
+
return resource.data[field] == request.resource.data[field];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Ensure multiple fields are unchanged between existing and incoming document.
|
|
277
|
+
// Uses MapDiff — one expression instead of N chained fieldUnchanged() calls.
|
|
278
|
+
export function immutableFields(fields) {
|
|
279
|
+
return request.resource.data.diff(resource.data).unchangedKeys().hasAll(fields);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Ensure a field is set to the server timestamp.
|
|
283
|
+
// Use on create or update rules to enforce server-side time.
|
|
284
|
+
export function isServerTimestamp(field) {
|
|
285
|
+
return request.resource.data[field] == request.time;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// The dual of immutableFields: every changed field is in the allowed
|
|
289
|
+
// list (fields NOT listed are implicitly immutable). Top-level keys
|
|
290
|
+
// only — nested-map diffs are unreliable in production; flatten the
|
|
291
|
+
// schema instead of reaching for a nested diff.
|
|
292
|
+
export function onlyFieldsChanged(fields) {
|
|
293
|
+
return request.resource.data.diff(resource.data).affectedKeys().hasOnly(fields);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Exactly n top-level fields changed in this write. n == 1 is the
|
|
297
|
+
// board-integrity / edit-one-field-per-write guard (Pattern 5,
|
|
298
|
+
// generalized beyond games).
|
|
299
|
+
export function nFieldsChanged(n) {
|
|
300
|
+
return request.resource.data.diff(resource.data).affectedKeys().size() == n;
|
|
301
|
+
}
|
|
302
|
+
`,lobby:`// @pyric-services cloud.firestore
|
|
303
|
+
// Lobby module for 2-participant coordination.
|
|
304
|
+
//
|
|
305
|
+
// Convention: documents must have these fields:
|
|
306
|
+
// host: string (creator's UID)
|
|
307
|
+
// guest: string ('' when waiting, joiner's UID when joined)
|
|
308
|
+
// status: 'waiting' | 'playing' | 'ready' (or any active state)
|
|
309
|
+
//
|
|
310
|
+
// Usage:
|
|
311
|
+
// import { validCreate, validJoin, canCancel } from 'lobby';
|
|
312
|
+
//
|
|
313
|
+
// allow create: if validCreate();
|
|
314
|
+
// allow update: if validJoin();
|
|
315
|
+
// allow delete: if canCancel();
|
|
316
|
+
|
|
317
|
+
// Is this a waiting lobby with an open seat?
|
|
318
|
+
function isWaiting() {
|
|
319
|
+
return resource.data.status == 'waiting'
|
|
320
|
+
&& resource.data.guest == '';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Create: caller is host, seat empty, status waiting
|
|
324
|
+
export function validCreate() {
|
|
325
|
+
return request.auth != null
|
|
326
|
+
&& request.resource.data.host == request.auth.uid
|
|
327
|
+
&& request.resource.data.guest == ''
|
|
328
|
+
&& request.resource.data.status == 'waiting';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Join: fill empty seat, transition to next status, no self-join
|
|
332
|
+
export function validJoin() {
|
|
333
|
+
return request.auth != null
|
|
334
|
+
&& isWaiting()
|
|
335
|
+
&& request.resource.data.guest == request.auth.uid
|
|
336
|
+
&& request.auth.uid != resource.data.host
|
|
337
|
+
&& request.resource.data.status == 'playing'
|
|
338
|
+
&& request.resource.data.host == resource.data.host;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Cancel: only host, only while waiting
|
|
342
|
+
export function canCancel() {
|
|
343
|
+
return request.auth != null
|
|
344
|
+
&& resource.data.status == 'waiting'
|
|
345
|
+
&& request.auth.uid == resource.data.host;
|
|
346
|
+
}
|
|
347
|
+
`,membership:`// @pyric-services cloud.firestore,firebase.storage
|
|
348
|
+
// @pyric-evidence storage-rules#125,firestore-rules#189
|
|
349
|
+
// Membership module for role-based and claims-based access control.
|
|
350
|
+
//
|
|
351
|
+
// Two access patterns:
|
|
352
|
+
// 1. Custom claims on auth token (0 get() calls, set server-side)
|
|
353
|
+
// 2. Member maps on documents (0 get() calls on the doc itself)
|
|
354
|
+
//
|
|
355
|
+
// Usage:
|
|
356
|
+
// import { hasClaim, hasClaimRole, isMemberOf, hasRole } from 'membership';
|
|
357
|
+
//
|
|
358
|
+
// // Workspace access via custom claims
|
|
359
|
+
// allow read: if hasClaim('workspace_id');
|
|
360
|
+
// allow write: if hasClaimRole('workspace_role', 'admin');
|
|
361
|
+
//
|
|
362
|
+
// // Project access via member map on document
|
|
363
|
+
// allow read: if isMemberOf(resource.data.members);
|
|
364
|
+
// allow update: if hasRole(resource.data.members, 'admin');
|
|
365
|
+
|
|
366
|
+
// Check if the auth token has a non-null value for a claim key.
|
|
367
|
+
export function hasClaim(claim) {
|
|
368
|
+
return request.auth != null
|
|
369
|
+
&& request.auth.token[claim] != null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check if the auth token has a specific value for a claim key.
|
|
373
|
+
export function hasClaimRole(claim, role) {
|
|
374
|
+
return request.auth != null
|
|
375
|
+
&& request.auth.token[claim] == role;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Check if the caller's UID exists as a key in a members map.
|
|
379
|
+
export function isMemberOf(membersMap) {
|
|
380
|
+
return request.auth != null
|
|
381
|
+
&& request.auth.uid in membersMap;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Check if the caller has a specific role in a members map.
|
|
385
|
+
export function hasRole(membersMap, role) {
|
|
386
|
+
return request.auth != null
|
|
387
|
+
&& membersMap[request.auth.uid] == role;
|
|
388
|
+
}
|
|
389
|
+
`,spaces:`// @pyric-services cloud.firestore
|
|
390
|
+
// Spaces module — cross-document membership gating for shared spaces
|
|
391
|
+
// (teams, rooms, groups, projects, parties). The second-most-common
|
|
392
|
+
// app shape after author-owned content: a PARENT document defines who
|
|
393
|
+
// may touch its children.
|
|
394
|
+
//
|
|
395
|
+
// Explicit-param design (like geometry): the caller reads the parent
|
|
396
|
+
// doc ONCE via a helper and passes its data in. Define the helper in
|
|
397
|
+
// your rules — get() is cached per request, so every function below
|
|
398
|
+
// can share one read (of the 10-get budget):
|
|
399
|
+
//
|
|
400
|
+
// import { isSpaceMember, hasSpaceRole, validMemberCreate } from 'spaces';
|
|
401
|
+
//
|
|
402
|
+
// match /spaces/{spaceId}/tasks/{taskId} {
|
|
403
|
+
// function space() {
|
|
404
|
+
// return get(/databases/$(database)/documents/spaces/$(spaceId)).data;
|
|
405
|
+
// }
|
|
406
|
+
// allow read: if isSpaceMember(space());
|
|
407
|
+
// allow create: if validMemberCreate(space(), 'author');
|
|
408
|
+
// allow delete: if hasSpaceRole(space(), 'admin');
|
|
409
|
+
// }
|
|
410
|
+
//
|
|
411
|
+
// Members field shapes verified against the production engine:
|
|
412
|
+
// list: members: ['uidA', 'uidB'] → isSpaceMember only
|
|
413
|
+
// map: members: { uidA: 'admin', ... } → isSpaceMember + hasSpaceRole
|
|
414
|
+
// A missing members field, a non-member uid, or a missing parent doc
|
|
415
|
+
// (get() of a missing doc errors) all fail CLOSED.
|
|
416
|
+
|
|
417
|
+
// The caller is in the space's members field. \`in\` covers both
|
|
418
|
+
// shapes: list membership and map keys.
|
|
419
|
+
export function isSpaceMember(spaceData) {
|
|
420
|
+
return request.auth != null && request.auth.uid in spaceData.members;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// The caller's role in a MAP-shaped members field equals \`role\`.
|
|
424
|
+
// List-shaped members carry no roles — this denies on them.
|
|
425
|
+
export function hasSpaceRole(spaceData, role) {
|
|
426
|
+
return request.auth != null && spaceData.members[request.auth.uid] == role;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Member-gated authored create: the caller is a member AND the
|
|
430
|
+
// incoming child doc's author field is the caller. The "post a
|
|
431
|
+
// message / add a task" guard.
|
|
432
|
+
export function validMemberCreate(spaceData, authorField) {
|
|
433
|
+
return isSpaceMember(spaceData)
|
|
434
|
+
&& request.resource.data[authorField] == request.auth.uid;
|
|
435
|
+
}
|
|
436
|
+
`,state:`// @pyric-services cloud.firestore
|
|
437
|
+
// Game state machine helpers.
|
|
438
|
+
//
|
|
439
|
+
// Convention: documents must have these fields:
|
|
440
|
+
// status: 'waiting' | 'playing' | 'won' | 'draw'
|
|
441
|
+
// moveCount: number
|
|
442
|
+
// host: string (UID)
|
|
443
|
+
// guest: string (UID)
|
|
444
|
+
//
|
|
445
|
+
// Usage:
|
|
446
|
+
// import { isPlaying, moveIncremented, participantsUnchanged } from 'state';
|
|
447
|
+
//
|
|
448
|
+
// allow update: if isPlaying() && moveIncremented() && participantsUnchanged();
|
|
449
|
+
|
|
450
|
+
// Is the game actively in play?
|
|
451
|
+
export function isPlaying() {
|
|
452
|
+
return resource.data.status == 'playing';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Did moveCount increment by exactly 1?
|
|
456
|
+
export function moveIncremented() {
|
|
457
|
+
return request.resource.data.moveCount == resource.data.moveCount + 1;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Are host and guest unchanged? (prevents mid-game identity swap)
|
|
461
|
+
export function participantsUnchanged() {
|
|
462
|
+
return request.resource.data.host == resource.data.host
|
|
463
|
+
&& request.resource.data.guest == resource.data.guest;
|
|
464
|
+
}
|
|
465
|
+
`,"storage/metadata":`// @pyric-services firebase.storage
|
|
466
|
+
// @pyric-evidence storage-rules#132
|
|
467
|
+
// Storage custom-metadata policies. Custom metadata is a flat string map.
|
|
468
|
+
|
|
469
|
+
export function hasRequiredMetadata(keys) {
|
|
470
|
+
return request.resource.metadata.keys().hasAll(keys);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function metadataString(key, min, max) {
|
|
474
|
+
return request.resource.metadata[key] is string
|
|
475
|
+
&& request.resource.metadata[key].size() >= min
|
|
476
|
+
&& request.resource.metadata[key].size() <= max;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export function incomingMetadataOwner(key) {
|
|
480
|
+
return request.auth != null
|
|
481
|
+
&& request.resource.metadata[key] == request.auth.uid;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function existingMetadataOwner(key) {
|
|
485
|
+
return request.auth != null
|
|
486
|
+
&& resource.metadata[key] == request.auth.uid;
|
|
487
|
+
}
|
|
488
|
+
`,"storage/objects":`// @pyric-services firebase.storage
|
|
489
|
+
// @pyric-evidence storage-rules#132
|
|
490
|
+
// Storage operation identity. Use request.method rather than null checks:
|
|
491
|
+
// production treats missing resource/request.resource bindings as errors.
|
|
492
|
+
|
|
493
|
+
export function isCreate() {
|
|
494
|
+
return request.method == 'create';
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function isUpdate() {
|
|
498
|
+
return request.method == 'update';
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function isDelete() {
|
|
502
|
+
return request.method == 'delete';
|
|
503
|
+
}
|
|
504
|
+
`,"storage/time":`// @pyric-services firebase.storage
|
|
505
|
+
// @pyric-evidence storage-rules#132
|
|
506
|
+
// Freshness checks over server-owned Storage object timestamps. Boundaries are
|
|
507
|
+
// strict: equality with the deadline is not "within" the window.
|
|
508
|
+
|
|
509
|
+
export function createdWithin(seconds) {
|
|
510
|
+
return request.time < resource.timeCreated + duration.value(seconds, 's');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function updatedWithin(seconds) {
|
|
514
|
+
return request.time < resource.updated + duration.value(seconds, 's');
|
|
515
|
+
}
|
|
516
|
+
`,"storage/uploads":`// @pyric-services firebase.storage
|
|
517
|
+
// @pyric-evidence storage-rules#132
|
|
518
|
+
// Storage upload request primitives. These inspect declared object metadata;
|
|
519
|
+
// MIME helpers do not inspect or authenticate the uploaded bytes.
|
|
520
|
+
|
|
521
|
+
export function sizeAtMost(maxBytes) {
|
|
522
|
+
return request.resource.size <= maxBytes;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function sizeBetween(minBytes, maxBytes) {
|
|
526
|
+
return request.resource.size >= minBytes
|
|
527
|
+
&& request.resource.size <= maxBytes;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function contentTypeMatches(pattern) {
|
|
531
|
+
return request.resource.contentType.matches(pattern);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function contentTypeIsOneOf(types) {
|
|
535
|
+
return request.resource.contentType in types;
|
|
536
|
+
}
|
|
537
|
+
`,timing:`// @pyric-services cloud.firestore
|
|
538
|
+
// Timing module for cooldown / rate-limit enforcement.
|
|
539
|
+
//
|
|
540
|
+
// Rules CAN rate-limit (contrary to a common assumption — jrpg
|
|
541
|
+
// FINDINGS F-004): compare request.time against a stored server
|
|
542
|
+
// timestamp. Pair with lifecycle's isServerTimestamp on the SAME
|
|
543
|
+
// write so the stored timestamp cannot be forged by the client:
|
|
544
|
+
//
|
|
545
|
+
// import { cooldownElapsed } from 'timing';
|
|
546
|
+
// import { isServerTimestamp } from 'lifecycle';
|
|
547
|
+
//
|
|
548
|
+
// // At most one move every 2 seconds:
|
|
549
|
+
// allow update: if cooldownElapsed('lastMoveAt', 2)
|
|
550
|
+
// && isServerTimestamp('lastMoveAt');
|
|
551
|
+
|
|
552
|
+
// The stored timestamp is more than \`seconds\` old (strict). Update
|
|
553
|
+
// rules only — needs \`resource\`. The field must be a Timestamp; a
|
|
554
|
+
// missing field errors, which denies (fail-closed).
|
|
555
|
+
export function cooldownElapsed(field, seconds) {
|
|
556
|
+
return request.time > resource.data[field] + duration.value(seconds, 's');
|
|
557
|
+
}
|
|
558
|
+
`,transitions:`// @pyric-services cloud.firestore
|
|
559
|
+
// Transitions module for state machine enforcement.
|
|
560
|
+
//
|
|
561
|
+
// Validates that a field transitions from one known value to another.
|
|
562
|
+
// Use to enforce valid status flows (e.g., open -> in_progress -> completed).
|
|
563
|
+
//
|
|
564
|
+
// Usage:
|
|
565
|
+
// import { validTransition, statusIs, newStatusIs } from 'transitions';
|
|
566
|
+
//
|
|
567
|
+
// // Only allow open -> in_progress
|
|
568
|
+
// allow update: if validTransition('status', 'open', 'in_progress');
|
|
569
|
+
//
|
|
570
|
+
// // Allow update only when currently active
|
|
571
|
+
// allow update: if statusIs('status', 'active');
|
|
572
|
+
//
|
|
573
|
+
// // Compound: multiple valid transitions via OR
|
|
574
|
+
// allow update: if validTransition('status', 'open', 'in_progress')
|
|
575
|
+
// || validTransition('status', 'in_progress', 'completed')
|
|
576
|
+
// || validTransition('status', 'completed', 'archived');
|
|
577
|
+
|
|
578
|
+
// Check that a field transitions from one value to another.
|
|
579
|
+
export function validTransition(field, from, to) {
|
|
580
|
+
return resource.data[field] == from
|
|
581
|
+
&& request.resource.data[field] == to;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Check the current (existing) value of a field.
|
|
585
|
+
export function statusIs(field, value) {
|
|
586
|
+
return resource.data[field] == value;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Check the incoming (new) value of a field.
|
|
590
|
+
export function newStatusIs(field, value) {
|
|
591
|
+
return request.resource.data[field] == value;
|
|
592
|
+
}
|
|
593
|
+
`,turns:`// @pyric-services cloud.firestore
|
|
594
|
+
// Turn enforcement for 2-participant games/sessions.
|
|
595
|
+
//
|
|
596
|
+
// Convention: documents must have these fields:
|
|
597
|
+
// host: string (UID)
|
|
598
|
+
// guest: string (UID)
|
|
599
|
+
// currentTurn: 'host' | 'guest'
|
|
600
|
+
//
|
|
601
|
+
// Usage:
|
|
602
|
+
// import { isMyTurn, turnFlipped } from 'turns';
|
|
603
|
+
//
|
|
604
|
+
// allow update: if isMyTurn() && turnFlipped();
|
|
605
|
+
|
|
606
|
+
// Is the caller the participant whose turn it is?
|
|
607
|
+
export function isMyTurn() {
|
|
608
|
+
return (resource.data.currentTurn == 'host'
|
|
609
|
+
&& request.auth.uid == resource.data.host)
|
|
610
|
+
|| (resource.data.currentTurn == 'guest'
|
|
611
|
+
&& request.auth.uid == resource.data.guest);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Did the turn alternate?
|
|
615
|
+
export function turnFlipped() {
|
|
616
|
+
return (resource.data.currentTurn == 'host'
|
|
617
|
+
&& request.resource.data.currentTurn == 'guest')
|
|
618
|
+
|| (resource.data.currentTurn == 'guest'
|
|
619
|
+
&& request.resource.data.currentTurn == 'host');
|
|
620
|
+
}
|
|
621
|
+
`,validation:`// @pyric-services cloud.firestore
|
|
622
|
+
// Validation module for document field shape and value checks.
|
|
623
|
+
//
|
|
624
|
+
// Usage:
|
|
625
|
+
// import { hasRequired, hasOnly, validString, isOneOf } from 'validation';
|
|
626
|
+
//
|
|
627
|
+
// allow create: if hasRequired(['title', 'status'])
|
|
628
|
+
// && validString('title', 1, 100)
|
|
629
|
+
// && isOneOf('status', ['draft', 'published']);
|
|
630
|
+
|
|
631
|
+
export function hasRequired(fields) {
|
|
632
|
+
return request.resource.data.keys().hasAll(fields);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export function hasOnly(fields) {
|
|
636
|
+
return request.resource.data.keys().hasOnly(fields);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Incoming field is a string with size in [min, max] (inclusive).
|
|
640
|
+
// Uses dynamic access, so a MISSING field reads as null (not an
|
|
641
|
+
// error) and fails the \`is string\` check — safe on optional fields.
|
|
642
|
+
export function validString(field, min, max) {
|
|
643
|
+
return request.resource.data[field] is string
|
|
644
|
+
&& request.resource.data[field].size() >= min
|
|
645
|
+
&& request.resource.data[field].size() <= max;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Enum check: the incoming field value is one of the allowed values.
|
|
649
|
+
// (\`in\` on a list — NOT \`.includes()\`, which does not exist in rules.)
|
|
650
|
+
export function isOneOf(field, values) {
|
|
651
|
+
return request.resource.data[field] in values;
|
|
652
|
+
}
|
|
653
|
+
`};function Tt(){const t={};for(const[e,n]of Object.entries(Et))t[e]=n,t[`./stdlib/${e}.rules`]=n;return t}const Oe=Tt(),Rt=new Set(Object.keys(Oe));function gn(t,e){const n=Object.entries(e?.modules??{}),r=Object.fromEntries(n),s=new Set(n.map(([a])=>a)),i=new Set([...Rt].filter(a=>!s.has(a)));return st(null,t,{...e,modules:{...Oe,...r}},i)}const Ft=Symbol.for("pyric.remote.sandbox");function de(t){return t!=null&&t[Ft]===!0}function bn(t={}){return it()}function D(t){return t.replace(/^\/+|\/+$/g,"").split("/")}function Ie(t){return D(t).length%2===0&&D(t).length>0}function le(t){return D(t).length%2===1}function Y(t){const e=D(t);return e[e.length-1]??""}function De(t){return D(t).slice(0,-1).join("/")}function je(t,e){const n=e!==void 0,r=n?oe(e):void 0;return{id:t.id,ref:t,exists:n,data:()=>n?r:void 0,get:s=>J(r,s)}}const ge=new WeakMap;let At=1;function be(t){let e=ge.get(t);return e===void 0&&(e=At++,ge.set(t,e)),e}function $(t){if(t===null||typeof t=="boolean")return t;if(typeof t=="string")return at(t);if(typeof t>"u")return{type:"undefined"};if(typeof t=="bigint")return T("bigint",t.toString());if(typeof t=="number"){const n=Number.isNaN(t)?"NaN":t===1/0?"Infinity":t===-1/0?"-Infinity":Object.is(t,-0)?"-0":String(t);return T("number",n)}if(typeof t!="object"&&typeof t!="function")return{type:typeof t,value:String(t)};const e=ot(t);if(e!==void 0)return e;try{if(Array.isArray(t))return{type:"array",id:be(t)}}catch{}return{type:"opaque-object",id:be(t)}}function w(t){return new f({code:"invalid-argument",message:t})}function ne(t,e,n,r=!1,s=!1){if(t===null||typeof t=="string"||typeof t=="boolean")return{comparison:t,execution:t};if(typeof t=="number")return{comparison:t,execution:t};if(typeof t>"u"||typeof t=="bigint"||typeof t=="function"||typeof t=="symbol")throw w(`Unsupported Firestore query value: ${typeof t}.`);if(typeof t!="object")throw w("Unsupported Firestore query value.");const i=ut(t);if(i!==void 0){const o=ct(t);if(o!==void 0&&n!==void 0&&o!==n)throw w("Document reference belongs to a different Firestore database.");return{comparison:{type:"registered-firestore-value",value:i},execution:dt(t)}}if(t instanceof Date){const o=t.getTime();if(!Number.isFinite(o))throw w("Invalid Date query value.");const c=Math.floor(o/1e3),u=Object.freeze({seconds:c,nanoseconds:Math.floor((o-c*1e3)*1e6)}),l={type:"timestamp",...u};return H(u,l,()=>Object.freeze({...u})),{comparison:{type:"registered-firestore-value",value:l},execution:u}}if(t instanceof Uint8Array){const o=t.slice();return{comparison:{type:"bytes",values:Array.from(o)},execution:o}}if(e.has(t))throw w("Cyclic query operands are not Firestore values.");const a=Object.getPrototypeOf(t);if(a!==Object.prototype&&a!==null&&!Array.isArray(t))throw w("Firestore query maps must be plain objects.");e.add(t);try{if(Array.isArray(t)){if(s&&!r)throw w("Nested arrays are not supported.");const u=t.map(d=>ne(d,e,n,r,!0)),l=u.map(d=>d.execution);return S(l,$(t)),{comparison:{type:"array",values:u.map(d=>d.comparison)},execution:l}}const o=Object.keys(t).sort().map(u=>({key:u,captured:ne(t[u],e,n,r,!1)})),c=Object.fromEntries(o.map(({key:u,captured:l})=>[u,l.execution]));return S(c,$(t)),{comparison:{type:"map",entries:o.map(({key:u,captured:l})=>[u,l.comparison])},execution:c}}finally{e.delete(t)}}function Me(t,e,n=!1){try{const r=ne(t,new Set,e,n);return Object.freeze({kind:"canonical",value:r.comparison,executionValue:r.execution})}catch(r){if(r instanceof f)throw r;const s=r instanceof Error?r.message:String(r);throw w(`Invalid Firestore query value: ${s}`)}}function _e(t,e){return lt(t.value,e.value)}function N(t,e){if(t.kind==="where"){const n="comparisonValue"in t?t.comparisonValue:Me(t.value,e,t.op==="in"||t.op==="not-in");return Object.freeze({kind:"where",field:t.field,op:t.op,value:n.executionValue,comparisonValue:n})}return Object.freeze({kind:t.kind,filters:Object.freeze(t.filters.map(n=>N(n,e)))})}function U(t,e,n,r){const s=t.map(i=>Me(i,r));return Object.freeze({values:Object.freeze(s.map(i=>i.executionValue)),comparisonValues:Object.freeze(s),inclusive:e,fromSnapshot:n})}function ze(t,e){return t.length!==e.length?!1:t.every((n,r)=>{const s=e[r];return n.kind!==s.kind?!1:n.kind==="where"&&s.kind==="where"?n.field===s.field&&n.op===s.op&&_e(n.comparisonValue,s.comparisonValue):n.kind!=="where"&&s.kind!=="where"&&ze(n.filters,s.filters)})}function we(t,e){return t===void 0||e===void 0?t===e:t.inclusive===e.inclusive&&t.comparisonValues.length===e.comparisonValues.length&&t.comparisonValues.every((n,r)=>_e(n,e.comparisonValues[r]))}function Ot(t,e){return t.kind!==e.kind?!1:t.kind==="collection"&&e.kind==="collection"?t.path===e.path:t.kind==="collection-group"&&e.kind==="collection-group"&&t.collectionId===e.collectionId}function Pe(t){return t.kind==="where"?Object.freeze({kind:t.kind,field:t.field,op:t.op,value:t.value}):Object.freeze({kind:t.kind,filters:Object.freeze(t.filters.map(Pe))})}function ve(t){if(t!==void 0)return Object.freeze({values:t.values,inclusive:t.inclusive,fromSnapshot:t.fromSnapshot})}function It(t,e){return ze(t.filters,e.filters)&&t.orders.length===e.orders.length&&t.orders.every((n,r)=>{const s=e.orders[r];return n.field===s.field&&n.direction===s.direction})&&t.limitCount===e.limitCount&&t.limitFromEnd===e.limitFromEnd&&we(t.start,e.start)&&we(t.end,e.end)}const Ue="__name__";function xe(t,e){const n=t.data();if(n===void 0)throw new f({code:"not-found",message:`Snapshot-based cursors require an existing document — got an empty snapshot for ${t.id??"<unknown>"}.`});return e.map(r=>r.field===Ue?t.ref:n[r.field])}class j{env;auth;collectionPath;clauses;orders;limitCount;limitFromEnd;start;end;bypassRules;documentRef;constructor(e){this.env=e.env,this.auth=e.auth,this.collectionPath=e.collectionPath,this.documentRef=e.documentRef,this.clauses=Object.freeze((e.clauses??[]).map(n=>N(n))),this.orders=Object.freeze((e.orders??[]).map(n=>Object.freeze({...n}))),this.limitCount=e.limitCount,this.limitFromEnd=e.limitFromEnd??!1,this.start=e.start===void 0?void 0:Object.freeze({...e.start,values:Object.freeze(e.start.comparisonValues.map(n=>n.executionValue)),comparisonValues:e.start.comparisonValues}),this.end=e.end===void 0?void 0:Object.freeze({...e.end,values:Object.freeze(e.end.comparisonValues.map(n=>n.executionValue)),comparisonValues:e.end.comparisonValues}),this.bypassRules=e.bypassRules??!1}where(e,n,r){return this.clone({clauses:[...this.clauses,N({kind:"where",field:e,op:n,value:r})]})}applyFilter(e){return this.clone({clauses:[...this.clauses,N(e)]})}orderBy(e,n="asc"){return this.clone({orders:[...this.orders,{field:e,direction:n}]})}limit(e){return this.clone({limitCount:e,limitFromEnd:!1})}limitToLast(e){return this.clone({limitCount:e,limitFromEnd:!0})}startCursor(e,n){return this.clone({start:U(this.normalizeExplicitCursorValues(e),n,!1)})}endCursor(e,n){return this.clone({end:U(this.normalizeExplicitCursorValues(e),n,!1)})}startCursorFromSnapshot(e,n){return this.clone({start:U(xe(e,this.normalizedOrders()),n,!0)})}endCursorFromSnapshot(e,n){return this.clone({end:U(xe(e,this.normalizedOrders()),n,!0)})}isStructurallyEqual(e){return e instanceof j?this.env===e.env&&this.bypassRules===e.bypassRules&&Ot(this.queryScope(),e.queryScope())&&It(this.comparableExecutionSpec(),e.comparableExecutionSpec()):!1}clone(e){return new j({env:this.env,auth:this.auth,collectionPath:this.collectionPath,documentRef:this.documentRef,clauses:e.clauses??this.clauses,orders:e.orders??this.orders,limitCount:"limitCount"in e?e.limitCount:this.limitCount,limitFromEnd:e.limitFromEnd??this.limitFromEnd,start:"start"in e?e.start:this.start,end:"end"in e?e.end:this.end,bypassRules:this.bypassRules})}queryScope(){return{kind:"collection",path:this.collectionPath}}readQueryRows(e){const n=e?.auth!==void 0?e.auth:this.auth;let r;try{r=this.env.runQuery({scope:this.queryScope(),auth:n,execution:this.executionSpec(),bypassRules:this.bypassRules,activityQuery:this.activityQuery()})}catch(s){const i=s?.simError;throw i?new f(i):s}if(!r.allowed)throw new f(r.error);return r.docs}activityQuery(){const e=r=>r.kind==="where"?{kind:"where",field:r.field,op:r.op,value:$(r.value)}:{kind:r.kind,filters:r.filters.map(e)},n=r=>r===void 0?null:{values:r.values.map(s=>$(s)),inclusive:r.inclusive,fromSnapshot:r.fromSnapshot};return{scope:this.activityScope(),filters:this.clauses.map(e),orderBy:this.orders.map(r=>({...r})),limit:this.limitCount??null,limitFromEnd:this.limitFromEnd,start:n(this.start),end:n(this.end)}}activityScope(){return{kind:"collection"}}normalizedOrders(){return ht(this.executionSpec())}normalizeExplicitCursorValues(e){const n=this.orders;return e.map((r,s)=>{if(n[s]?.field!==Ue)return r;if(typeof r!="string")throw new f({code:"invalid-argument",message:"Expected a string for a document ID cursor value."});const i=r.includes("/")?r:`${this.collectionPath}/${r}`;return this.documentRef(i)})}executionSpec(){const e={filters:Object.freeze(this.clauses.map(Pe)),orders:this.orders,limitCount:this.limitCount,limitFromEnd:this.limitFromEnd,start:ve(this.start),end:ve(this.end)};return Object.freeze(e)}comparableExecutionSpec(){return{filters:this.clauses,orders:this.orders,limitCount:this.limitCount,limitFromEnd:this.limitFromEnd,start:this.start,end:this.end}}snapshotConstraints(){return Object.freeze({execution:this.executionSpec(),activityQuery:this.activityQuery()})}async get(e){const r=this.readQueryRows(e).map(s=>{const i=this.documentRef(s.path),a=oe(s.data);return{id:i.id,ref:i,exists:!0,data:()=>a,get:o=>J(a,o)}});return{size:r.length,empty:r.length===0,docs:r,forEach(s){r.forEach(s)}}}async aggregate(e,n){const r=this.readQueryRows(n),s={};for(const i of Object.keys(e))s[i]=jt(e[i],r);return{data:()=>s}}}function Dt(t,e){if(!e.includes("."))return t[e];let n=t;for(const r of e.split(".")){if(typeof n!="object"||n===null||Array.isArray(n))return;n=n[r]}return n}function jt(t,e){if(t.kind==="count")return e.length;let n=0,r=0;for(const s of e){const i=Dt(s.data,t.field);typeof i=="number"&&Number.isFinite(i)&&(n+=i,r++)}return t.kind==="sum"?n:r===0?null:n/r}function k(t,e){if(t.error)throw new f(t.error);const n=t.debugMessages.length>0?t.debugMessages.join("; "):"(no debug message)";throw new f(q("permission-denied",`${e}: ${n}`))}class Mt{env;auth;bypassRules;collectionRef;id;path;constructor(e,n,r,s=!1,i){this.env=e,this.auth=n,this.bypassRules=s,this.collectionRef=i,this.path=r,this.id=Y(r),S(this,T("reference",r)),ue(this,r,e)}get parent(){return this.collectionRef(De(this.path))}collection(e){return this.collectionRef(`${this.path}/${e}`)}async get(e){const n=this.env.execute({method:"get",path:this.path,auth:e?.auth!==void 0?e.auth:this.auth,bypassRules:this.bypassRules});n.allowed||k(n,"get denied");const r=n.data===void 0||n.data===null?void 0:n.data;return je(this,r)}async set(e,n){const r=n?.auth!==void 0?n.auth:this.auth;if(n?.mergeFields!==void 0){const i=n.mergeFields.map(u=>String(u)),o=this.env.getDocument(this.path)===null?"create":"update",c=this.env.execute({method:o,path:this.path,data:e,auth:r,merge:{mergeFields:i},bypassRules:this.bypassRules});c.allowed||k(c,`set denied (as ${o})`);return}if(n?.merge===!0){const a=this.env.getDocument(this.path)===null?"create":"update",o=this.env.execute({method:a,path:this.path,data:e,auth:r,merge:!0,bypassRules:this.bypassRules});o.allowed||k(o,`set denied (as ${a})`);return}const s=this.env.execute({method:"set",path:this.path,data:e,auth:r,bypassRules:this.bypassRules});s.allowed||k(s,"set denied")}async update(e,n){const r=this.env.execute({method:"update",path:this.path,data:e,auth:n?.auth!==void 0?n.auth:this.auth,bypassRules:this.bypassRules});r.allowed||k(r,"update denied")}async delete(e){const n=this.env.execute({method:"delete",path:this.path,auth:e?.auth!==void 0?e.auth:this.auth,bypassRules:this.bypassRules});n.allowed||k(n,"delete denied")}}function L(t,e,n,r){return new Mt(t,e,n,r,s=>new Ne(t,e,s,r))}class Ne extends j{id;path;constructor(e,n,r,s=!1){super({env:e,auth:n,collectionPath:r,bypassRules:s,documentRef:i=>L(e,n,i,s)}),this.path=r,this.id=Y(r)}doc(e){const n=e??Fe();return L(this.env,this.auth,`${this.path}/${n}`,this.bypassRules)}async add(e,n){const r=this.doc();return await r.set(e,n),r}}class he extends j{collectionId;constructor(e){super({...e,collectionPath:"",documentRef:n=>L(e.env,e.auth,n,e.bypassRules??!1)}),this.collectionId=e.collectionId}clone(e){return new he({env:this.env,auth:this.auth,collectionId:this.collectionId,clauses:e.clauses??this.clauses,orders:e.orders??this.orders,limitCount:"limitCount"in e?e.limitCount:this.limitCount,limitFromEnd:e.limitFromEnd??this.limitFromEnd,start:"start"in e?e.start:this.start,end:"end"in e?e.end:this.end,bypassRules:this.bypassRules})}queryScope(){return{kind:"collection-group",collectionId:this.collectionId}}activityScope(){return{kind:"collection-group"}}}class _t{env;auth;bypassRules;ops=[];constructor(e,n,r=!1){this.env=e,this.auth=n,this.bypassRules=r}set(e,n){const s=this.env.getDocument(e.path)===null?"create":"update";return this.ops.push({method:s,path:e.path,data:n}),this}update(e,n){return this.ops.push({method:"update",path:e.path,data:n}),this}delete(e){return this.ops.push({method:"delete",path:e.path}),this}async commit(e){if(this.ops.length===0)return;const n=this.env.batch(this.ops,e?.auth!==void 0?e.auth:this.auth,this.bypassRules);if(n.allowed)return;if(n.error)throw new f(n.error);const s=n.results.find(i=>!i.allowed)?.debugMessages.join("; ")??"batch denied";throw new f(q("permission-denied",`batch failed: ${s}`))}}function zt(t){return typeof t.where=="function"}class Pt{simTx;constructor(e){this.simTx=e}async get(e){if(zt(e)){const s=await e.get();return s.size>0&&this.simTx.getAll(...s.docs.map(i=>i.ref.path)),s}const r=this.simTx.get(e.path).data();return je(e,r)}set(e,n){return this.simTx.set(e.path,n),this}update(e,n){return this.simTx.update(e.path,n),this}delete(e){return this.simTx.delete(e.path),this}}class Ut{env;auth;bypassRules;provenance;constructor(e,n,r=!1,s){this.env=e,this.auth=n,this.bypassRules=r,this.provenance=s}collection(e){if(!le(e))throw new f(q("invalid-argument",`collection path must have an odd number of segments: ${e}`));return new Ne(this.env,this.auth,e,this.bypassRules)}doc(e){if(!Ie(e))throw new f(q("invalid-argument",`document path must have an even number of segments: ${e}`));return L(this.env,this.auth,e,this.bypassRules)}collectionGroup(e){if(e.length===0||e.includes("/"))throw new f(q("invalid-argument",`collection group id must be a single non-empty segment with no '/': ${e}`));return new he({env:this.env,auth:this.auth,collectionId:e,bypassRules:this.bypassRules})}batch(){return new _t(this.env,this.auth,this.bypassRules)}async runTransaction(e,n){let r;try{r=await this.env.transaction(s=>{const i=new Pt(s);return e(i)},{auth:n?.auth!==void 0?n.auth:this.auth,bypassRules:this.bypassRules,provenance:this.provenance,maxAttempts:n?.maxAttempts})}catch(s){throw s instanceof ft?new f(s.simError):s instanceof mt?new f(s.simError):s}if(!r.allowed){if(r.error)throw new f(r.error);const i=r.writes.find(a=>!a.allowed)?.debugMessages.join("; ")??"transaction denied at commit";throw new f(q("permission-denied",`transaction failed: ${i}`))}return r.returnValue}}function Nt(t,e){return new Ut(t,e?.auth??null,e?.bypassRules??!1,e?.provenance)}const Bt=Symbol("pyric/sandbox/context"),Vt=Symbol("pyric/sandbox/bypassRules");function $t(t){const e=t.trim();if(!e)return[];const n=e.indexOf(": ");if(n!==-1){const s=e.slice(n+2).trim().split("; ").map(i=>i.trim()).filter(i=>i.length>0);if(s.length>0)return s}return[e]}function Se(t,e){if(t instanceof f===!1)return t;const r=t.code;if(r==="permission-denied"===!1)return new p(r,t.message);const i={auth:e.auth,reasons:$t(t.message)},a=t.simError;if(a!==void 0){if(a.request!==void 0){const m=a.request;i.request={method:m.method,path:m.path},m.resourceData!==void 0&&(i.request.resourceData=m.resourceData)}if(a.resource!==void 0&&(i.resource={data:a.resource.data,exists:a.resource.exists}),a.query!==void 0&&(i.query=a.query),a.rule!==void 0&&(i.rule=a.rule),a.remediation!==void 0)return new p({code:r,message:t.message,denialContext:i,remediation:a.remediation})}return new p(r,t.message,i)}function W(t,e,n=!1){return new Proxy(t,{get(r,s,i){if(s===Bt)return e;if(s===Vt)return n;const a=Reflect.get(r,s,i);return typeof a!="function"?a:function(...o){let c;try{const u=()=>a.apply(r,o);c=e.sandbox.runWithProvenance?.(Ae(e.operationContext),u)??u()}catch(u){throw Se(u,e)}return c instanceof Promise?c.then(u=>u&&typeof u=="object"?W(u,e,n):u).catch(u=>{throw Se(u,e)}):c&&typeof c=="object"?W(c,e,n):c}}})}function Be(t,e=!1){if(de(t.sandbox))throw new Error("pyric/sandbox/admin-firestore: internal — a remote sandbox context reached the local engine builder; remote dispatch should have handled it. Please report this.");const n=()=>Nt(O(t.sandbox),{auth:t.auth,bypassRules:e,provenance:Ae(t.operationContext)});return{collection(r){return n().collection(r)},doc(r){return n().doc(r)},collectionGroup(r){return n().collectionGroup(r)},batch(){return n().batch()},runTransaction(r,s){return n().runTransaction(r,s)},setRules(r){return O(t.sandbox).deployRules(r)},seed(r){const s=O(t.sandbox);return s.seed({rules:s.getRules(),documents:r?.documents??{}})},snapshot(){return O(t.sandbox).snapshot()}}}function re(t){if(t instanceof p)return t;if(t!==null&&typeof t=="object"){const e=t;if(typeof e.code=="string"&&typeof e.message=="string"){const n=e.denialContext!==null&&typeof e.denialContext=="object"?e.denialContext:void 0;return new p(e.code,e.message,n)}}return t}function g(t){return new p("invalid-argument",t)}async function y(t,e){try{return await t.sandbox.channel.op({...e,actAs:t.lens})}catch(n){throw re(n)}}const Lt=new Set(["serverTimestamp","increment","arrayUnion","arrayRemove","deleteField"]);function Wt(t){return t!==null&&typeof t=="object"&&typeof t.__type=="string"&&Lt.has(t.__type)}function Qt(t){switch(t.__type){case"serverTimestamp":return{__sentinel:"serverTimestamp"};case"increment":return{__sentinel:"increment",n:t.value};case"arrayUnion":return{__sentinel:"arrayUnion",values:t.values.map(b)};case"arrayRemove":return{__sentinel:"arrayRemove",values:t.values.map(b)};case"deleteField":return{__sentinel:"deleteField"}}}function b(t){if(t===null||typeof t!="object")return t;if(Wt(t))return Qt(t);if(t instanceof ye)return t.toJSON();if(t instanceof Date)return ye.fromDate(t).toJSON();if(Array.isArray(t))return t.map(b);const e=Object.getPrototypeOf(t);if(e!==Object.prototype&&e!==null){const r=t.toJSON;if(typeof r=="function")return r.call(t);throw g(`remote Firestore: cannot serialize a ${e.constructor?.name??"class"} instance for the wire — use plain data, Timestamp, Date, or FieldValue sentinels.`)}const n={};for(const[r,s]of Object.entries(t))n[r]=b(s);return n}function C(t){return b(t)}function se(t){return pt(JSON.parse(t.json))}function Ve(t){return oe(se(t))}function $e(t,e){const n=e.exists&&e.data?Ve(e.data):void 0;return{id:t.id,ref:t,exists:e.exists,data:()=>n,get:r=>J(n,r)}}function Le(t,e){const n=e.docs.map(r=>{const s=r.path??r.id,i=Q(t,s),a=r.data?Ve(r.data):{};return{id:i.id,ref:i,exists:!0,data:()=>a,get:o=>J(a,o)}});return{size:n.length,empty:n.length===0,docs:n,forEach(r){n.forEach(s=>r(s))}}}const We=Symbol("pyric/sandbox/admin-firestore/remoteSnapshotRegistrar");function Qe(...t){throw new p("failed-precondition","remote Firestore: onSnapshot is not wired yet — import pyric-admin/firestore (or pyric/sandbox/admin-firestore) before subscribing.")}function Ge(t,e,n,r,s){const i=n.includeMetadataChanges!==!0;let a,o;try{o=t.sandbox.channel.subscribe({target:e,actAs:t.lens},c=>{if(!r)return;const u=c;if(Array.isArray(u.docs)){const l=u.docs.map(m=>({path:m.path??m.id,data:m.data?se(m.data):{}})),d=e.__ref==="query"?e.source.__ref==="group"?e.source.collectionId:e.source.path:e.__ref==="group"?e.collectionId:e.path,h=yt({path:d},l,{excludesMetadataChanges:i},a);a=l,r(h)}else if(typeof u.id=="string"){const l=u.path??u.id,d=u.exists&&u.data?se(u.data):null;r(gt(l,d))}},c=>{const u=re(c);s?s(u):console.error("pyric remote Firestore: uncaught onSnapshot error:",u)})}catch(c){throw re(c)}return o}const Je=Symbol("pyric/sandbox/admin-firestore/remoteQueryState");function Gt(t){if(!(t===null||typeof t!="object"))return t[Je]}function He(t){return t.kind==="where"?(Ye(t.op),{kind:"where",field:t.field,op:t.op,value:b(t.value)}):{kind:t.kind,filters:t.filters.map(He)}}const Jt=new Set(["<","<=","==","!=",">=",">","in","not-in","array-contains","array-contains-any"]);function Ye(t){if(!Jt.has(t))throw g(`invalid where operator: ${String(t)}`)}function B(t){const e=[...t.filters];for(const n of t.orders)e.push({kind:"orderBy",field:n.field,direction:n.direction});return t.start&&e.push({kind:t.start.inclusive?"startAt":"startAfter",values:t.start.values.map(b)}),t.end&&e.push({kind:t.end.inclusive?"endAt":"endBefore",values:t.end.values.map(b)}),t.limitCount!==void 0&&e.push(t.limitFromEnd?{kind:"limitToLast",n:t.limitCount}:{kind:"limit",n:t.limitCount}),e.length===0?t.source:{__ref:"query",source:t.source,constraints:e}}function Ce(t,e,n){if(e.length===0)throw g(`${n}(snapshot) on a remote sandbox requires at least one explicit orderBy() clause — the relay protocol has no document-key cursor.`);if(e.some(s=>s.field==="__name__"))throw g(`${n}(snapshot) on a remote sandbox does not support orderBy('__name__') — the relay protocol has no document-key cursor, and __name__ is not a data field a value cursor can carry. Order by a real document field instead.`);const r=t.data();if(r===void 0)throw new p("not-found",`Snapshot-based cursors require an existing document — got an empty snapshot for ${t.id??"<unknown>"}.`);return e.map(s=>r[s.field])}function fe(t,e){const n=s=>fe(t,{...e,...s});return{[Je]:e,where(s,i,a){return Ye(i),n({filters:[...e.filters,{kind:"where",field:s,op:i,value:b(a)}]})},applyFilter(s){return n({filters:[...e.filters,He(s)]})},orderBy(s,i="asc"){return n({orders:[...e.orders,{field:s,direction:i}]})},limit(s){return n({limitCount:s,limitFromEnd:!1})},limitToLast(s){return n({limitCount:s,limitFromEnd:!0})},startCursor(s,i){return n({start:{values:[...s],inclusive:i}})},endCursor(s,i){return n({end:{values:[...s],inclusive:i}})},startCursorFromSnapshot(s,i){return n({start:{values:Ce(s,e.orders,i?"startAt":"startAfter"),inclusive:i}})},endCursorFromSnapshot(s,i){return n({end:{values:Ce(s,e.orders,i?"endAt":"endBefore"),inclusive:i}})},async get(s){V(e);const i=await y(t,{method:"getDocs",source:B(e)});return Le(t,i)},async aggregate(s){V(e);const i=await y(t,{method:"aggregate",source:B(e),spec:s});return{data:()=>i.data}},[We]:(s,i,a)=>(V(e),Ge(t,B(e),s,i,a)),onSnapshot:Qe}}function V(t){if(t.limitFromEnd&&t.orders.length===0)throw g("limitToLast() queries require at least one orderBy clause.")}function ie(t,e){const n=fe(t,{source:{__ref:"collection",path:e},filters:[],orders:[],limitFromEnd:!1});return Object.assign(Object.create(Object.getPrototypeOf(n)),n,{id:Y(e),path:e,doc(s){const i=s??Fe();return Q(t,`${e}/${i}`)},async add(s,i){const a=await y(t,{method:"addDoc",collectionPath:e,data:C(s)});return Q(t,a.path)}})}function Q(t,e){const n={id:Y(e),path:e,get parent(){return ie(t,De(e))},collection(r){const s=`${e}/${r}`;if(!le(s))throw g(`collection path must have an odd number of segments: ${s}`);return ie(t,s)},async get(r){const s=await y(t,{method:"getDoc",path:e});return $e(n,s)},async set(r,s){await y(t,{method:"setDoc",path:e,data:C(r),...ke(s)?{options:ke(s)}:{}})},async update(r,s){await y(t,{method:"updateDoc",path:e,data:C(r)})},async delete(r){await y(t,{method:"deleteDoc",path:e})},[We]:(r,s,i)=>Ge(t,{__ref:"doc",path:e},r,s,i),onSnapshot:Qe};return n}function ke(t){if(!t)return;const e={};return t.merge!==void 0&&(e.merge=t.merge),t.mergeFields!==void 0&&(e.mergeFields=[...t.mergeFields]),e.merge!==void 0||e.mergeFields!==void 0?e:void 0}function Ht(t){const e=[];let n=!1;const r={set(i,a){return s(),e.push({method:"set",path:i.path,data:C(a)}),r},update(i,a){return s(),e.push({method:"update",path:i.path,data:C(a)}),r},delete(i){return s(),e.push({method:"delete",path:i.path}),r},async commit(i){s(),n=!0,await y(t,{method:"batchCommit",writes:e})}};function s(){if(n)throw new p("failed-precondition","WriteBatch has already been committed.")}return r}const Yt=5;async function Xt(t,e,n=Yt){for(let r=0;r<n;r++){const s=[],i=[],a=()=>{if(i.length>0)throw new p("failed-precondition",bt)},o={get(d){a();const h=Gt(d);return h!==void 0?u(h):c(d)},set(d,h){return i.push({method:"set",path:d.path,data:C(h)}),o},update(d,h){return i.push({method:"update",path:d.path,data:C(h)}),o},delete(d){return i.push({method:"delete",path:d.path}),o}};async function c(d){const h=await y(t,{method:"getDoc",path:d.path});return s.push({path:h.path??d.path,data:h.exists&&h.data?h.data:null}),$e(d,h)}async function u(d){V(d);const h=await y(t,{method:"getDocs",source:B(d)});for(const m of h.docs)s.push({path:m.path??m.id,data:m.data??null});return Le(t,h)}const l=await e(o);try{return await y(t,{method:"txnCommit",reads:s,writes:i}),l}catch(d){if(d instanceof p&&d.code==="aborted")continue;throw d}}throw new p("aborted",`Transaction failed after ${n} attempts due to repeated conflicts. Another writer is concurrently updating the same documents.`)}function Z(t,e){return new p("unimplemented",`SandboxFirestore.${t} is not available on a remote sandbox — its return value is synchronous and the data lives in the browser worker. ${e}`)}function Xe(t,e){const n={sandbox:t,lens:e};return{collection(r){if(!le(r))throw g(`collection path must have an odd number of segments: ${r}`);return ie(n,r)},doc(r){if(!Ie(r))throw g(`document path must have an even number of segments: ${r}`);return Q(n,r)},collectionGroup(r){if(r.length===0||r.includes("/"))throw g(`collection group id must be a single non-empty segment with no '/': ${r}`);return fe(n,{source:{__ref:"group",collectionId:r},filters:[],orders:[],limitFromEnd:!1})},batch(){return Ht(n)},runTransaction(r,s){return Xt(n,r,s?.maxAttempts)},setRules(){throw Z("setRules","Deploy rules asynchronously through the relay instead: `await sandbox.channel.op({ method: 'setFirestoreRules', source })`.")},seed(){throw Z("seed","The relay has no atomic seed op; write seed docs through this handle (`db.doc(path).set(data)` / `db.batch()`), or drive `sandbox.channel.op({ method: 'admin.setDocument', path, data })` per document.")},snapshot(){throw Z("snapshot","Read the worker state asynchronously instead: `await sandbox.channel.op({ method: 'admin.readState' })`.")}}}const qe=new WeakMap;function Ke(t){const e=qe.get(t);if(e)return e;const n=de(t.sandbox)?Xe(t.sandbox,Kt(t.auth)):W(Be(t),t);return qe.set(t,n),n}function Kt(t){if(t==null)return{mode:"anon"};const e={mode:"as",uid:t.uid};return t.token!==void 0&&(e.token=t.token),t.tenant!==void 0&&(e.tenant=t.tenant),e}const Ee=new WeakMap;function Zt(t){const e=en(t)?t:t.withAuth(null),n=Ee.get(e);if(n)return n;const r=e.operationContext.authLens.mode==="admin"?e:ce(e,{source:e.operationContext.source,authLens:{mode:"admin"},...e.operationContext.planId===void 0?{}:{planId:e.operationContext.planId}}),s=de(r.sandbox)?Xe(r.sandbox,{mode:"admin"}):W(Be(r,!0),r,!0);return Ee.set(e,s),s}function en(t){return t!==null&&typeof t=="object"&&"sandbox"in t&&typeof t.withAuth=="function"}class x{bytes;constructor(e){this.bytes=e,S(this,wt(e)),H(this,Object.freeze({type:"bytes",values:Object.freeze(Array.from(e))}),()=>new x(e.slice()))}static fromBase64String(e){const n=atob(e),r=new Uint8Array(n.length);for(let s=0;s<n.length;s+=1)r[s]=n.charCodeAt(s);return new x(r)}static fromUint8Array(e){return new x(e.slice())}toBase64(){let e="";for(const n of this.bytes)e+=String.fromCharCode(n);return btoa(e)}toUint8Array(){return this.bytes.slice()}toString(){return`Bytes(base64: ${this.toBase64()})`}isEqual(e){return e instanceof x&&this.bytes.length===e.bytes.length&&this.bytes.every((n,r)=>n===e.bytes[r])}toJSON(){return{type:"firestore/bytes/1.0",bytes:this.toBase64()}}static fromJSON(e){const n=e;if(n.type!=="firestore/bytes/1.0"||typeof n.bytes!="string")throw new TypeError("Invalid Bytes JSON value.");return x.fromBase64String(n.bytes)}}class I{lat;lng;constructor(e,n){if(this.lat=e,this.lng=n,!Number.isFinite(e)||e<-90||e>90)throw new TypeError("Latitude must be a number between -90 and 90.");if(!Number.isFinite(n)||n<-180||n>180)throw new TypeError("Longitude must be a number between -180 and 180.");S(this,T("geo-point",String(e),"\0",String(n))),H(this,Object.freeze({type:"geo-point",latitude:e,longitude:n}),()=>new I(e,n))}get latitude(){return this.lat}get longitude(){return this.lng}isEqual(e){return e instanceof I&&this.lat===e.lat&&this.lng===e.lng}toJSON(){return{latitude:this.lat,longitude:this.lng,type:"firestore/geoPoint/1.0"}}static fromJSON(e){const n=e;if(n.type!=="firestore/geoPoint/1.0"||typeof n.latitude!="number"||typeof n.longitude!="number")throw new TypeError("Invalid GeoPoint JSON value.");return new I(n.latitude,n.longitude)}}class E{_values;constructor(e){this._values=e,H(this,Object.freeze({type:"vector",values:Object.freeze(e.slice())}),()=>new E(e.slice()))}static create(e){if(!e.every(n=>typeof n=="number"))throw new TypeError("Vector values must be numbers.");return new E(e.slice())}toArray(){return this._values.slice()}isEqual(e){const n=e instanceof E?e._values:void 0;return n!==void 0&&this._values.length===n.length&&this._values.every((r,s)=>r===n[s])}toJSON(){return{type:"firestore/vectorValue/1.0",vectorValues:this.toArray()}}static fromJSON(e){const n=e;if(n.type!=="firestore/vectorValue/1.0"||!Array.isArray(n.vectorValues)||!n.vectorValues.every(r=>typeof r=="number"))throw new TypeError("Invalid VectorValue JSON value.");return E.create(n.vectorValues)}}function tn(t=[]){return E.create(t)}function wn(){return vt.serverTimestamp()}const v=Symbol("pyric/firestore/target");function z(t){return t.kind==="sandbox"?t.db:t.getDb()}const X=new WeakMap;function nn(t,e){return X.set(t,e),t}const P=new WeakMap;function rn(t,e,n){return X.set(t,e),P.set(t,n),t}function me(t,e){const n=A(e);if(t.kind==="sandbox")return n;const r=P.get(n);if(!r)throw new TypeError("pyric/firestore: live ref missing rebuild closure — was it produced by a factory in this package?");return r(z(t))}function sn(t,e){const n=A(e);if(t.kind==="sandbox")return n;const r=P.get(n);if(!r)throw new TypeError("pyric/firestore: live collection ref missing rebuild closure.");return r(z(t))}function vn(t,e){const n=A(e);if(t.kind==="sandbox")return n;const r=P.get(n);if(!r)throw new TypeError("pyric/firestore: live query missing rebuild closure.");return r(z(t))}function M(t,e,n){return e.kind==="sandbox-live"?rn(t,e,n):nn(t,e)}function xn(t){const e=A(t),n=P.get(e);return n||(()=>e)}function F(t){const e=an(t);return e.assertUsable?.(),e}function an(t){let e;if(v in t?e=t[v]:e=X.get(t),!e)throw new TypeError("pyric/firestore: unrecognized reference — was it produced by a factory in this package?");return e}const Ze=new WeakMap,et=new WeakMap;function pe(t){return Ze.get(t)}function A(t){return et.get(t)??t}function tt(t,e,n){const r={id:t.id??"",path:t.path??""};return Ze.set(r,n),et.set(r,t),X.set(r,e),r}function on(t){return t instanceof Ct?Array.from(t.value):null}function ae(t){if(t instanceof xt)return x.fromUint8Array(t.data);if(t instanceof St)return new I(t.lat,t.lng);const e=on(t);if(e)return tn(e);if(Array.isArray(t))return t.map(ae);if(t!==null&&typeof t=="object"){const n=Object.getPrototypeOf(t);if(n===Object.prototype||n===null){const r={};for(const[s,i]of Object.entries(t))r[s]=ae(i);return r}}return t}function Sn(t){if(t!==void 0)return ae(t)}class R extends Error{code;customData;name="FirebaseError";constructor(e,n,r){super(n),this.code=e,this.customData=r,Object.setPrototypeOf(this,new.target.prototype)}}Object.defineProperty(R,"name",{value:"FirebaseError"});let G;function Cn(t){G=t}function nt(t){return G?.resolve(t,!1)}function un(){if(!G)throw new TypeError("No default sandbox app registry is installed - import and initialize pyric/app before calling a service factory without an app.");return G.defaultApp()}function cn(t){if(t===void 0)return cn(un());const e=nt(t);if(e)return e.service("firestore/default",()=>{const{sandbox:n,session:r}=e,s=ee();e.onDelete(s.terminate);const i=e.authScope;i&&s.own(r.onCurrentUserChanged(o=>{O(n).reevaluateLiveListeners(o,i)}));const a={kind:"sandbox-live",sandbox:n,getDb:Te(n,()=>r.currentUser),authScope:i,...s};return{[v]:a,app:t}});if(rt(t)){const r={kind:"sandbox",db:Ke(t),sandbox:t.sandbox,...ee()};return{[v]:r}}if(ln(t)){const n={kind:"sandbox-live",sandbox:t,getDb:Te(t),...ee()};return{[v]:n}}throw new TypeError("pyric/firestore is a sandbox-only mirror. Package resolution must leave firebase/firestore unchanged for production; activate pyric sandbox or @pyric/cli/register before importing to select the sandbox.")}function ee(){let t=!1;const e=new Set;return{own(n){return t?(n(),()=>{}):(e.add(n),()=>e.delete(n))},assertUsable(){if(t)throw new R("failed-precondition","The client has already been terminated.")},terminate(){if(!t){t=!0;for(const n of e)n();e.clear()}}}}function dn(t){const e=nt(t);if(e){e.assertAlive();const a=e.sandbox;return dn(ce(a.withAuth(null),{source:{kind:"app"},authLens:{mode:"admin"}}))}const n=rt(t)?t:t.withAuth(null),r=n.sandbox,i={kind:"sandbox",db:Zt(n),sandbox:r};return{[v]:i}}function rt(t){return t instanceof kt}function ln(t){if(t===null||typeof t!="object")return!1;const e=t;return typeof e.withAuth=="function"&&typeof e.onCurrentUserChanged=="function"&&"currentUser"in e&&"admin"in e}function Te(t,e=()=>t.currentUser){return()=>{const n=ce(t.withAuth(e()),{source:{kind:"app"},authLens:{mode:"app-session"}});return Ke(n)}}class hn{started=!1;networkEnabled=!0;persistenceMode;pending=new Set;cachedPaths=new Set;cachedQueries=new WeakSet;syncObservers=new Set;syncScheduled=!1;markStarted(){this.started=!0}enablePersistence(e){if(this.started||this.persistenceMode!==void 0)throw new R("failed-precondition","Firestore has already been started and persistence can no longer be enabled.");this.persistenceMode=e,this.started=!0}disableNetwork(){this.started=!0,this.networkEnabled=!1}async enableNetwork(){this.started=!0,this.networkEnabled=!0;for(const e of this.pending)e.localDone&&e.resolve(e.value);await Promise.all([...this.pending].map(e=>e.promise))}runWrite(e,n){if(this.started=!0,n&&this.cachedPaths.add(n),this.networkEnabled)return e();let r,s;const i=new Promise((o,c)=>{r=o,s=c}),a={path:n,promise:i,localDone:!1,resolve:r,reject:s};return this.pending.add(a),e().then(o=>{a.localDone=!0,a.value=o,this.networkEnabled&&a.resolve(o)},o=>a.reject(o)),i.then(()=>this.pending.delete(a),()=>this.pending.delete(a)),i}waitForPendingWrites(){this.started=!0;const e=[...this.pending].map(n=>n.promise);return Promise.all(e).then(()=>{})}cachePath(e){this.started=!0,this.cachedPaths.add(e)}assertPathCached(e){if(this.started=!0,!this.cachedPaths.has(e))throw new R("unavailable","Failed to get document from cache. (However, this document may exist on the server.)")}cacheQuery(e){this.started=!0,this.cachedQueries.add(e)}hasCachedQuery(e){return this.started=!0,this.cachedQueries.has(e)||this.cachedPaths.size>0}snapshotMetadata(e){return Object.freeze({fromCache:!this.networkEnabled,hasPendingWrites:[...this.pending].some(n=>n.path===e)})}querySnapshotMetadata(){return Object.freeze({fromCache:!this.networkEnabled,hasPendingWrites:this.pending.size>0})}addSnapshotsInSyncObserver(e){return this.syncObservers.add(e),e(),()=>this.syncObservers.delete(e)}notifySnapshotDelivered(){this.syncScheduled||this.syncObservers.size===0||(this.syncScheduled=!0,queueMicrotask(()=>{this.syncScheduled=!1;for(const e of[...this.syncObservers])e()}))}}const Re=new WeakMap;function _(t){let e=Re.get(t);return e||(e=new hn,Re.set(t,e)),e}function te(t,e,n){return S(t,T("reference",e)),ue(t,e,n),t}function kn(t,...e){const n=F(t),r=v in t,s=r?void 0:pe(t),i=z(n);if(r){if(e.length===0)throw new TypeError("doc(db, path) requires at least one path segment.");const l=e.join("/"),d=i.doc(l),h=M(d,n,m=>m.doc(l));return te(h,l,n)}const a=A(t),o=e.length===0?a.doc():a.doc(e.join("/")),c=o.path,u=M(o,n,l=>l.doc(c));if(s){const l=tt(u,n,s);return te(l,c,n)}return te(u,c,n)}function qn(t,...e){const n=F(t),r=v in t;if(e.length===0)throw new TypeError("collection() requires at least one path segment.");if(r){const c=e.join("/"),u=z(n).collection(c);return M(u,n,l=>l.collection(c))}const s=A(t),i=e.join("/"),a=s.collection(i),o=a.path;return M(a,n,c=>c.collection(o))}function fn(t){return t instanceof Error&&"code"in t&&typeof t.code=="string"}function mn(t){if(t instanceof R||!fn(t))return t;const e={};t.denialContext!==void 0&&(e.denialContext=t.denialContext),t.remediation!==void 0&&(e.remediation=t.remediation);const n=new R(t.code,t.message,Object.keys(e).length===0?void 0:e);return t.denialContext!==void 0&&Object.assign(n,{denialContext:t.denialContext}),t.remediation!==void 0&&Object.assign(n,{remediation:t.remediation}),n}async function K(t){try{return await t()}catch(e){throw mn(e)}}async function En(t,e,n){const r=F(t),s=pe(t),i=s?s.toFirestore(e):e;return _(r).runWrite(()=>K(()=>me(r,t).set(i,n)),t.path)}async function Tn(t,e){const n=F(t);return _(n).runWrite(()=>K(()=>me(n,t).update(e)),t.path)}async function Rn(t){const e=F(t);return _(e).runWrite(()=>K(()=>me(e,t).delete()),t.path)}async function Fn(t,e){const n=F(t),r=pe(t),s=r?r.toFirestore(e):e,i=await _(n).runWrite(async()=>{const c=await K(()=>sn(n,t).add(s));return _(n).cachePath(c.path),c}),a=i.path,o=M(i,n,c=>c.doc(a));if(S(o,T("reference",a)),ue(o,a,n,o),r){const c=tt(o,n,r);return qt(o,c),c}return o}export{Cn as A,dn as B,un as C,nt as D,x as E,R as F,I as G,wn as H,pe as a,et as b,_ as c,kn as d,z as e,Sn as f,cn as g,P as h,bn as i,Me as j,_e as k,F as l,vn as m,me as n,mn as o,xn as p,M as q,gn as r,En as s,nn as t,A as u,tt as v,qn as w,Fn as x,Tn as y,Rn as z};
|