@graph-knowledge/api 0.1.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 (74) hide show
  1. package/README.md +396 -0
  2. package/package.json +44 -0
  3. package/src/index.d.ts +18 -0
  4. package/src/index.js +19 -0
  5. package/src/lib/clients/firebase-auth-client.d.ts +24 -0
  6. package/src/lib/clients/firebase-auth-client.js +76 -0
  7. package/src/lib/clients/firebase-firestore-client.d.ts +22 -0
  8. package/src/lib/clients/firebase-firestore-client.js +79 -0
  9. package/src/lib/config/api-config.d.ts +20 -0
  10. package/src/lib/config/api-config.js +0 -0
  11. package/src/lib/constants/element-defaults.d.ts +15 -0
  12. package/src/lib/constants/element-defaults.js +19 -0
  13. package/src/lib/errors/api-errors.d.ts +33 -0
  14. package/src/lib/errors/api-errors.js +51 -0
  15. package/src/lib/graph-knowledge-api.d.ts +106 -0
  16. package/src/lib/graph-knowledge-api.js +154 -0
  17. package/src/lib/interfaces/auth-client.interface.d.ts +35 -0
  18. package/src/lib/interfaces/auth-client.interface.js +0 -0
  19. package/src/lib/interfaces/batch-operations.interface.d.ts +46 -0
  20. package/src/lib/interfaces/batch-operations.interface.js +0 -0
  21. package/src/lib/interfaces/document-operations.interface.d.ts +51 -0
  22. package/src/lib/interfaces/document-operations.interface.js +0 -0
  23. package/src/lib/interfaces/element-operations.interface.d.ts +57 -0
  24. package/src/lib/interfaces/element-operations.interface.js +0 -0
  25. package/src/lib/interfaces/element-validator.interface.d.ts +42 -0
  26. package/src/lib/interfaces/element-validator.interface.js +0 -0
  27. package/src/lib/interfaces/firestore-client.interface.d.ts +62 -0
  28. package/src/lib/interfaces/firestore-client.interface.js +0 -0
  29. package/src/lib/interfaces/node-operations.interface.d.ts +62 -0
  30. package/src/lib/interfaces/node-operations.interface.js +0 -0
  31. package/src/lib/models/document.model.d.ts +73 -0
  32. package/src/lib/models/document.model.js +13 -0
  33. package/src/lib/models/index.d.ts +6 -0
  34. package/src/lib/models/index.js +5 -0
  35. package/src/lib/operations/batch-operations.d.ts +30 -0
  36. package/src/lib/operations/batch-operations.js +181 -0
  37. package/src/lib/operations/document-operations.d.ts +20 -0
  38. package/src/lib/operations/document-operations.js +108 -0
  39. package/src/lib/operations/element-operations.d.ts +33 -0
  40. package/src/lib/operations/element-operations.js +175 -0
  41. package/src/lib/operations/node-operations.d.ts +22 -0
  42. package/src/lib/operations/node-operations.js +89 -0
  43. package/src/lib/testing/index.d.ts +2 -0
  44. package/src/lib/testing/index.js +3 -0
  45. package/src/lib/testing/mock-auth-client.d.ts +26 -0
  46. package/src/lib/testing/mock-auth-client.js +49 -0
  47. package/src/lib/testing/mock-firestore-client.d.ts +32 -0
  48. package/src/lib/testing/mock-firestore-client.js +126 -0
  49. package/src/lib/types/api-types.d.ts +61 -0
  50. package/src/lib/types/api-types.js +0 -0
  51. package/src/lib/types/element-input-types.d.ts +237 -0
  52. package/src/lib/types/element-input-types.js +3 -0
  53. package/src/lib/utils/link-level-manager.d.ts +66 -0
  54. package/src/lib/utils/link-level-manager.js +200 -0
  55. package/src/lib/utils/uuid.d.ts +5 -0
  56. package/src/lib/utils/uuid.js +16 -0
  57. package/src/lib/validators/document-validator.d.ts +22 -0
  58. package/src/lib/validators/document-validator.js +68 -0
  59. package/src/lib/validators/element-type-validators/base-element-validator.d.ts +35 -0
  60. package/src/lib/validators/element-type-validators/base-element-validator.js +96 -0
  61. package/src/lib/validators/element-type-validators/connector-validator.d.ts +13 -0
  62. package/src/lib/validators/element-type-validators/connector-validator.js +66 -0
  63. package/src/lib/validators/element-type-validators/custom-shape-validator.d.ts +22 -0
  64. package/src/lib/validators/element-type-validators/custom-shape-validator.js +44 -0
  65. package/src/lib/validators/element-type-validators/rectangle-validator.d.ts +11 -0
  66. package/src/lib/validators/element-type-validators/rectangle-validator.js +31 -0
  67. package/src/lib/validators/element-type-validators/text-validator.d.ts +14 -0
  68. package/src/lib/validators/element-type-validators/text-validator.js +66 -0
  69. package/src/lib/validators/element-type-validators/uml-validators.d.ts +58 -0
  70. package/src/lib/validators/element-type-validators/uml-validators.js +123 -0
  71. package/src/lib/validators/element-validator-registry.d.ts +18 -0
  72. package/src/lib/validators/element-validator-registry.js +58 -0
  73. package/src/lib/validators/node-validator.d.ts +26 -0
  74. package/src/lib/validators/node-validator.js +90 -0
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Interface for Firestore batch write operations.
3
+ */
4
+ export interface IBatchWriter {
5
+ /**
6
+ * Sets a document in the batch.
7
+ */
8
+ set<T extends object>(path: string, data: T): void;
9
+ /**
10
+ * Updates a document in the batch.
11
+ */
12
+ update<T extends object>(path: string, data: Partial<T>): void;
13
+ /**
14
+ * Deletes a document in the batch.
15
+ */
16
+ delete(path: string): void;
17
+ /**
18
+ * Commits all batched operations atomically.
19
+ */
20
+ commit(): Promise<void>;
21
+ }
22
+ /**
23
+ * Interface for Firestore operations.
24
+ * Abstracts Firebase Firestore for dependency inversion.
25
+ */
26
+ export interface IFirestoreClient {
27
+ /**
28
+ * Gets a single document by path.
29
+ * @returns The document data or null if not found
30
+ */
31
+ getDocument<T>(path: string): Promise<(T & {
32
+ id: string;
33
+ }) | null>;
34
+ /**
35
+ * Sets a document at the given path.
36
+ */
37
+ setDocument<T extends object>(path: string, data: T): Promise<void>;
38
+ /**
39
+ * Updates a document at the given path.
40
+ */
41
+ updateDocument<T extends object>(path: string, data: Partial<T>): Promise<void>;
42
+ /**
43
+ * Deletes a document at the given path.
44
+ */
45
+ deleteDocument(path: string): Promise<void>;
46
+ /**
47
+ * Gets all documents in a collection.
48
+ */
49
+ getCollection<T>(path: string): Promise<(T & {
50
+ id: string;
51
+ })[]>;
52
+ /**
53
+ * Queries documents in a collection where a field equals a value.
54
+ */
55
+ queryCollection<T>(path: string, field: string, value: string): Promise<(T & {
56
+ id: string;
57
+ })[]>;
58
+ /**
59
+ * Creates a new batch writer for atomic operations.
60
+ */
61
+ batch(): IBatchWriter;
62
+ }
@@ -0,0 +1,62 @@
1
+ import { GraphNode } from "@graph-knowledge/domain";
2
+ import { CreateNodeInput, UpdateNodeInput } from "../types/api-types";
3
+ /**
4
+ * Interface for node CRUD operations.
5
+ */
6
+ export interface INodeOperations {
7
+ /**
8
+ * Creates a new node within a document.
9
+ * @param documentId The parent document ID
10
+ * @param input Node creation parameters
11
+ * @returns The created node
12
+ * @throws AuthenticationError if not authenticated
13
+ * @throws NotFoundError if document doesn't exist
14
+ * @throws ValidationError if input is invalid
15
+ */
16
+ create(documentId: string, input: CreateNodeInput): Promise<GraphNode>;
17
+ /**
18
+ * Creates a new node with a specific ID.
19
+ * Useful when you need to reference the node ID before creation (e.g., for linking).
20
+ * @param documentId The parent document ID
21
+ * @param nodeId The specific ID to use for the node
22
+ * @param input Node creation parameters
23
+ * @returns The created node
24
+ * @throws AuthenticationError if not authenticated
25
+ * @throws NotFoundError if document doesn't exist
26
+ * @throws ValidationError if input is invalid
27
+ */
28
+ createWithId(documentId: string, nodeId: string, input: CreateNodeInput): Promise<GraphNode>;
29
+ /**
30
+ * Gets a node by ID.
31
+ * @param documentId The parent document ID
32
+ * @param nodeId The node ID
33
+ * @returns The node
34
+ * @throws AuthenticationError if not authenticated
35
+ * @throws NotFoundError if node doesn't exist
36
+ */
37
+ get(documentId: string, nodeId: string): Promise<GraphNode>;
38
+ /**
39
+ * Lists all nodes in a document.
40
+ * @param documentId The parent document ID
41
+ * @returns Array of nodes
42
+ * @throws AuthenticationError if not authenticated
43
+ */
44
+ list(documentId: string): Promise<GraphNode[]>;
45
+ /**
46
+ * Updates a node's properties.
47
+ * @param documentId The parent document ID
48
+ * @param nodeId The node ID
49
+ * @param input Fields to update
50
+ * @throws AuthenticationError if not authenticated
51
+ * @throws NotFoundError if node doesn't exist
52
+ * @throws ValidationError if input is invalid
53
+ */
54
+ update(documentId: string, nodeId: string, input: UpdateNodeInput): Promise<void>;
55
+ /**
56
+ * Deletes a node.
57
+ * @param documentId The parent document ID
58
+ * @param nodeId The node ID
59
+ * @throws AuthenticationError if not authenticated
60
+ */
61
+ delete(documentId: string, nodeId: string): Promise<void>;
62
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Domain models bundled for npm package self-containment.
3
+ * These are copies of the types from @graph-knowledge/domain.
4
+ */
5
+ /**
6
+ * Current document schema version.
7
+ * Increment this when making breaking changes to Document, GraphNode, or GraphElement structure.
8
+ *
9
+ * Version History:
10
+ * - 0: Legacy documents (no schemaVersion field)
11
+ * - 1: Initial versioned schema (added schemaVersion tracking)
12
+ */
13
+ export declare const CURRENT_DOCUMENT_SCHEMA_VERSION = 1;
14
+ export interface Document {
15
+ id: string;
16
+ /**
17
+ * Schema version for migration support.
18
+ * Documents without this field are treated as version 0 (legacy).
19
+ */
20
+ schemaVersion?: number;
21
+ title: string;
22
+ content: string;
23
+ /**
24
+ * UID of the document owner.
25
+ * OWNER-ONLY: Only the owner can modify this field.
26
+ */
27
+ owner: string;
28
+ /**
29
+ * Email addresses of users with whom this document is shared.
30
+ * OWNER-ONLY: Only the owner can modify this field.
31
+ * Changes trigger Cloud Function to sync sharedWithUser arrays.
32
+ */
33
+ sharedWith: string[];
34
+ /**
35
+ * Document creation timestamp.
36
+ * OWNER-ONLY: Set on creation, cannot be modified by shared users.
37
+ */
38
+ createdAt: Date | {
39
+ seconds: number;
40
+ nanoseconds: number;
41
+ } | null;
42
+ nodes?: GraphNode[];
43
+ rootNodeId?: string;
44
+ isPremiumContent?: boolean;
45
+ }
46
+ export interface GraphNode {
47
+ id: string;
48
+ title: string;
49
+ content: string;
50
+ elements: GraphElement[];
51
+ parentNodeId?: string;
52
+ canvasWidth?: number;
53
+ canvasHeight?: number;
54
+ level?: number;
55
+ owner?: string;
56
+ }
57
+ export interface GraphElement {
58
+ id: string;
59
+ elementType: string;
60
+ x: number;
61
+ y: number;
62
+ width: number;
63
+ height: number;
64
+ /**
65
+ * Rotation in degrees, always an integer in the range [0, 360).
66
+ * This enables reliable grouping and comparison for snapping operations.
67
+ * Legacy documents with floating-point values are normalized during loading.
68
+ */
69
+ rotation: number;
70
+ properties: Record<string, any>;
71
+ isLink?: boolean;
72
+ linkTarget?: string;
73
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Domain models bundled for npm package self-containment.
3
+ * These are copies of the types from @graph-knowledge/domain.
4
+ */
5
+ /**
6
+ * Current document schema version.
7
+ * Increment this when making breaking changes to Document, GraphNode, or GraphElement structure.
8
+ *
9
+ * Version History:
10
+ * - 0: Legacy documents (no schemaVersion field)
11
+ * - 1: Initial versioned schema (added schemaVersion tracking)
12
+ */
13
+ export const CURRENT_DOCUMENT_SCHEMA_VERSION = 1;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Domain models bundled for npm package.
3
+ * Re-exports types needed by the API.
4
+ */
5
+ export { CURRENT_DOCUMENT_SCHEMA_VERSION } from "./document.model";
6
+ export type { Document, GraphNode, GraphElement } from "./document.model";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Domain models bundled for npm package.
3
+ * Re-exports types needed by the API.
4
+ */
5
+ export { CURRENT_DOCUMENT_SCHEMA_VERSION } from "./document.model";
@@ -0,0 +1,30 @@
1
+ import { GraphElement } from "@graph-knowledge/domain";
2
+ import { IBatchOperations, BatchUpdateElementInput } from "../interfaces/batch-operations.interface";
3
+ import { IFirestoreClient } from "../interfaces/firestore-client.interface";
4
+ import { IAuthClient } from "../interfaces/auth-client.interface";
5
+ import { IElementValidatorRegistry } from "../interfaces/element-validator.interface";
6
+ import { AnyElementInput } from "../types/element-input-types";
7
+ /**
8
+ * Implementation of batch element operations.
9
+ */
10
+ export declare class BatchOperations implements IBatchOperations {
11
+ private readonly firestore;
12
+ private readonly auth;
13
+ private readonly validatorRegistry;
14
+ constructor(firestore: IFirestoreClient, auth: IAuthClient, validatorRegistry: IElementValidatorRegistry);
15
+ createElements(documentId: string, nodeId: string, inputs: AnyElementInput[]): Promise<GraphElement[]>;
16
+ updateElements(documentId: string, nodeId: string, updates: BatchUpdateElementInput[]): Promise<void>;
17
+ deleteElements(documentId: string, nodeId: string, elementIds: string[]): Promise<void>;
18
+ /**
19
+ * Gets default dimensions for an element type.
20
+ */
21
+ private getDefaultDimensions;
22
+ /**
23
+ * Normalizes rotation to integer in range [0, 360).
24
+ */
25
+ private normalizeRotation;
26
+ /**
27
+ * Builds properties object from element input.
28
+ */
29
+ private buildProperties;
30
+ }
@@ -0,0 +1,181 @@
1
+ import { generateUuid } from "../utils/uuid";
2
+ import { NotFoundError } from "../errors/api-errors";
3
+ import { DEFAULT_ELEMENT_DIMENSIONS, DEFAULT_CUSTOM_SHAPE_DIMENSIONS } from "../constants/element-defaults";
4
+ /**
5
+ * Implementation of batch element operations.
6
+ */
7
+ export class BatchOperations {
8
+ firestore;
9
+ auth;
10
+ validatorRegistry;
11
+ constructor(firestore, auth, validatorRegistry) {
12
+ this.firestore = firestore;
13
+ this.auth = auth;
14
+ this.validatorRegistry = validatorRegistry;
15
+ }
16
+ async createElements(documentId, nodeId, inputs) {
17
+ this.auth.requireAuth();
18
+ if (inputs.length === 0) {
19
+ return [];
20
+ }
21
+ // Validate ALL inputs first (before creating any)
22
+ for (const input of inputs) {
23
+ // Custom shapes require premium
24
+ if (input.type.startsWith("custom:")) {
25
+ await this.auth.requirePremium();
26
+ }
27
+ this.validatorRegistry.validate(input);
28
+ }
29
+ const nodePath = `documents/${documentId}/nodes/${nodeId}`;
30
+ const node = await this.firestore.getDocument(nodePath);
31
+ if (!node) {
32
+ throw new NotFoundError(`Node ${nodeId} not found`);
33
+ }
34
+ // Build all elements
35
+ const newElements = inputs.map((input) => {
36
+ const defaults = this.getDefaultDimensions(input.type);
37
+ const x = input.x ?? 0;
38
+ const y = input.y ?? 0;
39
+ const element = {
40
+ id: generateUuid(),
41
+ elementType: input.type,
42
+ x,
43
+ y,
44
+ width: input.width ?? defaults.width,
45
+ height: input.height ?? defaults.height,
46
+ rotation: this.normalizeRotation(input.rotation ?? 0),
47
+ properties: this.buildProperties(input)
48
+ };
49
+ if (input.isLink !== undefined) {
50
+ element.isLink = input.isLink;
51
+ }
52
+ if (input.linkTarget !== undefined) {
53
+ element.linkTarget = input.linkTarget;
54
+ }
55
+ return element;
56
+ });
57
+ // Update node with all new elements atomically
58
+ const updatedElements = [...(node.elements || []), ...newElements];
59
+ await this.firestore.updateDocument(nodePath, {
60
+ elements: updatedElements
61
+ });
62
+ return newElements;
63
+ }
64
+ async updateElements(documentId, nodeId, updates) {
65
+ this.auth.requireAuth();
66
+ if (updates.length === 0) {
67
+ return;
68
+ }
69
+ const nodePath = `documents/${documentId}/nodes/${nodeId}`;
70
+ const node = await this.firestore.getDocument(nodePath);
71
+ if (!node) {
72
+ throw new NotFoundError(`Node ${nodeId} not found`);
73
+ }
74
+ // Create a map for quick lookup
75
+ const updateMap = new Map(updates.map((u) => [u.elementId, u]));
76
+ // Verify all elements exist before updating any
77
+ for (const update of updates) {
78
+ const exists = node.elements.some((e) => e.id === update.elementId);
79
+ if (!exists) {
80
+ throw new NotFoundError(`Element ${update.elementId} not found`);
81
+ }
82
+ }
83
+ // Apply all updates
84
+ const updatedElements = node.elements.map((element) => {
85
+ const update = updateMap.get(element.id);
86
+ if (!update) {
87
+ return element;
88
+ }
89
+ const updated = {
90
+ ...element,
91
+ x: update.x ?? element.x,
92
+ y: update.y ?? element.y,
93
+ width: update.width ?? element.width,
94
+ height: update.height ?? element.height,
95
+ rotation: update.rotation !== undefined
96
+ ? this.normalizeRotation(update.rotation)
97
+ : element.rotation,
98
+ properties: {
99
+ ...element.properties,
100
+ ...(update.properties ?? {})
101
+ }
102
+ };
103
+ // Handle link properties: only change if the property is present on the update
104
+ if ("isLink" in update) {
105
+ updated.isLink = update.isLink;
106
+ }
107
+ else if (element.isLink !== undefined) {
108
+ updated.isLink = element.isLink;
109
+ }
110
+ if ("linkTarget" in update) {
111
+ updated.linkTarget = update.linkTarget;
112
+ }
113
+ else if (element.linkTarget !== undefined) {
114
+ updated.linkTarget = element.linkTarget;
115
+ }
116
+ return updated;
117
+ });
118
+ await this.firestore.updateDocument(nodePath, {
119
+ elements: updatedElements
120
+ });
121
+ }
122
+ async deleteElements(documentId, nodeId, elementIds) {
123
+ this.auth.requireAuth();
124
+ if (elementIds.length === 0) {
125
+ return;
126
+ }
127
+ const nodePath = `documents/${documentId}/nodes/${nodeId}`;
128
+ const node = await this.firestore.getDocument(nodePath);
129
+ if (!node) {
130
+ throw new NotFoundError(`Node ${nodeId} not found`);
131
+ }
132
+ const idsToDelete = new Set(elementIds);
133
+ const updatedElements = node.elements.filter((e) => !idsToDelete.has(e.id));
134
+ await this.firestore.updateDocument(nodePath, {
135
+ elements: updatedElements
136
+ });
137
+ }
138
+ /**
139
+ * Gets default dimensions for an element type.
140
+ */
141
+ getDefaultDimensions(type) {
142
+ if (type.startsWith("custom:")) {
143
+ return DEFAULT_CUSTOM_SHAPE_DIMENSIONS;
144
+ }
145
+ return (DEFAULT_ELEMENT_DIMENSIONS[type] ?? DEFAULT_CUSTOM_SHAPE_DIMENSIONS);
146
+ }
147
+ /**
148
+ * Normalizes rotation to integer in range [0, 360).
149
+ */
150
+ normalizeRotation(rotation) {
151
+ let normalized = rotation % 360;
152
+ if (normalized < 0) {
153
+ normalized += 360;
154
+ }
155
+ return Math.round(normalized);
156
+ }
157
+ /**
158
+ * Builds properties object from element input.
159
+ */
160
+ buildProperties(input) {
161
+ const props = {
162
+ __schemaVersion: 1
163
+ };
164
+ const commonFields = new Set([
165
+ "type",
166
+ "x",
167
+ "y",
168
+ "width",
169
+ "height",
170
+ "rotation",
171
+ "isLink",
172
+ "linkTarget"
173
+ ]);
174
+ for (const [key, value] of Object.entries(input)) {
175
+ if (!commonFields.has(key) && value !== undefined) {
176
+ props[key] = value;
177
+ }
178
+ }
179
+ return props;
180
+ }
181
+ }
@@ -0,0 +1,20 @@
1
+ import { IDocumentOperations } from "../interfaces/document-operations.interface";
2
+ import { IFirestoreClient } from "../interfaces/firestore-client.interface";
3
+ import { IAuthClient } from "../interfaces/auth-client.interface";
4
+ import { DocumentValidator } from "../validators/document-validator";
5
+ import { CreateDocumentInput, UpdateDocumentInput, DocumentResult } from "../types/api-types";
6
+ /**
7
+ * Implementation of document CRUD operations.
8
+ */
9
+ export declare class DocumentOperations implements IDocumentOperations {
10
+ private readonly firestore;
11
+ private readonly auth;
12
+ private readonly validator;
13
+ constructor(firestore: IFirestoreClient, auth: IAuthClient, validator: DocumentValidator);
14
+ create(input: CreateDocumentInput): Promise<DocumentResult>;
15
+ get(documentId: string): Promise<DocumentResult>;
16
+ list(): Promise<DocumentResult[]>;
17
+ update(documentId: string, input: UpdateDocumentInput): Promise<void>;
18
+ delete(documentId: string): Promise<void>;
19
+ share(documentId: string, userIds: string[]): Promise<void>;
20
+ }
@@ -0,0 +1,108 @@
1
+ import { CURRENT_DOCUMENT_SCHEMA_VERSION } from "@graph-knowledge/domain";
2
+ import { generateUuid } from "../utils/uuid";
3
+ import { NotFoundError } from "../errors/api-errors";
4
+ // Default canvas dimensions
5
+ const DEFAULT_CANVAS_WIDTH = 1920;
6
+ const DEFAULT_CANVAS_HEIGHT = 1080;
7
+ /**
8
+ * Implementation of document CRUD operations.
9
+ */
10
+ export class DocumentOperations {
11
+ firestore;
12
+ auth;
13
+ validator;
14
+ constructor(firestore, auth, validator) {
15
+ this.firestore = firestore;
16
+ this.auth = auth;
17
+ this.validator = validator;
18
+ }
19
+ async create(input) {
20
+ const userId = this.auth.requireAuth();
21
+ this.validator.validateCreate(input);
22
+ const docId = generateUuid();
23
+ const rootNodeId = generateUuid();
24
+ const rootNode = {
25
+ id: rootNodeId,
26
+ title: input.title,
27
+ content: "",
28
+ elements: [],
29
+ canvasWidth: input.canvasWidth ?? DEFAULT_CANVAS_WIDTH,
30
+ canvasHeight: input.canvasHeight ?? DEFAULT_CANVAS_HEIGHT,
31
+ level: 0,
32
+ owner: userId
33
+ };
34
+ const document = {
35
+ schemaVersion: CURRENT_DOCUMENT_SCHEMA_VERSION,
36
+ title: input.title,
37
+ content: input.content ?? "",
38
+ owner: userId,
39
+ sharedWith: [],
40
+ createdAt: new Date(),
41
+ nodes: [], // Nodes stored in sub-collection
42
+ rootNodeId: rootNodeId,
43
+ isPremiumContent: input.isPremiumContent ?? false
44
+ };
45
+ const batch = this.firestore.batch();
46
+ batch.set(`documents/${docId}`, document);
47
+ batch.set(`documents/${docId}/nodes/${rootNodeId}`, rootNode);
48
+ await batch.commit();
49
+ return {
50
+ id: docId,
51
+ ...document,
52
+ nodes: [rootNode]
53
+ };
54
+ }
55
+ async get(documentId) {
56
+ this.auth.requireAuth();
57
+ const docData = await this.firestore.getDocument(`documents/${documentId}`);
58
+ if (!docData) {
59
+ throw new NotFoundError(`Document ${documentId} not found`);
60
+ }
61
+ const nodes = await this.firestore.getCollection(`documents/${documentId}/nodes`);
62
+ return {
63
+ ...docData,
64
+ nodes
65
+ };
66
+ }
67
+ async list() {
68
+ const userId = this.auth.requireAuth();
69
+ const documents = await this.firestore.queryCollection("documents", "owner", userId);
70
+ // Return shallow documents without nodes
71
+ return documents.map((doc) => ({
72
+ ...doc,
73
+ nodes: []
74
+ }));
75
+ }
76
+ async update(documentId, input) {
77
+ this.auth.requireAuth();
78
+ this.validator.validateUpdate(input);
79
+ const docData = await this.firestore.getDocument(`documents/${documentId}`);
80
+ if (!docData) {
81
+ throw new NotFoundError(`Document ${documentId} not found`);
82
+ }
83
+ const updates = {};
84
+ if (input.title !== undefined)
85
+ updates.title = input.title;
86
+ if (input.content !== undefined)
87
+ updates.content = input.content;
88
+ if (Object.keys(updates).length > 0) {
89
+ await this.firestore.updateDocument(`documents/${documentId}`, updates);
90
+ }
91
+ }
92
+ async delete(documentId) {
93
+ this.auth.requireAuth();
94
+ await this.firestore.deleteDocument(`documents/${documentId}`);
95
+ // Note: Sub-collection nodes are not automatically deleted
96
+ // A Cloud Function should handle cascading deletion
97
+ }
98
+ async share(documentId, userIds) {
99
+ this.auth.requireAuth();
100
+ const docData = await this.firestore.getDocument(`documents/${documentId}`);
101
+ if (!docData) {
102
+ throw new NotFoundError(`Document ${documentId} not found`);
103
+ }
104
+ await this.firestore.updateDocument(`documents/${documentId}`, {
105
+ sharedWith: userIds
106
+ });
107
+ }
108
+ }
@@ -0,0 +1,33 @@
1
+ import { GraphElement } from "@graph-knowledge/domain";
2
+ import { IElementOperations } from "../interfaces/element-operations.interface";
3
+ import { IFirestoreClient } from "../interfaces/firestore-client.interface";
4
+ import { IAuthClient } from "../interfaces/auth-client.interface";
5
+ import { IElementValidatorRegistry } from "../interfaces/element-validator.interface";
6
+ import { AnyElementInput, UpdateElementInput } from "../types/element-input-types";
7
+ /**
8
+ * Implementation of element CRUD operations.
9
+ */
10
+ export declare class ElementOperations implements IElementOperations {
11
+ private readonly firestore;
12
+ private readonly auth;
13
+ private readonly validatorRegistry;
14
+ constructor(firestore: IFirestoreClient, auth: IAuthClient, validatorRegistry: IElementValidatorRegistry);
15
+ get(documentId: string, nodeId: string, elementId: string): Promise<GraphElement>;
16
+ list(documentId: string, nodeId: string): Promise<GraphElement[]>;
17
+ create(documentId: string, nodeId: string, input: AnyElementInput): Promise<GraphElement>;
18
+ update(documentId: string, nodeId: string, elementId: string, input: UpdateElementInput): Promise<void>;
19
+ delete(documentId: string, nodeId: string, elementId: string): Promise<void>;
20
+ /**
21
+ * Gets default dimensions for an element type.
22
+ */
23
+ private getDefaultDimensions;
24
+ /**
25
+ * Normalizes rotation to integer in range [0, 360).
26
+ */
27
+ private normalizeRotation;
28
+ /**
29
+ * Builds properties object from element input.
30
+ * Extracts type-specific properties, excluding common fields.
31
+ */
32
+ private buildProperties;
33
+ }