appwrite-utils-cli 0.0.46 → 0.0.48

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.
@@ -7,6 +7,9 @@ import { logger } from "./logging.js";
7
7
  import { updateOperation } from "./migrationHelper.js";
8
8
  import { BatchSchema, OperationCreateSchema, OperationSchema, } from "./backup.js";
9
9
  import { DataLoader } from "./dataLoader.js";
10
+ import { transferDocumentsBetweenDbsLocalToLocal } from "./collections.js";
11
+ import { transferDatabaseLocalToLocal } from "./databases.js";
12
+ import { transferStorageLocalToLocal } from "./storage.js";
10
13
  export class ImportController {
11
14
  config;
12
15
  database;
@@ -37,6 +40,7 @@ export class ImportController {
37
40
  this.setupOptions.runDev))
38
41
  .map((db) => db.name);
39
42
  let dataLoader;
43
+ let databaseRan;
40
44
  for (let db of this.config.databases) {
41
45
  if (db.name.toLowerCase().trim().replace(" ", "") === "migrations" ||
42
46
  !databasesToRun.includes(db.name)) {
@@ -54,21 +58,30 @@ export class ImportController {
54
58
  console.log(`Starting import data for database: ${db.name}`);
55
59
  console.log(`---------------------------------`);
56
60
  // await this.importCollections(db);
57
- if (!dataLoader) {
61
+ if (!databaseRan) {
62
+ databaseRan = db;
58
63
  dataLoader = new DataLoader(this.appwriteFolderPath, this.importDataActions, this.database, this.config, this.setupOptions.shouldWriteFile);
59
64
  await dataLoader.start(db.$id);
65
+ await this.importCollections(db, dataLoader);
66
+ await resolveAndUpdateRelationships(db.$id, this.database, this.config);
67
+ await this.executePostImportActions(db.$id, dataLoader);
60
68
  }
61
- else {
62
- console.log(`Using data from previous import run`);
69
+ else if (databaseRan.$id !== db.$id) {
70
+ await this.updateOthersToFinalData(databaseRan, db);
63
71
  }
64
- await this.importCollections(db, dataLoader);
65
- await resolveAndUpdateRelationships(db.$id, this.database, this.config);
66
- await this.executePostImportActions(db.$id, dataLoader);
67
72
  console.log(`---------------------------------`);
68
73
  console.log(`Finished import data for database: ${db.name}`);
69
74
  console.log(`---------------------------------`);
70
75
  }
71
76
  }
77
+ async updateOthersToFinalData(updatedDb, targetDb) {
78
+ await transferDatabaseLocalToLocal(this.database, updatedDb.$id, targetDb.$id);
79
+ await transferStorageLocalToLocal(this.storage, `${this.config.documentBucketId}_${updatedDb.name
80
+ .toLowerCase()
81
+ .replace(" ", "")}`, `${this.config.documentBucketId}_${targetDb.name
82
+ .toLowerCase()
83
+ .replace(" ", "")}`);
84
+ }
72
85
  async importCollections(db, dataLoader) {
73
86
  if (!this.config.collections) {
74
87
  return;
@@ -6,3 +6,5 @@ export declare const initOrGetBackupStorage: (storage: Storage) => Promise<Model
6
6
  export declare const initOrGetDocumentStorage: (storage: Storage, config: AppwriteConfig, dbName: string) => Promise<Models.Bucket | undefined>;
7
7
  export declare const wipeDocumentStorage: (storage: Storage, config: AppwriteConfig, dbName: string) => Promise<void>;
8
8
  export declare const backupDatabase: (database: Databases, databaseId: string, storage: Storage) => Promise<void>;
9
+ export declare const transferStorageLocalToLocal: (storage: Storage, fromBucketId: string, toBucketId: string) => Promise<void>;
10
+ export declare const transferStorageLocalToRemote: (localStorage: Storage, endpoint: string, projectId: string, apiKey: string, fromBucketId: string, toBucketId: string) => Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { Storage, Databases, Query, InputFile, ID, Permission, } from "node-appwrite";
2
2
  import {} from "./backup.js";
3
3
  import { splitIntoBatches } from "./migrationHelper.js";
4
- import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
4
+ import { getAppwriteClient, tryAwaitWithRetry, } from "../utils/helperFunctions.js";
5
5
  export const logOperation = async (db, dbId, operationDetails, operationId) => {
6
6
  try {
7
7
  let operation;
@@ -245,3 +245,70 @@ export const backupDatabase = async (database, databaseId, storage) => {
245
245
  console.log("Database Backup Complete");
246
246
  console.log("---------------------------------");
247
247
  };
248
+ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucketId) => {
249
+ console.log(`Transferring files from ${fromBucketId} to ${toBucketId}`);
250
+ let lastFileId;
251
+ let fromFiles = await tryAwaitWithRetry(async () => await storage.listFiles(fromBucketId, [Query.limit(100)]));
252
+ const allFromFiles = fromFiles.files;
253
+ let numberOfFiles = 0;
254
+ if (fromFiles.files.length < 100) {
255
+ for (const file of allFromFiles) {
256
+ const fileData = await storage.getFileDownload(file.bucketId, file.$id);
257
+ const fileToCreate = InputFile.fromBuffer(Buffer.from(fileData), file.name);
258
+ console.log(`Creating file: ${file.name}`);
259
+ tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
260
+ numberOfFiles++;
261
+ }
262
+ }
263
+ else {
264
+ lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
265
+ while (lastFileId) {
266
+ const files = await storage.listFiles(fromBucketId, [
267
+ Query.limit(100),
268
+ Query.cursorAfter(lastFileId),
269
+ ]);
270
+ allFromFiles.push(...files.files);
271
+ if (files.files.length < 100) {
272
+ lastFileId = undefined;
273
+ }
274
+ else {
275
+ lastFileId = files.files[files.files.length - 1].$id;
276
+ }
277
+ }
278
+ for (const file of allFromFiles) {
279
+ const fileData = await storage.getFileDownload(file.bucketId, file.$id);
280
+ const fileToCreate = InputFile.fromBuffer(Buffer.from(fileData), file.name);
281
+ await tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
282
+ numberOfFiles++;
283
+ }
284
+ }
285
+ console.log(`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`);
286
+ };
287
+ export const transferStorageLocalToRemote = async (localStorage, endpoint, projectId, apiKey, fromBucketId, toBucketId) => {
288
+ console.log(`Transferring files from current storage ${fromBucketId} to ${endpoint} bucket ${toBucketId}`);
289
+ const client = getAppwriteClient(endpoint, apiKey, projectId);
290
+ const remoteStorage = new Storage(client);
291
+ let numberOfFiles = 0;
292
+ let lastFileId;
293
+ let fromFiles = await tryAwaitWithRetry(async () => await localStorage.listFiles(fromBucketId, [Query.limit(100)]));
294
+ const allFromFiles = fromFiles.files;
295
+ if (fromFiles.files.length === 100) {
296
+ lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
297
+ while (lastFileId) {
298
+ const files = await localStorage.listFiles(fromBucketId, [
299
+ Query.limit(100),
300
+ Query.cursorAfter(lastFileId),
301
+ ]);
302
+ allFromFiles.push(...files.files);
303
+ if (files.files.length < 100) {
304
+ break;
305
+ }
306
+ lastFileId = files.files[files.files.length - 1].$id;
307
+ }
308
+ }
309
+ for (const file of allFromFiles) {
310
+ await tryAwaitWithRetry(async () => await remoteStorage.createFile(toBucketId, file.$id, file, file.$permissions));
311
+ numberOfFiles++;
312
+ }
313
+ console.log(`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`);
314
+ };
@@ -12,4 +12,5 @@ export declare class UsersController {
12
12
  createUserAndReturn(item: AuthUserCreate, numAttempts?: number): Promise<Models.User<Models.Preferences>>;
13
13
  createAndCheckForUserAndReturn(item: AuthUserCreate): Promise<Models.User<Models.Preferences> | undefined>;
14
14
  getUserIdByEmailOrPhone(email?: string, phone?: string): Promise<string | undefined>;
15
+ transferUsersBetweenDbsLocalToRemote: (endpoint: string, projectId: string, apiKey: string) => Promise<void>;
15
16
  }
@@ -3,7 +3,7 @@ import { AuthUserSchema, } from "../schemas/authUser.js";
3
3
  import _ from "lodash";
4
4
  import { logger } from "./logging.js";
5
5
  import { splitIntoBatches } from "./migrationHelper.js";
6
- import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
6
+ import { getAppwriteClient, tryAwaitWithRetry, } from "../utils/helperFunctions.js";
7
7
  export class UsersController {
8
8
  config;
9
9
  users;
@@ -232,4 +232,49 @@ export class UsersController {
232
232
  }
233
233
  }
234
234
  }
235
+ transferUsersBetweenDbsLocalToRemote = async (endpoint, projectId, apiKey) => {
236
+ const localUsers = this.users;
237
+ const client = getAppwriteClient(endpoint, projectId, apiKey);
238
+ const remoteUsers = new Users(client);
239
+ let fromUsers = await localUsers.list([Query.limit(50)]);
240
+ if (fromUsers.users.length === 0) {
241
+ console.log(`No users found`);
242
+ return;
243
+ }
244
+ else if (fromUsers.users.length < 50) {
245
+ console.log(`Transferring ${fromUsers.users.length} users to remote`);
246
+ const batchedPromises = fromUsers.users.map((user) => {
247
+ return tryAwaitWithRetry(async () => {
248
+ const toCreateObject = {
249
+ ...user,
250
+ };
251
+ delete toCreateObject.$id;
252
+ delete toCreateObject.$createdAt;
253
+ delete toCreateObject.$updatedAt;
254
+ await remoteUsers.create(user.$id, user.email, user.phone, user.password, user.name);
255
+ });
256
+ });
257
+ await Promise.all(batchedPromises);
258
+ }
259
+ else {
260
+ while (fromUsers.users.length === 50) {
261
+ fromUsers = await localUsers.list([
262
+ Query.limit(50),
263
+ Query.cursorAfter(fromUsers.users[fromUsers.users.length - 1].$id),
264
+ ]);
265
+ const batchedPromises = fromUsers.users.map((user) => {
266
+ return tryAwaitWithRetry(async () => {
267
+ const toCreateObject = {
268
+ ...user,
269
+ };
270
+ delete toCreateObject.$id;
271
+ delete toCreateObject.$createdAt;
272
+ delete toCreateObject.$updatedAt;
273
+ await remoteUsers.create(user.$id, user.email, user.phone, user.password, user.name);
274
+ });
275
+ });
276
+ await Promise.all(batchedPromises);
277
+ }
278
+ }
279
+ };
235
280
  }
@@ -1,4 +1,4 @@
1
- import { type Models } from "node-appwrite";
1
+ import { Client, type Models } from "node-appwrite";
2
2
  import type { CollectionImportData } from "../migrations/dataLoader.js";
3
3
  import type { ConfigCollection } from "appwrite-utils";
4
4
  export declare const toPascalCase: (str: string) => string;
@@ -44,3 +44,4 @@ export declare let numTimesFailedTotal: number;
44
44
  * @return {Promise<any>} - A promise that resolves to the result of the createFunction or rejects with an error if it fails after 5 attempts.
45
45
  */
46
46
  export declare const tryAwaitWithRetry: <T>(createFunction: () => Promise<T>, attemptNum?: number, throwError?: boolean) => Promise<T>;
47
+ export declare const getAppwriteClient: (endpoint: string, projectId: string, apiKey: string) => Client;
@@ -1,4 +1,4 @@
1
- import { AppwriteException } from "node-appwrite";
1
+ import { AppwriteException, Client, } from "node-appwrite";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  export const toPascalCase = (str) => {
@@ -109,3 +109,9 @@ export const tryAwaitWithRetry = async (createFunction, attemptNum = 0, throwErr
109
109
  return Promise.resolve();
110
110
  }
111
111
  };
112
+ export const getAppwriteClient = (endpoint, projectId, apiKey) => {
113
+ return new Client()
114
+ .setEndpoint(endpoint)
115
+ .setProject(projectId)
116
+ .setKey(apiKey);
117
+ };
@@ -16,6 +16,17 @@ export interface SetupOptions {
16
16
  endpoint?: string;
17
17
  project?: string;
18
18
  key?: string;
19
+ transfer?: boolean;
20
+ transferEndpoint?: string;
21
+ transferProject?: string;
22
+ transferKey?: string;
23
+ fromDbId?: string;
24
+ targetDbId?: string;
25
+ fromCollection?: string;
26
+ collection?: string;
27
+ transferUsers?: boolean;
28
+ fromBucket?: string;
29
+ targetBucket?: string;
19
30
  }
20
31
  export declare class UtilsController {
21
32
  private appwriteFolderPath;
@@ -12,6 +12,10 @@ import _ from "lodash";
12
12
  import { AppwriteToX } from "./migrations/appwriteToX.js";
13
13
  import { loadConfig as loadTsConfig } from "./utils/loadConfigs.js";
14
14
  import { findAppwriteConfig } from "./utils/loadConfigs.js";
15
+ import { transferDocumentsBetweenDbsLocalToLocal, transferDocumentsBetweenDbsLocalToRemote, } from "./migrations/collections.js";
16
+ import { UsersController } from "./migrations/users.js";
17
+ import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, } from "./migrations/databases.js";
18
+ import { transferStorageLocalToLocal, transferStorageLocalToRemote, } from "./migrations/storage.js";
15
19
  export class UtilsController {
16
20
  appwriteFolderPath;
17
21
  appwriteConfigPath;
@@ -96,6 +100,56 @@ export class UtilsController {
96
100
  if (!this.database || !this.storage || !this.config) {
97
101
  throw new Error("Database or storage not initialized");
98
102
  }
103
+ if (options.transfer) {
104
+ if (options.fromCollection) {
105
+ if (options.transferEndpoint &&
106
+ options.transferProject &&
107
+ options.transferKey) {
108
+ if (options.transferUsers) {
109
+ console.log(`Transferring users from local database ${options.fromDbId} to remote database ${options.targetDbId} on endpoint ${options.transferEndpoint}...`);
110
+ const usersController = new UsersController(this.config, this.database);
111
+ await usersController.transferUsersBetweenDbsLocalToRemote(options.transferEndpoint, options.transferProject, options.transferKey);
112
+ }
113
+ console.log("Transferring documents to remote database...");
114
+ await transferDocumentsBetweenDbsLocalToRemote(this.database, options.transferEndpoint, options.transferProject, options.transferKey, options.fromDbId, options.targetDbId, options.fromCollection, options.collection);
115
+ }
116
+ else {
117
+ console.log("Transferring documents between local databases...");
118
+ await transferDocumentsBetweenDbsLocalToLocal(this.database, options.fromDbId, options.targetDbId, options.fromCollection, options.collection);
119
+ }
120
+ }
121
+ else if (options.fromDbId && options.targetDbId) {
122
+ if (options.transferEndpoint &&
123
+ options.transferProject &&
124
+ options.transferKey) {
125
+ if (options.transferUsers) {
126
+ console.log(`Transferring users from local database ${options.fromDbId} to remote database ${options.targetDbId} on endpoint ${options.transferEndpoint}...`);
127
+ const usersController = new UsersController(this.config, this.database);
128
+ await usersController.transferUsersBetweenDbsLocalToRemote(options.transferEndpoint, options.transferProject, options.transferKey);
129
+ }
130
+ console.log(`Transferring databases from local database ${options.fromDbId} to remote database ${options.targetDbId} on endpoint ${options.transferEndpoint}...`);
131
+ await transferDatabaseLocalToRemote(this.database, options.transferEndpoint, options.transferProject, options.transferKey, options.fromDbId, options.targetDbId);
132
+ }
133
+ else {
134
+ console.log(`Transferring databases from local database ${options.fromDbId} to local database ${options.targetDbId}`);
135
+ await transferDatabaseLocalToLocal(this.database, options.fromDbId, options.targetDbId);
136
+ }
137
+ }
138
+ if (options.fromBucket && options.targetBucket) {
139
+ if (options.transferEndpoint &&
140
+ options.transferProject &&
141
+ options.transferKey) {
142
+ console.log(`Transferring files from bucket ${options.fromBucket} to bucket ${options.targetBucket} on endpoint ${options.transferEndpoint}...`);
143
+ await transferStorageLocalToRemote(this.storage, options.transferEndpoint, options.transferProject, options.transferKey, options.fromBucket, options.targetBucket);
144
+ }
145
+ else {
146
+ console.log(`Transferring files from bucket ${options.fromBucket} to bucket ${options.targetBucket}...`);
147
+ await transferStorageLocalToLocal(this.storage, options.fromBucket, options.targetBucket);
148
+ }
149
+ }
150
+ console.log("Transfer complete.");
151
+ return;
152
+ }
99
153
  if (options.sync) {
100
154
  console.log("Starting synchronization with server...");
101
155
  const appwriteToX = new AppwriteToX(this.config, this.appwriteFolderPath);
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.46",
4
+ "version": "0.0.48",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@types/inquirer": "^9.0.7",
36
- "appwrite-utils": "^0.2.6",
36
+ "appwrite-utils": "^0.2.7",
37
37
  "commander": "^12.0.0",
38
38
  "inquirer": "^9.2.20",
39
39
  "js-yaml": "^4.1.0",
package/src/main.ts CHANGED
@@ -9,6 +9,45 @@ program
9
9
  .option("--endpoint <endpoint>", "Set the Appwrite endpoint", undefined)
10
10
  .option("--project <project>", "Set the Appwrite project ID", undefined)
11
11
  .option("--key <key>", "Set the Appwrite API key", undefined)
12
+ .option("--transfer", "Transfer documents between databases", false)
13
+ .option("--transfer-users", "Transfer users between local and remote", false)
14
+ .option(
15
+ "--transferendpoint <transferEndpoint>",
16
+ "Set the transfer endpoint for remote transfers",
17
+ undefined
18
+ )
19
+ .option(
20
+ "--transferproject <transferProject>",
21
+ "Set the transfer project ID for remote transfers",
22
+ undefined
23
+ )
24
+ .option(
25
+ "--transferkey <transferKey>",
26
+ "Set the transfer key for remote transfers",
27
+ undefined
28
+ )
29
+ .option("--fromdb <fromDbId>", "Set the source database ID", undefined)
30
+ .option(
31
+ "--targetdb <targetDbId>",
32
+ "Set the destination database ID",
33
+ undefined
34
+ )
35
+ .option(
36
+ "--fromcoll <collectionId>",
37
+ "Set the source collection ID for transfer, only used for transfer",
38
+ undefined
39
+ )
40
+ .option(
41
+ "--targetcoll <collectionId>",
42
+ "Set the collection ID to import data into",
43
+ undefined
44
+ )
45
+ .option("--frombucket <bucketId>", "Set the source bucket ID", undefined)
46
+ .option(
47
+ "--targetbucket <bucketId>",
48
+ "Set the destination bucket ID",
49
+ undefined
50
+ )
12
51
  .option("--backup", "Perform a backup before executing the command", false)
13
52
  .option("--dev", "Run in development environment", false)
14
53
  .option("--prod", "Run in production environment", false)
@@ -28,6 +67,9 @@ program.on("--help", () => {
28
67
  console.log(
29
68
  " $ npx appwrite-utils-cli appwrite-migrate --sync --endpoint https://appwrite.example.com --project 123456 --key 7890"
30
69
  );
70
+ console.log(
71
+ " $ npx appwrite-utils-cli appwrite-migrate --transfer --fromdb fromDbId --targetdb toDbId --transferendpoint https://appwrite.otherserver.com --transferproject yourProjectId --transferkey yourApiKey"
72
+ );
31
73
  console.log(
32
74
  " $ npx appwrite-utils-cli appwrite-migrate --sync --dev --backup"
33
75
  );
@@ -70,6 +112,17 @@ program.action(async (options) => {
70
112
  endpoint: options.endpoint,
71
113
  project: options.project,
72
114
  key: options.key,
115
+ transfer: options.transfer,
116
+ transferEndpoint: options.transferEndpoint,
117
+ transferProject: options.transferProject,
118
+ transferKey: options.transferKey,
119
+ fromDbId: options.fromdb,
120
+ targetDbId: options.targetdb,
121
+ fromCollection: options.fromcoll,
122
+ collection: options.targetcoll, // Add this line
123
+ transferUsers: options.transferUsers,
124
+ fromBucket: options.frombucket,
125
+ targetBucket: options.targetbucket,
73
126
  };
74
127
  console.log("Running operation...", setupOptions);
75
128
 
@@ -1,4 +1,11 @@
1
- import { Databases, ID, Permission, Query, type Models } from "node-appwrite";
1
+ import {
2
+ Client,
3
+ Databases,
4
+ ID,
5
+ Permission,
6
+ Query,
7
+ type Models,
8
+ } from "node-appwrite";
2
9
  import type { AppwriteConfig, CollectionCreate } from "appwrite-utils";
3
10
  import { nameToIdMapping, processQueue } from "./queue.js";
4
11
  import { createUpdateCollectionAttributes } from "./attributes.js";
@@ -133,7 +140,9 @@ export const wipeDatabase = async (
133
140
  collectionId: collectionId,
134
141
  collectionName: name,
135
142
  });
136
- await database.deleteCollection(databaseId, collectionId);
143
+ tryAwaitWithRetry(
144
+ async () => await database.deleteCollection(databaseId, collectionId)
145
+ ); // Try to delete the collection and ignore errors if it doesn't exist or if it's already being deleted
137
146
  }
138
147
  return collectionsDeleted;
139
148
  };
@@ -201,16 +210,16 @@ export const createOrUpdateCollections = async (
201
210
  let collectionId: string;
202
211
  if (!collectionToUse) {
203
212
  console.log(`Creating collection: ${collection.name}`);
204
- const foundColl = deletedCollections?.find(
213
+ let foundColl = deletedCollections?.find(
205
214
  (coll) =>
206
215
  coll.collectionName.toLowerCase().trim().replace(" ", "") ===
207
216
  collection.name.toLowerCase().trim().replace(" ", "")
208
217
  );
209
218
 
210
- if (foundColl && !usedIds.has(foundColl.collectionId)) {
219
+ if (collection.$id) {
220
+ collectionId = collection.$id; // Always use the provided $id if present
221
+ } else if (foundColl && !usedIds.has(foundColl.collectionId)) {
211
222
  collectionId = foundColl.collectionId; // Use ID from deleted collection if not already used
212
- } else if (collection.$id && !usedIds.has(collection.$id)) {
213
- collectionId = collection.$id; // Use the provided $id if not already used
214
223
  } else {
215
224
  collectionId = ID.unique(); // Generate a new unique ID
216
225
  }
@@ -322,3 +331,214 @@ export const fetchAllCollections = async (
322
331
  console.log(`Fetched a total of ${collections.length} collections.`);
323
332
  return collections;
324
333
  };
334
+
335
+ /**
336
+ * Transfers all documents from one collection to another in a different database
337
+ * within the same Appwrite Project
338
+ */
339
+ export const transferDocumentsBetweenDbsLocalToLocal = async (
340
+ db: Databases,
341
+ fromDbId: string,
342
+ toDbId: string,
343
+ fromCollId: string,
344
+ toCollId: string
345
+ ) => {
346
+ let fromCollDocs = await tryAwaitWithRetry(async () =>
347
+ db.listDocuments(fromDbId, fromCollId, [Query.limit(50)])
348
+ );
349
+ let totalDocumentsTransferred = 0;
350
+
351
+ if (fromCollDocs.documents.length === 0) {
352
+ console.log(`No documents found in collection ${fromCollId}`);
353
+ return;
354
+ } else if (fromCollDocs.documents.length < 50) {
355
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
356
+ const toCreateObject: Partial<typeof doc> = {
357
+ ...doc,
358
+ };
359
+ delete toCreateObject.$databaseId;
360
+ delete toCreateObject.$collectionId;
361
+ delete toCreateObject.$createdAt;
362
+ delete toCreateObject.$updatedAt;
363
+ delete toCreateObject.$id;
364
+ delete toCreateObject.$permissions;
365
+ return tryAwaitWithRetry(
366
+ async () =>
367
+ await db.createDocument(
368
+ toDbId,
369
+ toCollId,
370
+ doc.$id,
371
+ toCreateObject,
372
+ doc.$permissions
373
+ )
374
+ );
375
+ });
376
+ await Promise.all(batchedPromises);
377
+ totalDocumentsTransferred += fromCollDocs.documents.length;
378
+ } else {
379
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
380
+ const toCreateObject: Partial<typeof doc> = {
381
+ ...doc,
382
+ };
383
+ delete toCreateObject.$databaseId;
384
+ delete toCreateObject.$collectionId;
385
+ delete toCreateObject.$createdAt;
386
+ delete toCreateObject.$updatedAt;
387
+ delete toCreateObject.$id;
388
+ delete toCreateObject.$permissions;
389
+ return tryAwaitWithRetry(async () =>
390
+ db.createDocument(
391
+ toDbId,
392
+ toCollId,
393
+ doc.$id,
394
+ toCreateObject,
395
+ doc.$permissions
396
+ )
397
+ );
398
+ });
399
+ await Promise.all(batchedPromises);
400
+ totalDocumentsTransferred += fromCollDocs.documents.length;
401
+ while (fromCollDocs.documents.length === 50) {
402
+ fromCollDocs = await db.listDocuments(fromDbId, fromCollId, [
403
+ Query.limit(50),
404
+ Query.cursorAfter(
405
+ fromCollDocs.documents[fromCollDocs.documents.length - 1].$id
406
+ ),
407
+ ]);
408
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
409
+ const toCreateObject: Partial<typeof doc> = {
410
+ ...doc,
411
+ };
412
+ delete toCreateObject.$databaseId;
413
+ delete toCreateObject.$collectionId;
414
+ delete toCreateObject.$createdAt;
415
+ delete toCreateObject.$updatedAt;
416
+ delete toCreateObject.$id;
417
+ delete toCreateObject.$permissions;
418
+ return tryAwaitWithRetry(
419
+ async () =>
420
+ await db.createDocument(
421
+ toDbId,
422
+ toCollId,
423
+ doc.$id,
424
+ toCreateObject,
425
+ doc.$permissions
426
+ )
427
+ );
428
+ });
429
+ await Promise.all(batchedPromises);
430
+ totalDocumentsTransferred += fromCollDocs.documents.length;
431
+ }
432
+ }
433
+
434
+ console.log(
435
+ `Transferred ${totalDocumentsTransferred} documents from database ${fromDbId} to database ${toDbId} -- collection ${fromCollId} to collection ${toCollId}`
436
+ );
437
+ };
438
+
439
+ export const transferDocumentsBetweenDbsLocalToRemote = async (
440
+ localDb: Databases,
441
+ endpoint: string,
442
+ projectId: string,
443
+ apiKey: string,
444
+ fromDbId: string,
445
+ toDbId: string,
446
+ fromCollId: string,
447
+ toCollId: string
448
+ ) => {
449
+ const client = new Client()
450
+ .setEndpoint(endpoint)
451
+ .setProject(projectId)
452
+ .setKey(apiKey);
453
+ let totalDocumentsTransferred = 0;
454
+ const remoteDb = new Databases(client);
455
+ let fromCollDocs = await tryAwaitWithRetry(async () =>
456
+ localDb.listDocuments(fromDbId, fromCollId, [Query.limit(50)])
457
+ );
458
+
459
+ if (fromCollDocs.documents.length === 0) {
460
+ console.log(`No documents found in collection ${fromCollId}`);
461
+ return;
462
+ } else if (fromCollDocs.documents.length < 50) {
463
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
464
+ const toCreateObject: Partial<typeof doc> = {
465
+ ...doc,
466
+ };
467
+ delete toCreateObject.$databaseId;
468
+ delete toCreateObject.$collectionId;
469
+ delete toCreateObject.$createdAt;
470
+ delete toCreateObject.$updatedAt;
471
+ delete toCreateObject.$id;
472
+ delete toCreateObject.$permissions;
473
+ return tryAwaitWithRetry(async () =>
474
+ remoteDb.createDocument(
475
+ toDbId,
476
+ toCollId,
477
+ doc.$id,
478
+ toCreateObject,
479
+ doc.$permissions
480
+ )
481
+ );
482
+ });
483
+ await Promise.all(batchedPromises);
484
+ totalDocumentsTransferred += fromCollDocs.documents.length;
485
+ } else {
486
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
487
+ const toCreateObject: Partial<typeof doc> = {
488
+ ...doc,
489
+ };
490
+ delete toCreateObject.$databaseId;
491
+ delete toCreateObject.$collectionId;
492
+ delete toCreateObject.$createdAt;
493
+ delete toCreateObject.$updatedAt;
494
+ delete toCreateObject.$id;
495
+ delete toCreateObject.$permissions;
496
+ return tryAwaitWithRetry(async () =>
497
+ remoteDb.createDocument(
498
+ toDbId,
499
+ toCollId,
500
+ doc.$id,
501
+ toCreateObject,
502
+ doc.$permissions
503
+ )
504
+ );
505
+ });
506
+ await Promise.all(batchedPromises);
507
+ totalDocumentsTransferred += fromCollDocs.documents.length;
508
+ while (fromCollDocs.documents.length === 50) {
509
+ fromCollDocs = await tryAwaitWithRetry(async () =>
510
+ localDb.listDocuments(fromDbId, fromCollId, [
511
+ Query.limit(50),
512
+ Query.cursorAfter(
513
+ fromCollDocs.documents[fromCollDocs.documents.length - 1].$id
514
+ ),
515
+ ])
516
+ );
517
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
518
+ const toCreateObject: Partial<typeof doc> = {
519
+ ...doc,
520
+ };
521
+ delete toCreateObject.$databaseId;
522
+ delete toCreateObject.$collectionId;
523
+ delete toCreateObject.$createdAt;
524
+ delete toCreateObject.$updatedAt;
525
+ delete toCreateObject.$id;
526
+ delete toCreateObject.$permissions;
527
+ return tryAwaitWithRetry(async () =>
528
+ remoteDb.createDocument(
529
+ toDbId,
530
+ toCollId,
531
+ doc.$id,
532
+ toCreateObject,
533
+ doc.$permissions
534
+ )
535
+ );
536
+ });
537
+ await Promise.all(batchedPromises);
538
+ totalDocumentsTransferred += fromCollDocs.documents.length;
539
+ }
540
+ }
541
+ console.log(
542
+ `Total documents transferred from database ${fromDbId} to database ${toDbId} -- collection ${fromCollId} to collection ${toCollId}: ${totalDocumentsTransferred}`
543
+ );
544
+ };