appwrite-utils-cli 0.10.82 → 0.10.84
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.
- package/README.md +1 -0
- package/dist/collections/attributes.js +86 -41
- package/dist/collections/methods.d.ts +6 -0
- package/dist/collections/methods.js +137 -2
- package/dist/migrations/appwriteToX.js +1 -1
- package/dist/migrations/attributes.js +0 -1
- package/dist/migrations/converters.js +3 -4
- package/dist/migrations/dataLoader.js +3 -3
- package/dist/migrations/importController.js +0 -1
- package/dist/migrations/queue.js +1 -2
- package/dist/migrations/relationships.js +1 -1
- package/dist/migrations/users.js +5 -4
- package/dist/migrations/validationRules.js +30 -30
- package/package.json +1 -1
- package/src/collections/attributes.ts +126 -50
- package/src/collections/methods.ts +222 -2
- package/src/migrations/appwriteToX.ts +4 -4
- package/src/migrations/attributes.ts +0 -1
- package/src/migrations/converters.ts +3 -5
- package/src/migrations/dataLoader.ts +4 -4
- package/src/migrations/importController.ts +0 -1
- package/src/migrations/queue.ts +1 -2
- package/src/migrations/relationships.ts +1 -1
- package/src/migrations/users.ts +8 -7
- package/src/migrations/validationRules.ts +56 -31
- package/src/migrations/collections.ts +0 -545
package/README.md
CHANGED
@@ -150,6 +150,7 @@ This updated CLI ensures that developers have robust tools at their fingertips t
|
|
150
150
|
|
151
151
|
## Changelog
|
152
152
|
|
153
|
+
- 0.10.83: Actually fixed the import oops
|
153
154
|
- 0.10.82: Fixed the `lodash` import, replaced with `es-toolkit`
|
154
155
|
- 0.10.81: Fixed `wipeCollection` -- it wasn't properly deleting all files in a loop
|
155
156
|
- 0.10.80: Updated `appwrite-utils` req
|
@@ -1,28 +1,27 @@
|
|
1
1
|
import { Query } from "node-appwrite";
|
2
2
|
import { attributeSchema, parseAttribute, } from "appwrite-utils";
|
3
3
|
import { nameToIdMapping, enqueueOperation } from "../migrations/queue.js";
|
4
|
-
import _ from "lodash";
|
5
4
|
import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
6
5
|
import chalk from "chalk";
|
7
6
|
const attributesSame = (databaseAttribute, configAttribute) => {
|
8
7
|
const attributesToCheck = [
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
8
|
+
"key",
|
9
|
+
"type",
|
10
|
+
"array",
|
11
|
+
"encrypted",
|
12
|
+
"required",
|
13
|
+
"size",
|
14
|
+
"min",
|
15
|
+
"max",
|
16
|
+
"xdefault",
|
17
|
+
"elements",
|
18
|
+
"relationType",
|
19
|
+
"twoWay",
|
20
|
+
"twoWayKey",
|
21
|
+
"onDelete",
|
22
|
+
"relatedCollection",
|
24
23
|
];
|
25
|
-
return attributesToCheck.every(attr => {
|
24
|
+
return attributesToCheck.every((attr) => {
|
26
25
|
// Check if both objects have the attribute
|
27
26
|
const dbHasAttr = attr in databaseAttribute;
|
28
27
|
const configHasAttr = attr in configAttribute;
|
@@ -31,7 +30,8 @@ const attributesSame = (databaseAttribute, configAttribute) => {
|
|
31
30
|
const dbValue = databaseAttribute[attr];
|
32
31
|
const configValue = configAttribute[attr];
|
33
32
|
// Consider undefined and null as equivalent
|
34
|
-
if ((dbValue === undefined || dbValue === null) &&
|
33
|
+
if ((dbValue === undefined || dbValue === null) &&
|
34
|
+
(configValue === undefined || configValue === null)) {
|
35
35
|
return true;
|
36
36
|
}
|
37
37
|
return dbValue === configValue;
|
@@ -68,11 +68,15 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
68
68
|
catch (error) {
|
69
69
|
foundAttribute = undefined;
|
70
70
|
}
|
71
|
-
if (foundAttribute &&
|
71
|
+
if (foundAttribute &&
|
72
|
+
attributesSame(foundAttribute, attribute) &&
|
73
|
+
updateEnabled) {
|
72
74
|
// No need to do anything, they are the same
|
73
75
|
return;
|
74
76
|
}
|
75
|
-
else if (foundAttribute &&
|
77
|
+
else if (foundAttribute &&
|
78
|
+
!attributesSame(foundAttribute, attribute) &&
|
79
|
+
updateEnabled) {
|
76
80
|
// console.log(
|
77
81
|
// `Updating attribute with same key ${attribute.key} but different values`
|
78
82
|
// );
|
@@ -82,7 +86,9 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
82
86
|
};
|
83
87
|
action = "update";
|
84
88
|
}
|
85
|
-
else if (!updateEnabled &&
|
89
|
+
else if (!updateEnabled &&
|
90
|
+
foundAttribute &&
|
91
|
+
!attributesSame(foundAttribute, attribute)) {
|
86
92
|
await db.deleteAttribute(dbId, collection.$id, attribute.key);
|
87
93
|
console.log(`Deleted attribute: ${attribute.key} to recreate it because they diff (update disabled temporarily)`);
|
88
94
|
return;
|
@@ -131,10 +137,14 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
131
137
|
switch (finalAttribute.type) {
|
132
138
|
case "string":
|
133
139
|
if (action === "create") {
|
134
|
-
await tryAwaitWithRetry(async () => await db.createStringAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.size, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
140
|
+
await tryAwaitWithRetry(async () => await db.createStringAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.size, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
141
|
+
? finalAttribute.xdefault
|
142
|
+
: null, finalAttribute.array || false, finalAttribute.encrypted));
|
135
143
|
}
|
136
144
|
else {
|
137
|
-
await tryAwaitWithRetry(async () => await db.updateStringAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
145
|
+
await tryAwaitWithRetry(async () => await db.updateStringAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
146
|
+
? finalAttribute.xdefault
|
147
|
+
: null));
|
138
148
|
}
|
139
149
|
break;
|
140
150
|
case "integer":
|
@@ -147,7 +157,9 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
147
157
|
BigInt(finalAttribute.max) === BigInt(9223372036854776000)) {
|
148
158
|
delete finalAttribute.max;
|
149
159
|
}
|
150
|
-
await tryAwaitWithRetry(async () => await db.createIntegerAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
160
|
+
await tryAwaitWithRetry(async () => await db.createIntegerAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
161
|
+
? finalAttribute.xdefault
|
162
|
+
: null, finalAttribute.array || false));
|
151
163
|
}
|
152
164
|
else {
|
153
165
|
if (finalAttribute.min &&
|
@@ -158,63 +170,93 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
158
170
|
BigInt(finalAttribute.max) === BigInt(9223372036854776000)) {
|
159
171
|
delete finalAttribute.max;
|
160
172
|
}
|
161
|
-
await tryAwaitWithRetry(async () => await db.updateIntegerAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
173
|
+
await tryAwaitWithRetry(async () => await db.updateIntegerAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
174
|
+
? finalAttribute.xdefault
|
175
|
+
: null));
|
162
176
|
}
|
163
177
|
break;
|
164
178
|
case "float":
|
165
179
|
if (action === "create") {
|
166
|
-
await tryAwaitWithRetry(async () => await db.createFloatAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
180
|
+
await tryAwaitWithRetry(async () => await db.createFloatAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
181
|
+
? finalAttribute.xdefault
|
182
|
+
: null, finalAttribute.array || false));
|
167
183
|
}
|
168
184
|
else {
|
169
|
-
await tryAwaitWithRetry(async () => await db.updateFloatAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
185
|
+
await tryAwaitWithRetry(async () => await db.updateFloatAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.min || -2147483647, finalAttribute.max || 2147483647, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
186
|
+
? finalAttribute.xdefault
|
187
|
+
: null));
|
170
188
|
}
|
171
189
|
break;
|
172
190
|
case "boolean":
|
173
191
|
if (action === "create") {
|
174
|
-
await tryAwaitWithRetry(async () => await db.createBooleanAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
192
|
+
await tryAwaitWithRetry(async () => await db.createBooleanAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
193
|
+
? finalAttribute.xdefault
|
194
|
+
: null, finalAttribute.array || false));
|
175
195
|
}
|
176
196
|
else {
|
177
|
-
await tryAwaitWithRetry(async () => await db.updateBooleanAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
197
|
+
await tryAwaitWithRetry(async () => await db.updateBooleanAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
198
|
+
? finalAttribute.xdefault
|
199
|
+
: null));
|
178
200
|
}
|
179
201
|
break;
|
180
202
|
case "datetime":
|
181
203
|
if (action === "create") {
|
182
|
-
await tryAwaitWithRetry(async () => await db.createDatetimeAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
204
|
+
await tryAwaitWithRetry(async () => await db.createDatetimeAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
205
|
+
? finalAttribute.xdefault
|
206
|
+
: null, finalAttribute.array || false));
|
183
207
|
}
|
184
208
|
else {
|
185
|
-
await tryAwaitWithRetry(async () => await db.updateDatetimeAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
209
|
+
await tryAwaitWithRetry(async () => await db.updateDatetimeAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
210
|
+
? finalAttribute.xdefault
|
211
|
+
: null));
|
186
212
|
}
|
187
213
|
break;
|
188
214
|
case "email":
|
189
215
|
if (action === "create") {
|
190
|
-
await tryAwaitWithRetry(async () => await db.createEmailAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
216
|
+
await tryAwaitWithRetry(async () => await db.createEmailAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
217
|
+
? finalAttribute.xdefault
|
218
|
+
: null, finalAttribute.array || false));
|
191
219
|
}
|
192
220
|
else {
|
193
|
-
await tryAwaitWithRetry(async () => await db.updateEmailAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
221
|
+
await tryAwaitWithRetry(async () => await db.updateEmailAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
222
|
+
? finalAttribute.xdefault
|
223
|
+
: null));
|
194
224
|
}
|
195
225
|
break;
|
196
226
|
case "ip":
|
197
227
|
if (action === "create") {
|
198
|
-
await tryAwaitWithRetry(async () => await db.createIpAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
228
|
+
await tryAwaitWithRetry(async () => await db.createIpAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
229
|
+
? finalAttribute.xdefault
|
230
|
+
: null, finalAttribute.array || false));
|
199
231
|
}
|
200
232
|
else {
|
201
|
-
await tryAwaitWithRetry(async () => await db.updateIpAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
233
|
+
await tryAwaitWithRetry(async () => await db.updateIpAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
234
|
+
? finalAttribute.xdefault
|
235
|
+
: null));
|
202
236
|
}
|
203
237
|
break;
|
204
238
|
case "url":
|
205
239
|
if (action === "create") {
|
206
|
-
await tryAwaitWithRetry(async () => await db.createUrlAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
240
|
+
await tryAwaitWithRetry(async () => await db.createUrlAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
241
|
+
? finalAttribute.xdefault
|
242
|
+
: null, finalAttribute.array || false));
|
207
243
|
}
|
208
244
|
else {
|
209
|
-
await tryAwaitWithRetry(async () => await db.updateUrlAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
245
|
+
await tryAwaitWithRetry(async () => await db.updateUrlAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
246
|
+
? finalAttribute.xdefault
|
247
|
+
: null));
|
210
248
|
}
|
211
249
|
break;
|
212
250
|
case "enum":
|
213
251
|
if (action === "create") {
|
214
|
-
await tryAwaitWithRetry(async () => await db.createEnumAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.elements, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
252
|
+
await tryAwaitWithRetry(async () => await db.createEnumAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.elements, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
253
|
+
? finalAttribute.xdefault
|
254
|
+
: null, finalAttribute.array || false));
|
215
255
|
}
|
216
256
|
else {
|
217
|
-
await tryAwaitWithRetry(async () => await db.updateEnumAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.elements, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
257
|
+
await tryAwaitWithRetry(async () => await db.updateEnumAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.elements, finalAttribute.required || false, finalAttribute.xdefault !== undefined && !finalAttribute.required
|
258
|
+
? finalAttribute.xdefault
|
259
|
+
: null));
|
218
260
|
}
|
219
261
|
break;
|
220
262
|
case "relationship":
|
@@ -232,13 +274,16 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
232
274
|
};
|
233
275
|
export const createUpdateCollectionAttributes = async (db, dbId, collection, attributes) => {
|
234
276
|
console.log(chalk.green(`Creating/Updating attributes for collection: ${collection.name}`));
|
277
|
+
const existingAttributes =
|
235
278
|
// @ts-expect-error
|
236
|
-
|
279
|
+
collection.attributes.map((attr) => parseAttribute(attr)) || [];
|
237
280
|
const attributesToRemove = existingAttributes.filter((attr) => !attributes.some((a) => a.key === attr.key));
|
238
281
|
const indexesToRemove = collection.indexes.filter((index) => attributesToRemove.some((attr) => index.attributes.includes(attr.key)));
|
239
282
|
if (attributesToRemove.length > 0) {
|
240
283
|
if (indexesToRemove.length > 0) {
|
241
|
-
console.log(chalk.red(`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove
|
284
|
+
console.log(chalk.red(`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove
|
285
|
+
.map((index) => index.key)
|
286
|
+
.join(", ")}`));
|
242
287
|
for (const index of indexesToRemove) {
|
243
288
|
await tryAwaitWithRetry(async () => await db.deleteIndex(dbId, collection.$id, index.key));
|
244
289
|
await delay(100);
|
@@ -15,3 +15,9 @@ export declare const createOrUpdateCollections: (database: Databases, databaseId
|
|
15
15
|
}[], selectedCollections?: Models.Collection[]) => Promise<void>;
|
16
16
|
export declare const generateMockData: (database: Databases, databaseId: string, configCollections: any[]) => Promise<void>;
|
17
17
|
export declare const fetchAllCollections: (dbId: string, database: Databases) => Promise<Models.Collection[]>;
|
18
|
+
/**
|
19
|
+
* Transfers all documents from one collection to another in a different database
|
20
|
+
* within the same Appwrite Project
|
21
|
+
*/
|
22
|
+
export declare const transferDocumentsBetweenDbsLocalToLocal: (db: Databases, fromDbId: string, toDbId: string, fromCollId: string, toCollId: string) => Promise<void>;
|
23
|
+
export declare const transferDocumentsBetweenDbsLocalToRemote: (localDb: Databases, endpoint: string, projectId: string, apiKey: string, fromDbId: string, toDbId: string, fromCollId: string, toCollId: string) => Promise<void>;
|
@@ -95,7 +95,7 @@ async function wipeDocumentsFromCollection(database, databaseId, collectionId) {
|
|
95
95
|
? initialDocuments.documents[initialDocuments.documents.length - 1].$id
|
96
96
|
: undefined;
|
97
97
|
while (cursor) {
|
98
|
-
const docsResponse = await database.listDocuments(databaseId, collectionId, [Query.limit(1000)]);
|
98
|
+
const docsResponse = await database.listDocuments(databaseId, collectionId, [Query.limit(1000), ...(cursor ? [Query.cursorAfter(cursor)] : [])]);
|
99
99
|
documents.push(...docsResponse.documents);
|
100
100
|
totalDocuments = documents.length;
|
101
101
|
cursor =
|
@@ -243,8 +243,12 @@ export const createOrUpdateCollections = async (database, databaseId, config, de
|
|
243
243
|
attributes);
|
244
244
|
// Add delay after creating attributes
|
245
245
|
await delay(250);
|
246
|
+
const indexesToUse = indexes.length > 0
|
247
|
+
? indexes
|
248
|
+
: config.collections?.find((c) => c.$id === collectionToUse.$id)
|
249
|
+
?.indexes ?? [];
|
246
250
|
console.log("Creating Indexes");
|
247
|
-
await createOrUpdateIndexes(databaseId, database, collectionToUse.$id,
|
251
|
+
await createOrUpdateIndexes(databaseId, database, collectionToUse.$id, indexesToUse);
|
248
252
|
// Add delay after creating indexes
|
249
253
|
await delay(250);
|
250
254
|
}
|
@@ -283,3 +287,134 @@ export const fetchAllCollections = async (dbId, database) => {
|
|
283
287
|
console.log(`Fetched a total of ${collections.length} collections.`);
|
284
288
|
return collections;
|
285
289
|
};
|
290
|
+
/**
|
291
|
+
* Transfers all documents from one collection to another in a different database
|
292
|
+
* within the same Appwrite Project
|
293
|
+
*/
|
294
|
+
export const transferDocumentsBetweenDbsLocalToLocal = async (db, fromDbId, toDbId, fromCollId, toCollId) => {
|
295
|
+
let fromCollDocs = await tryAwaitWithRetry(async () => db.listDocuments(fromDbId, fromCollId, [Query.limit(50)]));
|
296
|
+
let totalDocumentsTransferred = 0;
|
297
|
+
if (fromCollDocs.documents.length === 0) {
|
298
|
+
console.log(`No documents found in collection ${fromCollId}`);
|
299
|
+
return;
|
300
|
+
}
|
301
|
+
else if (fromCollDocs.documents.length < 50) {
|
302
|
+
const batchedPromises = fromCollDocs.documents.map((doc) => {
|
303
|
+
const toCreateObject = {
|
304
|
+
...doc,
|
305
|
+
};
|
306
|
+
delete toCreateObject.$databaseId;
|
307
|
+
delete toCreateObject.$collectionId;
|
308
|
+
delete toCreateObject.$createdAt;
|
309
|
+
delete toCreateObject.$updatedAt;
|
310
|
+
delete toCreateObject.$id;
|
311
|
+
delete toCreateObject.$permissions;
|
312
|
+
return tryAwaitWithRetry(async () => await db.createDocument(toDbId, toCollId, doc.$id, toCreateObject, doc.$permissions));
|
313
|
+
});
|
314
|
+
await Promise.all(batchedPromises);
|
315
|
+
totalDocumentsTransferred += fromCollDocs.documents.length;
|
316
|
+
}
|
317
|
+
else {
|
318
|
+
const batchedPromises = fromCollDocs.documents.map((doc) => {
|
319
|
+
const toCreateObject = {
|
320
|
+
...doc,
|
321
|
+
};
|
322
|
+
delete toCreateObject.$databaseId;
|
323
|
+
delete toCreateObject.$collectionId;
|
324
|
+
delete toCreateObject.$createdAt;
|
325
|
+
delete toCreateObject.$updatedAt;
|
326
|
+
delete toCreateObject.$id;
|
327
|
+
delete toCreateObject.$permissions;
|
328
|
+
return tryAwaitWithRetry(async () => db.createDocument(toDbId, toCollId, doc.$id, toCreateObject, doc.$permissions));
|
329
|
+
});
|
330
|
+
await Promise.all(batchedPromises);
|
331
|
+
totalDocumentsTransferred += fromCollDocs.documents.length;
|
332
|
+
while (fromCollDocs.documents.length === 50) {
|
333
|
+
fromCollDocs = await tryAwaitWithRetry(async () => await db.listDocuments(fromDbId, fromCollId, [
|
334
|
+
Query.limit(50),
|
335
|
+
Query.cursorAfter(fromCollDocs.documents[fromCollDocs.documents.length - 1].$id),
|
336
|
+
]));
|
337
|
+
const batchedPromises = fromCollDocs.documents.map((doc) => {
|
338
|
+
const toCreateObject = {
|
339
|
+
...doc,
|
340
|
+
};
|
341
|
+
delete toCreateObject.$databaseId;
|
342
|
+
delete toCreateObject.$collectionId;
|
343
|
+
delete toCreateObject.$createdAt;
|
344
|
+
delete toCreateObject.$updatedAt;
|
345
|
+
delete toCreateObject.$id;
|
346
|
+
delete toCreateObject.$permissions;
|
347
|
+
return tryAwaitWithRetry(async () => await db.createDocument(toDbId, toCollId, doc.$id, toCreateObject, doc.$permissions));
|
348
|
+
});
|
349
|
+
await Promise.all(batchedPromises);
|
350
|
+
totalDocumentsTransferred += fromCollDocs.documents.length;
|
351
|
+
}
|
352
|
+
}
|
353
|
+
console.log(`Transferred ${totalDocumentsTransferred} documents from database ${fromDbId} to database ${toDbId} -- collection ${fromCollId} to collection ${toCollId}`);
|
354
|
+
};
|
355
|
+
export const transferDocumentsBetweenDbsLocalToRemote = async (localDb, endpoint, projectId, apiKey, fromDbId, toDbId, fromCollId, toCollId) => {
|
356
|
+
const client = new Client()
|
357
|
+
.setEndpoint(endpoint)
|
358
|
+
.setProject(projectId)
|
359
|
+
.setKey(apiKey);
|
360
|
+
let totalDocumentsTransferred = 0;
|
361
|
+
const remoteDb = new Databases(client);
|
362
|
+
let fromCollDocs = await tryAwaitWithRetry(async () => localDb.listDocuments(fromDbId, fromCollId, [Query.limit(50)]));
|
363
|
+
if (fromCollDocs.documents.length === 0) {
|
364
|
+
console.log(`No documents found in collection ${fromCollId}`);
|
365
|
+
return;
|
366
|
+
}
|
367
|
+
else if (fromCollDocs.documents.length < 50) {
|
368
|
+
const batchedPromises = fromCollDocs.documents.map((doc) => {
|
369
|
+
const toCreateObject = {
|
370
|
+
...doc,
|
371
|
+
};
|
372
|
+
delete toCreateObject.$databaseId;
|
373
|
+
delete toCreateObject.$collectionId;
|
374
|
+
delete toCreateObject.$createdAt;
|
375
|
+
delete toCreateObject.$updatedAt;
|
376
|
+
delete toCreateObject.$id;
|
377
|
+
delete toCreateObject.$permissions;
|
378
|
+
return tryAwaitWithRetry(async () => remoteDb.createDocument(toDbId, toCollId, doc.$id, toCreateObject, doc.$permissions));
|
379
|
+
});
|
380
|
+
await Promise.all(batchedPromises);
|
381
|
+
totalDocumentsTransferred += fromCollDocs.documents.length;
|
382
|
+
}
|
383
|
+
else {
|
384
|
+
const batchedPromises = fromCollDocs.documents.map((doc) => {
|
385
|
+
const toCreateObject = {
|
386
|
+
...doc,
|
387
|
+
};
|
388
|
+
delete toCreateObject.$databaseId;
|
389
|
+
delete toCreateObject.$collectionId;
|
390
|
+
delete toCreateObject.$createdAt;
|
391
|
+
delete toCreateObject.$updatedAt;
|
392
|
+
delete toCreateObject.$id;
|
393
|
+
delete toCreateObject.$permissions;
|
394
|
+
return tryAwaitWithRetry(async () => remoteDb.createDocument(toDbId, toCollId, doc.$id, toCreateObject, doc.$permissions));
|
395
|
+
});
|
396
|
+
await Promise.all(batchedPromises);
|
397
|
+
totalDocumentsTransferred += fromCollDocs.documents.length;
|
398
|
+
while (fromCollDocs.documents.length === 50) {
|
399
|
+
fromCollDocs = await tryAwaitWithRetry(async () => localDb.listDocuments(fromDbId, fromCollId, [
|
400
|
+
Query.limit(50),
|
401
|
+
Query.cursorAfter(fromCollDocs.documents[fromCollDocs.documents.length - 1].$id),
|
402
|
+
]));
|
403
|
+
const batchedPromises = fromCollDocs.documents.map((doc) => {
|
404
|
+
const toCreateObject = {
|
405
|
+
...doc,
|
406
|
+
};
|
407
|
+
delete toCreateObject.$databaseId;
|
408
|
+
delete toCreateObject.$collectionId;
|
409
|
+
delete toCreateObject.$createdAt;
|
410
|
+
delete toCreateObject.$updatedAt;
|
411
|
+
delete toCreateObject.$id;
|
412
|
+
delete toCreateObject.$permissions;
|
413
|
+
return tryAwaitWithRetry(async () => remoteDb.createDocument(toDbId, toCollId, doc.$id, toCreateObject, doc.$permissions));
|
414
|
+
});
|
415
|
+
await Promise.all(batchedPromises);
|
416
|
+
totalDocumentsTransferred += fromCollDocs.documents.length;
|
417
|
+
}
|
418
|
+
}
|
419
|
+
console.log(`Total documents transferred from database ${fromDbId} to database ${toDbId} -- collection ${fromCollId} to collection ${toCollId}: ${totalDocumentsTransferred}`);
|
420
|
+
};
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { SchemaGenerator } from "./schemaStrings.js";
|
2
2
|
import { Client, Compression, Databases, Query, Storage, } from "node-appwrite";
|
3
|
-
import { fetchAllCollections } from "
|
3
|
+
import { fetchAllCollections } from "../collections/methods.js";
|
4
4
|
import { fetchAllDatabases } from "./databases.js";
|
5
5
|
import { CollectionSchema, attributeSchema, AppwriteConfigSchema, permissionsSchema, attributesSchema, indexesSchema, parseAttribute, } from "appwrite-utils";
|
6
6
|
import { getDatabaseFromConfig } from "./afterImportActions.js";
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import { Query } from "node-appwrite";
|
2
2
|
import { attributeSchema, parseAttribute, } from "appwrite-utils";
|
3
3
|
import { nameToIdMapping, enqueueOperation } from "./queue.js";
|
4
|
-
import _ from "lodash";
|
5
4
|
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
6
5
|
const attributesSame = (databaseAttribute, configAttribute) => {
|
7
6
|
const attributesToCheck = [
|
@@ -1,6 +1,5 @@
|
|
1
|
-
import _ from "lodash";
|
2
1
|
import { converterFunctions } from "appwrite-utils";
|
3
|
-
|
2
|
+
import { cloneDeep, isPlainObject } from "es-toolkit";
|
4
3
|
/**
|
5
4
|
* Deeply converts all properties of an object (or array) to strings.
|
6
5
|
* @param data The input data to convert.
|
@@ -10,7 +9,7 @@ export const deepAnyToString = (data) => {
|
|
10
9
|
if (Array.isArray(data)) {
|
11
10
|
return data.map((item) => deepAnyToString(item));
|
12
11
|
}
|
13
|
-
else if (
|
12
|
+
else if (isPlainObject(data)) {
|
14
13
|
return Object.keys(data).reduce((acc, key) => {
|
15
14
|
acc[key] = deepAnyToString(data[key]);
|
16
15
|
return acc;
|
@@ -31,7 +30,7 @@ export const deepConvert = (data, convertFn) => {
|
|
31
30
|
if (Array.isArray(data)) {
|
32
31
|
return data.map((item) => deepConvert(item, convertFn));
|
33
32
|
}
|
34
|
-
else if (
|
33
|
+
else if (isPlainObject(data)) {
|
35
34
|
return Object.keys(data).reduce((acc, key) => {
|
36
35
|
acc[key] = deepConvert(data[key], convertFn);
|
37
36
|
return acc;
|
@@ -3,14 +3,14 @@ import path from "path";
|
|
3
3
|
import fs from "fs";
|
4
4
|
import { convertObjectByAttributeMappings } from "./converters.js";
|
5
5
|
import { z } from "zod";
|
6
|
-
import { checkForCollection } from "
|
6
|
+
import { checkForCollection } from "../collections/methods.js";
|
7
7
|
import { ID, Users } from "node-appwrite";
|
8
8
|
import { logger } from "./logging.js";
|
9
9
|
import { findOrCreateOperation, updateOperation } from "./migrationHelper.js";
|
10
10
|
import { AuthUserCreateSchema } from "../schemas/authUser.js";
|
11
|
-
import _ from "lodash";
|
12
11
|
import { UsersController } from "./users.js";
|
13
12
|
import { finalizeByAttributeMap } from "../utils/helperFunctions.js";
|
13
|
+
import { isEmpty } from "es-toolkit/compat";
|
14
14
|
// Define a schema for the structure of collection import data using Zod for validation
|
15
15
|
export const CollectionImportDataSchema = z.object({
|
16
16
|
// Optional collection creation schema
|
@@ -447,7 +447,7 @@ export class DataLoader {
|
|
447
447
|
const sourceValue = this.getValueFromData(collectionData.data[i].finalData, collectionData.data[i].context, idMapping.sourceField);
|
448
448
|
// Skip if value to match is missing or empty
|
449
449
|
if (!sourceValue ||
|
450
|
-
|
450
|
+
isEmpty(sourceValue) ||
|
451
451
|
sourceValue === null)
|
452
452
|
continue;
|
453
453
|
const isFieldToSetArray = collectionConfig.attributes.find((attribute) => attribute.key === fieldToSetKey)?.array;
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { AppwriteException, ID, Query, } from "node-appwrite";
|
2
|
-
import _ from "lodash";
|
3
2
|
import { areCollectionNamesSame, tryAwaitWithRetry } from "../utils/index.js";
|
4
3
|
import { resolveAndUpdateRelationships } from "./relationships.js";
|
5
4
|
import { UsersController } from "./users.js";
|
package/dist/migrations/queue.js
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import { Query } from "node-appwrite";
|
2
2
|
import { createOrUpdateAttribute } from "./attributes.js";
|
3
|
-
import
|
4
|
-
import { fetchAndCacheCollectionByName } from "./collections.js";
|
3
|
+
import { fetchAndCacheCollectionByName } from "../collections/methods.js";
|
5
4
|
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
6
5
|
export const queuedOperations = [];
|
7
6
|
export const nameToIdMapping = new Map();
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Databases, Query } from "node-appwrite";
|
2
|
-
import { fetchAllCollections } from "
|
2
|
+
import { fetchAllCollections } from "../collections/methods.js";
|
3
3
|
import { logger } from "./logging.js";
|
4
4
|
/**
|
5
5
|
* Finds collections that have defined relationship attributes.
|
package/dist/migrations/users.js
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
import { AppwriteException, Databases, ID, Query, Users, } from "node-appwrite";
|
2
2
|
import { AuthUserSchema, } from "../schemas/authUser.js";
|
3
|
-
import _ from "lodash";
|
4
3
|
import { logger } from "./logging.js";
|
5
4
|
import { splitIntoBatches } from "./migrationHelper.js";
|
6
5
|
import { getAppwriteClient, tryAwaitWithRetry, } from "../utils/helperFunctions.js";
|
6
|
+
import { isUndefined } from "es-toolkit/compat";
|
7
|
+
import { isEmpty } from "es-toolkit/compat";
|
7
8
|
export class UsersController {
|
8
9
|
config;
|
9
10
|
users;
|
@@ -131,8 +132,8 @@ export class UsersController {
|
|
131
132
|
// Update user details as necessary, ensuring email uniqueness if attempting an update.
|
132
133
|
if (item.email &&
|
133
134
|
item.email !== userToReturn.email &&
|
134
|
-
!
|
135
|
-
!
|
135
|
+
!isEmpty(item.email) &&
|
136
|
+
!isUndefined(item.email)) {
|
136
137
|
const emailExists = await this.users.list([
|
137
138
|
Query.equal("email", item.email),
|
138
139
|
]);
|
@@ -153,7 +154,7 @@ export class UsersController {
|
|
153
154
|
item.phone !== userToReturn.phone &&
|
154
155
|
item.phone.length < 15 &&
|
155
156
|
item.phone.startsWith("+") &&
|
156
|
-
(
|
157
|
+
(isUndefined(userToReturn.phone) || isEmpty(userToReturn.phone))) {
|
157
158
|
const userFoundWithPhone = await this.users.list([
|
158
159
|
Query.equal("phone", item.phone),
|
159
160
|
]);
|
@@ -1,11 +1,11 @@
|
|
1
|
-
import
|
1
|
+
import { isNumber, isString, isBoolean, isArray, isPlainObject, isNull, isUndefined, isDate, isEmpty, isInteger, isArrayLike, isArrayLikeObject, isFunction, isLength, isMap, isSet, isRegExp, isSymbol, isObjectLike, isSafeInteger, isTypedArray, isEqual, isMatch, has, get, } from "es-toolkit/compat";
|
2
2
|
export const validationRules = {
|
3
|
-
isNumber: (value) =>
|
4
|
-
isString: (value) =>
|
5
|
-
isBoolean: (value) =>
|
6
|
-
isArray: (value) =>
|
7
|
-
isObject: (value) =>
|
8
|
-
isNull: (value) =>
|
3
|
+
isNumber: (value) => isNumber(value),
|
4
|
+
isString: (value) => isString(value),
|
5
|
+
isBoolean: (value) => isBoolean(value),
|
6
|
+
isArray: (value) => isArray(value),
|
7
|
+
isObject: (value) => isPlainObject(value) && !isArray(value) && !isFunction(value),
|
8
|
+
isNull: (value) => isNull(value),
|
9
9
|
isValidEmail: (value) => value.match(/^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$/) !== null,
|
10
10
|
isValidPhone: (value) => value.match(/^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/im) !==
|
11
11
|
null,
|
@@ -16,27 +16,27 @@ export const validationRules = {
|
|
16
16
|
isValidHexAlpha: (value) => value.match(/^#([a-f0-9]{8}|[a-f0-9]{4})$/i) !== null,
|
17
17
|
isValidDate: (value) => value.match(/^\d{4}-\d{2}-\d{2}$/) !== null,
|
18
18
|
isValidTime: (value) => value.match(/^\d{2}:\d{2}(:\d{2})?$/) !== null,
|
19
|
-
isNullish: (value) =>
|
20
|
-
isUndefined: (value) =>
|
21
|
-
isDefined: (value) => !
|
22
|
-
isDate: (value) =>
|
23
|
-
isEmpty: (value) =>
|
24
|
-
isInteger: (value) =>
|
25
|
-
isFloat: (value) =>
|
26
|
-
isArrayLike: (value) =>
|
27
|
-
isArrayLikeObject: (value) =>
|
28
|
-
isFunction: (value) =>
|
29
|
-
isLength: (value) =>
|
30
|
-
isMap: (value) =>
|
31
|
-
isSet: (value) =>
|
32
|
-
isRegExp: (value) =>
|
33
|
-
isSymbol: (value) =>
|
34
|
-
isObjectLike: (value) =>
|
35
|
-
isPlainObject: (value) =>
|
36
|
-
isSafeInteger: (value) =>
|
37
|
-
isTypedArray: (value) =>
|
38
|
-
isEqual: (value, other) =>
|
39
|
-
isMatch: (object, source) =>
|
40
|
-
has: (object, path) =>
|
41
|
-
get: (object, path, defaultValue) =>
|
19
|
+
isNullish: (value) => isNull(value) || isUndefined(value),
|
20
|
+
isUndefined: (value) => isUndefined(value),
|
21
|
+
isDefined: (value) => !isUndefined(value) && !isNull(value) && !isEmpty(value),
|
22
|
+
isDate: (value) => isDate(value),
|
23
|
+
isEmpty: (value) => isEmpty(value),
|
24
|
+
isInteger: (value) => isInteger(value),
|
25
|
+
isFloat: (value) => isNumber(value) && !isInteger(value),
|
26
|
+
isArrayLike: (value) => isArrayLike(value),
|
27
|
+
isArrayLikeObject: (value) => isArrayLikeObject(value),
|
28
|
+
isFunction: (value) => isFunction(value),
|
29
|
+
isLength: (value) => isLength(value),
|
30
|
+
isMap: (value) => isMap(value),
|
31
|
+
isSet: (value) => isSet(value),
|
32
|
+
isRegExp: (value) => isRegExp(value),
|
33
|
+
isSymbol: (value) => isSymbol(value),
|
34
|
+
isObjectLike: (value) => isObjectLike(value),
|
35
|
+
isPlainObject: (value) => isPlainObject(value),
|
36
|
+
isSafeInteger: (value) => isSafeInteger(value),
|
37
|
+
isTypedArray: (value) => isTypedArray(value),
|
38
|
+
isEqual: (value, other) => isEqual(value, other),
|
39
|
+
isMatch: (object, source) => isMatch(object, source),
|
40
|
+
has: (object, path) => has(object, path),
|
41
|
+
get: (object, path, defaultValue) => get(object, path, defaultValue),
|
42
42
|
};
|
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.10.
|
4
|
+
"version": "0.10.84",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|