@n8n-as-code/sync 0.5.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 (50) hide show
  1. package/README.md +22 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +10 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/services/directory-utils.d.ts +35 -0
  7. package/dist/services/directory-utils.d.ts.map +1 -0
  8. package/dist/services/directory-utils.js +75 -0
  9. package/dist/services/directory-utils.js.map +1 -0
  10. package/dist/services/hash-utils.d.ts +22 -0
  11. package/dist/services/hash-utils.d.ts.map +1 -0
  12. package/dist/services/hash-utils.js +31 -0
  13. package/dist/services/hash-utils.js.map +1 -0
  14. package/dist/services/n8n-api-client.d.ts +23 -0
  15. package/dist/services/n8n-api-client.d.ts.map +1 -0
  16. package/dist/services/n8n-api-client.js +193 -0
  17. package/dist/services/n8n-api-client.js.map +1 -0
  18. package/dist/services/resolution-manager.d.ts +73 -0
  19. package/dist/services/resolution-manager.d.ts.map +1 -0
  20. package/dist/services/resolution-manager.js +149 -0
  21. package/dist/services/resolution-manager.js.map +1 -0
  22. package/dist/services/state-manager.d.ts +43 -0
  23. package/dist/services/state-manager.d.ts.map +1 -0
  24. package/dist/services/state-manager.js +68 -0
  25. package/dist/services/state-manager.js.map +1 -0
  26. package/dist/services/sync-engine.d.ts +56 -0
  27. package/dist/services/sync-engine.d.ts.map +1 -0
  28. package/dist/services/sync-engine.js +312 -0
  29. package/dist/services/sync-engine.js.map +1 -0
  30. package/dist/services/sync-manager.d.ts +37 -0
  31. package/dist/services/sync-manager.d.ts.map +1 -0
  32. package/dist/services/sync-manager.js +280 -0
  33. package/dist/services/sync-manager.js.map +1 -0
  34. package/dist/services/trash-service.d.ts +17 -0
  35. package/dist/services/trash-service.d.ts.map +1 -0
  36. package/dist/services/trash-service.js +41 -0
  37. package/dist/services/trash-service.js.map +1 -0
  38. package/dist/services/watcher.d.ts +135 -0
  39. package/dist/services/watcher.d.ts.map +1 -0
  40. package/dist/services/watcher.js +655 -0
  41. package/dist/services/watcher.js.map +1 -0
  42. package/dist/services/workflow-sanitizer.d.ts +23 -0
  43. package/dist/services/workflow-sanitizer.d.ts.map +1 -0
  44. package/dist/services/workflow-sanitizer.js +100 -0
  45. package/dist/services/workflow-sanitizer.js.map +1 -0
  46. package/dist/types.d.ts +46 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +12 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +43 -0
@@ -0,0 +1,149 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { WorkflowSanitizer } from './workflow-sanitizer.js';
4
+ import { HashUtils } from './hash-utils.js';
5
+ /**
6
+ * Resolution & Validation Manager
7
+ *
8
+ * Responsibilities:
9
+ * 1. Conflict Resolution (6.1 from spec)
10
+ * 2. Deletion Validation (6.2 from spec)
11
+ *
12
+ * Bridges user intent with Sync Engine operations
13
+ */
14
+ export class ResolutionManager {
15
+ syncEngine;
16
+ watcher;
17
+ client;
18
+ directory;
19
+ constructor(syncEngine, watcher, client) {
20
+ this.syncEngine = syncEngine;
21
+ this.watcher = watcher;
22
+ this.client = client;
23
+ // Get directory from sync engine (private access)
24
+ this.directory = syncEngine.directory;
25
+ }
26
+ /**
27
+ * 6.1 Conflict Resolution - KEEP LOCAL
28
+ *
29
+ * Action: Force PUSH (Overwrite Remote with Local)
30
+ * Commit: Update lastSyncedHash = LocalHash. Status becomes IN_SYNC.
31
+ */
32
+ async keepLocal(workflowId, filename) {
33
+ const finalWorkflowId = await this.syncEngine.forcePush(workflowId, filename);
34
+ // Note: forcePush already calls watcher.finalizeSync
35
+ return finalWorkflowId;
36
+ }
37
+ /**
38
+ * 6.1 Conflict Resolution - KEEP REMOTE
39
+ *
40
+ * Action: Force PULL (Overwrite Local with Remote)
41
+ * Commit: Update lastSyncedHash = RemoteHash. Status becomes IN_SYNC.
42
+ */
43
+ async keepRemote(workflowId, filename) {
44
+ await this.syncEngine.forcePull(workflowId, filename);
45
+ // Note: forcePull already calls watcher.finalizeSync
46
+ }
47
+ /**
48
+ * 6.1 Conflict Resolution - SHOW DIFF
49
+ *
50
+ * Opens diff editor (implementation depends on UI layer)
51
+ * Returns diff data for UI to display
52
+ */
53
+ async showDiff(workflowId, filename) {
54
+ // Get local content
55
+ const filePath = path.join(this.directory, filename);
56
+ const localContent = this.readJsonFile(filePath);
57
+ // Get remote content
58
+ const remoteContent = await this.client.getWorkflow(workflowId);
59
+ if (!localContent || !remoteContent) {
60
+ throw new Error('Cannot show diff: missing local or remote content');
61
+ }
62
+ // Clean for comparison
63
+ const cleanLocal = WorkflowSanitizer.cleanForStorage(localContent);
64
+ const cleanRemote = WorkflowSanitizer.cleanForStorage(remoteContent);
65
+ // Compute hashes
66
+ const localHash = HashUtils.computeHash(cleanLocal);
67
+ const remoteHash = HashUtils.computeHash(cleanRemote);
68
+ return {
69
+ localContent: cleanLocal,
70
+ remoteContent: cleanRemote,
71
+ localHash,
72
+ remoteHash
73
+ };
74
+ }
75
+ /**
76
+ * 6.2 Deletion Validation - CONFIRM DELETION
77
+ *
78
+ * Case Deleted Locally: Send DELETE to API -> Remove from state
79
+ * Case Deleted Remotely: Move local file to _archive/ -> Remove from state
80
+ */
81
+ async confirmDeletion(workflowId, filename, deletionType) {
82
+ if (deletionType === 'local') {
83
+ // Local file was deleted, confirm remote deletion
84
+ await this.syncEngine.deleteRemote(workflowId, filename);
85
+ // Remove from state
86
+ await this.watcher.removeWorkflowState(workflowId);
87
+ }
88
+ else {
89
+ // Remote workflow was deleted, confirm local archiving
90
+ await this.syncEngine.archive(filename);
91
+ // Remove from state
92
+ await this.watcher.removeWorkflowState(workflowId);
93
+ }
94
+ }
95
+ /**
96
+ * 6.2 Deletion Validation - RESTORE WORKFLOW
97
+ *
98
+ * Case Deleted Locally: Move file from _archive/ to workflows/
99
+ * Case Deleted Remotely: Force PUSH (Re-create on Remote)
100
+ */
101
+ async restoreWorkflow(workflowId, filename, deletionType) {
102
+ if (deletionType === 'local') {
103
+ // Restore from archive
104
+ const restored = await this.syncEngine.restoreFromArchive(filename);
105
+ if (!restored) {
106
+ throw new Error(`Cannot restore ${filename}: not found in archive`);
107
+ }
108
+ // Watcher will detect file addition and update status
109
+ return workflowId;
110
+ }
111
+ else {
112
+ // Re-create on remote (force push)
113
+ const finalWorkflowId = await this.syncEngine.forcePush(workflowId, filename);
114
+ return finalWorkflowId;
115
+ }
116
+ }
117
+ /**
118
+ * Get current status for a workflow
119
+ */
120
+ async getWorkflowStatus(workflowId, filename) {
121
+ const status = this.watcher.calculateStatus(filename, workflowId);
122
+ const lastSyncedHash = this.watcher.getLastSyncedHash(workflowId);
123
+ // Get local hash
124
+ const filePath = path.join(this.directory, filename);
125
+ const localContent = this.readJsonFile(filePath);
126
+ const localHash = localContent ?
127
+ HashUtils.computeHash(WorkflowSanitizer.cleanForStorage(localContent)) :
128
+ undefined;
129
+ // Get remote hash from watcher cache
130
+ const remoteHash = this.watcher.remoteHashes?.get(workflowId);
131
+ return {
132
+ status,
133
+ localExists: !!localContent,
134
+ remoteExists: !!remoteHash,
135
+ lastSyncedHash,
136
+ localHash,
137
+ remoteHash
138
+ };
139
+ }
140
+ readJsonFile(filePath) {
141
+ try {
142
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
148
+ }
149
+ //# sourceMappingURL=resolution-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolution-manager.js","sourceRoot":"","sources":["../../src/services/resolution-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ export interface IWorkflowState {
2
+ lastSyncedHash: string;
3
+ lastSyncedAt: string;
4
+ }
5
+ export interface IInstanceState {
6
+ workflows: Record<string, IWorkflowState>;
7
+ }
8
+ /**
9
+ * Read-only State Manager
10
+ *
11
+ * Responsibilities:
12
+ * 1. Read state from .n8n-state.json
13
+ * 2. Provide read-only access to workflow states
14
+ *
15
+ * Note: Write operations are handled exclusively by Watcher component
16
+ * to maintain single source of truth for state mutations.
17
+ */
18
+ export declare class StateManager {
19
+ private stateFilePath;
20
+ constructor(directory: string);
21
+ /**
22
+ * Load state from disk (private method)
23
+ */
24
+ private load;
25
+ /**
26
+ * Gets the last known state (Base) for a workflow.
27
+ */
28
+ getWorkflowState(id: string): IWorkflowState | undefined;
29
+ /**
30
+ * Gets all tracked workflow IDs.
31
+ */
32
+ getTrackedWorkflowIds(): string[];
33
+ /**
34
+ * Checks if a hash matches the last synced state.
35
+ */
36
+ isSynced(id: string, currentHash: string): boolean;
37
+ /**
38
+ * Get the entire state object (for Watcher's internal use)
39
+ * @internal
40
+ */
41
+ getFullState(): IInstanceState;
42
+ }
43
+ //# sourceMappingURL=state-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-manager.d.ts","sourceRoot":"","sources":["../../src/services/state-manager.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC7C;AAED;;;;;;;;;GASG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,aAAa,CAAS;gBAElB,SAAS,EAAE,MAAM;IAI7B;;OAEG;IACH,OAAO,CAAC,IAAI;IAgBZ;;OAEG;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAKxD;;OAEG;IACH,qBAAqB,IAAI,MAAM,EAAE;IAKjC;;OAEG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO;IAMlD;;;OAGG;IACH,YAAY,IAAI,cAAc;CAGjC"}
@@ -0,0 +1,68 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Read-only State Manager
5
+ *
6
+ * Responsibilities:
7
+ * 1. Read state from .n8n-state.json
8
+ * 2. Provide read-only access to workflow states
9
+ *
10
+ * Note: Write operations are handled exclusively by Watcher component
11
+ * to maintain single source of truth for state mutations.
12
+ */
13
+ export class StateManager {
14
+ stateFilePath;
15
+ constructor(directory) {
16
+ this.stateFilePath = path.join(directory, '.n8n-state.json');
17
+ }
18
+ /**
19
+ * Load state from disk (private method)
20
+ */
21
+ load() {
22
+ if (fs.existsSync(this.stateFilePath)) {
23
+ try {
24
+ const data = JSON.parse(fs.readFileSync(this.stateFilePath, 'utf-8'));
25
+ // Ensure workflows object exists
26
+ if (!data.workflows) {
27
+ data.workflows = {};
28
+ }
29
+ return data;
30
+ }
31
+ catch (e) {
32
+ console.warn('Could not read state file, using empty state');
33
+ }
34
+ }
35
+ return { workflows: {} };
36
+ }
37
+ /**
38
+ * Gets the last known state (Base) for a workflow.
39
+ */
40
+ getWorkflowState(id) {
41
+ const state = this.load();
42
+ return state.workflows[id];
43
+ }
44
+ /**
45
+ * Gets all tracked workflow IDs.
46
+ */
47
+ getTrackedWorkflowIds() {
48
+ const state = this.load();
49
+ return Object.keys(state.workflows);
50
+ }
51
+ /**
52
+ * Checks if a hash matches the last synced state.
53
+ */
54
+ isSynced(id, currentHash) {
55
+ const state = this.getWorkflowState(id);
56
+ if (!state)
57
+ return false;
58
+ return state.lastSyncedHash === currentHash;
59
+ }
60
+ /**
61
+ * Get the entire state object (for Watcher's internal use)
62
+ * @internal
63
+ */
64
+ getFullState() {
65
+ return this.load();
66
+ }
67
+ }
68
+ //# sourceMappingURL=state-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-manager.js","sourceRoot":"","sources":["../../src/services/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB;;;;;;;;;GASG;AACH,MAAM,OAAO,YAAY;IACb,aAAa,CAAS;IAE9B,YAAY,SAAiB;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IACjE,CAAC;IAED;;OAEG;IACK,IAAI;QACR,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;gBACtE,iCAAiC;gBACjC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;oBAClB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;gBACxB,CAAC;gBACD,OAAO,IAAI,CAAC;YAChB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,OAAO,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;YACjE,CAAC;QACL,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,EAAU;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,qBAAqB;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,EAAU,EAAE,WAAmB;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,OAAO,KAAK,CAAC,cAAc,KAAK,WAAW,CAAC;IAChD,CAAC;IAED;;;OAGG;IACH,YAAY;QACR,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;CACJ"}
@@ -0,0 +1,56 @@
1
+ import { N8nApiClient } from './n8n-api-client.js';
2
+ import { Watcher } from './watcher.js';
3
+ import { WorkflowSyncStatus } from '../types.js';
4
+ /**
5
+ * Sync Engine - State Mutation Component
6
+ *
7
+ * Responsibilities:
8
+ * 1. Execute PULL/PUSH operations based on status
9
+ * 2. Call Watcher.finalizeSync after successful operations
10
+ * 3. Handle archive operations
11
+ *
12
+ * Stateless regarding history - never writes to state file directly
13
+ */
14
+ export declare class SyncEngine {
15
+ private client;
16
+ private watcher;
17
+ private directory;
18
+ private archiveDirectory;
19
+ constructor(client: N8nApiClient, watcher: Watcher, directory: string);
20
+ /**
21
+ * PULL Strategy: Remote -> Local
22
+ * Based on spec 5.2 PULL Strategy table
23
+ */
24
+ pull(workflowId: string, filename: string, status: WorkflowSyncStatus): Promise<void>;
25
+ /**
26
+ * PUSH Strategy: Local -> Remote
27
+ * Based on spec 5.3 PUSH Strategy table
28
+ */
29
+ push(filename: string, workflowId?: string, status?: WorkflowSyncStatus): Promise<string>;
30
+ /**
31
+ * Force PULL - overwrite local with remote (for conflict resolution)
32
+ */
33
+ forcePull(workflowId: string, filename: string): Promise<void>;
34
+ /**
35
+ * Force PUSH - overwrite remote with local (for conflict resolution and restoration)
36
+ * If workflow doesn't exist on remote, creates it
37
+ */
38
+ forcePush(workflowId: string, filename: string): Promise<string>;
39
+ /**
40
+ * Delete remote workflow (for deletion validation)
41
+ * Note: The Watcher already archived the remote content when it detected the local deletion
42
+ */
43
+ deleteRemote(workflowId: string, filename: string): Promise<void>;
44
+ /**
45
+ * Restore from archive (for deletion validation)
46
+ * Moves the file from archive back to workflows directory
47
+ * Then DELETES the archive file (no need to keep it after restoration)
48
+ */
49
+ restoreFromArchive(filename: string): Promise<boolean>;
50
+ private executePull;
51
+ private executeUpdate;
52
+ private executeCreate;
53
+ archive(filename: string): Promise<void>;
54
+ private readJsonFile;
55
+ }
56
+ //# sourceMappingURL=sync-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/services/sync-engine.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAa,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;GASG;AACH,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAS;gBAG7B,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM;IAYrB;;;OAGG;IACU,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDlG;;;OAGG;IACU,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IA8DtG;;OAEG;IACU,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3E;;;OAGG;IACU,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkC7E;;;OAGG;IACU,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB9E;;;;OAIG;IACU,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAyBrD,WAAW;YAwBX,aAAa;YA0Bb,aAAa;IAyBd,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrD,OAAO,CAAC,YAAY;CAOvB"}
@@ -0,0 +1,312 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { WorkflowSanitizer } from './workflow-sanitizer.js';
4
+ import { HashUtils } from './hash-utils.js';
5
+ import { WorkflowSyncStatus } from '../types.js';
6
+ /**
7
+ * Sync Engine - State Mutation Component
8
+ *
9
+ * Responsibilities:
10
+ * 1. Execute PULL/PUSH operations based on status
11
+ * 2. Call Watcher.finalizeSync after successful operations
12
+ * 3. Handle archive operations
13
+ *
14
+ * Stateless regarding history - never writes to state file directly
15
+ */
16
+ export class SyncEngine {
17
+ client;
18
+ watcher;
19
+ directory;
20
+ archiveDirectory;
21
+ constructor(client, watcher, directory) {
22
+ this.client = client;
23
+ this.watcher = watcher;
24
+ this.directory = directory;
25
+ this.archiveDirectory = path.join(directory, '.archive');
26
+ if (!fs.existsSync(this.archiveDirectory)) {
27
+ fs.mkdirSync(this.archiveDirectory, { recursive: true });
28
+ }
29
+ }
30
+ /**
31
+ * PULL Strategy: Remote -> Local
32
+ * Based on spec 5.2 PULL Strategy table
33
+ */
34
+ async pull(workflowId, filename, status) {
35
+ // Mark sync in progress to prevent race conditions
36
+ this.watcher.markSyncInProgress(workflowId);
37
+ this.watcher.pauseObservation(workflowId);
38
+ try {
39
+ switch (status) {
40
+ case WorkflowSyncStatus.EXIST_ONLY_REMOTELY:
41
+ // Download Remote JSON -> Write to disk
42
+ await this.executePull(workflowId, filename);
43
+ // Initialize lastSyncedHash via finalizeSync
44
+ await this.watcher.finalizeSync(workflowId);
45
+ break;
46
+ case WorkflowSyncStatus.MODIFIED_REMOTELY:
47
+ // Download Remote JSON -> Overwrite local file
48
+ await this.executePull(workflowId, filename);
49
+ // Update lastSyncedHash via finalizeSync
50
+ await this.watcher.finalizeSync(workflowId);
51
+ break;
52
+ case WorkflowSyncStatus.DELETED_REMOTELY:
53
+ // Move local file to archive
54
+ await this.archive(filename);
55
+ // Remove from state (handled by watcher after observation resumes)
56
+ // Watcher will detect file deletion and update status
57
+ break;
58
+ case WorkflowSyncStatus.CONFLICT:
59
+ // Halt - trigger conflict resolution
60
+ throw new Error(`Conflict detected for workflow ${workflowId}. Use resolveConflict instead.`);
61
+ case WorkflowSyncStatus.DELETED_LOCALLY:
62
+ // No action per spec
63
+ break;
64
+ case WorkflowSyncStatus.EXIST_ONLY_LOCALLY:
65
+ case WorkflowSyncStatus.IN_SYNC:
66
+ case WorkflowSyncStatus.MODIFIED_LOCALLY:
67
+ // No action per spec
68
+ break;
69
+ default:
70
+ console.warn(`[SyncEngine] Unhandled status ${status} for PULL operation`);
71
+ break;
72
+ }
73
+ }
74
+ finally {
75
+ this.watcher.markSyncComplete(workflowId);
76
+ this.watcher.resumeObservation(workflowId);
77
+ }
78
+ }
79
+ /**
80
+ * PUSH Strategy: Local -> Remote
81
+ * Based on spec 5.3 PUSH Strategy table
82
+ */
83
+ async push(filename, workflowId, status) {
84
+ // If workflow has an ID, pause observation by ID
85
+ if (workflowId) {
86
+ this.watcher.markSyncInProgress(workflowId);
87
+ this.watcher.pauseObservation(workflowId);
88
+ }
89
+ else {
90
+ // If no ID yet (new workflow), pause observation by filename
91
+ this.watcher.pauseObservationByFilename(filename);
92
+ }
93
+ try {
94
+ // If no workflowId, treat as EXIST_ONLY_LOCALLY
95
+ if (!workflowId || status === WorkflowSyncStatus.EXIST_ONLY_LOCALLY) {
96
+ // POST to API (Create)
97
+ const newWorkflowId = await this.executeCreate(filename);
98
+ // Initialize lastSyncedHash via finalizeSync
99
+ await this.watcher.finalizeSync(newWorkflowId);
100
+ return newWorkflowId;
101
+ }
102
+ // With workflowId and status
103
+ switch (status) {
104
+ case WorkflowSyncStatus.MODIFIED_LOCALLY:
105
+ // PUT to API (Update)
106
+ await this.executeUpdate(workflowId, filename);
107
+ // Update lastSyncedHash via finalizeSync
108
+ await this.watcher.finalizeSync(workflowId);
109
+ return workflowId;
110
+ case WorkflowSyncStatus.DELETED_LOCALLY:
111
+ // Step 1: Archive Remote to _archive/
112
+ await this.archive(filename);
113
+ // Step 2: Trigger Deletion Validation (caller should handle)
114
+ // Note: Actual API deletion happens in ResolutionManager
115
+ throw new Error(`Local deletion detected for workflow ${workflowId}. Use confirmDeletion instead.`);
116
+ case WorkflowSyncStatus.CONFLICT:
117
+ // Halt - trigger conflict resolution
118
+ throw new Error(`Conflict detected for workflow ${workflowId}. Use resolveConflict instead.`);
119
+ case WorkflowSyncStatus.EXIST_ONLY_REMOTELY:
120
+ case WorkflowSyncStatus.IN_SYNC:
121
+ case WorkflowSyncStatus.MODIFIED_REMOTELY:
122
+ case WorkflowSyncStatus.DELETED_REMOTELY:
123
+ // No action per spec
124
+ return workflowId;
125
+ default:
126
+ console.warn(`[SyncEngine] Unhandled status ${status} for PUSH operation`);
127
+ return workflowId;
128
+ }
129
+ }
130
+ finally {
131
+ if (workflowId) {
132
+ this.watcher.markSyncComplete(workflowId);
133
+ this.watcher.resumeObservation(workflowId);
134
+ }
135
+ else {
136
+ // Resume observation by filename if no ID (new workflow case)
137
+ this.watcher.resumeObservationByFilename(filename);
138
+ }
139
+ }
140
+ }
141
+ /**
142
+ * Force PULL - overwrite local with remote (for conflict resolution)
143
+ */
144
+ async forcePull(workflowId, filename) {
145
+ this.watcher.markSyncInProgress(workflowId);
146
+ this.watcher.pauseObservation(workflowId);
147
+ try {
148
+ await this.executePull(workflowId, filename);
149
+ await this.watcher.finalizeSync(workflowId);
150
+ }
151
+ finally {
152
+ this.watcher.markSyncComplete(workflowId);
153
+ this.watcher.resumeObservation(workflowId);
154
+ }
155
+ }
156
+ /**
157
+ * Force PUSH - overwrite remote with local (for conflict resolution and restoration)
158
+ * If workflow doesn't exist on remote, creates it
159
+ */
160
+ async forcePush(workflowId, filename) {
161
+ this.watcher.markSyncInProgress(workflowId);
162
+ this.watcher.pauseObservation(workflowId);
163
+ let finalWorkflowId = workflowId;
164
+ try {
165
+ // Try to update first
166
+ try {
167
+ await this.executeUpdate(workflowId, filename);
168
+ }
169
+ catch (error) {
170
+ // If update fails with 404, create the workflow instead
171
+ if (error.response?.status === 404 || error.message?.includes('404') || error.message?.includes('Not Found')) {
172
+ console.log(`[SyncEngine] Workflow ${workflowId} not found, creating new workflow`);
173
+ const newWorkflowId = await this.executeCreate(filename);
174
+ // Migrate state from old ID to new ID
175
+ if (newWorkflowId !== workflowId) {
176
+ await this.watcher.updateWorkflowId(workflowId, newWorkflowId);
177
+ finalWorkflowId = newWorkflowId;
178
+ }
179
+ }
180
+ else {
181
+ throw error;
182
+ }
183
+ }
184
+ await this.watcher.finalizeSync(finalWorkflowId);
185
+ return finalWorkflowId;
186
+ }
187
+ finally {
188
+ this.watcher.markSyncComplete(finalWorkflowId);
189
+ this.watcher.resumeObservation(finalWorkflowId);
190
+ }
191
+ }
192
+ /**
193
+ * Delete remote workflow (for deletion validation)
194
+ * Note: The Watcher already archived the remote content when it detected the local deletion
195
+ */
196
+ async deleteRemote(workflowId, filename) {
197
+ this.watcher.markSyncInProgress(workflowId);
198
+ this.watcher.pauseObservation(workflowId);
199
+ try {
200
+ // Delete from API
201
+ await this.client.deleteWorkflow(workflowId);
202
+ // Archive local file if it still exists (edge case - shouldn't happen for DELETED_LOCALLY)
203
+ await this.archive(filename);
204
+ // Note: State removal will be handled by caller (ResolutionManager)
205
+ }
206
+ finally {
207
+ this.watcher.markSyncComplete(workflowId);
208
+ this.watcher.resumeObservation(workflowId);
209
+ }
210
+ }
211
+ /**
212
+ * Restore from archive (for deletion validation)
213
+ * Moves the file from archive back to workflows directory
214
+ * Then DELETES the archive file (no need to keep it after restoration)
215
+ */
216
+ async restoreFromArchive(filename) {
217
+ const archiveFiles = fs.readdirSync(this.archiveDirectory);
218
+ const matchingArchives = archiveFiles.filter(f => f.includes(filename));
219
+ if (matchingArchives.length === 0) {
220
+ return false;
221
+ }
222
+ // Get most recent archive
223
+ const mostRecent = matchingArchives.sort().reverse()[0];
224
+ const archivePath = path.join(this.archiveDirectory, mostRecent);
225
+ const targetPath = path.join(this.directory, filename);
226
+ // Read content from archive
227
+ const content = fs.readFileSync(archivePath, 'utf-8');
228
+ // Write to target location
229
+ fs.writeFileSync(targetPath, content);
230
+ // Delete the archive file (no need to keep it after restoration)
231
+ fs.unlinkSync(archivePath);
232
+ return true;
233
+ }
234
+ async executePull(workflowId, filename) {
235
+ const fullWf = await this.client.getWorkflow(workflowId);
236
+ if (!fullWf) {
237
+ // Workflow might have been deleted (DELETED_REMOTELY case)
238
+ // Check if local file exists - if so, archive it
239
+ const filePath = path.join(this.directory, filename);
240
+ if (fs.existsSync(filePath)) {
241
+ await this.archive(filename);
242
+ // Don't throw - archiving is the expected behavior for DELETED_REMOTELY
243
+ return;
244
+ }
245
+ throw new Error(`Remote workflow ${workflowId} not found during pull`);
246
+ }
247
+ const clean = WorkflowSanitizer.cleanForStorage(fullWf);
248
+ const filePath = path.join(this.directory, filename);
249
+ fs.writeFileSync(filePath, JSON.stringify(clean, null, 2));
250
+ // Update Watcher's remote hash cache since we just fetched the workflow
251
+ // This ensures finalizeSync has the remote hash
252
+ const hash = HashUtils.computeHash(clean);
253
+ this.watcher.setRemoteHash(workflowId, hash);
254
+ }
255
+ async executeUpdate(workflowId, filename) {
256
+ const filePath = path.join(this.directory, filename);
257
+ const localWf = this.readJsonFile(filePath);
258
+ if (!localWf) {
259
+ throw new Error('Local file not found during push');
260
+ }
261
+ const payload = WorkflowSanitizer.cleanForPush(localWf);
262
+ const updatedWf = await this.client.updateWorkflow(workflowId, payload);
263
+ if (!updatedWf) {
264
+ throw new Error('Failed to update remote workflow');
265
+ }
266
+ // CRITICAL: Write the API response back to local file to ensure consistency
267
+ // This ensures local and remote have identical content after push
268
+ // IMPORTANT: We use cleanForStorage to remove dynamic metadata (versionId, etc.)
269
+ // This prevents the watcher from detecting a change and triggering another sync
270
+ const clean = WorkflowSanitizer.cleanForStorage(updatedWf);
271
+ fs.writeFileSync(filePath, JSON.stringify(clean, null, 2));
272
+ // Update Watcher's remote hash cache with the updated workflow
273
+ const hash = HashUtils.computeHash(clean);
274
+ this.watcher.setRemoteHash(workflowId, hash);
275
+ }
276
+ async executeCreate(filename) {
277
+ const filePath = path.join(this.directory, filename);
278
+ const localWf = this.readJsonFile(filePath);
279
+ if (!localWf) {
280
+ throw new Error('Local file not found during creation');
281
+ }
282
+ const payload = WorkflowSanitizer.cleanForPush(localWf);
283
+ if (!payload.name) {
284
+ payload.name = path.parse(filename).name;
285
+ }
286
+ const newWf = await this.client.createWorkflow(payload);
287
+ if (!newWf || !newWf.id) {
288
+ throw new Error('Failed to create remote workflow');
289
+ }
290
+ // Update local file with new ID and clean metadata
291
+ // IMPORTANT: Use cleanForStorage to prevent watcher from detecting metadata changes
292
+ const clean = WorkflowSanitizer.cleanForStorage(newWf);
293
+ fs.writeFileSync(filePath, JSON.stringify(clean, null, 2));
294
+ return newWf.id;
295
+ }
296
+ async archive(filename) {
297
+ const filePath = path.join(this.directory, filename);
298
+ if (fs.existsSync(filePath)) {
299
+ const archivePath = path.join(this.archiveDirectory, `${Date.now()}_${filename}`);
300
+ fs.renameSync(filePath, archivePath);
301
+ }
302
+ }
303
+ readJsonFile(filePath) {
304
+ try {
305
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
306
+ }
307
+ catch {
308
+ return null;
309
+ }
310
+ }
311
+ }
312
+ //# sourceMappingURL=sync-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.js","sourceRoot":"","sources":["../../src/services/sync-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAa,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,OAAO,UAAU;IACX,MAAM,CAAe;IACrB,OAAO,CAAU;IACjB,SAAS,CAAS;IAClB,gBAAgB,CAAS;IAEjC,YACI,MAAoB,EACpB,OAAgB,EAChB,SAAiB;QAEjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QAEzD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACxC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI,CAAC,UAAkB,EAAE,QAAgB,EAAE,MAA0B;QAC9E,mDAAmD;QACnD,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE1C,IAAI,CAAC;YACD,QAAQ,MAAM,EAAE,CAAC;gBACb,KAAK,kBAAkB,CAAC,mBAAmB;oBACvC,wCAAwC;oBACxC,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;oBAC7C,6CAA6C;oBAC7C,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;oBAC5C,MAAM;gBAEV,KAAK,kBAAkB,CAAC,iBAAiB;oBACrC,+CAA+C;oBAC/C,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;oBAC7C,yCAAyC;oBACzC,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;oBAC5C,MAAM;gBAEV,KAAK,kBAAkB,CAAC,gBAAgB;oBACpC,6BAA6B;oBAC7B,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBAC7B,mEAAmE;oBACnE,sDAAsD;oBACtD,MAAM;gBAEV,KAAK,kBAAkB,CAAC,QAAQ;oBAC5B,qCAAqC;oBACrC,MAAM,IAAI,KAAK,CAAC,kCAAkC,UAAU,gCAAgC,CAAC,CAAC;gBAElG,KAAK,kBAAkB,CAAC,eAAe;oBACnC,qBAAqB;oBACrB,MAAM;gBAEV,KAAK,kBAAkB,CAAC,kBAAkB,CAAC;gBAC3C,KAAK,kBAAkB,CAAC,OAAO,CAAC;gBAChC,KAAK,kBAAkB,CAAC,gBAAgB;oBACpC,qBAAqB;oBACrB,MAAM;gBAEV;oBACI,OAAO,CAAC,IAAI,CAAC,iCAAiC,MAAM,qBAAqB,CAAC,CAAC;oBAC3E,MAAM;YACd,CAAC;QACL,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAC1C,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI,CAAC,QAAgB,EAAE,UAAmB,EAAE,MAA2B;QAChF,iDAAiD;QACjD,IAAI,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;YAC5C,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC9C,CAAC;aAAM,CAAC;YACJ,6DAA6D;YAC7D,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,QAAQ,CAAC,CAAC;QACtD,CAAC;QAED,IAAI,CAAC;YACD,gDAAgD;YAChD,IAAI,CAAC,UAAU,IAAI,MAAM,KAAK,kBAAkB,CAAC,kBAAkB,EAAE,CAAC;gBAClE,uBAAuB;gBACvB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACzD,6CAA6C;gBAC7C,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;gBAC/C,OAAO,aAAa,CAAC;YACzB,CAAC;YAED,6BAA6B;YAC7B,QAAQ,MAAM,EAAE,CAAC;gBACb,KAAK,kBAAkB,CAAC,gBAAgB;oBACpC,sBAAsB;oBACtB,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;oBAC/C,yCAAyC;oBACzC,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;oBAC5C,OAAO,UAAU,CAAC;gBAEtB,KAAK,kBAAkB,CAAC,eAAe;oBACnC,sCAAsC;oBACtC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBAC7B,6DAA6D;oBAC7D,yDAAyD;oBACzD,MAAM,IAAI,KAAK,CAAC,wCAAwC,UAAU,gCAAgC,CAAC,CAAC;gBAExG,KAAK,kBAAkB,CAAC,QAAQ;oBAC5B,qCAAqC;oBACrC,MAAM,IAAI,KAAK,CAAC,kCAAkC,UAAU,gCAAgC,CAAC,CAAC;gBAElG,KAAK,kBAAkB,CAAC,mBAAmB,CAAC;gBAC5C,KAAK,kBAAkB,CAAC,OAAO,CAAC;gBAChC,KAAK,kBAAkB,CAAC,iBAAiB,CAAC;gBAC1C,KAAK,kBAAkB,CAAC,gBAAgB;oBACpC,qBAAqB;oBACrB,OAAO,UAAU,CAAC;gBAEtB;oBACI,OAAO,CAAC,IAAI,CAAC,iCAAiC,MAAM,qBAAqB,CAAC,CAAC;oBAC3E,OAAO,UAAU,CAAC;YAC1B,CAAC;QACL,CAAC;gBAAS,CAAC;YACP,IAAI,UAAU,EAAE,CAAC;gBACb,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;gBAC1C,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACJ,8DAA8D;gBAC9D,IAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,QAAQ,CAAC,CAAC;YACvD,CAAC;QACL,CAAC;IACL,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,SAAS,CAAC,UAAkB,EAAE,QAAgB;QACvD,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE1C,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YAC7C,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAC1C,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,SAAS,CAAC,UAAkB,EAAE,QAAgB;QACvD,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE1C,IAAI,eAAe,GAAG,UAAU,CAAC;QAEjC,IAAI,CAAC;YACD,sBAAsB;YACtB,IAAI,CAAC;gBACD,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACnD,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBAClB,wDAAwD;gBACxD,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC3G,OAAO,CAAC,GAAG,CAAC,yBAAyB,UAAU,mCAAmC,CAAC,CAAC;oBACpF,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;oBAEzD,sCAAsC;oBACtC,IAAI,aAAa,KAAK,UAAU,EAAE,CAAC;wBAC/B,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;wBAC/D,eAAe,GAAG,aAAa,CAAC;oBACpC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACJ,MAAM,KAAK,CAAC;gBAChB,CAAC;YACL,CAAC;YAED,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;YACjD,OAAO,eAAe,CAAC;QAC3B,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC;QACpD,CAAC;IACL,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,YAAY,CAAC,UAAkB,EAAE,QAAgB;QAC1D,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE1C,IAAI,CAAC;YACD,kBAAkB;YAClB,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;YAE7C,2FAA2F;YAC3F,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAE7B,oEAAoE;QACxE,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAC1C,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,kBAAkB,CAAC,QAAgB;QAC5C,MAAM,YAAY,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC3D,MAAM,gBAAgB,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAExE,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,0BAA0B;QAC1B,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;QACjE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEvD,4BAA4B;QAC5B,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAEtD,2BAA2B;QAC3B,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAEtC,iEAAiE;QACjE,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAE3B,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,UAAkB,EAAE,QAAgB;QAC1D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,2DAA2D;YAC3D,iDAAiD;YACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YACrD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC7B,wEAAwE;gBACxE,OAAO;YACX,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,mBAAmB,UAAU,wBAAwB,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAE3D,wEAAwE;QACxE,gDAAgD;QAChD,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,UAAkB,EAAE,QAAgB;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAExE,IAAI,CAAC,SAAS,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACxD,CAAC;QAED,4EAA4E;QAC5E,kEAAkE;QAClE,iFAAiF;QACjF,gFAAgF;QAChF,MAAM,KAAK,GAAG,iBAAiB,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QAC3D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAE3D,+DAA+D;QAC/D,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,QAAgB;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;QAC7C,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACxD,CAAC;QAED,mDAAmD;QACnD,oFAAoF;QACpF,MAAM,KAAK,GAAG,iBAAiB,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACvD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAE3D,OAAO,KAAK,CAAC,EAAE,CAAC;IACpB,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,QAAgB;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC,CAAC;YAClF,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACzC,CAAC;IACL,CAAC;IAEO,YAAY,CAAC,QAAgB;QACjC,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ"}