@memberjunction/metadata-sync 2.84.0 → 2.86.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.
@@ -34,7 +34,6 @@ export interface RecordData {
34
34
  * @example
35
35
  * ```typescript
36
36
  * const syncEngine = new SyncEngine(systemUser);
37
- * await syncEngine.initialize();
38
37
  *
39
38
  * // Process a field value with special references
40
39
  * const value = await syncEngine.processFieldValue('@lookup:Users.Email=admin@example.com', '/path/to/base');
@@ -52,7 +51,7 @@ export declare class SyncEngine {
52
51
  * Initializes the sync engine by refreshing metadata cache
53
52
  * @returns Promise that resolves when initialization is complete
54
53
  */
55
- initialize(forceRefresh?: boolean): Promise<void>;
54
+ initialize(): Promise<void>;
56
55
  /**
57
56
  * Process special references in field values and handle complex objects
58
57
  *
@@ -88,7 +87,7 @@ export declare class SyncEngine {
88
87
  * // Returns: '{\n "items": [\n {\n "id": 1\n },\n {\n "id": 2\n }\n ]\n}'
89
88
  * ```
90
89
  */
91
- processFieldValue(value: any, baseDir: string, parentRecord?: BaseEntity | null, rootRecord?: BaseEntity | null, depth?: number): Promise<any>;
90
+ processFieldValue(value: any, baseDir: string, parentRecord?: BaseEntity | null, rootRecord?: BaseEntity | null, depth?: number, batchContext?: Map<string, BaseEntity>): Promise<any>;
92
91
  /**
93
92
  * Resolve a lookup reference to an ID, optionally creating the record if it doesn't exist
94
93
  *
@@ -115,7 +114,7 @@ export declare class SyncEngine {
115
114
  resolveLookup(entityName: string, lookupFields: Array<{
116
115
  fieldName: string;
117
116
  fieldValue: string;
118
- }>, autoCreate?: boolean, createFields?: Record<string, any>): Promise<string>;
117
+ }>, autoCreate?: boolean, createFields?: Record<string, any>, batchContext?: Map<string, BaseEntity>): Promise<string>;
119
118
  /**
120
119
  * Build cascading defaults for a file path and process field values
121
120
  *
@@ -244,49 +243,6 @@ export declare class SyncEngine {
244
243
  * ```
245
244
  */
246
245
  loadEntity(entityName: string, primaryKey: Record<string, any>): Promise<BaseEntity | null>;
247
- /**
248
- * Process JSON object with template references
249
- *
250
- * Recursively processes JSON data structures to resolve `@template` references.
251
- * Templates can be defined at any level and support:
252
- * - Single template references: `"@template:path/to/template.json"`
253
- * - Object with @template field: `{ "@template": "file.json", "override": "value" }`
254
- * - Array of templates for merging: `{ "@template": ["base.json", "overrides.json"] }`
255
- * - Nested template references within templates
256
- *
257
- * @param data - JSON data structure to process
258
- * @param baseDir - Base directory for resolving relative template paths
259
- * @returns Promise resolving to the processed data with all templates resolved
260
- * @throws Error if template file is not found or contains invalid JSON
261
- *
262
- * @example
263
- * ```typescript
264
- * // Input data with template reference
265
- * const data = {
266
- * "@template": "defaults/ai-prompt.json",
267
- * "Name": "Custom Prompt",
268
- * "Prompt": "Override the template prompt"
269
- * };
270
- *
271
- * // Resolves template and merges with overrides
272
- * const result = await syncEngine.processTemplates(data, '/path/to/dir');
273
- * ```
274
- */
275
- processTemplates(data: any, baseDir: string): Promise<any>;
276
- /**
277
- * Load and process a template file
278
- *
279
- * Loads a JSON template file from the filesystem and recursively processes any
280
- * nested template references within it. Template paths are resolved relative to
281
- * the template file's directory, enabling template composition.
282
- *
283
- * @param templatePath - Path to the template file (relative or absolute)
284
- * @param baseDir - Base directory for resolving relative paths
285
- * @returns Promise resolving to the processed template content
286
- * @throws Error if template file not found or contains invalid JSON
287
- * @private
288
- */
289
- private loadAndProcessTemplate;
290
246
  /**
291
247
  * Process file content with {@include} references
292
248
  *
@@ -297,8 +253,8 @@ export declare class SyncEngine {
297
253
  * - Circular reference detection to prevent infinite loops
298
254
  * - Seamless content substitution maintaining surrounding text
299
255
  *
300
- * @param filePath - Path to the file being processed
301
256
  * @param content - The file content to process
257
+ * @param filePath - Path to the file being processed
302
258
  * @param visitedPaths - Set of already visited file paths for circular reference detection
303
259
  * @returns Promise resolving to the content with all includes resolved
304
260
  * @throws Error if circular reference detected or included file not found
@@ -314,33 +270,4 @@ export declare class SyncEngine {
314
270
  * ```
315
271
  */
316
272
  private processFileContentWithIncludes;
317
- /**
318
- * Deep merge two objects with target taking precedence
319
- *
320
- * Recursively merges two objects, with values from the target object overriding
321
- * values from the source object. Arrays and primitive values are not merged but
322
- * replaced entirely by the target value. Undefined values in target are skipped.
323
- *
324
- * @param source - Base object to merge from
325
- * @param target - Object with values that override source
326
- * @returns New object with merged values
327
- * @private
328
- *
329
- * @example
330
- * ```typescript
331
- * const source = {
332
- * a: 1,
333
- * b: { x: 10, y: 20 },
334
- * c: [1, 2, 3]
335
- * };
336
- * const target = {
337
- * a: 2,
338
- * b: { y: 30, z: 40 },
339
- * d: 'new'
340
- * };
341
- * const result = deepMerge(source, target);
342
- * // Result: { a: 2, b: { x: 10, y: 30, z: 40 }, c: [1, 2, 3], d: 'new' }
343
- * ```
344
- */
345
- private deepMerge;
346
273
  }
@@ -25,7 +25,6 @@ const core_1 = require("@memberjunction/core");
25
25
  * @example
26
26
  * ```typescript
27
27
  * const syncEngine = new SyncEngine(systemUser);
28
- * await syncEngine.initialize();
29
28
  *
30
29
  * // Process a field value with special references
31
30
  * const value = await syncEngine.processFieldValue('@lookup:Users.Email=admin@example.com', '/path/to/base');
@@ -46,10 +45,9 @@ class SyncEngine {
46
45
  * Initializes the sync engine by refreshing metadata cache
47
46
  * @returns Promise that resolves when initialization is complete
48
47
  */
49
- async initialize(forceRefresh = false) {
50
- if (forceRefresh) {
51
- await this.metadata.Refresh();
52
- }
48
+ async initialize() {
49
+ // Currently no initialization needed as metadata is managed globally
50
+ // Keeping this method for backward compatibility and future use
53
51
  }
54
52
  /**
55
53
  * Process special references in field values and handle complex objects
@@ -86,7 +84,7 @@ class SyncEngine {
86
84
  * // Returns: '{\n "items": [\n {\n "id": 1\n },\n {\n "id": 2\n }\n ]\n}'
87
85
  * ```
88
86
  */
89
- async processFieldValue(value, baseDir, parentRecord, rootRecord, depth = 0) {
87
+ async processFieldValue(value, baseDir, parentRecord, rootRecord, depth = 0, batchContext) {
90
88
  // Check recursion depth limit
91
89
  const MAX_RECURSION_DEPTH = 50;
92
90
  if (depth > MAX_RECURSION_DEPTH) {
@@ -127,7 +125,7 @@ class SyncEngine {
127
125
  if (await fs_extra_1.default.pathExists(fullPath)) {
128
126
  const fileContent = await fs_extra_1.default.readFile(fullPath, 'utf-8');
129
127
  // Process the file content for {@include} references
130
- return await this.processFileContentWithIncludes(fullPath, fileContent);
128
+ return await this.processFileContentWithIncludes(fileContent, fullPath);
131
129
  }
132
130
  else {
133
131
  throw new Error(`File not found: ${fullPath}`);
@@ -168,7 +166,7 @@ class SyncEngine {
168
166
  }
169
167
  const [, fieldName, fieldValue] = fieldMatch;
170
168
  // Recursively process the field value to resolve any nested @ commands
171
- const processedValue = await this.processFieldValue(fieldValue.trim(), baseDir, parentRecord, rootRecord, depth + 1);
169
+ const processedValue = await this.processFieldValue(fieldValue.trim(), baseDir, parentRecord, rootRecord, depth + 1, batchContext);
172
170
  lookupFields.push({ fieldName: fieldName.trim(), fieldValue: processedValue });
173
171
  }
174
172
  if (lookupFields.length === 0) {
@@ -184,11 +182,11 @@ class SyncEngine {
184
182
  if (key && val) {
185
183
  const decodedVal = decodeURIComponent(val);
186
184
  // Recursively process the field value to resolve any nested @ commands
187
- createFields[key] = await this.processFieldValue(decodedVal, baseDir, parentRecord, rootRecord, depth + 1);
185
+ createFields[key] = await this.processFieldValue(decodedVal, baseDir, parentRecord, rootRecord, depth + 1, batchContext);
188
186
  }
189
187
  }
190
188
  }
191
- return await this.resolveLookup(entityName, lookupFields, hasCreate, createFields);
189
+ return await this.resolveLookup(entityName, lookupFields, hasCreate, createFields, batchContext);
192
190
  }
193
191
  // Check for @env: reference
194
192
  if (value.startsWith('@env:')) {
@@ -224,8 +222,36 @@ class SyncEngine {
224
222
  * });
225
223
  * ```
226
224
  */
227
- async resolveLookup(entityName, lookupFields, autoCreate = false, createFields = {}) {
228
- // Debug logging handled by caller if needed
225
+ async resolveLookup(entityName, lookupFields, autoCreate = false, createFields = {}, batchContext) {
226
+ // First check batch context for in-memory entities
227
+ if (batchContext) {
228
+ // Try to find the entity in batch context
229
+ for (const [, entity] of batchContext) {
230
+ // Check if this is the right entity type
231
+ if (entity.EntityInfo?.Name === entityName) {
232
+ // Check if all lookup fields match
233
+ let allMatch = true;
234
+ for (const { fieldName, fieldValue } of lookupFields) {
235
+ const entityValue = entity.Get(fieldName);
236
+ const normalizedEntityValue = entityValue?.toString() || '';
237
+ const normalizedLookupValue = fieldValue?.toString() || '';
238
+ if (normalizedEntityValue !== normalizedLookupValue) {
239
+ allMatch = false;
240
+ break;
241
+ }
242
+ }
243
+ if (allMatch) {
244
+ // Found in batch context, return primary key
245
+ const entityInfo = this.metadata.EntityByName(entityName);
246
+ if (entityInfo && entityInfo.PrimaryKeys.length > 0) {
247
+ const pkeyField = entityInfo.PrimaryKeys[0].Name;
248
+ return entity.Get(pkeyField);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ // Not found in batch context, check database
229
255
  const rv = new core_1.RunView();
230
256
  const entityInfo = this.metadata.EntityByName(entityName);
231
257
  if (!entityInfo) {
@@ -565,103 +591,6 @@ class SyncEngine {
565
591
  const loaded = await entity.InnerLoad(compositeKey);
566
592
  return loaded ? entity : null;
567
593
  }
568
- /**
569
- * Process JSON object with template references
570
- *
571
- * Recursively processes JSON data structures to resolve `@template` references.
572
- * Templates can be defined at any level and support:
573
- * - Single template references: `"@template:path/to/template.json"`
574
- * - Object with @template field: `{ "@template": "file.json", "override": "value" }`
575
- * - Array of templates for merging: `{ "@template": ["base.json", "overrides.json"] }`
576
- * - Nested template references within templates
577
- *
578
- * @param data - JSON data structure to process
579
- * @param baseDir - Base directory for resolving relative template paths
580
- * @returns Promise resolving to the processed data with all templates resolved
581
- * @throws Error if template file is not found or contains invalid JSON
582
- *
583
- * @example
584
- * ```typescript
585
- * // Input data with template reference
586
- * const data = {
587
- * "@template": "defaults/ai-prompt.json",
588
- * "Name": "Custom Prompt",
589
- * "Prompt": "Override the template prompt"
590
- * };
591
- *
592
- * // Resolves template and merges with overrides
593
- * const result = await syncEngine.processTemplates(data, '/path/to/dir');
594
- * ```
595
- */
596
- async processTemplates(data, baseDir) {
597
- // Handle arrays
598
- if (Array.isArray(data)) {
599
- const processedArray = [];
600
- for (const item of data) {
601
- processedArray.push(await this.processTemplates(item, baseDir));
602
- }
603
- return processedArray;
604
- }
605
- // Handle objects
606
- if (data && typeof data === 'object') {
607
- // Check for @template reference
608
- if (typeof data === 'string' && data.startsWith('@template:')) {
609
- const templatePath = data.substring(10);
610
- return await this.loadAndProcessTemplate(templatePath, baseDir);
611
- }
612
- // Process object with possible @template field
613
- const processed = {};
614
- let templateData = {};
615
- // First, check if there's a @template field to process
616
- if (data['@template']) {
617
- const templates = Array.isArray(data['@template']) ? data['@template'] : [data['@template']];
618
- // Process templates in order, merging them
619
- for (const templateRef of templates) {
620
- const templateContent = await this.loadAndProcessTemplate(templateRef, baseDir);
621
- templateData = this.deepMerge(templateData, templateContent);
622
- }
623
- }
624
- // Process all other fields
625
- for (const [key, value] of Object.entries(data)) {
626
- if (key === '@template')
627
- continue; // Skip the template field itself
628
- // Process the value recursively
629
- processed[key] = await this.processTemplates(value, baseDir);
630
- }
631
- // Merge template data with processed data (processed data takes precedence)
632
- return this.deepMerge(templateData, processed);
633
- }
634
- // Return primitive values as-is
635
- return data;
636
- }
637
- /**
638
- * Load and process a template file
639
- *
640
- * Loads a JSON template file from the filesystem and recursively processes any
641
- * nested template references within it. Template paths are resolved relative to
642
- * the template file's directory, enabling template composition.
643
- *
644
- * @param templatePath - Path to the template file (relative or absolute)
645
- * @param baseDir - Base directory for resolving relative paths
646
- * @returns Promise resolving to the processed template content
647
- * @throws Error if template file not found or contains invalid JSON
648
- * @private
649
- */
650
- async loadAndProcessTemplate(templatePath, baseDir) {
651
- const fullPath = path_1.default.resolve(baseDir, templatePath);
652
- if (!await fs_extra_1.default.pathExists(fullPath)) {
653
- throw new Error(`Template file not found: ${fullPath}`);
654
- }
655
- try {
656
- const templateContent = await fs_extra_1.default.readJson(fullPath);
657
- // Recursively process any nested templates
658
- const templateDir = path_1.default.dirname(fullPath);
659
- return await this.processTemplates(templateContent, templateDir);
660
- }
661
- catch (error) {
662
- throw new Error(`Failed to load template ${fullPath}: ${error}`);
663
- }
664
- }
665
594
  /**
666
595
  * Process file content with {@include} references
667
596
  *
@@ -672,8 +601,8 @@ class SyncEngine {
672
601
  * - Circular reference detection to prevent infinite loops
673
602
  * - Seamless content substitution maintaining surrounding text
674
603
  *
675
- * @param filePath - Path to the file being processed
676
604
  * @param content - The file content to process
605
+ * @param filePath - Path to the file being processed
677
606
  * @param visitedPaths - Set of already visited file paths for circular reference detection
678
607
  * @returns Promise resolving to the content with all includes resolved
679
608
  * @throws Error if circular reference detected or included file not found
@@ -688,7 +617,7 @@ class SyncEngine {
688
617
  * // 'This is a [contents of header.md] example'
689
618
  * ```
690
619
  */
691
- async processFileContentWithIncludes(filePath, content, visitedPaths = new Set()) {
620
+ async processFileContentWithIncludes(content, filePath, visitedPaths = new Set()) {
692
621
  // Add current file to visited set
693
622
  const absolutePath = path_1.default.resolve(filePath);
694
623
  if (visitedPaths.has(absolutePath)) {
@@ -715,7 +644,7 @@ class SyncEngine {
715
644
  // Read the included file
716
645
  const includedContent = await fs_extra_1.default.readFile(resolvedPath, 'utf-8');
717
646
  // Recursively process the included content for nested includes
718
- const processedInclude = await this.processFileContentWithIncludes(resolvedPath, includedContent, new Set(visitedPaths) // Pass a copy to allow the same file in different branches
647
+ const processedInclude = await this.processFileContentWithIncludes(includedContent, resolvedPath, new Set(visitedPaths) // Pass a copy to allow the same file in different branches
719
648
  );
720
649
  // Replace the {@include} reference with the processed content
721
650
  processedContent = processedContent.replace(fullMatch, processedInclude);
@@ -727,65 +656,6 @@ class SyncEngine {
727
656
  }
728
657
  return processedContent;
729
658
  }
730
- /**
731
- * Deep merge two objects with target taking precedence
732
- *
733
- * Recursively merges two objects, with values from the target object overriding
734
- * values from the source object. Arrays and primitive values are not merged but
735
- * replaced entirely by the target value. Undefined values in target are skipped.
736
- *
737
- * @param source - Base object to merge from
738
- * @param target - Object with values that override source
739
- * @returns New object with merged values
740
- * @private
741
- *
742
- * @example
743
- * ```typescript
744
- * const source = {
745
- * a: 1,
746
- * b: { x: 10, y: 20 },
747
- * c: [1, 2, 3]
748
- * };
749
- * const target = {
750
- * a: 2,
751
- * b: { y: 30, z: 40 },
752
- * d: 'new'
753
- * };
754
- * const result = deepMerge(source, target);
755
- * // Result: { a: 2, b: { x: 10, y: 30, z: 40 }, c: [1, 2, 3], d: 'new' }
756
- * ```
757
- */
758
- deepMerge(source, target) {
759
- if (!source)
760
- return target;
761
- if (!target)
762
- return source;
763
- // If target is not an object, it completely overrides source
764
- if (typeof target !== 'object' || target === null || Array.isArray(target)) {
765
- return target;
766
- }
767
- // If source is not an object, target wins
768
- if (typeof source !== 'object' || source === null || Array.isArray(source)) {
769
- return target;
770
- }
771
- // Both are objects, merge them
772
- const result = { ...source };
773
- for (const [key, value] of Object.entries(target)) {
774
- if (value === undefined) {
775
- continue; // Skip undefined values
776
- }
777
- if (typeof value === 'object' && value !== null && !Array.isArray(value) &&
778
- typeof result[key] === 'object' && result[key] !== null && !Array.isArray(result[key])) {
779
- // Both are objects, merge recursively
780
- result[key] = this.deepMerge(result[key], value);
781
- }
782
- else {
783
- // Otherwise, target value wins
784
- result[key] = value;
785
- }
786
- }
787
- return result;
788
- }
789
659
  }
790
660
  exports.SyncEngine = SyncEngine;
791
661
  //# sourceMappingURL=sync-engine.js.map