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,63 +1,63 @@
1
- import _ from "lodash";
2
-
3
- export interface ValidationRules {
4
- [key: string]: (value: any, ...args: any[]) => boolean;
5
- }
6
-
7
- export const validationRules = {
8
- isNumber: (value: any): boolean => _.isNumber(value),
9
- isString: (value: any): boolean => _.isString(value),
10
- isBoolean: (value: any): boolean => _.isBoolean(value),
11
- isArray: (value: any): boolean => _.isArray(value),
12
- isObject: (value: any): boolean =>
13
- _.isObject(value) && !_.isArray(value) && !_.isFunction(value),
14
- isNull: (value: any): boolean => _.isNull(value),
15
- isValidEmail: (value: string): boolean =>
16
- value.match(/^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$/) !== null,
17
- isValidPhone: (value: string): boolean =>
18
- value.match(/^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/im) !==
19
- null,
20
- isValidPassword: (value: string): boolean =>
21
- value.match(
22
- /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/
23
- ) !== null,
24
- isValidUrl: (value: string): boolean =>
25
- value.match(
26
- /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
27
- ) !== null,
28
- isValidHex: (value: string): boolean =>
29
- value.match(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i) !== null,
30
- isValidHexColor: (value: string): boolean =>
31
- value.match(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i) !== null,
32
- isValidHexAlpha: (value: string): boolean =>
33
- value.match(/^#([a-f0-9]{8}|[a-f0-9]{4})$/i) !== null,
34
- isValidDate: (value: string): boolean =>
35
- value.match(/^\d{4}-\d{2}-\d{2}$/) !== null,
36
- isValidTime: (value: string): boolean =>
37
- value.match(/^\d{2}:\d{2}(:\d{2})?$/) !== null,
38
- isNullish: (value: any): boolean => _.isNull(value) || _.isUndefined(value),
39
- isUndefined: (value: any): boolean => _.isUndefined(value),
40
- isDefined: (value: any): boolean =>
41
- !_.isUndefined(value) && !_.isNull(value) && !_.isEmpty(value),
42
- isDate: (value: any): boolean => _.isDate(value),
43
- isEmpty: (value: any): boolean => _.isEmpty(value),
44
- isInteger: (value: any): boolean => _.isInteger(value),
45
- isFloat: (value: any): boolean => _.isNumber(value) && !_.isInteger(value),
46
- isArrayLike: (value: any): boolean => _.isArrayLike(value),
47
- isArrayLikeObject: (value: any): boolean => _.isArrayLikeObject(value),
48
- isFunction: (value: any): boolean => _.isFunction(value),
49
- isLength: (value: any): boolean => _.isLength(value),
50
- isMap: (value: any): boolean => _.isMap(value),
51
- isSet: (value: any): boolean => _.isSet(value),
52
- isRegExp: (value: any): boolean => _.isRegExp(value),
53
- isSymbol: (value: any): boolean => _.isSymbol(value),
54
- isObjectLike: (value: any): boolean => _.isObjectLike(value),
55
- isPlainObject: (value: any): boolean => _.isPlainObject(value),
56
- isSafeInteger: (value: any): boolean => _.isSafeInteger(value),
57
- isTypedArray: (value: any): boolean => _.isTypedArray(value),
58
- isEqual: (value: any, other: any): boolean => _.isEqual(value, other),
59
- isMatch: (object: any, source: any): boolean => _.isMatch(object, source),
60
- has: (object: any, path: string): boolean => _.has(object, path),
61
- get: (object: any, path: string, defaultValue: any): any =>
62
- _.get(object, path, defaultValue),
63
- };
1
+ import _ from "lodash";
2
+
3
+ export interface ValidationRules {
4
+ [key: string]: (value: any, ...args: any[]) => boolean;
5
+ }
6
+
7
+ export const validationRules = {
8
+ isNumber: (value: any): boolean => _.isNumber(value),
9
+ isString: (value: any): boolean => _.isString(value),
10
+ isBoolean: (value: any): boolean => _.isBoolean(value),
11
+ isArray: (value: any): boolean => _.isArray(value),
12
+ isObject: (value: any): boolean =>
13
+ _.isObject(value) && !_.isArray(value) && !_.isFunction(value),
14
+ isNull: (value: any): boolean => _.isNull(value),
15
+ isValidEmail: (value: string): boolean =>
16
+ value.match(/^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$/) !== null,
17
+ isValidPhone: (value: string): boolean =>
18
+ value.match(/^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/im) !==
19
+ null,
20
+ isValidPassword: (value: string): boolean =>
21
+ value.match(
22
+ /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/
23
+ ) !== null,
24
+ isValidUrl: (value: string): boolean =>
25
+ value.match(
26
+ /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
27
+ ) !== null,
28
+ isValidHex: (value: string): boolean =>
29
+ value.match(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i) !== null,
30
+ isValidHexColor: (value: string): boolean =>
31
+ value.match(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i) !== null,
32
+ isValidHexAlpha: (value: string): boolean =>
33
+ value.match(/^#([a-f0-9]{8}|[a-f0-9]{4})$/i) !== null,
34
+ isValidDate: (value: string): boolean =>
35
+ value.match(/^\d{4}-\d{2}-\d{2}$/) !== null,
36
+ isValidTime: (value: string): boolean =>
37
+ value.match(/^\d{2}:\d{2}(:\d{2})?$/) !== null,
38
+ isNullish: (value: any): boolean => _.isNull(value) || _.isUndefined(value),
39
+ isUndefined: (value: any): boolean => _.isUndefined(value),
40
+ isDefined: (value: any): boolean =>
41
+ !_.isUndefined(value) && !_.isNull(value) && !_.isEmpty(value),
42
+ isDate: (value: any): boolean => _.isDate(value),
43
+ isEmpty: (value: any): boolean => _.isEmpty(value),
44
+ isInteger: (value: any): boolean => _.isInteger(value),
45
+ isFloat: (value: any): boolean => _.isNumber(value) && !_.isInteger(value),
46
+ isArrayLike: (value: any): boolean => _.isArrayLike(value),
47
+ isArrayLikeObject: (value: any): boolean => _.isArrayLikeObject(value),
48
+ isFunction: (value: any): boolean => _.isFunction(value),
49
+ isLength: (value: any): boolean => _.isLength(value),
50
+ isMap: (value: any): boolean => _.isMap(value),
51
+ isSet: (value: any): boolean => _.isSet(value),
52
+ isRegExp: (value: any): boolean => _.isRegExp(value),
53
+ isSymbol: (value: any): boolean => _.isSymbol(value),
54
+ isObjectLike: (value: any): boolean => _.isObjectLike(value),
55
+ isPlainObject: (value: any): boolean => _.isPlainObject(value),
56
+ isSafeInteger: (value: any): boolean => _.isSafeInteger(value),
57
+ isTypedArray: (value: any): boolean => _.isTypedArray(value),
58
+ isEqual: (value: any, other: any): boolean => _.isEqual(value, other),
59
+ isMatch: (object: any, source: any): boolean => _.isMatch(object, source),
60
+ has: (object: any, path: string): boolean => _.has(object, path),
61
+ get: (object: any, path: string, defaultValue: any): any =>
62
+ _.get(object, path, defaultValue),
63
+ };
@@ -1,23 +1,23 @@
1
- import { z } from "zod";
2
-
3
- export const AuthUserSchema = z.object({
4
- $id: z.string(),
5
- $createdAt: z.string().optional(),
6
- $updatedAt: z.string().optional(),
7
- name: z.string().nullish(),
8
- email: z.string().email("Invalid Email Address").nullish(),
9
- phone: z.string().nullish(),
10
- prefs: z.record(z.string()).optional().default({}),
11
- labels: z.array(z.string()).optional().default([]),
12
- });
13
-
14
- export type AuthUser = z.infer<typeof AuthUserSchema>;
15
-
16
- export const AuthUserCreateSchema = AuthUserSchema.omit({
17
- $id: true,
18
- }).extend({
19
- userId: z.string().optional(),
20
- password: z.string().optional(),
21
- });
22
-
23
- export type AuthUserCreate = z.infer<typeof AuthUserCreateSchema>;
1
+ import { z } from "zod";
2
+
3
+ export const AuthUserSchema = z.object({
4
+ $id: z.string(),
5
+ $createdAt: z.string().optional(),
6
+ $updatedAt: z.string().optional(),
7
+ name: z.string().nullish(),
8
+ email: z.string().email("Invalid Email Address").nullish(),
9
+ phone: z.string().nullish(),
10
+ prefs: z.record(z.string()).optional().default({}),
11
+ labels: z.array(z.string()).optional().default([]),
12
+ });
13
+
14
+ export type AuthUser = z.infer<typeof AuthUserSchema>;
15
+
16
+ export const AuthUserCreateSchema = AuthUserSchema.omit({
17
+ $id: true,
18
+ }).extend({
19
+ userId: z.string().optional(),
20
+ password: z.string().optional(),
21
+ });
22
+
23
+ export type AuthUserCreate = z.infer<typeof AuthUserCreateSchema>;
package/src/setup.ts CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env node
2
- import { setupDirsFiles } from "./utils/setupFiles.js";
3
-
4
- const args = process.argv.slice(2);
5
-
6
- const genExample = args.includes("--example");
7
-
8
- setupDirsFiles(genExample);
1
+ #!/usr/bin/env node
2
+ import { setupDirsFiles } from "./utils/setupFiles.js";
3
+
4
+ const args = process.argv.slice(2);
5
+
6
+ const genExample = args.includes("--example");
7
+
8
+ setupDirsFiles(genExample);
@@ -0,0 +1,371 @@
1
+ import {
2
+ Compression,
3
+ Databases,
4
+ Permission,
5
+ Query,
6
+ Role,
7
+ Storage,
8
+ type Models,
9
+ } from "node-appwrite";
10
+ import { tryAwaitWithRetry, type AppwriteConfig } from "appwrite-utils";
11
+ import { getClientFromConfig } from "../utils/getClientFromConfig.js";
12
+ import { ulid } from "ulidx";
13
+ import type { BackupCreate } from "./schemas.js";
14
+ import { logOperation } from "../migrations/helper.js";
15
+ import { splitIntoBatches } from "../migrations/migrationHelper.js";
16
+ import { retryFailedPromises } from "../utils/retryFailedPromises.js";
17
+ import { InputFile } from "node-appwrite/file";
18
+
19
+ export const getStorage = (config: AppwriteConfig) => {
20
+ const client = getClientFromConfig(config);
21
+ return new Storage(client!);
22
+ };
23
+
24
+ export const listBuckets = async (
25
+ storage: Storage,
26
+ queries?: string[],
27
+ search?: string
28
+ ) => {
29
+ return await storage.listBuckets(queries, search);
30
+ };
31
+
32
+ export const getBucket = async (storage: Storage, bucketId: string) => {
33
+ return await storage.getBucket(bucketId);
34
+ };
35
+
36
+ export const createBucket = async (
37
+ storage: Storage,
38
+ bucket: Omit<Models.Bucket, "$id" | "$createdAt" | "$updatedAt">,
39
+ bucketId?: string
40
+ ) => {
41
+ return await storage.createBucket(
42
+ bucketId ?? ulid(),
43
+ bucket.name,
44
+ bucket.$permissions,
45
+ bucket.fileSecurity,
46
+ bucket.enabled,
47
+ bucket.maximumFileSize,
48
+ bucket.allowedFileExtensions,
49
+ bucket.compression as Compression,
50
+ bucket.encryption,
51
+ bucket.antivirus
52
+ );
53
+ };
54
+
55
+ export const updateBucket = async (
56
+ storage: Storage,
57
+ bucket: Models.Bucket,
58
+ bucketId: string
59
+ ) => {
60
+ return await storage.updateBucket(
61
+ bucketId,
62
+ bucket.name,
63
+ bucket.$permissions,
64
+ bucket.fileSecurity,
65
+ bucket.enabled,
66
+ bucket.maximumFileSize,
67
+ bucket.allowedFileExtensions,
68
+ bucket.compression as Compression,
69
+ bucket.encryption,
70
+ bucket.antivirus
71
+ );
72
+ };
73
+
74
+ export const deleteBucket = async (storage: Storage, bucketId: string) => {
75
+ return await storage.deleteBucket(bucketId);
76
+ };
77
+
78
+ export const getFile = async (
79
+ storage: Storage,
80
+ bucketId: string,
81
+ fileId: string
82
+ ) => {
83
+ return await storage.getFile(bucketId, fileId);
84
+ };
85
+
86
+ export const listFiles = async (
87
+ storage: Storage,
88
+ bucketId: string,
89
+ queries?: string[],
90
+ search?: string
91
+ ) => {
92
+ return await storage.listFiles(bucketId, queries, search);
93
+ };
94
+
95
+ export const deleteFile = async (
96
+ storage: Storage,
97
+ bucketId: string,
98
+ fileId: string
99
+ ) => {
100
+ return await storage.deleteFile(bucketId, fileId);
101
+ };
102
+
103
+ export const wipeDocumentStorage = async (
104
+ storage: Storage,
105
+ bucketId: string
106
+ ): Promise<void> => {
107
+ console.log(`Wiping storage for bucket ID: ${bucketId}`);
108
+ let moreFiles = true;
109
+ let lastFileId: string | undefined;
110
+ const allFiles: string[] = [];
111
+ while (moreFiles) {
112
+ const queries = [Query.limit(100)]; // Adjust the limit as needed
113
+ if (lastFileId) {
114
+ queries.push(Query.cursorAfter(lastFileId));
115
+ }
116
+ const filesPulled = await tryAwaitWithRetry(
117
+ async () => await storage.listFiles(bucketId, queries)
118
+ );
119
+ if (filesPulled.files.length === 0) {
120
+ console.log("No files found, done!");
121
+ moreFiles = false;
122
+ break;
123
+ } else if (filesPulled.files.length > 0) {
124
+ const fileIds = filesPulled.files.map((file) => file.$id);
125
+ allFiles.push(...fileIds);
126
+ }
127
+ moreFiles = filesPulled.files.length === 100; // Adjust based on the limit
128
+ if (moreFiles) {
129
+ lastFileId = filesPulled.files[filesPulled.files.length - 1].$id;
130
+ }
131
+ }
132
+
133
+ for (const fileId of allFiles) {
134
+ console.log(`Deleting file: ${fileId}`);
135
+ await tryAwaitWithRetry(
136
+ async () => await storage.deleteFile(bucketId, fileId)
137
+ );
138
+ }
139
+ console.log(`All files in bucket ${bucketId} have been deleted.`);
140
+ };
141
+
142
+ export const initOrGetDocumentStorage = async (
143
+ storage: Storage,
144
+ config: AppwriteConfig,
145
+ dbId: string,
146
+ bucketName?: string
147
+ ) => {
148
+ const bucketId =
149
+ bucketName ??
150
+ `${config.documentBucketId}_${dbId.toLowerCase().replace(" ", "")}`;
151
+ try {
152
+ return await tryAwaitWithRetry(
153
+ async () => await storage.getBucket(bucketId)
154
+ );
155
+ } catch (e) {
156
+ return await tryAwaitWithRetry(
157
+ async () =>
158
+ await storage.createBucket(bucketId, `${dbId} Storage`, [
159
+ Permission.read(Role.any()),
160
+ Permission.create(Role.users()),
161
+ Permission.update(Role.users()),
162
+ Permission.delete(Role.users()),
163
+ ])
164
+ );
165
+ }
166
+ };
167
+
168
+ export const initOrGetBackupStorage = async (
169
+ config: AppwriteConfig,
170
+ storage: Storage
171
+ ) => {
172
+ try {
173
+ return await tryAwaitWithRetry(
174
+ async () => await storage.getBucket("backup")
175
+ );
176
+ } catch (e) {
177
+ return await initOrGetDocumentStorage(
178
+ storage,
179
+ config,
180
+ "backups",
181
+ "Database Backups"
182
+ );
183
+ }
184
+ };
185
+
186
+ export const backupDatabase = async (
187
+ config: AppwriteConfig,
188
+ database: Databases,
189
+ databaseId: string,
190
+ storage: Storage
191
+ ): Promise<void> => {
192
+ console.log("---------------------------------");
193
+ console.log("Starting Database Backup of " + databaseId);
194
+ console.log("---------------------------------");
195
+
196
+ let data: BackupCreate = {
197
+ database: "",
198
+ collections: [],
199
+ documents: [],
200
+ };
201
+
202
+ const backupOperation = await logOperation(database, databaseId, {
203
+ operationType: "backup",
204
+ collectionId: "",
205
+ data: "Starting backup...",
206
+ progress: 0,
207
+ total: 100,
208
+ error: "",
209
+ status: "in_progress",
210
+ });
211
+
212
+ try {
213
+ const db = await tryAwaitWithRetry(
214
+ async () => await database.get(databaseId)
215
+ );
216
+ data.database = JSON.stringify(db);
217
+
218
+ let lastCollectionId = "";
219
+ let moreCollections = true;
220
+ let progress = 0;
221
+ let total = 0;
222
+
223
+ while (moreCollections) {
224
+ const collectionResponse = await tryAwaitWithRetry(
225
+ async () =>
226
+ await database.listCollections(databaseId, [
227
+ Query.limit(500),
228
+ ...(lastCollectionId ? [Query.cursorAfter(lastCollectionId)] : []),
229
+ ])
230
+ );
231
+
232
+ total += collectionResponse.collections.length;
233
+
234
+ for (const {
235
+ $id: collectionId,
236
+ name: collectionName,
237
+ } of collectionResponse.collections) {
238
+ try {
239
+ const collection = await tryAwaitWithRetry(
240
+ async () => await database.getCollection(databaseId, collectionId)
241
+ );
242
+ progress++;
243
+ data.collections.push(JSON.stringify(collection));
244
+
245
+ let lastDocumentId = "";
246
+ let moreDocuments = true;
247
+ let collectionDocumentCount = 0;
248
+
249
+ while (moreDocuments) {
250
+ const documentResponse = await tryAwaitWithRetry(
251
+ async () =>
252
+ await database.listDocuments(databaseId, collectionId, [
253
+ Query.limit(500),
254
+ ...(lastDocumentId
255
+ ? [Query.cursorAfter(lastDocumentId)]
256
+ : []),
257
+ ])
258
+ );
259
+
260
+ total += documentResponse.documents.length;
261
+ collectionDocumentCount += documentResponse.documents.length;
262
+
263
+ const documentPromises = documentResponse.documents.map(
264
+ ({ $id: documentId }) =>
265
+ database.getDocument(databaseId, collectionId, documentId)
266
+ );
267
+
268
+ const promiseBatches = splitIntoBatches(documentPromises);
269
+ const documentsPulled = [];
270
+ for (const batch of promiseBatches) {
271
+ const successfulDocuments = await retryFailedPromises(batch);
272
+ documentsPulled.push(...successfulDocuments);
273
+ }
274
+
275
+ data.documents.push({
276
+ collectionId: collectionId,
277
+ data: JSON.stringify(documentsPulled),
278
+ });
279
+ progress += documentsPulled.length;
280
+
281
+ await logOperation(
282
+ database,
283
+ databaseId,
284
+ {
285
+ operationType: "backup",
286
+ collectionId: collectionId,
287
+ data: `Backing up, ${data.collections.length} collections so far`,
288
+ progress: progress,
289
+ total: total,
290
+ error: "",
291
+ status: "in_progress",
292
+ },
293
+ backupOperation.$id
294
+ );
295
+
296
+ moreDocuments = documentResponse.documents.length === 500;
297
+ if (moreDocuments) {
298
+ lastDocumentId =
299
+ documentResponse.documents[
300
+ documentResponse.documents.length - 1
301
+ ].$id;
302
+ }
303
+ }
304
+
305
+ console.log(
306
+ `Collection ${collectionName} backed up with ${collectionDocumentCount} documents.`
307
+ );
308
+ } catch (error) {
309
+ console.log(
310
+ `Collection ${collectionName} must not exist, continuing...`
311
+ );
312
+ continue;
313
+ }
314
+ }
315
+
316
+ moreCollections = collectionResponse.collections.length === 500;
317
+ if (moreCollections) {
318
+ lastCollectionId =
319
+ collectionResponse.collections[
320
+ collectionResponse.collections.length - 1
321
+ ].$id;
322
+ }
323
+ }
324
+
325
+ const bucket = await initOrGetDocumentStorage(storage, config, databaseId);
326
+ const inputFile = InputFile.fromPlainText(
327
+ JSON.stringify(data),
328
+ `${new Date().toISOString()}-${databaseId}.json`
329
+ );
330
+ const fileCreated = await storage.createFile(
331
+ bucket!.$id,
332
+ ulid(),
333
+ inputFile
334
+ );
335
+
336
+ await logOperation(
337
+ database,
338
+ databaseId,
339
+ {
340
+ operationType: "backup",
341
+ collectionId: "",
342
+ data: fileCreated.$id,
343
+ progress: 100,
344
+ total: total,
345
+ error: "",
346
+ status: "completed",
347
+ },
348
+ backupOperation.$id
349
+ );
350
+
351
+ console.log("---------------------------------");
352
+ console.log("Database Backup Complete");
353
+ console.log("---------------------------------");
354
+ } catch (error) {
355
+ console.error("Error during backup:", error);
356
+ await logOperation(
357
+ database,
358
+ databaseId,
359
+ {
360
+ operationType: "backup",
361
+ collectionId: "",
362
+ data: "Backup failed",
363
+ progress: 0,
364
+ total: 100,
365
+ error: String(error),
366
+ status: "error",
367
+ },
368
+ backupOperation.$id
369
+ );
370
+ }
371
+ };