appwrite-utils-cli 1.5.2 → 1.6.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 (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 +479 -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 +209 -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 +274 -1563
  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
@@ -4,6 +4,7 @@ import type {
4
4
  Attribute,
5
5
  RelationshipAttribute,
6
6
  } from "appwrite-utils";
7
+ import { getVersionAwareDirectory, resolveDirectoryForApiMode, getDualDirectoryPaths } from "appwrite-utils";
7
8
  import { z } from "zod";
8
9
  import fs from "fs";
9
10
  import path from "path";
@@ -12,54 +13,60 @@ import { getDatabaseFromConfig } from "../migrations/afterImportActions.js";
12
13
  import { ulid } from "ulidx";
13
14
  import { JsonSchemaGenerator } from "./jsonSchemaGenerator.js";
14
15
  import { collectionToYaml, getCollectionYamlFilename } from "../utils/yamlConverter.js";
16
+ import {
17
+ extractTwoWayRelationships,
18
+ resolveCollectionName,
19
+ type RelationshipDetail
20
+ } from "./relationshipExtractor.js";
21
+ import { resolveSchemaDir } from "../utils/pathResolvers.js";
22
+ import { MessageFormatter } from "./messageFormatter.js";
15
23
 
16
- interface RelationshipDetail {
17
- parentCollection: string;
18
- childCollection: string;
19
- parentKey: string;
20
- childKey: string;
21
- isArray: boolean;
22
- isChild: boolean;
23
- }
24
+ export class SchemaGenerator {
25
+ private relationshipMap = new Map<string, RelationshipDetail[]>();
26
+ private config: AppwriteConfig;
27
+ private appwriteFolderPath: string;
28
+
29
+ constructor(config: AppwriteConfig, appwriteFolderPath: string) {
30
+ this.config = config;
31
+ this.appwriteFolderPath = appwriteFolderPath;
32
+ this.extractRelationships();
33
+ }
24
34
 
25
- export class SchemaGenerator {
26
- private relationshipMap = new Map<string, RelationshipDetail[]>();
27
- private config: AppwriteConfig;
28
- private appwriteFolderPath: string;
29
-
30
- constructor(config: AppwriteConfig, appwriteFolderPath: string) {
31
- this.config = config;
32
- this.appwriteFolderPath = appwriteFolderPath;
33
- this.extractRelationships();
34
- }
35
-
36
- private resolveCollectionName = (idOrName: string): string => {
37
- const col = this.config.collections?.find(
38
- (c) => c.$id === (idOrName as any) || c.name === idOrName
39
- );
40
- return col?.name ?? idOrName;
41
- };
35
+ private resolveCollectionName = (idOrName: string): string => {
36
+ return resolveCollectionName(this.config, idOrName);
37
+ };
42
38
 
43
39
  public updateYamlCollections(): void {
44
40
  const collections = this.config.collections;
45
41
  delete this.config.collections;
46
42
 
47
- const collectionsDir = path.join(this.appwriteFolderPath, "collections");
43
+ // Determine output directory based on API mode/version detection
44
+ const outputDir = this.getVersionAwareCollectionsDirectory();
45
+ const collectionsDir = path.join(this.appwriteFolderPath, outputDir);
48
46
  if (!fs.existsSync(collectionsDir)) {
49
47
  fs.mkdirSync(collectionsDir, { recursive: true });
50
48
  }
51
49
 
52
50
  collections?.forEach((collection) => {
53
- // Determine schema path based on config
51
+ // Determine schema path based on config and output directory
54
52
  const schemaDir = this.config.schemaConfig?.yamlSchemaDirectory || ".yaml_schemas";
55
- const schemaPath = `../${schemaDir}/collection.schema.json`;
56
-
57
- const yamlContent = collectionToYaml(collection, schemaPath);
53
+ const isTablesMode = outputDir === "tables";
54
+ const schemaPath = isTablesMode
55
+ ? `../${schemaDir}/table.schema.json`
56
+ : `../${schemaDir}/collection.schema.json`;
57
+
58
+ const yamlConfig = {
59
+ useTableTerminology: isTablesMode,
60
+ entityType: isTablesMode ? 'table' as const : 'collection' as const,
61
+ schemaPath
62
+ };
63
+
64
+ const yamlContent = collectionToYaml(collection, yamlConfig);
58
65
  const filename = getCollectionYamlFilename(collection);
59
66
  const filePath = path.join(collectionsDir, filename);
60
-
67
+
61
68
  fs.writeFileSync(filePath, yamlContent, { encoding: "utf-8" });
62
- console.log(`Collection YAML written to ${filePath}`);
69
+ MessageFormatter.success(`${outputDir === "tables" ? "Table" : "Collection"} YAML written to ${filePath}`, { prefix: "Schema" });
63
70
  });
64
71
  }
65
72
 
@@ -120,9 +127,11 @@ export class SchemaGenerator {
120
127
  `;
121
128
  fs.writeFileSync(configPath, configContent, { encoding: "utf-8" });
122
129
 
130
+ // Determine output directory based on API mode/version detection
131
+ const outputDir = this.getVersionAwareCollectionsDirectory();
123
132
  const collectionsFolderPath = path.join(
124
133
  this.appwriteFolderPath,
125
- "collections"
134
+ outputDir
126
135
  );
127
136
  if (!fs.existsSync(collectionsFolderPath)) {
128
137
  fs.mkdirSync(collectionsFolderPath, { recursive: true });
@@ -200,10 +209,35 @@ export class SchemaGenerator {
200
209
  fs.writeFileSync(collectionFilePath, collectionContent, {
201
210
  encoding: "utf-8",
202
211
  });
203
- console.log(`Collection schema written to ${collectionFilePath}`);
212
+ MessageFormatter.success(`${outputDir === "tables" ? "Table" : "Collection"} schema written to ${collectionFilePath}`, { prefix: "Schema" });
204
213
  });
205
214
  }
206
215
 
216
+ /**
217
+ * Determines the appropriate directory for collections/tables based on API mode
218
+ * Uses version detection or config hints to choose between 'collections' and 'tables'
219
+ */
220
+ private getVersionAwareCollectionsDirectory(): string {
221
+ return getVersionAwareDirectory(this.config, this.appwriteFolderPath);
222
+ }
223
+
224
+ /**
225
+ * Get directory for a specific API mode (legacy or tablesdb)
226
+ * @param apiMode - The API mode to get directory for
227
+ * @returns The directory name for the specified API mode
228
+ */
229
+ public getDirectoryForApiMode(apiMode: 'legacy' | 'tablesdb'): string {
230
+ return resolveDirectoryForApiMode(this.config, apiMode, this.appwriteFolderPath);
231
+ }
232
+
233
+ /**
234
+ * Get both directory paths for dual API support
235
+ * @returns Object with both collectionsDirectory and tablesDirectory paths
236
+ */
237
+ public getDualDirectoryConfiguration(): { collectionsDirectory: string; tablesDirectory: string } {
238
+ return getDualDirectoryPaths(this.config);
239
+ }
240
+
207
241
  public async updateConfig(config: AppwriteConfig, isYamlConfig: boolean = false): Promise<void> {
208
242
  if (isYamlConfig) {
209
243
  // User has YAML config - find the config file and update it + generate individual collection files
@@ -213,7 +247,7 @@ export class SchemaGenerator {
213
247
  if (yamlConfigPath) {
214
248
  await this.updateYamlConfig(config, yamlConfigPath);
215
249
  } else {
216
- console.warn("⚠️ YAML config expected but not found, falling back to TypeScript");
250
+ MessageFormatter.warning("YAML config expected but not found, falling back to TypeScript", { prefix: "Schema" });
217
251
  this.updateTypeScriptConfig(config);
218
252
  }
219
253
  } else {
@@ -231,10 +265,10 @@ export class SchemaGenerator {
231
265
 
232
266
  // Generate individual collection YAML files
233
267
  this.updateYamlCollections();
234
-
235
- console.log("Updated YAML configuration and collection files");
268
+
269
+ MessageFormatter.success("Updated YAML configuration and collection files", { prefix: "Schema" });
236
270
  } catch (error) {
237
- console.error("Error updating YAML config:", error instanceof Error ? error.message : error);
271
+ MessageFormatter.error("Error updating YAML config", error as Error, { prefix: "Schema" });
238
272
  throw error;
239
273
  }
240
274
  }
@@ -287,87 +321,11 @@ const appwriteConfig: AppwriteConfig = {
287
321
  export default appwriteConfig;
288
322
  `;
289
323
  fs.writeFileSync(configPath, configContent, { encoding: "utf-8" });
290
- console.log("Updated TypeScript configuration file");
324
+ MessageFormatter.success("Updated TypeScript configuration file", { prefix: "Schema" });
291
325
  }
292
326
 
293
327
  private extractRelationships(): void {
294
- if (!this.config.collections) {
295
- return;
296
- }
297
- this.config.collections.forEach((collection) => {
298
- if (!collection.attributes) {
299
- return;
300
- }
301
- collection.attributes.forEach((attr) => {
302
- if (attr.type === "relationship" && attr.twoWay && attr.twoWayKey) {
303
- const relationshipAttr = attr as RelationshipAttribute;
304
- let isArrayParent = false;
305
- let isArrayChild = false;
306
- switch (relationshipAttr.relationType) {
307
- case "oneToMany":
308
- isArrayParent = true;
309
- isArrayChild = false;
310
- break;
311
- case "manyToMany":
312
- isArrayParent = true;
313
- isArrayChild = true;
314
- break;
315
- case "oneToOne":
316
- isArrayParent = false;
317
- isArrayChild = false;
318
- break;
319
- case "manyToOne":
320
- isArrayParent = false;
321
- isArrayChild = true;
322
- break;
323
- default:
324
- break;
325
- }
326
- this.addRelationship(
327
- collection.name,
328
- this.resolveCollectionName(relationshipAttr.relatedCollection),
329
- attr.key,
330
- relationshipAttr.twoWayKey!,
331
- isArrayParent,
332
- isArrayChild
333
- );
334
- console.log(
335
- `Extracted relationship: ${attr.key}\n\t${collection.name} -> ${relationshipAttr.relatedCollection}, databaseId: ${collection.databaseId}`
336
- );
337
- }
338
- });
339
- });
340
- }
341
-
342
- private addRelationship(
343
- parentCollection: string,
344
- childCollection: string,
345
- parentKey: string,
346
- childKey: string,
347
- isArrayParent: boolean,
348
- isArrayChild: boolean
349
- ): void {
350
- const relationshipsChild = this.relationshipMap.get(childCollection) || [];
351
- const relationshipsParent =
352
- this.relationshipMap.get(parentCollection) || [];
353
- relationshipsParent.push({
354
- parentCollection,
355
- childCollection,
356
- parentKey,
357
- childKey,
358
- isArray: isArrayParent,
359
- isChild: false,
360
- });
361
- relationshipsChild.push({
362
- parentCollection,
363
- childCollection,
364
- parentKey,
365
- childKey,
366
- isArray: isArrayChild,
367
- isChild: true,
368
- });
369
- this.relationshipMap.set(childCollection, relationshipsChild);
370
- this.relationshipMap.set(parentCollection, relationshipsParent);
328
+ this.relationshipMap = extractTwoWayRelationships(this.config);
371
329
  }
372
330
 
373
331
  public generateSchemas(options: {
@@ -382,7 +340,9 @@ export default appwriteConfig;
382
340
 
383
341
  // Create schemas directory using config setting
384
342
  const outputDir = this.config.schemaConfig?.outputDirectory || "schemas";
385
- const schemasPath = path.join(this.appwriteFolderPath, outputDir);
343
+ const schemasPath = outputDir === "schemas"
344
+ ? resolveSchemaDir(this.appwriteFolderPath)
345
+ : path.join(this.appwriteFolderPath, outputDir);
386
346
  if (!fs.existsSync(schemasPath)) {
387
347
  fs.mkdirSync(schemasPath, { recursive: true });
388
348
  }
@@ -390,19 +350,19 @@ export default appwriteConfig;
390
350
  // Generate Zod schemas (TypeScript)
391
351
  if (format === "zod" || format === "both") {
392
352
  this.config.collections.forEach((collection) => {
393
- const schemaString = this.createSchemaStringV4(
394
- collection.name,
395
- collection.attributes || []
396
- );
353
+ const schemaString = this.createSchemaStringV4(
354
+ collection.name,
355
+ collection.attributes || []
356
+ );
397
357
  const camelCaseName = toCamelCase(collection.name);
398
358
  const schemaPath = path.join(schemasPath, `${camelCaseName}.ts`);
399
359
  fs.writeFileSync(schemaPath, schemaString, { encoding: "utf-8" });
400
360
  if (verbose) {
401
- console.log(`Zod schema written to ${schemaPath}`);
361
+ MessageFormatter.success(`Zod schema written to ${schemaPath}`, { prefix: "Schema" });
402
362
  }
403
363
  });
404
364
  }
405
-
365
+
406
366
  // Generate JSON schemas (all at once)
407
367
  if (format === "json" || format === "both") {
408
368
  const jsonSchemaGenerator = new JsonSchemaGenerator(this.config, this.appwriteFolderPath);
@@ -412,110 +372,110 @@ export default appwriteConfig;
412
372
  verbose: verbose
413
373
  });
414
374
  }
415
-
375
+
416
376
  if (verbose) {
417
- console.log(`✓ Schema generation completed (format: ${format})`);
377
+ MessageFormatter.success(`Schema generation completed (format: ${format})`, { prefix: "Schema" });
418
378
  }
419
- }
420
-
421
- // Zod v4 recursive getter-based schemas
422
- createSchemaStringV4 = (name: string, attributes: Attribute[]): string => {
423
- const pascalName = toPascalCase(name);
424
- let imports = `import { z } from "zod";\n`;
425
-
426
- // Use the relationshipMap to find related collections
427
- const relationshipDetails = this.relationshipMap.get(name) || [];
428
- let relatedCollections = relationshipDetails
429
- .filter((detail, index, self) => {
430
- const uniqueKey = `${detail.parentCollection}-${detail.childCollection}-${detail.parentKey}-${detail.childKey}`;
431
- return (
432
- index ===
433
- self.findIndex(
379
+ }
380
+
381
+ // Zod v4 recursive getter-based schemas
382
+ createSchemaStringV4 = (name: string, attributes: Attribute[]): string => {
383
+ const pascalName = toPascalCase(name);
384
+ let imports = `import { z } from "zod";\n`;
385
+
386
+ // Use the relationshipMap to find related collections
387
+ const relationshipDetails = this.relationshipMap.get(name) || [];
388
+ let relatedCollections = relationshipDetails
389
+ .filter((detail, index, self) => {
390
+ const uniqueKey = `${detail.parentCollection}-${detail.childCollection}-${detail.parentKey}-${detail.childKey}`;
391
+ return (
392
+ index ===
393
+ self.findIndex(
434
394
  (obj) =>
435
395
  `${obj.parentCollection}-${obj.childCollection}-${obj.parentKey}-${obj.childKey}` ===
436
396
  uniqueKey
437
397
  )
438
398
  );
439
399
  })
440
- .map((detail) => {
441
- const relatedCollectionName = detail.isChild
442
- ? detail.parentCollection
443
- : detail.childCollection;
444
- const key = detail.isChild ? detail.childKey : detail.parentKey;
445
- const isArray = detail.isArray ? "array" : "";
446
- return [relatedCollectionName, key, isArray];
447
- });
448
-
449
- // Include one-way relationship attributes directly (no twoWayKey)
450
- const oneWayRels: Array<[string, string, string]> = [];
451
- for (const attr of attributes) {
452
- if (attr.type === "relationship" && attr.relatedCollection) {
453
- const relatedName = this.resolveCollectionName(attr.relatedCollection);
454
- const isArray =
455
- attr.relationType === "oneToMany" || attr.relationType === "manyToMany"
456
- ? "array"
457
- : "";
458
- oneWayRels.push([relatedName, attr.key, isArray]);
459
- }
460
- }
461
-
462
- // Merge and dedupe (by relatedName+key)
463
- relatedCollections = [...relatedCollections, ...oneWayRels].filter(
464
- (item, idx, self) =>
465
- idx === self.findIndex((o) => `${o[0]}::${o[1]}` === `${item[0]}::${item[1]}`)
466
- );
467
-
468
- const hasRelationships = relatedCollections.length > 0;
469
-
470
- // Build imports for related collections
471
- if (hasRelationships) {
472
- const importLines = relatedCollections.map((rel) => {
473
- const relatedPascalName = toPascalCase(rel[0]);
474
- const relatedCamelName = toCamelCase(rel[0]);
475
- return `import { ${relatedPascalName}Schema } from "./${relatedCamelName}";`;
476
- });
477
- const unique = Array.from(new Set(importLines));
478
- imports += unique.join("\n") + (unique.length ? "\n" : "");
479
- }
480
-
481
- let schemaString = `${imports}\n`;
482
-
483
- // Single object schema with recursive getters (Zod v4)
484
- schemaString += `export const ${pascalName}Schema = z.object({\n`;
485
- schemaString += ` $id: z.string(),\n`;
486
- schemaString += ` $createdAt: z.string(),\n`;
487
- schemaString += ` $updatedAt: z.string(),\n`;
488
- schemaString += ` $permissions: z.array(z.string()),\n`;
489
- for (const attribute of attributes) {
490
- if (attribute.type === "relationship") continue;
491
- schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
492
- }
493
-
494
- // Add recursive getters for relationships (respect required flag)
495
- relatedCollections.forEach((rel) => {
496
- const relatedPascalName = toPascalCase(rel[0]);
497
- const isArray = rel[2] === "array";
498
- const key = String(rel[1]);
499
- const attrMeta = attributes.find(a => a.key === key && a.type === "relationship");
500
- const isRequired = !!attrMeta?.required;
501
- let getterBody = "";
502
- if (isArray) {
503
- getterBody = isRequired
504
- ? `${relatedPascalName}Schema.array()`
505
- : `${relatedPascalName}Schema.array().nullish()`;
506
- } else {
507
- getterBody = isRequired
508
- ? `${relatedPascalName}Schema`
509
- : `${relatedPascalName}Schema.nullish()`;
510
- }
511
- schemaString += ` get ${key}(){\n return ${getterBody}\n },\n`;
512
- });
513
-
514
- schemaString += `});\n\n`;
515
- schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
516
-
517
- return schemaString;
518
- };
400
+ .map((detail) => {
401
+ const relatedCollectionName = detail.isChild
402
+ ? detail.parentCollection
403
+ : detail.childCollection;
404
+ const key = detail.isChild ? detail.childKey : detail.parentKey;
405
+ const isArray = detail.isArray ? "array" : "";
406
+ return [relatedCollectionName, key, isArray];
407
+ });
408
+
409
+ // Include one-way relationship attributes directly (no twoWayKey)
410
+ const oneWayRels: Array<[string, string, string]> = [];
411
+ for (const attr of attributes) {
412
+ if (attr.type === "relationship" && attr.relatedCollection) {
413
+ const relatedName = this.resolveCollectionName(attr.relatedCollection);
414
+ const isArray =
415
+ attr.relationType === "oneToMany" || attr.relationType === "manyToMany"
416
+ ? "array"
417
+ : "";
418
+ oneWayRels.push([relatedName, attr.key, isArray]);
419
+ }
420
+ }
421
+
422
+ // Merge and dedupe (by relatedName+key)
423
+ relatedCollections = [...relatedCollections, ...oneWayRels].filter(
424
+ (item, idx, self) =>
425
+ idx === self.findIndex((o) => `${o[0]}::${o[1]}` === `${item[0]}::${item[1]}`)
426
+ );
427
+
428
+ const hasRelationships = relatedCollections.length > 0;
429
+
430
+ // Build imports for related collections
431
+ if (hasRelationships) {
432
+ const importLines = relatedCollections.map((rel) => {
433
+ const relatedPascalName = toPascalCase(rel[0]);
434
+ const relatedCamelName = toCamelCase(rel[0]);
435
+ return `import { ${relatedPascalName}Schema } from "./${relatedCamelName}";`;
436
+ });
437
+ const unique = Array.from(new Set(importLines));
438
+ imports += unique.join("\n") + (unique.length ? "\n" : "");
439
+ }
440
+
441
+ let schemaString = `${imports}\n`;
442
+
443
+ // Single object schema with recursive getters (Zod v4)
444
+ schemaString += `export const ${pascalName}Schema = z.object({\n`;
445
+ schemaString += ` $id: z.string(),\n`;
446
+ schemaString += ` $createdAt: z.string(),\n`;
447
+ schemaString += ` $updatedAt: z.string(),\n`;
448
+ schemaString += ` $permissions: z.array(z.string()),\n`;
449
+ for (const attribute of attributes) {
450
+ if (attribute.type === "relationship") continue;
451
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
452
+ }
453
+
454
+ // Add recursive getters for relationships (respect required flag)
455
+ relatedCollections.forEach((rel) => {
456
+ const relatedPascalName = toPascalCase(rel[0]);
457
+ const isArray = rel[2] === "array";
458
+ const key = String(rel[1]);
459
+ const attrMeta = attributes.find(a => a.key === key && a.type === "relationship");
460
+ const isRequired = !!attrMeta?.required;
461
+ let getterBody = "";
462
+ if (isArray) {
463
+ getterBody = isRequired
464
+ ? `${relatedPascalName}Schema.array()`
465
+ : `${relatedPascalName}Schema.array().nullish()`;
466
+ } else {
467
+ getterBody = isRequired
468
+ ? `${relatedPascalName}Schema`
469
+ : `${relatedPascalName}Schema.nullish()`;
470
+ }
471
+ schemaString += ` get ${key}(){\n return ${getterBody}\n },\n`;
472
+ });
473
+
474
+ schemaString += `});\n\n`;
475
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
476
+
477
+ return schemaString;
478
+ };
519
479
 
520
480
  typeToZod = (attribute: Attribute) => {
521
481
  let baseSchemaCode = "";
@@ -0,0 +1,88 @@
1
+ import JSZip from "jszip";
2
+ import type { BackupCreate } from "./schemas.js";
3
+
4
+ export interface BackupCompressionOptions {
5
+ includeFiles?: boolean;
6
+ compressionLevel?: number; // 0-9, default 6
7
+ }
8
+
9
+ /**
10
+ * Creates a compressed ZIP backup from backup data
11
+ *
12
+ * Structure:
13
+ * - metadata.json (backup metadata)
14
+ * - database.json (database config)
15
+ * - collections/*.json (one file per collection)
16
+ * - documents/*.json (one file per collection's documents)
17
+ * - files/ (optional, if includeFiles is true)
18
+ */
19
+ export async function createBackupZip(
20
+ backupData: BackupCreate,
21
+ options: BackupCompressionOptions = {}
22
+ ): Promise<Buffer> {
23
+ const zip = new JSZip();
24
+ const compressionLevel = options.compressionLevel ?? 6;
25
+
26
+ // Add metadata.json
27
+ const metadata = {
28
+ version: "1.0",
29
+ createdAt: new Date().toISOString(),
30
+ databaseId: JSON.parse(backupData.database).$id,
31
+ format: "zip",
32
+ includesFiles: options.includeFiles ?? false
33
+ };
34
+ zip.file("metadata.json", JSON.stringify(metadata, null, 2));
35
+
36
+ // Add database.json
37
+ zip.file("database.json", backupData.database);
38
+
39
+ // Add collections/*.json
40
+ const collectionsFolder = zip.folder("collections");
41
+ if (collectionsFolder) {
42
+ backupData.collections.forEach((collectionStr, index) => {
43
+ const collection = JSON.parse(collectionStr);
44
+ collectionsFolder.file(`${collection.$id || `collection_${index}`}.json`, collectionStr);
45
+ });
46
+ }
47
+
48
+ // Add documents/*.json
49
+ const documentsFolder = zip.folder("documents");
50
+ if (documentsFolder) {
51
+ backupData.documents.forEach((docBatch) => {
52
+ const collectionId = docBatch.collectionId;
53
+ documentsFolder.file(`${collectionId}.json`, docBatch.data);
54
+ });
55
+ }
56
+
57
+ // TODO: Add files support in future task (C3.5)
58
+ if (options.includeFiles) {
59
+ // Placeholder for file backup support
60
+ const filesFolder = zip.folder("files");
61
+ if (filesFolder) {
62
+ filesFolder.file("file-manifest.json", JSON.stringify({
63
+ note: "File backup not yet implemented"
64
+ }, null, 2));
65
+ }
66
+ }
67
+
68
+ // Generate ZIP buffer
69
+ const buffer = await zip.generateAsync({
70
+ type: "nodebuffer",
71
+ compression: "DEFLATE",
72
+ compressionOptions: { level: compressionLevel }
73
+ });
74
+
75
+ return buffer;
76
+ }
77
+
78
+ /**
79
+ * Estimates compression ratio for backup data
80
+ */
81
+ export function estimateCompressedSize(
82
+ uncompressedSize: number,
83
+ format: string = "json"
84
+ ): number {
85
+ // JSON typically compresses to 20-30% of original size with gzip
86
+ const compressionRatio = format === "json" ? 0.25 : 0.5;
87
+ return Math.ceil(uncompressedSize * compressionRatio);
88
+ }