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,1467 @@
1
+ import { emitSandboxEvent, makeSandboxCommitEvent, makeSandboxListenerEvent, makeSandboxOperationEvent, makeServiceMutationEvent, } from 'pyric/sandbox/internal';
2
+ import { DataTree, cloneJson, jsonValuesEqual, joinPath, pathSegments, } from './data-tree.js';
3
+ import { generatePushId } from './push-id.js';
4
+ import { resolveSentinels } from './sentinels.js';
5
+ import { RulesEvaluator, permissionDenied, } from './rules-eval.js';
6
+ import { executeQuery } from './query.js';
7
+ import { normalizeWrite, coerceArrays } from './normalize.js';
8
+ /**
9
+ * RtdbBackend — the per-`Sandbox` data plane.
10
+ */
11
+ export class RtdbBackend {
12
+ tree = new DataTree();
13
+ rules = new RulesEvaluator();
14
+ valueListeners = new Set();
15
+ childListeners = new Set();
16
+ nextId = 0;
17
+ /**
18
+ * Persistence change-subscribers. The sandbox persistence controller
19
+ * registers one of these (via {@link RtdbBackend.subscribeWrites}) so any
20
+ * tree mutation schedules a debounced flush. RTDB writes emit
21
+ * `kind: 'service_mutation'` events, which are NOT in the controller's
22
+ * `isPersistableEvent` set (`write`/`session_boundary`) — so the sandbox
23
+ * event stream never triggers an RTDB flush. This subscriber channel is
24
+ * the sole flush trigger, mirroring how auth drives its own flushes.
25
+ */
26
+ writeSubscribers = new Set();
27
+ /**
28
+ * The owning `Sandbox`, when this backend was created through the
29
+ * modular surface's `getDatabase(sandbox)` / `getDatabase(ctx)` path.
30
+ * Held only to emit RTDB write activity onto the unified Studio
31
+ * `onEvent`/`history()` stream (keystone, track T1). Optional because
32
+ * the backend is also constructed bare in unit tests that exercise the
33
+ * data plane directly; in that case emission is simply skipped.
34
+ */
35
+ sandbox;
36
+ constructor(sandbox) {
37
+ this.sandbox = sandbox;
38
+ }
39
+ /**
40
+ * Emit an RTDB {@link ServiceMutationEvent} onto the sandbox's unified
41
+ * stream. No-op when this backend has no owning sandbox (bare-test
42
+ * construction). Best-effort + isolated — a throw must not fail the
43
+ * write that triggered it.
44
+ */
45
+ emitRtdbEvent(auth, op, path, fields = {}) {
46
+ if (!this.sandbox)
47
+ return;
48
+ try {
49
+ emitSandboxEvent(this.sandbox, makeServiceMutationEvent({
50
+ service: 'rtdb',
51
+ op,
52
+ path,
53
+ auth,
54
+ before: fields.before,
55
+ after: fields.after,
56
+ detail: fields.detail,
57
+ }), { service: 'rtdb' });
58
+ }
59
+ catch {
60
+ // Observational — never let event emission break an RTDB write.
61
+ }
62
+ }
63
+ nextListenerId() {
64
+ this.nextId += 1;
65
+ return `rtdb-listener-${this.nextId.toString(36)}`;
66
+ }
67
+ nextGroupId(prefix) {
68
+ this.nextId += 1;
69
+ return `rtdb-${prefix}-${this.nextId.toString(36)}`;
70
+ }
71
+ emitOperation(auth, method, path, result, evaluation, fields = {}) {
72
+ if (!this.sandbox)
73
+ return;
74
+ try {
75
+ emitSandboxEvent(this.sandbox, makeSandboxOperationEvent({
76
+ service: 'rtdb',
77
+ method,
78
+ path: canonicalPath(path),
79
+ auth,
80
+ result,
81
+ origin: fields.origin ?? 'user',
82
+ durationMs: fields.durationMs,
83
+ reasons: evaluation?.reasons,
84
+ rules: evaluation
85
+ ? {
86
+ engine: 'rtdb',
87
+ matchedPath: evaluation.matchedPath,
88
+ matchedRule: evaluation.matchedRule,
89
+ pathVariableBindings: evaluation.pathVariableBindings,
90
+ reason: evaluation.reason,
91
+ errorCode: evaluation.errorCode,
92
+ }
93
+ : undefined,
94
+ request: fields.request,
95
+ resourceBefore: fields.resourceBefore,
96
+ resourceAfter: fields.resourceAfter,
97
+ groupId: fields.groupId,
98
+ groupKind: fields.groupKind,
99
+ triggeredBy: fields.triggeredBy,
100
+ detail: fields.detail,
101
+ at: fields.at,
102
+ }), { service: 'rtdb' });
103
+ }
104
+ catch {
105
+ // Observational — never let telemetry break RTDB semantics.
106
+ }
107
+ }
108
+ emitCommit(auth, method, path, fields = {}) {
109
+ if (!this.sandbox)
110
+ return;
111
+ try {
112
+ emitSandboxEvent(this.sandbox, makeSandboxCommitEvent({
113
+ service: 'rtdb',
114
+ method,
115
+ path: canonicalPath(path),
116
+ auth,
117
+ data: fields.data,
118
+ priorState: fields.priorState,
119
+ nextState: fields.nextState,
120
+ groupId: fields.groupId,
121
+ groupKind: fields.groupKind,
122
+ replay: fields.replay,
123
+ detail: fields.detail,
124
+ }), { service: 'rtdb' });
125
+ }
126
+ catch {
127
+ // Observational — never let telemetry break RTDB semantics.
128
+ }
129
+ }
130
+ emitListener(phase, listener, auth, fields = {}) {
131
+ if (!this.sandbox)
132
+ return;
133
+ try {
134
+ emitSandboxEvent(this.sandbox, makeSandboxListenerEvent({
135
+ service: 'rtdb',
136
+ phase,
137
+ listenerId: listener.id,
138
+ target: {
139
+ kind: fields.event ?? 'value',
140
+ path: canonicalPath(listener.path),
141
+ },
142
+ auth,
143
+ result: fields.result,
144
+ size: fields.size,
145
+ sample: fields.sample,
146
+ reason: fields.reason,
147
+ error: fields.error,
148
+ triggeredBy: fields.triggeredBy,
149
+ detail: fields.detail,
150
+ }), { service: 'rtdb' });
151
+ }
152
+ catch {
153
+ // Observational — never let telemetry break RTDB semantics.
154
+ }
155
+ }
156
+ // ─── Admin-plane (rule-bypass) operations ───────────────────────────
157
+ //
158
+ // Used by the `sandbox.setData` / `sandbox.snapshotState` test
159
+ // helpers exported on the modular SDK. Don't go through the rule
160
+ // engine.
161
+ setData(seed) {
162
+ this.tree.restore({});
163
+ // Seed each path individually so the tree's prior-trim semantics
164
+ // apply consistently. For a flat seed of nested data we'd just
165
+ // restore(); but per-path seeding lets `setData({'/a/b': 1})` work
166
+ // when the user supplies dotted-out paths.
167
+ for (const [path, value] of Object.entries(seed)) {
168
+ // Resolve sentinels + normalize in seed too — keeps the seed API
169
+ // symmetric with the user-plane writes (array coercion, pruning).
170
+ const resolved = normalizeWrite(resolveSentinels(value, Date.now()), path === '/' ? '' : path);
171
+ this.tree.write(path, resolved);
172
+ }
173
+ this.notifyWrite();
174
+ }
175
+ setRules(rulesJson) {
176
+ this.rules.setRules(rulesJson);
177
+ }
178
+ snapshotState() {
179
+ return this.tree.snapshot();
180
+ }
181
+ adminGet(path) {
182
+ const value = this.tree.read(path);
183
+ this.emitOperation(null, 'get', path, 'not-applicable', undefined, {
184
+ origin: 'admin',
185
+ resourceBefore: { data: value, exists: value !== null },
186
+ });
187
+ return value;
188
+ }
189
+ adminGetQuery(path, spec) {
190
+ const rows = executeQuery(this.tree.read(path), spec);
191
+ this.emitOperation(null, 'get', path, 'not-applicable', undefined, {
192
+ origin: 'admin',
193
+ request: { query: spec },
194
+ resourceBefore: { data: rowsToVal(rows), exists: rows.length > 0 },
195
+ });
196
+ return rows;
197
+ }
198
+ adminSet(path, value) {
199
+ const now = Date.now();
200
+ const before = this.tree.read(path);
201
+ const resolved = normalizeWrite(resolveSentinels(value, now, before), path === '/' ? '' : path);
202
+ this.emitOperation(null, resolved === null ? 'remove' : 'set', path, 'not-applicable', undefined, {
203
+ origin: 'admin',
204
+ request: { data: value, resourceData: value },
205
+ resourceBefore: { data: before, exists: before !== null },
206
+ resourceAfter: { data: resolved, exists: resolved !== null },
207
+ });
208
+ const priors = this.snapshotChildListenerParents();
209
+ this.tree.write(path, resolved);
210
+ this.fanOut([path]);
211
+ this.fanOutChildren(priors);
212
+ const after = this.tree.read(path);
213
+ const method = after === null ? 'remove' : 'set';
214
+ this.emitCommit(null, method, path, {
215
+ data: value,
216
+ priorState: before,
217
+ nextState: after,
218
+ replay: { requestTime: now },
219
+ detail: { admin: true },
220
+ });
221
+ this.notifyWrite();
222
+ }
223
+ adminUpdate(path, patch) {
224
+ const now = Date.now();
225
+ const before = this.tree.read(path);
226
+ const isMultiPath = Object.keys(patch).some((k) => k.includes('/'));
227
+ const priors = this.snapshotChildListenerParents();
228
+ if (isMultiPath) {
229
+ const expanded = {};
230
+ for (const [k, v] of Object.entries(patch)) {
231
+ const absPath = joinPath([...pathSegments(path), ...pathSegments(k)]);
232
+ expanded[absPath] = normalizeWrite(resolveSentinels(v, now, this.tree.read(absPath)), absPath);
233
+ }
234
+ this.emitOperation(null, 'update', path, 'not-applicable', undefined, {
235
+ origin: 'admin',
236
+ request: { data: patch, resourceData: patch },
237
+ resourceBefore: { data: before, exists: before !== null },
238
+ detail: { admin: true, multiPath: true, paths: Object.keys(expanded) },
239
+ });
240
+ this.tree.multiUpdate(expanded);
241
+ this.fanOut(Object.keys(expanded));
242
+ this.fanOutChildren(priors);
243
+ const after = this.tree.read(path);
244
+ this.emitCommit(null, 'update', path, {
245
+ data: patch,
246
+ priorState: before,
247
+ nextState: after,
248
+ replay: { requestTime: now },
249
+ detail: { admin: true, multiPath: true, paths: Object.keys(expanded) },
250
+ });
251
+ this.notifyWrite();
252
+ return;
253
+ }
254
+ const resolvedPatch = {};
255
+ const touched = [];
256
+ for (const [k, v] of Object.entries(patch)) {
257
+ const sub = joinPath([...pathSegments(path), ...pathSegments(k)]);
258
+ touched.push(sub);
259
+ resolvedPatch[k] = normalizeWrite(resolveSentinels(v, now, this.tree.read(sub)), sub);
260
+ }
261
+ this.emitOperation(null, 'update', path, 'not-applicable', undefined, {
262
+ origin: 'admin',
263
+ request: { data: patch, resourceData: patch },
264
+ resourceBefore: { data: before, exists: before !== null },
265
+ detail: { admin: true, multiPath: false, keys: Object.keys(resolvedPatch) },
266
+ });
267
+ this.tree.shallowUpdate(path, resolvedPatch);
268
+ this.fanOut(touched);
269
+ this.fanOutChildren(priors);
270
+ const after = this.tree.read(path);
271
+ this.emitCommit(null, 'update', path, {
272
+ data: patch,
273
+ priorState: before,
274
+ nextState: after,
275
+ replay: { requestTime: now },
276
+ detail: { admin: true, multiPath: false, keys: Object.keys(resolvedPatch) },
277
+ });
278
+ this.notifyWrite();
279
+ }
280
+ adminRemove(path) {
281
+ this.adminSet(path, null);
282
+ }
283
+ // ─── User-plane (rule-gated) operations ────────────────────────────
284
+ get(auth, path) {
285
+ const at = Date.now();
286
+ const before = this.tree.read(path);
287
+ const evaluation = this.rules.evaluate('read', path === '/' ? '/' : path, {
288
+ auth,
289
+ mockData: this.tree.snapshot(),
290
+ });
291
+ if (evaluation.check !== 'allow') {
292
+ this.emitOperation(auth, 'get', path, 'deny', evaluation, {
293
+ at,
294
+ durationMs: Date.now() - at,
295
+ resourceBefore: { data: before, exists: before !== null },
296
+ });
297
+ throw permissionDenied();
298
+ }
299
+ this.emitOperation(auth, 'get', path, 'allow', evaluation, {
300
+ at,
301
+ durationMs: Date.now() - at,
302
+ resourceBefore: { data: before, exists: before !== null },
303
+ });
304
+ // Return the stored (integer-keyed) shape. Array coercion (DB-B2) is
305
+ // a `DataSnapshot.val()`-render concern applied by the snapshot
306
+ // wrapper — structural ops (`forEach`/`child`/`size`) walk the node.
307
+ return this.tree.read(path);
308
+ }
309
+ /**
310
+ * `set(path, value)` — full overwrite at the path. `null` deletes the
311
+ * subtree (sandbox match for the RTDB invariant
312
+ * `remove(ref) === set(ref, null)`).
313
+ */
314
+ set(auth, path, value) {
315
+ this.setInternal(auth, path, value, 'set');
316
+ }
317
+ /** Shared `set`/`remove` write path. `op` selects the emitted event
318
+ * label so a `remove` (which is `set(_, null)`) surfaces as `remove`
319
+ * rather than `set` on the Studio stream. */
320
+ setInternal(auth, path, value, op) {
321
+ const now = Date.now();
322
+ // Pass the current value at the path so `increment()` sentinels
323
+ // resolve against the field's prior value (DB-GAP).
324
+ const resolved0 = resolveSentinels(value, now, this.tree.read(path));
325
+ // Write-boundary normalization (DB-B1/B2/B3): validate keys + reject
326
+ // `undefined`/non-finite, coerce arrays → integer-keyed objects, prune
327
+ // null/empty subtrees. An object that prunes to nothing is a delete
328
+ // (`set(ref, {})` === `remove(ref)`).
329
+ const resolved = normalizeWrite(resolved0, path === '/' ? '' : path);
330
+ const before = this.tree.read(path);
331
+ const at = Date.now();
332
+ const evaluation = this.rules.evaluate('write', path === '/' ? '/' : path, {
333
+ auth,
334
+ mockData: this.tree.snapshot(),
335
+ newData: resolved,
336
+ });
337
+ const resourceBefore = { data: before, exists: before !== null };
338
+ const resourceAfter = { data: resolved, exists: resolved !== null };
339
+ if (evaluation.check !== 'allow') {
340
+ this.emitOperation(auth, op, path, 'deny', evaluation, {
341
+ at,
342
+ durationMs: Date.now() - at,
343
+ request: { data: value, resourceData: value },
344
+ resourceBefore,
345
+ resourceAfter,
346
+ });
347
+ throw permissionDenied();
348
+ }
349
+ this.emitOperation(auth, op, path, 'allow', evaluation, {
350
+ at,
351
+ durationMs: Date.now() - at,
352
+ request: { data: value, resourceData: value },
353
+ resourceBefore,
354
+ resourceAfter,
355
+ });
356
+ const priors = this.snapshotChildListenerParents();
357
+ this.tree.write(path, resolved);
358
+ this.fanOut([path]);
359
+ this.fanOutChildren(priors);
360
+ // A write that pruned to nothing (`set(ref, null)` / `set(ref, {})`)
361
+ // is semantically a remove; label it as such even if it arrived via
362
+ // `set`. `after` is the post-write value at the path (`null` when the
363
+ // subtree was deleted).
364
+ const after = this.tree.read(path);
365
+ const effectiveOp = after === null ? 'remove' : op;
366
+ this.emitCommit(auth, effectiveOp, path, {
367
+ data: value,
368
+ priorState: before,
369
+ nextState: after,
370
+ replay: { requestTime: now },
371
+ });
372
+ this.emitRtdbEvent(auth, effectiveOp, canonicalPath(path), { before, after });
373
+ this.notifyWrite();
374
+ }
375
+ remove(auth, path) {
376
+ this.setInternal(auth, path, null, 'remove');
377
+ }
378
+ /**
379
+ * `update(path, patch)` — multi-path atomic update when keys are
380
+ * `/`-prefixed and `path === '/'`; otherwise a shallow merge at the
381
+ * named path. Matches `firebase/database`'s discrimination: an
382
+ * `update(rootRef, { '/users/a/x': 1, '/users/b/y': 2 })` fans out
383
+ * to both paths atomically, while `update(usersRef, { name: 'A' })`
384
+ * shallow-merges into `/users`.
385
+ */
386
+ update(auth, path, patch) {
387
+ const now = Date.now();
388
+ const isMultiPath = Object.keys(patch).some((k) => k.includes('/'));
389
+ if (isMultiPath) {
390
+ const groupId = this.nextGroupId('update');
391
+ // Each key is treated as a path relative to `path`. Resolve
392
+ // sentinels per-leaf so a key whose value contains a
393
+ // `serverTimestamp()` lands as a number.
394
+ const expanded = {};
395
+ for (const [k, v] of Object.entries(patch)) {
396
+ const absSegs = [...pathSegments(path), ...pathSegments(k)];
397
+ const absPath = joinPath(absSegs);
398
+ expanded[absPath] = normalizeWrite(resolveSentinels(v, now, this.tree.read(absPath)), absPath);
399
+ }
400
+ // Check every path under the rules engine. ANY denial fails the
401
+ // entire update — the multi-path atomicity contract.
402
+ const mock = this.tree.snapshot();
403
+ for (const [absPath, value] of Object.entries(expanded)) {
404
+ const at = Date.now();
405
+ const before = this.tree.read(absPath);
406
+ const evaluation = this.rules.evaluate('write', absPath, {
407
+ auth,
408
+ mockData: mock,
409
+ newData: value,
410
+ });
411
+ const common = {
412
+ at,
413
+ durationMs: Date.now() - at,
414
+ origin: 'batch',
415
+ request: { data: value, resourceData: value },
416
+ resourceBefore: { data: before, exists: before !== null },
417
+ resourceAfter: { data: value, exists: value !== null },
418
+ groupId,
419
+ groupKind: 'batch',
420
+ detail: { multiPath: true, rootPath: canonicalPath(path) },
421
+ };
422
+ if (evaluation.check !== 'allow') {
423
+ this.emitOperation(auth, 'update', absPath, 'deny', evaluation, common);
424
+ throw permissionDenied();
425
+ }
426
+ this.emitOperation(auth, 'update', absPath, 'allow', evaluation, common);
427
+ }
428
+ // Apply atomically via the tree's overlap-checked multi-write.
429
+ const beforeRoot = this.tree.read(path);
430
+ const priors = this.snapshotChildListenerParents();
431
+ this.tree.multiUpdate(expanded);
432
+ this.fanOut(Object.keys(expanded));
433
+ this.fanOutChildren(priors);
434
+ const afterRoot = this.tree.read(path);
435
+ this.emitCommit(auth, 'update', path, {
436
+ data: patch,
437
+ priorState: beforeRoot,
438
+ nextState: afterRoot,
439
+ groupId,
440
+ groupKind: 'batch',
441
+ replay: { requestTime: now },
442
+ detail: { multiPath: true, paths: Object.keys(expanded) },
443
+ });
444
+ this.emitRtdbEvent(auth, 'update', canonicalPath(path), {
445
+ after: expanded,
446
+ detail: { multiPath: true, paths: Object.keys(expanded) },
447
+ });
448
+ this.notifyWrite();
449
+ return;
450
+ }
451
+ // Shallow merge mode. Each top-level key of `patch` replaces the
452
+ // key at `<path>/<key>`. `null` deletes.
453
+ const resolvedPatch = {};
454
+ for (const [k, v] of Object.entries(patch)) {
455
+ const sub = joinPath([...pathSegments(path), ...pathSegments(k)]);
456
+ resolvedPatch[k] = normalizeWrite(resolveSentinels(v, now, this.tree.read(sub)), sub);
457
+ }
458
+ const mock = this.tree.snapshot();
459
+ const touched = [];
460
+ const groupId = this.nextGroupId('update');
461
+ for (const [k, v] of Object.entries(resolvedPatch)) {
462
+ const sub = joinPath([...pathSegments(path), ...pathSegments(k)]);
463
+ touched.push(sub);
464
+ const at = Date.now();
465
+ const before = this.tree.read(sub);
466
+ const evaluation = this.rules.evaluate('write', sub, {
467
+ auth,
468
+ mockData: mock,
469
+ newData: v,
470
+ });
471
+ const common = {
472
+ at,
473
+ durationMs: Date.now() - at,
474
+ origin: 'batch',
475
+ request: { data: v, resourceData: v },
476
+ resourceBefore: { data: before, exists: before !== null },
477
+ resourceAfter: { data: v, exists: v !== null },
478
+ groupId,
479
+ groupKind: 'batch',
480
+ detail: { multiPath: false, rootPath: canonicalPath(path), key: k },
481
+ };
482
+ if (evaluation.check !== 'allow') {
483
+ this.emitOperation(auth, 'update', sub, 'deny', evaluation, common);
484
+ throw permissionDenied();
485
+ }
486
+ this.emitOperation(auth, 'update', sub, 'allow', evaluation, common);
487
+ }
488
+ const beforeRoot = this.tree.read(path);
489
+ const priors = this.snapshotChildListenerParents();
490
+ this.tree.shallowUpdate(path, resolvedPatch);
491
+ this.fanOut(touched);
492
+ this.fanOutChildren(priors);
493
+ const afterRoot = this.tree.read(path);
494
+ this.emitCommit(auth, 'update', path, {
495
+ data: patch,
496
+ priorState: beforeRoot,
497
+ nextState: afterRoot,
498
+ groupId,
499
+ groupKind: 'batch',
500
+ replay: { requestTime: now },
501
+ detail: { multiPath: false, keys: Object.keys(resolvedPatch) },
502
+ });
503
+ this.emitRtdbEvent(auth, 'update', canonicalPath(path), {
504
+ after: resolvedPatch,
505
+ detail: { multiPath: false, keys: Object.keys(resolvedPatch) },
506
+ });
507
+ this.notifyWrite();
508
+ }
509
+ // Note: `push()` is implemented entirely on the modular surface
510
+ // (`modular.ts`) — the key is minted client-side via {@link mintKey}
511
+ // (no rule check) so it's available synchronously even under a denying
512
+ // rule (DB-B7), and the optional value write is deferred onto the
513
+ // returned ThenableReference's promise via the normal `set` path.
514
+ // ─── Listeners ─────────────────────────────────────────────────────
515
+ /**
516
+ * Subscribe to `onValue`. Fires immediately with the current value
517
+ * (or `{ val: null, exists: false }` for an absent path — locked by
518
+ * the prod SDK's behavior), then on every write that touches `path`
519
+ * or any descendant.
520
+ *
521
+ * Returns an unsubscribe.
522
+ */
523
+ onValue(auth, path, cb, query) {
524
+ // Rules check at subscribe time. A denied subscribe never gets
525
+ // the initial fire — matches production where the listener errors
526
+ // out before any callback.
527
+ const listenerId = this.nextListenerId();
528
+ const at = Date.now();
529
+ const evaluation = this.rules.evaluate('read', path === '/' ? '/' : path, {
530
+ auth,
531
+ mockData: this.tree.snapshot(),
532
+ });
533
+ if (evaluation.check !== 'allow') {
534
+ this.emitOperation(auth, 'listen', path, 'deny', evaluation, {
535
+ at,
536
+ durationMs: Date.now() - at,
537
+ request: query ? { query } : undefined,
538
+ origin: 'listener',
539
+ });
540
+ this.emitListener('errored', { id: listenerId, path }, auth, {
541
+ event: 'value',
542
+ result: 'deny',
543
+ error: {
544
+ code: 'PERMISSION_DENIED',
545
+ message: 'PERMISSION_DENIED: Permission denied',
546
+ reasons: evaluation.reasons,
547
+ },
548
+ });
549
+ throw permissionDenied();
550
+ }
551
+ this.emitOperation(auth, 'listen', path, 'allow', evaluation, {
552
+ at,
553
+ durationMs: Date.now() - at,
554
+ request: query ? { query } : undefined,
555
+ origin: 'listener',
556
+ });
557
+ const listener = { id: listenerId, auth, cb, path, query };
558
+ this.valueListeners.add(listener);
559
+ this.emitListener('attach', listener, auth, {
560
+ event: 'value',
561
+ result: 'allow',
562
+ detail: query ? { query } : undefined,
563
+ });
564
+ // Initial fire — for queries we record the initial window so the
565
+ // diff check on the next write knows what "the previous result"
566
+ // was. For plain listeners we just feed the path snapshot.
567
+ if (query) {
568
+ const initialWindow = executeQuery(this.tree.read(path), query);
569
+ listener.lastWindow = initialWindow;
570
+ const snap = {
571
+ val: rowsToVal(initialWindow),
572
+ exists: initialWindow.length > 0,
573
+ key: this.keyForPath(path),
574
+ };
575
+ try {
576
+ cb(snap);
577
+ this.emitListener('delivery', listener, auth, {
578
+ event: 'value',
579
+ size: initialWindow.length,
580
+ sample: snap.val,
581
+ detail: { initial: true, query: true },
582
+ });
583
+ }
584
+ catch (e) {
585
+ this.emitListener('errored', listener, auth, {
586
+ event: 'value',
587
+ result: 'error',
588
+ error: { message: e instanceof Error ? e.message : String(e) },
589
+ detail: { initial: true, query: true },
590
+ });
591
+ }
592
+ }
593
+ else {
594
+ const snap = this.makeSnap(path);
595
+ // Record the initial value so a subsequent no-change write is
596
+ // suppressed (DB-B8).
597
+ listener.lastValue = snap.val;
598
+ try {
599
+ cb(snap);
600
+ this.emitListener('delivery', listener, auth, {
601
+ event: 'value',
602
+ size: snap.exists ? 1 : 0,
603
+ sample: snap.val,
604
+ detail: { initial: true },
605
+ });
606
+ }
607
+ catch (e) {
608
+ this.emitListener('errored', listener, auth, {
609
+ event: 'value',
610
+ result: 'error',
611
+ error: { message: e instanceof Error ? e.message : String(e) },
612
+ detail: { initial: true },
613
+ });
614
+ }
615
+ }
616
+ return () => {
617
+ this.valueListeners.delete(listener);
618
+ this.emitListener('detach', listener, auth, { event: 'value' });
619
+ };
620
+ }
621
+ /**
622
+ * One-shot `get` against a query — evaluates the constraint chain
623
+ * against the current tree snapshot. Same rules check as plain `get`
624
+ * (the read is at the query's root path; per-child rule evaluation
625
+ * isn't modeled here — RTDB itself rejects queries that span
626
+ * children with mixed read permissions).
627
+ */
628
+ getQuery(auth, path, spec) {
629
+ const at = Date.now();
630
+ const evaluation = this.rules.evaluate('read', path === '/' ? '/' : path, {
631
+ auth,
632
+ mockData: this.tree.snapshot(),
633
+ });
634
+ if (evaluation.check !== 'allow') {
635
+ this.emitOperation(auth, 'get', path, 'deny', evaluation, {
636
+ at,
637
+ durationMs: Date.now() - at,
638
+ request: { query: spec },
639
+ });
640
+ throw permissionDenied();
641
+ }
642
+ const rows = executeQuery(this.tree.read(path), spec);
643
+ this.emitOperation(auth, 'get', path, 'allow', evaluation, {
644
+ at,
645
+ durationMs: Date.now() - at,
646
+ request: { query: spec },
647
+ resourceBefore: { data: rowsToVal(rows), exists: rows.length > 0 },
648
+ });
649
+ return rows;
650
+ }
651
+ /** The last segment of `path`, or `null` for root. Mirrors
652
+ * `DataSnapshot.key`. */
653
+ keyForPath(path) {
654
+ const segs = pathSegments(path);
655
+ return segs.length === 0 ? null : segs[segs.length - 1];
656
+ }
657
+ /**
658
+ * Compute the listener payload for `path` against the current tree.
659
+ * `key` is the last segment of the path, or `null` for root —
660
+ * matches `DataSnapshot.key`.
661
+ */
662
+ makeSnap(path) {
663
+ const val = this.tree.read(path);
664
+ const exists = val !== null;
665
+ const segs = pathSegments(path);
666
+ const key = segs.length === 0 ? null : segs[segs.length - 1];
667
+ return { val, exists, key };
668
+ }
669
+ /**
670
+ * Fan out `value` listeners after a batch of paths were written.
671
+ * Fires every listener whose path either equals one of the touched
672
+ * paths or is an ancestor/descendant of any (the listener observes
673
+ * the subtree it's watching, so any descendant write triggers).
674
+ */
675
+ fanOut(touched) {
676
+ if (this.valueListeners.size === 0)
677
+ return;
678
+ const touchedSet = touched.map((p) => joinPath(pathSegments(p)));
679
+ for (const listener of this.valueListeners) {
680
+ const lp = joinPath(pathSegments(listener.path));
681
+ const subtreeTouched = touchedSet.some((tp) => {
682
+ if (tp === lp)
683
+ return true;
684
+ const lpp = lp === '/' ? '/' : lp + '/';
685
+ const tpp = tp === '/' ? '/' : tp + '/';
686
+ // Listener watches root: every write fires it.
687
+ if (lp === '/')
688
+ return true;
689
+ // Touched is a descendant of the listener's path → fires.
690
+ if (tp.startsWith(lpp))
691
+ return true;
692
+ // Touched is an ancestor of the listener's path → also fires
693
+ // (the listener's subtree might be different now).
694
+ if (lp.startsWith(tpp))
695
+ return true;
696
+ return false;
697
+ });
698
+ if (!subtreeTouched)
699
+ continue;
700
+ if (listener.query) {
701
+ // Query listener: only fire if the windowed result changed.
702
+ // Locked by oracle observation
703
+ // `rtdb-modular-onvalue-with-query.json` — a write OUTSIDE the
704
+ // window doesn't fire the query listener.
705
+ const nextWindow = executeQuery(this.tree.read(listener.path), listener.query);
706
+ if (windowsEqual(listener.lastWindow ?? [], nextWindow)) {
707
+ this.emitListener('suppressed', listener, listener.auth, {
708
+ event: 'value',
709
+ reason: 'no-op',
710
+ detail: { query: true },
711
+ });
712
+ continue;
713
+ }
714
+ listener.lastWindow = nextWindow;
715
+ const snap = {
716
+ val: rowsToVal(nextWindow),
717
+ exists: nextWindow.length > 0,
718
+ key: this.keyForPath(listener.path),
719
+ };
720
+ this.emitListener('delivery', listener, listener.auth, {
721
+ event: 'value',
722
+ size: nextWindow.length,
723
+ sample: snap.val,
724
+ detail: { query: true },
725
+ });
726
+ try {
727
+ listener.cb(snap);
728
+ }
729
+ catch (e) {
730
+ this.emitListener('errored', listener, listener.auth, {
731
+ event: 'value',
732
+ result: 'error',
733
+ error: { message: e instanceof Error ? e.message : String(e) },
734
+ detail: { query: true },
735
+ });
736
+ // Swallow — see plain-listener branch below.
737
+ }
738
+ continue;
739
+ }
740
+ // Plain listener: suppress the fire when the value at the
741
+ // listener's exact path is byte-identical to the last fired value
742
+ // (DB-B8). An ancestor/descendant write that leaves this subtree
743
+ // unchanged must NOT re-fire — RTDB's SyncTree dedups no-change.
744
+ const snap = this.makeSnap(listener.path);
745
+ const last = listener.lastValue;
746
+ if (last !== undefined && jsonValuesEqual(last, snap.val)) {
747
+ this.emitListener('suppressed', listener, listener.auth, {
748
+ event: 'value',
749
+ reason: 'no-op',
750
+ });
751
+ continue;
752
+ }
753
+ listener.lastValue = snap.val;
754
+ this.emitListener('delivery', listener, listener.auth, {
755
+ event: 'value',
756
+ size: snap.exists ? 1 : 0,
757
+ sample: snap.val,
758
+ });
759
+ try {
760
+ listener.cb(snap);
761
+ }
762
+ catch (e) {
763
+ this.emitListener('errored', listener, listener.auth, {
764
+ event: 'value',
765
+ result: 'error',
766
+ error: { message: e instanceof Error ? e.message : String(e) },
767
+ });
768
+ // Swallow — matches `firebase/database` which doesn't let
769
+ // one observer's throw block another. The faulty listener
770
+ // is the caller's problem.
771
+ }
772
+ }
773
+ }
774
+ /**
775
+ * `runTransaction(path, updateFn, options?)` — RTDB-flavored atomic
776
+ * read-modify-write. The contract mirrors `firebase/database`'s
777
+ * modular `runTransaction`:
778
+ *
779
+ * - Read the current value at `path`.
780
+ * - Call `updateFn(currentValue)`.
781
+ * - `currentValue` is `null` for an absent path (oracle:
782
+ * `rtdb-modular-runtransaction-current-value-arg.json` →
783
+ * `missingArgs[0].isNull === true`).
784
+ * - If `updateFn` returns `undefined` → **abort**: no write happens,
785
+ * resolves `{ committed: false, snapshot }` where the snapshot
786
+ * reflects the pre-transaction value (oracle:
787
+ * `rtdb-modular-runtransaction-abort-undefined.json` → `committed:
788
+ * false, snapVal: null, afterValOnServer: 100`).
789
+ * - If `updateFn` returns any defined value → run the write under
790
+ * the current identity's rules; on allow, commit and resolve
791
+ * `{ committed: true, snapshot }` with the new value. On deny,
792
+ * throw the RTDB-transaction error shape (oracle:
793
+ * `rtdb-modular-runtransaction-on-rules-denied-path.json` →
794
+ * `threw: true, message: 'permission_denied', code: null,
795
+ * constructorName: 'Error'`).
796
+ *
797
+ * Single-client harness → no real concurrency to retry against; the
798
+ * documented "optimistic concurrency with retry" is degenerate here.
799
+ * The sandbox doesn't speculatively call `updateFn` with `null` first
800
+ * for a seeded path (prod does — second invocation with the server
801
+ * value); our contract is the SIMPLEST observable: one invocation
802
+ * with the actual current value.
803
+ *
804
+ * `applyLocally` semantics: when omitted or `true`, the in-flight
805
+ * (post-update-fn, pre-commit) value fans out to listeners as an
806
+ * optimistic fire. With `false`, no listener fire until commit. The
807
+ * difference is invisible in a single-client harness with a
808
+ * synchronous commit path; we still respect the flag so consumers can
809
+ * tune intermediate-fire counts under custom assertions.
810
+ *
811
+ * Returns a payload the modular surface wraps in a `DataSnapshot`
812
+ * shape — the backend stays snapshot-agnostic so the rules engine
813
+ * doesn't need to know about ref objects.
814
+ */
815
+ runTransaction(auth, path, updateFn, options) {
816
+ const applyLocally = options?.applyLocally !== false;
817
+ const segs = pathSegments(path);
818
+ const key = segs.length === 0 ? null : segs[segs.length - 1];
819
+ const current = this.tree.read(path);
820
+ // Hand the user-fn a deep clone so mutation of the arg doesn't
821
+ // corrupt our stored tree (consumer code that does
822
+ // `current.count++; return current` would otherwise mutate-then-
823
+ // read the same reference). Array-coerce so the fn sees the same
824
+ // shape `get()` would return (DB-B2).
825
+ const currentForFn = current === null ? null : coerceArrays(cloneJson(current));
826
+ const proposed = updateFn(currentForFn);
827
+ const groupId = this.nextGroupId('transaction');
828
+ if (proposed === undefined) {
829
+ this.emitOperation(auth, 'transaction', path, 'not-applicable', undefined, {
830
+ origin: 'transaction',
831
+ groupId,
832
+ groupKind: 'transaction',
833
+ resourceBefore: { data: current, exists: current !== null },
834
+ detail: { committed: false, aborted: true },
835
+ });
836
+ // Abort — no write, no rule check, no listener fire. Resolve with
837
+ // the pre-transaction value (oracle pinned for the seeded case:
838
+ // `afterValOnServer: 100` preserved).
839
+ return { committed: false, val: current, key };
840
+ }
841
+ // Resolve sentinels + normalize first so the rule check sees the
842
+ // stored shape (consistent with `set`): array coercion, null/empty
843
+ // pruning, key validation.
844
+ const now = Date.now();
845
+ const resolved = normalizeWrite(resolveSentinels(proposed, now, current), path === '/' ? '' : path);
846
+ // Apply locally if allowed — fires listeners with the optimistic
847
+ // value BEFORE the rule check. The single-client sandbox has a
848
+ // synchronous commit path so this is effectively a no-op in
849
+ // practice; the flag is honored for parity with prod's contract.
850
+ if (applyLocally) {
851
+ // Stash the prior value so a denial below can roll back. The
852
+ // tree's write replaces; we hold the snapshot of the pre-write
853
+ // root to restore on rule-deny.
854
+ const priorRoot = this.tree.snapshot();
855
+ this.tree.write(path, resolved);
856
+ this.fanOut([path]);
857
+ const at = Date.now();
858
+ const evaluation = this.rules.evaluate('write', path === '/' ? '/' : path, {
859
+ auth,
860
+ mockData: priorRoot,
861
+ newData: resolved,
862
+ });
863
+ if (evaluation.check !== 'allow') {
864
+ this.emitOperation(auth, 'transaction', path, 'deny', evaluation, {
865
+ at,
866
+ durationMs: Date.now() - at,
867
+ origin: 'transaction',
868
+ request: { data: proposed, resourceData: proposed },
869
+ resourceBefore: { data: current, exists: current !== null },
870
+ resourceAfter: { data: resolved, exists: resolved !== null },
871
+ groupId,
872
+ groupKind: 'transaction',
873
+ });
874
+ // Roll back the local apply and re-fan-out so listeners see
875
+ // the restored value. Then throw the transaction-specific
876
+ // denial shape.
877
+ this.tree.restore(priorRoot);
878
+ this.fanOut([path]);
879
+ throw transactionPermissionDenied();
880
+ }
881
+ this.emitOperation(auth, 'transaction', path, 'allow', evaluation, {
882
+ at,
883
+ durationMs: Date.now() - at,
884
+ origin: 'transaction',
885
+ request: { data: proposed, resourceData: proposed },
886
+ resourceBefore: { data: current, exists: current !== null },
887
+ resourceAfter: { data: resolved, exists: resolved !== null },
888
+ groupId,
889
+ groupKind: 'transaction',
890
+ });
891
+ this.emitCommit(auth, 'transaction', path, {
892
+ data: proposed,
893
+ priorState: current,
894
+ nextState: resolved,
895
+ groupId,
896
+ groupKind: 'transaction',
897
+ replay: { requestTime: now },
898
+ detail: { committed: true, applyLocally: true },
899
+ });
900
+ this.emitRtdbEvent(auth, 'transaction', canonicalPath(path), {
901
+ before: current,
902
+ after: resolved,
903
+ detail: { committed: true },
904
+ });
905
+ this.notifyWrite();
906
+ return { committed: true, val: resolved, key };
907
+ }
908
+ // applyLocally: false — rule-check FIRST, write only if allowed,
909
+ // listeners see only the committed value.
910
+ const at = Date.now();
911
+ const evaluation = this.rules.evaluate('write', path === '/' ? '/' : path, {
912
+ auth,
913
+ mockData: this.tree.snapshot(),
914
+ newData: resolved,
915
+ });
916
+ if (evaluation.check !== 'allow') {
917
+ this.emitOperation(auth, 'transaction', path, 'deny', evaluation, {
918
+ at,
919
+ durationMs: Date.now() - at,
920
+ origin: 'transaction',
921
+ request: { data: proposed, resourceData: proposed },
922
+ resourceBefore: { data: current, exists: current !== null },
923
+ resourceAfter: { data: resolved, exists: resolved !== null },
924
+ groupId,
925
+ groupKind: 'transaction',
926
+ });
927
+ throw transactionPermissionDenied();
928
+ }
929
+ this.emitOperation(auth, 'transaction', path, 'allow', evaluation, {
930
+ at,
931
+ durationMs: Date.now() - at,
932
+ origin: 'transaction',
933
+ request: { data: proposed, resourceData: proposed },
934
+ resourceBefore: { data: current, exists: current !== null },
935
+ resourceAfter: { data: resolved, exists: resolved !== null },
936
+ groupId,
937
+ groupKind: 'transaction',
938
+ });
939
+ this.tree.write(path, resolved);
940
+ this.fanOut([path]);
941
+ this.emitCommit(auth, 'transaction', path, {
942
+ data: proposed,
943
+ priorState: current,
944
+ nextState: resolved,
945
+ groupId,
946
+ groupKind: 'transaction',
947
+ replay: { requestTime: now },
948
+ detail: { committed: true, applyLocally: false },
949
+ });
950
+ this.emitRtdbEvent(auth, 'transaction', canonicalPath(path), {
951
+ before: current,
952
+ after: resolved,
953
+ detail: { committed: true },
954
+ });
955
+ return { committed: true, val: resolved, key };
956
+ }
957
+ // ─── Child-event listeners (Tier 2) ────────────────────────────────
958
+ //
959
+ // Per-child diff fanout. Each child listener watches a parent path;
960
+ // on write we compare the parent's prior children against its next
961
+ // children and dispatch `child_added` / `child_changed` /
962
+ // `child_removed` accordingly.
963
+ //
964
+ // `child_moved` requires an ordered query (see oracle observation
965
+ // `rtdb-modular-onchildmoved-with-orderby.json`) — for plain refs
966
+ // it never fires. Tier 3 will wire the ordered-query path in.
967
+ /**
968
+ * Subscribe to a child event at `path`.
969
+ *
970
+ * Semantics (locked by oracle observations under
971
+ * `scripts/oracle/observations/rtdb-modular-onchild*.json`):
972
+ *
973
+ * - `child_added`: replays every existing direct child of `path` on
974
+ * subscribe (one fire per existing key, in current key-iteration
975
+ * order — matches the upstream `orderByKey` default observed in
976
+ * `rtdb-modular-onchildadded-initial-replay`). Subsequent
977
+ * additions fire once each.
978
+ * - `child_changed`: no initial replay. Fires when an existing
979
+ * child's value transitions to a NEW non-null value. Snapshot
980
+ * carries the NEW value.
981
+ * - `child_removed`: no initial replay. Fires when a child is
982
+ * deleted (its value transitions to absent). Snapshot carries
983
+ * the PRIOR (now-removed) value.
984
+ * - `child_moved`: ordered-query only — never fires on a plain
985
+ * ref (matches the upstream contract; Tier 3 will wire ordered
986
+ * queries in).
987
+ *
988
+ * Rules check happens at subscribe time, identical to `onValue`. A
989
+ * denied subscribe throws the plain-`Error` `PERMISSION_DENIED`
990
+ * shape and the listener is never registered.
991
+ *
992
+ * Returns an idempotent unsubscribe.
993
+ */
994
+ onChild(auth, event, path, cb, spec) {
995
+ const listenerId = this.nextListenerId();
996
+ const at = Date.now();
997
+ const evaluation = this.rules.evaluate('read', path === '/' ? '/' : path, {
998
+ auth,
999
+ mockData: this.tree.snapshot(),
1000
+ });
1001
+ if (evaluation.check !== 'allow') {
1002
+ this.emitOperation(auth, 'listen', path, 'deny', evaluation, {
1003
+ at,
1004
+ durationMs: Date.now() - at,
1005
+ origin: 'listener',
1006
+ detail: { event },
1007
+ });
1008
+ this.emitListener('errored', { id: listenerId, path }, auth, {
1009
+ event,
1010
+ result: 'deny',
1011
+ error: {
1012
+ code: 'PERMISSION_DENIED',
1013
+ message: 'PERMISSION_DENIED: Permission denied',
1014
+ reasons: evaluation.reasons,
1015
+ },
1016
+ });
1017
+ throw permissionDenied();
1018
+ }
1019
+ this.emitOperation(auth, 'listen', path, 'allow', evaluation, {
1020
+ at,
1021
+ durationMs: Date.now() - at,
1022
+ origin: 'listener',
1023
+ detail: { event },
1024
+ });
1025
+ const listener = { id: listenerId, auth, event, path, cb, spec };
1026
+ this.childListeners.add(listener);
1027
+ this.emitListener('attach', listener, auth, {
1028
+ event,
1029
+ result: 'allow',
1030
+ detail: spec ? { query: spec } : undefined,
1031
+ });
1032
+ // Initial state. A query child listener records its ordered window so
1033
+ // the next write's diff knows what "the previous result" was; a
1034
+ // `child_added` query listener also replays the window (in window
1035
+ // order). A plain-ref listener replays raw direct children.
1036
+ if (spec) {
1037
+ const initialWindow = executeQuery(this.tree.read(path), spec);
1038
+ listener.lastWindow = initialWindow;
1039
+ if (event === 'child_added') {
1040
+ for (const { key, value } of initialWindow) {
1041
+ this.emitListener('delivery', listener, auth, {
1042
+ event,
1043
+ size: 1,
1044
+ sample: { key, val: value },
1045
+ detail: { initial: true, query: true },
1046
+ });
1047
+ try {
1048
+ cb({ key, val: value });
1049
+ }
1050
+ catch (e) {
1051
+ this.emitListener('errored', listener, auth, {
1052
+ event,
1053
+ result: 'error',
1054
+ error: { message: e instanceof Error ? e.message : String(e) },
1055
+ detail: { initial: true, query: true },
1056
+ });
1057
+ // Swallow — see fanOut.
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ else if (event === 'child_added') {
1063
+ // Initial replay — only `child_added` replays existing children.
1064
+ for (const { key, val } of this.directChildren(path)) {
1065
+ this.emitListener('delivery', listener, auth, {
1066
+ event,
1067
+ size: 1,
1068
+ sample: { key, val },
1069
+ detail: { initial: true },
1070
+ });
1071
+ try {
1072
+ cb({ key, val });
1073
+ }
1074
+ catch (e) {
1075
+ this.emitListener('errored', listener, auth, {
1076
+ event,
1077
+ result: 'error',
1078
+ error: { message: e instanceof Error ? e.message : String(e) },
1079
+ detail: { initial: true },
1080
+ });
1081
+ // Swallow — see fanOut.
1082
+ }
1083
+ }
1084
+ }
1085
+ return () => {
1086
+ this.childListeners.delete(listener);
1087
+ this.emitListener('detach', listener, auth, { event });
1088
+ };
1089
+ }
1090
+ /**
1091
+ * `off(refPath, eventType?, callback?)` — unsubscribe variant. Matches
1092
+ * the upstream contract (oracle: `rtdb-modular-off-stops-child-fires`):
1093
+ *
1094
+ * - `off(refPath)` removes ALL listeners (value + child) at the path.
1095
+ * - `off(refPath, 'value')` removes only `value` listeners at the path.
1096
+ * - `off(refPath, 'child_added')` (or any child event type) removes
1097
+ * only that child-event variety at the path.
1098
+ * - `off(refPath, eventType, cb)` removes only the matching callback.
1099
+ *
1100
+ * No-throw on absent listeners — matches the upstream behavior.
1101
+ */
1102
+ off(path, eventType, callback) {
1103
+ const canonical = joinPath(pathSegments(path));
1104
+ // Value listeners.
1105
+ if (eventType === undefined || eventType === 'value') {
1106
+ for (const l of [...this.valueListeners]) {
1107
+ if (joinPath(pathSegments(l.path)) !== canonical)
1108
+ continue;
1109
+ if (callback !== undefined && l.cb !== callback)
1110
+ continue;
1111
+ this.valueListeners.delete(l);
1112
+ this.emitListener('detach', l, l.auth, { event: 'value' });
1113
+ }
1114
+ }
1115
+ // Child listeners.
1116
+ const childEvents = [
1117
+ 'child_added',
1118
+ 'child_changed',
1119
+ 'child_removed',
1120
+ 'child_moved',
1121
+ ];
1122
+ const targetEvents = eventType === undefined
1123
+ ? childEvents
1124
+ : eventType === 'value'
1125
+ ? []
1126
+ : [eventType];
1127
+ if (targetEvents.length > 0) {
1128
+ for (const l of [...this.childListeners]) {
1129
+ if (!targetEvents.includes(l.event))
1130
+ continue;
1131
+ if (joinPath(pathSegments(l.path)) !== canonical)
1132
+ continue;
1133
+ if (callback !== undefined && l.cb !== callback)
1134
+ continue;
1135
+ this.childListeners.delete(l);
1136
+ this.emitListener('detach', l, l.auth, { event: l.event });
1137
+ }
1138
+ }
1139
+ }
1140
+ /** Direct children of `path` as `{ key, val }` pairs. Returns [] if
1141
+ * the path is absent or its value isn't an object. Key iteration
1142
+ * follows `Object.keys` order, which matches `firebase/database`'s
1143
+ * `orderByKey` default (insertion order for non-numeric keys). */
1144
+ directChildren(path) {
1145
+ const v = this.tree.read(path);
1146
+ if (v === null || typeof v !== 'object' || Array.isArray(v))
1147
+ return [];
1148
+ const out = [];
1149
+ for (const [k, val] of Object.entries(v)) {
1150
+ out.push({ key: k, val });
1151
+ }
1152
+ return out;
1153
+ }
1154
+ /** Snapshot the current direct children of every registered child
1155
+ * listener's parent path. Used to capture the "prior" half of the
1156
+ * diff before a write applies. */
1157
+ snapshotChildListenerParents() {
1158
+ const out = new Map();
1159
+ if (this.childListeners.size === 0)
1160
+ return out;
1161
+ for (const listener of this.childListeners) {
1162
+ const canonical = joinPath(pathSegments(listener.path));
1163
+ if (out.has(canonical))
1164
+ continue;
1165
+ const map = new Map();
1166
+ for (const { key, val } of this.directChildren(canonical)) {
1167
+ map.set(key, val);
1168
+ }
1169
+ out.set(canonical, map);
1170
+ }
1171
+ return out;
1172
+ }
1173
+ /**
1174
+ * Fan out child events after a write. `priorByParent` is the snapshot
1175
+ * captured by `snapshotChildListenerParents` before the write applied.
1176
+ *
1177
+ * For each listener, compute the diff between prior and current
1178
+ * children, then dispatch:
1179
+ * - `child_added` when a key appears that wasn't in prior.
1180
+ * - `child_changed` when a key's value transitions.
1181
+ * - `child_removed` when a prior key is gone.
1182
+ */
1183
+ fanOutChildren(priorByParent) {
1184
+ if (this.childListeners.size === 0)
1185
+ return;
1186
+ // Group PLAIN-ref listeners by their canonical parent path so we only
1187
+ // do one diff per parent regardless of how many listeners are attached.
1188
+ // Query listeners diff their ordered window per-listener (below) — the
1189
+ // raw key-set diff would ignore the window/ordering.
1190
+ const byParent = new Map();
1191
+ for (const listener of this.childListeners) {
1192
+ if (listener.spec) {
1193
+ this.fanOutQueryChild(listener);
1194
+ continue;
1195
+ }
1196
+ const canonical = joinPath(pathSegments(listener.path));
1197
+ const arr = byParent.get(canonical) ?? [];
1198
+ arr.push(listener);
1199
+ byParent.set(canonical, arr);
1200
+ }
1201
+ for (const [parentPath, listeners] of byParent) {
1202
+ const prior = priorByParent.get(parentPath) ?? new Map();
1203
+ const next = new Map();
1204
+ for (const { key, val } of this.directChildren(parentPath)) {
1205
+ next.set(key, val);
1206
+ }
1207
+ // Build event lists.
1208
+ const added = [];
1209
+ const changed = [];
1210
+ const removed = [];
1211
+ for (const [k, v] of next) {
1212
+ if (!prior.has(k)) {
1213
+ added.push({ key: k, val: v });
1214
+ }
1215
+ else if (!jsonValuesEqual(prior.get(k), v)) {
1216
+ changed.push({ key: k, val: v });
1217
+ }
1218
+ }
1219
+ for (const [k, v] of prior) {
1220
+ if (!next.has(k)) {
1221
+ removed.push({ key: k, val: v });
1222
+ }
1223
+ }
1224
+ for (const listener of listeners) {
1225
+ let events;
1226
+ switch (listener.event) {
1227
+ case 'child_added':
1228
+ events = added;
1229
+ break;
1230
+ case 'child_changed':
1231
+ events = changed;
1232
+ break;
1233
+ case 'child_removed':
1234
+ events = removed;
1235
+ break;
1236
+ case 'child_moved':
1237
+ events = [];
1238
+ break; // ordered-query only
1239
+ }
1240
+ for (const ev of events) {
1241
+ this.emitListener('delivery', listener, listener.auth, {
1242
+ event: listener.event,
1243
+ size: 1,
1244
+ sample: ev,
1245
+ });
1246
+ try {
1247
+ listener.cb(ev);
1248
+ }
1249
+ catch (e) {
1250
+ this.emitListener('errored', listener, listener.auth, {
1251
+ event: listener.event,
1252
+ result: 'error',
1253
+ error: { message: e instanceof Error ? e.message : String(e) },
1254
+ });
1255
+ // Swallow — see fanOut.
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ }
1261
+ /**
1262
+ * Fan out a single QUERY child listener after a write, diffing the
1263
+ * ordered window against the listener's last-fired window:
1264
+ *
1265
+ * - a key that ENTERED the window emits `child_added` (window order);
1266
+ * - a key that LEFT the window emits `child_removed` (carrying its
1267
+ * prior value);
1268
+ * - a key that stayed in-window but changed value emits `child_changed`.
1269
+ *
1270
+ * `child_moved` (reorder within the window) is deliberately NOT emitted
1271
+ * — the reorder / `previousChildName` semantics are held pending fresh
1272
+ * oracle captures (matrix row `rtdb-modular#137`). The window is always
1273
+ * advanced so a later real add/change/remove diffs correctly.
1274
+ */
1275
+ fanOutQueryChild(listener) {
1276
+ const prior = listener.lastWindow ?? [];
1277
+ const next = executeQuery(this.tree.read(listener.path), listener.spec);
1278
+ listener.lastWindow = next;
1279
+ const priorByKey = new Map(prior.map((r) => [r.key, r.value]));
1280
+ const nextByKey = new Map(next.map((r) => [r.key, r.value]));
1281
+ let events = [];
1282
+ switch (listener.event) {
1283
+ case 'child_added':
1284
+ for (const { key, value } of next) {
1285
+ if (!priorByKey.has(key))
1286
+ events.push({ key, val: value });
1287
+ }
1288
+ break;
1289
+ case 'child_changed':
1290
+ for (const { key, value } of next) {
1291
+ if (priorByKey.has(key) && !jsonValuesEqual(priorByKey.get(key), value)) {
1292
+ events.push({ key, val: value });
1293
+ }
1294
+ }
1295
+ break;
1296
+ case 'child_removed':
1297
+ for (const { key, value } of prior) {
1298
+ if (!nextByKey.has(key))
1299
+ events.push({ key, val: value });
1300
+ }
1301
+ break;
1302
+ case 'child_moved':
1303
+ // Held — reorder semantics pending two new oracle captures.
1304
+ events = [];
1305
+ break;
1306
+ }
1307
+ for (const ev of events) {
1308
+ this.emitListener('delivery', listener, listener.auth, {
1309
+ event: listener.event,
1310
+ size: 1,
1311
+ sample: ev,
1312
+ detail: { query: true },
1313
+ });
1314
+ try {
1315
+ listener.cb(ev);
1316
+ }
1317
+ catch (e) {
1318
+ this.emitListener('errored', listener, listener.auth, {
1319
+ event: listener.event,
1320
+ result: 'error',
1321
+ error: { message: e instanceof Error ? e.message : String(e) },
1322
+ detail: { query: true },
1323
+ });
1324
+ // Swallow — see fanOut.
1325
+ }
1326
+ }
1327
+ }
1328
+ // ─── Persistence (PersistableService) ──────────────────────────────
1329
+ //
1330
+ // The RTDB tree rides the sandbox persistence controller's `services`
1331
+ // blob exactly like the auth user DB. `getDatabase(sandbox)` registers
1332
+ // these hooks once per sandbox (see modular.ts). Together they give RTDB
1333
+ // the same durability contract as Firestore/auth: worker death / browser
1334
+ // restart restores the whole tree instead of losing it.
1335
+ /** Serialize the whole tree as a plain JSON value for the persistence
1336
+ * snapshot. Defensive deep-copy (via `DataTree.snapshot`). */
1337
+ exportTree() {
1338
+ return this.tree.snapshot();
1339
+ }
1340
+ /**
1341
+ * Replace the whole tree with a persisted snapshot, then fire listeners
1342
+ * so any live UI (Studio's RTDB tab) converges on the restored data
1343
+ * rather than showing a stale/empty view.
1344
+ *
1345
+ * REPLACE, not merge — the tree becomes EXACTLY the snapshot. On boot /
1346
+ * late-registration the tree is empty so this is a pure load; for a
1347
+ * runtime `loadSnapshot()` it clobbers divergent state, matching auth's
1348
+ * restore policy.
1349
+ *
1350
+ * Notification: we capture each child-listener parent's prior children
1351
+ * BEFORE the swap, then fan out value listeners from the root (every
1352
+ * listener re-reads its path) and child listeners against the diff. The
1353
+ * value-listener no-op suppression (DB-B8) means a listener whose subtree
1354
+ * is byte-identical to the snapshot won't spuriously re-fire.
1355
+ */
1356
+ restoreTree(root) {
1357
+ const priors = this.snapshotChildListenerParents();
1358
+ this.tree.restore(root ?? {});
1359
+ this.fanOut(['/']);
1360
+ this.fanOutChildren(priors);
1361
+ }
1362
+ /**
1363
+ * Register a persistence change-subscriber. Returns an unsubscribe.
1364
+ * Fired (best-effort, isolated) after every committed tree mutation so
1365
+ * the controller schedules a debounced flush.
1366
+ */
1367
+ subscribeWrites(onChange) {
1368
+ this.writeSubscribers.add(onChange);
1369
+ return () => {
1370
+ this.writeSubscribers.delete(onChange);
1371
+ };
1372
+ }
1373
+ /** Notify persistence subscribers that the tree changed. Best-effort +
1374
+ * isolated — a subscriber throw must never break the write. */
1375
+ notifyWrite() {
1376
+ if (this.writeSubscribers.size === 0)
1377
+ return;
1378
+ for (const sub of this.writeSubscribers) {
1379
+ try {
1380
+ sub();
1381
+ }
1382
+ catch {
1383
+ // Observational — never let a flush-scheduler throw break a write.
1384
+ }
1385
+ }
1386
+ }
1387
+ /** Generate a key without writing (advisory — same key the next
1388
+ * `push(path)` would produce). Used by the `ref.push().key`
1389
+ * pattern. Each call returns a fresh key. */
1390
+ mintKey() {
1391
+ return generatePushId();
1392
+ }
1393
+ /** Test helper — count value listeners. */
1394
+ listenerCount() {
1395
+ return this.valueListeners.size;
1396
+ }
1397
+ /** Test helper — count child listeners. */
1398
+ childListenerCount() {
1399
+ return this.childListeners.size;
1400
+ }
1401
+ }
1402
+ /** Normalize a path to its canonical `/`-joined form for event paths. */
1403
+ function canonicalPath(path) {
1404
+ return joinPath(pathSegments(path));
1405
+ }
1406
+ /**
1407
+ * Transaction-specific denial constructor. Pinned by oracle observation
1408
+ * `rtdb-modular-runtransaction-on-rules-denied-path.json`:
1409
+ *
1410
+ * - plain `Error` (NOT `FirebaseError`).
1411
+ * - `.message === 'permission_denied'` (LOWERCASE — distinct from
1412
+ * `set`/`get`'s `'PERMISSION_DENIED: Permission denied'` shape).
1413
+ * - **No** `.code` field on the error (prod observation shows
1414
+ * `code: null` — the FirebaseError code shape isn't applied to
1415
+ * transaction rejections).
1416
+ * - `.constructor.name === 'Error'`.
1417
+ *
1418
+ * This is a deliberately distinct shape from the regular
1419
+ * `permissionDenied()` used by `set`/`get`/`update` — prod really does
1420
+ * emit a different surface for `runTransaction` rejections. The test
1421
+ * suite asserts both shapes so a future "unify the error shape"
1422
+ * refactor would catch the divergence at unit time.
1423
+ */
1424
+ function transactionPermissionDenied() {
1425
+ return new Error('permission_denied');
1426
+ }
1427
+ /**
1428
+ * Pack an ordered list of `QueryRow`s into a JSON object — the shape
1429
+ * the `DataSnapshot.val()` contract returns from a query.
1430
+ *
1431
+ * RTDB query snapshots present the windowed rows as a key→value object;
1432
+ * ordering is observable via `snap.forEach` (NOT through `val()`'s
1433
+ * iteration order, which is unspecified for plain objects but happens
1434
+ * to be insertion order in modern V8). The sandbox snap-wrapper
1435
+ * preserves the row order so `snap.forEach` yields children in the
1436
+ * computed query order.
1437
+ */
1438
+ function rowsToVal(rows) {
1439
+ if (rows.length === 0)
1440
+ return null;
1441
+ const out = {};
1442
+ for (const { key, value } of rows) {
1443
+ out[key] = value;
1444
+ }
1445
+ return out;
1446
+ }
1447
+ /** Compare two windowed result lists. Used to decide whether a query
1448
+ * listener should re-fire. Uses RTDB JSON value equality rather than
1449
+ * `JSON.stringify` so a re-write that only reorders object keys is
1450
+ * correctly treated as "no change" (DB-B11: RTDB treats objects as
1451
+ * order-equal). */
1452
+ function windowsEqual(a, b) {
1453
+ if (a.length !== b.length)
1454
+ return false;
1455
+ for (let i = 0; i < a.length; i++) {
1456
+ const ai = a[i];
1457
+ const bi = b[i];
1458
+ if (ai.key !== bi.key)
1459
+ return false;
1460
+ if (!jsonValuesEqual(ai.value, bi.value))
1461
+ return false;
1462
+ }
1463
+ return true;
1464
+ }
1465
+ // Re-exports the modular SDK uses internally.
1466
+ export { cloneJson, pathSegments, joinPath } from './data-tree.js';
1467
+ //# sourceMappingURL=backend.js.map