appwrite-utils-cli 0.0.20 → 0.0.21
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.
|
@@ -148,7 +148,7 @@ export class ImportController {
|
|
|
148
148
|
async processBatch(db, collection, importDef, dataToImport, updateDefs = [], isMembersCollection = false) {
|
|
149
149
|
for (let i = 0; i < dataToImport.length; i += this.batchLimit) {
|
|
150
150
|
const batch = dataToImport.slice(i, i + this.batchLimit);
|
|
151
|
-
const
|
|
151
|
+
for (const item of batch) {
|
|
152
152
|
let context = this.createContext(db, collection, item);
|
|
153
153
|
let finalItem = await this.transformData(item, importDef.attributeMappings);
|
|
154
154
|
let createIdToUse = undefined;
|
|
@@ -222,13 +222,17 @@ export class ImportController {
|
|
|
222
222
|
// attributeMappings: attributeMappingsWithActions,
|
|
223
223
|
// });
|
|
224
224
|
}
|
|
225
|
-
}
|
|
226
|
-
results.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
225
|
+
}
|
|
226
|
+
// const results = await Promise.allSettled(
|
|
227
|
+
// batch.map(async (item: any) => {
|
|
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
|
+
// });
|
|
232
236
|
}
|
|
233
237
|
}
|
|
234
238
|
async handleCreate(context, finalItem, updateDefs, id) {
|
|
@@ -49,7 +49,6 @@ export const getAfterImportOperations = async (database, collectionId) => {
|
|
|
49
49
|
const query = [
|
|
50
50
|
Query.equal("collectionId", collectionId),
|
|
51
51
|
Query.equal("operationType", "afterImportAction"),
|
|
52
|
-
Query.equal("status", "ready"),
|
|
53
52
|
Query.limit(100),
|
|
54
53
|
];
|
|
55
54
|
if (lastDocumentId) {
|
|
@@ -58,7 +57,7 @@ export const getAfterImportOperations = async (database, collectionId) => {
|
|
|
58
57
|
const operations = await database.listDocuments("migrations", "currentOperations", query);
|
|
59
58
|
total = operations.total; // Update total with the latest fetch
|
|
60
59
|
allOperations.push(...operations.documents);
|
|
61
|
-
if (operations.documents.length > 0) {
|
|
60
|
+
if (operations.documents.length > 0 && operations.documents.length >= 100) {
|
|
62
61
|
lastDocumentId =
|
|
63
62
|
operations.documents[operations.documents.length - 1].$id;
|
|
64
63
|
}
|
|
@@ -79,7 +78,7 @@ export const setAllPendingAfterImportActionsToReady = async (database, dbId, col
|
|
|
79
78
|
await database.updateDocument("migrations", "currentOperations", operation.$id, { status: "ready" });
|
|
80
79
|
}
|
|
81
80
|
// Prepare for the next iteration in case there are more than 100 documents
|
|
82
|
-
if (operations.documents.length > 0) {
|
|
81
|
+
if (operations.documents.length > 0 && operations.documents.length >= 100) {
|
|
83
82
|
lastDocumentId =
|
|
84
83
|
operations.documents[operations.documents.length - 1].$id;
|
|
85
84
|
}
|
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.21",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -257,133 +257,135 @@ export class ImportController {
|
|
|
257
257
|
) {
|
|
258
258
|
for (let i = 0; i < dataToImport.length; i += this.batchLimit) {
|
|
259
259
|
const batch = dataToImport.slice(i, i + this.batchLimit);
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
260
|
+
for (const item of batch) {
|
|
261
|
+
let context = this.createContext(db, collection, item);
|
|
262
|
+
let finalItem = await this.transformData(
|
|
263
|
+
item,
|
|
264
|
+
importDef.attributeMappings
|
|
265
|
+
);
|
|
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
|
+
const usersController = new UsersController(
|
|
273
|
+
this.config,
|
|
274
|
+
this.database
|
|
266
275
|
);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
280
|
-
if (!userToCreate.success) {
|
|
281
|
-
console.error(userToCreate.error);
|
|
282
|
-
logger.error(userToCreate.error);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const user = await usersController.createUserAndReturn(
|
|
286
|
-
userToCreate.data
|
|
287
|
-
);
|
|
288
|
-
if (!user) {
|
|
289
|
-
logger.error(
|
|
290
|
-
`Skipping user & contact creation for ${item} because of an error...`
|
|
291
|
-
);
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
createIdToUse = user.$id;
|
|
295
|
-
context.docId = createIdToUse;
|
|
296
|
-
context = { ...context, ...user };
|
|
297
|
-
const associatedDocFound = await this.database.listDocuments(
|
|
298
|
-
db.$id,
|
|
299
|
-
context.collId,
|
|
300
|
-
[Query.equal("$id", createIdToUse)]
|
|
301
|
-
);
|
|
302
|
-
if (associatedDocFound.documents.length > 0) {
|
|
303
|
-
associatedDoc = associatedDocFound.documents[0];
|
|
304
|
-
}
|
|
305
|
-
// Delete keys in finalItem that also exist in user
|
|
306
|
-
let deletedKeys: string[] = [];
|
|
307
|
-
Object.keys(finalItem).forEach((key) => {
|
|
308
|
-
if (user.hasOwnProperty(key)) {
|
|
309
|
-
delete finalItem[key];
|
|
310
|
-
deletedKeys.push(key);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
} else if (isMembersCollection) {
|
|
276
|
+
const userToCreate = AuthUserCreateSchema.safeParse({
|
|
277
|
+
...finalItem,
|
|
278
|
+
});
|
|
279
|
+
if (!userToCreate.success) {
|
|
280
|
+
console.error(userToCreate.error);
|
|
281
|
+
logger.error(userToCreate.error);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const user = await usersController.createUserAndReturn(
|
|
285
|
+
userToCreate.data
|
|
286
|
+
);
|
|
287
|
+
if (!user) {
|
|
314
288
|
logger.error(
|
|
315
|
-
`Skipping user & contact creation for ${item}
|
|
289
|
+
`Skipping user & contact creation for ${item} because of an error...`
|
|
316
290
|
);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
createIdToUse = user.$id;
|
|
294
|
+
context.docId = createIdToUse;
|
|
295
|
+
context = { ...context, ...user };
|
|
296
|
+
const associatedDocFound = await this.database.listDocuments(
|
|
297
|
+
db.$id,
|
|
298
|
+
context.collId,
|
|
299
|
+
[Query.equal("$id", createIdToUse)]
|
|
300
|
+
);
|
|
301
|
+
if (associatedDocFound.documents.length > 0) {
|
|
302
|
+
associatedDoc = associatedDocFound.documents[0];
|
|
317
303
|
}
|
|
304
|
+
// Delete keys in finalItem that also exist in user
|
|
305
|
+
let deletedKeys: string[] = [];
|
|
306
|
+
Object.keys(finalItem).forEach((key) => {
|
|
307
|
+
if (user.hasOwnProperty(key)) {
|
|
308
|
+
delete finalItem[key];
|
|
309
|
+
deletedKeys.push(key);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
} else if (isMembersCollection) {
|
|
313
|
+
logger.error(
|
|
314
|
+
`Skipping user & contact creation for ${item} due to lack of email...`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
context = { ...context, ...finalItem };
|
|
319
|
+
const validated = await this.importDataActions.validateItem(
|
|
320
|
+
finalItem,
|
|
321
|
+
importDef.attributeMappings,
|
|
322
|
+
context
|
|
323
|
+
);
|
|
324
|
+
if (!validated) {
|
|
325
|
+
console.error("Validation failed for item:", finalItem);
|
|
326
|
+
logger.error("Validation failed for item:", finalItem);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
318
329
|
|
|
319
|
-
|
|
320
|
-
|
|
330
|
+
if (
|
|
331
|
+
(importDef.type === "create" || !importDef.type) &&
|
|
332
|
+
!associatedDoc
|
|
333
|
+
) {
|
|
334
|
+
const createdContext = await this.handleCreate(
|
|
335
|
+
context,
|
|
336
|
+
finalItem,
|
|
337
|
+
updateDefs,
|
|
338
|
+
createIdToUse
|
|
339
|
+
);
|
|
340
|
+
context = { ...context, ...createdContext };
|
|
341
|
+
} else {
|
|
342
|
+
const updatedContext = await this.handleUpdate(
|
|
343
|
+
context,
|
|
321
344
|
finalItem,
|
|
345
|
+
importDef
|
|
346
|
+
);
|
|
347
|
+
context = { ...context, ...updatedContext };
|
|
348
|
+
}
|
|
349
|
+
const afterImportActionContext = structuredClone(context);
|
|
350
|
+
const attributeMappingsWithActions =
|
|
351
|
+
this.getAttributeMappingsWithActions(
|
|
322
352
|
importDef.attributeMappings,
|
|
323
|
-
|
|
353
|
+
afterImportActionContext,
|
|
354
|
+
finalItem
|
|
324
355
|
);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
importDef
|
|
347
|
-
);
|
|
348
|
-
context = { ...context, ...updatedContext };
|
|
349
|
-
}
|
|
350
|
-
const afterImportActionContext = structuredClone(context);
|
|
351
|
-
const attributeMappingsWithActions =
|
|
352
|
-
this.getAttributeMappingsWithActions(
|
|
353
|
-
importDef.attributeMappings,
|
|
354
|
-
afterImportActionContext,
|
|
355
|
-
finalItem
|
|
356
|
-
);
|
|
357
|
-
if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
|
|
358
|
-
logger.info(
|
|
359
|
-
`Pushing to post-import actions queue for ${context.docId}`
|
|
360
|
-
);
|
|
361
|
-
const afterImportOperationContext = ContextObject.parse({
|
|
362
|
-
dbId: db.$id,
|
|
363
|
-
collectionId: collection.$id,
|
|
364
|
-
finalItem: finalItem,
|
|
365
|
-
attributeMappings: attributeMappingsWithActions,
|
|
366
|
-
context: afterImportActionContext,
|
|
367
|
-
});
|
|
368
|
-
await createOrFindAfterImportOperation(
|
|
369
|
-
this.database,
|
|
370
|
-
context.collId,
|
|
371
|
-
afterImportOperationContext
|
|
372
|
-
);
|
|
373
|
-
// this.postImportActionsQueue.push({
|
|
374
|
-
// context: afterImportActionContext,
|
|
375
|
-
// finalItem: finalItem,
|
|
376
|
-
// attributeMappings: attributeMappingsWithActions,
|
|
377
|
-
// });
|
|
378
|
-
}
|
|
379
|
-
})
|
|
380
|
-
);
|
|
381
|
-
results.forEach((result) => {
|
|
382
|
-
if (result.status === "rejected") {
|
|
383
|
-
console.error("A process batch promise was rejected:", result.reason);
|
|
384
|
-
logger.error("An error occurred during creation: ", result.reason);
|
|
356
|
+
if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
|
|
357
|
+
logger.info(
|
|
358
|
+
`Pushing to post-import actions queue for ${context.docId}`
|
|
359
|
+
);
|
|
360
|
+
const afterImportOperationContext = ContextObject.parse({
|
|
361
|
+
dbId: db.$id,
|
|
362
|
+
collectionId: collection.$id,
|
|
363
|
+
finalItem: finalItem,
|
|
364
|
+
attributeMappings: attributeMappingsWithActions,
|
|
365
|
+
context: afterImportActionContext,
|
|
366
|
+
});
|
|
367
|
+
await createOrFindAfterImportOperation(
|
|
368
|
+
this.database,
|
|
369
|
+
context.collId,
|
|
370
|
+
afterImportOperationContext
|
|
371
|
+
);
|
|
372
|
+
// this.postImportActionsQueue.push({
|
|
373
|
+
// context: afterImportActionContext,
|
|
374
|
+
// finalItem: finalItem,
|
|
375
|
+
// attributeMappings: attributeMappingsWithActions,
|
|
376
|
+
// });
|
|
385
377
|
}
|
|
386
|
-
}
|
|
378
|
+
}
|
|
379
|
+
// const results = await Promise.allSettled(
|
|
380
|
+
// batch.map(async (item: any) => {
|
|
381
|
+
// })
|
|
382
|
+
// );
|
|
383
|
+
// results.forEach((result) => {
|
|
384
|
+
// if (result.status === "rejected") {
|
|
385
|
+
// console.error("A process batch promise was rejected:", result.reason);
|
|
386
|
+
// logger.error("An error occurred during creation: ", result.reason);
|
|
387
|
+
// }
|
|
388
|
+
// });
|
|
387
389
|
}
|
|
388
390
|
}
|
|
389
391
|
|
|
@@ -78,7 +78,6 @@ export const getAfterImportOperations = async (
|
|
|
78
78
|
const query = [
|
|
79
79
|
Query.equal("collectionId", collectionId),
|
|
80
80
|
Query.equal("operationType", "afterImportAction"),
|
|
81
|
-
Query.equal("status", "ready"),
|
|
82
81
|
Query.limit(100),
|
|
83
82
|
];
|
|
84
83
|
|
|
@@ -94,7 +93,7 @@ export const getAfterImportOperations = async (
|
|
|
94
93
|
total = operations.total; // Update total with the latest fetch
|
|
95
94
|
allOperations.push(...operations.documents);
|
|
96
95
|
|
|
97
|
-
if (operations.documents.length > 0) {
|
|
96
|
+
if (operations.documents.length > 0 && operations.documents.length >= 100) {
|
|
98
97
|
lastDocumentId =
|
|
99
98
|
operations.documents[operations.documents.length - 1].$id;
|
|
100
99
|
}
|
|
@@ -134,7 +133,7 @@ export const setAllPendingAfterImportActionsToReady = async (
|
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
// Prepare for the next iteration in case there are more than 100 documents
|
|
137
|
-
if (operations.documents.length > 0) {
|
|
136
|
+
if (operations.documents.length > 0 && operations.documents.length >= 100) {
|
|
138
137
|
lastDocumentId =
|
|
139
138
|
operations.documents[operations.documents.length - 1].$id;
|
|
140
139
|
} else {
|