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.
- 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 +181 -1172
- 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 +278 -1596
- 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
@@ -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
|
-
|
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
|
-
|
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
|
34
|
-
const
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
223
|
+
MessageFormatter.success("Updated YAML configuration and collection files", { prefix: "Schema" });
|
187
224
|
}
|
188
225
|
catch (error) {
|
189
|
-
|
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
|
-
|
274
|
+
MessageFormatter.success("Updated TypeScript configuration file", { prefix: "Schema" });
|
238
275
|
}
|
239
276
|
extractRelationships() {
|
240
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
18
|
-
|
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>;
|
package/dist/storage/methods.js
CHANGED
@@ -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
|
-
|
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
|
-
|
179
|
+
// Try to get existing bucket
|
180
|
+
const bucket = await storage.getBucket(BACKUP_BUCKET_ID);
|
181
|
+
return bucket;
|
171
182
|
}
|
172
|
-
catch (
|
173
|
-
|
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
|
-
}
|
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
|
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
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
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
|
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
|
425
|
+
}, backupOperation.$id);
|
342
426
|
}
|
343
427
|
throw error;
|
344
428
|
}
|
package/dist/users/methods.js
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
45
|
+
MessageFormatter.progress(`Deleted ${usersDeleted} users...`, { prefix: "Users" });
|
45
46
|
}
|
46
47
|
}
|
47
48
|
}
|
48
49
|
else {
|
49
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
238
|
+
MessageFormatter.info("No users found", { prefix: "Users" });
|
238
239
|
return;
|
239
240
|
}
|
240
241
|
else if (fromUsers.users.length < 50) {
|
241
|
-
|
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[]>;
|