appwrite-utils-cli 0.9.5 → 0.9.52

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 (38) hide show
  1. package/README.md +2 -0
  2. package/dist/collections/attributes.js +4 -4
  3. package/dist/collections/indexes.js +3 -1
  4. package/dist/collections/methods.js +13 -7
  5. package/dist/interactiveCLI.js +28 -14
  6. package/dist/migrations/transfer.js +86 -52
  7. package/dist/utils/helperFunctions.d.ts +1 -0
  8. package/dist/utils/helperFunctions.js +1 -0
  9. package/package.json +53 -53
  10. package/src/collections/attributes.ts +5 -4
  11. package/src/collections/indexes.ts +3 -1
  12. package/src/collections/methods.ts +17 -7
  13. package/src/interactiveCLI.ts +41 -15
  14. package/src/migrations/transfer.ts +160 -95
  15. package/src/utils/helperFunctions.ts +3 -0
  16. package/zlogs/announcements.json +397 -0
  17. package/zlogs/announcementscomments.json +36 -0
  18. package/zlogs/articles.json +138 -0
  19. package/zlogs/articlescomments.json +4 -0
  20. package/zlogs/businesscategories.json +7097 -0
  21. package/zlogs/contacts.json +517063 -0
  22. package/zlogs/contactscouncils.json +61905 -0
  23. package/zlogs/contactssociallinks.json +13776 -0
  24. package/zlogs/councils.json +5076 -0
  25. package/zlogs/documents.json +917 -0
  26. package/zlogs/emails.json +4 -0
  27. package/zlogs/events.json +132625 -0
  28. package/zlogs/knowledgebase.json +333 -0
  29. package/zlogs/knowledgebasecomments.json +4 -0
  30. package/zlogs/linkcategories.json +180 -0
  31. package/zlogs/links.json +4364 -0
  32. package/zlogs/memberrequests.json +83 -0
  33. package/zlogs/memberrequestscomments.json +65 -0
  34. package/zlogs/mergedUserMap.json +56 -0
  35. package/zlogs/oldIdToNewIdPerCollectionMap.json +27663 -0
  36. package/zlogs/regions.json +145 -0
  37. package/zlogs/testimonials.json +335 -0
  38. package/zlogs/users.json +25516 -0
@@ -558,14 +558,14 @@ export class InteractiveCLI {
558
558
  },
559
559
  ]);
560
560
 
561
- const { checkDuplicates } = await inquirer.prompt([
562
- {
563
- type: "confirm",
564
- name: "checkDuplicates",
565
- message: "Do you want to check for duplicates during import?",
566
- default: true,
567
- },
568
- ]);
561
+ // const { checkDuplicates } = await inquirer.prompt([
562
+ // {
563
+ // type: "confirm",
564
+ // name: "checkDuplicates",
565
+ // message: "Do you want to check for duplicates during import?",
566
+ // default: true,
567
+ // },
568
+ // ]);
569
569
 
570
570
  console.log("Importing data...");
571
571
  await this.controller!.importData({
@@ -575,7 +575,8 @@ export class InteractiveCLI {
575
575
  ? selectedCollections.map((c) => c.$id)
576
576
  : undefined,
577
577
  shouldWriteFile,
578
- checkDuplicates,
578
+ checkDuplicates: false,
579
+ // checkDuplicates,
579
580
  });
580
581
  }
581
582
 
@@ -637,19 +638,42 @@ export class InteractiveCLI {
637
638
  targetDatabases = await fetchAllDatabases(targetClient);
638
639
  } else {
639
640
  targetClient = sourceClient;
640
- sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient);
641
+ const allDatabases = await fetchAllDatabases(sourceClient);
642
+ sourceDatabases = targetDatabases = allDatabases;
641
643
  }
642
644
 
643
- const [fromDb] = await this.selectDatabases(
645
+ const fromDbs = await this.selectDatabases(
644
646
  sourceDatabases,
645
647
  "Select the source database:",
646
648
  false
647
649
  );
648
- const [targetDb] = await this.selectDatabases(
649
- targetDatabases.filter((db) => db.$id !== fromDb.$id),
650
+ console.log(fromDbs);
651
+ const fromDb = fromDbs as unknown as {
652
+ $id: string;
653
+ name: string;
654
+ $createdAt: string;
655
+ $updatedAt: string;
656
+ enabled: boolean;
657
+ };
658
+ if (!fromDb) {
659
+ throw new Error("No source database selected");
660
+ }
661
+ const availableDbs = targetDatabases.filter((db) => db.$id !== fromDb.$id);
662
+ const targetDbs = await this.selectDatabases(
663
+ availableDbs,
650
664
  "Select the target database:",
651
665
  false
652
666
  );
667
+ const targetDb = targetDbs as unknown as {
668
+ $id: string;
669
+ name: string;
670
+ $createdAt: string;
671
+ $updatedAt: string;
672
+ enabled: boolean;
673
+ };
674
+ if (!targetDb) {
675
+ throw new Error("No target database selected");
676
+ }
653
677
 
654
678
  const selectedCollections = await this.selectCollections(
655
679
  fromDb,
@@ -685,16 +709,18 @@ export class InteractiveCLI {
685
709
  ? await listBuckets(targetStorage)
686
710
  : sourceBuckets;
687
711
 
688
- [sourceBucket] = await this.selectBuckets(
712
+ const sourceBucketPicked = await this.selectBuckets(
689
713
  sourceBuckets.buckets,
690
714
  "Select the source bucket:",
691
715
  false
692
716
  );
693
- [targetBucket] = await this.selectBuckets(
717
+ const targetBucketPicked = await this.selectBuckets(
694
718
  targetBuckets.buckets,
695
719
  "Select the target bucket:",
696
720
  false
697
721
  );
722
+ sourceBucket = sourceBucketPicked as unknown as Models.Bucket;
723
+ targetBucket = targetBucketPicked as unknown as Models.Bucket;
698
724
  }
699
725
 
700
726
  let transferOptions: TransferOptions = {
@@ -194,16 +194,42 @@ export const transferDocumentsBetweenDbsLocalToLocal = async (
194
194
  fromCollId: string,
195
195
  toCollId: string
196
196
  ) => {
197
- let fromCollDocs = await tryAwaitWithRetry(async () =>
198
- db.listDocuments(fromDbId, fromCollId, [Query.limit(50)])
199
- );
200
197
  let totalDocumentsTransferred = 0;
198
+ let lastDocumentId: string | undefined;
199
+ let hasMoreDocuments = true;
201
200
 
202
- if (fromCollDocs.documents.length === 0) {
203
- console.log(`No documents found in collection ${fromCollId}`);
204
- return;
205
- } else if (fromCollDocs.documents.length < 50) {
206
- const batchedPromises = fromCollDocs.documents.map((doc) => {
201
+ while (hasMoreDocuments) {
202
+ const queryParams = [Query.limit(50)];
203
+ if (lastDocumentId) {
204
+ queryParams.push(Query.cursorAfter(lastDocumentId));
205
+ }
206
+
207
+ const fromCollDocs = await tryAwaitWithRetry(async () =>
208
+ db.listDocuments(fromDbId, fromCollId, queryParams)
209
+ );
210
+
211
+ if (fromCollDocs.documents.length === 0) {
212
+ if (totalDocumentsTransferred === 0) {
213
+ console.log(`No documents found in collection ${fromCollId}`);
214
+ }
215
+ break;
216
+ }
217
+
218
+ const allDocsToCreateCheck = await tryAwaitWithRetry(
219
+ async () =>
220
+ await db.listDocuments(toDbId, toCollId, [
221
+ Query.equal(
222
+ "$id",
223
+ fromCollDocs.documents.map((doc) => doc.$id)
224
+ ),
225
+ ])
226
+ );
227
+
228
+ const docsToCreate = fromCollDocs.documents.filter(
229
+ (doc) => !allDocsToCreateCheck.documents.some((d) => d.$id === doc.$id)
230
+ );
231
+
232
+ const batchedPromises = docsToCreate.map((doc) => {
207
233
  const toCreateObject: Partial<typeof doc> = {
208
234
  ...doc,
209
235
  };
@@ -224,64 +250,15 @@ export const transferDocumentsBetweenDbsLocalToLocal = async (
224
250
  )
225
251
  );
226
252
  });
253
+
227
254
  await Promise.all(batchedPromises);
228
- totalDocumentsTransferred += fromCollDocs.documents.length;
229
- } else {
230
- const batchedPromises = fromCollDocs.documents.map((doc) => {
231
- const toCreateObject: Partial<typeof doc> = {
232
- ...doc,
233
- };
234
- delete toCreateObject.$databaseId;
235
- delete toCreateObject.$collectionId;
236
- delete toCreateObject.$createdAt;
237
- delete toCreateObject.$updatedAt;
238
- delete toCreateObject.$id;
239
- delete toCreateObject.$permissions;
240
- return tryAwaitWithRetry(async () =>
241
- db.createDocument(
242
- toDbId,
243
- toCollId,
244
- doc.$id,
245
- toCreateObject,
246
- doc.$permissions
247
- )
248
- );
249
- });
250
- await Promise.all(batchedPromises);
251
- totalDocumentsTransferred += fromCollDocs.documents.length;
252
- while (fromCollDocs.documents.length === 50) {
253
- fromCollDocs = await tryAwaitWithRetry(
254
- async () =>
255
- await db.listDocuments(fromDbId, fromCollId, [
256
- Query.limit(50),
257
- Query.cursorAfter(
258
- fromCollDocs.documents[fromCollDocs.documents.length - 1].$id
259
- ),
260
- ])
261
- );
262
- const batchedPromises = fromCollDocs.documents.map((doc) => {
263
- const toCreateObject: Partial<typeof doc> = {
264
- ...doc,
265
- };
266
- delete toCreateObject.$databaseId;
267
- delete toCreateObject.$collectionId;
268
- delete toCreateObject.$createdAt;
269
- delete toCreateObject.$updatedAt;
270
- delete toCreateObject.$id;
271
- delete toCreateObject.$permissions;
272
- return tryAwaitWithRetry(
273
- async () =>
274
- await db.createDocument(
275
- toDbId,
276
- toCollId,
277
- doc.$id,
278
- toCreateObject,
279
- doc.$permissions
280
- )
281
- );
282
- });
283
- await Promise.all(batchedPromises);
284
- totalDocumentsTransferred += fromCollDocs.documents.length;
255
+ totalDocumentsTransferred += docsToCreate.length;
256
+
257
+ if (fromCollDocs.documents.length < 50) {
258
+ hasMoreDocuments = false;
259
+ } else {
260
+ lastDocumentId =
261
+ fromCollDocs.documents[fromCollDocs.documents.length - 1].$id;
285
262
  }
286
263
  }
287
264
 
@@ -314,7 +291,19 @@ export const transferDocumentsBetweenDbsLocalToRemote = async (
314
291
  console.log(`No documents found in collection ${fromCollId}`);
315
292
  return;
316
293
  } else if (fromCollDocs.documents.length < 50) {
317
- const batchedPromises = fromCollDocs.documents.map((doc) => {
294
+ const allDocsToCreateCheck = await tryAwaitWithRetry(
295
+ async () =>
296
+ await remoteDb.listDocuments(toDbId, toCollId, [
297
+ Query.equal(
298
+ "$id",
299
+ fromCollDocs.documents.map((doc) => doc.$id)
300
+ ),
301
+ ])
302
+ );
303
+ const docsToCreate = fromCollDocs.documents.filter(
304
+ (doc) => !allDocsToCreateCheck.documents.some((d) => d.$id === doc.$id)
305
+ );
306
+ const batchedPromises = docsToCreate.map((doc) => {
318
307
  const toCreateObject: Partial<typeof doc> = {
319
308
  ...doc,
320
309
  };
@@ -337,7 +326,19 @@ export const transferDocumentsBetweenDbsLocalToRemote = async (
337
326
  await Promise.all(batchedPromises);
338
327
  totalDocumentsTransferred += fromCollDocs.documents.length;
339
328
  } else {
340
- const batchedPromises = fromCollDocs.documents.map((doc) => {
329
+ const allDocsToCreateCheck = await tryAwaitWithRetry(
330
+ async () =>
331
+ await remoteDb.listDocuments(toDbId, toCollId, [
332
+ Query.equal(
333
+ "$id",
334
+ fromCollDocs.documents.map((doc) => doc.$id)
335
+ ),
336
+ ])
337
+ );
338
+ const docsToCreate = fromCollDocs.documents.filter(
339
+ (doc) => !allDocsToCreateCheck.documents.some((d) => d.$id === doc.$id)
340
+ );
341
+ const batchedPromises = docsToCreate.map((doc) => {
341
342
  const toCreateObject: Partial<typeof doc> = {
342
343
  ...doc,
343
344
  };
@@ -533,8 +534,7 @@ export const transferDatabaseLocalToRemote = async (
533
534
  async () => await localDb.listCollections(fromDbId, [Query.limit(50)])
534
535
  );
535
536
  const allFromCollections = fromCollections.collections;
536
- if (fromCollections.collections.length < 50) {
537
- } else {
537
+ if (fromCollections.collections.length >= 50) {
538
538
  lastCollectionId =
539
539
  fromCollections.collections[fromCollections.collections.length - 1].$id;
540
540
  while (lastCollectionId) {
@@ -555,43 +555,108 @@ export const transferDatabaseLocalToRemote = async (
555
555
  }
556
556
 
557
557
  for (const collection of allFromCollections) {
558
- const toCollection = await tryAwaitWithRetry(
558
+ let toCollection: Models.Collection;
559
+ const toCollectionExists = await tryAwaitWithRetry(
559
560
  async () =>
560
- await remoteDb.createCollection(
561
- toDbId,
562
- collection.$id,
563
- collection.name,
564
- collection.$permissions,
565
- collection.documentSecurity,
566
- collection.enabled
567
- )
561
+ await remoteDb.listCollections(toDbId, [
562
+ Query.equal("$id", collection.$id),
563
+ ])
568
564
  );
569
- console.log(`Collection ${toCollection.name} created`);
570
565
 
571
- for (const attribute of collection.attributes) {
572
- await tryAwaitWithRetry(
566
+ if (toCollectionExists.collections.length > 0) {
567
+ console.log(`Collection ${collection.name} already exists. Updating...`);
568
+ toCollection = toCollectionExists.collections[0];
569
+ // Update collection if needed
570
+ if (
571
+ toCollection.name !== collection.name ||
572
+ toCollection.$permissions !== collection.$permissions ||
573
+ toCollection.documentSecurity !== collection.documentSecurity ||
574
+ toCollection.enabled !== collection.enabled
575
+ ) {
576
+ toCollection = await tryAwaitWithRetry(
577
+ async () =>
578
+ await remoteDb.updateCollection(
579
+ toDbId,
580
+ collection.$id,
581
+ collection.name,
582
+ collection.$permissions,
583
+ collection.documentSecurity,
584
+ collection.enabled
585
+ )
586
+ );
587
+ console.log(`Collection ${toCollection.name} updated`);
588
+ }
589
+ } else {
590
+ toCollection = await tryAwaitWithRetry(
573
591
  async () =>
574
- await createOrUpdateAttribute(
575
- remoteDb,
592
+ await remoteDb.createCollection(
576
593
  toDbId,
577
- toCollection,
578
- parseAttribute(attribute as any)
594
+ collection.$id,
595
+ collection.name,
596
+ collection.$permissions,
597
+ collection.documentSecurity,
598
+ collection.enabled
579
599
  )
580
600
  );
601
+ console.log(`Collection ${toCollection.name} created`);
602
+ }
603
+
604
+ // Check and update attributes
605
+ const existingAttributes = await tryAwaitWithRetry(
606
+ async () => await remoteDb.listAttributes(toDbId, toCollection.$id)
607
+ );
608
+ for (const attribute of collection.attributes) {
609
+ const parsedAttribute = parseAttribute(attribute as any);
610
+ const existingAttribute = existingAttributes.attributes.find(
611
+ // @ts-expect-error
612
+ (attr) => attr.key === parsedAttribute.key
613
+ );
614
+ if (!existingAttribute) {
615
+ await tryAwaitWithRetry(
616
+ async () =>
617
+ await createOrUpdateAttribute(
618
+ remoteDb,
619
+ toDbId,
620
+ toCollection,
621
+ parsedAttribute
622
+ )
623
+ );
624
+ console.log(`Attribute ${parsedAttribute.key} created`);
625
+ } else {
626
+ // Check if attribute needs updating
627
+ // Note: Appwrite doesn't allow updating most attribute properties
628
+ // You might need to delete and recreate the attribute if significant changes are needed
629
+ console.log(`Attribute ${parsedAttribute.key} already exists`);
630
+ }
581
631
  }
582
632
 
633
+ // Check and update indexes
634
+ const existingIndexes = await tryAwaitWithRetry(
635
+ async () => await remoteDb.listIndexes(toDbId, toCollection.$id)
636
+ );
583
637
  for (const index of collection.indexes) {
584
- await tryAwaitWithRetry(
585
- async () =>
586
- await remoteDb.createIndex(
587
- toDbId,
588
- toCollection.$id,
589
- index.key,
590
- index.type as IndexType,
591
- index.attributes,
592
- index.orders
593
- )
638
+ const existingIndex = existingIndexes.indexes.find(
639
+ (idx) => idx.key === index.key
594
640
  );
641
+ if (!existingIndex) {
642
+ await tryAwaitWithRetry(
643
+ async () =>
644
+ await remoteDb.createIndex(
645
+ toDbId,
646
+ toCollection.$id,
647
+ index.key,
648
+ index.type as IndexType,
649
+ index.attributes,
650
+ index.orders
651
+ )
652
+ );
653
+ console.log(`Index ${index.key} created`);
654
+ } else {
655
+ // Check if index needs updating
656
+ // Note: Appwrite doesn't allow updating indexes
657
+ // You might need to delete and recreate the index if changes are needed
658
+ console.log(`Index ${index.key} already exists`);
659
+ }
595
660
  }
596
661
 
597
662
  await transferDocumentsBetweenDbsLocalToRemote(
@@ -179,3 +179,6 @@ export const getAppwriteClient = (
179
179
  .setProject(projectId)
180
180
  .setKey(apiKey);
181
181
  };
182
+
183
+ export const delay = (ms: number) =>
184
+ new Promise((resolve) => setTimeout(resolve, ms));