appwrite-utils-cli 0.0.263 → 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/dist/main.js +0 -0
- package/dist/migrations/appwriteToX.js +23 -1
- package/dist/migrations/dataLoader.d.ts +3 -3
- package/dist/migrations/dataLoader.js +43 -31
- package/dist/migrations/indexes.js +7 -0
- package/dist/migrations/schemaStrings.js +25 -5
- package/dist/setup.js +0 -0
- package/package.json +1 -1
- package/src/migrations/appwriteToX.ts +35 -1
- package/src/migrations/dataLoader.ts +91 -84
- package/src/migrations/indexes.ts +8 -0
- package/src/migrations/schemaStrings.ts +37 -12
package/dist/main.js
CHANGED
File without changes
|
@@ -46,13 +46,35 @@ export class AppwriteToX {
|
|
46
46
|
let updatedConfig = { ...config };
|
47
47
|
// Loop through each database
|
48
48
|
for (const database of databases) {
|
49
|
+
if (database.name.toLowerCase() === "migrations") {
|
50
|
+
continue;
|
51
|
+
}
|
49
52
|
const collections = await fetchAllCollections(database.$id, db);
|
50
53
|
// Loop through each collection in the current database
|
51
54
|
for (const collection of collections) {
|
52
55
|
const existingCollectionIndex = updatedConfig.collections.findIndex((c) => c.name === collection.name);
|
53
56
|
// Parse the collection permissions and attributes
|
54
57
|
const collPermissions = this.parsePermissionsArray(collection.$permissions);
|
55
|
-
const collAttributes = attributesSchema
|
58
|
+
const collAttributes = attributesSchema
|
59
|
+
.parse(collection.attributes)
|
60
|
+
.filter((attribute) => attribute.type === "relationship"
|
61
|
+
? attribute.side !== "child"
|
62
|
+
: true);
|
63
|
+
for (const attribute of collAttributes) {
|
64
|
+
if (attribute.type === "relationship" &&
|
65
|
+
attribute.relatedCollection) {
|
66
|
+
console.log(`Fetching related collection for ID: ${attribute.relatedCollection}`);
|
67
|
+
try {
|
68
|
+
const relatedCollectionPulled = await db.getCollection(database.$id, attribute.relatedCollection);
|
69
|
+
console.log(`Fetched Collection Name: ${relatedCollectionPulled.name}`);
|
70
|
+
attribute.relatedCollection = relatedCollectionPulled.name;
|
71
|
+
console.log(`Updated attribute.relatedCollection to: ${attribute.relatedCollection}`);
|
72
|
+
}
|
73
|
+
catch (error) {
|
74
|
+
console.log("Error fetching related collection:", error);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
56
78
|
this.collToAttributeMap.set(collection.name, collAttributes);
|
57
79
|
const collIndexes = indexesSchema.parse(collection.indexes);
|
58
80
|
// Prepare the collection object to be added or updated
|
@@ -1573,7 +1573,7 @@ export declare class DataLoader {
|
|
1573
1573
|
* @returns The updated source object.
|
1574
1574
|
*/
|
1575
1575
|
mergeObjects(source: any, update: any): any;
|
1576
|
-
loadData(importDef: ImportDef):
|
1576
|
+
loadData(importDef: ImportDef): any[];
|
1577
1577
|
checkMapValuesForId(newId: string, collectionName: string): string | false;
|
1578
1578
|
getTrueUniqueId(collectionName: string): string;
|
1579
1579
|
createContext(db: ConfigDatabase, collection: ConfigCollection, item: any, docId: string): any;
|
@@ -1585,12 +1585,12 @@ export declare class DataLoader {
|
|
1585
1585
|
* @param attributeMappings - The mappings that define how each attribute should be transformed.
|
1586
1586
|
* @returns The transformed item.
|
1587
1587
|
*/
|
1588
|
-
transformData(item: any, attributeMappings: AttributeMappings):
|
1588
|
+
transformData(item: any, attributeMappings: AttributeMappings): any;
|
1589
1589
|
setupMaps(dbId: string): Promise<void>;
|
1590
1590
|
getAllUsers(): Promise<import("node-appwrite").Models.User<import("node-appwrite").Models.Preferences>[]>;
|
1591
1591
|
start(dbId: string): Promise<void>;
|
1592
1592
|
updateReferencesInRelatedCollections(): Promise<void>;
|
1593
|
-
findNewIdForOldId(oldId: string, idMapping: IdMapping):
|
1593
|
+
findNewIdForOldId(oldId: string, idMapping: IdMapping, importDef: ImportDef): any;
|
1594
1594
|
private writeMapsToJsonFile;
|
1595
1595
|
/**
|
1596
1596
|
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
@@ -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
|
@@ -288,24 +288,19 @@ export class DataLoader {
|
|
288
288
|
const resolvedNewIds = [];
|
289
289
|
oldIds.forEach((oldId) => {
|
290
290
|
// Attempt to find a new ID for the old ID
|
291
|
-
let newIdForOldId = this.findNewIdForOldId(oldId, idMapping);
|
292
|
-
|
293
|
-
if (newIdForOldId &&
|
294
|
-
!resolvedNewIds.includes(newIdForOldId)) {
|
291
|
+
let newIdForOldId = this.findNewIdForOldId(oldId, idMapping, importDef);
|
292
|
+
if (newIdForOldId && !resolvedNewIds.includes(newIdForOldId)) {
|
295
293
|
resolvedNewIds.push(newIdForOldId);
|
296
294
|
}
|
295
|
+
else {
|
296
|
+
logger.error(`No new ID found for old ID ${oldId} in collection ${collectionConfig.name}`);
|
297
|
+
}
|
297
298
|
});
|
298
299
|
if (resolvedNewIds.length) {
|
299
300
|
const targetField = idMapping.fieldToSet || idMapping.targetField;
|
300
|
-
const isArray = collectionConfig.attributes.some(
|
301
|
+
const isArray = collectionConfig.attributes.some(attribute => attribute.key === targetField && attribute.array);
|
301
302
|
// Set the target field based on whether it's an array or single value
|
302
|
-
|
303
|
-
item.finalData[targetField] = resolvedNewIds;
|
304
|
-
}
|
305
|
-
else {
|
306
|
-
// In case of a single value, use the first resolved ID
|
307
|
-
item.finalData[targetField] = resolvedNewIds[0];
|
308
|
-
}
|
303
|
+
item.finalData[targetField] = isArray ? resolvedNewIds : resolvedNewIds[0];
|
309
304
|
needsUpdate = true;
|
310
305
|
}
|
311
306
|
}
|
@@ -320,30 +315,47 @@ export class DataLoader {
|
|
320
315
|
}
|
321
316
|
}
|
322
317
|
}
|
323
|
-
findNewIdForOldId(oldId, idMapping) {
|
324
|
-
// First, check if
|
325
|
-
|
326
|
-
|
327
|
-
|
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
|
+
}
|
328
328
|
}
|
329
329
|
}
|
330
|
-
// If not merged,
|
331
|
-
const
|
332
|
-
|
333
|
-
|
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
|
336
|
+
}
|
337
|
+
}
|
338
|
+
logger.error(`No corresponding new ID found for ${oldId} in ${targetCollectionKey}`);
|
339
|
+
return null; // Return null if no new ID is found
|
334
340
|
}
|
335
341
|
writeMapsToJsonFile() {
|
336
342
|
const outputDir = path.resolve(process.cwd());
|
337
343
|
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
338
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()),
|
339
353
|
dataFromCollections: Array.from(this.importMap.entries()).map(([key, value]) => {
|
340
354
|
return {
|
341
355
|
collection: key,
|
342
356
|
data: value.data.map((item) => item.finalData),
|
343
357
|
};
|
344
358
|
}),
|
345
|
-
// Convert Maps to arrays of entries for serialization
|
346
|
-
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
347
359
|
// emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
|
348
360
|
// phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
|
349
361
|
};
|
@@ -372,7 +384,7 @@ export class DataLoader {
|
|
372
384
|
*/
|
373
385
|
async prepareUserData(item, attributeMappings, primaryKeyField, newId) {
|
374
386
|
// Transform the item data based on the attribute mappings
|
375
|
-
let transformedItem =
|
387
|
+
let transformedItem = this.transformData(item, attributeMappings);
|
376
388
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
377
389
|
if (!userData.success) {
|
378
390
|
logger.error(`Invalid user data: ${JSON.stringify(userData.error.errors, undefined, 2)}`);
|
@@ -438,7 +450,7 @@ export class DataLoader {
|
|
438
450
|
*/
|
439
451
|
async prepareUserCollectionCreateData(db, collection, importDef) {
|
440
452
|
// Load the raw data based on the import definition
|
441
|
-
const rawData =
|
453
|
+
const rawData = this.loadData(importDef);
|
442
454
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
443
455
|
// Initialize a new map for old ID to new ID mappings
|
444
456
|
const oldIdToNewIdMap = new Map();
|
@@ -556,7 +568,7 @@ export class DataLoader {
|
|
556
568
|
*/
|
557
569
|
async prepareCreateData(db, collection, importDef) {
|
558
570
|
// Load the raw data based on the import definition
|
559
|
-
const rawData =
|
571
|
+
const rawData = this.loadData(importDef);
|
560
572
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
561
573
|
if (!operationId) {
|
562
574
|
throw new Error(`No import operation found for collection ${collection.name}`);
|
@@ -582,7 +594,7 @@ export class DataLoader {
|
|
582
594
|
// Create a context object for the item, including the new ID
|
583
595
|
let context = this.createContext(db, collection, item, itemIdNew);
|
584
596
|
// Transform the item data based on the attribute mappings
|
585
|
-
const transformedData =
|
597
|
+
const transformedData = this.transformData(item, importDef.attributeMappings);
|
586
598
|
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
587
599
|
if (importDef.primaryKeyField) {
|
588
600
|
const oldId = item[importDef.primaryKeyField];
|
@@ -644,14 +656,14 @@ export class DataLoader {
|
|
644
656
|
return;
|
645
657
|
}
|
646
658
|
// Load the raw data based on the import definition
|
647
|
-
const rawData =
|
659
|
+
const rawData = this.loadData(importDef);
|
648
660
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
649
661
|
if (!operationId) {
|
650
662
|
throw new Error(`No import operation found for collection ${collection.name}`);
|
651
663
|
}
|
652
664
|
for (const item of rawData) {
|
653
665
|
// Transform the item data based on the attribute mappings
|
654
|
-
let transformedData =
|
666
|
+
let transformedData = this.transformData(item, importDef.attributeMappings);
|
655
667
|
let newId;
|
656
668
|
let oldId;
|
657
669
|
// Determine the new ID for the item based on the primary key field or update mapping
|
@@ -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
|
};
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
3
3
|
import fs from "fs";
|
4
4
|
import path from "path";
|
5
5
|
import { dump } from "js-yaml";
|
6
|
+
import { getDatabaseFromConfig } from "./afterImportActions.js";
|
6
7
|
export class SchemaGenerator {
|
7
8
|
relationshipMap = new Map();
|
8
9
|
config;
|
@@ -49,6 +50,7 @@ export class SchemaGenerator {
|
|
49
50
|
break;
|
50
51
|
}
|
51
52
|
this.addRelationship(collection.name, relationshipAttr.relatedCollection, attr.key, relationshipAttr.twoWayKey, isArrayParent, isArrayChild);
|
53
|
+
console.log(`Extracted relationship: ${attr.key}\n\t${collection.name} -> ${relationshipAttr.relatedCollection}, databaseId: ${collection.databaseId}`);
|
52
54
|
}
|
53
55
|
});
|
54
56
|
});
|
@@ -89,7 +91,14 @@ export class SchemaGenerator {
|
|
89
91
|
let imports = `import { z } from "zod";\n`;
|
90
92
|
// Use the relationshipMap to find related collections
|
91
93
|
const relationshipDetails = this.relationshipMap.get(name) || [];
|
92
|
-
const relatedCollections = relationshipDetails
|
94
|
+
const relatedCollections = relationshipDetails
|
95
|
+
.filter((detail, index, self) => {
|
96
|
+
const uniqueKey = `${detail.parentCollection}-${detail.childCollection}-${detail.parentKey}-${detail.childKey}`;
|
97
|
+
return (index ===
|
98
|
+
self.findIndex((obj) => `${obj.parentCollection}-${obj.childCollection}-${obj.parentKey}-${obj.childKey}` ===
|
99
|
+
uniqueKey));
|
100
|
+
})
|
101
|
+
.map((detail) => {
|
93
102
|
const relatedCollectionName = detail.isChild
|
94
103
|
? detail.parentCollection
|
95
104
|
: detail.childCollection;
|
@@ -102,8 +111,9 @@ export class SchemaGenerator {
|
|
102
111
|
let curNum = 0;
|
103
112
|
let maxNum = relatedCollections.length;
|
104
113
|
relatedCollections.forEach((relatedCollection) => {
|
105
|
-
|
106
|
-
|
114
|
+
console.log(relatedCollection);
|
115
|
+
let relatedPascalName = toPascalCase(relatedCollection[0]);
|
116
|
+
let relatedCamelName = toCamelCase(relatedCollection[0]);
|
107
117
|
curNum++;
|
108
118
|
let endNameTypes = relatedPascalName;
|
109
119
|
let endNameLazy = `${relatedPascalName}Schema`;
|
@@ -172,10 +182,20 @@ export class SchemaGenerator {
|
|
172
182
|
case "integer":
|
173
183
|
baseSchemaCode = "z.number().int()";
|
174
184
|
if (attribute.min !== undefined) {
|
175
|
-
|
185
|
+
if (BigInt(attribute.min) === BigInt(-9223372036854776000)) {
|
186
|
+
delete attribute.min;
|
187
|
+
}
|
188
|
+
else {
|
189
|
+
baseSchemaCode += `.min(${attribute.min}, "Minimum value of ${attribute.min} not met")`;
|
190
|
+
}
|
176
191
|
}
|
177
192
|
if (attribute.max !== undefined) {
|
178
|
-
|
193
|
+
if (BigInt(attribute.max) === BigInt(9223372036854776000)) {
|
194
|
+
delete attribute.max;
|
195
|
+
}
|
196
|
+
else {
|
197
|
+
baseSchemaCode += `.max(${attribute.max}, "Maximum value of ${attribute.max} exceeded")`;
|
198
|
+
}
|
179
199
|
}
|
180
200
|
if (attribute.xdefault !== undefined) {
|
181
201
|
baseSchemaCode += `.default(${attribute.xdefault})`;
|
package/dist/setup.js
CHANGED
File without changes
|
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.0.
|
4
|
+
"version": "0.0.264",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
@@ -65,6 +65,9 @@ export class AppwriteToX {
|
|
65
65
|
|
66
66
|
// Loop through each database
|
67
67
|
for (const database of databases) {
|
68
|
+
if (database.name.toLowerCase() === "migrations") {
|
69
|
+
continue;
|
70
|
+
}
|
68
71
|
const collections = await fetchAllCollections(database.$id, db);
|
69
72
|
|
70
73
|
// Loop through each collection in the current database
|
@@ -77,7 +80,38 @@ export class AppwriteToX {
|
|
77
80
|
const collPermissions = this.parsePermissionsArray(
|
78
81
|
collection.$permissions
|
79
82
|
);
|
80
|
-
const collAttributes = attributesSchema
|
83
|
+
const collAttributes = attributesSchema
|
84
|
+
.parse(collection.attributes)
|
85
|
+
.filter((attribute) =>
|
86
|
+
attribute.type === "relationship"
|
87
|
+
? attribute.side !== "child"
|
88
|
+
: true
|
89
|
+
);
|
90
|
+
for (const attribute of collAttributes) {
|
91
|
+
if (
|
92
|
+
attribute.type === "relationship" &&
|
93
|
+
attribute.relatedCollection
|
94
|
+
) {
|
95
|
+
console.log(
|
96
|
+
`Fetching related collection for ID: ${attribute.relatedCollection}`
|
97
|
+
);
|
98
|
+
try {
|
99
|
+
const relatedCollectionPulled = await db.getCollection(
|
100
|
+
database.$id,
|
101
|
+
attribute.relatedCollection
|
102
|
+
);
|
103
|
+
console.log(
|
104
|
+
`Fetched Collection Name: ${relatedCollectionPulled.name}`
|
105
|
+
);
|
106
|
+
attribute.relatedCollection = relatedCollectionPulled.name;
|
107
|
+
console.log(
|
108
|
+
`Updated attribute.relatedCollection to: ${attribute.relatedCollection}`
|
109
|
+
);
|
110
|
+
} catch (error) {
|
111
|
+
console.log("Error fetching related collection:", error);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
81
115
|
this.collToAttributeMap.set(collection.name, collAttributes);
|
82
116
|
const collIndexes = indexesSchema.parse(collection.indexes);
|
83
117
|
|
@@ -133,7 +133,7 @@ export class DataLoader {
|
|
133
133
|
}
|
134
134
|
|
135
135
|
// Method to load data from a file specified in the import definition
|
136
|
-
|
136
|
+
loadData(importDef: ImportDef): any[] {
|
137
137
|
// Resolve the file path and check if the file exists
|
138
138
|
const filePath = path.resolve(this.appwriteFolderPath, importDef.filePath);
|
139
139
|
if (!fs.existsSync(filePath)) {
|
@@ -194,10 +194,7 @@ export class DataLoader {
|
|
194
194
|
* @param attributeMappings - The mappings that define how each attribute should be transformed.
|
195
195
|
* @returns The transformed item.
|
196
196
|
*/
|
197
|
-
|
198
|
-
item: any,
|
199
|
-
attributeMappings: AttributeMappings
|
200
|
-
): Promise<any> {
|
197
|
+
transformData(item: any, attributeMappings: AttributeMappings): any {
|
201
198
|
// Convert the item using the attribute mappings provided
|
202
199
|
const convertedItem = convertObjectByAttributeMappings(
|
203
200
|
item,
|
@@ -345,99 +342,111 @@ export class DataLoader {
|
|
345
342
|
async updateReferencesInRelatedCollections() {
|
346
343
|
// Iterate over each collection configuration
|
347
344
|
for (const collectionConfig of this.config.collections) {
|
348
|
-
|
349
|
-
|
345
|
+
const collectionKey = this.getCollectionKey(collectionConfig.name);
|
346
|
+
const collectionData = this.importMap.get(collectionKey);
|
350
347
|
|
351
|
-
|
348
|
+
if (!collectionData || !collectionData.data) continue;
|
352
349
|
|
353
|
-
|
354
|
-
`Updating references for collection: ${collectionConfig.name}`
|
355
|
-
);
|
350
|
+
console.log(`Updating references for collection: ${collectionConfig.name}`);
|
356
351
|
|
357
|
-
|
358
|
-
|
359
|
-
|
352
|
+
// Iterate over each data item in the current collection
|
353
|
+
for (const item of collectionData.data) {
|
354
|
+
let needsUpdate = false;
|
360
355
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
: [item.context[idMapping.sourceField]];
|
372
|
-
const resolvedNewIds: string[] = [];
|
356
|
+
// Check if the current collection has import definitions with idMappings
|
357
|
+
if (collectionConfig.importDefs) {
|
358
|
+
for (const importDef of collectionConfig.importDefs) {
|
359
|
+
if (importDef.idMappings) {
|
360
|
+
// Iterate over each idMapping defined for the current import definition
|
361
|
+
for (const idMapping of importDef.idMappings) {
|
362
|
+
const oldIds = Array.isArray(item.context[idMapping.sourceField])
|
363
|
+
? item.context[idMapping.sourceField]
|
364
|
+
: [item.context[idMapping.sourceField]];
|
365
|
+
const resolvedNewIds: string[] = [];
|
373
366
|
|
374
|
-
|
375
|
-
|
376
|
-
|
367
|
+
oldIds.forEach((oldId: any) => {
|
368
|
+
// Attempt to find a new ID for the old ID
|
369
|
+
let newIdForOldId = this.findNewIdForOldId(
|
370
|
+
oldId,
|
371
|
+
idMapping,
|
372
|
+
importDef
|
373
|
+
);
|
377
374
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
}
|
385
|
-
});
|
386
|
-
if (resolvedNewIds.length) {
|
387
|
-
const targetField =
|
388
|
-
idMapping.fieldToSet || idMapping.targetField;
|
389
|
-
const isArray = collectionConfig.attributes.some(
|
390
|
-
(attribute) =>
|
391
|
-
attribute.key === targetField && attribute.array
|
392
|
-
);
|
375
|
+
if (newIdForOldId && !resolvedNewIds.includes(newIdForOldId)) {
|
376
|
+
resolvedNewIds.push(newIdForOldId);
|
377
|
+
} else {
|
378
|
+
logger.error(`No new ID found for old ID ${oldId} in collection ${collectionConfig.name}`);
|
379
|
+
}
|
380
|
+
});
|
393
381
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
382
|
+
if (resolvedNewIds.length) {
|
383
|
+
const targetField = idMapping.fieldToSet || idMapping.targetField;
|
384
|
+
const isArray = collectionConfig.attributes.some(
|
385
|
+
attribute => attribute.key === targetField && attribute.array
|
386
|
+
);
|
387
|
+
|
388
|
+
// Set the target field based on whether it's an array or single value
|
389
|
+
item.finalData[targetField] = isArray ? resolvedNewIds : resolvedNewIds[0];
|
390
|
+
needsUpdate = true;
|
391
|
+
}
|
392
|
+
}
|
393
|
+
}
|
402
394
|
}
|
403
|
-
}
|
404
395
|
}
|
405
|
-
}
|
406
|
-
}
|
407
396
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
);
|
397
|
+
// Update the importMap if changes were made to the item
|
398
|
+
if (needsUpdate) {
|
399
|
+
this.importMap.set(collectionKey, collectionData);
|
400
|
+
logger.info(`Updated item: ${JSON.stringify(item.finalData, undefined, 2)}`);
|
401
|
+
}
|
414
402
|
}
|
415
|
-
}
|
416
403
|
}
|
417
|
-
|
404
|
+
}
|
405
|
+
|
406
|
+
findNewIdForOldId(oldId: string, idMapping: IdMapping, importDef: ImportDef) {
|
407
|
+
// First, check if this ID mapping is related to the users collection.
|
408
|
+
const targetCollectionKey = this.getCollectionKey(idMapping.targetCollection);
|
409
|
+
const isUsersCollection = targetCollectionKey === this.getCollectionKey(this.config.usersCollectionName);
|
418
410
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
411
|
+
// If handling users, check the mergedUserMap for any existing new ID.
|
412
|
+
if (isUsersCollection) {
|
413
|
+
for (const [newUserId, oldIds] of this.mergedUserMap.entries()) {
|
414
|
+
if (oldIds.includes(oldId)) {
|
415
|
+
return newUserId;
|
416
|
+
}
|
424
417
|
}
|
425
|
-
|
418
|
+
}
|
426
419
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
420
|
+
// If not a user or no merged ID found, check the regular ID mapping from old to new.
|
421
|
+
const targetCollectionData = this.importMap.get(targetCollectionKey);
|
422
|
+
if (targetCollectionData) {
|
423
|
+
const foundEntry = targetCollectionData.data.find(entry => entry.context[importDef.primaryKeyField] === oldId);
|
424
|
+
if (foundEntry) {
|
425
|
+
return foundEntry.context.docId; // Assuming `docId` stores the new ID after import
|
426
|
+
}
|
434
427
|
}
|
435
428
|
|
429
|
+
logger.error(`No corresponding new ID found for ${oldId} in ${targetCollectionKey}`);
|
430
|
+
return null; // Return null if no new ID is found
|
431
|
+
}
|
432
|
+
|
433
|
+
|
434
|
+
|
436
435
|
private writeMapsToJsonFile() {
|
437
436
|
const outputDir = path.resolve(process.cwd());
|
438
437
|
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
439
438
|
|
440
439
|
const dataToWrite = {
|
440
|
+
// Convert Maps to arrays of entries for serialization
|
441
|
+
oldIdToNewIdPerCollectionMap: Array.from(
|
442
|
+
this.oldIdToNewIdPerCollectionMap.entries()
|
443
|
+
).map(([key, value]) => {
|
444
|
+
return {
|
445
|
+
collection: key,
|
446
|
+
data: Array.from(value.entries()),
|
447
|
+
};
|
448
|
+
}),
|
449
|
+
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
441
450
|
dataFromCollections: Array.from(this.importMap.entries()).map(
|
442
451
|
([key, value]) => {
|
443
452
|
return {
|
@@ -446,8 +455,6 @@ export class DataLoader {
|
|
446
455
|
};
|
447
456
|
}
|
448
457
|
),
|
449
|
-
// Convert Maps to arrays of entries for serialization
|
450
|
-
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
451
458
|
// emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
|
452
459
|
// phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
|
453
460
|
};
|
@@ -489,7 +496,7 @@ export class DataLoader {
|
|
489
496
|
newId: string
|
490
497
|
): Promise<any> {
|
491
498
|
// Transform the item data based on the attribute mappings
|
492
|
-
let transformedItem =
|
499
|
+
let transformedItem = this.transformData(item, attributeMappings);
|
493
500
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
494
501
|
if (!userData.success) {
|
495
502
|
logger.error(
|
@@ -569,7 +576,7 @@ export class DataLoader {
|
|
569
576
|
importDef: ImportDef
|
570
577
|
): Promise<void> {
|
571
578
|
// Load the raw data based on the import definition
|
572
|
-
const rawData =
|
579
|
+
const rawData = this.loadData(importDef);
|
573
580
|
const operationId = this.collectionImportOperations.get(
|
574
581
|
this.getCollectionKey(collection.name)
|
575
582
|
);
|
@@ -754,7 +761,7 @@ export class DataLoader {
|
|
754
761
|
importDef: ImportDef
|
755
762
|
): Promise<void> {
|
756
763
|
// Load the raw data based on the import definition
|
757
|
-
const rawData =
|
764
|
+
const rawData = this.loadData(importDef);
|
758
765
|
const operationId = this.collectionImportOperations.get(
|
759
766
|
this.getCollectionKey(collection.name)
|
760
767
|
);
|
@@ -793,7 +800,7 @@ export class DataLoader {
|
|
793
800
|
// Create a context object for the item, including the new ID
|
794
801
|
let context = this.createContext(db, collection, item, itemIdNew);
|
795
802
|
// Transform the item data based on the attribute mappings
|
796
|
-
const transformedData =
|
803
|
+
const transformedData = this.transformData(
|
797
804
|
item,
|
798
805
|
importDef.attributeMappings
|
799
806
|
);
|
@@ -884,7 +891,7 @@ export class DataLoader {
|
|
884
891
|
return;
|
885
892
|
}
|
886
893
|
// Load the raw data based on the import definition
|
887
|
-
const rawData =
|
894
|
+
const rawData = this.loadData(importDef);
|
888
895
|
const operationId = this.collectionImportOperations.get(
|
889
896
|
this.getCollectionKey(collection.name)
|
890
897
|
);
|
@@ -895,7 +902,7 @@ export class DataLoader {
|
|
895
902
|
}
|
896
903
|
for (const item of rawData) {
|
897
904
|
// Transform the item data based on the attribute mappings
|
898
|
-
let transformedData =
|
905
|
+
let transformedData = this.transformData(
|
899
906
|
item,
|
900
907
|
importDef.attributeMappings
|
901
908
|
);
|
@@ -14,6 +14,14 @@ export const createOrUpdateIndex = async (
|
|
14
14
|
if (existingIndex.total > 0) {
|
15
15
|
await db.deleteIndex(dbId, collectionId, existingIndex.indexes[0].key);
|
16
16
|
}
|
17
|
+
if (index.type === "fulltext" && index.attributes.length > 1) {
|
18
|
+
throw new Error(
|
19
|
+
`Fulltext index can only be created on a single attribute. Index: ${index.key}`
|
20
|
+
);
|
21
|
+
} else if (index.type === "fulltext") {
|
22
|
+
// @ts-ignore
|
23
|
+
index.attributes = index.attributes[0];
|
24
|
+
}
|
17
25
|
const newIndex = await db.createIndex(
|
18
26
|
dbId,
|
19
27
|
collectionId,
|
@@ -8,6 +8,7 @@ import { z } from "zod";
|
|
8
8
|
import fs from "fs";
|
9
9
|
import path from "path";
|
10
10
|
import { dump } from "js-yaml";
|
11
|
+
import { getDatabaseFromConfig } from "./afterImportActions.js";
|
11
12
|
|
12
13
|
interface RelationshipDetail {
|
13
14
|
parentCollection: string;
|
@@ -74,6 +75,9 @@ export class SchemaGenerator {
|
|
74
75
|
isArrayParent,
|
75
76
|
isArrayChild
|
76
77
|
);
|
78
|
+
console.log(
|
79
|
+
`Extracted relationship: ${attr.key}\n\t${collection.name} -> ${relationshipAttr.relatedCollection}, databaseId: ${collection.databaseId}`
|
80
|
+
);
|
77
81
|
}
|
78
82
|
});
|
79
83
|
});
|
@@ -133,22 +137,35 @@ export class SchemaGenerator {
|
|
133
137
|
|
134
138
|
// Use the relationshipMap to find related collections
|
135
139
|
const relationshipDetails = this.relationshipMap.get(name) || [];
|
136
|
-
const relatedCollections = relationshipDetails
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
140
|
+
const relatedCollections = relationshipDetails
|
141
|
+
.filter((detail, index, self) => {
|
142
|
+
const uniqueKey = `${detail.parentCollection}-${detail.childCollection}-${detail.parentKey}-${detail.childKey}`;
|
143
|
+
return (
|
144
|
+
index ===
|
145
|
+
self.findIndex(
|
146
|
+
(obj) =>
|
147
|
+
`${obj.parentCollection}-${obj.childCollection}-${obj.parentKey}-${obj.childKey}` ===
|
148
|
+
uniqueKey
|
149
|
+
)
|
150
|
+
);
|
151
|
+
})
|
152
|
+
.map((detail) => {
|
153
|
+
const relatedCollectionName = detail.isChild
|
154
|
+
? detail.parentCollection
|
155
|
+
: detail.childCollection;
|
156
|
+
const key = detail.isChild ? detail.childKey : detail.parentKey;
|
157
|
+
const isArray = detail.isArray ? "array" : "";
|
158
|
+
return [relatedCollectionName, key, isArray];
|
159
|
+
});
|
144
160
|
|
145
161
|
let relatedTypes = "";
|
146
162
|
let relatedTypesLazy = "";
|
147
163
|
let curNum = 0;
|
148
164
|
let maxNum = relatedCollections.length;
|
149
165
|
relatedCollections.forEach((relatedCollection) => {
|
150
|
-
|
151
|
-
|
166
|
+
console.log(relatedCollection);
|
167
|
+
let relatedPascalName = toPascalCase(relatedCollection[0]);
|
168
|
+
let relatedCamelName = toCamelCase(relatedCollection[0]);
|
152
169
|
curNum++;
|
153
170
|
let endNameTypes = relatedPascalName;
|
154
171
|
let endNameLazy = `${relatedPascalName}Schema`;
|
@@ -218,10 +235,18 @@ export class SchemaGenerator {
|
|
218
235
|
case "integer":
|
219
236
|
baseSchemaCode = "z.number().int()";
|
220
237
|
if (attribute.min !== undefined) {
|
221
|
-
|
238
|
+
if (BigInt(attribute.min) === BigInt(-9223372036854776000)) {
|
239
|
+
delete attribute.min;
|
240
|
+
} else {
|
241
|
+
baseSchemaCode += `.min(${attribute.min}, "Minimum value of ${attribute.min} not met")`;
|
242
|
+
}
|
222
243
|
}
|
223
244
|
if (attribute.max !== undefined) {
|
224
|
-
|
245
|
+
if (BigInt(attribute.max) === BigInt(9223372036854776000)) {
|
246
|
+
delete attribute.max;
|
247
|
+
} else {
|
248
|
+
baseSchemaCode += `.max(${attribute.max}, "Maximum value of ${attribute.max} exceeded")`;
|
249
|
+
}
|
225
250
|
}
|
226
251
|
if (attribute.xdefault !== undefined) {
|
227
252
|
baseSchemaCode += `.default(${attribute.xdefault})`;
|