appwrite-utils-cli 0.0.11 → 0.0.12
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.
|
@@ -119,8 +119,8 @@ export class ImportController {
|
|
|
119
119
|
continue;
|
|
120
120
|
console.log(`Processing update definitions for collection ID: ${collection.$id}`);
|
|
121
121
|
await this.processBatch(db, collection, importDef, dataToImport);
|
|
122
|
+
await setAllPendingAfterImportActionsToReady(this.database, db.$id, collection.$id);
|
|
122
123
|
}
|
|
123
|
-
await setAllPendingAfterImportActionsToReady(this.database, db.$id, collection.$id);
|
|
124
124
|
}
|
|
125
125
|
async loadData(importDef) {
|
|
126
126
|
const filePath = path.resolve(this.appwriteFolderPath, importDef.filePath);
|
|
@@ -151,7 +151,7 @@ export class ImportController {
|
|
|
151
151
|
async processBatch(db, collection, importDef, dataToImport, updateDefs = [], isMembersCollection = false) {
|
|
152
152
|
for (let i = 0; i < dataToImport.length; i += this.batchLimit) {
|
|
153
153
|
const batch = dataToImport.slice(i, i + this.batchLimit);
|
|
154
|
-
batch.map(async (item) => {
|
|
154
|
+
const results = await Promise.allSettled(batch.map(async (item) => {
|
|
155
155
|
let context = this.createContext(db, collection, item);
|
|
156
156
|
let finalItem = await this.transformData(item, importDef.attributeMappings);
|
|
157
157
|
let createIdToUse = undefined;
|
|
@@ -191,29 +191,23 @@ export class ImportController {
|
|
|
191
191
|
logger.error(`Skipping user & contact creation for ${item} due to lack of email...`);
|
|
192
192
|
}
|
|
193
193
|
context = { ...context, ...finalItem };
|
|
194
|
-
|
|
194
|
+
const validated = await this.importDataActions.validateItem(finalItem, importDef.attributeMappings, context);
|
|
195
|
+
if (!validated) {
|
|
195
196
|
console.error("Validation failed for item:", finalItem);
|
|
197
|
+
logger.error("Validation failed for item:", finalItem);
|
|
196
198
|
return;
|
|
197
199
|
}
|
|
198
|
-
let afterContext;
|
|
199
200
|
if ((importDef.type === "create" || !importDef.type) &&
|
|
200
201
|
!associatedDoc) {
|
|
201
202
|
const createdContext = await this.handleCreate(context, finalItem, updateDefs, createIdToUse);
|
|
202
|
-
|
|
203
|
-
afterContext = createdContext;
|
|
204
|
-
}
|
|
203
|
+
context = { ...context, ...createdContext };
|
|
205
204
|
logger.info(`Handled create for ${context.docId}}`);
|
|
206
205
|
}
|
|
207
206
|
else {
|
|
208
207
|
const updatedContext = await this.handleUpdate(context, finalItem, importDef);
|
|
209
|
-
|
|
210
|
-
afterContext = updatedContext;
|
|
211
|
-
}
|
|
208
|
+
context = { ...context, ...updatedContext };
|
|
212
209
|
logger.info(`Handled update for ${context.docId}`);
|
|
213
210
|
}
|
|
214
|
-
if (afterContext) {
|
|
215
|
-
context = { ...context, ...afterContext };
|
|
216
|
-
}
|
|
217
211
|
const afterImportActionContext = structuredClone(context);
|
|
218
212
|
const attributeMappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, finalItem);
|
|
219
213
|
if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
|
|
@@ -232,13 +226,13 @@ export class ImportController {
|
|
|
232
226
|
// attributeMappings: attributeMappingsWithActions,
|
|
233
227
|
// });
|
|
234
228
|
}
|
|
229
|
+
}));
|
|
230
|
+
results.forEach((result) => {
|
|
231
|
+
if (result.status === "rejected") {
|
|
232
|
+
console.error("A process batch promise was rejected:", result.reason);
|
|
233
|
+
logger.error("An error occurred during creation: ", result.reason);
|
|
234
|
+
}
|
|
235
235
|
});
|
|
236
|
-
// results.forEach((result) => {
|
|
237
|
-
// if (result.status === "rejected") {
|
|
238
|
-
// console.error("A process batch promise was rejected:", result.reason);
|
|
239
|
-
// logger.error("An error occurred during creation: ", result.reason);
|
|
240
|
-
// }
|
|
241
|
-
// });
|
|
242
236
|
}
|
|
243
237
|
}
|
|
244
238
|
async handleCreate(context, finalItem, updateDefs, id) {
|
|
@@ -364,10 +358,6 @@ export class ImportController {
|
|
|
364
358
|
await this.importDataActions.executeAfterImportActions(finalItem, attributeMappings, context);
|
|
365
359
|
// Mark batch as processed
|
|
366
360
|
await this.database.deleteDocument("migrations", "batches", batch.$id);
|
|
367
|
-
await updateOperation(this.database, operation.$id, {
|
|
368
|
-
status: "completed",
|
|
369
|
-
batches: [],
|
|
370
|
-
});
|
|
371
361
|
}
|
|
372
362
|
catch (error) {
|
|
373
363
|
logger.error(`Failed to execute batch ${batch.$id}:`, error);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appwrite-utils-cli",
|
|
3
3
|
"description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.12",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -113,7 +113,6 @@ export class ImportController {
|
|
|
113
113
|
async importCollections(db: ConfigDatabase) {
|
|
114
114
|
const maxParallel = 3; // Maximum number of collections to process in parallel
|
|
115
115
|
let activePromises: Promise<void>[] = []; // Array to keep track of active promises
|
|
116
|
-
|
|
117
116
|
for (const collection of this.config.collections) {
|
|
118
117
|
// Function that returns a promise for processing a single collection
|
|
119
118
|
const processCollection = async (col: ConfigCollection) => {
|
|
@@ -200,13 +199,12 @@ export class ImportController {
|
|
|
200
199
|
`Processing update definitions for collection ID: ${collection.$id}`
|
|
201
200
|
);
|
|
202
201
|
await this.processBatch(db, collection, importDef, dataToImport);
|
|
202
|
+
await setAllPendingAfterImportActionsToReady(
|
|
203
|
+
this.database,
|
|
204
|
+
db.$id,
|
|
205
|
+
collection.$id
|
|
206
|
+
);
|
|
203
207
|
}
|
|
204
|
-
|
|
205
|
-
await setAllPendingAfterImportActionsToReady(
|
|
206
|
-
this.database,
|
|
207
|
-
db.$id,
|
|
208
|
-
collection.$id
|
|
209
|
-
);
|
|
210
208
|
}
|
|
211
209
|
|
|
212
210
|
async loadData(importDef: ImportDef): Promise<any[]> {
|
|
@@ -258,145 +256,138 @@ export class ImportController {
|
|
|
258
256
|
) {
|
|
259
257
|
for (let i = 0; i < dataToImport.length; i += this.batchLimit) {
|
|
260
258
|
const batch = dataToImport.slice(i, i + this.batchLimit);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
let createIdToUse: string | undefined = undefined;
|
|
268
|
-
let associatedDoc: Models.Document | undefined;
|
|
269
|
-
if (
|
|
270
|
-
isMembersCollection &&
|
|
271
|
-
(finalItem.hasOwnProperty("email") || item.hasOwnProperty("phone"))
|
|
272
|
-
) {
|
|
273
|
-
console.log("Found members collection, creating user...");
|
|
274
|
-
const usersController = new UsersController(
|
|
275
|
-
this.config,
|
|
276
|
-
this.database
|
|
277
|
-
);
|
|
278
|
-
const userToCreate = AuthUserCreateSchema.safeParse({
|
|
279
|
-
...finalItem,
|
|
280
|
-
});
|
|
281
|
-
if (!userToCreate.success) {
|
|
282
|
-
console.error(userToCreate.error);
|
|
283
|
-
logger.error(userToCreate.error);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
const user = await usersController.createUserAndReturn(
|
|
287
|
-
userToCreate.data
|
|
288
|
-
);
|
|
289
|
-
createIdToUse = user.$id;
|
|
290
|
-
context.docId = createIdToUse;
|
|
291
|
-
context = { ...context, ...user };
|
|
292
|
-
console.log(
|
|
293
|
-
"Created user, deleting keys in finalItem that exist in user..."
|
|
294
|
-
);
|
|
295
|
-
const associatedDocFound = await this.database.listDocuments(
|
|
296
|
-
db.$id,
|
|
297
|
-
context.collId,
|
|
298
|
-
[Query.equal("$id", createIdToUse)]
|
|
259
|
+
const results = await Promise.allSettled(
|
|
260
|
+
batch.map(async (item: any) => {
|
|
261
|
+
let context = this.createContext(db, collection, item);
|
|
262
|
+
let finalItem = await this.transformData(
|
|
263
|
+
item,
|
|
264
|
+
importDef.attributeMappings
|
|
299
265
|
);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
266
|
+
let createIdToUse: string | undefined = undefined;
|
|
267
|
+
let associatedDoc: Models.Document | undefined;
|
|
268
|
+
if (
|
|
269
|
+
isMembersCollection &&
|
|
270
|
+
(finalItem.hasOwnProperty("email") || item.hasOwnProperty("phone"))
|
|
271
|
+
) {
|
|
272
|
+
console.log("Found members collection, creating user...");
|
|
273
|
+
const usersController = new UsersController(
|
|
274
|
+
this.config,
|
|
275
|
+
this.database
|
|
276
|
+
);
|
|
277
|
+
const userToCreate = AuthUserCreateSchema.safeParse({
|
|
278
|
+
...finalItem,
|
|
279
|
+
});
|
|
280
|
+
if (!userToCreate.success) {
|
|
281
|
+
console.error(userToCreate.error);
|
|
282
|
+
logger.error(userToCreate.error);
|
|
283
|
+
return;
|
|
309
284
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
285
|
+
const user = await usersController.createUserAndReturn(
|
|
286
|
+
userToCreate.data
|
|
287
|
+
);
|
|
288
|
+
createIdToUse = user.$id;
|
|
289
|
+
context.docId = createIdToUse;
|
|
290
|
+
context = { ...context, ...user };
|
|
291
|
+
console.log(
|
|
292
|
+
"Created user, deleting keys in finalItem that exist in user..."
|
|
293
|
+
);
|
|
294
|
+
const associatedDocFound = await this.database.listDocuments(
|
|
295
|
+
db.$id,
|
|
296
|
+
context.collId,
|
|
297
|
+
[Query.equal("$id", createIdToUse)]
|
|
298
|
+
);
|
|
299
|
+
if (associatedDocFound.documents.length > 0) {
|
|
300
|
+
associatedDoc = associatedDocFound.documents[0];
|
|
301
|
+
}
|
|
302
|
+
// Delete keys in finalItem that also exist in user
|
|
303
|
+
let deletedKeys: string[] = [];
|
|
304
|
+
Object.keys(finalItem).forEach((key) => {
|
|
305
|
+
if (user.hasOwnProperty(key)) {
|
|
306
|
+
delete finalItem[key];
|
|
307
|
+
deletedKeys.push(key);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
console.log(
|
|
311
|
+
`Set createIdToUse to ${createIdToUse}. Deleted keys: ${deletedKeys.join(
|
|
312
|
+
", "
|
|
313
|
+
)}.`
|
|
314
|
+
);
|
|
315
|
+
} else if (isMembersCollection) {
|
|
316
|
+
logger.error(
|
|
317
|
+
`Skipping user & contact creation for ${item} due to lack of email...`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
323
320
|
|
|
324
|
-
|
|
325
|
-
|
|
321
|
+
context = { ...context, ...finalItem };
|
|
322
|
+
const validated = await this.importDataActions.validateItem(
|
|
326
323
|
finalItem,
|
|
327
324
|
importDef.attributeMappings,
|
|
328
325
|
context
|
|
329
|
-
))
|
|
330
|
-
) {
|
|
331
|
-
console.error("Validation failed for item:", finalItem);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
let afterContext;
|
|
336
|
-
if (
|
|
337
|
-
(importDef.type === "create" || !importDef.type) &&
|
|
338
|
-
!associatedDoc
|
|
339
|
-
) {
|
|
340
|
-
const createdContext = await this.handleCreate(
|
|
341
|
-
context,
|
|
342
|
-
finalItem,
|
|
343
|
-
updateDefs,
|
|
344
|
-
createIdToUse
|
|
345
326
|
);
|
|
346
|
-
if (
|
|
347
|
-
|
|
327
|
+
if (!validated) {
|
|
328
|
+
console.error("Validation failed for item:", finalItem);
|
|
329
|
+
logger.error("Validation failed for item:", finalItem);
|
|
330
|
+
return;
|
|
348
331
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
332
|
+
|
|
333
|
+
if (
|
|
334
|
+
(importDef.type === "create" || !importDef.type) &&
|
|
335
|
+
!associatedDoc
|
|
336
|
+
) {
|
|
337
|
+
const createdContext = await this.handleCreate(
|
|
338
|
+
context,
|
|
339
|
+
finalItem,
|
|
340
|
+
updateDefs,
|
|
341
|
+
createIdToUse
|
|
342
|
+
);
|
|
343
|
+
context = { ...context, ...createdContext };
|
|
344
|
+
logger.info(`Handled create for ${context.docId}}`);
|
|
345
|
+
} else {
|
|
346
|
+
const updatedContext = await this.handleUpdate(
|
|
347
|
+
context,
|
|
348
|
+
finalItem,
|
|
349
|
+
importDef
|
|
350
|
+
);
|
|
351
|
+
context = { ...context, ...updatedContext };
|
|
352
|
+
logger.info(`Handled update for ${context.docId}`);
|
|
358
353
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
354
|
+
const afterImportActionContext = structuredClone(context);
|
|
355
|
+
const attributeMappingsWithActions =
|
|
356
|
+
this.getAttributeMappingsWithActions(
|
|
357
|
+
importDef.attributeMappings,
|
|
358
|
+
context,
|
|
359
|
+
finalItem
|
|
360
|
+
);
|
|
361
|
+
if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
|
|
362
|
+
logger.info(
|
|
363
|
+
`Pushing to post-import actions queue for ${context.docId}`
|
|
364
|
+
);
|
|
365
|
+
const afterImportOperationContext = ContextObject.parse({
|
|
366
|
+
dbId: db.$id,
|
|
367
|
+
collectionId: collection.$id,
|
|
368
|
+
finalItem: finalItem,
|
|
369
|
+
attributeMappings: attributeMappingsWithActions,
|
|
370
|
+
context: afterImportActionContext,
|
|
371
|
+
});
|
|
372
|
+
await createOrFindAfterImportOperation(
|
|
373
|
+
this.database,
|
|
374
|
+
context.collId,
|
|
375
|
+
afterImportOperationContext
|
|
376
|
+
);
|
|
377
|
+
// this.postImportActionsQueue.push({
|
|
378
|
+
// context: afterImportActionContext,
|
|
379
|
+
// finalItem: finalItem,
|
|
380
|
+
// attributeMappings: attributeMappingsWithActions,
|
|
381
|
+
// });
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
);
|
|
385
|
+
results.forEach((result) => {
|
|
386
|
+
if (result.status === "rejected") {
|
|
387
|
+
console.error("A process batch promise was rejected:", result.reason);
|
|
388
|
+
logger.error("An error occurred during creation: ", result.reason);
|
|
392
389
|
}
|
|
393
390
|
});
|
|
394
|
-
// results.forEach((result) => {
|
|
395
|
-
// if (result.status === "rejected") {
|
|
396
|
-
// console.error("A process batch promise was rejected:", result.reason);
|
|
397
|
-
// logger.error("An error occurred during creation: ", result.reason);
|
|
398
|
-
// }
|
|
399
|
-
// });
|
|
400
391
|
}
|
|
401
392
|
}
|
|
402
393
|
|
|
@@ -584,10 +575,6 @@ export class ImportController {
|
|
|
584
575
|
"batches",
|
|
585
576
|
batch.$id
|
|
586
577
|
);
|
|
587
|
-
await updateOperation(this.database, operation.$id, {
|
|
588
|
-
status: "completed",
|
|
589
|
-
batches: [],
|
|
590
|
-
});
|
|
591
578
|
} catch (error) {
|
|
592
579
|
logger.error(`Failed to execute batch ${batch.$id}:`, error);
|
|
593
580
|
}
|