@typicalday/firegraph 0.8.0 → 0.10.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/dist/backend-73p5Blx7.d.cts +97 -0
- package/dist/backend-BrqFkbid.d.ts +97 -0
- package/dist/backend.cjs +222 -0
- package/dist/backend.cjs.map +1 -0
- package/dist/backend.d.cts +122 -0
- package/dist/backend.d.ts +122 -0
- package/dist/backend.js +136 -0
- package/dist/backend.js.map +1 -0
- package/dist/{chunk-6OQW5OKO.js → chunk-5753Y42M.js} +12 -4
- package/dist/chunk-5753Y42M.js.map +1 -0
- package/dist/{chunk-YUXOALMR.js → chunk-LZOIQHYN.js} +69 -92
- package/dist/chunk-LZOIQHYN.js.map +1 -0
- package/dist/chunk-R7CRGYY4.js +94 -0
- package/dist/chunk-R7CRGYY4.js.map +1 -0
- package/dist/{chunk-KFA7G37W.js → chunk-SU4FNLC3.js} +32 -30
- package/dist/chunk-SU4FNLC3.js.map +1 -0
- package/dist/chunk-TYYPRVIE.js +57 -0
- package/dist/chunk-TYYPRVIE.js.map +1 -0
- package/dist/{do-sqlite.cjs → cloudflare/index.cjs} +1538 -1420
- package/dist/cloudflare/index.cjs.map +1 -0
- package/dist/cloudflare/index.d.cts +454 -0
- package/dist/cloudflare/index.d.ts +454 -0
- package/dist/cloudflare/index.js +822 -0
- package/dist/cloudflare/index.js.map +1 -0
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/editor/client/assets/index-Bq2bfzeY.js +411 -0
- package/dist/editor/client/index.html +1 -1
- package/dist/editor/server/index.mjs +6481 -6327
- package/dist/index.cjs +165 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -138
- package/dist/index.d.ts +14 -138
- package/dist/index.js +31 -22
- package/dist/index.js.map +1 -1
- package/dist/query-client/index.cjs +30 -28
- package/dist/query-client/index.cjs.map +1 -1
- package/dist/query-client/index.d.cts +2 -2
- package/dist/query-client/index.d.ts +2 -2
- package/dist/query-client/index.js +1 -1
- package/dist/react.cjs +0 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +0 -1
- package/dist/react.js.map +1 -1
- package/dist/scope-path-B1G3YiA7.d.cts +139 -0
- package/dist/scope-path-B1G3YiA7.d.ts +139 -0
- package/dist/{serialization-C6JNNOCS.js → serialization-ZZ7RSDRX.js} +2 -2
- package/dist/svelte.cjs +0 -2
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.js +0 -2
- package/dist/svelte.js.map +1 -1
- package/dist/{types-BVtx9zLv.d.cts → types-DOemdlVA.d.cts} +20 -2
- package/dist/{types-BVtx9zLv.d.ts → types-DOemdlVA.d.ts} +20 -2
- package/package.json +39 -40
- package/dist/chunk-6OQW5OKO.js.map +0 -1
- package/dist/chunk-KFA7G37W.js.map +0 -1
- package/dist/chunk-WOAJRVHD.js +0 -699
- package/dist/chunk-WOAJRVHD.js.map +0 -1
- package/dist/chunk-YUXOALMR.js.map +0 -1
- package/dist/d1.cjs +0 -2416
- package/dist/d1.cjs.map +0 -1
- package/dist/d1.d.cts +0 -54
- package/dist/d1.d.ts +0 -54
- package/dist/d1.js +0 -75
- package/dist/d1.js.map +0 -1
- package/dist/do-sqlite.cjs.map +0 -1
- package/dist/do-sqlite.d.cts +0 -41
- package/dist/do-sqlite.d.ts +0 -41
- package/dist/do-sqlite.js +0 -78
- package/dist/do-sqlite.js.map +0 -1
- package/dist/editor/client/assets/index-tyFcX6qG.js +0 -411
- /package/dist/{serialization-C6JNNOCS.js.map → serialization-ZZ7RSDRX.js.map} +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export { C as CrossBackendTransactionError, S as StorageScopeSegment, a as appendStorageScope, i as isAncestorScopeUid, p as parseStorageScope, r as resolveAncestorScope } from './scope-path-B1G3YiA7.js';
|
|
2
|
+
import { S as StorageBackend } from './backend-BrqFkbid.js';
|
|
3
|
+
export { B as BatchBackend, T as TransactionBackend, U as UpdatePayload, W as WritableRecord } from './backend-BrqFkbid.js';
|
|
4
|
+
import './types-DOemdlVA.js';
|
|
5
|
+
import '@google-cloud/firestore';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Routing `StorageBackend` wrapper.
|
|
9
|
+
*
|
|
10
|
+
* `createRoutingBackend(base, { route })` returns a `StorageBackend` that
|
|
11
|
+
* behaves identically to `base` except for `subgraph(parentUid, name)`:
|
|
12
|
+
* each such call consults the caller-supplied `route` function, and if it
|
|
13
|
+
* returns a non-null `StorageBackend`, that backend is used for the child
|
|
14
|
+
* scope.
|
|
15
|
+
*
|
|
16
|
+
* This is the single seam firegraph ships for splitting a logical graph
|
|
17
|
+
* across multiple physical storage backends — e.g. fanning particular
|
|
18
|
+
* subgraph names out to their own Durable Objects to stay under the 10 GB
|
|
19
|
+
* per-DO limit. The routing policy itself, the RPC protocol, and any
|
|
20
|
+
* live-scope index are left to the caller; firegraph only owns the
|
|
21
|
+
* composition primitive and the invariants that come with it.
|
|
22
|
+
*
|
|
23
|
+
* ## Contract — nested routing
|
|
24
|
+
*
|
|
25
|
+
* Whether `route()` returns a routed backend OR `null` (pass-through), the
|
|
26
|
+
* child returned by `subgraph()` is **always** itself wrapped by the same
|
|
27
|
+
* router. Without that self-wrap, a call chain like
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* router.subgraph(A, 'memories').subgraph(B, 'context')
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* would route the first hop correctly but bypass the router on the second
|
|
34
|
+
* hop (since the routed backend's own `.subgraph()` doesn't know about the
|
|
35
|
+
* caller's policy). Keeping routing active through grandchildren is the
|
|
36
|
+
* load-bearing behaviour; `'continues routing on grandchildren …'` in the
|
|
37
|
+
* unit tests locks it in.
|
|
38
|
+
*
|
|
39
|
+
* ## Contract — `route` is synchronous
|
|
40
|
+
*
|
|
41
|
+
* `.subgraph()` is synchronous in firegraph's public API. Making the
|
|
42
|
+
* routing callback async would require rippling Promises through every
|
|
43
|
+
* client-factory call site. Consequence: `route` can only consult data it
|
|
44
|
+
* already has in hand (DO bindings, naming rules, in-memory caches). If
|
|
45
|
+
* you need "does this DO exist?" checks, do them lazily — the first read
|
|
46
|
+
* against the returned backend will surface the failure naturally.
|
|
47
|
+
*
|
|
48
|
+
* ## Contract — cross-backend atomicity is not silently degraded
|
|
49
|
+
*
|
|
50
|
+
* The wrapper's `runTransaction` and `createBatch` delegate to `base` —
|
|
51
|
+
* they run entirely on the base backend. `TransactionBackend` and
|
|
52
|
+
* `BatchBackend` deliberately have no `subgraph()` method, so user code
|
|
53
|
+
* physically cannot open a routed child from inside a transaction
|
|
54
|
+
* callback. Any attempt to bypass that (via `as any` / unchecked casts)
|
|
55
|
+
* should surface as `CrossBackendTransactionError` so app code can catch
|
|
56
|
+
* it cleanly — the error type is part of the public surface.
|
|
57
|
+
*
|
|
58
|
+
* ## Contract — `findEdgesGlobal` is base-scope only
|
|
59
|
+
*
|
|
60
|
+
* When delegated, `findEdgesGlobal` runs against the base backend only.
|
|
61
|
+
* It does **not** fan out to routed children — firegraph has no
|
|
62
|
+
* enumeration index for which routed backends exist. Callers who need
|
|
63
|
+
* cross-shard collection-group queries must maintain their own scope
|
|
64
|
+
* directory and query it directly. This keeps the common case (local
|
|
65
|
+
* analytics inside one DO) fast.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Context passed to a routing callback when `subgraph(parentUid, name)` is
|
|
70
|
+
* called on a routed backend. All four strings describe the *child* scope
|
|
71
|
+
* the caller is requesting, so the router can key its decision off whichever
|
|
72
|
+
* representation is most convenient:
|
|
73
|
+
*
|
|
74
|
+
* - `parentUid` / `subgraphName` — the arguments just passed to `subgraph()`.
|
|
75
|
+
* - `scopePath` — logical, names-only chain (`'memories'`, `'memories/context'`).
|
|
76
|
+
* This is what `allowedIn` patterns match against.
|
|
77
|
+
* - `storageScope` — the materialized-path form (`'A/memories'`,
|
|
78
|
+
* `'A/memories/B/context'`), suitable for use as a DO name or shard key
|
|
79
|
+
* because it's globally unique within a root graph.
|
|
80
|
+
*/
|
|
81
|
+
interface RoutingContext {
|
|
82
|
+
parentUid: string;
|
|
83
|
+
subgraphName: string;
|
|
84
|
+
scopePath: string;
|
|
85
|
+
storageScope: string;
|
|
86
|
+
}
|
|
87
|
+
interface RoutingBackendOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Decide whether a `subgraph(parentUid, name)` call should route to a
|
|
90
|
+
* different backend. Return the target backend to route; return `null`
|
|
91
|
+
* (or `undefined`) to fall through to the wrapped base backend.
|
|
92
|
+
*
|
|
93
|
+
* The returned backend is itself wrapped by the same router so that
|
|
94
|
+
* nested `.subgraph()` calls on the returned child continue to be
|
|
95
|
+
* consulted.
|
|
96
|
+
*/
|
|
97
|
+
route: (ctx: RoutingContext) => StorageBackend | null | undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Wrap a `StorageBackend` so that `subgraph(parentUid, name)` calls can be
|
|
101
|
+
* routed to a different backend based on a user-supplied callback.
|
|
102
|
+
*
|
|
103
|
+
* See the module docstring for the atomicity rules. In short: transactions
|
|
104
|
+
* and batches opened on a routing backend run entirely on the *base*
|
|
105
|
+
* backend — they cannot span routed children, by design.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* // `base` is any StorageBackend — e.g. a Firestore-backed one, an
|
|
110
|
+
* // in-process SQLite backend, or the DO backend from firegraph/cloudflare.
|
|
111
|
+
* const routed = createRoutingBackend(base, {
|
|
112
|
+
* route: ({ subgraphName, storageScope }) => {
|
|
113
|
+
* if (subgraphName !== 'memories') return null;
|
|
114
|
+
* return createMyMemoriesBackend(storageScope); // caller-owned
|
|
115
|
+
* },
|
|
116
|
+
* });
|
|
117
|
+
* const client = createGraphClientFromBackend(routed, { registry });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function createRoutingBackend(base: StorageBackend, options: RoutingBackendOptions): StorageBackend;
|
|
121
|
+
|
|
122
|
+
export { type RoutingBackendOptions, type RoutingContext, StorageBackend, createRoutingBackend };
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendStorageScope,
|
|
3
|
+
isAncestorScopeUid,
|
|
4
|
+
parseStorageScope,
|
|
5
|
+
resolveAncestorScope
|
|
6
|
+
} from "./chunk-TYYPRVIE.js";
|
|
7
|
+
import {
|
|
8
|
+
CrossBackendTransactionError,
|
|
9
|
+
FiregraphError
|
|
10
|
+
} from "./chunk-R7CRGYY4.js";
|
|
11
|
+
|
|
12
|
+
// src/internal/routing-backend.ts
|
|
13
|
+
function assertValidSubgraphArgs(parentNodeUid, name) {
|
|
14
|
+
if (!parentNodeUid || parentNodeUid.includes("/")) {
|
|
15
|
+
throw new FiregraphError(
|
|
16
|
+
`Invalid parentNodeUid for subgraph: "${parentNodeUid}". Must be a non-empty string without "/".`,
|
|
17
|
+
"INVALID_SUBGRAPH"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (!name || name.includes("/")) {
|
|
21
|
+
throw new FiregraphError(
|
|
22
|
+
`Subgraph name must not contain "/" and must be non-empty: got "${name}". Use chained .subgraph() calls for nested subgraphs.`,
|
|
23
|
+
"INVALID_SUBGRAPH"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var RoutingStorageBackend = class _RoutingStorageBackend {
|
|
28
|
+
constructor(base, options, storageScope, logicalScopePath) {
|
|
29
|
+
this.base = base;
|
|
30
|
+
this.options = options;
|
|
31
|
+
this.collectionPath = base.collectionPath;
|
|
32
|
+
this.scopePath = logicalScopePath;
|
|
33
|
+
this.storageScope = storageScope;
|
|
34
|
+
if (base.findEdgesGlobal) {
|
|
35
|
+
this.findEdgesGlobal = (params, collectionName) => base.findEdgesGlobal(params, collectionName);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
collectionPath;
|
|
39
|
+
/**
|
|
40
|
+
* Logical (names-only) scope path for *this* wrapper. Tracked
|
|
41
|
+
* independently of `base.scopePath` because a routed backend returned by
|
|
42
|
+
* `options.route()` typically represents its own physical root and has
|
|
43
|
+
* no knowledge of the caller's logical chain. The wrapper is the
|
|
44
|
+
* authoritative source of the logical scope for routing decisions and
|
|
45
|
+
* for satisfying the `StorageBackend.scopePath` contract surfaced to
|
|
46
|
+
* client code.
|
|
47
|
+
*/
|
|
48
|
+
scopePath;
|
|
49
|
+
/**
|
|
50
|
+
* Materialized-path form of `scopePath` — interleaved `<uid>/<name>`
|
|
51
|
+
* pairs. Not a property on the underlying `StorageBackend` interface
|
|
52
|
+
* (Firestore doesn't produce one), so we track it ourselves from
|
|
53
|
+
* `.subgraph()` arguments. Root routers start with `''`.
|
|
54
|
+
*/
|
|
55
|
+
storageScope;
|
|
56
|
+
/**
|
|
57
|
+
* Conditionally installed in the constructor — only present when the
|
|
58
|
+
* wrapped base backend supports it. Declared as an optional instance
|
|
59
|
+
* property (rather than a prototype method) so `typeof router.findEdgesGlobal
|
|
60
|
+
* === 'function'` reflects the base's capability, matching the optional
|
|
61
|
+
* shape in the `StorageBackend` interface.
|
|
62
|
+
*/
|
|
63
|
+
findEdgesGlobal;
|
|
64
|
+
// --- Pass-through reads ---
|
|
65
|
+
getDoc(docId) {
|
|
66
|
+
return this.base.getDoc(docId);
|
|
67
|
+
}
|
|
68
|
+
query(filters, options) {
|
|
69
|
+
return this.base.query(filters, options);
|
|
70
|
+
}
|
|
71
|
+
// --- Pass-through writes ---
|
|
72
|
+
setDoc(docId, record) {
|
|
73
|
+
return this.base.setDoc(docId, record);
|
|
74
|
+
}
|
|
75
|
+
updateDoc(docId, update) {
|
|
76
|
+
return this.base.updateDoc(docId, update);
|
|
77
|
+
}
|
|
78
|
+
deleteDoc(docId) {
|
|
79
|
+
return this.base.deleteDoc(docId);
|
|
80
|
+
}
|
|
81
|
+
// --- Transactions / batches run against the base backend only ---
|
|
82
|
+
runTransaction(fn) {
|
|
83
|
+
return this.base.runTransaction(fn);
|
|
84
|
+
}
|
|
85
|
+
createBatch() {
|
|
86
|
+
return this.base.createBatch();
|
|
87
|
+
}
|
|
88
|
+
// --- Subgraphs: the only method that actually routes ---
|
|
89
|
+
subgraph(parentNodeUid, name) {
|
|
90
|
+
assertValidSubgraphArgs(parentNodeUid, name);
|
|
91
|
+
const childScopePath = this.scopePath ? `${this.scopePath}/${name}` : name;
|
|
92
|
+
const childStorageScope = this.storageScope ? `${this.storageScope}/${parentNodeUid}/${name}` : `${parentNodeUid}/${name}`;
|
|
93
|
+
const routed = this.options.route({
|
|
94
|
+
parentUid: parentNodeUid,
|
|
95
|
+
subgraphName: name,
|
|
96
|
+
scopePath: childScopePath,
|
|
97
|
+
storageScope: childStorageScope
|
|
98
|
+
});
|
|
99
|
+
if (routed) {
|
|
100
|
+
return new _RoutingStorageBackend(routed, this.options, childStorageScope, childScopePath);
|
|
101
|
+
}
|
|
102
|
+
const childBase = this.base.subgraph(parentNodeUid, name);
|
|
103
|
+
return new _RoutingStorageBackend(childBase, this.options, childStorageScope, childScopePath);
|
|
104
|
+
}
|
|
105
|
+
// --- Bulk operations: delegate, but cascade is base-scope only ---
|
|
106
|
+
removeNodeCascade(uid, reader, options) {
|
|
107
|
+
return this.base.removeNodeCascade(uid, reader, options);
|
|
108
|
+
}
|
|
109
|
+
bulkRemoveEdges(params, reader, options) {
|
|
110
|
+
return this.base.bulkRemoveEdges(params, reader, options);
|
|
111
|
+
}
|
|
112
|
+
// --- Collection-group queries are base-scope only ---
|
|
113
|
+
//
|
|
114
|
+
// `findEdgesGlobal` is installed in the constructor *only* when the base
|
|
115
|
+
// backend supports it, so `typeof router.findEdgesGlobal === 'function'`
|
|
116
|
+
// reflects the base's capability — matching the optional shape declared
|
|
117
|
+
// on `StorageBackend`.
|
|
118
|
+
};
|
|
119
|
+
function createRoutingBackend(base, options) {
|
|
120
|
+
if (typeof options?.route !== "function") {
|
|
121
|
+
throw new FiregraphError(
|
|
122
|
+
"createRoutingBackend: `options.route` must be a function.",
|
|
123
|
+
"INVALID_ARGUMENT"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return new RoutingStorageBackend(base, options, "", base.scopePath);
|
|
127
|
+
}
|
|
128
|
+
export {
|
|
129
|
+
CrossBackendTransactionError,
|
|
130
|
+
appendStorageScope,
|
|
131
|
+
createRoutingBackend,
|
|
132
|
+
isAncestorScopeUid,
|
|
133
|
+
parseStorageScope,
|
|
134
|
+
resolveAncestorScope
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/routing-backend.ts"],"sourcesContent":["/**\n * Routing `StorageBackend` wrapper.\n *\n * `createRoutingBackend(base, { route })` returns a `StorageBackend` that\n * behaves identically to `base` except for `subgraph(parentUid, name)`:\n * each such call consults the caller-supplied `route` function, and if it\n * returns a non-null `StorageBackend`, that backend is used for the child\n * scope.\n *\n * This is the single seam firegraph ships for splitting a logical graph\n * across multiple physical storage backends — e.g. fanning particular\n * subgraph names out to their own Durable Objects to stay under the 10 GB\n * per-DO limit. The routing policy itself, the RPC protocol, and any\n * live-scope index are left to the caller; firegraph only owns the\n * composition primitive and the invariants that come with it.\n *\n * ## Contract — nested routing\n *\n * Whether `route()` returns a routed backend OR `null` (pass-through), the\n * child returned by `subgraph()` is **always** itself wrapped by the same\n * router. Without that self-wrap, a call chain like\n *\n * ```ts\n * router.subgraph(A, 'memories').subgraph(B, 'context')\n * ```\n *\n * would route the first hop correctly but bypass the router on the second\n * hop (since the routed backend's own `.subgraph()` doesn't know about the\n * caller's policy). Keeping routing active through grandchildren is the\n * load-bearing behaviour; `'continues routing on grandchildren …'` in the\n * unit tests locks it in.\n *\n * ## Contract — `route` is synchronous\n *\n * `.subgraph()` is synchronous in firegraph's public API. Making the\n * routing callback async would require rippling Promises through every\n * client-factory call site. Consequence: `route` can only consult data it\n * already has in hand (DO bindings, naming rules, in-memory caches). If\n * you need \"does this DO exist?\" checks, do them lazily — the first read\n * against the returned backend will surface the failure naturally.\n *\n * ## Contract — cross-backend atomicity is not silently degraded\n *\n * The wrapper's `runTransaction` and `createBatch` delegate to `base` —\n * they run entirely on the base backend. `TransactionBackend` and\n * `BatchBackend` deliberately have no `subgraph()` method, so user code\n * physically cannot open a routed child from inside a transaction\n * callback. Any attempt to bypass that (via `as any` / unchecked casts)\n * should surface as `CrossBackendTransactionError` so app code can catch\n * it cleanly — the error type is part of the public surface.\n *\n * ## Contract — `findEdgesGlobal` is base-scope only\n *\n * When delegated, `findEdgesGlobal` runs against the base backend only.\n * It does **not** fan out to routed children — firegraph has no\n * enumeration index for which routed backends exist. Callers who need\n * cross-shard collection-group queries must maintain their own scope\n * directory and query it directly. This keeps the common case (local\n * analytics inside one DO) fast.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type {\n BulkOptions,\n BulkResult,\n CascadeResult,\n FindEdgesParams,\n GraphReader,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport type {\n BatchBackend,\n StorageBackend,\n TransactionBackend,\n UpdatePayload,\n WritableRecord,\n} from './backend.js';\n\n/**\n * Context passed to a routing callback when `subgraph(parentUid, name)` is\n * called on a routed backend. All four strings describe the *child* scope\n * the caller is requesting, so the router can key its decision off whichever\n * representation is most convenient:\n *\n * - `parentUid` / `subgraphName` — the arguments just passed to `subgraph()`.\n * - `scopePath` — logical, names-only chain (`'memories'`, `'memories/context'`).\n * This is what `allowedIn` patterns match against.\n * - `storageScope` — the materialized-path form (`'A/memories'`,\n * `'A/memories/B/context'`), suitable for use as a DO name or shard key\n * because it's globally unique within a root graph.\n */\nexport interface RoutingContext {\n parentUid: string;\n subgraphName: string;\n scopePath: string;\n storageScope: string;\n}\n\nexport interface RoutingBackendOptions {\n /**\n * Decide whether a `subgraph(parentUid, name)` call should route to a\n * different backend. Return the target backend to route; return `null`\n * (or `undefined`) to fall through to the wrapped base backend.\n *\n * The returned backend is itself wrapped by the same router so that\n * nested `.subgraph()` calls on the returned child continue to be\n * consulted.\n */\n route: (ctx: RoutingContext) => StorageBackend | null | undefined;\n}\n\nfunction assertValidSubgraphArgs(parentNodeUid: string, name: string): void {\n if (!parentNodeUid || parentNodeUid.includes('/')) {\n throw new FiregraphError(\n `Invalid parentNodeUid for subgraph: \"${parentNodeUid}\". ` +\n 'Must be a non-empty string without \"/\".',\n 'INVALID_SUBGRAPH',\n );\n }\n if (!name || name.includes('/')) {\n throw new FiregraphError(\n `Subgraph name must not contain \"/\" and must be non-empty: got \"${name}\". ` +\n 'Use chained .subgraph() calls for nested subgraphs.',\n 'INVALID_SUBGRAPH',\n );\n }\n}\n\nclass RoutingStorageBackend implements StorageBackend {\n readonly collectionPath: string;\n /**\n * Logical (names-only) scope path for *this* wrapper. Tracked\n * independently of `base.scopePath` because a routed backend returned by\n * `options.route()` typically represents its own physical root and has\n * no knowledge of the caller's logical chain. The wrapper is the\n * authoritative source of the logical scope for routing decisions and\n * for satisfying the `StorageBackend.scopePath` contract surfaced to\n * client code.\n */\n readonly scopePath: string;\n /**\n * Materialized-path form of `scopePath` — interleaved `<uid>/<name>`\n * pairs. Not a property on the underlying `StorageBackend` interface\n * (Firestore doesn't produce one), so we track it ourselves from\n * `.subgraph()` arguments. Root routers start with `''`.\n */\n private readonly storageScope: string;\n /**\n * Conditionally installed in the constructor — only present when the\n * wrapped base backend supports it. Declared as an optional instance\n * property (rather than a prototype method) so `typeof router.findEdgesGlobal\n * === 'function'` reflects the base's capability, matching the optional\n * shape in the `StorageBackend` interface.\n */\n findEdgesGlobal?: StorageBackend['findEdgesGlobal'];\n\n constructor(\n private readonly base: StorageBackend,\n private readonly options: RoutingBackendOptions,\n storageScope: string,\n logicalScopePath: string,\n ) {\n this.collectionPath = base.collectionPath;\n this.scopePath = logicalScopePath;\n this.storageScope = storageScope;\n if (base.findEdgesGlobal) {\n // We deliberately do *not* fan out across routed children: we have no\n // enumeration index for which backends exist. Callers needing\n // cross-shard collection-group queries must maintain their own index.\n this.findEdgesGlobal = (params, collectionName) =>\n base.findEdgesGlobal!(params, collectionName);\n }\n }\n\n // --- Pass-through reads ---\n\n getDoc(docId: string): Promise<StoredGraphRecord | null> {\n return this.base.getDoc(docId);\n }\n\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n return this.base.query(filters, options);\n }\n\n // --- Pass-through writes ---\n\n setDoc(docId: string, record: WritableRecord): Promise<void> {\n return this.base.setDoc(docId, record);\n }\n\n updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n return this.base.updateDoc(docId, update);\n }\n\n deleteDoc(docId: string): Promise<void> {\n return this.base.deleteDoc(docId);\n }\n\n // --- Transactions / batches run against the base backend only ---\n\n runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T> {\n // Transactions cannot span base + routed backends (different DBs /\n // DOs / Firestore projects). `TransactionBackend` has no `subgraph()`\n // method, so the user physically cannot open a routed child from\n // inside the callback — the compiler rejects it. At runtime, all\n // reads/writes are confined to the base backend.\n return this.base.runTransaction(fn);\n }\n\n createBatch(): BatchBackend {\n // Same constraint as transactions: `BatchBackend` has no `subgraph()`\n // so all buffered ops target the base backend. The router itself\n // doesn't need to guard anything here.\n return this.base.createBatch();\n }\n\n // --- Subgraphs: the only method that actually routes ---\n\n subgraph(parentNodeUid: string, name: string): StorageBackend {\n assertValidSubgraphArgs(parentNodeUid, name);\n\n const childScopePath = this.scopePath ? `${this.scopePath}/${name}` : name;\n const childStorageScope = this.storageScope\n ? `${this.storageScope}/${parentNodeUid}/${name}`\n : `${parentNodeUid}/${name}`;\n\n const routed = this.options.route({\n parentUid: parentNodeUid,\n subgraphName: name,\n scopePath: childScopePath,\n storageScope: childStorageScope,\n });\n\n if (routed) {\n // The user returned a different backend. We still wrap it so that\n // further `.subgraph()` calls on the returned child continue to\n // consult the router. The routed backend's own `scopePath` / storage\n // layout is its business — for routing purposes we carry *our*\n // logical view forward (`childScopePath`) so grandchildren see a\n // correct context regardless of what `routed.scopePath` happens to\n // be (typically `''` for a freshly-minted per-DO backend).\n return new RoutingStorageBackend(routed, this.options, childStorageScope, childScopePath);\n }\n\n // No route — delegate to the base backend and keep routing in effect\n // for grandchildren.\n const childBase = this.base.subgraph(parentNodeUid, name);\n return new RoutingStorageBackend(childBase, this.options, childStorageScope, childScopePath);\n }\n\n // --- Bulk operations: delegate, but cascade is base-scope only ---\n\n removeNodeCascade(\n uid: string,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<CascadeResult> {\n // `removeNodeCascade` on the base backend cannot see rows that live\n // in routed child backends — each routed backend is a different\n // physical store. Callers with routed subgraphs under `uid` are\n // responsible for cascading those themselves (see routing.md).\n return this.base.removeNodeCascade(uid, reader, options);\n }\n\n bulkRemoveEdges(\n params: FindEdgesParams,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n return this.base.bulkRemoveEdges(params, reader, options);\n }\n\n // --- Collection-group queries are base-scope only ---\n //\n // `findEdgesGlobal` is installed in the constructor *only* when the base\n // backend supports it, so `typeof router.findEdgesGlobal === 'function'`\n // reflects the base's capability — matching the optional shape declared\n // on `StorageBackend`.\n}\n\n/**\n * Wrap a `StorageBackend` so that `subgraph(parentUid, name)` calls can be\n * routed to a different backend based on a user-supplied callback.\n *\n * See the module docstring for the atomicity rules. In short: transactions\n * and batches opened on a routing backend run entirely on the *base*\n * backend — they cannot span routed children, by design.\n *\n * @example\n * ```ts\n * // `base` is any StorageBackend — e.g. a Firestore-backed one, an\n * // in-process SQLite backend, or the DO backend from firegraph/cloudflare.\n * const routed = createRoutingBackend(base, {\n * route: ({ subgraphName, storageScope }) => {\n * if (subgraphName !== 'memories') return null;\n * return createMyMemoriesBackend(storageScope); // caller-owned\n * },\n * });\n * const client = createGraphClientFromBackend(routed, { registry });\n * ```\n */\nexport function createRoutingBackend(\n base: StorageBackend,\n options: RoutingBackendOptions,\n): StorageBackend {\n if (typeof options?.route !== 'function') {\n throw new FiregraphError(\n 'createRoutingBackend: `options.route` must be a function.',\n 'INVALID_ARGUMENT',\n );\n }\n return new RoutingStorageBackend(base, options, '', base.scopePath);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/serialization.ts
|
|
2
|
-
import {
|
|
2
|
+
import { FieldValue, GeoPoint, Timestamp } from "@google-cloud/firestore";
|
|
3
3
|
var SERIALIZATION_TAG = "__firegraph_ser__";
|
|
4
4
|
var KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
|
|
5
5
|
var _docRefWarned = false;
|
|
@@ -31,10 +31,18 @@ function serializeValue(value) {
|
|
|
31
31
|
if (value === null || value === void 0) return value;
|
|
32
32
|
if (typeof value !== "object") return value;
|
|
33
33
|
if (isTimestamp(value)) {
|
|
34
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
[SERIALIZATION_TAG]: "Timestamp",
|
|
36
|
+
seconds: value.seconds,
|
|
37
|
+
nanoseconds: value.nanoseconds
|
|
38
|
+
};
|
|
35
39
|
}
|
|
36
40
|
if (isGeoPoint(value)) {
|
|
37
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
[SERIALIZATION_TAG]: "GeoPoint",
|
|
43
|
+
latitude: value.latitude,
|
|
44
|
+
longitude: value.longitude
|
|
45
|
+
};
|
|
38
46
|
}
|
|
39
47
|
if (isDocumentReference(value)) {
|
|
40
48
|
return { [SERIALIZATION_TAG]: "DocumentReference", path: value.path };
|
|
@@ -107,4 +115,4 @@ export {
|
|
|
107
115
|
serializeFirestoreTypes,
|
|
108
116
|
deserializeFirestoreTypes
|
|
109
117
|
};
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-5753Y42M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serialization.ts"],"sourcesContent":["/**\n * Firestore-aware serialization for the sandbox migration pipeline.\n *\n * Firestore documents can contain special types (Timestamp, GeoPoint,\n * VectorValue, DocumentReference) that don't survive plain JSON\n * round-tripping. This module provides tagged serialization: Firestore\n * types are wrapped in tagged plain objects before JSON marshaling and\n * reconstructed after.\n *\n * Only used by the `defaultExecutor` sandbox path. Static migrations\n * (in-memory functions) receive raw Firestore objects directly.\n */\n\nimport type { DocumentReference, Firestore } from '@google-cloud/firestore';\nimport { FieldValue, GeoPoint, Timestamp } from '@google-cloud/firestore';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Sentinel key used to tag serialized Firestore types. */\nexport const SERIALIZATION_TAG = '__firegraph_ser__' as const;\n\n/** Known discriminator values for tagged types. */\nconst KNOWN_TYPES = new Set(['Timestamp', 'GeoPoint', 'VectorValue', 'DocumentReference']);\n\n// One-time warning for DocumentReference deserialization without db\nlet _docRefWarned = false;\n\n// ---------------------------------------------------------------------------\n// Type guard\n// ---------------------------------------------------------------------------\n\n/** Check if a value is a tagged serialized Firestore type. */\nexport function isTaggedValue(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const tag = (value as Record<string, unknown>)[SERIALIZATION_TAG];\n return typeof tag === 'string' && KNOWN_TYPES.has(tag);\n}\n\n// ---------------------------------------------------------------------------\n// Detection helpers\n// ---------------------------------------------------------------------------\n\nfunction isTimestamp(value: unknown): value is Timestamp {\n return value instanceof Timestamp;\n}\n\nfunction isGeoPoint(value: unknown): value is GeoPoint {\n return value instanceof GeoPoint;\n}\n\nfunction isDocumentReference(value: unknown): value is DocumentReference {\n // Duck-type check: DocumentReference has path (string) and firestore properties\n if (value === null || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.path === 'string' &&\n v.firestore !== undefined &&\n typeof v.id === 'string' &&\n v.constructor?.name === 'DocumentReference'\n );\n}\n\nfunction isVectorValue(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n return (\n v.constructor?.name === 'VectorValue' && Array.isArray((v as Record<string, unknown>)._values)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Serialize\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively walk a data object and replace Firestore types with tagged\n * plain objects suitable for JSON serialization.\n *\n * Returns a new object tree — the input is never mutated.\n */\nexport function serializeFirestoreTypes(data: Record<string, unknown>): Record<string, unknown> {\n return serializeValue(data) as Record<string, unknown>;\n}\n\nfunction serializeValue(value: unknown): unknown {\n // Primitives\n if (value === null || value === undefined) return value;\n if (typeof value !== 'object') return value;\n\n // Firestore types (check before generic object/array)\n if (isTimestamp(value)) {\n return {\n [SERIALIZATION_TAG]: 'Timestamp',\n seconds: value.seconds,\n nanoseconds: value.nanoseconds,\n };\n }\n if (isGeoPoint(value)) {\n return {\n [SERIALIZATION_TAG]: 'GeoPoint',\n latitude: value.latitude,\n longitude: value.longitude,\n };\n }\n if (isDocumentReference(value)) {\n return { [SERIALIZATION_TAG]: 'DocumentReference', path: (value as DocumentReference).path };\n }\n if (isVectorValue(value)) {\n // Prefer toArray() (public API) over _values (private internal property)\n const v = value as Record<string, unknown>;\n const values =\n typeof v.toArray === 'function' ? (v.toArray as () => number[])() : (v._values as number[]);\n return { [SERIALIZATION_TAG]: 'VectorValue', values: [...values] };\n }\n\n // Arrays\n if (Array.isArray(value)) {\n return value.map(serializeValue);\n }\n\n // Plain objects — recurse\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n result[key] = serializeValue((value as Record<string, unknown>)[key]);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Deserialize\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively walk a data object and reconstruct Firestore types from\n * tagged plain objects.\n *\n * @param data - The data to deserialize (typically from JSON.parse)\n * @param db - Optional Firestore instance for DocumentReference reconstruction.\n * If not provided, tagged DocumentReferences are left as-is with a one-time warning.\n *\n * Returns a new object tree — the input is never mutated.\n */\nexport function deserializeFirestoreTypes(\n data: Record<string, unknown>,\n db?: Firestore,\n): Record<string, unknown> {\n return deserializeValue(data, db) as Record<string, unknown>;\n}\n\nfunction deserializeValue(value: unknown, db?: Firestore): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value !== 'object') return value;\n\n // Short-circuit for values that are already real Firestore types.\n // This makes deserializeFirestoreTypes idempotent — safe to call on data\n // that has already been deserialized (e.g., write-back after defaultExecutor\n // already reconstructed types, or static migrations that return raw types).\n if (\n isTimestamp(value) ||\n isGeoPoint(value) ||\n isDocumentReference(value) ||\n isVectorValue(value)\n ) {\n return value;\n }\n\n // Arrays\n if (Array.isArray(value)) {\n return value.map((v) => deserializeValue(v, db));\n }\n\n const obj = value as Record<string, unknown>;\n\n // Check for tagged Firestore type\n if (isTaggedValue(obj)) {\n const tag = obj[SERIALIZATION_TAG] as string;\n\n switch (tag) {\n case 'Timestamp':\n // Validate expected fields before reconstruction\n if (typeof obj.seconds !== 'number' || typeof obj.nanoseconds !== 'number') return obj;\n return new Timestamp(obj.seconds, obj.nanoseconds);\n\n case 'GeoPoint':\n if (typeof obj.latitude !== 'number' || typeof obj.longitude !== 'number') return obj;\n return new GeoPoint(obj.latitude, obj.longitude);\n\n case 'VectorValue':\n if (!Array.isArray(obj.values)) return obj;\n return FieldValue.vector(obj.values as number[]);\n\n case 'DocumentReference':\n if (typeof obj.path !== 'string') return obj;\n if (db) {\n return db.doc(obj.path);\n }\n // No db available — leave as tagged object with one-time warning\n if (!_docRefWarned) {\n _docRefWarned = true;\n console.warn(\n '[firegraph] DocumentReference encountered during migration deserialization ' +\n 'but no Firestore instance available. The reference will remain as a tagged ' +\n 'object with its path. Enable write-back for full reconstruction.',\n );\n }\n return obj;\n\n default:\n // Unknown tag — leave as-is (forward compatibility)\n return obj;\n }\n }\n\n // Plain object — recurse\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n result[key] = deserializeValue(obj[key], db);\n }\n return result;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DynamicRegistryError,
|
|
3
|
+
FiregraphError,
|
|
4
|
+
InvalidQueryError,
|
|
5
|
+
MigrationError,
|
|
6
|
+
QuerySafetyError,
|
|
7
|
+
RegistryScopeError,
|
|
8
|
+
RegistryViolationError,
|
|
9
|
+
ValidationError
|
|
10
|
+
} from "./chunk-R7CRGYY4.js";
|
|
11
|
+
|
|
1
12
|
// src/internal/constants.ts
|
|
2
13
|
var NODE_RELATION = "is";
|
|
3
14
|
var DEFAULT_QUERY_LIMIT = 500;
|
|
@@ -24,82 +35,6 @@ function computeEdgeDocId(aUid, axbType, bUid) {
|
|
|
24
35
|
return `${shard}${SHARD_SEPARATOR}${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
|
-
// src/errors.ts
|
|
28
|
-
var FiregraphError = class extends Error {
|
|
29
|
-
constructor(message, code) {
|
|
30
|
-
super(message);
|
|
31
|
-
this.code = code;
|
|
32
|
-
this.name = "FiregraphError";
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
var NodeNotFoundError = class extends FiregraphError {
|
|
36
|
-
constructor(uid) {
|
|
37
|
-
super(`Node not found: ${uid}`, "NODE_NOT_FOUND");
|
|
38
|
-
this.name = "NodeNotFoundError";
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
var EdgeNotFoundError = class extends FiregraphError {
|
|
42
|
-
constructor(aUid, axbType, bUid) {
|
|
43
|
-
super(`Edge not found: ${aUid} -[${axbType}]-> ${bUid}`, "EDGE_NOT_FOUND");
|
|
44
|
-
this.name = "EdgeNotFoundError";
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
var ValidationError = class extends FiregraphError {
|
|
48
|
-
constructor(message, details) {
|
|
49
|
-
super(message, "VALIDATION_ERROR");
|
|
50
|
-
this.details = details;
|
|
51
|
-
this.name = "ValidationError";
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
var RegistryViolationError = class extends FiregraphError {
|
|
55
|
-
constructor(aType, axbType, bType) {
|
|
56
|
-
super(
|
|
57
|
-
`Unregistered triple: (${aType}) -[${axbType}]-> (${bType})`,
|
|
58
|
-
"REGISTRY_VIOLATION"
|
|
59
|
-
);
|
|
60
|
-
this.name = "RegistryViolationError";
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
var InvalidQueryError = class extends FiregraphError {
|
|
64
|
-
constructor(message) {
|
|
65
|
-
super(message, "INVALID_QUERY");
|
|
66
|
-
this.name = "InvalidQueryError";
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
var TraversalError = class extends FiregraphError {
|
|
70
|
-
constructor(message) {
|
|
71
|
-
super(message, "TRAVERSAL_ERROR");
|
|
72
|
-
this.name = "TraversalError";
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
var DynamicRegistryError = class extends FiregraphError {
|
|
76
|
-
constructor(message) {
|
|
77
|
-
super(message, "DYNAMIC_REGISTRY_ERROR");
|
|
78
|
-
this.name = "DynamicRegistryError";
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
var QuerySafetyError = class extends FiregraphError {
|
|
82
|
-
constructor(message) {
|
|
83
|
-
super(message, "QUERY_SAFETY");
|
|
84
|
-
this.name = "QuerySafetyError";
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
var RegistryScopeError = class extends FiregraphError {
|
|
88
|
-
constructor(aType, axbType, bType, scopePath, allowedIn) {
|
|
89
|
-
super(
|
|
90
|
-
`Type (${aType}) -[${axbType}]-> (${bType}) is not allowed at scope "${scopePath || "root"}". Allowed in: [${allowedIn.join(", ")}]`,
|
|
91
|
-
"REGISTRY_SCOPE"
|
|
92
|
-
);
|
|
93
|
-
this.name = "RegistryScopeError";
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
var MigrationError = class extends FiregraphError {
|
|
97
|
-
constructor(message) {
|
|
98
|
-
super(message, "MIGRATION_ERROR");
|
|
99
|
-
this.name = "MigrationError";
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
38
|
// src/json-schema.ts
|
|
104
39
|
import Ajv from "ajv";
|
|
105
40
|
import addFormats from "ajv-formats";
|
|
@@ -120,9 +55,7 @@ function compileSchema(schema, label) {
|
|
|
120
55
|
}
|
|
121
56
|
function jsonSchemaToFieldMeta(schema) {
|
|
122
57
|
if (!schema || schema.type !== "object" || !schema.properties) return [];
|
|
123
|
-
const requiredSet = new Set(
|
|
124
|
-
Array.isArray(schema.required) ? schema.required : []
|
|
125
|
-
);
|
|
58
|
+
const requiredSet = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
126
59
|
return Object.entries(schema.properties).map(
|
|
127
60
|
([name, prop]) => propertyToFieldMeta(name, prop, requiredSet.has(name))
|
|
128
61
|
);
|
|
@@ -365,6 +298,28 @@ function createRegistry(input) {
|
|
|
365
298
|
for (const [key, arr] of axbBuild) {
|
|
366
299
|
axbIndex.set(key, Object.freeze(arr));
|
|
367
300
|
}
|
|
301
|
+
const topologyIndex = /* @__PURE__ */ new Map();
|
|
302
|
+
const topologyBuild = /* @__PURE__ */ new Map();
|
|
303
|
+
const topologySeen = /* @__PURE__ */ new Map();
|
|
304
|
+
for (const entry of entries) {
|
|
305
|
+
if (!entry.targetGraph) continue;
|
|
306
|
+
let seen = topologySeen.get(entry.aType);
|
|
307
|
+
if (!seen) {
|
|
308
|
+
seen = /* @__PURE__ */ new Set();
|
|
309
|
+
topologySeen.set(entry.aType, seen);
|
|
310
|
+
}
|
|
311
|
+
if (seen.has(entry.targetGraph)) continue;
|
|
312
|
+
seen.add(entry.targetGraph);
|
|
313
|
+
const existing = topologyBuild.get(entry.aType);
|
|
314
|
+
if (existing) {
|
|
315
|
+
existing.push(entry);
|
|
316
|
+
} else {
|
|
317
|
+
topologyBuild.set(entry.aType, [entry]);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
for (const [key, arr] of topologyBuild) {
|
|
321
|
+
topologyIndex.set(key, Object.freeze(arr));
|
|
322
|
+
}
|
|
368
323
|
return {
|
|
369
324
|
lookup(aType, axbType, bType) {
|
|
370
325
|
return map.get(tripleKey(aType, axbType, bType))?.entry;
|
|
@@ -372,6 +327,9 @@ function createRegistry(input) {
|
|
|
372
327
|
lookupByAxbType(axbType) {
|
|
373
328
|
return axbIndex.get(axbType) ?? [];
|
|
374
329
|
},
|
|
330
|
+
getSubgraphTopology(aType) {
|
|
331
|
+
return topologyIndex.get(aType) ?? [];
|
|
332
|
+
},
|
|
375
333
|
validate(aType, axbType, bType, data, scopePath) {
|
|
376
334
|
const rec = map.get(tripleKey(aType, axbType, bType));
|
|
377
335
|
if (!rec) {
|
|
@@ -419,6 +377,21 @@ function createMergedRegistry(base, extension) {
|
|
|
419
377
|
}
|
|
420
378
|
return Object.freeze(merged);
|
|
421
379
|
},
|
|
380
|
+
getSubgraphTopology(aType) {
|
|
381
|
+
const baseResults = base.getSubgraphTopology(aType);
|
|
382
|
+
const extResults = extension.getSubgraphTopology(aType);
|
|
383
|
+
if (extResults.length === 0) return baseResults;
|
|
384
|
+
if (baseResults.length === 0) return extResults;
|
|
385
|
+
const seen = new Set(baseResults.map((e) => e.targetGraph));
|
|
386
|
+
const merged = [...baseResults];
|
|
387
|
+
for (const entry of extResults) {
|
|
388
|
+
if (!seen.has(entry.targetGraph)) {
|
|
389
|
+
seen.add(entry.targetGraph);
|
|
390
|
+
merged.push(entry);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return Object.freeze(merged);
|
|
394
|
+
},
|
|
422
395
|
validate(aType, axbType, bType, data, scopePath) {
|
|
423
396
|
if (baseKeys.has(tripleKey(aType, axbType, bType))) {
|
|
424
397
|
return base.validate(aType, axbType, bType, data, scopePath);
|
|
@@ -670,7 +643,7 @@ function hashSource(source) {
|
|
|
670
643
|
var _serializationModule = null;
|
|
671
644
|
async function loadSerialization() {
|
|
672
645
|
if (_serializationModule) return _serializationModule;
|
|
673
|
-
_serializationModule = await import("./serialization-
|
|
646
|
+
_serializationModule = await import("./serialization-ZZ7RSDRX.js");
|
|
674
647
|
return _serializationModule;
|
|
675
648
|
}
|
|
676
649
|
function defaultExecutor(source) {
|
|
@@ -1209,6 +1182,21 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1209
1182
|
getBackend() {
|
|
1210
1183
|
return this.backend;
|
|
1211
1184
|
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Snapshot of the currently-effective registry. Returns the merged view
|
|
1187
|
+
* used for domain-type validation and migration — in dynamic mode this is
|
|
1188
|
+
* `dynamicRegistry ?? staticRegistry ?? bootstrapRegistry`, so callers see
|
|
1189
|
+
* updates after `reloadRegistry()` without having to re-resolve anything.
|
|
1190
|
+
*
|
|
1191
|
+
* Exposed for backends that need topology access during bulk operations
|
|
1192
|
+
* (e.g. the Cloudflare DO backend's cross-DO cascade). Not part of the
|
|
1193
|
+
* public `GraphClient` surface.
|
|
1194
|
+
*
|
|
1195
|
+
* @internal
|
|
1196
|
+
*/
|
|
1197
|
+
getRegistrySnapshot() {
|
|
1198
|
+
return this.getCombinedRegistry();
|
|
1199
|
+
}
|
|
1212
1200
|
// ---------------------------------------------------------------------------
|
|
1213
1201
|
// Registry routing
|
|
1214
1202
|
// ---------------------------------------------------------------------------
|
|
@@ -1610,17 +1598,6 @@ export {
|
|
|
1610
1598
|
DEFAULT_QUERY_LIMIT,
|
|
1611
1599
|
computeNodeDocId,
|
|
1612
1600
|
computeEdgeDocId,
|
|
1613
|
-
FiregraphError,
|
|
1614
|
-
NodeNotFoundError,
|
|
1615
|
-
EdgeNotFoundError,
|
|
1616
|
-
ValidationError,
|
|
1617
|
-
RegistryViolationError,
|
|
1618
|
-
InvalidQueryError,
|
|
1619
|
-
TraversalError,
|
|
1620
|
-
DynamicRegistryError,
|
|
1621
|
-
QuerySafetyError,
|
|
1622
|
-
RegistryScopeError,
|
|
1623
|
-
MigrationError,
|
|
1624
1601
|
compileSchema,
|
|
1625
1602
|
jsonSchemaToFieldMeta,
|
|
1626
1603
|
applyMigrationChain,
|
|
@@ -1650,4 +1627,4 @@ export {
|
|
|
1650
1627
|
GraphClientImpl,
|
|
1651
1628
|
createGraphClientFromBackend
|
|
1652
1629
|
};
|
|
1653
|
-
//# sourceMappingURL=chunk-
|
|
1630
|
+
//# sourceMappingURL=chunk-LZOIQHYN.js.map
|