flowspec-mcp 2.1.0 → 3.0.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 (70) hide show
  1. package/dist/analysis/analysisUtils.d.ts +36 -0
  2. package/dist/analysis/analysisUtils.js +284 -0
  3. package/dist/analysis/analysisUtils.js.map +1 -0
  4. package/dist/db.d.ts +68 -0
  5. package/dist/db.js +259 -0
  6. package/dist/db.js.map +1 -1
  7. package/dist/image/dimensions.d.ts +10 -0
  8. package/dist/image/dimensions.js +53 -0
  9. package/dist/image/dimensions.js.map +1 -0
  10. package/dist/import/yamlImporter.d.ts +22 -0
  11. package/dist/import/yamlImporter.js +227 -0
  12. package/dist/import/yamlImporter.js.map +1 -0
  13. package/dist/layout/autoLayout.d.ts +19 -0
  14. package/dist/layout/autoLayout.js +85 -0
  15. package/dist/layout/autoLayout.js.map +1 -0
  16. package/dist/layout/semanticLayout.d.ts +24 -0
  17. package/dist/layout/semanticLayout.js +233 -0
  18. package/dist/layout/semanticLayout.js.map +1 -0
  19. package/dist/server.js +26 -2
  20. package/dist/server.js.map +1 -1
  21. package/dist/tools/addRegion.d.ts +69 -0
  22. package/dist/tools/addRegion.js +39 -0
  23. package/dist/tools/addRegion.js.map +1 -0
  24. package/dist/tools/autoLayout.d.ts +27 -0
  25. package/dist/tools/autoLayout.js +52 -0
  26. package/dist/tools/autoLayout.js.map +1 -0
  27. package/dist/tools/captureScreen.d.ts +48 -0
  28. package/dist/tools/captureScreen.js +135 -0
  29. package/dist/tools/captureScreen.js.map +1 -0
  30. package/dist/tools/cloneProject.d.ts +21 -0
  31. package/dist/tools/cloneProject.js +21 -0
  32. package/dist/tools/cloneProject.js.map +1 -0
  33. package/dist/tools/createScreen.d.ts +36 -0
  34. package/dist/tools/createScreen.js +26 -0
  35. package/dist/tools/createScreen.js.map +1 -0
  36. package/dist/tools/deleteScreen.d.ts +24 -0
  37. package/dist/tools/deleteScreen.js +22 -0
  38. package/dist/tools/deleteScreen.js.map +1 -0
  39. package/dist/tools/generateSpec.d.ts +26 -0
  40. package/dist/tools/generateSpec.js +336 -0
  41. package/dist/tools/generateSpec.js.map +1 -0
  42. package/dist/tools/healthCheck.d.ts +8 -0
  43. package/dist/tools/healthCheck.js +16 -0
  44. package/dist/tools/healthCheck.js.map +1 -0
  45. package/dist/tools/importYaml.d.ts +33 -0
  46. package/dist/tools/importYaml.js +97 -0
  47. package/dist/tools/importYaml.js.map +1 -0
  48. package/dist/tools/ingestCodebase.d.ts +27 -0
  49. package/dist/tools/ingestCodebase.js +516 -0
  50. package/dist/tools/ingestCodebase.js.map +1 -0
  51. package/dist/tools/removeRegion.d.ts +27 -0
  52. package/dist/tools/removeRegion.js +23 -0
  53. package/dist/tools/removeRegion.js.map +1 -0
  54. package/dist/tools/smartLayout.d.ts +30 -0
  55. package/dist/tools/smartLayout.js +74 -0
  56. package/dist/tools/smartLayout.js.map +1 -0
  57. package/dist/tools/updateEdge.d.ts +39 -0
  58. package/dist/tools/updateEdge.js +50 -0
  59. package/dist/tools/updateEdge.js.map +1 -0
  60. package/dist/tools/updateRegion.d.ts +72 -0
  61. package/dist/tools/updateRegion.js +35 -0
  62. package/dist/tools/updateRegion.js.map +1 -0
  63. package/dist/tools/updateScreen.d.ts +39 -0
  64. package/dist/tools/updateScreen.js +28 -0
  65. package/dist/tools/updateScreen.js.map +1 -0
  66. package/dist/tools/uploadImage.d.ts +27 -0
  67. package/dist/tools/uploadImage.js +55 -0
  68. package/dist/tools/uploadImage.js.map +1 -0
  69. package/dist/types.d.ts +6 -6
  70. package/package.json +2 -1
@@ -0,0 +1,36 @@
1
+ import type { CanvasNode, CanvasEdge } from '../types.js';
2
+ export interface NamingViolation {
3
+ nodeId: string;
4
+ label: string;
5
+ issue: 'mixed-case' | 'special-chars' | 'inconsistent-prefix';
6
+ suggestion: string;
7
+ }
8
+ export interface FuzzyDuplicate {
9
+ primaryNodeId: string;
10
+ primaryLabel: string;
11
+ similarNodes: Array<{
12
+ nodeId: string;
13
+ label: string;
14
+ similarity: number;
15
+ method: 'levenshtein' | 'cosine';
16
+ }>;
17
+ }
18
+ export interface SubgraphCluster {
19
+ clusterIndex: number;
20
+ nodeIds: string[];
21
+ nodeLabels: string[];
22
+ isIsolated: boolean;
23
+ }
24
+ export interface ConsolidationSuggestion {
25
+ targetLabel: string;
26
+ nodeIds: string[];
27
+ reason: 'same-type-and-constraints' | 'duplicate-references';
28
+ confidence: 'high' | 'medium' | 'low';
29
+ }
30
+ export declare function detectNamingViolations(nodes: CanvasNode[]): NamingViolation[];
31
+ export declare function findFuzzyDuplicates(nodes: CanvasNode[], thresholds?: {
32
+ levenshtein: number;
33
+ cosine: number;
34
+ }): FuzzyDuplicate[];
35
+ export declare function detectSubgraphs(nodes: CanvasNode[], edges: CanvasEdge[]): SubgraphCluster[];
36
+ export declare function suggestConsolidations(nodes: CanvasNode[]): ConsolidationSuggestion[];
@@ -0,0 +1,284 @@
1
+ import levenshtein from 'fast-levenshtein';
2
+ import natural from 'natural';
3
+ // ─── Naming Convention Detection ─────────────────────────────────
4
+ const NAMING_RULES = {
5
+ // Prefer snake_case or kebab-case, flag mixed casing
6
+ mixedCase: /^[a-z]+([A-Z][a-z]+)+$/, // camelCase
7
+ specialChars: /[^a-zA-Z0-9_\-\s]/,
8
+ commonPrefixes: ['user', 'order', 'product', 'payment', 'auth', 'admin', 'cart', 'invoice']
9
+ };
10
+ export function detectNamingViolations(nodes) {
11
+ const violations = [];
12
+ const prefixGroups = new Map(); // prefix → [labels]
13
+ for (const node of nodes) {
14
+ const label = (node.data?.label ?? '').trim();
15
+ if (!label)
16
+ continue;
17
+ const nodeId = node.id;
18
+ // Check for mixed casing (camelCase)
19
+ if (NAMING_RULES.mixedCase.test(label)) {
20
+ const suggestion = label.replace(/([A-Z])/g, '_$1').toLowerCase();
21
+ violations.push({
22
+ nodeId,
23
+ label,
24
+ issue: 'mixed-case',
25
+ suggestion
26
+ });
27
+ }
28
+ // Check for special characters (excluding space, dash, underscore)
29
+ if (NAMING_RULES.specialChars.test(label)) {
30
+ const suggestion = label.replace(/[^a-zA-Z0-9_\-\s]/g, '');
31
+ violations.push({
32
+ nodeId,
33
+ label,
34
+ issue: 'special-chars',
35
+ suggestion
36
+ });
37
+ }
38
+ // Track prefix usage for consistency analysis
39
+ const lowerLabel = label.toLowerCase();
40
+ for (const prefix of NAMING_RULES.commonPrefixes) {
41
+ if (lowerLabel.startsWith(prefix)) {
42
+ const group = prefixGroups.get(prefix) ?? [];
43
+ group.push(label);
44
+ prefixGroups.set(prefix, group);
45
+ }
46
+ }
47
+ }
48
+ // Check for inconsistent prefix usage (e.g., user_id vs userId)
49
+ for (const [prefix, labels] of prefixGroups) {
50
+ if (labels.length < 2)
51
+ continue;
52
+ const hasCamelCase = labels.some((l) => /[A-Z]/.test(l));
53
+ const hasSnakeCase = labels.some((l) => l.includes('_'));
54
+ const hasKebabCase = labels.some((l) => l.includes('-'));
55
+ // If mixed styles exist for same prefix, flag inconsistency
56
+ const styles = [hasCamelCase, hasSnakeCase, hasKebabCase].filter(Boolean).length;
57
+ if (styles > 1) {
58
+ // Find the inconsistent nodes and suggest the most common style
59
+ const dominantStyle = hasSnakeCase ? 'snake_case' : hasCamelCase ? 'camelCase' : 'kebab-case';
60
+ for (const node of nodes) {
61
+ const label = (node.data?.label ?? '').trim();
62
+ if (!label.toLowerCase().startsWith(prefix))
63
+ continue;
64
+ const isInconsistent = (dominantStyle === 'snake_case' && /[A-Z]/.test(label)) ||
65
+ (dominantStyle === 'camelCase' && label.includes('_'));
66
+ if (isInconsistent) {
67
+ const suggestion = dominantStyle === 'snake_case'
68
+ ? label.replace(/([A-Z])/g, '_$1').toLowerCase()
69
+ : label.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
70
+ violations.push({
71
+ nodeId: node.id,
72
+ label,
73
+ issue: 'inconsistent-prefix',
74
+ suggestion
75
+ });
76
+ }
77
+ }
78
+ }
79
+ }
80
+ return violations;
81
+ }
82
+ // ─── Fuzzy Duplicate Detection ──────────────────────────────────
83
+ export function findFuzzyDuplicates(nodes, thresholds = { levenshtein: 2, cosine: 0.85 }) {
84
+ const duplicates = [];
85
+ const processed = new Set();
86
+ // Group nodes by type first to reduce comparisons
87
+ const nodesByType = new Map();
88
+ for (const node of nodes) {
89
+ const nodeType = node.type ?? 'unknown';
90
+ const group = nodesByType.get(nodeType) ?? [];
91
+ group.push(node);
92
+ nodesByType.set(nodeType, group);
93
+ }
94
+ // TF-IDF vectorizer for cosine similarity
95
+ const TfIdf = natural.TfIdf;
96
+ const tfidf = new TfIdf();
97
+ // Within each type group, find fuzzy matches
98
+ for (const [, typeNodes] of nodesByType) {
99
+ if (typeNodes.length < 2)
100
+ continue;
101
+ // Build TF-IDF corpus
102
+ const labels = typeNodes.map((n) => (n.data?.label ?? '').trim().toLowerCase());
103
+ for (const label of labels) {
104
+ if (label)
105
+ tfidf.addDocument(label);
106
+ }
107
+ for (let i = 0; i < typeNodes.length; i++) {
108
+ const node = typeNodes[i];
109
+ const label = (node.data?.label ?? '').trim();
110
+ if (!label || processed.has(node.id))
111
+ continue;
112
+ const similarNodes = [];
113
+ for (let j = i + 1; j < typeNodes.length; j++) {
114
+ const compareNode = typeNodes[j];
115
+ const compareLabel = (compareNode.data?.label ?? '').trim();
116
+ if (!compareLabel || processed.has(compareNode.id))
117
+ continue;
118
+ // Levenshtein distance (edit distance)
119
+ const distance = levenshtein.get(label.toLowerCase(), compareLabel.toLowerCase());
120
+ if (distance <= thresholds.levenshtein && distance > 0) {
121
+ // Distance 0 = exact match (handled by existing duplicate detection)
122
+ similarNodes.push({
123
+ nodeId: compareNode.id,
124
+ label: compareLabel,
125
+ similarity: 1 - distance / Math.max(label.length, compareLabel.length),
126
+ method: 'levenshtein'
127
+ });
128
+ }
129
+ else {
130
+ // Cosine similarity (semantic similarity)
131
+ tfidf.tfidfs(label.toLowerCase(), (k, measure) => {
132
+ if (k === j && measure >= thresholds.cosine) {
133
+ similarNodes.push({
134
+ nodeId: compareNode.id,
135
+ label: compareLabel,
136
+ similarity: measure,
137
+ method: 'cosine'
138
+ });
139
+ }
140
+ });
141
+ }
142
+ }
143
+ if (similarNodes.length > 0) {
144
+ duplicates.push({
145
+ primaryNodeId: node.id,
146
+ primaryLabel: label,
147
+ similarNodes: similarNodes.sort((a, b) => b.similarity - a.similarity)
148
+ });
149
+ // Mark all as processed
150
+ processed.add(node.id);
151
+ for (const sim of similarNodes) {
152
+ processed.add(sim.nodeId);
153
+ }
154
+ }
155
+ }
156
+ }
157
+ return duplicates;
158
+ }
159
+ // ─── Subgraph Detection (Connected Components) ───────────────────
160
+ export function detectSubgraphs(nodes, edges) {
161
+ // Build adjacency list
162
+ const adjacency = new Map();
163
+ const nodeIds = new Set(nodes.map((n) => n.id));
164
+ for (const node of nodes) {
165
+ adjacency.set(node.id, new Set());
166
+ }
167
+ for (const edge of edges) {
168
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
169
+ adjacency.get(edge.source)?.add(edge.target);
170
+ adjacency.get(edge.target)?.add(edge.source); // Undirected graph
171
+ }
172
+ }
173
+ // BFS to find connected components
174
+ const visited = new Set();
175
+ const clusters = [];
176
+ let clusterIndex = 0;
177
+ for (const node of nodes) {
178
+ if (visited.has(node.id))
179
+ continue;
180
+ // Start BFS from unvisited node
181
+ const cluster = [];
182
+ const queue = [node.id];
183
+ visited.add(node.id);
184
+ while (queue.length > 0) {
185
+ const current = queue.shift();
186
+ cluster.push(current);
187
+ const neighbors = adjacency.get(current) ?? new Set();
188
+ for (const neighbor of neighbors) {
189
+ if (!visited.has(neighbor)) {
190
+ visited.add(neighbor);
191
+ queue.push(neighbor);
192
+ }
193
+ }
194
+ }
195
+ const nodeLabels = cluster
196
+ .map((id) => {
197
+ const n = nodes.find((node) => node.id === id);
198
+ return n?.data?.label ?? 'Untitled';
199
+ });
200
+ clusters.push({
201
+ clusterIndex: clusterIndex++,
202
+ nodeIds: cluster,
203
+ nodeLabels,
204
+ isIsolated: cluster.length < nodes.length / 2 // Heuristic: small clusters are isolated
205
+ });
206
+ }
207
+ // Sort by size (largest first)
208
+ clusters.sort((a, b) => b.nodeIds.length - a.nodeIds.length);
209
+ // Mark small clusters as isolated (only if there's a dominant main cluster)
210
+ if (clusters.length > 1 && clusters[0].nodeIds.length > nodes.length * 0.5) {
211
+ for (let i = 1; i < clusters.length; i++) {
212
+ clusters[i].isIsolated = true;
213
+ }
214
+ }
215
+ return clusters;
216
+ }
217
+ // ─── Consolidation Suggestions ───────────────────────────────────
218
+ export function suggestConsolidations(nodes) {
219
+ const suggestions = [];
220
+ // Group by node type
221
+ const nodesByType = new Map();
222
+ for (const node of nodes) {
223
+ const nodeType = node.type ?? 'unknown';
224
+ const group = nodesByType.get(nodeType) ?? [];
225
+ group.push(node);
226
+ nodesByType.set(nodeType, group);
227
+ }
228
+ // For each type group, find nodes with same constraints
229
+ for (const [nodeType, typeNodes] of nodesByType) {
230
+ if (nodeType === 'datapoint') {
231
+ // Group by data type + constraints
232
+ const constraintGroups = new Map();
233
+ for (const node of typeNodes) {
234
+ const data = node.data;
235
+ const dataType = data.type ?? 'unknown';
236
+ const constraints = (data.constraints ?? []).sort().join('|');
237
+ const key = `${dataType}::${constraints}`;
238
+ const group = constraintGroups.get(key) ?? [];
239
+ group.push(node);
240
+ constraintGroups.set(key, group);
241
+ }
242
+ // Suggest consolidation for groups with multiple nodes
243
+ for (const [key, group] of constraintGroups) {
244
+ if (group.length > 1) {
245
+ const [dataType, constraintsStr] = key.split('::');
246
+ const hasConstraints = constraintsStr.length > 0;
247
+ suggestions.push({
248
+ targetLabel: group.map((n) => n.data?.label ?? '').join(' / '),
249
+ nodeIds: group.map((n) => n.id),
250
+ reason: 'same-type-and-constraints',
251
+ confidence: hasConstraints ? 'high' : 'medium'
252
+ });
253
+ }
254
+ }
255
+ }
256
+ else if (nodeType === 'component') {
257
+ // Group by displays/captures references
258
+ const referenceGroups = new Map();
259
+ for (const node of typeNodes) {
260
+ const data = node.data;
261
+ const displays = (data.displays ?? []).sort().join('|');
262
+ const captures = (data.captures ?? []).sort().join('|');
263
+ const key = `${displays}::${captures}`;
264
+ const group = referenceGroups.get(key) ?? [];
265
+ group.push(node);
266
+ referenceGroups.set(key, group);
267
+ }
268
+ for (const [key, group] of referenceGroups) {
269
+ if (group.length > 1) {
270
+ const [displaysStr, capturesStr] = key.split('::');
271
+ const hasReferences = displaysStr.length > 0 || capturesStr.length > 0;
272
+ suggestions.push({
273
+ targetLabel: group.map((n) => n.data?.label ?? '').join(' / '),
274
+ nodeIds: group.map((n) => n.id),
275
+ reason: 'duplicate-references',
276
+ confidence: hasReferences ? 'medium' : 'low'
277
+ });
278
+ }
279
+ }
280
+ }
281
+ }
282
+ return suggestions;
283
+ }
284
+ //# sourceMappingURL=analysisUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysisUtils.js","sourceRoot":"","sources":["../../src/analysis/analysisUtils.ts"],"names":[],"mappings":"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"}
package/dist/db.d.ts CHANGED
@@ -45,3 +45,71 @@ export declare function createEdgeViaApi(projectId: string, edge: {
45
45
  data: Record<string, unknown>;
46
46
  } | null>;
47
47
  export declare function deleteEdgeViaApi(projectId: string, edgeId: string): Promise<boolean>;
48
+ export declare function cloneProjectViaApi(projectId: string): Promise<string | null>;
49
+ export declare function uploadImageViaApi(base64Data: string, filename: string, contentType: string): Promise<{
50
+ url: string;
51
+ width: number;
52
+ height: number;
53
+ } | null>;
54
+ export declare function createScreenViaApi(projectId: string, name: string, imageUrl?: string, imageWidth?: number, imageHeight?: number, imageFilename?: string): Promise<{
55
+ id: string;
56
+ name: string;
57
+ } | null>;
58
+ export declare function updateScreenViaApi(projectId: string, screenId: string, updates: Partial<{
59
+ name: string;
60
+ imageUrl: string;
61
+ imageWidth: number;
62
+ imageHeight: number;
63
+ }>): Promise<{
64
+ id: string;
65
+ name: string;
66
+ } | null>;
67
+ export declare function deleteScreenViaApi(projectId: string, screenId: string): Promise<boolean>;
68
+ export declare function addRegionViaApi(projectId: string, screenId: string, region: {
69
+ label?: string;
70
+ position: {
71
+ x: number;
72
+ y: number;
73
+ };
74
+ size: {
75
+ width: number;
76
+ height: number;
77
+ };
78
+ elementIds?: string[];
79
+ componentNodeId?: string;
80
+ }): Promise<{
81
+ id: string;
82
+ label?: string;
83
+ } | null>;
84
+ export declare function updateRegionViaApi(projectId: string, screenId: string, regionId: string, updates: Partial<{
85
+ label: string;
86
+ position: {
87
+ x: number;
88
+ y: number;
89
+ };
90
+ size: {
91
+ width: number;
92
+ height: number;
93
+ };
94
+ elementIds: string[];
95
+ }>): Promise<{
96
+ id: string;
97
+ } | null>;
98
+ export declare function removeRegionViaApi(projectId: string, screenId: string, regionId: string): Promise<boolean>;
99
+ export declare function updateEdgeViaApi(projectId: string, edgeId: string, updates: Partial<{
100
+ type: string;
101
+ label: string;
102
+ sourceHandle: string | null;
103
+ targetHandle: string | null;
104
+ }>): Promise<{
105
+ id: string;
106
+ } | null>;
107
+ export declare function bulkImportCanvasState(projectId: string, canvasState: {
108
+ nodes: any[];
109
+ edges: any[];
110
+ screens?: any[];
111
+ }, merge: boolean): Promise<{
112
+ nodeCount: number;
113
+ edgeCount: number;
114
+ screenCount: number;
115
+ }>;
package/dist/db.js CHANGED
@@ -281,4 +281,263 @@ export async function deleteEdgeViaApi(projectId, edgeId) {
281
281
  `;
282
282
  return true;
283
283
  }
284
+ // ─── v3.0 API functions ────────────────────────────────────────────
285
+ export async function cloneProjectViaApi(projectId) {
286
+ if (MODE === 'local') {
287
+ const res = await fetchLocal(`/api/projects/${projectId}/clone`, { method: 'POST' });
288
+ if (!res.ok)
289
+ return null;
290
+ const data = await res.json();
291
+ return data.id;
292
+ }
293
+ const project = await getProject(projectId);
294
+ if (!project)
295
+ return null;
296
+ const rows = await sql `
297
+ INSERT INTO projects (name, canvas_state, user_id)
298
+ VALUES (${project.name + ' (Copy)'}, ${JSON.stringify(project.canvas_state)}, ${FLOWSPEC_USER_ID})
299
+ RETURNING id
300
+ `;
301
+ return rows[0].id;
302
+ }
303
+ export async function uploadImageViaApi(base64Data, filename, contentType) {
304
+ if (MODE !== 'local') {
305
+ throw new Error('Image upload is only supported in local mode');
306
+ }
307
+ const res = await fetchLocal('/api/images', {
308
+ method: 'POST',
309
+ body: JSON.stringify({ base64Data, filename, contentType }),
310
+ });
311
+ if (!res.ok)
312
+ return null;
313
+ return res.json();
314
+ }
315
+ export async function createScreenViaApi(projectId, name, imageUrl, imageWidth, imageHeight, imageFilename) {
316
+ if (MODE === 'local') {
317
+ const res = await fetchLocal(`/api/projects/${projectId}/screens`, {
318
+ method: 'POST',
319
+ body: JSON.stringify({ name, imageUrl, imageWidth, imageHeight, imageFilename }),
320
+ });
321
+ if (!res.ok)
322
+ return null;
323
+ return res.json();
324
+ }
325
+ const project = await getProject(projectId);
326
+ if (!project)
327
+ return null;
328
+ const screenId = randomUUID();
329
+ const newScreen = {
330
+ id: screenId,
331
+ name,
332
+ imageUrl: imageUrl ?? null,
333
+ imageWidth: imageWidth ?? null,
334
+ imageHeight: imageHeight ?? null,
335
+ imageFilename: imageFilename ?? null,
336
+ regions: [],
337
+ };
338
+ if (!project.canvas_state.screens) {
339
+ project.canvas_state.screens = [];
340
+ }
341
+ project.canvas_state.screens.push(newScreen);
342
+ await sql `
343
+ UPDATE projects
344
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
345
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
346
+ `;
347
+ return { id: screenId, name };
348
+ }
349
+ export async function updateScreenViaApi(projectId, screenId, updates) {
350
+ if (MODE === 'local') {
351
+ const res = await fetchLocal(`/api/projects/${projectId}/screens/${screenId}`, {
352
+ method: 'PATCH',
353
+ body: JSON.stringify(updates),
354
+ });
355
+ if (!res.ok)
356
+ return null;
357
+ return res.json();
358
+ }
359
+ const project = await getProject(projectId);
360
+ if (!project || !project.canvas_state.screens)
361
+ return null;
362
+ const screen = project.canvas_state.screens.find((s) => s.id === screenId);
363
+ if (!screen)
364
+ return null;
365
+ Object.assign(screen, updates);
366
+ await sql `
367
+ UPDATE projects
368
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
369
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
370
+ `;
371
+ return { id: screenId, name: screen.name };
372
+ }
373
+ export async function deleteScreenViaApi(projectId, screenId) {
374
+ if (MODE === 'local') {
375
+ const res = await fetchLocal(`/api/projects/${projectId}/screens/${screenId}`, { method: 'DELETE' });
376
+ return res.ok;
377
+ }
378
+ const project = await getProject(projectId);
379
+ if (!project || !project.canvas_state.screens)
380
+ return false;
381
+ const idx = project.canvas_state.screens.findIndex((s) => s.id === screenId);
382
+ if (idx === -1)
383
+ return false;
384
+ project.canvas_state.screens.splice(idx, 1);
385
+ await sql `
386
+ UPDATE projects
387
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
388
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
389
+ `;
390
+ return true;
391
+ }
392
+ export async function addRegionViaApi(projectId, screenId, region) {
393
+ if (MODE === 'local') {
394
+ const res = await fetchLocal(`/api/projects/${projectId}/screens/${screenId}/regions`, {
395
+ method: 'POST',
396
+ body: JSON.stringify(region),
397
+ });
398
+ if (!res.ok)
399
+ return null;
400
+ return res.json();
401
+ }
402
+ const project = await getProject(projectId);
403
+ if (!project || !project.canvas_state.screens)
404
+ return null;
405
+ const screen = project.canvas_state.screens.find((s) => s.id === screenId);
406
+ if (!screen)
407
+ return null;
408
+ const regionId = randomUUID();
409
+ const newRegion = {
410
+ id: regionId,
411
+ label: region.label ?? null,
412
+ position: region.position,
413
+ size: region.size,
414
+ elementIds: region.elementIds ?? [],
415
+ componentNodeId: region.componentNodeId ?? null,
416
+ };
417
+ if (!screen.regions)
418
+ screen.regions = [];
419
+ screen.regions.push(newRegion);
420
+ await sql `
421
+ UPDATE projects
422
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
423
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
424
+ `;
425
+ return { id: regionId, label: region.label };
426
+ }
427
+ export async function updateRegionViaApi(projectId, screenId, regionId, updates) {
428
+ if (MODE === 'local') {
429
+ const res = await fetchLocal(`/api/projects/${projectId}/screens/${screenId}/regions/${regionId}`, {
430
+ method: 'PATCH',
431
+ body: JSON.stringify(updates),
432
+ });
433
+ if (!res.ok)
434
+ return null;
435
+ return res.json();
436
+ }
437
+ const project = await getProject(projectId);
438
+ if (!project || !project.canvas_state.screens)
439
+ return null;
440
+ const screen = project.canvas_state.screens.find((s) => s.id === screenId);
441
+ if (!screen || !screen.regions)
442
+ return null;
443
+ const region = screen.regions.find((r) => r.id === regionId);
444
+ if (!region)
445
+ return null;
446
+ Object.assign(region, updates);
447
+ await sql `
448
+ UPDATE projects
449
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
450
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
451
+ `;
452
+ return { id: regionId };
453
+ }
454
+ export async function removeRegionViaApi(projectId, screenId, regionId) {
455
+ if (MODE === 'local') {
456
+ const res = await fetchLocal(`/api/projects/${projectId}/screens/${screenId}/regions/${regionId}`, { method: 'DELETE' });
457
+ return res.ok;
458
+ }
459
+ const project = await getProject(projectId);
460
+ if (!project || !project.canvas_state.screens)
461
+ return false;
462
+ const screen = project.canvas_state.screens.find((s) => s.id === screenId);
463
+ if (!screen || !screen.regions)
464
+ return false;
465
+ const idx = screen.regions.findIndex((r) => r.id === regionId);
466
+ if (idx === -1)
467
+ return false;
468
+ screen.regions.splice(idx, 1);
469
+ await sql `
470
+ UPDATE projects
471
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
472
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
473
+ `;
474
+ return true;
475
+ }
476
+ export async function updateEdgeViaApi(projectId, edgeId, updates) {
477
+ if (MODE === 'local') {
478
+ const res = await fetchLocal(`/api/projects/${projectId}/edges/${edgeId}`, {
479
+ method: 'PATCH',
480
+ body: JSON.stringify(updates),
481
+ });
482
+ if (!res.ok)
483
+ return null;
484
+ return res.json();
485
+ }
486
+ const project = await getProject(projectId);
487
+ if (!project)
488
+ return null;
489
+ const edge = project.canvas_state.edges.find((e) => e.id === edgeId);
490
+ if (!edge)
491
+ return null;
492
+ Object.assign(edge, updates);
493
+ if (updates.label !== undefined) {
494
+ if (!edge.data)
495
+ edge.data = {};
496
+ edge.data.label = updates.label;
497
+ }
498
+ await sql `
499
+ UPDATE projects
500
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
501
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
502
+ `;
503
+ return { id: edgeId };
504
+ }
505
+ export async function bulkImportCanvasState(projectId, canvasState, merge) {
506
+ if (MODE === 'local') {
507
+ const res = await fetchLocal(`/api/projects/${projectId}/import`, {
508
+ method: 'POST',
509
+ body: JSON.stringify({ canvasState, merge }),
510
+ });
511
+ if (!res.ok)
512
+ throw new Error(`Failed to import: ${res.status}`);
513
+ return res.json();
514
+ }
515
+ const project = await getProject(projectId);
516
+ if (!project)
517
+ throw new Error('Project not found');
518
+ if (merge) {
519
+ // Merge mode: add new nodes/edges/screens
520
+ project.canvas_state.nodes.push(...canvasState.nodes);
521
+ project.canvas_state.edges.push(...canvasState.edges);
522
+ if (canvasState.screens) {
523
+ if (!project.canvas_state.screens)
524
+ project.canvas_state.screens = [];
525
+ project.canvas_state.screens.push(...canvasState.screens);
526
+ }
527
+ }
528
+ else {
529
+ // Replace mode: replace entire canvas state
530
+ project.canvas_state = canvasState;
531
+ }
532
+ await sql `
533
+ UPDATE projects
534
+ SET canvas_state = ${JSON.stringify(project.canvas_state)}::jsonb, updated_at = NOW()
535
+ WHERE id = ${projectId} AND user_id = ${FLOWSPEC_USER_ID}
536
+ `;
537
+ return {
538
+ nodeCount: canvasState.nodes.length,
539
+ edgeCount: canvasState.edges.length,
540
+ screenCount: canvasState.screens?.length ?? 0,
541
+ };
542
+ }
284
543
  //# sourceMappingURL=db.js.map