pyric 0.1.0-alpha.19 → 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 (281) 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 +50 -0
  7. package/dist/ai/broker/broker.d.ts.map +1 -1
  8. package/dist/ai/broker/broker.js +157 -21
  9. package/dist/ai/broker/broker.js.map +1 -1
  10. package/dist/ai/broker/gemini-engine.d.ts +56 -0
  11. package/dist/ai/broker/gemini-engine.d.ts.map +1 -0
  12. package/dist/ai/broker/gemini-engine.js +235 -0
  13. package/dist/ai/broker/gemini-engine.js.map +1 -0
  14. package/dist/ai/broker/index.d.ts +2 -1
  15. package/dist/ai/broker/index.d.ts.map +1 -1
  16. package/dist/ai/broker/index.js +2 -1
  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 +29 -0
  27. package/dist/ai/broker/types.d.ts.map +1 -1
  28. package/dist/ai/instances.js +1 -1
  29. package/dist/ai/instances.js.map +1 -1
  30. package/dist/ai/internal.d.ts +5 -0
  31. package/dist/ai/internal.d.ts.map +1 -1
  32. package/dist/ai/internal.js +5 -0
  33. package/dist/ai/internal.js.map +1 -1
  34. package/dist/ai/response-helpers.d.ts.map +1 -1
  35. package/dist/ai/response-helpers.js +6 -24
  36. package/dist/ai/response-helpers.js.map +1 -1
  37. package/dist/analytics/index.d.ts +40 -0
  38. package/dist/analytics/index.d.ts.map +1 -0
  39. package/dist/analytics/index.js +25 -0
  40. package/dist/analytics/index.js.map +1 -0
  41. package/dist/app-check/index.d.ts +31 -0
  42. package/dist/app-check/index.d.ts.map +1 -0
  43. package/dist/app-check/index.js +20 -0
  44. package/dist/app-check/index.js.map +1 -0
  45. package/dist/auth/instances.js +1 -1
  46. package/dist/auth/instances.js.map +1 -1
  47. package/dist/database/controls.d.ts +1 -1
  48. package/dist/database/controls.js +1 -1
  49. package/dist/database/instances.js +1 -1
  50. package/dist/database/instances.js.map +1 -1
  51. package/dist/database/sandbox/backend.d.ts +1 -0
  52. package/dist/database/sandbox/backend.d.ts.map +1 -1
  53. package/dist/database/sandbox/backend.js +1 -0
  54. package/dist/database/sandbox/backend.js.map +1 -1
  55. package/dist/database/sandbox/rules-eval.d.ts +6 -2
  56. package/dist/database/sandbox/rules-eval.d.ts.map +1 -1
  57. package/dist/database/sandbox/rules-eval.js +15 -2
  58. package/dist/database/sandbox/rules-eval.js.map +1 -1
  59. package/dist/database/sandbox/write-plane.d.ts +1 -0
  60. package/dist/database/sandbox/write-plane.d.ts.map +1 -1
  61. package/dist/database/sandbox/write-plane.js +7 -2
  62. package/dist/database/sandbox/write-plane.js.map +1 -1
  63. package/dist/database/sandbox-controls.d.ts +12 -5
  64. package/dist/database/sandbox-controls.d.ts.map +1 -1
  65. package/dist/database/sandbox-controls.js +20 -9
  66. package/dist/database/sandbox-controls.js.map +1 -1
  67. package/dist/database/sandbox-namespace.d.ts +19 -1
  68. package/dist/database/sandbox-namespace.d.ts.map +1 -1
  69. package/dist/database/sandbox-namespace.js +24 -1
  70. package/dist/database/sandbox-namespace.js.map +1 -1
  71. package/dist/deferred/entry.d.ts +75 -0
  72. package/dist/deferred/entry.d.ts.map +1 -0
  73. package/dist/deferred/entry.js +143 -0
  74. package/dist/deferred/entry.js.map +1 -0
  75. package/dist/firestore/instances.js +1 -1
  76. package/dist/firestore/instances.js.map +1 -1
  77. package/dist/firestore/lite.d.ts +72 -0
  78. package/dist/firestore/lite.d.ts.map +1 -0
  79. package/dist/firestore/lite.js +22 -0
  80. package/dist/firestore/lite.js.map +1 -0
  81. package/dist/firestore/persistence.d.ts +2 -2
  82. package/dist/firestore/persistence.js +1 -1
  83. package/dist/firestore/sandbox-controls.d.ts +2 -0
  84. package/dist/firestore/sandbox-controls.d.ts.map +1 -1
  85. package/dist/firestore/sandbox-controls.js +8 -0
  86. package/dist/firestore/sandbox-controls.js.map +1 -1
  87. package/dist/functions/index.d.ts +29 -0
  88. package/dist/functions/index.d.ts.map +1 -0
  89. package/dist/functions/index.js +20 -0
  90. package/dist/functions/index.js.map +1 -0
  91. package/dist/messaging/client.d.ts +2 -1
  92. package/dist/messaging/client.d.ts.map +1 -1
  93. package/dist/messaging/client.js +2 -2
  94. package/dist/messaging/client.js.map +1 -1
  95. package/dist/messaging/instance.d.ts +2 -2
  96. package/dist/messaging/instance.d.ts.map +1 -1
  97. package/dist/messaging/instance.js +40 -6
  98. package/dist/messaging/instance.js.map +1 -1
  99. package/dist/messaging/sw.d.ts +2 -1
  100. package/dist/messaging/sw.d.ts.map +1 -1
  101. package/dist/messaging/sw.js +2 -2
  102. package/dist/messaging/sw.js.map +1 -1
  103. package/dist/performance/index.d.ts +22 -0
  104. package/dist/performance/index.d.ts.map +1 -0
  105. package/dist/performance/index.js +19 -0
  106. package/dist/performance/index.js.map +1 -0
  107. package/dist/remote-config/index.d.ts +40 -0
  108. package/dist/remote-config/index.d.ts.map +1 -0
  109. package/dist/remote-config/index.js +25 -0
  110. package/dist/remote-config/index.js.map +1 -0
  111. package/dist/rules/grammar/FirestoreValidator.d.ts.map +1 -1
  112. package/dist/rules/grammar/FirestoreValidator.js +43 -27
  113. package/dist/rules/grammar/FirestoreValidator.js.map +1 -1
  114. package/dist/rules/grammar/document-access-count.d.ts +24 -0
  115. package/dist/rules/grammar/document-access-count.d.ts.map +1 -0
  116. package/dist/rules/grammar/document-access-count.js +100 -0
  117. package/dist/rules/grammar/document-access-count.js.map +1 -0
  118. package/dist/rules/linter/ast-utils.d.ts +2 -6
  119. package/dist/rules/linter/ast-utils.d.ts.map +1 -1
  120. package/dist/rules/linter/ast-utils.js +2 -63
  121. package/dist/rules/linter/ast-utils.js.map +1 -1
  122. package/dist/rules/linter/hallucinations.d.ts +8 -0
  123. package/dist/rules/linter/hallucinations.d.ts.map +1 -1
  124. package/dist/rules/linter/hallucinations.js +121 -31
  125. package/dist/rules/linter/hallucinations.js.map +1 -1
  126. package/dist/rules/linter/linter.d.ts +10 -1
  127. package/dist/rules/linter/linter.d.ts.map +1 -1
  128. package/dist/rules/linter/linter.js +23 -9
  129. package/dist/rules/linter/linter.js.map +1 -1
  130. package/dist/rules/rtdb/grammar/RtdbExpr.ohm +5 -2
  131. package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.d.ts +1 -1
  132. package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.d.ts.map +1 -1
  133. package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.js +5 -2
  134. package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.js.map +1 -1
  135. package/dist/rules/rtdb/grammar/simulator.d.ts.map +1 -1
  136. package/dist/rules/rtdb/grammar/simulator.js +61 -15
  137. package/dist/rules/rtdb/grammar/simulator.js.map +1 -1
  138. package/dist/rules/rtdb/simulation/handler.d.ts.map +1 -1
  139. package/dist/rules/rtdb/simulation/handler.js +96 -35
  140. package/dist/rules/rtdb/simulation/handler.js.map +1 -1
  141. package/dist/rules/simulator/document-lookups.d.ts +4 -0
  142. package/dist/rules/simulator/document-lookups.d.ts.map +1 -1
  143. package/dist/rules/simulator/document-lookups.js +42 -3
  144. package/dist/rules/simulator/document-lookups.js.map +1 -1
  145. package/dist/rules/simulator/eval-error.d.ts +11 -0
  146. package/dist/rules/simulator/eval-error.d.ts.map +1 -1
  147. package/dist/rules/simulator/eval-error.js +14 -0
  148. package/dist/rules/simulator/eval-error.js.map +1 -1
  149. package/dist/rules/simulator/evaluation-builtins.d.ts.map +1 -1
  150. package/dist/rules/simulator/evaluation-builtins.js +33 -8
  151. package/dist/rules/simulator/evaluation-builtins.js.map +1 -1
  152. package/dist/rules/simulator/evaluation-context.d.ts +12 -0
  153. package/dist/rules/simulator/evaluation-context.d.ts.map +1 -1
  154. package/dist/rules/simulator/evaluator.d.ts +1 -1
  155. package/dist/rules/simulator/evaluator.d.ts.map +1 -1
  156. package/dist/rules/simulator/evaluator.js +16 -4
  157. package/dist/rules/simulator/evaluator.js.map +1 -1
  158. package/dist/rules/simulator/handler.d.ts.map +1 -1
  159. package/dist/rules/simulator/handler.js +52 -5
  160. package/dist/rules/simulator/handler.js.map +1 -1
  161. package/dist/rules/simulator/lookup-budget.d.ts +37 -0
  162. package/dist/rules/simulator/lookup-budget.d.ts.map +1 -0
  163. package/dist/rules/simulator/lookup-budget.js +96 -0
  164. package/dist/rules/simulator/lookup-budget.js.map +1 -0
  165. package/dist/rules/stdlib-modules.d.ts +7 -0
  166. package/dist/rules/stdlib-modules.d.ts.map +1 -1
  167. package/dist/rules/stdlib-modules.js +11 -11
  168. package/dist/rules/stdlib-modules.js.map +1 -1
  169. package/dist/sandbox/admin-firestore/get-firestore.d.ts +6 -0
  170. package/dist/sandbox/admin-firestore/get-firestore.d.ts.map +1 -1
  171. package/dist/sandbox/admin-firestore/get-firestore.js +22 -3
  172. package/dist/sandbox/admin-firestore/get-firestore.js.map +1 -1
  173. package/dist/sandbox/index.d.ts +1 -1
  174. package/dist/sandbox/index.d.ts.map +1 -1
  175. package/dist/sandbox/index.js +1 -1
  176. package/dist/sandbox/index.js.map +1 -1
  177. package/dist/sandbox/persistence/types.d.ts +1 -1
  178. package/dist/sandbox/remote.d.ts +2 -2
  179. package/dist/sandbox/sandbox-context.d.ts +8 -1
  180. package/dist/sandbox/sandbox-context.d.ts.map +1 -1
  181. package/dist/sandbox/sandbox-context.js +59 -8
  182. package/dist/sandbox/sandbox-context.js.map +1 -1
  183. package/dist/sandbox/types/auth-state.d.ts +1 -0
  184. package/dist/sandbox/types/auth-state.d.ts.map +1 -1
  185. package/dist/sandbox/types/events.d.ts +2 -1
  186. package/dist/sandbox/types/events.d.ts.map +1 -1
  187. package/dist/sandbox/types/operation.d.ts +1 -0
  188. package/dist/sandbox/types/operation.d.ts.map +1 -1
  189. package/dist/storage/enforce.d.ts +20 -1
  190. package/dist/storage/enforce.d.ts.map +1 -1
  191. package/dist/storage/enforce.js +47 -14
  192. package/dist/storage/enforce.js.map +1 -1
  193. package/dist/storage/index.d.ts +1 -1
  194. package/dist/storage/index.d.ts.map +1 -1
  195. package/dist/storage/list.js +1 -1
  196. package/dist/storage/list.js.map +1 -1
  197. package/dist/storage/persistence.d.ts.map +1 -1
  198. package/dist/storage/persistence.js +66 -14
  199. package/dist/storage/persistence.js.map +1 -1
  200. package/dist/storage/sandbox/rules-evaluation-error.d.ts +25 -0
  201. package/dist/storage/sandbox/rules-evaluation-error.d.ts.map +1 -1
  202. package/dist/storage/sandbox/rules-evaluation-error.js +29 -0
  203. package/dist/storage/sandbox/rules-evaluation-error.js.map +1 -1
  204. package/dist/storage/sandbox/rules-evaluator.d.ts +10 -5
  205. package/dist/storage/sandbox/rules-evaluator.d.ts.map +1 -1
  206. package/dist/storage/sandbox/rules-evaluator.js +105 -33
  207. package/dist/storage/sandbox/rules-evaluator.js.map +1 -1
  208. package/dist/storage/sandbox/rules-methods.d.ts.map +1 -1
  209. package/dist/storage/sandbox/rules-methods.js +17 -7
  210. package/dist/storage/sandbox/rules-methods.js.map +1 -1
  211. package/dist/storage/service.d.ts +23 -2
  212. package/dist/storage/service.d.ts.map +1 -1
  213. package/dist/storage/service.js +37 -26
  214. package/dist/storage/service.js.map +1 -1
  215. package/package.json +42 -2
  216. package/src/ai/blocked.ts +97 -0
  217. package/src/ai/broker/broker.ts +153 -22
  218. package/src/ai/broker/gemini-engine.ts +311 -0
  219. package/src/ai/broker/index.ts +2 -0
  220. package/src/ai/broker/openai-engine.ts +31 -3
  221. package/src/ai/broker/synthesizer.ts +34 -0
  222. package/src/ai/broker/types.ts +27 -1
  223. package/src/ai/instances.ts +1 -1
  224. package/src/ai/internal.ts +5 -0
  225. package/src/ai/response-helpers.ts +6 -25
  226. package/src/analytics/index.ts +53 -0
  227. package/src/app/dispatch.test.ts +3 -1
  228. package/src/app-check/index.ts +41 -0
  229. package/src/auth/instances.ts +1 -1
  230. package/src/database/controls.ts +1 -1
  231. package/src/database/instances.ts +1 -1
  232. package/src/database/sandbox/backend.ts +1 -0
  233. package/src/database/sandbox/rules-eval.ts +18 -2
  234. package/src/database/sandbox/write-plane.ts +8 -2
  235. package/src/database/sandbox-controls.ts +29 -9
  236. package/src/database/sandbox-namespace.ts +28 -1
  237. package/src/deferred/entry.ts +167 -0
  238. package/src/firestore/instances.ts +1 -1
  239. package/src/firestore/lite.ts +91 -0
  240. package/src/firestore/persistence.ts +2 -2
  241. package/src/firestore/sandbox-controls.ts +10 -0
  242. package/src/functions/index.ts +38 -0
  243. package/src/messaging/client.ts +3 -2
  244. package/src/messaging/instance.ts +44 -7
  245. package/src/messaging/sw.ts +3 -2
  246. package/src/performance/index.ts +31 -0
  247. package/src/remote-config/index.ts +53 -0
  248. package/src/rules/grammar/FirestoreValidator.ts +44 -28
  249. package/src/rules/grammar/document-access-count.ts +75 -0
  250. package/src/rules/linter/ast-utils.ts +2 -36
  251. package/src/rules/linter/hallucinations.ts +123 -25
  252. package/src/rules/linter/linter.ts +32 -10
  253. package/src/rules/rtdb/grammar/RtdbExpr.ohm +5 -2
  254. package/src/rules/rtdb/grammar/RtdbExpr.ohm.generated.ts +5 -2
  255. package/src/rules/rtdb/grammar/simulator.ts +41 -15
  256. package/src/rules/rtdb/simulation/handler.ts +149 -46
  257. package/src/rules/simulator/document-lookups.ts +46 -3
  258. package/src/rules/simulator/eval-error.ts +16 -0
  259. package/src/rules/simulator/evaluation-builtins.ts +35 -8
  260. package/src/rules/simulator/evaluation-context.ts +12 -0
  261. package/src/rules/simulator/evaluator.ts +14 -3
  262. package/src/rules/simulator/handler.ts +68 -4
  263. package/src/rules/simulator/lookup-budget.ts +101 -0
  264. package/src/rules/stdlib-modules.ts +19 -11
  265. package/src/sandbox/admin-firestore/get-firestore.ts +19 -3
  266. package/src/sandbox/index.ts +5 -1
  267. package/src/sandbox/persistence/types.ts +1 -1
  268. package/src/sandbox/remote.ts +2 -2
  269. package/src/sandbox/sandbox-context.ts +69 -9
  270. package/src/sandbox/types/auth-state.ts +1 -1
  271. package/src/sandbox/types/events.ts +2 -1
  272. package/src/sandbox/types/operation.ts +1 -1
  273. package/src/storage/enforce.ts +53 -14
  274. package/src/storage/index.ts +1 -1
  275. package/src/storage/list.ts +1 -1
  276. package/src/storage/persistence.ts +74 -16
  277. package/src/storage/sandbox/rules-evaluation-error.ts +32 -0
  278. package/src/storage/sandbox/rules-evaluator.ts +109 -32
  279. package/src/storage/sandbox/rules-methods.ts +21 -7
  280. package/src/storage/service.ts +93 -27
  281. package/README.md.orig +0 -53
@@ -69,7 +69,7 @@ export function forceWebSockets(): void {
69
69
  *
70
70
  * Accepted no-op: the sandbox has no modular-SDK-style logger to wire a
71
71
  * level/sink into (it uses host-level `console` logging directly, gated
72
- * by `pyric dev`'s own flags — matching `pyric/firestore`'s
72
+ * by `pyric sandbox`'s own flags — matching `pyric/firestore`'s
73
73
  * `setLogLevel`). Accepted so init code that calls it compiles + runs.
74
74
  */
75
75
  export function enableLogging(
@@ -119,7 +119,7 @@ export function getAdminDatabase(target: Sandbox | SandboxContext | FirebaseApp)
119
119
 
120
120
  function packageResolutionError(): TypeError {
121
121
  return new TypeError(
122
- 'pyric/database is a sandbox-only mirror. Package resolution must leave firebase/database unchanged for production; activate pyric dev or @pyric/cli/register before importing to select the sandbox.',
122
+ 'pyric/database is a sandbox-only mirror. Package resolution must leave firebase/database unchanged for production; activate pyric sandbox or @pyric/cli/register before importing to select the sandbox.',
123
123
  );
124
124
  }
125
125
 
@@ -42,6 +42,7 @@ export class RtdbBackend {
42
42
  getPriority(path: string): Priority { return this.state.priorities.get(path); }
43
43
  setData(seed: Record<string, JsonValue>): void { this.writes.setData(seed); }
44
44
  setRules(rules: { rules: Record<string, unknown> } | null): void { this.writes.setRules(rules); }
45
+ setDefaultPolicy(policy: 'allow' | 'deny'): void { this.writes.setDefaultPolicy(policy); }
45
46
  getActiveRules(): { rules: Record<string, unknown> } | null { return this.writes.getActiveRules(); }
46
47
  snapshotState(): JsonValue { return this.writes.snapshotState(); }
47
48
 
@@ -95,11 +95,19 @@ export interface EvalContext {
95
95
  updates?: { path: string; value: unknown }[];
96
96
  }
97
97
 
98
+ export type RtdbDefaultPolicy = 'allow' | 'deny';
99
+
98
100
  export class RulesEvaluator {
99
101
  private compiled: CompiledRtdbRules | null = null;
102
+ private defaultPolicy: RtdbDefaultPolicy = 'deny';
103
+
104
+ /** Set default access policy when no rules are loaded ('allow' or 'deny'). */
105
+ setDefaultPolicy(policy: RtdbDefaultPolicy): void {
106
+ this.defaultPolicy = policy;
107
+ }
100
108
 
101
109
  /** Replace the deployed rules. `null` clears (sandbox returns to
102
- * default-allow). */
110
+ * configured default policy). */
103
111
  setRules(rulesJson: { rules: Record<string, unknown> } | null): void {
104
112
  if (rulesJson === null) {
105
113
  this.compiled = null;
@@ -114,7 +122,7 @@ export class RulesEvaluator {
114
122
  }
115
123
 
116
124
  /**
117
- * Evaluate one op. Default-allow when no rules are loaded.
125
+ * Evaluate one op. Default-deny when no rules are loaded.
118
126
  *
119
127
  * Errors from the underlying simulator (grammar mismatches, etc.)
120
128
  * surface as `'no-rule'` — they're treated as deny by user-mode
@@ -161,6 +169,14 @@ export class RulesEvaluator {
161
169
  }
162
170
  }
163
171
  if (this.compiled === null) {
172
+ if (this.defaultPolicy === 'deny') {
173
+ return {
174
+ check: 'no-rule',
175
+ reasons: ['No RTDB rules loaded; default deny.'],
176
+ errorCode: 'NO_MATCHING_RULE',
177
+ errorMessage: 'No RTDB rules loaded; default deny.',
178
+ };
179
+ }
164
180
  return {
165
181
  check: 'allow',
166
182
  reasons: ['No RTDB rules loaded; default allow.'],
@@ -52,6 +52,12 @@ export class WritePlane {
52
52
  this.state.notifyWrite();
53
53
  }
54
54
 
55
+ setDefaultPolicy(policy: 'allow' | 'deny'): void {
56
+ this.state.rules.setDefaultPolicy(policy);
57
+ this.cancelDeniedListeners();
58
+ this.state.notifyWrite();
59
+ }
60
+
55
61
  getActiveRules(): { rules: Record<string, unknown> } | null {
56
62
  const isRulesNull = this.state.activeRules === null;
57
63
  let result: { rules: Record<string, unknown> } | null = null;
@@ -227,7 +233,7 @@ export class WritePlane {
227
233
  });
228
234
  for (const update of updates) {
229
235
  if (this.state.rules.evaluate('write', update.path, {
230
- auth, mockData, newData: update.value, ...(updates.length > 1 ? { updates } : {}),
236
+ auth, mockData, newData: update.value, updates,
231
237
  }).check !== 'allow') throw permissionDenied();
232
238
  }
233
239
  }
@@ -246,7 +252,7 @@ export class WritePlane {
246
252
  const at = Date.now();
247
253
  const before = this.state.tree.read(update.path);
248
254
  const evaluation = this.state.rules.evaluate('write', update.path, {
249
- auth, mockData, newData: update.value, ...(multiPath ? { updates } : {}),
255
+ auth, mockData, newData: update.value, updates,
250
256
  });
251
257
  const fields = {
252
258
  at, durationMs: Date.now() - at, origin: 'batch' as const,
@@ -2,34 +2,54 @@
2
2
  import type { LocalSandbox } from 'pyric/sandbox';
3
3
 
4
4
  import { getOrCreateBackend } from './sandbox/backend-for.js';
5
+ import type { RtdbBackend } from './sandbox/backend.js';
5
6
  import type { JsonValue } from './sandbox/data-tree.js';
7
+ import { TARGET_SYMBOL, targetOf } from './routing.js';
6
8
 
7
9
  export type RtdbRulesJson = { rules: Record<string, unknown> };
8
10
 
9
- /** Replace the active RTDB rules. Pass `null` to restore default allow. */
11
+ /** Target accepting either a LocalSandbox root or a branded Database handle. */
12
+ export type RtdbTarget = LocalSandbox | { [TARGET_SYMBOL]: unknown };
13
+
14
+ function backendOf(target: RtdbTarget): RtdbBackend {
15
+ if (target && typeof target === 'object' && TARGET_SYMBOL in target) {
16
+ return targetOf(target).backend;
17
+ }
18
+ return getOrCreateBackend(target as LocalSandbox);
19
+ }
20
+
21
+ /** Set default access policy when no rules are loaded ('allow' or 'deny'). Internal test/dev harness control. */
22
+ export function setDefaultPolicy(
23
+ target: RtdbTarget,
24
+ policy: 'allow' | 'deny',
25
+ ): void {
26
+ backendOf(target).setDefaultPolicy(policy);
27
+ }
28
+
29
+ /** Replace the active RTDB rules. Pass `null` to restore default deny. */
10
30
  export function setRules(
11
- sandbox: LocalSandbox,
31
+ target: RtdbTarget,
12
32
  rules: RtdbRulesJson | null,
13
33
  ): void {
14
- getOrCreateBackend(sandbox).setRules(rules);
34
+ backendOf(target).setRules(rules);
15
35
  }
16
36
 
17
37
  /** Read the currently active rules as detached JSON. */
18
- export function getActiveRules(sandbox: LocalSandbox): RtdbRulesJson | null {
19
- return getOrCreateBackend(sandbox).getActiveRules();
38
+ export function getActiveRules(target: RtdbTarget): RtdbRulesJson | null {
39
+ return backendOf(target).getActiveRules();
20
40
  }
21
41
 
22
42
  /** Replace RTDB data in bulk without applying security rules. */
23
43
  export function setData(
24
- sandbox: LocalSandbox,
44
+ target: RtdbTarget,
25
45
  data: Record<string, unknown>,
26
46
  ): void {
27
- getOrCreateBackend(sandbox).setData(data as Record<string, JsonValue>);
47
+ backendOf(target).setData(data as Record<string, JsonValue>);
28
48
  }
29
49
 
30
50
  /** Snapshot the complete RTDB tree without applying security rules. */
31
- export function snapshotState(sandbox: LocalSandbox): JsonValue {
32
- return getOrCreateBackend(sandbox).snapshotState();
51
+ export function snapshotState(target: RtdbTarget): JsonValue {
52
+ return backendOf(target).snapshotState();
33
53
  }
34
54
 
35
55
  /**
@@ -7,10 +7,37 @@ import type { Database } from './types.js';
7
7
  // Mirrors `pyric/firestore`'s `sandbox` namespace — explicit
8
8
  // per-package sandbox lifecycle.
9
9
 
10
+ export const DEFAULT_DENY_RTDB_RULES: { rules: Record<string, unknown> } = {
11
+ rules: {
12
+ '.read': false,
13
+ '.write': false,
14
+ },
15
+ };
16
+
17
+ export const DEFAULT_OPEN_RTDB_RULES: { rules: Record<string, unknown> } = {
18
+ rules: {
19
+ '.read': true,
20
+ '.write': true,
21
+ },
22
+ };
23
+
10
24
  export const sandbox = {
25
+ DEFAULT_DENY_RULES: DEFAULT_DENY_RTDB_RULES,
26
+ DEFAULT_OPEN_RULES: DEFAULT_OPEN_RTDB_RULES,
27
+
28
+ /**
29
+ * Set the default access policy when no rules are loaded ('allow' or 'deny').
30
+ * In 'deny' mode (matching production Firebase), all client reads and writes
31
+ * without rules are rejected with PERMISSION_DENIED.
32
+ */
33
+ setDefaultPolicy(db: Database, policy: 'allow' | 'deny'): void {
34
+ const target = targetOf(db);
35
+ target.backend.setDefaultPolicy(policy);
36
+ },
37
+
11
38
  /**
12
39
  * Replace deployed rules. Pass `null` to clear (sandbox returns to
13
- * default-allow). Rules are evaluated through the existing
40
+ * default policy). Rules are evaluated through the existing
14
41
  * RTDB rules simulator — the same engine used by the rules tooling.
15
42
  *
16
43
  * @example
@@ -0,0 +1,167 @@
1
+ /**
2
+ * The shared factory behind pyric's *deferred* Firebase Web SDK subpaths —
3
+ * `pyric/functions`, `pyric/analytics`, `pyric/app-check`,
4
+ * `pyric/firestore/lite`, `pyric/performance` and `pyric/remote-config`.
5
+ *
6
+ * WHY THESE EXIST AT ALL. pyric is a symbol-for-symbol drop-in for the
7
+ * Firebase Web SDK: an app swaps the `firebase` specifier for `pyric` and its
8
+ * module graph must keep loading. A subpath that is simply absent from the
9
+ * package `exports` map fails at RESOLVE time — Node raises
10
+ * `ERR_PACKAGE_PATH_NOT_EXPORTED`, bundlers raise their own variants — long
11
+ * before any pyric code runs. That error names neither pyric nor the reason,
12
+ * so a developer whose app merely *mentions* `firebase/functions` on a code
13
+ * path it never takes sees an unattributable build failure.
14
+ *
15
+ * THE CONTRACT. Resolution and linking succeed; *use* fails loudly.
16
+ * - The subpath resolves, so the module graph loads.
17
+ * - Every symbol the real Firebase entry exports is present as a value, so
18
+ * named-import linking (which is static in ESM — a missing name is a link
19
+ * error, not a runtime one) succeeds and bundlers can tree-shake.
20
+ * - Calling, constructing, or reading a member off one of those values
21
+ * throws {@link PyricDeferredApiError} with a message that names the
22
+ * subpath and points at the conformance matrix.
23
+ *
24
+ * An app that imports but never calls therefore runs unchanged. An app that
25
+ * genuinely depends on a deferred service gets one clear, attributed error at
26
+ * the exact call site instead of a cryptic resolver failure.
27
+ *
28
+ * NOT a mirror. These entries deliberately implement nothing. They are marked
29
+ * deferred in the conformance surface story and must never be counted as
30
+ * implemented surface.
31
+ */
32
+
33
+ /**
34
+ * Compose the single user-facing message every deferred entry raises.
35
+ *
36
+ * @param subpath - The Firebase subpath *without* the `firebase/` prefix, e.g.
37
+ * `functions` or `firestore/lite`.
38
+ */
39
+ function deferredApiMessage(subpath: string): string {
40
+ return (
41
+ `pyric: 'firebase/${subpath}' is not yet mirrored by the local sandbox. ` +
42
+ 'This API is deferred — see the conformance matrix. Imports resolve so ' +
43
+ 'module graphs load; calls fail with this message.'
44
+ );
45
+ }
46
+
47
+ /**
48
+ * The error thrown by every deferred Firebase entry. Carries the subpath and
49
+ * the symbol that was touched so tooling can group these without parsing the
50
+ * message.
51
+ */
52
+ export class PyricDeferredApiError extends Error {
53
+ override readonly name = 'PyricDeferredApiError';
54
+
55
+ /** The Firebase subpath, without the `firebase/` prefix (e.g. `functions`). */
56
+ readonly subpath: string;
57
+
58
+ /** The exported symbol whose use triggered this error (e.g. `getFunctions`). */
59
+ readonly symbol: string;
60
+
61
+ constructor(subpath: string, symbol: string) {
62
+ super(deferredApiMessage(subpath));
63
+ this.subpath = subpath;
64
+ this.symbol = symbol;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * The static type of a deferred export.
70
+ *
71
+ * It is callable, constructible, and indexable so a deferred symbol can stand
72
+ * in for any of the three shapes a Firebase entry exports — a factory function
73
+ * (`getFunctions`), a class (`ReCaptchaV3Provider`), or an enum-like constant
74
+ * object (`HarmCategory`). Every result is `never`, which is assignable to
75
+ * anything, so a consumer's own annotations keep type-checking.
76
+ */
77
+ export interface DeferredApi {
78
+ (...args: readonly unknown[]): never;
79
+ new (...args: readonly unknown[]): never;
80
+ readonly [member: string]: never;
81
+ }
82
+
83
+ /**
84
+ * Property reads that must NOT throw.
85
+ *
86
+ * Bundlers, test runners, `console.log`, promise adoption and React all probe
87
+ * values with these keys as part of ordinary machinery, never as an intent to
88
+ * use the API. Throwing on `then` in particular would turn `await`-ing
89
+ * anything holding a deferred value into this error at the wrong site, and
90
+ * throwing on `$$typeof` would break React's element check. They read as
91
+ * `undefined`, exactly as they would on a real function.
92
+ */
93
+ const INERT_PROPERTIES: ReadonlySet<string> = new Set([
94
+ '$$typeof',
95
+ '__esModule',
96
+ 'constructor',
97
+ 'displayName',
98
+ 'inspect',
99
+ 'length',
100
+ 'name',
101
+ 'nodeType',
102
+ 'prototype',
103
+ 'then',
104
+ 'toJSON',
105
+ 'toString',
106
+ 'valueOf',
107
+ ]);
108
+
109
+ /**
110
+ * Build one deferred export: a function that throws when called or
111
+ * constructed, wrapped in a proxy so member reads (the enum-constant shape)
112
+ * throw the same error.
113
+ */
114
+ function deferredSymbol(subpath: string, symbol: string): DeferredApi {
115
+ const throwing = function deferred(): never {
116
+ throw new PyricDeferredApiError(subpath, symbol);
117
+ };
118
+ Object.defineProperty(throwing, 'name', { value: symbol, configurable: true });
119
+
120
+ return new Proxy(throwing, {
121
+ get(target, property, receiver) {
122
+ // Symbol-keyed reads are always machinery (Symbol.toPrimitive,
123
+ // Symbol.hasInstance, Symbol.toStringTag, the inspect hook, …) — never a
124
+ // user reaching for an API member.
125
+ if (typeof property === 'symbol' || INERT_PROPERTIES.has(property)) {
126
+ return Reflect.get(target, property, receiver);
127
+ }
128
+ throw new PyricDeferredApiError(subpath, `${symbol}.${property}`);
129
+ },
130
+ construct() {
131
+ throw new PyricDeferredApiError(subpath, symbol);
132
+ },
133
+ }) as unknown as DeferredApi;
134
+ }
135
+
136
+ /**
137
+ * The lazily-materialised export bag for one deferred subpath.
138
+ *
139
+ * Each entry module destructures the symbols it needs off this object:
140
+ *
141
+ * ```ts
142
+ * export const { getFunctions, httpsCallable } = deferredEntry('functions');
143
+ * ```
144
+ *
145
+ * Destructuring is what makes the names real ESM exports (and what keeps the
146
+ * generated `.d.ts` honest); the proxy just mints a correctly-attributed stub
147
+ * for whichever name is read.
148
+ *
149
+ * @param subpath - The Firebase subpath, without the `firebase/` prefix.
150
+ */
151
+ export function deferredEntry(subpath: string): Record<string, DeferredApi> {
152
+ const minted = new Map<string, DeferredApi>();
153
+ return new Proxy(Object.create(null) as Record<string, DeferredApi>, {
154
+ get(_target, property) {
155
+ if (typeof property === 'symbol') return undefined as unknown as DeferredApi;
156
+ let api = minted.get(property);
157
+ if (!api) {
158
+ api = deferredSymbol(subpath, property);
159
+ minted.set(property, api);
160
+ }
161
+ return api;
162
+ },
163
+ has() {
164
+ return true;
165
+ },
166
+ });
167
+ }
@@ -115,7 +115,7 @@ export function getFirestore(target?: SandboxContext | Sandbox | FirebaseApp): F
115
115
  return { [TARGET_SYMBOL]: t };
116
116
  }
117
117
  throw new TypeError(
118
- 'pyric/firestore is a sandbox-only mirror. Package resolution must leave firebase/firestore unchanged for production; activate pyric dev or @pyric/cli/register before importing to select the sandbox.',
118
+ 'pyric/firestore is a sandbox-only mirror. Package resolution must leave firebase/firestore unchanged for production; activate pyric sandbox or @pyric/cli/register before importing to select the sandbox.',
119
119
  );
120
120
  }
121
121
 
@@ -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
  */
@@ -41,6 +41,8 @@ export interface FirestoreInspectReport {
41
41
  method: string;
42
42
  auth: unknown;
43
43
  debugMessage?: string;
44
+ rule?: unknown;
45
+ reasons?: string[];
44
46
  }>;
45
47
  recentRequests: Array<{
46
48
  path: string;
@@ -187,11 +189,19 @@ export function inspect(
187
189
  if (typeof event.debugMessage === 'string') {
188
190
  debugMessageVal = event.debugMessage;
189
191
  }
192
+ const ruleVal = (event as any).evaluatedRule ?? (event as any).rule ?? undefined;
193
+ const reasonsVal = Array.isArray((event as any).debugMessages)
194
+ ? (event as any).debugMessages
195
+ : typeof event.debugMessage === 'string'
196
+ ? [event.debugMessage]
197
+ : undefined;
190
198
  return {
191
199
  path: pathVal,
192
200
  method: methodVal,
193
201
  auth: authVal,
194
202
  debugMessage: debugMessageVal,
203
+ rule: ruleVal,
204
+ reasons: reasonsVal,
195
205
  };
196
206
  });
197
207
 
@@ -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;
@@ -14,6 +14,7 @@
14
14
  * upstream counterpart and is additive to the mirrored surface.
15
15
  */
16
16
  import type { FirebaseApp } from '../app/types.js';
17
+ import type { Sandbox, SandboxContext } from '../sandbox/index.js';
17
18
  import {
18
19
  defaultRegistration,
19
20
  deliverToMessaging,
@@ -88,8 +89,8 @@ export interface GetTokenOptions {
88
89
  * Return the FCM `Messaging` instance for the given (or default) app —
89
90
  * window-client plane (upstream component name `messaging`).
90
91
  */
91
- export function getMessaging(app?: FirebaseApp): Messaging {
92
- return resolveMessaging('window', app);
92
+ export function getMessaging(target?: FirebaseApp | Sandbox | SandboxContext): Messaging {
93
+ return resolveMessaging('window', target);
93
94
  }
94
95
 
95
96
  /**
@@ -12,7 +12,8 @@
12
12
  */
13
13
  import type { FirebaseApp } from '../app/types.js';
14
14
  import { defaultClientApp, resolveClientApp } from '../sandbox/internal/client-app.js';
15
- import type { Sandbox } from '../sandbox/types/service.js';
15
+ import type { Sandbox, SandboxContext } from '../sandbox/index.js';
16
+ import { SandboxContextImpl } from '../sandbox/index.js';
16
17
  import { DEFAULT_CLIENT_ID, getMessagingBroker, MessagingBroker } from './broker/index.js';
17
18
  import type { DeliveredPayload, DeliveryResult } from './broker/index.js';
18
19
 
@@ -38,6 +39,22 @@ interface InstanceState {
38
39
  const instanceState = new WeakMap<Messaging, InstanceState>();
39
40
  /** One instance per (app, plane); brokers remain shared by sandbox. */
40
41
  const instancesByApp = new WeakMap<FirebaseApp, Partial<Record<MessagingPlane, Messaging>>>();
42
+ const instancesBySandbox = new WeakMap<Sandbox, Partial<Record<MessagingPlane, Messaging>>>();
43
+
44
+ function isSandboxContext(target: unknown): target is SandboxContext {
45
+ return target instanceof SandboxContextImpl;
46
+ }
47
+
48
+ function isSandbox(target: unknown): target is Sandbox {
49
+ if (target === null || typeof target !== 'object') return false;
50
+ const o = target as Record<string, unknown>;
51
+ return (
52
+ typeof o.withAuth === 'function' &&
53
+ typeof o.onCurrentUserChanged === 'function' &&
54
+ 'currentUser' in o &&
55
+ 'admin' in o
56
+ );
57
+ }
41
58
 
42
59
  // ── Simulated service-worker registrations ──────────────────────────────────
43
60
 
@@ -81,19 +98,39 @@ function resolveDefaultApp(): FirebaseApp {
81
98
 
82
99
  // ── Instance resolution ──────────────────────────────────────────────────────
83
100
 
84
- export function resolveMessaging(plane: MessagingPlane, app?: FirebaseApp): Messaging {
85
- const resolved = app ?? resolveDefaultApp();
86
- const runtime = resolveClientApp(resolved);
101
+ export function resolveMessaging(plane: MessagingPlane, target?: FirebaseApp | Sandbox | SandboxContext): Messaging {
102
+ const resolved = target ?? resolveDefaultApp();
103
+ if (isSandbox(resolved) || isSandboxContext(resolved)) {
104
+ const sandbox = isSandboxContext(resolved) ? resolved.sandbox : resolved;
105
+ const bySandbox = instancesBySandbox.get(sandbox) ?? {};
106
+ instancesBySandbox.set(sandbox, bySandbox);
107
+ const existing = bySandbox[plane];
108
+ if (existing !== undefined) return existing;
109
+
110
+ const broker = getMessagingBroker(sandbox);
111
+ const instance: Messaging = { app: undefined as unknown as FirebaseApp };
112
+ instanceState.set(instance, {
113
+ broker,
114
+ sandbox,
115
+ plane,
116
+ activeRegistrationId: registrationIdOf(DEFAULT_REGISTRATION),
117
+ own: (cleanup) => () => { cleanup(); },
118
+ });
119
+ bySandbox[plane] = instance;
120
+ return instance;
121
+ }
122
+ const app = resolved as FirebaseApp;
123
+ const runtime = resolveClientApp(app);
87
124
  if (!runtime) throw new TypeError('pyric/messaging: unrecognized FirebaseApp handle');
88
125
  const sandbox = runtime.sandbox;
89
- const byApp = instancesByApp.get(resolved) ?? {};
90
- instancesByApp.set(resolved, byApp);
126
+ const byApp = instancesByApp.get(app) ?? {};
127
+ instancesByApp.set(app, byApp);
91
128
  const existing = byApp[plane];
92
129
  if (existing !== undefined) return existing;
93
130
  runtime.assertAlive();
94
131
 
95
132
  const broker = getMessagingBroker(sandbox);
96
- const instance: Messaging = { app: resolved };
133
+ const instance: Messaging = { app };
97
134
  instanceState.set(instance, {
98
135
  broker,
99
136
  sandbox,