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