@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,181 @@
1
+ /**
2
+ * Firestore read execution — document/collection reads, keyspace enumeration,
3
+ * count/aggregate reads, and the `onSnapshot` listener. Each RPCs the worker and
4
+ * rehydrates the reply into the modular SDK's snapshot shapes.
5
+ */
6
+
7
+ import type {
8
+ TargetDescriptor,
9
+ AggregateFieldDescriptor,
10
+ AggregateSpecDescriptor,
11
+ InboundMessage,
12
+ } from '../protocol.js';
13
+ import { closeSubscription, nextId, nextSubId, dataRpc, _defaultLens, openSnapshotSubscription, stampIssuer } from './core.js';
14
+ import type { ClientDb, DocRefHandle, CollRefHandle, QueryHandle, Unsubscribe } from './handles.js';
15
+ import { makeDocSnapshot, makeQuerySnapshot } from './snapshots.js';
16
+ import type { RawDocResult, RawQueryResult, ClientDocSnapshot, ClientQuerySnapshot } from './snapshots.js';
17
+
18
+ // ─── Execution functions (RPC) ────────────────────────────────────────────
19
+
20
+ export async function getDoc(ref: DocRefHandle): Promise<ClientDocSnapshot> {
21
+ const result = await dataRpc(ref.port, {
22
+ t: 'op',
23
+ id: nextId(),
24
+ method: 'getDoc',
25
+ path: ref.descriptor.path,
26
+ }) as RawDocResult;
27
+ return makeDocSnapshot(result, ref.port);
28
+ }
29
+
30
+ export async function getDocs(
31
+ source: CollRefHandle | QueryHandle,
32
+ ): Promise<ClientQuerySnapshot> {
33
+ const result = await dataRpc(source.port, {
34
+ t: 'op',
35
+ id: nextId(),
36
+ method: 'getDocs',
37
+ source: source.__kind === 'coll-ref'
38
+ ? (source as CollRefHandle).descriptor
39
+ : (source as QueryHandle).descriptor,
40
+ }) as RawQueryResult;
41
+ return makeQuerySnapshot(result, source.port);
42
+ }
43
+
44
+ /**
45
+ * Enumerate root collection ids (Pyric Studio data browse). The modular SDK has
46
+ * no client `listCollections`, so the host scans the sandbox keyspace and
47
+ * returns the ids. Lens is attached (via dataRpc) but the host enumeration is
48
+ * lens-independent.
49
+ */
50
+ export async function listRootCollections(db: ClientDb): Promise<string[]> {
51
+ const r = (await dataRpc(db.port, {
52
+ t: 'op',
53
+ id: nextId(),
54
+ method: 'listRootCollections',
55
+ })) as { ids: string[] };
56
+ return r.ids;
57
+ }
58
+
59
+ /** Enumerate subcollection ids under a document path (Pyric Studio data browse). */
60
+ export async function listSubcollections(db: ClientDb, docPath: string): Promise<string[]> {
61
+ const r = (await dataRpc(db.port, {
62
+ t: 'op',
63
+ id: nextId(),
64
+ method: 'listSubcollections',
65
+ docPath,
66
+ })) as { ids: string[] };
67
+ return r.ids;
68
+ }
69
+
70
+ export async function getCountFromServer(
71
+ source: CollRefHandle | QueryHandle,
72
+ ): Promise<{ data(): { count: number } }> {
73
+ const result = await dataRpc(source.port, {
74
+ t: 'op',
75
+ id: nextId(),
76
+ method: 'count',
77
+ source: source.__kind === 'coll-ref'
78
+ ? (source as CollRefHandle).descriptor
79
+ : (source as QueryHandle).descriptor,
80
+ }) as { count: number };
81
+ return { data: () => ({ count: result.count }) };
82
+ }
83
+
84
+ // ─── Multi-field aggregates (count / sum / average) ───────────────────────
85
+
86
+ /** Factory: count() aggregate field. Mirrors `pyric/firestore`'s `count()`. */
87
+ export function count(): AggregateFieldDescriptor {
88
+ return { kind: 'count' };
89
+ }
90
+
91
+ /** Factory: sum-of-`field` aggregate. Mirrors `pyric/firestore`'s `sum()`. */
92
+ export function sum(field: string): AggregateFieldDescriptor {
93
+ return { kind: 'sum', field };
94
+ }
95
+
96
+ /** Factory: average-of-`field` aggregate. Empty input yields `null`. */
97
+ export function average(field: string): AggregateFieldDescriptor {
98
+ return { kind: 'average', field };
99
+ }
100
+
101
+ /**
102
+ * Run a multi-field aggregate on the worker. Mirrors `pyric/firestore`'s
103
+ * `getAggregateFromServer(query, spec)`: spec entries are keyed by
104
+ * caller-chosen aliases; `.data()` returns the numbers under the same keys
105
+ * (`average` over no rows is `null`).
106
+ */
107
+ export async function getAggregateFromServer<S extends AggregateSpecDescriptor>(
108
+ source: CollRefHandle | QueryHandle,
109
+ spec: S,
110
+ ): Promise<{ data(): { [K in keyof S]: number | null } }> {
111
+ const result = await dataRpc(source.port, {
112
+ t: 'op',
113
+ id: nextId(),
114
+ method: 'aggregate',
115
+ source: source.__kind === 'coll-ref'
116
+ ? (source as CollRefHandle).descriptor
117
+ : (source as QueryHandle).descriptor,
118
+ spec,
119
+ }) as { data: { [K in keyof S]: number | null } };
120
+ return { data: () => result.data };
121
+ }
122
+
123
+ // ─── onSnapshot ──────────────────────────────────────────────────────────
124
+
125
+ /**
126
+ * Subscribe to a document or query. Mirrors `pyric/firestore`'s `onSnapshot`.
127
+ *
128
+ * Returns an `unsub` function. Sends `{ t:'unsub', subId }` to the worker
129
+ * to deregister the listener on the worker side.
130
+ */
131
+ export function onSnapshot(
132
+ target: DocRefHandle | CollRefHandle | QueryHandle,
133
+ callback: (snap: ClientDocSnapshot | ClientQuerySnapshot) => void,
134
+ errorCallback?: (err: unknown) => void,
135
+ ): Unsubscribe {
136
+ const subId = nextSubId();
137
+ const port = target.port;
138
+
139
+ const subscription = {
140
+ port,
141
+ service: 'firestore' as const,
142
+ next: (raw: unknown) => {
143
+ const r = raw as Record<string, unknown>;
144
+ if ('docs' in r) {
145
+ callback(makeQuerySnapshot(r as unknown as RawQueryResult, port));
146
+ } else {
147
+ callback(makeDocSnapshot(r as unknown as RawDocResult, port));
148
+ }
149
+ },
150
+ error: errorCallback,
151
+ };
152
+
153
+ const descriptor: TargetDescriptor =
154
+ target.__kind === 'doc-ref'
155
+ ? (target as DocRefHandle).descriptor
156
+ : target.__kind === 'coll-ref'
157
+ ? (target as CollRefHandle).descriptor
158
+ : (target as QueryHandle).descriptor;
159
+
160
+ // Stamp the active default lens onto the sub (Pyric Studio "watch as user")
161
+ // exactly as `dataRpc` does for ops, so a `setLens({mode:'as',uid})` choice
162
+ // makes listeners impersonate too. Omitted when no lens is set → byte-identical
163
+ // wire message, preserving the additive contract. The declared op source
164
+ // rides along the same way (client-constructed subs only — the relay's
165
+ // subs post verbatim).
166
+ const opened = openSnapshotSubscription(
167
+ port,
168
+ subId,
169
+ subscription,
170
+ stampIssuer(
171
+ (_defaultLens
172
+ ? { t: 'sub', subId, target: descriptor, actAs: _defaultLens }
173
+ : { t: 'sub', subId, target: descriptor }) satisfies InboundMessage,
174
+ ),
175
+ );
176
+ if (!opened && errorCallback) queueMicrotask(() => errorCallback(new Error('Firebase App was deleted')));
177
+
178
+ return () => {
179
+ closeSubscription(port, subId);
180
+ };
181
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Firestore reference, query, and sentinel factories — the synchronous,
3
+ * client-side descriptor builders. No RPC fires here: each returns a handle or
4
+ * marker carrying a serializable descriptor the execution functions send later.
5
+ */
6
+
7
+ import type {
8
+ DocRef,
9
+ CollRef,
10
+ GroupRef,
11
+ QueryDescriptor,
12
+ QueryConstraintDescriptor,
13
+ FilterConstraintDescriptor,
14
+ TargetDescriptor,
15
+ SentinelMarker,
16
+ } from '../protocol.js';
17
+ import { lastSegment } from './handles.js';
18
+ import type { ClientDb, DocRefHandle, CollRefHandle, QueryHandle } from './handles.js';
19
+
20
+ // ─── Path factories (client-side only — no RPC) ──────────────────────────
21
+
22
+ /**
23
+ * Build a document reference. Mirrors `pyric/firestore`'s `doc(db, path)`.
24
+ *
25
+ * WHY CLIENT-SIDE: Firebase's `doc()` is synchronous and path-only — it
26
+ * needs no data from the sandbox. We build a descriptor object here and
27
+ * include the port so execution calls can route to the worker.
28
+ */
29
+ export function doc(
30
+ parent: ClientDb | CollRefHandle,
31
+ ...pathSegments: string[]
32
+ ): DocRefHandle {
33
+ const port = 'port' in parent ? parent.port : (parent as ClientDb).port;
34
+ let path: string;
35
+
36
+ if (parent.__kind === 'client-db') {
37
+ if (pathSegments.length === 0) throw new TypeError('doc(db, path) requires a path segment.');
38
+ path = pathSegments.join('/');
39
+ } else {
40
+ // parent is a CollRefHandle
41
+ const collPath = (parent as CollRefHandle).descriptor.path;
42
+ path = pathSegments.length > 0
43
+ ? `${collPath}/${pathSegments.join('/')}`
44
+ : collPath; // caller will get auto-id via addDoc; doc() without id is unusual
45
+ }
46
+
47
+ const descriptor: DocRef = { __ref: 'doc', path };
48
+ return {
49
+ __kind: 'doc-ref',
50
+ descriptor,
51
+ port,
52
+ id: lastSegment(path),
53
+ path,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Build a collection reference. Mirrors `pyric/firestore`'s `collection(db, path)`.
59
+ */
60
+ export function collection(
61
+ parent: ClientDb | DocRefHandle,
62
+ ...pathSegments: string[]
63
+ ): CollRefHandle {
64
+ const port = parent.port;
65
+ if (pathSegments.length === 0) throw new TypeError('collection() requires a path segment.');
66
+
67
+ let path: string;
68
+ if (parent.__kind === 'client-db') {
69
+ path = pathSegments.join('/');
70
+ } else {
71
+ const docPath = (parent as DocRefHandle).descriptor.path;
72
+ path = `${docPath}/${pathSegments.join('/')}`;
73
+ }
74
+
75
+ const descriptor: CollRef = { __ref: 'collection', path };
76
+ return {
77
+ __kind: 'coll-ref',
78
+ descriptor,
79
+ port,
80
+ id: lastSegment(path),
81
+ path,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Build a collection-group query. Mirrors `pyric/firestore`'s `collectionGroup(db, id)`.
87
+ */
88
+ export function collectionGroup(db: ClientDb, collectionId: string): QueryHandle {
89
+ const descriptor: QueryDescriptor = {
90
+ __ref: 'query',
91
+ source: { __ref: 'group', collectionId },
92
+ constraints: [],
93
+ };
94
+ return { __kind: 'query', descriptor, port: db.port };
95
+ }
96
+
97
+ // ─── Query constraint factories (client-side) ─────────────────────────────
98
+
99
+ /** Opaque query constraint — carries its descriptor for embedding in queries. */
100
+ export interface QueryConstraintHandle {
101
+ readonly _descriptor: QueryConstraintDescriptor;
102
+ }
103
+
104
+ export function where(field: string, op: string, value: unknown): QueryConstraintHandle {
105
+ return { _descriptor: { kind: 'where', field, op, value } };
106
+ }
107
+
108
+ /**
109
+ * Extract a constraint's FILTER descriptor for composite embedding — throws
110
+ * the same TypeError `pyric/firestore`'s `and()`/`or()` raise when handed a
111
+ * non-filter (`orderBy` / `limit` / cursors are not valid inside composites).
112
+ */
113
+ function toFilterDescriptor(
114
+ kind: 'and' | 'or',
115
+ c: QueryConstraintHandle,
116
+ ): FilterConstraintDescriptor {
117
+ const d = c._descriptor;
118
+ if (d.kind !== 'where' && d.kind !== 'and' && d.kind !== 'or') {
119
+ throw new TypeError(
120
+ `pyric worker client: ${kind}() received a non-filter constraint (orderBy / limit are not valid here).`,
121
+ );
122
+ }
123
+ return d;
124
+ }
125
+
126
+ function composite(kind: 'and' | 'or', filters: QueryConstraintHandle[]): QueryConstraintHandle {
127
+ if (filters.length === 0) {
128
+ throw new TypeError(`pyric worker client: ${kind}() requires at least one filter argument.`);
129
+ }
130
+ return { _descriptor: { kind, filters: filters.map((f) => toFilterDescriptor(kind, f)) } };
131
+ }
132
+
133
+ /**
134
+ * OR composite filter — at least one operand must match. Operands must be
135
+ * filters (`where()`, or nested `or()`/`and()`). Mirrors `pyric/firestore`'s
136
+ * `or(...)`; the worker rebuilds it with the real modular factory.
137
+ */
138
+ export function or(...filters: QueryConstraintHandle[]): QueryConstraintHandle {
139
+ return composite('or', filters);
140
+ }
141
+
142
+ /** AND composite filter — every operand must match. See {@link or}. */
143
+ export function and(...filters: QueryConstraintHandle[]): QueryConstraintHandle {
144
+ return composite('and', filters);
145
+ }
146
+
147
+ export function orderBy(field: string, direction?: 'asc' | 'desc'): QueryConstraintHandle {
148
+ return { _descriptor: { kind: 'orderBy', field, direction } };
149
+ }
150
+
151
+ export function limit(n: number): QueryConstraintHandle {
152
+ return { _descriptor: { kind: 'limit', n } };
153
+ }
154
+
155
+ export function limitToLast(n: number): QueryConstraintHandle {
156
+ return { _descriptor: { kind: 'limitToLast', n } };
157
+ }
158
+
159
+ export function startAt(...values: unknown[]): QueryConstraintHandle {
160
+ return { _descriptor: { kind: 'startAt', values } };
161
+ }
162
+
163
+ export function startAfter(...values: unknown[]): QueryConstraintHandle {
164
+ return { _descriptor: { kind: 'startAfter', values } };
165
+ }
166
+
167
+ export function endAt(...values: unknown[]): QueryConstraintHandle {
168
+ return { _descriptor: { kind: 'endAt', values } };
169
+ }
170
+
171
+ export function endBefore(...values: unknown[]): QueryConstraintHandle {
172
+ return { _descriptor: { kind: 'endBefore', values } };
173
+ }
174
+
175
+ /**
176
+ * Apply query constraints to a source ref or query.
177
+ * Mirrors `pyric/firestore`'s `query(source, ...constraints)`.
178
+ */
179
+ export function query(
180
+ source: CollRefHandle | QueryHandle,
181
+ ...constraints: QueryConstraintHandle[]
182
+ ): QueryHandle {
183
+ const sourceDescriptor: TargetDescriptor =
184
+ source.__kind === 'coll-ref'
185
+ ? (source as CollRefHandle).descriptor
186
+ : (source as QueryHandle).descriptor;
187
+
188
+ const existingConstraints: readonly QueryConstraintDescriptor[] =
189
+ source.__kind === 'query'
190
+ ? (source as QueryHandle).descriptor.constraints
191
+ : [];
192
+
193
+ const descriptor: QueryDescriptor = {
194
+ __ref: 'query',
195
+ source: sourceDescriptor.__ref === 'query'
196
+ ? (sourceDescriptor as QueryDescriptor).source
197
+ : (sourceDescriptor as DocRef | CollRef | GroupRef),
198
+ constraints: [
199
+ ...existingConstraints,
200
+ ...constraints.map((c) => c._descriptor),
201
+ ],
202
+ };
203
+ return { __kind: 'query', descriptor, port: source.port };
204
+ }
205
+
206
+ // ─── Sentinel factories (client-side markers) ────────────────────────────
207
+
208
+ export function serverTimestamp(): SentinelMarker {
209
+ return { __sentinel: 'serverTimestamp' };
210
+ }
211
+
212
+ export function increment(n: number): SentinelMarker {
213
+ return { __sentinel: 'increment', n };
214
+ }
215
+
216
+ export function arrayUnion(...values: unknown[]): SentinelMarker {
217
+ return { __sentinel: 'arrayUnion', values };
218
+ }
219
+
220
+ export function arrayRemove(...values: unknown[]): SentinelMarker {
221
+ return { __sentinel: 'arrayRemove', values };
222
+ }
223
+
224
+ export function deleteField(): SentinelMarker {
225
+ return { __sentinel: 'deleteField' };
226
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Firestore write execution — single-document writes, `writeBatch`, and
3
+ * `runTransaction` with read-set validation + retry for multi-tab correctness.
4
+ */
5
+
6
+ import type { WriteDescriptor, TxnReadEntry } from '../protocol.js';
7
+ import { nextId, dataRpc } from './core.js';
8
+ import type { ClientDb, DocRefHandle, CollRefHandle } from './handles.js';
9
+ import { makeDocSnapshot } from './snapshots.js';
10
+ import type { RawDocResult, ClientDocSnapshot } from './snapshots.js';
11
+
12
+ export async function setDoc(
13
+ ref: DocRefHandle,
14
+ data: Record<string, unknown>,
15
+ options?: { merge?: boolean; mergeFields?: string[] },
16
+ ): Promise<void> {
17
+ await dataRpc(ref.port, {
18
+ t: 'op',
19
+ id: nextId(),
20
+ method: 'setDoc',
21
+ path: ref.descriptor.path,
22
+ data,
23
+ options,
24
+ });
25
+ }
26
+
27
+ export async function updateDoc(
28
+ ref: DocRefHandle,
29
+ data: Record<string, unknown>,
30
+ ): Promise<void> {
31
+ await dataRpc(ref.port, {
32
+ t: 'op',
33
+ id: nextId(),
34
+ method: 'updateDoc',
35
+ path: ref.descriptor.path,
36
+ data,
37
+ });
38
+ }
39
+
40
+ export async function deleteDoc(ref: DocRefHandle): Promise<void> {
41
+ await dataRpc(ref.port, {
42
+ t: 'op',
43
+ id: nextId(),
44
+ method: 'deleteDoc',
45
+ path: ref.descriptor.path,
46
+ });
47
+ }
48
+
49
+ export async function addDoc(
50
+ coll: CollRefHandle,
51
+ data: Record<string, unknown>,
52
+ ): Promise<DocRefHandle> {
53
+ const result = await dataRpc(coll.port, {
54
+ t: 'op',
55
+ id: nextId(),
56
+ method: 'addDoc',
57
+ collectionPath: coll.descriptor.path,
58
+ data,
59
+ }) as { id: string; path: string };
60
+
61
+ return {
62
+ __kind: 'doc-ref',
63
+ descriptor: { __ref: 'doc', path: result.path },
64
+ port: coll.port,
65
+ id: result.id,
66
+ path: result.path,
67
+ };
68
+ }
69
+
70
+ // ─── writeBatch ──────────────────────────────────────────────────────────
71
+
72
+ /**
73
+ * Client-side write batch. Buffers `set`/`update`/`delete` calls and
74
+ * sends them all to the worker on `.commit()`.
75
+ *
76
+ * Mirrors `pyric/firestore`'s `writeBatch(db)` shape:
77
+ * const batch = writeBatch(db);
78
+ * batch.set(ref, { ... });
79
+ * batch.delete(ref2);
80
+ * await batch.commit();
81
+ */
82
+ export interface ClientWriteBatch {
83
+ set(ref: DocRefHandle, data: Record<string, unknown>, options?: { merge?: boolean; mergeFields?: string[] }): ClientWriteBatch;
84
+ update(ref: DocRefHandle, data: Record<string, unknown>): ClientWriteBatch;
85
+ delete(ref: DocRefHandle): ClientWriteBatch;
86
+ commit(): Promise<void>;
87
+ }
88
+
89
+ export function writeBatch(db: ClientDb): ClientWriteBatch {
90
+ const writes: WriteDescriptor[] = [];
91
+ const port = db.port;
92
+
93
+ const batch: ClientWriteBatch = {
94
+ set(ref, data, options) {
95
+ writes.push({ method: 'set', path: ref.descriptor.path, data, options });
96
+ return batch;
97
+ },
98
+ update(ref, data) {
99
+ writes.push({ method: 'update', path: ref.descriptor.path, data });
100
+ return batch;
101
+ },
102
+ delete(ref) {
103
+ writes.push({ method: 'delete', path: ref.descriptor.path });
104
+ return batch;
105
+ },
106
+ async commit() {
107
+ await dataRpc(port, {
108
+ t: 'op',
109
+ id: nextId(),
110
+ method: 'batchCommit',
111
+ writes: [...writes],
112
+ });
113
+ },
114
+ };
115
+ return batch;
116
+ }
117
+
118
+ // ─── runTransaction ──────────────────────────────────────────────────────
119
+
120
+ /** Client-side transaction handle. */
121
+ export interface ClientTransaction {
122
+ get(ref: DocRefHandle): Promise<ClientDocSnapshot>;
123
+ set(ref: DocRefHandle, data: Record<string, unknown>, options?: { merge?: boolean; mergeFields?: string[] }): void;
124
+ update(ref: DocRefHandle, data: Record<string, unknown>): void;
125
+ delete(ref: DocRefHandle): void;
126
+ }
127
+
128
+ /**
129
+ * Maximum number of times the client will retry `updateFn` on a
130
+ * worker-reported conflict (`aborted`). Matches Firestore SDK default.
131
+ */
132
+ const TXN_MAX_ATTEMPTS = 5;
133
+
134
+ /**
135
+ * Run a transaction. Mirrors `pyric/firestore`'s `runTransaction(db, fn)`.
136
+ *
137
+ * MULTI-TAB CORRECTNESS — READ-SET VALIDATION + RETRY
138
+ * ----------------------------------------------------
139
+ * A transaction spans two messages: the `txn.get` RPC (read) and the
140
+ * `txnCommit` RPC (commit). Between those two messages another tab may
141
+ * write to a doc the current tab read — a silent lost update without
142
+ * validation. We fix this the standard way:
143
+ *
144
+ * 1. Each `txn.get(ref)` records `{ path, data }` in a per-attempt
145
+ * read-set (`data` is the raw `SerializedDocData` the worker
146
+ * returned, or `null` if the doc was missing).
147
+ * 2. `txnCommit` carries both `reads` (the read-set) and `writes`.
148
+ * 3. The worker re-reads each doc inside a sandbox transaction, re-
149
+ * serializes it the same way, and compares the JSON strings.
150
+ * Any mismatch → `{ ok: false, error: { code: 'aborted' } }`.
151
+ * 4. On `aborted`, the client discards the result of `updateFn` and
152
+ * re-runs it with a fresh transaction object (fresh reads, empty
153
+ * write buffer). Up to `TXN_MAX_ATTEMPTS` attempts are made.
154
+ * 5. After the cap, throws an error with `.code === 'aborted'`.
155
+ *
156
+ * This matches real Firestore's behaviour: the SDK retries `updateFn`
157
+ * on conflict rather than surfacing the error immediately.
158
+ */
159
+ export async function runTransaction<R>(
160
+ db: ClientDb,
161
+ updateFn: (txn: ClientTransaction) => Promise<R> | R,
162
+ ): Promise<R> {
163
+ const port = db.port;
164
+
165
+ for (let attempt = 0; attempt < TXN_MAX_ATTEMPTS; attempt++) {
166
+ // Fresh read-set and write buffer for each attempt.
167
+ const reads: TxnReadEntry[] = [];
168
+ const writes: WriteDescriptor[] = [];
169
+
170
+ const txn: ClientTransaction = {
171
+ async get(ref) {
172
+ // RPC to the worker — capture the raw result before rehydration.
173
+ const rawResult = await dataRpc(ref.port, {
174
+ t: 'op',
175
+ id: nextId(),
176
+ method: 'getDoc',
177
+ path: ref.descriptor.path,
178
+ activity: { groupKind: 'transaction' },
179
+ }) as RawDocResult;
180
+
181
+ // Record the raw serialized data (or null) in the read-set.
182
+ // We preserve the wire-form SerializedDocData so the worker can
183
+ // re-serialize the current doc and compare JSON strings.
184
+ reads.push({
185
+ path: ref.descriptor.path,
186
+ data: (rawResult.exists && rawResult.data) ? rawResult.data : null,
187
+ });
188
+
189
+ return makeDocSnapshot(rawResult, ref.port);
190
+ },
191
+ set(ref, data, options) {
192
+ writes.push({ method: 'set', path: ref.descriptor.path, data, options });
193
+ },
194
+ update(ref, data) {
195
+ writes.push({ method: 'update', path: ref.descriptor.path, data });
196
+ },
197
+ delete(ref) {
198
+ writes.push({ method: 'delete', path: ref.descriptor.path });
199
+ },
200
+ };
201
+
202
+ const result = await updateFn(txn);
203
+
204
+ // Send read-set + writes to the worker for validation and commit.
205
+ try {
206
+ await dataRpc(port, {
207
+ t: 'op',
208
+ id: nextId(),
209
+ method: 'txnCommit',
210
+ reads: [...reads],
211
+ writes: [...writes],
212
+ });
213
+ return result;
214
+ } catch (err) {
215
+ const e = err as { code?: string };
216
+ if (e.code === 'aborted') {
217
+ // Conflict detected — retry updateFn on the next attempt.
218
+ continue;
219
+ }
220
+ // Permission-denied, not-found, etc. — propagate immediately.
221
+ throw err;
222
+ }
223
+ }
224
+
225
+ // Exceeded max attempts.
226
+ const abortErr = new Error(
227
+ `Transaction failed after ${TXN_MAX_ATTEMPTS} attempts due to repeated conflicts. ` +
228
+ 'Another tab is concurrently writing to the same documents.',
229
+ ) as Error & { code: string };
230
+ abortErr.code = 'aborted';
231
+ throw abortErr;
232
+ }