@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,227 @@
1
+ /**
2
+ * Worker-client connection + lifecycle — open the SharedWorker port, query the
3
+ * worker's version/instance, drive state export/import + named branches, forward
4
+ * agent tool-calls, and relay raw worker-protocol frames from the bridge peer.
5
+ */
6
+
7
+ import type { InboundMessage } from '../protocol.js';
8
+ import type { WorkerOpPayload, WorkerSubPayload } from '../../../bridge/protocol.js';
9
+ import {
10
+ nextId,
11
+ nextSubId,
12
+ wirePort,
13
+ rpc,
14
+ rpcWithTimeout,
15
+ rawRpc,
16
+ openSnapshotSubscription,
17
+ closeSubscription,
18
+ } from './core.js';
19
+ import type { ClientDb } from './handles.js';
20
+
21
+ // ─── Public API ────────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Connect to the SharedWorker and return a client Firestore handle.
25
+ *
26
+ * Pass the URL of the SharedWorker script as `workerUrl`. Under `pyric dev`
27
+ * this is `/__pyric/sdk/worker.js`; for tests or standalone use, pass the path
28
+ * explicitly.
29
+ *
30
+ * `getFirestore` mirrors `pyric/firestore`'s `getFirestore(sandbox)` shape
31
+ * but returns a `ClientDb` backed by a `MessagePort` instead of a sandbox.
32
+ */
33
+ export interface SharedWorkerConnectionOptions {
34
+ onError?: (error: Error) => void;
35
+ }
36
+
37
+ export function getFirestore(
38
+ workerUrl: string | URL,
39
+ name?: string,
40
+ options: SharedWorkerConnectionOptions = {},
41
+ ): ClientDb {
42
+ if (typeof SharedWorker === 'undefined') {
43
+ throw new Error(
44
+ 'SharedWorker is not available. ' +
45
+ 'Open this page over http:// (not file://) and use a supported browser ' +
46
+ '(Chrome 4+, Firefox 29+, Safari 16.4+).',
47
+ );
48
+ }
49
+ const worker = new SharedWorker(workerUrl, {
50
+ type: 'classic',
51
+ name: name ?? 'pyric-shared-worker',
52
+ });
53
+ if (options.onError) {
54
+ worker.addEventListener('error', (event) => {
55
+ const detail = event.message || `failed to load ${String(workerUrl)}`;
56
+ options.onError?.(new Error(`Pyric SharedWorker error: ${detail}`));
57
+ });
58
+ }
59
+ const port = worker.port;
60
+ wirePort(port);
61
+ port.start();
62
+ return { __kind: 'client-db', port };
63
+ }
64
+
65
+ /**
66
+ * Ask the worker for its baked build version (staleness guard). The page
67
+ * compares it to the served bundle version and warns when a still-running OLD
68
+ * worker is older than what's served (a SharedWorker can't hot-update).
69
+ */
70
+ export async function getWorkerVersion(
71
+ db: ClientDb,
72
+ options: { timeoutMs?: number } = {},
73
+ ): Promise<string> {
74
+ const id = nextId();
75
+ const timeoutMs = options.timeoutMs ?? 2_000;
76
+ const r = (await rpcWithTimeout(
77
+ db.port,
78
+ { t: 'op', id, method: 'getRuntimeEpoch' },
79
+ timeoutMs,
80
+ `Timed out waiting for the Pyric SharedWorker version handshake after ${timeoutMs}ms.`,
81
+ )) as { version: string };
82
+ return r.version;
83
+ }
84
+
85
+ /**
86
+ * Ask the worker for its stable per-instance id (see host `INSTANCE_ID_KEY`).
87
+ * Studio renders a human-friendly form so a user can tell which sandbox instance
88
+ * they're looking at — the same `localhost:<port>` in a different browser profile
89
+ * is a SEPARATE sandbox (a separate SharedWorker + IndexedDB), and this is how
90
+ * the two are told apart.
91
+ */
92
+ export async function getWorkerInstanceId(db: ClientDb): Promise<string> {
93
+ const r = (await rpc(db.port, { t: 'op', id: nextId(), method: 'getVersion' })) as { instanceId?: string };
94
+ return r.instanceId ?? '';
95
+ }
96
+
97
+ /**
98
+ * Phase 2 (transfer): export the FULL sandbox state as a portable bundle string
99
+ * (the chunk format the persist layer uses, so wrapper types round-trip). Save
100
+ * it to a file and {@link importWorkerState} it into another instance.
101
+ */
102
+ export async function exportWorkerState(db: ClientDb): Promise<string> {
103
+ const r = (await rpc(db.port, { t: 'op', id: nextId(), method: 'exportState' })) as { bundle: string };
104
+ return r.bundle;
105
+ }
106
+
107
+ /** Phase 2 (clobber): replace this sandbox's ENTIRE state with `bundle`. */
108
+ export async function importWorkerState(db: ClientDb, bundle: string): Promise<void> {
109
+ await rpc(db.port, { t: 'op', id: nextId(), method: 'importState', bundle });
110
+ }
111
+
112
+ /** Phase 3: save the live sandbox as a named branch (a saved state bundle). */
113
+ export async function saveWorkerBranch(db: ClientDb, name: string): Promise<void> {
114
+ await rpc(db.port, { t: 'op', id: nextId(), method: 'saveBranch', name });
115
+ }
116
+
117
+ /** Phase 3: list this instance's saved branch names. */
118
+ export async function listWorkerBranches(db: ClientDb): Promise<string[]> {
119
+ const r = (await rpc(db.port, { t: 'op', id: nextId(), method: 'listBranches' })) as { branches?: string[] };
120
+ return r.branches ?? [];
121
+ }
122
+
123
+ /** Phase 3 (clobber): switch the live sandbox to a named branch's state. */
124
+ export async function switchWorkerBranch(db: ClientDb, name: string): Promise<void> {
125
+ await rpc(db.port, { t: 'op', id: nextId(), method: 'switchBranch', name });
126
+ }
127
+
128
+ /** Phase 3: delete a named branch. */
129
+ export async function deleteWorkerBranch(db: ClientDb, name: string): Promise<void> {
130
+ await rpc(db.port, { t: 'op', id: nextId(), method: 'deleteBranch', name });
131
+ }
132
+
133
+ /**
134
+ * Forward an agent tool-call to the worker so it executes against the SAME
135
+ * sandbox the app + Studio use. The worker runs the canonical tool dispatcher
136
+ * (`buildSandboxDispatcher`) and replies with the `{ ok, summary, data }`
137
+ * result. Used by the bridge peer on the worker path (`connectBridgePeer` in
138
+ * `entries/runtime.ts`) so the agent shares the one authoritative sandbox.
139
+ */
140
+ export async function callTool(
141
+ db: ClientDb,
142
+ name: string,
143
+ args: Record<string, unknown>,
144
+ ): Promise<{ ok: boolean; summary: string; data?: unknown }> {
145
+ return (await rpc(db.port, { t: 'tool', id: nextId(), name, args })) as {
146
+ ok: boolean;
147
+ summary: string;
148
+ data?: unknown;
149
+ };
150
+ }
151
+
152
+ // ─── Generic worker relay (remote sandbox, slice 1) ────────────────────────
153
+ //
154
+ // The bridge peer forwards `worker-op` / `worker-sub` frames from the Node
155
+ // side (`connectRemoteSandbox`) into the SharedWorker through these two
156
+ // functions (see `workerRelay` in `bridge/client/bridge.ts` + the wiring in
157
+ // `entries/runtime.ts`). Ids are re-minted LOCALLY (`nextId`/`nextSubId`) so
158
+ // relayed traffic shares this page's pending/sub maps without any chance of
159
+ // colliding with the page's own ids — the bridge correlates by ITS frame id.
160
+
161
+ /**
162
+ * Relay one raw worker-protocol op into the SharedWorker. `op` is the op
163
+ * message minus `t`/`id` (the `WorkerOpPayload` wire shape); resolves with
164
+ * the worker's `res.value`, rejects with an Error carrying `.code`.
165
+ */
166
+ export function relayWorkerOp(db: ClientDb, op: WorkerOpPayload): Promise<unknown> {
167
+ // rawRpc, NOT rpc: the relay owns provenance and must not inherit this
168
+ // browser page's Studio issuer. Override any untrusted incoming marker so
169
+ // Node/agent/system traffic can never be attributed to page app activity.
170
+ return rawRpc(db.port, {
171
+ ...op,
172
+ issuer: undefined,
173
+ relaySource: 'remote',
174
+ t: 'op',
175
+ id: nextId(),
176
+ } as InboundMessage);
177
+ }
178
+
179
+ /**
180
+ * Relay a raw worker-protocol subscription into the SharedWorker. `sub` is
181
+ * the sub message minus `t`/`subId` (the `WorkerSubPayload` wire shape).
182
+ * `onValue` receives every snap value VERBATIM — including the worker host's
183
+ * `{ __error: { code, message } }` establishment-failure convention (listener
184
+ * errors are re-wrapped into the same shape so the far side sees one form).
185
+ * Returns the unsubscribe function.
186
+ *
187
+ * The unified event stream (`target: 'events'`) is NOT relayable yet — its
188
+ * history batches aren't coalescible, so it needs bounded backpressure first
189
+ * (slice 2).
190
+ */
191
+ export function relayWorkerSub(
192
+ db: ClientDb,
193
+ sub: WorkerSubPayload,
194
+ onValue: (value: unknown) => void,
195
+ ): () => void {
196
+ if (sub.target === 'events') {
197
+ throw new Error(
198
+ 'event-stream subscriptions cannot be relayed over the bridge yet (needs bounded backpressure — slice 2)',
199
+ );
200
+ }
201
+ const subId = nextSubId();
202
+ const opened = openSnapshotSubscription(db.port, subId, {
203
+ port: db.port,
204
+ next: onValue,
205
+ error: (err) =>
206
+ onValue({
207
+ __error: {
208
+ code: (err as { code?: string }).code ?? 'unknown',
209
+ message: err instanceof Error ? err.message : String(err),
210
+ },
211
+ }),
212
+ }, {
213
+ ...sub,
214
+ issuer: undefined,
215
+ relaySource: 'remote',
216
+ t: 'sub',
217
+ subId,
218
+ } as InboundMessage);
219
+ if (!opened) {
220
+ queueMicrotask(() => onValue({
221
+ __error: { code: 'app/app-deleted', message: 'Firebase App was deleted' },
222
+ }));
223
+ }
224
+ return () => {
225
+ closeSubscription(db.port, subId);
226
+ };
227
+ }
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Worker-client transport core — the shared singleton machinery every API
3
+ * family (firestore, auth, rtdb, storage) rides on: port wiring, RPC
4
+ * correlation, the pending/subscriber maps, the Pyric Studio auth-lens default,
5
+ * and the op-source (issuer) stamp.
6
+ *
7
+ * This module owns the client's mutable module state. Because ES module
8
+ * bindings are live, family modules import `_defaultLens` (and the maps) and
9
+ * observe every `setLens(...)` update through the same singleton — exactly as
10
+ * they did when all of this lived inline in `client.ts`.
11
+ */
12
+ import type { InboundMessage, OutboundMessage } from '../protocol.js';
13
+ import type { RuntimeReloadMessage } from '../protocol.js';
14
+ // TYPE-ONLY — the auth-lens contract + the cross-service event envelope, shared
15
+ // with the worker host + the sandbox's event provenance. Erased at build, so the
16
+ // leaf client bundle stays engine-free.
17
+ import type { AuthLens, SandboxEvent } from 'pyric/sandbox';
18
+ import { FirebaseError } from 'pyric/app';
19
+ import type { ClientPort } from './handles.js';
20
+
21
+ // ─── Port + correlation machinery ─────────────────────────────────────────
22
+
23
+ let _opCounter = 0;
24
+ let _subCounter = 0;
25
+
26
+ export function nextId(): string { return `op-${++_opCounter}`; }
27
+ export function nextSubId(): string { return `sub-${++_subCounter}`; }
28
+
29
+ /**
30
+ * Pending RPC resolvers. Keyed by correlation id.
31
+ * Resolves with the `value` field on success; rejects with a typed error
32
+ * on failure.
33
+ */
34
+ export const _pending = new Map<string, {
35
+ port: ClientPort;
36
+ resolve: (v: unknown) => void;
37
+ reject: (e: Error & { code: string }) => void;
38
+ }>();
39
+
40
+ /**
41
+ * Active snapshot subscribers. Keyed by subId.
42
+ * `next` is the user-supplied callback; `error` is the optional error handler.
43
+ */
44
+ export const _snapSubs = new Map<string, {
45
+ port: ClientPort;
46
+ next: (snap: unknown) => void;
47
+ error?: (err: unknown) => void;
48
+ /** Firestore listeners abort on app deletion; RTDB/Auth stop silently. */
49
+ service?: 'firestore';
50
+ }>();
51
+
52
+ /**
53
+ * Active event-stream subscribers (Pyric Studio keystone). Keyed by subId.
54
+ * `next` receives each delivered BATCH of `SandboxEvent`s — the first batch is
55
+ * the initial `history()` snapshot, subsequent batches are single live events.
56
+ */
57
+ export const _eventSubs = new Map<string, {
58
+ port: ClientPort;
59
+ next: (events: readonly SandboxEvent[]) => void;
60
+ }>();
61
+ const disconnectedPorts = new WeakSet<ClientPort>();
62
+ const runtimeReloadListeners = new Set<(message: RuntimeReloadMessage) => void>();
63
+
64
+ export function subscribeRuntimeReload(
65
+ listener: (message: RuntimeReloadMessage) => void,
66
+ ): () => void {
67
+ runtimeReloadListeners.add(listener);
68
+ return () => runtimeReloadListeners.delete(listener);
69
+ }
70
+
71
+ function appDeletedError(): Error & { code: string } {
72
+ return Object.assign(new Error('Firebase App was deleted'), { code: 'app/app-deleted' });
73
+ }
74
+
75
+ export function isDisconnectedPort(port: ClientPort): boolean {
76
+ return disconnectedPorts.has(port);
77
+ }
78
+
79
+ /** Atomically register and post a subscription unless its app port is closed. */
80
+ export function openSnapshotSubscription(
81
+ port: ClientPort,
82
+ subId: string,
83
+ subscription: (typeof _snapSubs extends Map<string, infer T> ? T : never),
84
+ message: InboundMessage,
85
+ ): boolean {
86
+ if (disconnectedPorts.has(port)) return false;
87
+ _snapSubs.set(subId, subscription);
88
+ port.postMessage(message);
89
+ return true;
90
+ }
91
+
92
+ /** Atomically register an event-stream subscription unless its port is closed. */
93
+ export function openEventSubscription(
94
+ port: ClientPort,
95
+ subId: string,
96
+ next: (events: readonly SandboxEvent[]) => void,
97
+ message: InboundMessage,
98
+ ): boolean {
99
+ if (disconnectedPorts.has(port)) return false;
100
+ _eventSubs.set(subId, { port, next });
101
+ port.postMessage(message);
102
+ return true;
103
+ }
104
+
105
+ /** Remove a local subscription and notify only a live app port. */
106
+ export function closeSubscription(port: ClientPort, subId: string): void {
107
+ _snapSubs.delete(subId);
108
+ _eventSubs.delete(subId);
109
+ if (!disconnectedPorts.has(port)) {
110
+ port.postMessage({ t: 'unsub', subId } satisfies InboundMessage);
111
+ }
112
+ }
113
+
114
+ /** Drop every client-side correlation owned by a closing app port. */
115
+ export function disconnectPort(port: ClientPort): void {
116
+ disconnectedPorts.add(port);
117
+ const error = appDeletedError();
118
+ for (const [id, pending] of [..._pending]) {
119
+ if (pending.port !== port) continue;
120
+ _pending.delete(id);
121
+ pending.reject(error);
122
+ }
123
+ for (const [id, subscription] of [..._snapSubs]) {
124
+ if (subscription.port !== port) continue;
125
+ _snapSubs.delete(id);
126
+ if (subscription.service === 'firestore') {
127
+ subscription.error?.(new FirebaseError('aborted', 'The operation was aborted.'));
128
+ }
129
+ }
130
+ for (const [id, subscription] of [..._eventSubs]) {
131
+ if (subscription.port !== port) continue;
132
+ _eventSubs.delete(id);
133
+ }
134
+ port.onmessage = null;
135
+ }
136
+
137
+ // ─── Denial relay (headless dev visibility) ────────────────────────────────
138
+ //
139
+ // An agent driving `pyric dev` headlessly never sees the browser console, so
140
+ // a rules denial is otherwise invisible to it. Relay every denial (an error
141
+ // carrying `denialContext`) to the dev server, which prints it to the
142
+ // terminal — regardless of whether the app itself catches/handles the
143
+ // error. Fire-and-forget: never throws, never awaited into app flow, and a
144
+ // relay failure (dev server down, no `fetch`, non-pyric host) is swallowed
145
+ // silently — this is a diagnostics side channel, not a correctness path.
146
+
147
+ /** Shape POSTed to the dev server's `/__pyric/denials` endpoint. */
148
+ interface DenialRelayPayload {
149
+ kind: 'listener' | 'read';
150
+ code: string;
151
+ message: string;
152
+ denialContext: unknown;
153
+ /** Sibling of denialContext on the sandbox error (query-proof denials
154
+ * attach the suggested where() fix there); forwarded when present. */
155
+ remediation?: string;
156
+ }
157
+
158
+ /** POST a denial to the dev server for terminal visibility. No-op when the
159
+ * error carries no `denialContext` (only rules denials are relayed) or when
160
+ * `fetch` isn't available (non-browser host). */
161
+ function relayDenial(
162
+ kind: 'listener' | 'read',
163
+ err: { message: string; code: string; denialContext?: unknown; remediation?: unknown },
164
+ ): void {
165
+ if (err.denialContext === undefined) return;
166
+ if (typeof fetch !== 'function') return;
167
+ const payload: DenialRelayPayload = {
168
+ kind,
169
+ code: err.code,
170
+ message: err.message,
171
+ denialContext: err.denialContext,
172
+ ...(typeof err.remediation === 'string' ? { remediation: err.remediation } : {}),
173
+ };
174
+ try {
175
+ void fetch('/__pyric/denials', {
176
+ method: 'POST',
177
+ headers: { 'content-type': 'application/json' },
178
+ body: JSON.stringify(payload),
179
+ }).catch(() => {});
180
+ } catch {
181
+ /* fire-and-forget — never let the relay disturb app flow */
182
+ }
183
+ }
184
+
185
+ /** Wire up the port's onmessage handler (idempotent per-port). */
186
+ export function wirePort(port: ClientPort): void {
187
+ port.onmessage = (ev: MessageEvent<OutboundMessage>) => {
188
+ const msg = ev.data;
189
+ if (msg.t === 'res') {
190
+ const pending = _pending.get(msg.id);
191
+ if (!pending) return;
192
+ _pending.delete(msg.id);
193
+ if (msg.ok) {
194
+ pending.resolve(msg.value);
195
+ } else {
196
+ const err = new Error(msg.error.message) as Error & {
197
+ code: string;
198
+ denialContext?: unknown;
199
+ aiEnvelope?: unknown;
200
+ };
201
+ err.code = msg.error.code;
202
+ // Structured denial context (spike gap 6): re-attach so consumers —
203
+ // and the bridge relay, which re-serializes thrown errors — see the
204
+ // same shape a local SandboxError carries.
205
+ if (msg.error.denialContext !== undefined) {
206
+ err.denialContext = msg.error.denialContext;
207
+ }
208
+ // AI wire error envelope (pyric/ai): re-attach so the served
209
+ // `firebase/ai` entry can mint the exact SDK AIError decoration the
210
+ // in-process plane applies (see entries/ai.ts).
211
+ if (msg.error.aiEnvelope !== undefined) {
212
+ err.aiEnvelope = msg.error.aiEnvelope;
213
+ }
214
+ relayDenial('read', err);
215
+ pending.reject(err);
216
+ }
217
+ } else if (msg.t === 'snap') {
218
+ const sub = _snapSubs.get(msg.subId);
219
+ if (!sub) return;
220
+ // Auth snaps carry `SerializedUser | null` — a null value is a valid
221
+ // "signed out" payload, not an error, so guard the __error sniff.
222
+ const value = (msg.value ?? {}) as Record<string, unknown>;
223
+ if (value.__error) {
224
+ const errPayload = value.__error as { code: string; message: string; denialContext?: unknown; aiEnvelope?: unknown };
225
+ const err = new Error(errPayload.message) as Error & { code: string; denialContext?: unknown; aiEnvelope?: unknown };
226
+ err.code = errPayload.code;
227
+ if (errPayload.denialContext !== undefined) err.denialContext = errPayload.denialContext;
228
+ if (errPayload.aiEnvelope !== undefined) err.aiEnvelope = errPayload.aiEnvelope;
229
+ relayDenial('listener', err);
230
+ // Surface an unobserved listener error instead of swallowing it — the
231
+ // worker-path twin of the in-page default (a denied listener after a
232
+ // rules change / sign-out must not fail silently on the page console).
233
+ if (sub.error) sub.error(err);
234
+ else console.error('pyric/firestore: Uncaught Error in snapshot listener:', err);
235
+ return;
236
+ }
237
+ sub.next(msg.value);
238
+ } else if (msg.t === 'event') {
239
+ // Event-stream batch (Pyric Studio keystone). Plain JSON SandboxEvents —
240
+ // no rehydration. Deliver the whole batch to the registered subscriber.
241
+ const subscription = _eventSubs.get(msg.subId);
242
+ if (subscription) subscription.next(msg.events);
243
+ } else if (msg.t === 'runtime-reload') {
244
+ for (const listener of runtimeReloadListeners) listener(msg);
245
+ }
246
+ };
247
+ }
248
+
249
+ // ─── Auth lens (Pyric Studio) ──────────────────────────────────────────────
250
+ //
251
+ // The default per-op auth lens carried on every FIRESTORE DATA op this client
252
+ // sends. The host resolves a data handle from it (`lensDb` in host.ts):
253
+ // - `{ mode: 'app-session' }` (the default): the served app's own session.
254
+ // - `{ mode: 'as', uid }`: impersonate — rules evaluate as that user.
255
+ // - `{ mode: 'admin' }`: admin lens (rule bypass; see host.ts gap note).
256
+ //
257
+ // Studio sets this so its data grids / rules-debug "re-run as user" views run
258
+ // under the chosen identity without threading `actAs` through every call. AUTH
259
+ // ops (`auth.*`) and `getVersion` are NEVER lensed — they operate the worker's
260
+ // session, not data — so the lens is stamped only on the data-op path.
261
+
262
+ /** Module-level default lens. `undefined` ⇒ the worker treats the op as the
263
+ * app's session (the additive default — existing senders omit `actAs`).
264
+ *
265
+ * Exported as a LIVE binding: family modules read it to stamp the lens onto
266
+ * subscriptions (`onSnapshot`, `rtdbOnValue`), and see `setLens(...)` updates
267
+ * through this one singleton. */
268
+ export let _defaultLens: AuthLens | undefined;
269
+
270
+ /**
271
+ * Set the default auth lens applied to subsequent Firestore DATA ops from this
272
+ * client (Pyric Studio). Pass `{ mode: 'as', uid }` to read/write AS a user
273
+ * (rules apply), `{ mode: 'admin' }` for the admin lens, or
274
+ * `{ mode: 'app-session' }` / `undefined` to revert to the app's own session.
275
+ *
276
+ * The lens is process-wide for this client module (one served page = one
277
+ * worker port), mirroring how Studio drives a single active identity at a time.
278
+ * Auth ops are unaffected — they always operate the real session.
279
+ */
280
+ export function setLens(lens: AuthLens | undefined): void {
281
+ _defaultLens = lens && lens.mode === 'app-session' ? undefined : lens;
282
+ }
283
+
284
+ /** The active default lens (read-only view), for Studio UI to reflect state. */
285
+ export function getLens(): AuthLens | undefined {
286
+ return _defaultLens;
287
+ }
288
+
289
+ /**
290
+ * Module-level op-source declaration (Pyric Studio traffic attribution).
291
+ * When set, every op/sub message THIS CLIENT MODULE CONSTRUCTS is stamped
292
+ * `source: 'studio'` on the wire; the host maps that onto the unified
293
+ * event stream's `actor` field so Traffic can filter Studio's own
294
+ * viewer/editor ops out of the app's stream.
295
+ *
296
+ * Studio's live plane sets this once at connect (`connectWorkerLive`);
297
+ * the served APP page never calls it (its service handles remain the source
298
+ * of app attribution), and RELAYED frames bypass Studio stamping entirely —
299
+ * the bridge relay ({@link relayWorkerOp} / {@link relayWorkerSub}) clears the
300
+ * Studio issuer and marks remote frames through {@link rawRpc} / a direct postMessage, so a
301
+ * user's own admin-SDK traffic through the remote bridge is never
302
+ * mislabeled as Studio's even though it rides the same port.
303
+ */
304
+ let _opIssuer: 'studio' | undefined;
305
+
306
+ /** Declare who issues the ops this client module constructs. See {@link _opIssuer}. */
307
+ export function setOpIssuer(source: 'studio' | undefined): void {
308
+ _opIssuer = source;
309
+ }
310
+
311
+ /** Stamp the declared op source onto a client-constructed message. Ops and
312
+ * subscriptions only — control frames (`tool`, `unsub`) never carry it
313
+ * (agent tool-calls dispatched through this port must not inherit
314
+ * Studio's source; their inner ops are attributed by the host). */
315
+ export function stampIssuer<T extends { t?: string }>(msg: T): T {
316
+ return _opIssuer && (msg.t === 'op' || msg.t === 'sub')
317
+ ? { ...msg, issuer: _opIssuer }
318
+ : msg;
319
+ }
320
+
321
+ /**
322
+ * Send an already-final message and return a promise for its result — no
323
+ * implicit stamping. The RELAY path ({@link relayWorkerOp}) explicitly owns
324
+ * the final remote provenance fields before sending through this function.
325
+ */
326
+ export function rawRpc(port: ClientPort, msg: InboundMessage): Promise<unknown> {
327
+ if (disconnectedPorts.has(port)) return Promise.reject(appDeletedError());
328
+ return new Promise<unknown>((resolve, reject) => {
329
+ const opMsg = msg as { id: string };
330
+ _pending.set(opMsg.id, {
331
+ port,
332
+ resolve,
333
+ reject: reject as (e: Error & { code: string }) => void,
334
+ });
335
+ port.postMessage(msg);
336
+ });
337
+ }
338
+
339
+ /** Send a CLIENT-CONSTRUCTED message: stamps the declared op source, then sends. */
340
+ export function rpc(port: ClientPort, msg: InboundMessage): Promise<unknown> {
341
+ return rawRpc(port, stampIssuer(msg));
342
+ }
343
+
344
+ /**
345
+ * Send a client RPC with a bounded wait, while keeping correlation cleanup
346
+ * inside the transport module that owns the pending-request registry.
347
+ */
348
+ export function rpcWithTimeout(
349
+ port: ClientPort,
350
+ msg: InboundMessage,
351
+ timeoutMs: number,
352
+ timeoutMessage: string,
353
+ ): Promise<unknown> {
354
+ const id = (msg as { id: string }).id;
355
+ let timer: ReturnType<typeof setTimeout> | undefined;
356
+ const timeout = new Promise<never>((_, reject) => {
357
+ timer = setTimeout(() => {
358
+ if (!_pending.delete(id)) return;
359
+ reject(new Error(timeoutMessage));
360
+ }, timeoutMs);
361
+ });
362
+ return Promise.race([rpc(port, msg), timeout]).finally(() => {
363
+ if (timer) clearTimeout(timer);
364
+ });
365
+ }
366
+
367
+ /**
368
+ * Like {@link rpc} but stamps the active default auth lens onto the op message
369
+ * (Pyric Studio). Used by data-service ops so a `setLens(...)` choice
370
+ * carries per op without every call site threading `actAs`. Auth ops + version
371
+ * use the bare {@link rpc} so they never carry a lens.
372
+ *
373
+ * `actAs` is only attached when a lens is active — when none is set the wire
374
+ * message is byte-identical to before, preserving the additive contract.
375
+ */
376
+ export function dataRpc(port: ClientPort, msg: InboundMessage & { t: 'op' }): Promise<unknown> {
377
+ if (disconnectedPorts.has(port) && !msg.method.startsWith('rtdb.')) {
378
+ return Promise.reject(
379
+ new FirebaseError('failed-precondition', 'The client has already been terminated.'),
380
+ );
381
+ }
382
+ const withLens = _defaultLens ? { ...msg, actAs: _defaultLens } : msg;
383
+ return rpc(port, withLens);
384
+ }
@@ -0,0 +1,45 @@
1
+ /** Internal app-port teardown; intentionally absent from the public worker-client barrel. */
2
+ import type { InboundMessage } from '../protocol.js';
3
+ import { disconnectPort, nextId, rawRpc } from './core.js';
4
+ import type { ClientDb } from './handles.js';
5
+
6
+ export interface DisconnectClientOptions {
7
+ /** Bound version-skew and worker-crash cases where no disconnect reply arrives. */
8
+ ackTimeoutMs?: number;
9
+ }
10
+
11
+ const DEFAULT_ACK_TIMEOUT_MS = 5_000;
12
+
13
+ function deleteTimeoutError(): Error & { code: string } {
14
+ return Object.assign(
15
+ new Error(
16
+ 'Timed out waiting for SharedWorker app cleanup acknowledgment. ' +
17
+ 'Close all tabs using this pyric dev origin so the worker can restart.',
18
+ ),
19
+ { code: 'app/delete-timeout' },
20
+ );
21
+ }
22
+
23
+ export async function disconnectClient(
24
+ db: ClientDb,
25
+ options: DisconnectClientOptions = {},
26
+ ): Promise<void> {
27
+ let timeout: ReturnType<typeof setTimeout> | undefined;
28
+ try {
29
+ const acknowledged = rawRpc(db.port, {
30
+ t: 'disconnect',
31
+ id: nextId(),
32
+ } satisfies InboundMessage);
33
+ const timedOut = new Promise<never>((_resolve, reject) => {
34
+ timeout = setTimeout(
35
+ () => reject(deleteTimeoutError()),
36
+ options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS,
37
+ );
38
+ });
39
+ await Promise.race([acknowledged, timedOut]);
40
+ } finally {
41
+ if (timeout !== undefined) clearTimeout(timeout);
42
+ disconnectPort(db.port);
43
+ db.port.close();
44
+ }
45
+ }