@typicalday/firegraph 0.11.2 → 0.12.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 (44) hide show
  1. package/README.md +38 -5
  2. package/dist/backend-BsR0lnFL.d.ts +200 -0
  3. package/dist/backend-Ct-fLlkG.d.cts +200 -0
  4. package/dist/backend.cjs +143 -2
  5. package/dist/backend.cjs.map +1 -1
  6. package/dist/backend.d.cts +3 -3
  7. package/dist/backend.d.ts +3 -3
  8. package/dist/backend.js +13 -4
  9. package/dist/backend.js.map +1 -1
  10. package/dist/chunk-AWW4MUJ5.js +245 -0
  11. package/dist/chunk-AWW4MUJ5.js.map +1 -0
  12. package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
  13. package/dist/chunk-C2QMD7RY.js.map +1 -0
  14. package/dist/chunk-EQJUUVFG.js +14 -0
  15. package/dist/chunk-EQJUUVFG.js.map +1 -0
  16. package/dist/{chunk-NJSOD64C.js → chunk-HONQY4HF.js} +80 -17
  17. package/dist/chunk-HONQY4HF.js.map +1 -0
  18. package/dist/cloudflare/index.cjs +458 -73
  19. package/dist/cloudflare/index.cjs.map +1 -1
  20. package/dist/cloudflare/index.d.cts +8 -5
  21. package/dist/cloudflare/index.d.ts +8 -5
  22. package/dist/cloudflare/index.js +234 -56
  23. package/dist/cloudflare/index.js.map +1 -1
  24. package/dist/codegen/index.d.cts +1 -1
  25. package/dist/codegen/index.d.ts +1 -1
  26. package/dist/index.cjs +271 -36
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +21 -69
  29. package/dist/index.d.ts +21 -69
  30. package/dist/index.js +58 -28
  31. package/dist/index.js.map +1 -1
  32. package/dist/registry-B1qsVL0E.d.cts +64 -0
  33. package/dist/registry-Fi074zVa.d.ts +64 -0
  34. package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
  35. package/dist/{types-BGWxcpI_.d.cts → types-DxYLy8Ol.d.cts} +36 -2
  36. package/dist/{types-BGWxcpI_.d.ts → types-DxYLy8Ol.d.ts} +36 -2
  37. package/package.json +1 -1
  38. package/dist/backend-U-MLShlg.d.ts +0 -97
  39. package/dist/backend-np4gEVhB.d.cts +0 -97
  40. package/dist/chunk-5753Y42M.js.map +0 -1
  41. package/dist/chunk-NJSOD64C.js.map +0 -1
  42. package/dist/chunk-R7CRGYY4.js +0 -94
  43. package/dist/chunk-R7CRGYY4.js.map +0 -1
  44. /package/dist/{serialization-ZZ7RSDRX.js.map → serialization-OE2PFZMY.js.map} +0 -0
@@ -0,0 +1,64 @@
1
+ import { R as RegistryEntry, e as GraphRegistry, l as GraphReader, i as MigrationExecutor, b as DiscoveryResult } from './types-DxYLy8Ol.js';
2
+
3
+ /** The aType used for node type definition meta-nodes. */
4
+ declare const META_NODE_TYPE = "nodeType";
5
+ /** The aType used for edge type definition meta-nodes. */
6
+ declare const META_EDGE_TYPE = "edgeType";
7
+ /** JSON Schema for the `data` payload of a `nodeType` meta-node. */
8
+ declare const NODE_TYPE_SCHEMA: object;
9
+ /** JSON Schema for the `data` payload of an `edgeType` meta-node. */
10
+ declare const EDGE_TYPE_SCHEMA: object;
11
+ /** Registry entries for the two meta-types (always present). */
12
+ declare const BOOTSTRAP_ENTRIES: readonly RegistryEntry[];
13
+ declare function createBootstrapRegistry(): GraphRegistry;
14
+ /**
15
+ * Generate a deterministic UID for a meta-type definition.
16
+ * This ensures that defining the same type name always targets the same
17
+ * Firestore document, enabling upsert semantics.
18
+ *
19
+ * Format: 21-char base64url substring of SHA-256(`metaType:name`).
20
+ */
21
+ declare function generateDeterministicUid(metaType: string, name: string): string;
22
+ /**
23
+ * Read meta-type nodes from the graph and compile them into a GraphRegistry.
24
+ *
25
+ * The returned registry includes both the dynamic entries AND the bootstrap
26
+ * meta-type entries, so meta-type writes remain validateable after a reload.
27
+ *
28
+ * @param reader - A GraphReader pointed at the collection containing meta-nodes.
29
+ * @param executor - Optional custom executor for compiling stored migration source strings.
30
+ */
31
+ declare function createRegistryFromGraph(reader: GraphReader, executor?: MigrationExecutor): Promise<GraphRegistry>;
32
+
33
+ declare function generateId(): string;
34
+
35
+ /**
36
+ * Build a registry from either explicit entries or a DiscoveryResult.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * // From explicit entries (programmatic)
41
+ * const registry = createRegistry([
42
+ * { aType: 'user', axbType: 'is', bType: 'user', jsonSchema: userSchema },
43
+ * { aType: 'user', axbType: 'follows', bType: 'user', jsonSchema: followsSchema },
44
+ * ]);
45
+ *
46
+ * // From discovery result (folder convention)
47
+ * const discovered = await discoverEntities('./entities');
48
+ * const registry = createRegistry(discovered);
49
+ * ```
50
+ */
51
+ declare function createRegistry(input: RegistryEntry[] | DiscoveryResult): GraphRegistry;
52
+ /**
53
+ * Create a merged registry where `base` entries take priority and `extension`
54
+ * entries fill in gaps. Lookups and validation check `base` first; only if the
55
+ * triple is not found there does the merged registry fall through to
56
+ * `extension`.
57
+ *
58
+ * The `entries()` method returns a deduplicated list (base wins on collision).
59
+ * The `lookupByAxbType()` method merges results from both registries,
60
+ * deduplicating by triple key with base entries winning.
61
+ */
62
+ declare function createMergedRegistry(base: GraphRegistry, extension: GraphRegistry): GraphRegistry;
63
+
64
+ export { BOOTSTRAP_ENTRIES as B, EDGE_TYPE_SCHEMA as E, META_EDGE_TYPE as M, NODE_TYPE_SCHEMA as N, META_NODE_TYPE as a, createMergedRegistry as b, createBootstrapRegistry as c, createRegistry as d, createRegistryFromGraph as e, generateId as f, generateDeterministicUid as g };
@@ -1,13 +1,15 @@
1
1
  import {
2
- SERIALIZATION_TAG,
3
2
  deserializeFirestoreTypes,
4
- isTaggedValue,
5
3
  serializeFirestoreTypes
6
- } from "./chunk-5753Y42M.js";
4
+ } from "./chunk-C2QMD7RY.js";
5
+ import {
6
+ SERIALIZATION_TAG,
7
+ isTaggedValue
8
+ } from "./chunk-EQJUUVFG.js";
7
9
  export {
8
10
  SERIALIZATION_TAG,
9
11
  deserializeFirestoreTypes,
10
12
  isTaggedValue,
11
13
  serializeFirestoreTypes
12
14
  };
13
- //# sourceMappingURL=serialization-ZZ7RSDRX.js.map
15
+ //# sourceMappingURL=serialization-OE2PFZMY.js.map
@@ -260,7 +260,8 @@ interface IndexFieldSpec {
260
260
  * because SQLite expression indexes must match the query compiler's
261
261
  * output verbatim, and inlining quoted path components into DDL would
262
262
  * desynchronize the two compilers. If you need to filter by an exotic
263
- * key, use `replaceData` writes rather than an indexed field.
263
+ * key, use `replaceNode` / `replaceEdge` writes rather than an indexed
264
+ * field.
264
265
  */
265
266
  path: string;
266
267
  /** Descending order; defaults to ascending. */
@@ -583,9 +584,42 @@ interface GraphReader {
583
584
  findNodes(params: FindNodesParams): Promise<StoredGraphRecord[]>;
584
585
  }
585
586
  interface GraphWriter {
587
+ /**
588
+ * Write a node, deep-merging into any existing record.
589
+ *
590
+ * Nested objects are merged recursively — sibling keys at any depth
591
+ * survive. Arrays are terminal (replaced as a unit, not element-merged).
592
+ * `undefined` values are omitted; `null` is preserved. To delete a field,
593
+ * pass the `deleteField()` sentinel as its value.
594
+ *
595
+ * Use {@link replaceNode} when you want full-document replacement.
596
+ */
586
597
  putNode(aType: string, uid: string, data: Record<string, unknown>): Promise<void>;
598
+ /**
599
+ * Write an edge, deep-merging into any existing record. See
600
+ * {@link putNode} for the merge contract.
601
+ */
587
602
  putEdge(aType: string, aUid: string, axbType: string, bType: string, bUid: string, data: Record<string, unknown>): Promise<void>;
603
+ /**
604
+ * Replace a node's `data` payload entirely. Any field absent from
605
+ * `data` is dropped. Use sparingly — prefer {@link putNode} unless you
606
+ * specifically need to drop unknown fields.
607
+ */
608
+ replaceNode(aType: string, uid: string, data: Record<string, unknown>): Promise<void>;
609
+ /**
610
+ * Replace an edge's `data` payload entirely. See {@link replaceNode}.
611
+ */
612
+ replaceEdge(aType: string, aUid: string, axbType: string, bType: string, bUid: string, data: Record<string, unknown>): Promise<void>;
613
+ /**
614
+ * Patch a node's `data` payload. Like {@link putNode} this is a deep
615
+ * merge — nested objects are walked, only leaves are written. Use the
616
+ * `deleteField()` sentinel to remove a field.
617
+ */
588
618
  updateNode(uid: string, data: Record<string, unknown>): Promise<void>;
619
+ /**
620
+ * Patch an edge's `data` payload. See {@link updateNode}.
621
+ */
622
+ updateEdge(aUid: string, axbType: string, bUid: string, data: Record<string, unknown>): Promise<void>;
589
623
  removeNode(uid: string): Promise<void>;
590
624
  removeEdge(aUid: string, axbType: string, bUid: string): Promise<void>;
591
625
  }
@@ -733,4 +767,4 @@ interface CascadeResult extends BulkResult {
733
767
  nodeDeleted: boolean;
734
768
  }
735
769
 
736
- export { type ScanProtection as A, type BulkBatchError as B, type CascadeResult as C, type DynamicGraphClient as D, type EdgeTopology as E, type FindEdgesParams as F, type GraphClientOptions as G, type HopDefinition as H, type IndexSpec as I, type TraversalOptions as J, type TraversalResult as K, type ViewDefaultsConfig as L, type MigrationExecutor as M, type NodeTypeData as N, type ViewResolverConfig as O, defineConfig as P, type QueryPlan as Q, type RegistryEntry as R, type StoredGraphRecord as S, type TraversalBuilder as T, resolveView as U, type ViewContext as V, type WhereClause as W, type GraphClient as a, type DiscoveryResult as b, type GraphRegistry as c, type GraphReader as d, type DynamicRegistryConfig as e, type MigrationWriteBack as f, type MigrationStep as g, type FindNodesParams as h, type QueryFilter as i, type GraphRecord as j, type MigrationFn as k, type StoredMigrationStep as l, type BulkOptions as m, type BulkProgress as n, type BulkResult as o, type DefineTypeOptions as p, type DiscoveredEntity as q, type EdgeTypeData as r, type FiregraphConfig as s, type GraphBatch as t, type GraphTransaction as u, type GraphWriter as v, type HopResult as w, type IndexFieldSpec as x, type QueryMode as y, type QueryOptions as z };
770
+ export { type ScanProtection as A, type BulkBatchError as B, type CascadeResult as C, type DynamicGraphClient as D, type EdgeTopology as E, type FindEdgesParams as F, type GraphClientOptions as G, type HopDefinition as H, type IndexSpec as I, type TraversalOptions as J, type TraversalResult as K, type ViewDefaultsConfig as L, type MigrationWriteBack as M, type NodeTypeData as N, type ViewResolverConfig as O, defineConfig as P, type QueryPlan as Q, type RegistryEntry as R, type StoredGraphRecord as S, type TraversalBuilder as T, resolveView as U, type ViewContext as V, type WhereClause as W, type GraphClient as a, type DiscoveryResult as b, type DynamicRegistryConfig as c, type MigrationStep as d, type GraphRegistry as e, type FindNodesParams as f, type QueryFilter as g, type GraphRecord as h, type MigrationExecutor as i, type MigrationFn as j, type StoredMigrationStep as k, type GraphReader as l, type BulkOptions as m, type BulkProgress as n, type BulkResult as o, type DefineTypeOptions as p, type DiscoveredEntity as q, type EdgeTypeData as r, type FiregraphConfig as s, type GraphBatch as t, type GraphTransaction as u, type GraphWriter as v, type HopResult as w, type IndexFieldSpec as x, type QueryMode as y, type QueryOptions as z };
@@ -260,7 +260,8 @@ interface IndexFieldSpec {
260
260
  * because SQLite expression indexes must match the query compiler's
261
261
  * output verbatim, and inlining quoted path components into DDL would
262
262
  * desynchronize the two compilers. If you need to filter by an exotic
263
- * key, use `replaceData` writes rather than an indexed field.
263
+ * key, use `replaceNode` / `replaceEdge` writes rather than an indexed
264
+ * field.
264
265
  */
265
266
  path: string;
266
267
  /** Descending order; defaults to ascending. */
@@ -583,9 +584,42 @@ interface GraphReader {
583
584
  findNodes(params: FindNodesParams): Promise<StoredGraphRecord[]>;
584
585
  }
585
586
  interface GraphWriter {
587
+ /**
588
+ * Write a node, deep-merging into any existing record.
589
+ *
590
+ * Nested objects are merged recursively — sibling keys at any depth
591
+ * survive. Arrays are terminal (replaced as a unit, not element-merged).
592
+ * `undefined` values are omitted; `null` is preserved. To delete a field,
593
+ * pass the `deleteField()` sentinel as its value.
594
+ *
595
+ * Use {@link replaceNode} when you want full-document replacement.
596
+ */
586
597
  putNode(aType: string, uid: string, data: Record<string, unknown>): Promise<void>;
598
+ /**
599
+ * Write an edge, deep-merging into any existing record. See
600
+ * {@link putNode} for the merge contract.
601
+ */
587
602
  putEdge(aType: string, aUid: string, axbType: string, bType: string, bUid: string, data: Record<string, unknown>): Promise<void>;
603
+ /**
604
+ * Replace a node's `data` payload entirely. Any field absent from
605
+ * `data` is dropped. Use sparingly — prefer {@link putNode} unless you
606
+ * specifically need to drop unknown fields.
607
+ */
608
+ replaceNode(aType: string, uid: string, data: Record<string, unknown>): Promise<void>;
609
+ /**
610
+ * Replace an edge's `data` payload entirely. See {@link replaceNode}.
611
+ */
612
+ replaceEdge(aType: string, aUid: string, axbType: string, bType: string, bUid: string, data: Record<string, unknown>): Promise<void>;
613
+ /**
614
+ * Patch a node's `data` payload. Like {@link putNode} this is a deep
615
+ * merge — nested objects are walked, only leaves are written. Use the
616
+ * `deleteField()` sentinel to remove a field.
617
+ */
588
618
  updateNode(uid: string, data: Record<string, unknown>): Promise<void>;
619
+ /**
620
+ * Patch an edge's `data` payload. See {@link updateNode}.
621
+ */
622
+ updateEdge(aUid: string, axbType: string, bUid: string, data: Record<string, unknown>): Promise<void>;
589
623
  removeNode(uid: string): Promise<void>;
590
624
  removeEdge(aUid: string, axbType: string, bUid: string): Promise<void>;
591
625
  }
@@ -733,4 +767,4 @@ interface CascadeResult extends BulkResult {
733
767
  nodeDeleted: boolean;
734
768
  }
735
769
 
736
- export { type ScanProtection as A, type BulkBatchError as B, type CascadeResult as C, type DynamicGraphClient as D, type EdgeTopology as E, type FindEdgesParams as F, type GraphClientOptions as G, type HopDefinition as H, type IndexSpec as I, type TraversalOptions as J, type TraversalResult as K, type ViewDefaultsConfig as L, type MigrationExecutor as M, type NodeTypeData as N, type ViewResolverConfig as O, defineConfig as P, type QueryPlan as Q, type RegistryEntry as R, type StoredGraphRecord as S, type TraversalBuilder as T, resolveView as U, type ViewContext as V, type WhereClause as W, type GraphClient as a, type DiscoveryResult as b, type GraphRegistry as c, type GraphReader as d, type DynamicRegistryConfig as e, type MigrationWriteBack as f, type MigrationStep as g, type FindNodesParams as h, type QueryFilter as i, type GraphRecord as j, type MigrationFn as k, type StoredMigrationStep as l, type BulkOptions as m, type BulkProgress as n, type BulkResult as o, type DefineTypeOptions as p, type DiscoveredEntity as q, type EdgeTypeData as r, type FiregraphConfig as s, type GraphBatch as t, type GraphTransaction as u, type GraphWriter as v, type HopResult as w, type IndexFieldSpec as x, type QueryMode as y, type QueryOptions as z };
770
+ export { type ScanProtection as A, type BulkBatchError as B, type CascadeResult as C, type DynamicGraphClient as D, type EdgeTopology as E, type FindEdgesParams as F, type GraphClientOptions as G, type HopDefinition as H, type IndexSpec as I, type TraversalOptions as J, type TraversalResult as K, type ViewDefaultsConfig as L, type MigrationWriteBack as M, type NodeTypeData as N, type ViewResolverConfig as O, defineConfig as P, type QueryPlan as Q, type RegistryEntry as R, type StoredGraphRecord as S, type TraversalBuilder as T, resolveView as U, type ViewContext as V, type WhereClause as W, type GraphClient as a, type DiscoveryResult as b, type DynamicRegistryConfig as c, type MigrationStep as d, type GraphRegistry as e, type FindNodesParams as f, type QueryFilter as g, type GraphRecord as h, type MigrationExecutor as i, type MigrationFn as j, type StoredMigrationStep as k, type GraphReader as l, type BulkOptions as m, type BulkProgress as n, type BulkResult as o, type DefineTypeOptions as p, type DiscoveredEntity as q, type EdgeTypeData as r, type FiregraphConfig as s, type GraphBatch as t, type GraphTransaction as u, type GraphWriter as v, type HopResult as w, type IndexFieldSpec as x, type QueryMode as y, type QueryOptions as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typicalday/firegraph",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Generic Firestore adjacency graph client with smart query planning",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,97 +0,0 @@
1
- import { S as StoredGraphRecord, i as QueryFilter, z as QueryOptions, d as GraphReader, m as BulkOptions, C as CascadeResult, F as FindEdgesParams, o as BulkResult } from './types-BGWxcpI_.js';
2
-
3
- /**
4
- * Backend abstraction for firegraph.
5
- *
6
- * `StorageBackend` is the single interface every storage driver implements.
7
- * The Firestore backend wraps `@google-cloud/firestore`; the SQLite backend
8
- * (shared by D1 and Durable Object SQLite) uses a parameterized SQL executor.
9
- *
10
- * `GraphClientImpl` and friends depend only on this interface — they have
11
- * no direct knowledge of Firestore or SQLite.
12
- */
13
-
14
- /**
15
- * Per-record write payload — backend-agnostic. Timestamps are not present;
16
- * the backend supplies them via `serverTimestamp()` placeholders that it
17
- * itself resolves at commit time.
18
- */
19
- interface WritableRecord {
20
- aType: string;
21
- aUid: string;
22
- axbType: string;
23
- bType: string;
24
- bUid: string;
25
- data: Record<string, unknown>;
26
- /** Schema version (set by the writer when registry has migrations). */
27
- v?: number;
28
- }
29
- /**
30
- * Patch shape for `updateDoc`. Captures the two patterns that exist today:
31
- * - `dataFields`: shallow merge under `data` (used by `updateNode`)
32
- * - `replaceData`: full data replacement (used by migration write-back)
33
- * - `v`: optional schema-version stamp
34
- *
35
- * `updatedAt` is always set by the backend.
36
- */
37
- interface UpdatePayload {
38
- dataFields?: Record<string, unknown>;
39
- replaceData?: Record<string, unknown>;
40
- v?: number;
41
- }
42
- /**
43
- * Read/write transaction adapter. Mirrors Firestore's transaction semantics:
44
- * reads are snapshot-consistent; writes are issued inside the transaction
45
- * and a rejection from any write aborts the surrounding `runTransaction`.
46
- *
47
- * Writes return `Promise<void>` so SQL drivers can surface row-level errors
48
- * (constraint violations, malformed JSON paths) rather than swallowing them.
49
- * Firestore implementations can resolve synchronously since the underlying
50
- * `Transaction.set/update/delete` calls are themselves synchronous buffers.
51
- */
52
- interface TransactionBackend {
53
- getDoc(docId: string): Promise<StoredGraphRecord | null>;
54
- query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
55
- setDoc(docId: string, record: WritableRecord): Promise<void>;
56
- updateDoc(docId: string, update: UpdatePayload): Promise<void>;
57
- deleteDoc(docId: string): Promise<void>;
58
- }
59
- /**
60
- * Atomic multi-write batch.
61
- */
62
- interface BatchBackend {
63
- setDoc(docId: string, record: WritableRecord): void;
64
- updateDoc(docId: string, update: UpdatePayload): void;
65
- deleteDoc(docId: string): void;
66
- commit(): Promise<void>;
67
- }
68
- /**
69
- * The single storage abstraction.
70
- *
71
- * Each backend instance is scoped to a "graph location" — for Firestore
72
- * that's a collection path; for SQLite it's a (table, scopePath) pair.
73
- * `subgraph()` returns a child backend bound to a nested location.
74
- */
75
- interface StorageBackend {
76
- /** Backend-internal location identifier (collection path or table name). */
77
- readonly collectionPath: string;
78
- /** Subgraph scope (empty string for root). */
79
- readonly scopePath: string;
80
- getDoc(docId: string): Promise<StoredGraphRecord | null>;
81
- query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
82
- setDoc(docId: string, record: WritableRecord): Promise<void>;
83
- updateDoc(docId: string, update: UpdatePayload): Promise<void>;
84
- deleteDoc(docId: string): Promise<void>;
85
- runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;
86
- createBatch(): BatchBackend;
87
- subgraph(parentNodeUid: string, name: string): StorageBackend;
88
- removeNodeCascade(uid: string, reader: GraphReader, options?: BulkOptions): Promise<CascadeResult>;
89
- bulkRemoveEdges(params: FindEdgesParams, reader: GraphReader, options?: BulkOptions): Promise<BulkResult>;
90
- /**
91
- * Find edges across all subgraphs sharing a given collection name.
92
- * Optional — backends that can't support this should throw a clear error.
93
- */
94
- findEdgesGlobal?(params: FindEdgesParams, collectionName?: string): Promise<StoredGraphRecord[]>;
95
- }
96
-
97
- export type { BatchBackend as B, StorageBackend as S, TransactionBackend as T, UpdatePayload as U, WritableRecord as W };
@@ -1,97 +0,0 @@
1
- import { S as StoredGraphRecord, i as QueryFilter, z as QueryOptions, d as GraphReader, m as BulkOptions, C as CascadeResult, F as FindEdgesParams, o as BulkResult } from './types-BGWxcpI_.cjs';
2
-
3
- /**
4
- * Backend abstraction for firegraph.
5
- *
6
- * `StorageBackend` is the single interface every storage driver implements.
7
- * The Firestore backend wraps `@google-cloud/firestore`; the SQLite backend
8
- * (shared by D1 and Durable Object SQLite) uses a parameterized SQL executor.
9
- *
10
- * `GraphClientImpl` and friends depend only on this interface — they have
11
- * no direct knowledge of Firestore or SQLite.
12
- */
13
-
14
- /**
15
- * Per-record write payload — backend-agnostic. Timestamps are not present;
16
- * the backend supplies them via `serverTimestamp()` placeholders that it
17
- * itself resolves at commit time.
18
- */
19
- interface WritableRecord {
20
- aType: string;
21
- aUid: string;
22
- axbType: string;
23
- bType: string;
24
- bUid: string;
25
- data: Record<string, unknown>;
26
- /** Schema version (set by the writer when registry has migrations). */
27
- v?: number;
28
- }
29
- /**
30
- * Patch shape for `updateDoc`. Captures the two patterns that exist today:
31
- * - `dataFields`: shallow merge under `data` (used by `updateNode`)
32
- * - `replaceData`: full data replacement (used by migration write-back)
33
- * - `v`: optional schema-version stamp
34
- *
35
- * `updatedAt` is always set by the backend.
36
- */
37
- interface UpdatePayload {
38
- dataFields?: Record<string, unknown>;
39
- replaceData?: Record<string, unknown>;
40
- v?: number;
41
- }
42
- /**
43
- * Read/write transaction adapter. Mirrors Firestore's transaction semantics:
44
- * reads are snapshot-consistent; writes are issued inside the transaction
45
- * and a rejection from any write aborts the surrounding `runTransaction`.
46
- *
47
- * Writes return `Promise<void>` so SQL drivers can surface row-level errors
48
- * (constraint violations, malformed JSON paths) rather than swallowing them.
49
- * Firestore implementations can resolve synchronously since the underlying
50
- * `Transaction.set/update/delete` calls are themselves synchronous buffers.
51
- */
52
- interface TransactionBackend {
53
- getDoc(docId: string): Promise<StoredGraphRecord | null>;
54
- query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
55
- setDoc(docId: string, record: WritableRecord): Promise<void>;
56
- updateDoc(docId: string, update: UpdatePayload): Promise<void>;
57
- deleteDoc(docId: string): Promise<void>;
58
- }
59
- /**
60
- * Atomic multi-write batch.
61
- */
62
- interface BatchBackend {
63
- setDoc(docId: string, record: WritableRecord): void;
64
- updateDoc(docId: string, update: UpdatePayload): void;
65
- deleteDoc(docId: string): void;
66
- commit(): Promise<void>;
67
- }
68
- /**
69
- * The single storage abstraction.
70
- *
71
- * Each backend instance is scoped to a "graph location" — for Firestore
72
- * that's a collection path; for SQLite it's a (table, scopePath) pair.
73
- * `subgraph()` returns a child backend bound to a nested location.
74
- */
75
- interface StorageBackend {
76
- /** Backend-internal location identifier (collection path or table name). */
77
- readonly collectionPath: string;
78
- /** Subgraph scope (empty string for root). */
79
- readonly scopePath: string;
80
- getDoc(docId: string): Promise<StoredGraphRecord | null>;
81
- query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
82
- setDoc(docId: string, record: WritableRecord): Promise<void>;
83
- updateDoc(docId: string, update: UpdatePayload): Promise<void>;
84
- deleteDoc(docId: string): Promise<void>;
85
- runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;
86
- createBatch(): BatchBackend;
87
- subgraph(parentNodeUid: string, name: string): StorageBackend;
88
- removeNodeCascade(uid: string, reader: GraphReader, options?: BulkOptions): Promise<CascadeResult>;
89
- bulkRemoveEdges(params: FindEdgesParams, reader: GraphReader, options?: BulkOptions): Promise<BulkResult>;
90
- /**
91
- * Find edges across all subgraphs sharing a given collection name.
92
- * Optional — backends that can't support this should throw a clear error.
93
- */
94
- findEdgesGlobal?(params: FindEdgesParams, collectionName?: string): Promise<StoredGraphRecord[]>;
95
- }
96
-
97
- export type { BatchBackend as B, StorageBackend as S, TransactionBackend as T, UpdatePayload as U, WritableRecord as W };
@@ -1 +0,0 @@
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":[]}