@memberjunction/metadata-sync 2.111.0 → 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.
- package/README.md +50 -50
- package/dist/lib/EntityPropertyExtractor.d.ts +1 -1
- package/dist/lib/EntityPropertyExtractor.js +18 -4
- package/dist/lib/EntityPropertyExtractor.js.map +1 -1
- package/dist/lib/FieldExternalizer.d.ts +1 -1
- package/dist/lib/FieldExternalizer.js +2 -2
- package/dist/lib/FieldExternalizer.js.map +1 -1
- package/dist/lib/RecordProcessor.d.ts +1 -1
- package/dist/lib/RecordProcessor.js +9 -10
- package/dist/lib/RecordProcessor.js.map +1 -1
- package/dist/lib/RelatedEntityHandler.d.ts +1 -1
- package/dist/lib/RelatedEntityHandler.js +5 -5
- package/dist/lib/RelatedEntityHandler.js.map +1 -1
- package/dist/lib/provider-utils.d.ts +1 -1
- package/dist/lib/provider-utils.js +16 -19
- package/dist/lib/provider-utils.js.map +1 -1
- package/dist/lib/record-dependency-analyzer.js +21 -27
- package/dist/lib/record-dependency-analyzer.js.map +1 -1
- package/dist/lib/singleton-manager.d.ts +1 -1
- package/dist/lib/singleton-manager.js.map +1 -1
- package/dist/lib/sync-engine.d.ts +1 -1
- package/dist/lib/sync-engine.js +22 -18
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/services/PullService.d.ts +1 -1
- package/dist/services/PullService.js +22 -18
- package/dist/services/PullService.js.map +1 -1
- package/dist/services/PushService.d.ts +1 -1
- package/dist/services/PushService.js +15 -21
- package/dist/services/PushService.js.map +1 -1
- package/dist/services/ValidationService.js +5 -5
- package/dist/services/ValidationService.js.map +1 -1
- package/dist/services/WatchService.d.ts +1 -1
- package/dist/services/WatchService.js +13 -14
- package/dist/services/WatchService.js.map +1 -1
- package/package.json +7 -8
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/
|
|
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
|
|
478
|
-
|
|
479
|
-
| `No entity directories found`
|
|
480
|
-
| `Field 'X' does not exist on entity 'Y'` | Using non-existent field
|
|
481
|
-
| `User ID cannot be null`
|
|
482
|
-
| `Processing 0 records`
|
|
483
|
-
| Failed validation
|
|
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
|
|
1514
|
-
|
|
1515
|
-
| `enabled`
|
|
1516
|
-
| `outputDirectory`
|
|
1517
|
-
| `formatAsMigration` | boolean
|
|
1518
|
-
| `filterPatterns`
|
|
1519
|
-
| `filterType`
|
|
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
|
|
1637
|
-
|
|
1638
|
-
| `enabled`
|
|
1639
|
-
| `allowedRoles`
|
|
1640
|
-
| `allowUsersWithoutRoles` | boolean
|
|
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
|
|
1868
|
-
|
|
1869
|
-
| `recursive` | boolean | false
|
|
1870
|
-
| `maxDepth`
|
|
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
|
|
1938
|
-
|
|
1939
|
-
| `filePattern`
|
|
1940
|
-
| `createNewFileIfNotFound`
|
|
1941
|
-
| `newFileName`
|
|
1942
|
-
| `appendRecordsToExistingFile` | boolean
|
|
1943
|
-
| `updateExistingRecords`
|
|
1944
|
-
| `preserveFields`
|
|
1945
|
-
| `mergeStrategy`
|
|
1946
|
-
| `backupBeforeUpdate`
|
|
1947
|
-
| `backupDirectory`
|
|
1948
|
-
| `filter`
|
|
1949
|
-
| `externalizeFields`
|
|
1950
|
-
| `excludeFields`
|
|
1951
|
-
| `lookupFields`
|
|
1952
|
-
| `relatedEntities`
|
|
1953
|
-
| `ignoreNullFields`
|
|
1954
|
-
| `ignoreVirtualFields`
|
|
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
|
|
2463
|
-
|
|
2464
|
-
| `Field "X" does not exist` | Typo or wrong entity
|
|
2465
|
-
| `Entity "X" not found`
|
|
2466
|
-
| `File not found`
|
|
2467
|
-
| `Lookup not found`
|
|
2468
|
-
| `Circular dependency`
|
|
2469
|
-
| `Required field missing`
|
|
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/
|
|
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',
|
|
159
|
-
'
|
|
160
|
-
'
|
|
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"]}
|
|
@@ -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":";;;;;;AAAA,wDAA0B;AAC1B,gDAAwB;AAGxB;;GAEG;AACH,MAAa,iBAAiB;IAC5B;;OAEG;IACH,KAAK,CAAC,gBAAgB,CACpB,SAAiB,EACjB,UAAe,EACf,OAAe,EACf,UAAsB,EACtB,SAAiB,EACjB,qBAA8B,EAC9B,gBAAwB,OAAO,EAC/B,OAAiB;QAEjB,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAC7D,OAAO,EACP,UAAU,EACV,SAAS,EACT,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,OAAO,CACR,CAAC;QAEF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QAErF,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,iBAAiB,CAAC,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAC9E,CAAC;aAAM,IAAI,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,iBAAiB,aAAa,4BAA4B,CAAC,CAAC;QAC1E,CAAC;QAED,OAAO,aAAa,CAAC;IACvB,CAAC;IAED;;OAEG;IACK,iBAAiB,CACvB,OAAe,EACf,UAAsB,EACtB,SAAiB,EACjB,qBAA8B,EAC9B,gBAAwB,OAAO,EAC/B,YAAoB,EAAE,EACtB,OAAiB;QAEjB,IAAI,IAAI,CAAC,0BAA0B,CAAC,qBAAqB,EAAE,aAAa,CAAC,EAAE,CAAC;YAC1E,OAAO,IAAI,CAAC,wBAAwB,CAAC,qBAAsB,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QACnF,CAAC;QAED,OAAO,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACzF,CAAC;IAED;;OAEG;IACK,0BAA0B,CAAC,qBAA8B,EAAE,gBAAwB,OAAO;QAChG,OAAO,aAAa,KAAK,OAAO;YACzB,CAAC,CAAC,qBAAqB;YACvB,OAAO,qBAAqB,KAAK,QAAQ;YACzC,qBAAqB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACK,wBAAwB,CAC9B,qBAA6B,EAC7B,SAAiB,EACjB,OAAiB;QAEjB,MAAM,YAAY,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB;QAChF,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QAE5D,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,iCAAiC,aAAa,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,qBAAqB,EAAE,CAAC;IACjE,CAAC;IAED;;OAEG;IACK,sBAAsB,CAC5B,OAAe,EACf,UAAsB,EACtB,SAAiB,EACjB,SAAiB,EACjB,OAAiB;QAEjB,MAAM,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QAC7E,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;QAC7D,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QAC5D,MAAM,aAAa,GAAG,SAAS,YAAY,EAAE,CAAC;QAE9C,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,+BAA+B,aAAa,EAAE,CAAC,CAAC;QAC9D,CAAC;QAED,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;IAC1C,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,OAAe,EAAE,UAAsB,EAAE,SAAiB;QAC/E,IAAI,gBAAgB,GAAG,OAAO,CAAC;QAE/B,8BAA8B;QAC9B,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,EAAG,UAAkB,CAAC,IAAI,CAAC,CAAC;QAC/F,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,EAAE,IAAI,EAAG,UAAkB,CAAC,EAAE,CAAC,CAAC;QAC3F,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;QAErF,uCAAuC;QACvC,gBAAgB,GAAG,IAAI,CAAC,wBAAwB,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;QAE/E,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED;;OAEG;IACK,kBAAkB,CAAC,OAAe,EAAE,WAAmB,EAAE,KAAU;QACzE,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;YAClB,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/D,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,MAAM,WAAW,KAAK,EAAE,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,wBAAwB,CAAC,OAAe,EAAE,UAAsB;QACtE,IAAI,gBAAgB,GAAG,OAAO,CAAC;QAE/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAiB,CAAC,EAAE,CAAC;YAC7D,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC/D,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC;YAC/F,CAAC;QACH,CAAC;QAED,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED;;OAEG;IACK,gBAAgB,CAAC,OAAe;QACtC,OAAO,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACvE,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,eAAe,CAAC,aAAqB,EAAE,UAAe,EAAE,SAAiB;QACrF,IAAI,CAAC,CAAC,MAAM,kBAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC,CAAC,mCAAmC;QAClD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,eAAe,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YACjE,MAAM,cAAc,GAAG,IAAI,CAAC,wBAAwB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YAE5E,OAAO,eAAe,KAAK,cAAc,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,CAAC,4CAA4C;QAC3D,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAC7B,aAAqB,EACrB,UAAe,EACf,SAAiB,EACjB,OAAiB;QAEjB,8BAA8B;QAC9B,MAAM,kBAAE,CAAC,SAAS,CAAC,cAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;QAEhD,oCAAoC;QACpC,MAAM,cAAc,GAAG,IAAI,CAAC,wBAAwB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC5E,MAAM,kBAAE,CAAC,SAAS,CAAC,aAAa,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QAE1D,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,4BAA4B,SAAS,OAAO,aAAa,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED;;OAEG;IACK,wBAAwB,CAAC,UAAe,EAAE,SAAiB;QACjE,IAAI,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAExC,uDAAuD;QACvD,IAAI,IAAI,CAAC,uBAAuB,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5C,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;gBAC1C,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,4BAA4B;YAC9B,CAAC;QACH,CAAC;QAED,OAAO,cAAc,CAAC;IACxB,CAAC;IAED;;OAEG;IACK,uBAAuB,CAAC,SAAiB;QAC/C,MAAM,cAAc,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QAC/C,OAAO,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC/E,CAAC;IAED;;OAEG;IACK,mBAAmB,CAAC,KAAa;QACvC,OAAO,KAAK;aACT,WAAW,EAAE;aACb,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,8BAA8B;aACnD,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,oDAAoD;aAChF,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,8CAA8C;aACnE,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,kCAAkC;IAChE,CAAC;CACF;AAxOD,8CAwOC","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,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RecordProcessor = void 0;
|
|
4
|
-
const
|
|
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
|
|
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":";;;AAAA,+CAAiF;AAGjF,2DAAsD;AACtD,uEAAoE;AACpE,2DAAwD;AACxD,iEAA8D;AAE9D;;GAEG;AACH,MAAa,eAAe;IAMhB;IACA;IANF,iBAAiB,CAA0B;IAC3C,iBAAiB,CAAoB;IACrC,oBAAoB,CAAuB;IAEnD,YACU,UAAsB,EACtB,WAAqB;QADrB,eAAU,GAAV,UAAU,CAAY;QACtB,gBAAW,GAAX,WAAW,CAAU;QAE7B,IAAI,CAAC,iBAAiB,GAAG,IAAI,iDAAuB,EAAE,CAAC;QACvD,IAAI,CAAC,iBAAiB,GAAG,IAAI,qCAAiB,EAAE,CAAC;QACjD,IAAI,CAAC,oBAAoB,GAAG,IAAI,2CAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAChF,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CACjB,MAAkB,EAClB,UAA+B,EAC/B,SAAiB,EACjB,YAA0B,EAC1B,OAAiB,EACjB,cAAuB,IAAI,EAC3B,kBAA+B,EAC/B,eAAuB,CAAC,EACxB,eAA4B,IAAI,GAAG,EAAE,EACrC,cAAoC;QAEpC,yCAAyC;QACzC,MAAM,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QAE1F,sCAAsC;QACtC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAC9D,aAAa,EACb,MAAM,EACN,UAAU,EACV,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,OAAO,CACR,CAAC;QAEF,uCAAuC;QACvC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAC/C,MAAM,EACN,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,OAAO,CACR,CAAC;QAEF,mDAAmD;QACnD,OAAO,mCAAe,CAAC,uBAAuB,CAC5C,MAAM,EACN,eAAe,EACf,UAAU,EACV,QAAQ,CACT,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAC7B,aAAkC,EAClC,MAAkB,EAClB,UAA+B,EAC/B,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,YAAoB,EACpB,YAAyB,EACzB,OAAiB;QAEjB,MAAM,MAAM,GAAwB,EAAE,CAAC;QACvC,MAAM,eAAe,GAAiC,EAAE,CAAC;QAEzD,4BAA4B;QAC5B,MAAM,IAAI,CAAC,aAAa,CACtB,aAAa,EACb,UAAU,EACV,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,MAAM,EACN,OAAO,CACR,CAAC;QAEF,yCAAyC;QACzC,MAAM,IAAI,CAAC,sBAAsB,CAC/B,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,OAAO,CACR,CAAC;QAEF,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACrC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa,CACzB,aAAkC,EAClC,UAA+B,EAC/B,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,MAA2B,EAC3B,OAAiB;QAEjB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAEtE,KAAK,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;YACpE,IAAI,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,CAAC;gBACtF,SAAS;YACX,CAAC;YAED,IAAI,cAAc,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAC/C,SAAS,EACT,UAAU,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,OAAO,CACR,CAAC;YAEF,MAAM,CAAC,SAAS,CAAC,GAAG,cAAc,CAAC;QACrC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,eAAe,CACrB,SAAiB,EACjB,UAAe,EACf,UAA+B,EAC/B,YAA0B,EAC1B,UAA6B;QAE7B,0BAA0B;QAC1B,IAAI,UAAU,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;YACxC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,uBAAuB;QACvB,IAAI,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,uBAAuB;QACvB,IAAI,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,oCAAoC;QACpC,IAAI,IAAI,CAAC,sBAAsB,CAAC,SAAS,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,iCAAiC;QACjC,IAAI,YAAY,CAAC,IAAI,EAAE,gBAAgB,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACK,sBAAsB,CAC5B,SAAiB,EACjB,YAA0B,EAC1B,UAA6B;QAE7B,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,mBAAmB,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QACpE,OAAO,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IACvC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAC7B,SAAiB,EACjB,UAAe,EACf,aAAkC,EAClC,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,OAAiB;QAEjB,IAAI,cAAc,GAAG,UAAU,CAAC;QAEhC,8CAA8C;QAC9C,cAAc,GAAG,MAAM,IAAI,CAAC,0BAA0B,CACpD,SAAS,EACT,cAAc,EACd,YAAY,EACZ,OAAO,CACR,CAAC;QAEF,qBAAqB;QACrB,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;QAEtD,4CAA4C;QAC5C,cAAc,GAAG,MAAM,IAAI,CAAC,yBAAyB,CACnD,SAAS,EACT,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,OAAO,CACR,CAAC;QAEF,OAAO,cAAc,CAAC;IACxB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,0BAA0B,CACtC,SAAiB,EACjB,UAAe,EACf,YAA0B,EAC1B,OAAiB;QAEjB,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC;QAClE,IAAI,CAAC,YAAY,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACxC,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,qBAAqB,SAAS,eAAe,KAAK,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,UAAU,CAAC,CAAC,sCAAsC;QAC3D,CAAC;IACH,CAAC;IAED;;OAEG;IACK,eAAe,CAAC,KAAU;QAChC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1D,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,yBAAyB,CACrC,SAAiB,EACjB,UAAe,EACf,aAAkC,EAClC,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,OAAiB;QAEjB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,iBAAiB,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YAChE,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,yBAAyB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QACnF,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,qBAAqB,GAAG,kBAAkB,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC;YACtE,MAAM,UAAU,GAAG,IAAI,CAAC,kCAAkC,CAAC,aAAa,CAAC,CAAC;YAE1E,OAAO,MAAM,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAClD,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,qBAAqB,EACrB,YAAY,CAAC,IAAI,EAAE,aAAa,IAAI,OAAO,EAC3C,OAAO,CACR,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,+BAA+B,SAAS,KAAK,KAAK,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,UAAU,CAAC,CAAC,+CAA+C;QACpE,CAAC;IACH,CAAC;IAED;;OAEG;IACK,yBAAyB,CAAC,SAAiB,EAAE,YAA0B;QAC7E,MAAM,iBAAiB,GAAG,YAAY,CAAC,IAAI,EAAE,iBAAiB,CAAC;QAC/D,IAAI,CAAC,iBAAiB;YAAE,OAAO,IAAI,CAAC;QAEpC,IAAI,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC3E,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC,+BAA+B,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAED;;OAEG;IACK,8BAA8B,CACpC,SAAiB,EACjB,iBAAwB;QAExB,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,iBAAiB,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC7E,6BAA6B;YAC7B,IAAK,iBAA8B,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACxD,OAAO,gBAAgB,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC;YACtD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,0BAA0B;YAC1B,MAAM,WAAW,GAAI,iBAA6D;iBAC/E,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;YAC9C,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,WAAW,CAAC,OAAO,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,+BAA+B,CACrC,SAAiB,EACjB,iBAAsC;QAEtC,MAAM,WAAW,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,WAAW,CAAC,SAAS,IAAI,KAAK,CAAC;YACjD,OAAO,gBAAgB,SAAS,CAAC,WAAW,EAAE,GAAG,SAAS,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,kCAAkC,CAAC,aAAkC;QAC3E,OAAO,aAAkC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,sBAAsB,CAClC,MAAkB,EAClB,YAA0B,EAC1B,kBAA0C,EAC1C,YAAoB,EACpB,YAAyB,EACzB,eAA6C,EAC7C,OAAiB;QAEjB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC;YACxC,OAAO;QACT,CAAC;QAED,KAAK,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YAC9F,IAAI,CAAC;gBACH,MAAM,eAAe,GAAG,kBAAkB,EAAE,eAAe,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;gBAEjF,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,mBAAmB,CACxE,MAAM,EACN,cAAc,EACd,YAAY,EACZ,eAAe,EACf,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,8BAA8B;gBAC7D,YAAY,EACZ,YAAY,EACZ,OAAO,CACR,CAAC;gBAEF,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,eAAe,CAAC,WAAW,CAAC,GAAG,cAAc,CAAC;gBAChD,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,IAAI,CAAC,uCAAuC,WAAW,KAAK,KAAK,EAAE,CAAC,CAAC;gBAC/E,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,qBAAqB,CACjC,MAA2B,EAC3B,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,OAAiB;QAEjB,mEAAmE;QACnE,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAE/E,MAAM,QAAQ,GAAG,qBAAqB;YACpC,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,gCAAgC,CAAC,MAAM,EAAE,SAAS,CAAC;YAC3E,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAE9C,IAAI,OAAO,IAAI,qBAAqB,EAAE,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;QAChF,CAAC;QAED,8DAA8D;QAC9D,IAAI,kBAAkB,EAAE,IAAI,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACpD,uDAAuD;YACvD,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;YAC/E,CAAC;YACD,OAAO;gBACL,YAAY,EAAE,kBAAkB,CAAC,IAAI,CAAC,YAAY;gBAClD,QAAQ,EAAE,QAAQ;aACnB,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,qCAAqC;YACrC,IAAI,OAAO,IAAI,kBAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAClD,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;YACjE,CAAC;YACD,OAAO;gBACL,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACtC,QAAQ,EAAE,QAAQ;aACnB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,MAA2B,EAAE,YAA0B;QACnF,OAAO,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,iBAAiB;YACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CACjC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CACxD,CAAC;IACX,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,mBAAmB,CAC/B,SAAiB,EACjB,YAA+C,EAC/C,OAAiB;QAEjB,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAChD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,cAAO,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;gBAC9B,UAAU,EAAE,YAAY,CAAC,MAAM;gBAC/B,WAAW,EAAE,SAAS,SAAS,GAAG;gBAClC,UAAU,EAAE,eAAe;aAC5B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YAErB,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClE,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACvC,MAAM,WAAW,GAAG,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;gBAErD,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;oBACxB,OAAO,WAAW,YAAY,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,IAAI,WAAW,EAAE,CAAC;gBAC/E,CAAC;YACH,CAAC;YAED,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,qBAAqB,SAAS,OAAO,YAAY,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;YACjG,CAAC;YAED,OAAO,SAAS,CAAC,CAAC,uCAAuC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,mCAAmC,KAAK,EAAE,CAAC,CAAC;YAC3D,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;CACF;AApfD,0CAofC","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":";;;AAAA,mDAAmF;AAGnF,2DAAsD;AACtD,uEAAoE;AACpE,2DAAwD;AACxD,iEAA8D;AAE9D;;GAEG;AACH,MAAa,eAAe;IAMhB;IACA;IANF,iBAAiB,CAA0B;IAC3C,iBAAiB,CAAoB;IACrC,oBAAoB,CAAuB;IAEnD,YACU,UAAsB,EACtB,WAAqB;QADrB,eAAU,GAAV,UAAU,CAAY;QACtB,gBAAW,GAAX,WAAW,CAAU;QAE7B,IAAI,CAAC,iBAAiB,GAAG,IAAI,iDAAuB,EAAE,CAAC;QACvD,IAAI,CAAC,iBAAiB,GAAG,IAAI,qCAAiB,EAAE,CAAC;QACjD,IAAI,CAAC,oBAAoB,GAAG,IAAI,2CAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAChF,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CACjB,MAAkB,EAClB,UAA+B,EAC/B,SAAiB,EACjB,YAA0B,EAC1B,OAAiB,EACjB,cAAuB,IAAI,EAC3B,kBAA+B,EAC/B,eAAuB,CAAC,EACxB,eAA4B,IAAI,GAAG,EAAE,EACrC,cAAoC;QAEpC,yCAAyC;QACzC,MAAM,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QAE1F,sCAAsC;QACtC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAC9D,aAAa,EACb,MAAM,EACN,UAAU,EACV,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,OAAO,CACR,CAAC;QAEF,uCAAuC;QACvC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC;QAEhH,mDAAmD;QACnD,OAAO,mCAAe,CAAC,uBAAuB,CAAC,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IAChG,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAC7B,aAAkC,EAClC,MAAkB,EAClB,UAA+B,EAC/B,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,YAAoB,EACpB,YAAyB,EACzB,OAAiB;QAEjB,MAAM,MAAM,GAAwB,EAAE,CAAC;QACvC,MAAM,eAAe,GAAiC,EAAE,CAAC;QAEzD,4BAA4B;QAC5B,MAAM,IAAI,CAAC,aAAa,CAAC,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAElH,yCAAyC;QACzC,MAAM,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;QAElI,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACrC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa,CACzB,aAAkC,EAClC,UAA+B,EAC/B,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,MAA2B,EAC3B,OAAiB;QAEjB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAEtE,KAAK,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;YACpE,IAAI,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,CAAC;gBACtF,SAAS;YACX,CAAC;YAED,IAAI,cAAc,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAC/C,SAAS,EACT,UAAU,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,OAAO,CACR,CAAC;YAEF,MAAM,CAAC,SAAS,CAAC,GAAG,cAAc,CAAC;QACrC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,eAAe,CACrB,SAAiB,EACjB,UAAe,EACf,UAA+B,EAC/B,YAA0B,EAC1B,UAA6B;QAE7B,0BAA0B;QAC1B,IAAI,UAAU,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;YACxC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,uBAAuB;QACvB,IAAI,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,uBAAuB;QACvB,IAAI,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,oCAAoC;QACpC,IAAI,IAAI,CAAC,sBAAsB,CAAC,SAAS,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,iCAAiC;QACjC,IAAI,YAAY,CAAC,IAAI,EAAE,gBAAgB,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACK,sBAAsB,CAAC,SAAiB,EAAE,YAA0B,EAAE,UAA6B;QACzG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,mBAAmB,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QACtE,OAAO,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IACvC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAC7B,SAAiB,EACjB,UAAe,EACf,aAAkC,EAClC,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,OAAiB;QAEjB,IAAI,cAAc,GAAG,UAAU,CAAC;QAEhC,8CAA8C;QAC9C,cAAc,GAAG,MAAM,IAAI,CAAC,0BAA0B,CAAC,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QAEzG,qBAAqB;QACrB,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;QAEtD,4CAA4C;QAC5C,cAAc,GAAG,MAAM,IAAI,CAAC,yBAAyB,CACnD,SAAS,EACT,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,OAAO,CACR,CAAC;QAEF,OAAO,cAAc,CAAC;IACxB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,0BAA0B,CACtC,SAAiB,EACjB,UAAe,EACf,YAA0B,EAC1B,OAAiB;QAEjB,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC;QAClE,IAAI,CAAC,YAAY,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACxC,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,qBAAqB,SAAS,eAAe,KAAK,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,UAAU,CAAC,CAAC,sCAAsC;QAC3D,CAAC;IACH,CAAC;IAED;;OAEG;IACK,eAAe,CAAC,KAAU;QAChC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1D,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,yBAAyB,CACrC,SAAiB,EACjB,UAAe,EACf,aAAkC,EAClC,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,OAAiB;QAEjB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,iBAAiB,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YAChE,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,yBAAyB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QACnF,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,qBAAqB,GAAG,kBAAkB,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC;YACtE,MAAM,UAAU,GAAG,IAAI,CAAC,kCAAkC,CAAC,aAAa,CAAC,CAAC;YAE1E,OAAO,MAAM,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAClD,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,qBAAqB,EACrB,YAAY,CAAC,IAAI,EAAE,aAAa,IAAI,OAAO,EAC3C,OAAO,CACR,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,+BAA+B,SAAS,KAAK,KAAK,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,UAAU,CAAC,CAAC,+CAA+C;QACpE,CAAC;IACH,CAAC;IAED;;OAEG;IACK,yBAAyB,CAAC,SAAiB,EAAE,YAA0B;QAC7E,MAAM,iBAAiB,GAAG,YAAY,CAAC,IAAI,EAAE,iBAAiB,CAAC;QAC/D,IAAI,CAAC,iBAAiB;YAAE,OAAO,IAAI,CAAC;QAEpC,IAAI,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC3E,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC,+BAA+B,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAED;;OAEG;IACK,8BAA8B,CAAC,SAAiB,EAAE,iBAAwB;QAChF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,iBAAiB,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC7E,6BAA6B;YAC7B,IAAK,iBAA8B,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACxD,OAAO,gBAAgB,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC;YACtD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,0BAA0B;YAC1B,MAAM,WAAW,GAAI,iBAA+D,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;YAClI,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,WAAW,CAAC,OAAO,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,+BAA+B,CAAC,SAAiB,EAAE,iBAAsC;QAC/F,MAAM,WAAW,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,WAAW,CAAC,SAAS,IAAI,KAAK,CAAC;YACjD,OAAO,gBAAgB,SAAS,CAAC,WAAW,EAAE,GAAG,SAAS,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,kCAAkC,CAAC,aAAkC;QAC3E,OAAO,aAAkC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,sBAAsB,CAClC,MAAkB,EAClB,YAA0B,EAC1B,kBAA0C,EAC1C,YAAoB,EACpB,YAAyB,EACzB,eAA6C,EAC7C,OAAiB;QAEjB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC;YACxC,OAAO;QACT,CAAC;QAED,KAAK,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YAC9F,IAAI,CAAC;gBACH,MAAM,eAAe,GAAG,kBAAkB,EAAE,eAAe,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;gBAEjF,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,mBAAmB,CACxE,MAAM,EACN,cAAc,EACd,YAAY,EACZ,eAAe,EACf,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,8BAA8B;gBAC7D,YAAY,EACZ,YAAY,EACZ,OAAO,CACR,CAAC;gBAEF,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,eAAe,CAAC,WAAW,CAAC,GAAG,cAAc,CAAC;gBAChD,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,IAAI,CAAC,uCAAuC,WAAW,KAAK,KAAK,EAAE,CAAC,CAAC;gBAC/E,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,qBAAqB,CACjC,MAA2B,EAC3B,SAAiB,EACjB,YAA0B,EAC1B,kBAA0C,EAC1C,OAAiB;QAEjB,mEAAmE;QACnE,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAE/E,MAAM,QAAQ,GAAG,qBAAqB;YACpC,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,gCAAgC,CAAC,MAAM,EAAE,SAAS,CAAC;YAC3E,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAE9C,IAAI,OAAO,IAAI,qBAAqB,EAAE,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;QAChF,CAAC;QAED,8DAA8D;QAC9D,IAAI,kBAAkB,EAAE,IAAI,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACpD,uDAAuD;YACvD,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;YAC/E,CAAC;YACD,OAAO;gBACL,YAAY,EAAE,kBAAkB,CAAC,IAAI,CAAC,YAAY;gBAClD,QAAQ,EAAE,QAAQ;aACnB,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,qCAAqC;YACrC,IAAI,OAAO,IAAI,kBAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAClD,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;YACjE,CAAC;YACD,OAAO;gBACL,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACtC,QAAQ,EAAE,QAAQ;aACnB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,MAA2B,EAAE,YAA0B;QACnF,OAAO,CACL,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,iBAAiB;YACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAC/F,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,mBAAmB,CAC/B,SAAiB,EACjB,YAA+C,EAC/C,OAAiB;QAEjB,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAChD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,gBAAO,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAC7B;gBACE,UAAU,EAAE,YAAY,CAAC,MAAM;gBAC/B,WAAW,EAAE,SAAS,SAAS,GAAG;gBAClC,UAAU,EAAE,eAAe;aAC5B,EACD,IAAI,CAAC,WAAW,CACjB,CAAC;YAEF,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClE,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACvC,MAAM,WAAW,GAAG,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;gBAErD,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;oBACxB,OAAO,WAAW,YAAY,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,IAAI,WAAW,EAAE,CAAC;gBAC/E,CAAC;YACH,CAAC;YAED,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,qBAAqB,SAAS,OAAO,YAAY,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;YACjG,CAAC;YAED,OAAO,SAAS,CAAC,CAAC,uCAAuC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,mCAAmC,KAAK,EAAE,CAAC,CAAC;YAC3D,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;CACF;AA5cD,0CA4cC","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,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RelatedEntityHandler = void 0;
|
|
4
|
-
const
|
|
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
|
|
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
|
/**
|