@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,137 @@
1
+ import { sandbox as authOps } from 'pyric/auth';
2
+ import {
3
+ bundleRecords,
4
+ parseBundle,
5
+ serializeToBuckets,
6
+ type PersistenceBackend,
7
+ } from 'pyric/sandbox';
8
+ import type { InitPayload } from '../namespace.js';
9
+ import { ensureAuth, type HostCtx } from './host.js';
10
+
11
+ export interface DurablePersistenceEnv {
12
+ fetch: typeof fetch;
13
+ captureDebounceMs?: number;
14
+ }
15
+
16
+ const WORKER_WRITER_ID = 'pyric-shared-worker';
17
+
18
+ const stateSection = (name: 'firestore' | 'auth'): string => `/__pyric/state?section=${name}`;
19
+
20
+ /** Add the committable server-file or seed-fixture tier to the worker's IDB store. */
21
+ export function createWorkerDurableBackend(
22
+ idb: PersistenceBackend,
23
+ payload: InitPayload,
24
+ env: DurablePersistenceEnv,
25
+ ): PersistenceBackend {
26
+ const persist = Boolean(payload.persist);
27
+ const seed = (payload.seedState ?? null) as {
28
+ firestore?: Record<string, Record<string, unknown>>;
29
+ services?: Record<string, unknown>;
30
+ } | null;
31
+ const fixtureRecords = !persist && seed != null
32
+ ? serializeToBuckets(seed.firestore ?? {}, seed.services ?? {}, 0)
33
+ : null;
34
+
35
+ if (!persist && fixtureRecords === null) return idb;
36
+
37
+ let primed = false;
38
+ const primeOnce = async (key: string): Promise<void> => {
39
+ if (primed) return;
40
+ primed = true;
41
+ if ((await idb.listRecords(key)).length > 0) return;
42
+ if (persist) {
43
+ const res = await env.fetch(stateSection('firestore'));
44
+ if (res.status === 200) {
45
+ const records = parseBundle(await res.text());
46
+ if (records.size > 0) await idb.putRecords(key, records);
47
+ }
48
+ } else if (fixtureRecords !== null) {
49
+ await idb.putRecords(key, fixtureRecords);
50
+ }
51
+ };
52
+
53
+ const mirror = async (key: string): Promise<void> => {
54
+ if (!persist) return;
55
+ try {
56
+ const ids = await idb.listRecords(key);
57
+ const all = new Map<string, unknown>();
58
+ for (const id of ids) {
59
+ const record = await idb.getRecord(key, id);
60
+ if (record != null) all.set(id, record);
61
+ }
62
+ const res = await env.fetch(stateSection('firestore'), {
63
+ method: 'POST',
64
+ headers: { 'content-type': 'application/json', 'x-pyric-writer': WORKER_WRITER_ID },
65
+ body: bundleRecords(all),
66
+ });
67
+ if (res.status === 423) {
68
+ console.warn('[pyric worker] --persist: server file is held by another writer; export skipped.');
69
+ }
70
+ } catch {
71
+ /* export sink offline; local IDB is unaffected. */
72
+ }
73
+ };
74
+
75
+ return {
76
+ async getRecord(key, recordId) {
77
+ await primeOnce(key);
78
+ return idb.getRecord(key, recordId);
79
+ },
80
+ async listRecords(key) {
81
+ await primeOnce(key);
82
+ return idb.listRecords(key);
83
+ },
84
+ async putRecords(key, records) {
85
+ await idb.putRecords(key, records);
86
+ await mirror(key);
87
+ },
88
+ async deleteRecords(key, recordIds) {
89
+ await idb.deleteRecords(key, recordIds);
90
+ await mirror(key);
91
+ },
92
+ async clear(key) {
93
+ await idb.clear(key);
94
+ if (!persist) return;
95
+ try {
96
+ await env.fetch(stateSection('firestore'), {
97
+ method: 'POST',
98
+ headers: { 'content-type': 'application/json', 'x-pyric-writer': WORKER_WRITER_ID },
99
+ body: 'null',
100
+ });
101
+ } catch {
102
+ /* export sink offline; local IDB is unaffected. */
103
+ }
104
+ },
105
+ };
106
+ }
107
+
108
+ /** Mirror the auth user DB to the committable server file when persistence is enabled. */
109
+ export function setupServerAuthFlush(
110
+ ctx: HostCtx,
111
+ payload: InitPayload,
112
+ env: DurablePersistenceEnv,
113
+ ): () => void {
114
+ if (!payload.persist) return () => {};
115
+ const auth = ensureAuth(ctx);
116
+ const debounceMs = env.captureDebounceMs ?? 400;
117
+ let timer: ReturnType<typeof setTimeout> | null = null;
118
+
119
+ const flush = (): void => {
120
+ const body = JSON.stringify({ users: authOps.exportUsers(auth) });
121
+ env.fetch(stateSection('auth'), {
122
+ method: 'POST',
123
+ headers: { 'content-type': 'application/json', 'x-pyric-writer': WORKER_WRITER_ID },
124
+ body,
125
+ }).catch(() => {});
126
+ };
127
+
128
+ const unsubscribe = authOps.subscribeUsers(auth, () => {
129
+ if (timer) clearTimeout(timer);
130
+ timer = setTimeout(flush, debounceMs);
131
+ });
132
+
133
+ return () => {
134
+ if (timer) clearTimeout(timer);
135
+ unsubscribe();
136
+ };
137
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * SharedWorker entry point — the actual SharedWorker script.
3
+ *
4
+ * WHY A SEPARATE ENTRY
5
+ * --------------------
6
+ * `host.ts` is pure-ish: it accepts an injected `{ db, sandbox }` and
7
+ * fake ports, so tests can drive it without a real SharedWorker runtime.
8
+ * This file is the integration shell: it creates the ONE real sandbox,
9
+ * attaches IDB persistence, deploys permissive starter rules, and wires
10
+ * every connecting port to `handleMessage`.
11
+ *
12
+ * Do NOT import this file in tests — import `host.ts` directly.
13
+ *
14
+ * LIFECYCLE
15
+ * ---------
16
+ * The SharedWorker lives while at least one tab holds a port. When all
17
+ * tabs close or navigate away, the worker is GC'd. IDB persistence means
18
+ * data survives full close → reopen cycles: on the next connect the sandbox
19
+ * restores from IDB, and the tab reconnects to a warm state.
20
+ *
21
+ * PERSISTENCE KEY
22
+ * ---------------
23
+ * Project-scoped: `'pyric-shared-worker:<projectKey>'`, derived inside
24
+ * `buildWorkerCtx` from the init payload's `projectKey` (issue #359 family —
25
+ * IndexedDB is origin-scoped, so the old fixed key shared one sandbox
26
+ * snapshot database across every project on a localhost port). The legacy
27
+ * `'pyric-shared-worker'` key applies only when no project identity exists
28
+ * (older servers, standalone workers); see `workerPersistenceKey` in
29
+ * serve-init.ts for the orphan-don't-delete migration decision.
30
+ *
31
+ * RULES
32
+ * -----
33
+ * We deploy a permissive starter ruleset on first init. Callers change
34
+ * rules at runtime via `{ t:'op', method:'setRules', source }` messages.
35
+ *
36
+ * SINGLE SANDBOX GUARANTEE
37
+ * ------------------------
38
+ * `_ctx` is module-level. The first `connect` event lazily creates it;
39
+ * all subsequent ports reuse the same ctx — one sandbox, one Firestore
40
+ * backend for all tabs. This is the architectural invariant the whole
41
+ * SharedWorker approach depends on.
42
+ */
43
+
44
+ // SharedWorkerGlobalScope is not in the default TypeScript lib (it's in
45
+ // lib.webworker.d.ts which conflicts with lib.dom.d.ts). This entry file is
46
+ // browser-only and only bundled with esbuild (never compiled by tsc for
47
+ // production). We declare the minimal interface we need so tsc can type-check
48
+ // without enabling the full webworker lib.
49
+ interface SharedWorkerGlobalScope {
50
+ onconnect: ((e: MessageEvent) => void) | null;
51
+ close(): void;
52
+ }
53
+
54
+ import { createIndexedDBBackend } from 'pyric/sandbox';
55
+
56
+ import {
57
+ handleMessage,
58
+ cleanupPort,
59
+ type HostCtx,
60
+ type PortLike,
61
+ } from './host.js';
62
+ import { buildWorkerCtx, type EventSourceLike } from './serve-init.js';
63
+ import type { InboundMessage } from './protocol.js';
64
+ import {
65
+ SERVICE_WORKER_CHANNEL,
66
+ type ServiceWorkerChannelMessage,
67
+ } from './service-worker-channel.js';
68
+ import { createServiceWorkerRelay } from './service-worker-relay.js';
69
+ import { createWorkerRetirement } from './retirement.js';
70
+
71
+ declare const __PYRIC_WORKER_VERSION__: string;
72
+ const workerEpoch = typeof __PYRIC_WORKER_VERSION__ !== 'undefined'
73
+ ? __PYRIC_WORKER_VERSION__
74
+ : 'dev';
75
+ const workerScope = self as unknown as SharedWorkerGlobalScope;
76
+ const retirement = createWorkerRetirement({
77
+ closeWorker: () => workerScope.close(),
78
+ beforeAnnounce: async () => { await _ctx?.captureFlush?.(); },
79
+ });
80
+
81
+ // ─── Singleton context ────────────────────────────────────────────────────
82
+
83
+ // `_ctx` is the RESOLVED context, kept for the synchronous `close` handler.
84
+ // The build is memoized as a PROMISE (`_ctxPromise`) — NOT as the resolved
85
+ // value — because init is async (it awaits `/__pyric/init.json` + persistence
86
+ // restore). The first messages arrive in a BURST (the authState sub, the first
87
+ // firestore sub, an op — possibly across two tabs at once); if each re-checked
88
+ // only the resolved `_ctx` they would every one start a fresh init and build a
89
+ // SEPARATE sandbox, so a listener and a write could bind to different
90
+ // instances and never see each other. The promise memo guarantees ONE init.
91
+ let _ctx: HostCtx | null = null;
92
+ let _ctxPromise: Promise<HostCtx> | null = null;
93
+
94
+ /**
95
+ * Return the shared context, memoizing the in-flight init promise so
96
+ * concurrent first messages await ONE build (see `_ctxPromise` above).
97
+ */
98
+ function getCtx(): Promise<HostCtx> {
99
+ return (_ctxPromise ??= buildCtx());
100
+ }
101
+
102
+ /**
103
+ * Build the ONE shared sandbox + Firestore handle. Invoked exactly once per
104
+ * worker lifetime via the `getCtx` memo — never concurrently. The real boot
105
+ * logic lives in `buildWorkerCtx` (serve-init.ts) so it is testable without
106
+ * a SharedWorker runtime; this shell only supplies the real ambients.
107
+ *
108
+ * NOTE (#754): no worker-side session restore. Sessions are per-port; each
109
+ * PAGE re-establishes its own session from web storage via
110
+ * `auth.restorePortSession` (runtime.ts). The user DB itself is restored
111
+ * from the persisted snapshot inside buildWorkerCtx (#629).
112
+ */
113
+ async function buildCtx(): Promise<HostCtx> {
114
+ // ONE IDB backend for the whole worker. createIndexedDBBackend's guard
115
+ // checks `typeof indexedDB === 'undefined'` — SharedWorkers have full
116
+ // IndexedDB access, so this succeeds. The persistence controller's
117
+ // window.addEventListener('beforeunload', ...) is guarded by
118
+ // `typeof window !== 'undefined'`, so enablePersistence is worker-safe.
119
+ const ctx = await buildWorkerCtx({
120
+ fetch,
121
+ idb: createIndexedDBBackend(),
122
+ // EventSource is available in workers; guard anyway for exotic hosts.
123
+ makeEventSource:
124
+ typeof EventSource !== 'undefined'
125
+ ? (url) => new EventSource(url) as unknown as EventSourceLike
126
+ : null,
127
+ });
128
+ _ctx = ctx; // publish the resolved ctx for the synchronous close handler
129
+ return ctx;
130
+ }
131
+
132
+ // ─── SharedWorker connect handler ─────────────────────────────────────────
133
+
134
+ /**
135
+ * `self` in a SharedWorker is `SharedWorkerGlobalScope`, not `Window`.
136
+ * Each tab that opens the SharedWorker fires one `connect` event.
137
+ * Each tab gets its own `MessagePort`; we start() it (required for
138
+ * classic-mode workers to un-pause the message queue) and wire onmessage.
139
+ */
140
+ workerScope.onconnect = (e: MessageEvent) => {
141
+ const port = e.ports[0];
142
+ port.start();
143
+ retirement.connect(port);
144
+
145
+ let messageQueue = Promise.resolve();
146
+ port.onmessage = (ev: MessageEvent<InboundMessage>) => {
147
+ if (ev.data.t === 'op' && ev.data.method === 'getRuntimeEpoch') {
148
+ port.postMessage({
149
+ t: 'res',
150
+ id: ev.data.id,
151
+ ok: true,
152
+ value: {
153
+ version: workerEpoch,
154
+ },
155
+ });
156
+ return;
157
+ }
158
+ if (ev.data.t === 'op' && ev.data.method === 'retireRuntime') {
159
+ void retirement.retire(port, ev.data.id, ev.data.targetEpoch);
160
+ return;
161
+ }
162
+ if (!retirement.accepting()) {
163
+ if ('id' in ev.data) {
164
+ port.postMessage({
165
+ t: 'res', id: ev.data.id, ok: false,
166
+ error: { code: 'pyric/worker-retiring', message: 'The Pyric worker is restarting.' },
167
+ });
168
+ }
169
+ return;
170
+ }
171
+ // Serialize each port's frames. A disconnect acknowledgement therefore
172
+ // cannot overtake an already-posted mutation, and later frames see the
173
+ // disconnected-port tombstone instead of touching the shared backend.
174
+ messageQueue = messageQueue.then(async () => {
175
+ try {
176
+ const ctx = await getCtx();
177
+ await handleMessage(ctx, port as unknown as PortLike, ev.data);
178
+ } catch (e) {
179
+ // eslint-disable-next-line no-console
180
+ console.error('[pyric worker] message handler error:', (e as Error)?.stack ?? e, 'msg:', ev.data);
181
+ }
182
+ });
183
+ retirement.track(port, messageQueue);
184
+ };
185
+
186
+ // Best-effort port cleanup on tab close/navigation.
187
+ // Chrome doesn't reliably fire 'close' on MessagePort — handled as
188
+ // best-effort; subscriptions also GC when the worker itself dies.
189
+ port.addEventListener('close', () => {
190
+ retirement.disconnect(port);
191
+ if (_ctx) cleanupPort(_ctx, port as unknown as PortLike);
192
+ });
193
+ };
194
+
195
+ // ServiceWorkerGlobalScope cannot construct a SharedWorker, but both worker
196
+ // kinds share BroadcastChannel. Adapt each SW app connection to the host's
197
+ // existing PortLike seam so it reaches this exact context/broker instead of
198
+ // constructing a second in-Service-Worker sandbox.
199
+ if (typeof BroadcastChannel !== 'undefined') {
200
+ const channel = new BroadcastChannel(SERVICE_WORKER_CHANNEL);
201
+ const relay = createServiceWorkerRelay({
202
+ getCtx,
203
+ send(message) { channel.postMessage(message); },
204
+ onError(error, envelope) {
205
+ console.error(
206
+ '[pyric worker] service-worker relay error:',
207
+ (error as Error)?.stack ?? error,
208
+ 'msg:',
209
+ envelope,
210
+ );
211
+ },
212
+ });
213
+ channel.onmessage = (event: MessageEvent<ServiceWorkerChannelMessage>) => {
214
+ const envelope = event.data;
215
+ if (envelope.direction !== 'host') return;
216
+ // A generation-targeted replacement can briefly overlap this old worker
217
+ // with its successor. Only the accepting generation may answer the shared
218
+ // service-worker relay channel, otherwise both workers would reply.
219
+ if (!retirement.accepting()) return;
220
+ const work = relay.handle(envelope);
221
+ retirement.trackDetached(work);
222
+ };
223
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * SharedWorker host — admin-lens Firestore document ops (rules bypass).
3
+ *
4
+ * The `admin.*` surface used by the pyric-admin remote arm + Pyric Studio's
5
+ * "edit anything as admin": direct document get/list/set/delete against the
6
+ * sandbox admin plane, plus `admin.readState` (a prefix/depth-scoped snapshot
7
+ * of the whole store through the cached admin handle).
8
+ *
9
+ * Routed here by the host dispatcher. These ops act on `ctx.sandbox.admin` /
10
+ * the admin handle directly, so they take no resolved `db`. Never imports the
11
+ * dispatcher.
12
+ */
13
+
14
+ import type { OpMessage } from '../protocol.js';
15
+ import { snapshotDocuments } from 'pyric/sandbox/firestore';
16
+ import { type HostCtx, type PortLike, ok, fail, bestEffortFlush } from '../host-context.js';
17
+
18
+ /** The admin-lens op methods routed to {@link handleAdminFirestoreOp}. */
19
+ const ADMIN_METHODS = new Set<string>([
20
+ 'admin.getDocument',
21
+ 'admin.listDocuments',
22
+ 'admin.setDocument',
23
+ 'admin.deleteDocument',
24
+ 'admin.readState',
25
+ ]);
26
+
27
+ export function isAdminFirestoreOp(method: OpMessage['method']): boolean {
28
+ return ADMIN_METHODS.has(method);
29
+ }
30
+
31
+ export async function handleAdminFirestoreOp(
32
+ ctx: HostCtx,
33
+ port: PortLike,
34
+ msg: OpMessage,
35
+ ): Promise<void> {
36
+ switch (msg.method) {
37
+ case 'admin.getDocument': {
38
+ try {
39
+ ok(port, msg.id, ctx.sandbox.admin.getDocument(msg.path));
40
+ } catch (e) { fail(port, msg.id, e); }
41
+ break;
42
+ }
43
+
44
+ case 'admin.listDocuments': {
45
+ try {
46
+ ok(port, msg.id, ctx.sandbox.admin.listDocuments(msg.path));
47
+ } catch (e) { fail(port, msg.id, e); }
48
+ break;
49
+ }
50
+
51
+ case 'admin.setDocument': {
52
+ try {
53
+ ctx.sandbox.admin.setDocument(msg.path, msg.data as Record<string, unknown>);
54
+ await bestEffortFlush(ctx);
55
+ ok(port, msg.id, null);
56
+ } catch (e) { fail(port, msg.id, e); }
57
+ break;
58
+ }
59
+
60
+ case 'admin.deleteDocument': {
61
+ try {
62
+ const deleted = ctx.sandbox.admin.deleteDocument(msg.path);
63
+ await bestEffortFlush(ctx);
64
+ ok(port, msg.id, deleted);
65
+ } catch (e) { fail(port, msg.id, e); }
66
+ break;
67
+ }
68
+
69
+ case 'admin.readState': {
70
+ try {
71
+ const snap = snapshotDocuments(ctx.sandbox);
72
+ const out: Record<string, unknown> = {};
73
+ const prefix = msg.path ?? '';
74
+ for (const [path, data] of Object.entries(snap)) {
75
+ if (prefix && !path.startsWith(prefix)) continue;
76
+ if (msg.maxDepth !== undefined && path.split('/').length > msg.maxDepth) continue;
77
+ out[path] = data;
78
+ }
79
+ ok(port, msg.id, out);
80
+ } catch (e) { fail(port, msg.id, e); }
81
+ break;
82
+ }
83
+
84
+ default: {
85
+ fail(port, msg.id, new Error(`Unknown method: ${String((msg as { method: unknown }).method)}`));
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * SharedWorker host — connection / instance / state-transfer ops.
3
+ *
4
+ * The worker-lifecycle surface the client's `connection` family talks to:
5
+ * - `getVersion` (build hash + per-worker instance id),
6
+ * - full-state transfer (`exportState`/`importState`, the portable bundle),
7
+ * - named branches (`saveBranch`/`listBranches`/`switchBranch`/`deleteBranch`,
8
+ * saved-state bundles in the RAW idb).
9
+ *
10
+ * Owns the stable per-worker instance id (persisted to the raw idb) and the
11
+ * branch registry helpers. `getOrCreateInstanceId` is imported by serve-init;
12
+ * the instance-id + branch constants/helpers are part of the host's public
13
+ * surface (re-exported by the host barrel). Never imports the dispatcher.
14
+ */
15
+
16
+ import type { PersistenceBackend } from 'pyric/sandbox';
17
+ import { serializeToBuckets, bundleRecords, parseBundle, deserializeFromBuckets } from 'pyric/sandbox';
18
+
19
+ import type { OpMessage } from '../protocol.js';
20
+ import { type HostCtx, type PortLike, ok, fail } from '../host-context.js';
21
+
22
+ /** Build hash injected by the bundler's esbuild `define`. Undefined when the
23
+ * compiled host is imported directly (tests) — guarded with `typeof`. */
24
+ declare const __PYRIC_WORKER_VERSION__: string;
25
+
26
+ /**
27
+ * Per-SharedWorker instance id — generated once and persisted to the RAW idb
28
+ * (local-only, like the session record above; it must NEVER reach the
29
+ * committable server file). Because IndexedDB is per (origin + browser profile),
30
+ * two profiles on the same `localhost:<port>` get two distinct ids — which is
31
+ * exactly how the UI tells same-port-different-profile sandboxes apart.
32
+ */
33
+ export const INSTANCE_ID_KEY = 'pyric:worker:instance';
34
+
35
+ /**
36
+ * `crypto.randomUUID()` is secure-context-only (https or localhost), so it is
37
+ * `undefined` over plain http on a non-localhost host (a Tailscale or LAN
38
+ * hostname). `crypto.getRandomValues` is NOT gated, so build a v4 UUID from it as
39
+ * the fallback. Without this the worker throws on init over Tailscale and the
40
+ * whole sandbox (auth, firestore, bridge) silently fails to come up.
41
+ */
42
+ export function randomUuid(): string {
43
+ if (typeof crypto.randomUUID === 'function') return crypto.randomUUID();
44
+ const b = crypto.getRandomValues(new Uint8Array(16));
45
+ b[6] = (b[6] & 0x0f) | 0x40;
46
+ b[8] = (b[8] & 0x3f) | 0x80;
47
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, '0'));
48
+ return `${h.slice(0, 4).join('')}-${h.slice(4, 6).join('')}-${h.slice(6, 8).join('')}-${h.slice(8, 10).join('')}-${h.slice(10, 16).join('')}`;
49
+ }
50
+
51
+ export async function getOrCreateInstanceId(idb: PersistenceBackend): Promise<string> {
52
+ const rec = (await idb.getRecord(INSTANCE_ID_KEY, 'id')) as { value?: string } | undefined;
53
+ if (rec && typeof rec.value === 'string') return rec.value;
54
+ const id = randomUuid();
55
+ await idb.putRecords(INSTANCE_ID_KEY, new Map([['id', { value: id }]]));
56
+ return id;
57
+ }
58
+
59
+ // ── Phase 3: named branches ─────────────────────────────────────────────────
60
+ // A branch is a named saved state bundle in the RAW idb (local-only, like the
61
+ // instance id + session; it must NEVER reach the committable server file). They
62
+ // let one instance keep several named states it can switch between
63
+ // (switchBranch = loadSnapshot the bundle, a clobber). A registry record holds
64
+ // the ordered name list, since the backend lists records WITHIN a key, not keys.
65
+ export const BRANCH_PREFIX = 'pyric:worker:branch:';
66
+ export const BRANCH_REGISTRY_KEY = 'pyric:worker:branches';
67
+
68
+ export async function listBranchNames(idb?: PersistenceBackend): Promise<string[]> {
69
+ if (!idb) return [];
70
+ const rec = (await idb.getRecord(BRANCH_REGISTRY_KEY, 'names')) as { value?: string[] } | undefined;
71
+ return Array.isArray(rec?.value) ? rec.value : [];
72
+ }
73
+
74
+ async function writeBranchRegistry(idb: PersistenceBackend, names: string[]): Promise<void> {
75
+ await idb.putRecords(BRANCH_REGISTRY_KEY, new Map([['names', { value: names }]]));
76
+ }
77
+
78
+ /** The connection/state/branch op methods routed to {@link handleConnectionOp}. */
79
+ const CONNECTION_METHODS = new Set<string>([
80
+ 'getVersion',
81
+ 'exportState',
82
+ 'importState',
83
+ 'saveBranch',
84
+ 'listBranches',
85
+ 'switchBranch',
86
+ 'deleteBranch',
87
+ ]);
88
+
89
+ export function isConnectionOp(method: OpMessage['method']): boolean {
90
+ return CONNECTION_METHODS.has(method);
91
+ }
92
+
93
+ export async function handleConnectionOp(
94
+ ctx: HostCtx,
95
+ port: PortLike,
96
+ msg: OpMessage,
97
+ ): Promise<void> {
98
+ switch (msg.method) {
99
+ case 'getVersion': {
100
+ // The build hash is injected by the bundler (esbuild `define`). `typeof`
101
+ // guards the non-bundled path (tests import the compiled host directly,
102
+ // where the global is undefined) → reports 'dev'.
103
+ ok(port, msg.id, {
104
+ version: typeof __PYRIC_WORKER_VERSION__ !== 'undefined' ? __PYRIC_WORKER_VERSION__ : 'dev',
105
+ instanceId: ctx.instanceId,
106
+ });
107
+ break;
108
+ }
109
+
110
+ case 'exportState': {
111
+ // Phase 2 (transfer): serialize the FULL sandbox state to a portable bundle
112
+ // string using the SAME chunk format the persist layer uses, so wrapper
113
+ // types (Timestamp / Bytes / GeoPoint / VectorValue) round-trip. The string
114
+ // crosses the MessagePort cleanly (unlike the raw snapshot object).
115
+ const snap = ctx.sandbox.snapshot();
116
+ ok(port, msg.id, { bundle: bundleRecords(serializeToBuckets(snap.firestore, snap.services, 0)) });
117
+ break;
118
+ }
119
+
120
+ case 'importState': {
121
+ // Phase 2 (clobber): replace this sandbox's ENTIRE state with the imported
122
+ // bundle via the public loadSnapshot() (reset + rebuild firestore + restore
123
+ // services; listeners re-evaluate, persist re-flushes).
124
+ ctx.sandbox.loadSnapshot(deserializeFromBuckets(parseBundle(msg.bundle)));
125
+ ok(port, msg.id, { ok: true });
126
+ break;
127
+ }
128
+
129
+ case 'saveBranch': {
130
+ // Phase 3: snapshot the live sandbox into a named branch bundle (raw idb).
131
+ if (!ctx.sessionBackend) {
132
+ ok(port, msg.id, { ok: false, error: 'no persistence backend' });
133
+ break;
134
+ }
135
+ const snap = ctx.sandbox.snapshot();
136
+ const bundle = bundleRecords(serializeToBuckets(snap.firestore, snap.services, 0));
137
+ await ctx.sessionBackend.putRecords(BRANCH_PREFIX + msg.name, new Map([['bundle', { value: bundle }]]));
138
+ const names = await listBranchNames(ctx.sessionBackend);
139
+ if (!names.includes(msg.name)) await writeBranchRegistry(ctx.sessionBackend, [...names, msg.name]);
140
+ ok(port, msg.id, { ok: true });
141
+ break;
142
+ }
143
+
144
+ case 'listBranches': {
145
+ ok(port, msg.id, { branches: await listBranchNames(ctx.sessionBackend) });
146
+ break;
147
+ }
148
+
149
+ case 'switchBranch': {
150
+ // Phase 3: loadSnapshot the named branch bundle (a clobber).
151
+ const rec = ctx.sessionBackend
152
+ ? ((await ctx.sessionBackend.getRecord(BRANCH_PREFIX + msg.name, 'bundle')) as { value?: string } | undefined)
153
+ : undefined;
154
+ if (!rec?.value) {
155
+ ok(port, msg.id, { ok: false, error: `no such branch: ${msg.name}` });
156
+ break;
157
+ }
158
+ ctx.sandbox.loadSnapshot(deserializeFromBuckets(parseBundle(rec.value)));
159
+ ok(port, msg.id, { ok: true });
160
+ break;
161
+ }
162
+
163
+ case 'deleteBranch': {
164
+ if (ctx.sessionBackend) {
165
+ await ctx.sessionBackend.clear(BRANCH_PREFIX + msg.name);
166
+ await writeBranchRegistry(
167
+ ctx.sessionBackend,
168
+ (await listBranchNames(ctx.sessionBackend)).filter((n) => n !== msg.name),
169
+ );
170
+ }
171
+ ok(port, msg.id, { ok: true });
172
+ break;
173
+ }
174
+
175
+ default: {
176
+ fail(port, msg.id, new Error(`Unknown method: ${String((msg as { method: unknown }).method)}`));
177
+ }
178
+ }
179
+ }