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
@@ -7,6 +7,7 @@ import { CollectionSchema, attributeSchema, AppwriteConfigSchema, permissionsSch
|
|
7
7
|
import { getDatabaseFromConfig } from "./afterImportActions.js";
|
8
8
|
import { listBuckets } from "../storage/methods.js";
|
9
9
|
import { listFunctions, listFunctionDeployments } from "../functions/methods.js";
|
10
|
+
import { MessageFormatter } from "../shared/messageFormatter.js";
|
10
11
|
export class AppwriteToX {
|
11
12
|
config;
|
12
13
|
storage;
|
@@ -62,16 +63,51 @@ export class AppwriteToX {
|
|
62
63
|
async appwriteSync(config, databases) {
|
63
64
|
const db = getDatabaseFromConfig(config);
|
64
65
|
if (!databases) {
|
65
|
-
|
66
|
+
try {
|
67
|
+
MessageFormatter.info("Fetching remote databases...", { prefix: "Migration" });
|
68
|
+
databases = await fetchAllDatabases(db);
|
69
|
+
MessageFormatter.info(`Found ${databases.length} remote databases`, { prefix: "Migration" });
|
70
|
+
}
|
71
|
+
catch (error) {
|
72
|
+
MessageFormatter.error("Failed to fetch remote databases", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
73
|
+
throw new Error(`Database fetch failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
74
|
+
}
|
66
75
|
}
|
67
76
|
let updatedConfig = { ...config };
|
77
|
+
// Initialize databases array if it doesn't exist
|
78
|
+
if (!updatedConfig.databases) {
|
79
|
+
updatedConfig.databases = [];
|
80
|
+
}
|
81
|
+
// Sync remote databases to local config - add missing ones
|
82
|
+
MessageFormatter.info(`Syncing ${databases.length} remote databases with local config...`, { prefix: "Migration" });
|
83
|
+
let addedCount = 0;
|
84
|
+
let updatedCount = 0;
|
85
|
+
for (const remoteDb of databases) {
|
86
|
+
// Check if this database already exists in the config
|
87
|
+
const existingDbIndex = updatedConfig.databases.findIndex((localDb) => localDb.$id === remoteDb.$id);
|
88
|
+
if (existingDbIndex === -1) {
|
89
|
+
// Database doesn't exist locally, add it
|
90
|
+
MessageFormatter.success(`Adding new database to config: ${remoteDb.name} (${remoteDb.$id})`, { prefix: "Migration" });
|
91
|
+
updatedConfig.databases.push({
|
92
|
+
$id: remoteDb.$id,
|
93
|
+
name: remoteDb.name,
|
94
|
+
});
|
95
|
+
addedCount++;
|
96
|
+
}
|
97
|
+
else {
|
98
|
+
// Database exists, update name if different
|
99
|
+
if (updatedConfig.databases[existingDbIndex].name !== remoteDb.name) {
|
100
|
+
MessageFormatter.info(`Updating database name: ${updatedConfig.databases[existingDbIndex].name} -> ${remoteDb.name}`, { prefix: "Migration" });
|
101
|
+
updatedConfig.databases[existingDbIndex].name = remoteDb.name;
|
102
|
+
updatedCount++;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
MessageFormatter.success(`Database sync summary: ${addedCount} added, ${updatedCount} updated, ${updatedConfig.databases.length} total`, { prefix: "Migration" });
|
68
107
|
// Fetch all buckets
|
69
108
|
const allBuckets = await listBuckets(this.storage);
|
70
109
|
// Loop through each database
|
71
110
|
for (const database of databases) {
|
72
|
-
if (!this.config.useMigrations && database.name.toLowerCase() === "migrations") {
|
73
|
-
continue;
|
74
|
-
}
|
75
111
|
// Match bucket to database
|
76
112
|
const matchedBucket = allBuckets.buckets.find((bucket) => bucket.$id.toLowerCase().includes(database.$id.toLowerCase()));
|
77
113
|
if (matchedBucket) {
|
@@ -95,7 +131,7 @@ export class AppwriteToX {
|
|
95
131
|
updatedConfig.collections = [];
|
96
132
|
}
|
97
133
|
for (const collection of collections) {
|
98
|
-
|
134
|
+
MessageFormatter.processing(`Processing collection: ${collection.name}`, { prefix: "Migration" });
|
99
135
|
const existingCollectionIndex = updatedConfig.collections.findIndex((c) => c.name === collection.name);
|
100
136
|
// Parse the collection permissions and attributes
|
101
137
|
const collPermissions = this.parsePermissionsArray(collection.$permissions);
|
@@ -109,15 +145,15 @@ export class AppwriteToX {
|
|
109
145
|
for (const attribute of collAttributes) {
|
110
146
|
if (attribute.type === "relationship" &&
|
111
147
|
attribute.relatedCollection) {
|
112
|
-
|
148
|
+
MessageFormatter.info(`Fetching related collection for ID: ${attribute.relatedCollection}`, { prefix: "Migration" });
|
113
149
|
try {
|
114
150
|
const relatedCollectionPulled = await db.getCollection(database.$id, attribute.relatedCollection);
|
115
|
-
|
151
|
+
MessageFormatter.info(`Fetched Collection Name: ${relatedCollectionPulled.name}`, { prefix: "Migration" });
|
116
152
|
attribute.relatedCollection = relatedCollectionPulled.name;
|
117
|
-
|
153
|
+
MessageFormatter.info(`Updated attribute.relatedCollection to: ${attribute.relatedCollection}`, { prefix: "Migration" });
|
118
154
|
}
|
119
155
|
catch (error) {
|
120
|
-
|
156
|
+
MessageFormatter.error("Error fetching related collection", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
121
157
|
}
|
122
158
|
}
|
123
159
|
}
|
@@ -152,7 +188,7 @@ export class AppwriteToX {
|
|
152
188
|
updatedConfig.collections.push(collToPush);
|
153
189
|
}
|
154
190
|
}
|
155
|
-
|
191
|
+
MessageFormatter.success(`Processed ${collections.length} collections in ${database.name}`, { prefix: "Migration" });
|
156
192
|
}
|
157
193
|
// Add unmatched buckets as global buckets
|
158
194
|
const globalBuckets = allBuckets.buckets.filter((bucket) => !updatedConfig.databases.some((db) => db.bucket && db.bucket.$id === bucket.$id));
|
@@ -184,26 +220,36 @@ export class AppwriteToX {
|
|
184
220
|
dirPath: `functions/${func.name}`,
|
185
221
|
specification: func.specification,
|
186
222
|
}));
|
187
|
-
// Make sure to update the config with all changes
|
223
|
+
// Make sure to update the config with all changes including databases
|
188
224
|
updatedConfig.functions = this.updatedConfig.functions;
|
189
225
|
this.updatedConfig = updatedConfig;
|
226
|
+
MessageFormatter.success(`Sync completed - ${updatedConfig.databases.length} databases, ${updatedConfig.collections?.length || 0} collections, ${updatedConfig.buckets?.length || 0} buckets, ${updatedConfig.functions?.length || 0} functions`, { prefix: "Migration" });
|
190
227
|
}
|
191
228
|
async toSchemas(databases) {
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
229
|
+
try {
|
230
|
+
MessageFormatter.info("Starting sync-from-Appwrite process...", { prefix: "Migration" });
|
231
|
+
await this.appwriteSync(this.config, databases);
|
232
|
+
const generator = new SchemaGenerator(this.updatedConfig, this.appwriteFolderPath);
|
233
|
+
// Check if this is a YAML-based project
|
234
|
+
const yamlConfigPath = findYamlConfig(this.appwriteFolderPath);
|
235
|
+
const isYamlProject = !!yamlConfigPath;
|
236
|
+
if (isYamlProject) {
|
237
|
+
MessageFormatter.info("Detected YAML configuration - generating YAML collection definitions", { prefix: "Migration" });
|
238
|
+
generator.updateYamlCollections();
|
239
|
+
await generator.updateConfig(this.updatedConfig, true);
|
240
|
+
}
|
241
|
+
else {
|
242
|
+
MessageFormatter.info("Generating TypeScript collection definitions", { prefix: "Migration" });
|
243
|
+
generator.updateTsSchemas();
|
244
|
+
await generator.updateConfig(this.updatedConfig, false);
|
245
|
+
}
|
246
|
+
MessageFormatter.info("Generating Zod schemas from synced collections...", { prefix: "Migration" });
|
247
|
+
generator.generateSchemas();
|
248
|
+
MessageFormatter.success("Sync-from-Appwrite process completed successfully", { prefix: "Migration" });
|
201
249
|
}
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
await generator.updateConfig(this.updatedConfig, false);
|
250
|
+
catch (error) {
|
251
|
+
MessageFormatter.error("Error during sync-from-Appwrite process", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
252
|
+
throw error;
|
206
253
|
}
|
207
|
-
generator.generateSchemas();
|
208
254
|
}
|
209
255
|
}
|
@@ -8,9 +8,11 @@ import { ID, Users } from "node-appwrite";
|
|
8
8
|
import { logger } from "../shared/logging.js";
|
9
9
|
import { findOrCreateOperation, updateOperation } from "../shared/migrationHelpers.js";
|
10
10
|
import { AuthUserCreateSchema } from "../schemas/authUser.js";
|
11
|
+
import { LegacyAdapter } from "../adapters/LegacyAdapter.js";
|
11
12
|
import { UsersController } from "../users/methods.js";
|
12
13
|
import { finalizeByAttributeMap } from "../utils/helperFunctions.js";
|
13
14
|
import { isEmpty } from "es-toolkit/compat";
|
15
|
+
import { MessageFormatter } from "../shared/messageFormatter.js";
|
14
16
|
// Define a schema for the structure of collection import data using Zod for validation
|
15
17
|
export const CollectionImportDataSchema = z.object({
|
16
18
|
// Optional collection creation schema
|
@@ -149,9 +151,9 @@ export class DataLoader {
|
|
149
151
|
loadData(importDef) {
|
150
152
|
// Simply join appwriteFolderPath with the importDef.filePath
|
151
153
|
const filePath = path.resolve(this.appwriteFolderPath, importDef.filePath);
|
152
|
-
|
154
|
+
MessageFormatter.info(`Loading data from: ${filePath}`, { prefix: "Data" });
|
153
155
|
if (!fs.existsSync(filePath)) {
|
154
|
-
|
156
|
+
MessageFormatter.error(`File not found: ${filePath}`, undefined, { prefix: "Data" });
|
155
157
|
return [];
|
156
158
|
}
|
157
159
|
// Read the file and parse the JSON data
|
@@ -159,7 +161,7 @@ export class DataLoader {
|
|
159
161
|
const parsedData = importDef.basePath
|
160
162
|
? JSON.parse(rawData)[importDef.basePath]
|
161
163
|
: JSON.parse(rawData);
|
162
|
-
|
164
|
+
MessageFormatter.success(`Loaded ${parsedData?.length || 0} items from ${filePath}`, { prefix: "Data" });
|
163
165
|
return parsedData;
|
164
166
|
}
|
165
167
|
// Helper method to check if a new ID already exists in the old-to-new ID map
|
@@ -249,7 +251,8 @@ export class DataLoader {
|
|
249
251
|
collection.$id = collectionExists.$id;
|
250
252
|
this.config.collections[index] = collectionConfig;
|
251
253
|
// Find or create an import operation for the collection
|
252
|
-
const
|
254
|
+
const adapter = new LegacyAdapter(this.database);
|
255
|
+
const collectionImportOperation = await findOrCreateOperation(adapter, dbId, "importData", collection.$id);
|
253
256
|
// Store the operation ID in the map
|
254
257
|
this.collectionImportOperations.set(this.getCollectionKey(collection.name), collectionImportOperation.$id);
|
255
258
|
// Initialize the collection in the import map
|
@@ -300,12 +303,12 @@ export class DataLoader {
|
|
300
303
|
}
|
301
304
|
// Main method to start the data loading process for a given database ID
|
302
305
|
async start(dbId) {
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
+
MessageFormatter.divider();
|
307
|
+
MessageFormatter.info(`Starting data setup for database: ${dbId}`, { prefix: "Data" });
|
308
|
+
MessageFormatter.divider();
|
306
309
|
await this.setupMaps(dbId);
|
307
310
|
const allUsers = await this.getAllUsers();
|
308
|
-
|
311
|
+
MessageFormatter.info(`Fetched ${allUsers.length} users, waiting a few seconds to let the program catch up...`, { prefix: "Data" });
|
309
312
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
310
313
|
// Iterate over the configured databases to find the matching one
|
311
314
|
for (const db of this.config.databases) {
|
@@ -346,17 +349,17 @@ export class DataLoader {
|
|
346
349
|
this.prepareUpdateData(db, collection, updateDef);
|
347
350
|
}
|
348
351
|
}
|
349
|
-
|
352
|
+
MessageFormatter.info("Running update references", { prefix: "Data" });
|
350
353
|
// this.dealWithMergedUsers();
|
351
354
|
this.updateOldReferencesForNew();
|
352
|
-
|
355
|
+
MessageFormatter.success("Done running update references", { prefix: "Data" });
|
353
356
|
}
|
354
357
|
// for (const collection of this.config.collections) {
|
355
358
|
// this.resolveDataItemRelationships(collection);
|
356
359
|
// }
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
+
MessageFormatter.divider();
|
361
|
+
MessageFormatter.success(`Data setup for database: ${dbId} completed`, { prefix: "Data" });
|
362
|
+
MessageFormatter.divider();
|
360
363
|
if (this.shouldWriteFile) {
|
361
364
|
this.writeMapsToJsonFile();
|
362
365
|
}
|
@@ -433,7 +436,7 @@ export class DataLoader {
|
|
433
436
|
const collectionData = this.importMap.get(collectionKey);
|
434
437
|
if (!collectionData || !collectionData.data)
|
435
438
|
continue;
|
436
|
-
|
439
|
+
MessageFormatter.processing(`Updating references for collection: ${collectionConfig.name}`, { prefix: "Data" });
|
437
440
|
let needsUpdate = false;
|
438
441
|
// Iterate over each data item in the current collection
|
439
442
|
for (let i = 0; i < collectionData.data.length; i++) {
|
@@ -567,10 +570,10 @@ export class DataLoader {
|
|
567
570
|
const outputFile = path.join(outputDir, fileName);
|
568
571
|
fs.writeFile(outputFile, JSON.stringify(data, null, 2), "utf8", (err) => {
|
569
572
|
if (err) {
|
570
|
-
|
573
|
+
MessageFormatter.error(`Error writing data to ${fileName}`, err instanceof Error ? err : new Error(String(err)), { prefix: "Data" });
|
571
574
|
return;
|
572
575
|
}
|
573
|
-
|
576
|
+
MessageFormatter.success(`Data successfully written to ${fileName}`, { prefix: "Data" });
|
574
577
|
});
|
575
578
|
};
|
576
579
|
// Convert Maps to arrays of entries for serialization
|
@@ -751,16 +754,19 @@ export class DataLoader {
|
|
751
754
|
this.oldIdToNewIdPerCollectionMap
|
752
755
|
.set(this.getCollectionKey(collection.name), oldIdToNewIdMap)
|
753
756
|
.get(this.getCollectionKey(collection.name));
|
757
|
+
const adapter = new LegacyAdapter(this.database);
|
754
758
|
if (!operationId) {
|
755
|
-
const collectionImportOperation = await findOrCreateOperation(
|
759
|
+
const collectionImportOperation = await findOrCreateOperation(adapter, db.$id, "importData", collection.$id);
|
756
760
|
// Store the operation ID in the map
|
757
761
|
this.collectionImportOperations.set(this.getCollectionKey(collection.name), collectionImportOperation.$id);
|
758
762
|
operationId = collectionImportOperation.$id;
|
759
763
|
}
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
+
if (operationId) {
|
765
|
+
await updateOperation(adapter, db.$id, operationId, {
|
766
|
+
status: "ready",
|
767
|
+
total: rawData.length,
|
768
|
+
});
|
769
|
+
}
|
764
770
|
// Retrieve the current user data and the current collection data from the import map
|
765
771
|
const currentUserData = this.importMap.get(this.getCollectionKey("users"));
|
766
772
|
const currentData = this.importMap.get(this.getCollectionKey(collection.name));
|
@@ -901,16 +907,19 @@ export class DataLoader {
|
|
901
907
|
// Load the raw data based on the import definition
|
902
908
|
const rawData = this.loadData(importDef);
|
903
909
|
let operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
910
|
+
const adapter = new LegacyAdapter(this.database);
|
904
911
|
if (!operationId) {
|
905
|
-
const collectionImportOperation = await findOrCreateOperation(
|
912
|
+
const collectionImportOperation = await findOrCreateOperation(adapter, db.$id, "importData", collection.$id);
|
906
913
|
// Store the operation ID in the map
|
907
914
|
this.collectionImportOperations.set(this.getCollectionKey(collection.name), collectionImportOperation.$id);
|
908
915
|
operationId = collectionImportOperation.$id;
|
909
916
|
}
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
917
|
+
if (operationId) {
|
918
|
+
await updateOperation(adapter, db.$id, operationId, {
|
919
|
+
status: "ready",
|
920
|
+
total: rawData.length,
|
921
|
+
});
|
922
|
+
}
|
914
923
|
// Initialize a new map for old ID to new ID mappings
|
915
924
|
const oldIdToNewIdMapNew = new Map();
|
916
925
|
// Retrieve or initialize the collection-specific old ID to new ID map
|
@@ -4,6 +4,7 @@ import { resolveAndUpdateRelationships } from "./relationships.js";
|
|
4
4
|
import { UsersController } from "../users/methods.js";
|
5
5
|
import { logger } from "../shared/logging.js";
|
6
6
|
import { updateOperation } from "../shared/migrationHelpers.js";
|
7
|
+
import { LegacyAdapter } from "../adapters/LegacyAdapter.js";
|
7
8
|
import { BatchSchema, OperationCreateSchema, OperationSchema, } from "../storage/schemas.js";
|
8
9
|
import { DataLoader } from "./dataLoader.js";
|
9
10
|
import { transferDatabaseLocalToLocal, transferStorageLocalToLocal, } from "./transfer.js";
|
@@ -45,9 +46,6 @@ export class ImportController {
|
|
45
46
|
let dataLoader;
|
46
47
|
let databaseRan;
|
47
48
|
for (let db of databasesToProcess) {
|
48
|
-
if (!this.config.useMigrations && db.name.toLowerCase().trim().replace(" ", "") === "migrations") {
|
49
|
-
continue;
|
50
|
-
}
|
51
49
|
MessageFormatter.banner(`Starting import data for database: ${db.name}`, "Database Import");
|
52
50
|
if (!databaseRan) {
|
53
51
|
databaseRan = db;
|
@@ -61,9 +59,9 @@ export class ImportController {
|
|
61
59
|
else if (databaseRan.$id !== db.$id) {
|
62
60
|
await this.updateOthersToFinalData(databaseRan, db);
|
63
61
|
}
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
MessageFormatter.divider();
|
63
|
+
MessageFormatter.success(`Finished import data for database: ${db.name}`, { prefix: "Import" });
|
64
|
+
MessageFormatter.divider();
|
67
65
|
}
|
68
66
|
}
|
69
67
|
async updateOthersToFinalData(updatedDb, targetDb) {
|
@@ -162,10 +160,10 @@ export class ImportController {
|
|
162
160
|
item.context.docId, true);
|
163
161
|
}
|
164
162
|
}
|
165
|
-
|
163
|
+
MessageFormatter.success("Finished importing users batch", { prefix: "Import" });
|
166
164
|
}
|
167
165
|
this.hasImportedUsers = true;
|
168
|
-
|
166
|
+
MessageFormatter.success("Finished importing users", { prefix: "Import" });
|
169
167
|
}
|
170
168
|
}
|
171
169
|
if (!importOperationId) {
|
@@ -173,23 +171,22 @@ export class ImportController {
|
|
173
171
|
continue;
|
174
172
|
}
|
175
173
|
let importOperation = null;
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
}
|
174
|
+
importOperation = await this.database.getDocument("migrations", "currentOperations", importOperationId);
|
175
|
+
const adapter = new LegacyAdapter(this.database);
|
176
|
+
await updateOperation(adapter, db.$id, importOperation.$id, {
|
177
|
+
status: "in_progress",
|
178
|
+
});
|
182
179
|
const collectionData = dataLoader.importMap.get(dataLoader.getCollectionKey(collection.name));
|
183
|
-
|
180
|
+
MessageFormatter.processing(`Processing collection: ${collection.name}...`, { prefix: "Import" });
|
184
181
|
if (!collectionData) {
|
185
|
-
|
182
|
+
MessageFormatter.warning(`No collection data for ${collection.name}`, { prefix: "Import" });
|
186
183
|
continue;
|
187
184
|
}
|
188
185
|
const dataSplit = createBatches(collectionData.data);
|
189
186
|
let processedItems = 0;
|
190
187
|
for (let i = 0; i < dataSplit.length; i++) {
|
191
188
|
const batches = dataSplit[i];
|
192
|
-
|
189
|
+
MessageFormatter.progress(`Processing batch ${i + 1} of ${dataSplit.length}`, { prefix: "Import" });
|
193
190
|
const batchPromises = batches.map((item, index) => {
|
194
191
|
try {
|
195
192
|
const id = item.finalData.docId ||
|
@@ -208,41 +205,43 @@ export class ImportController {
|
|
208
205
|
return tryAwaitWithRetry(async () => await this.database.createDocument(db.$id, collection.$id, id, item.finalData));
|
209
206
|
}
|
210
207
|
catch (error) {
|
211
|
-
|
208
|
+
MessageFormatter.error("Error creating document", error instanceof Error ? error : new Error(String(error)), { prefix: "Import" });
|
212
209
|
return Promise.resolve();
|
213
210
|
}
|
214
211
|
});
|
215
212
|
// Wait for all promises in the current batch to resolve
|
216
213
|
await Promise.all(batchPromises);
|
217
|
-
|
218
|
-
if (
|
219
|
-
|
214
|
+
MessageFormatter.success(`Completed batch ${i + 1} of ${dataSplit.length}`, { prefix: "Import" });
|
215
|
+
if (importOperation) {
|
216
|
+
const adapter = new LegacyAdapter(this.database);
|
217
|
+
await updateOperation(adapter, db.$id, importOperation.$id, {
|
220
218
|
progress: processedItems,
|
221
|
-
}
|
219
|
+
});
|
222
220
|
}
|
223
221
|
}
|
224
222
|
// After all batches are processed, update the operation status to completed
|
225
|
-
if (
|
226
|
-
|
223
|
+
if (importOperation) {
|
224
|
+
const adapter = new LegacyAdapter(this.database);
|
225
|
+
await updateOperation(adapter, db.$id, importOperation.$id, {
|
227
226
|
status: "completed",
|
228
|
-
}
|
227
|
+
});
|
229
228
|
}
|
230
229
|
}
|
231
230
|
}
|
232
231
|
}
|
233
232
|
async executePostImportActions(dbId, dataLoader, specificCollections) {
|
234
|
-
|
233
|
+
MessageFormatter.info("Executing post-import actions...", { prefix: "Import" });
|
235
234
|
const collectionsToProcess = specificCollections && specificCollections.length > 0
|
236
235
|
? specificCollections
|
237
236
|
: this.config.collections
|
238
237
|
? this.config.collections.map((c) => c.name)
|
239
238
|
: Array.from(dataLoader.importMap.keys());
|
240
|
-
|
239
|
+
MessageFormatter.info(`Collections to process: ${collectionsToProcess.join(", ")}`, { prefix: "Import" });
|
241
240
|
// Iterate over each collection in the importMap
|
242
241
|
for (const [collectionKey, collectionData,] of dataLoader.importMap.entries()) {
|
243
242
|
const allCollectionKeys = collectionsToProcess.map((c) => dataLoader.getCollectionKey(c));
|
244
243
|
if (allCollectionKeys.includes(collectionKey)) {
|
245
|
-
|
244
|
+
MessageFormatter.processing(`Processing post-import actions for collection: ${collectionKey}`, { prefix: "Import" });
|
246
245
|
// Iterate over each item in the collectionData.data
|
247
246
|
for (const item of collectionData.data) {
|
248
247
|
// Assuming each item has attributeMappings that contain actions to be executed
|
@@ -256,13 +255,13 @@ export class ImportController {
|
|
256
255
|
await this.importDataActions.executeAfterImportActions(item.finalData, item.importDef.attributeMappings, context);
|
257
256
|
}
|
258
257
|
catch (error) {
|
259
|
-
|
258
|
+
MessageFormatter.error(`Failed to execute post-import actions for item in collection ${collectionKey}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Import" });
|
260
259
|
}
|
261
260
|
}
|
262
261
|
}
|
263
262
|
}
|
264
263
|
else {
|
265
|
-
|
264
|
+
MessageFormatter.info(`Skipping collection: ${collectionKey} because it's not valid for post-import actions`, { prefix: "Import" });
|
266
265
|
}
|
267
266
|
}
|
268
267
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Databases, Query } from "node-appwrite";
|
2
2
|
import { fetchAllCollections } from "../collections/methods.js";
|
3
3
|
import { logger } from "../shared/logging.js";
|
4
|
+
import { MessageFormatter } from "../shared/messageFormatter.js";
|
4
5
|
/**
|
5
6
|
* Finds collections that have defined relationship attributes.
|
6
7
|
*/
|
@@ -30,15 +31,15 @@ export async function resolveAndUpdateRelationships(dbId, database, config) {
|
|
30
31
|
const collectionsWithRelationships = findCollectionsWithRelationships(config);
|
31
32
|
// Process each collection sequentially
|
32
33
|
for (const collection of collections) {
|
33
|
-
|
34
|
+
MessageFormatter.processing(`Processing collection: ${collection.name} (${collection.$id})`, { prefix: "Migration" });
|
34
35
|
const relAttributeMap = collectionsWithRelationships.get(collection.name); // Get the relationship attributes for the collections
|
35
36
|
if (!relAttributeMap) {
|
36
|
-
|
37
|
+
MessageFormatter.info(`No mapping found for collection: ${collection.name}, skipping...`, { prefix: "Migration" });
|
37
38
|
continue;
|
38
39
|
}
|
39
40
|
await processCollection(dbId, database, collection, relAttributeMap);
|
40
41
|
}
|
41
|
-
|
42
|
+
MessageFormatter.success(`Completed relationship resolution and update for database ID: ${dbId}`, { prefix: "Migration" });
|
42
43
|
}
|
43
44
|
async function processCollection(dbId, database, collection, relAttributeMap) {
|
44
45
|
let after; // For pagination
|
@@ -49,7 +50,7 @@ async function processCollection(dbId, database, collection, relAttributeMap) {
|
|
49
50
|
...(after ? [Query.cursorAfter(after)] : []),
|
50
51
|
]);
|
51
52
|
const documents = response.documents;
|
52
|
-
|
53
|
+
MessageFormatter.info(`Fetched ${documents.length} documents from collection: ${collection.name}`, { prefix: "Migration" });
|
53
54
|
if (documents.length > 0) {
|
54
55
|
const updates = await prepareDocumentUpdates(database, dbId, collection.name, documents, relAttributeMap);
|
55
56
|
// Execute updates for the current batch
|
@@ -69,14 +70,14 @@ async function findDocumentsByOriginalId(database, dbId, targetCollection, targe
|
|
69
70
|
Query.equal("$id", relatedCollectionId),
|
70
71
|
]);
|
71
72
|
if (collection.total === 0) {
|
72
|
-
|
73
|
+
MessageFormatter.warning(`Collection ${relatedCollectionId} doesn't exist, skipping...`, { prefix: "Migration" });
|
73
74
|
return undefined;
|
74
75
|
}
|
75
76
|
const targetAttr = collection.collections[0].attributes.find(
|
76
77
|
// @ts-ignore
|
77
78
|
(attr) => attr.key === targetKey);
|
78
79
|
if (!targetAttr) {
|
79
|
-
|
80
|
+
MessageFormatter.warning(`Attribute ${targetKey} not found in collection ${relatedCollectionId}, skipping...`, { prefix: "Migration" });
|
80
81
|
return undefined;
|
81
82
|
}
|
82
83
|
let queries = [];
|
@@ -102,12 +103,12 @@ async function findDocumentsByOriginalId(database, dbId, targetCollection, targe
|
|
102
103
|
}
|
103
104
|
}
|
104
105
|
async function prepareDocumentUpdates(database, dbId, collectionName, documents, relationships) {
|
105
|
-
|
106
|
+
MessageFormatter.processing(`Preparing updates for collection: ${collectionName}`, { prefix: "Migration" });
|
106
107
|
const updates = [];
|
107
108
|
const thisCollection = (await database.listCollections(dbId, [Query.equal("name", collectionName)])).collections[0];
|
108
109
|
const thisCollectionId = thisCollection?.$id;
|
109
110
|
if (!thisCollectionId) {
|
110
|
-
|
111
|
+
MessageFormatter.warning(`No collection found with name: ${collectionName}`, { prefix: "Migration" });
|
111
112
|
return [];
|
112
113
|
}
|
113
114
|
for (const doc of documents) {
|
@@ -115,14 +116,14 @@ async function prepareDocumentUpdates(database, dbId, collectionName, documents,
|
|
115
116
|
for (const rel of relationships) {
|
116
117
|
// Skip if not dealing with the parent side of a two-way relationship
|
117
118
|
if (rel.twoWay && rel.side !== "parent") {
|
118
|
-
|
119
|
+
MessageFormatter.info("Skipping non-parent side of two-way relationship...", { prefix: "Migration" });
|
119
120
|
continue;
|
120
121
|
}
|
121
122
|
const isSingleReference = rel.relationType === "oneToOne" || rel.relationType === "manyToOne";
|
122
123
|
const originalIdField = rel.importMapping?.originalIdField;
|
123
124
|
const targetField = rel.importMapping?.targetField || originalIdField; // Use originalIdField if targetField is not specified
|
124
125
|
if (!originalIdField) {
|
125
|
-
|
126
|
+
MessageFormatter.warning("Missing originalIdField in importMapping, skipping...", { prefix: "Migration" });
|
126
127
|
continue;
|
127
128
|
}
|
128
129
|
const originalId = doc[originalIdField];
|
@@ -133,7 +134,7 @@ async function prepareDocumentUpdates(database, dbId, collectionName, documents,
|
|
133
134
|
Query.equal("name", rel.relatedCollection),
|
134
135
|
])).collections[0];
|
135
136
|
if (!relatedCollection) {
|
136
|
-
|
137
|
+
MessageFormatter.warning(`Related collection ${rel.relatedCollection} not found, skipping...`, { prefix: "Migration" });
|
137
138
|
continue;
|
138
139
|
}
|
139
140
|
const foundDocuments = await findDocumentsByOriginalId(database, dbId, relatedCollection, targetField, String(originalId));
|
@@ -155,7 +156,7 @@ async function prepareDocumentUpdates(database, dbId, collectionName, documents,
|
|
155
156
|
updatePayload[relationshipKey] = isSingleReference
|
156
157
|
? newRefs[0] || existingRefIds[0]
|
157
158
|
: allRefs;
|
158
|
-
|
159
|
+
MessageFormatter.info(`Updating ${relationshipKey} with ${allRefs.length} refs`, { prefix: "Migration" });
|
159
160
|
}
|
160
161
|
}
|
161
162
|
if (Object.keys(updatePayload).length > 0) {
|