@memberjunction/metadata-sync 2.95.0 → 2.97.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 +73 -16
- package/dist/lib/json-preprocessor.js +18 -5
- package/dist/lib/json-preprocessor.js.map +1 -1
- package/dist/lib/json-write-helper.js +4 -1
- package/dist/lib/json-write-helper.js.map +1 -1
- package/dist/lib/sync-engine.d.ts +9 -0
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/services/PushService.d.ts +6 -1
- package/dist/services/PushService.js +140 -7
- package/dist/services/PushService.js.map +1 -1
- package/dist/services/ValidationService.d.ts +12 -0
- package/dist/services/ValidationService.js +167 -2
- package/dist/services/ValidationService.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ The Metadata Sync tool bridges the gap between database-stored metadata and file
|
|
|
64
64
|
- **External files**: Store large text fields (prompts, templates, etc.) in appropriate formats (.md, .html, .sql)
|
|
65
65
|
- **File references**: Use `@file:filename.ext` to link external files from JSON
|
|
66
66
|
|
|
67
|
-
### Embedded Collections
|
|
67
|
+
### Embedded Collections
|
|
68
68
|
- **Related Entities**: Store related records as arrays within parent JSON files
|
|
69
69
|
- **Hierarchical References**: Use `@parent:` and `@root:` to reference parent/root entity fields
|
|
70
70
|
- **Automatic Metadata**: Related entities maintain their own primaryKey and sync metadata
|
|
@@ -526,7 +526,7 @@ Each JSON file contains one record:
|
|
|
526
526
|
}
|
|
527
527
|
```
|
|
528
528
|
|
|
529
|
-
#### Multiple Records per File
|
|
529
|
+
#### Multiple Records per File
|
|
530
530
|
JSON files can contain arrays of records:
|
|
531
531
|
```json
|
|
532
532
|
[
|
|
@@ -604,7 +604,7 @@ metadata/
|
|
|
604
604
|
}
|
|
605
605
|
```
|
|
606
606
|
|
|
607
|
-
### Record with Embedded Collections
|
|
607
|
+
### Record with Embedded Collections
|
|
608
608
|
```json
|
|
609
609
|
{
|
|
610
610
|
"fields": {
|
|
@@ -706,6 +706,63 @@ The tool automatically detects primary key fields from entity metadata:
|
|
|
706
706
|
- **Auto-detection**: Tool reads entity metadata to determine primary key structure
|
|
707
707
|
- **No hardcoding**: Works with any primary key field name(s)
|
|
708
708
|
|
|
709
|
+
### deleteRecord Directive
|
|
710
|
+
The tool now supports deleting records from the database using a special `deleteRecord` directive in JSON files. This allows you to remove obsolete records as part of your metadata sync workflow:
|
|
711
|
+
|
|
712
|
+
#### How to Delete a Record
|
|
713
|
+
1. Add a `deleteRecord` section to any record JSON file
|
|
714
|
+
2. Set `delete: true` to mark the record for deletion
|
|
715
|
+
3. Run `mj sync push` to execute the deletion
|
|
716
|
+
4. The tool will update the JSON with a deletion timestamp
|
|
717
|
+
|
|
718
|
+
#### Syntax
|
|
719
|
+
```json
|
|
720
|
+
{
|
|
721
|
+
"fields": {
|
|
722
|
+
"Name": "Obsolete Prompt",
|
|
723
|
+
"Description": "This prompt is no longer needed"
|
|
724
|
+
},
|
|
725
|
+
"primaryKey": {
|
|
726
|
+
"ID": "550e8400-e29b-41d4-a716-446655440000"
|
|
727
|
+
},
|
|
728
|
+
"deleteRecord": {
|
|
729
|
+
"delete": true
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
#### After Deletion
|
|
735
|
+
After successfully deleting the record, the tool updates the JSON file:
|
|
736
|
+
```json
|
|
737
|
+
{
|
|
738
|
+
"fields": {
|
|
739
|
+
"Name": "Obsolete Prompt",
|
|
740
|
+
"Description": "This prompt is no longer needed"
|
|
741
|
+
},
|
|
742
|
+
"primaryKey": {
|
|
743
|
+
"ID": "550e8400-e29b-41d4-a716-446655440000"
|
|
744
|
+
},
|
|
745
|
+
"deleteRecord": {
|
|
746
|
+
"delete": true,
|
|
747
|
+
"deletedAt": "2024-01-15T14:30:00.000Z"
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
#### Important Notes
|
|
753
|
+
- **Primary key required**: You must specify the `primaryKey` to identify which record to delete
|
|
754
|
+
- **One-time operation**: Once `deletedAt` is set, the deletion won't be attempted again
|
|
755
|
+
- **SQL logging**: Delete operations are included in SQL logs when enabled
|
|
756
|
+
- **Foreign key constraints**: Deletions may fail if other records reference this record
|
|
757
|
+
- **Dry-run support**: Use `--dry-run` to preview what would be deleted
|
|
758
|
+
- **Takes precedence**: If `deleteRecord` is present, normal create/update operations are skipped
|
|
759
|
+
|
|
760
|
+
#### Use Cases
|
|
761
|
+
- Removing deprecated prompts, templates, or configurations
|
|
762
|
+
- Cleaning up test data
|
|
763
|
+
- Synchronizing deletions across environments
|
|
764
|
+
- Maintaining clean metadata through version control
|
|
765
|
+
|
|
709
766
|
### @file: References
|
|
710
767
|
When a field value starts with `@file:`, the tool will:
|
|
711
768
|
1. Read content from the specified file for push operations
|
|
@@ -743,7 +800,7 @@ Examples:
|
|
|
743
800
|
- `@lookup:AI Prompt Categories.Name=Examples?create` - Creates if missing
|
|
744
801
|
- `@lookup:AI Prompt Categories.Name=Examples?create&Description=Example prompts` - Creates with description
|
|
745
802
|
|
|
746
|
-
#### Multi-Field Lookups
|
|
803
|
+
#### Multi-Field Lookups
|
|
747
804
|
When you need to match records based on multiple criteria, use the multi-field syntax:
|
|
748
805
|
```json
|
|
749
806
|
{
|
|
@@ -754,13 +811,13 @@ When you need to match records based on multiple criteria, use the multi-field s
|
|
|
754
811
|
|
|
755
812
|
This ensures you get the exact record you want when multiple records might have the same value in a single field.
|
|
756
813
|
|
|
757
|
-
### @parent: References
|
|
814
|
+
### @parent: References
|
|
758
815
|
Reference fields from the immediate parent entity in embedded collections:
|
|
759
816
|
- `@parent:ID` - Get the parent's ID field
|
|
760
817
|
- `@parent:Name` - Get the parent's Name field
|
|
761
818
|
- Works with any field from the parent entity
|
|
762
819
|
|
|
763
|
-
### @root: References
|
|
820
|
+
### @root: References
|
|
764
821
|
Reference fields from the root entity in nested structures:
|
|
765
822
|
- `@root:ID` - Get the root entity's ID
|
|
766
823
|
- `@root:CategoryID` - Get the root's CategoryID
|
|
@@ -1122,7 +1179,7 @@ This ensures that included content can contain other special references that wil
|
|
|
1122
1179
|
- Those @file references are resolved to their actual content
|
|
1123
1180
|
- If the @file points to a JSON file with @include directives, those are also processed
|
|
1124
1181
|
|
|
1125
|
-
### @template: References
|
|
1182
|
+
### @template: References
|
|
1126
1183
|
Enable JSON template composition for reusable configurations:
|
|
1127
1184
|
|
|
1128
1185
|
#### String Template Reference
|
|
@@ -1211,7 +1268,7 @@ mj sync pull --entity="AI Prompts"
|
|
|
1211
1268
|
# Pull specific records by filter
|
|
1212
1269
|
mj sync pull --entity="AI Prompts" --filter="CategoryID='customer-service-id'"
|
|
1213
1270
|
|
|
1214
|
-
# Pull multiple records into a single file
|
|
1271
|
+
# Pull multiple records into a single file
|
|
1215
1272
|
mj sync pull --entity="AI Prompts" --multi-file="all-prompts"
|
|
1216
1273
|
mj sync pull --entity="AI Prompts" --filter="Status='Active'" --multi-file="active-prompts.json"
|
|
1217
1274
|
|
|
@@ -1221,14 +1278,14 @@ mj sync push
|
|
|
1221
1278
|
# Push only specific entity directory
|
|
1222
1279
|
mj sync push --dir="ai-prompts"
|
|
1223
1280
|
|
|
1224
|
-
# Push with verbose output
|
|
1281
|
+
# Push with verbose output
|
|
1225
1282
|
mj sync push -v
|
|
1226
1283
|
mj sync push --verbose
|
|
1227
1284
|
|
|
1228
1285
|
# Dry run to see what would change
|
|
1229
1286
|
mj sync push --dry-run
|
|
1230
1287
|
|
|
1231
|
-
# Push with parallel processing
|
|
1288
|
+
# Push with parallel processing
|
|
1232
1289
|
mj sync push --parallel-batch-size=20 # Process 20 records in parallel (default: 10, max: 50)
|
|
1233
1290
|
|
|
1234
1291
|
# Show status of local vs database
|
|
@@ -1257,7 +1314,7 @@ Configuration follows a hierarchical structure:
|
|
|
1257
1314
|
- **Entity configs**: Each entity directory has its own config defining the entity type
|
|
1258
1315
|
- **Inheritance**: All files within an entity directory are treated as records of that entity type
|
|
1259
1316
|
|
|
1260
|
-
### Parallel Processing
|
|
1317
|
+
### Parallel Processing
|
|
1261
1318
|
|
|
1262
1319
|
MetadataSync now supports parallel processing of records during push operations, significantly improving performance for large datasets.
|
|
1263
1320
|
|
|
@@ -1307,7 +1364,7 @@ mj sync push --parallel-batch-size=1
|
|
|
1307
1364
|
- Working with complex dependencies
|
|
1308
1365
|
- Limited database connection pools
|
|
1309
1366
|
|
|
1310
|
-
### Directory Processing Order
|
|
1367
|
+
### Directory Processing Order
|
|
1311
1368
|
|
|
1312
1369
|
The MetadataSync tool now supports custom directory processing order to handle dependencies between entity types. This feature ensures that dependent entities are processed in the correct order.
|
|
1313
1370
|
|
|
@@ -1385,7 +1442,7 @@ In this example:
|
|
|
1385
1442
|
- Production subdirectory further adds "drafts"
|
|
1386
1443
|
- All patterns are cumulative, so production inherits all parent ignores
|
|
1387
1444
|
|
|
1388
|
-
### SQL Logging
|
|
1445
|
+
### SQL Logging
|
|
1389
1446
|
|
|
1390
1447
|
The MetadataSync tool now supports SQL logging for capturing all database operations during push commands. This feature is useful for:
|
|
1391
1448
|
- Creating migration files from MetadataSync operations
|
|
@@ -1508,7 +1565,7 @@ The `filterPatterns` option allows you to include or exclude specific SQL statem
|
|
|
1508
1565
|
- `*pattern` - Ends with pattern
|
|
1509
1566
|
- `pattern` - Exact match
|
|
1510
1567
|
|
|
1511
|
-
### User Role Validation
|
|
1568
|
+
### User Role Validation
|
|
1512
1569
|
|
|
1513
1570
|
MetadataSync now supports validating UserID fields against specific roles in the MemberJunction system. This ensures that only users with appropriate roles can be referenced in metadata files.
|
|
1514
1571
|
|
|
@@ -1662,7 +1719,7 @@ The validation will:
|
|
|
1662
1719
|
}
|
|
1663
1720
|
```
|
|
1664
1721
|
|
|
1665
|
-
## Embedded Collections
|
|
1722
|
+
## Embedded Collections
|
|
1666
1723
|
|
|
1667
1724
|
The tool now supports managing related entities as embedded collections within parent JSON files. This is ideal for entities that have a strong parent-child relationship.
|
|
1668
1725
|
|
|
@@ -1672,7 +1729,7 @@ The tool now supports managing related entities as embedded collections within p
|
|
|
1672
1729
|
- **Cleaner Organization**: Fewer files to manage
|
|
1673
1730
|
- **Relationship Clarity**: Visual representation of data relationships
|
|
1674
1731
|
|
|
1675
|
-
## Recursive Patterns
|
|
1732
|
+
## Recursive Patterns
|
|
1676
1733
|
|
|
1677
1734
|
The tool now supports automatic recursive patterns for self-referencing entities, eliminating the need to manually define each nesting level for hierarchical data structures.
|
|
1678
1735
|
|
|
@@ -166,6 +166,11 @@ class JsonPreprocessor {
|
|
|
166
166
|
throw new Error(`Circular reference detected: ${absolutePath} is already being processed`);
|
|
167
167
|
}
|
|
168
168
|
if (!await fs_extra_1.default.pathExists(filePath)) {
|
|
169
|
+
// Log error details before throwing
|
|
170
|
+
console.error(`\n❌ INCLUDE FILE NOT FOUND`);
|
|
171
|
+
console.error(` Referenced file: ${filePath}`);
|
|
172
|
+
console.error(` Reference type: @include`);
|
|
173
|
+
console.error(` Tip: Check that the file path is correct relative to the including file\n`);
|
|
169
174
|
throw new Error(`Include file not found: ${filePath}`);
|
|
170
175
|
}
|
|
171
176
|
// Add to visited paths before processing
|
|
@@ -187,9 +192,12 @@ class JsonPreprocessor {
|
|
|
187
192
|
*/
|
|
188
193
|
async loadFileContent(filePath) {
|
|
189
194
|
if (!await fs_extra_1.default.pathExists(filePath)) {
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
// Log error details before throwing
|
|
196
|
+
console.error(`\n❌ FILE NOT FOUND`);
|
|
197
|
+
console.error(` Referenced file: ${filePath}`);
|
|
198
|
+
console.error(` Reference type: @file:`);
|
|
199
|
+
console.error(` Tip: Check that the file path is correct and the file exists\n`);
|
|
200
|
+
throw new Error(`File not found: ${filePath} (referenced via @file:)`);
|
|
193
201
|
}
|
|
194
202
|
try {
|
|
195
203
|
if (filePath.endsWith('.json')) {
|
|
@@ -212,8 +220,13 @@ class JsonPreprocessor {
|
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
catch (error) {
|
|
215
|
-
//
|
|
216
|
-
|
|
223
|
+
// Log error details before re-throwing
|
|
224
|
+
console.error(`\n❌ FILE LOAD ERROR`);
|
|
225
|
+
console.error(` Failed to load file: ${filePath}`);
|
|
226
|
+
console.error(` Error: ${error}`);
|
|
227
|
+
console.error(` Tip: Check file permissions and that the file is not corrupted\n`);
|
|
228
|
+
// Re-throw with enhanced context
|
|
229
|
+
throw new Error(`Failed to load file content from ${filePath}: ${error}`);
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
/**
|
|
@@ -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 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 // Return the original @file reference if file doesn't exist\n // This matches the behavior of SyncEngine.processFieldValue\n return `@file:${path.relative(path.dirname(filePath), filePath)}`;\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 // On error, return the original @file reference\n return `@file:${path.relative(path.dirname(filePath), filePath)}`;\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';\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}"]}
|
|
@@ -36,7 +36,7 @@ class JsonWriteHelper {
|
|
|
36
36
|
if (data.fields !== undefined) {
|
|
37
37
|
// This is a RecordData object - rebuild with correct order
|
|
38
38
|
const ordered = {};
|
|
39
|
-
// Add properties in desired order: fields, relatedEntities, primaryKey, sync
|
|
39
|
+
// Add properties in desired order: fields, relatedEntities, primaryKey, sync, deleteRecord
|
|
40
40
|
if (data.fields !== undefined) {
|
|
41
41
|
ordered.fields = this.normalizeRecordDataOrder(data.fields);
|
|
42
42
|
}
|
|
@@ -49,6 +49,9 @@ class JsonWriteHelper {
|
|
|
49
49
|
if (data.sync !== undefined) {
|
|
50
50
|
ordered.sync = this.normalizeRecordDataOrder(data.sync);
|
|
51
51
|
}
|
|
52
|
+
if (data.deleteRecord !== undefined) {
|
|
53
|
+
ordered.deleteRecord = this.normalizeRecordDataOrder(data.deleteRecord);
|
|
54
|
+
}
|
|
52
55
|
return ordered;
|
|
53
56
|
}
|
|
54
57
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-write-helper.js","sourceRoot":"","sources":["../../src/lib/json-write-helper.ts"],"names":[],"mappings":";;;;;;AAAA,wDAAgD;AAGhD;;;GAGG;AACH,MAAa,eAAe;IAE1B;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,QAAgB,EAAE,IAA+B;QACnF,wEAAwE;QACxE,MAAM,cAAc,GAAG,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;QAE3D,yCAAyC;QACzC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3D,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,wBAAwB,CAAC,IAAS;QAC/C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/D,CAAC;QAED,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrC,+CAA+C;YAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC9B,2DAA2D;gBAC3D,MAAM,OAAO,GAAQ,EAAE,CAAC;gBAExB,
|
|
1
|
+
{"version":3,"file":"json-write-helper.js","sourceRoot":"","sources":["../../src/lib/json-write-helper.ts"],"names":[],"mappings":"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","sourcesContent":["import fs, { JsonWriteOptions } from 'fs-extra';\nimport { RecordData } from './sync-engine';\n\n/**\n * Helper class for writing JSON files with consistent property ordering for RecordData objects.\n * Ensures that all metadata files have the same property order: fields, relatedEntities, primaryKey, sync\n */\nexport class JsonWriteHelper {\n \n /**\n * Write RecordData or arrays of RecordData with consistent property ordering\n * @param filePath - Path to the JSON file to write\n * @param data - RecordData object or array of RecordData objects\n */\n static async writeOrderedRecordData(filePath: string, data: RecordData | RecordData[]): Promise<void> {\n // Pre-process the data to ensure correct ordering before JSON.stringify\n const normalizedData = this.normalizeRecordDataOrder(data);\n \n // Use JSON.stringify with proper spacing\n const jsonString = JSON.stringify(normalizedData, null, 2);\n await fs.writeFile(filePath, jsonString, 'utf8');\n }\n\n /**\n * Recursively normalize RecordData objects to ensure correct property ordering\n * @param data - RecordData object, array of RecordData objects, or any nested structure\n * @returns Normalized data with consistent property ordering\n */\n private static normalizeRecordDataOrder(data: any): any {\n if (Array.isArray(data)) {\n return data.map(item => this.normalizeRecordDataOrder(item));\n }\n \n if (data && typeof data === 'object') {\n // Check if this looks like a RecordData object\n if (data.fields !== undefined) {\n // This is a RecordData object - rebuild with correct order\n const ordered: any = {};\n \n // Add properties in desired order: fields, relatedEntities, primaryKey, sync, deleteRecord\n if (data.fields !== undefined) {\n ordered.fields = this.normalizeRecordDataOrder(data.fields);\n }\n if (data.relatedEntities !== undefined) {\n ordered.relatedEntities = this.normalizeRecordDataOrder(data.relatedEntities);\n }\n if (data.primaryKey !== undefined) {\n ordered.primaryKey = this.normalizeRecordDataOrder(data.primaryKey);\n }\n if (data.sync !== undefined) {\n ordered.sync = this.normalizeRecordDataOrder(data.sync);\n }\n if (data.deleteRecord !== undefined) {\n ordered.deleteRecord = this.normalizeRecordDataOrder(data.deleteRecord);\n }\n \n return ordered;\n } else {\n // Regular object - recursively process properties\n const processed: any = {};\n for (const [key, value] of Object.entries(data)) {\n processed[key] = this.normalizeRecordDataOrder(value);\n }\n return processed;\n }\n }\n \n return data;\n }\n\n /**\n * Create a RecordData object with explicit property ordering for consistent JSON output\n * @param fields - Entity field data\n * @param relatedEntities - Related entity data\n * @param primaryKey - Primary key data\n * @param sync - Sync metadata\n * @returns RecordData object with guaranteed property order\n */\n static createOrderedRecordData(\n fields: Record<string, any>,\n relatedEntities: Record<string, RecordData[]>,\n primaryKey: Record<string, any>,\n sync: { lastModified: string; checksum: string }\n ): RecordData {\n // Use a Map to preserve insertion order, then convert to object\n const orderedProps = new Map<string, any>();\n \n // Add properties in the desired order\n orderedProps.set('fields', fields);\n \n if (Object.keys(relatedEntities).length > 0) {\n orderedProps.set('relatedEntities', relatedEntities);\n }\n \n orderedProps.set('primaryKey', primaryKey);\n orderedProps.set('sync', sync);\n \n // Convert Map to object while preserving order\n const recordData = {} as RecordData;\n for (const [key, value] of orderedProps) {\n (recordData as any)[key] = value;\n }\n \n return recordData;\n }\n\n /**\n * Write regular JSON data (non-RecordData) with standard formatting\n * @param filePath - Path to the JSON file to write\n * @param data - Any JSON-serializable data\n * @param options - Optional JSON write options\n */\n static async writeJson(filePath: string, data: any, options?: JsonWriteOptions): Promise<void> {\n const defaultOptions = { spaces: 2 };\n const writeOptions = typeof options === 'object' && options !== null \n ? { ...defaultOptions, ...options }\n : defaultOptions;\n await fs.writeJson(filePath, data, writeOptions);\n }\n}"]}
|
|
@@ -26,6 +26,15 @@ export interface RecordData {
|
|
|
26
26
|
/** SHA256 checksum of the fields object */
|
|
27
27
|
checksum: string;
|
|
28
28
|
};
|
|
29
|
+
/** Delete record directive for removing records from the database */
|
|
30
|
+
deleteRecord?: {
|
|
31
|
+
/** Flag to indicate this record should be deleted */
|
|
32
|
+
delete: boolean;
|
|
33
|
+
/** ISO timestamp of when the deletion was performed */
|
|
34
|
+
deletedAt?: string;
|
|
35
|
+
/** Flag to indicate the record was not found when attempting deletion */
|
|
36
|
+
notFound?: boolean;
|
|
37
|
+
};
|
|
29
38
|
}
|
|
30
39
|
/**
|
|
31
40
|
* Core engine for synchronizing MemberJunction metadata between database and files
|