appwrite-utils-cli 1.5.2 → 1.6.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 (233) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +251 -29
  3. package/dist/adapters/AdapterFactory.d.ts +10 -3
  4. package/dist/adapters/AdapterFactory.js +213 -17
  5. package/dist/adapters/TablesDBAdapter.js +60 -17
  6. package/dist/backups/operations/bucketBackup.d.ts +19 -0
  7. package/dist/backups/operations/bucketBackup.js +197 -0
  8. package/dist/backups/operations/collectionBackup.d.ts +30 -0
  9. package/dist/backups/operations/collectionBackup.js +201 -0
  10. package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
  11. package/dist/backups/operations/comprehensiveBackup.js +238 -0
  12. package/dist/backups/schemas/bucketManifest.d.ts +93 -0
  13. package/dist/backups/schemas/bucketManifest.js +33 -0
  14. package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
  15. package/dist/backups/schemas/comprehensiveManifest.js +32 -0
  16. package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
  17. package/dist/backups/tracking/centralizedTracking.js +274 -0
  18. package/dist/cli/commands/configCommands.d.ts +8 -0
  19. package/dist/cli/commands/configCommands.js +160 -0
  20. package/dist/cli/commands/databaseCommands.d.ts +13 -0
  21. package/dist/cli/commands/databaseCommands.js +478 -0
  22. package/dist/cli/commands/functionCommands.d.ts +7 -0
  23. package/dist/cli/commands/functionCommands.js +289 -0
  24. package/dist/cli/commands/schemaCommands.d.ts +7 -0
  25. package/dist/cli/commands/schemaCommands.js +134 -0
  26. package/dist/cli/commands/transferCommands.d.ts +5 -0
  27. package/dist/cli/commands/transferCommands.js +384 -0
  28. package/dist/collections/attributes.d.ts +5 -4
  29. package/dist/collections/attributes.js +539 -246
  30. package/dist/collections/indexes.js +39 -37
  31. package/dist/collections/methods.d.ts +2 -16
  32. package/dist/collections/methods.js +90 -538
  33. package/dist/collections/transferOperations.d.ts +7 -0
  34. package/dist/collections/transferOperations.js +331 -0
  35. package/dist/collections/wipeOperations.d.ts +16 -0
  36. package/dist/collections/wipeOperations.js +328 -0
  37. package/dist/config/configMigration.d.ts +87 -0
  38. package/dist/config/configMigration.js +390 -0
  39. package/dist/config/configValidation.d.ts +66 -0
  40. package/dist/config/configValidation.js +358 -0
  41. package/dist/config/yamlConfig.d.ts +455 -1
  42. package/dist/config/yamlConfig.js +145 -52
  43. package/dist/databases/methods.js +3 -2
  44. package/dist/databases/setup.d.ts +1 -2
  45. package/dist/databases/setup.js +9 -87
  46. package/dist/examples/yamlTerminologyExample.d.ts +42 -0
  47. package/dist/examples/yamlTerminologyExample.js +269 -0
  48. package/dist/functions/deployments.js +11 -10
  49. package/dist/functions/methods.d.ts +1 -1
  50. package/dist/functions/methods.js +5 -4
  51. package/dist/init.js +9 -9
  52. package/dist/interactiveCLI.d.ts +8 -17
  53. package/dist/interactiveCLI.js +181 -1172
  54. package/dist/main.js +364 -21
  55. package/dist/migrations/afterImportActions.js +22 -30
  56. package/dist/migrations/appwriteToX.js +71 -25
  57. package/dist/migrations/dataLoader.js +35 -26
  58. package/dist/migrations/importController.js +29 -30
  59. package/dist/migrations/relationships.js +13 -12
  60. package/dist/migrations/services/ImportOrchestrator.js +16 -19
  61. package/dist/migrations/transfer.js +46 -46
  62. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
  63. package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
  64. package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
  65. package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
  66. package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
  67. package/dist/migrations/yaml/generateImportSchemas.js +736 -7
  68. package/dist/schemas/authUser.d.ts +1 -1
  69. package/dist/setupController.js +3 -2
  70. package/dist/shared/backupMetadataSchema.d.ts +94 -0
  71. package/dist/shared/backupMetadataSchema.js +38 -0
  72. package/dist/shared/backupTracking.d.ts +18 -0
  73. package/dist/shared/backupTracking.js +176 -0
  74. package/dist/shared/confirmationDialogs.js +15 -15
  75. package/dist/shared/errorUtils.d.ts +54 -0
  76. package/dist/shared/errorUtils.js +95 -0
  77. package/dist/shared/functionManager.js +20 -19
  78. package/dist/shared/indexManager.js +12 -11
  79. package/dist/shared/jsonSchemaGenerator.js +10 -26
  80. package/dist/shared/logging.d.ts +51 -0
  81. package/dist/shared/logging.js +70 -0
  82. package/dist/shared/messageFormatter.d.ts +2 -0
  83. package/dist/shared/messageFormatter.js +10 -0
  84. package/dist/shared/migrationHelpers.d.ts +6 -16
  85. package/dist/shared/migrationHelpers.js +24 -21
  86. package/dist/shared/operationLogger.d.ts +8 -1
  87. package/dist/shared/operationLogger.js +11 -24
  88. package/dist/shared/operationQueue.d.ts +28 -1
  89. package/dist/shared/operationQueue.js +268 -66
  90. package/dist/shared/operationsTable.d.ts +26 -0
  91. package/dist/shared/operationsTable.js +286 -0
  92. package/dist/shared/operationsTableSchema.d.ts +48 -0
  93. package/dist/shared/operationsTableSchema.js +35 -0
  94. package/dist/shared/relationshipExtractor.d.ts +56 -0
  95. package/dist/shared/relationshipExtractor.js +138 -0
  96. package/dist/shared/schemaGenerator.d.ts +19 -1
  97. package/dist/shared/schemaGenerator.js +56 -75
  98. package/dist/storage/backupCompression.d.ts +20 -0
  99. package/dist/storage/backupCompression.js +67 -0
  100. package/dist/storage/methods.d.ts +16 -2
  101. package/dist/storage/methods.js +98 -14
  102. package/dist/users/methods.js +9 -8
  103. package/dist/utils/configDiscovery.d.ts +78 -0
  104. package/dist/utils/configDiscovery.js +430 -0
  105. package/dist/utils/directoryUtils.d.ts +22 -0
  106. package/dist/utils/directoryUtils.js +59 -0
  107. package/dist/utils/getClientFromConfig.d.ts +17 -8
  108. package/dist/utils/getClientFromConfig.js +162 -17
  109. package/dist/utils/helperFunctions.d.ts +16 -2
  110. package/dist/utils/helperFunctions.js +19 -5
  111. package/dist/utils/loadConfigs.d.ts +34 -9
  112. package/dist/utils/loadConfigs.js +236 -316
  113. package/dist/utils/pathResolvers.d.ts +53 -0
  114. package/dist/utils/pathResolvers.js +72 -0
  115. package/dist/utils/projectConfig.d.ts +119 -0
  116. package/dist/utils/projectConfig.js +171 -0
  117. package/dist/utils/retryFailedPromises.js +4 -2
  118. package/dist/utils/sessionAuth.d.ts +48 -0
  119. package/dist/utils/sessionAuth.js +164 -0
  120. package/dist/utils/sessionPreservationExample.d.ts +1666 -0
  121. package/dist/utils/sessionPreservationExample.js +101 -0
  122. package/dist/utils/setupFiles.js +301 -41
  123. package/dist/utils/typeGuards.d.ts +35 -0
  124. package/dist/utils/typeGuards.js +57 -0
  125. package/dist/utils/versionDetection.js +145 -9
  126. package/dist/utils/yamlConverter.d.ts +53 -3
  127. package/dist/utils/yamlConverter.js +232 -13
  128. package/dist/utils/yamlLoader.d.ts +70 -0
  129. package/dist/utils/yamlLoader.js +263 -0
  130. package/dist/utilsController.d.ts +36 -3
  131. package/dist/utilsController.js +186 -56
  132. package/package.json +12 -2
  133. package/src/adapters/AdapterFactory.ts +263 -35
  134. package/src/adapters/TablesDBAdapter.ts +225 -36
  135. package/src/backups/operations/bucketBackup.ts +277 -0
  136. package/src/backups/operations/collectionBackup.ts +310 -0
  137. package/src/backups/operations/comprehensiveBackup.ts +342 -0
  138. package/src/backups/schemas/bucketManifest.ts +78 -0
  139. package/src/backups/schemas/comprehensiveManifest.ts +76 -0
  140. package/src/backups/tracking/centralizedTracking.ts +352 -0
  141. package/src/cli/commands/configCommands.ts +194 -0
  142. package/src/cli/commands/databaseCommands.ts +635 -0
  143. package/src/cli/commands/functionCommands.ts +379 -0
  144. package/src/cli/commands/schemaCommands.ts +163 -0
  145. package/src/cli/commands/transferCommands.ts +457 -0
  146. package/src/collections/attributes.ts +900 -621
  147. package/src/collections/attributes.ts.backup +1555 -0
  148. package/src/collections/indexes.ts +116 -114
  149. package/src/collections/methods.ts +295 -968
  150. package/src/collections/transferOperations.ts +516 -0
  151. package/src/collections/wipeOperations.ts +501 -0
  152. package/src/config/README.md +274 -0
  153. package/src/config/configMigration.ts +575 -0
  154. package/src/config/configValidation.ts +445 -0
  155. package/src/config/yamlConfig.ts +168 -55
  156. package/src/databases/methods.ts +3 -2
  157. package/src/databases/setup.ts +11 -138
  158. package/src/examples/yamlTerminologyExample.ts +341 -0
  159. package/src/functions/deployments.ts +14 -12
  160. package/src/functions/methods.ts +11 -11
  161. package/src/functions/templates/hono-typescript/README.md +286 -0
  162. package/src/functions/templates/hono-typescript/package.json +26 -0
  163. package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  164. package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  165. package/src/functions/templates/hono-typescript/src/app.ts +180 -0
  166. package/src/functions/templates/hono-typescript/src/context.ts +103 -0
  167. package/src/functions/templates/hono-typescript/src/index.ts +54 -0
  168. package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  169. package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
  170. package/src/functions/templates/typescript-node/package.json +2 -1
  171. package/src/functions/templates/typescript-node/src/context.ts +103 -0
  172. package/src/functions/templates/typescript-node/src/index.ts +18 -12
  173. package/src/functions/templates/uv/pyproject.toml +1 -0
  174. package/src/functions/templates/uv/src/context.py +125 -0
  175. package/src/functions/templates/uv/src/index.py +35 -5
  176. package/src/init.ts +9 -11
  177. package/src/interactiveCLI.ts +278 -1596
  178. package/src/main.ts +418 -24
  179. package/src/migrations/afterImportActions.ts +71 -44
  180. package/src/migrations/appwriteToX.ts +100 -34
  181. package/src/migrations/dataLoader.ts +48 -34
  182. package/src/migrations/importController.ts +44 -39
  183. package/src/migrations/relationships.ts +28 -18
  184. package/src/migrations/services/ImportOrchestrator.ts +24 -27
  185. package/src/migrations/transfer.ts +159 -121
  186. package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
  187. package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
  188. package/src/migrations/yaml/generateImportSchemas.ts +751 -12
  189. package/src/setupController.ts +3 -2
  190. package/src/shared/backupMetadataSchema.ts +93 -0
  191. package/src/shared/backupTracking.ts +211 -0
  192. package/src/shared/confirmationDialogs.ts +19 -19
  193. package/src/shared/errorUtils.ts +110 -0
  194. package/src/shared/functionManager.ts +21 -20
  195. package/src/shared/indexManager.ts +12 -11
  196. package/src/shared/jsonSchemaGenerator.ts +38 -52
  197. package/src/shared/logging.ts +75 -0
  198. package/src/shared/messageFormatter.ts +14 -1
  199. package/src/shared/migrationHelpers.ts +45 -38
  200. package/src/shared/operationLogger.ts +11 -36
  201. package/src/shared/operationQueue.ts +322 -93
  202. package/src/shared/operationsTable.ts +338 -0
  203. package/src/shared/operationsTableSchema.ts +60 -0
  204. package/src/shared/relationshipExtractor.ts +214 -0
  205. package/src/shared/schemaGenerator.ts +179 -219
  206. package/src/storage/backupCompression.ts +88 -0
  207. package/src/storage/methods.ts +131 -34
  208. package/src/users/methods.ts +11 -9
  209. package/src/utils/configDiscovery.ts +502 -0
  210. package/src/utils/directoryUtils.ts +61 -0
  211. package/src/utils/getClientFromConfig.ts +205 -22
  212. package/src/utils/helperFunctions.ts +23 -5
  213. package/src/utils/loadConfigs.ts +313 -345
  214. package/src/utils/pathResolvers.ts +81 -0
  215. package/src/utils/projectConfig.ts +299 -0
  216. package/src/utils/retryFailedPromises.ts +4 -2
  217. package/src/utils/sessionAuth.ts +230 -0
  218. package/src/utils/setupFiles.ts +322 -54
  219. package/src/utils/typeGuards.ts +65 -0
  220. package/src/utils/versionDetection.ts +218 -64
  221. package/src/utils/yamlConverter.ts +296 -13
  222. package/src/utils/yamlLoader.ts +364 -0
  223. package/src/utilsController.ts +314 -110
  224. package/tests/README.md +497 -0
  225. package/tests/adapters/AdapterFactory.test.ts +277 -0
  226. package/tests/integration/syncOperations.test.ts +463 -0
  227. package/tests/jest.config.js +25 -0
  228. package/tests/migration/configMigration.test.ts +546 -0
  229. package/tests/setup.ts +62 -0
  230. package/tests/testUtils.ts +340 -0
  231. package/tests/utils/loadConfigs.test.ts +350 -0
  232. package/tests/validation/configValidation.test.ts +412 -0
  233. package/src/utils/schemaStrings.ts +0 -517
@@ -1,3 +1,10 @@
1
1
  import type { Databases, Models } from "node-appwrite";
2
2
  import type { OperationCreate } from "../storage/schemas.js";
3
- export declare const logOperation: (db: Databases, dbId: string, operationDetails: OperationCreate, operationId?: string, useMigrations?: boolean) => Promise<Models.Document | null>;
3
+ /**
4
+ * Legacy operation logger - deprecated
5
+ * This function is maintained for backward compatibility but no longer performs any logging.
6
+ * The operations table system has been refactored to use the dynamic adapter pattern.
7
+ *
8
+ * @deprecated This function will be removed in a future version
9
+ */
10
+ export declare const logOperation: (db: Databases, dbId: string, operationDetails: OperationCreate, operationId?: string) => Promise<Models.Document | null>;
@@ -1,25 +1,12 @@
1
- import { tryAwaitWithRetry } from "appwrite-utils";
2
- import { ulid } from "ulidx";
3
- export const logOperation = async (db, dbId, operationDetails, operationId, useMigrations = true) => {
4
- if (!useMigrations) {
5
- console.log("Migrations disabled, skipping operation logging");
6
- return null;
7
- }
8
- try {
9
- let operation;
10
- if (operationId) {
11
- // Update existing operation log
12
- operation = await tryAwaitWithRetry(async () => await db.updateDocument("migrations", "currentOperations", operationId, operationDetails));
13
- }
14
- else {
15
- // Create new operation log
16
- operation = await db.createDocument("migrations", "currentOperations", ulid(), operationDetails);
17
- }
18
- console.log(`Operation logged: ${operation.$id}`);
19
- return operation;
20
- }
21
- catch (error) {
22
- console.error(`Error logging operation: ${error}`);
23
- throw error;
24
- }
1
+ /**
2
+ * Legacy operation logger - deprecated
3
+ * This function is maintained for backward compatibility but no longer performs any logging.
4
+ * The operations table system has been refactored to use the dynamic adapter pattern.
5
+ *
6
+ * @deprecated This function will be removed in a future version
7
+ */
8
+ export const logOperation = async (db, dbId, operationDetails, operationId) => {
9
+ // No-op: Operations logging has been moved to the new operations table system
10
+ // Callers should migrate to using operationsTable.ts functions directly
11
+ return null;
25
12
  };
@@ -1,5 +1,6 @@
1
1
  import { type Databases, type Models } from "node-appwrite";
2
2
  import type { Attribute } from "appwrite-utils";
3
+ import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
3
4
  export interface QueuedOperation {
4
5
  type: "attribute";
5
6
  collectionId?: string;
@@ -9,5 +10,31 @@ export interface QueuedOperation {
9
10
  }
10
11
  export declare const queuedOperations: QueuedOperation[];
11
12
  export declare const nameToIdMapping: Map<string, string>;
13
+ export declare const processedCollections: Set<string>;
14
+ export declare const processedAttributes: Set<string>;
12
15
  export declare const enqueueOperation: (operation: QueuedOperation) => void;
13
- export declare const processQueue: (db: Databases, dbId: string) => Promise<void>;
16
+ /**
17
+ * Clear all caches and processing state - use between operations
18
+ */
19
+ export declare const clearProcessingState: () => void;
20
+ /**
21
+ * Check if a collection has already been fully processed
22
+ */
23
+ export declare const isCollectionProcessed: (collectionId: string) => boolean;
24
+ /**
25
+ * Mark a collection as fully processed
26
+ */
27
+ export declare const markCollectionProcessed: (collectionId: string, collectionName?: string) => void;
28
+ /**
29
+ * Check if a specific attribute has been processed
30
+ */
31
+ export declare const isAttributeProcessed: (collectionId: string, attributeKey: string) => boolean;
32
+ /**
33
+ * Mark a specific attribute as processed
34
+ */
35
+ export declare const markAttributeProcessed: (collectionId: string, attributeKey: string) => void;
36
+ /**
37
+ * Process only specific attributes in the queue, not entire collections
38
+ * This prevents triggering full collection re-processing cycles
39
+ */
40
+ export declare const processQueue: (db: Databases | DatabaseAdapter, dbId: string) => Promise<void>;
@@ -2,100 +2,302 @@ import { Query } from "node-appwrite";
2
2
  import { createOrUpdateAttributeWithStatusCheck } from "../collections/attributes.js";
3
3
  import { fetchAndCacheCollectionByName } from "../collections/methods.js";
4
4
  import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
5
- import chalk from "chalk";
5
+ import { logger } from "../shared/logging.js";
6
+ import { MessageFormatter } from "../shared/messageFormatter.js";
7
+ // Global state management
6
8
  export const queuedOperations = [];
7
9
  export const nameToIdMapping = new Map();
10
+ export const processedCollections = new Set();
11
+ export const processedAttributes = new Set(); // format: "collectionId:attributeKey"
8
12
  export const enqueueOperation = (operation) => {
13
+ // Avoid duplicate queue entries for same attribute
14
+ const attributeKey = operation.attribute?.key;
15
+ const collectionId = operation.collectionId;
16
+ logger.info('Enqueueing operation', {
17
+ type: operation.type,
18
+ attributeKey,
19
+ collectionId,
20
+ dependencies: operation.dependencies,
21
+ queueSizeBefore: queuedOperations.length,
22
+ operation: 'enqueueOperation'
23
+ });
24
+ if (attributeKey && collectionId) {
25
+ const duplicateIndex = queuedOperations.findIndex((op) => op.collectionId === collectionId && op.attribute?.key === attributeKey);
26
+ if (duplicateIndex !== -1) {
27
+ MessageFormatter.info(`Replacing existing queue entry for attribute: ${attributeKey}`);
28
+ logger.info('Replacing duplicate queue entry', {
29
+ attributeKey,
30
+ collectionId,
31
+ duplicateIndex,
32
+ operation: 'enqueueOperation'
33
+ });
34
+ queuedOperations[duplicateIndex] = operation;
35
+ return;
36
+ }
37
+ }
9
38
  queuedOperations.push(operation);
39
+ logger.debug('Operation enqueued successfully', {
40
+ attributeKey,
41
+ collectionId,
42
+ queueSizeAfter: queuedOperations.length,
43
+ operation: 'enqueueOperation'
44
+ });
45
+ };
46
+ /**
47
+ * Clear all caches and processing state - use between operations
48
+ */
49
+ export const clearProcessingState = () => {
50
+ const sizeBefore = {
51
+ collections: processedCollections.size,
52
+ attributes: processedAttributes.size,
53
+ nameMapping: nameToIdMapping.size
54
+ };
55
+ processedCollections.clear();
56
+ processedAttributes.clear();
57
+ nameToIdMapping.clear();
58
+ MessageFormatter.success("Cleared processing state caches");
59
+ logger.info('Processing state cleared', {
60
+ sizeBefore,
61
+ operation: 'clearProcessingState'
62
+ });
63
+ };
64
+ /**
65
+ * Check if a collection has already been fully processed
66
+ */
67
+ export const isCollectionProcessed = (collectionId) => {
68
+ return processedCollections.has(collectionId);
69
+ };
70
+ /**
71
+ * Mark a collection as fully processed
72
+ */
73
+ export const markCollectionProcessed = (collectionId, collectionName) => {
74
+ processedCollections.add(collectionId);
75
+ const logData = {
76
+ collectionId,
77
+ collectionName,
78
+ totalProcessedCollections: processedCollections.size,
79
+ operation: 'markCollectionProcessed'
80
+ };
81
+ if (collectionName) {
82
+ MessageFormatter.success(`Marked collection '${collectionName}' (${collectionId}) as processed`);
83
+ }
84
+ logger.info('Collection marked as processed', logData);
85
+ };
86
+ /**
87
+ * Check if a specific attribute has been processed
88
+ */
89
+ export const isAttributeProcessed = (collectionId, attributeKey) => {
90
+ return processedAttributes.has(`${collectionId}:${attributeKey}`);
10
91
  };
92
+ /**
93
+ * Mark a specific attribute as processed
94
+ */
95
+ export const markAttributeProcessed = (collectionId, attributeKey) => {
96
+ const identifier = `${collectionId}:${attributeKey}`;
97
+ processedAttributes.add(identifier);
98
+ logger.debug('Attribute marked as processed', {
99
+ collectionId,
100
+ attributeKey,
101
+ identifier,
102
+ totalProcessedAttributes: processedAttributes.size,
103
+ operation: 'markAttributeProcessed'
104
+ });
105
+ };
106
+ /**
107
+ * Process only specific attributes in the queue, not entire collections
108
+ * This prevents triggering full collection re-processing cycles
109
+ */
11
110
  export const processQueue = async (db, dbId) => {
12
- console.log("---------------------------------");
13
- console.log(`Starting Queue processing of ${dbId}`);
14
- console.log("---------------------------------");
111
+ const startTime = Date.now();
112
+ if (queuedOperations.length === 0) {
113
+ MessageFormatter.info("No queued operations to process");
114
+ logger.info('Queue processing skipped - no operations', {
115
+ dbId,
116
+ operation: 'processQueue'
117
+ });
118
+ return;
119
+ }
120
+ MessageFormatter.section(`Starting surgical queue processing of ${queuedOperations.length} operations for ${dbId}`);
121
+ logger.info('Starting queue processing', {
122
+ dbId,
123
+ queueSize: queuedOperations.length,
124
+ operations: queuedOperations.map(op => ({
125
+ type: op.type,
126
+ attributeKey: op.attribute?.key,
127
+ collectionId: op.collectionId,
128
+ dependencies: op.dependencies
129
+ })),
130
+ operation: 'processQueue'
131
+ });
15
132
  let progress = true;
16
- while (progress) {
133
+ let attempts = 0;
134
+ const maxAttempts = 3; // Prevent infinite loops
135
+ while (progress && attempts < maxAttempts) {
17
136
  progress = false;
18
- console.log("Processing queued operations:");
19
- for (let i = 0; i < queuedOperations.length; i++) {
137
+ attempts++;
138
+ MessageFormatter.info(`Queue processing attempt ${attempts}/${maxAttempts}`);
139
+ logger.info('Queue processing attempt started', {
140
+ attempt: attempts,
141
+ maxAttempts,
142
+ remainingOperations: queuedOperations.length,
143
+ dbId,
144
+ operation: 'processQueue'
145
+ });
146
+ for (let i = queuedOperations.length - 1; i >= 0; i--) {
20
147
  const operation = queuedOperations[i];
21
- let collectionFound;
22
- // Handle relationship attribute operations
23
- if (operation.attribute?.type === "relationship") {
24
- // Attempt to resolve the collection directly if collectionId is specified
25
- if (operation.collectionId) {
26
- console.log(`\tFetching collection by ID: ${operation.collectionId}`);
27
- try {
28
- collectionFound = await tryAwaitWithRetry(async () => await db.getCollection(dbId, operation.collectionId));
148
+ if (!operation.attribute || !operation.collectionId) {
149
+ MessageFormatter.warning("Invalid operation, removing from queue");
150
+ queuedOperations.splice(i, 1);
151
+ continue;
152
+ }
153
+ const attributeKey = operation.attribute.key;
154
+ const collectionId = operation.collectionId;
155
+ // Skip if this specific attribute was already processed
156
+ if (isAttributeProcessed(collectionId, attributeKey)) {
157
+ MessageFormatter.debug(`Attribute '${attributeKey}' already processed, removing from queue`);
158
+ logger.debug('Removing already processed attribute from queue', {
159
+ attributeKey,
160
+ collectionId,
161
+ queueIndex: i,
162
+ operation: 'processQueue'
163
+ });
164
+ queuedOperations.splice(i, 1);
165
+ continue;
166
+ }
167
+ let targetCollection;
168
+ // Resolve the target collection (where the attribute will be created)
169
+ try {
170
+ targetCollection = await tryAwaitWithRetry(async () => {
171
+ if ('getMetadata' in db && typeof db.getMetadata === 'function') {
172
+ // DatabaseAdapter
173
+ return (await db.getTable({ databaseId: dbId, tableId: collectionId })).data;
29
174
  }
30
- catch (e) {
31
- console.log(`\tCollection not found by ID: ${operation.collectionId}`);
175
+ else {
176
+ // Legacy Databases
177
+ return await db.getCollection(dbId, collectionId);
32
178
  }
33
- }
34
- // Attempt to resolve related collection if specified and not already found
35
- if (!collectionFound && operation.attribute?.relatedCollection) {
36
- // First, try treating relatedCollection as an ID
179
+ });
180
+ }
181
+ catch (e) {
182
+ const errorMessage = e instanceof Error ? e.message : String(e);
183
+ MessageFormatter.error(`Target collection ${collectionId} not found, removing from queue`);
184
+ logger.error('Target collection not found during queue processing', {
185
+ collectionId,
186
+ attributeKey,
187
+ error: errorMessage,
188
+ operation: 'processQueue'
189
+ });
190
+ queuedOperations.splice(i, 1);
191
+ continue;
192
+ }
193
+ // For relationship attributes, ensure the related collection exists
194
+ let canProcess = true;
195
+ if (operation.attribute.type === "relationship") {
196
+ const relatedCollection = operation.attribute.relatedCollection;
197
+ if (relatedCollection) {
198
+ // Try to resolve related collection by ID first, then by name
199
+ let relatedFound = false;
37
200
  try {
38
- const relAttr = operation.attribute;
39
- const byId = await tryAwaitWithRetry(async () => await db.getCollection(dbId, relAttr.relatedCollection));
40
- // We still need the target collection (operation.collectionId) to create the attribute on,
41
- // so only use this branch to warm caches/mappings and continue to dependency checks.
42
- // Do not override collectionFound with the related collection.
201
+ await tryAwaitWithRetry(async () => {
202
+ if ('getMetadata' in db && typeof db.getMetadata === 'function') {
203
+ // DatabaseAdapter
204
+ return (await db.getTable({ databaseId: dbId, tableId: relatedCollection })).data;
205
+ }
206
+ else {
207
+ // Legacy Databases
208
+ return await db.getCollection(dbId, relatedCollection);
209
+ }
210
+ });
211
+ relatedFound = true;
212
+ nameToIdMapping.set(relatedCollection, relatedCollection); // Cache the ID mapping
43
213
  }
44
214
  catch (_) {
45
- // Not an ID or not found; fall back to name-based cache
215
+ // Try by name lookup
216
+ const cachedId = nameToIdMapping.get(relatedCollection);
217
+ if (cachedId) {
218
+ try {
219
+ await tryAwaitWithRetry(async () => {
220
+ if ('getMetadata' in db && typeof db.getMetadata === 'function') {
221
+ // DatabaseAdapter
222
+ return (await db.getTable({ databaseId: dbId, tableId: cachedId })).data;
223
+ }
224
+ else {
225
+ // Legacy Databases
226
+ return await db.getCollection(dbId, cachedId);
227
+ }
228
+ });
229
+ relatedFound = true;
230
+ }
231
+ catch (_) {
232
+ nameToIdMapping.delete(relatedCollection); // Remove stale cache
233
+ }
234
+ }
235
+ if (!relatedFound) {
236
+ // Final attempt: search by name
237
+ try {
238
+ const collections = 'getMetadata' in db && typeof db.getMetadata === 'function'
239
+ ? await db.listTables({ databaseId: dbId, queries: [Query.equal("name", relatedCollection)] })
240
+ : await db.listCollections(dbId, [Query.equal("name", relatedCollection)]);
241
+ if (collections.total && collections.total > 0) {
242
+ const firstCollection = 'getMetadata' in db && typeof db.getMetadata === 'function'
243
+ ? collections.tables?.[0]
244
+ : collections.collections?.[0];
245
+ nameToIdMapping.set(relatedCollection, firstCollection.$id);
246
+ relatedFound = true;
247
+ }
248
+ }
249
+ catch (_) {
250
+ // Related collection truly doesn't exist yet
251
+ }
252
+ }
46
253
  }
47
- // Warm cache by name (used by attribute creation path), but do not use as target collection
48
- const relAttr = operation.attribute;
49
- await fetchAndCacheCollectionByName(db, dbId, relAttr.relatedCollection);
50
- }
51
- // Handle dependencies if collection still not found
52
- if (!collectionFound) {
53
- for (const dep of operation.dependencies || []) {
54
- collectionFound = await fetchAndCacheCollectionByName(db, dbId, dep);
55
- if (collectionFound)
56
- break; // Break early if collection is found
254
+ if (!relatedFound) {
255
+ MessageFormatter.warning(`Related collection '${relatedCollection}' not ready for attribute '${attributeKey}', keeping in queue`);
256
+ canProcess = false;
57
257
  }
58
258
  }
59
259
  }
60
- else if (operation.collectionId) {
61
- // Handle non-relationship operations with a specified collectionId
62
- console.log(`\tFetching collection by ID: ${operation.collectionId}`);
63
- try {
64
- collectionFound = await tryAwaitWithRetry(async () => await db.getCollection(dbId, operation.collectionId));
65
- }
66
- catch (e) {
67
- console.log(`\tCollection not found by ID: ${operation.collectionId}`);
68
- }
69
- }
70
- // Process the operation if the collection is found
71
- if (collectionFound && operation.attribute) {
72
- console.log(chalk.cyan(`\t📋 Queue processing relationship attribute: ${operation.attribute.key} for collection: ${collectionFound.name}`));
73
- const success = await createOrUpdateAttributeWithStatusCheck(db, dbId, collectionFound, operation.attribute);
260
+ if (canProcess && targetCollection) {
261
+ MessageFormatter.progress(`Processing queued ${operation.attribute.type} attribute: '${attributeKey}' for collection: '${targetCollection.name}'`);
262
+ const success = await createOrUpdateAttributeWithStatusCheck(db, dbId, targetCollection, operation.attribute);
74
263
  if (success) {
75
- console.log(chalk.green(`\t✅ Successfully processed queued attribute: ${operation.attribute.key}`));
264
+ MessageFormatter.success(`Successfully processed queued attribute: '${attributeKey}'`);
265
+ logger.info('Queued attribute processed successfully', {
266
+ attributeKey,
267
+ collectionId,
268
+ targetCollectionName: targetCollection.name,
269
+ operation: 'processQueue'
270
+ });
271
+ markAttributeProcessed(collectionId, attributeKey);
76
272
  queuedOperations.splice(i, 1);
77
- i--; // Adjust index since we're modifying the array
78
273
  progress = true;
79
274
  }
80
275
  else {
81
- console.log(chalk.red(`\t❌ Failed to process queued attribute: ${operation.attribute.key}, removing from queue`));
276
+ MessageFormatter.error(`Failed to process queued attribute: '${attributeKey}', removing from queue`);
277
+ logger.error('Failed to process queued attribute', {
278
+ attributeKey,
279
+ collectionId,
280
+ targetCollectionName: targetCollection.name,
281
+ operation: 'processQueue'
282
+ });
82
283
  queuedOperations.splice(i, 1);
83
- i--; // Adjust index since we're modifying the array
84
284
  }
85
285
  }
86
- else {
87
- console.log(chalk.yellow(`\t⚠️ Collection not found for queued operation, removing from queue: ${operation.attribute?.key || 'unknown'}`));
88
- queuedOperations.splice(i, 1);
89
- i--; // Adjust index since we're modifying the array
90
- }
91
286
  }
92
- console.log(`\tFinished processing queued operations`);
287
+ if (queuedOperations.length === 0) {
288
+ break;
289
+ }
290
+ MessageFormatter.info(`Remaining operations after attempt ${attempts}: ${queuedOperations.length}`);
93
291
  }
94
292
  if (queuedOperations.length > 0) {
95
- console.error("Unresolved operations remain due to unmet dependencies.");
96
- console.log(queuedOperations);
293
+ MessageFormatter.warning(`${queuedOperations.length} operations remain unresolved after ${maxAttempts} attempts:`);
294
+ queuedOperations.forEach((op, index) => {
295
+ MessageFormatter.warning(` ${index + 1}. ${op.attribute?.type} attribute '${op.attribute?.key}' for collection ${op.collectionId}`);
296
+ });
297
+ MessageFormatter.warning("These may have unmet dependencies or require manual intervention");
298
+ }
299
+ else {
300
+ MessageFormatter.success("All queued operations processed successfully");
97
301
  }
98
- console.log("---------------------------------");
99
- console.log(`Queue processing complete for ${dbId}`);
100
- console.log("---------------------------------");
302
+ MessageFormatter.section(`Surgical queue processing complete for ${dbId}`);
101
303
  };
@@ -0,0 +1,26 @@
1
+ import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
2
+ import { type OperationRecord } from "./operationsTableSchema.js";
3
+ /**
4
+ * Creates the operations tracking table in the specified database
5
+ * Table is created with underscore prefix to indicate it's a system table
6
+ */
7
+ export declare function createOperationsTable(db: DatabaseAdapter, databaseId: string): Promise<void>;
8
+ /**
9
+ * Finds an existing operation or creates a new one
10
+ * Useful for resuming interrupted operations
11
+ */
12
+ export declare function findOrCreateOperation(db: DatabaseAdapter, databaseId: string, operationType: string, params?: Partial<OperationRecord>): Promise<OperationRecord>;
13
+ /**
14
+ * Updates an existing operation record
15
+ */
16
+ export declare function updateOperation(db: DatabaseAdapter, databaseId: string, operationId: string, updates: Partial<OperationRecord>): Promise<OperationRecord>;
17
+ /**
18
+ * Gets a single operation by ID
19
+ */
20
+ export declare function getOperation(db: DatabaseAdapter, databaseId: string, operationId: string): Promise<OperationRecord | null>;
21
+ /**
22
+ * Cleans up old completed operations
23
+ * @param olderThan - Optional date, defaults to operations older than 7 days
24
+ * @returns Number of operations deleted
25
+ */
26
+ export declare function cleanupOperations(db: DatabaseAdapter, databaseId: string, olderThan?: Date): Promise<number>;