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,4 +1,5 @@
1
1
  import { toCamelCase, toPascalCase } from "../utils/index.js";
2
+ import { getVersionAwareDirectory, resolveDirectoryForApiMode, getDualDirectoryPaths } from "appwrite-utils";
2
3
  import { z } from "zod";
3
4
  import fs from "fs";
4
5
  import path from "path";
@@ -7,6 +8,9 @@ import { getDatabaseFromConfig } from "../migrations/afterImportActions.js";
7
8
  import { ulid } from "ulidx";
8
9
  import { JsonSchemaGenerator } from "./jsonSchemaGenerator.js";
9
10
  import { collectionToYaml, getCollectionYamlFilename } from "../utils/yamlConverter.js";
11
+ import { extractTwoWayRelationships, resolveCollectionName } from "./relationshipExtractor.js";
12
+ import { resolveSchemaDir } from "../utils/pathResolvers.js";
13
+ import { MessageFormatter } from "./messageFormatter.js";
10
14
  export class SchemaGenerator {
11
15
  relationshipMap = new Map();
12
16
  config;
@@ -17,25 +21,34 @@ export class SchemaGenerator {
17
21
  this.extractRelationships();
18
22
  }
19
23
  resolveCollectionName = (idOrName) => {
20
- const col = this.config.collections?.find((c) => c.$id === idOrName || c.name === idOrName);
21
- return col?.name ?? idOrName;
24
+ return resolveCollectionName(this.config, idOrName);
22
25
  };
23
26
  updateYamlCollections() {
24
27
  const collections = this.config.collections;
25
28
  delete this.config.collections;
26
- const collectionsDir = path.join(this.appwriteFolderPath, "collections");
29
+ // Determine output directory based on API mode/version detection
30
+ const outputDir = this.getVersionAwareCollectionsDirectory();
31
+ const collectionsDir = path.join(this.appwriteFolderPath, outputDir);
27
32
  if (!fs.existsSync(collectionsDir)) {
28
33
  fs.mkdirSync(collectionsDir, { recursive: true });
29
34
  }
30
35
  collections?.forEach((collection) => {
31
- // Determine schema path based on config
36
+ // Determine schema path based on config and output directory
32
37
  const schemaDir = this.config.schemaConfig?.yamlSchemaDirectory || ".yaml_schemas";
33
- const schemaPath = `../${schemaDir}/collection.schema.json`;
34
- const yamlContent = collectionToYaml(collection, schemaPath);
38
+ const isTablesMode = outputDir === "tables";
39
+ const schemaPath = isTablesMode
40
+ ? `../${schemaDir}/table.schema.json`
41
+ : `../${schemaDir}/collection.schema.json`;
42
+ const yamlConfig = {
43
+ useTableTerminology: isTablesMode,
44
+ entityType: isTablesMode ? 'table' : 'collection',
45
+ schemaPath
46
+ };
47
+ const yamlContent = collectionToYaml(collection, yamlConfig);
35
48
  const filename = getCollectionYamlFilename(collection);
36
49
  const filePath = path.join(collectionsDir, filename);
37
50
  fs.writeFileSync(filePath, yamlContent, { encoding: "utf-8" });
38
- console.log(`Collection YAML written to ${filePath}`);
51
+ MessageFormatter.success(`${outputDir === "tables" ? "Table" : "Collection"} YAML written to ${filePath}`, { prefix: "Schema" });
39
52
  });
40
53
  }
41
54
  updateTsSchemas() {
@@ -89,7 +102,9 @@ export class SchemaGenerator {
89
102
  export default appwriteConfig;
90
103
  `;
91
104
  fs.writeFileSync(configPath, configContent, { encoding: "utf-8" });
92
- const collectionsFolderPath = path.join(this.appwriteFolderPath, "collections");
105
+ // Determine output directory based on API mode/version detection
106
+ const outputDir = this.getVersionAwareCollectionsDirectory();
107
+ const collectionsFolderPath = path.join(this.appwriteFolderPath, outputDir);
93
108
  if (!fs.existsSync(collectionsFolderPath)) {
94
109
  fs.mkdirSync(collectionsFolderPath, { recursive: true });
95
110
  }
@@ -155,9 +170,31 @@ export class SchemaGenerator {
155
170
  fs.writeFileSync(collectionFilePath, collectionContent, {
156
171
  encoding: "utf-8",
157
172
  });
158
- console.log(`Collection schema written to ${collectionFilePath}`);
173
+ MessageFormatter.success(`${outputDir === "tables" ? "Table" : "Collection"} schema written to ${collectionFilePath}`, { prefix: "Schema" });
159
174
  });
160
175
  }
176
+ /**
177
+ * Determines the appropriate directory for collections/tables based on API mode
178
+ * Uses version detection or config hints to choose between 'collections' and 'tables'
179
+ */
180
+ getVersionAwareCollectionsDirectory() {
181
+ return getVersionAwareDirectory(this.config, this.appwriteFolderPath);
182
+ }
183
+ /**
184
+ * Get directory for a specific API mode (legacy or tablesdb)
185
+ * @param apiMode - The API mode to get directory for
186
+ * @returns The directory name for the specified API mode
187
+ */
188
+ getDirectoryForApiMode(apiMode) {
189
+ return resolveDirectoryForApiMode(this.config, apiMode, this.appwriteFolderPath);
190
+ }
191
+ /**
192
+ * Get both directory paths for dual API support
193
+ * @returns Object with both collectionsDirectory and tablesDirectory paths
194
+ */
195
+ getDualDirectoryConfiguration() {
196
+ return getDualDirectoryPaths(this.config);
197
+ }
161
198
  async updateConfig(config, isYamlConfig = false) {
162
199
  if (isYamlConfig) {
163
200
  // User has YAML config - find the config file and update it + generate individual collection files
@@ -167,7 +204,7 @@ export class SchemaGenerator {
167
204
  await this.updateYamlConfig(config, yamlConfigPath);
168
205
  }
169
206
  else {
170
- console.warn("⚠️ YAML config expected but not found, falling back to TypeScript");
207
+ MessageFormatter.warning("YAML config expected but not found, falling back to TypeScript", { prefix: "Schema" });
171
208
  this.updateTypeScriptConfig(config);
172
209
  }
173
210
  }
@@ -183,10 +220,10 @@ export class SchemaGenerator {
183
220
  await writeYamlConfig(yamlConfigPath, config);
184
221
  // Generate individual collection YAML files
185
222
  this.updateYamlCollections();
186
- console.log("Updated YAML configuration and collection files");
223
+ MessageFormatter.success("Updated YAML configuration and collection files", { prefix: "Schema" });
187
224
  }
188
225
  catch (error) {
189
- console.error("Error updating YAML config:", error instanceof Error ? error.message : error);
226
+ MessageFormatter.error("Error updating YAML config", error, { prefix: "Schema" });
190
227
  throw error;
191
228
  }
192
229
  }
@@ -234,68 +271,10 @@ const appwriteConfig: AppwriteConfig = {
234
271
  export default appwriteConfig;
235
272
  `;
236
273
  fs.writeFileSync(configPath, configContent, { encoding: "utf-8" });
237
- console.log("Updated TypeScript configuration file");
274
+ MessageFormatter.success("Updated TypeScript configuration file", { prefix: "Schema" });
238
275
  }
239
276
  extractRelationships() {
240
- if (!this.config.collections) {
241
- return;
242
- }
243
- this.config.collections.forEach((collection) => {
244
- if (!collection.attributes) {
245
- return;
246
- }
247
- collection.attributes.forEach((attr) => {
248
- if (attr.type === "relationship" && attr.twoWay && attr.twoWayKey) {
249
- const relationshipAttr = attr;
250
- let isArrayParent = false;
251
- let isArrayChild = false;
252
- switch (relationshipAttr.relationType) {
253
- case "oneToMany":
254
- isArrayParent = true;
255
- isArrayChild = false;
256
- break;
257
- case "manyToMany":
258
- isArrayParent = true;
259
- isArrayChild = true;
260
- break;
261
- case "oneToOne":
262
- isArrayParent = false;
263
- isArrayChild = false;
264
- break;
265
- case "manyToOne":
266
- isArrayParent = false;
267
- isArrayChild = true;
268
- break;
269
- default:
270
- break;
271
- }
272
- this.addRelationship(collection.name, this.resolveCollectionName(relationshipAttr.relatedCollection), attr.key, relationshipAttr.twoWayKey, isArrayParent, isArrayChild);
273
- console.log(`Extracted relationship: ${attr.key}\n\t${collection.name} -> ${relationshipAttr.relatedCollection}, databaseId: ${collection.databaseId}`);
274
- }
275
- });
276
- });
277
- }
278
- addRelationship(parentCollection, childCollection, parentKey, childKey, isArrayParent, isArrayChild) {
279
- const relationshipsChild = this.relationshipMap.get(childCollection) || [];
280
- const relationshipsParent = this.relationshipMap.get(parentCollection) || [];
281
- relationshipsParent.push({
282
- parentCollection,
283
- childCollection,
284
- parentKey,
285
- childKey,
286
- isArray: isArrayParent,
287
- isChild: false,
288
- });
289
- relationshipsChild.push({
290
- parentCollection,
291
- childCollection,
292
- parentKey,
293
- childKey,
294
- isArray: isArrayChild,
295
- isChild: true,
296
- });
297
- this.relationshipMap.set(childCollection, relationshipsChild);
298
- this.relationshipMap.set(parentCollection, relationshipsParent);
277
+ this.relationshipMap = extractTwoWayRelationships(this.config);
299
278
  }
300
279
  generateSchemas(options = {}) {
301
280
  const { format = "both", verbose = false } = options;
@@ -304,7 +283,9 @@ export default appwriteConfig;
304
283
  }
305
284
  // Create schemas directory using config setting
306
285
  const outputDir = this.config.schemaConfig?.outputDirectory || "schemas";
307
- const schemasPath = path.join(this.appwriteFolderPath, outputDir);
286
+ const schemasPath = outputDir === "schemas"
287
+ ? resolveSchemaDir(this.appwriteFolderPath)
288
+ : path.join(this.appwriteFolderPath, outputDir);
308
289
  if (!fs.existsSync(schemasPath)) {
309
290
  fs.mkdirSync(schemasPath, { recursive: true });
310
291
  }
@@ -316,7 +297,7 @@ export default appwriteConfig;
316
297
  const schemaPath = path.join(schemasPath, `${camelCaseName}.ts`);
317
298
  fs.writeFileSync(schemaPath, schemaString, { encoding: "utf-8" });
318
299
  if (verbose) {
319
- console.log(`Zod schema written to ${schemaPath}`);
300
+ MessageFormatter.success(`Zod schema written to ${schemaPath}`, { prefix: "Schema" });
320
301
  }
321
302
  });
322
303
  }
@@ -330,7 +311,7 @@ export default appwriteConfig;
330
311
  });
331
312
  }
332
313
  if (verbose) {
333
- console.log(`✓ Schema generation completed (format: ${format})`);
314
+ MessageFormatter.success(`Schema generation completed (format: ${format})`, { prefix: "Schema" });
334
315
  }
335
316
  }
336
317
  // Zod v4 recursive getter-based schemas
@@ -0,0 +1,20 @@
1
+ import type { BackupCreate } from "./schemas.js";
2
+ export interface BackupCompressionOptions {
3
+ includeFiles?: boolean;
4
+ compressionLevel?: number;
5
+ }
6
+ /**
7
+ * Creates a compressed ZIP backup from backup data
8
+ *
9
+ * Structure:
10
+ * - metadata.json (backup metadata)
11
+ * - database.json (database config)
12
+ * - collections/*.json (one file per collection)
13
+ * - documents/*.json (one file per collection's documents)
14
+ * - files/ (optional, if includeFiles is true)
15
+ */
16
+ export declare function createBackupZip(backupData: BackupCreate, options?: BackupCompressionOptions): Promise<Buffer>;
17
+ /**
18
+ * Estimates compression ratio for backup data
19
+ */
20
+ export declare function estimateCompressedSize(uncompressedSize: number, format?: string): number;
@@ -0,0 +1,67 @@
1
+ import JSZip from "jszip";
2
+ /**
3
+ * Creates a compressed ZIP backup from backup data
4
+ *
5
+ * Structure:
6
+ * - metadata.json (backup metadata)
7
+ * - database.json (database config)
8
+ * - collections/*.json (one file per collection)
9
+ * - documents/*.json (one file per collection's documents)
10
+ * - files/ (optional, if includeFiles is true)
11
+ */
12
+ export async function createBackupZip(backupData, options = {}) {
13
+ const zip = new JSZip();
14
+ const compressionLevel = options.compressionLevel ?? 6;
15
+ // Add metadata.json
16
+ const metadata = {
17
+ version: "1.0",
18
+ createdAt: new Date().toISOString(),
19
+ databaseId: JSON.parse(backupData.database).$id,
20
+ format: "zip",
21
+ includesFiles: options.includeFiles ?? false
22
+ };
23
+ zip.file("metadata.json", JSON.stringify(metadata, null, 2));
24
+ // Add database.json
25
+ zip.file("database.json", backupData.database);
26
+ // Add collections/*.json
27
+ const collectionsFolder = zip.folder("collections");
28
+ if (collectionsFolder) {
29
+ backupData.collections.forEach((collectionStr, index) => {
30
+ const collection = JSON.parse(collectionStr);
31
+ collectionsFolder.file(`${collection.$id || `collection_${index}`}.json`, collectionStr);
32
+ });
33
+ }
34
+ // Add documents/*.json
35
+ const documentsFolder = zip.folder("documents");
36
+ if (documentsFolder) {
37
+ backupData.documents.forEach((docBatch) => {
38
+ const collectionId = docBatch.collectionId;
39
+ documentsFolder.file(`${collectionId}.json`, docBatch.data);
40
+ });
41
+ }
42
+ // TODO: Add files support in future task (C3.5)
43
+ if (options.includeFiles) {
44
+ // Placeholder for file backup support
45
+ const filesFolder = zip.folder("files");
46
+ if (filesFolder) {
47
+ filesFolder.file("file-manifest.json", JSON.stringify({
48
+ note: "File backup not yet implemented"
49
+ }, null, 2));
50
+ }
51
+ }
52
+ // Generate ZIP buffer
53
+ const buffer = await zip.generateAsync({
54
+ type: "nodebuffer",
55
+ compression: "DEFLATE",
56
+ compressionOptions: { level: compressionLevel }
57
+ });
58
+ return buffer;
59
+ }
60
+ /**
61
+ * Estimates compression ratio for backup data
62
+ */
63
+ export function estimateCompressedSize(uncompressedSize, format = "json") {
64
+ // JSON typically compresses to 20-30% of original size with gzip
65
+ const compressionRatio = format === "json" ? 0.25 : 0.5;
66
+ return Math.ceil(uncompressedSize * compressionRatio);
67
+ }
@@ -14,5 +14,19 @@ export declare const wipeDocumentStorage: (storage: Storage, bucketId: string, o
14
14
  skipConfirmation?: boolean;
15
15
  }) => Promise<void>;
16
16
  export declare const initOrGetDocumentStorage: (storage: Storage, config: AppwriteConfig, dbId: string, bucketName?: string) => Promise<Models.Bucket>;
17
- export declare const initOrGetBackupStorage: (config: AppwriteConfig, storage: Storage) => Promise<Models.Bucket>;
18
- export declare const backupDatabase: (config: AppwriteConfig, database: Databases, databaseId: string, storage: Storage) => Promise<void>;
17
+ /**
18
+ * Initializes or gets the centralized backup bucket
19
+ * All backups are stored in a single "appwrite-backups" bucket
20
+ */
21
+ export declare const initBackupBucket: (storage: Storage) => Promise<Models.Bucket | undefined>;
22
+ export interface DatabaseBackupResult {
23
+ backupFileId: string;
24
+ backupFileName: string;
25
+ backupSizeBytes: number;
26
+ databaseId: string;
27
+ databaseName: string;
28
+ collectionCount: number;
29
+ documentCount: number;
30
+ format: 'json' | 'zip';
31
+ }
32
+ export declare const backupDatabase: (config: AppwriteConfig, database: Databases, databaseId: string, storage: Storage, format?: "json" | "zip") => Promise<DatabaseBackupResult>;
@@ -8,6 +8,9 @@ import { retryFailedPromises } from "../utils/retryFailedPromises.js";
8
8
  import { InputFile } from "node-appwrite/file";
9
9
  import { MessageFormatter, Messages } from "../shared/messageFormatter.js";
10
10
  import { ProgressManager } from "../shared/progressManager.js";
11
+ import { recordBackup } from "../shared/backupTracking.js";
12
+ import { AdapterFactory } from "../adapters/AdapterFactory.js";
13
+ import { createBackupZip } from "./backupCompression.js";
11
14
  export const getStorage = (config) => {
12
15
  const client = getClientFromConfig(config);
13
16
  return new Storage(client);
@@ -165,15 +168,44 @@ export const initOrGetDocumentStorage = async (storage, config, dbId, bucketName
165
168
  ]));
166
169
  }
167
170
  };
168
- export const initOrGetBackupStorage = async (config, storage) => {
171
+ /**
172
+ * Initializes or gets the centralized backup bucket
173
+ * All backups are stored in a single "appwrite-backups" bucket
174
+ */
175
+ export const initBackupBucket = async (storage) => {
176
+ const BACKUP_BUCKET_ID = "appwrite-backups";
177
+ const BACKUP_BUCKET_NAME = "Backups";
169
178
  try {
170
- return await tryAwaitWithRetry(async () => await storage.getBucket("backup"));
179
+ // Try to get existing bucket
180
+ const bucket = await storage.getBucket(BACKUP_BUCKET_ID);
181
+ return bucket;
171
182
  }
172
- catch (e) {
173
- return await initOrGetDocumentStorage(storage, config, "backups", "Database Backups");
183
+ catch (error) {
184
+ // Bucket doesn't exist, create it
185
+ try {
186
+ const bucket = await storage.createBucket(BACKUP_BUCKET_ID, BACKUP_BUCKET_NAME, [
187
+ Permission.read(Role.any()),
188
+ Permission.create(Role.users()),
189
+ Permission.update(Role.users()),
190
+ Permission.delete(Role.users())
191
+ ], false, // fileSecurity
192
+ true, // enabled
193
+ undefined, // maximumFileSize
194
+ undefined, // allowedFileExtensions
195
+ Compression.Gzip, // compression
196
+ false, // encryption
197
+ false // antivirus
198
+ );
199
+ MessageFormatter.success(`Created backup bucket: ${BACKUP_BUCKET_ID}`);
200
+ return bucket;
201
+ }
202
+ catch (createError) {
203
+ MessageFormatter.error("Failed to create backup bucket", createError instanceof Error ? createError : new Error(String(createError)));
204
+ return undefined;
205
+ }
174
206
  }
175
207
  };
176
- export const backupDatabase = async (config, database, databaseId, storage) => {
208
+ export const backupDatabase = async (config, database, databaseId, storage, format = 'json') => {
177
209
  const startTime = Date.now();
178
210
  MessageFormatter.banner("Database Backup", `Backing up database: ${databaseId}`);
179
211
  MessageFormatter.info(Messages.BACKUP_STARTED(databaseId));
@@ -190,7 +222,7 @@ export const backupDatabase = async (config, database, databaseId, storage) => {
190
222
  total: 100,
191
223
  error: "",
192
224
  status: "in_progress",
193
- }, undefined, config.useMigrations);
225
+ });
194
226
  let progress = null;
195
227
  let totalDocuments = 0;
196
228
  let processedDocuments = 0;
@@ -275,7 +307,7 @@ export const backupDatabase = async (config, database, databaseId, storage) => {
275
307
  total: totalDocuments,
276
308
  error: "",
277
309
  status: "in_progress",
278
- }, backupOperation.$id, config.useMigrations);
310
+ }, backupOperation.$id);
279
311
  }
280
312
  moreDocuments = documentResponse.documents.length === 500;
281
313
  if (moreDocuments) {
@@ -297,13 +329,53 @@ export const backupDatabase = async (config, database, databaseId, storage) => {
297
329
  }
298
330
  }
299
331
  MessageFormatter.step(3, 3, "Creating backup file");
300
- const bucket = await initOrGetDocumentStorage(storage, config, databaseId);
301
- const backupData = JSON.stringify(data);
302
- const backupSize = Buffer.byteLength(backupData, 'utf8');
303
- const fileName = `${new Date().toISOString()}-${databaseId}.json`;
304
- const inputFile = InputFile.fromPlainText(backupData, fileName);
332
+ const bucket = await initBackupBucket(storage);
333
+ if (!bucket) {
334
+ throw new Error("Failed to initialize backup bucket");
335
+ }
336
+ let inputFile;
337
+ let fileName;
338
+ let backupSize;
339
+ if (format === 'zip') {
340
+ // Create compressed backup
341
+ const zipBuffer = await createBackupZip(data);
342
+ fileName = `${new Date().toISOString()}-${databaseId}.zip`;
343
+ backupSize = zipBuffer.length;
344
+ inputFile = InputFile.fromBuffer(new Uint8Array(zipBuffer), fileName);
345
+ }
346
+ else {
347
+ // Use JSON format (existing logic)
348
+ const backupData = JSON.stringify(data, null, 2);
349
+ fileName = `${new Date().toISOString()}-${databaseId}.json`;
350
+ backupSize = Buffer.byteLength(backupData, 'utf8');
351
+ inputFile = InputFile.fromPlainText(backupData, fileName);
352
+ }
305
353
  const fileCreated = await storage.createFile(bucket.$id, ulid(), inputFile);
306
354
  progress?.stop();
355
+ // Record backup metadata
356
+ try {
357
+ const { adapter } = await AdapterFactory.create({
358
+ appwriteEndpoint: config.appwriteEndpoint,
359
+ appwriteProject: config.appwriteProject,
360
+ appwriteKey: config.appwriteKey,
361
+ sessionCookie: config.sessionCookie
362
+ });
363
+ await recordBackup(adapter, databaseId, {
364
+ backupId: fileCreated.$id,
365
+ backupType: 'database',
366
+ databaseId: databaseId,
367
+ sizeBytes: backupSize,
368
+ collections: data.collections.length,
369
+ documents: processedDocuments,
370
+ format: format,
371
+ status: 'completed',
372
+ restorationStatus: 'not_restored'
373
+ });
374
+ }
375
+ catch (metadataError) {
376
+ // Don't fail backup if metadata recording fails
377
+ MessageFormatter.warning(`Failed to record backup metadata: ${metadataError instanceof Error ? metadataError.message : String(metadataError)}`);
378
+ }
307
379
  if (backupOperation) {
308
380
  await logOperation(database, databaseId, {
309
381
  operationType: "backup",
@@ -313,7 +385,7 @@ export const backupDatabase = async (config, database, databaseId, storage) => {
313
385
  total: totalItems,
314
386
  error: "",
315
387
  status: "completed",
316
- }, backupOperation.$id, config.useMigrations);
388
+ }, backupOperation.$id);
317
389
  }
318
390
  const duration = Date.now() - startTime;
319
391
  MessageFormatter.operationSummary("Backup", {
@@ -325,6 +397,18 @@ export const backupDatabase = async (config, database, databaseId, storage) => {
325
397
  bucket: bucket.$id,
326
398
  }, duration);
327
399
  MessageFormatter.success(Messages.BACKUP_COMPLETED(databaseId, backupSize));
400
+ // Return backup result for tracking
401
+ const dbData = JSON.parse(data.database);
402
+ return {
403
+ backupFileId: fileCreated.$id,
404
+ backupFileName: fileName,
405
+ backupSizeBytes: backupSize,
406
+ databaseId: databaseId,
407
+ databaseName: dbData.name || databaseId,
408
+ collectionCount: data.collections.length,
409
+ documentCount: processedDocuments,
410
+ format: format
411
+ };
328
412
  }
329
413
  catch (error) {
330
414
  progress?.fail(error instanceof Error ? error.message : String(error));
@@ -338,7 +422,7 @@ export const backupDatabase = async (config, database, databaseId, storage) => {
338
422
  total: totalDocuments,
339
423
  error: String(error),
340
424
  status: "error",
341
- }, backupOperation.$id, config.useMigrations);
425
+ }, backupOperation.$id);
342
426
  }
343
427
  throw error;
344
428
  }
@@ -5,6 +5,7 @@ import { splitIntoBatches } from "../shared/migrationHelpers.js";
5
5
  import { getAppwriteClient, tryAwaitWithRetry, } from "../utils/helperFunctions.js";
6
6
  import { isUndefined } from "es-toolkit/compat";
7
7
  import { isEmpty } from "es-toolkit/compat";
8
+ import { MessageFormatter } from "../shared/messageFormatter.js";
8
9
  export class UsersController {
9
10
  config;
10
11
  users;
@@ -25,7 +26,7 @@ export class UsersController {
25
26
  }
26
27
  async wipeUsers() {
27
28
  const allUsers = await this.getAllUsers();
28
- console.log("Deleting all users...");
29
+ MessageFormatter.progress("Deleting all users...", { prefix: "Users" });
29
30
  const createBatches = (finalData, batchSize) => {
30
31
  const finalBatches = [];
31
32
  for (let i = 0; i < finalData.length; i += batchSize) {
@@ -37,16 +38,16 @@ export class UsersController {
37
38
  if (allUsers.length > 0) {
38
39
  const batchedUserPromises = createBatches(allUsers, 25); // Batch size of 25
39
40
  for (const batch of batchedUserPromises) {
40
- console.log(`Deleting ${batch.length} users...`);
41
+ MessageFormatter.progress(`Deleting ${batch.length} users...`, { prefix: "Users" });
41
42
  await Promise.all(batch.map((user) => tryAwaitWithRetry(async () => await this.users.delete(user.$id))));
42
43
  usersDeleted += batch.length;
43
44
  if (usersDeleted % 100 === 0) {
44
- console.log(`Deleted ${usersDeleted} users...`);
45
+ MessageFormatter.progress(`Deleted ${usersDeleted} users...`, { prefix: "Users" });
45
46
  }
46
47
  }
47
48
  }
48
49
  else {
49
- console.log("No users to delete");
50
+ MessageFormatter.info("No users to delete", { prefix: "Users" });
50
51
  }
51
52
  }
52
53
  async getAllUsers() {
@@ -141,7 +142,7 @@ export class UsersController {
141
142
  userToReturn = await this.users.updateEmail(userToReturn.$id, item.email);
142
143
  }
143
144
  else {
144
- console.log("Email update skipped: Email already exists.");
145
+ MessageFormatter.warning("Email update skipped: Email already exists.", { prefix: "Users" });
145
146
  }
146
147
  }
147
148
  if (item.password) {
@@ -164,7 +165,7 @@ export class UsersController {
164
165
  }
165
166
  }
166
167
  if (item.$createdAt && item.$updatedAt) {
167
- console.log("$createdAt and $updatedAt are not yet supported, sorry about that!");
168
+ MessageFormatter.warning("$createdAt and $updatedAt are not yet supported, sorry about that!", { prefix: "Users" });
168
169
  }
169
170
  if (item.labels && item.labels.length) {
170
171
  userToReturn = await this.users.updateLabels(userToReturn.$id, item.labels);
@@ -234,11 +235,11 @@ export class UsersController {
234
235
  const remoteUsers = new Users(client);
235
236
  let fromUsers = await localUsers.list([Query.limit(50)]);
236
237
  if (fromUsers.users.length === 0) {
237
- console.log(`No users found`);
238
+ MessageFormatter.info("No users found", { prefix: "Users" });
238
239
  return;
239
240
  }
240
241
  else if (fromUsers.users.length < 50) {
241
- console.log(`Transferring ${fromUsers.users.length} users to remote`);
242
+ MessageFormatter.progress(`Transferring ${fromUsers.users.length} users to remote`, { prefix: "Users" });
242
243
  const batchedPromises = fromUsers.users.map((user) => {
243
244
  return tryAwaitWithRetry(async () => {
244
245
  const toCreateObject = {
@@ -0,0 +1,78 @@
1
+ import { type CollectionCreate } from "appwrite-utils";
2
+ /**
3
+ * Recursively searches for TypeScript configuration files (appwriteConfig.ts)
4
+ * @param dir The directory to start the search from
5
+ * @param depth Current search depth for recursion limiting
6
+ * @returns Path to the config file or null if not found
7
+ */
8
+ export declare const findAppwriteConfigTS: (dir: string, depth?: number) => string | null;
9
+ /**
10
+ * Recursively searches for configuration files starting from the given directory.
11
+ * Priority: 1) YAML configs in .appwrite directories, 2) appwriteConfig.ts files in subdirectories
12
+ * @param dir The directory to start the search from
13
+ * @returns The directory path where the config was found, suitable for passing to loadConfig()
14
+ */
15
+ export declare const findAppwriteConfig: (dir: string) => string | null;
16
+ /**
17
+ * Recursively searches for the functions directory
18
+ * @param dir The directory to start searching from
19
+ * @param depth Current search depth for recursion limiting
20
+ * @returns Path to the functions directory or null if not found
21
+ */
22
+ export declare const findFunctionsDir: (dir: string, depth?: number) => string | null;
23
+ /**
24
+ * Loads a YAML collection file and converts it to CollectionCreate format
25
+ * @param filePath Path to the YAML collection file
26
+ * @returns CollectionCreate object or null if loading fails
27
+ */
28
+ export declare const loadYamlCollection: (filePath: string) => CollectionCreate | null;
29
+ /**
30
+ * Loads a YAML table file and converts it to table format
31
+ * @param filePath Path to the YAML table file
32
+ * @returns Table object or null if loading fails
33
+ */
34
+ export declare const loadYamlTable: (filePath: string) => any | null;
35
+ /**
36
+ * Result of discovering collections from a directory
37
+ */
38
+ export interface CollectionDiscoveryResult {
39
+ collections: CollectionCreate[];
40
+ loadedNames: Set<string>;
41
+ conflicts: Array<{
42
+ name: string;
43
+ source1: string;
44
+ source2: string;
45
+ }>;
46
+ }
47
+ /**
48
+ * Discovers and loads collections from a collections/ directory
49
+ * @param collectionsDir Path to the collections directory
50
+ * @returns Discovery result with loaded collections and metadata
51
+ */
52
+ export declare const discoverCollections: (collectionsDir: string) => Promise<CollectionDiscoveryResult>;
53
+ /**
54
+ * Result of discovering tables from a directory
55
+ */
56
+ export interface TableDiscoveryResult {
57
+ tables: any[];
58
+ loadedNames: Set<string>;
59
+ conflicts: Array<{
60
+ name: string;
61
+ source1: string;
62
+ source2: string;
63
+ }>;
64
+ }
65
+ /**
66
+ * Discovers and loads tables from a tables/ directory
67
+ * @param tablesDir Path to the tables directory
68
+ * @param existingNames Set of already-loaded collection names to check for conflicts
69
+ * @returns Discovery result with loaded tables and metadata
70
+ */
71
+ export declare const discoverTables: (tablesDir: string, existingNames?: Set<string>) => Promise<TableDiscoveryResult>;
72
+ /**
73
+ * Discovers and loads collections/tables from legacy single directory structure
74
+ * @param configFileDir Directory containing the config file
75
+ * @param dirName Directory name to search for ('collections' or 'tables')
76
+ * @returns Array of discovered collections/tables
77
+ */
78
+ export declare const discoverLegacyDirectory: (configFileDir: string, dirName: "collections" | "tables") => Promise<CollectionCreate[]>;