@typicalday/firegraph 0.9.0 → 0.11.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.
Files changed (60) hide show
  1. package/README.md +93 -90
  2. package/bin/firegraph.mjs +21 -7
  3. package/dist/backend-U-MLShlg.d.ts +97 -0
  4. package/dist/backend-np4gEVhB.d.cts +97 -0
  5. package/dist/backend.cjs.map +1 -1
  6. package/dist/backend.d.cts +7 -6
  7. package/dist/backend.d.ts +7 -6
  8. package/dist/backend.js +1 -1
  9. package/dist/backend.js.map +1 -1
  10. package/dist/{chunk-EVUM6ORB.js → chunk-6SB34IPQ.js} +76 -8
  11. package/dist/chunk-6SB34IPQ.js.map +1 -0
  12. package/dist/{chunk-SU4FNLC3.js → chunk-EEKWRX5E.js} +1 -1
  13. package/dist/{chunk-SU4FNLC3.js.map → chunk-EEKWRX5E.js.map} +1 -1
  14. package/dist/{chunk-YLGXLEUE.js → chunk-GJVVRTQT.js} +5 -14
  15. package/dist/chunk-GJVVRTQT.js.map +1 -0
  16. package/dist/{chunk-GLOVWKQH.js → chunk-R7CRGYY4.js} +1 -1
  17. package/dist/{chunk-GLOVWKQH.js.map → chunk-R7CRGYY4.js.map} +1 -1
  18. package/dist/{do-sqlite.cjs → cloudflare/index.cjs} +1659 -1422
  19. package/dist/cloudflare/index.cjs.map +1 -0
  20. package/dist/cloudflare/index.d.cts +529 -0
  21. package/dist/cloudflare/index.d.ts +529 -0
  22. package/dist/cloudflare/index.js +934 -0
  23. package/dist/cloudflare/index.js.map +1 -0
  24. package/dist/codegen/index.cjs +4 -13
  25. package/dist/codegen/index.cjs.map +1 -1
  26. package/dist/codegen/index.d.cts +1 -1
  27. package/dist/codegen/index.d.ts +1 -1
  28. package/dist/codegen/index.js +1 -1
  29. package/dist/index.cjs +144 -132
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +116 -27
  32. package/dist/index.d.ts +116 -27
  33. package/dist/index.js +77 -123
  34. package/dist/index.js.map +1 -1
  35. package/dist/query-client/index.cjs.map +1 -1
  36. package/dist/query-client/index.js +1 -1
  37. package/dist/{scope-path-BtajqNK5.d.ts → scope-path-B1G3YiA7.d.cts} +5 -100
  38. package/dist/{scope-path-D2mNENJ-.d.cts → scope-path-B1G3YiA7.d.ts} +5 -100
  39. package/dist/{types-DfWVTsMn.d.ts → types-BGWxcpI_.d.cts} +92 -1
  40. package/dist/{types-DfWVTsMn.d.cts → types-BGWxcpI_.d.ts} +92 -1
  41. package/package.json +13 -17
  42. package/dist/chunk-EVUM6ORB.js.map +0 -1
  43. package/dist/chunk-SZ6W4VAS.js +0 -701
  44. package/dist/chunk-SZ6W4VAS.js.map +0 -1
  45. package/dist/chunk-YLGXLEUE.js.map +0 -1
  46. package/dist/d1.cjs +0 -2421
  47. package/dist/d1.cjs.map +0 -1
  48. package/dist/d1.d.cts +0 -54
  49. package/dist/d1.d.ts +0 -54
  50. package/dist/d1.js +0 -76
  51. package/dist/d1.js.map +0 -1
  52. package/dist/do-sqlite.cjs.map +0 -1
  53. package/dist/do-sqlite.d.cts +0 -41
  54. package/dist/do-sqlite.d.ts +0 -41
  55. package/dist/do-sqlite.js +0 -79
  56. package/dist/do-sqlite.js.map +0 -1
  57. package/dist/editor/client/assets/index-Bq2bfzeY.js +0 -411
  58. package/dist/editor/client/assets/index-CJ4m_EOL.css +0 -1
  59. package/dist/editor/client/index.html +0 -16
  60. package/dist/editor/server/index.mjs +0 -51511
@@ -0,0 +1,529 @@
1
+ import { W as WritableRecord, U as UpdatePayload, S as StorageBackend, T as TransactionBackend, B as BatchBackend } from '../backend-np4gEVhB.cjs';
2
+ import { c as GraphRegistry, I as IndexSpec, i as QueryFilter, z as QueryOptions, C as CascadeResult, F as FindEdgesParams, m as BulkOptions, o as BulkResult, a as GraphClient, D as DynamicGraphClient, S as StoredGraphRecord, d as GraphReader, G as GraphClientOptions, e as DynamicRegistryConfig } from '../types-BGWxcpI_.cjs';
3
+ import '@google-cloud/firestore';
4
+
5
+ /**
6
+ * SQL compilation for the DO SQLite backend.
7
+ *
8
+ * Every `FiregraphDO` instance owns one SQLite database holding exactly one
9
+ * subgraph's triples — there is no `scope` column and no scope discriminator
10
+ * on any statement. Contrast with `src/internal/sqlite-sql.ts`, which
11
+ * carries a scope prefix on every read and write for the legacy shared-table
12
+ * D1/DO SQLite backend.
13
+ *
14
+ * Filter compilation, JSON-path validation, and value binding mirror the
15
+ * legacy module so the query planner (`src/query.ts`) emits the same
16
+ * `QueryFilter[]` shape regardless of backend.
17
+ */
18
+
19
+ /**
20
+ * Wire representation of a stored record across the DO RPC boundary.
21
+ *
22
+ * Durable Object RPC uses structured clone, which preserves plain data but
23
+ * drops user-defined class prototypes — a `GraphTimestampImpl` from the DO
24
+ * arrives at the client as a plain `{seconds, nanoseconds}` object without
25
+ * its `toMillis()` / `toDate()` methods. To avoid silent `.toMillis is not a
26
+ * function` crashes downstream, records returned from DO RPC carry the two
27
+ * timestamps as plain millisecond numbers in `createdAtMs` / `updatedAtMs`;
28
+ * the client-side backend rewraps them as `GraphTimestampImpl` via
29
+ * `hydrateDORecord` before handing the record to the GraphClient.
30
+ */
31
+ interface DORecordWire {
32
+ aType: string;
33
+ aUid: string;
34
+ axbType: string;
35
+ bType: string;
36
+ bUid: string;
37
+ data: Record<string, unknown>;
38
+ v?: number;
39
+ createdAtMs: number;
40
+ updatedAtMs: number;
41
+ }
42
+
43
+ /**
44
+ * `FiregraphDO` — the Durable Object class that holds a single subgraph's
45
+ * triples.
46
+ *
47
+ * The Cloudflare-native Firegraph design puts each subgraph in its own DO
48
+ * instance: the root graph in one DO, `client.subgraph(uid, 'memories')` in
49
+ * another, nested subgraphs in their own DOs, and so on. Each DO owns a
50
+ * private flat SQLite database (`src/cloudflare/schema.ts`) — no `scope`
51
+ * column, no shared table, no discriminator. The client routes to a
52
+ * specific DO by hashing a stable name (`namespace.idFromName(storageKey)`);
53
+ * on first RPC, the DO lazily materializes and runs the schema DDL.
54
+ *
55
+ * ## Using it in a Worker
56
+ *
57
+ * Bind the class in `wrangler.toml` and re-export it from the Worker entry:
58
+ *
59
+ * ```toml
60
+ * [[durable_objects.bindings]]
61
+ * name = "GRAPH"
62
+ * class_name = "FiregraphDO"
63
+ *
64
+ * [[migrations]]
65
+ * tag = "v1"
66
+ * new_sqlite_classes = ["FiregraphDO"]
67
+ * ```
68
+ *
69
+ * ```ts
70
+ * // worker.ts
71
+ * export { FiregraphDO } from '@typicalday/firegraph/cloudflare';
72
+ * ```
73
+ *
74
+ * To add custom RPC methods, extend the class:
75
+ *
76
+ * ```ts
77
+ * export class GraphDO extends FiregraphDO {
78
+ * async myCustomRpc() { ... }
79
+ * }
80
+ * ```
81
+ *
82
+ * ## Why a plain class (not `extends DurableObject`)?
83
+ *
84
+ * Cloudflare accepts any class with the `(state, env)` constructor shape as
85
+ * a DO class. Extending `DurableObject` from `cloudflare:workers` would pull
86
+ * a runtime import into this module and prevent it from loading in Node
87
+ * tests. The plain-class form keeps this file runtime-neutral — the only
88
+ * Cloudflare thing we touch is `ctx.storage.sql`, typed via local minimal
89
+ * interfaces.
90
+ */
91
+
92
+ interface DOSqlCursor<T> {
93
+ toArray(): T[];
94
+ }
95
+ interface DOSqlExecutor {
96
+ exec<T = Record<string, unknown>>(sql: string, ...params: unknown[]): DOSqlCursor<T>;
97
+ }
98
+ interface DOStorage {
99
+ sql: DOSqlExecutor;
100
+ transactionSync<T>(fn: () => T): T;
101
+ }
102
+ interface DurableObjectStateLike {
103
+ readonly storage: DOStorage;
104
+ blockConcurrencyWhile<T>(fn: () => Promise<T>): Promise<T>;
105
+ }
106
+ /**
107
+ * One op in a `batch()` RPC call. Discriminated by `kind` so the DO can
108
+ * dispatch to the correct compiler.
109
+ */
110
+ type BatchOp = {
111
+ kind: 'set';
112
+ docId: string;
113
+ record: WritableRecord;
114
+ } | {
115
+ kind: 'update';
116
+ docId: string;
117
+ update: UpdatePayload;
118
+ } | {
119
+ kind: 'delete';
120
+ docId: string;
121
+ };
122
+ /**
123
+ * Options controlling `FiregraphDO` construction.
124
+ */
125
+ interface FiregraphDOOptions {
126
+ /** Table name for firegraph triples. Default: `firegraph`. */
127
+ table?: string;
128
+ /** Run schema DDL on first boot. Default: `true`. */
129
+ autoMigrate?: boolean;
130
+ /**
131
+ * Registry whose per-entry `indexes` get compiled into `CREATE INDEX`
132
+ * statements during schema bootstrap. Supply the same registry you pass
133
+ * to `createGraphClient` on the Worker side to keep DO and client in sync.
134
+ */
135
+ registry?: GraphRegistry;
136
+ /**
137
+ * Replaces the built-in core index preset
138
+ * (`DEFAULT_CORE_INDEXES`). Supply this when the default set of
139
+ * `(aUid, axbType)`, `(axbType, bUid)`, etc. composites doesn't fit your
140
+ * query shapes — e.g., you want descending timestamps or a reduced set.
141
+ * Entry-level `RegistryEntry.indexes` remain additive on top.
142
+ *
143
+ * Pass `[]` to disable core indexes entirely (advanced — only safe when
144
+ * the provided `registry`'s entries cover every query shape your app
145
+ * issues).
146
+ */
147
+ coreIndexes?: IndexSpec[];
148
+ }
149
+ declare class FiregraphDO {
150
+ /** @internal — exposed for subclass access, not part of the public RPC. */
151
+ protected readonly ctx: DurableObjectStateLike;
152
+ /** @internal — exposed for subclass access; opaque to this class. */
153
+ protected readonly env: unknown;
154
+ /** @internal — table name used by every compiled statement. */
155
+ protected readonly table: string;
156
+ /** @internal — registry consulted by `runSchema` for per-entry indexes. */
157
+ protected readonly registry?: GraphRegistry;
158
+ /** @internal — overrides `DEFAULT_CORE_INDEXES` when set. */
159
+ protected readonly coreIndexes?: IndexSpec[];
160
+ constructor(ctx: DurableObjectStateLike, env: unknown, options?: FiregraphDOOptions);
161
+ _fgGetDoc(docId: string): Promise<DORecordWire | null>;
162
+ _fgQuery(filters: QueryFilter[], options?: QueryOptions): Promise<DORecordWire[]>;
163
+ _fgSetDoc(docId: string, record: WritableRecord): Promise<void>;
164
+ _fgUpdateDoc(docId: string, update: UpdatePayload): Promise<void>;
165
+ _fgDeleteDoc(docId: string): Promise<void>;
166
+ /**
167
+ * Execute a list of write ops atomically. DO SQLite's `transactionSync`
168
+ * provides real atomicity — either every statement commits or none do.
169
+ * No statement-count cap applies (contrast with D1's ~100-statement batch
170
+ * limit), so the caller can submit as many ops as they like in one call.
171
+ */
172
+ _fgBatch(ops: BatchOp[]): Promise<void>;
173
+ _fgRemoveNodeCascade(uid: string): Promise<CascadeResult>;
174
+ _fgBulkRemoveEdges(params: FindEdgesParams, _options?: BulkOptions): Promise<BulkResult>;
175
+ /**
176
+ * Wipe every row. Called by the client when tearing down a subgraph DO as
177
+ * part of cascade — the DO itself can't be destroyed (DO IDs persist
178
+ * forever), but its storage can be emptied.
179
+ */
180
+ _fgDestroy(): Promise<void>;
181
+ protected runSchema(): void;
182
+ private execAll;
183
+ private execRun;
184
+ }
185
+
186
+ /**
187
+ * Client-side `StorageBackend` that forwards every operation to a
188
+ * `FiregraphDO` instance over Durable Object RPC.
189
+ *
190
+ * One `DORPCBackend` corresponds to one DO — the root graph's DO, or a
191
+ * subgraph's DO. `subgraph()` returns a new `DORPCBackend` bound to a
192
+ * different DO, identified by deriving a new stable name from the chain of
193
+ * parent UIDs and subgraph names. The library uses `namespace.idFromName()`
194
+ * on that key, so two clients with the same key always reach the same DO.
195
+ *
196
+ * Key invariants:
197
+ *
198
+ * - There is no shared table and no `scope` column. Each DO owns its own
199
+ * flat SQLite database; isolation is physical.
200
+ * - Interactive transactions throw `UNSUPPORTED_OPERATION` — holding a
201
+ * synchronous SQLite transaction across async RPC calls would block the
202
+ * DO's single-threaded executor (see `transactionsUnsupported` below).
203
+ * - `findEdgesGlobal` is deliberately left undefined on this class. The
204
+ * `GraphClient` surfaces the generic "not supported by current storage
205
+ * backend" error before running any query planning, which is both
206
+ * accurate and sidesteps the misleading `QuerySafetyError` that would
207
+ * otherwise fire for scan-unsafe calls. `createDOClient`'s docstring
208
+ * explains the design rationale (no collection-group index across DOs).
209
+ * - `removeNodeCascade` cascades across DOs: when a registry accessor is
210
+ * wired, the backend walks `registry.getSubgraphTopology(aType)` for the
211
+ * node being removed and destroys every descendant subgraph DO before
212
+ * deleting the node itself. Pass `{ deleteSubcollections: false }` to
213
+ * disable the cross-DO fan-out (parent node is still removed, child DOs
214
+ * are left intact — matching the Firestore/SQLite backends' semantic).
215
+ * Without an accessor (e.g. registry-less clients) it cascades within
216
+ * the current DO only.
217
+ */
218
+
219
+ interface DurableObjectIdLike {
220
+ toString(): string;
221
+ }
222
+ /**
223
+ * The RPC surface this backend calls on the DO stub. Every method matches a
224
+ * `_fg…` method on `FiregraphDO`. Kept structurally typed so users can bring
225
+ * their own subclass without the types having to know about it.
226
+ *
227
+ * Reads return `DORecordWire` (plain data — safe through DO structured
228
+ * clone); the backend rewraps each record via `hydrateDORecord` before
229
+ * handing it to the GraphClient, which expects `GraphTimestampImpl`
230
+ * instances (not plain `{seconds, nanoseconds}` objects).
231
+ */
232
+ interface FiregraphStub {
233
+ _fgGetDoc(docId: string): Promise<DORecordWire | null>;
234
+ _fgQuery(filters: QueryFilter[], options?: QueryOptions): Promise<DORecordWire[]>;
235
+ _fgSetDoc(docId: string, record: WritableRecord): Promise<void>;
236
+ _fgUpdateDoc(docId: string, update: UpdatePayload): Promise<void>;
237
+ _fgDeleteDoc(docId: string): Promise<void>;
238
+ _fgBatch(ops: BatchOp[]): Promise<void>;
239
+ _fgRemoveNodeCascade(uid: string): Promise<CascadeResult>;
240
+ _fgBulkRemoveEdges(params: FindEdgesParams, options?: BulkOptions): Promise<BulkResult>;
241
+ _fgDestroy(): Promise<void>;
242
+ }
243
+ interface FiregraphNamespace {
244
+ idFromName(name: string): DurableObjectIdLike;
245
+ get(id: DurableObjectIdLike): FiregraphStub;
246
+ }
247
+ interface DORPCBackendOptions {
248
+ /** Scope path (names-only chain, used for `allowedIn`). Default: `''`. */
249
+ scopePath?: string;
250
+ /**
251
+ * Opaque storage key used to derive the DO instance via
252
+ * `namespace.idFromName(storageKey)`. Defaults to the root key passed to
253
+ * `createDOClient` when the backend is first created.
254
+ */
255
+ storageKey: string;
256
+ /**
257
+ * Live registry accessor used by `removeNodeCascade` to consult the
258
+ * subgraph topology and fan out `_fgDestroy` calls to child subgraph DOs.
259
+ *
260
+ * A function (not a snapshot) so that dynamic-registry clients see the
261
+ * latest definitions after `reloadRegistry()`. Wired by `createDOClient`
262
+ * via a forward reference to the constructed `GraphClient`. When
263
+ * `undefined`, cross-DO cascade is disabled and `removeNodeCascade`
264
+ * cascades within this DO only.
265
+ * @internal
266
+ */
267
+ registryAccessor?: () => GraphRegistry | undefined;
268
+ /**
269
+ * Factory used by `createSiblingClient` to construct a peer `GraphClient`
270
+ * that shares this client's namespace, registry, and other options but
271
+ * targets a different root DO. Wired by `createDOClient`. Leaving it
272
+ * `undefined` (e.g. when `DORPCBackend` is instantiated directly) disables
273
+ * sibling-client construction — `createSiblingClient` will throw.
274
+ *
275
+ * The union return type mirrors `createDOClient`'s two overloads: dynamic
276
+ * mode yields a `DynamicGraphClient`, everything else yields a plain
277
+ * `GraphClient`. `createSiblingClient` narrows at the boundary via its
278
+ * own overload signatures.
279
+ * @internal
280
+ */
281
+ makeSiblingClient?: (siblingStorageKey: string) => GraphClient | DynamicGraphClient;
282
+ }
283
+ declare class DORPCBackend implements StorageBackend {
284
+ readonly collectionPath = "firegraph";
285
+ readonly scopePath: string;
286
+ /** @internal */
287
+ readonly storageKey: string;
288
+ /** @internal */
289
+ readonly namespace: FiregraphNamespace;
290
+ private readonly registryAccessor?;
291
+ /** @internal — see `DORPCBackendOptions.makeSiblingClient` for the union-type rationale. */
292
+ readonly makeSiblingClient?: (siblingStorageKey: string) => GraphClient | DynamicGraphClient;
293
+ private cachedStub;
294
+ constructor(namespace: FiregraphNamespace, options: DORPCBackendOptions);
295
+ private get stub();
296
+ getDoc(docId: string): Promise<StoredGraphRecord | null>;
297
+ query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
298
+ setDoc(docId: string, record: WritableRecord): Promise<void>;
299
+ updateDoc(docId: string, update: UpdatePayload): Promise<void>;
300
+ deleteDoc(docId: string): Promise<void>;
301
+ runTransaction<T>(_fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;
302
+ createBatch(): BatchBackend;
303
+ subgraph(parentNodeUid: string, name: string): StorageBackend;
304
+ removeNodeCascade(uid: string, reader: GraphReader, options?: BulkOptions): Promise<CascadeResult>;
305
+ bulkRemoveEdges(params: FindEdgesParams, _reader: GraphReader, options?: BulkOptions): Promise<BulkResult>;
306
+ /**
307
+ * Wipe this DO's storage. The DO itself can't be deleted — its ID
308
+ * persists forever — but its rows can be emptied, which is what the
309
+ * cascade walk does on every descendant subgraph DO.
310
+ *
311
+ * Exposed on the concrete class (not `StorageBackend`) so generic
312
+ * backend code doesn't reach for it.
313
+ */
314
+ destroy(): Promise<void>;
315
+ /**
316
+ * Tear down every descendant subgraph DO, then wipe this DO's own rows.
317
+ *
318
+ * Invoked by cross-DO cascade: for each node in this DO we enumerate the
319
+ * subgraph topology and recurse into child DOs depth-first before
320
+ * wiping the current DO. The current DO's own rows are destroyed last so
321
+ * that a partial failure mid-recursion leaves the caller's reader able
322
+ * to discover what's still present.
323
+ *
324
+ * @internal
325
+ */
326
+ destroyRecursively(registry: GraphRegistry): Promise<void>;
327
+ }
328
+
329
+ /**
330
+ * `createDOClient` — the user-facing factory for the Cloudflare DO backend.
331
+ *
332
+ * Given a Durable Object namespace binding and a stable root key, returns a
333
+ * `GraphClient` that speaks to a `FiregraphDO` instance. The root key is
334
+ * hashed via `namespace.idFromName()` to derive the DO ID, so two clients
335
+ * instantiated with the same key always reach the same DO — that's how we
336
+ * achieve "subgraphs are auto-provisioned" without a separate allocation
337
+ * step. Subsequent `.subgraph(uid, name)` calls derive child DO IDs from the
338
+ * extended key chain (`${key}/${uid}/${name}`).
339
+ *
340
+ * ## What's supported
341
+ *
342
+ * - **Static registries.** Pass `registry` and every read/write validates
343
+ * and migrates exactly like the Firestore/SQLite backends.
344
+ * - **Dynamic registries.** Pass `registryMode: { mode: 'dynamic' }` and
345
+ * call `defineNodeType` / `defineEdgeType` / `reloadRegistry` as with
346
+ * any other backend. Meta-types live in the root DO by default; pass
347
+ * `registryMode: { mode: 'dynamic', collection: 'meta-root' }` to put
348
+ * them in a separately-addressed DO. Merged mode (static `registry`
349
+ * plus `registryMode`) is also supported.
350
+ * - **Cross-DO cascade.** `removeNodeCascade` consults the registry's
351
+ * subgraph topology and wipes every descendant subgraph DO before
352
+ * deleting the node. Requires a registry; without one it cascades
353
+ * within the current DO only. Pass `{ deleteSubcollections: false }` to
354
+ * keep the node removal but leave every descendant DO intact (mirrors
355
+ * the Firestore/SQLite `bulk` option). In dynamic mode the accessor is
356
+ * live, so cascading a node whose subgraph topology was just added via
357
+ * `defineEdgeType` correctly fans out to the new descendants — **but
358
+ * only after a `reloadRegistry()` call**. A cascade invoked between
359
+ * `defineEdgeType` and `reloadRegistry` sees only the pre-define
360
+ * topology and silently skips newly-declared subgraphs. This is the
361
+ * same trade-off every dynamic-registry backend makes.
362
+ * - **Static migrations** on registry entries (`migrations`,
363
+ * `migrationWriteBack`, `migrationSandbox`) run in-process on the
364
+ * Worker and don't cross the DO RPC boundary. The read-path migration
365
+ * pipeline lives in `GraphClient`, not in `FiregraphDO`.
366
+ *
367
+ * ## Performance note on cascade
368
+ *
369
+ * Cross-DO cascade instantiates (via `namespace.get`) every declared child
370
+ * subgraph DO even when it's empty — Durable Objects have no "does this ID
371
+ * exist" primitive, and the cheapest way to tear a DO down is to issue one
372
+ * RPC. For a node with N declared subgraph segments, expect N+1 RPCs per
373
+ * cascade (one per child DO wipe, one for the parent). Topology width is
374
+ * typically small and bounded by registry size, but keep it in mind for
375
+ * wide fan-out designs.
376
+ *
377
+ * ## What's not supported
378
+ *
379
+ * - **Interactive transactions.** Would require pinning a SQLite
380
+ * transaction open across async RPC calls — see `backend.ts` for the
381
+ * rationale. Use `batch()` for atomic multi-write commits.
382
+ * - **`findEdgesGlobal`.** Cross-DO collection-group queries don't map
383
+ * onto "one DO owns one subgraph's rows" — each subgraph is a separate
384
+ * DO with private SQLite and there's no namespace-wide catalog. The
385
+ * method is intentionally undefined on the backend so `GraphClient`
386
+ * surfaces a generic `UNSUPPORTED_OPERATION` error immediately, before
387
+ * any query planning. Callers that need this should maintain an
388
+ * application-level index DO or run an explicit traversal via
389
+ * `client.subgraph(...)`.
390
+ *
391
+ * ## Binding example
392
+ *
393
+ * ```ts
394
+ * // worker.ts
395
+ * export { FiregraphDO } from '@typicalday/firegraph/cloudflare';
396
+ *
397
+ * export default {
398
+ * async fetch(req: Request, env: Env) {
399
+ * const client = createDOClient(env.GRAPH, 'main', { registry });
400
+ * const project = await client.getNode('project', projectUid);
401
+ * return Response.json(project);
402
+ * },
403
+ * };
404
+ * ```
405
+ *
406
+ * ```toml
407
+ * # wrangler.toml
408
+ * [[durable_objects.bindings]]
409
+ * name = "GRAPH"
410
+ * class_name = "FiregraphDO"
411
+ *
412
+ * [[migrations]]
413
+ * tag = "v1"
414
+ * new_sqlite_classes = ["FiregraphDO"]
415
+ * ```
416
+ */
417
+
418
+ /**
419
+ * Options for `createDOClient`. Same shape as `GraphClientOptions`; the DO
420
+ * backend does not expose a table label of its own — the DO owns its SQLite
421
+ * schema (see `FiregraphDOOptions.table`, defaults to `'firegraph'`) and
422
+ * that choice isn't surfaced through the client factory.
423
+ */
424
+ type DOClientOptions = GraphClientOptions;
425
+ /**
426
+ * Create a `GraphClient` backed by a `FiregraphDO` Durable Object.
427
+ *
428
+ * @param namespace The DO namespace binding (`env.GRAPH` in Worker code).
429
+ * @param rootKey Stable name for the root graph's DO. The same value
430
+ * always addresses the same DO — treat it as the graph's
431
+ * identity. Subgraph DOs derive their names from this.
432
+ * @param options Optional `GraphClientOptions` (registry, query mode,
433
+ * `registryMode` for dynamic registries, etc.).
434
+ * When `registryMode` is set the return type is
435
+ * narrowed to `DynamicGraphClient`.
436
+ */
437
+ declare function createDOClient(namespace: FiregraphNamespace, rootKey: string, options: DOClientOptions & {
438
+ registryMode: DynamicRegistryConfig;
439
+ }): DynamicGraphClient;
440
+ declare function createDOClient(namespace: FiregraphNamespace, rootKey: string, options?: DOClientOptions): GraphClient;
441
+ /**
442
+ * Construct a peer `GraphClient` that shares `client`'s DO namespace and
443
+ * construction options but targets a different root DO (i.e. a different
444
+ * root key — typically another tenant, workspace, or shard).
445
+ *
446
+ * This is the cheap, ergonomic way to talk to another root graph from
447
+ * inside a single Worker request without re-plumbing `createDOClient`'s
448
+ * arguments. The namespace binding plus the options snapshot captured at
449
+ * the original `createDOClient` call (registry, query mode, migration
450
+ * sandbox, `registryMode`, etc.) are inherited by the sibling.
451
+ *
452
+ * Works from any DO-backed client — root or subgraph. Passing a client
453
+ * that wasn't produced by `createDOClient` (e.g. a Firestore-backed
454
+ * client, or a `DORPCBackend` instantiated directly without the sibling
455
+ * factory wired in) throws `UNSUPPORTED_OPERATION` with an explanation.
456
+ *
457
+ * ## Dynamic-registry caveat
458
+ *
459
+ * When the original client uses `registryMode: 'dynamic'`, siblings
460
+ * inherit the *config* (so they're also dynamic clients) but NOT the
461
+ * compiled runtime state. Meta-type nodes and `reloadRegistry()` results
462
+ * are per-client; a sibling's `defineNodeType`/`defineEdgeType` calls go
463
+ * to that sibling's own root DO, and its registry must be independently
464
+ * populated and reloaded. If every tenant shares the same schema, pass a
465
+ * static `registry` instead of dynamic mode — static registries ARE
466
+ * inherited verbatim.
467
+ *
468
+ * @param client A client previously returned by `createDOClient`.
469
+ * @param siblingRootKey Root key for the peer DO. Same validation rules
470
+ * as `createDOClient`'s `rootKey`: non-empty,
471
+ * no `/`.
472
+ */
473
+ declare function createSiblingClient(client: GraphClient, siblingRootKey: string): GraphClient;
474
+ declare function createSiblingClient(client: DynamicGraphClient, siblingRootKey: string): DynamicGraphClient;
475
+
476
+ /**
477
+ * Flat SQLite schema for a single firegraph DO.
478
+ *
479
+ * Each `FiregraphDO` instance owns its own SQLite database and holds exactly
480
+ * one subgraph's triples. Subgraph isolation is physical (one DO per
481
+ * subgraph), so there is no `scope` column — every row in this DO belongs to
482
+ * the same logical scope. This is the Cloudflare-native design: the scope
483
+ * discriminator used by the legacy shared-table SQLite backend
484
+ * (`src/internal/sqlite-schema.ts`) does not exist here.
485
+ *
486
+ * Document IDs:
487
+ * - Nodes: the UID itself
488
+ * - Edges: `shard:aUid:axbType:bUid`
489
+ *
490
+ * Indexes come from two sources and are appended to the DDL list:
491
+ *
492
+ * 1. The core preset (`DEFAULT_CORE_INDEXES`), overridable per-DO via
493
+ * `FiregraphDOOptions.coreIndexes`.
494
+ * 2. Per-registry-entry `indexes` declared on `RegistryEntry` (from code or
495
+ * `meta.json` via entity discovery).
496
+ *
497
+ * Both sets are deduplicated by canonical fingerprint, so declaring the same
498
+ * composite twice (e.g., by preset + registry entry) collapses to one
499
+ * `CREATE INDEX`.
500
+ */
501
+
502
+ /**
503
+ * Options controlling DDL emission for `buildDOSchemaStatements`.
504
+ */
505
+ interface BuildDOSchemaOptions {
506
+ /**
507
+ * Replaces the built-in core preset. Defaults to `DEFAULT_CORE_INDEXES`.
508
+ * Pass `[]` to disable core indexes entirely.
509
+ */
510
+ coreIndexes?: IndexSpec[];
511
+ /**
512
+ * Registry contributing per-triple `indexes` declarations. Entries with
513
+ * no `indexes` field are ignored; the rest are flattened and deduplicated
514
+ * against the core preset by canonical fingerprint.
515
+ */
516
+ registry?: GraphRegistry;
517
+ }
518
+ /**
519
+ * DDL statements that create the firegraph table and its indexes. Returned
520
+ * as separate statements because DO SQLite's `exec()` runs one statement per
521
+ * call. Run via `FiregraphDO.ensureSchema()` on DO boot.
522
+ *
523
+ * The CREATE TABLE statement is always first; index statements follow in
524
+ * deterministic order. Same specs across runs produce the same statements,
525
+ * so `CREATE INDEX IF NOT EXISTS` is idempotent.
526
+ */
527
+ declare function buildDOSchemaStatements(table: string, options?: BuildDOSchemaOptions): string[];
528
+
529
+ export { type BatchOp, type BuildDOSchemaOptions, type DOClientOptions, DORPCBackend, type DORPCBackendOptions, type DOSqlCursor, type DOSqlExecutor, type DOStorage, type DurableObjectIdLike, type DurableObjectStateLike, FiregraphDO, type FiregraphDOOptions, type FiregraphNamespace, type FiregraphStub, buildDOSchemaStatements, createDOClient, createSiblingClient };