appwrite-utils-cli 0.10.86 → 1.0.1

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 (178) hide show
  1. package/.appwrite/.yaml_schemas/appwrite-config.schema.json +380 -0
  2. package/.appwrite/.yaml_schemas/collection.schema.json +255 -0
  3. package/.appwrite/collections/Categories.yaml +182 -0
  4. package/.appwrite/collections/ExampleCollection.yaml +36 -0
  5. package/.appwrite/collections/Posts.yaml +227 -0
  6. package/.appwrite/collections/Users.yaml +149 -0
  7. package/.appwrite/config.yaml +109 -0
  8. package/.appwrite/import/README.md +148 -0
  9. package/.appwrite/import/categories-import.yaml +129 -0
  10. package/.appwrite/import/posts-import.yaml +208 -0
  11. package/.appwrite/import/users-import.yaml +130 -0
  12. package/.appwrite/importData/categories.json +194 -0
  13. package/.appwrite/importData/posts.json +270 -0
  14. package/.appwrite/importData/users.json +220 -0
  15. package/.appwrite/schemas/categories.json +128 -0
  16. package/.appwrite/schemas/exampleCollection.json +52 -0
  17. package/.appwrite/schemas/posts.json +173 -0
  18. package/.appwrite/schemas/users.json +125 -0
  19. package/README.md +260 -33
  20. package/dist/collections/attributes.js +3 -2
  21. package/dist/collections/methods.js +56 -38
  22. package/dist/config/yamlConfig.d.ts +501 -0
  23. package/dist/config/yamlConfig.js +452 -0
  24. package/dist/databases/setup.d.ts +6 -0
  25. package/dist/databases/setup.js +119 -0
  26. package/dist/functions/methods.d.ts +1 -1
  27. package/dist/functions/methods.js +5 -2
  28. package/dist/functions/openapi.d.ts +4 -0
  29. package/dist/functions/openapi.js +60 -0
  30. package/dist/interactiveCLI.d.ts +5 -0
  31. package/dist/interactiveCLI.js +194 -49
  32. package/dist/main.js +91 -30
  33. package/dist/migrations/afterImportActions.js +2 -2
  34. package/dist/migrations/appwriteToX.d.ts +10 -0
  35. package/dist/migrations/appwriteToX.js +15 -4
  36. package/dist/migrations/backup.d.ts +16 -16
  37. package/dist/migrations/dataLoader.d.ts +83 -1
  38. package/dist/migrations/dataLoader.js +4 -4
  39. package/dist/migrations/importController.js +25 -18
  40. package/dist/migrations/importDataActions.js +2 -2
  41. package/dist/migrations/logging.d.ts +9 -1
  42. package/dist/migrations/logging.js +41 -22
  43. package/dist/migrations/migrationHelper.d.ts +4 -4
  44. package/dist/migrations/relationships.js +1 -1
  45. package/dist/migrations/services/DataTransformationService.d.ts +55 -0
  46. package/dist/migrations/services/DataTransformationService.js +158 -0
  47. package/dist/migrations/services/FileHandlerService.d.ts +75 -0
  48. package/dist/migrations/services/FileHandlerService.js +236 -0
  49. package/dist/migrations/services/ImportOrchestrator.d.ts +97 -0
  50. package/dist/migrations/services/ImportOrchestrator.js +488 -0
  51. package/dist/migrations/services/RateLimitManager.d.ts +138 -0
  52. package/dist/migrations/services/RateLimitManager.js +279 -0
  53. package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
  54. package/dist/migrations/services/RelationshipResolver.js +332 -0
  55. package/dist/migrations/services/UserMappingService.d.ts +109 -0
  56. package/dist/migrations/services/UserMappingService.js +277 -0
  57. package/dist/migrations/services/ValidationService.d.ts +74 -0
  58. package/dist/migrations/services/ValidationService.js +260 -0
  59. package/dist/migrations/transfer.d.ts +0 -6
  60. package/dist/migrations/transfer.js +16 -132
  61. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +384 -0
  62. package/dist/migrations/yaml/YamlImportConfigLoader.js +375 -0
  63. package/dist/migrations/yaml/YamlImportIntegration.d.ts +87 -0
  64. package/dist/migrations/yaml/YamlImportIntegration.js +330 -0
  65. package/dist/migrations/yaml/generateImportSchemas.d.ts +17 -0
  66. package/dist/migrations/yaml/generateImportSchemas.js +575 -0
  67. package/dist/schemas/authUser.d.ts +9 -9
  68. package/dist/shared/attributeManager.d.ts +17 -0
  69. package/dist/shared/attributeManager.js +273 -0
  70. package/dist/shared/confirmationDialogs.d.ts +75 -0
  71. package/dist/shared/confirmationDialogs.js +236 -0
  72. package/dist/shared/functionManager.d.ts +48 -0
  73. package/dist/shared/functionManager.js +322 -0
  74. package/dist/shared/indexManager.d.ts +24 -0
  75. package/dist/shared/indexManager.js +150 -0
  76. package/dist/shared/jsonSchemaGenerator.d.ts +51 -0
  77. package/dist/shared/jsonSchemaGenerator.js +313 -0
  78. package/dist/shared/logging.d.ts +10 -0
  79. package/dist/shared/logging.js +46 -0
  80. package/dist/shared/messageFormatter.d.ts +37 -0
  81. package/dist/shared/messageFormatter.js +152 -0
  82. package/dist/shared/migrationHelpers.d.ts +173 -0
  83. package/dist/shared/migrationHelpers.js +142 -0
  84. package/dist/shared/operationLogger.d.ts +3 -0
  85. package/dist/shared/operationLogger.js +25 -0
  86. package/dist/shared/operationQueue.d.ts +13 -0
  87. package/dist/shared/operationQueue.js +79 -0
  88. package/dist/shared/progressManager.d.ts +62 -0
  89. package/dist/shared/progressManager.js +215 -0
  90. package/dist/shared/schemaGenerator.d.ts +18 -0
  91. package/dist/shared/schemaGenerator.js +523 -0
  92. package/dist/storage/methods.d.ts +3 -1
  93. package/dist/storage/methods.js +144 -55
  94. package/dist/storage/schemas.d.ts +56 -16
  95. package/dist/types.d.ts +2 -2
  96. package/dist/types.js +1 -1
  97. package/dist/users/methods.d.ts +16 -0
  98. package/dist/users/methods.js +276 -0
  99. package/dist/utils/configMigration.d.ts +1 -0
  100. package/dist/utils/configMigration.js +156 -0
  101. package/dist/utils/dataConverters.d.ts +46 -0
  102. package/dist/utils/dataConverters.js +139 -0
  103. package/dist/utils/loadConfigs.d.ts +15 -4
  104. package/dist/utils/loadConfigs.js +377 -51
  105. package/dist/utils/schemaStrings.js +2 -1
  106. package/dist/utils/setupFiles.d.ts +2 -1
  107. package/dist/utils/setupFiles.js +723 -28
  108. package/dist/utils/validationRules.d.ts +43 -0
  109. package/dist/utils/validationRules.js +42 -0
  110. package/dist/utils/yamlConverter.d.ts +48 -0
  111. package/dist/utils/yamlConverter.js +98 -0
  112. package/dist/utilsController.js +65 -43
  113. package/package.json +19 -15
  114. package/src/collections/attributes.ts +3 -2
  115. package/src/collections/methods.ts +85 -51
  116. package/src/config/yamlConfig.ts +488 -0
  117. package/src/{migrations/setupDatabase.ts → databases/setup.ts} +11 -5
  118. package/src/functions/methods.ts +8 -4
  119. package/src/functions/templates/count-docs-in-collection/package.json +25 -0
  120. package/src/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  121. package/src/functions/templates/typescript-node/package.json +24 -0
  122. package/src/functions/templates/typescript-node/tsconfig.json +28 -0
  123. package/src/functions/templates/uv/README.md +31 -0
  124. package/src/functions/templates/uv/pyproject.toml +29 -0
  125. package/src/interactiveCLI.ts +226 -61
  126. package/src/main.ts +111 -37
  127. package/src/migrations/afterImportActions.ts +2 -2
  128. package/src/migrations/appwriteToX.ts +17 -4
  129. package/src/migrations/dataLoader.ts +4 -4
  130. package/src/migrations/importController.ts +30 -22
  131. package/src/migrations/importDataActions.ts +2 -2
  132. package/src/migrations/relationships.ts +1 -1
  133. package/src/migrations/services/DataTransformationService.ts +196 -0
  134. package/src/migrations/services/FileHandlerService.ts +311 -0
  135. package/src/migrations/services/ImportOrchestrator.ts +669 -0
  136. package/src/migrations/services/RateLimitManager.ts +363 -0
  137. package/src/migrations/services/RelationshipResolver.ts +461 -0
  138. package/src/migrations/services/UserMappingService.ts +345 -0
  139. package/src/migrations/services/ValidationService.ts +349 -0
  140. package/src/migrations/transfer.ts +22 -228
  141. package/src/migrations/yaml/YamlImportConfigLoader.ts +427 -0
  142. package/src/migrations/yaml/YamlImportIntegration.ts +419 -0
  143. package/src/migrations/yaml/generateImportSchemas.ts +589 -0
  144. package/src/shared/attributeManager.ts +429 -0
  145. package/src/shared/confirmationDialogs.ts +327 -0
  146. package/src/shared/functionManager.ts +515 -0
  147. package/src/shared/indexManager.ts +253 -0
  148. package/src/shared/jsonSchemaGenerator.ts +403 -0
  149. package/src/shared/logging.ts +74 -0
  150. package/src/shared/messageFormatter.ts +195 -0
  151. package/src/{migrations/migrationHelper.ts → shared/migrationHelpers.ts} +22 -4
  152. package/src/{migrations/helper.ts → shared/operationLogger.ts} +7 -2
  153. package/src/{migrations/queue.ts → shared/operationQueue.ts} +1 -1
  154. package/src/shared/progressManager.ts +278 -0
  155. package/src/{migrations/schemaStrings.ts → shared/schemaGenerator.ts} +71 -17
  156. package/src/storage/methods.ts +199 -78
  157. package/src/types.ts +2 -2
  158. package/src/{migrations/users.ts → users/methods.ts} +2 -2
  159. package/src/utils/configMigration.ts +212 -0
  160. package/src/utils/loadConfigs.ts +414 -52
  161. package/src/utils/schemaStrings.ts +2 -1
  162. package/src/utils/setupFiles.ts +742 -40
  163. package/src/{migrations → utils}/validationRules.ts +1 -1
  164. package/src/utils/yamlConverter.ts +131 -0
  165. package/src/utilsController.ts +75 -54
  166. package/src/functions/templates/poetry/README.md +0 -30
  167. package/src/functions/templates/poetry/pyproject.toml +0 -16
  168. package/src/migrations/attributes.ts +0 -561
  169. package/src/migrations/backup.ts +0 -205
  170. package/src/migrations/databases.ts +0 -39
  171. package/src/migrations/dbHelpers.ts +0 -92
  172. package/src/migrations/indexes.ts +0 -40
  173. package/src/migrations/logging.ts +0 -29
  174. package/src/migrations/storage.ts +0 -538
  175. /package/src/{migrations → functions}/openapi.ts +0 -0
  176. /package/src/functions/templates/{poetry → uv}/src/__init__.py +0 -0
  177. /package/src/functions/templates/{poetry → uv}/src/index.py +0 -0
  178. /package/src/{migrations/converters.ts → utils/dataConverters.ts} +0 -0
@@ -8,8 +8,10 @@ import { z } from "zod";
8
8
  import fs from "fs";
9
9
  import path from "path";
10
10
  import { dump } from "js-yaml";
11
- import { getDatabaseFromConfig } from "./afterImportActions.js";
11
+ import { getDatabaseFromConfig } from "../migrations/afterImportActions.js";
12
12
  import { ulid } from "ulidx";
13
+ import { JsonSchemaGenerator } from "./jsonSchemaGenerator.js";
14
+ import { collectionToYaml, getCollectionYamlFilename } from "../utils/yamlConverter.js";
13
15
 
14
16
  interface RelationshipDetail {
15
17
  parentCollection: string;
@@ -31,6 +33,29 @@ export class SchemaGenerator {
31
33
  this.extractRelationships();
32
34
  }
33
35
 
36
+ public updateYamlCollections(): void {
37
+ const collections = this.config.collections;
38
+ delete this.config.collections;
39
+
40
+ const collectionsDir = path.join(this.appwriteFolderPath, "collections");
41
+ if (!fs.existsSync(collectionsDir)) {
42
+ fs.mkdirSync(collectionsDir, { recursive: true });
43
+ }
44
+
45
+ collections?.forEach((collection) => {
46
+ // Determine schema path based on config
47
+ const schemaDir = this.config.schemaConfig?.yamlSchemaDirectory || ".yaml_schemas";
48
+ const schemaPath = `../${schemaDir}/collection.schema.json`;
49
+
50
+ const yamlContent = collectionToYaml(collection, schemaPath);
51
+ const filename = getCollectionYamlFilename(collection);
52
+ const filePath = path.join(collectionsDir, filename);
53
+
54
+ fs.writeFileSync(filePath, yamlContent, { encoding: "utf-8" });
55
+ console.log(`Collection YAML written to ${filePath}`);
56
+ });
57
+ }
58
+
34
59
  public updateTsSchemas(): void {
35
60
  const collections = this.config.collections;
36
61
  const functions = this.config.functions || [];
@@ -301,24 +326,52 @@ export default appwriteConfig;
301
326
  this.relationshipMap.set(parentCollection, relationshipsParent);
302
327
  }
303
328
 
304
- public generateSchemas(): void {
329
+ public generateSchemas(options: {
330
+ format?: "zod" | "json" | "both";
331
+ verbose?: boolean;
332
+ } = {}): void {
333
+ const { format = "both", verbose = false } = options;
334
+
305
335
  if (!this.config.collections) {
306
336
  return;
307
337
  }
308
- this.config.collections.forEach((collection) => {
309
- const schemaString = this.createSchemaString(
310
- collection.name,
311
- collection.attributes || []
312
- );
313
- const camelCaseName = toCamelCase(collection.name);
314
- const schemaPath = path.join(
315
- this.appwriteFolderPath,
316
- "schemas",
317
- `${camelCaseName}.ts`
318
- );
319
- fs.writeFileSync(schemaPath, schemaString, { encoding: "utf-8" });
320
- console.log(`Schema written to ${schemaPath}`);
321
- });
338
+
339
+ // Create schemas directory using config setting
340
+ const outputDir = this.config.schemaConfig?.outputDirectory || "schemas";
341
+ const schemasPath = path.join(this.appwriteFolderPath, outputDir);
342
+ if (!fs.existsSync(schemasPath)) {
343
+ fs.mkdirSync(schemasPath, { recursive: true });
344
+ }
345
+
346
+ // Generate Zod schemas (TypeScript)
347
+ if (format === "zod" || format === "both") {
348
+ this.config.collections.forEach((collection) => {
349
+ const schemaString = this.createSchemaString(
350
+ collection.name,
351
+ collection.attributes || []
352
+ );
353
+ const camelCaseName = toCamelCase(collection.name);
354
+ const schemaPath = path.join(schemasPath, `${camelCaseName}.ts`);
355
+ fs.writeFileSync(schemaPath, schemaString, { encoding: "utf-8" });
356
+ if (verbose) {
357
+ console.log(`Zod schema written to ${schemaPath}`);
358
+ }
359
+ });
360
+ }
361
+
362
+ // Generate JSON schemas (all at once)
363
+ if (format === "json" || format === "both") {
364
+ const jsonSchemaGenerator = new JsonSchemaGenerator(this.config, this.appwriteFolderPath);
365
+ jsonSchemaGenerator.generateJsonSchemas({
366
+ outputFormat: format === "json" ? "json" : "both",
367
+ outputDirectory: outputDir,
368
+ verbose: verbose
369
+ });
370
+ }
371
+
372
+ if (verbose) {
373
+ console.log(`✓ Schema generation completed (format: ${format})`);
374
+ }
322
375
  }
323
376
 
324
377
  createSchemaString = (name: string, attributes: Attribute[]): string => {
@@ -457,7 +510,8 @@ export default appwriteConfig;
457
510
  baseSchemaCode += ".nullish()";
458
511
  }
459
512
  break;
460
- case "float":
513
+ case "double":
514
+ case "float": // Backward compatibility
461
515
  baseSchemaCode = "z.number()";
462
516
  if (finalAttribute.min !== undefined) {
463
517
  baseSchemaCode += `.min(${finalAttribute.min}, "Minimum value of ${finalAttribute.min} not met")`;
@@ -11,10 +11,12 @@ import { tryAwaitWithRetry, type AppwriteConfig } from "appwrite-utils";
11
11
  import { getClientFromConfig } from "../utils/getClientFromConfig.js";
12
12
  import { ulid } from "ulidx";
13
13
  import type { BackupCreate } from "./schemas.js";
14
- import { logOperation } from "../migrations/helper.js";
15
- import { splitIntoBatches } from "../migrations/migrationHelper.js";
14
+ import { logOperation } from "../shared/operationLogger.js";
15
+ import { splitIntoBatches } from "../shared/migrationHelpers.js";
16
16
  import { retryFailedPromises } from "../utils/retryFailedPromises.js";
17
17
  import { InputFile } from "node-appwrite/file";
18
+ import { MessageFormatter, Messages } from "../shared/messageFormatter.js";
19
+ import { ProgressManager } from "../shared/progressManager.js";
18
20
 
19
21
  export const getStorage = (config: AppwriteConfig) => {
20
22
  const client = getClientFromConfig(config);
@@ -167,14 +169,39 @@ export const ensureDatabaseConfigBucketsExist = async (
167
169
 
168
170
  export const wipeDocumentStorage = async (
169
171
  storage: Storage,
170
- bucketId: string
172
+ bucketId: string,
173
+ options: { skipConfirmation?: boolean } = {}
171
174
  ): Promise<void> => {
172
- console.log(`Wiping storage for bucket ID: ${bucketId}`);
175
+ MessageFormatter.warning(`About to delete all files in bucket: ${bucketId}`);
176
+
177
+ if (!options.skipConfirmation) {
178
+ const { ConfirmationDialogs } = await import("../shared/confirmationDialogs.js");
179
+ const confirmed = await ConfirmationDialogs.confirmDestructiveOperation({
180
+ operation: "Storage Wipe",
181
+ targets: [bucketId],
182
+ consequences: [
183
+ "Delete ALL files in the storage bucket",
184
+ "This action cannot be undone",
185
+ ],
186
+ requireExplicitConfirmation: true,
187
+ confirmationText: "DELETE FILES",
188
+ });
189
+
190
+ if (!confirmed) {
191
+ MessageFormatter.info("Storage wipe cancelled by user");
192
+ return;
193
+ }
194
+ }
195
+
196
+ MessageFormatter.progress(`Scanning files in bucket: ${bucketId}`);
197
+
173
198
  let moreFiles = true;
174
199
  let lastFileId: string | undefined;
175
200
  const allFiles: string[] = [];
201
+
202
+ // First pass: collect all file IDs
176
203
  while (moreFiles) {
177
- const queries = [Query.limit(100)]; // Adjust the limit as needed
204
+ const queries = [Query.limit(100)];
178
205
  if (lastFileId) {
179
206
  queries.push(Query.cursorAfter(lastFileId));
180
207
  }
@@ -182,26 +209,43 @@ export const wipeDocumentStorage = async (
182
209
  async () => await storage.listFiles(bucketId, queries)
183
210
  );
184
211
  if (filesPulled.files.length === 0) {
185
- console.log("No files found, done!");
186
212
  moreFiles = false;
187
213
  break;
188
214
  } else if (filesPulled.files.length > 0) {
189
215
  const fileIds = filesPulled.files.map((file) => file.$id);
190
216
  allFiles.push(...fileIds);
191
217
  }
192
- moreFiles = filesPulled.files.length === 100; // Adjust based on the limit
218
+ moreFiles = filesPulled.files.length === 100;
193
219
  if (moreFiles) {
194
220
  lastFileId = filesPulled.files[filesPulled.files.length - 1].$id;
195
221
  }
196
222
  }
197
223
 
198
- for (const fileId of allFiles) {
199
- console.log(`Deleting file: ${fileId}`);
200
- await tryAwaitWithRetry(
201
- async () => await storage.deleteFile(bucketId, fileId)
202
- );
224
+ if (allFiles.length === 0) {
225
+ MessageFormatter.info("No files found in bucket");
226
+ return;
227
+ }
228
+
229
+ // Second pass: delete files with progress tracking
230
+ const progress = ProgressManager.create(`wipe-${bucketId}`, allFiles.length, {
231
+ title: `Deleting files from ${bucketId}`,
232
+ });
233
+
234
+ try {
235
+ for (let i = 0; i < allFiles.length; i++) {
236
+ const fileId = allFiles[i];
237
+ await tryAwaitWithRetry(
238
+ async () => await storage.deleteFile(bucketId, fileId)
239
+ );
240
+ progress.update(i + 1, `Deleted file: ${fileId.slice(0, 20)}...`);
241
+ }
242
+
243
+ progress.stop();
244
+ MessageFormatter.success(`All ${MessageFormatter.formatNumber(allFiles.length)} files in bucket ${bucketId} have been deleted`);
245
+ } catch (error) {
246
+ progress.fail(error instanceof Error ? error.message : String(error));
247
+ throw error;
203
248
  }
204
- console.log(`All files in bucket ${bucketId} have been deleted.`);
205
249
  };
206
250
 
207
251
  export const initOrGetDocumentStorage = async (
@@ -254,9 +298,10 @@ export const backupDatabase = async (
254
298
  databaseId: string,
255
299
  storage: Storage
256
300
  ): Promise<void> => {
257
- console.log("---------------------------------");
258
- console.log("Starting Database Backup of " + databaseId);
259
- console.log("---------------------------------");
301
+ const startTime = Date.now();
302
+
303
+ MessageFormatter.banner("Database Backup", `Backing up database: ${databaseId}`);
304
+ MessageFormatter.info(Messages.BACKUP_STARTED(databaseId));
260
305
 
261
306
  let data: BackupCreate = {
262
307
  database: "",
@@ -272,7 +317,11 @@ export const backupDatabase = async (
272
317
  total: 100,
273
318
  error: "",
274
319
  status: "in_progress",
275
- });
320
+ }, undefined, config.useMigrations);
321
+
322
+ let progress: ProgressManager | null = null;
323
+ let totalDocuments = 0;
324
+ let processedDocuments = 0;
276
325
 
277
326
  try {
278
327
  const db = await tryAwaitWithRetry(
@@ -280,10 +329,11 @@ export const backupDatabase = async (
280
329
  );
281
330
  data.database = JSON.stringify(db);
282
331
 
332
+ // First pass: count collections and documents for progress tracking
333
+ MessageFormatter.step(1, 3, "Analyzing database structure");
283
334
  let lastCollectionId = "";
284
335
  let moreCollections = true;
285
- let progress = 0;
286
- let total = 0;
336
+ let totalCollections = 0;
287
337
 
288
338
  while (moreCollections) {
289
339
  const collectionResponse = await tryAwaitWithRetry(
@@ -294,7 +344,45 @@ export const backupDatabase = async (
294
344
  ])
295
345
  );
296
346
 
297
- total += collectionResponse.collections.length;
347
+ totalCollections += collectionResponse.collections.length;
348
+
349
+ // Count documents in each collection
350
+ for (const { $id: collectionId } of collectionResponse.collections) {
351
+ try {
352
+ const documentCount = await tryAwaitWithRetry(
353
+ async () => (await database.listDocuments(databaseId, collectionId, [Query.limit(1)])).total
354
+ );
355
+ totalDocuments += documentCount;
356
+ } catch (error) {
357
+ MessageFormatter.warning(`Could not count documents in collection ${collectionId}`);
358
+ }
359
+ }
360
+
361
+ moreCollections = collectionResponse.collections.length === 500;
362
+ if (moreCollections) {
363
+ lastCollectionId = collectionResponse.collections[collectionResponse.collections.length - 1].$id;
364
+ }
365
+ }
366
+
367
+ const totalItems = totalCollections + totalDocuments;
368
+ progress = ProgressManager.create(`backup-${databaseId}`, totalItems, {
369
+ title: `Backing up ${databaseId}`,
370
+ });
371
+
372
+ MessageFormatter.step(2, 3, `Processing ${totalCollections} collections and ${totalDocuments} documents`);
373
+
374
+ // Second pass: actual backup with progress tracking
375
+ lastCollectionId = "";
376
+ moreCollections = true;
377
+
378
+ while (moreCollections) {
379
+ const collectionResponse = await tryAwaitWithRetry(
380
+ async () =>
381
+ await database.listCollections(databaseId, [
382
+ Query.limit(500),
383
+ ...(lastCollectionId ? [Query.cursorAfter(lastCollectionId)] : []),
384
+ ])
385
+ );
298
386
 
299
387
  for (const {
300
388
  $id: collectionId,
@@ -304,8 +392,9 @@ export const backupDatabase = async (
304
392
  const collection = await tryAwaitWithRetry(
305
393
  async () => await database.getCollection(databaseId, collectionId)
306
394
  );
307
- progress++;
395
+
308
396
  data.collections.push(JSON.stringify(collection));
397
+ progress?.increment(1, `Processing collection: ${collectionName}`);
309
398
 
310
399
  let lastDocumentId = "";
311
400
  let moreDocuments = true;
@@ -322,7 +411,6 @@ export const backupDatabase = async (
322
411
  ])
323
412
  );
324
413
 
325
- total += documentResponse.documents.length;
326
414
  collectionDocumentCount += documentResponse.documents.length;
327
415
 
328
416
  const documentPromises = documentResponse.documents.map(
@@ -335,28 +423,36 @@ export const backupDatabase = async (
335
423
  for (const batch of promiseBatches) {
336
424
  const successfulDocuments = await retryFailedPromises(batch);
337
425
  documentsPulled.push(...successfulDocuments);
426
+
427
+ // Update progress for each batch
428
+ progress?.increment(successfulDocuments.length,
429
+ `Processing ${collectionName}: ${processedDocuments + successfulDocuments.length}/${totalDocuments} documents`
430
+ );
431
+ processedDocuments += successfulDocuments.length;
338
432
  }
339
433
 
340
434
  data.documents.push({
341
435
  collectionId: collectionId,
342
436
  data: JSON.stringify(documentsPulled),
343
437
  });
344
- progress += documentsPulled.length;
345
-
346
- await logOperation(
347
- database,
348
- databaseId,
349
- {
350
- operationType: "backup",
351
- collectionId: collectionId,
352
- data: `Backing up, ${data.collections.length} collections so far`,
353
- progress: progress,
354
- total: total,
355
- error: "",
356
- status: "in_progress",
357
- },
358
- backupOperation.$id
359
- );
438
+
439
+ if (backupOperation) {
440
+ await logOperation(
441
+ database,
442
+ databaseId,
443
+ {
444
+ operationType: "backup",
445
+ collectionId: collectionId,
446
+ data: `Backing up, ${data.collections.length} collections so far`,
447
+ progress: processedDocuments,
448
+ total: totalDocuments,
449
+ error: "",
450
+ status: "in_progress",
451
+ },
452
+ backupOperation.$id,
453
+ config.useMigrations
454
+ );
455
+ }
360
456
 
361
457
  moreDocuments = documentResponse.documents.length === 500;
362
458
  if (moreDocuments) {
@@ -367,12 +463,12 @@ export const backupDatabase = async (
367
463
  }
368
464
  }
369
465
 
370
- console.log(
371
- `Collection ${collectionName} backed up with ${collectionDocumentCount} documents.`
466
+ MessageFormatter.success(
467
+ `Collection ${collectionName} backed up with ${MessageFormatter.formatNumber(collectionDocumentCount)} documents`
372
468
  );
373
469
  } catch (error) {
374
- console.log(
375
- `Collection ${collectionName} must not exist, continuing...`
470
+ MessageFormatter.warning(
471
+ `Collection ${collectionName} could not be backed up: ${error instanceof Error ? error.message : String(error)}`
376
472
  );
377
473
  continue;
378
474
  }
@@ -387,50 +483,75 @@ export const backupDatabase = async (
387
483
  }
388
484
  }
389
485
 
486
+ MessageFormatter.step(3, 3, "Creating backup file");
487
+
390
488
  const bucket = await initOrGetDocumentStorage(storage, config, databaseId);
391
- const inputFile = InputFile.fromPlainText(
392
- JSON.stringify(data),
393
- `${new Date().toISOString()}-${databaseId}.json`
394
- );
489
+ const backupData = JSON.stringify(data);
490
+ const backupSize = Buffer.byteLength(backupData, 'utf8');
491
+ const fileName = `${new Date().toISOString()}-${databaseId}.json`;
492
+
493
+ const inputFile = InputFile.fromPlainText(backupData, fileName);
395
494
  const fileCreated = await storage.createFile(
396
495
  bucket!.$id,
397
496
  ulid(),
398
497
  inputFile
399
498
  );
400
499
 
401
- await logOperation(
402
- database,
403
- databaseId,
404
- {
405
- operationType: "backup",
406
- collectionId: "",
407
- data: fileCreated.$id,
408
- progress: 100,
409
- total: total,
410
- error: "",
411
- status: "completed",
412
- },
413
- backupOperation.$id
414
- );
500
+ progress?.stop();
501
+
502
+ if (backupOperation) {
503
+ await logOperation(
504
+ database,
505
+ databaseId,
506
+ {
507
+ operationType: "backup",
508
+ collectionId: "",
509
+ data: fileCreated.$id,
510
+ progress: totalItems,
511
+ total: totalItems,
512
+ error: "",
513
+ status: "completed",
514
+ },
515
+ backupOperation.$id,
516
+ config.useMigrations
517
+ );
518
+ }
415
519
 
416
- console.log("---------------------------------");
417
- console.log("Database Backup Complete");
418
- console.log("---------------------------------");
520
+ const duration = Date.now() - startTime;
521
+
522
+ MessageFormatter.operationSummary("Backup", {
523
+ database: databaseId,
524
+ collections: data.collections.length,
525
+ documents: processedDocuments,
526
+ fileSize: MessageFormatter.formatBytes(backupSize),
527
+ backupFile: fileName,
528
+ bucket: bucket!.$id,
529
+ }, duration);
530
+
531
+ MessageFormatter.success(Messages.BACKUP_COMPLETED(databaseId, backupSize));
419
532
  } catch (error) {
420
- console.error("Error during backup:", error);
421
- await logOperation(
422
- database,
423
- databaseId,
424
- {
425
- operationType: "backup",
426
- collectionId: "",
427
- data: "Backup failed",
428
- progress: 0,
429
- total: 100,
430
- error: String(error),
431
- status: "error",
432
- },
433
- backupOperation.$id
434
- );
533
+ progress?.fail(error instanceof Error ? error.message : String(error));
534
+
535
+ MessageFormatter.error("Backup failed", error instanceof Error ? error : new Error(String(error)));
536
+
537
+ if (backupOperation) {
538
+ await logOperation(
539
+ database,
540
+ databaseId,
541
+ {
542
+ operationType: "backup",
543
+ collectionId: "",
544
+ data: "Backup failed",
545
+ progress: 0,
546
+ total: totalDocuments,
547
+ error: String(error),
548
+ status: "error",
549
+ },
550
+ backupOperation.$id,
551
+ config.useMigrations
552
+ );
553
+ }
554
+
555
+ throw error;
435
556
  }
436
557
  };
package/src/types.ts CHANGED
@@ -1,9 +1,9 @@
1
- export type { ValidationRules } from "./migrations/validationRules.js";
1
+ export type { ValidationRules } from "./utils/validationRules.js";
2
2
  export {
3
3
  type AuthUserCreate,
4
4
  AuthUserCreateSchema,
5
5
  type AuthUser,
6
6
  AuthUserSchema,
7
7
  } from "./schemas/authUser.js";
8
- export { validationRules } from "./migrations/validationRules.js";
8
+ export { validationRules } from "./utils/validationRules.js";
9
9
  export { afterImportActions } from "./migrations/afterImportActions.js";
@@ -12,8 +12,8 @@ import {
12
12
  type AuthUser,
13
13
  type AuthUserCreate,
14
14
  } from "../schemas/authUser.js";
15
- import { logger } from "./logging.js";
16
- import { splitIntoBatches } from "./migrationHelper.js";
15
+ import { logger } from "../shared/logging.js";
16
+ import { splitIntoBatches } from "../shared/migrationHelpers.js";
17
17
  import {
18
18
  getAppwriteClient,
19
19
  tryAwaitWithRetry,