@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,312 @@
1
+ /**
2
+ * SharedWorker host — worker-backed Cloud Storage ops.
3
+ *
4
+ * Object browse (`storage.listAll`/`getMetadata`), the MessagePort-only
5
+ * `getBlob`, and the relay-safe base64 byte transfer (`putBytes`/`getBytes`)
6
+ * plus idempotent `deleteObject`. Owns the storage lens resolver
7
+ * (`lensStorage` — the storage mirror of the firestore/rtdb lens resolvers),
8
+ * the lazily-created shared handle, and the wire → `SettableMetadata` mapper.
9
+ *
10
+ * Routed here by the host dispatcher. Storage dispatch is async, so the
11
+ * mutating ops thread the op's provenance EXPLICITLY (their events escape the
12
+ * dispatcher's synchronous ambient-provenance window). Never imports the
13
+ * dispatcher.
14
+ */
15
+
16
+ import {
17
+ getStorageSandbox,
18
+ ref as storageRef,
19
+ listAll as storageListAll,
20
+ getMetadata as storageGetMetadata,
21
+ getBlob as storageGetBlob,
22
+ getBytes as storageGetBytes,
23
+ uploadBytes as storageUploadBytes,
24
+ deleteObject as storageDeleteObject,
25
+ type FirebaseStorage,
26
+ type SettableMetadata,
27
+ } from 'pyric/storage';
28
+ // Host-only rules-bypass admin plane — the storage mirror of
29
+ // `getAdminFirestore`/`getAdminDatabase`, resolved for `actAs: { mode: 'admin' }`.
30
+ import {
31
+ bindStorageOperationContext,
32
+ getAdminStorageSandbox,
33
+ } from 'pyric/storage/internal';
34
+ import type { AuthLens } from 'pyric/sandbox';
35
+ import { bindOperationContext } from 'pyric/sandbox/internal';
36
+
37
+ import type { OpMessage } from '../protocol.js';
38
+ import {
39
+ bytesToBase64,
40
+ base64ToBytes,
41
+ storagePayloadTooLarge,
42
+ MAX_STORAGE_OP_BYTES,
43
+ MAX_STORAGE_OP_B64_LENGTH,
44
+ } from '../protocol.js';
45
+ import { type HostCtx, type PortLike, ok, fail } from '../host-context.js';
46
+ import { authStateForLens, lensCacheKey, opProvenance, sessionCacheKey } from './core.js';
47
+ import { portSession } from '../host-auth.js';
48
+
49
+ /** The shared Storage handle, lazily created (Pyric Studio data browse): one per
50
+ * worker, directly over the shared sandbox. The high-level
51
+ * `pyric/storage` ops enforce rules, so the host reads through them. */
52
+ function ensureStorage(ctx: HostCtx): FirebaseStorage {
53
+ return (ctx.storage ??= getStorageSandbox(bindOperationContext(ctx.sandbox.withAuth(null), {
54
+ source: { kind: 'app' },
55
+ authLens: { mode: 'app-session' },
56
+ })));
57
+ }
58
+
59
+ /**
60
+ * Resolve the Storage handle a storage op runs against, given its `actAs`
61
+ * lens — the storage mirror of {@link lensRtdb}:
62
+ *
63
+ * - absent / `app-session` → a handle carrying the initiating port's Auth
64
+ * session, or the shared anonymous handle when that port is signed out.
65
+ * Storage rules apply only when the HOST
66
+ * configured them on this sandbox's storage service (first call per
67
+ * sandbox wins) — the SERVED worker configures them via
68
+ * `applyServeInit` (`serve-init.ts`), which opens the storage service
69
+ * with `payload.storageRules` BEFORE any op can reach `ensureStorage`/
70
+ * `lensStorage`, so every lens on a served worker enforces the
71
+ * project's storage.rules (or runs open when the project has none,
72
+ * matching Firestore/RTDB's no-rules posture). The lens split still
73
+ * matters for embedding/test hosts that open the service directly.
74
+ * - `{ mode: 'admin' }` → the rules-BYPASS handle from
75
+ * `pyric/storage/internal`'s admin plane — same per-sandbox store +
76
+ * ruleset, rule evaluation skipped (firebase-admin semantics for the
77
+ * `pyric-admin` remote arm / Studio admin lens).
78
+ * - `{ mode: 'as', uid }` → a frozen-identity `getStorageSandbox(
79
+ * sandbox.withAuth({ uid, token? }))` handle; rules evaluate AS that
80
+ * user. Cached per uid/token key on `ctx.lensStorages`.
81
+ */
82
+ function sessionStorage(ctx: HostCtx, port: PortLike): FirebaseStorage {
83
+ const session = portSession(ctx, port);
84
+ if (!session) return ensureStorage(ctx);
85
+ const key = sessionCacheKey(session);
86
+ const handles = (ctx.sessionStorages ??= new Map());
87
+ let handle = handles.get(key);
88
+ if (!handle) {
89
+ handle = getStorageSandbox(ctx.sandbox.withAuth(session.state));
90
+ handles.set(key, handle);
91
+ }
92
+ return handle;
93
+ }
94
+
95
+ function lensStorage(ctx: HostCtx, actAs: AuthLens | undefined, port: PortLike): FirebaseStorage {
96
+ if (!actAs || actAs.mode === 'app-session') {
97
+ return sessionStorage(ctx, port);
98
+ }
99
+ if (actAs.mode === 'admin') {
100
+ return (ctx.adminStorage ??= getAdminStorageSandbox(ctx.sandbox));
101
+ }
102
+ // Genuinely unauthenticated — see the `anon` note on lensDb. Distinct from
103
+ // the shared page handle only when the host configured storage rules, but
104
+ // pinning it keeps remote `withAuth(null)` semantics uniform across services.
105
+ if (actAs.mode === 'anon') {
106
+ return (ctx.anonStorage ??= getStorageSandbox(ctx.sandbox.withAuth(null)));
107
+ }
108
+ const handles = (ctx.lensStorages ??= new Map());
109
+ const key = lensCacheKey(actAs);
110
+ let handle = handles.get(key);
111
+ if (!handle) {
112
+ handle = getStorageSandbox(ctx.sandbox.withAuth(authStateForLens(actAs)));
113
+ handles.set(key, handle);
114
+ }
115
+ return handle;
116
+ }
117
+
118
+ /**
119
+ * Map a wire `storage.putBytes` payload to `pyric/storage`'s
120
+ * `SettableMetadata`. The explicit `contentType` field wins; recognized
121
+ * settable fields are lifted from `metadata`; a GCS-style nested custom map
122
+ * (`metadata.metadata`, as `@google-cloud/storage`'s `save` spells it) or a
123
+ * pyric-style `metadata.customMetadata` becomes `customMetadata` with
124
+ * values coerced to strings (the storage-rules `metadata` model).
125
+ */
126
+ function toSettableMetadata(msg: {
127
+ contentType?: string;
128
+ metadata?: Record<string, unknown>;
129
+ }): SettableMetadata {
130
+ const md = msg.metadata ?? {};
131
+ const str = (key: string): string | undefined =>
132
+ typeof md[key] === 'string' ? (md[key] as string) : undefined;
133
+ const customSource = md['customMetadata'] ?? md['metadata'];
134
+ let customMetadata: Record<string, string> | undefined;
135
+ if (customSource !== null && typeof customSource === 'object' && !Array.isArray(customSource)) {
136
+ customMetadata = {};
137
+ for (const [k, v] of Object.entries(customSource as Record<string, unknown>)) {
138
+ customMetadata[k] = String(v);
139
+ }
140
+ }
141
+ const settable: SettableMetadata = {};
142
+ const contentType = msg.contentType ?? str('contentType');
143
+ if (contentType !== undefined) settable.contentType = contentType;
144
+ const cacheControl = str('cacheControl');
145
+ if (cacheControl !== undefined) settable.cacheControl = cacheControl;
146
+ const contentDisposition = str('contentDisposition');
147
+ if (contentDisposition !== undefined) settable.contentDisposition = contentDisposition;
148
+ const contentEncoding = str('contentEncoding');
149
+ if (contentEncoding !== undefined) settable.contentEncoding = contentEncoding;
150
+ const contentLanguage = str('contentLanguage');
151
+ if (contentLanguage !== undefined) settable.contentLanguage = contentLanguage;
152
+ if (customMetadata !== undefined) settable.customMetadata = customMetadata;
153
+ return settable;
154
+ }
155
+
156
+ /** The storage op methods routed to {@link handleStorageOp}. */
157
+ const STORAGE_METHODS = new Set<string>([
158
+ 'storage.listAll',
159
+ 'storage.getMetadata',
160
+ 'storage.getBlob',
161
+ 'storage.putBytes',
162
+ 'storage.getBytes',
163
+ 'storage.deleteObject',
164
+ ]);
165
+
166
+ export function isStorageOp(method: OpMessage['method']): boolean {
167
+ return STORAGE_METHODS.has(method);
168
+ }
169
+
170
+ export async function handleStorageOp(
171
+ ctx: HostCtx,
172
+ port: PortLike,
173
+ msg: OpMessage,
174
+ ): Promise<void> {
175
+ switch (msg.method) {
176
+ case 'storage.listAll': {
177
+ // Object browse. `listAll` enforces `read` rules on the scanned prefix
178
+ // under the op's lens (admin lens bypasses — see lensStorage).
179
+ try {
180
+ const storage = bindStorageOperationContext(
181
+ lensStorage(ctx, msg.actAs, port),
182
+ opProvenance(msg),
183
+ );
184
+ const result = await storageListAll(storageRef(storage, msg.path));
185
+ ok(port, msg.id, {
186
+ items: result.items.map((r) => ({ fullPath: r.fullPath, name: r.name })),
187
+ prefixes: result.prefixes.map((r) => ({ fullPath: r.fullPath, name: r.name })),
188
+ });
189
+ } catch (e) { fail(port, msg.id, e); }
190
+ break;
191
+ }
192
+
193
+ case 'storage.getMetadata': {
194
+ try {
195
+ const storage = bindStorageOperationContext(
196
+ lensStorage(ctx, msg.actAs, port),
197
+ opProvenance(msg),
198
+ );
199
+ // FullMetadata is plain JSON (bucket/fullPath/name/size/contentType/...).
200
+ ok(
201
+ port,
202
+ msg.id,
203
+ await storageGetMetadata(storageRef(storage, msg.path)),
204
+ );
205
+ } catch (e) { fail(port, msg.id, e); }
206
+ break;
207
+ }
208
+
209
+ case 'storage.getBlob': {
210
+ // MessagePort-ONLY: the Blob structured-clones to in-page callers
211
+ // (Studio previews) but silently corrupts under the JSON WS relay — the
212
+ // bridge client rejects relaying it (binary-payload guard). Remote
213
+ // callers use `storage.getBytes` (base64) instead.
214
+ try {
215
+ const storage = bindStorageOperationContext(
216
+ lensStorage(ctx, msg.actAs, port),
217
+ opProvenance(msg),
218
+ );
219
+ ok(
220
+ port,
221
+ msg.id,
222
+ await storageGetBlob(storageRef(storage, msg.path)),
223
+ );
224
+ } catch (e) { fail(port, msg.id, e); }
225
+ break;
226
+ }
227
+
228
+ case 'storage.putBytes': {
229
+ // Byte upload (remote sandbox, slice 2). Decode-end size cap: reject an
230
+ // oversized base64 string BEFORE materializing its bytes, then re-check
231
+ // the exact decoded length. Rules enforce under the op's lens; the
232
+ // upload emits `service_mutation` events. Storage dispatch is async, so
233
+ // the event escapes the ambient-provenance window `handleMessage` opened;
234
+ // thread the op's provenance EXPLICITLY so a Studio-issued write is
235
+ // attributable end to end (issue #84 item 3).
236
+ try {
237
+ if (msg.dataB64.length > MAX_STORAGE_OP_B64_LENGTH) {
238
+ throw storagePayloadTooLarge(
239
+ Math.floor(msg.dataB64.length * 0.75),
240
+ `storage.putBytes payload for '${msg.path}'`,
241
+ );
242
+ }
243
+ const bytes = base64ToBytes(msg.dataB64);
244
+ if (bytes.byteLength > MAX_STORAGE_OP_BYTES) {
245
+ throw storagePayloadTooLarge(bytes.byteLength, `storage.putBytes payload for '${msg.path}'`);
246
+ }
247
+ const storage = bindStorageOperationContext(
248
+ lensStorage(ctx, msg.actAs, port),
249
+ opProvenance(msg),
250
+ );
251
+ const result = await storageUploadBytes(
252
+ storageRef(storage, msg.path),
253
+ bytes,
254
+ toSettableMetadata(msg),
255
+ );
256
+ // FullMetadata — plain JSON, relay-safe.
257
+ ok(port, msg.id, result.metadata);
258
+ } catch (e) { fail(port, msg.id, e); }
259
+ break;
260
+ }
261
+
262
+ case 'storage.getBytes': {
263
+ // JSON-safe byte download (remote sandbox, slice 2): base64 in the
264
+ // result. Encode-end size cap so a big browser-side object can't blow
265
+ // up the relay. Metadata is read alongside for contentType (both reads
266
+ // run under the same lens; rule-eval order keeps `unauthorized`
267
+ // superseding `not-found`, matching pyric/storage).
268
+ try {
269
+ const storage = bindStorageOperationContext(
270
+ lensStorage(ctx, msg.actAs, port),
271
+ opProvenance(msg),
272
+ );
273
+ const r = storageRef(storage, msg.path);
274
+ const meta = await storageGetMetadata(r);
275
+ if (meta.size > MAX_STORAGE_OP_BYTES) {
276
+ throw storagePayloadTooLarge(meta.size, `object '${msg.path}'`);
277
+ }
278
+ const buf = await storageGetBytes(r);
279
+ if (buf.byteLength > MAX_STORAGE_OP_BYTES) {
280
+ throw storagePayloadTooLarge(buf.byteLength, `object '${msg.path}'`);
281
+ }
282
+ ok(port, msg.id, {
283
+ dataB64: bytesToBase64(new Uint8Array(buf)),
284
+ contentType: meta.contentType,
285
+ size: buf.byteLength,
286
+ });
287
+ } catch (e) { fail(port, msg.id, e); }
288
+ break;
289
+ }
290
+
291
+ case 'storage.deleteObject': {
292
+ // `pyric/storage`'s sandbox delete is idempotent (no-op on missing) —
293
+ // matching the pyric-admin local arm's delete semantics. Rules enforce
294
+ // `write` under the op's lens; deletes emit `service_mutation` events.
295
+ // Async dispatch escapes the ambient window — thread provenance
296
+ // explicitly (issue #84 item 3).
297
+ try {
298
+ const storage = bindStorageOperationContext(
299
+ lensStorage(ctx, msg.actAs, port),
300
+ opProvenance(msg),
301
+ );
302
+ await storageDeleteObject(storageRef(storage, msg.path));
303
+ ok(port, msg.id, null);
304
+ } catch (e) { fail(port, msg.id, e); }
305
+ break;
306
+ }
307
+
308
+ default: {
309
+ fail(port, msg.id, new Error(`Unknown method: ${String((msg as { method: unknown }).method)}`));
310
+ }
311
+ }
312
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * SharedWorker host — Pyric Studio control ops.
3
+ *
4
+ * The sandbox-snapshot export Studio forks for rules re-run (`getSnapshot`).
5
+ * Kept as its own family to mirror the client's `studio` module.
6
+ *
7
+ * Routed here by the host dispatcher. Never imports the dispatcher.
8
+ */
9
+
10
+ import { setRules } from 'pyric/sandbox/firestore';
11
+
12
+ import type { OpMessage } from '../protocol.js';
13
+ import { type HostCtx, type PortLike, ok, fail } from '../host-context.js';
14
+
15
+ /** The Studio control op methods routed to {@link handleStudioOp}. */
16
+ const STUDIO_METHODS = new Set<string>([
17
+ 'getSnapshot',
18
+ 'resetAll',
19
+ ]);
20
+
21
+ export function isStudioOp(method: OpMessage['method']): boolean {
22
+ return STUDIO_METHODS.has(method);
23
+ }
24
+
25
+ export async function handleStudioOp(
26
+ ctx: HostCtx,
27
+ port: PortLike,
28
+ msg: OpMessage,
29
+ ): Promise<void> {
30
+ switch (msg.method) {
31
+ case 'getSnapshot': {
32
+ // Export the current sandbox snapshot (Pyric Studio rules re-run): Studio
33
+ // forks it locally to test edited rules / re-issue as the user on a branch.
34
+ ok(port, msg.id, ctx.sandbox.snapshot());
35
+ break;
36
+ }
37
+
38
+ case 'resetAll': {
39
+ // Sandbox-owned full reset (issue #359): the ONE wipe-everything path.
40
+ // `resetAll` iterates the worker sandbox's persistable-service registry,
41
+ // so auth users, the RTDB tree, AND storage objects clear along with the
42
+ // Firestore env — Studio can't forget a service the sandbox knows about.
43
+ // Awaited (storage clears IndexedDB stores) before the ack. Op-level
44
+ // failures reply as errors; per-service reset failures ride the reply
45
+ // payload's `errors` so Studio can surface a half-clear instead of
46
+ // reporting a clean reset.
47
+ try {
48
+ const { errors } = await ctx.sandbox.resetAll();
49
+ // `resetAll` swapped the env, wiping env-owned FIRESTORE rules (RTDB /
50
+ // storage rules live on their service objects and survive). Re-deploy
51
+ // the active project rules so a DATA reset never de-governs writes.
52
+ const firestoreRules = ctx.activeRules?.firestore;
53
+ const source =
54
+ firestoreRules?.status === 'active'
55
+ ? firestoreRules.source
56
+ : firestoreRules?.lastKnownGood;
57
+ if (typeof source === 'string') setRules(ctx.sandbox, source);
58
+ // The server capture (`.pyric/last-session.json`) persists the event
59
+ // history a rebooting worker re-primes into Traffic. Flush it NOW —
60
+ // reset just emptied `sandbox.history()`, and waiting out the
61
+ // capture debounce leaves a window where a worker death resurrects
62
+ // the wiped session's events on the next boot.
63
+ await ctx.captureFlush?.();
64
+ ok(port, msg.id, { errors });
65
+ } catch (e) {
66
+ fail(port, msg.id, e instanceof Error ? e : new Error(String(e)));
67
+ }
68
+ break;
69
+ }
70
+
71
+ default: {
72
+ fail(port, msg.id, new Error(`Unknown method: ${String((msg as { method: unknown }).method)}`));
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * SharedWorker host — Firestore + RTDB value-subscription registry.
3
+ *
4
+ * `onSnapshot` / RTDB `onValue` listeners registered against the op's
5
+ * lens-resolved handle, with cross-port snapshot fan-out. Owns the
6
+ * session-bound sub registry (#754) that re-establishes a port's lens-less
7
+ * listeners under its NEW identity on every auth transition, mirroring prod's
8
+ * stream re-establishment (a sign-out re-evaluates live listeners so an
9
+ * auth-gated stream loses access instead of leaking the previous user's data).
10
+ *
11
+ * Routed here by the host dispatcher. Never imports the dispatcher.
12
+ */
13
+
14
+ import {
15
+ onSnapshot,
16
+ type DocumentReference,
17
+ type CollectionReference,
18
+ type Query,
19
+ } from 'pyric/firestore';
20
+ import {
21
+ ref as rtdbRef,
22
+ onValue as rtdbOnValue,
23
+ type DatabaseReference,
24
+ } from 'pyric/database';
25
+
26
+ import type { FirestoreSubMessage, RtdbValueSubMessage, UnsubMessage } from '../protocol.js';
27
+ import { serializeError, isRtdbSub } from '../protocol.js';
28
+ import { activityJourneyId, type HostCtx, type PortLike, post } from '../host-context.js';
29
+ import {
30
+ lensDb,
31
+ sessionDb,
32
+ lensRtdb,
33
+ opProvenance,
34
+ resolveTarget,
35
+ serializeDocSnap,
36
+ } from './core.js';
37
+ import { rtdbSnapToWire } from './rtdb.js';
38
+
39
+ /**
40
+ * Session-bound sub registry (#754): the original sub message for every
41
+ * listener a port opened WITHOUT an explicit lens, so a port session change
42
+ * can re-establish it under the new identity (see resubscribeSessionSubs).
43
+ * Parallel to `ctx.subs` (which holds only the unsub fns).
44
+ */
45
+ type SessionBoundSubMessage = FirestoreSubMessage | RtdbValueSubMessage;
46
+
47
+ const _sessionSubs = new WeakMap<HostCtx, Map<PortLike, Map<string, SessionBoundSubMessage>>>();
48
+
49
+ function sessionSubsFor(ctx: HostCtx, port: PortLike): Map<string, SessionBoundSubMessage> {
50
+ let byPort = _sessionSubs.get(ctx);
51
+ if (!byPort) {
52
+ byPort = new Map();
53
+ _sessionSubs.set(ctx, byPort);
54
+ }
55
+ let bySubId = byPort.get(port);
56
+ if (!bySubId) {
57
+ bySubId = new Map();
58
+ byPort.set(port, bySubId);
59
+ }
60
+ return bySubId;
61
+ }
62
+
63
+ /** Drop a port's session-bound sub records — invoked by the dispatcher's
64
+ * `cleanupPort` on port disconnect. */
65
+ export function dropPortSessionSubs(ctx: HostCtx, port: PortLike): void {
66
+ _sessionSubs.get(ctx)?.delete(port);
67
+ }
68
+
69
+ /**
70
+ * Re-establish a port's session-bound listeners under its CURRENT session —
71
+ * invoked (via the ctx hook) on every port session change. Mirrors prod's
72
+ * stream re-establishment on auth transitions: each listener is torn down and
73
+ * re-registered through `sessionDb`, so the fresh evaluation either delivers
74
+ * a snapshot (allowed) or a `permission-denied` snap-error (revoked). A
75
+ * signed-out page no longer keeps receiving auth-gated data.
76
+ */
77
+ function resubscribeSessionSubs(ctx: HostCtx, port: PortLike): void {
78
+ const bound = _sessionSubs.get(ctx)?.get(port);
79
+ if (!bound || bound.size === 0) return;
80
+ const portSubs = ctx.subs.get(port);
81
+ for (const [subId, msg] of [...bound]) {
82
+ const unsub = portSubs?.get(subId);
83
+ if (isRtdbSub(msg)) {
84
+ if (unsub) unsub();
85
+ portSubs?.delete(subId);
86
+ bound.delete(subId); // handleRtdbSub re-records it
87
+ handleRtdbSub(ctx, port, msg);
88
+ } else {
89
+ // Auth transitions enter through host-auth rather than the dispatcher,
90
+ // so restore the original app/page provenance explicitly. The detach is
91
+ // correlated to the old accepted registration by listener id; this
92
+ // replacement attach must retain the same logical page attribution.
93
+ const provenance = {
94
+ ...opProvenance(msg, activityJourneyId(ctx, port)),
95
+ activity: { listenerId: subId, listenerLifecycle: 'reauthorize' as const },
96
+ };
97
+ const reauthorize = (): void => {
98
+ if (unsub) unsub();
99
+ portSubs?.delete(subId);
100
+ bound.delete(subId); // handleSub re-records it
101
+ handleSub(ctx, port, msg);
102
+ };
103
+ if (provenance && ctx.sandbox.runWithProvenance) {
104
+ ctx.sandbox.runWithProvenance(provenance, reauthorize);
105
+ } else {
106
+ reauthorize();
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ export function handleSub(ctx: HostCtx, port: PortLike, msg: FirestoreSubMessage): void {
113
+ // Resolve the listener's data handle through the SAME lens path ops use
114
+ // (Pyric Studio F4 "watch as user"): `{ mode: 'as', uid }` registers the
115
+ // listener as that user so its rule evals impersonate, `{ mode: 'admin' }`
116
+ // bypasses rules. Absent ⇒ the PORT'S SESSION (#754), so an app listener
117
+ // evaluates rules as whoever this tab signed in as.
118
+ const db = msg.actAs ? lensDb(ctx, msg.actAs) : sessionDb(ctx, port);
119
+ ensurePortSubs(ctx, port);
120
+ const portSubs = ctx.subs.get(port)!;
121
+
122
+ if (portSubs.has(msg.subId)) return; // idempotent
123
+
124
+ // Session-bound listeners re-establish on this port's auth transitions.
125
+ if (!msg.actAs) {
126
+ ctx.resubscribePortSubs ??= (p) => resubscribeSessionSubs(ctx, p);
127
+ sessionSubsFor(ctx, port).set(msg.subId, msg);
128
+ }
129
+
130
+ let target: DocumentReference | CollectionReference | Query;
131
+ let unsub: () => void;
132
+ try {
133
+ target = resolveTarget(db, msg.target);
134
+ unsub = registerListener(ctx, port, msg, target);
135
+ } catch (e) {
136
+ // resolveTarget / onSnapshot can throw synchronously (e.g. an invalid
137
+ // query or a rules-rejected target). Deliver it to the client's onSnapshot
138
+ // error callback as a snap-error instead of letting it escape handleMessage
139
+ // as an unhandled rejection (which would silently deliver NOTHING).
140
+ post(port, { t: 'snap', subId: msg.subId, value: { __error: serializeError(e) } });
141
+ return;
142
+ }
143
+
144
+ portSubs.set(msg.subId, unsub);
145
+ }
146
+
147
+ export function handleRtdbSub(ctx: HostCtx, port: PortLike, msg: RtdbValueSubMessage): void {
148
+ ensurePortSubs(ctx, port);
149
+ const portSubs = ctx.subs.get(port)!;
150
+ if (portSubs.has(msg.subId)) return;
151
+
152
+ if (!msg.actAs) {
153
+ ctx.resubscribePortSubs ??= (p) => resubscribeSessionSubs(ctx, p);
154
+ sessionSubsFor(ctx, port).set(msg.subId, msg);
155
+ }
156
+
157
+ try {
158
+ const ref = rtdbRef(lensRtdb(ctx, msg.actAs, port), msg.target.path);
159
+ const unsub = rtdbOnValue(
160
+ ref as DatabaseReference,
161
+ (snap) => post(port, { t: 'snap', subId: msg.subId, value: rtdbSnapToWire(snap) }),
162
+ );
163
+ portSubs.set(msg.subId, unsub);
164
+ } catch (e) {
165
+ post(port, { t: 'snap', subId: msg.subId, value: { __error: serializeError(e) } });
166
+ }
167
+ }
168
+
169
+ /** Register the real sandbox listener for a resolved target; returns its unsub.
170
+ * Split out of handleSub so the throwing surface (resolveTarget + onSnapshot)
171
+ * is inside handleSub's try/catch. */
172
+ function registerListener(
173
+ _ctx: HostCtx,
174
+ port: PortLike,
175
+ msg: FirestoreSubMessage,
176
+ target: DocumentReference | CollectionReference | Query,
177
+ ): () => void {
178
+ return onSnapshot(
179
+ target as DocumentReference | Query,
180
+ (snap) => {
181
+ // Detect doc vs query snapshot by shape.
182
+ const snapAny = snap as {
183
+ id?: string;
184
+ path?: string;
185
+ exists?: boolean | (() => boolean);
186
+ data?: () => Record<string, unknown> | undefined;
187
+ docs?: Array<{
188
+ id: string;
189
+ path?: string;
190
+ exists: boolean | (() => boolean);
191
+ data(): Record<string, unknown>;
192
+ }>;
193
+ };
194
+
195
+ if (Array.isArray(snapAny.docs)) {
196
+ // Query snapshot
197
+ const docs = snapAny.docs.map((d) =>
198
+ serializeDocSnap(d as Parameters<typeof serializeDocSnap>[0]),
199
+ );
200
+ post(port, { t: 'snap', subId: msg.subId, value: { docs } });
201
+ } else if (snapAny.id !== undefined) {
202
+ // Doc snapshot
203
+ post(port, {
204
+ t: 'snap',
205
+ subId: msg.subId,
206
+ value: serializeDocSnap(snapAny as Parameters<typeof serializeDocSnap>[0]),
207
+ });
208
+ }
209
+ },
210
+ (err) => {
211
+ // Snapshot listener error (e.g. rules changed to deny).
212
+ // We forward as a snap with an __error field so the client can
213
+ // surface it to the original onSnapshot error callback.
214
+ post(port, { t: 'snap', subId: msg.subId, value: { __error: serializeError(err) } });
215
+ },
216
+ );
217
+ }
218
+
219
+ export function handleUnsub(ctx: HostCtx, port: PortLike, msg: UnsubMessage): void {
220
+ // Drop the session-bound record first — even when the live listener never
221
+ // registered (it errored at sub time), the record must not resurrect the
222
+ // sub on a later session change.
223
+ _sessionSubs.get(ctx)?.get(port)?.delete(msg.subId);
224
+ const portSubs = ctx.subs.get(port);
225
+ if (!portSubs) return;
226
+ const unsub = portSubs.get(msg.subId);
227
+ if (!unsub) return;
228
+ unsub();
229
+ portSubs.delete(msg.subId);
230
+ }
231
+
232
+ // ─── Internal helpers ─────────────────────────────────────────────────────
233
+
234
+ function ensurePortSubs(ctx: HostCtx, port: PortLike): void {
235
+ if (!ctx.subs.has(port)) {
236
+ ctx.subs.set(port, new Map());
237
+ }
238
+ }