appwrite-utils-cli 0.0.19 → 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.
@@ -59,6 +59,7 @@ export class ImportController {
59
59
  await this.importCollections(db);
60
60
  await resolveAndUpdateRelationships(db.$id, this.database, this.config);
61
61
  await this.executePostImportActions(db.$id);
62
+ await this.executePostImportActions(db.$id);
62
63
  console.log(`---------------------------------`);
63
64
  console.log(`Finished import data for database: ${db.name}`);
64
65
  console.log(`---------------------------------`);
@@ -147,7 +148,7 @@ export class ImportController {
147
148
  async processBatch(db, collection, importDef, dataToImport, updateDefs = [], isMembersCollection = false) {
148
149
  for (let i = 0; i < dataToImport.length; i += this.batchLimit) {
149
150
  const batch = dataToImport.slice(i, i + this.batchLimit);
150
- const results = await Promise.allSettled(batch.map(async (item) => {
151
+ for (const item of batch) {
151
152
  let context = this.createContext(db, collection, item);
152
153
  let finalItem = await this.transformData(item, importDef.attributeMappings);
153
154
  let createIdToUse = undefined;
@@ -207,31 +208,31 @@ export class ImportController {
207
208
  const attributeMappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, afterImportActionContext, finalItem);
208
209
  if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
209
210
  logger.info(`Pushing to post-import actions queue for ${context.docId}`);
210
- // const afterImportOperationContext = ContextObject.parse({
211
- // dbId: db.$id,
212
- // collectionId: collection.$id,
213
- // finalItem: finalItem,
214
- // attributeMappings: attributeMappingsWithActions,
215
- // context: afterImportActionContext,
216
- // });
217
- // await createOrFindAfterImportOperation(
218
- // this.database,
219
- // context.collId,
220
- // afterImportOperationContext
221
- // );
222
- this.postImportActionsQueue.push({
223
- context: afterImportActionContext,
211
+ const afterImportOperationContext = ContextObject.parse({
212
+ dbId: db.$id,
213
+ collectionId: collection.$id,
224
214
  finalItem: finalItem,
225
215
  attributeMappings: attributeMappingsWithActions,
216
+ context: afterImportActionContext,
226
217
  });
218
+ await createOrFindAfterImportOperation(this.database, context.collId, afterImportOperationContext);
219
+ // this.postImportActionsQueue.push({
220
+ // context: afterImportActionContext,
221
+ // finalItem: finalItem,
222
+ // attributeMappings: attributeMappingsWithActions,
223
+ // });
227
224
  }
228
- }));
229
- results.forEach((result) => {
230
- if (result.status === "rejected") {
231
- console.error("A process batch promise was rejected:", result.reason);
232
- logger.error("An error occurred during creation: ", result.reason);
233
- }
234
- });
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
+ // });
235
236
  }
236
237
  }
237
238
  async handleCreate(context, finalItem, updateDefs, id) {
@@ -318,33 +319,41 @@ export class ImportController {
318
319
  });
319
320
  }
320
321
  async executePostImportActions(dbId) {
321
- let actionQueue = [];
322
- for (const action of this.postImportActionsQueue) {
323
- actionQueue.push(this.importDataActions.executeAfterImportActions(action.finalItem, action.attributeMappings, action.context));
324
- }
325
- const BATCH_LIMIT = 20;
326
- const splitQueue = _.chunk(actionQueue, BATCH_LIMIT);
327
- for (const queue of splitQueue) {
328
- const results = await Promise.allSettled(queue);
329
- results.forEach((result) => {
330
- if (result.status === "rejected") {
331
- console.error("An action promise was rejected:", result.reason);
332
- logger.error(`An action promise was rejected: ${result.reason} -- ${JSON.stringify(result)}`);
333
- }
334
- });
335
- }
336
- // const collectionActionsPromises = [];
337
- // for (const collection of this.config.collections) {
338
- // collectionActionsPromises.push(
339
- // this.executeActionsInParallel(dbId, collection)
322
+ // let actionQueue: Promise<any>[] = [];
323
+ // for (const action of this.postImportActionsQueue) {
324
+ // actionQueue.push(
325
+ // this.importDataActions.executeAfterImportActions(
326
+ // action.finalItem,
327
+ // action.attributeMappings,
328
+ // action.context
329
+ // )
340
330
  // );
341
331
  // }
342
- // const results = await Promise.allSettled(collectionActionsPromises);
343
- // results.forEach((result) => {
344
- // if (result.status === "rejected") {
345
- // console.error("A process batch promise was rejected:", result.reason);
346
- // }
347
- // });
332
+ // const BATCH_LIMIT = 5;
333
+ // const splitQueue = _.chunk(actionQueue, BATCH_LIMIT);
334
+ // for (const queue of splitQueue) {
335
+ // const results = await Promise.allSettled(queue);
336
+ // results.forEach((result) => {
337
+ // if (result.status === "rejected") {
338
+ // console.error("An action promise was rejected:", result.reason);
339
+ // logger.error(
340
+ // `An action promise was rejected: ${
341
+ // result.reason
342
+ // } -- ${JSON.stringify(result)}`
343
+ // );
344
+ // }
345
+ // });
346
+ // }
347
+ const collectionActionsPromises = [];
348
+ for (const collection of this.config.collections) {
349
+ collectionActionsPromises.push(this.executeActionsInParallel(dbId, collection));
350
+ }
351
+ const results = await Promise.allSettled(collectionActionsPromises);
352
+ results.forEach((result) => {
353
+ if (result.status === "rejected") {
354
+ console.error("A process batch promise was rejected:", result.reason);
355
+ }
356
+ });
348
357
  }
349
358
  async executeActionsInParallel(dbId, collection) {
350
359
  const collectionExists = await checkForCollection(this.database, dbId, collection);
@@ -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
  }
@@ -69,11 +68,7 @@ export const getAfterImportOperations = async (database, collectionId) => {
69
68
  export const setAllPendingAfterImportActionsToReady = async (database, dbId, collectionId) => {
70
69
  let lastDocumentId;
71
70
  do {
72
- const query = [
73
- Query.equal("collectionId", collectionId),
74
- Query.equal("status", "pending"),
75
- Query.limit(100),
76
- ];
71
+ const query = [Query.equal("collectionId", collectionId), Query.limit(100)];
77
72
  if (lastDocumentId) {
78
73
  query.push(Query.cursorAfter(lastDocumentId));
79
74
  }
@@ -83,7 +78,7 @@ export const setAllPendingAfterImportActionsToReady = async (database, dbId, col
83
78
  await database.updateDocument("migrations", "currentOperations", operation.$id, { status: "ready" });
84
79
  }
85
80
  // Prepare for the next iteration in case there are more than 100 documents
86
- if (operations.documents.length > 0) {
81
+ if (operations.documents.length > 0 && operations.documents.length >= 100) {
87
82
  lastDocumentId =
88
83
  operations.documents[operations.documents.length - 1].$id;
89
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.19",
4
+ "version": "0.0.21",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -104,6 +104,7 @@ export class ImportController {
104
104
  await this.importCollections(db);
105
105
  await resolveAndUpdateRelationships(db.$id, this.database!, this.config!);
106
106
  await this.executePostImportActions(db.$id);
107
+ await this.executePostImportActions(db.$id);
107
108
  console.log(`---------------------------------`);
108
109
  console.log(`Finished import data for database: ${db.name}`);
109
110
  console.log(`---------------------------------`);
@@ -256,133 +257,135 @@ export class ImportController {
256
257
  ) {
257
258
  for (let i = 0; i < dataToImport.length; i += this.batchLimit) {
258
259
  const batch = dataToImport.slice(i, i + this.batchLimit);
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
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
265
275
  );
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
275
- );
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) {
288
- logger.error(
289
- `Skipping user & contact creation for ${item} because of an error...`
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];
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) {
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) {
313
288
  logger.error(
314
- `Skipping user & contact creation for ${item} due to lack of email...`
289
+ `Skipping user & contact creation for ${item} because of an error...`
315
290
  );
291
+ return;
316
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];
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
+ }
317
329
 
318
- context = { ...context, ...finalItem };
319
- const validated = await this.importDataActions.validateItem(
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,
320
344
  finalItem,
345
+ importDef
346
+ );
347
+ context = { ...context, ...updatedContext };
348
+ }
349
+ const afterImportActionContext = structuredClone(context);
350
+ const attributeMappingsWithActions =
351
+ this.getAttributeMappingsWithActions(
321
352
  importDef.attributeMappings,
322
- context
353
+ afterImportActionContext,
354
+ finalItem
323
355
  );
324
- if (!validated) {
325
- console.error("Validation failed for item:", finalItem);
326
- logger.error("Validation failed for item:", finalItem);
327
- return;
328
- }
329
-
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,
344
- finalItem,
345
- importDef
346
- );
347
- context = { ...context, ...updatedContext };
348
- }
349
- const afterImportActionContext = structuredClone(context);
350
- const attributeMappingsWithActions =
351
- this.getAttributeMappingsWithActions(
352
- importDef.attributeMappings,
353
- afterImportActionContext,
354
- finalItem
355
- );
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
- });
377
- }
378
- })
379
- );
380
- results.forEach((result) => {
381
- if (result.status === "rejected") {
382
- console.error("A process batch promise was rejected:", result.reason);
383
- 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
+ // });
384
377
  }
385
- });
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
+ // });
386
389
  }
387
390
  }
388
391
 
@@ -512,43 +515,43 @@ export class ImportController {
512
515
  }
513
516
 
514
517
  async executePostImportActions(dbId: string) {
515
- let actionQueue: Promise<any>[] = [];
516
- for (const action of this.postImportActionsQueue) {
517
- actionQueue.push(
518
- this.importDataActions.executeAfterImportActions(
519
- action.finalItem,
520
- action.attributeMappings,
521
- action.context
522
- )
523
- );
524
- }
525
- const BATCH_LIMIT = 20;
526
- const splitQueue = _.chunk(actionQueue, BATCH_LIMIT);
527
- for (const queue of splitQueue) {
528
- const results = await Promise.allSettled(queue);
529
- results.forEach((result) => {
530
- if (result.status === "rejected") {
531
- console.error("An action promise was rejected:", result.reason);
532
- logger.error(
533
- `An action promise was rejected: ${
534
- result.reason
535
- } -- ${JSON.stringify(result)}`
536
- );
537
- }
538
- });
539
- }
540
- // const collectionActionsPromises = [];
541
- // for (const collection of this.config.collections) {
542
- // collectionActionsPromises.push(
543
- // this.executeActionsInParallel(dbId, collection)
518
+ // let actionQueue: Promise<any>[] = [];
519
+ // for (const action of this.postImportActionsQueue) {
520
+ // actionQueue.push(
521
+ // this.importDataActions.executeAfterImportActions(
522
+ // action.finalItem,
523
+ // action.attributeMappings,
524
+ // action.context
525
+ // )
544
526
  // );
545
527
  // }
546
- // const results = await Promise.allSettled(collectionActionsPromises);
547
- // results.forEach((result) => {
548
- // if (result.status === "rejected") {
549
- // console.error("A process batch promise was rejected:", result.reason);
550
- // }
551
- // });
528
+ // const BATCH_LIMIT = 5;
529
+ // const splitQueue = _.chunk(actionQueue, BATCH_LIMIT);
530
+ // for (const queue of splitQueue) {
531
+ // const results = await Promise.allSettled(queue);
532
+ // results.forEach((result) => {
533
+ // if (result.status === "rejected") {
534
+ // console.error("An action promise was rejected:", result.reason);
535
+ // logger.error(
536
+ // `An action promise was rejected: ${
537
+ // result.reason
538
+ // } -- ${JSON.stringify(result)}`
539
+ // );
540
+ // }
541
+ // });
542
+ // }
543
+ const collectionActionsPromises = [];
544
+ for (const collection of this.config.collections) {
545
+ collectionActionsPromises.push(
546
+ this.executeActionsInParallel(dbId, collection)
547
+ );
548
+ }
549
+ const results = await Promise.allSettled(collectionActionsPromises);
550
+ results.forEach((result) => {
551
+ if (result.status === "rejected") {
552
+ console.error("A process batch promise was rejected:", result.reason);
553
+ }
554
+ });
552
555
  }
553
556
 
554
557
  async executeActionsInParallel(dbId: string, collection: ConfigCollection) {
@@ -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
  }
@@ -111,11 +110,7 @@ export const setAllPendingAfterImportActionsToReady = async (
111
110
  ) => {
112
111
  let lastDocumentId: string | undefined;
113
112
  do {
114
- const query = [
115
- Query.equal("collectionId", collectionId),
116
- Query.equal("status", "pending"),
117
- Query.limit(100),
118
- ];
113
+ const query = [Query.equal("collectionId", collectionId), Query.limit(100)];
119
114
 
120
115
  if (lastDocumentId) {
121
116
  query.push(Query.cursorAfter(lastDocumentId));
@@ -138,7 +133,7 @@ export const setAllPendingAfterImportActionsToReady = async (
138
133
  }
139
134
 
140
135
  // Prepare for the next iteration in case there are more than 100 documents
141
- if (operations.documents.length > 0) {
136
+ if (operations.documents.length > 0 && operations.documents.length >= 100) {
142
137
  lastDocumentId =
143
138
  operations.documents[operations.documents.length - 1].$id;
144
139
  } else {