@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.12

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 (301) hide show
  1. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  2. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  4. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  5. package/dist/conformance/.generated/can-i-use.js +2 -2
  6. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  7. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  8. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  9. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  10. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  11. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  12. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  13. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  14. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  19. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  20. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  23. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  24. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/index.json +200 -25
  30. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  35. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  36. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  37. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  38. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  39. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  40. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  41. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  42. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  50. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  51. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +131 -66
  57. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +130 -65
  58. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  68. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  69. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  71. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  72. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  80. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  81. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  99. package/dist/serve/docs-ui/llms.txt +1 -1
  100. package/dist/serve/worker/host/core.js +1 -1
  101. package/dist/serve/worker/host/core.js.map +1 -1
  102. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  103. package/dist/serve/worker/host/firestore-writes.js +3 -2
  104. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  105. package/dist/serve/worker/index.d.ts +2 -1
  106. package/dist/serve/worker/index.d.ts.map +1 -1
  107. package/dist/serve/worker/index.js +2 -1
  108. package/dist/serve/worker/index.js.map +1 -1
  109. package/dist/serve/worker/protocol.js +2 -2
  110. package/dist/serve/worker/protocol.js.map +1 -1
  111. package/package.json +5 -4
  112. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  113. package/src/assurance/attachment.ts +211 -0
  114. package/src/assurance/browser.ts +80 -0
  115. package/src/assurance/campaign.ts +496 -0
  116. package/src/assurance/capabilities.ts +446 -0
  117. package/src/assurance/cases.ts +43 -0
  118. package/src/assurance/index.ts +75 -0
  119. package/src/assurance/runner.ts +885 -0
  120. package/src/assurance/tool-names.ts +15 -0
  121. package/src/assurance/tools.ts +830 -0
  122. package/src/assurance/types.ts +352 -0
  123. package/src/assurance/validation.ts +434 -0
  124. package/src/bridge/client/bridge.ts +550 -0
  125. package/src/bridge/client/dispatch.ts +137 -0
  126. package/src/bridge/client.ts +45 -0
  127. package/src/bridge/protocol.ts +351 -0
  128. package/src/bridge/server/audit.ts +53 -0
  129. package/src/bridge/server/bridge.ts +597 -0
  130. package/src/bridge/server/headless.ts +160 -0
  131. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  132. package/src/bridge/server/local-bridge.ts +66 -0
  133. package/src/bridge/server/logger.ts +50 -0
  134. package/src/bridge/server/mcp-contract.ts +93 -0
  135. package/src/bridge/server/mcp.ts +129 -0
  136. package/src/bridge/server/peer.ts +232 -0
  137. package/src/bridge/server/standalone.ts +347 -0
  138. package/src/bridge/server/tool-metadata.ts +99 -0
  139. package/src/bridge/server.ts +28 -0
  140. package/src/cli/can-i-use.ts +50 -0
  141. package/src/cli/cli.test.ts +493 -0
  142. package/src/cli/database-rules.ts +329 -0
  143. package/src/cli/dev-runner.ts +282 -0
  144. package/src/cli/firebase-json.ts +100 -0
  145. package/src/cli/firestore-indexes.ts +70 -0
  146. package/src/cli/index.ts +425 -0
  147. package/src/cli/init.ts +260 -0
  148. package/src/cli/mcp-proxy.ts +196 -0
  149. package/src/cli/parse-args.ts +80 -0
  150. package/src/cli/rules.ts +259 -0
  151. package/src/cli/scope.ts +67 -0
  152. package/src/cli/serve.ts +996 -0
  153. package/src/cli/service-commands.ts +73 -0
  154. package/src/cli/snapshot.ts +164 -0
  155. package/src/cli/storage-rules.ts +172 -0
  156. package/src/cli/verify.ts +416 -0
  157. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  158. package/src/conformance/.generated/can-i-use.ts +117 -0
  159. package/src/conformance/.generated/conformance-docs.ts +14 -0
  160. package/src/conformance/browser.ts +24 -0
  161. package/src/conformance/can-i-use-tool.ts +51 -0
  162. package/src/conformance/can-i-use.ts +18 -0
  163. package/src/conformance/docs.ts +8 -0
  164. package/src/conformance/index.ts +20 -0
  165. package/src/conformance/tools.ts +16 -0
  166. package/src/credentials/core/memoize-ttl.ts +142 -0
  167. package/src/credentials/core/types.ts +10 -0
  168. package/src/credentials/node/from-adc.ts +96 -0
  169. package/src/credentials/node/from-service-account.ts +133 -0
  170. package/src/credentials/node/index.ts +6 -0
  171. package/src/discover/concurrency.ts +127 -0
  172. package/src/discover/crawler-adapter.ts +142 -0
  173. package/src/discover/crawler.ts +1127 -0
  174. package/src/discover/credential-free.ts +27 -0
  175. package/src/discover/findCollectionGroup.ts +131 -0
  176. package/src/discover/firestore-source.ts +59 -0
  177. package/src/discover/index.ts +5 -0
  178. package/src/discover/merge.ts +523 -0
  179. package/src/discover/session.ts +402 -0
  180. package/src/discover/tools.ts +201 -0
  181. package/src/discover/types.ts +187 -0
  182. package/src/discover/wire.ts +324 -0
  183. package/src/functions-rtdb/child.ts +382 -0
  184. package/src/functions-rtdb/delivery.ts +7 -0
  185. package/src/functions-rtdb/discovery.ts +136 -0
  186. package/src/functions-rtdb/event.ts +64 -0
  187. package/src/functions-rtdb/execution.ts +111 -0
  188. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  189. package/src/functions-rtdb/project.ts +74 -0
  190. package/src/functions-rtdb/projection.ts +104 -0
  191. package/src/functions-rtdb/reference-pattern.ts +20 -0
  192. package/src/functions-rtdb/remote-delivery.ts +23 -0
  193. package/src/pkg-version.ts +49 -0
  194. package/src/register/esm-exports.ts +63 -0
  195. package/src/register/hooks.ts +30 -0
  196. package/src/register/index.ts +150 -0
  197. package/src/register/mapping.ts +33 -0
  198. package/src/remote/index.ts +1050 -0
  199. package/src/rtdb/crawl-snapshot.ts +118 -0
  200. package/src/rtdb/inspection.ts +137 -0
  201. package/src/rtdb/load-rules-document.ts +56 -0
  202. package/src/rtdb/rules-generation-tool.ts +38 -0
  203. package/src/rtdb/rules-json.ts +29 -0
  204. package/src/serve/activity-guard.ts +29 -0
  205. package/src/serve/activity-route.ts +164 -0
  206. package/src/serve/activity-warning.ts +26 -0
  207. package/src/serve/bridge-mount.ts +200 -0
  208. package/src/serve/bundler.ts +493 -0
  209. package/src/serve/capture-store.ts +65 -0
  210. package/src/serve/discovery.ts +199 -0
  211. package/src/serve/entries/ai.ts +292 -0
  212. package/src/serve/entries/app-backend.ts +4 -0
  213. package/src/serve/entries/app-client.ts +26 -0
  214. package/src/serve/entries/app-session-store.ts +47 -0
  215. package/src/serve/entries/app.ts +11 -0
  216. package/src/serve/entries/auth-helper-core.ts +181 -0
  217. package/src/serve/entries/auth-helper-dom.ts +134 -0
  218. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  219. package/src/serve/entries/auth.ts +255 -0
  220. package/src/serve/entries/bridge-url.ts +31 -0
  221. package/src/serve/entries/database.ts +129 -0
  222. package/src/serve/entries/firestore.ts +232 -0
  223. package/src/serve/entries/init.ts +46 -0
  224. package/src/serve/entries/keepalive.ts +46 -0
  225. package/src/serve/entries/messaging-sw.ts +75 -0
  226. package/src/serve/entries/messaging.ts +96 -0
  227. package/src/serve/entries/runtime.ts +540 -0
  228. package/src/serve/entries/session-store.ts +97 -0
  229. package/src/serve/entries/storage.ts +92 -0
  230. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  231. package/src/serve/entries/worker-runtime.ts +75 -0
  232. package/src/serve/init-payload.ts +42 -0
  233. package/src/serve/namespace.ts +675 -0
  234. package/src/serve/open-browser.ts +68 -0
  235. package/src/serve/rules.ts +272 -0
  236. package/src/serve/sandbox-marker.ts +31 -0
  237. package/src/serve/server.ts +428 -0
  238. package/src/serve/standalone-assets.ts +180 -0
  239. package/src/serve/state-store.ts +166 -0
  240. package/src/serve/studio/disk-project-store.ts +185 -0
  241. package/src/serve/studio/disk-workspace.ts +162 -0
  242. package/src/serve/studio/index.ts +20 -0
  243. package/src/serve/studio/routes.ts +237 -0
  244. package/src/serve/studio/store-types.ts +48 -0
  245. package/src/serve/studio/studio-storage.test.ts +289 -0
  246. package/src/serve/vite-plugin.ts +1102 -0
  247. package/src/serve/worker/activity-bootstrap.ts +24 -0
  248. package/src/serve/worker/client/admin-firestore.ts +38 -0
  249. package/src/serve/worker/client/ai.ts +124 -0
  250. package/src/serve/worker/client/auth.ts +489 -0
  251. package/src/serve/worker/client/connection.ts +202 -0
  252. package/src/serve/worker/client/core.ts +350 -0
  253. package/src/serve/worker/client/disconnect.ts +45 -0
  254. package/src/serve/worker/client/firestore-reads.ts +181 -0
  255. package/src/serve/worker/client/firestore-refs.ts +226 -0
  256. package/src/serve/worker/client/firestore-writes.ts +232 -0
  257. package/src/serve/worker/client/handles.ts +91 -0
  258. package/src/serve/worker/client/messaging.ts +119 -0
  259. package/src/serve/worker/client/presence.ts +174 -0
  260. package/src/serve/worker/client/rtdb.ts +311 -0
  261. package/src/serve/worker/client/rules.ts +63 -0
  262. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  263. package/src/serve/worker/client/snapshots.ts +86 -0
  264. package/src/serve/worker/client/storage.ts +195 -0
  265. package/src/serve/worker/client/studio.ts +88 -0
  266. package/src/serve/worker/client.ts +57 -0
  267. package/src/serve/worker/durable-persistence.ts +137 -0
  268. package/src/serve/worker/entry.ts +179 -0
  269. package/src/serve/worker/host/admin-firestore.ts +88 -0
  270. package/src/serve/worker/host/connection.ts +179 -0
  271. package/src/serve/worker/host/core.ts +387 -0
  272. package/src/serve/worker/host/dispatch.ts +316 -0
  273. package/src/serve/worker/host/firestore-reads.ts +117 -0
  274. package/src/serve/worker/host/firestore-writes.ts +456 -0
  275. package/src/serve/worker/host/presence.ts +312 -0
  276. package/src/serve/worker/host/rtdb.ts +136 -0
  277. package/src/serve/worker/host/rules.ts +128 -0
  278. package/src/serve/worker/host/storage.ts +312 -0
  279. package/src/serve/worker/host/studio.ts +75 -0
  280. package/src/serve/worker/host/subscriptions.ts +238 -0
  281. package/src/serve/worker/host-ai.ts +165 -0
  282. package/src/serve/worker/host-auth.ts +469 -0
  283. package/src/serve/worker/host-context.ts +273 -0
  284. package/src/serve/worker/host-events.ts +94 -0
  285. package/src/serve/worker/host-messaging.ts +240 -0
  286. package/src/serve/worker/host.ts +47 -0
  287. package/src/serve/worker/index.ts +196 -0
  288. package/src/serve/worker/presence-timing.ts +15 -0
  289. package/src/serve/worker/protocol.ts +1112 -0
  290. package/src/serve/worker/serve-init.ts +606 -0
  291. package/src/serve/worker/service-worker-channel.ts +33 -0
  292. package/src/serve/worker/service-worker-relay.ts +86 -0
  293. package/src/serve/writer-lock.ts +53 -0
  294. package/src/verify/cases.ts +233 -0
  295. package/src/verify/fixture.ts +258 -0
  296. package/src/verify/index.ts +519 -0
  297. package/src/verify/tools.ts +108 -0
  298. package/src/version/compat-target.ts +17 -0
  299. package/src/vite.ts +17 -0
  300. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  301. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,179 @@
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
+ }
52
+
53
+ import { createIndexedDBBackend } from 'pyric/sandbox';
54
+
55
+ import {
56
+ handleMessage,
57
+ cleanupPort,
58
+ type HostCtx,
59
+ type PortLike,
60
+ } from './host.js';
61
+ import { buildWorkerCtx, type EventSourceLike } from './serve-init.js';
62
+ import type { InboundMessage } from './protocol.js';
63
+ import {
64
+ SERVICE_WORKER_CHANNEL,
65
+ type ServiceWorkerChannelMessage,
66
+ } from './service-worker-channel.js';
67
+ import { createServiceWorkerRelay } from './service-worker-relay.js';
68
+
69
+ // ─── Singleton context ────────────────────────────────────────────────────
70
+
71
+ // `_ctx` is the RESOLVED context, kept for the synchronous `close` handler.
72
+ // The build is memoized as a PROMISE (`_ctxPromise`) — NOT as the resolved
73
+ // value — because init is async (it awaits `/__pyric/init.json` + persistence
74
+ // restore). The first messages arrive in a BURST (the authState sub, the first
75
+ // firestore sub, an op — possibly across two tabs at once); if each re-checked
76
+ // only the resolved `_ctx` they would every one start a fresh init and build a
77
+ // SEPARATE sandbox, so a listener and a write could bind to different
78
+ // instances and never see each other. The promise memo guarantees ONE init.
79
+ let _ctx: HostCtx | null = null;
80
+ let _ctxPromise: Promise<HostCtx> | null = null;
81
+
82
+ /**
83
+ * Return the shared context, memoizing the in-flight init promise so
84
+ * concurrent first messages await ONE build (see `_ctxPromise` above).
85
+ */
86
+ function getCtx(): Promise<HostCtx> {
87
+ return (_ctxPromise ??= buildCtx());
88
+ }
89
+
90
+ /**
91
+ * Build the ONE shared sandbox + Firestore handle. Invoked exactly once per
92
+ * worker lifetime via the `getCtx` memo — never concurrently. The real boot
93
+ * logic lives in `buildWorkerCtx` (serve-init.ts) so it is testable without
94
+ * a SharedWorker runtime; this shell only supplies the real ambients.
95
+ *
96
+ * NOTE (#754): no worker-side session restore. Sessions are per-port; each
97
+ * PAGE re-establishes its own session from web storage via
98
+ * `auth.restorePortSession` (runtime.ts). The user DB itself is restored
99
+ * from the persisted snapshot inside buildWorkerCtx (#629).
100
+ */
101
+ async function buildCtx(): Promise<HostCtx> {
102
+ // ONE IDB backend for the whole worker. createIndexedDBBackend's guard
103
+ // checks `typeof indexedDB === 'undefined'` — SharedWorkers have full
104
+ // IndexedDB access, so this succeeds. The persistence controller's
105
+ // window.addEventListener('beforeunload', ...) is guarded by
106
+ // `typeof window !== 'undefined'`, so enablePersistence is worker-safe.
107
+ const ctx = await buildWorkerCtx({
108
+ fetch,
109
+ idb: createIndexedDBBackend(),
110
+ // EventSource is available in workers; guard anyway for exotic hosts.
111
+ makeEventSource:
112
+ typeof EventSource !== 'undefined'
113
+ ? (url) => new EventSource(url) as unknown as EventSourceLike
114
+ : null,
115
+ });
116
+ _ctx = ctx; // publish the resolved ctx for the synchronous close handler
117
+ return ctx;
118
+ }
119
+
120
+ // ─── SharedWorker connect handler ─────────────────────────────────────────
121
+
122
+ /**
123
+ * `self` in a SharedWorker is `SharedWorkerGlobalScope`, not `Window`.
124
+ * Each tab that opens the SharedWorker fires one `connect` event.
125
+ * Each tab gets its own `MessagePort`; we start() it (required for
126
+ * classic-mode workers to un-pause the message queue) and wire onmessage.
127
+ */
128
+ (self as unknown as SharedWorkerGlobalScope).onconnect = (e: MessageEvent) => {
129
+ const port = e.ports[0];
130
+ port.start();
131
+
132
+ let messageQueue = Promise.resolve();
133
+ port.onmessage = (ev: MessageEvent<InboundMessage>) => {
134
+ // Serialize each port's frames. A disconnect acknowledgement therefore
135
+ // cannot overtake an already-posted mutation, and later frames see the
136
+ // disconnected-port tombstone instead of touching the shared backend.
137
+ messageQueue = messageQueue.then(async () => {
138
+ try {
139
+ const ctx = await getCtx();
140
+ await handleMessage(ctx, port as unknown as PortLike, ev.data);
141
+ } catch (e) {
142
+ // eslint-disable-next-line no-console
143
+ console.error('[pyric worker] message handler error:', (e as Error)?.stack ?? e, 'msg:', ev.data);
144
+ }
145
+ });
146
+ };
147
+
148
+ // Best-effort port cleanup on tab close/navigation.
149
+ // Chrome doesn't reliably fire 'close' on MessagePort — handled as
150
+ // best-effort; subscriptions also GC when the worker itself dies.
151
+ port.addEventListener('close', () => {
152
+ if (_ctx) cleanupPort(_ctx, port as unknown as PortLike);
153
+ });
154
+ };
155
+
156
+ // ServiceWorkerGlobalScope cannot construct a SharedWorker, but both worker
157
+ // kinds share BroadcastChannel. Adapt each SW app connection to the host's
158
+ // existing PortLike seam so it reaches this exact context/broker instead of
159
+ // constructing a second in-Service-Worker sandbox.
160
+ if (typeof BroadcastChannel !== 'undefined') {
161
+ const channel = new BroadcastChannel(SERVICE_WORKER_CHANNEL);
162
+ const relay = createServiceWorkerRelay({
163
+ getCtx,
164
+ send(message) { channel.postMessage(message); },
165
+ onError(error, envelope) {
166
+ console.error(
167
+ '[pyric worker] service-worker relay error:',
168
+ (error as Error)?.stack ?? error,
169
+ 'msg:',
170
+ envelope,
171
+ );
172
+ },
173
+ });
174
+ channel.onmessage = (event: MessageEvent<ServiceWorkerChannelMessage>) => {
175
+ const envelope = event.data;
176
+ if (envelope.direction !== 'host') return;
177
+ void relay.handle(envelope);
178
+ };
179
+ }
@@ -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
+ }