@memberjunction/metadata-sync 2.111.1 → 2.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +50 -50
  2. package/dist/lib/EntityPropertyExtractor.d.ts +1 -1
  3. package/dist/lib/EntityPropertyExtractor.js +18 -4
  4. package/dist/lib/EntityPropertyExtractor.js.map +1 -1
  5. package/dist/lib/FieldExternalizer.d.ts +1 -1
  6. package/dist/lib/FieldExternalizer.js +2 -2
  7. package/dist/lib/FieldExternalizer.js.map +1 -1
  8. package/dist/lib/RecordProcessor.d.ts +1 -1
  9. package/dist/lib/RecordProcessor.js +9 -10
  10. package/dist/lib/RecordProcessor.js.map +1 -1
  11. package/dist/lib/RelatedEntityHandler.d.ts +1 -1
  12. package/dist/lib/RelatedEntityHandler.js +5 -5
  13. package/dist/lib/RelatedEntityHandler.js.map +1 -1
  14. package/dist/lib/provider-utils.d.ts +1 -1
  15. package/dist/lib/provider-utils.js +16 -19
  16. package/dist/lib/provider-utils.js.map +1 -1
  17. package/dist/lib/record-dependency-analyzer.js +21 -27
  18. package/dist/lib/record-dependency-analyzer.js.map +1 -1
  19. package/dist/lib/singleton-manager.d.ts +1 -1
  20. package/dist/lib/singleton-manager.js.map +1 -1
  21. package/dist/lib/sync-engine.d.ts +1 -1
  22. package/dist/lib/sync-engine.js +22 -18
  23. package/dist/lib/sync-engine.js.map +1 -1
  24. package/dist/services/PullService.d.ts +1 -1
  25. package/dist/services/PullService.js +22 -18
  26. package/dist/services/PullService.js.map +1 -1
  27. package/dist/services/PushService.d.ts +1 -1
  28. package/dist/services/PushService.js +15 -21
  29. package/dist/services/PushService.js.map +1 -1
  30. package/dist/services/ValidationService.js +5 -5
  31. package/dist/services/ValidationService.js.map +1 -1
  32. package/dist/services/WatchService.d.ts +1 -1
  33. package/dist/services/WatchService.js +13 -14
  34. package/dist/services/WatchService.js.map +1 -1
  35. package/package.json +6 -7
package/README.md CHANGED
@@ -212,7 +212,7 @@ Entity classes are located in:
212
212
 
213
213
  3. **Runtime Metadata Discovery**:
214
214
  ```typescript
215
- import { Metadata } from '@memberjunction/core';
215
+ import { Metadata } from '@memberjunction/global';
216
216
 
217
217
  const md = new Metadata();
218
218
  const entityInfo = md.EntityByName('Templates');
@@ -474,13 +474,13 @@ With `"filePattern": ".*.json"`:
474
474
 
475
475
  ### Troubleshooting Quick Reference
476
476
 
477
- | Error Message | Cause | Solution |
478
- |---------------|-------|----------|
479
- | `No entity directories found` | Missing .mj-sync.json or wrong filePattern | Check .mj-sync.json exists and uses `"*.json"` |
480
- | `Field 'X' does not exist on entity 'Y'` | Using non-existent field | Check BaseEntity class in entity_subclasses.ts |
481
- | `User ID cannot be null` | Missing required UserID | Add `"UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"` |
482
- | `Processing 0 records` | Files don't match filePattern | Check files match pattern in .mj-sync.json |
483
- | Failed validation | Wrong data type or format | Check BaseEntity class for field types |
477
+ | Error Message | Cause | Solution |
478
+ | ---------------------------------------- | ------------------------------------------ | ------------------------------------------------------ |
479
+ | `No entity directories found` | Missing .mj-sync.json or wrong filePattern | Check .mj-sync.json exists and uses `"*.json"` |
480
+ | `Field 'X' does not exist on entity 'Y'` | Using non-existent field | Check BaseEntity class in entity_subclasses.ts |
481
+ | `User ID cannot be null` | Missing required UserID | Add `"UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"` |
482
+ | `Processing 0 records` | Files don't match filePattern | Check files match pattern in .mj-sync.json |
483
+ | Failed validation | Wrong data type or format | Check BaseEntity class for field types |
484
484
 
485
485
  ### System User ID Reference
486
486
 
@@ -1510,13 +1510,13 @@ SQL logging is configured in the root-level `.mj-sync.json` file only (not inher
1510
1510
 
1511
1511
  #### SQL Logging Options
1512
1512
 
1513
- | Option | Type | Default | Description |
1514
- |--------|------|---------|-------------|
1515
- | `enabled` | boolean | false | Whether to enable SQL logging during push operations |
1516
- | `outputDirectory` | string | "./sql_logging" | Directory to output SQL log files (relative to command execution directory) |
1517
- | `formatAsMigration` | boolean | false | Whether to format SQL as migration-ready files with Flyway schema placeholders |
1518
- | `filterPatterns` | string[] | undefined | Array of patterns to filter SQL statements (see below) |
1519
- | `filterType` | "exclude" \| "include" | "exclude" | How to apply filter patterns |
1513
+ | Option | Type | Default | Description |
1514
+ | ------------------- | ---------------------- | --------------- | ------------------------------------------------------------------------------ |
1515
+ | `enabled` | boolean | false | Whether to enable SQL logging during push operations |
1516
+ | `outputDirectory` | string | "./sql_logging" | Directory to output SQL log files (relative to command execution directory) |
1517
+ | `formatAsMigration` | boolean | false | Whether to format SQL as migration-ready files with Flyway schema placeholders |
1518
+ | `filterPatterns` | string[] | undefined | Array of patterns to filter SQL statements (see below) |
1519
+ | `filterType` | "exclude" \| "include" | "exclude" | How to apply filter patterns |
1520
1520
 
1521
1521
  #### SQL Log File Format
1522
1522
 
@@ -1633,11 +1633,11 @@ Add the `userRoleValidation` configuration to your root `.mj-sync.json` file:
1633
1633
 
1634
1634
  #### Configuration Options
1635
1635
 
1636
- | Option | Type | Default | Description |
1637
- |--------|------|---------|-------------|
1638
- | `enabled` | boolean | false | Enable user role validation for UserID fields |
1639
- | `allowedRoles` | string[] | [] | List of role names that are allowed |
1640
- | `allowUsersWithoutRoles` | boolean | false | Allow users without any assigned roles |
1636
+ | Option | Type | Default | Description |
1637
+ | ------------------------ | -------- | ------- | --------------------------------------------- |
1638
+ | `enabled` | boolean | false | Enable user role validation for UserID fields |
1639
+ | `allowedRoles` | string[] | [] | List of role names that are allowed |
1640
+ | `allowUsersWithoutRoles` | boolean | false | Allow users without any assigned roles |
1641
1641
 
1642
1642
  #### How It Works
1643
1643
 
@@ -1864,10 +1864,10 @@ When `recursive: true` is set:
1864
1864
 
1865
1865
  ### Configuration Options
1866
1866
 
1867
- | Option | Type | Default | Description |
1868
- |--------|------|---------|-------------|
1869
- | `recursive` | boolean | false | Enable automatic recursive fetching |
1870
- | `maxDepth` | number | 10 | Maximum recursion depth to prevent infinite loops |
1867
+ | Option | Type | Default | Description |
1868
+ | ----------- | ------- | ------- | ------------------------------------------------- |
1869
+ | `recursive` | boolean | false | Enable automatic recursive fetching |
1870
+ | `maxDepth` | number | 10 | Maximum recursion depth to prevent infinite loops |
1871
1871
 
1872
1872
  ### Safeguards
1873
1873
 
@@ -1934,24 +1934,24 @@ The pull command now supports smart update capabilities with extensive configura
1934
1934
 
1935
1935
  #### Pull Configuration Options
1936
1936
 
1937
- | Option | Type | Default | Description |
1938
- |--------|------|---------|-------------|
1939
- | `filePattern` | string | Entity filePattern | Pattern for finding existing files to update |
1940
- | `createNewFileIfNotFound` | boolean | true | Create files for records not found locally |
1941
- | `newFileName` | string | - | Filename for new records when appending (see warning below) |
1942
- | `appendRecordsToExistingFile` | boolean | false | Append new records to a single file |
1943
- | `updateExistingRecords` | boolean | true | Update existing records found in local files |
1944
- | `preserveFields` | string[] | [] | Fields that retain local values during updates (see detailed explanation below) |
1945
- | `mergeStrategy` | string | "merge" | How to merge updates: "merge", "overwrite", or "skip" |
1946
- | `backupBeforeUpdate` | boolean | false | Create timestamped backups before updating files |
1947
- | `backupDirectory` | string | ".backups" | Directory name for backup files (relative to entity directory) |
1948
- | `filter` | string | - | SQL WHERE clause for filtering records |
1949
- | `externalizeFields` | array/object | - | Fields to save as external files with optional patterns |
1950
- | `excludeFields` | string[] | [] | Fields to completely omit from pulled data (see detailed explanation below) |
1951
- | `lookupFields` | object | - | Foreign keys to convert to @lookup references |
1952
- | `relatedEntities` | object | - | Related entities to pull as embedded collections |
1953
- | `ignoreNullFields` | boolean | false | Exclude fields with null values from pulled data |
1954
- | `ignoreVirtualFields` | boolean | false | Exclude virtual fields (view-only fields) from pulled data |
1937
+ | Option | Type | Default | Description |
1938
+ | ----------------------------- | ------------ | ------------------ | ------------------------------------------------------------------------------- |
1939
+ | `filePattern` | string | Entity filePattern | Pattern for finding existing files to update |
1940
+ | `createNewFileIfNotFound` | boolean | true | Create files for records not found locally |
1941
+ | `newFileName` | string | - | Filename for new records when appending (see warning below) |
1942
+ | `appendRecordsToExistingFile` | boolean | false | Append new records to a single file |
1943
+ | `updateExistingRecords` | boolean | true | Update existing records found in local files |
1944
+ | `preserveFields` | string[] | [] | Fields that retain local values during updates (see detailed explanation below) |
1945
+ | `mergeStrategy` | string | "merge" | How to merge updates: "merge", "overwrite", or "skip" |
1946
+ | `backupBeforeUpdate` | boolean | false | Create timestamped backups before updating files |
1947
+ | `backupDirectory` | string | ".backups" | Directory name for backup files (relative to entity directory) |
1948
+ | `filter` | string | - | SQL WHERE clause for filtering records |
1949
+ | `externalizeFields` | array/object | - | Fields to save as external files with optional patterns |
1950
+ | `excludeFields` | string[] | [] | Fields to completely omit from pulled data (see detailed explanation below) |
1951
+ | `lookupFields` | object | - | Foreign keys to convert to @lookup references |
1952
+ | `relatedEntities` | object | - | Related entities to pull as embedded collections |
1953
+ | `ignoreNullFields` | boolean | false | Exclude fields with null values from pulled data |
1954
+ | `ignoreVirtualFields` | boolean | false | Exclude virtual fields (view-only fields) from pulled data |
1955
1955
 
1956
1956
  > **⚠️ Important Configuration Warning**
1957
1957
  >
@@ -2459,14 +2459,14 @@ When using virtual properties, related required fields are skipped:
2459
2459
 
2460
2460
  ### Common Validation Errors
2461
2461
 
2462
- | Error | Cause | Solution |
2463
- |-------|-------|----------|
2464
- | `Field "X" does not exist` | Typo or wrong entity | Check entity definition in generated files |
2465
- | `Entity "X" not found` | Wrong entity name | Use exact entity name from database |
2466
- | `File not found` | Bad @file: reference | Check file path is relative and exists |
2467
- | `Lookup not found` | No matching record | Verify lookup value or use ?create |
2468
- | `Circular dependency` | A→B→A references | Restructure to avoid cycles |
2469
- | `Required field missing` | Missing required field | Add field with appropriate value |
2462
+ | Error | Cause | Solution |
2463
+ | -------------------------- | ---------------------- | ------------------------------------------ |
2464
+ | `Field "X" does not exist` | Typo or wrong entity | Check entity definition in generated files |
2465
+ | `Entity "X" not found` | Wrong entity name | Use exact entity name from database |
2466
+ | `File not found` | Bad @file: reference | Check file path is relative and exists |
2467
+ | `Lookup not found` | No matching record | Verify lookup value or use ?create |
2468
+ | `Circular dependency` | A→B→A references | Restructure to avoid cycles |
2469
+ | `Required field missing` | Missing required field | Add field with appropriate value |
2470
2470
 
2471
2471
  ### Validation Configuration
2472
2472
 
@@ -1,4 +1,4 @@
1
- import { BaseEntity } from '@memberjunction/core';
1
+ import { BaseEntity } from '@memberjunction/global';
2
2
  /**
3
3
  * Handles discovery and extraction of all properties from BaseEntity objects,
4
4
  * including both database fields and virtual properties defined in subclasses.
@@ -86,7 +86,7 @@ class EntityPropertyExtractor {
86
86
  const dbFieldNames = new Set();
87
87
  if (typeof record.GetAll === 'function') {
88
88
  const dbFields = record.GetAll();
89
- Object.keys(dbFields).forEach(key => dbFieldNames.add(key));
89
+ Object.keys(dbFields).forEach((key) => dbFieldNames.add(key));
90
90
  }
91
91
  return dbFieldNames;
92
92
  }
@@ -155,9 +155,23 @@ class EntityPropertyExtractor {
155
155
  */
156
156
  isBaseEntityMethod(propertyName) {
157
157
  const baseEntityMethods = [
158
- 'Get', 'Set', 'GetAll', 'SetMany', 'LoadFromData', 'Save', 'Load', 'Delete',
159
- 'Fields', 'Dirty', 'IsSaved', 'PrimaryKeys', 'EntityInfo', 'ContextCurrentUser',
160
- 'ProviderToUse', 'RecordChanges', 'TransactionGroup'
158
+ 'Get',
159
+ 'Set',
160
+ 'GetAll',
161
+ 'SetMany',
162
+ 'LoadFromData',
163
+ 'Save',
164
+ 'Load',
165
+ 'Delete',
166
+ 'Fields',
167
+ 'Dirty',
168
+ 'IsSaved',
169
+ 'PrimaryKeys',
170
+ 'EntityInfo',
171
+ 'ContextCurrentUser',
172
+ 'ProviderToUse',
173
+ 'RecordChanges',
174
+ 'TransactionGroup',
161
175
  ];
162
176
  return baseEntityMethods.includes(propertyName);
163
177
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EntityPropertyExtractor.js","sourceRoot":"","sources":["../../src/lib/EntityPropertyExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity } from '@memberjunction/core';\n\n/**\n * Handles discovery and extraction of all properties from BaseEntity objects,\n * including both database fields and virtual properties defined in subclasses.\n */\nexport class EntityPropertyExtractor {\n /**\n * Gets ALL properties from a BaseEntity object, including both:\n * 1. Database fields (from record.GetAll())\n * 2. Virtual properties (getters defined in subclasses like TemplateText)\n * @param record The BaseEntity object to get properties from\n * @param fieldOverrides Optional field value overrides (e.g., for @parent:ID syntax)\n */\n extractAllProperties(record: BaseEntity, fieldOverrides?: Record<string, any>): Record<string, any> {\n const allProperties: Record<string, any> = {};\n \n // 1. Get database fields using GetAll()\n this.extractDatabaseFields(record, allProperties);\n \n // 2. Apply field overrides (e.g., for @parent:ID replacement in related entities)\n this.applyFieldOverrides(allProperties, fieldOverrides);\n \n // 3. Extract virtual properties by walking the prototype chain\n this.extractVirtualProperties(record, allProperties, fieldOverrides);\n \n return allProperties;\n }\n\n /**\n * Extracts database fields from the entity using GetAll()\n */\n private extractDatabaseFields(record: BaseEntity, allProperties: Record<string, any>): void {\n if (typeof record.GetAll === 'function') {\n const dbFields = record.GetAll();\n Object.assign(allProperties, dbFields);\n }\n }\n\n /**\n * Applies field overrides to the properties collection\n */\n private applyFieldOverrides(allProperties: Record<string, any>, fieldOverrides?: Record<string, any>): void {\n if (fieldOverrides) {\n Object.assign(allProperties, fieldOverrides);\n }\n }\n\n /**\n * Extracts virtual properties by walking the prototype chain\n */\n private extractVirtualProperties(\n record: BaseEntity, \n allProperties: Record<string, any>, \n fieldOverrides?: Record<string, any>\n ): void {\n const virtualProperties = this.discoverVirtualProperties(record);\n \n for (const propertyName of virtualProperties) {\n try {\n // Skip if this property is overridden\n if (fieldOverrides && propertyName in fieldOverrides) {\n continue;\n }\n \n // Use bracket notation to access the getter\n const value = (record as any)[propertyName];\n \n // Only include if the value is not undefined and not a function\n if (value !== undefined && typeof value !== 'function') {\n allProperties[propertyName] = value;\n }\n } catch (error) {\n // Skip properties that throw errors when accessed\n continue;\n }\n }\n }\n\n /**\n * Discovers virtual properties (getters) defined in BaseEntity subclasses\n * Returns property names that are getters but not in the base database fields\n */\n private discoverVirtualProperties(record: BaseEntity): string[] {\n const virtualProperties: string[] = [];\n const dbFieldNames = this.getDatabaseFieldNames(record);\n \n // Walk the prototype chain to find getters\n let currentPrototype = Object.getPrototypeOf(record);\n \n while (currentPrototype && currentPrototype !== Object.prototype) {\n this.extractPropertiesFromPrototype(currentPrototype, virtualProperties, dbFieldNames);\n currentPrototype = Object.getPrototypeOf(currentPrototype);\n }\n \n return virtualProperties;\n }\n\n /**\n * Gets the set of database field names from the entity\n */\n private getDatabaseFieldNames(record: BaseEntity): Set<string> {\n const dbFieldNames = new Set<string>();\n \n if (typeof record.GetAll === 'function') {\n const dbFields = record.GetAll();\n Object.keys(dbFields).forEach(key => dbFieldNames.add(key));\n }\n \n return dbFieldNames;\n }\n\n /**\n * Extracts properties from a single prototype level\n */\n private extractPropertiesFromPrototype(\n prototype: any, \n virtualProperties: string[], \n dbFieldNames: Set<string>\n ): void {\n const propertyNames = Object.getOwnPropertyNames(prototype);\n \n for (const propertyName of propertyNames) {\n if (this.shouldIncludeProperty(propertyName, virtualProperties, dbFieldNames)) {\n const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);\n if (this.isVirtualProperty(descriptor)) {\n virtualProperties.push(propertyName);\n }\n }\n }\n }\n\n /**\n * Determines if a property should be considered for inclusion\n */\n private shouldIncludeProperty(\n propertyName: string, \n virtualProperties: string[], \n dbFieldNames: Set<string>\n ): boolean {\n // Skip if already found or is a database field\n if (virtualProperties.includes(propertyName) || dbFieldNames.has(propertyName)) {\n return false;\n }\n \n // Skip internal properties and methods\n return !this.shouldSkipProperty(propertyName);\n }\n\n /**\n * Determines if a property descriptor represents a virtual property\n */\n private isVirtualProperty(descriptor: PropertyDescriptor | undefined): boolean {\n if (!descriptor) return false;\n \n // Skip read-only getters (might be computed properties)\n if (typeof descriptor.get === 'function' && !descriptor.set) {\n return false;\n }\n \n // Include read-write getter/setter pairs (likely virtual properties)\n return typeof descriptor.get === 'function' && typeof descriptor.set === 'function';\n }\n\n /**\n * Determines if a property should be skipped during virtual property discovery\n */\n private shouldSkipProperty(propertyName: string): boolean {\n // Skip private properties (starting with _ or __)\n if (propertyName.startsWith('_') || propertyName.startsWith('__')) {\n return true;\n }\n \n // Skip constructor and common Object.prototype methods\n if (this.isCommonObjectMethod(propertyName)) {\n return true;\n }\n \n // Skip known BaseEntity methods and properties\n return this.isBaseEntityMethod(propertyName);\n }\n\n /**\n * Checks if property is a common Object.prototype method\n */\n private isCommonObjectMethod(propertyName: string): boolean {\n const commonMethods = ['constructor', 'toString', 'valueOf'];\n return commonMethods.includes(propertyName);\n }\n\n /**\n * Checks if property is a known BaseEntity method or property\n */\n private isBaseEntityMethod(propertyName: string): boolean {\n const baseEntityMethods = [\n 'Get', 'Set', 'GetAll', 'SetMany', 'LoadFromData', 'Save', 'Load', 'Delete',\n 'Fields', 'Dirty', 'IsSaved', 'PrimaryKeys', 'EntityInfo', 'ContextCurrentUser',\n 'ProviderToUse', 'RecordChanges', 'TransactionGroup'\n ];\n \n return baseEntityMethods.includes(propertyName);\n }\n}"]}
1
+ {"version":3,"file":"EntityPropertyExtractor.js","sourceRoot":"","sources":["../../src/lib/EntityPropertyExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity } from '@memberjunction/global';\n\n/**\n * Handles discovery and extraction of all properties from BaseEntity objects,\n * including both database fields and virtual properties defined in subclasses.\n */\nexport class EntityPropertyExtractor {\n /**\n * Gets ALL properties from a BaseEntity object, including both:\n * 1. Database fields (from record.GetAll())\n * 2. Virtual properties (getters defined in subclasses like TemplateText)\n * @param record The BaseEntity object to get properties from\n * @param fieldOverrides Optional field value overrides (e.g., for @parent:ID syntax)\n */\n extractAllProperties(record: BaseEntity, fieldOverrides?: Record<string, any>): Record<string, any> {\n const allProperties: Record<string, any> = {};\n\n // 1. Get database fields using GetAll()\n this.extractDatabaseFields(record, allProperties);\n\n // 2. Apply field overrides (e.g., for @parent:ID replacement in related entities)\n this.applyFieldOverrides(allProperties, fieldOverrides);\n\n // 3. Extract virtual properties by walking the prototype chain\n this.extractVirtualProperties(record, allProperties, fieldOverrides);\n\n return allProperties;\n }\n\n /**\n * Extracts database fields from the entity using GetAll()\n */\n private extractDatabaseFields(record: BaseEntity, allProperties: Record<string, any>): void {\n if (typeof record.GetAll === 'function') {\n const dbFields = record.GetAll();\n Object.assign(allProperties, dbFields);\n }\n }\n\n /**\n * Applies field overrides to the properties collection\n */\n private applyFieldOverrides(allProperties: Record<string, any>, fieldOverrides?: Record<string, any>): void {\n if (fieldOverrides) {\n Object.assign(allProperties, fieldOverrides);\n }\n }\n\n /**\n * Extracts virtual properties by walking the prototype chain\n */\n private extractVirtualProperties(record: BaseEntity, allProperties: Record<string, any>, fieldOverrides?: Record<string, any>): void {\n const virtualProperties = this.discoverVirtualProperties(record);\n\n for (const propertyName of virtualProperties) {\n try {\n // Skip if this property is overridden\n if (fieldOverrides && propertyName in fieldOverrides) {\n continue;\n }\n\n // Use bracket notation to access the getter\n const value = (record as any)[propertyName];\n\n // Only include if the value is not undefined and not a function\n if (value !== undefined && typeof value !== 'function') {\n allProperties[propertyName] = value;\n }\n } catch (error) {\n // Skip properties that throw errors when accessed\n continue;\n }\n }\n }\n\n /**\n * Discovers virtual properties (getters) defined in BaseEntity subclasses\n * Returns property names that are getters but not in the base database fields\n */\n private discoverVirtualProperties(record: BaseEntity): string[] {\n const virtualProperties: string[] = [];\n const dbFieldNames = this.getDatabaseFieldNames(record);\n\n // Walk the prototype chain to find getters\n let currentPrototype = Object.getPrototypeOf(record);\n\n while (currentPrototype && currentPrototype !== Object.prototype) {\n this.extractPropertiesFromPrototype(currentPrototype, virtualProperties, dbFieldNames);\n currentPrototype = Object.getPrototypeOf(currentPrototype);\n }\n\n return virtualProperties;\n }\n\n /**\n * Gets the set of database field names from the entity\n */\n private getDatabaseFieldNames(record: BaseEntity): Set<string> {\n const dbFieldNames = new Set<string>();\n\n if (typeof record.GetAll === 'function') {\n const dbFields = record.GetAll();\n Object.keys(dbFields).forEach((key) => dbFieldNames.add(key));\n }\n\n return dbFieldNames;\n }\n\n /**\n * Extracts properties from a single prototype level\n */\n private extractPropertiesFromPrototype(prototype: any, virtualProperties: string[], dbFieldNames: Set<string>): void {\n const propertyNames = Object.getOwnPropertyNames(prototype);\n\n for (const propertyName of propertyNames) {\n if (this.shouldIncludeProperty(propertyName, virtualProperties, dbFieldNames)) {\n const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);\n if (this.isVirtualProperty(descriptor)) {\n virtualProperties.push(propertyName);\n }\n }\n }\n }\n\n /**\n * Determines if a property should be considered for inclusion\n */\n private shouldIncludeProperty(propertyName: string, virtualProperties: string[], dbFieldNames: Set<string>): boolean {\n // Skip if already found or is a database field\n if (virtualProperties.includes(propertyName) || dbFieldNames.has(propertyName)) {\n return false;\n }\n\n // Skip internal properties and methods\n return !this.shouldSkipProperty(propertyName);\n }\n\n /**\n * Determines if a property descriptor represents a virtual property\n */\n private isVirtualProperty(descriptor: PropertyDescriptor | undefined): boolean {\n if (!descriptor) return false;\n\n // Skip read-only getters (might be computed properties)\n if (typeof descriptor.get === 'function' && !descriptor.set) {\n return false;\n }\n\n // Include read-write getter/setter pairs (likely virtual properties)\n return typeof descriptor.get === 'function' && typeof descriptor.set === 'function';\n }\n\n /**\n * Determines if a property should be skipped during virtual property discovery\n */\n private shouldSkipProperty(propertyName: string): boolean {\n // Skip private properties (starting with _ or __)\n if (propertyName.startsWith('_') || propertyName.startsWith('__')) {\n return true;\n }\n\n // Skip constructor and common Object.prototype methods\n if (this.isCommonObjectMethod(propertyName)) {\n return true;\n }\n\n // Skip known BaseEntity methods and properties\n return this.isBaseEntityMethod(propertyName);\n }\n\n /**\n * Checks if property is a common Object.prototype method\n */\n private isCommonObjectMethod(propertyName: string): boolean {\n const commonMethods = ['constructor', 'toString', 'valueOf'];\n return commonMethods.includes(propertyName);\n }\n\n /**\n * Checks if property is a known BaseEntity method or property\n */\n private isBaseEntityMethod(propertyName: string): boolean {\n const baseEntityMethods = [\n 'Get',\n 'Set',\n 'GetAll',\n 'SetMany',\n 'LoadFromData',\n 'Save',\n 'Load',\n 'Delete',\n 'Fields',\n 'Dirty',\n 'IsSaved',\n 'PrimaryKeys',\n 'EntityInfo',\n 'ContextCurrentUser',\n 'ProviderToUse',\n 'RecordChanges',\n 'TransactionGroup',\n ];\n\n return baseEntityMethods.includes(propertyName);\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { BaseEntity } from '@memberjunction/core';
1
+ import { BaseEntity } from '@memberjunction/global';
2
2
  /**
3
3
  * Handles externalization of field values to separate files with @file: references
4
4
  */
@@ -37,10 +37,10 @@ class FieldExternalizer {
37
37
  * Checks if we should use an existing file reference
38
38
  */
39
39
  shouldUseExistingReference(existingFileReference, mergeStrategy = 'merge') {
40
- return mergeStrategy === 'merge' &&
40
+ return (mergeStrategy === 'merge' &&
41
41
  !!existingFileReference &&
42
42
  typeof existingFileReference === 'string' &&
43
- existingFileReference.startsWith('@file:');
43
+ existingFileReference.startsWith('@file:'));
44
44
  }
45
45
  /**
46
46
  * Uses an existing file reference
@@ -1 +1 @@
1
- {"version":3,"file":"FieldExternalizer.js","sourceRoot":"","sources":["../../src/lib/FieldExternalizer.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\nimport { BaseEntity } from '@memberjunction/core';\n\n/**\n * Handles externalization of field values to separate files with @file: references\n */\nexport class FieldExternalizer {\n /**\n * Externalize a field value to a separate file and return @file: reference\n */\n async externalizeField(\n fieldName: string,\n fieldValue: any,\n pattern: string,\n recordData: BaseEntity,\n targetDir: string,\n existingFileReference?: string,\n mergeStrategy: string = 'merge',\n verbose?: boolean\n ): Promise<string> {\n const { finalFilePath, fileReference } = this.determineFilePath(\n pattern, \n recordData, \n targetDir, \n existingFileReference, \n mergeStrategy, \n fieldName, \n verbose\n );\n \n const shouldWrite = await this.shouldWriteFile(finalFilePath, fieldValue, fieldName);\n \n if (shouldWrite) {\n await this.writeExternalFile(finalFilePath, fieldValue, fieldName, verbose);\n } else if (verbose) {\n console.log(`External file ${finalFilePath} unchanged, skipping write`);\n }\n \n return fileReference;\n }\n\n /**\n * Determines the file path and reference for externalization\n */\n private determineFilePath(\n pattern: string,\n recordData: BaseEntity,\n targetDir: string,\n existingFileReference?: string,\n mergeStrategy: string = 'merge',\n fieldName: string = '',\n verbose?: boolean\n ): { finalFilePath: string; fileReference: string } {\n if (this.shouldUseExistingReference(existingFileReference, mergeStrategy)) {\n return this.useExistingFileReference(existingFileReference!, targetDir, verbose);\n }\n \n return this.createNewFileReference(pattern, recordData, targetDir, fieldName, verbose);\n }\n\n /**\n * Checks if we should use an existing file reference\n */\n private shouldUseExistingReference(existingFileReference?: string, mergeStrategy: string = 'merge'): boolean {\n return mergeStrategy === 'merge' && \n !!existingFileReference && \n typeof existingFileReference === 'string' && \n existingFileReference.startsWith('@file:');\n }\n\n /**\n * Uses an existing file reference\n */\n private useExistingFileReference(\n existingFileReference: string, \n targetDir: string, \n verbose?: boolean\n ): { finalFilePath: string; fileReference: string } {\n const existingPath = existingFileReference.substring(6); // Remove @file: prefix\n const finalFilePath = path.resolve(targetDir, existingPath);\n \n if (verbose) {\n console.log(`Using existing external file: ${finalFilePath}`);\n }\n \n return { finalFilePath, fileReference: existingFileReference };\n }\n\n /**\n * Creates a new file reference using the pattern\n */\n private createNewFileReference(\n pattern: string,\n recordData: BaseEntity,\n targetDir: string,\n fieldName: string,\n verbose?: boolean\n ): { finalFilePath: string; fileReference: string } {\n const processedPattern = this.processPattern(pattern, recordData, fieldName);\n const cleanPattern = this.removeFilePrefix(processedPattern);\n const finalFilePath = path.resolve(targetDir, cleanPattern);\n const fileReference = `@file:${cleanPattern}`;\n \n if (verbose) {\n console.log(`Creating new external file: ${finalFilePath}`);\n }\n \n return { finalFilePath, fileReference };\n }\n\n /**\n * Processes pattern placeholders with actual values\n */\n private processPattern(pattern: string, recordData: BaseEntity, fieldName: string): string {\n let processedPattern = pattern;\n \n // Replace common placeholders\n processedPattern = this.replacePlaceholder(processedPattern, 'Name', (recordData as any).Name);\n processedPattern = this.replacePlaceholder(processedPattern, 'ID', (recordData as any).ID);\n processedPattern = this.replacePlaceholder(processedPattern, 'FieldName', fieldName);\n \n // Replace any other field placeholders\n processedPattern = this.replaceFieldPlaceholders(processedPattern, recordData);\n \n return processedPattern;\n }\n\n /**\n * Replaces a single placeholder in the pattern\n */\n private replacePlaceholder(pattern: string, placeholder: string, value: any): string {\n if (value != null) {\n const sanitizedValue = this.sanitizeForFilename(String(value));\n return pattern.replace(new RegExp(`\\\\{${placeholder}\\\\}`, 'g'), sanitizedValue);\n }\n return pattern;\n }\n\n /**\n * Replaces field placeholders with values from the record\n */\n private replaceFieldPlaceholders(pattern: string, recordData: BaseEntity): string {\n let processedPattern = pattern;\n \n for (const [key, value] of Object.entries(recordData as any)) {\n if (value != null) {\n const sanitizedValue = this.sanitizeForFilename(String(value));\n processedPattern = processedPattern.replace(new RegExp(`\\\\{${key}\\\\}`, 'g'), sanitizedValue);\n }\n }\n \n return processedPattern;\n }\n\n /**\n * Removes @file: prefix if present\n */\n private removeFilePrefix(pattern: string): string {\n return pattern.startsWith('@file:') ? pattern.substring(6) : pattern;\n }\n\n /**\n * Determines if the file should be written based on content comparison\n */\n private async shouldWriteFile(finalFilePath: string, fieldValue: any, fieldName: string): Promise<boolean> {\n if (!(await fs.pathExists(finalFilePath))) {\n return true; // File doesn't exist, should write\n }\n \n try {\n const existingContent = await fs.readFile(finalFilePath, 'utf8');\n const contentToWrite = this.prepareContentForWriting(fieldValue, fieldName);\n \n return existingContent !== contentToWrite;\n } catch (error) {\n return true; // Error reading existing file, should write\n }\n }\n\n /**\n * Writes the external file with the field content\n */\n private async writeExternalFile(\n finalFilePath: string, \n fieldValue: any, \n fieldName: string, \n verbose?: boolean\n ): Promise<void> {\n // Ensure the directory exists\n await fs.ensureDir(path.dirname(finalFilePath));\n \n // Write the field value to the file\n const contentToWrite = this.prepareContentForWriting(fieldValue, fieldName);\n await fs.writeFile(finalFilePath, contentToWrite, 'utf8');\n \n if (verbose) {\n console.log(`Wrote externalized field ${fieldName} to ${finalFilePath}`);\n }\n }\n\n /**\n * Prepares content for writing, with JSON pretty-printing if applicable\n */\n private prepareContentForWriting(fieldValue: any, fieldName: string): string {\n let contentToWrite = String(fieldValue);\n \n // If the value looks like JSON, try to pretty-print it\n if (this.shouldPrettyPrintAsJson(fieldName)) {\n try {\n const parsed = JSON.parse(contentToWrite);\n contentToWrite = JSON.stringify(parsed, null, 2);\n } catch {\n // Not valid JSON, use as-is\n }\n }\n \n return contentToWrite;\n }\n\n /**\n * Determines if content should be pretty-printed as JSON\n */\n private shouldPrettyPrintAsJson(fieldName: string): boolean {\n const lowerFieldName = fieldName.toLowerCase();\n return lowerFieldName.includes('json') || lowerFieldName.includes('example');\n }\n\n /**\n * Sanitize a string for use in filenames\n */\n private sanitizeForFilename(input: string): string {\n return input\n .toLowerCase()\n .replace(/\\s+/g, '-') // Replace spaces with hyphens\n .replace(/[^a-z0-9.-]/g, '') // Remove special characters except dots and hyphens\n .replace(/--+/g, '-') // Replace multiple hyphens with single hyphen\n .replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens\n }\n}"]}
1
+ {"version":3,"file":"FieldExternalizer.js","sourceRoot":"","sources":["../../src/lib/FieldExternalizer.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\nimport { BaseEntity } from '@memberjunction/global';\n\n/**\n * Handles externalization of field values to separate files with @file: references\n */\nexport class FieldExternalizer {\n /**\n * Externalize a field value to a separate file and return @file: reference\n */\n async externalizeField(\n fieldName: string,\n fieldValue: any,\n pattern: string,\n recordData: BaseEntity,\n targetDir: string,\n existingFileReference?: string,\n mergeStrategy: string = 'merge',\n verbose?: boolean\n ): Promise<string> {\n const { finalFilePath, fileReference } = this.determineFilePath(\n pattern,\n recordData,\n targetDir,\n existingFileReference,\n mergeStrategy,\n fieldName,\n verbose\n );\n\n const shouldWrite = await this.shouldWriteFile(finalFilePath, fieldValue, fieldName);\n\n if (shouldWrite) {\n await this.writeExternalFile(finalFilePath, fieldValue, fieldName, verbose);\n } else if (verbose) {\n console.log(`External file ${finalFilePath} unchanged, skipping write`);\n }\n\n return fileReference;\n }\n\n /**\n * Determines the file path and reference for externalization\n */\n private determineFilePath(\n pattern: string,\n recordData: BaseEntity,\n targetDir: string,\n existingFileReference?: string,\n mergeStrategy: string = 'merge',\n fieldName: string = '',\n verbose?: boolean\n ): { finalFilePath: string; fileReference: string } {\n if (this.shouldUseExistingReference(existingFileReference, mergeStrategy)) {\n return this.useExistingFileReference(existingFileReference!, targetDir, verbose);\n }\n\n return this.createNewFileReference(pattern, recordData, targetDir, fieldName, verbose);\n }\n\n /**\n * Checks if we should use an existing file reference\n */\n private shouldUseExistingReference(existingFileReference?: string, mergeStrategy: string = 'merge'): boolean {\n return (\n mergeStrategy === 'merge' &&\n !!existingFileReference &&\n typeof existingFileReference === 'string' &&\n existingFileReference.startsWith('@file:')\n );\n }\n\n /**\n * Uses an existing file reference\n */\n private useExistingFileReference(\n existingFileReference: string,\n targetDir: string,\n verbose?: boolean\n ): { finalFilePath: string; fileReference: string } {\n const existingPath = existingFileReference.substring(6); // Remove @file: prefix\n const finalFilePath = path.resolve(targetDir, existingPath);\n\n if (verbose) {\n console.log(`Using existing external file: ${finalFilePath}`);\n }\n\n return { finalFilePath, fileReference: existingFileReference };\n }\n\n /**\n * Creates a new file reference using the pattern\n */\n private createNewFileReference(\n pattern: string,\n recordData: BaseEntity,\n targetDir: string,\n fieldName: string,\n verbose?: boolean\n ): { finalFilePath: string; fileReference: string } {\n const processedPattern = this.processPattern(pattern, recordData, fieldName);\n const cleanPattern = this.removeFilePrefix(processedPattern);\n const finalFilePath = path.resolve(targetDir, cleanPattern);\n const fileReference = `@file:${cleanPattern}`;\n\n if (verbose) {\n console.log(`Creating new external file: ${finalFilePath}`);\n }\n\n return { finalFilePath, fileReference };\n }\n\n /**\n * Processes pattern placeholders with actual values\n */\n private processPattern(pattern: string, recordData: BaseEntity, fieldName: string): string {\n let processedPattern = pattern;\n\n // Replace common placeholders\n processedPattern = this.replacePlaceholder(processedPattern, 'Name', (recordData as any).Name);\n processedPattern = this.replacePlaceholder(processedPattern, 'ID', (recordData as any).ID);\n processedPattern = this.replacePlaceholder(processedPattern, 'FieldName', fieldName);\n\n // Replace any other field placeholders\n processedPattern = this.replaceFieldPlaceholders(processedPattern, recordData);\n\n return processedPattern;\n }\n\n /**\n * Replaces a single placeholder in the pattern\n */\n private replacePlaceholder(pattern: string, placeholder: string, value: any): string {\n if (value != null) {\n const sanitizedValue = this.sanitizeForFilename(String(value));\n return pattern.replace(new RegExp(`\\\\{${placeholder}\\\\}`, 'g'), sanitizedValue);\n }\n return pattern;\n }\n\n /**\n * Replaces field placeholders with values from the record\n */\n private replaceFieldPlaceholders(pattern: string, recordData: BaseEntity): string {\n let processedPattern = pattern;\n\n for (const [key, value] of Object.entries(recordData as any)) {\n if (value != null) {\n const sanitizedValue = this.sanitizeForFilename(String(value));\n processedPattern = processedPattern.replace(new RegExp(`\\\\{${key}\\\\}`, 'g'), sanitizedValue);\n }\n }\n\n return processedPattern;\n }\n\n /**\n * Removes @file: prefix if present\n */\n private removeFilePrefix(pattern: string): string {\n return pattern.startsWith('@file:') ? pattern.substring(6) : pattern;\n }\n\n /**\n * Determines if the file should be written based on content comparison\n */\n private async shouldWriteFile(finalFilePath: string, fieldValue: any, fieldName: string): Promise<boolean> {\n if (!(await fs.pathExists(finalFilePath))) {\n return true; // File doesn't exist, should write\n }\n\n try {\n const existingContent = await fs.readFile(finalFilePath, 'utf8');\n const contentToWrite = this.prepareContentForWriting(fieldValue, fieldName);\n\n return existingContent !== contentToWrite;\n } catch (error) {\n return true; // Error reading existing file, should write\n }\n }\n\n /**\n * Writes the external file with the field content\n */\n private async writeExternalFile(finalFilePath: string, fieldValue: any, fieldName: string, verbose?: boolean): Promise<void> {\n // Ensure the directory exists\n await fs.ensureDir(path.dirname(finalFilePath));\n\n // Write the field value to the file\n const contentToWrite = this.prepareContentForWriting(fieldValue, fieldName);\n await fs.writeFile(finalFilePath, contentToWrite, 'utf8');\n\n if (verbose) {\n console.log(`Wrote externalized field ${fieldName} to ${finalFilePath}`);\n }\n }\n\n /**\n * Prepares content for writing, with JSON pretty-printing if applicable\n */\n private prepareContentForWriting(fieldValue: any, fieldName: string): string {\n let contentToWrite = String(fieldValue);\n\n // If the value looks like JSON, try to pretty-print it\n if (this.shouldPrettyPrintAsJson(fieldName)) {\n try {\n const parsed = JSON.parse(contentToWrite);\n contentToWrite = JSON.stringify(parsed, null, 2);\n } catch {\n // Not valid JSON, use as-is\n }\n }\n\n return contentToWrite;\n }\n\n /**\n * Determines if content should be pretty-printed as JSON\n */\n private shouldPrettyPrintAsJson(fieldName: string): boolean {\n const lowerFieldName = fieldName.toLowerCase();\n return lowerFieldName.includes('json') || lowerFieldName.includes('example');\n }\n\n /**\n * Sanitize a string for use in filenames\n */\n private sanitizeForFilename(input: string): string {\n return input\n .toLowerCase()\n .replace(/\\s+/g, '-') // Replace spaces with hyphens\n .replace(/[^a-z0-9.-]/g, '') // Remove special characters except dots and hyphens\n .replace(/--+/g, '-') // Replace multiple hyphens with single hyphen\n .replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { BaseEntity, UserInfo } from '@memberjunction/core';
1
+ import { BaseEntity, UserInfo } from '@memberjunction/global';
2
2
  import { SyncEngine, RecordData } from '../lib/sync-engine';
3
3
  import { EntityConfig } from '../config';
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RecordProcessor = void 0;
4
- const core_1 = require("@memberjunction/core");
4
+ const global_1 = require("@memberjunction/global");
5
5
  const json_write_helper_1 = require("./json-write-helper");
6
6
  const EntityPropertyExtractor_1 = require("./EntityPropertyExtractor");
7
7
  const FieldExternalizer_1 = require("./FieldExternalizer");
@@ -93,7 +93,7 @@ class RecordProcessor {
93
93
  if (!entityConfig.pull?.ignoreVirtualFields || !entityInfo) {
94
94
  return false;
95
95
  }
96
- const fieldInfo = entityInfo.Fields.find(f => f.Name === fieldName);
96
+ const fieldInfo = entityInfo.Fields.find((f) => f.Name === fieldName);
97
97
  return fieldInfo?.IsVirtual === true;
98
98
  }
99
99
  /**
@@ -182,8 +182,7 @@ class RecordProcessor {
182
182
  }
183
183
  else {
184
184
  // Array of objects format
185
- const fieldConfig = externalizeConfig
186
- .find(config => config.field === fieldName);
185
+ const fieldConfig = externalizeConfig.find((config) => config.field === fieldName);
187
186
  if (fieldConfig) {
188
187
  return fieldConfig.pattern;
189
188
  }
@@ -250,7 +249,7 @@ class RecordProcessor {
250
249
  }
251
250
  return {
252
251
  lastModified: existingRecordData.sync.lastModified,
253
- checksum: checksum
252
+ checksum: checksum,
254
253
  };
255
254
  }
256
255
  else {
@@ -260,7 +259,7 @@ class RecordProcessor {
260
259
  }
261
260
  return {
262
261
  lastModified: new Date().toISOString(),
263
- checksum: checksum
262
+ checksum: checksum,
264
263
  };
265
264
  }
266
265
  }
@@ -268,8 +267,8 @@ class RecordProcessor {
268
267
  * Checks if the record has externalized fields
269
268
  */
270
269
  hasExternalizedFields(fields, entityConfig) {
271
- return !!entityConfig.pull?.externalizeFields &&
272
- Object.values(fields).some(value => typeof value === 'string' && value.startsWith('@file:'));
270
+ return (!!entityConfig.pull?.externalizeFields &&
271
+ Object.values(fields).some((value) => typeof value === 'string' && value.startsWith('@file:')));
273
272
  }
274
273
  /**
275
274
  * Convert a GUID value to @lookup syntax by looking up the human-readable value
@@ -279,11 +278,11 @@ class RecordProcessor {
279
278
  return guidValue;
280
279
  }
281
280
  try {
282
- const rv = new core_1.RunView();
281
+ const rv = new global_1.RunView();
283
282
  const result = await rv.RunView({
284
283
  EntityName: lookupConfig.entity,
285
284
  ExtraFilter: `ID = '${guidValue}'`,
286
- ResultType: 'entity_object'
285
+ ResultType: 'entity_object',
287
286
  }, this.contextUser);
288
287
  if (result.Success && result.Results && result.Results.length > 0) {
289
288
  const targetRecord = result.Results[0];
@@ -1 +1 @@
1
- {"version":3,"file":"RecordProcessor.js","sourceRoot":"","sources":["../../src/lib/RecordProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity, RunView, UserInfo, EntityInfo } from '@memberjunction/core';\nimport { SyncEngine, RecordData } from '../lib/sync-engine';\nimport { EntityConfig } from '../config';\nimport { JsonWriteHelper } from './json-write-helper';\nimport { EntityPropertyExtractor } from './EntityPropertyExtractor';\nimport { FieldExternalizer } from './FieldExternalizer';\nimport { RelatedEntityHandler } from './RelatedEntityHandler';\n\n/**\n * Handles the core processing of individual record data into the sync format\n */\nexport class RecordProcessor {\n private propertyExtractor: EntityPropertyExtractor;\n private fieldExternalizer: FieldExternalizer;\n private relatedEntityHandler: RelatedEntityHandler;\n\n constructor(\n private syncEngine: SyncEngine,\n private contextUser: UserInfo\n ) {\n this.propertyExtractor = new EntityPropertyExtractor();\n this.fieldExternalizer = new FieldExternalizer();\n this.relatedEntityHandler = new RelatedEntityHandler(syncEngine, contextUser);\n }\n\n /**\n * Processes a record into the standardized RecordData format\n */\n async processRecord(\n record: BaseEntity, \n primaryKey: Record<string, any>,\n targetDir: string, \n entityConfig: EntityConfig,\n verbose?: boolean,\n isNewRecord: boolean = true,\n existingRecordData?: RecordData,\n currentDepth: number = 0,\n ancestryPath: Set<string> = new Set(),\n fieldOverrides?: Record<string, any>\n ): Promise<RecordData> {\n // Extract all properties from the entity\n const allProperties = this.propertyExtractor.extractAllProperties(record, fieldOverrides);\n \n // Process fields and related entities\n const { fields, relatedEntities } = await this.processEntityData(\n allProperties,\n record,\n primaryKey,\n targetDir,\n entityConfig,\n existingRecordData,\n currentDepth,\n ancestryPath,\n verbose\n );\n \n // Calculate checksum and sync metadata\n const syncData = await this.calculateSyncMetadata(\n fields, \n targetDir, \n entityConfig, \n existingRecordData, \n verbose\n );\n \n // Build the final record data with proper ordering\n return JsonWriteHelper.createOrderedRecordData(\n fields,\n relatedEntities,\n primaryKey,\n syncData\n );\n }\n\n /**\n * Processes entity data into fields and related entities\n */\n private async processEntityData(\n allProperties: Record<string, any>,\n record: BaseEntity,\n primaryKey: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<{ fields: Record<string, any>; relatedEntities: Record<string, RecordData[]> }> {\n const fields: Record<string, any> = {};\n const relatedEntities: Record<string, RecordData[]> = {};\n \n // Process individual fields\n await this.processFields(\n allProperties, \n primaryKey, \n targetDir, \n entityConfig, \n existingRecordData, \n fields, \n verbose\n );\n \n // Process related entities if configured\n await this.processRelatedEntities(\n record, \n entityConfig, \n existingRecordData, \n currentDepth, \n ancestryPath, \n relatedEntities, \n verbose\n );\n \n return { fields, relatedEntities };\n }\n\n /**\n * Processes individual fields from the entity\n */\n private async processFields(\n allProperties: Record<string, any>,\n primaryKey: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n fields: Record<string, any>,\n verbose?: boolean\n ): Promise<void> {\n const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);\n \n for (const [fieldName, fieldValue] of Object.entries(allProperties)) {\n if (this.shouldSkipField(fieldName, fieldValue, primaryKey, entityConfig, entityInfo)) {\n continue;\n }\n \n let processedValue = await this.processFieldValue(\n fieldName,\n fieldValue,\n allProperties,\n targetDir,\n entityConfig,\n existingRecordData,\n verbose\n );\n \n fields[fieldName] = processedValue;\n }\n }\n\n /**\n * Determines if a field should be skipped during processing\n */\n private shouldSkipField(\n fieldName: string,\n fieldValue: any,\n primaryKey: Record<string, any>,\n entityConfig: EntityConfig,\n entityInfo: EntityInfo | null\n ): boolean {\n // Skip primary key fields\n if (primaryKey[fieldName] !== undefined) {\n return true;\n }\n \n // Skip internal fields\n if (fieldName.startsWith('__mj_')) {\n return true;\n }\n \n // Skip excluded fields\n if (entityConfig.pull?.excludeFields?.includes(fieldName)) {\n return true;\n }\n \n // Skip virtual fields if configured\n if (this.shouldSkipVirtualField(fieldName, entityConfig, entityInfo)) {\n return true;\n }\n \n // Skip null fields if configured\n if (entityConfig.pull?.ignoreNullFields && fieldValue === null) {\n return true;\n }\n \n return false;\n }\n\n /**\n * Checks if a virtual field should be skipped\n */\n private shouldSkipVirtualField(\n fieldName: string,\n entityConfig: EntityConfig,\n entityInfo: EntityInfo | null\n ): boolean {\n if (!entityConfig.pull?.ignoreVirtualFields || !entityInfo) {\n return false;\n }\n \n const fieldInfo = entityInfo.Fields.find(f => f.Name === fieldName);\n return fieldInfo?.IsVirtual === true;\n }\n\n /**\n * Processes a single field value through various transformations\n */\n private async processFieldValue(\n fieldName: string,\n fieldValue: any,\n allProperties: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n verbose?: boolean\n ): Promise<any> {\n let processedValue = fieldValue;\n \n // Apply lookup field conversion if configured\n processedValue = await this.applyLookupFieldConversion(\n fieldName, \n processedValue, \n entityConfig, \n verbose\n );\n \n // Trim string values\n processedValue = this.trimStringValue(processedValue);\n \n // Apply field externalization if configured\n processedValue = await this.applyFieldExternalization(\n fieldName,\n processedValue,\n allProperties,\n targetDir,\n entityConfig,\n existingRecordData,\n verbose\n );\n \n return processedValue;\n }\n\n /**\n * Applies lookup field conversion if configured\n */\n private async applyLookupFieldConversion(\n fieldName: string,\n fieldValue: any,\n entityConfig: EntityConfig,\n verbose?: boolean\n ): Promise<any> {\n const lookupConfig = entityConfig.pull?.lookupFields?.[fieldName];\n if (!lookupConfig || fieldValue == null) {\n return fieldValue;\n }\n \n try {\n return await this.convertGuidToLookup(String(fieldValue), lookupConfig, verbose);\n } catch (error) {\n if (verbose) {\n console.warn(`Failed to convert ${fieldName} to lookup: ${error}`);\n }\n return fieldValue; // Keep original value if lookup fails\n }\n }\n\n /**\n * Trims string values to remove whitespace\n */\n private trimStringValue(value: any): any {\n return typeof value === 'string' ? value.trim() : value;\n }\n\n /**\n * Applies field externalization if configured\n */\n private async applyFieldExternalization(\n fieldName: string,\n fieldValue: any,\n allProperties: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n verbose?: boolean\n ): Promise<any> {\n if (!entityConfig.pull?.externalizeFields || fieldValue == null) {\n return fieldValue;\n }\n \n const externalizePattern = this.getExternalizationPattern(fieldName, entityConfig);\n if (!externalizePattern) {\n return fieldValue;\n }\n \n try {\n const existingFileReference = existingRecordData?.fields?.[fieldName];\n const recordData = this.createRecordDataForExternalization(allProperties);\n \n return await this.fieldExternalizer.externalizeField(\n fieldName,\n fieldValue,\n externalizePattern,\n recordData,\n targetDir,\n existingFileReference,\n entityConfig.pull?.mergeStrategy || 'merge',\n verbose\n );\n } catch (error) {\n if (verbose) {\n console.warn(`Failed to externalize field ${fieldName}: ${error}`);\n }\n return fieldValue; // Keep original value if externalization fails\n }\n }\n\n /**\n * Gets the externalization pattern for a field\n */\n private getExternalizationPattern(fieldName: string, entityConfig: EntityConfig): string | null {\n const externalizeConfig = entityConfig.pull?.externalizeFields;\n if (!externalizeConfig) return null;\n \n if (Array.isArray(externalizeConfig)) {\n return this.getArrayExternalizationPattern(fieldName, externalizeConfig);\n } else {\n return this.getObjectExternalizationPattern(fieldName, externalizeConfig);\n }\n }\n\n /**\n * Gets externalization pattern from array configuration\n */\n private getArrayExternalizationPattern(\n fieldName: string, \n externalizeConfig: any[]\n ): string | null {\n if (externalizeConfig.length > 0 && typeof externalizeConfig[0] === 'string') {\n // Simple string array format\n if ((externalizeConfig as string[]).includes(fieldName)) {\n return `@file:{Name}.${fieldName.toLowerCase()}.md`;\n }\n } else {\n // Array of objects format\n const fieldConfig = (externalizeConfig as Array<{field: string; pattern: string}>)\n .find(config => config.field === fieldName);\n if (fieldConfig) {\n return fieldConfig.pattern;\n }\n }\n return null;\n }\n\n /**\n * Gets externalization pattern from object configuration\n */\n private getObjectExternalizationPattern(\n fieldName: string, \n externalizeConfig: Record<string, any>\n ): string | null {\n const fieldConfig = externalizeConfig[fieldName];\n if (fieldConfig) {\n const extension = fieldConfig.extension || '.md';\n return `@file:{Name}.${fieldName.toLowerCase()}${extension}`;\n }\n return null;\n }\n\n /**\n * Creates a BaseEntity-like object for externalization processing\n */\n private createRecordDataForExternalization(allProperties: Record<string, any>): BaseEntity {\n return allProperties as any as BaseEntity;\n }\n\n /**\n * Processes related entities for the record\n */\n private async processRelatedEntities(\n record: BaseEntity,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n currentDepth: number,\n ancestryPath: Set<string>,\n relatedEntities: Record<string, RecordData[]>,\n verbose?: boolean\n ): Promise<void> {\n if (!entityConfig.pull?.relatedEntities) {\n return;\n }\n \n for (const [relationKey, relationConfig] of Object.entries(entityConfig.pull.relatedEntities)) {\n try {\n const existingRelated = existingRecordData?.relatedEntities?.[relationKey] || [];\n \n const relatedRecords = await this.relatedEntityHandler.loadRelatedEntities(\n record,\n relationConfig,\n entityConfig,\n existingRelated,\n this.processRecord.bind(this), // Pass bound method reference\n currentDepth,\n ancestryPath,\n verbose\n );\n \n if (relatedRecords.length > 0) {\n relatedEntities[relationKey] = relatedRecords;\n }\n } catch (error) {\n if (verbose) {\n console.warn(`Failed to load related entities for ${relationKey}: ${error}`);\n }\n }\n }\n }\n\n /**\n * Calculates sync metadata including checksum and last modified timestamp\n */\n private async calculateSyncMetadata(\n fields: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n verbose?: boolean\n ): Promise<{ lastModified: string; checksum: string }> {\n // Determine if we should include external file content in checksum\n const hasExternalizedFields = this.hasExternalizedFields(fields, entityConfig);\n \n const checksum = hasExternalizedFields\n ? await this.syncEngine.calculateChecksumWithFileContent(fields, targetDir)\n : this.syncEngine.calculateChecksum(fields);\n \n if (verbose && hasExternalizedFields) {\n console.log(`Calculated checksum including external file content for record`);\n }\n \n // Compare with existing checksum to determine if data changed\n if (existingRecordData?.sync?.checksum === checksum) {\n // No change detected - preserve existing sync metadata\n if (verbose) {\n console.log(`No changes detected for record, preserving existing timestamp`);\n }\n return {\n lastModified: existingRecordData.sync.lastModified,\n checksum: checksum\n };\n } else {\n // Change detected - update timestamp\n if (verbose && existingRecordData?.sync?.checksum) {\n console.log(`Changes detected for record, updating timestamp`);\n }\n return {\n lastModified: new Date().toISOString(),\n checksum: checksum\n };\n }\n }\n\n /**\n * Checks if the record has externalized fields\n */\n private hasExternalizedFields(fields: Record<string, any>, entityConfig: EntityConfig): boolean {\n return !!entityConfig.pull?.externalizeFields && \n Object.values(fields).some(value => \n typeof value === 'string' && value.startsWith('@file:')\n );\n }\n\n /**\n * Convert a GUID value to @lookup syntax by looking up the human-readable value\n */\n private async convertGuidToLookup(\n guidValue: string,\n lookupConfig: { entity: string; field: string },\n verbose?: boolean\n ): Promise<string> {\n if (!guidValue || typeof guidValue !== 'string') {\n return guidValue;\n }\n\n try {\n const rv = new RunView();\n const result = await rv.RunView({\n EntityName: lookupConfig.entity,\n ExtraFilter: `ID = '${guidValue}'`,\n ResultType: 'entity_object'\n }, this.contextUser);\n\n if (result.Success && result.Results && result.Results.length > 0) {\n const targetRecord = result.Results[0];\n const lookupValue = targetRecord[lookupConfig.field];\n \n if (lookupValue != null) {\n return `@lookup:${lookupConfig.entity}.${lookupConfig.field}=${lookupValue}`;\n }\n }\n\n if (verbose) {\n console.warn(`Lookup failed for ${guidValue} in ${lookupConfig.entity}.${lookupConfig.field}`);\n }\n \n return guidValue; // Return original GUID if lookup fails\n } catch (error) {\n if (verbose) {\n console.warn(`Error during lookup conversion: ${error}`);\n }\n return guidValue;\n }\n }\n}"]}
1
+ {"version":3,"file":"RecordProcessor.js","sourceRoot":"","sources":["../../src/lib/RecordProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity, RunView, UserInfo, EntityInfo } from '@memberjunction/global';\nimport { SyncEngine, RecordData } from '../lib/sync-engine';\nimport { EntityConfig } from '../config';\nimport { JsonWriteHelper } from './json-write-helper';\nimport { EntityPropertyExtractor } from './EntityPropertyExtractor';\nimport { FieldExternalizer } from './FieldExternalizer';\nimport { RelatedEntityHandler } from './RelatedEntityHandler';\n\n/**\n * Handles the core processing of individual record data into the sync format\n */\nexport class RecordProcessor {\n private propertyExtractor: EntityPropertyExtractor;\n private fieldExternalizer: FieldExternalizer;\n private relatedEntityHandler: RelatedEntityHandler;\n\n constructor(\n private syncEngine: SyncEngine,\n private contextUser: UserInfo\n ) {\n this.propertyExtractor = new EntityPropertyExtractor();\n this.fieldExternalizer = new FieldExternalizer();\n this.relatedEntityHandler = new RelatedEntityHandler(syncEngine, contextUser);\n }\n\n /**\n * Processes a record into the standardized RecordData format\n */\n async processRecord(\n record: BaseEntity,\n primaryKey: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n verbose?: boolean,\n isNewRecord: boolean = true,\n existingRecordData?: RecordData,\n currentDepth: number = 0,\n ancestryPath: Set<string> = new Set(),\n fieldOverrides?: Record<string, any>\n ): Promise<RecordData> {\n // Extract all properties from the entity\n const allProperties = this.propertyExtractor.extractAllProperties(record, fieldOverrides);\n\n // Process fields and related entities\n const { fields, relatedEntities } = await this.processEntityData(\n allProperties,\n record,\n primaryKey,\n targetDir,\n entityConfig,\n existingRecordData,\n currentDepth,\n ancestryPath,\n verbose\n );\n\n // Calculate checksum and sync metadata\n const syncData = await this.calculateSyncMetadata(fields, targetDir, entityConfig, existingRecordData, verbose);\n\n // Build the final record data with proper ordering\n return JsonWriteHelper.createOrderedRecordData(fields, relatedEntities, primaryKey, syncData);\n }\n\n /**\n * Processes entity data into fields and related entities\n */\n private async processEntityData(\n allProperties: Record<string, any>,\n record: BaseEntity,\n primaryKey: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<{ fields: Record<string, any>; relatedEntities: Record<string, RecordData[]> }> {\n const fields: Record<string, any> = {};\n const relatedEntities: Record<string, RecordData[]> = {};\n\n // Process individual fields\n await this.processFields(allProperties, primaryKey, targetDir, entityConfig, existingRecordData, fields, verbose);\n\n // Process related entities if configured\n await this.processRelatedEntities(record, entityConfig, existingRecordData, currentDepth, ancestryPath, relatedEntities, verbose);\n\n return { fields, relatedEntities };\n }\n\n /**\n * Processes individual fields from the entity\n */\n private async processFields(\n allProperties: Record<string, any>,\n primaryKey: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n fields: Record<string, any>,\n verbose?: boolean\n ): Promise<void> {\n const entityInfo = this.syncEngine.getEntityInfo(entityConfig.entity);\n\n for (const [fieldName, fieldValue] of Object.entries(allProperties)) {\n if (this.shouldSkipField(fieldName, fieldValue, primaryKey, entityConfig, entityInfo)) {\n continue;\n }\n\n let processedValue = await this.processFieldValue(\n fieldName,\n fieldValue,\n allProperties,\n targetDir,\n entityConfig,\n existingRecordData,\n verbose\n );\n\n fields[fieldName] = processedValue;\n }\n }\n\n /**\n * Determines if a field should be skipped during processing\n */\n private shouldSkipField(\n fieldName: string,\n fieldValue: any,\n primaryKey: Record<string, any>,\n entityConfig: EntityConfig,\n entityInfo: EntityInfo | null\n ): boolean {\n // Skip primary key fields\n if (primaryKey[fieldName] !== undefined) {\n return true;\n }\n\n // Skip internal fields\n if (fieldName.startsWith('__mj_')) {\n return true;\n }\n\n // Skip excluded fields\n if (entityConfig.pull?.excludeFields?.includes(fieldName)) {\n return true;\n }\n\n // Skip virtual fields if configured\n if (this.shouldSkipVirtualField(fieldName, entityConfig, entityInfo)) {\n return true;\n }\n\n // Skip null fields if configured\n if (entityConfig.pull?.ignoreNullFields && fieldValue === null) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Checks if a virtual field should be skipped\n */\n private shouldSkipVirtualField(fieldName: string, entityConfig: EntityConfig, entityInfo: EntityInfo | null): boolean {\n if (!entityConfig.pull?.ignoreVirtualFields || !entityInfo) {\n return false;\n }\n\n const fieldInfo = entityInfo.Fields.find((f) => f.Name === fieldName);\n return fieldInfo?.IsVirtual === true;\n }\n\n /**\n * Processes a single field value through various transformations\n */\n private async processFieldValue(\n fieldName: string,\n fieldValue: any,\n allProperties: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n verbose?: boolean\n ): Promise<any> {\n let processedValue = fieldValue;\n\n // Apply lookup field conversion if configured\n processedValue = await this.applyLookupFieldConversion(fieldName, processedValue, entityConfig, verbose);\n\n // Trim string values\n processedValue = this.trimStringValue(processedValue);\n\n // Apply field externalization if configured\n processedValue = await this.applyFieldExternalization(\n fieldName,\n processedValue,\n allProperties,\n targetDir,\n entityConfig,\n existingRecordData,\n verbose\n );\n\n return processedValue;\n }\n\n /**\n * Applies lookup field conversion if configured\n */\n private async applyLookupFieldConversion(\n fieldName: string,\n fieldValue: any,\n entityConfig: EntityConfig,\n verbose?: boolean\n ): Promise<any> {\n const lookupConfig = entityConfig.pull?.lookupFields?.[fieldName];\n if (!lookupConfig || fieldValue == null) {\n return fieldValue;\n }\n\n try {\n return await this.convertGuidToLookup(String(fieldValue), lookupConfig, verbose);\n } catch (error) {\n if (verbose) {\n console.warn(`Failed to convert ${fieldName} to lookup: ${error}`);\n }\n return fieldValue; // Keep original value if lookup fails\n }\n }\n\n /**\n * Trims string values to remove whitespace\n */\n private trimStringValue(value: any): any {\n return typeof value === 'string' ? value.trim() : value;\n }\n\n /**\n * Applies field externalization if configured\n */\n private async applyFieldExternalization(\n fieldName: string,\n fieldValue: any,\n allProperties: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n verbose?: boolean\n ): Promise<any> {\n if (!entityConfig.pull?.externalizeFields || fieldValue == null) {\n return fieldValue;\n }\n\n const externalizePattern = this.getExternalizationPattern(fieldName, entityConfig);\n if (!externalizePattern) {\n return fieldValue;\n }\n\n try {\n const existingFileReference = existingRecordData?.fields?.[fieldName];\n const recordData = this.createRecordDataForExternalization(allProperties);\n\n return await this.fieldExternalizer.externalizeField(\n fieldName,\n fieldValue,\n externalizePattern,\n recordData,\n targetDir,\n existingFileReference,\n entityConfig.pull?.mergeStrategy || 'merge',\n verbose\n );\n } catch (error) {\n if (verbose) {\n console.warn(`Failed to externalize field ${fieldName}: ${error}`);\n }\n return fieldValue; // Keep original value if externalization fails\n }\n }\n\n /**\n * Gets the externalization pattern for a field\n */\n private getExternalizationPattern(fieldName: string, entityConfig: EntityConfig): string | null {\n const externalizeConfig = entityConfig.pull?.externalizeFields;\n if (!externalizeConfig) return null;\n\n if (Array.isArray(externalizeConfig)) {\n return this.getArrayExternalizationPattern(fieldName, externalizeConfig);\n } else {\n return this.getObjectExternalizationPattern(fieldName, externalizeConfig);\n }\n }\n\n /**\n * Gets externalization pattern from array configuration\n */\n private getArrayExternalizationPattern(fieldName: string, externalizeConfig: any[]): string | null {\n if (externalizeConfig.length > 0 && typeof externalizeConfig[0] === 'string') {\n // Simple string array format\n if ((externalizeConfig as string[]).includes(fieldName)) {\n return `@file:{Name}.${fieldName.toLowerCase()}.md`;\n }\n } else {\n // Array of objects format\n const fieldConfig = (externalizeConfig as Array<{ field: string; pattern: string }>).find((config) => config.field === fieldName);\n if (fieldConfig) {\n return fieldConfig.pattern;\n }\n }\n return null;\n }\n\n /**\n * Gets externalization pattern from object configuration\n */\n private getObjectExternalizationPattern(fieldName: string, externalizeConfig: Record<string, any>): string | null {\n const fieldConfig = externalizeConfig[fieldName];\n if (fieldConfig) {\n const extension = fieldConfig.extension || '.md';\n return `@file:{Name}.${fieldName.toLowerCase()}${extension}`;\n }\n return null;\n }\n\n /**\n * Creates a BaseEntity-like object for externalization processing\n */\n private createRecordDataForExternalization(allProperties: Record<string, any>): BaseEntity {\n return allProperties as any as BaseEntity;\n }\n\n /**\n * Processes related entities for the record\n */\n private async processRelatedEntities(\n record: BaseEntity,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n currentDepth: number,\n ancestryPath: Set<string>,\n relatedEntities: Record<string, RecordData[]>,\n verbose?: boolean\n ): Promise<void> {\n if (!entityConfig.pull?.relatedEntities) {\n return;\n }\n\n for (const [relationKey, relationConfig] of Object.entries(entityConfig.pull.relatedEntities)) {\n try {\n const existingRelated = existingRecordData?.relatedEntities?.[relationKey] || [];\n\n const relatedRecords = await this.relatedEntityHandler.loadRelatedEntities(\n record,\n relationConfig,\n entityConfig,\n existingRelated,\n this.processRecord.bind(this), // Pass bound method reference\n currentDepth,\n ancestryPath,\n verbose\n );\n\n if (relatedRecords.length > 0) {\n relatedEntities[relationKey] = relatedRecords;\n }\n } catch (error) {\n if (verbose) {\n console.warn(`Failed to load related entities for ${relationKey}: ${error}`);\n }\n }\n }\n }\n\n /**\n * Calculates sync metadata including checksum and last modified timestamp\n */\n private async calculateSyncMetadata(\n fields: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n existingRecordData: RecordData | undefined,\n verbose?: boolean\n ): Promise<{ lastModified: string; checksum: string }> {\n // Determine if we should include external file content in checksum\n const hasExternalizedFields = this.hasExternalizedFields(fields, entityConfig);\n\n const checksum = hasExternalizedFields\n ? await this.syncEngine.calculateChecksumWithFileContent(fields, targetDir)\n : this.syncEngine.calculateChecksum(fields);\n\n if (verbose && hasExternalizedFields) {\n console.log(`Calculated checksum including external file content for record`);\n }\n\n // Compare with existing checksum to determine if data changed\n if (existingRecordData?.sync?.checksum === checksum) {\n // No change detected - preserve existing sync metadata\n if (verbose) {\n console.log(`No changes detected for record, preserving existing timestamp`);\n }\n return {\n lastModified: existingRecordData.sync.lastModified,\n checksum: checksum,\n };\n } else {\n // Change detected - update timestamp\n if (verbose && existingRecordData?.sync?.checksum) {\n console.log(`Changes detected for record, updating timestamp`);\n }\n return {\n lastModified: new Date().toISOString(),\n checksum: checksum,\n };\n }\n }\n\n /**\n * Checks if the record has externalized fields\n */\n private hasExternalizedFields(fields: Record<string, any>, entityConfig: EntityConfig): boolean {\n return (\n !!entityConfig.pull?.externalizeFields &&\n Object.values(fields).some((value) => typeof value === 'string' && value.startsWith('@file:'))\n );\n }\n\n /**\n * Convert a GUID value to @lookup syntax by looking up the human-readable value\n */\n private async convertGuidToLookup(\n guidValue: string,\n lookupConfig: { entity: string; field: string },\n verbose?: boolean\n ): Promise<string> {\n if (!guidValue || typeof guidValue !== 'string') {\n return guidValue;\n }\n\n try {\n const rv = new RunView();\n const result = await rv.RunView(\n {\n EntityName: lookupConfig.entity,\n ExtraFilter: `ID = '${guidValue}'`,\n ResultType: 'entity_object',\n },\n this.contextUser\n );\n\n if (result.Success && result.Results && result.Results.length > 0) {\n const targetRecord = result.Results[0];\n const lookupValue = targetRecord[lookupConfig.field];\n\n if (lookupValue != null) {\n return `@lookup:${lookupConfig.entity}.${lookupConfig.field}=${lookupValue}`;\n }\n }\n\n if (verbose) {\n console.warn(`Lookup failed for ${guidValue} in ${lookupConfig.entity}.${lookupConfig.field}`);\n }\n\n return guidValue; // Return original GUID if lookup fails\n } catch (error) {\n if (verbose) {\n console.warn(`Error during lookup conversion: ${error}`);\n }\n return guidValue;\n }\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { BaseEntity, UserInfo } from '@memberjunction/core';
1
+ import { BaseEntity, UserInfo } from '@memberjunction/global';
2
2
  import { SyncEngine, RecordData } from '../lib/sync-engine';
3
3
  import { RelatedEntityConfig, EntityConfig } from '../config';
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RelatedEntityHandler = void 0;
4
- const core_1 = require("@memberjunction/core");
4
+ const global_1 = require("@memberjunction/global");
5
5
  /**
6
6
  * Handles loading and processing of related entities for records
7
7
  */
@@ -42,11 +42,11 @@ class RelatedEntityHandler {
42
42
  if (verbose) {
43
43
  console.log(`Loading related entities: ${relationConfig.entity} with filter: ${filter}`);
44
44
  }
45
- const rv = new core_1.RunView();
45
+ const rv = new global_1.RunView();
46
46
  const result = await rv.RunView({
47
47
  EntityName: relationConfig.entity,
48
48
  ExtraFilter: filter,
49
- ResultType: 'entity_object'
49
+ ResultType: 'entity_object',
50
50
  }, this.contextUser);
51
51
  if (!result.Success) {
52
52
  this.logWarning(`Failed to load related entities ${relationConfig.entity}: ${result.ErrorMessage}`, verbose);
@@ -79,8 +79,8 @@ class RelatedEntityHandler {
79
79
  externalizeFields: relationConfig.externalizeFields || [],
80
80
  relatedEntities: relationConfig.relatedEntities || {},
81
81
  ignoreVirtualFields: parentEntityConfig.pull?.ignoreVirtualFields || false,
82
- ignoreNullFields: parentEntityConfig.pull?.ignoreNullFields || false
83
- }
82
+ ignoreNullFields: parentEntityConfig.pull?.ignoreNullFields || false,
83
+ },
84
84
  };
85
85
  }
86
86
  /**