@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/serve.js +4 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +2 -2
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/serve/bundler.d.ts +13 -8
- package/dist/serve/bundler.d.ts.map +1 -1
- package/dist/serve/bundler.js +31 -36
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
- package/dist/serve/docs-ui/docs/api-reference.md +1 -1
- package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
- package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
- package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
- package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
- package/dist/serve/docs-ui/docs/index.json +349 -29
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/docs-ui/docs/overview/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
- package/dist/serve/docs-ui/docs/tutorial.md +701 -0
- package/dist/serve/docs-ui/llms.txt +3 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +3 -0
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +20 -5
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/worker-runtime.d.ts +2 -2
- package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
- package/dist/serve/entries/worker-runtime.js +45 -10
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts +19 -0
- package/dist/serve/runtime/chip-config.d.ts.map +1 -0
- package/dist/serve/runtime/chip-config.js +19 -0
- package/dist/serve/runtime/chip-config.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +10 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -0
- package/dist/serve/runtime/chip-install.js +15 -0
- package/dist/serve/runtime/chip-install.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +17 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -0
- package/dist/serve/runtime/chip.js +257 -0
- package/dist/serve/runtime/chip.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +22 -0
- package/dist/serve/runtime/manifest.d.ts.map +1 -0
- package/dist/serve/runtime/manifest.js +20 -0
- package/dist/serve/runtime/manifest.js.map +1 -0
- package/dist/serve/runtime/status.d.ts +49 -0
- package/dist/serve/runtime/status.d.ts.map +1 -0
- package/dist/serve/runtime/status.js +164 -0
- package/dist/serve/runtime/status.js.map +1 -0
- package/dist/serve/runtime/worker-connection.d.ts +4 -0
- package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
- package/dist/serve/runtime/worker-connection.js +11 -0
- package/dist/serve/runtime/worker-connection.js.map +1 -0
- package/dist/serve/runtime/worker-generation.d.ts +14 -0
- package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
- package/dist/serve/runtime/worker-generation.js +56 -0
- package/dist/serve/runtime/worker-generation.js.map +1 -0
- package/dist/serve/runtime/worker-replacement.d.ts +18 -0
- package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
- package/dist/serve/runtime/worker-replacement.js +29 -0
- package/dist/serve/runtime/worker-replacement.js.map +1 -0
- package/dist/serve/standalone-assets.d.ts +2 -2
- package/dist/serve/standalone-assets.d.ts.map +1 -1
- package/dist/serve/standalone-assets.js +1 -1
- package/dist/serve/standalone-assets.js.map +1 -1
- package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
- package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
- package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
- package/dist/serve/studio-ui/index.html +1 -1
- package/dist/serve/vite-ai-config.d.ts +25 -0
- package/dist/serve/vite-ai-config.d.ts.map +1 -0
- package/dist/serve/vite-ai-config.js +45 -0
- package/dist/serve/vite-ai-config.js.map +1 -0
- package/dist/serve/vite-plugin.d.ts +17 -36
- package/dist/serve/vite-plugin.d.ts.map +1 -1
- package/dist/serve/vite-plugin.js +38 -57
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-rules-source.d.ts +8 -0
- package/dist/serve/vite-rules-source.d.ts.map +1 -0
- package/dist/serve/vite-rules-source.js +23 -0
- package/dist/serve/vite-rules-source.js.map +1 -0
- package/dist/serve/vite-worker-runtime.d.ts +20 -0
- package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
- package/dist/serve/vite-worker-runtime.js +25 -0
- package/dist/serve/vite-worker-runtime.js.map +1 -0
- package/dist/serve/worker/client/connection.d.ts +7 -2
- package/dist/serve/worker/client/connection.d.ts.map +1 -1
- package/dist/serve/worker/client/connection.js +13 -16
- package/dist/serve/worker/client/connection.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +7 -0
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +28 -0
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/runtime-control.d.ts +8 -0
- package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
- package/dist/serve/worker/client/runtime-control.js +11 -0
- package/dist/serve/worker/client/runtime-control.js.map +1 -0
- package/dist/serve/worker/entry.js +44 -2
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.js +1 -1
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
- package/dist/serve/worker/host/firestore-writes.js +3 -2
- package/dist/serve/worker/host/firestore-writes.js.map +1 -1
- package/dist/serve/worker/host/studio.js +1 -1
- package/dist/serve/worker/host/studio.js.map +1 -1
- package/dist/serve/worker/host-context.d.ts +1 -1
- package/dist/serve/worker/host-context.d.ts.map +1 -1
- package/dist/serve/worker/index.d.ts +2 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -1
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/protocol.d.ts +15 -1
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js +2 -2
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/retirement.d.ts +21 -0
- package/dist/serve/worker/retirement.d.ts.map +1 -0
- package/dist/serve/worker/retirement.js +97 -0
- package/dist/serve/worker/retirement.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +5 -6
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/package.json +6 -4
- package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
- package/src/assurance/attachment.ts +211 -0
- package/src/assurance/browser.ts +80 -0
- package/src/assurance/campaign.ts +496 -0
- package/src/assurance/capabilities.ts +446 -0
- package/src/assurance/cases.ts +43 -0
- package/src/assurance/index.ts +75 -0
- package/src/assurance/runner.ts +885 -0
- package/src/assurance/tool-names.ts +15 -0
- package/src/assurance/tools.ts +830 -0
- package/src/assurance/types.ts +352 -0
- package/src/assurance/validation.ts +434 -0
- package/src/bridge/client/bridge.ts +550 -0
- package/src/bridge/client/dispatch.ts +137 -0
- package/src/bridge/client.ts +45 -0
- package/src/bridge/protocol.ts +351 -0
- package/src/bridge/server/audit.ts +53 -0
- package/src/bridge/server/bridge.ts +597 -0
- package/src/bridge/server/headless.ts +160 -0
- package/src/bridge/server/json-schema-to-zod.ts +108 -0
- package/src/bridge/server/local-bridge.ts +66 -0
- package/src/bridge/server/logger.ts +50 -0
- package/src/bridge/server/mcp-contract.ts +93 -0
- package/src/bridge/server/mcp.ts +129 -0
- package/src/bridge/server/peer.ts +232 -0
- package/src/bridge/server/standalone.ts +347 -0
- package/src/bridge/server/tool-metadata.ts +99 -0
- package/src/bridge/server.ts +28 -0
- package/src/cli/can-i-use.ts +50 -0
- package/src/cli/cli.test.ts +493 -0
- package/src/cli/database-rules.ts +329 -0
- package/src/cli/dev-runner.ts +282 -0
- package/src/cli/firebase-json.ts +100 -0
- package/src/cli/firestore-indexes.ts +70 -0
- package/src/cli/index.ts +425 -0
- package/src/cli/init.ts +260 -0
- package/src/cli/mcp-proxy.ts +196 -0
- package/src/cli/parse-args.ts +80 -0
- package/src/cli/rules.ts +259 -0
- package/src/cli/scope.ts +67 -0
- package/src/cli/serve.ts +996 -0
- package/src/cli/service-commands.ts +73 -0
- package/src/cli/snapshot.ts +164 -0
- package/src/cli/storage-rules.ts +172 -0
- package/src/cli/verify.ts +416 -0
- package/src/conformance/.generated/can-i-use-browser.ts +113 -0
- package/src/conformance/.generated/can-i-use.ts +117 -0
- package/src/conformance/.generated/conformance-docs.ts +14 -0
- package/src/conformance/browser.ts +24 -0
- package/src/conformance/can-i-use-tool.ts +51 -0
- package/src/conformance/can-i-use.ts +18 -0
- package/src/conformance/docs.ts +8 -0
- package/src/conformance/index.ts +20 -0
- package/src/conformance/tools.ts +16 -0
- package/src/credentials/core/memoize-ttl.ts +142 -0
- package/src/credentials/core/types.ts +10 -0
- package/src/credentials/node/from-adc.ts +96 -0
- package/src/credentials/node/from-service-account.ts +133 -0
- package/src/credentials/node/index.ts +6 -0
- package/src/discover/concurrency.ts +127 -0
- package/src/discover/crawler-adapter.ts +142 -0
- package/src/discover/crawler.ts +1127 -0
- package/src/discover/credential-free.ts +27 -0
- package/src/discover/findCollectionGroup.ts +131 -0
- package/src/discover/firestore-source.ts +59 -0
- package/src/discover/index.ts +5 -0
- package/src/discover/merge.ts +523 -0
- package/src/discover/session.ts +402 -0
- package/src/discover/tools.ts +201 -0
- package/src/discover/types.ts +187 -0
- package/src/discover/wire.ts +324 -0
- package/src/functions-rtdb/child.ts +382 -0
- package/src/functions-rtdb/delivery.ts +7 -0
- package/src/functions-rtdb/discovery.ts +136 -0
- package/src/functions-rtdb/event.ts +64 -0
- package/src/functions-rtdb/execution.ts +111 -0
- package/src/functions-rtdb/in-memory-delivery.ts +40 -0
- package/src/functions-rtdb/project.ts +74 -0
- package/src/functions-rtdb/projection.ts +104 -0
- package/src/functions-rtdb/reference-pattern.ts +20 -0
- package/src/functions-rtdb/remote-delivery.ts +23 -0
- package/src/pkg-version.ts +49 -0
- package/src/register/esm-exports.ts +63 -0
- package/src/register/hooks.ts +30 -0
- package/src/register/index.ts +150 -0
- package/src/register/mapping.ts +33 -0
- package/src/remote/index.ts +1050 -0
- package/src/rtdb/crawl-snapshot.ts +118 -0
- package/src/rtdb/inspection.ts +137 -0
- package/src/rtdb/load-rules-document.ts +56 -0
- package/src/rtdb/rules-generation-tool.ts +38 -0
- package/src/rtdb/rules-json.ts +29 -0
- package/src/serve/activity-guard.ts +29 -0
- package/src/serve/activity-route.ts +164 -0
- package/src/serve/activity-warning.ts +26 -0
- package/src/serve/bridge-mount.ts +200 -0
- package/src/serve/bundler.ts +519 -0
- package/src/serve/capture-store.ts +65 -0
- package/src/serve/discovery.ts +199 -0
- package/src/serve/entries/ai.ts +292 -0
- package/src/serve/entries/app-backend.ts +4 -0
- package/src/serve/entries/app-client.ts +26 -0
- package/src/serve/entries/app-session-store.ts +47 -0
- package/src/serve/entries/app.ts +11 -0
- package/src/serve/entries/auth-helper-core.ts +181 -0
- package/src/serve/entries/auth-helper-dom.ts +134 -0
- package/src/serve/entries/auth-helper-runtime.ts +20 -0
- package/src/serve/entries/auth.ts +255 -0
- package/src/serve/entries/bridge-url.ts +31 -0
- package/src/serve/entries/database.ts +129 -0
- package/src/serve/entries/firestore.ts +232 -0
- package/src/serve/entries/init.ts +49 -0
- package/src/serve/entries/keepalive.ts +46 -0
- package/src/serve/entries/messaging-sw.ts +75 -0
- package/src/serve/entries/messaging.ts +96 -0
- package/src/serve/entries/runtime.ts +556 -0
- package/src/serve/entries/session-store.ts +97 -0
- package/src/serve/entries/storage.ts +92 -0
- package/src/serve/entries/tab-sync-wiring.ts +274 -0
- package/src/serve/entries/worker-runtime.ts +135 -0
- package/src/serve/init-payload.ts +42 -0
- package/src/serve/namespace.ts +675 -0
- package/src/serve/open-browser.ts +68 -0
- package/src/serve/rules.ts +272 -0
- package/src/serve/runtime/chip-config.ts +30 -0
- package/src/serve/runtime/chip-install.ts +27 -0
- package/src/serve/runtime/chip.ts +282 -0
- package/src/serve/runtime/manifest.ts +37 -0
- package/src/serve/runtime/status.ts +216 -0
- package/src/serve/runtime/worker-connection.ts +14 -0
- package/src/serve/runtime/worker-generation.ts +75 -0
- package/src/serve/runtime/worker-replacement.ts +42 -0
- package/src/serve/sandbox-marker.ts +31 -0
- package/src/serve/server.ts +428 -0
- package/src/serve/standalone-assets.ts +180 -0
- package/src/serve/state-store.ts +166 -0
- package/src/serve/studio/disk-project-store.ts +185 -0
- package/src/serve/studio/disk-workspace.ts +162 -0
- package/src/serve/studio/index.ts +20 -0
- package/src/serve/studio/routes.ts +237 -0
- package/src/serve/studio/store-types.ts +48 -0
- package/src/serve/studio/studio-storage.test.ts +289 -0
- package/src/serve/vite-ai-config.ts +77 -0
- package/src/serve/vite-plugin.ts +1075 -0
- package/src/serve/vite-rules-source.ts +28 -0
- package/src/serve/vite-worker-runtime.ts +51 -0
- package/src/serve/worker/activity-bootstrap.ts +24 -0
- package/src/serve/worker/client/admin-firestore.ts +38 -0
- package/src/serve/worker/client/ai.ts +124 -0
- package/src/serve/worker/client/auth.ts +489 -0
- package/src/serve/worker/client/connection.ts +227 -0
- package/src/serve/worker/client/core.ts +384 -0
- package/src/serve/worker/client/disconnect.ts +45 -0
- package/src/serve/worker/client/firestore-reads.ts +181 -0
- package/src/serve/worker/client/firestore-refs.ts +226 -0
- package/src/serve/worker/client/firestore-writes.ts +232 -0
- package/src/serve/worker/client/handles.ts +91 -0
- package/src/serve/worker/client/messaging.ts +119 -0
- package/src/serve/worker/client/presence.ts +174 -0
- package/src/serve/worker/client/rtdb.ts +311 -0
- package/src/serve/worker/client/rules.ts +63 -0
- package/src/serve/worker/client/runtime-control.ts +22 -0
- package/src/serve/worker/client/service-worker-connection.ts +59 -0
- package/src/serve/worker/client/snapshots.ts +86 -0
- package/src/serve/worker/client/storage.ts +195 -0
- package/src/serve/worker/client/studio.ts +88 -0
- package/src/serve/worker/client.ts +57 -0
- package/src/serve/worker/durable-persistence.ts +137 -0
- package/src/serve/worker/entry.ts +223 -0
- package/src/serve/worker/host/admin-firestore.ts +88 -0
- package/src/serve/worker/host/connection.ts +179 -0
- package/src/serve/worker/host/core.ts +387 -0
- package/src/serve/worker/host/dispatch.ts +316 -0
- package/src/serve/worker/host/firestore-reads.ts +117 -0
- package/src/serve/worker/host/firestore-writes.ts +456 -0
- package/src/serve/worker/host/presence.ts +312 -0
- package/src/serve/worker/host/rtdb.ts +136 -0
- package/src/serve/worker/host/rules.ts +128 -0
- package/src/serve/worker/host/storage.ts +312 -0
- package/src/serve/worker/host/studio.ts +75 -0
- package/src/serve/worker/host/subscriptions.ts +238 -0
- package/src/serve/worker/host-ai.ts +165 -0
- package/src/serve/worker/host-auth.ts +469 -0
- package/src/serve/worker/host-context.ts +273 -0
- package/src/serve/worker/host-events.ts +94 -0
- package/src/serve/worker/host-messaging.ts +240 -0
- package/src/serve/worker/host.ts +47 -0
- package/src/serve/worker/index.ts +196 -0
- package/src/serve/worker/presence-timing.ts +15 -0
- package/src/serve/worker/protocol.ts +1124 -0
- package/src/serve/worker/retirement.ts +125 -0
- package/src/serve/worker/serve-init.ts +605 -0
- package/src/serve/worker/service-worker-channel.ts +33 -0
- package/src/serve/worker/service-worker-relay.ts +86 -0
- package/src/serve/writer-lock.ts +53 -0
- package/src/verify/cases.ts +233 -0
- package/src/verify/fixture.ts +258 -0
- package/src/verify/index.ts +519 -0
- package/src/verify/tools.ts +108 -0
- package/src/version/compat-target.ts +17 -0
- package/src/vite.ts +18 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFORMANCE_SUPPORTS,
|
|
3
|
+
resolveCanIUse,
|
|
4
|
+
type BrowserFeatureSupport,
|
|
5
|
+
type CanIUseOptions,
|
|
6
|
+
} from './.generated/can-i-use-browser.js';
|
|
7
|
+
|
|
8
|
+
export { createCanIUseTool, type CanIUseToolOptions } from './can-i-use-tool.js';
|
|
9
|
+
export type {
|
|
10
|
+
Assurance,
|
|
11
|
+
Availability,
|
|
12
|
+
BrowserFeatureSupport,
|
|
13
|
+
CanIUseMatch,
|
|
14
|
+
CanIUseOptions,
|
|
15
|
+
CanIUseResult,
|
|
16
|
+
DeveloperSurface,
|
|
17
|
+
Fidelity,
|
|
18
|
+
} from './.generated/can-i-use-browser.js';
|
|
19
|
+
|
|
20
|
+
/** Query the compact browser projection. Full claims and evidence remain on
|
|
21
|
+
* the Node-only `@pyric/cli/conformance` entry point. */
|
|
22
|
+
export function canIUse(query: string, options?: CanIUseOptions) {
|
|
23
|
+
return resolveCanIUse<BrowserFeatureSupport>(CONFORMANCE_SUPPORTS, query, options);
|
|
24
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ToolHandler } from '@inbrowser/agent';
|
|
2
|
+
|
|
3
|
+
interface CanIUseResultLike {
|
|
4
|
+
match: 'exact' | 'ambiguous' | 'suggestions' | 'none';
|
|
5
|
+
supports: readonly { summary: string }[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CanIUseToolOptions<R extends CanIUseResultLike> {
|
|
9
|
+
description: string;
|
|
10
|
+
featureDescription: string;
|
|
11
|
+
importPathDescription: string;
|
|
12
|
+
query: (feature: string, options: { importPath?: string }) => R;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resultSummary(result: CanIUseResultLike): string {
|
|
16
|
+
if (result.match === 'none') return 'No conformance feature matched that query';
|
|
17
|
+
if (result.match === 'suggestions') return `No exact match; found ${result.supports.length} deterministic suggestions`;
|
|
18
|
+
if (result.match === 'ambiguous') return `Found ${result.supports.length} exact matches across surfaces`;
|
|
19
|
+
return result.supports[0]!.summary;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The one authored pyric_can_i_use handler shape. Node and browser agents
|
|
23
|
+
* differ only in the query they pass in, so match semantics, argument
|
|
24
|
+
* validation, and summaries cannot drift between surfaces. */
|
|
25
|
+
export function createCanIUseTool<R extends CanIUseResultLike>(options: CanIUseToolOptions<R>): ToolHandler {
|
|
26
|
+
return {
|
|
27
|
+
name: 'pyric_can_i_use',
|
|
28
|
+
description: options.description,
|
|
29
|
+
parameters: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
feature: { type: 'string', description: options.featureDescription },
|
|
33
|
+
importPath: { type: 'string', description: options.importPathDescription },
|
|
34
|
+
},
|
|
35
|
+
required: ['feature'],
|
|
36
|
+
},
|
|
37
|
+
async execute(args) {
|
|
38
|
+
const { feature, importPath } = args as { feature?: unknown; importPath?: unknown };
|
|
39
|
+
if (typeof feature !== 'string' || feature.trim() === '') {
|
|
40
|
+
return { ok: false, summary: 'pyric_can_i_use requires a non-empty feature name' };
|
|
41
|
+
}
|
|
42
|
+
if (importPath !== undefined && (typeof importPath !== 'string' || importPath.trim() === '')) {
|
|
43
|
+
return { ok: false, summary: 'pyric_can_i_use importPath must be a non-empty string when provided' };
|
|
44
|
+
}
|
|
45
|
+
const result = options.query(feature, {
|
|
46
|
+
importPath: typeof importPath === 'string' ? importPath : undefined,
|
|
47
|
+
});
|
|
48
|
+
return { ok: result.match === 'exact', summary: resultSummary(result), data: result };
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFORMANCE_IMPORT_EVIDENCE,
|
|
3
|
+
CONFORMANCE_SUPPORTS,
|
|
4
|
+
resolveCanIUse,
|
|
5
|
+
resolveImportEvidence,
|
|
6
|
+
type CanIUseOptions,
|
|
7
|
+
type FeatureSupport,
|
|
8
|
+
} from './.generated/can-i-use.js';
|
|
9
|
+
|
|
10
|
+
/** Query the generated, build-time conformance support projection. */
|
|
11
|
+
export function canIUse(query: string, options?: CanIUseOptions) {
|
|
12
|
+
return resolveCanIUse<FeatureSupport>(CONFORMANCE_SUPPORTS, query, options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Find the generated compatibility-page evidence for a published import. */
|
|
16
|
+
export function canIUseImport(importPath: string) {
|
|
17
|
+
return resolveImportEvidence(CONFORMANCE_IMPORT_EVIDENCE, importPath);
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The docs projection: every conformance page (per-service compatibility
|
|
2
|
+
* matrices plus the public API coverage scoreboard) fully rendered at package
|
|
3
|
+
* build from the central conformance model. Consumers display these pages —
|
|
4
|
+
* the docs site is one such consumer — they never re-derive the model. */
|
|
5
|
+
export {
|
|
6
|
+
CONFORMANCE_DOCS_PAGES,
|
|
7
|
+
type ConformanceDocsPage,
|
|
8
|
+
} from './.generated/conformance-docs.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Pyric's build-time conformance model by developer-facing feature name.
|
|
3
|
+
* Results keep availability, behavior fidelity, and assurance eligibility as
|
|
4
|
+
* separate trust axes.
|
|
5
|
+
*/
|
|
6
|
+
export {
|
|
7
|
+
type Assurance,
|
|
8
|
+
type Availability,
|
|
9
|
+
type DeveloperSurface,
|
|
10
|
+
type FeatureClaim,
|
|
11
|
+
type FeatureClaimKind,
|
|
12
|
+
type FeatureSupport,
|
|
13
|
+
type Fidelity,
|
|
14
|
+
type ImportEvidence,
|
|
15
|
+
type CanIUseMatch,
|
|
16
|
+
type CanIUseOptions,
|
|
17
|
+
type CanIUseResult,
|
|
18
|
+
} from './.generated/can-i-use.js';
|
|
19
|
+
|
|
20
|
+
export { canIUse, canIUseImport } from './can-i-use.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ToolHandler } from '@inbrowser/agent';
|
|
2
|
+
import { createCanIUseTool } from './can-i-use-tool.js';
|
|
3
|
+
import { CONFORMANCE_SUPPORTS, resolveCanIUse, type FeatureSupport } from './.generated/can-i-use.js';
|
|
4
|
+
|
|
5
|
+
/** Node-only conformance tools. The richer model never enters a browser bundle. */
|
|
6
|
+
export function createConformanceTools(): ToolHandler[] {
|
|
7
|
+
return [
|
|
8
|
+
createCanIUseTool({
|
|
9
|
+
description:
|
|
10
|
+
'Check whether Pyric can support a developer-facing Firebase feature. Reports availability, behavior fidelity, and assurance eligibility separately, with caveats and auditable claims. Exact names resolve first; fuzzy names return deterministic candidates.',
|
|
11
|
+
featureDescription: 'Developer feature name, optionally prefixed by a surface (for example firestore-rules/getAfter).',
|
|
12
|
+
importPathDescription: 'Optional published import path that must expose the feature (for example pyric/storage).',
|
|
13
|
+
query: (feature, options) => resolveCanIUse<FeatureSupport>(CONFORMANCE_SUPPORTS, feature, options),
|
|
14
|
+
}),
|
|
15
|
+
];
|
|
16
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side caching wrapper for credential resolvers.
|
|
3
|
+
*
|
|
4
|
+
* Per F4, the SDK convention is "resolvers fire per-dispatch; hosts
|
|
5
|
+
* memoize via `memoizeTtl` if cost matters." This is that memoizer.
|
|
6
|
+
*
|
|
7
|
+
* Default behavior (locked in v4.1):
|
|
8
|
+
* - Parse `expires_in` from the resolved token's claims when the
|
|
9
|
+
* resolver returns an object whose shape carries it.
|
|
10
|
+
* - Refresh proactively at **90% of TTL** — gives the next caller a
|
|
11
|
+
* fresh token before the in-flight window hits expiry.
|
|
12
|
+
* - Accept an explicit `ttlMs` override when the resolver doesn't
|
|
13
|
+
* surface `expires_in` (rare; tests, mock tokens).
|
|
14
|
+
*
|
|
15
|
+
* Two shapes supported:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Plain string resolver** — `() => Promise<string>`. Caller
|
|
18
|
+
* must supply `ttlMs` (the SDK can't infer TTL from an opaque
|
|
19
|
+
* string).
|
|
20
|
+
* 2. **Structured token resolver** — `() => Promise<{ token: string;
|
|
21
|
+
* expiresIn?: number }>`. `expiresIn` is parsed as seconds; the
|
|
22
|
+
* memoizer refreshes at 90% of that. Callers can still pass
|
|
23
|
+
* `ttlMs` to override.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface MemoizedEntry {
|
|
27
|
+
value: string;
|
|
28
|
+
/** Epoch ms when this entry should be refreshed (not when it
|
|
29
|
+
* expires — the 90% point). */
|
|
30
|
+
refreshAt: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MemoizeTtlOptions {
|
|
34
|
+
/** Explicit TTL in milliseconds. Required for plain-string
|
|
35
|
+
* resolvers; optional override for structured-token resolvers
|
|
36
|
+
* (defaults to 90% of `expiresIn`). */
|
|
37
|
+
ttlMs?: number;
|
|
38
|
+
/** Fraction of TTL to use as the refresh window. Default 0.9. */
|
|
39
|
+
refreshAtFraction?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Per-attempt timeout (ms) on the underlying resolver. Default
|
|
42
|
+
* 30_000 (30 seconds). If the resolver hangs longer than this,
|
|
43
|
+
* the in-flight wait rejects with a timeout error and the next
|
|
44
|
+
* caller can retry afresh — prevents a wedged resolver from
|
|
45
|
+
* deadlocking every subsequent call.
|
|
46
|
+
*/
|
|
47
|
+
resolverTimeoutMs?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wrap a plain string-returning resolver with TTL memoization.
|
|
52
|
+
* Callers must supply `ttlMs` since opaque strings carry no
|
|
53
|
+
* expiry information.
|
|
54
|
+
*/
|
|
55
|
+
export function memoizeTtl(
|
|
56
|
+
resolver: () => Promise<string>,
|
|
57
|
+
opts: MemoizeTtlOptions & { ttlMs: number },
|
|
58
|
+
): () => Promise<string>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Wrap a structured-token resolver with TTL memoization. The
|
|
62
|
+
* memoizer parses `expiresIn` (seconds) from the resolver's return
|
|
63
|
+
* value and refreshes at 90% of TTL by default.
|
|
64
|
+
*/
|
|
65
|
+
export function memoizeTtl(
|
|
66
|
+
resolver: () => Promise<{ token: string; expiresIn?: number }>,
|
|
67
|
+
opts?: MemoizeTtlOptions,
|
|
68
|
+
): () => Promise<string>;
|
|
69
|
+
|
|
70
|
+
export function memoizeTtl(
|
|
71
|
+
resolver:
|
|
72
|
+
| (() => Promise<string>)
|
|
73
|
+
| (() => Promise<{ token: string; expiresIn?: number }>),
|
|
74
|
+
opts: MemoizeTtlOptions = {},
|
|
75
|
+
): () => Promise<string> {
|
|
76
|
+
let entry: MemoizedEntry | null = null;
|
|
77
|
+
let inFlight: Promise<string> | null = null;
|
|
78
|
+
const fraction = opts.refreshAtFraction ?? 0.9;
|
|
79
|
+
const timeoutMs = opts.resolverTimeoutMs ?? 30_000;
|
|
80
|
+
|
|
81
|
+
return async () => {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
if (entry && entry.refreshAt > now) return entry.value;
|
|
84
|
+
// Coalesce concurrent refreshes — only one inflight at a time.
|
|
85
|
+
if (inFlight) return inFlight;
|
|
86
|
+
|
|
87
|
+
inFlight = (async () => {
|
|
88
|
+
// Race the resolver against a timeout so a hung resolver
|
|
89
|
+
// can't wedge every subsequent caller. The timer is cleared
|
|
90
|
+
// either way so we don't keep the process alive.
|
|
91
|
+
let timerHandle: ReturnType<typeof setTimeout> | undefined;
|
|
92
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
93
|
+
timerHandle = setTimeout(() => {
|
|
94
|
+
reject(
|
|
95
|
+
new Error(
|
|
96
|
+
`memoizeTtl: resolver did not complete within ${timeoutMs}ms`,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
}, timeoutMs);
|
|
100
|
+
});
|
|
101
|
+
let raw: string | { token: string; expiresIn?: number };
|
|
102
|
+
try {
|
|
103
|
+
raw = await Promise.race([resolver(), timeout]);
|
|
104
|
+
} finally {
|
|
105
|
+
if (timerHandle !== undefined) clearTimeout(timerHandle);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let token: string;
|
|
109
|
+
let ttlMs: number;
|
|
110
|
+
if (typeof raw === 'string') {
|
|
111
|
+
token = raw;
|
|
112
|
+
if (opts.ttlMs === undefined) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'memoizeTtl: plain-string resolver requires explicit ttlMs',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
ttlMs = opts.ttlMs;
|
|
118
|
+
} else {
|
|
119
|
+
token = raw.token;
|
|
120
|
+
ttlMs =
|
|
121
|
+
opts.ttlMs ??
|
|
122
|
+
(raw.expiresIn !== undefined
|
|
123
|
+
? raw.expiresIn * 1000
|
|
124
|
+
: (() => {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'memoizeTtl: structured-token resolver returned no expiresIn; pass an explicit ttlMs',
|
|
127
|
+
);
|
|
128
|
+
})());
|
|
129
|
+
}
|
|
130
|
+
entry = {
|
|
131
|
+
value: token,
|
|
132
|
+
refreshAt: Date.now() + Math.floor(ttlMs * fraction),
|
|
133
|
+
};
|
|
134
|
+
return token;
|
|
135
|
+
})();
|
|
136
|
+
try {
|
|
137
|
+
return await inFlight;
|
|
138
|
+
} finally {
|
|
139
|
+
inFlight = null;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project identity plus a fresh-enough Google OAuth token resolver.
|
|
3
|
+
*
|
|
4
|
+
* The identity is stable for the lifetime of an operation while token
|
|
5
|
+
* acquisition remains lazy so long-running commands can refresh credentials.
|
|
6
|
+
*/
|
|
7
|
+
export interface ProjectScope {
|
|
8
|
+
readonly projectId: string;
|
|
9
|
+
resolveToken(): Promise<string>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Default Credentials source — the gcloud / workload-identity path,
|
|
3
|
+
* the modern keyless alternative to a long-lived token. Reads the well-known ADC
|
|
4
|
+
* file (`gcloud auth application-default login`):
|
|
5
|
+
* - `authorized_user` -> refresh-token grant (the common dev case).
|
|
6
|
+
* - `service_account` -> delegate to `fromServiceAccount`.
|
|
7
|
+
* The metadata-server path (GCE / Cloud Run) is a separate fallback, not yet
|
|
8
|
+
* wired. Node-only (reads disk) -> lives in node/.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from 'node:fs/promises';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import type { ProjectScope } from '../core/types.js';
|
|
14
|
+
import { memoizeTtl } from '../core/memoize-ttl.js';
|
|
15
|
+
import { fromServiceAccount } from './from-service-account.js';
|
|
16
|
+
|
|
17
|
+
const GOOGLE_TOKEN_URI = 'https://oauth2.googleapis.com/token';
|
|
18
|
+
|
|
19
|
+
/** The well-known ADC file path (honours CLOUDSDK_CONFIG; %APPDATA% on Windows). */
|
|
20
|
+
export function adcWellKnownPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
21
|
+
const base =
|
|
22
|
+
env.CLOUDSDK_CONFIG ??
|
|
23
|
+
(process.platform === 'win32' && env.APPDATA ? join(env.APPDATA, 'gcloud') : join(homedir(), '.config', 'gcloud'));
|
|
24
|
+
return join(base, 'application_default_credentials.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a `ProjectScope` from ADC, or `null` if no ADC file is present. The
|
|
29
|
+
* project is supplied by the caller — an ADC user credential isn't bound to one
|
|
30
|
+
* (`--project` / `.firebaserc`).
|
|
31
|
+
*/
|
|
32
|
+
export async function fromAdc(
|
|
33
|
+
projectId: string,
|
|
34
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
35
|
+
path: string = adcWellKnownPath(env),
|
|
36
|
+
): Promise<ProjectScope | null> {
|
|
37
|
+
let raw: string;
|
|
38
|
+
try {
|
|
39
|
+
raw = await readFile(path, 'utf-8');
|
|
40
|
+
} catch {
|
|
41
|
+
return null; // no ADC file
|
|
42
|
+
}
|
|
43
|
+
let parsed: { type?: string; client_id?: string; client_secret?: string; refresh_token?: string };
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(raw);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (parsed.type === 'authorized_user' && parsed.client_id && parsed.client_secret && parsed.refresh_token) {
|
|
51
|
+
const clientId = parsed.client_id;
|
|
52
|
+
const clientSecret = parsed.client_secret;
|
|
53
|
+
const refreshToken = parsed.refresh_token;
|
|
54
|
+
const resolveToken = memoizeTtl(async () => {
|
|
55
|
+
const { access_token, expires_in } = await exchangeAdcRefreshToken(
|
|
56
|
+
clientId,
|
|
57
|
+
clientSecret,
|
|
58
|
+
refreshToken,
|
|
59
|
+
);
|
|
60
|
+
return { token: access_token, expiresIn: expires_in };
|
|
61
|
+
});
|
|
62
|
+
return Object.freeze({ projectId, resolveToken });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (parsed.type === 'service_account') {
|
|
66
|
+
const sa = await fromServiceAccount(raw); // fromServiceAccount accepts a raw JSON string
|
|
67
|
+
return projectId && projectId !== sa.projectId
|
|
68
|
+
? Object.freeze({ projectId, resolveToken: sa.resolveToken })
|
|
69
|
+
: sa;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null; // unknown ADC type (e.g. external_account) — not supported here
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function exchangeAdcRefreshToken(
|
|
76
|
+
clientId: string,
|
|
77
|
+
clientSecret: string,
|
|
78
|
+
refreshToken: string,
|
|
79
|
+
): Promise<{ access_token: string; expires_in: number }> {
|
|
80
|
+
const response = await fetch(GOOGLE_TOKEN_URI, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
83
|
+
body: new URLSearchParams({
|
|
84
|
+
client_id: clientId,
|
|
85
|
+
client_secret: clientSecret,
|
|
86
|
+
refresh_token: refreshToken,
|
|
87
|
+
grant_type: 'refresh_token',
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`pyric: Application Default Credentials token exchange failed (${response.status}): ${await response.text()}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return (await response.json()) as { access_token: string; expires_in: number };
|
|
96
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a `ProjectScope` from a Google service
|
|
3
|
+
* account JSON file. Exchanges the SA key for a short-lived OAuth
|
|
4
|
+
* access token via the standard JWT-bearer flow, and wires
|
|
5
|
+
* `memoizeTtl` internally so consumers get caching for free.
|
|
6
|
+
*
|
|
7
|
+
* No `firebase-admin` dependency — uses only Node's built-in
|
|
8
|
+
* `crypto` for RS256 signing and `fetch` for the token exchange.
|
|
9
|
+
*
|
|
10
|
+
* **Node-only credential adapter**: the function itself requires
|
|
11
|
+
* `node:fs/promises` + `node:crypto`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { memoizeTtl } from '../core/memoize-ttl.js';
|
|
15
|
+
import type { ProjectScope } from '../core/types.js';
|
|
16
|
+
|
|
17
|
+
const GOOGLE_TOKEN_URI = 'https://oauth2.googleapis.com/token';
|
|
18
|
+
const SCOPE = [
|
|
19
|
+
// Project verification and inspection use Firebase and Google Cloud APIs.
|
|
20
|
+
'https://www.googleapis.com/auth/firebase',
|
|
21
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
|
22
|
+
].join(' ');
|
|
23
|
+
|
|
24
|
+
interface ServiceAccount {
|
|
25
|
+
client_email: string;
|
|
26
|
+
private_key: string;
|
|
27
|
+
project_id: string;
|
|
28
|
+
token_uri?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface TokenResponse {
|
|
32
|
+
access_token: string;
|
|
33
|
+
expires_in: number;
|
|
34
|
+
token_type: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read the service account from a JSON file and return a
|
|
39
|
+
* `ProjectScope` whose `resolveToken` is memoized internally.
|
|
40
|
+
*
|
|
41
|
+
* The JSON file path can be:
|
|
42
|
+
* - An absolute / relative filesystem path (Node only).
|
|
43
|
+
* - A base64-encoded JSON string (when prefixed with `base64:`),
|
|
44
|
+
* for environments that ship the SA in an env var.
|
|
45
|
+
*/
|
|
46
|
+
export async function fromServiceAccount(
|
|
47
|
+
saJsonOrPath: string,
|
|
48
|
+
): Promise<ProjectScope> {
|
|
49
|
+
const sa = await loadServiceAccount(saJsonOrPath);
|
|
50
|
+
const resolveToken = memoizeTtl(async () => {
|
|
51
|
+
const { access_token, expires_in } = await exchangeJwtForAccessToken(sa);
|
|
52
|
+
return { token: access_token, expiresIn: expires_in };
|
|
53
|
+
});
|
|
54
|
+
// Freeze the returned scope so `projectId` can't be mutated at
|
|
55
|
+
// runtime. TS `readonly` is compile-time-only; Object.freeze
|
|
56
|
+
// enforces it at runtime — defense in depth for the
|
|
57
|
+
// security-relevant identity field.
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
projectId: sa.project_id,
|
|
60
|
+
resolveToken,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function loadServiceAccount(saJsonOrPath: string): Promise<ServiceAccount> {
|
|
65
|
+
let raw: string;
|
|
66
|
+
if (saJsonOrPath.startsWith('base64:')) {
|
|
67
|
+
raw = Buffer.from(saJsonOrPath.slice('base64:'.length), 'base64').toString('utf-8');
|
|
68
|
+
} else if (saJsonOrPath.trim().startsWith('{')) {
|
|
69
|
+
raw = saJsonOrPath;
|
|
70
|
+
} else {
|
|
71
|
+
// Dynamic import keeps `node:fs/promises` out of browser bundles
|
|
72
|
+
// when this function isn't called.
|
|
73
|
+
const { readFile } = await import('node:fs/promises');
|
|
74
|
+
raw = await readFile(saJsonOrPath, 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
const parsed = JSON.parse(raw) as Partial<ServiceAccount>;
|
|
77
|
+
if (!parsed.client_email || !parsed.private_key || !parsed.project_id) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
'fromServiceAccount: input is missing required fields (client_email, private_key, project_id)',
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return parsed as ServiceAccount;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function exchangeJwtForAccessToken(sa: ServiceAccount): Promise<TokenResponse> {
|
|
86
|
+
const jwt = await buildSignedJwt(sa);
|
|
87
|
+
const res = await fetch(sa.token_uri ?? GOOGLE_TOKEN_URI, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
90
|
+
body: new URLSearchParams({
|
|
91
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
92
|
+
assertion: jwt,
|
|
93
|
+
}).toString(),
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`fromServiceAccount: token exchange failed (${res.status}): ${await res.text()}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return (await res.json()) as TokenResponse;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function buildSignedJwt(sa: ServiceAccount): Promise<string> {
|
|
104
|
+
// Dynamic import keeps `node:crypto` out of browser bundles when
|
|
105
|
+
// `fromServiceAccount` is never called.
|
|
106
|
+
const { createSign } = await import('node:crypto');
|
|
107
|
+
const now = Math.floor(Date.now() / 1000);
|
|
108
|
+
const header = { alg: 'RS256', typ: 'JWT' };
|
|
109
|
+
const claims = {
|
|
110
|
+
iss: sa.client_email,
|
|
111
|
+
scope: SCOPE,
|
|
112
|
+
aud: sa.token_uri ?? GOOGLE_TOKEN_URI,
|
|
113
|
+
exp: now + 3600,
|
|
114
|
+
iat: now,
|
|
115
|
+
};
|
|
116
|
+
const headerB64 = base64UrlEncode(JSON.stringify(header));
|
|
117
|
+
const claimsB64 = base64UrlEncode(JSON.stringify(claims));
|
|
118
|
+
const signingInput = `${headerB64}.${claimsB64}`;
|
|
119
|
+
const signer = createSign('RSA-SHA256');
|
|
120
|
+
signer.update(signingInput);
|
|
121
|
+
signer.end();
|
|
122
|
+
const signature = signer.sign(sa.private_key);
|
|
123
|
+
const signatureB64 = base64UrlEncodeBuffer(signature);
|
|
124
|
+
return `${signingInput}.${signatureB64}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function base64UrlEncode(s: string): string {
|
|
128
|
+
return base64UrlEncodeBuffer(Buffer.from(s, 'utf-8'));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function base64UrlEncodeBuffer(b: Buffer): string {
|
|
132
|
+
return b.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
133
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded concurrency primitives for the Firestore discover_paths crawler.
|
|
3
|
+
*
|
|
4
|
+
* Phase 0.3 of the validation plan locked layered BFS with `Promise.all`-per-
|
|
5
|
+
* layer as the production strategy (38× faster than serial DFS on the
|
|
6
|
+
* representative corpus). This module provides the two pieces every layer
|
|
7
|
+
* needs:
|
|
8
|
+
*
|
|
9
|
+
* - `Semaphore` — FIFO-ordered counting semaphore with `acquire`/`release`.
|
|
10
|
+
* - `runWithLimit` — ergonomic wrapper that maps a list of async producers
|
|
11
|
+
* through the semaphore and resolves to the ordered results.
|
|
12
|
+
*
|
|
13
|
+
* Kept self-contained so the `firestore/discover` subsystem owns its own
|
|
14
|
+
* concurrency contract.
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* FIFO counting semaphore. `acquire()` resolves immediately while
|
|
20
|
+
* fewer than `max` permits are checked out, otherwise it queues until
|
|
21
|
+
* a `release()` frees a slot. Waiters are served in arrival order.
|
|
22
|
+
*
|
|
23
|
+
* `release()` without a prior `acquire()` is a no-op (does not go
|
|
24
|
+
* negative). This is intentional — it lets defensive `try/finally`
|
|
25
|
+
* release in error paths without bookkeeping.
|
|
26
|
+
*/
|
|
27
|
+
export class Semaphore {
|
|
28
|
+
private readonly max: number;
|
|
29
|
+
private active = 0;
|
|
30
|
+
private queue: Array<() => void> = [];
|
|
31
|
+
|
|
32
|
+
constructor(max: number) {
|
|
33
|
+
if (!Number.isInteger(max) || max < 1) {
|
|
34
|
+
throw new RangeError(
|
|
35
|
+
`Semaphore: max must be a positive integer, got ${max}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
this.max = max;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async acquire(): Promise<void> {
|
|
42
|
+
if (this.active < this.max) {
|
|
43
|
+
this.active++;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
return new Promise<void>((resolve) => {
|
|
47
|
+
this.queue.push(() => {
|
|
48
|
+
this.active++;
|
|
49
|
+
resolve();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
release(): void {
|
|
55
|
+
if (this.active === 0) return; // never go negative
|
|
56
|
+
this.active--;
|
|
57
|
+
const next = this.queue.shift();
|
|
58
|
+
if (next) next();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Number of permits currently checked out. For tests and instrumentation
|
|
63
|
+
* only; do not branch on this in production logic.
|
|
64
|
+
*/
|
|
65
|
+
get inFlight(): number {
|
|
66
|
+
return this.active;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Number of waiters queued. For tests and instrumentation only.
|
|
71
|
+
*/
|
|
72
|
+
get pending(): number {
|
|
73
|
+
return this.queue.length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Run `items` through `producer` concurrently, capping in-flight calls
|
|
79
|
+
* at `limit`. Returns results in input order (same shape as
|
|
80
|
+
* `Promise.all(items.map(producer))` — but bounded).
|
|
81
|
+
*
|
|
82
|
+
* `producer` may throw; the rejection propagates after in-flight work
|
|
83
|
+
* settles. Other items continue running so the rejection isn't masked
|
|
84
|
+
* by a Promise.all-style fast-fail leaving zombie pending work.
|
|
85
|
+
*
|
|
86
|
+
* Implementation detail: uses an internal `Semaphore(limit)`; callers
|
|
87
|
+
* who need to share a permit pool across multiple `runWithLimit` calls
|
|
88
|
+
* (e.g. crawler global RPC cap) should use the `Semaphore` class
|
|
89
|
+
* directly via `acquire`/`release`.
|
|
90
|
+
*/
|
|
91
|
+
export async function runWithLimit<T, R>(
|
|
92
|
+
items: readonly T[],
|
|
93
|
+
limit: number,
|
|
94
|
+
producer: (item: T, index: number) => Promise<R>,
|
|
95
|
+
): Promise<R[]> {
|
|
96
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
97
|
+
throw new RangeError(
|
|
98
|
+
`runWithLimit: limit must be a positive integer, got ${limit}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (items.length === 0) return [];
|
|
102
|
+
|
|
103
|
+
const sem = new Semaphore(limit);
|
|
104
|
+
const results: R[] = new Array<R>(items.length);
|
|
105
|
+
const errors: { index: number; error: unknown }[] = [];
|
|
106
|
+
|
|
107
|
+
await Promise.all(
|
|
108
|
+
items.map(async (item, index) => {
|
|
109
|
+
await sem.acquire();
|
|
110
|
+
try {
|
|
111
|
+
results[index] = await producer(item, index);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
errors.push({ index, error });
|
|
114
|
+
} finally {
|
|
115
|
+
sem.release();
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
// Surface the first error in input order — agents/tests can rely on
|
|
122
|
+
// a deterministic surface even when multiple producers throw.
|
|
123
|
+
errors.sort((a, b) => a.index - b.index);
|
|
124
|
+
throw errors[0]!.error;
|
|
125
|
+
}
|
|
126
|
+
return results;
|
|
127
|
+
}
|