appwrite-utils-cli 0.0.11 → 0.0.13

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.
@@ -151,14 +151,13 @@ 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;
158
158
  let associatedDoc;
159
159
  if (isMembersCollection &&
160
160
  (finalItem.hasOwnProperty("email") || item.hasOwnProperty("phone"))) {
161
- console.log("Found members collection, creating user...");
162
161
  const usersController = new UsersController(this.config, this.database);
163
162
  const userToCreate = AuthUserCreateSchema.safeParse({
164
163
  ...finalItem,
@@ -172,7 +171,6 @@ export class ImportController {
172
171
  createIdToUse = user.$id;
173
172
  context.docId = createIdToUse;
174
173
  context = { ...context, ...user };
175
- console.log("Created user, deleting keys in finalItem that exist in user...");
176
174
  const associatedDocFound = await this.database.listDocuments(db.$id, context.collId, [Query.equal("$id", createIdToUse)]);
177
175
  if (associatedDocFound.documents.length > 0) {
178
176
  associatedDoc = associatedDocFound.documents[0];
@@ -185,37 +183,28 @@ export class ImportController {
185
183
  deletedKeys.push(key);
186
184
  }
187
185
  });
188
- console.log(`Set createIdToUse to ${createIdToUse}. Deleted keys: ${deletedKeys.join(", ")}.`);
189
186
  }
190
187
  else if (isMembersCollection) {
191
188
  logger.error(`Skipping user & contact creation for ${item} due to lack of email...`);
192
189
  }
193
190
  context = { ...context, ...finalItem };
194
- if (!(await this.importDataActions.validateItem(finalItem, importDef.attributeMappings, context))) {
191
+ const validated = await this.importDataActions.validateItem(finalItem, importDef.attributeMappings, context);
192
+ if (!validated) {
195
193
  console.error("Validation failed for item:", finalItem);
194
+ logger.error("Validation failed for item:", finalItem);
196
195
  return;
197
196
  }
198
- let afterContext;
199
197
  if ((importDef.type === "create" || !importDef.type) &&
200
198
  !associatedDoc) {
201
199
  const createdContext = await this.handleCreate(context, finalItem, updateDefs, createIdToUse);
202
- if (createdContext) {
203
- afterContext = createdContext;
204
- }
205
- logger.info(`Handled create for ${context.docId}}`);
200
+ context = { ...context, ...createdContext };
206
201
  }
207
202
  else {
208
203
  const updatedContext = await this.handleUpdate(context, finalItem, importDef);
209
- if (updatedContext) {
210
- afterContext = updatedContext;
211
- }
212
- logger.info(`Handled update for ${context.docId}`);
213
- }
214
- if (afterContext) {
215
- context = { ...context, ...afterContext };
204
+ context = { ...context, ...updatedContext };
216
205
  }
217
206
  const afterImportActionContext = structuredClone(context);
218
- const attributeMappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, finalItem);
207
+ const attributeMappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, afterImportActionContext, finalItem);
219
208
  if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
220
209
  logger.info(`Pushing to post-import actions queue for ${context.docId}`);
221
210
  const afterImportOperationContext = ContextObject.parse({
@@ -232,21 +221,18 @@ export class ImportController {
232
221
  // attributeMappings: attributeMappingsWithActions,
233
222
  // });
234
223
  }
224
+ }));
225
+ results.forEach((result) => {
226
+ if (result.status === "rejected") {
227
+ console.error("A process batch promise was rejected:", result.reason);
228
+ logger.error("An error occurred during creation: ", result.reason);
229
+ }
235
230
  });
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
231
  }
243
232
  }
244
233
  async handleCreate(context, finalItem, updateDefs, id) {
245
234
  const existing = await documentExists(this.database, context.dbId, context.collId, finalItem);
246
235
  if (!existing) {
247
- if (id) {
248
- console.log(`Creating document with provided ID (member): ${id}`);
249
- }
250
236
  const createdDoc = await this.database.createDocument(context.dbId, context.collId, id || ID.unique(), finalItem);
251
237
  context.docId = createdDoc.$id;
252
238
  context.createdDoc = createdDoc;
@@ -259,7 +245,6 @@ export class ImportController {
259
245
  }
260
246
  });
261
247
  }
262
- console.log(`Created document ID: ${createdDoc.$id}`);
263
248
  return context;
264
249
  }
265
250
  else {
@@ -360,17 +345,18 @@ export class ImportController {
360
345
  for (const batch of resultsData) {
361
346
  const actionOperation = ContextObject.parse(JSON.parse(batch.data));
362
347
  const { context, finalItem, attributeMappings } = actionOperation;
348
+ if (finalItem.$id && !context.docId) {
349
+ context.docId = finalItem.$id;
350
+ logger.info(`Setting docId to ${finalItem.$id} because docId not found in context, batch ${batch.$id}, context is ${JSON.stringify(context)}`);
351
+ }
363
352
  try {
364
353
  await this.importDataActions.executeAfterImportActions(finalItem, attributeMappings, context);
365
354
  // Mark batch as processed
366
355
  await this.database.deleteDocument("migrations", "batches", batch.$id);
367
- await updateOperation(this.database, operation.$id, {
368
- status: "completed",
369
- batches: [],
370
- });
371
356
  }
372
357
  catch (error) {
373
- logger.error(`Failed to execute batch ${batch.$id}:`, error);
358
+ logger.error(`Failed to execute batch ${batch.$id}:`, error, "Context is :", context);
359
+ await this.database.deleteDocument("migrations", "batches", batch.$id);
374
360
  }
375
361
  }
376
362
  // After processing all batches, update the operation status
@@ -1,7 +1,7 @@
1
1
  import winston from "winston";
2
2
  export const logger = winston.createLogger({
3
3
  level: "info",
4
- format: winston.format.prettyPrint(),
4
+ format: winston.format.json({ space: 2 }),
5
5
  defaultMeta: { service: "appwrite-utils-cli" },
6
6
  transports: [
7
7
  //
@@ -9,6 +9,7 @@ export const logger = winston.createLogger({
9
9
  // - Write all logs with importance level of `info` or less to `combined.log`
10
10
  //
11
11
  new winston.transports.File({ filename: "error.log", level: "error" }),
12
+ new winston.transports.File({ filename: "warn.log", level: "warn" }),
12
13
  new winston.transports.File({ filename: "combined.log" }),
13
14
  ],
14
15
  });
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.11",
4
+ "version": "0.0.13",
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) => {
@@ -201,7 +200,6 @@ export class ImportController {
201
200
  );
202
201
  await this.processBatch(db, collection, importDef, dataToImport);
203
202
  }
204
-
205
203
  await setAllPendingAfterImportActionsToReady(
206
204
  this.database,
207
205
  db.$id,
@@ -258,145 +256,127 @@ 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
- batch.map(async (item: any) => {
262
- let context = this.createContext(db, collection, item);
263
- let finalItem = await this.transformData(
264
- item,
265
- importDef.attributeMappings
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
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
288
265
  );
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)]
299
- );
300
- if (associatedDocFound.documents.length > 0) {
301
- associatedDoc = associatedDocFound.documents[0];
302
- }
303
- // Delete keys in finalItem that also exist in user
304
- let deletedKeys: string[] = [];
305
- Object.keys(finalItem).forEach((key) => {
306
- if (user.hasOwnProperty(key)) {
307
- delete finalItem[key];
308
- deletedKeys.push(key);
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;
309
283
  }
310
- });
311
- console.log(
312
- `Set createIdToUse to ${createIdToUse}. Deleted keys: ${deletedKeys.join(
313
- ", "
314
- )}.`
315
- );
316
- } else if (isMembersCollection) {
317
- logger.error(
318
- `Skipping user & contact creation for ${item} due to lack of email...`
319
- );
320
- }
321
-
322
- context = { ...context, ...finalItem };
284
+ const user = await usersController.createUserAndReturn(
285
+ userToCreate.data
286
+ );
287
+ createIdToUse = user.$id;
288
+ context.docId = createIdToUse;
289
+ context = { ...context, ...user };
290
+ const associatedDocFound = await this.database.listDocuments(
291
+ db.$id,
292
+ context.collId,
293
+ [Query.equal("$id", createIdToUse)]
294
+ );
295
+ if (associatedDocFound.documents.length > 0) {
296
+ associatedDoc = associatedDocFound.documents[0];
297
+ }
298
+ // Delete keys in finalItem that also exist in user
299
+ let deletedKeys: string[] = [];
300
+ Object.keys(finalItem).forEach((key) => {
301
+ if (user.hasOwnProperty(key)) {
302
+ delete finalItem[key];
303
+ deletedKeys.push(key);
304
+ }
305
+ });
306
+ } else if (isMembersCollection) {
307
+ logger.error(
308
+ `Skipping user & contact creation for ${item} due to lack of email...`
309
+ );
310
+ }
323
311
 
324
- if (
325
- !(await this.importDataActions.validateItem(
312
+ context = { ...context, ...finalItem };
313
+ const validated = await this.importDataActions.validateItem(
326
314
  finalItem,
327
315
  importDef.attributeMappings,
328
316
  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
317
  );
346
- if (createdContext) {
347
- afterContext = createdContext;
318
+ if (!validated) {
319
+ console.error("Validation failed for item:", finalItem);
320
+ logger.error("Validation failed for item:", finalItem);
321
+ return;
348
322
  }
349
- logger.info(`Handled create for ${context.docId}}`);
350
- } else {
351
- const updatedContext = await this.handleUpdate(
352
- context,
353
- finalItem,
354
- importDef
355
- );
356
- if (updatedContext) {
357
- afterContext = updatedContext;
323
+
324
+ if (
325
+ (importDef.type === "create" || !importDef.type) &&
326
+ !associatedDoc
327
+ ) {
328
+ const createdContext = await this.handleCreate(
329
+ context,
330
+ finalItem,
331
+ updateDefs,
332
+ createIdToUse
333
+ );
334
+ context = { ...context, ...createdContext };
335
+ } else {
336
+ const updatedContext = await this.handleUpdate(
337
+ context,
338
+ finalItem,
339
+ importDef
340
+ );
341
+ context = { ...context, ...updatedContext };
358
342
  }
359
- logger.info(`Handled update for ${context.docId}`);
360
- }
361
- if (afterContext) {
362
- context = { ...context, ...afterContext };
363
- }
364
- const afterImportActionContext = structuredClone(context);
365
- const attributeMappingsWithActions =
366
- this.getAttributeMappingsWithActions(
367
- importDef.attributeMappings,
368
- context,
369
- finalItem
370
- );
371
- if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
372
- logger.info(
373
- `Pushing to post-import actions queue for ${context.docId}`
374
- );
375
- const afterImportOperationContext = ContextObject.parse({
376
- dbId: db.$id,
377
- collectionId: collection.$id,
378
- finalItem: finalItem,
379
- attributeMappings: attributeMappingsWithActions,
380
- context: afterImportActionContext,
381
- });
382
- await createOrFindAfterImportOperation(
383
- this.database,
384
- context.collId,
385
- afterImportOperationContext
386
- );
387
- // this.postImportActionsQueue.push({
388
- // context: afterImportActionContext,
389
- // finalItem: finalItem,
390
- // attributeMappings: attributeMappingsWithActions,
391
- // });
343
+ const afterImportActionContext = structuredClone(context);
344
+ const attributeMappingsWithActions =
345
+ this.getAttributeMappingsWithActions(
346
+ importDef.attributeMappings,
347
+ afterImportActionContext,
348
+ finalItem
349
+ );
350
+ if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
351
+ logger.info(
352
+ `Pushing to post-import actions queue for ${context.docId}`
353
+ );
354
+ const afterImportOperationContext = ContextObject.parse({
355
+ dbId: db.$id,
356
+ collectionId: collection.$id,
357
+ finalItem: finalItem,
358
+ attributeMappings: attributeMappingsWithActions,
359
+ context: afterImportActionContext,
360
+ });
361
+ await createOrFindAfterImportOperation(
362
+ this.database,
363
+ context.collId,
364
+ afterImportOperationContext
365
+ );
366
+ // this.postImportActionsQueue.push({
367
+ // context: afterImportActionContext,
368
+ // finalItem: finalItem,
369
+ // attributeMappings: attributeMappingsWithActions,
370
+ // });
371
+ }
372
+ })
373
+ );
374
+ results.forEach((result) => {
375
+ if (result.status === "rejected") {
376
+ console.error("A process batch promise was rejected:", result.reason);
377
+ logger.error("An error occurred during creation: ", result.reason);
392
378
  }
393
379
  });
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
380
  }
401
381
  }
402
382
 
@@ -413,9 +393,6 @@ export class ImportController {
413
393
  finalItem
414
394
  );
415
395
  if (!existing) {
416
- if (id) {
417
- console.log(`Creating document with provided ID (member): ${id}`);
418
- }
419
396
  const createdDoc = await this.database.createDocument(
420
397
  context.dbId,
421
398
  context.collId,
@@ -437,8 +414,6 @@ export class ImportController {
437
414
  }
438
415
  });
439
416
  }
440
-
441
- console.log(`Created document ID: ${createdDoc.$id}`);
442
417
  return context;
443
418
  } else {
444
419
  console.log("Document already exists, skipping creation.");
@@ -572,6 +547,16 @@ export class ImportController {
572
547
  for (const batch of resultsData) {
573
548
  const actionOperation = ContextObject.parse(JSON.parse(batch.data));
574
549
  const { context, finalItem, attributeMappings } = actionOperation;
550
+ if (finalItem.$id && !context.docId) {
551
+ context.docId = finalItem.$id;
552
+ logger.info(
553
+ `Setting docId to ${
554
+ finalItem.$id
555
+ } because docId not found in context, batch ${
556
+ batch.$id
557
+ }, context is ${JSON.stringify(context)}`
558
+ );
559
+ }
575
560
  try {
576
561
  await this.importDataActions.executeAfterImportActions(
577
562
  finalItem,
@@ -584,12 +569,18 @@ export class ImportController {
584
569
  "batches",
585
570
  batch.$id
586
571
  );
587
- await updateOperation(this.database, operation.$id, {
588
- status: "completed",
589
- batches: [],
590
- });
591
572
  } catch (error) {
592
- logger.error(`Failed to execute batch ${batch.$id}:`, error);
573
+ logger.error(
574
+ `Failed to execute batch ${batch.$id}:`,
575
+ error,
576
+ "Context is :",
577
+ context
578
+ );
579
+ await this.database.deleteDocument(
580
+ "migrations",
581
+ "batches",
582
+ batch.$id
583
+ );
593
584
  }
594
585
  }
595
586
 
@@ -2,7 +2,7 @@ import winston from "winston";
2
2
 
3
3
  export const logger = winston.createLogger({
4
4
  level: "info",
5
- format: winston.format.prettyPrint(),
5
+ format: winston.format.json({ space: 2 }),
6
6
  defaultMeta: { service: "appwrite-utils-cli" },
7
7
  transports: [
8
8
  //
@@ -10,6 +10,7 @@ export const logger = winston.createLogger({
10
10
  // - Write all logs with importance level of `info` or less to `combined.log`
11
11
  //
12
12
  new winston.transports.File({ filename: "error.log", level: "error" }),
13
+ new winston.transports.File({ filename: "warn.log", level: "warn" }),
13
14
  new winston.transports.File({ filename: "combined.log" }),
14
15
  ],
15
16
  });