pyric 0.1.0-alpha.20 → 0.1.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/README.md +64 -89
  2. package/dist/ai/blocked.d.ts +52 -0
  3. package/dist/ai/blocked.d.ts.map +1 -0
  4. package/dist/ai/blocked.js +83 -0
  5. package/dist/ai/blocked.js.map +1 -0
  6. package/dist/ai/broker/broker.d.ts +49 -0
  7. package/dist/ai/broker/broker.d.ts.map +1 -1
  8. package/dist/ai/broker/broker.js +90 -0
  9. package/dist/ai/broker/broker.js.map +1 -1
  10. package/dist/ai/broker/gemini-engine.d.ts +14 -1
  11. package/dist/ai/broker/gemini-engine.d.ts.map +1 -1
  12. package/dist/ai/broker/gemini-engine.js +30 -9
  13. package/dist/ai/broker/gemini-engine.js.map +1 -1
  14. package/dist/ai/broker/index.d.ts +2 -2
  15. package/dist/ai/broker/index.d.ts.map +1 -1
  16. package/dist/ai/broker/index.js +2 -2
  17. package/dist/ai/broker/index.js.map +1 -1
  18. package/dist/ai/broker/openai-engine.d.ts +10 -1
  19. package/dist/ai/broker/openai-engine.d.ts.map +1 -1
  20. package/dist/ai/broker/openai-engine.js +26 -4
  21. package/dist/ai/broker/openai-engine.js.map +1 -1
  22. package/dist/ai/broker/synthesizer.d.ts +14 -0
  23. package/dist/ai/broker/synthesizer.d.ts.map +1 -1
  24. package/dist/ai/broker/synthesizer.js +31 -0
  25. package/dist/ai/broker/synthesizer.js.map +1 -1
  26. package/dist/ai/broker/types.d.ts +24 -0
  27. package/dist/ai/broker/types.d.ts.map +1 -1
  28. package/dist/ai/internal.d.ts +5 -0
  29. package/dist/ai/internal.d.ts.map +1 -1
  30. package/dist/ai/internal.js +5 -0
  31. package/dist/ai/internal.js.map +1 -1
  32. package/dist/ai/response-helpers.d.ts.map +1 -1
  33. package/dist/ai/response-helpers.js +6 -24
  34. package/dist/ai/response-helpers.js.map +1 -1
  35. package/dist/analytics/index.d.ts +40 -0
  36. package/dist/analytics/index.d.ts.map +1 -0
  37. package/dist/analytics/index.js +25 -0
  38. package/dist/analytics/index.js.map +1 -0
  39. package/dist/app-check/index.d.ts +31 -0
  40. package/dist/app-check/index.d.ts.map +1 -0
  41. package/dist/app-check/index.js +20 -0
  42. package/dist/app-check/index.js.map +1 -0
  43. package/dist/database/controls.d.ts +1 -1
  44. package/dist/database/controls.js +1 -1
  45. package/dist/database/sandbox/rules-eval.d.ts +1 -1
  46. package/dist/database/sandbox/rules-eval.d.ts.map +1 -1
  47. package/dist/database/sandbox/rules-eval.js +2 -2
  48. package/dist/database/sandbox/rules-eval.js.map +1 -1
  49. package/dist/database/sandbox/write-plane.js +2 -2
  50. package/dist/database/sandbox/write-plane.js.map +1 -1
  51. package/dist/database/sandbox-controls.d.ts +12 -5
  52. package/dist/database/sandbox-controls.d.ts.map +1 -1
  53. package/dist/database/sandbox-controls.js +20 -9
  54. package/dist/database/sandbox-controls.js.map +1 -1
  55. package/dist/deferred/entry.d.ts +75 -0
  56. package/dist/deferred/entry.d.ts.map +1 -0
  57. package/dist/deferred/entry.js +143 -0
  58. package/dist/deferred/entry.js.map +1 -0
  59. package/dist/firestore/lite.d.ts +72 -0
  60. package/dist/firestore/lite.d.ts.map +1 -0
  61. package/dist/firestore/lite.js +22 -0
  62. package/dist/firestore/lite.js.map +1 -0
  63. package/dist/firestore/persistence.d.ts +2 -2
  64. package/dist/firestore/persistence.js +1 -1
  65. package/dist/functions/index.d.ts +29 -0
  66. package/dist/functions/index.d.ts.map +1 -0
  67. package/dist/functions/index.js +20 -0
  68. package/dist/functions/index.js.map +1 -0
  69. package/dist/performance/index.d.ts +22 -0
  70. package/dist/performance/index.d.ts.map +1 -0
  71. package/dist/performance/index.js +19 -0
  72. package/dist/performance/index.js.map +1 -0
  73. package/dist/remote-config/index.d.ts +40 -0
  74. package/dist/remote-config/index.d.ts.map +1 -0
  75. package/dist/remote-config/index.js +25 -0
  76. package/dist/remote-config/index.js.map +1 -0
  77. package/dist/rules/grammar/FirestoreValidator.d.ts.map +1 -1
  78. package/dist/rules/grammar/FirestoreValidator.js +43 -27
  79. package/dist/rules/grammar/FirestoreValidator.js.map +1 -1
  80. package/dist/rules/grammar/document-access-count.d.ts +24 -0
  81. package/dist/rules/grammar/document-access-count.d.ts.map +1 -0
  82. package/dist/rules/grammar/document-access-count.js +100 -0
  83. package/dist/rules/grammar/document-access-count.js.map +1 -0
  84. package/dist/rules/linter/ast-utils.d.ts +2 -6
  85. package/dist/rules/linter/ast-utils.d.ts.map +1 -1
  86. package/dist/rules/linter/ast-utils.js +2 -63
  87. package/dist/rules/linter/ast-utils.js.map +1 -1
  88. package/dist/rules/linter/hallucinations.d.ts +8 -0
  89. package/dist/rules/linter/hallucinations.d.ts.map +1 -1
  90. package/dist/rules/linter/hallucinations.js +121 -31
  91. package/dist/rules/linter/hallucinations.js.map +1 -1
  92. package/dist/rules/linter/linter.d.ts +10 -1
  93. package/dist/rules/linter/linter.d.ts.map +1 -1
  94. package/dist/rules/linter/linter.js +23 -9
  95. package/dist/rules/linter/linter.js.map +1 -1
  96. package/dist/rules/rtdb/grammar/simulator.d.ts.map +1 -1
  97. package/dist/rules/rtdb/grammar/simulator.js +13 -13
  98. package/dist/rules/rtdb/grammar/simulator.js.map +1 -1
  99. package/dist/rules/rtdb/simulation/handler.d.ts.map +1 -1
  100. package/dist/rules/rtdb/simulation/handler.js +96 -35
  101. package/dist/rules/rtdb/simulation/handler.js.map +1 -1
  102. package/dist/rules/simulator/document-lookups.d.ts +4 -0
  103. package/dist/rules/simulator/document-lookups.d.ts.map +1 -1
  104. package/dist/rules/simulator/document-lookups.js +42 -3
  105. package/dist/rules/simulator/document-lookups.js.map +1 -1
  106. package/dist/rules/simulator/eval-error.d.ts +11 -0
  107. package/dist/rules/simulator/eval-error.d.ts.map +1 -1
  108. package/dist/rules/simulator/eval-error.js +14 -0
  109. package/dist/rules/simulator/eval-error.js.map +1 -1
  110. package/dist/rules/simulator/evaluation-builtins.d.ts.map +1 -1
  111. package/dist/rules/simulator/evaluation-builtins.js +33 -8
  112. package/dist/rules/simulator/evaluation-builtins.js.map +1 -1
  113. package/dist/rules/simulator/evaluation-context.d.ts +12 -0
  114. package/dist/rules/simulator/evaluation-context.d.ts.map +1 -1
  115. package/dist/rules/simulator/evaluator.d.ts +1 -1
  116. package/dist/rules/simulator/evaluator.d.ts.map +1 -1
  117. package/dist/rules/simulator/evaluator.js +16 -4
  118. package/dist/rules/simulator/evaluator.js.map +1 -1
  119. package/dist/rules/simulator/handler.d.ts.map +1 -1
  120. package/dist/rules/simulator/handler.js +52 -5
  121. package/dist/rules/simulator/handler.js.map +1 -1
  122. package/dist/rules/simulator/lookup-budget.d.ts +37 -0
  123. package/dist/rules/simulator/lookup-budget.d.ts.map +1 -0
  124. package/dist/rules/simulator/lookup-budget.js +96 -0
  125. package/dist/rules/simulator/lookup-budget.js.map +1 -0
  126. package/dist/rules/stdlib-modules.d.ts +7 -0
  127. package/dist/rules/stdlib-modules.d.ts.map +1 -1
  128. package/dist/rules/stdlib-modules.js +11 -11
  129. package/dist/rules/stdlib-modules.js.map +1 -1
  130. package/dist/sandbox/admin-firestore/get-firestore.d.ts +6 -0
  131. package/dist/sandbox/admin-firestore/get-firestore.d.ts.map +1 -1
  132. package/dist/sandbox/admin-firestore/get-firestore.js +22 -3
  133. package/dist/sandbox/admin-firestore/get-firestore.js.map +1 -1
  134. package/dist/sandbox/index.d.ts +1 -1
  135. package/dist/sandbox/index.d.ts.map +1 -1
  136. package/dist/sandbox/index.js +1 -1
  137. package/dist/sandbox/index.js.map +1 -1
  138. package/dist/sandbox/persistence/types.d.ts +1 -1
  139. package/dist/sandbox/remote.d.ts +2 -2
  140. package/dist/sandbox/sandbox-context.d.ts +8 -1
  141. package/dist/sandbox/sandbox-context.d.ts.map +1 -1
  142. package/dist/sandbox/sandbox-context.js +59 -8
  143. package/dist/sandbox/sandbox-context.js.map +1 -1
  144. package/dist/sandbox/types/auth-state.d.ts +1 -0
  145. package/dist/sandbox/types/auth-state.d.ts.map +1 -1
  146. package/dist/sandbox/types/events.d.ts +2 -1
  147. package/dist/sandbox/types/events.d.ts.map +1 -1
  148. package/dist/sandbox/types/operation.d.ts +1 -0
  149. package/dist/sandbox/types/operation.d.ts.map +1 -1
  150. package/dist/storage/enforce.d.ts +20 -1
  151. package/dist/storage/enforce.d.ts.map +1 -1
  152. package/dist/storage/enforce.js +47 -14
  153. package/dist/storage/enforce.js.map +1 -1
  154. package/dist/storage/index.d.ts +1 -1
  155. package/dist/storage/index.d.ts.map +1 -1
  156. package/dist/storage/list.js +1 -1
  157. package/dist/storage/list.js.map +1 -1
  158. package/dist/storage/sandbox/rules-evaluation-error.d.ts +25 -0
  159. package/dist/storage/sandbox/rules-evaluation-error.d.ts.map +1 -1
  160. package/dist/storage/sandbox/rules-evaluation-error.js +29 -0
  161. package/dist/storage/sandbox/rules-evaluation-error.js.map +1 -1
  162. package/dist/storage/sandbox/rules-evaluator.d.ts +10 -5
  163. package/dist/storage/sandbox/rules-evaluator.d.ts.map +1 -1
  164. package/dist/storage/sandbox/rules-evaluator.js +105 -33
  165. package/dist/storage/sandbox/rules-evaluator.js.map +1 -1
  166. package/dist/storage/sandbox/rules-methods.d.ts.map +1 -1
  167. package/dist/storage/sandbox/rules-methods.js +17 -7
  168. package/dist/storage/sandbox/rules-methods.js.map +1 -1
  169. package/dist/storage/service.d.ts +23 -2
  170. package/dist/storage/service.d.ts.map +1 -1
  171. package/dist/storage/service.js +36 -26
  172. package/dist/storage/service.js.map +1 -1
  173. package/package.json +41 -1
  174. package/src/ai/blocked.ts +97 -0
  175. package/src/ai/broker/broker.ts +88 -0
  176. package/src/ai/broker/gemini-engine.ts +36 -9
  177. package/src/ai/broker/index.ts +2 -1
  178. package/src/ai/broker/openai-engine.ts +31 -3
  179. package/src/ai/broker/synthesizer.ts +34 -0
  180. package/src/ai/broker/types.ts +25 -0
  181. package/src/ai/internal.ts +5 -0
  182. package/src/ai/response-helpers.ts +6 -25
  183. package/src/analytics/index.ts +53 -0
  184. package/src/app/dispatch.test.ts +3 -1
  185. package/src/app-check/index.ts +41 -0
  186. package/src/database/controls.ts +1 -1
  187. package/src/database/sandbox/rules-eval.ts +2 -2
  188. package/src/database/sandbox/write-plane.ts +2 -2
  189. package/src/database/sandbox-controls.ts +29 -9
  190. package/src/deferred/entry.ts +167 -0
  191. package/src/firestore/lite.ts +91 -0
  192. package/src/firestore/persistence.ts +2 -2
  193. package/src/functions/index.ts +38 -0
  194. package/src/performance/index.ts +31 -0
  195. package/src/remote-config/index.ts +53 -0
  196. package/src/rules/grammar/FirestoreValidator.ts +44 -28
  197. package/src/rules/grammar/document-access-count.ts +75 -0
  198. package/src/rules/linter/ast-utils.ts +2 -36
  199. package/src/rules/linter/hallucinations.ts +123 -25
  200. package/src/rules/linter/linter.ts +32 -10
  201. package/src/rules/rtdb/grammar/simulator.ts +12 -13
  202. package/src/rules/rtdb/simulation/handler.ts +149 -46
  203. package/src/rules/simulator/document-lookups.ts +46 -3
  204. package/src/rules/simulator/eval-error.ts +16 -0
  205. package/src/rules/simulator/evaluation-builtins.ts +35 -8
  206. package/src/rules/simulator/evaluation-context.ts +12 -0
  207. package/src/rules/simulator/evaluator.ts +14 -3
  208. package/src/rules/simulator/handler.ts +68 -4
  209. package/src/rules/simulator/lookup-budget.ts +101 -0
  210. package/src/rules/stdlib-modules.ts +19 -11
  211. package/src/sandbox/admin-firestore/get-firestore.ts +19 -3
  212. package/src/sandbox/index.ts +5 -1
  213. package/src/sandbox/persistence/types.ts +1 -1
  214. package/src/sandbox/remote.ts +2 -2
  215. package/src/sandbox/sandbox-context.ts +69 -9
  216. package/src/sandbox/types/auth-state.ts +1 -1
  217. package/src/sandbox/types/events.ts +2 -1
  218. package/src/sandbox/types/operation.ts +1 -1
  219. package/src/storage/enforce.ts +53 -14
  220. package/src/storage/index.ts +1 -1
  221. package/src/storage/list.ts +1 -1
  222. package/src/storage/sandbox/rules-evaluation-error.ts +32 -0
  223. package/src/storage/sandbox/rules-evaluator.ts +109 -32
  224. package/src/storage/sandbox/rules-methods.ts +21 -7
  225. package/src/storage/service.ts +92 -27
@@ -0,0 +1,91 @@
1
+ /**
2
+ * `pyric/firestore/lite` — a DEFERRED mirror of `firebase/firestore/lite`.
3
+ *
4
+ * The Lite SDK is a separate build of Firestore with no local cache, no
5
+ * listeners and no offline queue. pyric mirrors the full `firebase/firestore`
6
+ * surface against the in-process sandbox; the Lite build needs its own
7
+ * cache-free instance identity, so it is deferred rather than aliased — an
8
+ * alias would silently give Lite consumers listener-backed semantics the real
9
+ * Lite SDK does not have.
10
+ *
11
+ * Every symbol below resolves and links so an app that swaps `firebase` for
12
+ * `pyric` still loads; touching one throws `PyricDeferredApiError` naming this
13
+ * subpath. See `../deferred/entry.ts` for the full rationale.
14
+ *
15
+ * The value list is the exact public runtime surface of `firebase/firestore/lite`
16
+ * (Firebase Web SDK 12.13.0). Keep it in sync when this entry graduates to a
17
+ * real mirror.
18
+ */
19
+ import { deferredEntry, type DeferredApi } from '../deferred/entry.js';
20
+
21
+ export { PyricDeferredApiError } from '../deferred/entry.js';
22
+
23
+ export const {
24
+ AggregateField, AggregateQuerySnapshot, Bytes, CollectionReference, DocumentReference,
25
+ DocumentSnapshot, FieldPath, FieldValue, Firestore, FirestoreError, GeoPoint, Query,
26
+ QueryCompositeFilterConstraint, QueryConstraint, QueryDocumentSnapshot, QueryEndAtConstraint,
27
+ QueryFieldFilterConstraint, QueryLimitConstraint, QueryOrderByConstraint, QuerySnapshot,
28
+ QueryStartAtConstraint, Timestamp, Transaction, VectorValue, WriteBatch, addDoc,
29
+ aggregateFieldEqual, aggregateQuerySnapshotEqual, and, arrayRemove, arrayUnion, average,
30
+ collection, collectionGroup, connectFirestoreEmulator, count, deleteDoc, deleteField, doc,
31
+ documentId, endAt, endBefore, getAggregate, getCount, getDoc, getDocs, getFirestore,
32
+ increment, initializeFirestore, limit, limitToLast, or, orderBy, query, queryEqual, refEqual,
33
+ runTransaction, serverTimestamp, setDoc, setLogLevel, snapshotEqual, startAfter, startAt,
34
+ sum, terminate, updateDoc, vector, where, writeBatch,
35
+ } = deferredEntry('firestore/lite');
36
+
37
+ // Type declarations. Aliased to the deferred placeholder so a consumer's own
38
+ // annotations keep type-checking: every deferred call returns `never`, which is
39
+ // assignable to any of these. Names that Firebase exports as a CLASS appear both
40
+ // here and above — a class is a value and a type, and both meanings must survive
41
+ // the swap.
42
+ export type AddPrefixToKeys = DeferredApi;
43
+ export type AggregateField = DeferredApi;
44
+ export type AggregateFieldType = DeferredApi;
45
+ export type AggregateQuerySnapshot = DeferredApi;
46
+ export type AggregateSpec = DeferredApi;
47
+ export type AggregateSpecData = DeferredApi;
48
+ export type AggregateType = DeferredApi;
49
+ export type Bytes = DeferredApi;
50
+ export type ChildUpdateFields = DeferredApi;
51
+ export type CollectionReference = DeferredApi;
52
+ export type DocumentData = DeferredApi;
53
+ export type DocumentReference = DeferredApi;
54
+ export type DocumentSnapshot = DeferredApi;
55
+ export type EmulatorMockTokenOptions = DeferredApi;
56
+ export type FieldPath = DeferredApi;
57
+ export type FieldValue = DeferredApi;
58
+ export type Firestore = DeferredApi;
59
+ export type FirestoreDataConverter = DeferredApi;
60
+ export type FirestoreError = DeferredApi;
61
+ export type FirestoreErrorCode = DeferredApi;
62
+ export type GeoPoint = DeferredApi;
63
+ export type LogLevel = DeferredApi;
64
+ export type NestedUpdateFields = DeferredApi;
65
+ export type OrderByDirection = DeferredApi;
66
+ export type PartialWithFieldValue = DeferredApi;
67
+ export type Primitive = DeferredApi;
68
+ export type Query = DeferredApi;
69
+ export type QueryCompositeFilterConstraint = DeferredApi;
70
+ export type QueryConstraint = DeferredApi;
71
+ export type QueryConstraintType = DeferredApi;
72
+ export type QueryDocumentSnapshot = DeferredApi;
73
+ export type QueryEndAtConstraint = DeferredApi;
74
+ export type QueryFieldFilterConstraint = DeferredApi;
75
+ export type QueryFilterConstraint = DeferredApi;
76
+ export type QueryLimitConstraint = DeferredApi;
77
+ export type QueryNonFilterConstraint = DeferredApi;
78
+ export type QueryOrderByConstraint = DeferredApi;
79
+ export type QuerySnapshot = DeferredApi;
80
+ export type QueryStartAtConstraint = DeferredApi;
81
+ export type SetOptions = DeferredApi;
82
+ export type Settings = DeferredApi;
83
+ export type Timestamp = DeferredApi;
84
+ export type Transaction = DeferredApi;
85
+ export type TransactionOptions = DeferredApi;
86
+ export type UnionToIntersection = DeferredApi;
87
+ export type UpdateData = DeferredApi;
88
+ export type VectorValue = DeferredApi;
89
+ export type WhereFilterOp = DeferredApi;
90
+ export type WithFieldValue = DeferredApi;
91
+ export type WriteBatch = DeferredApi;
@@ -191,7 +191,7 @@ const TAB_MANAGER_SYMBOL: unique symbol = Symbol('pyric/firestore/tabManager');
191
191
  const GC_SYMBOL: unique symbol = Symbol('pyric/firestore/gc');
192
192
 
193
193
  /** Opaque tab-manager config token. Inert — persistence is always on,
194
- * and the SharedWorker/`pyric dev` path already is the one shared
194
+ * and the SharedWorker/`pyric sandbox` path already is the one shared
195
195
  * store every tab talks to, so there is no separate multi-tab mode
196
196
  * to opt into. Carries the requested kind only for debugging. */
197
197
  export interface PersistentTabManager {
@@ -409,7 +409,7 @@ export type LogLevel = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent
409
409
  /**
410
410
  * Accepted no-op: the sandbox has no modular-SDK-style logger to wire
411
411
  * a level into (it uses host-level `console` logging directly, gated
412
- * by `pyric dev`'s own flags, not this call). Exists purely so app
412
+ * by `pyric sandbox`'s own flags, not this call). Exists purely so app
413
413
  * code that calls this defensively at startup doesn't crash on a
414
414
  * missing export.
415
415
  */
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `pyric/functions` — a DEFERRED mirror of `firebase/functions`.
3
+ *
4
+ * Callable Cloud Functions dispatch to deployed server code. Mirroring this
5
+ * means running the user’s functions, which is the emulator’s job, not the
6
+ * in-process sandbox’s — deferred pending a decision on the function-host
7
+ * seam.
8
+ *
9
+ * Every symbol below resolves and links so an app that swaps `firebase` for
10
+ * `pyric` still loads; touching one throws `PyricDeferredApiError` naming this
11
+ * subpath. See `../deferred/entry.ts` for the full rationale.
12
+ *
13
+ * The value list is the exact public runtime surface of `firebase/functions`
14
+ * (Firebase Web SDK 12.13.0). Keep it in sync when this entry graduates to a
15
+ * real mirror.
16
+ */
17
+ import { deferredEntry, type DeferredApi } from '../deferred/entry.js';
18
+
19
+ export { PyricDeferredApiError } from '../deferred/entry.js';
20
+
21
+ export const {
22
+ FunctionsError, connectFunctionsEmulator, getFunctions, httpsCallable, httpsCallableFromURL,
23
+ } = deferredEntry('functions');
24
+
25
+ // Type declarations. Aliased to the deferred placeholder so a consumer's own
26
+ // annotations keep type-checking: every deferred call returns `never`, which is
27
+ // assignable to any of these. Names that Firebase exports as a CLASS appear both
28
+ // here and above — a class is a value and a type, and both meanings must survive
29
+ // the swap.
30
+ export type Functions = DeferredApi;
31
+ export type FunctionsError = DeferredApi;
32
+ export type FunctionsErrorCode = DeferredApi;
33
+ export type FunctionsErrorCodeCore = DeferredApi;
34
+ export type HttpsCallable = DeferredApi;
35
+ export type HttpsCallableOptions = DeferredApi;
36
+ export type HttpsCallableResult = DeferredApi;
37
+ export type HttpsCallableStreamOptions = DeferredApi;
38
+ export type HttpsCallableStreamResult = DeferredApi;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `pyric/performance` — a DEFERRED mirror of `firebase/performance`.
3
+ *
4
+ * Performance Monitoring instruments real network and render timings and
5
+ * ships them to Google. Nothing about it is locally observable, so it is
6
+ * deferred.
7
+ *
8
+ * Every symbol below resolves and links so an app that swaps `firebase` for
9
+ * `pyric` still loads; touching one throws `PyricDeferredApiError` naming this
10
+ * subpath. See `../deferred/entry.ts` for the full rationale.
11
+ *
12
+ * The value list is the exact public runtime surface of `firebase/performance`
13
+ * (Firebase Web SDK 12.13.0). Keep it in sync when this entry graduates to a
14
+ * real mirror.
15
+ */
16
+ import { deferredEntry, type DeferredApi } from '../deferred/entry.js';
17
+
18
+ export { PyricDeferredApiError } from '../deferred/entry.js';
19
+
20
+ export const {
21
+ getPerformance, initializePerformance, trace,
22
+ } = deferredEntry('performance');
23
+
24
+ // Type declarations. Aliased to the deferred placeholder so a consumer's own
25
+ // annotations keep type-checking: every deferred call returns `never`, which is
26
+ // assignable to any of these. Names that Firebase exports as a CLASS appear both
27
+ // here and above — a class is a value and a type, and both meanings must survive
28
+ // the swap.
29
+ export type FirebasePerformance = DeferredApi;
30
+ export type PerformanceSettings = DeferredApi;
31
+ export type PerformanceTrace = DeferredApi;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `pyric/remote-config` — a DEFERRED mirror of `firebase/remote-config`.
3
+ *
4
+ * Remote Config serves server-side parameter values with fetch/activate
5
+ * caching semantics. Mirroring it needs a sandbox-side config store and a
6
+ * model of the fetch throttle — buildable, not yet built.
7
+ *
8
+ * Every symbol below resolves and links so an app that swaps `firebase` for
9
+ * `pyric` still loads; touching one throws `PyricDeferredApiError` naming this
10
+ * subpath. See `../deferred/entry.ts` for the full rationale.
11
+ *
12
+ * The value list is the exact public runtime surface of `firebase/remote-config`
13
+ * (Firebase Web SDK 12.13.0). Keep it in sync when this entry graduates to a
14
+ * real mirror.
15
+ */
16
+ import { deferredEntry, type DeferredApi } from '../deferred/entry.js';
17
+
18
+ export { PyricDeferredApiError } from '../deferred/entry.js';
19
+
20
+ export const {
21
+ activate, ensureInitialized, fetchAndActivate, fetchConfig, getAll, getBoolean, getNumber,
22
+ getRemoteConfig, getString, getValue, onConfigUpdate, setCustomSignals,
23
+ setLogLevel,
24
+ } = deferredEntry('remote-config');
25
+
26
+ /**
27
+ * The one deferred symbol that answers instead of throwing: the real SDK
28
+ * resolves a boolean, and a deferred entry IS unsupported, so the standard
29
+ * `isSupported().then(ok => ok && get…(app))` guard must run — not crash.
30
+ */
31
+ export const isSupported = async (): Promise<boolean> => false;
32
+
33
+
34
+ // Type declarations. Aliased to the deferred placeholder so a consumer's own
35
+ // annotations keep type-checking: every deferred call returns `never`, which is
36
+ // assignable to any of these. Names that Firebase exports as a CLASS appear both
37
+ // here and above — a class is a value and a type, and both meanings must survive
38
+ // the swap.
39
+ export type ConfigUpdate = DeferredApi;
40
+ export type ConfigUpdateObserver = DeferredApi;
41
+ export type CustomSignals = DeferredApi;
42
+ export type FetchResponse = DeferredApi;
43
+ export type FetchStatus = DeferredApi;
44
+ export type FetchType = DeferredApi;
45
+ export type FirebaseExperimentDescription = DeferredApi;
46
+ export type FirebaseRemoteConfigObject = DeferredApi;
47
+ export type LogLevel = DeferredApi;
48
+ export type RemoteConfig = DeferredApi;
49
+ export type RemoteConfigOptions = DeferredApi;
50
+ export type RemoteConfigSettings = DeferredApi;
51
+ export type Unsubscribe = DeferredApi;
52
+ export type Value = DeferredApi;
53
+ export type ValueSource = DeferredApi;
@@ -2,6 +2,7 @@ import type {
2
2
  FirestoreRules, MatchBlock, AllowRule, FunctionDef, Expression, Operation,
3
3
  } from './FirestoreAST.js';
4
4
  import { RULES_BUILTIN_FUNCTIONS } from './builtin-functions.js';
5
+ import { countDocumentAccessCalls } from './document-access-count.js';
5
6
 
6
7
  export interface ValidationFinding {
7
8
  code: string;
@@ -26,8 +27,9 @@ export function validateFirestoreRules(ast: FirestoreRules): ValidationFinding[]
26
27
  // SEC-4: Check for default deny
27
28
  checkDefaultDeny(rootMatch, findings);
28
29
 
29
- // QUA-3: Duplicate function names
30
- checkDuplicateFunctions(rootMatch, findings);
30
+ // QUA-3: Duplicate function names in one scope (production compile
31
+ // rejection, so critical). Nested shadowing is legal and is not flagged.
32
+ checkDuplicateFunctions(ast, findings);
31
33
 
32
34
  // QUA-4: Unused functions
33
35
  checkUnusedFunctions(rootMatch, allCalls, findings);
@@ -124,12 +126,15 @@ function walkMatch(
124
126
  });
125
127
  }
126
128
 
127
- // SEM-3: get()/exists() budget exceeded
128
- const docReads = countDocReads(cond, new Map(localScope.map(f => [f.name, f])));
129
+ // SEM-3: document access budget exceeded. Production allows exactly 10
130
+ // get/exists/getAfter/existsAfter reads per request evaluation; the
131
+ // 11th fails, so the finding fires only ABOVE 10 (same boundary as the
132
+ // linter's GET_COUNT error and the simulator's runtime LookupBudget).
133
+ const docReads = countDocumentAccessCalls(cond, fnMap);
129
134
  if (docReads > 10) {
130
135
  findings.push({
131
136
  code: 'SEM-3', severity: 'high', path: pathStr, operation: opStr,
132
- message: `Rule at ${pathStr} has ${docReads} document reads (get/exists) — exceeds the 10-call budget`,
137
+ message: `Rule at ${pathStr} may perform ${docReads} document access calls (get/exists/getAfter/existsAfter), which exceeds the 10-call budget`,
133
138
  });
134
139
  }
135
140
 
@@ -323,21 +328,6 @@ function referencesResourceData(expr: Expression): boolean {
323
328
  );
324
329
  }
325
330
 
326
- function countDocReads(expr: Expression, functions: Map<string, FunctionDef>, visited = new Set<string>()): number {
327
- let count = 0;
328
- walkExpr(expr, e => {
329
- if (e.type === 'functionCall' && (e.name === 'get' || e.name === 'exists' || e.name === 'getAfter')) {
330
- count++;
331
- }
332
- // Follow user-defined function calls
333
- if (e.type === 'functionCall' && functions.has(e.name) && !visited.has(e.name)) {
334
- visited.add(e.name);
335
- count += countDocReads(functions.get(e.name)!.body, functions, visited);
336
- }
337
- });
338
- return count;
339
- }
340
-
341
331
  function collectFunctionCalls(expr: Expression): string[] {
342
332
  const calls: string[] = [];
343
333
  walkExpr(expr, e => {
@@ -357,22 +347,48 @@ function collectAllFunctions(match: MatchBlock): Set<string> {
357
347
  }
358
348
 
359
349
  // ---- QUA-3: Duplicate functions ----
350
+ //
351
+ // Production REJECTS two declarations of one function name in the SAME
352
+ // scope at compile time, so this is severity 'critical': the write gate
353
+ // (`write/handler.ts`) blocks only critical validator findings, and
354
+ // 'medium' folded to a mere warning that deployed anyway.
355
+ //
356
+ // A scope is one declaration list: the global list above `service`, the
357
+ // list directly inside `service`, or one match block's own list. A nested
358
+ // match block that redeclares a name from an enclosing scope SHADOWS it and
359
+ // is legal: the simulator implements shadowing in
360
+ // `simulator/match-resolution.ts`, and the production capture in
361
+ // `conformance/rules-corpus/storage/function-scopes-and-shadowing.ts` pins
362
+ // inner-shadows-outer as accepted. Flagging that would block a ruleset
363
+ // production deploys.
364
+ //
365
+ // The code stays QUA-3 (already registered and asserted by consumers);
366
+ // `DUPLICATE_FUNCTION` was considered and rejected, because the module
367
+ // resolver already uses that name for its own error code
368
+ // (`modules/resolver-core.ts`), and colliding would make mixed issue
369
+ // lists ambiguous.
370
+
371
+ function checkDuplicateFunctions(ast: FirestoreRules, findings: ValidationFinding[]) {
372
+ checkDupsInScope(ast.functions ?? [], 'global scope', findings);
373
+ checkDupsInScope(ast.service.functions ?? [], `service ${ast.service.name}`, findings);
374
+ checkDupsInMatchScopes(ast.service.match, findings);
375
+ }
360
376
 
361
- function checkDuplicateFunctions(match: MatchBlock, findings: ValidationFinding[]) {
377
+ function checkDupsInMatchScopes(match: MatchBlock, findings: ValidationFinding[]) {
362
378
  checkDupsInScope(match.functions, match.path.raw, findings);
363
- for (const child of match.children) checkDuplicateFunctions(child, findings);
379
+ for (const child of match.children) checkDupsInMatchScopes(child, findings);
364
380
  }
365
381
 
366
- function checkDupsInScope(fns: FunctionDef[], path: string, findings: ValidationFinding[]) {
367
- const seen = new Set<string>();
382
+ function checkDupsInScope(fns: readonly FunctionDef[], scope: string, findings: ValidationFinding[]) {
383
+ const declared = new Set<string>();
368
384
  for (const fn of fns) {
369
- if (seen.has(fn.name)) {
385
+ if (declared.has(fn.name)) {
370
386
  findings.push({
371
- code: 'QUA-3', severity: 'medium', path,
372
- message: `Duplicate function '${fn.name}' in scope at ${path}`,
387
+ code: 'QUA-3', severity: 'critical', path: scope,
388
+ message: `Duplicate function '${fn.name}' declared twice in ${scope}. Production rejects duplicate function declarations in one scope at compile time`,
373
389
  });
374
390
  }
375
- seen.add(fn.name);
391
+ declared.add(fn.name);
376
392
  }
377
393
  }
378
394
 
@@ -0,0 +1,75 @@
1
+ import type { Expression, FunctionDef } from './FirestoreAST.js';
2
+
3
+ /**
4
+ * The four built-in document access calls production counts against a
5
+ * request's 10-read budget.
6
+ */
7
+ const DOCUMENT_ACCESS_BUILTINS = new Set(['get', 'exists', 'getAfter', 'existsAfter']);
8
+
9
+ /**
10
+ * Count the document access calls (get/exists/getAfter/existsAfter)
11
+ * reachable from a rule condition, expanding each user-defined function
12
+ * once PER CALL SITE. `isOwner(a) && isOwner(b) && isOwner(c)` with three
13
+ * gets inside `isOwner` costs nine, matching production where each call
14
+ * performs its own reads (different arguments mean different paths).
15
+ * A function's `let` bindings evaluate on every call, so their reads count
16
+ * too. The call stack guards recursion only, and entries unwind on return,
17
+ * so sibling call sites each pay full price.
18
+ *
19
+ * This is a static over-approximation: production caches repeated reads of
20
+ * the SAME path within a request (see site-docs
21
+ * secure/firestore-rules-limits.md), but path identity is not decidable
22
+ * statically, so every call site is charged. The cache-aware, distinct-path
23
+ * count is the runtime concern of LookupBudget in
24
+ * simulator/lookup-budget.ts, which counts what an evaluation actually
25
+ * reads rather than what a condition could reach.
26
+ *
27
+ * This is the single walker behind both static consumers: the validator's
28
+ * SEM-3 finding and the linter's GET_COUNT warning.
29
+ */
30
+ export function countDocumentAccessCalls(
31
+ expr: Expression,
32
+ functions: Map<string, FunctionDef>,
33
+ callStack: Set<string> = new Set<string>(),
34
+ ): number {
35
+ let count = 0;
36
+
37
+ const visit = (e: Expression): void => {
38
+ switch (e.type) {
39
+ case 'functionCall':
40
+ if (DOCUMENT_ACCESS_BUILTINS.has(e.name)) {
41
+ count++;
42
+ } else {
43
+ const fn = functions.get(e.name);
44
+ const isExpandable = fn !== undefined && !callStack.has(e.name);
45
+ if (isExpandable) {
46
+ callStack.add(e.name);
47
+ for (const binding of fn!.lets) visit(binding.value);
48
+ visit(fn!.body);
49
+ callStack.delete(e.name); // unwind: count once per call site, not once per rule
50
+ }
51
+ }
52
+ e.args.forEach(visit);
53
+ break;
54
+ case 'binaryOp': visit(e.left); visit(e.right); break;
55
+ case 'unaryOp': visit(e.operand); break;
56
+ case 'ternary': visit(e.condition); visit(e.consequent); visit(e.alternate); break;
57
+ case 'methodCall': visit(e.object); e.args.forEach(visit); break;
58
+ case 'memberAccess': visit(e.object); break;
59
+ case 'bracketAccess': visit(e.object); visit(e.index); break;
60
+ case 'sliceAccess': visit(e.object); visit(e.start); visit(e.end); break;
61
+ case 'inExpr': visit(e.element); visit(e.collection); break;
62
+ case 'isExpr': visit(e.value); break;
63
+ case 'listLiteral': e.elements.forEach(visit); break;
64
+ case 'mapLiteral': e.entries.forEach((entry) => { visit(entry.key); visit(entry.value); }); break;
65
+ case 'pathLiteral':
66
+ for (const segment of e.segments) {
67
+ if (typeof segment !== 'string') visit(segment);
68
+ }
69
+ break;
70
+ }
71
+ };
72
+
73
+ visit(expr);
74
+ return count;
75
+ }
@@ -218,44 +218,10 @@ export function maxCallDepth(start: string, graph: Map<string, string[]>, visite
218
218
  return 1 + max;
219
219
  }
220
220
 
221
- /**
222
- * Count get() and exists() calls reachable from an expression,
223
- * following function calls transitively.
224
- */
225
- export function countGetCalls(expr: Expression, functions: Map<string, FunctionDef>, visited = new Set<string>()): number {
226
- let count = 0;
227
- const walk = (e: Expression) => {
228
- switch (e.type) {
229
- case 'functionCall':
230
- if (e.name === 'get' || e.name === 'exists') {
231
- count++;
232
- } else if (functions.has(e.name) && !visited.has(e.name)) {
233
- visited.add(e.name);
234
- const fn = functions.get(e.name)!;
235
- walk(fn.body);
236
- for (const b of fn.lets) walk(b.value);
237
- }
238
- e.args.forEach(walk);
239
- break;
240
- case 'binaryOp': walk(e.left); walk(e.right); break;
241
- case 'unaryOp': walk(e.operand); break;
242
- case 'methodCall': walk(e.object); e.args.forEach(walk); break;
243
- case 'memberAccess': walk(e.object); break;
244
- case 'bracketAccess': walk(e.object); walk(e.index); break;
245
- case 'ternary': walk(e.condition); walk(e.consequent); walk(e.alternate); break;
246
- case 'inExpr': walk(e.element); walk(e.collection); break;
247
- case 'isExpr': walk(e.value); break;
248
- case 'listLiteral': e.elements.forEach(walk); break;
249
- case 'mapLiteral': e.entries.forEach(en => { walk(en.key); walk(en.value); }); break;
250
- }
251
- };
252
- walk(expr);
253
- return count;
254
- }
255
-
256
221
  /**
257
222
  * Count how many times each user-defined function is called in an expression.
258
- * Unlike countGetCalls (which deduplicates), this counts raw call-site occurrences.
223
+ * Counts raw call-site occurrences at the top level only: it does not expand
224
+ * nested user-function bodies the way countDocumentAccessCalls does.
259
225
  */
260
226
  export function countFunctionCallSites(expr: Expression, fnNames: Set<string>): Map<string, number> {
261
227
  const counts = new Map<string, number>();