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.
- package/README.md +64 -89
- package/dist/ai/blocked.d.ts +52 -0
- package/dist/ai/blocked.d.ts.map +1 -0
- package/dist/ai/blocked.js +83 -0
- package/dist/ai/blocked.js.map +1 -0
- package/dist/ai/broker/broker.d.ts +50 -0
- package/dist/ai/broker/broker.d.ts.map +1 -1
- package/dist/ai/broker/broker.js +157 -21
- package/dist/ai/broker/broker.js.map +1 -1
- package/dist/ai/broker/gemini-engine.d.ts +56 -0
- package/dist/ai/broker/gemini-engine.d.ts.map +1 -0
- package/dist/ai/broker/gemini-engine.js +235 -0
- package/dist/ai/broker/gemini-engine.js.map +1 -0
- package/dist/ai/broker/index.d.ts +2 -1
- package/dist/ai/broker/index.d.ts.map +1 -1
- package/dist/ai/broker/index.js +2 -1
- package/dist/ai/broker/index.js.map +1 -1
- package/dist/ai/broker/openai-engine.d.ts +10 -1
- package/dist/ai/broker/openai-engine.d.ts.map +1 -1
- package/dist/ai/broker/openai-engine.js +26 -4
- package/dist/ai/broker/openai-engine.js.map +1 -1
- package/dist/ai/broker/synthesizer.d.ts +14 -0
- package/dist/ai/broker/synthesizer.d.ts.map +1 -1
- package/dist/ai/broker/synthesizer.js +31 -0
- package/dist/ai/broker/synthesizer.js.map +1 -1
- package/dist/ai/broker/types.d.ts +29 -0
- package/dist/ai/broker/types.d.ts.map +1 -1
- package/dist/ai/instances.js +1 -1
- package/dist/ai/instances.js.map +1 -1
- package/dist/ai/internal.d.ts +5 -0
- package/dist/ai/internal.d.ts.map +1 -1
- package/dist/ai/internal.js +5 -0
- package/dist/ai/internal.js.map +1 -1
- package/dist/ai/response-helpers.d.ts.map +1 -1
- package/dist/ai/response-helpers.js +6 -24
- package/dist/ai/response-helpers.js.map +1 -1
- package/dist/analytics/index.d.ts +40 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +25 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/app-check/index.d.ts +31 -0
- package/dist/app-check/index.d.ts.map +1 -0
- package/dist/app-check/index.js +20 -0
- package/dist/app-check/index.js.map +1 -0
- package/dist/auth/instances.js +1 -1
- package/dist/auth/instances.js.map +1 -1
- package/dist/database/controls.d.ts +1 -1
- package/dist/database/controls.js +1 -1
- package/dist/database/instances.js +1 -1
- package/dist/database/instances.js.map +1 -1
- package/dist/database/sandbox/backend.d.ts +1 -0
- package/dist/database/sandbox/backend.d.ts.map +1 -1
- package/dist/database/sandbox/backend.js +1 -0
- package/dist/database/sandbox/backend.js.map +1 -1
- package/dist/database/sandbox/rules-eval.d.ts +6 -2
- package/dist/database/sandbox/rules-eval.d.ts.map +1 -1
- package/dist/database/sandbox/rules-eval.js +15 -2
- package/dist/database/sandbox/rules-eval.js.map +1 -1
- package/dist/database/sandbox/write-plane.d.ts +1 -0
- package/dist/database/sandbox/write-plane.d.ts.map +1 -1
- package/dist/database/sandbox/write-plane.js +7 -2
- package/dist/database/sandbox/write-plane.js.map +1 -1
- package/dist/database/sandbox-controls.d.ts +12 -5
- package/dist/database/sandbox-controls.d.ts.map +1 -1
- package/dist/database/sandbox-controls.js +20 -9
- package/dist/database/sandbox-controls.js.map +1 -1
- package/dist/database/sandbox-namespace.d.ts +19 -1
- package/dist/database/sandbox-namespace.d.ts.map +1 -1
- package/dist/database/sandbox-namespace.js +24 -1
- package/dist/database/sandbox-namespace.js.map +1 -1
- package/dist/deferred/entry.d.ts +75 -0
- package/dist/deferred/entry.d.ts.map +1 -0
- package/dist/deferred/entry.js +143 -0
- package/dist/deferred/entry.js.map +1 -0
- package/dist/firestore/instances.js +1 -1
- package/dist/firestore/instances.js.map +1 -1
- package/dist/firestore/lite.d.ts +72 -0
- package/dist/firestore/lite.d.ts.map +1 -0
- package/dist/firestore/lite.js +22 -0
- package/dist/firestore/lite.js.map +1 -0
- package/dist/firestore/persistence.d.ts +2 -2
- package/dist/firestore/persistence.js +1 -1
- package/dist/firestore/sandbox-controls.d.ts +2 -0
- package/dist/firestore/sandbox-controls.d.ts.map +1 -1
- package/dist/firestore/sandbox-controls.js +8 -0
- package/dist/firestore/sandbox-controls.js.map +1 -1
- package/dist/functions/index.d.ts +29 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/functions/index.js +20 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/messaging/client.d.ts +2 -1
- package/dist/messaging/client.d.ts.map +1 -1
- package/dist/messaging/client.js +2 -2
- package/dist/messaging/client.js.map +1 -1
- package/dist/messaging/instance.d.ts +2 -2
- package/dist/messaging/instance.d.ts.map +1 -1
- package/dist/messaging/instance.js +40 -6
- package/dist/messaging/instance.js.map +1 -1
- package/dist/messaging/sw.d.ts +2 -1
- package/dist/messaging/sw.d.ts.map +1 -1
- package/dist/messaging/sw.js +2 -2
- package/dist/messaging/sw.js.map +1 -1
- package/dist/performance/index.d.ts +22 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +19 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/remote-config/index.d.ts +40 -0
- package/dist/remote-config/index.d.ts.map +1 -0
- package/dist/remote-config/index.js +25 -0
- package/dist/remote-config/index.js.map +1 -0
- package/dist/rules/grammar/FirestoreValidator.d.ts.map +1 -1
- package/dist/rules/grammar/FirestoreValidator.js +43 -27
- package/dist/rules/grammar/FirestoreValidator.js.map +1 -1
- package/dist/rules/grammar/document-access-count.d.ts +24 -0
- package/dist/rules/grammar/document-access-count.d.ts.map +1 -0
- package/dist/rules/grammar/document-access-count.js +100 -0
- package/dist/rules/grammar/document-access-count.js.map +1 -0
- package/dist/rules/linter/ast-utils.d.ts +2 -6
- package/dist/rules/linter/ast-utils.d.ts.map +1 -1
- package/dist/rules/linter/ast-utils.js +2 -63
- package/dist/rules/linter/ast-utils.js.map +1 -1
- package/dist/rules/linter/hallucinations.d.ts +8 -0
- package/dist/rules/linter/hallucinations.d.ts.map +1 -1
- package/dist/rules/linter/hallucinations.js +121 -31
- package/dist/rules/linter/hallucinations.js.map +1 -1
- package/dist/rules/linter/linter.d.ts +10 -1
- package/dist/rules/linter/linter.d.ts.map +1 -1
- package/dist/rules/linter/linter.js +23 -9
- package/dist/rules/linter/linter.js.map +1 -1
- package/dist/rules/rtdb/grammar/RtdbExpr.ohm +5 -2
- package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.d.ts +1 -1
- package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.d.ts.map +1 -1
- package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.js +5 -2
- package/dist/rules/rtdb/grammar/RtdbExpr.ohm.generated.js.map +1 -1
- package/dist/rules/rtdb/grammar/simulator.d.ts.map +1 -1
- package/dist/rules/rtdb/grammar/simulator.js +61 -15
- package/dist/rules/rtdb/grammar/simulator.js.map +1 -1
- package/dist/rules/rtdb/simulation/handler.d.ts.map +1 -1
- package/dist/rules/rtdb/simulation/handler.js +96 -35
- package/dist/rules/rtdb/simulation/handler.js.map +1 -1
- package/dist/rules/simulator/document-lookups.d.ts +4 -0
- package/dist/rules/simulator/document-lookups.d.ts.map +1 -1
- package/dist/rules/simulator/document-lookups.js +42 -3
- package/dist/rules/simulator/document-lookups.js.map +1 -1
- package/dist/rules/simulator/eval-error.d.ts +11 -0
- package/dist/rules/simulator/eval-error.d.ts.map +1 -1
- package/dist/rules/simulator/eval-error.js +14 -0
- package/dist/rules/simulator/eval-error.js.map +1 -1
- package/dist/rules/simulator/evaluation-builtins.d.ts.map +1 -1
- package/dist/rules/simulator/evaluation-builtins.js +33 -8
- package/dist/rules/simulator/evaluation-builtins.js.map +1 -1
- package/dist/rules/simulator/evaluation-context.d.ts +12 -0
- package/dist/rules/simulator/evaluation-context.d.ts.map +1 -1
- package/dist/rules/simulator/evaluator.d.ts +1 -1
- package/dist/rules/simulator/evaluator.d.ts.map +1 -1
- package/dist/rules/simulator/evaluator.js +16 -4
- package/dist/rules/simulator/evaluator.js.map +1 -1
- package/dist/rules/simulator/handler.d.ts.map +1 -1
- package/dist/rules/simulator/handler.js +52 -5
- package/dist/rules/simulator/handler.js.map +1 -1
- package/dist/rules/simulator/lookup-budget.d.ts +37 -0
- package/dist/rules/simulator/lookup-budget.d.ts.map +1 -0
- package/dist/rules/simulator/lookup-budget.js +96 -0
- package/dist/rules/simulator/lookup-budget.js.map +1 -0
- package/dist/rules/stdlib-modules.d.ts +7 -0
- package/dist/rules/stdlib-modules.d.ts.map +1 -1
- package/dist/rules/stdlib-modules.js +11 -11
- package/dist/rules/stdlib-modules.js.map +1 -1
- package/dist/sandbox/admin-firestore/get-firestore.d.ts +6 -0
- package/dist/sandbox/admin-firestore/get-firestore.d.ts.map +1 -1
- package/dist/sandbox/admin-firestore/get-firestore.js +22 -3
- package/dist/sandbox/admin-firestore/get-firestore.js.map +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +1 -1
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/persistence/types.d.ts +1 -1
- package/dist/sandbox/remote.d.ts +2 -2
- package/dist/sandbox/sandbox-context.d.ts +8 -1
- package/dist/sandbox/sandbox-context.d.ts.map +1 -1
- package/dist/sandbox/sandbox-context.js +59 -8
- package/dist/sandbox/sandbox-context.js.map +1 -1
- package/dist/sandbox/types/auth-state.d.ts +1 -0
- package/dist/sandbox/types/auth-state.d.ts.map +1 -1
- package/dist/sandbox/types/events.d.ts +2 -1
- package/dist/sandbox/types/events.d.ts.map +1 -1
- package/dist/sandbox/types/operation.d.ts +1 -0
- package/dist/sandbox/types/operation.d.ts.map +1 -1
- package/dist/storage/enforce.d.ts +20 -1
- package/dist/storage/enforce.d.ts.map +1 -1
- package/dist/storage/enforce.js +47 -14
- package/dist/storage/enforce.js.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/list.js +1 -1
- package/dist/storage/list.js.map +1 -1
- package/dist/storage/persistence.d.ts.map +1 -1
- package/dist/storage/persistence.js +66 -14
- package/dist/storage/persistence.js.map +1 -1
- package/dist/storage/sandbox/rules-evaluation-error.d.ts +25 -0
- package/dist/storage/sandbox/rules-evaluation-error.d.ts.map +1 -1
- package/dist/storage/sandbox/rules-evaluation-error.js +29 -0
- package/dist/storage/sandbox/rules-evaluation-error.js.map +1 -1
- package/dist/storage/sandbox/rules-evaluator.d.ts +10 -5
- package/dist/storage/sandbox/rules-evaluator.d.ts.map +1 -1
- package/dist/storage/sandbox/rules-evaluator.js +105 -33
- package/dist/storage/sandbox/rules-evaluator.js.map +1 -1
- package/dist/storage/sandbox/rules-methods.d.ts.map +1 -1
- package/dist/storage/sandbox/rules-methods.js +17 -7
- package/dist/storage/sandbox/rules-methods.js.map +1 -1
- package/dist/storage/service.d.ts +23 -2
- package/dist/storage/service.d.ts.map +1 -1
- package/dist/storage/service.js +37 -26
- package/dist/storage/service.js.map +1 -1
- package/package.json +42 -2
- package/src/ai/blocked.ts +97 -0
- package/src/ai/broker/broker.ts +153 -22
- package/src/ai/broker/gemini-engine.ts +311 -0
- package/src/ai/broker/index.ts +2 -0
- package/src/ai/broker/openai-engine.ts +31 -3
- package/src/ai/broker/synthesizer.ts +34 -0
- package/src/ai/broker/types.ts +27 -1
- package/src/ai/instances.ts +1 -1
- package/src/ai/internal.ts +5 -0
- package/src/ai/response-helpers.ts +6 -25
- package/src/analytics/index.ts +53 -0
- package/src/app/dispatch.test.ts +3 -1
- package/src/app-check/index.ts +41 -0
- package/src/auth/instances.ts +1 -1
- package/src/database/controls.ts +1 -1
- package/src/database/instances.ts +1 -1
- package/src/database/sandbox/backend.ts +1 -0
- package/src/database/sandbox/rules-eval.ts +18 -2
- package/src/database/sandbox/write-plane.ts +8 -2
- package/src/database/sandbox-controls.ts +29 -9
- package/src/database/sandbox-namespace.ts +28 -1
- package/src/deferred/entry.ts +167 -0
- package/src/firestore/instances.ts +1 -1
- package/src/firestore/lite.ts +91 -0
- package/src/firestore/persistence.ts +2 -2
- package/src/firestore/sandbox-controls.ts +10 -0
- package/src/functions/index.ts +38 -0
- package/src/messaging/client.ts +3 -2
- package/src/messaging/instance.ts +44 -7
- package/src/messaging/sw.ts +3 -2
- package/src/performance/index.ts +31 -0
- package/src/remote-config/index.ts +53 -0
- package/src/rules/grammar/FirestoreValidator.ts +44 -28
- package/src/rules/grammar/document-access-count.ts +75 -0
- package/src/rules/linter/ast-utils.ts +2 -36
- package/src/rules/linter/hallucinations.ts +123 -25
- package/src/rules/linter/linter.ts +32 -10
- package/src/rules/rtdb/grammar/RtdbExpr.ohm +5 -2
- package/src/rules/rtdb/grammar/RtdbExpr.ohm.generated.ts +5 -2
- package/src/rules/rtdb/grammar/simulator.ts +41 -15
- package/src/rules/rtdb/simulation/handler.ts +149 -46
- package/src/rules/simulator/document-lookups.ts +46 -3
- package/src/rules/simulator/eval-error.ts +16 -0
- package/src/rules/simulator/evaluation-builtins.ts +35 -8
- package/src/rules/simulator/evaluation-context.ts +12 -0
- package/src/rules/simulator/evaluator.ts +14 -3
- package/src/rules/simulator/handler.ts +68 -4
- package/src/rules/simulator/lookup-budget.ts +101 -0
- package/src/rules/stdlib-modules.ts +19 -11
- package/src/sandbox/admin-firestore/get-firestore.ts +19 -3
- package/src/sandbox/index.ts +5 -1
- package/src/sandbox/persistence/types.ts +1 -1
- package/src/sandbox/remote.ts +2 -2
- package/src/sandbox/sandbox-context.ts +69 -9
- package/src/sandbox/types/auth-state.ts +1 -1
- package/src/sandbox/types/events.ts +2 -1
- package/src/sandbox/types/operation.ts +1 -1
- package/src/storage/enforce.ts +53 -14
- package/src/storage/index.ts +1 -1
- package/src/storage/list.ts +1 -1
- package/src/storage/persistence.ts +74 -16
- package/src/storage/sandbox/rules-evaluation-error.ts +32 -0
- package/src/storage/sandbox/rules-evaluator.ts +109 -32
- package/src/storage/sandbox/rules-methods.ts +21 -7
- package/src/storage/service.ts +93 -27
- package/README.md.orig +0 -53
package/src/database/controls.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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-
|
|
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,
|
|
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,
|
|
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
|
-
/**
|
|
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
|
-
|
|
31
|
+
target: RtdbTarget,
|
|
12
32
|
rules: RtdbRulesJson | null,
|
|
13
33
|
): void {
|
|
14
|
-
|
|
34
|
+
backendOf(target).setRules(rules);
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
/** Read the currently active rules as detached JSON. */
|
|
18
|
-
export function getActiveRules(
|
|
19
|
-
return
|
|
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
|
-
|
|
44
|
+
target: RtdbTarget,
|
|
25
45
|
data: Record<string, unknown>,
|
|
26
46
|
): void {
|
|
27
|
-
|
|
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(
|
|
32
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/src/messaging/client.ts
CHANGED
|
@@ -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(
|
|
92
|
-
return resolveMessaging('window',
|
|
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/
|
|
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,
|
|
85
|
-
const resolved =
|
|
86
|
-
|
|
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(
|
|
90
|
-
instancesByApp.set(
|
|
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
|
|
133
|
+
const instance: Messaging = { app };
|
|
97
134
|
instanceState.set(instance, {
|
|
98
135
|
broker,
|
|
99
136
|
sandbox,
|