appwrite-utils-cli 0.0.262 → 0.0.264
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 +24 -5
- package/dist/main.js +6 -0
- package/dist/migrations/afterImportActions.d.ts +3 -1
- package/dist/migrations/afterImportActions.js +2 -2
- package/dist/migrations/appwriteToX.d.ts +109 -0
- package/dist/migrations/appwriteToX.js +110 -0
- package/dist/migrations/backup.d.ts +4 -4
- package/dist/migrations/dataLoader.d.ts +20 -27
- package/dist/migrations/dataLoader.js +49 -43
- package/dist/migrations/databases.d.ts +2 -0
- package/dist/migrations/databases.js +23 -0
- package/dist/migrations/indexes.js +7 -0
- package/dist/migrations/schema.d.ts +345 -32
- package/dist/migrations/schema.js +34 -14
- package/dist/migrations/schemaStrings.d.ts +1 -0
- package/dist/migrations/schemaStrings.js +35 -5
- package/dist/setup.js +0 -0
- package/dist/utilsController.d.ts +1 -0
- package/dist/utilsController.js +7 -0
- package/package.json +1 -1
- package/src/main.ts +6 -0
- package/src/migrations/afterImportActions.ts +2 -2
- package/src/migrations/appwriteToX.ts +156 -0
- package/src/migrations/dataLoader.ts +88 -89
- package/src/migrations/databases.ts +25 -0
- package/src/migrations/indexes.ts +8 -0
- package/src/migrations/schema.ts +40 -14
- package/src/migrations/schemaStrings.ts +48 -12
- package/src/utilsController.ts +9 -0
@@ -99,7 +99,7 @@ export class DataLoader {
|
|
99
99
|
return result;
|
100
100
|
}
|
101
101
|
// Method to load data from a file specified in the import definition
|
102
|
-
|
102
|
+
loadData(importDef) {
|
103
103
|
// Resolve the file path and check if the file exists
|
104
104
|
const filePath = path.resolve(this.appwriteFolderPath, importDef.filePath);
|
105
105
|
if (!fs.existsSync(filePath)) {
|
@@ -150,7 +150,7 @@ export class DataLoader {
|
|
150
150
|
* @param attributeMappings - The mappings that define how each attribute should be transformed.
|
151
151
|
* @returns The transformed item.
|
152
152
|
*/
|
153
|
-
|
153
|
+
transformData(item, attributeMappings) {
|
154
154
|
// Convert the item using the attribute mappings provided
|
155
155
|
const convertedItem = convertObjectByAttributeMappings(item, attributeMappings);
|
156
156
|
// Run additional converter functions on the converted item, if any
|
@@ -285,28 +285,24 @@ export class DataLoader {
|
|
285
285
|
const oldIds = Array.isArray(item.context[idMapping.sourceField])
|
286
286
|
? item.context[idMapping.sourceField]
|
287
287
|
: [item.context[idMapping.sourceField]];
|
288
|
+
const resolvedNewIds = [];
|
288
289
|
oldIds.forEach((oldId) => {
|
289
|
-
|
290
|
-
|
291
|
-
newIdForOldId
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
if (isArray) {
|
297
|
-
if (!Array.isArray(item.finalData[targetField])) {
|
298
|
-
item.finalData[targetField] = [newIdForOldId];
|
299
|
-
}
|
300
|
-
else if (!item.finalData[targetField].includes(newIdForOldId)) {
|
301
|
-
item.finalData[targetField].push(newIdForOldId);
|
302
|
-
}
|
303
|
-
}
|
304
|
-
else {
|
305
|
-
item.finalData[targetField] = newIdForOldId;
|
306
|
-
}
|
307
|
-
needsUpdate = true;
|
290
|
+
// Attempt to find a new ID for the old ID
|
291
|
+
let newIdForOldId = this.findNewIdForOldId(oldId, idMapping, importDef);
|
292
|
+
if (newIdForOldId && !resolvedNewIds.includes(newIdForOldId)) {
|
293
|
+
resolvedNewIds.push(newIdForOldId);
|
294
|
+
}
|
295
|
+
else {
|
296
|
+
logger.error(`No new ID found for old ID ${oldId} in collection ${collectionConfig.name}`);
|
308
297
|
}
|
309
298
|
});
|
299
|
+
if (resolvedNewIds.length) {
|
300
|
+
const targetField = idMapping.fieldToSet || idMapping.targetField;
|
301
|
+
const isArray = collectionConfig.attributes.some(attribute => attribute.key === targetField && attribute.array);
|
302
|
+
// Set the target field based on whether it's an array or single value
|
303
|
+
item.finalData[targetField] = isArray ? resolvedNewIds : resolvedNewIds[0];
|
304
|
+
needsUpdate = true;
|
305
|
+
}
|
310
306
|
}
|
311
307
|
}
|
312
308
|
}
|
@@ -319,37 +315,47 @@ export class DataLoader {
|
|
319
315
|
}
|
320
316
|
}
|
321
317
|
}
|
322
|
-
findNewIdForOldId(oldId, idMapping) {
|
323
|
-
//
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
318
|
+
findNewIdForOldId(oldId, idMapping, importDef) {
|
319
|
+
// First, check if this ID mapping is related to the users collection.
|
320
|
+
const targetCollectionKey = this.getCollectionKey(idMapping.targetCollection);
|
321
|
+
const isUsersCollection = targetCollectionKey === this.getCollectionKey(this.config.usersCollectionName);
|
322
|
+
// If handling users, check the mergedUserMap for any existing new ID.
|
323
|
+
if (isUsersCollection) {
|
324
|
+
for (const [newUserId, oldIds] of this.mergedUserMap.entries()) {
|
325
|
+
if (oldIds.includes(oldId)) {
|
326
|
+
return newUserId;
|
327
|
+
}
|
329
328
|
}
|
330
329
|
}
|
331
|
-
// If
|
332
|
-
|
333
|
-
|
334
|
-
const
|
335
|
-
if (
|
336
|
-
|
330
|
+
// If not a user or no merged ID found, check the regular ID mapping from old to new.
|
331
|
+
const targetCollectionData = this.importMap.get(targetCollectionKey);
|
332
|
+
if (targetCollectionData) {
|
333
|
+
const foundEntry = targetCollectionData.data.find(entry => entry.context[importDef.primaryKeyField] === oldId);
|
334
|
+
if (foundEntry) {
|
335
|
+
return foundEntry.context.docId; // Assuming `docId` stores the new ID after import
|
337
336
|
}
|
338
337
|
}
|
339
|
-
|
338
|
+
logger.error(`No corresponding new ID found for ${oldId} in ${targetCollectionKey}`);
|
339
|
+
return null; // Return null if no new ID is found
|
340
340
|
}
|
341
341
|
writeMapsToJsonFile() {
|
342
342
|
const outputDir = path.resolve(process.cwd());
|
343
343
|
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
344
344
|
const dataToWrite = {
|
345
|
+
// Convert Maps to arrays of entries for serialization
|
346
|
+
oldIdToNewIdPerCollectionMap: Array.from(this.oldIdToNewIdPerCollectionMap.entries()).map(([key, value]) => {
|
347
|
+
return {
|
348
|
+
collection: key,
|
349
|
+
data: Array.from(value.entries()),
|
350
|
+
};
|
351
|
+
}),
|
352
|
+
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
345
353
|
dataFromCollections: Array.from(this.importMap.entries()).map(([key, value]) => {
|
346
354
|
return {
|
347
355
|
collection: key,
|
348
356
|
data: value.data.map((item) => item.finalData),
|
349
357
|
};
|
350
358
|
}),
|
351
|
-
// Convert Maps to arrays of entries for serialization
|
352
|
-
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
353
359
|
// emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
|
354
360
|
// phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
|
355
361
|
};
|
@@ -378,7 +384,7 @@ export class DataLoader {
|
|
378
384
|
*/
|
379
385
|
async prepareUserData(item, attributeMappings, primaryKeyField, newId) {
|
380
386
|
// Transform the item data based on the attribute mappings
|
381
|
-
let transformedItem =
|
387
|
+
let transformedItem = this.transformData(item, attributeMappings);
|
382
388
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
383
389
|
if (!userData.success) {
|
384
390
|
logger.error(`Invalid user data: ${JSON.stringify(userData.error.errors, undefined, 2)}`);
|
@@ -444,7 +450,7 @@ export class DataLoader {
|
|
444
450
|
*/
|
445
451
|
async prepareUserCollectionCreateData(db, collection, importDef) {
|
446
452
|
// Load the raw data based on the import definition
|
447
|
-
const rawData =
|
453
|
+
const rawData = this.loadData(importDef);
|
448
454
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
449
455
|
// Initialize a new map for old ID to new ID mappings
|
450
456
|
const oldIdToNewIdMap = new Map();
|
@@ -562,7 +568,7 @@ export class DataLoader {
|
|
562
568
|
*/
|
563
569
|
async prepareCreateData(db, collection, importDef) {
|
564
570
|
// Load the raw data based on the import definition
|
565
|
-
const rawData =
|
571
|
+
const rawData = this.loadData(importDef);
|
566
572
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
567
573
|
if (!operationId) {
|
568
574
|
throw new Error(`No import operation found for collection ${collection.name}`);
|
@@ -588,7 +594,7 @@ export class DataLoader {
|
|
588
594
|
// Create a context object for the item, including the new ID
|
589
595
|
let context = this.createContext(db, collection, item, itemIdNew);
|
590
596
|
// Transform the item data based on the attribute mappings
|
591
|
-
const transformedData =
|
597
|
+
const transformedData = this.transformData(item, importDef.attributeMappings);
|
592
598
|
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
593
599
|
if (importDef.primaryKeyField) {
|
594
600
|
const oldId = item[importDef.primaryKeyField];
|
@@ -650,14 +656,14 @@ export class DataLoader {
|
|
650
656
|
return;
|
651
657
|
}
|
652
658
|
// Load the raw data based on the import definition
|
653
|
-
const rawData =
|
659
|
+
const rawData = this.loadData(importDef);
|
654
660
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
655
661
|
if (!operationId) {
|
656
662
|
throw new Error(`No import operation found for collection ${collection.name}`);
|
657
663
|
}
|
658
664
|
for (const item of rawData) {
|
659
665
|
// Transform the item data based on the attribute mappings
|
660
|
-
let transformedData =
|
666
|
+
let transformedData = this.transformData(item, importDef.attributeMappings);
|
661
667
|
let newId;
|
662
668
|
let oldId;
|
663
669
|
// Determine the new ID for the item based on the primary key field or update mapping
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Databases, Query } from "node-appwrite";
|
2
|
+
export const fetchAllDatabases = async (database) => {
|
3
|
+
const databases = await database.list([Query.limit(25)]);
|
4
|
+
const allDatabases = databases.databases;
|
5
|
+
let lastDatabaseId = allDatabases[allDatabases.length - 1].$id;
|
6
|
+
if (databases.databases.length < 25) {
|
7
|
+
return allDatabases;
|
8
|
+
}
|
9
|
+
else {
|
10
|
+
while (lastDatabaseId) {
|
11
|
+
const databases = await database.list([
|
12
|
+
Query.limit(25),
|
13
|
+
Query.cursorAfter(lastDatabaseId),
|
14
|
+
]);
|
15
|
+
allDatabases.push(...databases.databases);
|
16
|
+
if (databases.databases.length < 25) {
|
17
|
+
break;
|
18
|
+
}
|
19
|
+
lastDatabaseId = databases.databases[databases.databases.length - 1].$id;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
return allDatabases;
|
23
|
+
};
|
@@ -8,6 +8,13 @@ export const createOrUpdateIndex = async (dbId, db, collectionId, index) => {
|
|
8
8
|
if (existingIndex.total > 0) {
|
9
9
|
await db.deleteIndex(dbId, collectionId, existingIndex.indexes[0].key);
|
10
10
|
}
|
11
|
+
if (index.type === "fulltext" && index.attributes.length > 1) {
|
12
|
+
throw new Error(`Fulltext index can only be created on a single attribute. Index: ${index.key}`);
|
13
|
+
}
|
14
|
+
else if (index.type === "fulltext") {
|
15
|
+
// @ts-ignore
|
16
|
+
index.attributes = index.attributes[0];
|
17
|
+
}
|
11
18
|
const newIndex = await db.createIndex(dbId, collectionId, index.key, index.type, index.attributes, index.orders);
|
12
19
|
return newIndex;
|
13
20
|
};
|