appwrite-utils-cli 0.0.286 → 0.9.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.
Files changed (109) hide show
  1. package/README.md +122 -96
  2. package/dist/collections/attributes.d.ts +4 -0
  3. package/dist/collections/attributes.js +224 -0
  4. package/dist/collections/indexes.d.ts +4 -0
  5. package/dist/collections/indexes.js +27 -0
  6. package/dist/collections/methods.d.ts +16 -0
  7. package/dist/collections/methods.js +216 -0
  8. package/dist/databases/methods.d.ts +6 -0
  9. package/dist/databases/methods.js +33 -0
  10. package/dist/interactiveCLI.d.ts +19 -0
  11. package/dist/interactiveCLI.js +555 -0
  12. package/dist/main.js +227 -62
  13. package/dist/migrations/afterImportActions.js +37 -40
  14. package/dist/migrations/appwriteToX.d.ts +26 -25
  15. package/dist/migrations/appwriteToX.js +42 -6
  16. package/dist/migrations/attributes.js +21 -20
  17. package/dist/migrations/backup.d.ts +93 -87
  18. package/dist/migrations/collections.d.ts +6 -0
  19. package/dist/migrations/collections.js +149 -20
  20. package/dist/migrations/converters.d.ts +2 -18
  21. package/dist/migrations/converters.js +13 -2
  22. package/dist/migrations/dataLoader.d.ts +276 -161
  23. package/dist/migrations/dataLoader.js +535 -292
  24. package/dist/migrations/databases.js +8 -2
  25. package/dist/migrations/helper.d.ts +3 -0
  26. package/dist/migrations/helper.js +21 -0
  27. package/dist/migrations/importController.d.ts +5 -2
  28. package/dist/migrations/importController.js +125 -88
  29. package/dist/migrations/importDataActions.d.ts +9 -1
  30. package/dist/migrations/importDataActions.js +15 -3
  31. package/dist/migrations/indexes.js +3 -2
  32. package/dist/migrations/logging.js +20 -8
  33. package/dist/migrations/migrationHelper.d.ts +9 -4
  34. package/dist/migrations/migrationHelper.js +6 -5
  35. package/dist/migrations/openapi.d.ts +1 -1
  36. package/dist/migrations/openapi.js +33 -18
  37. package/dist/migrations/queue.js +3 -2
  38. package/dist/migrations/relationships.d.ts +2 -2
  39. package/dist/migrations/schemaStrings.js +53 -41
  40. package/dist/migrations/setupDatabase.d.ts +2 -4
  41. package/dist/migrations/setupDatabase.js +24 -105
  42. package/dist/migrations/storage.d.ts +3 -1
  43. package/dist/migrations/storage.js +110 -16
  44. package/dist/migrations/transfer.d.ts +30 -0
  45. package/dist/migrations/transfer.js +337 -0
  46. package/dist/migrations/users.d.ts +2 -1
  47. package/dist/migrations/users.js +78 -43
  48. package/dist/schemas/authUser.d.ts +2 -2
  49. package/dist/storage/methods.d.ts +15 -0
  50. package/dist/storage/methods.js +207 -0
  51. package/dist/storage/schemas.d.ts +687 -0
  52. package/dist/storage/schemas.js +175 -0
  53. package/dist/utils/getClientFromConfig.d.ts +4 -0
  54. package/dist/utils/getClientFromConfig.js +16 -0
  55. package/dist/utils/helperFunctions.d.ts +11 -1
  56. package/dist/utils/helperFunctions.js +38 -0
  57. package/dist/utils/retryFailedPromises.d.ts +2 -0
  58. package/dist/utils/retryFailedPromises.js +21 -0
  59. package/dist/utils/schemaStrings.d.ts +13 -0
  60. package/dist/utils/schemaStrings.js +403 -0
  61. package/dist/utils/setupFiles.js +110 -61
  62. package/dist/utilsController.d.ts +40 -22
  63. package/dist/utilsController.js +164 -84
  64. package/package.json +13 -15
  65. package/src/collections/attributes.ts +483 -0
  66. package/src/collections/indexes.ts +53 -0
  67. package/src/collections/methods.ts +331 -0
  68. package/src/databases/methods.ts +47 -0
  69. package/src/init.ts +64 -64
  70. package/src/interactiveCLI.ts +767 -0
  71. package/src/main.ts +292 -83
  72. package/src/migrations/afterImportActions.ts +553 -490
  73. package/src/migrations/appwriteToX.ts +237 -174
  74. package/src/migrations/attributes.ts +483 -422
  75. package/src/migrations/backup.ts +205 -205
  76. package/src/migrations/collections.ts +545 -300
  77. package/src/migrations/converters.ts +161 -150
  78. package/src/migrations/dataLoader.ts +1615 -1304
  79. package/src/migrations/databases.ts +44 -25
  80. package/src/migrations/dbHelpers.ts +92 -92
  81. package/src/migrations/helper.ts +40 -0
  82. package/src/migrations/importController.ts +448 -384
  83. package/src/migrations/importDataActions.ts +315 -307
  84. package/src/migrations/indexes.ts +40 -37
  85. package/src/migrations/logging.ts +29 -16
  86. package/src/migrations/migrationHelper.ts +207 -201
  87. package/src/migrations/openapi.ts +83 -70
  88. package/src/migrations/queue.ts +118 -119
  89. package/src/migrations/relationships.ts +324 -324
  90. package/src/migrations/schemaStrings.ts +472 -460
  91. package/src/migrations/setupDatabase.ts +118 -219
  92. package/src/migrations/storage.ts +538 -358
  93. package/src/migrations/transfer.ts +608 -0
  94. package/src/migrations/users.ts +362 -285
  95. package/src/migrations/validationRules.ts +63 -63
  96. package/src/schemas/authUser.ts +23 -23
  97. package/src/setup.ts +8 -8
  98. package/src/storage/methods.ts +371 -0
  99. package/src/storage/schemas.ts +205 -0
  100. package/src/types.ts +9 -9
  101. package/src/utils/getClientFromConfig.ts +17 -0
  102. package/src/utils/helperFunctions.ts +181 -127
  103. package/src/utils/index.ts +2 -2
  104. package/src/utils/loadConfigs.ts +59 -59
  105. package/src/utils/retryFailedPromises.ts +27 -0
  106. package/src/utils/schemaStrings.ts +473 -0
  107. package/src/utils/setupFiles.ts +228 -182
  108. package/src/utilsController.ts +325 -194
  109. package/tsconfig.json +37 -37
@@ -1,384 +1,448 @@
1
- import {
2
- ID,
3
- Query,
4
- type Databases,
5
- type Models,
6
- type Storage,
7
- } from "node-appwrite";
8
- import type {
9
- AppwriteConfig,
10
- ConfigCollection,
11
- ConfigDatabase,
12
- AttributeMappings,
13
- } from "appwrite-utils";
14
- import type { ImportDataActions } from "./importDataActions.js";
15
- import _ from "lodash";
16
- import { areCollectionNamesSame } from "../utils/index.js";
17
- import type { SetupOptions } from "../utilsController.js";
18
- import { resolveAndUpdateRelationships } from "./relationships.js";
19
- import { UsersController } from "./users.js";
20
- import { logger } from "./logging.js";
21
- import { updateOperation } from "./migrationHelper.js";
22
- import {
23
- BatchSchema,
24
- OperationCreateSchema,
25
- OperationSchema,
26
- } from "./backup.js";
27
- import { DataLoader, type CollectionImportData } from "./dataLoader.js";
28
-
29
- export class ImportController {
30
- private config: AppwriteConfig;
31
- private database: Databases;
32
- private storage: Storage;
33
- private appwriteFolderPath: string;
34
- private importDataActions: ImportDataActions;
35
- private setupOptions: SetupOptions;
36
- private documentCache: Map<string, any>;
37
- private batchLimit: number = 25; // Define batch size limit
38
- private postImportActionsQueue: {
39
- context: any;
40
- finalItem: any;
41
- attributeMappings: AttributeMappings;
42
- }[] = [];
43
-
44
- constructor(
45
- config: AppwriteConfig,
46
- database: Databases,
47
- storage: Storage,
48
- appwriteFolderPath: string,
49
- importDataActions: ImportDataActions,
50
- setupOptions: SetupOptions
51
- ) {
52
- this.config = config;
53
- this.database = database;
54
- this.storage = storage;
55
- this.appwriteFolderPath = appwriteFolderPath;
56
- this.importDataActions = importDataActions;
57
- this.setupOptions = setupOptions;
58
- this.documentCache = new Map();
59
- }
60
-
61
- async run() {
62
- const databasesToRun = this.config.databases
63
- .filter(
64
- (db) =>
65
- (areCollectionNamesSame(db.name, this.config!.databases[0].name) &&
66
- this.setupOptions.runProd) ||
67
- (areCollectionNamesSame(db.name, this.config!.databases[1].name) &&
68
- this.setupOptions.runStaging) ||
69
- (areCollectionNamesSame(db.name, this.config!.databases[2].name) &&
70
- this.setupOptions.runDev)
71
- )
72
- .map((db) => db.name);
73
-
74
- for (let db of this.config.databases) {
75
- if (
76
- db.name.toLowerCase().trim().replace(" ", "") === "migrations" ||
77
- !databasesToRun.includes(db.name)
78
- ) {
79
- continue;
80
- }
81
- if (!db.$id) {
82
- const databases = await this.database!.list([
83
- Query.equal("name", db.name),
84
- ]);
85
- if (databases.databases.length > 0) {
86
- db.$id = databases.databases[0].$id;
87
- }
88
- }
89
- console.log(`---------------------------------`);
90
- console.log(`Starting import data for database: ${db.name}`);
91
- console.log(`---------------------------------`);
92
- // await this.importCollections(db);
93
- const dataLoader = new DataLoader(
94
- this.appwriteFolderPath,
95
- this.importDataActions,
96
- this.database,
97
- this.config,
98
- this.setupOptions.shouldWriteFile
99
- );
100
- await dataLoader.start(db.$id);
101
- await this.importCollections(db, dataLoader);
102
- await resolveAndUpdateRelationships(db.$id, this.database, this.config);
103
- await this.executePostImportActions(db.$id, dataLoader);
104
- console.log(`---------------------------------`);
105
- console.log(`Finished import data for database: ${db.name}`);
106
- console.log(`---------------------------------`);
107
- }
108
- }
109
-
110
- async importCollections(db: ConfigDatabase, dataLoader: DataLoader) {
111
- if (!this.config.collections) {
112
- return;
113
- }
114
- for (const collection of this.config.collections) {
115
- let isUsersCollection =
116
- dataLoader.getCollectionKey(this.config.usersCollectionName) ===
117
- dataLoader.getCollectionKey(collection.name);
118
- const importOperationId = dataLoader.collectionImportOperations.get(
119
- dataLoader.getCollectionKey(collection.name)
120
- );
121
- const createBatches = (finalData: CollectionImportData["data"]) => {
122
- let maxBatchLength = 50;
123
- const finalBatches: CollectionImportData["data"][] = [];
124
- for (let i = 0; i < finalData.length; i++) {
125
- if (i % maxBatchLength === 0) {
126
- finalBatches.push([]);
127
- }
128
- finalBatches[finalBatches.length - 1].push(finalData[i]);
129
- }
130
- return finalBatches;
131
- };
132
-
133
- if (isUsersCollection) {
134
- const usersDataMap = dataLoader.importMap.get(
135
- dataLoader.getCollectionKey("users")
136
- );
137
- const usersData = usersDataMap?.data;
138
- const usersController = new UsersController(this.config, this.database);
139
- if (usersData) {
140
- console.log("Found users data");
141
- const userBatchesAll = createBatches(usersData);
142
- console.log(`${userBatchesAll.length} user batches`);
143
- for (let i = 0; i < userBatchesAll.length; i++) {
144
- const userBatches = userBatchesAll[i];
145
- console.log(
146
- `Processing user batch ${i + 1} of ${userBatchesAll.length}`
147
- );
148
- const userBatchPromises = userBatches
149
- .map((userBatch) => {
150
- if (userBatch.finalData && userBatch.finalData.length > 0) {
151
- const userId = userBatch.finalData.userId;
152
- if (dataLoader.userExistsMap.has(userId)) {
153
- // We only are storing the existing user ID's as true, so we need to check for that
154
- if (!(dataLoader.userExistsMap.get(userId) === true)) {
155
- const userId =
156
- userBatch.finalData.userId ||
157
- userBatch.context.userId ||
158
- userBatch.context.docId;
159
- if (!userBatch.finalData.userId) {
160
- userBatch.finalData.userId = userId;
161
- }
162
- return usersController
163
- .createUserAndReturn(userBatch.finalData)
164
- .then(() => console.log("Created user"))
165
- .catch((error) => {
166
- logger.error(
167
- "Error creating user:",
168
- error,
169
- "\nUser data is ",
170
- userBatch.finalData
171
- );
172
- });
173
- } else {
174
- console.log("Skipped existing user: ", userId);
175
- return Promise.resolve();
176
- }
177
- }
178
- }
179
- })
180
- .flat();
181
- // Wait for all promises in the current user batch to resolve
182
- await Promise.allSettled(userBatchPromises);
183
- console.log(
184
- `Completed user batch ${i + 1} of ${userBatchesAll.length}`
185
- );
186
- }
187
- console.log("Finished importing users");
188
- }
189
- }
190
-
191
- if (!importOperationId) {
192
- // Skip further processing if no import operation is found
193
- continue;
194
- }
195
-
196
- const importOperation = await this.database.getDocument(
197
- "migrations",
198
- "currentOperations",
199
- importOperationId
200
- );
201
- await updateOperation(this.database, importOperation.$id, {
202
- status: "in_progress",
203
- });
204
- const collectionData = dataLoader.importMap.get(
205
- dataLoader.getCollectionKey(collection.name)
206
- );
207
- if (!collectionData) {
208
- console.log("No collection data for ", collection.name);
209
- continue;
210
- }
211
-
212
- const dataSplit = createBatches(collectionData.data);
213
- let processedItems = 0;
214
- for (let i = 0; i < dataSplit.length; i++) {
215
- const batches = dataSplit[i];
216
- console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
217
- const batchPromises = batches.map((item) => {
218
- const id =
219
- item.context.docId ||
220
- item.context.userId ||
221
- item.finalData.docId ||
222
- item.finalData.userId;
223
- if (item.finalData.hasOwnProperty("userId")) {
224
- delete item.finalData.userId;
225
- }
226
- if (item.finalData.hasOwnProperty("docId")) {
227
- delete item.finalData.docId;
228
- }
229
- if (!item.finalData) {
230
- return Promise.resolve();
231
- }
232
- return this.database
233
- .createDocument(db.$id, collection.$id, id, item.finalData)
234
- .then(() => {
235
- processedItems++;
236
- console.log("Created item");
237
- })
238
- .catch((error) => {
239
- console.error(
240
- `Error creating item in ${collection.name}:`,
241
- error,
242
- "\nItem data is ",
243
- item.finalData
244
- );
245
- throw error;
246
- // Optionally, log the failed item for retry or review
247
- });
248
- });
249
- // Wait for all promises in the current batch to resolve
250
- await Promise.allSettled(batchPromises);
251
- console.log(`Completed batch ${i + 1} of ${dataSplit.length}`);
252
- await updateOperation(this.database, importOperation.$id, {
253
- progress: processedItems,
254
- });
255
- }
256
- // After all batches are processed, update the operation status to completed
257
- await updateOperation(this.database, importOperation.$id, {
258
- status: "completed",
259
- });
260
- }
261
- }
262
-
263
- async executePostImportActions(dbId: string, dataLoader: DataLoader) {
264
- // Iterate over each collection in the importMap
265
- for (const [
266
- collectionKey,
267
- collectionData,
268
- ] of dataLoader.importMap.entries()) {
269
- console.log(
270
- `Processing post-import actions for collection: ${collectionKey}`
271
- );
272
-
273
- // Iterate over each item in the collectionData.data
274
- for (const item of collectionData.data) {
275
- // Assuming each item has attributeMappings that contain actions to be executed
276
- if (item.importDef && item.importDef.attributeMappings) {
277
- // Use item.context as the context for action execution
278
- const context = item.context; // Directly use item.context as the context for action execution
279
- // Iterate through attributeMappings to execute actions
280
- try {
281
- // Execute post-import actions for the current attributeMapping
282
- // Pass item.finalData as the data to be processed along with the context
283
- await this.importDataActions.executeAfterImportActions(
284
- item.finalData,
285
- item.importDef.attributeMappings,
286
- context
287
- );
288
- } catch (error) {
289
- console.error(
290
- `Failed to execute post-import actions for item in collection ${collectionKey}:`,
291
- error
292
- );
293
- // Handle error (e.g., log, retry, continue with next action)
294
- }
295
- }
296
- }
297
- }
298
- }
299
-
300
- // async executeActionsInParallel(dbId: string, collection: ConfigCollection) {
301
- // const collectionExists = await checkForCollection(
302
- // this.database,
303
- // dbId,
304
- // collection
305
- // );
306
- // if (!collectionExists) {
307
- // logger.error(`No collection found for ${collection.name}`);
308
- // return; // Skip this iteration
309
- // }
310
- // const operations = await getAfterImportOperations(
311
- // this.database,
312
- // collectionExists.$id
313
- // );
314
-
315
- // for (const operation of operations) {
316
- // if (!operation.batches) {
317
- // continue;
318
- // }
319
- // const batches = operation.batches;
320
- // const promises = [];
321
- // for (const batch of batches) {
322
- // const batchId = batch;
323
- // promises.push(
324
- // this.database.getDocument("migrations", "batches", batchId)
325
- // );
326
- // }
327
- // const results = await Promise.allSettled(promises);
328
- // results.forEach((result) => {
329
- // if (result.status === "rejected") {
330
- // logger.error("A process batch promise was rejected:", result.reason);
331
- // }
332
- // });
333
- // const resultsData = results
334
- // .map((result) => (result.status === "fulfilled" ? result.value : null))
335
- // .filter((result: any) => result !== null && !result.processed)
336
- // .map((result) => BatchSchema.parse(result));
337
- // for (const batch of resultsData) {
338
- // const actionOperation = ContextObject.parse(JSON.parse(batch.data));
339
- // const { context, finalItem, attributeMappings } = actionOperation;
340
- // if (finalItem.$id && !context.docId) {
341
- // context.docId =
342
- // finalItem.$id || context.createdDoc.$id || context.$id || undefined;
343
- // logger.info(
344
- // `Setting docId to ${
345
- // finalItem.$id
346
- // } because docId not found in context, batch ${
347
- // batch.$id
348
- // }, context is ${JSON.stringify(context)}`
349
- // );
350
- // }
351
- // try {
352
- // await this.importDataActions.executeAfterImportActions(
353
- // finalItem,
354
- // attributeMappings,
355
- // context
356
- // );
357
- // // Mark batch as processed
358
- // await this.database.deleteDocument(
359
- // "migrations",
360
- // "batches",
361
- // batch.$id
362
- // );
363
- // } catch (error) {
364
- // logger.error(
365
- // `Failed to execute batch ${batch.$id}:`,
366
- // error,
367
- // "Context is :",
368
- // context
369
- // );
370
- // await this.database.deleteDocument(
371
- // "migrations",
372
- // "batches",
373
- // batch.$id
374
- // );
375
- // }
376
- // }
377
-
378
- // // After processing all batches, update the operation status
379
- // await updateOperation(this.database, operation.$id, {
380
- // status: "completed", // Or determine based on batch success/failure
381
- // });
382
- // }
383
- // }
384
- }
1
+ import {
2
+ AppwriteException,
3
+ ID,
4
+ Query,
5
+ type Databases,
6
+ type Models,
7
+ type Storage,
8
+ } from "node-appwrite";
9
+ import type {
10
+ AppwriteConfig,
11
+ ConfigCollection,
12
+ ConfigDatabase,
13
+ AttributeMappings,
14
+ } from "appwrite-utils";
15
+ import type { ImportDataActions } from "./importDataActions.js";
16
+ import _ from "lodash";
17
+ import { areCollectionNamesSame, tryAwaitWithRetry } from "../utils/index.js";
18
+ import type { SetupOptions } from "../utilsController.js";
19
+ import { resolveAndUpdateRelationships } from "./relationships.js";
20
+ import { UsersController } from "./users.js";
21
+ import { logger } from "./logging.js";
22
+ import { updateOperation } from "./migrationHelper.js";
23
+ import {
24
+ BatchSchema,
25
+ OperationCreateSchema,
26
+ OperationSchema,
27
+ } from "./backup.js";
28
+ import { DataLoader, type CollectionImportData } from "./dataLoader.js";
29
+ import {
30
+ transferDatabaseLocalToLocal,
31
+ transferStorageLocalToLocal,
32
+ } from "./transfer.js";
33
+
34
+ export class ImportController {
35
+ private config: AppwriteConfig;
36
+ private database: Databases;
37
+ private storage: Storage;
38
+ private appwriteFolderPath: string;
39
+ private importDataActions: ImportDataActions;
40
+ private setupOptions: SetupOptions;
41
+ private documentCache: Map<string, any>;
42
+ private batchLimit: number = 25; // Define batch size limit
43
+ private hasImportedUsers = false;
44
+ private postImportActionsQueue: {
45
+ context: any;
46
+ finalItem: any;
47
+ attributeMappings: AttributeMappings;
48
+ }[] = [];
49
+ private databasesToRun: Models.Database[];
50
+
51
+ constructor(
52
+ config: AppwriteConfig,
53
+ database: Databases,
54
+ storage: Storage,
55
+ appwriteFolderPath: string,
56
+ importDataActions: ImportDataActions,
57
+ setupOptions: SetupOptions,
58
+ databasesToRun?: Models.Database[]
59
+ ) {
60
+ this.config = config;
61
+ this.database = database;
62
+ this.storage = storage;
63
+ this.appwriteFolderPath = appwriteFolderPath;
64
+ this.importDataActions = importDataActions;
65
+ this.setupOptions = setupOptions;
66
+ this.documentCache = new Map();
67
+ this.databasesToRun = databasesToRun || [];
68
+ }
69
+
70
+ async run() {
71
+ let databasesToProcess: Models.Database[];
72
+
73
+ if (this.databasesToRun.length > 0) {
74
+ // Use the provided databases
75
+ databasesToProcess = this.databasesToRun;
76
+ } else {
77
+ // If no databases are specified, fetch all databases
78
+ const allDatabases = await this.database.list();
79
+ databasesToProcess = allDatabases.databases;
80
+ }
81
+
82
+ let dataLoader: DataLoader | undefined;
83
+ let databaseRan: Models.Database | undefined;
84
+
85
+ for (let db of databasesToProcess) {
86
+ if (db.name.toLowerCase().trim().replace(" ", "") === "migrations") {
87
+ continue;
88
+ }
89
+
90
+ console.log(`---------------------------------`);
91
+ console.log(`Starting import data for database: ${db.name}`);
92
+ console.log(`---------------------------------`);
93
+
94
+ if (!databaseRan) {
95
+ databaseRan = db;
96
+ dataLoader = new DataLoader(
97
+ this.appwriteFolderPath,
98
+ this.importDataActions,
99
+ this.database,
100
+ this.config,
101
+ this.setupOptions.shouldWriteFile
102
+ );
103
+ await dataLoader.start(db.$id);
104
+ await this.importCollections(db, dataLoader);
105
+ await resolveAndUpdateRelationships(db.$id, this.database, this.config);
106
+ await this.executePostImportActions(db.$id, dataLoader);
107
+ } else if (databaseRan.$id !== db.$id) {
108
+ await this.updateOthersToFinalData(databaseRan, db);
109
+ }
110
+
111
+ console.log(`---------------------------------`);
112
+ console.log(`Finished import data for database: ${db.name}`);
113
+ console.log(`---------------------------------`);
114
+ }
115
+ }
116
+
117
+ async updateOthersToFinalData(
118
+ updatedDb: Models.Database,
119
+ targetDb: Models.Database
120
+ ) {
121
+ await transferDatabaseLocalToLocal(
122
+ this.database,
123
+ updatedDb.$id,
124
+ targetDb.$id
125
+ );
126
+
127
+ // Find the corresponding database configs
128
+ const updatedDbConfig = this.config.databases.find(
129
+ (db) => db.$id === updatedDb.$id
130
+ );
131
+ const targetDbConfig = this.config.databases.find(
132
+ (db) => db.$id === targetDb.$id
133
+ );
134
+
135
+ // Transfer database-specific bucket if both databases have a bucket defined
136
+ if (updatedDbConfig?.bucket && targetDbConfig?.bucket) {
137
+ await transferStorageLocalToLocal(
138
+ this.storage,
139
+ updatedDbConfig.bucket.$id,
140
+ targetDbConfig.bucket.$id
141
+ );
142
+ }
143
+ }
144
+
145
+ async importCollections(db: ConfigDatabase, dataLoader: DataLoader) {
146
+ if (!this.config.collections) {
147
+ return;
148
+ }
149
+ for (const collection of this.config.collections) {
150
+ let isUsersCollection =
151
+ dataLoader.getCollectionKey(this.config.usersCollectionName) ===
152
+ dataLoader.getCollectionKey(collection.name);
153
+ const importOperationId = dataLoader.collectionImportOperations.get(
154
+ dataLoader.getCollectionKey(collection.name)
155
+ );
156
+ const createBatches = (finalData: CollectionImportData["data"]) => {
157
+ let maxBatchLength = 100;
158
+ const finalBatches: CollectionImportData["data"][] = [];
159
+ for (let i = 0; i < finalData.length; i++) {
160
+ if (i % maxBatchLength === 0) {
161
+ finalBatches.push([]);
162
+ }
163
+ finalBatches[finalBatches.length - 1].push(finalData[i]);
164
+ }
165
+ return finalBatches;
166
+ };
167
+
168
+ if (isUsersCollection && !this.hasImportedUsers) {
169
+ const usersDataMap = dataLoader.importMap.get(
170
+ dataLoader.getCollectionKey("users")
171
+ );
172
+ const usersData = usersDataMap?.data;
173
+ const usersController = new UsersController(this.config, this.database);
174
+ if (usersData) {
175
+ console.log("Found users data", usersData.length);
176
+ const userDataBatches = createBatches(usersData);
177
+ for (const batch of userDataBatches) {
178
+ console.log("Importing users batch", batch.length);
179
+ const userBatchPromises = batch
180
+ .filter((item) => {
181
+ let itemId: string | undefined;
182
+ if (item.finalData.userId) {
183
+ itemId = item.finalData.userId;
184
+ } else if (item.finalData.docId) {
185
+ itemId = item.finalData.docId;
186
+ }
187
+ if (!itemId) {
188
+ return false;
189
+ }
190
+ return (
191
+ item &&
192
+ item.finalData &&
193
+ !dataLoader.userExistsMap.has(itemId)
194
+ );
195
+ })
196
+ .map((item) => {
197
+ dataLoader.userExistsMap.set(
198
+ item.finalData.userId ||
199
+ item.finalData.docId ||
200
+ item.context.userId ||
201
+ item.context.docId,
202
+ true
203
+ );
204
+ return usersController.createUserAndReturn(item.finalData);
205
+ });
206
+ const promiseResults = await Promise.allSettled(userBatchPromises);
207
+ for (const item of batch) {
208
+ if (item && item.finalData) {
209
+ dataLoader.userExistsMap.set(
210
+ item.finalData.userId ||
211
+ item.finalData.docId ||
212
+ item.context.userId ||
213
+ item.context.docId,
214
+ true
215
+ );
216
+ }
217
+ }
218
+ console.log("Finished importing users batch");
219
+ }
220
+ this.hasImportedUsers = true;
221
+ console.log("Finished importing users");
222
+ }
223
+ }
224
+
225
+ if (!importOperationId) {
226
+ // Skip further processing if no import operation is found
227
+ continue;
228
+ }
229
+
230
+ const importOperation = await this.database.getDocument(
231
+ "migrations",
232
+ "currentOperations",
233
+ importOperationId
234
+ );
235
+ await updateOperation(this.database, importOperation.$id, {
236
+ status: "in_progress",
237
+ });
238
+ const collectionData = dataLoader.importMap.get(
239
+ dataLoader.getCollectionKey(collection.name)
240
+ );
241
+ console.log(`Processing collection: ${collection.name}...`);
242
+ if (!collectionData) {
243
+ console.log("No collection data for ", collection.name);
244
+ continue;
245
+ }
246
+
247
+ const dataSplit = createBatches(collectionData.data);
248
+ let processedItems = 0;
249
+ for (let i = 0; i < dataSplit.length; i++) {
250
+ const batches = dataSplit[i];
251
+ console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
252
+
253
+ // const documentExistsPromises = batches.map(async (item) => {
254
+ // try {
255
+ // const id =
256
+ // item.finalData.docId ||
257
+ // item.finalData.userId ||
258
+ // item.context.docId ||
259
+ // item.context.userId;
260
+
261
+ // if (!item.finalData) {
262
+ // return Promise.resolve(null);
263
+ // }
264
+ // return tryAwaitWithRetry(
265
+ // async () =>
266
+ // await documentExists(
267
+ // this.database,
268
+ // db.$id,
269
+ // collection.$id,
270
+ // item.finalData
271
+ // )
272
+ // );
273
+ // } catch (error) {
274
+ // console.error(error);
275
+ // return Promise.resolve(null);
276
+ // }
277
+ // });
278
+
279
+ // const documentExistsResults = await Promise.all(documentExistsPromises);
280
+
281
+ const batchPromises = batches.map((item, index) => {
282
+ try {
283
+ const id =
284
+ item.finalData.docId ||
285
+ item.finalData.userId ||
286
+ item.context.docId ||
287
+ item.context.userId;
288
+
289
+ if (item.finalData.hasOwnProperty("userId")) {
290
+ delete item.finalData.userId;
291
+ }
292
+ if (item.finalData.hasOwnProperty("docId")) {
293
+ delete item.finalData.docId;
294
+ }
295
+ if (!item.finalData) {
296
+ return Promise.resolve();
297
+ }
298
+ return tryAwaitWithRetry(
299
+ async () =>
300
+ await this.database.createDocument(
301
+ db.$id,
302
+ collection.$id,
303
+ id,
304
+ item.finalData
305
+ )
306
+ );
307
+ } catch (error) {
308
+ console.error(error);
309
+ return Promise.resolve();
310
+ }
311
+ });
312
+
313
+ // Wait for all promises in the current batch to resolve
314
+ await Promise.all(batchPromises);
315
+ console.log(`Completed batch ${i + 1} of ${dataSplit.length}`);
316
+ await updateOperation(this.database, importOperation.$id, {
317
+ progress: processedItems,
318
+ });
319
+ }
320
+ // After all batches are processed, update the operation status to completed
321
+ await updateOperation(this.database, importOperation.$id, {
322
+ status: "completed",
323
+ });
324
+ }
325
+ }
326
+
327
+ async executePostImportActions(dbId: string, dataLoader: DataLoader) {
328
+ // Iterate over each collection in the importMap
329
+ for (const [
330
+ collectionKey,
331
+ collectionData,
332
+ ] of dataLoader.importMap.entries()) {
333
+ console.log(
334
+ `Processing post-import actions for collection: ${collectionKey}`
335
+ );
336
+
337
+ // Iterate over each item in the collectionData.data
338
+ for (const item of collectionData.data) {
339
+ // Assuming each item has attributeMappings that contain actions to be executed
340
+ if (item.importDef && item.importDef.attributeMappings) {
341
+ // Use item.context as the context for action execution
342
+ const context = item.context; // Directly use item.context as the context for action execution
343
+ // Iterate through attributeMappings to execute actions
344
+ try {
345
+ // Execute post-import actions for the current attributeMapping
346
+ // Pass item.finalData as the data to be processed along with the context
347
+ await this.importDataActions.executeAfterImportActions(
348
+ item.finalData,
349
+ item.importDef.attributeMappings,
350
+ context
351
+ );
352
+ } catch (error) {
353
+ console.error(
354
+ `Failed to execute post-import actions for item in collection ${collectionKey}:`,
355
+ error
356
+ );
357
+ // Handle error (e.g., log, retry, continue with next action)
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ // async executeActionsInParallel(dbId: string, collection: ConfigCollection) {
365
+ // const collectionExists = await checkForCollection(
366
+ // this.database,
367
+ // dbId,
368
+ // collection
369
+ // );
370
+ // if (!collectionExists) {
371
+ // logger.error(`No collection found for ${collection.name}`);
372
+ // return; // Skip this iteration
373
+ // }
374
+ // const operations = await getAfterImportOperations(
375
+ // this.database,
376
+ // collectionExists.$id
377
+ // );
378
+
379
+ // for (const operation of operations) {
380
+ // if (!operation.batches) {
381
+ // continue;
382
+ // }
383
+ // const batches = operation.batches;
384
+ // const promises = [];
385
+ // for (const batch of batches) {
386
+ // const batchId = batch;
387
+ // promises.push(
388
+ // this.database.getDocument("migrations", "batches", batchId)
389
+ // );
390
+ // }
391
+ // const results = await Promise.allSettled(promises);
392
+ // results.forEach((result) => {
393
+ // if (result.status === "rejected") {
394
+ // logger.error("A process batch promise was rejected:", result.reason);
395
+ // }
396
+ // });
397
+ // const resultsData = results
398
+ // .map((result) => (result.status === "fulfilled" ? result.value : null))
399
+ // .filter((result: any) => result !== null && !result.processed)
400
+ // .map((result) => BatchSchema.parse(result));
401
+ // for (const batch of resultsData) {
402
+ // const actionOperation = ContextObject.parse(JSON.parse(batch.data));
403
+ // const { context, finalItem, attributeMappings } = actionOperation;
404
+ // if (finalItem.$id && !context.docId) {
405
+ // context.docId =
406
+ // finalItem.$id || context.createdDoc.$id || context.$id || undefined;
407
+ // logger.info(
408
+ // `Setting docId to ${
409
+ // finalItem.$id
410
+ // } because docId not found in context, batch ${
411
+ // batch.$id
412
+ // }, context is ${JSON.stringify(context)}`
413
+ // );
414
+ // }
415
+ // try {
416
+ // await this.importDataActions.executeAfterImportActions(
417
+ // finalItem,
418
+ // attributeMappings,
419
+ // context
420
+ // );
421
+ // // Mark batch as processed
422
+ // await this.database.deleteDocument(
423
+ // "migrations",
424
+ // "batches",
425
+ // batch.$id
426
+ // );
427
+ // } catch (error) {
428
+ // logger.error(
429
+ // `Failed to execute batch ${batch.$id}:`,
430
+ // error,
431
+ // "Context is :",
432
+ // context
433
+ // );
434
+ // await this.database.deleteDocument(
435
+ // "migrations",
436
+ // "batches",
437
+ // batch.$id
438
+ // );
439
+ // }
440
+ // }
441
+
442
+ // // After processing all batches, update the operation status
443
+ // await updateOperation(this.database, operation.$id, {
444
+ // status: "completed", // Or determine based on batch success/failure
445
+ // });
446
+ // }
447
+ // }
448
+ }