appwrite-utils-cli 0.10.82 → 0.10.83

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.
@@ -1,545 +0,0 @@
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
- };