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