pyric 0.0.1 → 0.1.0-alpha.7

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 (786) hide show
  1. package/README.md +52 -1
  2. package/dist/app/index.d.ts +81 -0
  3. package/dist/app/index.d.ts.map +1 -0
  4. package/dist/app/index.js +78 -0
  5. package/dist/app/index.js.map +1 -0
  6. package/dist/auth/index.d.ts +346 -0
  7. package/dist/auth/index.d.ts.map +1 -0
  8. package/dist/auth/index.js +701 -0
  9. package/dist/auth/index.js.map +1 -0
  10. package/dist/auth/prod-backend.d.ts +39 -0
  11. package/dist/auth/prod-backend.d.ts.map +1 -0
  12. package/dist/auth/prod-backend.js +179 -0
  13. package/dist/auth/prod-backend.js.map +1 -0
  14. package/dist/auth/providers.d.ts +107 -0
  15. package/dist/auth/providers.d.ts.map +1 -0
  16. package/dist/auth/providers.js +138 -0
  17. package/dist/auth/providers.js.map +1 -0
  18. package/dist/auth/sandbox-backend.d.ts +769 -0
  19. package/dist/auth/sandbox-backend.d.ts.map +1 -0
  20. package/dist/auth/sandbox-backend.js +1598 -0
  21. package/dist/auth/sandbox-backend.js.map +1 -0
  22. package/dist/auth/target.d.ts +40 -0
  23. package/dist/auth/target.d.ts.map +1 -0
  24. package/dist/auth/target.js +28 -0
  25. package/dist/auth/target.js.map +1 -0
  26. package/dist/auth/types.d.ts +252 -0
  27. package/dist/auth/types.d.ts.map +1 -0
  28. package/dist/auth/types.js +26 -0
  29. package/dist/auth/types.js.map +1 -0
  30. package/dist/database/constraints/atoms.d.ts +29 -0
  31. package/dist/database/constraints/atoms.d.ts.map +1 -0
  32. package/dist/database/constraints/atoms.js +43 -0
  33. package/dist/database/constraints/atoms.js.map +1 -0
  34. package/dist/database/constraints/compose.d.ts +16 -0
  35. package/dist/database/constraints/compose.d.ts.map +1 -0
  36. package/dist/database/constraints/compose.js +15 -0
  37. package/dist/database/constraints/compose.js.map +1 -0
  38. package/dist/database/constraints/data.d.ts +30 -0
  39. package/dist/database/constraints/data.d.ts.map +1 -0
  40. package/dist/database/constraints/data.js +46 -0
  41. package/dist/database/constraints/data.js.map +1 -0
  42. package/dist/database/constraints/document.d.ts +42 -0
  43. package/dist/database/constraints/document.d.ts.map +1 -0
  44. package/dist/database/constraints/document.js +86 -0
  45. package/dist/database/constraints/document.js.map +1 -0
  46. package/dist/database/constraints/game.d.ts +30 -0
  47. package/dist/database/constraints/game.d.ts.map +1 -0
  48. package/dist/database/constraints/game.js +49 -0
  49. package/dist/database/constraints/game.js.map +1 -0
  50. package/dist/database/constraints/index.d.ts +12 -0
  51. package/dist/database/constraints/index.d.ts.map +1 -0
  52. package/dist/database/constraints/index.js +9 -0
  53. package/dist/database/constraints/index.js.map +1 -0
  54. package/dist/database/constraints/policies.d.ts +16 -0
  55. package/dist/database/constraints/policies.d.ts.map +1 -0
  56. package/dist/database/constraints/policies.js +17 -0
  57. package/dist/database/constraints/policies.js.map +1 -0
  58. package/dist/database/constraints/ruleset.d.ts +5 -0
  59. package/dist/database/constraints/ruleset.d.ts.map +1 -0
  60. package/dist/database/constraints/ruleset.js +132 -0
  61. package/dist/database/constraints/ruleset.js.map +1 -0
  62. package/dist/database/constraints/schema.d.ts +17 -0
  63. package/dist/database/constraints/schema.d.ts.map +1 -0
  64. package/dist/database/constraints/schema.js +74 -0
  65. package/dist/database/constraints/schema.js.map +1 -0
  66. package/dist/database/constraints/types.d.ts +22 -0
  67. package/dist/database/constraints/types.d.ts.map +1 -0
  68. package/dist/database/constraints/types.js +2 -0
  69. package/dist/database/constraints/types.js.map +1 -0
  70. package/dist/database/crawl/handler.d.ts +7 -0
  71. package/dist/database/crawl/handler.d.ts.map +1 -0
  72. package/dist/database/crawl/handler.js +91 -0
  73. package/dist/database/crawl/handler.js.map +1 -0
  74. package/dist/database/crawl/semaphore.d.ts +9 -0
  75. package/dist/database/crawl/semaphore.d.ts.map +1 -0
  76. package/dist/database/crawl/semaphore.js +27 -0
  77. package/dist/database/crawl/semaphore.js.map +1 -0
  78. package/dist/database/crawl/spec.d.ts +33 -0
  79. package/dist/database/crawl/spec.d.ts.map +1 -0
  80. package/dist/database/crawl/spec.js +12 -0
  81. package/dist/database/crawl/spec.js.map +1 -0
  82. package/dist/database/data/handler.d.ts +9 -0
  83. package/dist/database/data/handler.d.ts.map +1 -0
  84. package/dist/database/data/handler.js +96 -0
  85. package/dist/database/data/handler.js.map +1 -0
  86. package/dist/database/data/spec.d.ts +77 -0
  87. package/dist/database/data/spec.d.ts.map +1 -0
  88. package/dist/database/data/spec.js +17 -0
  89. package/dist/database/data/spec.js.map +1 -0
  90. package/dist/database/data/validated.d.ts +8 -0
  91. package/dist/database/data/validated.d.ts.map +1 -0
  92. package/dist/database/data/validated.js +99 -0
  93. package/dist/database/data/validated.js.map +1 -0
  94. package/dist/database/grammar/RtdbExpr.ohm +98 -0
  95. package/dist/database/grammar/RtdbExpr.ohm.generated.d.ts +2 -0
  96. package/dist/database/grammar/RtdbExpr.ohm.generated.d.ts.map +1 -0
  97. package/dist/database/grammar/RtdbExpr.ohm.generated.js +103 -0
  98. package/dist/database/grammar/RtdbExpr.ohm.generated.js.map +1 -0
  99. package/dist/database/grammar/RtdbExprParser.d.ts +5 -0
  100. package/dist/database/grammar/RtdbExprParser.d.ts.map +1 -0
  101. package/dist/database/grammar/RtdbExprParser.js +53 -0
  102. package/dist/database/grammar/RtdbExprParser.js.map +1 -0
  103. package/dist/database/grammar/linter.d.ts +3 -0
  104. package/dist/database/grammar/linter.d.ts.map +1 -0
  105. package/dist/database/grammar/linter.js +66 -0
  106. package/dist/database/grammar/linter.js.map +1 -0
  107. package/dist/database/grammar/simulator.d.ts +31 -0
  108. package/dist/database/grammar/simulator.d.ts.map +1 -0
  109. package/dist/database/grammar/simulator.js +255 -0
  110. package/dist/database/grammar/simulator.js.map +1 -0
  111. package/dist/database/grammar/validator.d.ts +3 -0
  112. package/dist/database/grammar/validator.d.ts.map +1 -0
  113. package/dist/database/grammar/validator.js +84 -0
  114. package/dist/database/grammar/validator.js.map +1 -0
  115. package/dist/database/host.d.ts +50 -0
  116. package/dist/database/host.d.ts.map +1 -0
  117. package/dist/database/host.js +41 -0
  118. package/dist/database/host.js.map +1 -0
  119. package/dist/database/index.d.ts +40 -0
  120. package/dist/database/index.d.ts.map +1 -0
  121. package/dist/database/index.js +47 -0
  122. package/dist/database/index.js.map +1 -0
  123. package/dist/database/initialize-from-app.d.ts +28 -0
  124. package/dist/database/initialize-from-app.d.ts.map +1 -0
  125. package/dist/database/initialize-from-app.js +23 -0
  126. package/dist/database/initialize-from-app.js.map +1 -0
  127. package/dist/database/ir/handler.d.ts +6 -0
  128. package/dist/database/ir/handler.d.ts.map +1 -0
  129. package/dist/database/ir/handler.js +52 -0
  130. package/dist/database/ir/handler.js.map +1 -0
  131. package/dist/database/ir/spec.d.ts +25 -0
  132. package/dist/database/ir/spec.d.ts.map +1 -0
  133. package/dist/database/ir/spec.js +11 -0
  134. package/dist/database/ir/spec.js.map +1 -0
  135. package/dist/database/mapper.d.ts +9 -0
  136. package/dist/database/mapper.d.ts.map +1 -0
  137. package/dist/database/mapper.js +115 -0
  138. package/dist/database/mapper.js.map +1 -0
  139. package/dist/database/modular.d.ts +604 -0
  140. package/dist/database/modular.d.ts.map +1 -0
  141. package/dist/database/modular.js +1226 -0
  142. package/dist/database/modular.js.map +1 -0
  143. package/dist/database/replay.d.ts +33 -0
  144. package/dist/database/replay.d.ts.map +1 -0
  145. package/dist/database/replay.js +124 -0
  146. package/dist/database/replay.js.map +1 -0
  147. package/dist/database/resolver.d.ts +4 -0
  148. package/dist/database/resolver.d.ts.map +1 -0
  149. package/dist/database/resolver.js +66 -0
  150. package/dist/database/resolver.js.map +1 -0
  151. package/dist/database/sandbox/backend.d.ts +403 -0
  152. package/dist/database/sandbox/backend.d.ts.map +1 -0
  153. package/dist/database/sandbox/backend.js +1467 -0
  154. package/dist/database/sandbox/backend.js.map +1 -0
  155. package/dist/database/sandbox/data-tree.d.ts +123 -0
  156. package/dist/database/sandbox/data-tree.d.ts.map +1 -0
  157. package/dist/database/sandbox/data-tree.js +310 -0
  158. package/dist/database/sandbox/data-tree.js.map +1 -0
  159. package/dist/database/sandbox/normalize.d.ts +77 -0
  160. package/dist/database/sandbox/normalize.d.ts.map +1 -0
  161. package/dist/database/sandbox/normalize.js +165 -0
  162. package/dist/database/sandbox/normalize.js.map +1 -0
  163. package/dist/database/sandbox/push-id.d.ts +32 -0
  164. package/dist/database/sandbox/push-id.d.ts.map +1 -0
  165. package/dist/database/sandbox/push-id.js +82 -0
  166. package/dist/database/sandbox/push-id.js.map +1 -0
  167. package/dist/database/sandbox/query.d.ts +175 -0
  168. package/dist/database/sandbox/query.d.ts.map +1 -0
  169. package/dist/database/sandbox/query.js +269 -0
  170. package/dist/database/sandbox/query.js.map +1 -0
  171. package/dist/database/sandbox/rules-eval.d.ts +69 -0
  172. package/dist/database/sandbox/rules-eval.d.ts.map +1 -0
  173. package/dist/database/sandbox/rules-eval.js +124 -0
  174. package/dist/database/sandbox/rules-eval.js.map +1 -0
  175. package/dist/database/sandbox/sentinels.d.ts +71 -0
  176. package/dist/database/sandbox/sentinels.d.ts.map +1 -0
  177. package/dist/database/sandbox/sentinels.js +92 -0
  178. package/dist/database/sandbox/sentinels.js.map +1 -0
  179. package/dist/database/simulation/handler.d.ts +6 -0
  180. package/dist/database/simulation/handler.d.ts.map +1 -0
  181. package/dist/database/simulation/handler.js +236 -0
  182. package/dist/database/simulation/handler.js.map +1 -0
  183. package/dist/database/simulation/spec.d.ts +69 -0
  184. package/dist/database/simulation/spec.d.ts.map +1 -0
  185. package/dist/database/simulation/spec.js +25 -0
  186. package/dist/database/simulation/spec.js.map +1 -0
  187. package/dist/database/tools.d.ts +22 -0
  188. package/dist/database/tools.d.ts.map +1 -0
  189. package/dist/database/tools.js +272 -0
  190. package/dist/database/tools.js.map +1 -0
  191. package/dist/database/types.d.ts +210 -0
  192. package/dist/database/types.d.ts.map +1 -0
  193. package/dist/database/types.js +26 -0
  194. package/dist/database/types.js.map +1 -0
  195. package/dist/database/write/handler.d.ts +7 -0
  196. package/dist/database/write/handler.d.ts.map +1 -0
  197. package/dist/database/write/handler.js +42 -0
  198. package/dist/database/write/handler.js.map +1 -0
  199. package/dist/database/write/spec.d.ts +18 -0
  200. package/dist/database/write/spec.d.ts.map +1 -0
  201. package/dist/database/write/spec.js +7 -0
  202. package/dist/database/write/spec.js.map +1 -0
  203. package/dist/firestore/index.d.ts +533 -0
  204. package/dist/firestore/index.d.ts.map +1 -0
  205. package/dist/firestore/index.js +1536 -0
  206. package/dist/firestore/index.js.map +1 -0
  207. package/dist/firestore/tools.d.ts +67 -0
  208. package/dist/firestore/tools.d.ts.map +1 -0
  209. package/dist/firestore/tools.js +318 -0
  210. package/dist/firestore/tools.js.map +1 -0
  211. package/dist/firestore-values/index.d.ts +63 -0
  212. package/dist/firestore-values/index.d.ts.map +1 -0
  213. package/dist/firestore-values/index.js +148 -0
  214. package/dist/firestore-values/index.js.map +1 -0
  215. package/dist/project-scope.d.ts +5 -0
  216. package/dist/project-scope.d.ts.map +1 -0
  217. package/dist/project-scope.js +2 -0
  218. package/dist/project-scope.js.map +1 -0
  219. package/dist/rules/extract.d.ts +28 -0
  220. package/dist/rules/extract.d.ts.map +1 -0
  221. package/dist/rules/extract.js +25 -0
  222. package/dist/rules/extract.js.map +1 -0
  223. package/dist/rules/generators/assembler.d.ts +19 -0
  224. package/dist/rules/generators/assembler.d.ts.map +1 -0
  225. package/dist/rules/generators/assembler.js +292 -0
  226. package/dist/rules/generators/assembler.js.map +1 -0
  227. package/dist/rules/generators/expressions.d.ts +52 -0
  228. package/dist/rules/generators/expressions.d.ts.map +1 -0
  229. package/dist/rules/generators/expressions.js +117 -0
  230. package/dist/rules/generators/expressions.js.map +1 -0
  231. package/dist/rules/generators/grid.d.ts +22 -0
  232. package/dist/rules/generators/grid.d.ts.map +1 -0
  233. package/dist/rules/generators/grid.js +52 -0
  234. package/dist/rules/generators/grid.js.map +1 -0
  235. package/dist/rules/grammar/FirestoreAST.d.ts +129 -0
  236. package/dist/rules/grammar/FirestoreAST.d.ts.map +1 -0
  237. package/dist/rules/grammar/FirestoreAST.js +3 -0
  238. package/dist/rules/grammar/FirestoreAST.js.map +1 -0
  239. package/dist/rules/grammar/FirestoreAssembler.d.ts +5 -0
  240. package/dist/rules/grammar/FirestoreAssembler.d.ts.map +1 -0
  241. package/dist/rules/grammar/FirestoreAssembler.js +158 -0
  242. package/dist/rules/grammar/FirestoreAssembler.js.map +1 -0
  243. package/dist/rules/grammar/FirestoreParser.d.ts +46 -0
  244. package/dist/rules/grammar/FirestoreParser.d.ts.map +1 -0
  245. package/dist/rules/grammar/FirestoreParser.js +409 -0
  246. package/dist/rules/grammar/FirestoreParser.js.map +1 -0
  247. package/dist/rules/grammar/FirestoreRules.ohm +229 -0
  248. package/dist/rules/grammar/FirestoreRules.ohm.generated.d.ts +2 -0
  249. package/dist/rules/grammar/FirestoreRules.ohm.generated.d.ts.map +1 -0
  250. package/dist/rules/grammar/FirestoreRules.ohm.generated.js +234 -0
  251. package/dist/rules/grammar/FirestoreRules.ohm.generated.js.map +1 -0
  252. package/dist/rules/grammar/FirestoreValidator.d.ts +10 -0
  253. package/dist/rules/grammar/FirestoreValidator.d.ts.map +1 -0
  254. package/dist/rules/grammar/FirestoreValidator.js +482 -0
  255. package/dist/rules/grammar/FirestoreValidator.js.map +1 -0
  256. package/dist/rules/index.d.ts +67 -0
  257. package/dist/rules/index.d.ts.map +1 -0
  258. package/dist/rules/index.js +80 -0
  259. package/dist/rules/index.js.map +1 -0
  260. package/dist/rules/indexes/extract/annotation-collect.d.ts +42 -0
  261. package/dist/rules/indexes/extract/annotation-collect.d.ts.map +1 -0
  262. package/dist/rules/indexes/extract/annotation-collect.js +69 -0
  263. package/dist/rules/indexes/extract/annotation-collect.js.map +1 -0
  264. package/dist/rules/indexes/extract/annotations.d.ts +50 -0
  265. package/dist/rules/indexes/extract/annotations.d.ts.map +1 -0
  266. package/dist/rules/indexes/extract/annotations.js +144 -0
  267. package/dist/rules/indexes/extract/annotations.js.map +1 -0
  268. package/dist/rules/indexes/extract/ast.d.ts +54 -0
  269. package/dist/rules/indexes/extract/ast.d.ts.map +1 -0
  270. package/dist/rules/indexes/extract/ast.js +119 -0
  271. package/dist/rules/indexes/extract/ast.js.map +1 -0
  272. package/dist/rules/indexes/extract/classify.d.ts +41 -0
  273. package/dist/rules/indexes/extract/classify.d.ts.map +1 -0
  274. package/dist/rules/indexes/extract/classify.js +138 -0
  275. package/dist/rules/indexes/extract/classify.js.map +1 -0
  276. package/dist/rules/indexes/extract/composite.d.ts +38 -0
  277. package/dist/rules/indexes/extract/composite.d.ts.map +1 -0
  278. package/dist/rules/indexes/extract/composite.js +85 -0
  279. package/dist/rules/indexes/extract/composite.js.map +1 -0
  280. package/dist/rules/indexes/extract/dataflow.d.ts +45 -0
  281. package/dist/rules/indexes/extract/dataflow.d.ts.map +1 -0
  282. package/dist/rules/indexes/extract/dataflow.js +200 -0
  283. package/dist/rules/indexes/extract/dataflow.js.map +1 -0
  284. package/dist/rules/indexes/extract/enumerate.d.ts +41 -0
  285. package/dist/rules/indexes/extract/enumerate.d.ts.map +1 -0
  286. package/dist/rules/indexes/extract/enumerate.js +163 -0
  287. package/dist/rules/indexes/extract/enumerate.js.map +1 -0
  288. package/dist/rules/indexes/extract/extractor.d.ts +3 -0
  289. package/dist/rules/indexes/extract/extractor.d.ts.map +1 -0
  290. package/dist/rules/indexes/extract/extractor.js +274 -0
  291. package/dist/rules/indexes/extract/extractor.js.map +1 -0
  292. package/dist/rules/indexes/extract/types.d.ts +211 -0
  293. package/dist/rules/indexes/extract/types.d.ts.map +1 -0
  294. package/dist/rules/indexes/extract/types.js +14 -0
  295. package/dist/rules/indexes/extract/types.js.map +1 -0
  296. package/dist/rules/indexes/extractHandler.d.ts +16 -0
  297. package/dist/rules/indexes/extractHandler.d.ts.map +1 -0
  298. package/dist/rules/indexes/extractHandler.js +100 -0
  299. package/dist/rules/indexes/extractHandler.js.map +1 -0
  300. package/dist/rules/indexes/extractTool.d.ts +15 -0
  301. package/dist/rules/indexes/extractTool.d.ts.map +1 -0
  302. package/dist/rules/indexes/extractTool.js +55 -0
  303. package/dist/rules/indexes/extractTool.js.map +1 -0
  304. package/dist/rules/indexes/types.d.ts +71 -0
  305. package/dist/rules/indexes/types.d.ts.map +1 -0
  306. package/dist/rules/indexes/types.js +15 -0
  307. package/dist/rules/indexes/types.js.map +1 -0
  308. package/dist/rules/inspect/handler.d.ts +6 -0
  309. package/dist/rules/inspect/handler.d.ts.map +1 -0
  310. package/dist/rules/inspect/handler.js +144 -0
  311. package/dist/rules/inspect/handler.js.map +1 -0
  312. package/dist/rules/inspect/spec.d.ts +37 -0
  313. package/dist/rules/inspect/spec.d.ts.map +1 -0
  314. package/dist/rules/inspect/spec.js +2 -0
  315. package/dist/rules/inspect/spec.js.map +1 -0
  316. package/dist/rules/inspect/tools.d.ts +21 -0
  317. package/dist/rules/inspect/tools.d.ts.map +1 -0
  318. package/dist/rules/inspect/tools.js +21 -0
  319. package/dist/rules/inspect/tools.js.map +1 -0
  320. package/dist/rules/linter/ast-utils.d.ts +97 -0
  321. package/dist/rules/linter/ast-utils.d.ts.map +1 -0
  322. package/dist/rules/linter/ast-utils.js +491 -0
  323. package/dist/rules/linter/ast-utils.js.map +1 -0
  324. package/dist/rules/linter/hallucinations.d.ts +34 -0
  325. package/dist/rules/linter/hallucinations.d.ts.map +1 -0
  326. package/dist/rules/linter/hallucinations.js +334 -0
  327. package/dist/rules/linter/hallucinations.js.map +1 -0
  328. package/dist/rules/linter/linter.d.ts +84 -0
  329. package/dist/rules/linter/linter.d.ts.map +1 -0
  330. package/dist/rules/linter/linter.js +784 -0
  331. package/dist/rules/linter/linter.js.map +1 -0
  332. package/dist/rules/modules/resolver-browser.d.ts +41 -0
  333. package/dist/rules/modules/resolver-browser.d.ts.map +1 -0
  334. package/dist/rules/modules/resolver-browser.js +63 -0
  335. package/dist/rules/modules/resolver-browser.js.map +1 -0
  336. package/dist/rules/modules/resolver-core.d.ts +51 -0
  337. package/dist/rules/modules/resolver-core.d.ts.map +1 -0
  338. package/dist/rules/modules/resolver-core.js +338 -0
  339. package/dist/rules/modules/resolver-core.js.map +1 -0
  340. package/dist/rules/modules/resolver.d.ts +17 -0
  341. package/dist/rules/modules/resolver.d.ts.map +1 -0
  342. package/dist/rules/modules/resolver.js +49 -0
  343. package/dist/rules/modules/resolver.js.map +1 -0
  344. package/dist/rules/modules/stdlib/atomic.rules +52 -0
  345. package/dist/rules/modules/stdlib/atomic.test.json +152 -0
  346. package/dist/rules/modules/stdlib/auth.rules +7 -0
  347. package/dist/rules/modules/stdlib/auth.test.json +53 -0
  348. package/dist/rules/modules/stdlib/content.rules +50 -0
  349. package/dist/rules/modules/stdlib/content.test.json +238 -0
  350. package/dist/rules/modules/stdlib/counters.rules +41 -0
  351. package/dist/rules/modules/stdlib/counters.test.json +254 -0
  352. package/dist/rules/modules/stdlib/geometry.rules +43 -0
  353. package/dist/rules/modules/stdlib/geometry.test.json +200 -0
  354. package/dist/rules/modules/stdlib/joining.rules +49 -0
  355. package/dist/rules/modules/stdlib/joining.test.json +287 -0
  356. package/dist/rules/modules/stdlib/lifecycle.rules +51 -0
  357. package/dist/rules/modules/stdlib/lifecycle.test.json +288 -0
  358. package/dist/rules/modules/stdlib/lobby.rules +44 -0
  359. package/dist/rules/modules/stdlib/lobby.test.json +32 -0
  360. package/dist/rules/modules/stdlib/membership.rules +40 -0
  361. package/dist/rules/modules/stdlib/membership.test.json +107 -0
  362. package/dist/rules/modules/stdlib/spaces.rules +46 -0
  363. package/dist/rules/modules/stdlib/spaces.test.json +392 -0
  364. package/dist/rules/modules/stdlib/state.rules +28 -0
  365. package/dist/rules/modules/stdlib/state.test.json +55 -0
  366. package/dist/rules/modules/stdlib/timing.rules +20 -0
  367. package/dist/rules/modules/stdlib/timing.test.json +106 -0
  368. package/dist/rules/modules/stdlib/transitions.rules +34 -0
  369. package/dist/rules/modules/stdlib/transitions.test.json +86 -0
  370. package/dist/rules/modules/stdlib/turns.rules +27 -0
  371. package/dist/rules/modules/stdlib/turns.test.json +64 -0
  372. package/dist/rules/modules/stdlib/validation.rules +31 -0
  373. package/dist/rules/modules/stdlib/validation.test.json +184 -0
  374. package/dist/rules/modules/stdlib-content.d.ts +11 -0
  375. package/dist/rules/modules/stdlib-content.d.ts.map +1 -0
  376. package/dist/rules/modules/stdlib-content.js +590 -0
  377. package/dist/rules/modules/stdlib-content.js.map +1 -0
  378. package/dist/rules/node.d.ts +17 -0
  379. package/dist/rules/node.d.ts.map +1 -0
  380. package/dist/rules/node.js +21 -0
  381. package/dist/rules/node.js.map +1 -0
  382. package/dist/rules/rtdb.d.ts +28 -0
  383. package/dist/rules/rtdb.d.ts.map +1 -0
  384. package/dist/rules/rtdb.js +15 -0
  385. package/dist/rules/rtdb.js.map +1 -0
  386. package/dist/rules/simulator/evaluator.d.ts +179 -0
  387. package/dist/rules/simulator/evaluator.d.ts.map +1 -0
  388. package/dist/rules/simulator/evaluator.js +1221 -0
  389. package/dist/rules/simulator/evaluator.js.map +1 -0
  390. package/dist/rules/simulator/expression/eval-errors.d.ts +86 -0
  391. package/dist/rules/simulator/expression/eval-errors.d.ts.map +1 -0
  392. package/dist/rules/simulator/expression/eval-errors.js +68 -0
  393. package/dist/rules/simulator/expression/eval-errors.js.map +1 -0
  394. package/dist/rules/simulator/expression/evaluator.d.ts +43 -0
  395. package/dist/rules/simulator/expression/evaluator.d.ts.map +1 -0
  396. package/dist/rules/simulator/expression/evaluator.js +298 -0
  397. package/dist/rules/simulator/expression/evaluator.js.map +1 -0
  398. package/dist/rules/simulator/expression/lexer.d.ts +36 -0
  399. package/dist/rules/simulator/expression/lexer.d.ts.map +1 -0
  400. package/dist/rules/simulator/expression/lexer.js +318 -0
  401. package/dist/rules/simulator/expression/lexer.js.map +1 -0
  402. package/dist/rules/simulator/expression/parser.d.ts +39 -0
  403. package/dist/rules/simulator/expression/parser.d.ts.map +1 -0
  404. package/dist/rules/simulator/expression/parser.js +329 -0
  405. package/dist/rules/simulator/expression/parser.js.map +1 -0
  406. package/dist/rules/simulator/expression/types.d.ts +137 -0
  407. package/dist/rules/simulator/expression/types.d.ts.map +1 -0
  408. package/dist/rules/simulator/expression/types.js +90 -0
  409. package/dist/rules/simulator/expression/types.js.map +1 -0
  410. package/dist/rules/simulator/expression/walk-data.d.ts +52 -0
  411. package/dist/rules/simulator/expression/walk-data.d.ts.map +1 -0
  412. package/dist/rules/simulator/expression/walk-data.js +167 -0
  413. package/dist/rules/simulator/expression/walk-data.js.map +1 -0
  414. package/dist/rules/simulator/firestore-set.d.ts +25 -0
  415. package/dist/rules/simulator/firestore-set.d.ts.map +1 -0
  416. package/dist/rules/simulator/firestore-set.js +89 -0
  417. package/dist/rules/simulator/firestore-set.js.map +1 -0
  418. package/dist/rules/simulator/handler.d.ts +36 -0
  419. package/dist/rules/simulator/handler.d.ts.map +1 -0
  420. package/dist/rules/simulator/handler.js +583 -0
  421. package/dist/rules/simulator/handler.js.map +1 -0
  422. package/dist/rules/simulator/mapdiff.d.ts +29 -0
  423. package/dist/rules/simulator/mapdiff.d.ts.map +1 -0
  424. package/dist/rules/simulator/mapdiff.js +72 -0
  425. package/dist/rules/simulator/mapdiff.js.map +1 -0
  426. package/dist/rules/simulator/project-after-state.d.ts +33 -0
  427. package/dist/rules/simulator/project-after-state.d.ts.map +1 -0
  428. package/dist/rules/simulator/project-after-state.js +88 -0
  429. package/dist/rules/simulator/project-after-state.js.map +1 -0
  430. package/dist/rules/simulator/query-proof.d.ts +80 -0
  431. package/dist/rules/simulator/query-proof.d.ts.map +1 -0
  432. package/dist/rules/simulator/query-proof.js +142 -0
  433. package/dist/rules/simulator/query-proof.js.map +1 -0
  434. package/dist/rules/simulator/value-equality.d.ts +8 -0
  435. package/dist/rules/simulator/value-equality.d.ts.map +1 -0
  436. package/dist/rules/simulator/value-equality.js +40 -0
  437. package/dist/rules/simulator/value-equality.js.map +1 -0
  438. package/dist/rules/simulator/wrappers/base.d.ts +129 -0
  439. package/dist/rules/simulator/wrappers/base.d.ts.map +1 -0
  440. package/dist/rules/simulator/wrappers/base.js +90 -0
  441. package/dist/rules/simulator/wrappers/base.js.map +1 -0
  442. package/dist/rules/simulator/wrappers/bytes.d.ts +45 -0
  443. package/dist/rules/simulator/wrappers/bytes.d.ts.map +1 -0
  444. package/dist/rules/simulator/wrappers/bytes.js +130 -0
  445. package/dist/rules/simulator/wrappers/bytes.js.map +1 -0
  446. package/dist/rules/simulator/wrappers/duration.d.ts +76 -0
  447. package/dist/rules/simulator/wrappers/duration.d.ts.map +1 -0
  448. package/dist/rules/simulator/wrappers/duration.js +176 -0
  449. package/dist/rules/simulator/wrappers/duration.js.map +1 -0
  450. package/dist/rules/simulator/wrappers/float.d.ts +52 -0
  451. package/dist/rules/simulator/wrappers/float.d.ts.map +1 -0
  452. package/dist/rules/simulator/wrappers/float.js +88 -0
  453. package/dist/rules/simulator/wrappers/float.js.map +1 -0
  454. package/dist/rules/simulator/wrappers/latlng.d.ts +40 -0
  455. package/dist/rules/simulator/wrappers/latlng.d.ts.map +1 -0
  456. package/dist/rules/simulator/wrappers/latlng.js +100 -0
  457. package/dist/rules/simulator/wrappers/latlng.js.map +1 -0
  458. package/dist/rules/simulator/wrappers/path.d.ts +75 -0
  459. package/dist/rules/simulator/wrappers/path.d.ts.map +1 -0
  460. package/dist/rules/simulator/wrappers/path.js +158 -0
  461. package/dist/rules/simulator/wrappers/path.js.map +1 -0
  462. package/dist/rules/simulator/wrappers/reference.d.ts +82 -0
  463. package/dist/rules/simulator/wrappers/reference.d.ts.map +1 -0
  464. package/dist/rules/simulator/wrappers/reference.js +145 -0
  465. package/dist/rules/simulator/wrappers/reference.js.map +1 -0
  466. package/dist/rules/simulator/wrappers/timestamp.d.ts +86 -0
  467. package/dist/rules/simulator/wrappers/timestamp.d.ts.map +1 -0
  468. package/dist/rules/simulator/wrappers/timestamp.js +207 -0
  469. package/dist/rules/simulator/wrappers/timestamp.js.map +1 -0
  470. package/dist/rules/simulator/wrappers/vector.d.ts +43 -0
  471. package/dist/rules/simulator/wrappers/vector.d.ts.map +1 -0
  472. package/dist/rules/simulator/wrappers/vector.js +80 -0
  473. package/dist/rules/simulator/wrappers/vector.js.map +1 -0
  474. package/dist/rules/simulator-tools-impl.d.ts +64 -0
  475. package/dist/rules/simulator-tools-impl.d.ts.map +1 -0
  476. package/dist/rules/simulator-tools-impl.js +462 -0
  477. package/dist/rules/simulator-tools-impl.js.map +1 -0
  478. package/dist/rules/simulator.d.ts +20 -0
  479. package/dist/rules/simulator.d.ts.map +1 -0
  480. package/dist/rules/simulator.js +24 -0
  481. package/dist/rules/simulator.js.map +1 -0
  482. package/dist/rules/stdlib-modules.d.ts +73 -0
  483. package/dist/rules/stdlib-modules.d.ts.map +1 -0
  484. package/dist/rules/stdlib-modules.js +849 -0
  485. package/dist/rules/stdlib-modules.js.map +1 -0
  486. package/dist/rules/stdlib-tools.d.ts +19 -0
  487. package/dist/rules/stdlib-tools.d.ts.map +1 -0
  488. package/dist/rules/stdlib-tools.js +164 -0
  489. package/dist/rules/stdlib-tools.js.map +1 -0
  490. package/dist/rules/test/handler.d.ts +9 -0
  491. package/dist/rules/test/handler.d.ts.map +1 -0
  492. package/dist/rules/test/handler.js +124 -0
  493. package/dist/rules/test/handler.js.map +1 -0
  494. package/dist/rules/test/spec.d.ts +517 -0
  495. package/dist/rules/test/spec.d.ts.map +1 -0
  496. package/dist/rules/test/spec.js +227 -0
  497. package/dist/rules/test/spec.js.map +1 -0
  498. package/dist/rules/tools.d.ts +41 -0
  499. package/dist/rules/tools.d.ts.map +1 -0
  500. package/dist/rules/tools.js +124 -0
  501. package/dist/rules/tools.js.map +1 -0
  502. package/dist/rules/write/handler.d.ts +6 -0
  503. package/dist/rules/write/handler.d.ts.map +1 -0
  504. package/dist/rules/write/handler.js +126 -0
  505. package/dist/rules/write/handler.js.map +1 -0
  506. package/dist/rules/write/spec.d.ts +28 -0
  507. package/dist/rules/write/spec.d.ts.map +1 -0
  508. package/dist/rules/write/spec.js +2 -0
  509. package/dist/rules/write/spec.js.map +1 -0
  510. package/dist/sandbox/admin-compat.d.ts +20 -0
  511. package/dist/sandbox/admin-compat.d.ts.map +1 -0
  512. package/dist/sandbox/admin-compat.js +19 -0
  513. package/dist/sandbox/admin-compat.js.map +1 -0
  514. package/dist/sandbox/admin-firestore/error-translation.d.ts +86 -0
  515. package/dist/sandbox/admin-firestore/error-translation.d.ts.map +1 -0
  516. package/dist/sandbox/admin-firestore/error-translation.js +204 -0
  517. package/dist/sandbox/admin-firestore/error-translation.js.map +1 -0
  518. package/dist/sandbox/admin-firestore/index.d.ts +169 -0
  519. package/dist/sandbox/admin-firestore/index.d.ts.map +1 -0
  520. package/dist/sandbox/admin-firestore/index.js +456 -0
  521. package/dist/sandbox/admin-firestore/index.js.map +1 -0
  522. package/dist/sandbox/admin-firestore/remote.d.ts +97 -0
  523. package/dist/sandbox/admin-firestore/remote.d.ts.map +1 -0
  524. package/dist/sandbox/admin-firestore/remote.js +735 -0
  525. package/dist/sandbox/admin-firestore/remote.js.map +1 -0
  526. package/dist/sandbox/branches/index.d.ts +141 -0
  527. package/dist/sandbox/branches/index.d.ts.map +1 -0
  528. package/dist/sandbox/branches/index.js +327 -0
  529. package/dist/sandbox/branches/index.js.map +1 -0
  530. package/dist/sandbox/firestore/admin-compat/batch.d.ts +34 -0
  531. package/dist/sandbox/firestore/admin-compat/batch.d.ts.map +1 -0
  532. package/dist/sandbox/firestore/admin-compat/batch.js +73 -0
  533. package/dist/sandbox/firestore/admin-compat/batch.js.map +1 -0
  534. package/dist/sandbox/firestore/admin-compat/doc-ref.d.ts +38 -0
  535. package/dist/sandbox/firestore/admin-compat/doc-ref.d.ts.map +1 -0
  536. package/dist/sandbox/firestore/admin-compat/doc-ref.js +154 -0
  537. package/dist/sandbox/firestore/admin-compat/doc-ref.js.map +1 -0
  538. package/dist/sandbox/firestore/admin-compat/firestore.d.ts +45 -0
  539. package/dist/sandbox/firestore/admin-compat/firestore.d.ts.map +1 -0
  540. package/dist/sandbox/firestore/admin-compat/firestore.js +114 -0
  541. package/dist/sandbox/firestore/admin-compat/firestore.js.map +1 -0
  542. package/dist/sandbox/firestore/admin-compat/index.d.ts +45 -0
  543. package/dist/sandbox/firestore/admin-compat/index.d.ts.map +1 -0
  544. package/dist/sandbox/firestore/admin-compat/index.js +27 -0
  545. package/dist/sandbox/firestore/admin-compat/index.js.map +1 -0
  546. package/dist/sandbox/firestore/admin-compat/paths.d.ts +37 -0
  547. package/dist/sandbox/firestore/admin-compat/paths.d.ts.map +1 -0
  548. package/dist/sandbox/firestore/admin-compat/paths.js +49 -0
  549. package/dist/sandbox/firestore/admin-compat/paths.js.map +1 -0
  550. package/dist/sandbox/firestore/admin-compat/query.d.ts +261 -0
  551. package/dist/sandbox/firestore/admin-compat/query.d.ts.map +1 -0
  552. package/dist/sandbox/firestore/admin-compat/query.js +704 -0
  553. package/dist/sandbox/firestore/admin-compat/query.js.map +1 -0
  554. package/dist/sandbox/firestore/admin-compat/read-translation.d.ts +8 -0
  555. package/dist/sandbox/firestore/admin-compat/read-translation.d.ts.map +1 -0
  556. package/dist/sandbox/firestore/admin-compat/read-translation.js +59 -0
  557. package/dist/sandbox/firestore/admin-compat/read-translation.js.map +1 -0
  558. package/dist/sandbox/firestore/admin-compat/snapshots.d.ts +39 -0
  559. package/dist/sandbox/firestore/admin-compat/snapshots.d.ts.map +1 -0
  560. package/dist/sandbox/firestore/admin-compat/snapshots.js +50 -0
  561. package/dist/sandbox/firestore/admin-compat/snapshots.js.map +1 -0
  562. package/dist/sandbox/firestore/admin-compat/transaction.d.ts +35 -0
  563. package/dist/sandbox/firestore/admin-compat/transaction.d.ts.map +1 -0
  564. package/dist/sandbox/firestore/admin-compat/transaction.js +88 -0
  565. package/dist/sandbox/firestore/admin-compat/transaction.js.map +1 -0
  566. package/dist/sandbox/firestore/admin-compat/types.d.ts +297 -0
  567. package/dist/sandbox/firestore/admin-compat/types.d.ts.map +1 -0
  568. package/dist/sandbox/firestore/admin-compat/types.js +127 -0
  569. package/dist/sandbox/firestore/admin-compat/types.js.map +1 -0
  570. package/dist/sandbox/firestore/admin-compat/value-order.d.ts +49 -0
  571. package/dist/sandbox/firestore/admin-compat/value-order.d.ts.map +1 -0
  572. package/dist/sandbox/firestore/admin-compat/value-order.js +172 -0
  573. package/dist/sandbox/firestore/admin-compat/value-order.js.map +1 -0
  574. package/dist/sandbox/firestore/auto-id.d.ts +26 -0
  575. package/dist/sandbox/firestore/auto-id.d.ts.map +1 -0
  576. package/dist/sandbox/firestore/auto-id.js +36 -0
  577. package/dist/sandbox/firestore/auto-id.js.map +1 -0
  578. package/dist/sandbox/firestore/converters/bytes-geopoint.d.ts +48 -0
  579. package/dist/sandbox/firestore/converters/bytes-geopoint.d.ts.map +1 -0
  580. package/dist/sandbox/firestore/converters/bytes-geopoint.js +85 -0
  581. package/dist/sandbox/firestore/converters/bytes-geopoint.js.map +1 -0
  582. package/dist/sandbox/firestore/converters/fieldvalue.d.ts +69 -0
  583. package/dist/sandbox/firestore/converters/fieldvalue.d.ts.map +1 -0
  584. package/dist/sandbox/firestore/converters/fieldvalue.js +166 -0
  585. package/dist/sandbox/firestore/converters/fieldvalue.js.map +1 -0
  586. package/dist/sandbox/firestore/converters/reference.d.ts +31 -0
  587. package/dist/sandbox/firestore/converters/reference.d.ts.map +1 -0
  588. package/dist/sandbox/firestore/converters/reference.js +51 -0
  589. package/dist/sandbox/firestore/converters/reference.js.map +1 -0
  590. package/dist/sandbox/firestore/converters/timestamp.d.ts +32 -0
  591. package/dist/sandbox/firestore/converters/timestamp.d.ts.map +1 -0
  592. package/dist/sandbox/firestore/converters/timestamp.js +63 -0
  593. package/dist/sandbox/firestore/converters/timestamp.js.map +1 -0
  594. package/dist/sandbox/firestore/converters/user-timestamp.d.ts +46 -0
  595. package/dist/sandbox/firestore/converters/user-timestamp.d.ts.map +1 -0
  596. package/dist/sandbox/firestore/converters/user-timestamp.js +65 -0
  597. package/dist/sandbox/firestore/converters/user-timestamp.js.map +1 -0
  598. package/dist/sandbox/firestore/converters/vector.d.ts +24 -0
  599. package/dist/sandbox/firestore/converters/vector.d.ts.map +1 -0
  600. package/dist/sandbox/firestore/converters/vector.js +53 -0
  601. package/dist/sandbox/firestore/converters/vector.js.map +1 -0
  602. package/dist/sandbox/firestore/errors.d.ts +82 -0
  603. package/dist/sandbox/firestore/errors.d.ts.map +1 -0
  604. package/dist/sandbox/firestore/errors.js +54 -0
  605. package/dist/sandbox/firestore/errors.js.map +1 -0
  606. package/dist/sandbox/firestore/event-log.d.ts +99 -0
  607. package/dist/sandbox/firestore/event-log.d.ts.map +1 -0
  608. package/dist/sandbox/firestore/event-log.js +67 -0
  609. package/dist/sandbox/firestore/event-log.js.map +1 -0
  610. package/dist/sandbox/firestore/field-merge.d.ts +64 -0
  611. package/dist/sandbox/firestore/field-merge.d.ts.map +1 -0
  612. package/dist/sandbox/firestore/field-merge.js +221 -0
  613. package/dist/sandbox/firestore/field-merge.js.map +1 -0
  614. package/dist/sandbox/firestore/list-query-proof.d.ts +77 -0
  615. package/dist/sandbox/firestore/list-query-proof.d.ts.map +1 -0
  616. package/dist/sandbox/firestore/list-query-proof.js +170 -0
  617. package/dist/sandbox/firestore/list-query-proof.js.map +1 -0
  618. package/dist/sandbox/firestore/local-environment.d.ts +806 -0
  619. package/dist/sandbox/firestore/local-environment.d.ts.map +1 -0
  620. package/dist/sandbox/firestore/local-environment.js +3028 -0
  621. package/dist/sandbox/firestore/local-environment.js.map +1 -0
  622. package/dist/sandbox/firestore/local-state.d.ts +196 -0
  623. package/dist/sandbox/firestore/local-state.d.ts.map +1 -0
  624. package/dist/sandbox/firestore/local-state.js +413 -0
  625. package/dist/sandbox/firestore/local-state.js.map +1 -0
  626. package/dist/sandbox/firestore/overlay-backing.d.ts +44 -0
  627. package/dist/sandbox/firestore/overlay-backing.d.ts.map +1 -0
  628. package/dist/sandbox/firestore/overlay-backing.js +82 -0
  629. package/dist/sandbox/firestore/overlay-backing.js.map +1 -0
  630. package/dist/sandbox/firestore/sentinel-capture.d.ts +30 -0
  631. package/dist/sandbox/firestore/sentinel-capture.d.ts.map +1 -0
  632. package/dist/sandbox/firestore/sentinel-capture.js +61 -0
  633. package/dist/sandbox/firestore/sentinel-capture.js.map +1 -0
  634. package/dist/sandbox/firestore/snapshot-listeners.d.ts +284 -0
  635. package/dist/sandbox/firestore/snapshot-listeners.d.ts.map +1 -0
  636. package/dist/sandbox/firestore/snapshot-listeners.js +194 -0
  637. package/dist/sandbox/firestore/snapshot-listeners.js.map +1 -0
  638. package/dist/sandbox/firestore/topk.d.ts +12 -0
  639. package/dist/sandbox/firestore/topk.d.ts.map +1 -0
  640. package/dist/sandbox/firestore/topk.js +42 -0
  641. package/dist/sandbox/firestore/topk.js.map +1 -0
  642. package/dist/sandbox/firestore/transaction-merge.d.ts +55 -0
  643. package/dist/sandbox/firestore/transaction-merge.d.ts.map +1 -0
  644. package/dist/sandbox/firestore/transaction-merge.js +107 -0
  645. package/dist/sandbox/firestore/transaction-merge.js.map +1 -0
  646. package/dist/sandbox/firestore/transaction-types.d.ts +152 -0
  647. package/dist/sandbox/firestore/transaction-types.d.ts.map +1 -0
  648. package/dist/sandbox/firestore/transaction-types.js +10 -0
  649. package/dist/sandbox/firestore/transaction-types.js.map +1 -0
  650. package/dist/sandbox/firestore/transaction.d.ts +79 -0
  651. package/dist/sandbox/firestore/transaction.d.ts.map +1 -0
  652. package/dist/sandbox/firestore/transaction.js +95 -0
  653. package/dist/sandbox/firestore/transaction.js.map +1 -0
  654. package/dist/sandbox/firestore/value-equality.d.ts +2 -0
  655. package/dist/sandbox/firestore/value-equality.d.ts.map +1 -0
  656. package/dist/sandbox/firestore/value-equality.js +35 -0
  657. package/dist/sandbox/firestore/value-equality.js.map +1 -0
  658. package/dist/sandbox/firestore/value-resolver.d.ts +172 -0
  659. package/dist/sandbox/firestore/value-resolver.d.ts.map +1 -0
  660. package/dist/sandbox/firestore/value-resolver.js +221 -0
  661. package/dist/sandbox/firestore/value-resolver.js.map +1 -0
  662. package/dist/sandbox/firestore/wire-encoder.d.ts +14 -0
  663. package/dist/sandbox/firestore/wire-encoder.d.ts.map +1 -0
  664. package/dist/sandbox/firestore/wire-encoder.js +130 -0
  665. package/dist/sandbox/firestore/wire-encoder.js.map +1 -0
  666. package/dist/sandbox/index.d.ts +50 -0
  667. package/dist/sandbox/index.d.ts.map +1 -0
  668. package/dist/sandbox/index.js +63 -0
  669. package/dist/sandbox/index.js.map +1 -0
  670. package/dist/sandbox/internal/index.d.ts +36 -0
  671. package/dist/sandbox/internal/index.d.ts.map +1 -0
  672. package/dist/sandbox/internal/index.js +72 -0
  673. package/dist/sandbox/internal/index.js.map +1 -0
  674. package/dist/sandbox/internal/sandbox-impl.d.ts +343 -0
  675. package/dist/sandbox/internal/sandbox-impl.d.ts.map +1 -0
  676. package/dist/sandbox/internal/sandbox-impl.js +718 -0
  677. package/dist/sandbox/internal/sandbox-impl.js.map +1 -0
  678. package/dist/sandbox/persistence/backends.d.ts +37 -0
  679. package/dist/sandbox/persistence/backends.d.ts.map +1 -0
  680. package/dist/sandbox/persistence/backends.js +202 -0
  681. package/dist/sandbox/persistence/backends.js.map +1 -0
  682. package/dist/sandbox/persistence/chunk-format.d.ts +58 -0
  683. package/dist/sandbox/persistence/chunk-format.d.ts.map +1 -0
  684. package/dist/sandbox/persistence/chunk-format.js +163 -0
  685. package/dist/sandbox/persistence/chunk-format.js.map +1 -0
  686. package/dist/sandbox/persistence/controller.d.ts +39 -0
  687. package/dist/sandbox/persistence/controller.d.ts.map +1 -0
  688. package/dist/sandbox/persistence/controller.js +605 -0
  689. package/dist/sandbox/persistence/controller.js.map +1 -0
  690. package/dist/sandbox/persistence/index.d.ts +13 -0
  691. package/dist/sandbox/persistence/index.d.ts.map +1 -0
  692. package/dist/sandbox/persistence/index.js +5 -0
  693. package/dist/sandbox/persistence/index.js.map +1 -0
  694. package/dist/sandbox/persistence/serialize.d.ts +53 -0
  695. package/dist/sandbox/persistence/serialize.d.ts.map +1 -0
  696. package/dist/sandbox/persistence/serialize.js +98 -0
  697. package/dist/sandbox/persistence/serialize.js.map +1 -0
  698. package/dist/sandbox/persistence/types.d.ts +118 -0
  699. package/dist/sandbox/persistence/types.d.ts.map +1 -0
  700. package/dist/sandbox/persistence/types.js +15 -0
  701. package/dist/sandbox/persistence/types.js.map +1 -0
  702. package/dist/sandbox/remote.d.ts +118 -0
  703. package/dist/sandbox/remote.d.ts.map +1 -0
  704. package/dist/sandbox/remote.js +60 -0
  705. package/dist/sandbox/remote.js.map +1 -0
  706. package/dist/sandbox/replay/index.d.ts +106 -0
  707. package/dist/sandbox/replay/index.d.ts.map +1 -0
  708. package/dist/sandbox/replay/index.js +246 -0
  709. package/dist/sandbox/replay/index.js.map +1 -0
  710. package/dist/sandbox/sandbox-context.d.ts +40 -0
  711. package/dist/sandbox/sandbox-context.d.ts.map +1 -0
  712. package/dist/sandbox/sandbox-context.js +72 -0
  713. package/dist/sandbox/sandbox-context.js.map +1 -0
  714. package/dist/sandbox/tab-sync/index.d.ts +120 -0
  715. package/dist/sandbox/tab-sync/index.d.ts.map +1 -0
  716. package/dist/sandbox/tab-sync/index.js +272 -0
  717. package/dist/sandbox/tab-sync/index.js.map +1 -0
  718. package/dist/sandbox/types.d.ts +1235 -0
  719. package/dist/sandbox/types.d.ts.map +1 -0
  720. package/dist/sandbox/types.js +46 -0
  721. package/dist/sandbox/types.js.map +1 -0
  722. package/dist/storage/admin/api.d.ts +206 -0
  723. package/dist/storage/admin/api.d.ts.map +1 -0
  724. package/dist/storage/admin/api.js +349 -0
  725. package/dist/storage/admin/api.js.map +1 -0
  726. package/dist/storage/admin/handler.d.ts +29 -0
  727. package/dist/storage/admin/handler.d.ts.map +1 -0
  728. package/dist/storage/admin/handler.js +82 -0
  729. package/dist/storage/admin/handler.js.map +1 -0
  730. package/dist/storage/admin/spec.d.ts +64 -0
  731. package/dist/storage/admin/spec.d.ts.map +1 -0
  732. package/dist/storage/admin/spec.js +8 -0
  733. package/dist/storage/admin/spec.js.map +1 -0
  734. package/dist/storage/admin/tools.d.ts +23 -0
  735. package/dist/storage/admin/tools.d.ts.map +1 -0
  736. package/dist/storage/admin/tools.js +90 -0
  737. package/dist/storage/admin/tools.js.map +1 -0
  738. package/dist/storage/download.d.ts +27 -0
  739. package/dist/storage/download.d.ts.map +1 -0
  740. package/dist/storage/download.js +136 -0
  741. package/dist/storage/download.js.map +1 -0
  742. package/dist/storage/enforce.d.ts +26 -0
  743. package/dist/storage/enforce.d.ts.map +1 -0
  744. package/dist/storage/enforce.js +16 -0
  745. package/dist/storage/enforce.js.map +1 -0
  746. package/dist/storage/errors.d.ts +46 -0
  747. package/dist/storage/errors.d.ts.map +1 -0
  748. package/dist/storage/errors.js +62 -0
  749. package/dist/storage/errors.js.map +1 -0
  750. package/dist/storage/index.d.ts +49 -0
  751. package/dist/storage/index.d.ts.map +1 -0
  752. package/dist/storage/index.js +60 -0
  753. package/dist/storage/index.js.map +1 -0
  754. package/dist/storage/internal.d.ts +18 -0
  755. package/dist/storage/internal.d.ts.map +1 -0
  756. package/dist/storage/internal.js +18 -0
  757. package/dist/storage/internal.js.map +1 -0
  758. package/dist/storage/list.d.ts +17 -0
  759. package/dist/storage/list.d.ts.map +1 -0
  760. package/dist/storage/list.js +91 -0
  761. package/dist/storage/list.js.map +1 -0
  762. package/dist/storage/metadata.d.ts +86 -0
  763. package/dist/storage/metadata.d.ts.map +1 -0
  764. package/dist/storage/metadata.js +167 -0
  765. package/dist/storage/metadata.js.map +1 -0
  766. package/dist/storage/persistence.d.ts +134 -0
  767. package/dist/storage/persistence.d.ts.map +1 -0
  768. package/dist/storage/persistence.js +132 -0
  769. package/dist/storage/persistence.js.map +1 -0
  770. package/dist/storage/reference.d.ts +63 -0
  771. package/dist/storage/reference.d.ts.map +1 -0
  772. package/dist/storage/reference.js +156 -0
  773. package/dist/storage/reference.js.map +1 -0
  774. package/dist/storage/rules.d.ts +176 -0
  775. package/dist/storage/rules.d.ts.map +1 -0
  776. package/dist/storage/rules.js +621 -0
  777. package/dist/storage/rules.js.map +1 -0
  778. package/dist/storage/service.d.ts +139 -0
  779. package/dist/storage/service.d.ts.map +1 -0
  780. package/dist/storage/service.js +241 -0
  781. package/dist/storage/service.js.map +1 -0
  782. package/dist/storage/upload.d.ts +43 -0
  783. package/dist/storage/upload.d.ts.map +1 -0
  784. package/dist/storage/upload.js +235 -0
  785. package/dist/storage/upload.js.map +1 -0
  786. package/package.json +109 -3
@@ -0,0 +1,1536 @@
1
+ /**
2
+ * `pyric/firestore` — modular Web-SDK Firestore adapter for the
3
+ * Pyric sandbox.
4
+ *
5
+ * Mirrors `firebase/firestore`'s tree-shakable free-function shape:
6
+ * `getDoc`, `setDoc`, `addDoc`, `query`, `where`, `orderBy`, `limit`,
7
+ * `onSnapshot`, `runTransaction`, etc. All operations route to one of
8
+ * two backends, picked at init time:
9
+ *
10
+ * - **Sandbox target** — wraps `pyric-admin`'s chainable adapter,
11
+ * which sits on `pyric/sandbox`'s `LocalEnvironment`. No
12
+ * network. Identity comes from a `SandboxContext`.
13
+ * - **Prod target** — wraps `firebase/firestore` against a real
14
+ * Firebase project. Identity comes from `firebase/auth`'s
15
+ * current user.
16
+ *
17
+ * Same call surface across both. Agent code that writes against the
18
+ * sandbox during iteration runs unmodified against prod at deploy.
19
+ *
20
+ * The dual-target machinery lives in two pieces:
21
+ * - {@link TARGET_SYMBOL} — hidden brand on every {@link Firestore}
22
+ * handle that names the active backend.
23
+ * - {@link refToTarget} — WeakMap from refs/queries back to their
24
+ * owning target, so chained calls (`query(coll, where(...))`,
25
+ * `getDocs(q)`) recover routing without re-tagging at every step.
26
+ *
27
+ * Every public function has a `target.kind` switch with explicit
28
+ * branches — the structure is parallel and grep-friendly.
29
+ */
30
+ import { getFirestore as getChainableFirestore, getAdminFirestore as getChainableAdminFirestore, Timestamp as ChainTimestamp, FieldValue as ChainFieldValue, FOLLOWS_CURRENT_USER, SandboxError, } from 'pyric/sandbox/admin-firestore';
31
+ import { SandboxContextImpl } from 'pyric/sandbox';
32
+ import { getInternalEnv } from 'pyric/sandbox/internal';
33
+ import { Bytes as RulesBytes, LatLng as RulesLatLng, Vector as RulesVector, lintFirestoreRules, } from 'pyric/rules';
34
+ import * as fb from 'firebase/firestore';
35
+ // Phase 3 unified app handle. Adapter dispatch reads `APP_TARGET` and
36
+ // routes to the existing direct-handle path (sandbox vs prod).
37
+ import { APP_TARGET } from 'pyric/app';
38
+ // ─── Branding + routing ───────────────────────────────────────────────
39
+ /**
40
+ * Hidden property on every {@link Firestore} handle. Discriminates
41
+ * between sandbox and prod backends so free functions can route
42
+ * without consumer-visible API differences.
43
+ */
44
+ export const TARGET_SYMBOL = Symbol('pyric/firestore/target');
45
+ /** True for both sandbox variants — used at dispatch sites that share a
46
+ * branch (every sandbox op routes the same way once we've resolved the
47
+ * chainable handle). The prod branch is the complement. */
48
+ function isSandboxKind(target) {
49
+ return target.kind === 'sandbox' || target.kind === 'sandbox-live';
50
+ }
51
+ /**
52
+ * Resolve a sandbox-flavored target to its chainable Firestore handle.
53
+ *
54
+ * `sandbox` targets carry a frozen handle from `getFirestore(ctx)` time;
55
+ * `sandbox-live` targets build a fresh handle bound to the sandbox's
56
+ * current `currentUser` at every call. Callers that issue multiple ops
57
+ * in a row should cache the result into a local — every `getDb()` call
58
+ * rebuilds the chainable, which is correct but wasteful when the auth
59
+ * state hasn't moved between adjacent ops.
60
+ */
61
+ function sandboxDb(target) {
62
+ return target.kind === 'sandbox' ? target.db : target.getDb();
63
+ }
64
+ /**
65
+ * Map from refs / queries to their owning target. Populated by every
66
+ * factory + chaining operation; consulted by every free function.
67
+ *
68
+ * WeakMap keys let entries GC alongside the refs that produced them.
69
+ */
70
+ const refToTarget = new WeakMap();
71
+ function tag(obj, target) {
72
+ refToTarget.set(obj, target);
73
+ return obj;
74
+ }
75
+ /**
76
+ * For `sandbox-live` refs/queries, record a closure that rebuilds the
77
+ * chainable ref against a fresh `SandboxFirestore` handle. Used by
78
+ * every dispatch site to re-resolve the ref under the sandbox's
79
+ * *current* `currentUser` rather than the auth that was active when
80
+ * the ref was first built.
81
+ *
82
+ * Necessary because `pyric-admin`'s chainable refs capture auth at
83
+ * construction time — calling `chainRef.get()` on a held ref runs
84
+ * under the auth from when the ref was built, not from when the op
85
+ * fires. For sandbox-live, every op must construct a fresh chainable
86
+ * ref bound to the current ctx before issuing the op.
87
+ *
88
+ * The closure form supports the full chain: `doc(coll, id)` rebuilds
89
+ * via `rebuild(coll)(db).doc(id)`; `query(coll, where(...))` rebuilds
90
+ * via `apply(constraints)` over the rebuilt source. Stored only for
91
+ * sandbox-live refs — the `sandbox` (frozen-ctx) and `prod` paths
92
+ * keep their existing held-ref semantics.
93
+ */
94
+ const sandboxLiveRebuild = new WeakMap();
95
+ function tagLive(obj, target, rebuild) {
96
+ refToTarget.set(obj, target);
97
+ sandboxLiveRebuild.set(obj, rebuild);
98
+ return obj;
99
+ }
100
+ /** Resolve the chainable doc ref to use for an op against `ref`. For
101
+ * `sandbox`, returns the held chainable; for `sandbox-live`, runs
102
+ * the recorded rebuild closure against a fresh handle. */
103
+ function chainDocFor(target, ref) {
104
+ const underlying = underlyingOf(ref);
105
+ if (target.kind === 'sandbox')
106
+ return underlying;
107
+ const rebuild = sandboxLiveRebuild.get(underlying);
108
+ if (!rebuild) {
109
+ throw new TypeError('pyric/firestore: live ref missing rebuild closure — was it produced by a factory in this package?');
110
+ }
111
+ return rebuild(sandboxDb(target));
112
+ }
113
+ /** Same as {@link chainDocFor} but typed for collection refs. */
114
+ function chainCollFor(target, ref) {
115
+ const underlying = underlyingOf(ref);
116
+ if (target.kind === 'sandbox')
117
+ return underlying;
118
+ const rebuild = sandboxLiveRebuild.get(underlying);
119
+ if (!rebuild) {
120
+ throw new TypeError('pyric/firestore: live collection ref missing rebuild closure.');
121
+ }
122
+ return rebuild(sandboxDb(target));
123
+ }
124
+ /** Same as {@link chainDocFor} but typed for queries. */
125
+ function chainQueryFor(target, q) {
126
+ const underlying = underlyingOf(q);
127
+ if (target.kind === 'sandbox')
128
+ return underlying;
129
+ const rebuild = sandboxLiveRebuild.get(underlying);
130
+ if (!rebuild) {
131
+ throw new TypeError('pyric/firestore: live query missing rebuild closure.');
132
+ }
133
+ return rebuild(sandboxDb(target));
134
+ }
135
+ /**
136
+ * Tag a sandbox-flavored ref + (optionally) record its rebuild
137
+ * closure. Routes to {@link tag} for frozen-ctx targets and
138
+ * {@link tagLive} for live targets. Centralizes the per-factory
139
+ * "record rebuild on live, plain tag on frozen" decision.
140
+ */
141
+ function tagSandboxRef(obj, target, rebuild) {
142
+ if (target.kind === 'sandbox-live') {
143
+ return tagLive(obj, target, rebuild);
144
+ }
145
+ return tag(obj, target);
146
+ }
147
+ /** Resolve a sandbox-flavored parent's rebuild closure. For
148
+ * sandbox-live, the parent must have been tagged via
149
+ * {@link tagSandboxRef}; for sandbox, returns a no-op stand-in
150
+ * since the held chainable is the only ref the dispatch sites use.
151
+ *
152
+ * Used by chaining factories (`doc(coll, …)`, `collection(doc, …)`,
153
+ * `query(coll, …)`) to derive a rebuild that reads the parent's
154
+ * rebuild and applies one more step. */
155
+ function parentRebuild(parent) {
156
+ const underlying = underlyingOf(parent);
157
+ const fn = sandboxLiveRebuild.get(underlying);
158
+ if (fn)
159
+ return fn;
160
+ // Frozen-ctx target. The chainable parent ref is bound to the same
161
+ // ctx for life; returning it as-is is correct because frozen-ctx
162
+ // chaining is what `pyric-admin`'s adapter already supports.
163
+ return () => underlying;
164
+ }
165
+ function targetOf(refOrDb) {
166
+ if (TARGET_SYMBOL in refOrDb) {
167
+ return refOrDb[TARGET_SYMBOL];
168
+ }
169
+ const t = refToTarget.get(refOrDb);
170
+ if (!t) {
171
+ throw new TypeError('pyric/firestore: unrecognized reference — was it produced by a factory in this package?');
172
+ }
173
+ return t;
174
+ }
175
+ const refToConverter = new WeakMap();
176
+ const refToUnderlying = new WeakMap();
177
+ function converterOf(obj) {
178
+ return refToConverter.get(obj);
179
+ }
180
+ /** Resolve to the plain chain / fb ref. `withConverter` shells point
181
+ * at the underlying; everything else is its own underlying. */
182
+ function underlyingOf(obj) {
183
+ return refToUnderlying.get(obj) ?? obj;
184
+ }
185
+ /** Build a new sandbox shell that points at `underlying` and carries
186
+ * `converter`. Inherits routing + identity (id/path) from underlying.
187
+ *
188
+ * Accepts both `SandboxTarget` and `SandboxLiveTarget`: the shell
189
+ * exists at the modular layer (above the chainable), so it only
190
+ * needs to record routing for `targetOf` and the underlying pointer
191
+ * for `underlyingOf`. The chain re-resolution (under current auth)
192
+ * happens at op time via {@link chainDocFor} / {@link chainCollFor}
193
+ * / {@link chainQueryFor} on the underlying. */
194
+ function buildSandboxShell(underlying, target, converter) {
195
+ const shell = {
196
+ id: underlying.id ?? '',
197
+ path: underlying.path ?? '',
198
+ };
199
+ refToConverter.set(shell, converter);
200
+ refToUnderlying.set(shell, underlying);
201
+ refToTarget.set(shell, target);
202
+ return shell;
203
+ }
204
+ export function getFirestore(target) {
205
+ // PyricApp dispatch: inspect the brand and forward to the existing
206
+ // direct-handle path. Kept ABOVE the legacy probes so a sandbox-app
207
+ // handle (which structurally carries `sandbox` + the brand symbol)
208
+ // routes through the unified path, not the structural sandbox sniff.
209
+ if (isPyricApp(target)) {
210
+ return target[APP_TARGET] === 'sandbox'
211
+ ? getFirestore(target.sandbox)
212
+ : getFirestore(target.firebaseApp);
213
+ }
214
+ if (isSandboxContext(target)) {
215
+ const chainable = getChainableFirestore(target);
216
+ const t = { kind: 'sandbox', db: chainable, sandbox: target.sandbox };
217
+ return { [TARGET_SYMBOL]: t };
218
+ }
219
+ if (isSandbox(target)) {
220
+ const t = {
221
+ kind: 'sandbox-live',
222
+ sandbox: target,
223
+ getDb: makeGetDb(target),
224
+ };
225
+ return { [TARGET_SYMBOL]: t };
226
+ }
227
+ const fbDb = fb.getFirestore(target);
228
+ const t = { kind: 'prod', db: fbDb };
229
+ return { [TARGET_SYMBOL]: t };
230
+ }
231
+ /**
232
+ * A Firestore handle scoped to a specific identity, for multi-user testing.
233
+ *
234
+ * `actingAs(sandbox, { uid })` returns a `Firestore` whose ops evaluate security
235
+ * rules as that user (`request.auth.uid === uid`; custom claims via `token`);
236
+ * `actingAs(sandbox, null)` is the anonymous (signed-out) path. Multiple
237
+ * identities over ONE sandbox share the same store, so a write by one is
238
+ * delivered to another's `onSnapshot`: the basis for multi-user sync testing.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * import { initializeSandbox } from 'pyric/sandbox';
243
+ * import { actingAs, doc, setDoc, onSnapshot } from 'pyric/firestore';
244
+ * const sandbox = initializeSandbox();
245
+ * const alice = actingAs(sandbox, { uid: 'alice' });
246
+ * const bob = actingAs(sandbox, { uid: 'bob', token: { role: 'member' } });
247
+ * onSnapshot(doc(bob, 'rooms/r1'), () => {
248
+ * // fires when alice writes (same store; rules evaluated as bob)
249
+ * });
250
+ * await setDoc(doc(alice, 'rooms/r1'), { owner: 'alice' });
251
+ * ```
252
+ *
253
+ * Thin sugar over `getFirestore(sandbox.withAuth(identity))`; the value is a
254
+ * named, discoverable seam for multi-user scenarios. See
255
+ * the design rationale.
256
+ */
257
+ export function actingAs(sandbox, identity) {
258
+ return getFirestore(sandbox.withAuth(identity));
259
+ }
260
+ export function getAdminFirestore(target) {
261
+ // Sandbox-backed PyricApp → unwrap to its Sandbox. A prod-backed app has
262
+ // no admin-bypass analog, so reject it loudly rather than silently
263
+ // returning a rules-enforced prod handle.
264
+ if (isPyricApp(target)) {
265
+ if (target[APP_TARGET] !== 'sandbox') {
266
+ throw new TypeError('getAdminFirestore: the admin (rules-bypass) lens is sandbox-only — ' +
267
+ 'a prod-backed app has no way to bypass deployed security rules.');
268
+ }
269
+ return getAdminFirestore(target.sandbox);
270
+ }
271
+ const sandbox = isSandboxContext(target) ? target.sandbox : target;
272
+ const chainable = getChainableAdminFirestore(sandbox);
273
+ const t = { kind: 'sandbox', db: chainable, sandbox };
274
+ return { [TARGET_SYMBOL]: t };
275
+ }
276
+ /**
277
+ * Brand-based test for the {@link PyricApp} overload. Reads the
278
+ * `APP_TARGET` symbol that `pyric/app`'s `initializeApp` stamps on
279
+ * every handle. Cheap + collision-free: a `Sandbox` / `FirebaseApp`
280
+ * / `SandboxContext` will never carry this symbol.
281
+ */
282
+ function isPyricApp(target) {
283
+ return (target !== null
284
+ && typeof target === 'object'
285
+ && APP_TARGET in target);
286
+ }
287
+ /**
288
+ * Brand-based test for the SandboxContext overload. Uses
289
+ * `instanceof SandboxContextImpl` for robustness — structural
290
+ * dispatch would silently break if `SandboxContext`'s shape ever
291
+ * changed, or if a future `FirebaseApp` grew a `withAuth` method.
292
+ *
293
+ * Internal — consumers always call `getFirestore` and let the
294
+ * dispatch figure it out.
295
+ */
296
+ function isSandboxContext(target) {
297
+ return target instanceof SandboxContextImpl;
298
+ }
299
+ /**
300
+ * Structural test for the `Sandbox` overload. The class is internal
301
+ * to `pyric/sandbox` (not exported), so we recognize it by the
302
+ * presence of `currentUser` + `onCurrentUserChanged` + `withAuth` +
303
+ * `admin` — the four members that distinguish `Sandbox` from
304
+ * `SandboxContext` (which has `withAuth` only) and `FirebaseApp`
305
+ * (which has none). Tightening to a brand symbol would require an
306
+ * `pyric/sandbox` change; structural recognition is safe here
307
+ * because every member of this set has been on `Sandbox` since v0
308
+ * and `FirebaseApp` would have to grow all four to collide.
309
+ */
310
+ function isSandbox(target) {
311
+ if (target === null || typeof target !== 'object')
312
+ return false;
313
+ const o = target;
314
+ return (typeof o.withAuth === 'function'
315
+ && typeof o.onCurrentUserChanged === 'function'
316
+ && 'currentUser' in o
317
+ && 'admin' in o);
318
+ }
319
+ /**
320
+ * Build the per-call ctx-resolver for a `sandbox-live` target. Each
321
+ * call reads `sandbox.currentUser` (the source of truth that
322
+ * `pyric/auth` writes through to), constructs a fresh
323
+ * `SandboxContext`, and returns the chainable Firestore handle bound
324
+ * to it. Constructing per-call is cheap — `SandboxContext` is a tiny
325
+ * object — and `pyric-admin`'s `getFirestore(ctx)` caches by ctx
326
+ * identity via WeakMap, so the chainable is collected once the ctx
327
+ * is.
328
+ *
329
+ * `withAuth(null)` is the anonymous path; rules evaluate with
330
+ * `request.auth == null`, matching production Firebase Auth's
331
+ * "signed out" state.
332
+ */
333
+ function makeGetDb(sandbox) {
334
+ return () => {
335
+ const ctx = sandbox.withAuth(sandbox.currentUser);
336
+ return getChainableFirestore(ctx);
337
+ };
338
+ }
339
+ export { SandboxError };
340
+ // ─── Tier 1: scalar types + sentinels re-exported from firebase ──────
341
+ //
342
+ // These types are class shapes (or sentinels) the `firebase/firestore`
343
+ // modular SDK exposes. They're shared across both targets in the
344
+ // sense that:
345
+ //
346
+ // - **Prod target** uses them natively (round-trips through the
347
+ // wire encoder, server stores them as proper Firestore values).
348
+ // - **Sandbox target** rides `Bytes`, `GeoPoint`, and `VectorValue`
349
+ // through their converters at `sandbox/firestore/converters/` on
350
+ // write, then finalizes the read back to `fb.Bytes` / `fb.GeoPoint`
351
+ // / `fb.VectorValue` in {@link finalizeSandboxValue} so consumer
352
+ // code's `instanceof` checks match prod. `vector()` / `VectorValue`
353
+ // are re-exported from `firebase/firestore` like the others.
354
+ //
355
+ // `documentId()` and `FieldPath` ARE supported on both sides today —
356
+ // `documentId()` returns a `FieldPath` sentinel that the chainable
357
+ // adapter recognizes as "by document id" when passed to `where()`.
358
+ export { Bytes, GeoPoint, documentId, FieldPath, vector, VectorValue, } from 'firebase/firestore';
359
+ // ─── Tier 1: equality helpers (target-aware) ─────────────────────────
360
+ /**
361
+ * Structural equality for two refs / queries / snapshots. The
362
+ * native helpers (`fb.refEqual` etc.) only handle prod-shape values;
363
+ * for sandbox-shape values we fall back to a fullPath / toString
364
+ * comparison that matches the JS SDK's semantics. Throws when the
365
+ * pair routes to different targets — that's a programming error.
366
+ */
367
+ /**
368
+ * Routing match for equality helpers. Sandbox and sandbox-live are
369
+ * compatible at the equality layer — two refs that name the same
370
+ * path are equal regardless of whether their owning Firestore
371
+ * handle was built from a frozen `SandboxContext` or a live
372
+ * `Sandbox`. The structural comparison falls back to `fullPath` /
373
+ * `toString` per the JS SDK's semantics, which works uniformly
374
+ * across both shapes.
375
+ *
376
+ * Crossing sandbox vs prod is still a programming error and throws.
377
+ */
378
+ function targetMatch(a, b) {
379
+ const ta = targetOf(a);
380
+ const tb = targetOf(b);
381
+ const aSandbox = isSandboxKind(ta);
382
+ const bSandbox = isSandboxKind(tb);
383
+ if (aSandbox !== bSandbox) {
384
+ throw new TypeError('pyric/firestore: cannot compare references / queries / snapshots across different targets.');
385
+ }
386
+ return ta;
387
+ }
388
+ /** True when two `DocumentReference`s point at the same path under
389
+ * the same target. */
390
+ export function refEqual(a, b) {
391
+ const t = targetMatch(a, b);
392
+ if (t.kind === 'prod')
393
+ return fb.refEqual(asFbDoc(a), asFbDoc(b));
394
+ // Sandbox + sandbox-live both compare by path — `underlyingOf` peels
395
+ // any `withConverter` shell so a typed ref equals its underlying.
396
+ return underlyingOf(a).path
397
+ === underlyingOf(b).path;
398
+ }
399
+ /** True when two `Query`s are structurally identical (same source +
400
+ * same constraint chain). */
401
+ export function queryEqual(a, b) {
402
+ const t = targetMatch(a, b);
403
+ if (t.kind === 'prod')
404
+ return fb.queryEqual(asFbQuery(a), asFbQuery(b));
405
+ // Sandbox-target queries don't expose a deep-equality contract;
406
+ // fall back to identity. Most consumer code that asks `queryEqual`
407
+ // is comparing the same returned object against a cached one,
408
+ // which identity handles.
409
+ return a === b;
410
+ }
411
+ /** True when two `DocumentSnapshot` / `QuerySnapshot` pairs describe
412
+ * the same underlying data + path. */
413
+ export function snapshotEqual(a, b) {
414
+ const t = targetMatch(a, b);
415
+ if (t.kind === 'prod') {
416
+ return fb.snapshotEqual(a, b);
417
+ }
418
+ return a === b;
419
+ }
420
+ // ─── Tier 1: emulator connect (prod-target only) ─────────────────────
421
+ /**
422
+ * Point the prod-target Firestore handle at the Firestore emulator.
423
+ * Must be called BEFORE any operation runs against `db`. No-op on
424
+ * sandbox-target handles (the sandbox IS a local emulator).
425
+ *
426
+ * `options` mirrors `firebase/firestore`'s shape — pass
427
+ * `{ mockUserToken: '…' }` to bypass auth checks under the emulator,
428
+ * matching how the JS SDK lets tests skip Firebase Auth.
429
+ */
430
+ export function connectFirestoreEmulator(db, host, port, options) {
431
+ const target = targetOf(db);
432
+ if (isSandboxKind(target)) {
433
+ // The sandbox already runs locally; pointing it at an emulator
434
+ // host is a no-op rather than an error so consumer code that
435
+ // does the wiring unconditionally compiles against both targets.
436
+ return;
437
+ }
438
+ fb.connectFirestoreEmulator(target.db, host, port, options);
439
+ }
440
+ // Shorthand: cast helpers used inside route arms. The runtime objects
441
+ // match these shapes precisely; the casts let the routing keep its
442
+ // single-typed public surface.
443
+ function asChainDoc(r) { return r; }
444
+ function asChainColl(r) { return r; }
445
+ function asChainQuery(r) { return r; }
446
+ function asFbDoc(r) { return r; }
447
+ function asFbColl(r) { return r; }
448
+ function asFbQuery(r) { return r; }
449
+ // ─── Path constructors ────────────────────────────────────────────────
450
+ export function doc(parent, ...pathSegments) {
451
+ const target = targetOf(parent);
452
+ const isHandle = TARGET_SYMBOL in parent;
453
+ // Propagate any converter from a typed collection to the resulting
454
+ // doc — `doc(coll<User>, 'u1')` returns `DocumentReference<User>`.
455
+ // For prod, fb's `.doc()` chain inherits the converter natively, so
456
+ // this only needs to fire on the sandbox side.
457
+ const conv = isHandle ? undefined : converterOf(parent);
458
+ if (isSandboxKind(target)) {
459
+ const db = sandboxDb(target);
460
+ if (isHandle) {
461
+ if (pathSegments.length === 0) {
462
+ throw new TypeError('doc(db, path) requires at least one path segment.');
463
+ }
464
+ const path = pathSegments.join('/');
465
+ const built = db.doc(path);
466
+ const tagged = tagSandboxRef(built, target, (fresh) => fresh.doc(path));
467
+ return tagged;
468
+ }
469
+ const coll = asChainColl(underlyingOf(parent));
470
+ const ref = pathSegments.length === 0
471
+ ? coll.doc()
472
+ : coll.doc(pathSegments.join('/'));
473
+ // The path is now known (either the explicit segments under the
474
+ // parent's path, or the auto-minted id appended). Rebuild from
475
+ // the absolute path so the live ref re-resolves under whatever
476
+ // auth the next op picks up.
477
+ const absPath = ref.path;
478
+ const tagged = tagSandboxRef(ref, target, (fresh) => fresh.doc(absPath));
479
+ if (conv) {
480
+ return buildSandboxShell(tagged, target, conv);
481
+ }
482
+ return tagged;
483
+ }
484
+ // prod
485
+ if (isHandle) {
486
+ if (pathSegments.length === 0) {
487
+ throw new TypeError('doc(db, path) requires at least one path segment.');
488
+ }
489
+ return tag(fb.doc(target.db, pathSegments.join('/')), target);
490
+ }
491
+ const coll = asFbColl(parent);
492
+ const ref = pathSegments.length === 0
493
+ ? fb.doc(coll)
494
+ : fb.doc(coll, pathSegments.join('/'));
495
+ return tag(ref, target);
496
+ }
497
+ /**
498
+ * Cross-collection query — scans every document under every
499
+ * collection whose final segment matches `collectionId`. Mirrors
500
+ * `firebase/firestore`'s `collectionGroup(db, id)` shape.
501
+ *
502
+ * Returned `Query` accepts the same `where` / `orderBy` / `limit`
503
+ * constraints as any other query.
504
+ */
505
+ export function collectionGroup(db, collectionId) {
506
+ const target = targetOf(db);
507
+ if (isSandboxKind(target)) {
508
+ const q = sandboxDb(target).collectionGroup(collectionId);
509
+ return tagSandboxRef(q, target, (fresh) => fresh.collectionGroup(collectionId));
510
+ }
511
+ const q = fb.collectionGroup(target.db, collectionId);
512
+ return tag(q, target);
513
+ }
514
+ export function collection(parent, ...pathSegments) {
515
+ const target = targetOf(parent);
516
+ const isHandle = TARGET_SYMBOL in parent;
517
+ if (pathSegments.length === 0) {
518
+ throw new TypeError('collection() requires at least one path segment.');
519
+ }
520
+ // Note: any converter on `parent` (typed DocumentReference<T>) does
521
+ // NOT propagate to the sub-collection — matches `firebase/firestore`'s
522
+ // `collection(typedDoc, path)` returning `CollectionReference<DocumentData>`.
523
+ // A parent doc's T describes its own data, not its subcollections'.
524
+ if (isSandboxKind(target)) {
525
+ if (isHandle) {
526
+ const path = pathSegments.join('/');
527
+ const built = sandboxDb(target).collection(path);
528
+ return tagSandboxRef(built, target, (fresh) => fresh.collection(path));
529
+ }
530
+ const docRef = asChainDoc(underlyingOf(parent));
531
+ const subPath = pathSegments.join('/');
532
+ const built = docRef.collection(subPath);
533
+ const absPath = built.path;
534
+ return tagSandboxRef(built, target, (fresh) => fresh.collection(absPath));
535
+ }
536
+ // prod
537
+ if (isHandle) {
538
+ return tag(fb.collection(target.db, pathSegments.join('/')), target);
539
+ }
540
+ const docRef = asFbDoc(parent);
541
+ return tag(fb.collection(docRef, pathSegments.join('/')), target);
542
+ }
543
+ // ─── Reads ────────────────────────────────────────────────────────────
544
+ function isNumberArray(a) {
545
+ return Array.isArray(a) && a.every((n) => typeof n === 'number');
546
+ }
547
+ /**
548
+ * A vector read from the sandbox may arrive as a live `Vector` wrapper, a
549
+ * prototype-stripped plain object (`{typeName:'vector', value}`), or the wire
550
+ * sentinel (`{__type__:'__vector__', value}`). Duck-type all three; `instanceof`
551
+ * alone misses the stripped form the read path hands back. Returns the
552
+ * components, or null if `value` isn't a vector.
553
+ */
554
+ function vectorValuesOf(value) {
555
+ if (value instanceof RulesVector)
556
+ return Array.from(value.value);
557
+ if (value === null || typeof value !== 'object')
558
+ return null;
559
+ const o = value;
560
+ if (o.typeName === 'vector' && isNumberArray(o.value))
561
+ return o.value;
562
+ if (o.__type__ === '__vector__' && isNumberArray(o.value))
563
+ return o.value;
564
+ return null;
565
+ }
566
+ /**
567
+ * Final read-path translation for sandbox-target snapshots.
568
+ *
569
+ * The admin-compat read-path walker (in `pyric/sandbox`'s admin-compat
570
+ * `snapshots.ts`) leaves `pyric/rules` wrappers (`Bytes`,
571
+ * `LatLng`) as identity — it can't translate them to
572
+ * `firebase/firestore`'s `Bytes` / `GeoPoint` because the admin-compat
573
+ * layer doesn't depend on `firebase/firestore`.
574
+ *
575
+ * This package does, so we do the final hop here: walk the value tree
576
+ * and convert any rules-wrapper instance into its `firebase/firestore`
577
+ * counterpart so consumer code can rely on
578
+ * `data.b instanceof fb.Bytes === true` and
579
+ * `data.g instanceof fb.GeoPoint === true` against sandbox reads —
580
+ * matching the prod-target invariant.
581
+ *
582
+ * Closes firestore COMPAT rows #109 (`Bytes`) and #110 (`GeoPoint`).
583
+ * Vectors (#111, `VectorValue` / `vector()`) round-trip the same way as of
584
+ * Phase 0-F; the formal COMPAT ✓ + oracle observation land with the
585
+ * conformance phase (Phase 5b). Vector SEARCH (`findNearest`) is separate.
586
+ */
587
+ function finalizeSandboxValue(value) {
588
+ if (value instanceof RulesBytes) {
589
+ return fb.Bytes.fromUint8Array(value.data);
590
+ }
591
+ if (value instanceof RulesLatLng) {
592
+ return new fb.GeoPoint(value.lat, value.lng);
593
+ }
594
+ const vectorValues = vectorValuesOf(value);
595
+ if (vectorValues) {
596
+ return fb.vector(vectorValues);
597
+ }
598
+ if (Array.isArray(value)) {
599
+ return value.map(finalizeSandboxValue);
600
+ }
601
+ // Only walk plain objects (`{...}` literals + `Object.create(null)`).
602
+ // Class instances we don't recognize pass through as identity so we
603
+ // don't accidentally destructure their private state (the same gap
604
+ // that motivated #109/#110 in the first place).
605
+ if (value !== null && typeof value === 'object') {
606
+ const proto = Object.getPrototypeOf(value);
607
+ if (proto === Object.prototype || proto === null) {
608
+ const out = {};
609
+ for (const [k, v] of Object.entries(value)) {
610
+ out[k] = finalizeSandboxValue(v);
611
+ }
612
+ return out;
613
+ }
614
+ }
615
+ return value;
616
+ }
617
+ function finalizeSandboxData(data) {
618
+ if (data === undefined)
619
+ return undefined;
620
+ return finalizeSandboxValue(data);
621
+ }
622
+ /**
623
+ * Wrap a raw sandbox `DocumentSnapshot` so `.data()` runs the final
624
+ * value translation (rules wrappers → `firebase/firestore` types).
625
+ * Other surface (id / ref / exists) passes through unchanged so
626
+ * `tagSnapshotRefs` still operates on the original snap object.
627
+ */
628
+ function wrapSandboxDocSnap(snap) {
629
+ const s = snap;
630
+ const original = s.data.bind(snap);
631
+ Object.defineProperty(s, 'data', {
632
+ value: () => finalizeSandboxData(original()),
633
+ configurable: true,
634
+ writable: true,
635
+ });
636
+ return snap;
637
+ }
638
+ /**
639
+ * Wrap a plain sandbox `DocumentSnapshot` so `.data()` runs the
640
+ * converter's `fromFirestore`. Identity / `exists` pass through.
641
+ */
642
+ function applyConverterToDocSnap(snap, conv) {
643
+ return {
644
+ id: snap.id,
645
+ exists: snap.exists,
646
+ data: () => {
647
+ // Sandbox snaps expose `exists` as a property (Admin shape).
648
+ const exists = typeof snap.exists === 'function'
649
+ ? snap.exists()
650
+ : snap.exists;
651
+ if (!exists)
652
+ return undefined;
653
+ const raw = finalizeSandboxData(snap.data());
654
+ // fromFirestore receives a QueryDocumentSnapshot-narrowed view —
655
+ // doc is known to exist at this branch, so `data()` returns the
656
+ // raw value (never undefined).
657
+ const queryDocSnap = {
658
+ id: snap.id,
659
+ exists: true,
660
+ data: () => raw,
661
+ };
662
+ return conv.fromFirestore(queryDocSnap);
663
+ },
664
+ };
665
+ }
666
+ export async function getDoc(ref) {
667
+ const target = targetOf(ref);
668
+ if (isSandboxKind(target)) {
669
+ const conv = converterOf(ref);
670
+ const snap = await chainDocFor(target, ref).get();
671
+ if (conv) {
672
+ return applyConverterToDocSnap(snap, conv);
673
+ }
674
+ // Normalize `.exists` to method form + tag the snap's ref so
675
+ // consumers get Firebase-modular-SDK shape uniformly.
676
+ tagSnapshotRefs(snap, target);
677
+ // Wrap .data() to translate `pyric/rules` wrappers
678
+ // (Bytes / LatLng) into `firebase/firestore` types so reads match
679
+ // prod's instanceof semantics. Closes firestore #109 + #110.
680
+ return wrapSandboxDocSnap(snap);
681
+ }
682
+ return fb.getDoc(asFbDoc(ref));
683
+ }
684
+ export async function getDocs(query) {
685
+ const target = targetOf(query);
686
+ if (isSandboxKind(target)) {
687
+ const conv = converterOf(query);
688
+ const snap = await chainQueryFor(target, query).get();
689
+ if (conv) {
690
+ const c = conv;
691
+ const wrappedDocs = snap.docs.map((d) => applyConverterToDocSnap(d, c));
692
+ return {
693
+ size: wrappedDocs.length,
694
+ empty: wrappedDocs.length === 0,
695
+ docs: wrappedDocs,
696
+ };
697
+ }
698
+ // Normalize each doc's `.exists` + tag the doc refs so consumer
699
+ // code targeting Firebase's modular SDK works uniformly.
700
+ tagSnapshotRefs(snap, target);
701
+ // Wrap each doc snap's .data() to translate rules-wrappers to
702
+ // `firebase/firestore` types — same parity hop as `getDoc` above.
703
+ const docs = snap.docs;
704
+ for (const d of docs)
705
+ wrapSandboxDocSnap(d);
706
+ return snap;
707
+ }
708
+ return fb.getDocs(asFbQuery(query));
709
+ }
710
+ export async function setDoc(ref, data, options) {
711
+ const target = targetOf(ref);
712
+ if (isSandboxKind(target)) {
713
+ const conv = converterOf(ref);
714
+ const payload = conv
715
+ ? conv.toFirestore(data)
716
+ : data;
717
+ // Pass `options` through verbatim — `ChainSetOptions` is structurally
718
+ // the same shape as our public `SetOptions` (sandbox layer adds an
719
+ // optional `auth` field we don't expose at the modular layer).
720
+ return chainDocFor(target, ref).set(payload, options);
721
+ }
722
+ // Prod refs that came through `withConverter` carry their converter
723
+ // on the fb ref itself — `fb.setDoc` invokes `toFirestore` natively.
724
+ if (options === undefined)
725
+ return fb.setDoc(asFbDoc(ref), data);
726
+ return fb.setDoc(asFbDoc(ref), data, options);
727
+ }
728
+ /**
729
+ * `updateDoc` does NOT run the converter. Matches `firebase/firestore`'s
730
+ * Web SDK shape — partial updates can target any subset of fields, so
731
+ * a translator built around a full `AppModelType` would be a type-shape
732
+ * mismatch. Use the underlying `DocumentData` view (`withConverter(ref,
733
+ * null)`) for typed-and-untyped mixed access if you need both styles
734
+ * against the same path.
735
+ */
736
+ export async function updateDoc(ref, data) {
737
+ const target = targetOf(ref);
738
+ if (isSandboxKind(target))
739
+ return chainDocFor(target, ref).update(data);
740
+ return fb.updateDoc(asFbDoc(ref), data);
741
+ }
742
+ export async function deleteDoc(ref) {
743
+ const target = targetOf(ref);
744
+ if (isSandboxKind(target))
745
+ return chainDocFor(target, ref).delete();
746
+ return fb.deleteDoc(asFbDoc(ref));
747
+ }
748
+ export async function addDoc(coll, data) {
749
+ const target = targetOf(coll);
750
+ if (isSandboxKind(target)) {
751
+ const conv = converterOf(coll);
752
+ const payload = conv
753
+ ? conv.toFirestore(data)
754
+ : data;
755
+ const ref = await chainCollFor(target, coll).add(payload);
756
+ // The freshly-minted doc has its auto-id path baked in. Record a
757
+ // rebuild closure for sandbox-live so subsequent ops on `ref`
758
+ // re-resolve against the current user (matches the doc()-factory
759
+ // semantics for explicitly-pathed refs).
760
+ const absPath = ref.path;
761
+ const tagged = tagSandboxRef(ref, target, (fresh) => fresh.doc(absPath));
762
+ // Propagate the collection's converter onto the freshly-created
763
+ // doc ref so `getDoc(addDocResult)` round-trips through the same
764
+ // typing without an extra `withConverter` call. Matches fb's
765
+ // native behavior.
766
+ if (conv) {
767
+ return buildSandboxShell(tagged, target, conv);
768
+ }
769
+ return tagged;
770
+ }
771
+ const ref = await fb.addDoc(asFbColl(coll), data);
772
+ return tag(ref, target);
773
+ }
774
+ export function withConverter(source, converter) {
775
+ const target = targetOf(source);
776
+ if (target.kind === 'prod') {
777
+ // fb's native withConverter is on every ref / query — applies the
778
+ // converter to all subsequent reads / writes through fb's own API.
779
+ const native = source;
780
+ const out = native.withConverter(converter);
781
+ return tag(out, target);
782
+ }
783
+ // Sandbox.
784
+ if (converter === null) {
785
+ // Strip — return the underlying plain ref. Falls back to `source`
786
+ // itself if it was never wrapped (no-op).
787
+ return underlyingOf(source);
788
+ }
789
+ const underlying = underlyingOf(source);
790
+ return buildSandboxShell(underlying, target, converter);
791
+ }
792
+ export function query(source, ...constraints) {
793
+ const target = targetOf(source);
794
+ const conv = converterOf(source);
795
+ if (isSandboxKind(target)) {
796
+ // Apply constraints to the source's chainable query. For
797
+ // sandbox-live we rebuild via the parent's rebuild closure under
798
+ // a transient handle so the resulting tagged query has a known
799
+ // shape; the rebuild closure we record below applies the same
800
+ // constraint chain against a *fresh* handle at op time.
801
+ const sourceRebuild = parentRebuild(source);
802
+ const buildAt = (db) => {
803
+ let q = sourceRebuild(db);
804
+ for (const c of constraints)
805
+ q = c.applySandbox(q);
806
+ return q;
807
+ };
808
+ const q = buildAt(sandboxDb(target));
809
+ const tagged = tagSandboxRef(q, target, (fresh) => buildAt(fresh));
810
+ // Propagate any converter from a typed source through the new query.
811
+ if (conv) {
812
+ return buildSandboxShell(tagged, target, conv);
813
+ }
814
+ return tagged;
815
+ }
816
+ let q = asFbQuery(source);
817
+ for (const c of constraints)
818
+ q = c.applyProd(q);
819
+ return tag(q, target);
820
+ }
821
+ export function where(field, op, value) {
822
+ const sandboxFilter = { kind: 'where', field, op, value };
823
+ const fbFilter = fb.where(field, op, value);
824
+ return {
825
+ applySandbox: (q) => q.where(field, op, value),
826
+ applyProd: (q) => fb.query(q, fbFilter),
827
+ _sandboxFilter: sandboxFilter,
828
+ _fbFilter: fbFilter,
829
+ };
830
+ }
831
+ /**
832
+ * OR composite — at least one of the inner constraints must match.
833
+ * Each argument must itself be a filter constraint (`where()`, or
834
+ * nested `or()` / `and()`); passing `orderBy()` or `limit()` here is
835
+ * a type error at runtime.
836
+ *
837
+ * Mirrors `firebase/firestore`'s `or(...filters)` shape.
838
+ */
839
+ export function or(...filters) {
840
+ return composite('or', filters);
841
+ }
842
+ /**
843
+ * AND composite. Same shape as `or()` but every inner constraint
844
+ * must match. Useful inside an `or()` to combine constraints that
845
+ * would otherwise be at the top level.
846
+ */
847
+ export function and(...filters) {
848
+ return composite('and', filters);
849
+ }
850
+ /**
851
+ * Build a composite QueryConstraint. Extracts each sub-constraint's
852
+ * sandbox + fb filter representations; throws when any input is a
853
+ * non-filter (`orderBy` / `limit`).
854
+ */
855
+ function composite(kind, filters) {
856
+ if (filters.length === 0) {
857
+ throw new TypeError(`pyric/firestore: ${kind}() requires at least one filter argument.`);
858
+ }
859
+ const sandboxSubs = [];
860
+ const fbSubs = [];
861
+ for (const c of filters) {
862
+ if (c._sandboxFilter === undefined || c._fbFilter === undefined) {
863
+ throw new TypeError(`pyric/firestore: ${kind}() received a non-filter constraint (orderBy / limit are not valid here).`);
864
+ }
865
+ sandboxSubs.push(c._sandboxFilter);
866
+ fbSubs.push(c._fbFilter);
867
+ }
868
+ const sandboxFilter = { kind, filters: sandboxSubs };
869
+ const fbFilter = kind === 'or' ? fb.or(...fbSubs) : fb.and(...fbSubs);
870
+ return {
871
+ applySandbox: (q) => q.applyFilter(sandboxFilter),
872
+ applyProd: (q) => fb.query(q, fbFilter),
873
+ _sandboxFilter: sandboxFilter,
874
+ _fbFilter: fbFilter,
875
+ };
876
+ }
877
+ export function orderBy(field, direction) {
878
+ return {
879
+ applySandbox: (q) => q.orderBy(field, direction),
880
+ applyProd: (q) => fb.query(q, fb.orderBy(field, direction)),
881
+ };
882
+ }
883
+ export function limit(n) {
884
+ return {
885
+ applySandbox: (q) => q.limit(n),
886
+ applyProd: (q) => fb.query(q, fb.limit(n)),
887
+ };
888
+ }
889
+ // ─── Cursor pagination + limitToLast (Tier 3) ────────────────────────
890
+ //
891
+ // Modular Web-SDK shape:
892
+ //
893
+ // query(coll,
894
+ // orderBy('priority'),
895
+ // startAfter(prevPagePriority),
896
+ // limit(10),
897
+ // );
898
+ //
899
+ // Each cursor factory accepts a positional values list (one per
900
+ // orderBy clause). For sandbox-target, the values pass straight into
901
+ // the chainable Query.startCursor / endCursor methods; for prod, we
902
+ // spread into `fb.startAt(...values)` etc. The DocumentSnapshot
903
+ // overload (`startAt(snapshot)`) lands in a follow-up commit.
904
+ /**
905
+ * Limit the query to the LAST `n` documents in the ordered result.
906
+ * Requires at least one `orderBy` on the query (production-aligned —
907
+ * the simulator throws at execute time without one).
908
+ */
909
+ export function limitToLast(n) {
910
+ return {
911
+ applySandbox: (q) => q.limitToLast(n),
912
+ applyProd: (q) => fb.query(q, fb.limitToLast(n)),
913
+ };
914
+ }
915
+ /**
916
+ * Heuristic for the snapshot overload: a single argument whose
917
+ * `.data` is a function. Both the chainable adapter's
918
+ * `AdminDocumentSnapshot` and `firebase/firestore`'s
919
+ * `DocumentSnapshot` expose `.data()` so this catches both targets
920
+ * cleanly. Falls back to the values-spread variant for everything
921
+ * else (including a single non-snapshot scalar arg, which is
922
+ * legitimate when the orderBy is on one field).
923
+ */
924
+ function isDocumentSnapshot(args) {
925
+ if (args.length !== 1)
926
+ return false;
927
+ const a = args[0];
928
+ return (a !== null &&
929
+ typeof a === 'object' &&
930
+ 'data' in a &&
931
+ typeof a.data === 'function');
932
+ }
933
+ export function startAt(...args) {
934
+ if (isDocumentSnapshot(args)) {
935
+ const snap = args[0];
936
+ return {
937
+ applySandbox: (q) => q.startCursorFromSnapshot(snap, true),
938
+ applyProd: (q) => fb.query(q, fb.startAt(snap)),
939
+ };
940
+ }
941
+ return {
942
+ applySandbox: (q) => q.startCursor(args, true),
943
+ applyProd: (q) => fb.query(q, fb.startAt(...args)),
944
+ };
945
+ }
946
+ export function startAfter(...args) {
947
+ if (isDocumentSnapshot(args)) {
948
+ const snap = args[0];
949
+ return {
950
+ applySandbox: (q) => q.startCursorFromSnapshot(snap, false),
951
+ applyProd: (q) => fb.query(q, fb.startAfter(snap)),
952
+ };
953
+ }
954
+ return {
955
+ applySandbox: (q) => q.startCursor(args, false),
956
+ applyProd: (q) => fb.query(q, fb.startAfter(...args)),
957
+ };
958
+ }
959
+ export function endAt(...args) {
960
+ if (isDocumentSnapshot(args)) {
961
+ const snap = args[0];
962
+ return {
963
+ applySandbox: (q) => q.endCursorFromSnapshot(snap, true),
964
+ applyProd: (q) => fb.query(q, fb.endAt(snap)),
965
+ };
966
+ }
967
+ return {
968
+ applySandbox: (q) => q.endCursor(args, true),
969
+ applyProd: (q) => fb.query(q, fb.endAt(...args)),
970
+ };
971
+ }
972
+ export function endBefore(...args) {
973
+ if (isDocumentSnapshot(args)) {
974
+ const snap = args[0];
975
+ return {
976
+ applySandbox: (q) => q.endCursorFromSnapshot(snap, false),
977
+ applyProd: (q) => fb.query(q, fb.endBefore(snap)),
978
+ };
979
+ }
980
+ return {
981
+ applySandbox: (q) => q.endCursor(args, false),
982
+ applyProd: (q) => fb.query(q, fb.endBefore(...args)),
983
+ };
984
+ }
985
+ /** Factory: count() aggregate. */
986
+ export function count() {
987
+ return { kind: 'count' };
988
+ }
989
+ /** Factory: sum-of-`field` aggregate. */
990
+ export function sum(field) {
991
+ return { kind: 'sum', field };
992
+ }
993
+ /** Factory: average-of-`field` aggregate. */
994
+ export function average(field) {
995
+ return { kind: 'average', field };
996
+ }
997
+ /**
998
+ * Count documents matching the query. Returns a snapshot whose
999
+ * `.data()` yields `{ count: N }` — same shape `firebase/firestore`'s
1000
+ * `getCountFromServer` produces.
1001
+ */
1002
+ export async function getCountFromServer(source) {
1003
+ const target = targetOf(source);
1004
+ if (isSandboxKind(target)) {
1005
+ const snap = await chainQueryFor(target, source).aggregate({ count: { kind: 'count' } });
1006
+ const data = snap.data();
1007
+ return { data: () => ({ count: (data.count ?? 0) }) };
1008
+ }
1009
+ const snap = await fb.getCountFromServer(asFbQuery(source));
1010
+ return { data: () => ({ count: snap.data().count }) };
1011
+ }
1012
+ /**
1013
+ * Run a multi-field aggregate against the query. Spec entries are
1014
+ * keyed by caller-chosen aliases; the returned snapshot's `.data()`
1015
+ * uses the same keys.
1016
+ *
1017
+ * Sandbox target dispatches straight into the chainable adapter.
1018
+ * Prod target translates pyric's `AggregateField` shapes into
1019
+ * `firebase/firestore` AggregateField instances (`fb.count()`,
1020
+ * `fb.sum(...)`, `fb.average(...)`) before delegating.
1021
+ */
1022
+ export async function getAggregateFromServer(source, spec) {
1023
+ const target = targetOf(source);
1024
+ if (isSandboxKind(target)) {
1025
+ // The sandbox spec shape is structurally identical to ours, but
1026
+ // we re-construct so the type system sees `ChainAggregateSpec`
1027
+ // explicitly (avoids a chained cast at the call site).
1028
+ const chainSpec = {};
1029
+ for (const alias of Object.keys(spec))
1030
+ chainSpec[alias] = spec[alias];
1031
+ const snap = await chainQueryFor(target, source).aggregate(chainSpec);
1032
+ return { data: () => snap.data() };
1033
+ }
1034
+ // Prod — translate spec to firebase/firestore AggregateField objects.
1035
+ const fbSpec = {};
1036
+ for (const alias of Object.keys(spec)) {
1037
+ const f = spec[alias];
1038
+ if (f.kind === 'count')
1039
+ fbSpec[alias] = fb.count();
1040
+ else if (f.kind === 'sum')
1041
+ fbSpec[alias] = fb.sum(f.field);
1042
+ else
1043
+ fbSpec[alias] = fb.average(f.field);
1044
+ }
1045
+ const snap = await fb.getAggregateFromServer(asFbQuery(source), fbSpec);
1046
+ return { data: () => snap.data() };
1047
+ }
1048
+ export function onSnapshot(ref, arg2, arg3, arg4) {
1049
+ const target = targetOf(ref);
1050
+ if (isSandboxKind(target)) {
1051
+ // Resolve the chainable ref. For frozen sandbox, this is the held
1052
+ // chainable; for sandbox-live, build a fresh chainable bound to the
1053
+ // *current* `sandbox.currentUser` — the listener captures that
1054
+ // identity as its initial auth. For sandbox-live the listener also
1055
+ // FOLLOWS `currentUser`: a later sign-out / sign-in re-evaluates it
1056
+ // under the new auth (an auth-gated listener loses access on
1057
+ // sign-out, regains under a different signed-in user). This matches
1058
+ // production, which re-establishes the listen stream on a session
1059
+ // auth change. Frozen-ctx (`getFirestore(ctx)`) listeners stay
1060
+ // pinned to their chosen identity (admin/testing path).
1061
+ const r = resolveSandboxListenable(target, ref);
1062
+ // Wrap the next callback so any ref the snapshot carries (the
1063
+ // document's `.ref` for a doc snapshot; each doc's `.ref` for a
1064
+ // query snapshot) gets tagged in `refToTarget` before the consumer
1065
+ // ever sees it. Without this, `snap.ref` / `snap.docs[i].ref`
1066
+ // come back from the chainable adapter untagged, and the next
1067
+ // `onSnapshot(ref, …)` / `setDoc(ref, …)` throws
1068
+ // "pyric/firestore: unrecognized reference."
1069
+ const wrappedArgs = tagSandboxSnapshotArgs(arg2, arg3, arg4, target);
1070
+ // Thread the live-vs-frozen marker down to the chainable adapter so
1071
+ // it can set `addSnapshotListener`'s `followsCurrentUser` flag. The
1072
+ // adapter sees only a `SandboxContext` and can't recover the
1073
+ // distinction on its own; we stamp an internal symbol onto the
1074
+ // forwarded options object (prepending one when the call used the
1075
+ // callback-first form). The adapter reads + strips it.
1076
+ const finalArgs = markSandboxLiveSnapshotArgs(wrappedArgs, target);
1077
+ return finalArgs.length === 3
1078
+ ? r.onSnapshot(finalArgs[0], finalArgs[1], finalArgs[2])
1079
+ : finalArgs.length === 2
1080
+ ? r.onSnapshot(finalArgs[0], finalArgs[1])
1081
+ : r.onSnapshot(finalArgs[0]);
1082
+ }
1083
+ // prod — `firebase/firestore`'s onSnapshot takes the same arg shapes.
1084
+ // fb's modular SDK already returns refs that route through `targetOf`'s
1085
+ // `TARGET_SYMBOL` path (we set the symbol on each ref the package
1086
+ // exposes), so no wrapping is needed on the prod side.
1087
+ const fbRef = ref;
1088
+ // The fb overload set is wide — cast through `any` for the arg
1089
+ // forwarding; the types match at runtime.
1090
+ return arg4 !== undefined
1091
+ ? fb.onSnapshot(fbRef, arg2, arg3, arg4)
1092
+ : arg3 !== undefined
1093
+ ? fb.onSnapshot(fbRef, arg2, arg3)
1094
+ : fb.onSnapshot(fbRef, arg2);
1095
+ }
1096
+ /**
1097
+ * Pick the chainable object whose `.onSnapshot` we attach to. For
1098
+ * `sandbox`, this is just the underlying held chainable (existing
1099
+ * behavior). For `sandbox-live`, this is a fresh chainable rebuilt
1100
+ * against the current `sandbox.currentUser` — the listener captures
1101
+ * that identity as its initial auth. Unlike before, the live listener
1102
+ * then FOLLOWS `currentUser`: a later sign-out / sign-in re-evaluates
1103
+ * it under the new identity (wired via `FOLLOWS_CURRENT_USER` →
1104
+ * `LocalEnvironment.reevaluateLiveListeners`), so an auth-gated
1105
+ * listener loses access on sign-out without the caller having to
1106
+ * unsubscribe — matching production's listener re-establishment.
1107
+ */
1108
+ function resolveSandboxListenable(target, ref) {
1109
+ const underlying = underlyingOf(ref);
1110
+ if (target.kind === 'sandbox') {
1111
+ return underlying;
1112
+ }
1113
+ const rebuild = sandboxLiveRebuild.get(underlying);
1114
+ if (!rebuild) {
1115
+ throw new TypeError('pyric/firestore: live ref missing rebuild closure for onSnapshot.');
1116
+ }
1117
+ return rebuild(sandboxDb(target));
1118
+ }
1119
+ /**
1120
+ * Wrap the `next` callback in an `onSnapshot(…)` call so every ref the
1121
+ * snapshot carries is tagged into `refToTarget` before delivery. Returns
1122
+ * the rewritten arg tuple ready to forward to the underlying chainable
1123
+ * adapter. Only used for the sandbox target — prod refs from
1124
+ * `firebase/firestore` already carry `TARGET_SYMBOL`.
1125
+ */
1126
+ /**
1127
+ * FS-B14 — `true` when `obj` is a partial `onSnapshot` observer (an object
1128
+ * carrying at least one of `next` / `error` / `complete` as a function).
1129
+ * Mirrors `isPartialObserver` in `clones/.../api/observer.ts`. Used to keep
1130
+ * an `{ error: fn }` / `{ complete: fn }` observer from being misrouted as a
1131
+ * `SnapshotListenOptions` object.
1132
+ */
1133
+ function isPartialObserver(obj) {
1134
+ if (typeof obj !== 'object' || obj === null)
1135
+ return false;
1136
+ const o = obj;
1137
+ return (typeof o.next === 'function' ||
1138
+ typeof o.error === 'function' ||
1139
+ typeof o.complete === 'function');
1140
+ }
1141
+ function tagSandboxSnapshotArgs(arg2, arg3, arg4, target) {
1142
+ // Detect whether arg2 is the SnapshotListenOptions form. FS-B14 — an
1143
+ // observer is any object carrying at least one of `next` / `error` /
1144
+ // `complete` as a function (mirrors `isPartialObserver` in
1145
+ // `clones/.../api/observer.ts`). The old `!('next' in arg2)` test
1146
+ // misrouted an `{ error: fn }` observer as options, dropping it and
1147
+ // surfacing "missing next handler". A SnapshotListenOptions object has
1148
+ // none of those function-valued keys.
1149
+ const isOptions = typeof arg2 === 'object' && arg2 !== null && !isPartialObserver(arg2);
1150
+ if (isOptions) {
1151
+ // (options, next | observer, error?)
1152
+ const next = arg3;
1153
+ if (typeof next === 'function') {
1154
+ return [arg2, wrapNext(next, target), arg4 ?? defaultSnapshotErrorHandler];
1155
+ }
1156
+ if (next && typeof next === 'object') {
1157
+ return [arg2, wrapObserver(next, target)];
1158
+ }
1159
+ return [arg2];
1160
+ }
1161
+ // (next | observer, error?)
1162
+ if (typeof arg2 === 'function') {
1163
+ return [
1164
+ wrapNext(arg2, target),
1165
+ arg3 ?? defaultSnapshotErrorHandler,
1166
+ ];
1167
+ }
1168
+ if (arg2 && typeof arg2 === 'object') {
1169
+ return [wrapObserver(arg2, target)];
1170
+ }
1171
+ return [arg2];
1172
+ }
1173
+ /**
1174
+ * Stamp the internal `FOLLOWS_CURRENT_USER` marker onto the options
1175
+ * object of a forwarded `onSnapshot` arg tuple, so the chainable adapter
1176
+ * can set `addSnapshotListener`'s `followsCurrentUser` flag. Only applies
1177
+ * to `sandbox-live` targets — frozen-ctx (`sandbox`) and prod listeners
1178
+ * keep their pinned identity and are returned unchanged.
1179
+ *
1180
+ * `args` is the post-`tagSandboxSnapshotArgs` tuple, which is one of:
1181
+ * - `[options, next|observer, error?]` — options-first form
1182
+ * - `[next|observer, error?]` — callback-first form
1183
+ * - `[observer]` — observer-only form
1184
+ *
1185
+ * For the options-first form we clone the options and add the symbol.
1186
+ * For the other forms we PREPEND a fresh options object carrying only
1187
+ * the symbol — safe because the chainable adapter's normalizer
1188
+ * discriminates `(options, …)` from `(callback, …)` by inspecting the
1189
+ * first arg, and a marker-only object has no `next`/`error`/`complete`
1190
+ * handler so it reads as options.
1191
+ */
1192
+ function markSandboxLiveSnapshotArgs(args, target) {
1193
+ if (target.kind !== 'sandbox-live')
1194
+ return args;
1195
+ const first = args[0];
1196
+ const firstIsOptions = typeof first === 'object' && first !== null && !isPartialObserver(first);
1197
+ if (firstIsOptions) {
1198
+ const opts = { ...first, [FOLLOWS_CURRENT_USER]: true };
1199
+ return [opts, ...args.slice(1)];
1200
+ }
1201
+ return [{ [FOLLOWS_CURRENT_USER]: true }, ...args];
1202
+ }
1203
+ function finalizeSandboxSnapshot(snap, target) {
1204
+ if (!isSandboxKind(target) || !snap || typeof snap !== 'object')
1205
+ return snap;
1206
+ const s = snap;
1207
+ if (typeof s.data === 'function') {
1208
+ wrapSandboxDocSnap(snap);
1209
+ }
1210
+ if (Array.isArray(s.docs)) {
1211
+ for (const d of s.docs)
1212
+ wrapSandboxDocSnap(d);
1213
+ }
1214
+ return snap;
1215
+ }
1216
+ /**
1217
+ * Default error handler for a sandbox `onSnapshot` listener whose caller
1218
+ * supplied none. Mirrors the Firebase Web SDK, which logs an uncaught listener
1219
+ * error to the console rather than swallowing it — WITHOUT this, a denied
1220
+ * listener (a rules change, or a sign-out that revokes read access) fails
1221
+ * INVISIBLY: the last snapshot stays on screen and the app gets no signal that
1222
+ * its read was rejected. (Prod listeners go through the real SDK, which applies
1223
+ * its own default; this only fires on the sandbox path.)
1224
+ */
1225
+ function defaultSnapshotErrorHandler(error) {
1226
+ // eslint-disable-next-line no-console
1227
+ console.error('pyric/firestore: Uncaught Error in snapshot listener:', error);
1228
+ }
1229
+ function wrapNext(next, target) {
1230
+ return (snap) => next(finalizeSandboxSnapshot(tagSnapshotRefs(snap, target), target));
1231
+ }
1232
+ function wrapObserver(obs, target) {
1233
+ return {
1234
+ ...obs,
1235
+ next: obs.next
1236
+ ? (snap) => obs.next(finalizeSandboxSnapshot(tagSnapshotRefs(snap, target), target))
1237
+ : undefined,
1238
+ // Surface an unobserved listener error instead of swallowing it.
1239
+ error: obs.error ?? defaultSnapshotErrorHandler,
1240
+ };
1241
+ }
1242
+ /**
1243
+ * Walk a snapshot and tag every ref-shaped field in `refToTarget`,
1244
+ * AND normalize `.exists` to method form so consumer code targeting
1245
+ * Firebase's modular SDK (`if (snap.exists())`) works uniformly.
1246
+ *
1247
+ * The chainable adapter exposes `exists` as a property (Admin SDK
1248
+ * shape — `if (snap.exists)`). Firebase's modular SDK exposes it as
1249
+ * a method (`if (snap.exists())`). Without this normalization, user
1250
+ * code written against the documented Firebase API crashes against
1251
+ * the sandbox with "snap.exists is not a function" — a parity bug
1252
+ * agents and developers all rediscover by hand.
1253
+ *
1254
+ * Both forms remain readable on the returned object — the function
1255
+ * branch in any "did the existing FirestoreTab handle both shapes"
1256
+ * code still works.
1257
+ *
1258
+ * Idempotent — tagging an already-tagged object is a no-op, and the
1259
+ * `exists` getter we install is a no-op when one already exists.
1260
+ */
1261
+ function tagSnapshotRefs(snap, target) {
1262
+ if (!snap || typeof snap !== 'object')
1263
+ return snap;
1264
+ const s = snap;
1265
+ if (s.ref) {
1266
+ tag(s.ref, target);
1267
+ wireSnapshotRefToUnderlying(s.ref, target);
1268
+ }
1269
+ normalizeExists(s);
1270
+ if (Array.isArray(s.docs)) {
1271
+ for (const doc of s.docs) {
1272
+ if (doc?.ref) {
1273
+ tag(doc.ref, target);
1274
+ wireSnapshotRefToUnderlying(doc.ref, target);
1275
+ }
1276
+ if (doc)
1277
+ normalizeExists(doc);
1278
+ }
1279
+ }
1280
+ return snap;
1281
+ }
1282
+ /**
1283
+ * Snapshot `.ref` objects from the sandbox carry only `{ id, path }`
1284
+ * — no `.onSnapshot`, `.get`, `.set`, etc. — so a follow-up
1285
+ * `onSnapshot(snap.ref)` would crash with "ref.onSnapshot is not a
1286
+ * function." Bind the snapshot ref to a full chainable doc ref via
1287
+ * `refToUnderlying`; `underlyingOf(snap.ref)` then returns the real
1288
+ * ref the chainable adapter exposes its op methods on. No-op for
1289
+ * prod (their refs are already operative) and for refs that already
1290
+ * have a registered underlying.
1291
+ */
1292
+ function wireSnapshotRefToUnderlying(snapRef, target) {
1293
+ if (!isSandboxKind(target))
1294
+ return;
1295
+ if (typeof snapRef.path !== 'string' || snapRef.path.length === 0)
1296
+ return;
1297
+ if (refToUnderlying.has(snapRef))
1298
+ return;
1299
+ const path = snapRef.path;
1300
+ try {
1301
+ // Build a concrete chainable doc to back the snap-ref via
1302
+ // `refToUnderlying`. For sandbox-live, also record a rebuild
1303
+ // closure so subsequent ops on the snap ref re-resolve against
1304
+ // the *current* `sandbox.currentUser` (rather than re-using the
1305
+ // listener-time auth that was frozen into the chainable).
1306
+ const full = sandboxDb(target).doc(path);
1307
+ refToUnderlying.set(snapRef, full);
1308
+ if (target.kind === 'sandbox-live') {
1309
+ sandboxLiveRebuild.set(full, (fresh) => fresh.doc(path));
1310
+ }
1311
+ }
1312
+ catch {
1313
+ // path → doc resolution can throw if the path is malformed
1314
+ // (odd segment count, etc.). Best-effort — leave the snap ref
1315
+ // pointing at itself; the next op call will surface a clear
1316
+ // "ref.onSnapshot is not a function" instead of mangling state.
1317
+ }
1318
+ }
1319
+ /**
1320
+ * If `obj.exists` is a boolean property, replace it with a function
1321
+ * that returns that value — so consumer code can do `snap.exists()`
1322
+ * uniformly across the sandbox + Firebase backends. Idempotent: if
1323
+ * `exists` is already a function, leave it alone.
1324
+ *
1325
+ * `configurable: true` so a later normalize (over a re-yielded
1326
+ * snapshot from the same listener) can re-install cleanly.
1327
+ */
1328
+ function normalizeExists(obj) {
1329
+ const current = obj.exists;
1330
+ if (typeof current === 'function')
1331
+ return;
1332
+ const value = current === true;
1333
+ try {
1334
+ Object.defineProperty(obj, 'exists', {
1335
+ value: () => value,
1336
+ writable: true,
1337
+ configurable: true,
1338
+ enumerable: true,
1339
+ });
1340
+ }
1341
+ catch {
1342
+ // Some snapshot impls might freeze the object. Best-effort —
1343
+ // a frozen snap already provides the property reading the user
1344
+ // can do `if (snap.exists)` against; the call-site failure is
1345
+ // surfaced via the error boundary.
1346
+ }
1347
+ }
1348
+ // ─── Transactions + batches ───────────────────────────────────────────
1349
+ export async function runTransaction(db, fn) {
1350
+ const target = targetOf(db);
1351
+ if (isSandboxKind(target)) {
1352
+ // For sandbox-live, the transaction (and every op inside the
1353
+ // callback) runs under the auth captured at `runTransaction`
1354
+ // start. Mutating `sandbox.currentUser` mid-transaction does
1355
+ // not retro-actively re-auth in-flight reads (matches
1356
+ // production: a transaction is identity-stable by design).
1357
+ return sandboxDb(target).runTransaction(fn);
1358
+ }
1359
+ return fb.runTransaction(target.db, fn);
1360
+ }
1361
+ export function writeBatch(db) {
1362
+ const target = targetOf(db);
1363
+ if (isSandboxKind(target)) {
1364
+ // Each batch is constructed under a fresh chainable bound to
1365
+ // the current user. The batch holds that identity for every
1366
+ // op queued through it (matches transactions — once a batch is
1367
+ // opened, identity is frozen until `.commit()`).
1368
+ const batch = sandboxDb(target).batch();
1369
+ return tag(batch, target);
1370
+ }
1371
+ const batch = fb.writeBatch(target.db);
1372
+ return tag(batch, target);
1373
+ }
1374
+ // ─── Sandbox-only operations ──────────────────────────────────────────
1375
+ //
1376
+ // Grouped as a named-object export per the v4 plan. These have no
1377
+ // `firebase/firestore` analog — keeping them under a `sandbox`
1378
+ // namespace at the import site documents the sandbox-only
1379
+ // constraint better than the runtime throw could. Calling them on
1380
+ // a prod-target Firestore still throws `SandboxError` defensively.
1381
+ /**
1382
+ * Sandbox lifecycle operations. Only meaningful against a
1383
+ * sandbox-target `Firestore` (built via `getFirestore(ctx)` with a
1384
+ * `SandboxContext`). Each function throws `SandboxError` when
1385
+ * handed a prod-target handle.
1386
+ *
1387
+ * **Naming note**: the `sandbox` export name conflicts with the
1388
+ * common local variable from `initializeSandbox()` (which also
1389
+ * returns a `Sandbox` typically held as `const sandbox = …`).
1390
+ * When both are in scope, alias the import:
1391
+ *
1392
+ * ```ts
1393
+ * import { sandbox as sandboxOps } from 'pyric/firestore';
1394
+ * import { initializeSandbox } from 'pyric/sandbox';
1395
+ *
1396
+ * const sandbox = initializeSandbox(); // local var
1397
+ * const db = getFirestore(sandbox.withAuth(…));
1398
+ * sandboxOps.setRules(db, RULES); // SDK ops
1399
+ * ```
1400
+ *
1401
+ * The `sandbox` name was kept because it accurately describes the
1402
+ * surface (sandbox-only lifecycle). The renaming alternatives —
1403
+ * `sbx`, `firestoreSandbox`, `localOps` — all read worse than the
1404
+ * import-time alias.
1405
+ */
1406
+ export const sandbox = {
1407
+ /** Load a rules source into the underlying `LocalEnvironment`. */
1408
+ setRules(db, rules) {
1409
+ const target = targetOf(db);
1410
+ if (!isSandboxKind(target)) {
1411
+ throw new SandboxError('failed-precondition', 'sandbox.setRules is sandbox-only; use firestore.rules.deploy from pyric-tools/deploy for prod targets.');
1412
+ }
1413
+ return sandboxDb(target).setRules(rules);
1414
+ },
1415
+ /** Bulk-load documents bypassing rules. */
1416
+ seedDocuments(db, documents) {
1417
+ const target = targetOf(db);
1418
+ if (!isSandboxKind(target)) {
1419
+ throw new SandboxError('failed-precondition', 'sandbox.seedDocuments is sandbox-only; populate prod data via writes.');
1420
+ }
1421
+ return sandboxDb(target).seed({ documents });
1422
+ },
1423
+ /** Dump every document the underlying `LocalEnvironment` has stored. */
1424
+ snapshotState(db) {
1425
+ const target = targetOf(db);
1426
+ if (!isSandboxKind(target)) {
1427
+ throw new SandboxError('failed-precondition', 'sandbox.snapshotState is sandbox-only.');
1428
+ }
1429
+ return sandboxDb(target).snapshot();
1430
+ },
1431
+ /**
1432
+ * Single-call diagnostic an agent uses to answer "what state is the
1433
+ * sandbox in?" without grepping internal modules. Born out of
1434
+ * CLAUDE_DEBUG_SESSION.md: a real agent took 51 tool calls + 72k
1435
+ * tokens to figure out that the scaffold's rules weren't loaded.
1436
+ *
1437
+ * Returns: current rules source, lint summary, doc count by
1438
+ * collection, recent denials, recent requests. Stable JSON shape —
1439
+ * marshalled over MCP by `sandbox_inspect`.
1440
+ */
1441
+ inspect(db, opts) {
1442
+ const target = targetOf(db);
1443
+ if (!isSandboxKind(target)) {
1444
+ throw new SandboxError('failed-precondition', 'sandbox.inspect is sandbox-only.');
1445
+ }
1446
+ return inspectSandbox(target.sandbox, opts?.recentEventLimit ?? 10);
1447
+ },
1448
+ };
1449
+ function inspectSandbox(sandbox, recentLimit) {
1450
+ const env = getInternalEnv(sandbox);
1451
+ const rulesSource = env.getRules();
1452
+ const lint = rulesSource ? lintFirestoreRules(rulesSource) : { warnings: [] };
1453
+ const findings = lint.warnings.map((w) => ({
1454
+ rule: w.rule,
1455
+ severity: w.severity,
1456
+ message: w.message,
1457
+ }));
1458
+ const counts = { errors: 0, warnings: 0, info: 0 };
1459
+ for (const w of lint.warnings) {
1460
+ if (w.severity === 'error')
1461
+ counts.errors++;
1462
+ else if (w.severity === 'warning')
1463
+ counts.warnings++;
1464
+ else
1465
+ counts.info++;
1466
+ }
1467
+ const docs = env.snapshot();
1468
+ const byCollection = {};
1469
+ for (const path of Object.keys(docs)) {
1470
+ const top = path.split('/')[0] ?? '';
1471
+ if (!top)
1472
+ continue;
1473
+ byCollection[top] = (byCollection[top] ?? 0) + 1;
1474
+ }
1475
+ const history = sandbox.history();
1476
+ const requests = history.filter((e) => e.kind === 'request');
1477
+ const denials = requests.filter((e) => e.result === 'deny');
1478
+ const recentRequests = requests.slice(-recentLimit).map((e) => ({
1479
+ path: String(e.path ?? ''),
1480
+ method: String(e.method ?? ''),
1481
+ result: String(e.result ?? ''),
1482
+ auth: e.auth ?? null,
1483
+ }));
1484
+ const recentDenials = denials.slice(-recentLimit).map((e) => ({
1485
+ path: String(e.path ?? ''),
1486
+ method: String(e.method ?? ''),
1487
+ auth: e.auth ?? null,
1488
+ debugMessage: typeof e.debugMessage === 'string' ? e.debugMessage : undefined,
1489
+ }));
1490
+ return {
1491
+ rules: {
1492
+ source: rulesSource,
1493
+ sizeBytes: new TextEncoder().encode(rulesSource).byteLength,
1494
+ isEmpty: rulesSource.trim().length === 0,
1495
+ lint: { ...counts, findings },
1496
+ },
1497
+ documents: {
1498
+ totalCount: Object.keys(docs).length,
1499
+ byCollection,
1500
+ },
1501
+ events: {
1502
+ totalCount: history.length,
1503
+ recentDenials,
1504
+ recentRequests,
1505
+ },
1506
+ };
1507
+ }
1508
+ // ─── Sentinels + scalar types ─────────────────────────────────────────
1509
+ //
1510
+ // Sentinels in pyric ride on `pyric-admin`'s sentinel objects, which
1511
+ // are structurally identical to `firebase/firestore`'s — the simulator
1512
+ // recognizes them by `__type` discriminator, and so does production
1513
+ // Firestore (their wire format normalizes through the same path). One
1514
+ // `FieldValue.increment(1)` works in both targets.
1515
+ //
1516
+ // `Timestamp` is similar — admin-shape `{seconds, nanoseconds}` matches
1517
+ // the modular SDK's `Timestamp` shape.
1518
+ export { ChainFieldValue as FieldValue, ChainTimestamp as Timestamp };
1519
+ export function serverTimestamp() {
1520
+ return ChainFieldValue.serverTimestamp();
1521
+ }
1522
+ export function increment(n) {
1523
+ return ChainFieldValue.increment(n);
1524
+ }
1525
+ export function arrayUnion(...values) {
1526
+ return ChainFieldValue.arrayUnion(...values);
1527
+ }
1528
+ export function arrayRemove(...values) {
1529
+ return ChainFieldValue.arrayRemove(...values);
1530
+ }
1531
+ export function deleteField() {
1532
+ return ChainFieldValue.delete();
1533
+ }
1534
+ // ─── Tool factories (Slice 10) ────────────────────────────────────────
1535
+ export { createFirestoreDataTools, createFirestoreInspectTools } from './tools.js';
1536
+ //# sourceMappingURL=index.js.map