@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.
Files changed (424) hide show
  1. package/dist/cli/serve.js +4 -4
  2. package/dist/cli/serve.js.map +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  4. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  5. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  6. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  7. package/dist/conformance/.generated/can-i-use.js +2 -2
  8. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  9. package/dist/serve/bundler.d.ts +13 -8
  10. package/dist/serve/bundler.d.ts.map +1 -1
  11. package/dist/serve/bundler.js +31 -36
  12. package/dist/serve/bundler.js.map +1 -1
  13. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  14. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  19. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  20. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
  23. package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
  24. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  30. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
  35. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
  36. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
  37. package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
  38. package/dist/serve/docs-ui/docs/index.json +349 -29
  39. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  40. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
  41. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
  42. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  50. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  51. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  57. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  58. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
  68. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
  69. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
  71. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
  72. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  80. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  81. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  99. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  100. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  101. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  102. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  103. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  104. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  105. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  106. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  107. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  108. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  109. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  110. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  111. package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
  112. package/dist/serve/docs-ui/docs/tutorial.md +701 -0
  113. package/dist/serve/docs-ui/llms.txt +3 -1
  114. package/dist/serve/entries/init.d.ts.map +1 -1
  115. package/dist/serve/entries/init.js +3 -0
  116. package/dist/serve/entries/init.js.map +1 -1
  117. package/dist/serve/entries/runtime.d.ts.map +1 -1
  118. package/dist/serve/entries/runtime.js +20 -5
  119. package/dist/serve/entries/runtime.js.map +1 -1
  120. package/dist/serve/entries/worker-runtime.d.ts +2 -2
  121. package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
  122. package/dist/serve/entries/worker-runtime.js +45 -10
  123. package/dist/serve/entries/worker-runtime.js.map +1 -1
  124. package/dist/serve/runtime/chip-config.d.ts +19 -0
  125. package/dist/serve/runtime/chip-config.d.ts.map +1 -0
  126. package/dist/serve/runtime/chip-config.js +19 -0
  127. package/dist/serve/runtime/chip-config.js.map +1 -0
  128. package/dist/serve/runtime/chip-install.d.ts +10 -0
  129. package/dist/serve/runtime/chip-install.d.ts.map +1 -0
  130. package/dist/serve/runtime/chip-install.js +15 -0
  131. package/dist/serve/runtime/chip-install.js.map +1 -0
  132. package/dist/serve/runtime/chip.d.ts +17 -0
  133. package/dist/serve/runtime/chip.d.ts.map +1 -0
  134. package/dist/serve/runtime/chip.js +257 -0
  135. package/dist/serve/runtime/chip.js.map +1 -0
  136. package/dist/serve/runtime/manifest.d.ts +22 -0
  137. package/dist/serve/runtime/manifest.d.ts.map +1 -0
  138. package/dist/serve/runtime/manifest.js +20 -0
  139. package/dist/serve/runtime/manifest.js.map +1 -0
  140. package/dist/serve/runtime/status.d.ts +49 -0
  141. package/dist/serve/runtime/status.d.ts.map +1 -0
  142. package/dist/serve/runtime/status.js +164 -0
  143. package/dist/serve/runtime/status.js.map +1 -0
  144. package/dist/serve/runtime/worker-connection.d.ts +4 -0
  145. package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
  146. package/dist/serve/runtime/worker-connection.js +11 -0
  147. package/dist/serve/runtime/worker-connection.js.map +1 -0
  148. package/dist/serve/runtime/worker-generation.d.ts +14 -0
  149. package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
  150. package/dist/serve/runtime/worker-generation.js +56 -0
  151. package/dist/serve/runtime/worker-generation.js.map +1 -0
  152. package/dist/serve/runtime/worker-replacement.d.ts +18 -0
  153. package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
  154. package/dist/serve/runtime/worker-replacement.js +29 -0
  155. package/dist/serve/runtime/worker-replacement.js.map +1 -0
  156. package/dist/serve/standalone-assets.d.ts +2 -2
  157. package/dist/serve/standalone-assets.d.ts.map +1 -1
  158. package/dist/serve/standalone-assets.js +1 -1
  159. package/dist/serve/standalone-assets.js.map +1 -1
  160. package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
  161. package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
  162. package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
  163. package/dist/serve/studio-ui/index.html +1 -1
  164. package/dist/serve/vite-ai-config.d.ts +25 -0
  165. package/dist/serve/vite-ai-config.d.ts.map +1 -0
  166. package/dist/serve/vite-ai-config.js +45 -0
  167. package/dist/serve/vite-ai-config.js.map +1 -0
  168. package/dist/serve/vite-plugin.d.ts +17 -36
  169. package/dist/serve/vite-plugin.d.ts.map +1 -1
  170. package/dist/serve/vite-plugin.js +38 -57
  171. package/dist/serve/vite-plugin.js.map +1 -1
  172. package/dist/serve/vite-rules-source.d.ts +8 -0
  173. package/dist/serve/vite-rules-source.d.ts.map +1 -0
  174. package/dist/serve/vite-rules-source.js +23 -0
  175. package/dist/serve/vite-rules-source.js.map +1 -0
  176. package/dist/serve/vite-worker-runtime.d.ts +20 -0
  177. package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
  178. package/dist/serve/vite-worker-runtime.js +25 -0
  179. package/dist/serve/vite-worker-runtime.js.map +1 -0
  180. package/dist/serve/worker/client/connection.d.ts +7 -2
  181. package/dist/serve/worker/client/connection.d.ts.map +1 -1
  182. package/dist/serve/worker/client/connection.js +13 -16
  183. package/dist/serve/worker/client/connection.js.map +1 -1
  184. package/dist/serve/worker/client/core.d.ts +7 -0
  185. package/dist/serve/worker/client/core.d.ts.map +1 -1
  186. package/dist/serve/worker/client/core.js +28 -0
  187. package/dist/serve/worker/client/core.js.map +1 -1
  188. package/dist/serve/worker/client/runtime-control.d.ts +8 -0
  189. package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
  190. package/dist/serve/worker/client/runtime-control.js +11 -0
  191. package/dist/serve/worker/client/runtime-control.js.map +1 -0
  192. package/dist/serve/worker/entry.js +44 -2
  193. package/dist/serve/worker/entry.js.map +1 -1
  194. package/dist/serve/worker/host/core.js +1 -1
  195. package/dist/serve/worker/host/core.js.map +1 -1
  196. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  197. package/dist/serve/worker/host/firestore-writes.js +3 -2
  198. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  199. package/dist/serve/worker/host/studio.js +1 -1
  200. package/dist/serve/worker/host/studio.js.map +1 -1
  201. package/dist/serve/worker/host-context.d.ts +1 -1
  202. package/dist/serve/worker/host-context.d.ts.map +1 -1
  203. package/dist/serve/worker/index.d.ts +2 -1
  204. package/dist/serve/worker/index.d.ts.map +1 -1
  205. package/dist/serve/worker/index.js +2 -1
  206. package/dist/serve/worker/index.js.map +1 -1
  207. package/dist/serve/worker/protocol.d.ts +15 -1
  208. package/dist/serve/worker/protocol.d.ts.map +1 -1
  209. package/dist/serve/worker/protocol.js +2 -2
  210. package/dist/serve/worker/protocol.js.map +1 -1
  211. package/dist/serve/worker/retirement.d.ts +21 -0
  212. package/dist/serve/worker/retirement.d.ts.map +1 -0
  213. package/dist/serve/worker/retirement.js +97 -0
  214. package/dist/serve/worker/retirement.js.map +1 -0
  215. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  216. package/dist/serve/worker/serve-init.js +5 -6
  217. package/dist/serve/worker/serve-init.js.map +1 -1
  218. package/dist/vite.d.ts +1 -0
  219. package/dist/vite.d.ts.map +1 -1
  220. package/dist/vite.js.map +1 -1
  221. package/package.json +6 -4
  222. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  223. package/src/assurance/attachment.ts +211 -0
  224. package/src/assurance/browser.ts +80 -0
  225. package/src/assurance/campaign.ts +496 -0
  226. package/src/assurance/capabilities.ts +446 -0
  227. package/src/assurance/cases.ts +43 -0
  228. package/src/assurance/index.ts +75 -0
  229. package/src/assurance/runner.ts +885 -0
  230. package/src/assurance/tool-names.ts +15 -0
  231. package/src/assurance/tools.ts +830 -0
  232. package/src/assurance/types.ts +352 -0
  233. package/src/assurance/validation.ts +434 -0
  234. package/src/bridge/client/bridge.ts +550 -0
  235. package/src/bridge/client/dispatch.ts +137 -0
  236. package/src/bridge/client.ts +45 -0
  237. package/src/bridge/protocol.ts +351 -0
  238. package/src/bridge/server/audit.ts +53 -0
  239. package/src/bridge/server/bridge.ts +597 -0
  240. package/src/bridge/server/headless.ts +160 -0
  241. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  242. package/src/bridge/server/local-bridge.ts +66 -0
  243. package/src/bridge/server/logger.ts +50 -0
  244. package/src/bridge/server/mcp-contract.ts +93 -0
  245. package/src/bridge/server/mcp.ts +129 -0
  246. package/src/bridge/server/peer.ts +232 -0
  247. package/src/bridge/server/standalone.ts +347 -0
  248. package/src/bridge/server/tool-metadata.ts +99 -0
  249. package/src/bridge/server.ts +28 -0
  250. package/src/cli/can-i-use.ts +50 -0
  251. package/src/cli/cli.test.ts +493 -0
  252. package/src/cli/database-rules.ts +329 -0
  253. package/src/cli/dev-runner.ts +282 -0
  254. package/src/cli/firebase-json.ts +100 -0
  255. package/src/cli/firestore-indexes.ts +70 -0
  256. package/src/cli/index.ts +425 -0
  257. package/src/cli/init.ts +260 -0
  258. package/src/cli/mcp-proxy.ts +196 -0
  259. package/src/cli/parse-args.ts +80 -0
  260. package/src/cli/rules.ts +259 -0
  261. package/src/cli/scope.ts +67 -0
  262. package/src/cli/serve.ts +996 -0
  263. package/src/cli/service-commands.ts +73 -0
  264. package/src/cli/snapshot.ts +164 -0
  265. package/src/cli/storage-rules.ts +172 -0
  266. package/src/cli/verify.ts +416 -0
  267. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  268. package/src/conformance/.generated/can-i-use.ts +117 -0
  269. package/src/conformance/.generated/conformance-docs.ts +14 -0
  270. package/src/conformance/browser.ts +24 -0
  271. package/src/conformance/can-i-use-tool.ts +51 -0
  272. package/src/conformance/can-i-use.ts +18 -0
  273. package/src/conformance/docs.ts +8 -0
  274. package/src/conformance/index.ts +20 -0
  275. package/src/conformance/tools.ts +16 -0
  276. package/src/credentials/core/memoize-ttl.ts +142 -0
  277. package/src/credentials/core/types.ts +10 -0
  278. package/src/credentials/node/from-adc.ts +96 -0
  279. package/src/credentials/node/from-service-account.ts +133 -0
  280. package/src/credentials/node/index.ts +6 -0
  281. package/src/discover/concurrency.ts +127 -0
  282. package/src/discover/crawler-adapter.ts +142 -0
  283. package/src/discover/crawler.ts +1127 -0
  284. package/src/discover/credential-free.ts +27 -0
  285. package/src/discover/findCollectionGroup.ts +131 -0
  286. package/src/discover/firestore-source.ts +59 -0
  287. package/src/discover/index.ts +5 -0
  288. package/src/discover/merge.ts +523 -0
  289. package/src/discover/session.ts +402 -0
  290. package/src/discover/tools.ts +201 -0
  291. package/src/discover/types.ts +187 -0
  292. package/src/discover/wire.ts +324 -0
  293. package/src/functions-rtdb/child.ts +382 -0
  294. package/src/functions-rtdb/delivery.ts +7 -0
  295. package/src/functions-rtdb/discovery.ts +136 -0
  296. package/src/functions-rtdb/event.ts +64 -0
  297. package/src/functions-rtdb/execution.ts +111 -0
  298. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  299. package/src/functions-rtdb/project.ts +74 -0
  300. package/src/functions-rtdb/projection.ts +104 -0
  301. package/src/functions-rtdb/reference-pattern.ts +20 -0
  302. package/src/functions-rtdb/remote-delivery.ts +23 -0
  303. package/src/pkg-version.ts +49 -0
  304. package/src/register/esm-exports.ts +63 -0
  305. package/src/register/hooks.ts +30 -0
  306. package/src/register/index.ts +150 -0
  307. package/src/register/mapping.ts +33 -0
  308. package/src/remote/index.ts +1050 -0
  309. package/src/rtdb/crawl-snapshot.ts +118 -0
  310. package/src/rtdb/inspection.ts +137 -0
  311. package/src/rtdb/load-rules-document.ts +56 -0
  312. package/src/rtdb/rules-generation-tool.ts +38 -0
  313. package/src/rtdb/rules-json.ts +29 -0
  314. package/src/serve/activity-guard.ts +29 -0
  315. package/src/serve/activity-route.ts +164 -0
  316. package/src/serve/activity-warning.ts +26 -0
  317. package/src/serve/bridge-mount.ts +200 -0
  318. package/src/serve/bundler.ts +519 -0
  319. package/src/serve/capture-store.ts +65 -0
  320. package/src/serve/discovery.ts +199 -0
  321. package/src/serve/entries/ai.ts +292 -0
  322. package/src/serve/entries/app-backend.ts +4 -0
  323. package/src/serve/entries/app-client.ts +26 -0
  324. package/src/serve/entries/app-session-store.ts +47 -0
  325. package/src/serve/entries/app.ts +11 -0
  326. package/src/serve/entries/auth-helper-core.ts +181 -0
  327. package/src/serve/entries/auth-helper-dom.ts +134 -0
  328. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  329. package/src/serve/entries/auth.ts +255 -0
  330. package/src/serve/entries/bridge-url.ts +31 -0
  331. package/src/serve/entries/database.ts +129 -0
  332. package/src/serve/entries/firestore.ts +232 -0
  333. package/src/serve/entries/init.ts +49 -0
  334. package/src/serve/entries/keepalive.ts +46 -0
  335. package/src/serve/entries/messaging-sw.ts +75 -0
  336. package/src/serve/entries/messaging.ts +96 -0
  337. package/src/serve/entries/runtime.ts +556 -0
  338. package/src/serve/entries/session-store.ts +97 -0
  339. package/src/serve/entries/storage.ts +92 -0
  340. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  341. package/src/serve/entries/worker-runtime.ts +135 -0
  342. package/src/serve/init-payload.ts +42 -0
  343. package/src/serve/namespace.ts +675 -0
  344. package/src/serve/open-browser.ts +68 -0
  345. package/src/serve/rules.ts +272 -0
  346. package/src/serve/runtime/chip-config.ts +30 -0
  347. package/src/serve/runtime/chip-install.ts +27 -0
  348. package/src/serve/runtime/chip.ts +282 -0
  349. package/src/serve/runtime/manifest.ts +37 -0
  350. package/src/serve/runtime/status.ts +216 -0
  351. package/src/serve/runtime/worker-connection.ts +14 -0
  352. package/src/serve/runtime/worker-generation.ts +75 -0
  353. package/src/serve/runtime/worker-replacement.ts +42 -0
  354. package/src/serve/sandbox-marker.ts +31 -0
  355. package/src/serve/server.ts +428 -0
  356. package/src/serve/standalone-assets.ts +180 -0
  357. package/src/serve/state-store.ts +166 -0
  358. package/src/serve/studio/disk-project-store.ts +185 -0
  359. package/src/serve/studio/disk-workspace.ts +162 -0
  360. package/src/serve/studio/index.ts +20 -0
  361. package/src/serve/studio/routes.ts +237 -0
  362. package/src/serve/studio/store-types.ts +48 -0
  363. package/src/serve/studio/studio-storage.test.ts +289 -0
  364. package/src/serve/vite-ai-config.ts +77 -0
  365. package/src/serve/vite-plugin.ts +1075 -0
  366. package/src/serve/vite-rules-source.ts +28 -0
  367. package/src/serve/vite-worker-runtime.ts +51 -0
  368. package/src/serve/worker/activity-bootstrap.ts +24 -0
  369. package/src/serve/worker/client/admin-firestore.ts +38 -0
  370. package/src/serve/worker/client/ai.ts +124 -0
  371. package/src/serve/worker/client/auth.ts +489 -0
  372. package/src/serve/worker/client/connection.ts +227 -0
  373. package/src/serve/worker/client/core.ts +384 -0
  374. package/src/serve/worker/client/disconnect.ts +45 -0
  375. package/src/serve/worker/client/firestore-reads.ts +181 -0
  376. package/src/serve/worker/client/firestore-refs.ts +226 -0
  377. package/src/serve/worker/client/firestore-writes.ts +232 -0
  378. package/src/serve/worker/client/handles.ts +91 -0
  379. package/src/serve/worker/client/messaging.ts +119 -0
  380. package/src/serve/worker/client/presence.ts +174 -0
  381. package/src/serve/worker/client/rtdb.ts +311 -0
  382. package/src/serve/worker/client/rules.ts +63 -0
  383. package/src/serve/worker/client/runtime-control.ts +22 -0
  384. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  385. package/src/serve/worker/client/snapshots.ts +86 -0
  386. package/src/serve/worker/client/storage.ts +195 -0
  387. package/src/serve/worker/client/studio.ts +88 -0
  388. package/src/serve/worker/client.ts +57 -0
  389. package/src/serve/worker/durable-persistence.ts +137 -0
  390. package/src/serve/worker/entry.ts +223 -0
  391. package/src/serve/worker/host/admin-firestore.ts +88 -0
  392. package/src/serve/worker/host/connection.ts +179 -0
  393. package/src/serve/worker/host/core.ts +387 -0
  394. package/src/serve/worker/host/dispatch.ts +316 -0
  395. package/src/serve/worker/host/firestore-reads.ts +117 -0
  396. package/src/serve/worker/host/firestore-writes.ts +456 -0
  397. package/src/serve/worker/host/presence.ts +312 -0
  398. package/src/serve/worker/host/rtdb.ts +136 -0
  399. package/src/serve/worker/host/rules.ts +128 -0
  400. package/src/serve/worker/host/storage.ts +312 -0
  401. package/src/serve/worker/host/studio.ts +75 -0
  402. package/src/serve/worker/host/subscriptions.ts +238 -0
  403. package/src/serve/worker/host-ai.ts +165 -0
  404. package/src/serve/worker/host-auth.ts +469 -0
  405. package/src/serve/worker/host-context.ts +273 -0
  406. package/src/serve/worker/host-events.ts +94 -0
  407. package/src/serve/worker/host-messaging.ts +240 -0
  408. package/src/serve/worker/host.ts +47 -0
  409. package/src/serve/worker/index.ts +196 -0
  410. package/src/serve/worker/presence-timing.ts +15 -0
  411. package/src/serve/worker/protocol.ts +1124 -0
  412. package/src/serve/worker/retirement.ts +125 -0
  413. package/src/serve/worker/serve-init.ts +605 -0
  414. package/src/serve/worker/service-worker-channel.ts +33 -0
  415. package/src/serve/worker/service-worker-relay.ts +86 -0
  416. package/src/serve/writer-lock.ts +53 -0
  417. package/src/verify/cases.ts +233 -0
  418. package/src/verify/fixture.ts +258 -0
  419. package/src/verify/index.ts +519 -0
  420. package/src/verify/tools.ts +108 -0
  421. package/src/version/compat-target.ts +17 -0
  422. package/src/vite.ts +18 -0
  423. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  424. 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,6 @@
1
+ /**
2
+ * Node-only credentials used by hosted Rules Test API verification.
3
+ * Pyric does not own an interactive Google login or credential store.
4
+ */
5
+ export { fromAdc } from './from-adc.js';
6
+ export { fromServiceAccount } from './from-service-account.js';
@@ -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
+ }