appwrite-utils-cli 1.5.1 → 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.
- package/CHANGELOG.md +199 -0
- package/README.md +251 -29
- package/dist/adapters/AdapterFactory.d.ts +10 -3
- package/dist/adapters/AdapterFactory.js +213 -17
- package/dist/adapters/TablesDBAdapter.js +60 -17
- package/dist/backups/operations/bucketBackup.d.ts +19 -0
- package/dist/backups/operations/bucketBackup.js +197 -0
- package/dist/backups/operations/collectionBackup.d.ts +30 -0
- package/dist/backups/operations/collectionBackup.js +201 -0
- package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
- package/dist/backups/operations/comprehensiveBackup.js +238 -0
- package/dist/backups/schemas/bucketManifest.d.ts +93 -0
- package/dist/backups/schemas/bucketManifest.js +33 -0
- package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
- package/dist/backups/schemas/comprehensiveManifest.js +32 -0
- package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
- package/dist/backups/tracking/centralizedTracking.js +274 -0
- package/dist/cli/commands/configCommands.d.ts +8 -0
- package/dist/cli/commands/configCommands.js +160 -0
- package/dist/cli/commands/databaseCommands.d.ts +13 -0
- package/dist/cli/commands/databaseCommands.js +478 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +289 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +134 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +384 -0
- package/dist/collections/attributes.d.ts +5 -4
- package/dist/collections/attributes.js +539 -246
- package/dist/collections/indexes.js +39 -37
- package/dist/collections/methods.d.ts +2 -16
- package/dist/collections/methods.js +90 -538
- package/dist/collections/transferOperations.d.ts +7 -0
- package/dist/collections/transferOperations.js +331 -0
- package/dist/collections/wipeOperations.d.ts +16 -0
- package/dist/collections/wipeOperations.js +328 -0
- package/dist/config/configMigration.d.ts +87 -0
- package/dist/config/configMigration.js +390 -0
- package/dist/config/configValidation.d.ts +66 -0
- package/dist/config/configValidation.js +358 -0
- package/dist/config/yamlConfig.d.ts +455 -1
- package/dist/config/yamlConfig.js +145 -52
- package/dist/databases/methods.js +3 -2
- package/dist/databases/setup.d.ts +1 -2
- package/dist/databases/setup.js +9 -87
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +269 -0
- package/dist/functions/deployments.js +11 -10
- package/dist/functions/methods.d.ts +1 -1
- package/dist/functions/methods.js +5 -4
- package/dist/init.js +9 -9
- package/dist/interactiveCLI.d.ts +8 -17
- package/dist/interactiveCLI.js +186 -1171
- package/dist/main.js +364 -21
- package/dist/migrations/afterImportActions.js +22 -30
- package/dist/migrations/appwriteToX.js +71 -25
- package/dist/migrations/dataLoader.js +35 -26
- package/dist/migrations/importController.js +29 -30
- package/dist/migrations/relationships.js +13 -12
- package/dist/migrations/services/ImportOrchestrator.js +16 -19
- package/dist/migrations/transfer.js +46 -46
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
- package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
- package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
- package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
- package/dist/migrations/yaml/generateImportSchemas.js +736 -7
- package/dist/schemas/authUser.d.ts +1 -1
- package/dist/setupController.js +3 -2
- package/dist/shared/backupMetadataSchema.d.ts +94 -0
- package/dist/shared/backupMetadataSchema.js +38 -0
- package/dist/shared/backupTracking.d.ts +18 -0
- package/dist/shared/backupTracking.js +176 -0
- package/dist/shared/confirmationDialogs.js +15 -15
- package/dist/shared/errorUtils.d.ts +54 -0
- package/dist/shared/errorUtils.js +95 -0
- package/dist/shared/functionManager.js +20 -19
- package/dist/shared/indexManager.js +12 -11
- package/dist/shared/jsonSchemaGenerator.js +10 -26
- package/dist/shared/logging.d.ts +51 -0
- package/dist/shared/logging.js +70 -0
- package/dist/shared/messageFormatter.d.ts +2 -0
- package/dist/shared/messageFormatter.js +10 -0
- package/dist/shared/migrationHelpers.d.ts +6 -16
- package/dist/shared/migrationHelpers.js +24 -21
- package/dist/shared/operationLogger.d.ts +8 -1
- package/dist/shared/operationLogger.js +11 -24
- package/dist/shared/operationQueue.d.ts +28 -1
- package/dist/shared/operationQueue.js +268 -66
- package/dist/shared/operationsTable.d.ts +26 -0
- package/dist/shared/operationsTable.js +286 -0
- package/dist/shared/operationsTableSchema.d.ts +48 -0
- package/dist/shared/operationsTableSchema.js +35 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/schemaGenerator.d.ts +19 -1
- package/dist/shared/schemaGenerator.js +56 -75
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +16 -2
- package/dist/storage/methods.js +98 -14
- package/dist/users/methods.js +9 -8
- package/dist/utils/configDiscovery.d.ts +78 -0
- package/dist/utils/configDiscovery.js +430 -0
- package/dist/utils/directoryUtils.d.ts +22 -0
- package/dist/utils/directoryUtils.js +59 -0
- package/dist/utils/getClientFromConfig.d.ts +17 -8
- package/dist/utils/getClientFromConfig.js +162 -17
- package/dist/utils/helperFunctions.d.ts +16 -2
- package/dist/utils/helperFunctions.js +19 -5
- package/dist/utils/loadConfigs.d.ts +34 -9
- package/dist/utils/loadConfigs.js +236 -316
- package/dist/utils/pathResolvers.d.ts +53 -0
- package/dist/utils/pathResolvers.js +72 -0
- package/dist/utils/projectConfig.d.ts +119 -0
- package/dist/utils/projectConfig.js +171 -0
- package/dist/utils/retryFailedPromises.js +4 -2
- package/dist/utils/sessionAuth.d.ts +48 -0
- package/dist/utils/sessionAuth.js +164 -0
- package/dist/utils/sessionPreservationExample.d.ts +1666 -0
- package/dist/utils/sessionPreservationExample.js +101 -0
- package/dist/utils/setupFiles.js +301 -41
- package/dist/utils/typeGuards.d.ts +35 -0
- package/dist/utils/typeGuards.js +57 -0
- package/dist/utils/versionDetection.js +145 -9
- package/dist/utils/yamlConverter.d.ts +53 -3
- package/dist/utils/yamlConverter.js +232 -13
- package/dist/utils/yamlLoader.d.ts +70 -0
- package/dist/utils/yamlLoader.js +263 -0
- package/dist/utilsController.d.ts +36 -3
- package/dist/utilsController.js +186 -56
- package/package.json +12 -2
- package/src/adapters/AdapterFactory.ts +263 -35
- package/src/adapters/TablesDBAdapter.ts +225 -36
- package/src/backups/operations/bucketBackup.ts +277 -0
- package/src/backups/operations/collectionBackup.ts +310 -0
- package/src/backups/operations/comprehensiveBackup.ts +342 -0
- package/src/backups/schemas/bucketManifest.ts +78 -0
- package/src/backups/schemas/comprehensiveManifest.ts +76 -0
- package/src/backups/tracking/centralizedTracking.ts +352 -0
- package/src/cli/commands/configCommands.ts +194 -0
- package/src/cli/commands/databaseCommands.ts +635 -0
- package/src/cli/commands/functionCommands.ts +379 -0
- package/src/cli/commands/schemaCommands.ts +163 -0
- package/src/cli/commands/transferCommands.ts +457 -0
- package/src/collections/attributes.ts +900 -621
- package/src/collections/attributes.ts.backup +1555 -0
- package/src/collections/indexes.ts +116 -114
- package/src/collections/methods.ts +295 -968
- package/src/collections/transferOperations.ts +516 -0
- package/src/collections/wipeOperations.ts +501 -0
- package/src/config/README.md +274 -0
- package/src/config/configMigration.ts +575 -0
- package/src/config/configValidation.ts +445 -0
- package/src/config/yamlConfig.ts +168 -55
- package/src/databases/methods.ts +3 -2
- package/src/databases/setup.ts +11 -138
- package/src/examples/yamlTerminologyExample.ts +341 -0
- package/src/functions/deployments.ts +14 -12
- package/src/functions/methods.ts +11 -11
- package/src/functions/templates/hono-typescript/README.md +286 -0
- package/src/functions/templates/hono-typescript/package.json +26 -0
- package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/src/functions/templates/hono-typescript/src/app.ts +180 -0
- package/src/functions/templates/hono-typescript/src/context.ts +103 -0
- package/src/functions/templates/hono-typescript/src/index.ts +54 -0
- package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/src/functions/templates/typescript-node/package.json +2 -1
- package/src/functions/templates/typescript-node/src/context.ts +103 -0
- package/src/functions/templates/typescript-node/src/index.ts +18 -12
- package/src/functions/templates/uv/pyproject.toml +1 -0
- package/src/functions/templates/uv/src/context.py +125 -0
- package/src/functions/templates/uv/src/index.py +35 -5
- package/src/init.ts +9 -11
- package/src/interactiveCLI.ts +276 -1591
- package/src/main.ts +418 -24
- package/src/migrations/afterImportActions.ts +71 -44
- package/src/migrations/appwriteToX.ts +100 -34
- package/src/migrations/dataLoader.ts +48 -34
- package/src/migrations/importController.ts +44 -39
- package/src/migrations/relationships.ts +28 -18
- package/src/migrations/services/ImportOrchestrator.ts +24 -27
- package/src/migrations/transfer.ts +159 -121
- package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
- package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
- package/src/migrations/yaml/generateImportSchemas.ts +751 -12
- package/src/setupController.ts +3 -2
- package/src/shared/backupMetadataSchema.ts +93 -0
- package/src/shared/backupTracking.ts +211 -0
- package/src/shared/confirmationDialogs.ts +19 -19
- package/src/shared/errorUtils.ts +110 -0
- package/src/shared/functionManager.ts +21 -20
- package/src/shared/indexManager.ts +12 -11
- package/src/shared/jsonSchemaGenerator.ts +38 -52
- package/src/shared/logging.ts +75 -0
- package/src/shared/messageFormatter.ts +14 -1
- package/src/shared/migrationHelpers.ts +45 -38
- package/src/shared/operationLogger.ts +11 -36
- package/src/shared/operationQueue.ts +322 -93
- package/src/shared/operationsTable.ts +338 -0
- package/src/shared/operationsTableSchema.ts +60 -0
- package/src/shared/relationshipExtractor.ts +214 -0
- package/src/shared/schemaGenerator.ts +179 -219
- package/src/storage/backupCompression.ts +88 -0
- package/src/storage/methods.ts +131 -34
- package/src/users/methods.ts +11 -9
- package/src/utils/configDiscovery.ts +502 -0
- package/src/utils/directoryUtils.ts +61 -0
- package/src/utils/getClientFromConfig.ts +205 -22
- package/src/utils/helperFunctions.ts +23 -5
- package/src/utils/loadConfigs.ts +313 -345
- package/src/utils/pathResolvers.ts +81 -0
- package/src/utils/projectConfig.ts +299 -0
- package/src/utils/retryFailedPromises.ts +4 -2
- package/src/utils/sessionAuth.ts +230 -0
- package/src/utils/setupFiles.ts +322 -54
- package/src/utils/typeGuards.ts +65 -0
- package/src/utils/versionDetection.ts +218 -64
- package/src/utils/yamlConverter.ts +296 -13
- package/src/utils/yamlLoader.ts +364 -0
- package/src/utilsController.ts +314 -110
- package/tests/README.md +497 -0
- package/tests/adapters/AdapterFactory.test.ts +277 -0
- package/tests/integration/syncOperations.test.ts +463 -0
- package/tests/jest.config.js +25 -0
- package/tests/migration/configMigration.test.ts +546 -0
- package/tests/setup.ts +62 -0
- package/tests/testUtils.ts +340 -0
- package/tests/utils/loadConfigs.test.ts +350 -0
- package/tests/validation/configValidation.test.ts +412 -0
- 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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
268
|
+
|
269
|
+
MessageFormatter.success("Updated YAML configuration and collection files", { prefix: "Schema" });
|
236
270
|
} catch (error) {
|
237
|
-
|
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
|
-
|
324
|
+
MessageFormatter.success("Updated TypeScript configuration file", { prefix: "Schema" });
|
291
325
|
}
|
292
326
|
|
293
327
|
private extractRelationships(): void {
|
294
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
+
}
|