appwrite-utils-cli 0.0.263 → 0.0.265

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 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.parse(collection.attributes);
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): Promise<any[]>;
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): Promise<any>;
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): string | undefined;
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
- async loadData(importDef) {
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
- async transformData(item, attributeMappings) {
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
- // Check if a new ID was found and it's not already included
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((attribute) => attribute.key === targetField && attribute.array);
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
- if (isArray) {
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 the old ID has been merged into a new one
325
- for (const [newUserId, oldIds] of this.mergedUserMap.entries()) {
326
- if (oldIds.includes(oldId)) {
327
- return newUserId;
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, look for a direct mapping from old to new ID
331
- const targetCollectionKey = this.getCollectionKey(idMapping.targetCollection);
332
- const targetOldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(targetCollectionKey);
333
- return targetOldIdToNewIdMap?.get(oldId);
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 = await this.transformData(item, attributeMappings);
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 = await this.loadData(importDef);
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 = await this.loadData(importDef);
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 = await this.transformData(item, importDef.attributeMappings);
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 = await this.loadData(importDef);
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 = await this.transformData(item, importDef.attributeMappings);
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,12 @@ 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
+ index.attributes = [index.attributes[0]];
16
+ }
11
17
  const newIndex = await db.createIndex(dbId, collectionId, index.key, index.type, index.attributes, index.orders);
12
18
  return newIndex;
13
19
  };
@@ -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.map((detail) => {
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
- const relatedPascalName = toPascalCase(relatedCollection[0]);
106
- const relatedCamelName = toCamelCase(relatedCollection[0]);
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
- baseSchemaCode += `.min(${attribute.min}, "Minimum value of ${attribute.min} not met")`;
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
- baseSchemaCode += `.max(${attribute.max}, "Maximum value of ${attribute.max} exceeded")`;
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.263",
4
+ "version": "0.0.265",
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.parse(collection.attributes);
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
- async loadData(importDef: ImportDef): Promise<any[]> {
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
- async transformData(
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
- const collectionKey = this.getCollectionKey(collectionConfig.name);
349
- const collectionData = this.importMap.get(collectionKey);
345
+ const collectionKey = this.getCollectionKey(collectionConfig.name);
346
+ const collectionData = this.importMap.get(collectionKey);
350
347
 
351
- if (!collectionData || !collectionData.data) continue;
348
+ if (!collectionData || !collectionData.data) continue;
352
349
 
353
- console.log(
354
- `Updating references for collection: ${collectionConfig.name}`
355
- );
350
+ console.log(`Updating references for collection: ${collectionConfig.name}`);
356
351
 
357
- // Iterate over each data item in the current collection
358
- for (const item of collectionData.data) {
359
- let needsUpdate = false;
352
+ // Iterate over each data item in the current collection
353
+ for (const item of collectionData.data) {
354
+ let needsUpdate = false;
360
355
 
361
- // Check if the current collection has import definitions with idMappings
362
- if (collectionConfig.importDefs) {
363
- for (const importDef of collectionConfig.importDefs) {
364
- if (importDef.idMappings) {
365
- // Iterate over each idMapping defined for the current import definition
366
- for (const idMapping of importDef.idMappings) {
367
- const oldIds = Array.isArray(
368
- item.context[idMapping.sourceField]
369
- )
370
- ? item.context[idMapping.sourceField]
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
- oldIds.forEach((oldId: any) => {
375
- // Attempt to find a new ID for the old ID
376
- let newIdForOldId = this.findNewIdForOldId(oldId, idMapping);
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
- // Check if a new ID was found and it's not already included
379
- if (
380
- newIdForOldId &&
381
- !resolvedNewIds.includes(newIdForOldId)
382
- ) {
383
- resolvedNewIds.push(newIdForOldId);
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
- // Set the target field based on whether it's an array or single value
395
- if (isArray) {
396
- item.finalData[targetField] = resolvedNewIds;
397
- } else {
398
- // In case of a single value, use the first resolved ID
399
- item.finalData[targetField] = resolvedNewIds[0];
400
- }
401
- needsUpdate = true;
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
- // Update the importMap if changes were made to the item
409
- if (needsUpdate) {
410
- this.importMap.set(collectionKey, collectionData);
411
- logger.info(
412
- `Updated item: ${JSON.stringify(item.finalData, undefined, 2)}`
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
- findNewIdForOldId(oldId: string, idMapping: IdMapping) {
420
- // First, check if the old ID has been merged into a new one
421
- for (const [newUserId, oldIds] of this.mergedUserMap.entries()) {
422
- if (oldIds.includes(oldId)) {
423
- return newUserId;
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
- // If not merged, look for a direct mapping from old to new ID
428
- const targetCollectionKey = this.getCollectionKey(
429
- idMapping.targetCollection
430
- );
431
- const targetOldIdToNewIdMap =
432
- this.oldIdToNewIdPerCollectionMap.get(targetCollectionKey);
433
- return targetOldIdToNewIdMap?.get(oldId);
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 = await this.transformData(item, attributeMappings);
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 = await this.loadData(importDef);
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 = await this.loadData(importDef);
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 = await this.transformData(
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 = await this.loadData(importDef);
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 = await this.transformData(
905
+ let transformedData = this.transformData(
899
906
  item,
900
907
  importDef.attributeMappings
901
908
  );
@@ -14,6 +14,13 @@ 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
+ index.attributes = [index.attributes[0]];
23
+ }
17
24
  const newIndex = await db.createIndex(
18
25
  dbId,
19
26
  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.map((detail) => {
137
- const relatedCollectionName = detail.isChild
138
- ? detail.parentCollection
139
- : detail.childCollection;
140
- const key = detail.isChild ? detail.childKey : detail.parentKey;
141
- const isArray = detail.isArray ? "array" : "";
142
- return [relatedCollectionName, key, isArray];
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
- const relatedPascalName = toPascalCase(relatedCollection[0]);
151
- const relatedCamelName = toCamelCase(relatedCollection[0]);
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
- baseSchemaCode += `.min(${attribute.min}, "Minimum value of ${attribute.min} not met")`;
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
- baseSchemaCode += `.max(${attribute.max}, "Maximum value of ${attribute.max} exceeded")`;
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})`;