@memberjunction/metadata-sync 2.112.0 → 2.113.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 +136 -61
- package/dist/constants/metadata-keywords.d.ts +282 -0
- package/dist/constants/metadata-keywords.js +364 -0
- package/dist/constants/metadata-keywords.js.map +1 -0
- package/dist/lib/EntityPropertyExtractor.d.ts +1 -1
- package/dist/lib/EntityPropertyExtractor.js +4 -18
- package/dist/lib/EntityPropertyExtractor.js.map +1 -1
- package/dist/lib/FieldExternalizer.d.ts +1 -1
- package/dist/lib/FieldExternalizer.js +6 -5
- package/dist/lib/FieldExternalizer.js.map +1 -1
- package/dist/lib/RecordProcessor.d.ts +1 -1
- package/dist/lib/RecordProcessor.js +14 -12
- 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/json-preprocessor.js +7 -6
- package/dist/lib/json-preprocessor.js.map +1 -1
- package/dist/lib/provider-utils.d.ts +1 -1
- package/dist/lib/provider-utils.js +19 -16
- package/dist/lib/provider-utils.js.map +1 -1
- package/dist/lib/record-dependency-analyzer.js +44 -37
- 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 +36 -44
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/services/PullService.d.ts +1 -1
- package/dist/services/PullService.js +18 -22
- package/dist/services/PullService.js.map +1 -1
- package/dist/services/PushService.d.ts +1 -1
- package/dist/services/PushService.js +21 -15
- package/dist/services/PushService.js.map +1 -1
- package/dist/services/ValidationService.js +32 -44
- package/dist/services/ValidationService.js.map +1 -1
- package/dist/services/WatchService.d.ts +1 -1
- package/dist/services/WatchService.js +14 -13
- package/dist/services/WatchService.js.map +1 -1
- package/dist/types/validation.d.ts +6 -1
- package/dist/types/validation.js.map +1 -1
- package/package.json +7 -6
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RecordProcessor = void 0;
|
|
4
|
-
const
|
|
4
|
+
const core_1 = require("@memberjunction/core");
|
|
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");
|
|
8
8
|
const RelatedEntityHandler_1 = require("./RelatedEntityHandler");
|
|
9
|
+
const metadata_keywords_1 = require("../constants/metadata-keywords");
|
|
9
10
|
/**
|
|
10
11
|
* Handles the core processing of individual record data into the sync format
|
|
11
12
|
*/
|
|
@@ -93,7 +94,7 @@ class RecordProcessor {
|
|
|
93
94
|
if (!entityConfig.pull?.ignoreVirtualFields || !entityInfo) {
|
|
94
95
|
return false;
|
|
95
96
|
}
|
|
96
|
-
const fieldInfo = entityInfo.Fields.find(
|
|
97
|
+
const fieldInfo = entityInfo.Fields.find(f => f.Name === fieldName);
|
|
97
98
|
return fieldInfo?.IsVirtual === true;
|
|
98
99
|
}
|
|
99
100
|
/**
|
|
@@ -177,12 +178,13 @@ class RecordProcessor {
|
|
|
177
178
|
if (externalizeConfig.length > 0 && typeof externalizeConfig[0] === 'string') {
|
|
178
179
|
// Simple string array format
|
|
179
180
|
if (externalizeConfig.includes(fieldName)) {
|
|
180
|
-
return
|
|
181
|
+
return (0, metadata_keywords_1.createKeywordReference)('file', `{Name}.${fieldName.toLowerCase()}.md`);
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
184
|
else {
|
|
184
185
|
// Array of objects format
|
|
185
|
-
const fieldConfig = externalizeConfig
|
|
186
|
+
const fieldConfig = externalizeConfig
|
|
187
|
+
.find(config => config.field === fieldName);
|
|
186
188
|
if (fieldConfig) {
|
|
187
189
|
return fieldConfig.pattern;
|
|
188
190
|
}
|
|
@@ -196,7 +198,7 @@ class RecordProcessor {
|
|
|
196
198
|
const fieldConfig = externalizeConfig[fieldName];
|
|
197
199
|
if (fieldConfig) {
|
|
198
200
|
const extension = fieldConfig.extension || '.md';
|
|
199
|
-
return
|
|
201
|
+
return (0, metadata_keywords_1.createKeywordReference)('file', `{Name}.${fieldName.toLowerCase()}${extension}`);
|
|
200
202
|
}
|
|
201
203
|
return null;
|
|
202
204
|
}
|
|
@@ -249,7 +251,7 @@ class RecordProcessor {
|
|
|
249
251
|
}
|
|
250
252
|
return {
|
|
251
253
|
lastModified: existingRecordData.sync.lastModified,
|
|
252
|
-
checksum: checksum
|
|
254
|
+
checksum: checksum
|
|
253
255
|
};
|
|
254
256
|
}
|
|
255
257
|
else {
|
|
@@ -259,7 +261,7 @@ class RecordProcessor {
|
|
|
259
261
|
}
|
|
260
262
|
return {
|
|
261
263
|
lastModified: new Date().toISOString(),
|
|
262
|
-
checksum: checksum
|
|
264
|
+
checksum: checksum
|
|
263
265
|
};
|
|
264
266
|
}
|
|
265
267
|
}
|
|
@@ -267,8 +269,8 @@ class RecordProcessor {
|
|
|
267
269
|
* Checks if the record has externalized fields
|
|
268
270
|
*/
|
|
269
271
|
hasExternalizedFields(fields, entityConfig) {
|
|
270
|
-
return
|
|
271
|
-
Object.values(fields).some(
|
|
272
|
+
return !!entityConfig.pull?.externalizeFields &&
|
|
273
|
+
Object.values(fields).some(value => typeof value === 'string' && value.startsWith(metadata_keywords_1.METADATA_KEYWORDS.FILE));
|
|
272
274
|
}
|
|
273
275
|
/**
|
|
274
276
|
* Convert a GUID value to @lookup syntax by looking up the human-readable value
|
|
@@ -278,17 +280,17 @@ class RecordProcessor {
|
|
|
278
280
|
return guidValue;
|
|
279
281
|
}
|
|
280
282
|
try {
|
|
281
|
-
const rv = new
|
|
283
|
+
const rv = new core_1.RunView();
|
|
282
284
|
const result = await rv.RunView({
|
|
283
285
|
EntityName: lookupConfig.entity,
|
|
284
286
|
ExtraFilter: `ID = '${guidValue}'`,
|
|
285
|
-
ResultType: 'entity_object'
|
|
287
|
+
ResultType: 'entity_object'
|
|
286
288
|
}, this.contextUser);
|
|
287
289
|
if (result.Success && result.Results && result.Results.length > 0) {
|
|
288
290
|
const targetRecord = result.Results[0];
|
|
289
291
|
const lookupValue = targetRecord[lookupConfig.field];
|
|
290
292
|
if (lookupValue != null) {
|
|
291
|
-
return
|
|
293
|
+
return (0, metadata_keywords_1.createKeywordReference)('lookup', `${lookupConfig.entity}.${lookupConfig.field}=${lookupValue}`);
|
|
292
294
|
}
|
|
293
295
|
}
|
|
294
296
|
if (verbose) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordProcessor.js","sourceRoot":"","sources":["../../src/lib/RecordProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity, RunView, UserInfo, EntityInfo } from '@memberjunction/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
|
+
{"version":3,"file":"RecordProcessor.js","sourceRoot":"","sources":["../../src/lib/RecordProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity, RunView, UserInfo, EntityInfo } from '@memberjunction/core';\nimport { SyncEngine, RecordData } from '../lib/sync-engine';\nimport { EntityConfig } from '../config';\nimport { JsonWriteHelper } from './json-write-helper';\nimport { EntityPropertyExtractor } from './EntityPropertyExtractor';\nimport { FieldExternalizer } from './FieldExternalizer';\nimport { RelatedEntityHandler } from './RelatedEntityHandler';\nimport { METADATA_KEYWORDS, createKeywordReference } from '../constants/metadata-keywords';\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 createKeywordReference('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 createKeywordReference('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(METADATA_KEYWORDS.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 createKeywordReference('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,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 core_1 = require("@memberjunction/core");
|
|
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 core_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
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RelatedEntityHandler.js","sourceRoot":"","sources":["../../src/lib/RelatedEntityHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity, RunView, UserInfo } from '@memberjunction/global';\nimport { SyncEngine, RecordData } from '../lib/sync-engine';\nimport { RelatedEntityConfig, EntityConfig } from '../config';\n\n/**\n * Handles loading and processing of related entities for records\n */\nexport class RelatedEntityHandler {\n constructor(\n private syncEngine: SyncEngine,\n private contextUser: UserInfo\n ) {}\n\n /**\n * Load related entities for a record\n */\n async loadRelatedEntities(\n parentRecord: BaseEntity,\n relationConfig: RelatedEntityConfig,\n parentEntityConfig: EntityConfig,\n existingRelatedEntities: RecordData[],\n processRecordData: (\n record: BaseEntity,\n primaryKey: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n verbose?: boolean,\n isNewRecord?: boolean,\n existingRecordData?: RecordData,\n currentDepth?: number,\n ancestryPath?: Set<string>,\n fieldOverrides?: Record<string, any>\n ) => Promise<RecordData>,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData[]> {\n try {\n const parentPrimaryKey = this.getRecordPrimaryKey(parentRecord);\n if (!parentPrimaryKey) {\n this.logWarning('Unable to determine primary key for parent record', verbose);\n return [];\n }\n\n const relatedRecords = await this.queryRelatedEntities(parentPrimaryKey, relationConfig, verbose);\n\n if (!relatedRecords) {\n return [];\n }\n\n const relatedEntityConfig = this.createRelatedEntityConfig(relationConfig, parentEntityConfig);\n\n return await this.processRelatedRecords(\n relatedRecords,\n relationConfig,\n relatedEntityConfig,\n existingRelatedEntities,\n processRecordData,\n currentDepth,\n ancestryPath,\n verbose\n );\n } catch (error) {\n this.logError(`Error loading related entities for ${relationConfig.entity}`, error, verbose);\n return [];\n }\n }\n\n /**\n * Queries the database for related entities\n */\n private async queryRelatedEntities(\n parentPrimaryKey: string,\n relationConfig: RelatedEntityConfig,\n verbose?: boolean\n ): Promise<BaseEntity[] | null> {\n const filter = this.buildRelatedEntityFilter(parentPrimaryKey, relationConfig);\n\n if (verbose) {\n console.log(`Loading related entities: ${relationConfig.entity} with filter: ${filter}`);\n }\n\n const rv = new RunView();\n const result = await rv.RunView(\n {\n EntityName: relationConfig.entity,\n ExtraFilter: filter,\n ResultType: 'entity_object',\n },\n this.contextUser\n );\n\n if (!result.Success) {\n this.logWarning(`Failed to load related entities ${relationConfig.entity}: ${result.ErrorMessage}`, verbose);\n return null;\n }\n\n if (verbose) {\n console.log(`Found ${result.Results.length} related records for ${relationConfig.entity}`);\n }\n\n return result.Results;\n }\n\n /**\n * Builds the filter for querying related entities\n */\n private buildRelatedEntityFilter(parentPrimaryKey: string, relationConfig: RelatedEntityConfig): string {\n let filter = `${relationConfig.foreignKey} = '${parentPrimaryKey}'`;\n if (relationConfig.filter) {\n filter += ` AND (${relationConfig.filter})`;\n }\n return filter;\n }\n\n /**\n * Creates entity config for related entity processing\n */\n private createRelatedEntityConfig(relationConfig: RelatedEntityConfig, parentEntityConfig: EntityConfig): EntityConfig {\n return {\n entity: relationConfig.entity,\n pull: {\n excludeFields: relationConfig.excludeFields || [],\n lookupFields: relationConfig.lookupFields || {},\n externalizeFields: relationConfig.externalizeFields || [],\n relatedEntities: relationConfig.relatedEntities || {},\n ignoreVirtualFields: parentEntityConfig.pull?.ignoreVirtualFields || false,\n ignoreNullFields: parentEntityConfig.pull?.ignoreNullFields || false,\n },\n };\n }\n\n /**\n * Processes all related records (both existing and new)\n */\n private async processRelatedRecords(\n dbRecords: BaseEntity[],\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n existingRelatedEntities: RecordData[],\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData[]> {\n const dbRecordMap = this.buildDatabaseRecordMap(dbRecords);\n const relatedRecords: RecordData[] = [];\n const processedIds = new Set<string>();\n\n // Process existing related entities first (preserving order)\n await this.processExistingRelatedEntities(\n existingRelatedEntities,\n dbRecordMap,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n currentDepth,\n ancestryPath,\n relatedRecords,\n processedIds,\n verbose\n );\n\n // Process new related entities (append to end)\n await this.processNewRelatedEntities(\n dbRecords,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n currentDepth,\n ancestryPath,\n relatedRecords,\n processedIds,\n verbose\n );\n\n return relatedRecords;\n }\n\n /**\n * Builds a map of database records by primary key for efficient lookup\n */\n private buildDatabaseRecordMap(dbRecords: BaseEntity[]): Map<string, BaseEntity> {\n const dbRecordMap = new Map<string, BaseEntity>();\n\n for (const relatedRecord of dbRecords) {\n const relatedPrimaryKey = this.getRecordPrimaryKey(relatedRecord);\n if (relatedPrimaryKey) {\n dbRecordMap.set(relatedPrimaryKey, relatedRecord);\n }\n }\n\n return dbRecordMap;\n }\n\n /**\n * Processes existing related entities\n */\n private async processExistingRelatedEntities(\n existingRelatedEntities: RecordData[],\n dbRecordMap: Map<string, BaseEntity>,\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n relatedRecords: RecordData[],\n processedIds: Set<string>,\n verbose?: boolean\n ): Promise<void> {\n for (const existingRelatedEntity of existingRelatedEntities) {\n const existingPrimaryKey = existingRelatedEntity.primaryKey?.ID;\n if (!existingPrimaryKey) {\n this.logWarning('Existing related entity missing primary key, skipping', verbose);\n continue;\n }\n\n const dbRecord = dbRecordMap.get(existingPrimaryKey);\n if (!dbRecord) {\n if (verbose) {\n console.log(`Related entity ${existingPrimaryKey} no longer exists in database, removing from results`);\n }\n continue; // Skip deleted records\n }\n\n const recordData = await this.processExistingRelatedEntity(\n dbRecord,\n existingPrimaryKey,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n existingRelatedEntity,\n currentDepth,\n ancestryPath,\n verbose\n );\n\n if (recordData) {\n relatedRecords.push(recordData);\n processedIds.add(existingPrimaryKey);\n }\n }\n }\n\n /**\n * Processes a single existing related entity\n */\n private async processExistingRelatedEntity(\n dbRecord: BaseEntity,\n existingPrimaryKey: string,\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n existingRelatedEntity: RecordData,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData | null> {\n const relatedRecordPrimaryKey = this.buildPrimaryKeyForRecord(existingPrimaryKey, dbRecord, relationConfig.entity);\n\n const fieldOverrides = this.createFieldOverrides(dbRecord, relationConfig);\n if (!fieldOverrides) {\n return null;\n }\n\n return await processRecordData(\n dbRecord,\n relatedRecordPrimaryKey,\n '', // targetDir not needed for related entities\n relatedEntityConfig,\n verbose,\n false, // isNewRecord = false for existing records\n existingRelatedEntity, // Pass existing data for change detection\n currentDepth + 1,\n ancestryPath,\n fieldOverrides // Pass the field override for @parent:ID\n );\n }\n\n /**\n * Processes new related entities\n */\n private async processNewRelatedEntities(\n dbRecords: BaseEntity[],\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n relatedRecords: RecordData[],\n processedIds: Set<string>,\n verbose?: boolean\n ): Promise<void> {\n for (const relatedRecord of dbRecords) {\n const relatedPrimaryKey = this.getRecordPrimaryKey(relatedRecord);\n if (!relatedPrimaryKey || processedIds.has(relatedPrimaryKey)) {\n continue; // Skip already processed records\n }\n\n const recordData = await this.processNewRelatedEntity(\n relatedRecord,\n relatedPrimaryKey,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n currentDepth,\n ancestryPath,\n verbose\n );\n\n if (recordData) {\n relatedRecords.push(recordData);\n processedIds.add(relatedPrimaryKey);\n }\n }\n }\n\n /**\n * Processes a single new related entity\n */\n private async processNewRelatedEntity(\n relatedRecord: BaseEntity,\n relatedPrimaryKey: string,\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData | null> {\n const relatedRecordPrimaryKey = this.buildPrimaryKeyForRecord(relatedPrimaryKey, relatedRecord, relationConfig.entity);\n\n const fieldOverrides = this.createFieldOverrides(relatedRecord, relationConfig);\n if (!fieldOverrides) {\n return null;\n }\n\n return await processRecordData(\n relatedRecord,\n relatedRecordPrimaryKey,\n '', // targetDir not needed for related entities\n relatedEntityConfig,\n verbose,\n true, // isNewRecord = true for new records\n undefined, // No existing data for new records\n currentDepth + 1,\n ancestryPath,\n fieldOverrides // Pass the field override for @parent:ID\n );\n }\n\n /**\n * Creates field overrides for @parent:ID replacement\n */\n private createFieldOverrides(record: BaseEntity, relationConfig: RelatedEntityConfig): Record<string, any> | null {\n if (typeof record.GetAll === 'function') {\n const data = record.GetAll();\n if (data[relationConfig.foreignKey] !== undefined) {\n return { [relationConfig.foreignKey]: '@parent:ID' };\n }\n } else {\n if ((record as any)[relationConfig.foreignKey] !== undefined) {\n return { [relationConfig.foreignKey]: '@parent:ID' };\n }\n }\n return null;\n }\n\n /**\n * Builds primary key for a record\n */\n private buildPrimaryKeyForRecord(primaryKeyValue: string, record: BaseEntity, entityName: string): Record<string, any> {\n const relatedRecordPrimaryKey: Record<string, any> = {};\n const entityInfo = this.syncEngine.getEntityInfo(entityName);\n\n for (const pk of entityInfo?.PrimaryKeys || []) {\n if (pk.Name === 'ID') {\n relatedRecordPrimaryKey[pk.Name] = primaryKeyValue;\n } else {\n // For compound keys, get the value from the related record\n relatedRecordPrimaryKey[pk.Name] = this.getFieldValue(record, pk.Name);\n }\n }\n\n return relatedRecordPrimaryKey;\n }\n\n /**\n * Get the primary key value from a record\n */\n private getRecordPrimaryKey(record: BaseEntity): string | null {\n if (!record) return null;\n\n // Try to get ID directly\n if ((record as any).ID) return (record as any).ID;\n\n // Try to get from GetAll() method if it's an entity object\n if (typeof record.GetAll === 'function') {\n const data = record.GetAll();\n if (data.ID) return data.ID;\n }\n\n // Try common variations\n if ((record as any).id) return (record as any).id;\n if ((record as any).Id) return (record as any).Id;\n\n return null;\n }\n\n /**\n * Get a field value from a record, handling both entity objects and plain objects\n */\n private getFieldValue(record: BaseEntity, fieldName: string): any {\n if (!record) return null;\n\n // Try to get field directly using bracket notation with type assertion\n if ((record as any)[fieldName] !== undefined) return (record as any)[fieldName];\n\n // Try to get from GetAll() method if it's an entity object\n if (typeof record.GetAll === 'function') {\n const data = record.GetAll();\n if (data[fieldName] !== undefined) return data[fieldName];\n }\n\n return null;\n }\n\n /**\n * Log warning message if verbose mode is enabled\n */\n private logWarning(message: string, verbose?: boolean): void {\n if (verbose) {\n console.warn(message);\n }\n }\n\n /**\n * Log error message if verbose mode is enabled\n */\n private logError(message: string, error: any, verbose?: boolean): void {\n if (verbose) {\n console.error(`${message}: ${error}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RelatedEntityHandler.js","sourceRoot":"","sources":["../../src/lib/RelatedEntityHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEntity, RunView, UserInfo } from '@memberjunction/core';\nimport { SyncEngine, RecordData } from '../lib/sync-engine';\nimport { RelatedEntityConfig, EntityConfig } from '../config';\n\n/**\n * Handles loading and processing of related entities for records\n */\nexport class RelatedEntityHandler {\n constructor(\n private syncEngine: SyncEngine,\n private contextUser: UserInfo\n ) {}\n\n /**\n * Load related entities for a record\n */\n async loadRelatedEntities(\n parentRecord: BaseEntity,\n relationConfig: RelatedEntityConfig,\n parentEntityConfig: EntityConfig,\n existingRelatedEntities: RecordData[],\n processRecordData: (\n record: BaseEntity,\n primaryKey: Record<string, any>,\n targetDir: string,\n entityConfig: EntityConfig,\n verbose?: boolean,\n isNewRecord?: boolean,\n existingRecordData?: RecordData,\n currentDepth?: number,\n ancestryPath?: Set<string>,\n fieldOverrides?: Record<string, any>\n ) => Promise<RecordData>,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData[]> {\n try {\n const parentPrimaryKey = this.getRecordPrimaryKey(parentRecord);\n if (!parentPrimaryKey) {\n this.logWarning('Unable to determine primary key for parent record', verbose);\n return [];\n }\n\n const relatedRecords = await this.queryRelatedEntities(\n parentPrimaryKey, \n relationConfig, \n verbose\n );\n\n if (!relatedRecords) {\n return [];\n }\n\n const relatedEntityConfig = this.createRelatedEntityConfig(relationConfig, parentEntityConfig);\n \n return await this.processRelatedRecords(\n relatedRecords,\n relationConfig,\n relatedEntityConfig,\n existingRelatedEntities,\n processRecordData,\n currentDepth,\n ancestryPath,\n verbose\n );\n } catch (error) {\n this.logError(`Error loading related entities for ${relationConfig.entity}`, error, verbose);\n return [];\n }\n }\n\n /**\n * Queries the database for related entities\n */\n private async queryRelatedEntities(\n parentPrimaryKey: string,\n relationConfig: RelatedEntityConfig,\n verbose?: boolean\n ): Promise<BaseEntity[] | null> {\n const filter = this.buildRelatedEntityFilter(parentPrimaryKey, relationConfig);\n \n if (verbose) {\n console.log(`Loading related entities: ${relationConfig.entity} with filter: ${filter}`);\n }\n\n const rv = new RunView();\n const result = await rv.RunView({\n EntityName: relationConfig.entity,\n ExtraFilter: filter,\n ResultType: 'entity_object'\n }, this.contextUser);\n\n if (!result.Success) {\n this.logWarning(`Failed to load related entities ${relationConfig.entity}: ${result.ErrorMessage}`, verbose);\n return null;\n }\n\n if (verbose) {\n console.log(`Found ${result.Results.length} related records for ${relationConfig.entity}`);\n }\n\n return result.Results;\n }\n\n /**\n * Builds the filter for querying related entities\n */\n private buildRelatedEntityFilter(parentPrimaryKey: string, relationConfig: RelatedEntityConfig): string {\n let filter = `${relationConfig.foreignKey} = '${parentPrimaryKey}'`;\n if (relationConfig.filter) {\n filter += ` AND (${relationConfig.filter})`;\n }\n return filter;\n }\n\n /**\n * Creates entity config for related entity processing\n */\n private createRelatedEntityConfig(\n relationConfig: RelatedEntityConfig, \n parentEntityConfig: EntityConfig\n ): EntityConfig {\n return {\n entity: relationConfig.entity,\n pull: {\n excludeFields: relationConfig.excludeFields || [],\n lookupFields: relationConfig.lookupFields || {},\n externalizeFields: relationConfig.externalizeFields || [],\n relatedEntities: relationConfig.relatedEntities || {},\n ignoreVirtualFields: parentEntityConfig.pull?.ignoreVirtualFields || false,\n ignoreNullFields: parentEntityConfig.pull?.ignoreNullFields || false\n }\n };\n }\n\n /**\n * Processes all related records (both existing and new)\n */\n private async processRelatedRecords(\n dbRecords: BaseEntity[],\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n existingRelatedEntities: RecordData[],\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData[]> {\n const dbRecordMap = this.buildDatabaseRecordMap(dbRecords);\n const relatedRecords: RecordData[] = [];\n const processedIds = new Set<string>();\n\n // Process existing related entities first (preserving order)\n await this.processExistingRelatedEntities(\n existingRelatedEntities,\n dbRecordMap,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n currentDepth,\n ancestryPath,\n relatedRecords,\n processedIds,\n verbose\n );\n\n // Process new related entities (append to end)\n await this.processNewRelatedEntities(\n dbRecords,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n currentDepth,\n ancestryPath,\n relatedRecords,\n processedIds,\n verbose\n );\n\n return relatedRecords;\n }\n\n /**\n * Builds a map of database records by primary key for efficient lookup\n */\n private buildDatabaseRecordMap(dbRecords: BaseEntity[]): Map<string, BaseEntity> {\n const dbRecordMap = new Map<string, BaseEntity>();\n \n for (const relatedRecord of dbRecords) {\n const relatedPrimaryKey = this.getRecordPrimaryKey(relatedRecord);\n if (relatedPrimaryKey) {\n dbRecordMap.set(relatedPrimaryKey, relatedRecord);\n }\n }\n \n return dbRecordMap;\n }\n\n /**\n * Processes existing related entities\n */\n private async processExistingRelatedEntities(\n existingRelatedEntities: RecordData[],\n dbRecordMap: Map<string, BaseEntity>,\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n relatedRecords: RecordData[],\n processedIds: Set<string>,\n verbose?: boolean\n ): Promise<void> {\n for (const existingRelatedEntity of existingRelatedEntities) {\n const existingPrimaryKey = existingRelatedEntity.primaryKey?.ID;\n if (!existingPrimaryKey) {\n this.logWarning('Existing related entity missing primary key, skipping', verbose);\n continue;\n }\n\n const dbRecord = dbRecordMap.get(existingPrimaryKey);\n if (!dbRecord) {\n if (verbose) {\n console.log(`Related entity ${existingPrimaryKey} no longer exists in database, removing from results`);\n }\n continue; // Skip deleted records\n }\n\n const recordData = await this.processExistingRelatedEntity(\n dbRecord,\n existingPrimaryKey,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n existingRelatedEntity,\n currentDepth,\n ancestryPath,\n verbose\n );\n\n if (recordData) {\n relatedRecords.push(recordData);\n processedIds.add(existingPrimaryKey);\n }\n }\n }\n\n /**\n * Processes a single existing related entity\n */\n private async processExistingRelatedEntity(\n dbRecord: BaseEntity,\n existingPrimaryKey: string,\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n existingRelatedEntity: RecordData,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData | null> {\n const relatedRecordPrimaryKey = this.buildPrimaryKeyForRecord(\n existingPrimaryKey, \n dbRecord, \n relationConfig.entity\n );\n\n const fieldOverrides = this.createFieldOverrides(dbRecord, relationConfig);\n if (!fieldOverrides) {\n return null;\n }\n\n return await processRecordData(\n dbRecord,\n relatedRecordPrimaryKey,\n '', // targetDir not needed for related entities\n relatedEntityConfig,\n verbose,\n false, // isNewRecord = false for existing records\n existingRelatedEntity, // Pass existing data for change detection\n currentDepth + 1,\n ancestryPath,\n fieldOverrides // Pass the field override for @parent:ID\n );\n }\n\n /**\n * Processes new related entities\n */\n private async processNewRelatedEntities(\n dbRecords: BaseEntity[],\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n relatedRecords: RecordData[],\n processedIds: Set<string>,\n verbose?: boolean\n ): Promise<void> {\n for (const relatedRecord of dbRecords) {\n const relatedPrimaryKey = this.getRecordPrimaryKey(relatedRecord);\n if (!relatedPrimaryKey || processedIds.has(relatedPrimaryKey)) {\n continue; // Skip already processed records\n }\n\n const recordData = await this.processNewRelatedEntity(\n relatedRecord,\n relatedPrimaryKey,\n relationConfig,\n relatedEntityConfig,\n processRecordData,\n currentDepth,\n ancestryPath,\n verbose\n );\n\n if (recordData) {\n relatedRecords.push(recordData);\n processedIds.add(relatedPrimaryKey);\n }\n }\n }\n\n /**\n * Processes a single new related entity\n */\n private async processNewRelatedEntity(\n relatedRecord: BaseEntity,\n relatedPrimaryKey: string,\n relationConfig: RelatedEntityConfig,\n relatedEntityConfig: EntityConfig,\n processRecordData: Function,\n currentDepth: number,\n ancestryPath: Set<string>,\n verbose?: boolean\n ): Promise<RecordData | null> {\n const relatedRecordPrimaryKey = this.buildPrimaryKeyForRecord(\n relatedPrimaryKey, \n relatedRecord, \n relationConfig.entity\n );\n\n const fieldOverrides = this.createFieldOverrides(relatedRecord, relationConfig);\n if (!fieldOverrides) {\n return null;\n }\n\n return await processRecordData(\n relatedRecord,\n relatedRecordPrimaryKey,\n '', // targetDir not needed for related entities\n relatedEntityConfig,\n verbose,\n true, // isNewRecord = true for new records\n undefined, // No existing data for new records\n currentDepth + 1,\n ancestryPath,\n fieldOverrides // Pass the field override for @parent:ID\n );\n }\n\n /**\n * Creates field overrides for @parent:ID replacement\n */\n private createFieldOverrides(record: BaseEntity, relationConfig: RelatedEntityConfig): Record<string, any> | null {\n if (typeof record.GetAll === 'function') {\n const data = record.GetAll();\n if (data[relationConfig.foreignKey] !== undefined) {\n return { [relationConfig.foreignKey]: '@parent:ID' };\n }\n } else {\n if ((record as any)[relationConfig.foreignKey] !== undefined) {\n return { [relationConfig.foreignKey]: '@parent:ID' };\n }\n }\n return null;\n }\n\n /**\n * Builds primary key for a record\n */\n private buildPrimaryKeyForRecord(\n primaryKeyValue: string, \n record: BaseEntity, \n entityName: string\n ): Record<string, any> {\n const relatedRecordPrimaryKey: Record<string, any> = {};\n const entityInfo = this.syncEngine.getEntityInfo(entityName);\n \n for (const pk of entityInfo?.PrimaryKeys || []) {\n if (pk.Name === 'ID') {\n relatedRecordPrimaryKey[pk.Name] = primaryKeyValue;\n } else {\n // For compound keys, get the value from the related record\n relatedRecordPrimaryKey[pk.Name] = this.getFieldValue(record, pk.Name);\n }\n }\n \n return relatedRecordPrimaryKey;\n }\n\n /**\n * Get the primary key value from a record\n */\n private getRecordPrimaryKey(record: BaseEntity): string | null {\n if (!record) return null;\n \n // Try to get ID directly\n if ((record as any).ID) return (record as any).ID;\n \n // Try to get from GetAll() method if it's an entity object\n if (typeof record.GetAll === 'function') {\n const data = record.GetAll();\n if (data.ID) return data.ID;\n }\n \n // Try common variations\n if ((record as any).id) return (record as any).id;\n if ((record as any).Id) return (record as any).Id;\n \n return null;\n }\n\n /**\n * Get a field value from a record, handling both entity objects and plain objects\n */\n private getFieldValue(record: BaseEntity, fieldName: string): any {\n if (!record) return null;\n \n // Try to get field directly using bracket notation with type assertion\n if ((record as any)[fieldName] !== undefined) return (record as any)[fieldName];\n \n // Try to get from GetAll() method if it's an entity object\n if (typeof record.GetAll === 'function') {\n const data = record.GetAll();\n if (data[fieldName] !== undefined) return data[fieldName];\n }\n \n return null;\n }\n\n /**\n * Log warning message if verbose mode is enabled\n */\n private logWarning(message: string, verbose?: boolean): void {\n if (verbose) {\n console.warn(message);\n }\n }\n\n /**\n * Log error message if verbose mode is enabled\n */\n private logError(message: string, error: any, verbose?: boolean): void {\n if (verbose) {\n console.error(`${message}: ${error}`);\n }\n }\n}"]}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.JsonPreprocessor = void 0;
|
|
7
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const metadata_keywords_1 = require("../constants/metadata-keywords");
|
|
9
10
|
/**
|
|
10
11
|
* Preprocesses JSON files to handle @include directives
|
|
11
12
|
* Supports including external JSON files with spreading or element insertion
|
|
@@ -50,8 +51,8 @@ class JsonPreprocessor {
|
|
|
50
51
|
const result = [];
|
|
51
52
|
for (const item of arr) {
|
|
52
53
|
// Check for string-based include in array (default element mode)
|
|
53
|
-
if (typeof item === 'string' && item.startsWith(
|
|
54
|
-
const includePath =
|
|
54
|
+
if (typeof item === 'string' && item.startsWith(`${metadata_keywords_1.METADATA_KEYWORDS.INCLUDE}:`)) {
|
|
55
|
+
const includePath = (0, metadata_keywords_1.extractKeywordValue)(item).trim();
|
|
55
56
|
const resolvedPath = this.resolvePath(includePath, currentFilePath);
|
|
56
57
|
const includedContent = await this.loadAndProcessInclude(resolvedPath);
|
|
57
58
|
// If included content is an array, spread its elements
|
|
@@ -85,7 +86,7 @@ class JsonPreprocessor {
|
|
|
85
86
|
const includeDirectives = new Map();
|
|
86
87
|
// First pass: identify all @include keys (both @include and @include.*)
|
|
87
88
|
for (const key of Object.keys(obj)) {
|
|
88
|
-
if (key ===
|
|
89
|
+
if (key === metadata_keywords_1.METADATA_KEYWORDS.INCLUDE || key.startsWith(`${metadata_keywords_1.METADATA_KEYWORDS.INCLUDE}.`)) {
|
|
89
90
|
includeKeys.push(key);
|
|
90
91
|
const includeValue = obj[key];
|
|
91
92
|
if (typeof includeValue === 'string') {
|
|
@@ -105,7 +106,7 @@ class JsonPreprocessor {
|
|
|
105
106
|
}
|
|
106
107
|
// Second pass: process all properties in order, spreading includes when encountered
|
|
107
108
|
for (const [key, value] of Object.entries(obj)) {
|
|
108
|
-
if (key ===
|
|
109
|
+
if (key === metadata_keywords_1.METADATA_KEYWORDS.INCLUDE || key.startsWith(`${metadata_keywords_1.METADATA_KEYWORDS.INCLUDE}.`)) {
|
|
109
110
|
// Process this include directive
|
|
110
111
|
const includeDirective = includeDirectives.get(key);
|
|
111
112
|
if (includeDirective) {
|
|
@@ -138,9 +139,9 @@ class JsonPreprocessor {
|
|
|
138
139
|
}
|
|
139
140
|
else {
|
|
140
141
|
// Regular property - process recursively and handle @file references
|
|
141
|
-
if (typeof value === 'string' && value.startsWith(
|
|
142
|
+
if (typeof value === 'string' && value.startsWith(metadata_keywords_1.METADATA_KEYWORDS.FILE)) {
|
|
142
143
|
// Process @file reference
|
|
143
|
-
const filePath =
|
|
144
|
+
const filePath = (0, metadata_keywords_1.extractKeywordValue)(value);
|
|
144
145
|
const resolvedPath = this.resolvePath(filePath, currentFilePath);
|
|
145
146
|
result[key] = await this.loadFileContent(resolvedPath);
|
|
146
147
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-preprocessor.js","sourceRoot":"","sources":["../../src/lib/json-preprocessor.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\n\n/**\n * Include directive configuration\n */\nexport interface IncludeDirective {\n file: string;\n mode?: 'spread' | 'element';\n}\n\n/**\n * Preprocesses JSON files to handle @include directives\n * Supports including external JSON files with spreading or element insertion\n */\nexport class JsonPreprocessor {\n private visitedPaths: Set<string> = new Set();\n\n /**\n * Process a JSON file and resolve all @include directives\n * @param filePath - Path to the JSON file to process\n * @returns The processed JSON data with all includes resolved\n */\n async processFile(filePath: string): Promise<any> {\n this.visitedPaths.clear();\n const fileContent = await fs.readJson(filePath);\n return this.processIncludesInternal(fileContent, filePath);\n }\n\n /**\n * Recursively process @include directives in JSON data\n * @param data - The JSON data to process\n * @param currentFilePath - Path of the current file for resolving relative paths\n * @returns The processed data with includes resolved\n */\n private async processIncludesInternal(data: any, currentFilePath: string): Promise<any> {\n // Process based on data type\n if (Array.isArray(data)) {\n return this.processArray(data, currentFilePath);\n } else if (data && typeof data === 'object') {\n return this.processObject(data, currentFilePath);\n } else {\n return data;\n }\n }\n\n /**\n * Process an array, handling @include directives\n * @param arr - The array to process\n * @param currentFilePath - Current file path for resolving relative paths\n * @returns Processed array with includes resolved\n */\n private async processArray(arr: any[], currentFilePath: string): Promise<any[]> {\n const result: any[] = [];\n \n for (const item of arr) {\n // Check for string-based include in array (default element mode)\n if (typeof item === 'string' && item.startsWith('@include:')) {\n const includePath = item.substring(9).trim();\n const resolvedPath = this.resolvePath(includePath, currentFilePath);\n const includedContent = await this.loadAndProcessInclude(resolvedPath);\n \n // If included content is an array, spread its elements\n if (Array.isArray(includedContent)) {\n result.push(...includedContent);\n } else {\n // Otherwise add as single element\n result.push(includedContent);\n }\n } else if (item && typeof item === 'object') {\n // Process nested objects/arrays\n result.push(await this.processIncludesInternal(item, currentFilePath));\n } else {\n result.push(item);\n }\n }\n \n return result;\n }\n\n /**\n * Process an object, handling @include directives\n * @param obj - The object to process\n * @param currentFilePath - Current file path for resolving relative paths\n * @returns Processed object with includes resolved\n */\n private async processObject(obj: any, currentFilePath: string): Promise<any> {\n const result: any = {};\n const includeKeys: string[] = [];\n const includeDirectives: Map<string, IncludeDirective> = new Map();\n \n // First pass: identify all @include keys (both @include and @include.*)\n for (const key of Object.keys(obj)) {\n if (key === '@include' || key.startsWith('@include.')) {\n includeKeys.push(key);\n const includeValue = obj[key];\n \n if (typeof includeValue === 'string') {\n // Simple string include - default to spread mode in objects\n includeDirectives.set(key, { file: includeValue, mode: 'spread' });\n } else if (includeValue && typeof includeValue === 'object') {\n // Explicit include configuration\n const directive = includeValue as IncludeDirective;\n // Default to spread mode if not specified\n if (!directive.mode) {\n directive.mode = 'spread';\n }\n includeDirectives.set(key, directive);\n }\n }\n }\n \n // Second pass: process all properties in order, spreading includes when encountered\n for (const [key, value] of Object.entries(obj)) {\n if (key === '@include' || key.startsWith('@include.')) {\n // Process this include directive\n const includeDirective = includeDirectives.get(key);\n if (includeDirective) {\n const resolvedPath = this.resolvePath(includeDirective.file, currentFilePath);\n const includedContent = await this.loadAndProcessInclude(resolvedPath);\n \n if (includeDirective.mode === 'spread') {\n // Spread mode: merge included object properties at this position\n if (includedContent && typeof includedContent === 'object' && !Array.isArray(includedContent)) {\n Object.assign(result, includedContent);\n } else {\n throw new Error(`Cannot spread non-object content from ${includeDirective.file}. Use mode: \"element\" for non-object includes.`);\n }\n } else if (includeDirective.mode === 'element') {\n // Element mode: directly insert the content\n // For dot notation includes, we can't replace the whole object,\n // so we'll add it as a property instead (though this is unusual)\n if (key.includes('.')) {\n // Extract the part after the dot to use as property name\n const propName = key.split('.').slice(1).join('.');\n result[propName] = includedContent;\n } else {\n // For plain @include with element mode, replace the entire object\n return includedContent;\n }\n }\n }\n } else {\n // Regular property - process recursively and handle @file references\n if (typeof value === 'string' && value.startsWith('@file:')) {\n // Process @file reference\n const filePath = value.substring(6); // Remove '@file:' prefix\n const resolvedPath = this.resolvePath(filePath, currentFilePath);\n result[key] = await this.loadFileContent(resolvedPath);\n } else if (value && typeof value === 'object') {\n result[key] = await this.processIncludesInternal(value, currentFilePath);\n } else {\n result[key] = value;\n }\n }\n }\n \n return result;\n }\n\n /**\n * Load and process an included file\n * @param filePath - Path to the file to include\n * @returns The processed content of the included file\n */\n private async loadAndProcessInclude(filePath: string): Promise<any> {\n const absolutePath = path.resolve(filePath);\n \n // Check if this file is already being processed (circular reference)\n if (this.visitedPaths.has(absolutePath)) {\n throw new Error(`Circular reference detected: ${absolutePath} is already being processed`);\n }\n \n if (!await fs.pathExists(filePath)) {\n // Log error details before throwing\n console.error(`\\n❌ INCLUDE FILE NOT FOUND`);\n console.error(` Referenced file: ${filePath}`);\n console.error(` Reference type: @include`);\n console.error(` Tip: Check that the file path is correct relative to the including file\\n`);\n \n throw new Error(`Include file not found: ${filePath}`);\n }\n \n // Add to visited paths before processing\n this.visitedPaths.add(absolutePath);\n \n try {\n const content = await fs.readJson(filePath);\n // Process the content (visited tracking is handled in this method)\n return this.processIncludesInternal(content, filePath);\n } finally {\n // Remove from visited paths after processing\n this.visitedPaths.delete(absolutePath);\n }\n }\n\n /**\n * Load file content and process it if it's JSON with @include directives\n * @param filePath - Path to the file to load\n * @returns The file content (processed if JSON with @includes)\n */\n private async loadFileContent(filePath: string): Promise<any> {\n if (!await fs.pathExists(filePath)) {\n // Log error details before throwing\n console.error(`\\n❌ FILE NOT FOUND`);\n console.error(` Referenced file: ${filePath}`);\n console.error(` Reference type: @file:`);\n console.error(` Tip: Check that the file path is correct and the file exists\\n`);\n \n throw new Error(`File not found: ${filePath} (referenced via @file:)`);\n }\n\n try {\n if (filePath.endsWith('.json')) {\n // For JSON files, load and check for @include directives\n const jsonContent = await fs.readJson(filePath);\n const jsonString = JSON.stringify(jsonContent);\n const hasIncludes = jsonString.includes('\"@include\"') || jsonString.includes('\"@include.');\n \n if (hasIncludes) {\n // Process @include directives in the JSON file\n return await this.processIncludesInternal(jsonContent, filePath);\n } else {\n // Return the JSON content as-is\n return jsonContent;\n }\n } else {\n // For non-JSON files, return the text content\n return await fs.readFile(filePath, 'utf-8');\n }\n } catch (error) {\n // Log error details before re-throwing\n console.error(`\\n❌ FILE LOAD ERROR`);\n console.error(` Failed to load file: ${filePath}`);\n console.error(` Error: ${error}`);\n console.error(` Tip: Check file permissions and that the file is not corrupted\\n`);\n \n // Re-throw with enhanced context\n throw new Error(`Failed to load file content from ${filePath}: ${error}`);\n }\n }\n\n /**\n * Resolve a potentially relative path to an absolute path\n * @param includePath - The path specified in the @include\n * @param currentFilePath - The current file's path\n * @returns Absolute path to the included file\n */\n private resolvePath(includePath: string, currentFilePath: string): string {\n const currentDir = path.dirname(currentFilePath);\n return path.resolve(currentDir, includePath);\n }\n\n /**\n * Process JSON data that's already loaded (for integration with existing code)\n * @param data - The JSON data to process\n * @param filePath - The file path (for resolving relative includes)\n * @returns Processed data with includes resolved\n */\n async processJsonData(data: any, filePath: string): Promise<any> {\n this.visitedPaths.clear();\n return this.processIncludesInternal(data, filePath);\n }\n}"]}
|
|
1
|
+
{"version":3,"file":"json-preprocessor.js","sourceRoot":"","sources":["../../src/lib/json-preprocessor.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\nimport { METADATA_KEYWORDS, extractKeywordValue } from '../constants/metadata-keywords';\n\n/**\n * Include directive configuration\n */\nexport interface IncludeDirective {\n file: string;\n mode?: 'spread' | 'element';\n}\n\n/**\n * Preprocesses JSON files to handle @include directives\n * Supports including external JSON files with spreading or element insertion\n */\nexport class JsonPreprocessor {\n private visitedPaths: Set<string> = new Set();\n\n /**\n * Process a JSON file and resolve all @include directives\n * @param filePath - Path to the JSON file to process\n * @returns The processed JSON data with all includes resolved\n */\n async processFile(filePath: string): Promise<any> {\n this.visitedPaths.clear();\n const fileContent = await fs.readJson(filePath);\n return this.processIncludesInternal(fileContent, filePath);\n }\n\n /**\n * Recursively process @include directives in JSON data\n * @param data - The JSON data to process\n * @param currentFilePath - Path of the current file for resolving relative paths\n * @returns The processed data with includes resolved\n */\n private async processIncludesInternal(data: any, currentFilePath: string): Promise<any> {\n // Process based on data type\n if (Array.isArray(data)) {\n return this.processArray(data, currentFilePath);\n } else if (data && typeof data === 'object') {\n return this.processObject(data, currentFilePath);\n } else {\n return data;\n }\n }\n\n /**\n * Process an array, handling @include directives\n * @param arr - The array to process\n * @param currentFilePath - Current file path for resolving relative paths\n * @returns Processed array with includes resolved\n */\n private async processArray(arr: any[], currentFilePath: string): Promise<any[]> {\n const result: any[] = [];\n \n for (const item of arr) {\n // Check for string-based include in array (default element mode)\n if (typeof item === 'string' && item.startsWith(`${METADATA_KEYWORDS.INCLUDE}:`)) {\n const includePath = (extractKeywordValue(item) as string).trim();\n const resolvedPath = this.resolvePath(includePath, currentFilePath);\n const includedContent = await this.loadAndProcessInclude(resolvedPath);\n \n // If included content is an array, spread its elements\n if (Array.isArray(includedContent)) {\n result.push(...includedContent);\n } else {\n // Otherwise add as single element\n result.push(includedContent);\n }\n } else if (item && typeof item === 'object') {\n // Process nested objects/arrays\n result.push(await this.processIncludesInternal(item, currentFilePath));\n } else {\n result.push(item);\n }\n }\n \n return result;\n }\n\n /**\n * Process an object, handling @include directives\n * @param obj - The object to process\n * @param currentFilePath - Current file path for resolving relative paths\n * @returns Processed object with includes resolved\n */\n private async processObject(obj: any, currentFilePath: string): Promise<any> {\n const result: any = {};\n const includeKeys: string[] = [];\n const includeDirectives: Map<string, IncludeDirective> = new Map();\n \n // First pass: identify all @include keys (both @include and @include.*)\n for (const key of Object.keys(obj)) {\n if (key === METADATA_KEYWORDS.INCLUDE || key.startsWith(`${METADATA_KEYWORDS.INCLUDE}.`)) {\n includeKeys.push(key);\n const includeValue = obj[key];\n \n if (typeof includeValue === 'string') {\n // Simple string include - default to spread mode in objects\n includeDirectives.set(key, { file: includeValue, mode: 'spread' });\n } else if (includeValue && typeof includeValue === 'object') {\n // Explicit include configuration\n const directive = includeValue as IncludeDirective;\n // Default to spread mode if not specified\n if (!directive.mode) {\n directive.mode = 'spread';\n }\n includeDirectives.set(key, directive);\n }\n }\n }\n \n // Second pass: process all properties in order, spreading includes when encountered\n for (const [key, value] of Object.entries(obj)) {\n if (key === METADATA_KEYWORDS.INCLUDE || key.startsWith(`${METADATA_KEYWORDS.INCLUDE}.`)) {\n // Process this include directive\n const includeDirective = includeDirectives.get(key);\n if (includeDirective) {\n const resolvedPath = this.resolvePath(includeDirective.file, currentFilePath);\n const includedContent = await this.loadAndProcessInclude(resolvedPath);\n \n if (includeDirective.mode === 'spread') {\n // Spread mode: merge included object properties at this position\n if (includedContent && typeof includedContent === 'object' && !Array.isArray(includedContent)) {\n Object.assign(result, includedContent);\n } else {\n throw new Error(`Cannot spread non-object content from ${includeDirective.file}. Use mode: \"element\" for non-object includes.`);\n }\n } else if (includeDirective.mode === 'element') {\n // Element mode: directly insert the content\n // For dot notation includes, we can't replace the whole object,\n // so we'll add it as a property instead (though this is unusual)\n if (key.includes('.')) {\n // Extract the part after the dot to use as property name\n const propName = key.split('.').slice(1).join('.');\n result[propName] = includedContent;\n } else {\n // For plain @include with element mode, replace the entire object\n return includedContent;\n }\n }\n }\n } else {\n // Regular property - process recursively and handle @file references\n if (typeof value === 'string' && value.startsWith(METADATA_KEYWORDS.FILE)) {\n // Process @file reference\n const filePath = extractKeywordValue(value) as string;\n const resolvedPath = this.resolvePath(filePath, currentFilePath);\n result[key] = await this.loadFileContent(resolvedPath);\n } else if (value && typeof value === 'object') {\n result[key] = await this.processIncludesInternal(value, currentFilePath);\n } else {\n result[key] = value;\n }\n }\n }\n \n return result;\n }\n\n /**\n * Load and process an included file\n * @param filePath - Path to the file to include\n * @returns The processed content of the included file\n */\n private async loadAndProcessInclude(filePath: string): Promise<any> {\n const absolutePath = path.resolve(filePath);\n \n // Check if this file is already being processed (circular reference)\n if (this.visitedPaths.has(absolutePath)) {\n throw new Error(`Circular reference detected: ${absolutePath} is already being processed`);\n }\n \n if (!await fs.pathExists(filePath)) {\n // Log error details before throwing\n console.error(`\\n❌ INCLUDE FILE NOT FOUND`);\n console.error(` Referenced file: ${filePath}`);\n console.error(` Reference type: @include`);\n console.error(` Tip: Check that the file path is correct relative to the including file\\n`);\n \n throw new Error(`Include file not found: ${filePath}`);\n }\n \n // Add to visited paths before processing\n this.visitedPaths.add(absolutePath);\n \n try {\n const content = await fs.readJson(filePath);\n // Process the content (visited tracking is handled in this method)\n return this.processIncludesInternal(content, filePath);\n } finally {\n // Remove from visited paths after processing\n this.visitedPaths.delete(absolutePath);\n }\n }\n\n /**\n * Load file content and process it if it's JSON with @include directives\n * @param filePath - Path to the file to load\n * @returns The file content (processed if JSON with @includes)\n */\n private async loadFileContent(filePath: string): Promise<any> {\n if (!await fs.pathExists(filePath)) {\n // Log error details before throwing\n console.error(`\\n❌ FILE NOT FOUND`);\n console.error(` Referenced file: ${filePath}`);\n console.error(` Reference type: @file:`);\n console.error(` Tip: Check that the file path is correct and the file exists\\n`);\n \n throw new Error(`File not found: ${filePath} (referenced via @file:)`);\n }\n\n try {\n if (filePath.endsWith('.json')) {\n // For JSON files, load and check for @include directives\n const jsonContent = await fs.readJson(filePath);\n const jsonString = JSON.stringify(jsonContent);\n const hasIncludes = jsonString.includes('\"@include\"') || jsonString.includes('\"@include.');\n \n if (hasIncludes) {\n // Process @include directives in the JSON file\n return await this.processIncludesInternal(jsonContent, filePath);\n } else {\n // Return the JSON content as-is\n return jsonContent;\n }\n } else {\n // For non-JSON files, return the text content\n return await fs.readFile(filePath, 'utf-8');\n }\n } catch (error) {\n // Log error details before re-throwing\n console.error(`\\n❌ FILE LOAD ERROR`);\n console.error(` Failed to load file: ${filePath}`);\n console.error(` Error: ${error}`);\n console.error(` Tip: Check file permissions and that the file is not corrupted\\n`);\n \n // Re-throw with enhanced context\n throw new Error(`Failed to load file content from ${filePath}: ${error}`);\n }\n }\n\n /**\n * Resolve a potentially relative path to an absolute path\n * @param includePath - The path specified in the @include\n * @param currentFilePath - The current file's path\n * @returns Absolute path to the included file\n */\n private resolvePath(includePath: string, currentFilePath: string): string {\n const currentDir = path.dirname(currentFilePath);\n return path.resolve(currentDir, includePath);\n }\n\n /**\n * Process JSON data that's already loaded (for integration with existing code)\n * @param data - The JSON data to process\n * @param filePath - The file path (for resolving relative includes)\n * @returns Processed data with includes resolved\n */\n async processJsonData(data: any, filePath: string): Promise<any> {\n this.visitedPaths.clear();\n return this.processIncludesInternal(data, filePath);\n }\n}"]}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
|
|
10
10
|
import type { MJConfig } from '../config';
|
|
11
|
-
import { DatabaseProviderBase, UserInfo } from '@memberjunction/
|
|
11
|
+
import { DatabaseProviderBase, UserInfo } from '@memberjunction/core';
|
|
12
12
|
/**
|
|
13
13
|
* Initialize a SQLServerDataProvider with the given configuration
|
|
14
14
|
*
|
|
@@ -79,11 +79,12 @@ async function initializeProvider(config) {
|
|
|
79
79
|
user: config.dbUsername,
|
|
80
80
|
password: config.dbPassword,
|
|
81
81
|
options: {
|
|
82
|
-
encrypt: config.dbEncrypt === 'Y' || config.dbEncrypt === 'true' ||
|
|
82
|
+
encrypt: config.dbEncrypt === 'Y' || config.dbEncrypt === 'true' ||
|
|
83
|
+
config.dbHost.includes('.database.windows.net'), // Auto-detect Azure SQL
|
|
83
84
|
trustServerCertificate: config.dbTrustServerCertificate === 'Y',
|
|
84
85
|
instanceName: config.dbInstanceName,
|
|
85
|
-
enableArithAbort: true
|
|
86
|
-
}
|
|
86
|
+
enableArithAbort: true
|
|
87
|
+
}
|
|
87
88
|
};
|
|
88
89
|
// Create and connect pool
|
|
89
90
|
const pool = new sql.ConnectionPool(poolConfig);
|
|
@@ -142,17 +143,17 @@ exports.cleanupProvider = cleanupProvider;
|
|
|
142
143
|
* ```
|
|
143
144
|
*/
|
|
144
145
|
function getSystemUser() {
|
|
145
|
-
const sysUser = sqlserver_dataprovider_1.UserCache.Instance.UserByName(
|
|
146
|
+
const sysUser = sqlserver_dataprovider_1.UserCache.Instance.UserByName("System", false);
|
|
146
147
|
if (!sysUser) {
|
|
147
|
-
throw new Error(
|
|
148
|
+
throw new Error("System user not found in cache. Ensure the system user exists in the database.");
|
|
148
149
|
}
|
|
149
150
|
// Check if the System user has the Developer role
|
|
150
|
-
const hasDeveloperRole = sysUser.UserRoles && sysUser.UserRoles.some(
|
|
151
|
+
const hasDeveloperRole = sysUser.UserRoles && sysUser.UserRoles.some(userRole => userRole.Role.trim().toLowerCase() === 'developer');
|
|
151
152
|
if (!hasDeveloperRole) {
|
|
152
153
|
throw new Error("System user does not have the 'Developer' role. " +
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
"The Developer role is required for metadata sync operations. " +
|
|
155
|
+
"Please ensure the System user is assigned the Developer role in the database:\n" +
|
|
156
|
+
"* Add a record to the __mj.UserRole table linking the System user to the Developer role");
|
|
156
157
|
}
|
|
157
158
|
return sysUser;
|
|
158
159
|
}
|
|
@@ -222,7 +223,10 @@ function findEntityDirectories(dir, specificDir, directoryOrder, ignoreDirectori
|
|
|
222
223
|
// and look for entity directories in its subdirectories
|
|
223
224
|
if (config.directoryOrder) {
|
|
224
225
|
// Merge ignore directories from parent with current config
|
|
225
|
-
const mergedIgnoreDirectories = [
|
|
226
|
+
const mergedIgnoreDirectories = [
|
|
227
|
+
...(ignoreDirectories || []),
|
|
228
|
+
...(config.ignoreDirectories || [])
|
|
229
|
+
];
|
|
226
230
|
return findEntityDirectories(targetDir, undefined, config.directoryOrder, mergedIgnoreDirectories);
|
|
227
231
|
}
|
|
228
232
|
}
|
|
@@ -241,11 +245,10 @@ function findEntityDirectories(dir, specificDir, directoryOrder, ignoreDirectori
|
|
|
241
245
|
for (const entry of entries) {
|
|
242
246
|
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
243
247
|
// Check if this directory should be ignored
|
|
244
|
-
if (ignoreDirectories &&
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
})) {
|
|
248
|
+
if (ignoreDirectories && ignoreDirectories.some(pattern => {
|
|
249
|
+
// Simple pattern matching: exact name or ends with pattern
|
|
250
|
+
return entry.name === pattern || entry.name.endsWith(pattern);
|
|
251
|
+
})) {
|
|
249
252
|
continue;
|
|
250
253
|
}
|
|
251
254
|
const subDir = path.join(dir, entry.name);
|
|
@@ -261,7 +264,7 @@ function findEntityDirectories(dir, specificDir, directoryOrder, ignoreDirectori
|
|
|
261
264
|
const unorderedDirs = [];
|
|
262
265
|
// First, add directories in the specified order
|
|
263
266
|
for (const dirName of directoryOrder) {
|
|
264
|
-
const matchingDir = foundDirectories.find(
|
|
267
|
+
const matchingDir = foundDirectories.find(fullPath => path.basename(fullPath) === dirName);
|
|
265
268
|
if (matchingDir) {
|
|
266
269
|
orderedDirs.push(matchingDir);
|
|
267
270
|
}
|