@typicalday/firegraph 0.11.2 → 0.13.0
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 +355 -78
- package/dist/backend-DuvHGgK1.d.cts +1897 -0
- package/dist/backend-DuvHGgK1.d.ts +1897 -0
- package/dist/backend.cjs +365 -5
- package/dist/backend.cjs.map +1 -1
- package/dist/backend.d.cts +25 -5
- package/dist/backend.d.ts +25 -5
- package/dist/backend.js +209 -7
- package/dist/backend.js.map +1 -1
- package/dist/chunk-2DHMNTV6.js +16 -0
- package/dist/chunk-2DHMNTV6.js.map +1 -0
- package/dist/chunk-4MMQ5W74.js +288 -0
- package/dist/chunk-4MMQ5W74.js.map +1 -0
- package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
- package/dist/chunk-C2QMD7RY.js.map +1 -0
- package/dist/chunk-D4J7Z4FE.js +67 -0
- package/dist/chunk-D4J7Z4FE.js.map +1 -0
- package/dist/chunk-EQJUUVFG.js +14 -0
- package/dist/chunk-EQJUUVFG.js.map +1 -0
- package/dist/chunk-N5HFDWQX.js +23 -0
- package/dist/chunk-N5HFDWQX.js.map +1 -0
- package/dist/chunk-PAD7WFFU.js +573 -0
- package/dist/chunk-PAD7WFFU.js.map +1 -0
- package/dist/chunk-TK64DNVK.js +256 -0
- package/dist/chunk-TK64DNVK.js.map +1 -0
- package/dist/{chunk-NJSOD64C.js → chunk-WRTFC5NG.js} +438 -30
- package/dist/chunk-WRTFC5NG.js.map +1 -0
- package/dist/client-BKi3vk0Q.d.ts +34 -0
- package/dist/client-BrsaXtDV.d.cts +34 -0
- package/dist/cloudflare/index.cjs +1386 -74
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +217 -13
- package/dist/cloudflare/index.d.ts +217 -13
- package/dist/cloudflare/index.js +639 -180
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/errors-BRc3I_eH.d.cts +73 -0
- package/dist/errors-BRc3I_eH.d.ts +73 -0
- package/dist/firestore-enterprise/index.cjs +3877 -0
- package/dist/firestore-enterprise/index.cjs.map +1 -0
- package/dist/firestore-enterprise/index.d.cts +141 -0
- package/dist/firestore-enterprise/index.d.ts +141 -0
- package/dist/firestore-enterprise/index.js +985 -0
- package/dist/firestore-enterprise/index.js.map +1 -0
- package/dist/firestore-standard/index.cjs +3117 -0
- package/dist/firestore-standard/index.cjs.map +1 -0
- package/dist/firestore-standard/index.d.cts +49 -0
- package/dist/firestore-standard/index.d.ts +49 -0
- package/dist/firestore-standard/index.js +283 -0
- package/dist/firestore-standard/index.js.map +1 -0
- package/dist/index.cjs +809 -534
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -100
- package/dist/index.d.ts +24 -100
- package/dist/index.js +184 -531
- package/dist/index.js.map +1 -1
- package/dist/registry-Bc7h6WTM.d.cts +64 -0
- package/dist/registry-C2KUPVZj.d.ts +64 -0
- package/dist/{scope-path-B1G3YiA7.d.ts → scope-path-CROFZGr9.d.cts} +1 -56
- package/dist/{scope-path-B1G3YiA7.d.cts → scope-path-CROFZGr9.d.ts} +1 -56
- package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
- package/dist/sqlite/index.cjs +3631 -0
- package/dist/sqlite/index.cjs.map +1 -0
- package/dist/sqlite/index.d.cts +111 -0
- package/dist/sqlite/index.d.ts +111 -0
- package/dist/sqlite/index.js +1164 -0
- package/dist/sqlite/index.js.map +1 -0
- package/package.json +33 -3
- package/dist/backend-U-MLShlg.d.ts +0 -97
- package/dist/backend-np4gEVhB.d.cts +0 -97
- package/dist/chunk-5753Y42M.js.map +0 -1
- package/dist/chunk-NJSOD64C.js.map +0 -1
- package/dist/chunk-R7CRGYY4.js +0 -94
- package/dist/chunk-R7CRGYY4.js.map +0 -1
- package/dist/types-BGWxcpI_.d.cts +0 -736
- package/dist/types-BGWxcpI_.d.ts +0 -736
- /package/dist/{serialization-ZZ7RSDRX.js.map → serialization-OE2PFZMY.js.map} +0 -0
package/dist/backend.cjs
CHANGED
|
@@ -20,10 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/backend.ts
|
|
21
21
|
var backend_exports = {};
|
|
22
22
|
__export(backend_exports, {
|
|
23
|
+
CapabilityNotSupportedError: () => CapabilityNotSupportedError,
|
|
23
24
|
CrossBackendTransactionError: () => CrossBackendTransactionError,
|
|
25
|
+
DELETE_FIELD: () => DELETE_FIELD,
|
|
24
26
|
appendStorageScope: () => appendStorageScope,
|
|
27
|
+
createCapabilities: () => createCapabilities,
|
|
25
28
|
createRoutingBackend: () => createRoutingBackend,
|
|
29
|
+
deleteField: () => deleteField,
|
|
30
|
+
flattenPatch: () => flattenPatch,
|
|
31
|
+
intersectCapabilities: () => intersectCapabilities,
|
|
26
32
|
isAncestorScopeUid: () => isAncestorScopeUid,
|
|
33
|
+
isDeleteSentinel: () => isDeleteSentinel,
|
|
27
34
|
parseStorageScope: () => parseStorageScope,
|
|
28
35
|
resolveAncestorScope: () => resolveAncestorScope
|
|
29
36
|
});
|
|
@@ -43,6 +50,34 @@ var CrossBackendTransactionError = class extends FiregraphError {
|
|
|
43
50
|
this.name = "CrossBackendTransactionError";
|
|
44
51
|
}
|
|
45
52
|
};
|
|
53
|
+
var CapabilityNotSupportedError = class extends FiregraphError {
|
|
54
|
+
constructor(capability, backendDescription) {
|
|
55
|
+
super(
|
|
56
|
+
`Capability "${capability}" is not supported by ${backendDescription}.`,
|
|
57
|
+
"CAPABILITY_NOT_SUPPORTED"
|
|
58
|
+
);
|
|
59
|
+
this.capability = capability;
|
|
60
|
+
this.name = "CapabilityNotSupportedError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/internal/backend.ts
|
|
65
|
+
function createCapabilities(caps) {
|
|
66
|
+
return {
|
|
67
|
+
has: (capability) => caps.has(capability),
|
|
68
|
+
values: () => caps.values()
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function intersectCapabilities(parts) {
|
|
72
|
+
if (parts.length === 0) return createCapabilities(/* @__PURE__ */ new Set());
|
|
73
|
+
const sets = parts.map((p) => new Set(p.values()));
|
|
74
|
+
const [first, ...rest] = sets;
|
|
75
|
+
const intersection = /* @__PURE__ */ new Set();
|
|
76
|
+
for (const c of first) {
|
|
77
|
+
if (rest.every((s) => s.has(c))) intersection.add(c);
|
|
78
|
+
}
|
|
79
|
+
return createCapabilities(intersection);
|
|
80
|
+
}
|
|
46
81
|
|
|
47
82
|
// src/internal/routing-backend.ts
|
|
48
83
|
function assertValidSubgraphArgs(parentNodeUid, name) {
|
|
@@ -60,16 +95,70 @@ function assertValidSubgraphArgs(parentNodeUid, name) {
|
|
|
60
95
|
}
|
|
61
96
|
}
|
|
62
97
|
var RoutingStorageBackend = class _RoutingStorageBackend {
|
|
63
|
-
constructor(base, options, storageScope, logicalScopePath) {
|
|
98
|
+
constructor(base, options, storageScope, logicalScopePath, capabilities) {
|
|
64
99
|
this.base = base;
|
|
65
100
|
this.options = options;
|
|
66
101
|
this.collectionPath = base.collectionPath;
|
|
67
102
|
this.scopePath = logicalScopePath;
|
|
68
103
|
this.storageScope = storageScope;
|
|
104
|
+
if (capabilities) {
|
|
105
|
+
this.capabilities = capabilities;
|
|
106
|
+
} else if (options.routedCapabilities && options.routedCapabilities.length > 0) {
|
|
107
|
+
this.capabilities = intersectCapabilities([base.capabilities, ...options.routedCapabilities]);
|
|
108
|
+
} else {
|
|
109
|
+
this.capabilities = base.capabilities;
|
|
110
|
+
}
|
|
69
111
|
if (base.findEdgesGlobal) {
|
|
70
112
|
this.findEdgesGlobal = (params, collectionName) => base.findEdgesGlobal(params, collectionName);
|
|
71
113
|
}
|
|
114
|
+
if (base.aggregate && this.capabilities.has("query.aggregate")) {
|
|
115
|
+
this.aggregate = (spec, filters) => base.aggregate(spec, filters);
|
|
116
|
+
}
|
|
117
|
+
if (base.bulkDelete && this.capabilities.has("query.dml")) {
|
|
118
|
+
this.bulkDelete = (filters, options2) => base.bulkDelete(filters, options2);
|
|
119
|
+
}
|
|
120
|
+
if (base.bulkUpdate && this.capabilities.has("query.dml")) {
|
|
121
|
+
this.bulkUpdate = (filters, patch, options2) => base.bulkUpdate(filters, patch, options2);
|
|
122
|
+
}
|
|
123
|
+
if (base.expand && this.capabilities.has("query.join")) {
|
|
124
|
+
this.expand = (params) => base.expand(params);
|
|
125
|
+
}
|
|
126
|
+
if (base.runEngineTraversal && this.capabilities.has("traversal.serverSide")) {
|
|
127
|
+
this.runEngineTraversal = (params) => base.runEngineTraversal(params);
|
|
128
|
+
}
|
|
129
|
+
if (base.findEdgesProjected && this.capabilities.has("query.select")) {
|
|
130
|
+
this.findEdgesProjected = (select, filters, options2) => base.findEdgesProjected(select, filters, options2);
|
|
131
|
+
}
|
|
132
|
+
if (base.findNearest && this.capabilities.has("search.vector")) {
|
|
133
|
+
this.findNearest = (params) => base.findNearest(params);
|
|
134
|
+
}
|
|
135
|
+
if (base.fullTextSearch && this.capabilities.has("search.fullText")) {
|
|
136
|
+
this.fullTextSearch = (params) => base.fullTextSearch(params);
|
|
137
|
+
}
|
|
138
|
+
if (base.geoSearch && this.capabilities.has("search.geo")) {
|
|
139
|
+
this.geoSearch = (params) => base.geoSearch(params);
|
|
140
|
+
}
|
|
72
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Effective capability set for this wrapper.
|
|
144
|
+
*
|
|
145
|
+
* - **Root wrapper** (`createRoutingBackend(...)` direct return): if the
|
|
146
|
+
* caller supplied `options.routedCapabilities`, the cap set is the
|
|
147
|
+
* intersection of `base.capabilities` and every set in that list. If
|
|
148
|
+
* not, the cap set mirrors `base.capabilities` (suitable when routes
|
|
149
|
+
* target peers of the same backend type — no capability differential
|
|
150
|
+
* to honour).
|
|
151
|
+
* - **Child wrapper** (returned from `subgraph()`): the cap set mirrors
|
|
152
|
+
* the *wrapped* backend (either `base.subgraph(...)` or the backend
|
|
153
|
+
* returned by `route()`). Each child handle reflects what's safe to
|
|
154
|
+
* call against the specific backend it targets — invariant 3 holds
|
|
155
|
+
* per-instance.
|
|
156
|
+
*
|
|
157
|
+
* This satisfies invariant 5 (intersection across mixed-backend graphs)
|
|
158
|
+
* when callers opt in, and falls back to a non-lying mirror when they
|
|
159
|
+
* don't.
|
|
160
|
+
*/
|
|
161
|
+
capabilities;
|
|
73
162
|
collectionPath;
|
|
74
163
|
/**
|
|
75
164
|
* Logical (names-only) scope path for *this* wrapper. Tracked
|
|
@@ -96,6 +185,125 @@ var RoutingStorageBackend = class _RoutingStorageBackend {
|
|
|
96
185
|
* shape in the `StorageBackend` interface.
|
|
97
186
|
*/
|
|
98
187
|
findEdgesGlobal;
|
|
188
|
+
/**
|
|
189
|
+
* Same conditional-install pattern as `findEdgesGlobal`. The router's
|
|
190
|
+
* declared capability set is mirrored from the base (or intersected with
|
|
191
|
+
* the user's `routedCapabilities`) — if `query.aggregate` is in that
|
|
192
|
+
* set, the underlying method must be present, otherwise `client.aggregate()`
|
|
193
|
+
* would resolve `UNSUPPORTED_OPERATION` despite the cap claim. This
|
|
194
|
+
* ensures the "declared capability ⇒ method exists" invariant holds
|
|
195
|
+
* through routing wrappers (Phase 4 audit C1).
|
|
196
|
+
*/
|
|
197
|
+
aggregate;
|
|
198
|
+
/**
|
|
199
|
+
* DML pass-throughs. Same conditional-install pattern as `aggregate`:
|
|
200
|
+
* gated on BOTH the base method's existence AND `this.capabilities`
|
|
201
|
+
* advertising `query.dml`. If `routedCapabilities` intersected
|
|
202
|
+
* `query.dml` away (e.g. one routed peer is Firestore Standard which
|
|
203
|
+
* has no pipeline-DML support), the methods are *not* installed even
|
|
204
|
+
* though `base.bulkDelete` exists — otherwise the router would silently
|
|
205
|
+
* outperform what the declared cap set promises across hops. This
|
|
206
|
+
* preserves the "declared capability ⇒ method exists" invariant in
|
|
207
|
+
* both directions (Phase 5).
|
|
208
|
+
*/
|
|
209
|
+
bulkDelete;
|
|
210
|
+
bulkUpdate;
|
|
211
|
+
/**
|
|
212
|
+
* Multi-source fan-out pass-through. Same conditional-install pattern as
|
|
213
|
+
* `aggregate` and the bulk-DML methods: gated on BOTH the base method's
|
|
214
|
+
* existence AND `this.capabilities` advertising `query.join`. If
|
|
215
|
+
* `routedCapabilities` intersected `query.join` away (e.g. one routed peer
|
|
216
|
+
* is Firestore Standard which has no pipeline-join support), the method is
|
|
217
|
+
* not installed even though `base.expand` exists. This preserves the
|
|
218
|
+
* "declared capability ⇒ method exists" invariant in both directions.
|
|
219
|
+
*
|
|
220
|
+
* Like `aggregate` and bulk DML, `expand` runs against the base backend
|
|
221
|
+
* only — it cannot fan out across routed children, since each routed
|
|
222
|
+
* subgraph is a separate physical store. Cross-graph hops (which resolve
|
|
223
|
+
* to per-source subgraph readers) are therefore never dispatched through
|
|
224
|
+
* `expand` by `traverse.ts`; the same constraint applies here, naturally.
|
|
225
|
+
*/
|
|
226
|
+
expand;
|
|
227
|
+
/**
|
|
228
|
+
* Engine-level multi-hop traversal pass-through. Same conditional-install
|
|
229
|
+
* pattern as `aggregate`, bulk DML, and `expand`: gated on BOTH the base
|
|
230
|
+
* method's existence AND `this.capabilities` advertising
|
|
231
|
+
* `traversal.serverSide`. If `routedCapabilities` intersected
|
|
232
|
+
* `traversal.serverSide` away (e.g. one routed peer is a SQLite-shaped
|
|
233
|
+
* backend that has no nested-pipeline path), the method is not installed
|
|
234
|
+
* even though `base.runEngineTraversal` exists. This preserves the
|
|
235
|
+
* "declared capability ⇒ method exists" invariant in both directions.
|
|
236
|
+
*
|
|
237
|
+
* Like the other extensions, engine traversal runs against the base
|
|
238
|
+
* backend only — a routed child's own `runEngineTraversal` is reached
|
|
239
|
+
* through `.subgraph().runEngineTraversal()` against the routed handle.
|
|
240
|
+
* Cross-graph hops never reach this method anyway: the traversal
|
|
241
|
+
* compiler in `firestore-traverse-compiler.ts` rejects specs whose
|
|
242
|
+
* hops carry `targetGraph`, falling back to the per-hop loop. Routed
|
|
243
|
+
* children are physically distinct backends, so even an "in-graph"
|
|
244
|
+
* traversal across a routed-child boundary is structurally a
|
|
245
|
+
* cross-backend hop and never compiles for engine dispatch.
|
|
246
|
+
*/
|
|
247
|
+
runEngineTraversal;
|
|
248
|
+
/**
|
|
249
|
+
* Server-side projection pass-through. Same conditional-install pattern as
|
|
250
|
+
* `aggregate`, bulk DML, and `expand`: gated on BOTH the base method's
|
|
251
|
+
* existence AND `this.capabilities` advertising `query.select`. If
|
|
252
|
+
* `routedCapabilities` intersected `query.select` away (e.g. one routed
|
|
253
|
+
* peer doesn't implement projection), the method is not installed even
|
|
254
|
+
* though `base.findEdgesProjected` exists. This preserves the "declared
|
|
255
|
+
* capability ⇒ method exists" invariant in both directions.
|
|
256
|
+
*
|
|
257
|
+
* Like `aggregate` and `expand`, projection runs against the base backend
|
|
258
|
+
* only — a routed child's own projection is reached through
|
|
259
|
+
* `.subgraph().findEdgesProjected()` against the routed handle.
|
|
260
|
+
*/
|
|
261
|
+
findEdgesProjected;
|
|
262
|
+
/**
|
|
263
|
+
* Vector / nearest-neighbour pass-through. Same conditional-install
|
|
264
|
+
* pattern as `aggregate`, bulk DML, `expand`, and `findEdgesProjected`:
|
|
265
|
+
* gated on BOTH the base method's existence AND `this.capabilities`
|
|
266
|
+
* advertising `search.vector`. If `routedCapabilities` intersected
|
|
267
|
+
* `search.vector` away (e.g. one routed peer is a SQLite-shaped backend
|
|
268
|
+
* that has no native ANN index), the method is not installed even
|
|
269
|
+
* though `base.findNearest` exists. This preserves the "declared
|
|
270
|
+
* capability ⇒ method exists" invariant in both directions.
|
|
271
|
+
*
|
|
272
|
+
* Like the other extensions, vector search runs against the base
|
|
273
|
+
* backend only — a routed child's own `findNearest` is reached through
|
|
274
|
+
* `.subgraph().findNearest()` against the routed handle.
|
|
275
|
+
*/
|
|
276
|
+
findNearest;
|
|
277
|
+
/**
|
|
278
|
+
* Full-text search pass-through. Same conditional-install pattern as
|
|
279
|
+
* `findNearest`: gated on BOTH the base method's existence AND
|
|
280
|
+
* `this.capabilities` advertising `search.fullText`. If
|
|
281
|
+
* `routedCapabilities` intersected `search.fullText` away (e.g. one
|
|
282
|
+
* routed peer is Firestore Standard or a SQLite-shaped backend that
|
|
283
|
+
* has no native FTS index), the method is not installed even though
|
|
284
|
+
* `base.fullTextSearch` exists. This preserves the "declared
|
|
285
|
+
* capability ⇒ method exists" invariant in both directions.
|
|
286
|
+
*
|
|
287
|
+
* Like the other extensions, FTS runs against the base backend only —
|
|
288
|
+
* a routed child's own `fullTextSearch` is reached through
|
|
289
|
+
* `.subgraph().fullTextSearch()` against the routed handle.
|
|
290
|
+
*/
|
|
291
|
+
fullTextSearch;
|
|
292
|
+
/**
|
|
293
|
+
* Geospatial distance pass-through. Same conditional-install pattern
|
|
294
|
+
* as `fullTextSearch`: gated on BOTH the base method's existence AND
|
|
295
|
+
* `this.capabilities` advertising `search.geo`. If `routedCapabilities`
|
|
296
|
+
* intersected `search.geo` away (e.g. one routed peer is Firestore
|
|
297
|
+
* Standard or a SQLite-shaped backend that has no native geo index),
|
|
298
|
+
* the method is not installed even though `base.geoSearch` exists.
|
|
299
|
+
* This preserves the "declared capability ⇒ method exists" invariant
|
|
300
|
+
* in both directions.
|
|
301
|
+
*
|
|
302
|
+
* Like the other extensions, geo search runs against the base backend
|
|
303
|
+
* only — a routed child's own `geoSearch` is reached through
|
|
304
|
+
* `.subgraph().geoSearch()` against the routed handle.
|
|
305
|
+
*/
|
|
306
|
+
geoSearch;
|
|
99
307
|
// --- Pass-through reads ---
|
|
100
308
|
getDoc(docId) {
|
|
101
309
|
return this.base.getDoc(docId);
|
|
@@ -104,8 +312,8 @@ var RoutingStorageBackend = class _RoutingStorageBackend {
|
|
|
104
312
|
return this.base.query(filters, options);
|
|
105
313
|
}
|
|
106
314
|
// --- Pass-through writes ---
|
|
107
|
-
setDoc(docId, record) {
|
|
108
|
-
return this.base.setDoc(docId, record);
|
|
315
|
+
setDoc(docId, record, mode) {
|
|
316
|
+
return this.base.setDoc(docId, record, mode);
|
|
109
317
|
}
|
|
110
318
|
updateDoc(docId, update) {
|
|
111
319
|
return this.base.updateDoc(docId, update);
|
|
@@ -132,10 +340,22 @@ var RoutingStorageBackend = class _RoutingStorageBackend {
|
|
|
132
340
|
storageScope: childStorageScope
|
|
133
341
|
});
|
|
134
342
|
if (routed) {
|
|
135
|
-
return new _RoutingStorageBackend(
|
|
343
|
+
return new _RoutingStorageBackend(
|
|
344
|
+
routed,
|
|
345
|
+
this.options,
|
|
346
|
+
childStorageScope,
|
|
347
|
+
childScopePath,
|
|
348
|
+
routed.capabilities
|
|
349
|
+
);
|
|
136
350
|
}
|
|
137
351
|
const childBase = this.base.subgraph(parentNodeUid, name);
|
|
138
|
-
return new _RoutingStorageBackend(
|
|
352
|
+
return new _RoutingStorageBackend(
|
|
353
|
+
childBase,
|
|
354
|
+
this.options,
|
|
355
|
+
childStorageScope,
|
|
356
|
+
childScopePath,
|
|
357
|
+
childBase.capabilities
|
|
358
|
+
);
|
|
139
359
|
}
|
|
140
360
|
// --- Bulk operations: delegate, but cascade is base-scope only ---
|
|
141
361
|
removeNodeCascade(uid, reader, options) {
|
|
@@ -161,6 +381,139 @@ function createRoutingBackend(base, options) {
|
|
|
161
381
|
return new RoutingStorageBackend(base, options, "", base.scopePath);
|
|
162
382
|
}
|
|
163
383
|
|
|
384
|
+
// src/internal/serialization-tag.ts
|
|
385
|
+
var SERIALIZATION_TAG = "__firegraph_ser__";
|
|
386
|
+
var KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
|
|
387
|
+
function isTaggedValue(value) {
|
|
388
|
+
if (value === null || typeof value !== "object") return false;
|
|
389
|
+
const tag = value[SERIALIZATION_TAG];
|
|
390
|
+
return typeof tag === "string" && KNOWN_TYPES.has(tag);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/internal/write-plan.ts
|
|
394
|
+
var DELETE_FIELD = /* @__PURE__ */ Symbol.for("firegraph.deleteField");
|
|
395
|
+
function deleteField() {
|
|
396
|
+
return DELETE_FIELD;
|
|
397
|
+
}
|
|
398
|
+
function isDeleteSentinel(value) {
|
|
399
|
+
return value === DELETE_FIELD;
|
|
400
|
+
}
|
|
401
|
+
var FIRESTORE_TERMINAL_CTOR = /* @__PURE__ */ new Set([
|
|
402
|
+
"Timestamp",
|
|
403
|
+
"GeoPoint",
|
|
404
|
+
"VectorValue",
|
|
405
|
+
"DocumentReference",
|
|
406
|
+
"FieldValue",
|
|
407
|
+
"NumericIncrementTransform",
|
|
408
|
+
"ArrayUnionTransform",
|
|
409
|
+
"ArrayRemoveTransform",
|
|
410
|
+
"ServerTimestampTransform",
|
|
411
|
+
"DeleteTransform"
|
|
412
|
+
]);
|
|
413
|
+
function isTerminalValue(value) {
|
|
414
|
+
if (value === null) return true;
|
|
415
|
+
const t = typeof value;
|
|
416
|
+
if (t !== "object") return true;
|
|
417
|
+
if (Array.isArray(value)) return true;
|
|
418
|
+
if (isTaggedValue(value)) return true;
|
|
419
|
+
const proto = Object.getPrototypeOf(value);
|
|
420
|
+
if (proto === null || proto === Object.prototype) return false;
|
|
421
|
+
const ctor = value.constructor;
|
|
422
|
+
if (ctor && typeof ctor.name === "string" && FIRESTORE_TERMINAL_CTOR.has(ctor.name)) return true;
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
var SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
426
|
+
function walkForDeleteSentinels(node, path, parent, visit) {
|
|
427
|
+
if (node === null || node === void 0) return;
|
|
428
|
+
if (isDeleteSentinel(node)) {
|
|
429
|
+
visit({ path, parent });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (typeof node !== "object") return;
|
|
433
|
+
if (isTaggedValue(node)) return;
|
|
434
|
+
if (Array.isArray(node)) {
|
|
435
|
+
for (let i = 0; i < node.length; i++) {
|
|
436
|
+
walkForDeleteSentinels(node[i], [...path, String(i)], { kind: "array", index: i }, visit);
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const proto = Object.getPrototypeOf(node);
|
|
441
|
+
if (proto !== null && proto !== Object.prototype) return;
|
|
442
|
+
const obj = node;
|
|
443
|
+
for (const key of Object.keys(obj)) {
|
|
444
|
+
walkForDeleteSentinels(obj[key], [...path, key], { kind: "object" }, visit);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function assertSafePath(path) {
|
|
448
|
+
for (const seg of path) {
|
|
449
|
+
if (!SAFE_KEY_RE.test(seg)) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`firegraph: unsafe object key ${JSON.stringify(seg)} at path ${path.map((p) => JSON.stringify(p)).join(" > ")}. Keys used inside update payloads must match /^[A-Za-z_][A-Za-z0-9_-]*$/ so they can be embedded safely in SQLite JSON paths.`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function flattenPatch(data) {
|
|
457
|
+
const ops = [];
|
|
458
|
+
walk(data, [], ops);
|
|
459
|
+
return ops;
|
|
460
|
+
}
|
|
461
|
+
function assertNoDeleteSentinelsInArrayValue(arr, arrayPath) {
|
|
462
|
+
walkForDeleteSentinels(arr, arrayPath, { kind: "root" }, ({ parent }) => {
|
|
463
|
+
const arrayPathStr = arrayPath.length === 0 ? "<root>" : arrayPath.map((p) => JSON.stringify(p)).join(" > ");
|
|
464
|
+
if (parent.kind === "array") {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`firegraph: deleteField() sentinel at index ${parent.index} inside an array at path ${arrayPathStr}. Arrays are terminal in update payloads (replaced as a unit), so the sentinel would be silently dropped by JSON serialization. To remove the field entirely, pass deleteField() in place of the whole array.`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
throw new Error(
|
|
470
|
+
`firegraph: deleteField() sentinel inside an array element at path ${arrayPathStr}. Arrays are terminal in update payloads \u2014 the sentinel would be silently dropped by JSON serialization.`
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
function walk(node, path, out) {
|
|
475
|
+
if (node === void 0) return;
|
|
476
|
+
if (isDeleteSentinel(node)) {
|
|
477
|
+
if (path.length === 0) {
|
|
478
|
+
throw new Error("firegraph: deleteField() cannot be the entire update payload.");
|
|
479
|
+
}
|
|
480
|
+
assertSafePath(path);
|
|
481
|
+
out.push({ path: [...path], value: void 0, delete: true });
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (isTerminalValue(node)) {
|
|
485
|
+
if (path.length === 0) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
"firegraph: update payload must be a plain object. Got " + (node === null ? "null" : Array.isArray(node) ? "array" : typeof node) + "."
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
if (Array.isArray(node)) {
|
|
491
|
+
assertNoDeleteSentinelsInArrayValue(node, path);
|
|
492
|
+
}
|
|
493
|
+
assertSafePath(path);
|
|
494
|
+
out.push({ path: [...path], value: node, delete: false });
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const obj = node;
|
|
498
|
+
const keys = Object.keys(obj);
|
|
499
|
+
if (keys.length === 0) {
|
|
500
|
+
if (path.length > 0) {
|
|
501
|
+
assertSafePath(path);
|
|
502
|
+
out.push({ path: [...path], value: {}, delete: false });
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
for (const key of keys) {
|
|
507
|
+
if (key === SERIALIZATION_TAG) {
|
|
508
|
+
const where = path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
|
|
509
|
+
throw new Error(
|
|
510
|
+
`firegraph: update payload contains a literal \`${SERIALIZATION_TAG}\` key at ${where}. That key is reserved for firegraph's serialization envelope and cannot appear on a plain object in user data. Use a different field name, or pass a recognized tagged value through replaceNode/replaceEdge instead.`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
walk(obj[key], [...path, key], out);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
164
517
|
// src/scope-path.ts
|
|
165
518
|
function parseStorageScope(scope) {
|
|
166
519
|
if (scope === "") return [];
|
|
@@ -212,10 +565,17 @@ function appendStorageScope(parentScope, uid, name) {
|
|
|
212
565
|
}
|
|
213
566
|
// Annotate the CommonJS export names for ESM import in node:
|
|
214
567
|
0 && (module.exports = {
|
|
568
|
+
CapabilityNotSupportedError,
|
|
215
569
|
CrossBackendTransactionError,
|
|
570
|
+
DELETE_FIELD,
|
|
216
571
|
appendStorageScope,
|
|
572
|
+
createCapabilities,
|
|
217
573
|
createRoutingBackend,
|
|
574
|
+
deleteField,
|
|
575
|
+
flattenPatch,
|
|
576
|
+
intersectCapabilities,
|
|
218
577
|
isAncestorScopeUid,
|
|
578
|
+
isDeleteSentinel,
|
|
219
579
|
parseStorageScope,
|
|
220
580
|
resolveAncestorScope
|
|
221
581
|
});
|