@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.
- package/dist/lib/record-dependency-analyzer.d.ts +77 -0
- package/dist/lib/record-dependency-analyzer.js +427 -0
- package/dist/lib/record-dependency-analyzer.js.map +1 -0
- package/dist/lib/sync-engine.d.ts +4 -77
- package/dist/lib/sync-engine.js +41 -171
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/services/PushService.d.ts +2 -16
- package/dist/services/PushService.js +139 -401
- package/dist/services/PushService.js.map +1 -1
- package/package.json +7 -7
|
@@ -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(
|
|
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
|
}
|
package/dist/lib/sync-engine.js
CHANGED
|
@@ -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(
|
|
50
|
-
|
|
51
|
-
|
|
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(
|
|
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
|
-
//
|
|
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(
|
|
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(
|
|
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
|