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,1221 @@
|
|
|
1
|
+
import { assembleExpression } from '../grammar/FirestoreAssembler.js';
|
|
2
|
+
import { MapDiff } from './mapdiff.js';
|
|
3
|
+
import { FirestoreSet } from './firestore-set.js';
|
|
4
|
+
import { rulesValuesEqual } from './value-equality.js';
|
|
5
|
+
import { RulesValue, NO_OP } from './wrappers/base.js';
|
|
6
|
+
import { LatLng } from './wrappers/latlng.js';
|
|
7
|
+
import { Duration } from './wrappers/duration.js';
|
|
8
|
+
import { Timestamp } from './wrappers/timestamp.js';
|
|
9
|
+
import { Bytes } from './wrappers/bytes.js';
|
|
10
|
+
import { Path } from './wrappers/path.js';
|
|
11
|
+
import { RulesFloat } from './wrappers/float.js';
|
|
12
|
+
import { md5 as md5Bytes } from 'js-md5';
|
|
13
|
+
import { sha256 as sha256Bytes } from 'js-sha256';
|
|
14
|
+
// ═══ Evaluation errors ═══
|
|
15
|
+
export class EvalError extends Error {
|
|
16
|
+
expr;
|
|
17
|
+
constructor(message, expr) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.expr = expr;
|
|
20
|
+
this.name = 'EvalError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when the simulator encounters a feature it doesn't yet implement
|
|
25
|
+
* (an unknown built-in function, namespace method, or method on a wrapper
|
|
26
|
+
* type we haven't added). Distinct from EvalError so the handler can map it
|
|
27
|
+
* to TestResult.state = 'UNSUPPORTED' instead of a silent DENY — that way
|
|
28
|
+
* agents see "sim abstained" rather than "your rule is wrong" when the gap
|
|
29
|
+
* is on our side. See REBUILD_PLAN.md Item 0.A.
|
|
30
|
+
*/
|
|
31
|
+
export class UnsupportedError extends EvalError {
|
|
32
|
+
constructor(message, expr) {
|
|
33
|
+
super(message, expr);
|
|
34
|
+
this.name = 'UnsupportedError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Records every sub-expression evaluation. Attach to a
|
|
39
|
+
* `SimulationContext.trace` to enable; the evaluator no-ops when absent
|
|
40
|
+
* so unsentinel'd callers pay zero cost.
|
|
41
|
+
*
|
|
42
|
+
* Recording strategy: each call to `evaluate(expr, ...)` is wrapped via
|
|
43
|
+
* `capture(expr, fn)`, which reserves a slot in `entries` (assigning
|
|
44
|
+
* `parent` from the current stack), pushes the slot index, invokes
|
|
45
|
+
* `fn`, then unwinds. This keeps recursion in the existing evaluator
|
|
46
|
+
* paths working unchanged — the wrapper sits on top.
|
|
47
|
+
*/
|
|
48
|
+
export class TraceRecorder {
|
|
49
|
+
entries = [];
|
|
50
|
+
parents = [];
|
|
51
|
+
/** Stack of enclosing user-defined function names. The top of stack is
|
|
52
|
+
* stamped onto each pushed entry as `inlinedFrom`. Independent of the
|
|
53
|
+
* `parents` stack because parameter expressions evaluate inside the
|
|
54
|
+
* capture stack but *outside* the function frame. */
|
|
55
|
+
frames = [];
|
|
56
|
+
capture(expr, fn) {
|
|
57
|
+
const index = this.entries.length;
|
|
58
|
+
const parent = this.parents.length > 0 ? this.parents[this.parents.length - 1] : null;
|
|
59
|
+
// Reserve the slot first so any child evaluations point at us as
|
|
60
|
+
// their parent. The `value` field is filled in after `fn()` runs.
|
|
61
|
+
const entry = { source: assembleExpression(expr), kind: expr.type, parent };
|
|
62
|
+
this.stampFrame(entry);
|
|
63
|
+
this.entries.push(entry);
|
|
64
|
+
this.parents.push(index);
|
|
65
|
+
try {
|
|
66
|
+
const value = fn();
|
|
67
|
+
this.entries[index].value = value;
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
this.entries[index].error = e instanceof Error ? e.message : String(e);
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this.parents.pop();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Record an operand the evaluator skipped because the LHS short-circuited.
|
|
79
|
+
* Emits an entry under the *current* parent so the trace tree mirrors
|
|
80
|
+
* the source AST (skip placeholder is a sibling of the LHS, not nested). */
|
|
81
|
+
skip(expr) {
|
|
82
|
+
const parent = this.parents.length > 0 ? this.parents[this.parents.length - 1] : null;
|
|
83
|
+
const entry = {
|
|
84
|
+
source: assembleExpression(expr),
|
|
85
|
+
kind: expr.type,
|
|
86
|
+
parent,
|
|
87
|
+
skipped: true,
|
|
88
|
+
};
|
|
89
|
+
this.stampFrame(entry);
|
|
90
|
+
this.entries.push(entry);
|
|
91
|
+
}
|
|
92
|
+
/** Push a user-defined function frame onto the inlinedFrom stack.
|
|
93
|
+
* Returns the popped value of the previous top (currently unused —
|
|
94
|
+
* callers pair `enterFrame` with `exitFrame()` via the stack, not a
|
|
95
|
+
* saved token — but keeping a return value lets future code switch
|
|
96
|
+
* to a save/restore idiom without API churn). */
|
|
97
|
+
enterFrame(name) {
|
|
98
|
+
this.frames.push(name);
|
|
99
|
+
}
|
|
100
|
+
/** Pop the topmost frame. Safe to call on an empty stack — no-ops. */
|
|
101
|
+
exitFrame() {
|
|
102
|
+
this.frames.pop();
|
|
103
|
+
}
|
|
104
|
+
stampFrame(entry) {
|
|
105
|
+
if (this.frames.length === 0)
|
|
106
|
+
return;
|
|
107
|
+
entry.inlinedFrom = { name: this.frames[this.frames.length - 1] };
|
|
108
|
+
}
|
|
109
|
+
/** Annotate the entry at `index` as a `let` binding — the value was
|
|
110
|
+
* bound to `name` in scope. Called from `evaluateFunctionCall` after
|
|
111
|
+
* each `let <name> = <expr>` line; the index is the binding root's
|
|
112
|
+
* position in the trace (`entries.length` captured BEFORE the call).
|
|
113
|
+
* Using a stable index instead of "last entry" is necessary because
|
|
114
|
+
* the binding's expression may have recursive children — the most
|
|
115
|
+
* recently pushed entry is the deepest leaf, not the root. */
|
|
116
|
+
markEntryAsLetBinding(index, name) {
|
|
117
|
+
if (index < 0 || index >= this.entries.length)
|
|
118
|
+
return;
|
|
119
|
+
this.entries[index].letBinding = { name };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// ═══ Evaluator ═══
|
|
123
|
+
/**
|
|
124
|
+
* Evaluate an expression against the simulation context. The public
|
|
125
|
+
* entry point — recursive calls inside the evaluator come back through
|
|
126
|
+
* here, so the optional trace wrapping naturally applies to every
|
|
127
|
+
* sub-expression.
|
|
128
|
+
*/
|
|
129
|
+
export function evaluate(expr, ctx, scope = {}) {
|
|
130
|
+
if (!ctx.trace)
|
|
131
|
+
return evaluateExpr(expr, ctx, scope);
|
|
132
|
+
return ctx.trace.capture(expr, () => evaluateExpr(expr, ctx, scope));
|
|
133
|
+
}
|
|
134
|
+
function evaluateExpr(expr, ctx, scope) {
|
|
135
|
+
switch (expr.type) {
|
|
136
|
+
// ═══ Layer 1: Literals, identifiers, binary ops ═══
|
|
137
|
+
case 'literal':
|
|
138
|
+
// RULES-B5: a source literal written with a decimal point is a FLOAT
|
|
139
|
+
// (`1.0`, `1.5`), even when its value is integral. The grammar's
|
|
140
|
+
// `number_float` rule preserves the original text in `raw`, so a `.`
|
|
141
|
+
// there is the float signal. Bare integer literals (`1`) stay raw
|
|
142
|
+
// numbers (= int). Non-numeric literals (string/bool/null) pass through.
|
|
143
|
+
if (typeof expr.value === 'number' && expr.raw.includes('.')) {
|
|
144
|
+
return new RulesFloat(expr.value);
|
|
145
|
+
}
|
|
146
|
+
return expr.value;
|
|
147
|
+
case 'identifier':
|
|
148
|
+
return resolveIdentifier(expr.name, ctx, scope);
|
|
149
|
+
case 'binaryOp':
|
|
150
|
+
return evaluateBinaryOp(expr.op, expr.left, expr.right, ctx, scope);
|
|
151
|
+
case 'unaryOp': {
|
|
152
|
+
if (expr.op === '!')
|
|
153
|
+
return !evaluate(expr.operand, ctx, scope);
|
|
154
|
+
if (expr.op === '-') {
|
|
155
|
+
const v = evaluate(expr.operand, ctx, scope);
|
|
156
|
+
// RULES-B5: negating a float stays a float (`-1.5 is float`); negating
|
|
157
|
+
// an int stays an int. Preserve the operand's type tag.
|
|
158
|
+
if (v instanceof RulesFloat)
|
|
159
|
+
return new RulesFloat(-v.value);
|
|
160
|
+
return -v;
|
|
161
|
+
}
|
|
162
|
+
throw new EvalError(`Unknown unary op: ${expr.op}`, expr);
|
|
163
|
+
}
|
|
164
|
+
case 'ternary': {
|
|
165
|
+
const cond = evaluate(expr.condition, ctx, scope);
|
|
166
|
+
return cond ? evaluate(expr.consequent, ctx, scope) : evaluate(expr.alternate, ctx, scope);
|
|
167
|
+
}
|
|
168
|
+
// ═══ Layer 2: Member access, bracket access, `in` ═══
|
|
169
|
+
case 'memberAccess': {
|
|
170
|
+
const obj = evaluate(expr.object, ctx, scope);
|
|
171
|
+
// RULES-B2: field access on null/undefined is a runtime ERROR in
|
|
172
|
+
// Firestore rules (CEL field selection has no overload for null),
|
|
173
|
+
// not a silent null. Production denies the request. The common
|
|
174
|
+
// guard `request.auth != null && request.auth.uid == ...` stays
|
|
175
|
+
// safe because `&&` absorbs this error commutatively (RULES-B3) —
|
|
176
|
+
// the `false` LHS uniquely determines the result.
|
|
177
|
+
if (obj === null || obj === undefined) {
|
|
178
|
+
throw new EvalError(`Property '${expr.property}' accessed on ${obj === null ? 'null' : 'undefined'} value`, expr);
|
|
179
|
+
}
|
|
180
|
+
// Wrapper-owned property dispatch (Item 0.B hook 2). Wrappers like
|
|
181
|
+
// Timestamp expose no readable properties — `t.year` returns null,
|
|
182
|
+
// `t.year()` goes through callMethod. The base default returns null
|
|
183
|
+
// so unknown properties stay consistent with Firestore's "missing
|
|
184
|
+
// map key reads as null" semantics.
|
|
185
|
+
if (obj instanceof RulesValue)
|
|
186
|
+
return obj.field(expr.property);
|
|
187
|
+
if (obj instanceof MapDiff) {
|
|
188
|
+
// MapDiff methods that return FirestoreSet
|
|
189
|
+
switch (expr.property) {
|
|
190
|
+
case 'addedKeys': return () => obj.addedKeys();
|
|
191
|
+
case 'removedKeys': return () => obj.removedKeys();
|
|
192
|
+
case 'changedKeys': return () => obj.changedKeys();
|
|
193
|
+
case 'affectedKeys': return () => obj.affectedKeys();
|
|
194
|
+
case 'unchangedKeys': return () => obj.unchangedKeys();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (obj instanceof FirestoreSet) {
|
|
198
|
+
switch (expr.property) {
|
|
199
|
+
case 'size': return () => obj.size();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// RULES-B2: reading a key that does not exist on a map is a runtime
|
|
203
|
+
// ERROR in Firestore rules (`resource.data.typo` denies the request),
|
|
204
|
+
// not the silent null this path used to return — that inversion made
|
|
205
|
+
// `resource.data.typo == null` ALLOW where production DENYs. A key
|
|
206
|
+
// present with an explicit null value still returns null (the key
|
|
207
|
+
// exists). Guard with the `in` operator (`'f' in resource.data`) or
|
|
208
|
+
// `resource.data.get('f', default)` to read a possibly-absent field.
|
|
209
|
+
if (Object.hasOwn(obj, expr.property)) {
|
|
210
|
+
return obj[expr.property];
|
|
211
|
+
}
|
|
212
|
+
throw new EvalError(`No field '${expr.property}' on map (use 'in' or .get() to read a possibly-absent field)`, expr);
|
|
213
|
+
}
|
|
214
|
+
case 'bracketAccess': {
|
|
215
|
+
const obj = evaluate(expr.object, ctx, scope);
|
|
216
|
+
const idx = evaluate(expr.index, ctx, scope);
|
|
217
|
+
// RULES-B2: index/key access on null/undefined errors in production
|
|
218
|
+
// (no CEL index overload for null), absorbed by &&/|| where guarded.
|
|
219
|
+
if (obj === null || obj === undefined) {
|
|
220
|
+
throw new EvalError(`Index access on ${obj === null ? 'null' : 'undefined'} value`, expr);
|
|
221
|
+
}
|
|
222
|
+
// Wrapper-owned bracket dispatch (Item 0.B hook 2, bracket variant).
|
|
223
|
+
// Path is the only wrapper that uses bracket access semantically
|
|
224
|
+
// (`/users/$(uid)`-style binding), but routing every wrapper through
|
|
225
|
+
// `field()` here keeps the contract uniform — wrappers that don't
|
|
226
|
+
// implement bracket access return null.
|
|
227
|
+
if (obj instanceof RulesValue)
|
|
228
|
+
return obj.field(String(idx));
|
|
229
|
+
// RULES-B2 scope note: DYNAMIC bracket/index access (`data[expr]`) is the
|
|
230
|
+
// documented idiom for "look up a key that may be absent" (e.g. a chess
|
|
231
|
+
// rule's `cfg().paths[from][to]`, `resource.data[squareVar]`). The
|
|
232
|
+
// Firebase docs explicitly confirm the ERROR semantics for DOTTED field
|
|
233
|
+
// access (`resource.data.typo`) — handled in `memberAccess` above — but
|
|
234
|
+
// NOT for dynamic index access, and flagship rules rely on null-on-miss
|
|
235
|
+
// here. Without an emulator to confirm bracket-vs-field divergence, we
|
|
236
|
+
// keep dynamic index access returning null on a missing key (the
|
|
237
|
+
// conservative, non-bug-laundering choice — the disputed-edge STOP).
|
|
238
|
+
// Present-with-null still returns null.
|
|
239
|
+
const key = String(idx);
|
|
240
|
+
return Object.hasOwn(obj, key) ? obj[key] : null;
|
|
241
|
+
}
|
|
242
|
+
case 'sliceAccess': {
|
|
243
|
+
// Range slice `obj[start:end]` — Item 4 (REBUILD_PLAN.md).
|
|
244
|
+
// Per type table:
|
|
245
|
+
// List: `[i:j]` returns sub-list, j exclusive.
|
|
246
|
+
// String: `[i:j]` returns substring, j exclusive.
|
|
247
|
+
// Indices must be integers; production rejects non-integer (incl.
|
|
248
|
+
// booleans coerced) so we surface as EvalError → DENY.
|
|
249
|
+
// Out-of-bounds: clamp to [0, length] (matches JS Array.slice and
|
|
250
|
+
// is the standard CEL-style semantics — locked in by parity pack).
|
|
251
|
+
// Negative indices: rejected (CEL doesn't support Python negatives).
|
|
252
|
+
const obj = evaluate(expr.object, ctx, scope);
|
|
253
|
+
const start = evaluate(expr.start, ctx, scope);
|
|
254
|
+
const end = evaluate(expr.end, ctx, scope);
|
|
255
|
+
if (obj === null || obj === undefined)
|
|
256
|
+
return null;
|
|
257
|
+
if (typeof start !== 'number' || !Number.isInteger(start)) {
|
|
258
|
+
throw new EvalError(`Slice start must be an integer, got ${typeof start}`);
|
|
259
|
+
}
|
|
260
|
+
if (typeof end !== 'number' || !Number.isInteger(end)) {
|
|
261
|
+
throw new EvalError(`Slice end must be an integer, got ${typeof end}`);
|
|
262
|
+
}
|
|
263
|
+
if (start < 0 || end < 0) {
|
|
264
|
+
throw new EvalError(`Slice indices must be non-negative, got [${start}:${end}]`);
|
|
265
|
+
}
|
|
266
|
+
if (typeof obj === 'string')
|
|
267
|
+
return obj.slice(start, end);
|
|
268
|
+
if (Array.isArray(obj))
|
|
269
|
+
return obj.slice(start, end);
|
|
270
|
+
throw new EvalError(`Slice not supported on ${typeof obj}`);
|
|
271
|
+
}
|
|
272
|
+
case 'inExpr': {
|
|
273
|
+
const element = evaluate(expr.element, ctx, scope);
|
|
274
|
+
const collection = evaluate(expr.collection, ctx, scope);
|
|
275
|
+
if (collection === null || collection === undefined)
|
|
276
|
+
return false;
|
|
277
|
+
// Use Rules value equality instead of Array.includes so wrapper
|
|
278
|
+
// value-equality applies (Item 0.B hook 6). Without this,
|
|
279
|
+
// `someTimestamp in [t1, t2]` is always false because `===`
|
|
280
|
+
// doesn't see the wrappers as equal even when their contents
|
|
281
|
+
// match. Map-key membership stays as a String() check — keys
|
|
282
|
+
// are always strings in Firestore rules.
|
|
283
|
+
if (Array.isArray(collection)) {
|
|
284
|
+
return collection.some(v => rulesValuesEqual(v, element));
|
|
285
|
+
}
|
|
286
|
+
// RULES-B7: map-key membership must use OWN keys only. `in` walks the JS
|
|
287
|
+
// prototype chain, so `'toString' in resource.data` wrongly returned
|
|
288
|
+
// true (and an `in`-guarded access then leaked the Object method).
|
|
289
|
+
// Object.hasOwn ignores inherited keys, matching Firestore's "the map
|
|
290
|
+
// has no inherited keys" model.
|
|
291
|
+
if (typeof collection === 'object')
|
|
292
|
+
return Object.hasOwn(collection, String(element));
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
case 'isExpr': {
|
|
296
|
+
const value = evaluate(expr.value, ctx, scope);
|
|
297
|
+
switch (expr.typeName) {
|
|
298
|
+
case 'string': return typeof value === 'string';
|
|
299
|
+
// RULES-B5: int and float are DISTINCT types. A bare JS number is an
|
|
300
|
+
// int; a RulesFloat wrapper is a float. `number` matches either.
|
|
301
|
+
// `1.5 is int` → false, `1 is float` → false, `1.0 is float` → true.
|
|
302
|
+
case 'int':
|
|
303
|
+
if (value instanceof RulesValue)
|
|
304
|
+
return false;
|
|
305
|
+
return typeof value === 'number';
|
|
306
|
+
case 'float':
|
|
307
|
+
return value instanceof RulesFloat;
|
|
308
|
+
case 'number':
|
|
309
|
+
// Reject wrappers that coerce to NaN (LatLng/Path) — they're
|
|
310
|
+
// not numbers even though valueOf() returns a Number. A RulesFloat
|
|
311
|
+
// IS a number, so it must pass here.
|
|
312
|
+
if (value instanceof RulesFloat)
|
|
313
|
+
return true;
|
|
314
|
+
if (value instanceof RulesValue)
|
|
315
|
+
return false;
|
|
316
|
+
return typeof value === 'number';
|
|
317
|
+
case 'bool': return typeof value === 'boolean';
|
|
318
|
+
case 'null': return value === null;
|
|
319
|
+
case 'list': return Array.isArray(value);
|
|
320
|
+
case 'map':
|
|
321
|
+
// Wrappers are objects but `is map` should be false for them —
|
|
322
|
+
// a Timestamp is not a Map. Filter via instanceof RulesValue.
|
|
323
|
+
if (value instanceof RulesValue)
|
|
324
|
+
return false;
|
|
325
|
+
// RULES-B12: MapDiff and FirestoreSet are internal types, not user
|
|
326
|
+
// maps — `someDiff is map` / `someSet is map` must be false.
|
|
327
|
+
if (value instanceof MapDiff || value instanceof FirestoreSet)
|
|
328
|
+
return false;
|
|
329
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
330
|
+
default:
|
|
331
|
+
// Wrapper type tags ('timestamp', 'duration', 'bytes', 'latlng',
|
|
332
|
+
// 'path') match via typeName. Item 0.B hook 5.
|
|
333
|
+
if (value instanceof RulesValue)
|
|
334
|
+
return value.typeName === expr.typeName;
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
case 'listLiteral':
|
|
339
|
+
return expr.elements.map(e => evaluate(e, ctx, scope));
|
|
340
|
+
case 'mapLiteral': {
|
|
341
|
+
const map = {};
|
|
342
|
+
for (const entry of expr.entries) {
|
|
343
|
+
const key = String(evaluate(entry.key, ctx, scope));
|
|
344
|
+
map[key] = evaluate(entry.value, ctx, scope);
|
|
345
|
+
}
|
|
346
|
+
return map;
|
|
347
|
+
}
|
|
348
|
+
// ═══ Layer 3: Function calls ═══
|
|
349
|
+
case 'functionCall':
|
|
350
|
+
return evaluateFunctionCall(expr.name, expr.args, ctx, scope);
|
|
351
|
+
// ═══ Layer 4: Method calls ═══
|
|
352
|
+
case 'methodCall':
|
|
353
|
+
return evaluateMethodCall(expr.object, expr.method, expr.args, ctx, scope);
|
|
354
|
+
// ═══ Layer 5: Path literals ═══
|
|
355
|
+
case 'pathLiteral': {
|
|
356
|
+
// Resolve segments — string literals pass through, embedded
|
|
357
|
+
// expressions (`$(uid)`) get evaluated and stringified.
|
|
358
|
+
// Item 5.4: returns Path wrapper instead of raw string so `is path`
|
|
359
|
+
// works. get/exists already String()-coerce, so resolveGet/Exists
|
|
360
|
+
// see the same '/foo/bar' shape via Path.toString().
|
|
361
|
+
const parts = [];
|
|
362
|
+
for (const seg of expr.segments) {
|
|
363
|
+
if (typeof seg === 'string') {
|
|
364
|
+
parts.push(seg);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
parts.push(String(evaluate(seg, ctx, scope)));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return new Path(parts);
|
|
371
|
+
}
|
|
372
|
+
default:
|
|
373
|
+
throw new EvalError(`Unknown expression type: ${expr.type}`, expr);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// ═══ Identifier resolution ═══
|
|
377
|
+
function resolveIdentifier(name, ctx, scope) {
|
|
378
|
+
// Local scope first (let bindings, function parameters)
|
|
379
|
+
if (name in scope)
|
|
380
|
+
return scope[name];
|
|
381
|
+
// Path variables
|
|
382
|
+
if (name in ctx.pathVariables)
|
|
383
|
+
return ctx.pathVariables[name];
|
|
384
|
+
// Built-in globals
|
|
385
|
+
switch (name) {
|
|
386
|
+
case 'request': return ctx.request;
|
|
387
|
+
case 'resource': return ctx.resource;
|
|
388
|
+
case 'true': return true;
|
|
389
|
+
case 'false': return false;
|
|
390
|
+
case 'null': return null;
|
|
391
|
+
}
|
|
392
|
+
// RULES-B2: an unbound identifier (typo'd variable, undeclared name) is a
|
|
393
|
+
// compile/runtime error in production rules, not a silent `undefined` that
|
|
394
|
+
// later reads as null. Surface it as an EvalError so the handler DENYs and
|
|
395
|
+
// &&/|| can absorb it where guarded.
|
|
396
|
+
throw new EvalError(`Undefined variable '${name}'`);
|
|
397
|
+
}
|
|
398
|
+
/** True for the built-in top-level globals resolvable as values. Used to
|
|
399
|
+
* decide whether an unknown method-call target is a possible (unimplemented)
|
|
400
|
+
* namespace vs a real resolvable identifier. */
|
|
401
|
+
function isKnownGlobal(name) {
|
|
402
|
+
return name === 'request' || name === 'resource'
|
|
403
|
+
|| name === 'true' || name === 'false' || name === 'null';
|
|
404
|
+
}
|
|
405
|
+
// ═══ Binary operations with short-circuit ═══
|
|
406
|
+
function evaluateBinaryOp(op, left, right, ctx, scope) {
|
|
407
|
+
// RULES-B3: && and || are COMMUTATIVE error-absorbing operators in CEL,
|
|
408
|
+
// not plain left-to-right short-circuit. Per the CEL spec: "if any of
|
|
409
|
+
// their operands uniquely determines the result (false for &&, true for
|
|
410
|
+
// ||) the other operand may or may not be evaluated, and if that
|
|
411
|
+
// evaluation produces a runtime error, it will be ignored." So
|
|
412
|
+
// `error && false` → false and `error || true` → true — the error is
|
|
413
|
+
// absorbed by the determining operand regardless of position.
|
|
414
|
+
//
|
|
415
|
+
// We preserve laziness in the no-error path (the determining LHS skips
|
|
416
|
+
// the RHS), and only evaluate the RHS to attempt absorption when the LHS
|
|
417
|
+
// either errored or did not determine the result. If neither operand
|
|
418
|
+
// determines the result and one errored, the error propagates (re-thrown)
|
|
419
|
+
// so the handler DENYs.
|
|
420
|
+
if (op === '&&') {
|
|
421
|
+
let lv, lErr;
|
|
422
|
+
try {
|
|
423
|
+
lv = evaluate(left, ctx, scope);
|
|
424
|
+
}
|
|
425
|
+
catch (e) {
|
|
426
|
+
lErr = e;
|
|
427
|
+
}
|
|
428
|
+
if (lErr === undefined && !lv) {
|
|
429
|
+
// LHS is false → determines the result; RHS need not be evaluated.
|
|
430
|
+
ctx.trace?.skip(right);
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
// LHS errored or was true — RHS may absorb the error (if RHS is false)
|
|
434
|
+
// or determine the truthy result.
|
|
435
|
+
let rv, rErr;
|
|
436
|
+
try {
|
|
437
|
+
rv = evaluate(right, ctx, scope);
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
rErr = e;
|
|
441
|
+
}
|
|
442
|
+
if (rErr === undefined && !rv)
|
|
443
|
+
return false; // RHS false absorbs any LHS error
|
|
444
|
+
if (lErr !== undefined)
|
|
445
|
+
throw lErr; // LHS error not absorbed
|
|
446
|
+
if (rErr !== undefined)
|
|
447
|
+
throw rErr; // RHS error, LHS was true
|
|
448
|
+
return !!rv;
|
|
449
|
+
}
|
|
450
|
+
if (op === '||') {
|
|
451
|
+
let lv, lErr;
|
|
452
|
+
try {
|
|
453
|
+
lv = evaluate(left, ctx, scope);
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
lErr = e;
|
|
457
|
+
}
|
|
458
|
+
if (lErr === undefined && lv) {
|
|
459
|
+
// LHS is true → determines the result; RHS need not be evaluated.
|
|
460
|
+
ctx.trace?.skip(right);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
let rv, rErr;
|
|
464
|
+
try {
|
|
465
|
+
rv = evaluate(right, ctx, scope);
|
|
466
|
+
}
|
|
467
|
+
catch (e) {
|
|
468
|
+
rErr = e;
|
|
469
|
+
}
|
|
470
|
+
if (rErr === undefined && rv)
|
|
471
|
+
return true; // RHS true absorbs any LHS error
|
|
472
|
+
if (lErr !== undefined)
|
|
473
|
+
throw lErr; // LHS error not absorbed
|
|
474
|
+
if (rErr !== undefined)
|
|
475
|
+
throw rErr; // RHS error, LHS was false
|
|
476
|
+
return !!rv;
|
|
477
|
+
}
|
|
478
|
+
const lv = evaluate(left, ctx, scope);
|
|
479
|
+
const rv = evaluate(right, ctx, scope);
|
|
480
|
+
// Wrapper-aware binary op dispatch (Item 0.B hook 4). Cross-type
|
|
481
|
+
// arithmetic like `Timestamp + Duration → Timestamp` and lexicographic
|
|
482
|
+
// `Bytes < Bytes` are handled here via the wrapper's own `binaryOp`
|
|
483
|
+
// method. NO_OP means the wrapper doesn't claim this op — fall through
|
|
484
|
+
// to the generic numeric switch below.
|
|
485
|
+
//
|
|
486
|
+
// == and != intentionally bypass this hook — they route through
|
|
487
|
+
// rulesValuesEqual above, which already calls the wrapper's
|
|
488
|
+
// `equals()`. Splitting "value equality" from "operator dispatch"
|
|
489
|
+
// keeps each wrapper's contract clean.
|
|
490
|
+
if (op !== '==' && op !== '!=') {
|
|
491
|
+
if (lv instanceof RulesValue) {
|
|
492
|
+
const r = lv.binaryOp(op, rv);
|
|
493
|
+
if (r !== NO_OP)
|
|
494
|
+
return r;
|
|
495
|
+
}
|
|
496
|
+
// RULES-B5: `int OP float` (bare-number LHS, RulesFloat RHS) must yield a
|
|
497
|
+
// float and preserve operand ORDER (unlike Duration/Timestamp this is a
|
|
498
|
+
// genuinely ordered numeric op for `-`/`/`/`%` and the comparisons). Route
|
|
499
|
+
// it through `RulesFloat(lv).binaryOp(op, rv)` so the result re-tags as a
|
|
500
|
+
// float and `5 / 2.0` does float division (2.5), not int truncation.
|
|
501
|
+
if (typeof lv === 'number' && rv instanceof RulesFloat) {
|
|
502
|
+
const r = new RulesFloat(lv).binaryOp(op, rv);
|
|
503
|
+
if (r !== NO_OP)
|
|
504
|
+
return r;
|
|
505
|
+
}
|
|
506
|
+
// Right-side dispatch for symmetric/commutative ops only. Avoids
|
|
507
|
+
// surprises like `1 - duration` accidentally reversing semantics.
|
|
508
|
+
if (rv instanceof RulesValue && (op === '+' || op === '*')) {
|
|
509
|
+
const r = rv.binaryOp(op, lv);
|
|
510
|
+
if (r !== NO_OP)
|
|
511
|
+
return r;
|
|
512
|
+
}
|
|
513
|
+
// Risk 2 (REBUILD_PLAN Item 1.2): if either operand is still a
|
|
514
|
+
// RulesValue at this point, the only remaining path is generic
|
|
515
|
+
// numeric coercion via valueOf(). For LatLng that yields NaN
|
|
516
|
+
// (silent DENY); for Duration/Timestamp/Bytes it would silently
|
|
517
|
+
// drop the type. Both are real type errors in the rule (e.g.
|
|
518
|
+
// `latlng + 1`, `duration > 60`), not sim gaps — surface as
|
|
519
|
+
// EvalError so handler.ts maps to DENY-with-error instead of a
|
|
520
|
+
// misleading "passed but came out false" or "UNSUPPORTED".
|
|
521
|
+
//
|
|
522
|
+
// Exception: `string + wrapper` is documented coercion that flows
|
|
523
|
+
// through `String(rv)` concat in the switch below.
|
|
524
|
+
if (op !== '+' || (typeof lv !== 'string' && typeof rv !== 'string')) {
|
|
525
|
+
if (lv instanceof RulesValue || rv instanceof RulesValue) {
|
|
526
|
+
const lhsType = lv instanceof RulesValue ? lv.typeName : typeof lv;
|
|
527
|
+
const rhsType = rv instanceof RulesValue ? rv.typeName : typeof rv;
|
|
528
|
+
throw new EvalError(`Operator '${op}' is not defined between ${lhsType} and ${rhsType}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// RULES-B12: ordered comparisons (`< > <= >=`) require both operands to be the
|
|
533
|
+
// SAME comparable type. CEL has no cross-type ordering overload, so `'a' < 1`
|
|
534
|
+
// is an error, not the JS-coerced `false` the bare `as number` casts produced.
|
|
535
|
+
// (Comparable wrappers — Bytes, Timestamp, Duration — were already dispatched
|
|
536
|
+
// via binaryOp above; anything reaching here is a raw number or string.)
|
|
537
|
+
if (op === '<' || op === '>' || op === '<=' || op === '>=') {
|
|
538
|
+
const sameComparable = (typeof lv === 'number' && typeof rv === 'number') ||
|
|
539
|
+
(typeof lv === 'string' && typeof rv === 'string');
|
|
540
|
+
if (!sameComparable) {
|
|
541
|
+
throw new EvalError(`Operator '${op}' is not defined between ${typeof lv} and ${typeof rv}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
switch (op) {
|
|
545
|
+
case '==': return lv === rv || rulesValuesEqual(lv, rv);
|
|
546
|
+
case '!=': return !(lv === rv || rulesValuesEqual(lv, rv));
|
|
547
|
+
case '<': return lv < rv;
|
|
548
|
+
case '>': return lv > rv;
|
|
549
|
+
case '<=': return lv <= rv;
|
|
550
|
+
case '>=': return lv >= rv;
|
|
551
|
+
case '+': {
|
|
552
|
+
// RULES-B6: CEL `+` has no mixed-type overloads — both operands must be
|
|
553
|
+
// the same type. Allowed: string+string, number+number, list+list (concat).
|
|
554
|
+
// `'a' + 1` is an error (no string+int overload), not the silent
|
|
555
|
+
// `String(rv)` coercion the old impl did. (Wrapper `+` like
|
|
556
|
+
// Timestamp+Duration was already handled above via binaryOp dispatch;
|
|
557
|
+
// the documented `string + wrapper` affordance also flows above this.)
|
|
558
|
+
if (Array.isArray(lv) && Array.isArray(rv))
|
|
559
|
+
return [...lv, ...rv];
|
|
560
|
+
if (typeof lv === 'string' && typeof rv === 'string')
|
|
561
|
+
return lv + rv;
|
|
562
|
+
if (typeof lv === 'number' && typeof rv === 'number')
|
|
563
|
+
return lv + rv;
|
|
564
|
+
// Documented sim affordance (see the `op !== '+'` guard above): a
|
|
565
|
+
// `string + wrapper` (e.g. `'at ' + request.time`) concatenates the
|
|
566
|
+
// wrapper's string form. Preserved here for back-compat with that path.
|
|
567
|
+
if (typeof lv === 'string' && rv instanceof RulesValue)
|
|
568
|
+
return lv + String(rv);
|
|
569
|
+
throw new EvalError(`Operator '+' is not defined between ${Array.isArray(lv) ? 'list' : typeof lv} and ${Array.isArray(rv) ? 'list' : typeof rv}`);
|
|
570
|
+
}
|
|
571
|
+
case '-': return lv - rv;
|
|
572
|
+
case '*': return lv * rv;
|
|
573
|
+
case '/':
|
|
574
|
+
// RULES-B5: both operands are bare numbers here (= int ÷ int; any float
|
|
575
|
+
// operand was already handled by RulesFloat.binaryOp above). CEL INT64
|
|
576
|
+
// division TRUNCATES toward zero (`10 / 4 == 2`) and ERRORS on a zero
|
|
577
|
+
// divisor (it does NOT yield ±Infinity the way JS / float division does).
|
|
578
|
+
// The EvalError propagates via the tri-state so the rule DENYs.
|
|
579
|
+
if (rv === 0) {
|
|
580
|
+
throw new EvalError('Division by zero');
|
|
581
|
+
}
|
|
582
|
+
return Math.trunc(lv / rv);
|
|
583
|
+
case '%':
|
|
584
|
+
// CEL INT64 modulo likewise errors on a zero divisor (JS would give NaN).
|
|
585
|
+
if (rv === 0) {
|
|
586
|
+
throw new EvalError('Modulo by zero');
|
|
587
|
+
}
|
|
588
|
+
return lv % rv;
|
|
589
|
+
default: throw new EvalError(`Unknown binary op: ${op}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// ═══ Type-conversion builtins (RULES-B5 / RULES-B6) ═══
|
|
593
|
+
//
|
|
594
|
+
// CEL's int()/float()/bool() conversions are STRICT — they error on inputs
|
|
595
|
+
// that JS would silently coerce. The pre-fix impl used `parseInt` (which
|
|
596
|
+
// grabs a leading numeric prefix, so `int('12abc')` → 12) and `Boolean`
|
|
597
|
+
// (truthiness, so `bool('false')` → true). Both diverge from prod, which
|
|
598
|
+
// rejects a malformed string outright. Docs: rules.Integer / rules.Float /
|
|
599
|
+
// rules.Boolean (string converters require a fully-valid literal).
|
|
600
|
+
/** `int(x)` — truncate a number/float, parse a strict-integer string, or 0/1 a bool. */
|
|
601
|
+
function rulesInt(v) {
|
|
602
|
+
if (typeof v === 'number')
|
|
603
|
+
return Math.trunc(v);
|
|
604
|
+
if (v instanceof RulesFloat)
|
|
605
|
+
return Math.trunc(v.value);
|
|
606
|
+
if (typeof v === 'boolean')
|
|
607
|
+
return v ? 1 : 0;
|
|
608
|
+
if (typeof v === 'string') {
|
|
609
|
+
// CEL parses the WHOLE string as an integer; a trailing non-digit (or any
|
|
610
|
+
// float/garbage) is an error, not a salvaged prefix like parseInt gives.
|
|
611
|
+
if (/^[+-]?\d+$/.test(v.trim()))
|
|
612
|
+
return parseInt(v.trim(), 10);
|
|
613
|
+
throw new EvalError(`int() cannot convert string '${v}' to an integer`);
|
|
614
|
+
}
|
|
615
|
+
throw new EvalError(`int() cannot convert ${typeof v} to an integer`);
|
|
616
|
+
}
|
|
617
|
+
/** `float(x)` — produce a FLOAT (tagged) from a number, float, or numeric string. */
|
|
618
|
+
function rulesFloatBuiltin(v) {
|
|
619
|
+
if (v instanceof RulesFloat)
|
|
620
|
+
return v;
|
|
621
|
+
if (typeof v === 'number')
|
|
622
|
+
return new RulesFloat(v);
|
|
623
|
+
if (typeof v === 'string') {
|
|
624
|
+
const trimmed = v.trim();
|
|
625
|
+
// Strict numeric form (optional sign, digits, optional fraction). Rejects
|
|
626
|
+
// `'1.2.3'`, `'abc'`, trailing junk — all errors in prod.
|
|
627
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)$/.test(trimmed))
|
|
628
|
+
return new RulesFloat(parseFloat(trimmed));
|
|
629
|
+
throw new EvalError(`float() cannot convert string '${v}' to a float`);
|
|
630
|
+
}
|
|
631
|
+
throw new EvalError(`float() cannot convert ${typeof v} to a float`);
|
|
632
|
+
}
|
|
633
|
+
/** `bool(x)` — pass a bool through; parse only the exact strings 'true'/'false'. */
|
|
634
|
+
function rulesBool(v) {
|
|
635
|
+
if (typeof v === 'boolean')
|
|
636
|
+
return v;
|
|
637
|
+
if (typeof v === 'string') {
|
|
638
|
+
if (v === 'true')
|
|
639
|
+
return true;
|
|
640
|
+
if (v === 'false')
|
|
641
|
+
return false;
|
|
642
|
+
throw new EvalError(`bool() cannot convert string '${v}' to a boolean`);
|
|
643
|
+
}
|
|
644
|
+
throw new EvalError(`bool() cannot convert ${typeof v} to a boolean`);
|
|
645
|
+
}
|
|
646
|
+
// ═══ Function calls ═══
|
|
647
|
+
function evaluateFunctionCall(name, args, ctx, scope) {
|
|
648
|
+
// Built-in functions
|
|
649
|
+
switch (name) {
|
|
650
|
+
case 'get': {
|
|
651
|
+
const path = String(evaluate(args[0], ctx, scope));
|
|
652
|
+
return resolveGet(path, ctx);
|
|
653
|
+
}
|
|
654
|
+
case 'exists': {
|
|
655
|
+
const path = String(evaluate(args[0], ctx, scope));
|
|
656
|
+
return resolveExists(path, ctx);
|
|
657
|
+
}
|
|
658
|
+
case 'getAfter': {
|
|
659
|
+
// Item 7 — projected post-write state.
|
|
660
|
+
// For the document being written (matches request.path), return the
|
|
661
|
+
// afterState. For unrelated paths, fall through to get() — the write
|
|
662
|
+
// under evaluation doesn't affect them.
|
|
663
|
+
const argVal = evaluate(args[0], ctx, scope);
|
|
664
|
+
const pathStr = String(argVal);
|
|
665
|
+
if (pathStr === ctx.afterStatePath.toString()) {
|
|
666
|
+
// RULES-B8: getAfter() of a doc that won't exist post-write (delete,
|
|
667
|
+
// or projected-null) ERRORS like get() — guard with existsAfter().
|
|
668
|
+
if (ctx.afterState === null) {
|
|
669
|
+
throw new EvalError(`getAfter() of non-existent document '${pathStr}' (guard with existsAfter() first)`);
|
|
670
|
+
}
|
|
671
|
+
return makeGetResource(normalizePath(pathStr, ctx), ctx.afterState);
|
|
672
|
+
}
|
|
673
|
+
return resolveGet(pathStr, ctx);
|
|
674
|
+
}
|
|
675
|
+
case 'existsAfter': {
|
|
676
|
+
// Item 7 — projected existence after the write.
|
|
677
|
+
// Same dispatch shape as getAfter: target path uses ctx.existsAfter,
|
|
678
|
+
// other paths fall through to exists().
|
|
679
|
+
const argVal = evaluate(args[0], ctx, scope);
|
|
680
|
+
const pathStr = String(argVal);
|
|
681
|
+
if (pathStr === ctx.afterStatePath.toString()) {
|
|
682
|
+
return ctx.existsAfter;
|
|
683
|
+
}
|
|
684
|
+
return resolveExists(pathStr, ctx);
|
|
685
|
+
}
|
|
686
|
+
case 'debug': {
|
|
687
|
+
const val = evaluate(args[0], ctx, scope);
|
|
688
|
+
return val; // debug() returns its argument
|
|
689
|
+
}
|
|
690
|
+
// RULES-B5 / RULES-B6: type-conversion builtins follow CEL's strict
|
|
691
|
+
// semantics, not JS coercion. `String()` routes a RulesFloat through its
|
|
692
|
+
// `.toString()` so `string(1.0)` → "1.0" (decimal preserved); bare ints
|
|
693
|
+
// and other types stringify normally.
|
|
694
|
+
case 'string': return String(evaluate(args[0], ctx, scope));
|
|
695
|
+
case 'int': return rulesInt(evaluate(args[0], ctx, scope));
|
|
696
|
+
case 'float': return rulesFloatBuiltin(evaluate(args[0], ctx, scope));
|
|
697
|
+
case 'bool': return rulesBool(evaluate(args[0], ctx, scope));
|
|
698
|
+
case 'path': {
|
|
699
|
+
// Item 5.4 — `path('users/alice')` returns a Path wrapper. Accepts
|
|
700
|
+
// strings or existing Paths (idempotent). Other inputs throw —
|
|
701
|
+
// mismatched argument is a real type error in the rule.
|
|
702
|
+
const arg = evaluate(args[0], ctx, scope);
|
|
703
|
+
if (arg instanceof Path)
|
|
704
|
+
return arg;
|
|
705
|
+
if (typeof arg === 'string')
|
|
706
|
+
return Path.fromString(arg);
|
|
707
|
+
throw new EvalError(`path() requires a string argument, got ${typeof arg}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// User-defined functions
|
|
711
|
+
const fn = ctx.functions.get(name);
|
|
712
|
+
if (!fn)
|
|
713
|
+
throw new UnsupportedError(`Unknown function: ${name}`);
|
|
714
|
+
// Bind parameters — parameter expressions evaluate in the CALLER's
|
|
715
|
+
// scope and frame. We push the inlinedFrom frame *after* this loop
|
|
716
|
+
// so parameter traces stay attributed to the caller (where the
|
|
717
|
+
// argument expression literally lives in source).
|
|
718
|
+
const fnScope = { ...scope };
|
|
719
|
+
for (let i = 0; i < fn.parameters.length; i++) {
|
|
720
|
+
fnScope[fn.parameters[i]] = evaluate(args[i], ctx, scope);
|
|
721
|
+
}
|
|
722
|
+
// Push frame for let bindings + body so their trace entries get
|
|
723
|
+
// tagged `inlinedFrom: { name }`. try/finally so an evaluation
|
|
724
|
+
// error still pops the frame and doesn't pollute sibling rules.
|
|
725
|
+
ctx.trace?.enterFrame(name);
|
|
726
|
+
try {
|
|
727
|
+
// Evaluate let bindings. Capture the trace entries' next-write
|
|
728
|
+
// position BEFORE the binding's expression evaluates — that slot
|
|
729
|
+
// will be the binding-root entry (recursive children land below
|
|
730
|
+
// it). After the evaluate returns, tag that root slot with the
|
|
731
|
+
// bound name so the agent can attribute "this subtree was the
|
|
732
|
+
// value of `<name>`" without re-walking the function AST.
|
|
733
|
+
for (const binding of fn.lets) {
|
|
734
|
+
const bindingRootIdx = ctx.trace ? ctx.trace.entries.length : -1;
|
|
735
|
+
fnScope[binding.name] = evaluate(binding.value, ctx, fnScope);
|
|
736
|
+
if (bindingRootIdx >= 0) {
|
|
737
|
+
ctx.trace?.markEntryAsLetBinding(bindingRootIdx, binding.name);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
// Evaluate body
|
|
741
|
+
return evaluate(fn.body, ctx, fnScope);
|
|
742
|
+
}
|
|
743
|
+
finally {
|
|
744
|
+
ctx.trace?.exitFrame();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// ═══ Method calls ═══
|
|
748
|
+
function evaluateMethodCall(objectExpr, method, args, ctx, scope) {
|
|
749
|
+
// Built-in namespaces (math, timestamp, duration) are not first-class
|
|
750
|
+
// values — they exist only as method-call targets. Dispatch them before
|
|
751
|
+
// calling evaluate(objectExpr), which would resolve the identifier to
|
|
752
|
+
// undefined and fall through to "Unknown method on undefined".
|
|
753
|
+
// Skip the dispatch if the namespace name is shadowed by a local binding
|
|
754
|
+
// or path variable.
|
|
755
|
+
if (objectExpr.type === 'identifier') {
|
|
756
|
+
const name = objectExpr.name;
|
|
757
|
+
if (isBuiltinNamespace(name) && !(name in scope) && !(name in ctx.pathVariables)) {
|
|
758
|
+
const argValues = args.map(a => evaluate(a, ctx, scope));
|
|
759
|
+
return evaluateNamespaceMethod(name, method, argValues);
|
|
760
|
+
}
|
|
761
|
+
// RULES-B2 interaction: an UNKNOWN bare identifier used as a method-call
|
|
762
|
+
// target (e.g. `foo.bar()`) is most likely a namespace the simulator
|
|
763
|
+
// hasn't implemented, not a typo'd value. Resolving it as a value now
|
|
764
|
+
// throws "Undefined variable" (RULES-B2), but we want the agent-facing
|
|
765
|
+
// UNSUPPORTED signal ("sim gap") here rather than a hard ERROR — that's
|
|
766
|
+
// the deliberate affordance the old `undefined.method()` path provided.
|
|
767
|
+
// (Bare `foo` used as a VALUE still errors via resolveIdentifier; only the
|
|
768
|
+
// method-call-target case is lifted to UNSUPPORTED.)
|
|
769
|
+
if (!isBuiltinNamespace(name)
|
|
770
|
+
&& !(name in scope)
|
|
771
|
+
&& !(name in ctx.pathVariables)
|
|
772
|
+
&& !isKnownGlobal(name)) {
|
|
773
|
+
throw new UnsupportedError(`Unknown namespace or method '${name}.${method}()'`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const obj = evaluate(objectExpr, ctx, scope);
|
|
777
|
+
const argValues = args.map(a => evaluate(a, ctx, scope));
|
|
778
|
+
// RulesValue method dispatch (Item 0.B hook 3). Placed above MapDiff /
|
|
779
|
+
// FirestoreSet / Map / Array / String branches because every wrapper
|
|
780
|
+
// is also `typeof === 'object'` — without this short-circuit, a
|
|
781
|
+
// Timestamp would fall into the Map branch and dispatch `.size()` on
|
|
782
|
+
// its private `{seconds, nanos}` shape. NO_OP from the wrapper means
|
|
783
|
+
// "I don't have this method" → UnsupportedError, which the handler
|
|
784
|
+
// maps to TestResult.state = 'UNSUPPORTED' (Item 0.A) so agents see
|
|
785
|
+
// a sim gap rather than a silent DENY.
|
|
786
|
+
if (obj instanceof RulesValue) {
|
|
787
|
+
const result = obj.callMethod(method, argValues);
|
|
788
|
+
if (result === NO_OP) {
|
|
789
|
+
throw new UnsupportedError(`Unknown method '${method}' on ${obj.typeName}`);
|
|
790
|
+
}
|
|
791
|
+
return result;
|
|
792
|
+
}
|
|
793
|
+
// MapDiff: obj.diff(other) → MapDiff
|
|
794
|
+
if (method === 'diff' && typeof obj === 'object' && obj !== null && !Array.isArray(obj)) {
|
|
795
|
+
const other = argValues[0];
|
|
796
|
+
if (typeof other === 'object' && other !== null) {
|
|
797
|
+
return new MapDiff(other, obj);
|
|
798
|
+
}
|
|
799
|
+
throw new EvalError('diff() requires a map argument');
|
|
800
|
+
}
|
|
801
|
+
// Callable methods (from memberAccess returning a function)
|
|
802
|
+
if (typeof obj === 'function') {
|
|
803
|
+
return obj(...argValues);
|
|
804
|
+
}
|
|
805
|
+
// FirestoreSet methods
|
|
806
|
+
if (obj instanceof FirestoreSet) {
|
|
807
|
+
switch (method) {
|
|
808
|
+
case 'hasOnly': return obj.hasOnly(argValues[0]);
|
|
809
|
+
case 'hasAll': return obj.hasAll(argValues[0]);
|
|
810
|
+
case 'hasAny': return obj.hasAny(argValues[0]);
|
|
811
|
+
case 'size': return obj.size();
|
|
812
|
+
// Set algebra (Item 5.1) — per REBUILD_PLAN type table.
|
|
813
|
+
case 'difference': return obj.difference(argValues[0]);
|
|
814
|
+
case 'union': return obj.union(argValues[0]);
|
|
815
|
+
case 'intersection': return obj.intersection(argValues[0]);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// MapDiff methods (when called directly, not via memberAccess)
|
|
819
|
+
if (obj instanceof MapDiff) {
|
|
820
|
+
switch (method) {
|
|
821
|
+
case 'addedKeys': return obj.addedKeys();
|
|
822
|
+
case 'removedKeys': return obj.removedKeys();
|
|
823
|
+
case 'changedKeys': return obj.changedKeys();
|
|
824
|
+
case 'affectedKeys': return obj.affectedKeys();
|
|
825
|
+
case 'unchangedKeys': return obj.unchangedKeys();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Map/object methods
|
|
829
|
+
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) {
|
|
830
|
+
const map = obj;
|
|
831
|
+
switch (method) {
|
|
832
|
+
case 'keys': return new FirestoreSet(Object.keys(map));
|
|
833
|
+
case 'values': return Object.values(map);
|
|
834
|
+
case 'size': return Object.keys(map).length;
|
|
835
|
+
// RULES-B7: key checks use OWN keys only (no prototype-chain leak).
|
|
836
|
+
case 'hasAll': return argValues[0].every(k => Object.hasOwn(map, k));
|
|
837
|
+
case 'hasAny': return argValues[0].some(k => Object.hasOwn(map, k));
|
|
838
|
+
case 'hasOnly': return Object.keys(map).every(k => argValues[0].includes(k));
|
|
839
|
+
case 'diff': {
|
|
840
|
+
const other = argValues[0];
|
|
841
|
+
return new MapDiff(other, map);
|
|
842
|
+
}
|
|
843
|
+
case 'get': {
|
|
844
|
+
// Map.get(key, default) — Item 3 (REBUILD_PLAN.md).
|
|
845
|
+
// Two forms (production behavior locked in by 0.H parity probe):
|
|
846
|
+
// m.get('a', default) → m.a if present, else default
|
|
847
|
+
// m.get(['a','b','c'], default) → m.a.b.c if full walk succeeds, else default
|
|
848
|
+
// Production *always* returns default on any walk failure (missing
|
|
849
|
+
// key, non-map intermediate, etc.). It does NOT distinguish "key
|
|
850
|
+
// missing" from "intermediate missing" — both collapse to default.
|
|
851
|
+
// An explicit `null` value at the leaf is returned as null (not
|
|
852
|
+
// default), since the key is present.
|
|
853
|
+
const keyArg = argValues[0];
|
|
854
|
+
const def = argValues[1];
|
|
855
|
+
if (Array.isArray(keyArg)) {
|
|
856
|
+
// List-form nested traversal
|
|
857
|
+
let cur = map;
|
|
858
|
+
for (const seg of keyArg) {
|
|
859
|
+
if (cur === null || cur === undefined)
|
|
860
|
+
return def;
|
|
861
|
+
// Cannot descend into non-maps (string, number, list, wrapper).
|
|
862
|
+
// Probe scenarios list_form_mid_is_string / list_form_mid_is_int
|
|
863
|
+
// confirmed prod returns default in these cases.
|
|
864
|
+
if (typeof cur !== 'object' || Array.isArray(cur) || cur instanceof RulesValue)
|
|
865
|
+
return def;
|
|
866
|
+
const k = String(seg);
|
|
867
|
+
if (!Object.hasOwn(cur, k))
|
|
868
|
+
return def; // RULES-B7: own keys only
|
|
869
|
+
cur = cur[k];
|
|
870
|
+
}
|
|
871
|
+
return cur;
|
|
872
|
+
}
|
|
873
|
+
// Single-key form
|
|
874
|
+
const k = String(keyArg);
|
|
875
|
+
return Object.hasOwn(map, k) ? map[k] : def; // RULES-B7: own keys only
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
// List methods
|
|
880
|
+
if (Array.isArray(obj)) {
|
|
881
|
+
switch (method) {
|
|
882
|
+
case 'size': return obj.length;
|
|
883
|
+
// RULES-B9: list membership uses VALUE equality, not JS identity, so it
|
|
884
|
+
// is consistent with `in` / `removeAll` (which already use
|
|
885
|
+
// rulesValuesEqual). Without this, `[t1].hasAll([t2])` for equal-valued
|
|
886
|
+
// Timestamp/Bytes/Path wrappers wrongly returned false because the two
|
|
887
|
+
// instances aren't `===`.
|
|
888
|
+
case 'hasAll': return argValues[0].every(v => obj.some(o => rulesValuesEqual(o, v)));
|
|
889
|
+
case 'hasAny': return argValues[0].some(v => obj.some(o => rulesValuesEqual(o, v)));
|
|
890
|
+
case 'hasOnly': {
|
|
891
|
+
const allowed = argValues[0];
|
|
892
|
+
return obj.every(v => allowed.some(o => rulesValuesEqual(o, v)));
|
|
893
|
+
}
|
|
894
|
+
case 'join': return obj.join(String(argValues[0] ?? ','));
|
|
895
|
+
// ─── Item 5.2: List.concat / removeAll / toSet ─────────────────────
|
|
896
|
+
case 'concat': {
|
|
897
|
+
const other = argValues[0];
|
|
898
|
+
if (!Array.isArray(other)) {
|
|
899
|
+
throw new EvalError(`List.concat requires a list argument, got ${typeof other}`);
|
|
900
|
+
}
|
|
901
|
+
return [...obj, ...other];
|
|
902
|
+
}
|
|
903
|
+
case 'removeAll': {
|
|
904
|
+
// Returns a new list with all items from `other` removed (by value
|
|
905
|
+
// equality). Uses rulesValuesEqual so wrapper instances (Timestamp,
|
|
906
|
+
// Duration, etc.) compare by value, not by JS identity — without
|
|
907
|
+
// this, `[t1].removeAll([t2])` where t1 and t2 are equal Timestamp
|
|
908
|
+
// wrappers would not remove t1.
|
|
909
|
+
const other = argValues[0];
|
|
910
|
+
if (!Array.isArray(other)) {
|
|
911
|
+
throw new EvalError(`List.removeAll requires a list argument, got ${typeof other}`);
|
|
912
|
+
}
|
|
913
|
+
return obj.filter(v => !other.some(o => rulesValuesEqual(v, o)));
|
|
914
|
+
}
|
|
915
|
+
case 'toSet': {
|
|
916
|
+
// Convert to FirestoreSet. The class stores strings only — coerce
|
|
917
|
+
// via String() on each element. This is sufficient for the common
|
|
918
|
+
// case of `[]Doc.tags.toSet()`-style usage where the source list
|
|
919
|
+
// is already strings. Non-string elements (numbers, booleans) get
|
|
920
|
+
// String()-coerced and may collapse onto each other (1 and '1'
|
|
921
|
+
// become the same set member). Locked-in behavior is whatever the
|
|
922
|
+
// parity pack confirms; if prod diverges we revisit.
|
|
923
|
+
return new FirestoreSet(obj.map(v => String(v)));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
// String methods
|
|
928
|
+
if (typeof obj === 'string') {
|
|
929
|
+
switch (method) {
|
|
930
|
+
case 'size': return obj.length;
|
|
931
|
+
case 'lower': return obj.toLowerCase();
|
|
932
|
+
case 'upper': return obj.toUpperCase();
|
|
933
|
+
case 'trim': return obj.trim();
|
|
934
|
+
// RULES-B4: split()/matches()/replace() take RE2 *regular expressions*
|
|
935
|
+
// and matches() is a FULL-STRING (anchored) test — production: "returns
|
|
936
|
+
// true if the whole string matches the regex". The previous impl used a
|
|
937
|
+
// partial JS `.test()`, a literal-string `split`, and a first-only
|
|
938
|
+
// `replace`. We compile via the JS RegExp engine (RE2 and JS share the
|
|
939
|
+
// common subset used by rules; exotic RE2-only constructs are a noted
|
|
940
|
+
// limitation, not a behavior divergence for the documented cases) and
|
|
941
|
+
// anchor matches() with `^(?:…)$`, apply `g` to replace/split.
|
|
942
|
+
case 'split': return obj.split(compileRulesRegex(String(argValues[0]), 'g'));
|
|
943
|
+
case 'matches':
|
|
944
|
+
return compileRulesRegex(`^(?:${String(argValues[0])})$`).test(obj);
|
|
945
|
+
case 'replace':
|
|
946
|
+
return obj.replace(compileRulesRegex(String(argValues[0]), 'g'), String(argValues[1]));
|
|
947
|
+
case 'toUtf8': return Bytes.fromUtf8(obj); // Item 5.3 — only producer of Bytes from a literal.
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
throw new UnsupportedError(`Unknown method '${method}' on ${typeof obj}`);
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Compile a Firestore-rules regex (RULES-B4). Rules regexes follow RE2
|
|
954
|
+
* syntax; we use the JS RegExp engine, which shares the common subset rules
|
|
955
|
+
* actually use. A pattern the engine can't compile is a real type error in
|
|
956
|
+
* the rule (not a sim gap) → EvalError so the handler DENYs, matching
|
|
957
|
+
* production's "invalid regex → error" behavior.
|
|
958
|
+
*/
|
|
959
|
+
function compileRulesRegex(pattern, flags = '') {
|
|
960
|
+
try {
|
|
961
|
+
return new RegExp(pattern, flags);
|
|
962
|
+
}
|
|
963
|
+
catch (e) {
|
|
964
|
+
throw new EvalError(`Invalid regex '${pattern}': ${e.message}`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
// ═══ Built-in namespaces (math, timestamp, duration) ═══
|
|
968
|
+
//
|
|
969
|
+
// Production parity: these used to throw via the "Unknown method on
|
|
970
|
+
// undefined" path because the identifiers `math` / `timestamp` / `duration`
|
|
971
|
+
// resolve to undefined. The throw was caught by handler.ts and counted as
|
|
972
|
+
// deny — silently denying every ALLOW case that touched a math/time
|
|
973
|
+
// built-in. Confirmed by parity-stress-integration.test.ts (Pack 1).
|
|
974
|
+
//
|
|
975
|
+
// We model timestamp values and duration values as plain numbers
|
|
976
|
+
// (milliseconds since epoch and milliseconds, respectively). That lets
|
|
977
|
+
// JavaScript's `<`, `>`, `<=`, `>=`, `+`, `-` work without a wrapper
|
|
978
|
+
// class. This is sufficient for any rule whose timestamp/duration values
|
|
979
|
+
// originate from the namespace built-ins themselves.
|
|
980
|
+
const BUILTIN_NAMESPACES = new Set(['math', 'timestamp', 'duration', 'latlng', 'hashing']);
|
|
981
|
+
function isBuiltinNamespace(name) {
|
|
982
|
+
return BUILTIN_NAMESPACES.has(name);
|
|
983
|
+
}
|
|
984
|
+
function evaluateNamespaceMethod(ns, method, args) {
|
|
985
|
+
// RULES-B5: the numeric namespaces (math/latlng/timestamp/duration) operate
|
|
986
|
+
// on the underlying double — `latlng.value(37.7, ...)` or `math.ceil(1.5)`
|
|
987
|
+
// doesn't care whether the arg was written as an int or a float literal, and
|
|
988
|
+
// the constructors store bare numbers. Unwrap RulesFloat args to their raw
|
|
989
|
+
// value so `args[0] as number` casts inside each handler stay valid (a
|
|
990
|
+
// RulesFloat is otherwise an object, breaking `latlng.value` equality etc.).
|
|
991
|
+
// hashing.* takes Bytes/String — no numeric args — so it's left untouched.
|
|
992
|
+
const a = ns === 'hashing' ? args : args.map(unwrapFloat);
|
|
993
|
+
switch (ns) {
|
|
994
|
+
case 'math': return evaluateMathMethod(method, a);
|
|
995
|
+
case 'timestamp': return evaluateTimestampMethod(method, a);
|
|
996
|
+
case 'duration': return evaluateDurationMethod(method, a);
|
|
997
|
+
case 'latlng': return evaluateLatLngMethod(method, a);
|
|
998
|
+
case 'hashing': return evaluateHashingMethod(method, args);
|
|
999
|
+
}
|
|
1000
|
+
throw new UnsupportedError(`Unknown namespace '${ns}'`);
|
|
1001
|
+
}
|
|
1002
|
+
/** RULES-B5: collapse a RulesFloat to its raw double; pass everything else through. */
|
|
1003
|
+
function unwrapFloat(v) {
|
|
1004
|
+
return v instanceof RulesFloat ? v.value : v;
|
|
1005
|
+
}
|
|
1006
|
+
// ─── hashing.* (Item 5.3) ───────────────────────────────────────────────
|
|
1007
|
+
//
|
|
1008
|
+
// Per docs (rules.hashing): crc32, crc32c, md5, sha256.
|
|
1009
|
+
// All accept Bytes | String; strings are hashed as UTF-8 bytes.
|
|
1010
|
+
// All return Bytes (round-trips with Bytes.toBase64() / .toHexString()).
|
|
1011
|
+
//
|
|
1012
|
+
// md5 / sha256 use node:crypto (CLAUDE.md: dev tools must use node:* APIs).
|
|
1013
|
+
// crc32 (IEEE 802.3) and crc32c (Castagnoli) are implemented inline via
|
|
1014
|
+
// table lookup since no crc dep is in the SDK.
|
|
1015
|
+
function coerceToBytes(arg) {
|
|
1016
|
+
if (arg instanceof Bytes)
|
|
1017
|
+
return arg;
|
|
1018
|
+
if (typeof arg === 'string')
|
|
1019
|
+
return Bytes.fromUtf8(arg);
|
|
1020
|
+
throw new EvalError(`hashing.* requires Bytes or String, got ${typeof arg}`);
|
|
1021
|
+
}
|
|
1022
|
+
function evaluateHashingMethod(method, args) {
|
|
1023
|
+
const input = coerceToBytes(args[0]);
|
|
1024
|
+
switch (method) {
|
|
1025
|
+
case 'md5': {
|
|
1026
|
+
// js-md5 / js-sha256 keep this path browser-safe (no node:crypto).
|
|
1027
|
+
// Both libs return ArrayBuffer with `.arrayBuffer()`, identical
|
|
1028
|
+
// bytes to the previous createHash() output — pinned by the
|
|
1029
|
+
// hashing.* receipt corpus.
|
|
1030
|
+
return new Bytes(new Uint8Array(md5Bytes.arrayBuffer(input.data)));
|
|
1031
|
+
}
|
|
1032
|
+
case 'sha256': {
|
|
1033
|
+
return new Bytes(new Uint8Array(sha256Bytes.arrayBuffer(input.data)));
|
|
1034
|
+
}
|
|
1035
|
+
case 'crc32': {
|
|
1036
|
+
const u32 = crc32(input.data);
|
|
1037
|
+
return new Bytes(u32ToBeBytes(u32));
|
|
1038
|
+
}
|
|
1039
|
+
case 'crc32c': {
|
|
1040
|
+
const u32 = crc32c(input.data);
|
|
1041
|
+
return new Bytes(u32ToBeBytes(u32));
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
throw new UnsupportedError(`Unknown hashing method '${method}'`);
|
|
1045
|
+
}
|
|
1046
|
+
function u32ToBeBytes(n) {
|
|
1047
|
+
// Big-endian 4 bytes — matches how Firestore returns CRC results.
|
|
1048
|
+
return new Uint8Array([
|
|
1049
|
+
(n >>> 24) & 0xff,
|
|
1050
|
+
(n >>> 16) & 0xff,
|
|
1051
|
+
(n >>> 8) & 0xff,
|
|
1052
|
+
n & 0xff,
|
|
1053
|
+
]);
|
|
1054
|
+
}
|
|
1055
|
+
// CRC32 IEEE 802.3 (polynomial 0xEDB88320, reflected).
|
|
1056
|
+
const CRC32_TABLE = (() => {
|
|
1057
|
+
const t = new Uint32Array(256);
|
|
1058
|
+
for (let i = 0; i < 256; i++) {
|
|
1059
|
+
let c = i;
|
|
1060
|
+
for (let k = 0; k < 8; k++) {
|
|
1061
|
+
c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
|
|
1062
|
+
}
|
|
1063
|
+
t[i] = c >>> 0;
|
|
1064
|
+
}
|
|
1065
|
+
return t;
|
|
1066
|
+
})();
|
|
1067
|
+
function crc32(data) {
|
|
1068
|
+
let c = 0xFFFFFFFF;
|
|
1069
|
+
for (let i = 0; i < data.length; i++) {
|
|
1070
|
+
c = CRC32_TABLE[(c ^ data[i]) & 0xff] ^ (c >>> 8);
|
|
1071
|
+
}
|
|
1072
|
+
return (c ^ 0xFFFFFFFF) >>> 0;
|
|
1073
|
+
}
|
|
1074
|
+
// CRC32C Castagnoli (polynomial 0x82F63B78, reflected).
|
|
1075
|
+
const CRC32C_TABLE = (() => {
|
|
1076
|
+
const t = new Uint32Array(256);
|
|
1077
|
+
for (let i = 0; i < 256; i++) {
|
|
1078
|
+
let c = i;
|
|
1079
|
+
for (let k = 0; k < 8; k++) {
|
|
1080
|
+
c = (c & 1) ? (0x82F63B78 ^ (c >>> 1)) : (c >>> 1);
|
|
1081
|
+
}
|
|
1082
|
+
t[i] = c >>> 0;
|
|
1083
|
+
}
|
|
1084
|
+
return t;
|
|
1085
|
+
})();
|
|
1086
|
+
function crc32c(data) {
|
|
1087
|
+
let c = 0xFFFFFFFF;
|
|
1088
|
+
for (let i = 0; i < data.length; i++) {
|
|
1089
|
+
c = CRC32C_TABLE[(c ^ data[i]) & 0xff] ^ (c >>> 8);
|
|
1090
|
+
}
|
|
1091
|
+
return (c ^ 0xFFFFFFFF) >>> 0;
|
|
1092
|
+
}
|
|
1093
|
+
function evaluateLatLngMethod(method, args) {
|
|
1094
|
+
switch (method) {
|
|
1095
|
+
case 'value': {
|
|
1096
|
+
// latlng.value(lat, lng) → LatLng wrapper. Item 1.1 closes the
|
|
1097
|
+
// entire `latlng` namespace gap (was throwing UnsupportedError).
|
|
1098
|
+
const [lat, lng] = args;
|
|
1099
|
+
return new LatLng(lat, lng);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
throw new UnsupportedError(`Unknown latlng method '${method}'`);
|
|
1103
|
+
}
|
|
1104
|
+
function evaluateMathMethod(method, args) {
|
|
1105
|
+
switch (method) {
|
|
1106
|
+
case 'abs': return Math.abs(args[0]);
|
|
1107
|
+
case 'ceil': return Math.ceil(args[0]);
|
|
1108
|
+
case 'floor': return Math.floor(args[0]);
|
|
1109
|
+
case 'round': return Math.round(args[0]);
|
|
1110
|
+
case 'sqrt': return Math.sqrt(args[0]);
|
|
1111
|
+
case 'pow': return Math.pow(args[0], args[1]);
|
|
1112
|
+
case 'isInfinite': {
|
|
1113
|
+
const n = args[0];
|
|
1114
|
+
return Number.isFinite(n) === false && Number.isNaN(n) === false;
|
|
1115
|
+
}
|
|
1116
|
+
case 'isNaN': return Number.isNaN(args[0]);
|
|
1117
|
+
}
|
|
1118
|
+
throw new UnsupportedError(`Unknown math method '${method}'`);
|
|
1119
|
+
}
|
|
1120
|
+
function evaluateTimestampMethod(method, args) {
|
|
1121
|
+
switch (method) {
|
|
1122
|
+
case 'date': {
|
|
1123
|
+
// timestamp.date(year, month, day) → Timestamp at midnight UTC.
|
|
1124
|
+
// Item 1.3 flip: was returning epoch-ms Number which broke
|
|
1125
|
+
// `is timestamp` and dropped sub-second precision under arithmetic.
|
|
1126
|
+
const [y, m, d] = args;
|
|
1127
|
+
return Timestamp.fromYMD(y, m, d);
|
|
1128
|
+
}
|
|
1129
|
+
case 'value': {
|
|
1130
|
+
// timestamp.value(epochMillis) → Timestamp wrapper. Item 1.3 flip.
|
|
1131
|
+
return Timestamp.fromMillis(args[0]);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
throw new UnsupportedError(`Unknown timestamp method '${method}'`);
|
|
1135
|
+
}
|
|
1136
|
+
function evaluateDurationMethod(method, args) {
|
|
1137
|
+
switch (method) {
|
|
1138
|
+
case 'value': {
|
|
1139
|
+
// duration.value(magnitude, unit) → Duration wrapper. Item 1.2 flip:
|
|
1140
|
+
// was returning a millis Number, which silently lost sub-ms precision
|
|
1141
|
+
// and made `is duration` always false. Item 1.0 dispatch hooks +
|
|
1142
|
+
// Duration.binaryOp keep arithmetic and comparison working without
|
|
1143
|
+
// numeric coercion.
|
|
1144
|
+
try {
|
|
1145
|
+
return Duration.fromValue(args[0], args[1]);
|
|
1146
|
+
}
|
|
1147
|
+
catch (e) {
|
|
1148
|
+
// Bad unit string is a real type error in the rule, not a sim gap.
|
|
1149
|
+
throw new EvalError(e.message);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
case 'time': {
|
|
1153
|
+
const [h, m, s, ns] = args;
|
|
1154
|
+
return Duration.fromTime(h, m, s, ns);
|
|
1155
|
+
}
|
|
1156
|
+
case 'abs': {
|
|
1157
|
+
const d = args[0];
|
|
1158
|
+
if (!(d instanceof Duration)) {
|
|
1159
|
+
throw new EvalError(`duration.abs() requires a Duration argument, got ${typeof d}`);
|
|
1160
|
+
}
|
|
1161
|
+
return Duration.abs(d);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
throw new UnsupportedError(`Unknown duration method '${method}'`);
|
|
1165
|
+
}
|
|
1166
|
+
// ═══ get()/exists() mock resolution ═══
|
|
1167
|
+
function normalizePath(rawPath, ctx) {
|
|
1168
|
+
// Path format: /databases/(default)/documents/collection/docId
|
|
1169
|
+
// or /databases/$(database)/documents/collection/docId
|
|
1170
|
+
return rawPath
|
|
1171
|
+
.replace(/\$\(database\)/g, '(default)')
|
|
1172
|
+
.replace(/^\/databases\/\(default\)\/documents\//, '');
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Build the resource value `get()` / `getAfter()` return for an existing
|
|
1176
|
+
* document. RULES-B8: production exposes the document identity alongside
|
|
1177
|
+
* `data` — `get(path).id` is the last path segment and `get(path).__name__`
|
|
1178
|
+
* is the full Path. These were never populated, so rules reading them
|
|
1179
|
+
* silently denied (the access returned undefined → now, post-B2, would
|
|
1180
|
+
* even error). Mirrors the top-level `resource` shape (SimResource).
|
|
1181
|
+
*/
|
|
1182
|
+
function makeGetResource(relPath, data) {
|
|
1183
|
+
const segs = relPath.split('/').filter(Boolean);
|
|
1184
|
+
const id = segs.length > 0 ? segs[segs.length - 1] : '';
|
|
1185
|
+
const fullSegs = ['databases', '(default)', 'documents', ...segs];
|
|
1186
|
+
return { data, id, __name__: new Path(fullSegs) };
|
|
1187
|
+
}
|
|
1188
|
+
function resolveGet(rawPath, ctx) {
|
|
1189
|
+
const path = normalizePath(rawPath, ctx);
|
|
1190
|
+
let doc = ctx.mockDocuments.get(path);
|
|
1191
|
+
if (!doc && ctx.getDoc) {
|
|
1192
|
+
const faulted = ctx.getDoc(path);
|
|
1193
|
+
if (faulted) {
|
|
1194
|
+
ctx.mockDocuments.set(path, faulted); // memoize for repeat reads in this eval
|
|
1195
|
+
doc = faulted;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (doc)
|
|
1199
|
+
return makeGetResource(path, doc);
|
|
1200
|
+
// RULES-B8: get() of a missing document is a runtime ERROR in production
|
|
1201
|
+
// (it performs a real read; a non-existent doc denies the request), NOT
|
|
1202
|
+
// the silent null this used to return. The safe pattern is
|
|
1203
|
+
// `exists(path) && get(path).data...` — the false exists() absorbs this
|
|
1204
|
+
// error commutatively (RULES-B3). Throwing here makes that guard behave
|
|
1205
|
+
// exactly as prod, and makes an UNguarded get() of a missing doc DENY.
|
|
1206
|
+
throw new EvalError(`get() of non-existent document '${path}' (guard with exists() first)`);
|
|
1207
|
+
}
|
|
1208
|
+
function resolveExists(rawPath, ctx) {
|
|
1209
|
+
const path = normalizePath(rawPath, ctx);
|
|
1210
|
+
if (ctx.mockDocuments.has(path))
|
|
1211
|
+
return true;
|
|
1212
|
+
if (ctx.getDoc) {
|
|
1213
|
+
const faulted = ctx.getDoc(path);
|
|
1214
|
+
if (faulted) {
|
|
1215
|
+
ctx.mockDocuments.set(path, faulted); // memoize so a later get() is consistent
|
|
1216
|
+
return true;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
//# sourceMappingURL=evaluator.js.map
|