appwrite-utils-cli 0.0.286 → 0.9.2

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 (109) hide show
  1. package/README.md +162 -96
  2. package/dist/collections/attributes.d.ts +4 -0
  3. package/dist/collections/attributes.js +224 -0
  4. package/dist/collections/indexes.d.ts +4 -0
  5. package/dist/collections/indexes.js +27 -0
  6. package/dist/collections/methods.d.ts +16 -0
  7. package/dist/collections/methods.js +216 -0
  8. package/dist/databases/methods.d.ts +6 -0
  9. package/dist/databases/methods.js +33 -0
  10. package/dist/interactiveCLI.d.ts +19 -0
  11. package/dist/interactiveCLI.js +555 -0
  12. package/dist/main.js +224 -62
  13. package/dist/migrations/afterImportActions.js +37 -40
  14. package/dist/migrations/appwriteToX.d.ts +26 -25
  15. package/dist/migrations/appwriteToX.js +42 -6
  16. package/dist/migrations/attributes.js +21 -20
  17. package/dist/migrations/backup.d.ts +93 -87
  18. package/dist/migrations/collections.d.ts +6 -0
  19. package/dist/migrations/collections.js +149 -20
  20. package/dist/migrations/converters.d.ts +2 -18
  21. package/dist/migrations/converters.js +13 -2
  22. package/dist/migrations/dataLoader.d.ts +276 -161
  23. package/dist/migrations/dataLoader.js +535 -292
  24. package/dist/migrations/databases.js +8 -2
  25. package/dist/migrations/helper.d.ts +3 -0
  26. package/dist/migrations/helper.js +21 -0
  27. package/dist/migrations/importController.d.ts +5 -2
  28. package/dist/migrations/importController.js +125 -88
  29. package/dist/migrations/importDataActions.d.ts +9 -1
  30. package/dist/migrations/importDataActions.js +15 -3
  31. package/dist/migrations/indexes.js +3 -2
  32. package/dist/migrations/logging.js +20 -8
  33. package/dist/migrations/migrationHelper.d.ts +9 -4
  34. package/dist/migrations/migrationHelper.js +6 -5
  35. package/dist/migrations/openapi.d.ts +1 -1
  36. package/dist/migrations/openapi.js +33 -18
  37. package/dist/migrations/queue.js +3 -2
  38. package/dist/migrations/relationships.d.ts +2 -2
  39. package/dist/migrations/schemaStrings.js +53 -41
  40. package/dist/migrations/setupDatabase.d.ts +2 -4
  41. package/dist/migrations/setupDatabase.js +24 -105
  42. package/dist/migrations/storage.d.ts +3 -1
  43. package/dist/migrations/storage.js +110 -16
  44. package/dist/migrations/transfer.d.ts +30 -0
  45. package/dist/migrations/transfer.js +337 -0
  46. package/dist/migrations/users.d.ts +2 -1
  47. package/dist/migrations/users.js +78 -43
  48. package/dist/schemas/authUser.d.ts +2 -2
  49. package/dist/storage/methods.d.ts +15 -0
  50. package/dist/storage/methods.js +207 -0
  51. package/dist/storage/schemas.d.ts +687 -0
  52. package/dist/storage/schemas.js +175 -0
  53. package/dist/utils/getClientFromConfig.d.ts +4 -0
  54. package/dist/utils/getClientFromConfig.js +16 -0
  55. package/dist/utils/helperFunctions.d.ts +11 -1
  56. package/dist/utils/helperFunctions.js +38 -0
  57. package/dist/utils/retryFailedPromises.d.ts +2 -0
  58. package/dist/utils/retryFailedPromises.js +21 -0
  59. package/dist/utils/schemaStrings.d.ts +13 -0
  60. package/dist/utils/schemaStrings.js +403 -0
  61. package/dist/utils/setupFiles.js +110 -61
  62. package/dist/utilsController.d.ts +40 -22
  63. package/dist/utilsController.js +164 -84
  64. package/package.json +13 -15
  65. package/src/collections/attributes.ts +483 -0
  66. package/src/collections/indexes.ts +53 -0
  67. package/src/collections/methods.ts +331 -0
  68. package/src/databases/methods.ts +47 -0
  69. package/src/init.ts +64 -64
  70. package/src/interactiveCLI.ts +767 -0
  71. package/src/main.ts +289 -83
  72. package/src/migrations/afterImportActions.ts +553 -490
  73. package/src/migrations/appwriteToX.ts +237 -174
  74. package/src/migrations/attributes.ts +483 -422
  75. package/src/migrations/backup.ts +205 -205
  76. package/src/migrations/collections.ts +545 -300
  77. package/src/migrations/converters.ts +161 -150
  78. package/src/migrations/dataLoader.ts +1615 -1304
  79. package/src/migrations/databases.ts +44 -25
  80. package/src/migrations/dbHelpers.ts +92 -92
  81. package/src/migrations/helper.ts +40 -0
  82. package/src/migrations/importController.ts +448 -384
  83. package/src/migrations/importDataActions.ts +315 -307
  84. package/src/migrations/indexes.ts +40 -37
  85. package/src/migrations/logging.ts +29 -16
  86. package/src/migrations/migrationHelper.ts +207 -201
  87. package/src/migrations/openapi.ts +83 -70
  88. package/src/migrations/queue.ts +118 -119
  89. package/src/migrations/relationships.ts +324 -324
  90. package/src/migrations/schemaStrings.ts +472 -460
  91. package/src/migrations/setupDatabase.ts +118 -219
  92. package/src/migrations/storage.ts +538 -358
  93. package/src/migrations/transfer.ts +608 -0
  94. package/src/migrations/users.ts +362 -285
  95. package/src/migrations/validationRules.ts +63 -63
  96. package/src/schemas/authUser.ts +23 -23
  97. package/src/setup.ts +8 -8
  98. package/src/storage/methods.ts +371 -0
  99. package/src/storage/schemas.ts +205 -0
  100. package/src/types.ts +9 -9
  101. package/src/utils/getClientFromConfig.ts +17 -0
  102. package/src/utils/helperFunctions.ts +181 -127
  103. package/src/utils/index.ts +2 -2
  104. package/src/utils/loadConfigs.ts +59 -59
  105. package/src/utils/retryFailedPromises.ts +27 -0
  106. package/src/utils/schemaStrings.ts +473 -0
  107. package/src/utils/setupFiles.ts +228 -182
  108. package/src/utilsController.ts +325 -194
  109. package/tsconfig.json +37 -37
@@ -1,300 +1,545 @@
1
- import { Databases, ID, Permission, Query, type Models } from "node-appwrite";
2
- import type { AppwriteConfig, CollectionCreate } from "appwrite-utils";
3
- import { nameToIdMapping, processQueue } from "./queue.js";
4
- import { createUpdateCollectionAttributes } from "./attributes.js";
5
- import { createOrUpdateIndexes } from "./indexes.js";
6
- import _ from "lodash";
7
- import { SchemaGenerator } from "./schemaStrings.js";
8
-
9
- export const documentExists = async (
10
- db: Databases,
11
- dbId: string,
12
- targetCollectionId: string,
13
- toCreateObject: any
14
- ): Promise<Models.Document | null> => {
15
- // Had to do this because kept running into issues with type checking arrays so, sorry 40ms
16
- const collection = await db.getCollection(dbId, targetCollectionId);
17
- const attributes = collection.attributes as any[];
18
- let arrayTypeAttributes = attributes
19
- .filter((attribute: any) => attribute.array === true)
20
- .map((attribute: any) => attribute.key);
21
- // Function to check if a string is JSON
22
- const isJsonString = (str: string) => {
23
- try {
24
- const json = JSON.parse(str);
25
- return typeof json === "object" && json !== null; // Check if parsed JSON is an object or array
26
- } catch (e) {
27
- return false;
28
- }
29
- };
30
-
31
- // Validate and prepare query parameters
32
- const validQueryParams = _.chain(toCreateObject)
33
- .pickBy(
34
- (value, key) =>
35
- !arrayTypeAttributes.includes(key) &&
36
- !key.startsWith("$") &&
37
- !_.isNull(value) &&
38
- !_.isUndefined(value) &&
39
- !_.isEmpty(value) &&
40
- !_.isObject(value) && // Keeps excluding objects
41
- !_.isArray(value) && // Explicitly exclude arrays
42
- !(_.isString(value) && isJsonString(value)) && // Exclude JSON strings
43
- (_.isString(value) ? value.length < 4096 && value.length > 0 : true) // String length check
44
- )
45
- .mapValues((value, key) =>
46
- _.isString(value) || _.isNumber(value) || _.isBoolean(value)
47
- ? value
48
- : null
49
- )
50
- .omitBy(_.isNull) // Remove any null values that might have been added in mapValues
51
- .toPairs()
52
- .slice(0, 25) // Limit to 25 to adhere to query limit
53
- .map(([key, value]) => Query.equal(key, value as any))
54
- .value();
55
-
56
- // Execute the query with the validated and prepared parameters
57
- const result = await db.listDocuments(
58
- dbId,
59
- targetCollectionId,
60
- validQueryParams
61
- );
62
- return result.documents[0] || null;
63
- };
64
-
65
- export const checkForCollection = async (
66
- db: Databases,
67
- dbId: string,
68
- collection: Partial<CollectionCreate>
69
- ): Promise<Models.Collection | null> => {
70
- try {
71
- console.log(`Checking for collection with name: ${collection.name}`);
72
- const response = await db.listCollections(dbId, [
73
- Query.equal("name", collection.name!),
74
- ]);
75
- if (response.collections.length > 0) {
76
- console.log(`Collection found: ${response.collections[0].$id}`);
77
- return { ...collection, ...response.collections[0] };
78
- } else {
79
- console.log(`No collection found with name: ${collection.name}`);
80
- return null;
81
- }
82
- } catch (error) {
83
- console.error(`Error checking for collection: ${error}`);
84
- return null;
85
- }
86
- };
87
-
88
- // Helper function to fetch and cache collection by name
89
- export const fetchAndCacheCollectionByName = async (
90
- db: Databases,
91
- dbId: string,
92
- collectionName: string
93
- ): Promise<Models.Collection | undefined> => {
94
- if (nameToIdMapping.has(collectionName)) {
95
- const collectionId = nameToIdMapping.get(collectionName);
96
- console.log(`\tCollection found in cache: ${collectionId}`);
97
- return await db.getCollection(dbId, collectionId!);
98
- } else {
99
- console.log(`\tFetching collection by name: ${collectionName}`);
100
- const collectionsPulled = await db.listCollections(dbId, [
101
- Query.equal("name", collectionName),
102
- ]);
103
- if (collectionsPulled.total > 0) {
104
- const collection = collectionsPulled.collections[0];
105
- console.log(`\tCollection found: ${collection.$id}`);
106
- nameToIdMapping.set(collectionName, collection.$id);
107
- return collection;
108
- } else {
109
- console.log(`\tCollection not found by name: ${collectionName}`);
110
- return undefined;
111
- }
112
- }
113
- };
114
-
115
- export const wipeDatabase = async (
116
- database: Databases,
117
- databaseId: string
118
- ): Promise<{ collectionId: string; collectionName: string }[]> => {
119
- console.log(`Wiping database: ${databaseId}`);
120
- const { collections: existingCollections } = await database.listCollections(
121
- databaseId
122
- );
123
- let collectionsDeleted: { collectionId: string; collectionName: string }[] =
124
- [];
125
- for (const { $id: collectionId, name: name } of existingCollections) {
126
- console.log(`Deleting collection: ${collectionId}`);
127
- collectionsDeleted.push({
128
- collectionId: collectionId,
129
- collectionName: name,
130
- });
131
- await database.deleteCollection(databaseId, collectionId);
132
- }
133
- return collectionsDeleted;
134
- };
135
-
136
- export const generateSchemas = async (
137
- config: AppwriteConfig,
138
- appwriteFolderPath: string
139
- ): Promise<void> => {
140
- const schemaGenerator = new SchemaGenerator(config, appwriteFolderPath);
141
- schemaGenerator.generateSchemas();
142
- };
143
-
144
- export const createOrUpdateCollections = async (
145
- database: Databases,
146
- databaseId: string,
147
- config: AppwriteConfig,
148
- deletedCollections?: { collectionId: string; collectionName: string }[]
149
- ): Promise<void> => {
150
- const configCollections = config.collections;
151
- if (!configCollections) {
152
- return;
153
- }
154
- const usedIds = new Set(); // To track IDs used in this operation
155
-
156
- for (const { attributes, indexes, ...collection } of configCollections) {
157
- // Prepare permissions for the collection
158
- const permissions = [];
159
- if (collection.$permissions && collection.$permissions.length > 0) {
160
- for (const permission of collection.$permissions) {
161
- switch (permission.permission) {
162
- case "read":
163
- permissions.push(Permission.read(permission.target));
164
- break;
165
- case "create":
166
- permissions.push(Permission.create(permission.target));
167
- break;
168
- case "update":
169
- permissions.push(Permission.update(permission.target));
170
- break;
171
- case "delete":
172
- permissions.push(Permission.delete(permission.target));
173
- break;
174
- case "write":
175
- permissions.push(Permission.write(permission.target));
176
- break;
177
- default:
178
- console.log(`Unknown permission: ${permission.permission}`);
179
- break;
180
- }
181
- }
182
- }
183
-
184
- // Check if the collection already exists by name
185
- let collectionsFound = await database.listCollections(databaseId, [
186
- Query.equal("name", collection.name),
187
- ]);
188
-
189
- let collectionToUse =
190
- collectionsFound.total > 0 ? collectionsFound.collections[0] : null;
191
-
192
- // Determine the correct ID for the collection
193
- let collectionId;
194
- if (!collectionToUse) {
195
- console.log(`Creating collection: ${collection.name}`);
196
- const foundColl = deletedCollections?.find(
197
- (coll) =>
198
- coll.collectionName.toLowerCase().trim().replace(" ", "") ===
199
- collection.name.toLowerCase().trim().replace(" ", "")
200
- );
201
-
202
- if (foundColl && !usedIds.has(foundColl.collectionId)) {
203
- collectionId = foundColl.collectionId; // Use ID from deleted collection if not already used
204
- } else if (collection.$id && !usedIds.has(collection.$id)) {
205
- collectionId = collection.$id; // Use the provided $id if not already used
206
- } else {
207
- collectionId = ID.unique(); // Generate a new unique ID
208
- }
209
-
210
- usedIds.add(collectionId); // Mark this ID as used
211
-
212
- // Create the collection with the determined ID
213
- try {
214
- collectionToUse = await database.createCollection(
215
- databaseId,
216
- collectionId,
217
- collection.name,
218
- permissions,
219
- collection.documentSecurity,
220
- collection.enabled
221
- );
222
- collection.$id = collectionToUse.$id;
223
- nameToIdMapping.set(collection.name, collectionToUse.$id);
224
- } catch (error) {
225
- console.error(
226
- `Failed to create collection ${collection.name} with ID ${collectionId}: ${error}`
227
- );
228
- continue; // Skip to the next collection on failure
229
- }
230
- } else {
231
- console.log(`Collection ${collection.name} exists, using it`);
232
- }
233
-
234
- // Update attributes and indexes for the collection
235
- console.log("Creating Attributes");
236
- await createUpdateCollectionAttributes(
237
- database,
238
- databaseId,
239
- collectionToUse,
240
- attributes
241
- );
242
- console.log("Creating Indexes");
243
- await createOrUpdateIndexes(
244
- databaseId,
245
- database,
246
- collectionToUse.$id,
247
- indexes ?? []
248
- );
249
- }
250
- // Process any remaining tasks in the queue
251
- await processQueue(database, databaseId);
252
- };
253
-
254
- export const generateMockData = async (
255
- database: Databases,
256
- databaseId: string,
257
- configCollections: any[]
258
- ): Promise<void> => {
259
- for (const { collection, mockFunction } of configCollections) {
260
- if (mockFunction) {
261
- console.log(`Generating mock data for collection: ${collection.name}`);
262
- const mockData = mockFunction();
263
- for (const data of mockData) {
264
- await database.createDocument(
265
- databaseId,
266
- collection.$id,
267
- ID.unique(),
268
- data
269
- );
270
- }
271
- }
272
- }
273
- };
274
-
275
- export const fetchAllCollections = async (
276
- dbId: string,
277
- database: Databases
278
- ): Promise<Models.Collection[]> => {
279
- console.log(`Fetching all collections for database ID: ${dbId}`);
280
- let collections: Models.Collection[] = [];
281
- let moreCollections = true;
282
- let lastCollectionId: string | undefined;
283
-
284
- while (moreCollections) {
285
- const queries = [Query.limit(500)];
286
- if (lastCollectionId) {
287
- queries.push(Query.cursorAfter(lastCollectionId));
288
- }
289
- const response = await database.listCollections(dbId, queries);
290
- collections = collections.concat(response.collections);
291
- moreCollections = response.collections.length === 500;
292
- if (moreCollections) {
293
- lastCollectionId =
294
- response.collections[response.collections.length - 1].$id;
295
- }
296
- }
297
-
298
- console.log(`Fetched a total of ${collections.length} collections.`);
299
- return collections;
300
- };
1
+ import {
2
+ Client,
3
+ Databases,
4
+ ID,
5
+ Permission,
6
+ Query,
7
+ type Models,
8
+ } from "node-appwrite";
9
+ import type { AppwriteConfig, CollectionCreate } from "appwrite-utils";
10
+ import { nameToIdMapping, processQueue } from "./queue.js";
11
+ import { createUpdateCollectionAttributes } from "./attributes.js";
12
+ import { createOrUpdateIndexes } from "./indexes.js";
13
+ import _ from "lodash";
14
+ import { SchemaGenerator } from "./schemaStrings.js";
15
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
16
+
17
+ export const documentExists = async (
18
+ db: Databases,
19
+ dbId: string,
20
+ targetCollectionId: string,
21
+ toCreateObject: any
22
+ ): Promise<Models.Document | null> => {
23
+ // Had to do this because kept running into issues with type checking arrays so, sorry 40ms
24
+ const collection = await db.getCollection(dbId, targetCollectionId);
25
+ const attributes = collection.attributes as any[];
26
+ let arrayTypeAttributes = attributes
27
+ .filter((attribute: any) => attribute.array === true)
28
+ .map((attribute: any) => attribute.key);
29
+ // Function to check if a string is JSON
30
+ const isJsonString = (str: string) => {
31
+ try {
32
+ const json = JSON.parse(str);
33
+ return typeof json === "object" && json !== null; // Check if parsed JSON is an object or array
34
+ } catch (e) {
35
+ return false;
36
+ }
37
+ };
38
+
39
+ // Validate and prepare query parameters
40
+ const validQueryParams = _.chain(toCreateObject)
41
+ .pickBy(
42
+ (value, key) =>
43
+ !arrayTypeAttributes.includes(key) &&
44
+ !key.startsWith("$") &&
45
+ !_.isNull(value) &&
46
+ !_.isUndefined(value) &&
47
+ !_.isEmpty(value) &&
48
+ !_.isObject(value) && // Keeps excluding objects
49
+ !_.isArray(value) && // Explicitly exclude arrays
50
+ !(_.isString(value) && isJsonString(value)) && // Exclude JSON strings
51
+ (_.isString(value) ? value.length < 4096 && value.length > 0 : true) // String length check
52
+ )
53
+ .mapValues((value, key) =>
54
+ _.isString(value) || _.isNumber(value) || _.isBoolean(value)
55
+ ? value
56
+ : null
57
+ )
58
+ .omitBy(_.isNull) // Remove any null values that might have been added in mapValues
59
+ .toPairs()
60
+ .slice(0, 25) // Limit to 25 to adhere to query limit
61
+ .map(([key, value]) => Query.equal(key, value as any))
62
+ .value();
63
+
64
+ // Execute the query with the validated and prepared parameters
65
+ const result = await db.listDocuments(
66
+ dbId,
67
+ targetCollectionId,
68
+ validQueryParams
69
+ );
70
+ return result.documents[0] || null;
71
+ };
72
+
73
+ export const checkForCollection = async (
74
+ db: Databases,
75
+ dbId: string,
76
+ collection: Partial<CollectionCreate>
77
+ ): Promise<Models.Collection | null> => {
78
+ try {
79
+ console.log(`Checking for collection with name: ${collection.name}`);
80
+ const response = await tryAwaitWithRetry(
81
+ async () =>
82
+ await db.listCollections(dbId, [Query.equal("name", collection.name!)])
83
+ );
84
+ if (response.collections.length > 0) {
85
+ console.log(`Collection found: ${response.collections[0].$id}`);
86
+ return { ...collection, ...response.collections[0] };
87
+ } else {
88
+ console.log(`No collection found with name: ${collection.name}`);
89
+ return null;
90
+ }
91
+ } catch (error) {
92
+ console.error(`Error checking for collection: ${error}`);
93
+ return null;
94
+ }
95
+ };
96
+
97
+ // Helper function to fetch and cache collection by name
98
+ export const fetchAndCacheCollectionByName = async (
99
+ db: Databases,
100
+ dbId: string,
101
+ collectionName: string
102
+ ): Promise<Models.Collection | undefined> => {
103
+ if (nameToIdMapping.has(collectionName)) {
104
+ const collectionId = nameToIdMapping.get(collectionName);
105
+ console.log(`\tCollection found in cache: ${collectionId}`);
106
+ return await tryAwaitWithRetry(
107
+ async () => await db.getCollection(dbId, collectionId!)
108
+ );
109
+ } else {
110
+ console.log(`\tFetching collection by name: ${collectionName}`);
111
+ const collectionsPulled = await tryAwaitWithRetry(
112
+ async () =>
113
+ await db.listCollections(dbId, [Query.equal("name", collectionName)])
114
+ );
115
+ if (collectionsPulled.total > 0) {
116
+ const collection = collectionsPulled.collections[0];
117
+ console.log(`\tCollection found: ${collection.$id}`);
118
+ nameToIdMapping.set(collectionName, collection.$id);
119
+ return collection;
120
+ } else {
121
+ console.log(`\tCollection not found by name: ${collectionName}`);
122
+ return undefined;
123
+ }
124
+ }
125
+ };
126
+
127
+ export const wipeDatabase = async (
128
+ database: Databases,
129
+ databaseId: string
130
+ ): Promise<{ collectionId: string; collectionName: string }[]> => {
131
+ console.log(`Wiping database: ${databaseId}`);
132
+ const existingCollections = await fetchAllCollections(databaseId, database);
133
+ let collectionsDeleted: { collectionId: string; collectionName: string }[] =
134
+ [];
135
+ for (const { $id: collectionId, name: name } of existingCollections) {
136
+ console.log(`Deleting collection: ${collectionId}`);
137
+ collectionsDeleted.push({
138
+ collectionId: collectionId,
139
+ collectionName: name,
140
+ });
141
+ tryAwaitWithRetry(
142
+ async () => await database.deleteCollection(databaseId, collectionId)
143
+ ); // Try to delete the collection and ignore errors if it doesn't exist or if it's already being deleted
144
+ }
145
+ return collectionsDeleted;
146
+ };
147
+
148
+ export const generateSchemas = async (
149
+ config: AppwriteConfig,
150
+ appwriteFolderPath: string
151
+ ): Promise<void> => {
152
+ const schemaGenerator = new SchemaGenerator(config, appwriteFolderPath);
153
+ schemaGenerator.generateSchemas();
154
+ };
155
+
156
+ export const createOrUpdateCollections = async (
157
+ database: Databases,
158
+ databaseId: string,
159
+ config: AppwriteConfig,
160
+ deletedCollections?: { collectionId: string; collectionName: string }[]
161
+ ): Promise<void> => {
162
+ const configCollections = config.collections;
163
+ if (!configCollections) {
164
+ return;
165
+ }
166
+ const usedIds = new Set(); // To track IDs used in this operation
167
+
168
+ for (const { attributes, indexes, ...collection } of configCollections) {
169
+ // Prepare permissions for the collection
170
+ const permissions: string[] = [];
171
+ if (collection.$permissions && collection.$permissions.length > 0) {
172
+ for (const permission of collection.$permissions) {
173
+ switch (permission.permission) {
174
+ case "read":
175
+ permissions.push(Permission.read(permission.target));
176
+ break;
177
+ case "create":
178
+ permissions.push(Permission.create(permission.target));
179
+ break;
180
+ case "update":
181
+ permissions.push(Permission.update(permission.target));
182
+ break;
183
+ case "delete":
184
+ permissions.push(Permission.delete(permission.target));
185
+ break;
186
+ case "write":
187
+ permissions.push(Permission.write(permission.target));
188
+ break;
189
+ default:
190
+ console.log(`Unknown permission: ${permission.permission}`);
191
+ break;
192
+ }
193
+ }
194
+ }
195
+
196
+ // Check if the collection already exists by name
197
+ let collectionsFound = await tryAwaitWithRetry(
198
+ async () =>
199
+ await database.listCollections(databaseId, [
200
+ Query.equal("name", collection.name),
201
+ ])
202
+ );
203
+
204
+ let collectionToUse =
205
+ collectionsFound.total > 0 ? collectionsFound.collections[0] : null;
206
+
207
+ // Determine the correct ID for the collection
208
+ let collectionId: string;
209
+ if (!collectionToUse) {
210
+ console.log(`Creating collection: ${collection.name}`);
211
+ let foundColl = deletedCollections?.find(
212
+ (coll) =>
213
+ coll.collectionName.toLowerCase().trim().replace(" ", "") ===
214
+ collection.name.toLowerCase().trim().replace(" ", "")
215
+ );
216
+
217
+ if (collection.$id) {
218
+ collectionId = collection.$id; // Always use the provided $id if present
219
+ } else if (foundColl && !usedIds.has(foundColl.collectionId)) {
220
+ collectionId = foundColl.collectionId; // Use ID from deleted collection if not already used
221
+ } else {
222
+ collectionId = ID.unique(); // Generate a new unique ID
223
+ }
224
+
225
+ usedIds.add(collectionId); // Mark this ID as used
226
+
227
+ // Create the collection with the determined ID
228
+ try {
229
+ collectionToUse = await tryAwaitWithRetry(
230
+ async () =>
231
+ await database.createCollection(
232
+ databaseId,
233
+ collectionId,
234
+ collection.name,
235
+ permissions,
236
+ collection.documentSecurity ?? false,
237
+ collection.enabled ?? true
238
+ )
239
+ );
240
+ collection.$id = collectionToUse!.$id;
241
+ nameToIdMapping.set(collection.name, collectionToUse!.$id);
242
+ } catch (error) {
243
+ console.error(
244
+ `Failed to create collection ${collection.name} with ID ${collectionId}: ${error}`
245
+ );
246
+ continue; // Skip to the next collection on failure
247
+ }
248
+ } else {
249
+ console.log(`Collection ${collection.name} exists, updating it`);
250
+ await tryAwaitWithRetry(
251
+ async () =>
252
+ await database.updateCollection(
253
+ databaseId,
254
+ collectionToUse!.$id,
255
+ collection.name,
256
+ permissions,
257
+ collection.documentSecurity ?? false,
258
+ collection.enabled ?? true
259
+ )
260
+ );
261
+ }
262
+
263
+ // Update attributes and indexes for the collection
264
+ console.log("Creating Attributes");
265
+ await createUpdateCollectionAttributes(
266
+ database,
267
+ databaseId,
268
+ collectionToUse!,
269
+ attributes
270
+ );
271
+ console.log("Creating Indexes");
272
+ await createOrUpdateIndexes(
273
+ databaseId,
274
+ database,
275
+ collectionToUse!.$id,
276
+ indexes ?? []
277
+ );
278
+ }
279
+ // Process any remaining tasks in the queue
280
+ await processQueue(database, databaseId);
281
+ };
282
+
283
+ export const generateMockData = async (
284
+ database: Databases,
285
+ databaseId: string,
286
+ configCollections: any[]
287
+ ): Promise<void> => {
288
+ for (const { collection, mockFunction } of configCollections) {
289
+ if (mockFunction) {
290
+ console.log(`Generating mock data for collection: ${collection.name}`);
291
+ const mockData = mockFunction();
292
+ for (const data of mockData) {
293
+ await database.createDocument(
294
+ databaseId,
295
+ collection.$id,
296
+ ID.unique(),
297
+ data
298
+ );
299
+ }
300
+ }
301
+ }
302
+ };
303
+
304
+ export const fetchAllCollections = async (
305
+ dbId: string,
306
+ database: Databases
307
+ ): Promise<Models.Collection[]> => {
308
+ console.log(`Fetching all collections for database ID: ${dbId}`);
309
+ let collections: Models.Collection[] = [];
310
+ let moreCollections = true;
311
+ let lastCollectionId: string | undefined;
312
+
313
+ while (moreCollections) {
314
+ const queries = [Query.limit(500)];
315
+ if (lastCollectionId) {
316
+ queries.push(Query.cursorAfter(lastCollectionId));
317
+ }
318
+ const response = await tryAwaitWithRetry(
319
+ async () => await database.listCollections(dbId, queries)
320
+ );
321
+ collections = collections.concat(response.collections);
322
+ moreCollections = response.collections.length === 500;
323
+ if (moreCollections) {
324
+ lastCollectionId =
325
+ response.collections[response.collections.length - 1].$id;
326
+ }
327
+ }
328
+
329
+ console.log(`Fetched a total of ${collections.length} collections.`);
330
+ return collections;
331
+ };
332
+
333
+ /**
334
+ * Transfers all documents from one collection to another in a different database
335
+ * within the same Appwrite Project
336
+ */
337
+ export const transferDocumentsBetweenDbsLocalToLocal = async (
338
+ db: Databases,
339
+ fromDbId: string,
340
+ toDbId: string,
341
+ fromCollId: string,
342
+ toCollId: string
343
+ ) => {
344
+ let fromCollDocs = await tryAwaitWithRetry(async () =>
345
+ db.listDocuments(fromDbId, fromCollId, [Query.limit(50)])
346
+ );
347
+ let totalDocumentsTransferred = 0;
348
+
349
+ if (fromCollDocs.documents.length === 0) {
350
+ console.log(`No documents found in collection ${fromCollId}`);
351
+ return;
352
+ } else if (fromCollDocs.documents.length < 50) {
353
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
354
+ const toCreateObject: Partial<typeof doc> = {
355
+ ...doc,
356
+ };
357
+ delete toCreateObject.$databaseId;
358
+ delete toCreateObject.$collectionId;
359
+ delete toCreateObject.$createdAt;
360
+ delete toCreateObject.$updatedAt;
361
+ delete toCreateObject.$id;
362
+ delete toCreateObject.$permissions;
363
+ return tryAwaitWithRetry(
364
+ async () =>
365
+ await db.createDocument(
366
+ toDbId,
367
+ toCollId,
368
+ doc.$id,
369
+ toCreateObject,
370
+ doc.$permissions
371
+ )
372
+ );
373
+ });
374
+ await Promise.all(batchedPromises);
375
+ totalDocumentsTransferred += fromCollDocs.documents.length;
376
+ } else {
377
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
378
+ const toCreateObject: Partial<typeof doc> = {
379
+ ...doc,
380
+ };
381
+ delete toCreateObject.$databaseId;
382
+ delete toCreateObject.$collectionId;
383
+ delete toCreateObject.$createdAt;
384
+ delete toCreateObject.$updatedAt;
385
+ delete toCreateObject.$id;
386
+ delete toCreateObject.$permissions;
387
+ return tryAwaitWithRetry(async () =>
388
+ db.createDocument(
389
+ toDbId,
390
+ toCollId,
391
+ doc.$id,
392
+ toCreateObject,
393
+ doc.$permissions
394
+ )
395
+ );
396
+ });
397
+ await Promise.all(batchedPromises);
398
+ totalDocumentsTransferred += fromCollDocs.documents.length;
399
+ while (fromCollDocs.documents.length === 50) {
400
+ fromCollDocs = await tryAwaitWithRetry(
401
+ async () =>
402
+ await db.listDocuments(fromDbId, fromCollId, [
403
+ Query.limit(50),
404
+ Query.cursorAfter(
405
+ fromCollDocs.documents[fromCollDocs.documents.length - 1].$id
406
+ ),
407
+ ])
408
+ );
409
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
410
+ const toCreateObject: Partial<typeof doc> = {
411
+ ...doc,
412
+ };
413
+ delete toCreateObject.$databaseId;
414
+ delete toCreateObject.$collectionId;
415
+ delete toCreateObject.$createdAt;
416
+ delete toCreateObject.$updatedAt;
417
+ delete toCreateObject.$id;
418
+ delete toCreateObject.$permissions;
419
+ return tryAwaitWithRetry(
420
+ async () =>
421
+ await db.createDocument(
422
+ toDbId,
423
+ toCollId,
424
+ doc.$id,
425
+ toCreateObject,
426
+ doc.$permissions
427
+ )
428
+ );
429
+ });
430
+ await Promise.all(batchedPromises);
431
+ totalDocumentsTransferred += fromCollDocs.documents.length;
432
+ }
433
+ }
434
+
435
+ console.log(
436
+ `Transferred ${totalDocumentsTransferred} documents from database ${fromDbId} to database ${toDbId} -- collection ${fromCollId} to collection ${toCollId}`
437
+ );
438
+ };
439
+
440
+ export const transferDocumentsBetweenDbsLocalToRemote = async (
441
+ localDb: Databases,
442
+ endpoint: string,
443
+ projectId: string,
444
+ apiKey: string,
445
+ fromDbId: string,
446
+ toDbId: string,
447
+ fromCollId: string,
448
+ toCollId: string
449
+ ) => {
450
+ const client = new Client()
451
+ .setEndpoint(endpoint)
452
+ .setProject(projectId)
453
+ .setKey(apiKey);
454
+ let totalDocumentsTransferred = 0;
455
+ const remoteDb = new Databases(client);
456
+ let fromCollDocs = await tryAwaitWithRetry(async () =>
457
+ localDb.listDocuments(fromDbId, fromCollId, [Query.limit(50)])
458
+ );
459
+
460
+ if (fromCollDocs.documents.length === 0) {
461
+ console.log(`No documents found in collection ${fromCollId}`);
462
+ return;
463
+ } else if (fromCollDocs.documents.length < 50) {
464
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
465
+ const toCreateObject: Partial<typeof doc> = {
466
+ ...doc,
467
+ };
468
+ delete toCreateObject.$databaseId;
469
+ delete toCreateObject.$collectionId;
470
+ delete toCreateObject.$createdAt;
471
+ delete toCreateObject.$updatedAt;
472
+ delete toCreateObject.$id;
473
+ delete toCreateObject.$permissions;
474
+ return tryAwaitWithRetry(async () =>
475
+ remoteDb.createDocument(
476
+ toDbId,
477
+ toCollId,
478
+ doc.$id,
479
+ toCreateObject,
480
+ doc.$permissions
481
+ )
482
+ );
483
+ });
484
+ await Promise.all(batchedPromises);
485
+ totalDocumentsTransferred += fromCollDocs.documents.length;
486
+ } else {
487
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
488
+ const toCreateObject: Partial<typeof doc> = {
489
+ ...doc,
490
+ };
491
+ delete toCreateObject.$databaseId;
492
+ delete toCreateObject.$collectionId;
493
+ delete toCreateObject.$createdAt;
494
+ delete toCreateObject.$updatedAt;
495
+ delete toCreateObject.$id;
496
+ delete toCreateObject.$permissions;
497
+ return tryAwaitWithRetry(async () =>
498
+ remoteDb.createDocument(
499
+ toDbId,
500
+ toCollId,
501
+ doc.$id,
502
+ toCreateObject,
503
+ doc.$permissions
504
+ )
505
+ );
506
+ });
507
+ await Promise.all(batchedPromises);
508
+ totalDocumentsTransferred += fromCollDocs.documents.length;
509
+ while (fromCollDocs.documents.length === 50) {
510
+ fromCollDocs = await tryAwaitWithRetry(async () =>
511
+ localDb.listDocuments(fromDbId, fromCollId, [
512
+ Query.limit(50),
513
+ Query.cursorAfter(
514
+ fromCollDocs.documents[fromCollDocs.documents.length - 1].$id
515
+ ),
516
+ ])
517
+ );
518
+ const batchedPromises = fromCollDocs.documents.map((doc) => {
519
+ const toCreateObject: Partial<typeof doc> = {
520
+ ...doc,
521
+ };
522
+ delete toCreateObject.$databaseId;
523
+ delete toCreateObject.$collectionId;
524
+ delete toCreateObject.$createdAt;
525
+ delete toCreateObject.$updatedAt;
526
+ delete toCreateObject.$id;
527
+ delete toCreateObject.$permissions;
528
+ return tryAwaitWithRetry(async () =>
529
+ remoteDb.createDocument(
530
+ toDbId,
531
+ toCollId,
532
+ doc.$id,
533
+ toCreateObject,
534
+ doc.$permissions
535
+ )
536
+ );
537
+ });
538
+ await Promise.all(batchedPromises);
539
+ totalDocumentsTransferred += fromCollDocs.documents.length;
540
+ }
541
+ }
542
+ console.log(
543
+ `Total documents transferred from database ${fromDbId} to database ${toDbId} -- collection ${fromCollId} to collection ${toCollId}: ${totalDocumentsTransferred}`
544
+ );
545
+ };