@memberjunction/metadata-sync 4.0.0 → 4.2.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 CHANGED
@@ -1,4 +1,4 @@
1
- # MemberJunction Metadata Sync
1
+ # @memberjunction/metadata-sync
2
2
 
3
3
  A library for synchronizing MemberJunction database metadata with local file system representations. This library is integrated into the MemberJunction CLI (`mj`) and is accessed through `mj sync` commands. It enables developers and non-technical users to manage MJ metadata using their preferred editors and version control systems while maintaining the database as the source of truth.
4
4
 
@@ -15,15 +15,56 @@ Then use the sync commands:
15
15
  mj sync --help
16
16
  ```
17
17
 
18
- ## Purpose
18
+ ## Overview
19
19
 
20
20
  MemberJunction is a powerful metadata-driven system where configuration, business logic, AI prompts, templates, and more are stored as metadata in the database. This approach provides tremendous flexibility and runtime configurability, but it can create friction in modern development workflows.
21
21
 
22
+ The Metadata Sync tool bridges the gap between database-stored metadata and file-based workflows by:
23
+ - Pulling metadata entities from database to JSON files with external file support
24
+ - Pushing local file changes back to the database
25
+ - Supporting embedded collections for related entities
26
+ - Enabling version control for all MJ metadata through Git
27
+ - Supporting CI/CD workflows for metadata deployment
28
+ - Providing a familiar file-based editing experience
29
+
30
+ ```mermaid
31
+ flowchart LR
32
+ subgraph Files["Local File System"]
33
+ JSON["JSON Metadata Files"]
34
+ EXT["External Files\n(.md, .html, .sql)"]
35
+ CFG[".mj-sync.json\nConfiguration"]
36
+ end
37
+
38
+ subgraph Engine["MetadataSync Engine"]
39
+ PULL["PullService"]
40
+ PUSH["PushService"]
41
+ VAL["ValidationService"]
42
+ SYNC["SyncEngine\n(Reference Resolution)"]
43
+ end
44
+
45
+ subgraph DB["MemberJunction Database"]
46
+ ENT["Entity Records"]
47
+ REL["Related Records"]
48
+ META["Entity Metadata"]
49
+ end
50
+
51
+ DB -->|"mj sync pull"| PULL
52
+ PULL --> Files
53
+ Files -->|"mj sync push"| VAL
54
+ VAL --> PUSH
55
+ PUSH --> SYNC
56
+ SYNC --> DB
57
+
58
+ style Files fill:#2d6a9f,stroke:#1a4971,color:#fff
59
+ style Engine fill:#7c5295,stroke:#563a6b,color:#fff
60
+ style DB fill:#2d8659,stroke:#1a5c3a,color:#fff
61
+ ```
62
+
22
63
  ### Why This Tool Matters
23
64
 
24
65
  **For Developers:**
25
66
  - **Full IDE Support**: Edit complex prompts and templates with syntax highlighting, IntelliSense, and all your favorite editor features
26
- - **Version Control**: Track every change with Git - see diffs, blame, history, and collaborate through pull requests
67
+ - **Version Control**: Track every change with Git -- see diffs, blame, history, and collaborate through pull requests
27
68
  - **Branch-based Development**: Work on features in isolation, test changes, and merge when ready
28
69
  - **CI/CD Integration**: Automatically deploy metadata changes as code moves through environments
29
70
  - **Bulk Operations**: Use familiar command-line tools (grep, sed, find) to make sweeping changes
@@ -37,7 +78,7 @@ MemberJunction is a powerful metadata-driven system where configuration, busines
37
78
  - **Simple Backups**: Copy/paste folders for personal backups
38
79
 
39
80
  **For Organizations:**
40
- - **Migration Path**: Metadata flows naturally from dev staging production with code
81
+ - **Migration Path**: Metadata flows naturally from dev to staging to production with code
41
82
  - **Compliance**: Full audit trail through version control
42
83
  - **Collaboration**: Multiple team members can work on different metadata simultaneously
43
84
  - **Disaster Recovery**: File-based backups complement database backups
@@ -47,16 +88,6 @@ MemberJunction is a powerful metadata-driven system where configuration, busines
47
88
 
48
89
  This tool preserves the power of MJ's metadata-driven architecture while adding the convenience of file-based workflows. The database remains the source of truth for runtime operations, while files become the medium for creation, editing, and deployment.
49
90
 
50
- ## Overview
51
-
52
- The Metadata Sync tool bridges the gap between database-stored metadata and file-based workflows by:
53
- - Pulling metadata entities from database to JSON files with external file support
54
- - Pushing local file changes back to the database
55
- - Supporting embedded collections for related entities
56
- - Enabling version control for all MJ metadata through Git
57
- - Supporting CI/CD workflows for metadata deployment
58
- - Providing a familiar file-based editing experience
59
-
60
91
  ## Key Features
61
92
 
62
93
  ### Hybrid File Storage
@@ -85,10 +116,6 @@ The Metadata Sync tool bridges the gap between database-stored metadata and file
85
116
  - **Selective Processing**: Use `--include` or `--exclude` to filter which entity directories are processed
86
117
  - **Pattern Support**: Supports glob patterns like `ai-*`, `*-test`, etc.
87
118
  - **Mutually Exclusive**: Cannot use both `--include` and `--exclude` together
88
- - **Use Cases**:
89
- - Speed up development by excluding large/slow directories
90
- - Focus on specific entities during debugging
91
- - Create specialized sync workflows for different teams
92
119
 
93
120
  ### Development Workflow Integration
94
121
  - Watch mode for automatic syncing during development
@@ -96,9 +123,57 @@ The Metadata Sync tool bridges the gap between database-stored metadata and file
96
123
  - CI/CD mode for automated deployments
97
124
  - Integration with existing mj.config.cjs configuration
98
125
 
126
+ ## Architecture
127
+
128
+ ```mermaid
129
+ flowchart TD
130
+ subgraph Services["Service Layer"]
131
+ IS["InitService"]
132
+ PS["PullService"]
133
+ PUS["PushService"]
134
+ SS["StatusService"]
135
+ WS["WatchService"]
136
+ VS["ValidationService"]
137
+ FRS["FileResetService"]
138
+ FS["FormattingService"]
139
+ end
140
+
141
+ subgraph Core["Core Engine"]
142
+ SE["SyncEngine\n(Reference Resolution)"]
143
+ CM["ConfigManager"]
144
+ JP["JsonPreprocessor\n(@include handling)"]
145
+ RDA["RecordDependencyAnalyzer\n(Topological Sort)"]
146
+ end
147
+
148
+ subgraph IO["I/O Layer"]
149
+ FE["FieldExternalizer"]
150
+ RP["RecordProcessor"]
151
+ FWB["FileWriteBatch"]
152
+ JWH["JsonWriteHelper"]
153
+ FBM["FileBackupManager"]
154
+ end
155
+
156
+ subgraph Data["Data & Auditing"]
157
+ TM["TransactionManager"]
158
+ SL["SQLLogger"]
159
+ DA["DeletionAuditor"]
160
+ DRS["DatabaseReferenceScanner"]
161
+ end
162
+
163
+ Services --> Core
164
+ Services --> IO
165
+ PUS --> Data
166
+ Core --> IO
167
+
168
+ style Services fill:#2d6a9f,stroke:#1a4971,color:#fff
169
+ style Core fill:#7c5295,stroke:#563a6b,color:#fff
170
+ style IO fill:#2d8659,stroke:#1a5c3a,color:#fff
171
+ style Data fill:#b8762f,stroke:#8a5722,color:#fff
172
+ ```
173
+
99
174
  ## Supported Entities
100
175
 
101
- The tool works with any MemberJunction entity - both core system entities and user-created entities. Each entity type can have its own directory structure, file naming conventions, and related entity configurations.
176
+ The tool works with any MemberJunction entity -- both core system entities and user-created entities. Each entity type can have its own directory structure, file naming conventions, and related entity configurations.
102
177
 
103
178
  ### Important Limitation: Database-Reflected Metadata
104
179
 
@@ -122,409 +197,81 @@ The tool is intended for managing business-level metadata such as:
122
197
 
123
198
  For more information about how CodeGen reflects system-level data from the database into the MJ metadata layer, see the [CodeGen documentation](../CodeGen/README.md).
124
199
 
125
- ## Creating Error-Free Entity Files
126
-
127
- ### Quick Start Checklist
128
-
129
- Before creating entity JSON files, follow this checklist to avoid common mistakes:
130
-
131
- ✅ **1. Find the Entity Definition**
132
- - Open `packages/MJCoreEntities/src/generated/entity_subclasses.ts` or `packages/GeneratedEntities/src/generated/entity_subclasses.ts`
133
- - Search for `class [EntityName]Entity` (e.g., `class TemplateEntity`)
134
- - Review JSDoc comments and property definitions to identify required vs optional fields
135
-
136
- ✅ **2. Check Required Fields**
137
- - Look for JSDoc comments with `@required` annotations
138
- - Fields without `?` in TypeScript definitions are typically required
139
- - Always include `Name` (almost always required)
140
- - Always include `UserID` (use System User ID: `ECAFCCEC-6A37-EF11-86D4-000D3A4E707E`)
141
-
142
- ✅ **3. Validate Field Names**
143
- - Use exact field names from the BaseEntity class definition
144
- - Field names are case-sensitive
145
- - Don't assume fields exist (e.g., not all entities have `Status`)
146
-
147
- ✅ **4. Use Correct File Naming**
148
- - Configuration files (.mj-sync.json, .mj-folder.json) must start with dot
149
- - Metadata files follow the `filePattern` in your .mj-sync.json
150
- - Most common: `"filePattern": "*.json"` (matches any .json file)
151
- - Alternative: `"filePattern": ".*.json"` (matches dot-prefixed .json files)
152
-
153
- ✅ **5. Set Up Directory Structure**
154
- - Create `.mj-sync.json` in the entity directory
155
- - Use glob patterns: `"filePattern": "*.json"` (not regex: `".*.json"`)
156
-
157
- ### Discovering Entity Structure
158
-
159
- **CRITICAL**: Before creating entity files, you must understand the entity's field structure. Most errors occur because users are unfamiliar with the required fields, data types, and constraints.
160
-
161
- #### Finding Entity Definitions
162
-
163
- The approach depends on whether you're working inside or outside the MemberJunction monorepo:
164
-
165
- ##### Working Inside MJ Monorepo
166
-
167
- Entity classes are located in:
168
-
169
- - **Core MJ Entities**: `packages/MJCoreEntities/src/generated/entity_subclasses.ts`
170
- - System entities like Users, Roles, EntityFields, etc.
171
- - AI-related entities like AI Prompts, AI Models, etc.
172
-
173
- - **Custom Entities**: `packages/GeneratedEntities/src/generated/entity_subclasses.ts`
174
- - Your application-specific entities
175
- - Business domain entities
176
-
177
- ##### Working Outside MJ Monorepo (In Your Own Project)
178
-
179
- Entity classes are located in:
180
-
181
- - **Core MJ Entities**: `node_modules/@memberjunction/core-entities/dist/generated/entity_subclasses.js`
182
- - Note: This is compiled JavaScript, but your IDE should provide IntelliSense
183
- - For TypeScript definitions: `node_modules/@memberjunction/core-entities/dist/generated/entity_subclasses.d.ts`
184
-
185
- - **Custom Entities**: Your project's generated entities location (varies by project structure)
186
- - Common locations: `src/generated/`, `packages/entities/`, or similar
187
- - Look for files containing your custom entity classes
188
-
189
- ##### Best Practice: Use Your IDE's IntelliSense
190
-
191
- **Recommended approach for all scenarios:**
192
-
193
- 1. **Import the entity class** in your IDE:
194
- ```typescript
195
- import { TemplateEntity } from '@memberjunction/core-entities';
196
- ```
197
-
198
- 2. **Create an instance and explore with IntelliSense**:
199
- ```typescript
200
- const template = new TemplateEntity();
201
- // Type "template." and let your IDE show available properties
202
- ```
203
-
204
- 3. **Check the class definition** (F12 or "Go to Definition") to see:
205
- - JSDoc comments with field descriptions
206
- - Required vs optional fields
207
- - Field types and validation rules
208
- - Relationships and constraints
209
-
210
- #### How to Find Required Fields
211
-
212
- 1. **Use IDE IntelliSense** (Recommended):
213
- - Import the entity class
214
- - Create an instance: `const entity = new TemplateEntity();`
215
- - Use "Go to Definition" (F12) to see the BaseEntity class
216
- - Look for JSDoc comments and field definitions
217
-
218
- 2. **Examine the BaseEntity Class**:
219
- - Find the entity class (e.g., `class TemplateEntity`)
220
- - Look at property declarations with JSDoc comments
221
- - Check for required vs optional field annotations
222
- - Review any validation methods or constraints
223
-
224
- 3. **Runtime Metadata Discovery**:
225
- ```typescript
226
- import { Metadata } from '@memberjunction/core';
227
-
228
- const md = new Metadata();
229
- const entityInfo = md.EntityByName('Templates');
230
- console.log('Required fields:', entityInfo.Fields.filter(f => !f.AllowsNull));
231
- ```
232
-
233
- #### Example: Templates Entity Structure
234
- ```typescript
235
- // BaseEntity class (accessible via IDE IntelliSense)
236
- export class TemplateEntity extends BaseEntity {
237
- /**
238
- * Primary key - auto-generated GUID
239
- */
240
- ID: string;
241
-
242
- /**
243
- * Template name - REQUIRED
244
- * @required
245
- */
246
- Name: string;
247
-
248
- /**
249
- * Template description - optional
250
- */
251
- Description?: string;
252
-
253
- /**
254
- * User who created this template - REQUIRED
255
- * Must be a valid User ID
256
- * @required
257
- * @foreignKey Users.ID
258
- */
259
- UserID: string;
260
-
261
- /**
262
- * Category for organizing templates - optional
263
- * @foreignKey TemplateCategories.ID
264
- */
265
- CategoryID?: string;
266
-
267
- // Note: Status field may not exist on all entities!
268
- }
269
- ```
270
-
271
- #### Common Required Fields Pattern
272
-
273
- Most MJ entities follow these patterns:
274
-
275
- **Always Required:**
276
- - `ID` - Primary key (GUID) - auto-generated if not provided
277
- - `Name` - Human-readable name
278
- - `UserID` - Creator/owner (use System User: `ECAFCCEC-6A37-EF11-86D4-000D3A4E707E`)
279
-
280
- **Often Required:**
281
- - `Description` - Usually optional but recommended
282
- - Foreign key fields ending in `ID` - Check if they have `.optional()`
283
-
284
- **Be Careful With:**
285
- - `Status` fields - Some entities have them, others don't
286
- - Enum fields - Must match exact values from database
287
- - DateTime fields - Use ISO format: `2024-01-15T10:30:00Z`
288
-
289
- ### Common Mistakes and Solutions
290
-
291
- #### ❌ Mistake 1: Using Non-Existent Fields
292
- ```json
293
- {
294
- "fields": {
295
- "Name": "My Template",
296
- "Status": "Active" // ❌ Templates entity may not have Status field
297
- }
298
- }
299
- ```
300
-
301
- **✅ Solution**: Check the BaseEntity class first
302
- ```typescript
303
- // In entity_subclasses.ts - if you don't see Status here, don't use it
304
- export class TemplateEntity extends BaseEntity {
305
- Name: string; // Required
306
- Description?: string; // Optional (note the ?)
307
- // No Status field defined
308
- }
309
- ```
310
-
311
- #### ❌ Mistake 2: Missing Required Fields
312
- ```json
313
- {
314
- "fields": {
315
- "Name": "My Template"
316
- // ❌ Missing required UserID
317
- }
318
- }
319
- ```
320
-
321
- **✅ Solution**: Include all required fields
322
- ```json
323
- {
324
- "fields": {
325
- "Name": "My Template",
326
- "UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"
327
- }
328
- }
329
- ```
330
-
331
- #### ❌ Mistake 3: Wrong File Pattern in .mj-sync.json
332
- ```json
333
- {
334
- "entity": "Templates",
335
- "filePattern": ".*.json" // ❌ This is regex, not glob
336
- }
337
- ```
338
-
339
- **✅ Solution**: Use glob patterns
340
- ```json
341
- {
342
- "entity": "Templates",
343
- "filePattern": "*.json" // ✅ Correct glob pattern
344
- }
345
- ```
346
-
347
- #### ❌ Mistake 4: Incorrect Data Types
348
- ```json
349
- {
350
- "fields": {
351
- "Name": "My Template",
352
- "CreatedAt": "2024-01-15", // ❌ Wrong datetime format
353
- "Priority": "1" // ❌ Should be number, not string
354
- }
355
- }
356
- ```
357
-
358
- **✅ Solution**: Use correct data types
359
- ```json
360
- {
361
- "fields": {
362
- "Name": "My Template",
363
- "CreatedAt": "2024-01-15T10:30:00Z", // ✅ ISO format
364
- "Priority": 1 // ✅ Number type
365
- }
366
- }
367
- ```
368
-
369
- #### ❌ Mistake 5: Files Not Being Detected
370
- ```
371
- mydir/
372
- ├── .mj-sync.json (with "filePattern": "*.json")
373
- ├── template1.txt // ❌ Wrong extension
374
- └── .template2.json // ❌ Dot prefix when pattern is "*.json"
375
- ```
376
-
377
- **✅ Solution**: Match your filePattern
378
- ```
379
- mydir/
380
- ├── .mj-sync.json (with "filePattern": "*.json")
381
- ├── template1.json // ✅ Matches *.json pattern
382
- └── template2.json // ✅ Matches *.json pattern
383
- ```
384
-
385
- ### Step-by-Step Entity File Creation
386
-
387
- #### Step 1: Research the Entity
388
- ```bash
389
- # Open in your IDE:
390
- packages/MJCoreEntities/src/generated/entity_subclasses.ts
391
-
392
- # Search for your entity class (Ctrl+F):
393
- class TemplateEntity
394
-
395
- # Note the required vs optional fields:
396
- Name: string; // Required (no ?)
397
- UserID: string; // Required (no ?)
398
- Description?: string; // Optional (note the ?)
399
- ```
400
-
401
- #### Step 2: Create Directory Structure
402
- ```bash
403
- mkdir templates
404
- cd templates
405
-
406
- # Create entity config (dot-prefixed configuration file)
407
- echo '{
408
- "entity": "Templates",
409
- "filePattern": "*.json"
410
- }' > .mj-sync.json
411
- ```
412
-
413
- #### Step 3: Create Your First Entity File
414
- ```bash
415
- # Create metadata file (follows filePattern from .mj-sync.json)
416
- echo '{
417
- "fields": {
418
- "Name": "My First Template",
419
- "Description": "A test template",
420
- "UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"
421
- }
422
- }' > my-first-template.json
423
- ```
424
-
425
- #### Step 4: Test and Validate
426
- ```bash
427
- # Dry run to check for errors
428
- mj sync push --dir="templates" --dry-run
429
-
430
- # If successful, do actual push
431
- mj sync push --dir="templates"
432
- ```
433
-
434
- ### AI/LLM Guidelines
435
-
436
- When using AI tools (like Claude, ChatGPT, etc.) to generate entity files:
437
-
438
- **🤖 For AI Assistants:**
439
-
440
- 1. **Always check entity definitions first** - Never assume field names or requirements
441
- 2. **Look up the exact BaseEntity class** in the generated entity files
442
- 3. **Use the System User ID** (`ECAFCCEC-6A37-EF11-86D4-000D3A4E707E`) for UserID fields
443
- 4. **Include only fields that exist** in the entity definition
444
- 5. **Use proper data types** as defined in the BaseEntity class
445
- 6. **Remember file naming rules**:
446
- - Configuration files (.mj-sync.json) must have dot prefix
447
- - Metadata files follow the filePattern in .mj-sync.json
448
- 7. **Use glob patterns** in .mj-sync.json, not regex patterns
449
-
450
- **📝 Prompt Template for AI:**
451
- ```
452
- I need to create entity files for the [EntityName] entity in MemberJunction.
453
-
454
- Please:
455
- 1. First, check the entity definition in packages/MJCoreEntities/src/generated/entity_subclasses.ts
456
- 2. Find the class [EntityName]Entity (e.g., class TemplateEntity)
457
- 3. Review JSDoc comments and property definitions to identify required vs optional fields
458
- 4. Create a .mj-sync.json file with correct glob pattern
459
- 5. Create sample metadata JSON files following the filePattern
460
- 6. Use UserID: "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E" for required UserID fields
461
- 7. Follow the exact field names and data types from the BaseEntity class definition
462
-
463
- CRITICAL: Configuration files (.mj-sync.json) must start with dot, but metadata files follow the filePattern specified in the configuration.
464
- ```
465
-
466
- ### Understanding File Naming Rules
467
-
468
- **Configuration Files (Always Dot-Prefixed):**
469
- - ✅ `.mj-sync.json` - Entity configuration
470
- - ✅ `.mj-folder.json` - Folder defaults
471
- - ❌ `mj-sync.json` - Won't be recognized
472
-
473
- **Metadata Files (Follow filePattern):**
474
- With `"filePattern": "*.json"`:
475
- - ✅ `my-template.json` - Will be processed
476
- - ✅ `greeting.json` - Will be processed
477
- - ❌ `.my-template.json` - Won't match pattern
478
- - ❌ `package.json` - Will be ignored (add to ignore list if needed)
479
-
480
- With `"filePattern": ".*.json"`:
481
- - ✅ `.my-template.json` - Will be processed
482
- - ✅ `.greeting.json` - Will be processed
483
- - ❌ `my-template.json` - Won't match pattern
484
- - ❌ `package.json` - Won't match pattern
485
-
486
- ### Troubleshooting Quick Reference
487
-
488
- | Error Message | Cause | Solution |
489
- |---------------|-------|----------|
490
- | `No entity directories found` | Missing .mj-sync.json or wrong filePattern | Check .mj-sync.json exists and uses `"*.json"` |
491
- | `Field 'X' does not exist on entity 'Y'` | Using non-existent field | Check BaseEntity class in entity_subclasses.ts |
492
- | `User ID cannot be null` | Missing required UserID | Add `"UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"` |
493
- | `Processing 0 records` | Files don't match filePattern | Check files match pattern in .mj-sync.json |
494
- | Failed validation | Wrong data type or format | Check BaseEntity class for field types |
495
-
496
- ### System User ID Reference
497
-
498
- **Always use this GUID for UserID fields:**
499
- ```
500
- ECAFCCEC-6A37-EF11-86D4-000D3A4E707E
501
- ```
502
-
503
- This is the System User ID that should be used when creating entity records through the MetadataSync tool. Using any other ID or leaving it null will cause validation errors.
504
-
505
200
  ## File Structure
506
201
 
507
202
  The tool uses a hierarchical directory structure with cascading defaults:
508
203
  - Each top-level directory represents an entity type
509
204
  - `.mj-sync.json` files define entities and base defaults
510
205
  - `.mj-folder.json` files define folder-specific defaults (optional)
511
- - Only dot-prefixed JSON files (e.g., `.prompt-template.json`, `.category.json`) are treated as metadata records
512
- - Regular JSON files without the dot prefix are ignored, allowing package.json and other config files to coexist
206
+ - Metadata JSON files follow the `filePattern` configured in `.mj-sync.json`
513
207
  - External files (`.md`, `.html`, etc.) are referenced from the JSON files
514
208
  - Defaults cascade down through the folder hierarchy
515
209
 
516
- ### File Naming Convention
517
-
518
- **Metadata files must be prefixed with a dot (.)** to be recognized by the sync tool. This convention:
519
- - Clearly distinguishes metadata files from regular configuration files
520
- - Allows `package.json`, `tsconfig.json` and other standard files to coexist without being processed
521
- - Follows established patterns like `.gitignore` and `.eslintrc.json`
210
+ ```mermaid
211
+ flowchart TD
212
+ subgraph Root["metadata/"]
213
+ RS[".mj-sync.json\n(Global Config)"]
214
+ end
215
+
216
+ subgraph AI["ai-prompts/"]
217
+ AS[".mj-sync.json\nentity: AI Prompts"]
218
+ subgraph CS["customer-service/"]
219
+ CF[".mj-folder.json\n(Folder Defaults)"]
220
+ GJ[".greeting.json\n(Record)"]
221
+ GM["greeting.prompt.md\n(External Content)"]
222
+ end
223
+ subgraph AN["analytics/"]
224
+ AF[".mj-folder.json"]
225
+ DJ[".daily-report.json"]
226
+ DM["daily-report.prompt.md"]
227
+ end
228
+ end
229
+
230
+ subgraph TE["templates/"]
231
+ TS[".mj-sync.json\nentity: Templates"]
232
+ end
233
+
234
+ Root --> AI
235
+ Root --> TE
236
+ AS --> CS
237
+ AS --> AN
238
+
239
+ style Root fill:#64748b,stroke:#475569,color:#fff
240
+ style AI fill:#2d6a9f,stroke:#1a4971,color:#fff
241
+ style CS fill:#7c5295,stroke:#563a6b,color:#fff
242
+ style AN fill:#7c5295,stroke:#563a6b,color:#fff
243
+ style TE fill:#2d6a9f,stroke:#1a4971,color:#fff
244
+ ```
522
245
 
523
- Examples:
524
- - ✅ `.greeting.json` - Will be processed as metadata
525
- - ✅ `.customer-prompt.json` - Will be processed as metadata
526
- - ❌ `greeting.json` - Will be ignored
527
- - ❌ `package.json` - Will be ignored
246
+ ### Example Structure
247
+ ```
248
+ metadata/
249
+ +-- .mj-sync.json # Global sync configuration
250
+ +-- ai-prompts/
251
+ | +-- .mj-sync.json # Defines entity: "AI Prompts"
252
+ | +-- customer-service/
253
+ | | +-- .mj-folder.json # Folder metadata (CategoryID, etc.)
254
+ | | +-- .greeting.json # AI Prompt record with embedded models
255
+ | | +-- greeting.prompt.md # Prompt content (referenced)
256
+ | | +-- greeting.notes.md # Notes field (referenced)
257
+ | +-- analytics/
258
+ | +-- .mj-folder.json # Folder metadata (CategoryID, etc.)
259
+ | +-- .daily-report.json # AI Prompt record
260
+ | +-- daily-report.prompt.md # Prompt content (referenced)
261
+ +-- templates/ # Reusable JSON templates
262
+ | +-- standard-prompt-settings.json
263
+ | +-- standard-ai-models.json
264
+ +-- template-entities/
265
+ +-- .mj-sync.json # Defines entity: "Templates"
266
+ +-- email/
267
+ | +-- .mj-folder.json
268
+ | +-- .welcome.json
269
+ | +-- welcome.template.html
270
+ +-- reports/
271
+ +-- .mj-folder.json
272
+ +-- .invoice.json
273
+ +-- invoice.template.html
274
+ ```
528
275
 
529
276
  ### File Format Options
530
277
 
@@ -552,46 +299,11 @@ JSON files can contain arrays of records:
552
299
  ]
553
300
  ```
554
301
 
555
- This is useful for:
556
- - Grouping related records in a single file
557
- - Reducing file clutter for entities with many small records
558
- - Maintaining logical groupings while using `@file:` references for large content
559
-
560
- ### Example Structure
561
- ```
562
- metadata/
563
- ├── .mj-sync.json # Global sync configuration
564
- ├── ai-prompts/
565
- │ ├── .mj-sync.json # Defines entity: "AI Prompts"
566
- │ ├── customer-service/
567
- │ │ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
568
- │ │ ├── .greeting.json # AI Prompt record with embedded models
569
- │ │ ├── greeting.prompt.md # Prompt content (referenced)
570
- │ │ └── greeting.notes.md # Notes field (referenced)
571
- │ └── analytics/
572
- │ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
573
- │ ├── .daily-report.json # AI Prompt record
574
- │ └── daily-report.prompt.md # Prompt content (referenced)
575
- ├── templates/ # Reusable JSON templates
576
- │ ├── standard-prompt-settings.json # Common prompt configurations
577
- │ ├── standard-ai-models.json # Standard model configurations
578
- │ ├── high-performance-models.json # High-power model configurations
579
- │ └── customer-service-defaults.json # CS-specific defaults
580
- └── template-entities/
581
- ├── .mj-sync.json # Defines entity: "Templates"
582
- ├── email/
583
- │ ├── .mj-folder.json # Folder metadata
584
- │ ├── .welcome.json # Template record (dot-prefixed)
585
- │ └── welcome.template.html # Template content (referenced)
586
- └── reports/
587
- ├── .mj-folder.json # Folder metadata
588
- ├── .invoice.json # Template record (dot-prefixed)
589
- └── invoice.template.html # Template content (referenced)
590
- ```
302
+ This is useful for grouping related records in a single file, reducing file clutter for entities with many small records, and maintaining logical groupings while using `@file:` references for large content.
591
303
 
592
304
  ## JSON Metadata Format
593
305
 
594
- ### Individual Record (e.g., ai-prompts/customer-service/.greeting.json)
306
+ ### Individual Record
595
307
  ```json
596
308
  {
597
309
  "fields": {
@@ -683,34 +395,23 @@ metadata/
683
395
  }
684
396
  ```
685
397
 
686
- ## Adding Comments to JSON Metadata Files
398
+ ### Reserved Keys
687
399
 
688
- Since JSON does not natively support comments, MetadataSync provides a convention for adding documentation to your metadata files using custom keys that are preserved but ignored during sync operations.
400
+ | Key | Purpose |
401
+ |-----|---------|
402
+ | `fields` | Entity field values |
403
+ | `relatedEntities` | Embedded related entity records |
404
+ | `primaryKey` | Record identifier |
405
+ | `sync` | Sync metadata (lastModified, checksum) |
406
+ | `__mj_sync_notes` | System-managed resolution tracking |
407
+ | `deleteRecord` | Deletion directive |
689
408
 
690
- ### Comment Convention
409
+ Any key that is not one of the reserved keys above is preserved but ignored during sync operations. By convention, use an underscore prefix (`_`) for comment keys:
691
410
 
692
- Any key that is not one of the reserved MetadataSync keys (`fields`, `relatedEntities`, `primaryKey`, `sync`, `deleteRecord`) will be preserved in your JSON files but ignored during push/pull operations. By convention, use an underscore prefix (`_`) for comment keys to clearly distinguish them from future MetadataSync features.
693
-
694
- **Reserved keys (processed by MetadataSync):**
695
- - `fields` - Entity field values
696
- - `relatedEntities` - Embedded related entity records
697
- - `primaryKey` - Record identifier
698
- - `sync` - Sync metadata (lastModified, checksum)
699
- - `__mj_sync_notes` - System-managed resolution tracking (see [Resolution Tracking](#resolution-tracking-with-__mj_sync_notes))
700
- - `deleteRecord` - Deletion directive
701
-
702
- **Recommended comment pattern:**
703
- - `_comments` - Array of comment strings
704
- - `_note` - Single comment string
705
- - `_description` - Descriptive text
706
- - Any key starting with `_` - Reserved for user documentation
707
-
708
- ### Example: Top-Level Comments
709
411
  ```json
710
412
  {
711
413
  "_comments": [
712
- "This file configures encryption settings for the Test Tables entity",
713
- "The encryption key is defined in /metadata/encryption-keys/"
414
+ "This file configures encryption settings for the Test Tables entity"
714
415
  ],
715
416
  "fields": {
716
417
  "Name": "Test Tables",
@@ -722,333 +423,81 @@ Any key that is not one of the reserved MetadataSync keys (`fields`, `relatedEnt
722
423
  }
723
424
  ```
724
425
 
725
- ### Example: Comments on Related Entities
726
- ```json
727
- {
728
- "_comments": ["Parent entity configuration"],
729
- "fields": {
730
- "Name": "My Entity"
731
- },
732
- "relatedEntities": {
733
- "Entity Fields": [
734
- {
735
- "fields": {
736
- "Encrypt": true,
737
- "AllowDecryptInAPI": false
738
- },
739
- "_comments": ["This field stores server-only encrypted data"],
740
- "primaryKey": {
741
- "ID": "F501E294-5F5F-44C6-AD06-5C9754A13D29"
742
- }
743
- },
744
- {
745
- "fields": {
746
- "Encrypt": true,
747
- "AllowDecryptInAPI": true
748
- },
749
- "_comments": ["This field is decrypted for API responses"],
750
- "primaryKey": {
751
- "ID": "CF4B94E4-8E68-4692-B13A-9A0D51D397B7"
752
- }
753
- }
754
- ]
755
- }
756
- }
757
- ```
758
-
759
- ### Key Ordering Preservation
426
+ ## Special Reference Types
760
427
 
761
- MetadataSync preserves the original order of keys in your JSON files. When you run `mj sync push` or `mj sync pull`, your comments will remain exactly where you placed them:
428
+ The tool supports special reference types that can be used in any field that accepts text content. These references are processed during push/pull operations to handle external content, lookups, and environment-specific values.
762
429
 
763
- ```json
764
- {
765
- "_comments": ["This comment stays at the top"],
766
- "fields": { ... },
767
- "_note": "This note stays between fields and relatedEntities",
768
- "relatedEntities": { ... }
769
- }
430
+ ```mermaid
431
+ flowchart TD
432
+ subgraph References["@ Reference Types"]
433
+ FILE["@file:\nExternal file content"]
434
+ URL["@url:\nRemote URL content"]
435
+ LOOKUP["@lookup:\nEntity ID resolution"]
436
+ PARENT["@parent:\nParent record field"]
437
+ ROOT["@root:\nRoot record field"]
438
+ ENV["@env:\nEnvironment variable"]
439
+ TEMPLATE["@template:\nJSON template merge"]
440
+ INCLUDE["@include\nJSON composition"]
441
+ end
442
+
443
+ subgraph External["External Resources"]
444
+ style External fill:#2d8659,stroke:#1a5c3a,color:#fff
445
+ EF["Local Files"]
446
+ UR["Remote URLs"]
447
+ end
448
+
449
+ subgraph Database["Database Lookups"]
450
+ style Database fill:#2d6a9f,stroke:#1a4971,color:#fff
451
+ EL["Entity Records"]
452
+ end
453
+
454
+ subgraph Context["Record Context"]
455
+ style Context fill:#b8762f,stroke:#8a5722,color:#fff
456
+ PR["Parent Entity"]
457
+ RR["Root Entity"]
458
+ end
459
+
460
+ subgraph Runtime["Runtime Values"]
461
+ style Runtime fill:#7c5295,stroke:#563a6b,color:#fff
462
+ EV["Environment Vars"]
463
+ end
464
+
465
+ FILE --> EF
466
+ URL --> UR
467
+ LOOKUP --> EL
468
+ PARENT --> PR
469
+ ROOT --> RR
470
+ ENV --> EV
471
+ TEMPLATE --> EF
472
+ INCLUDE --> EF
473
+
474
+ style References fill:#64748b,stroke:#475569,color:#fff
770
475
  ```
771
476
 
772
- ### Best Practices
477
+ ### @file: References
478
+ When a field value starts with `@file:`, the tool will:
479
+ 1. Read content from the specified file for push operations
480
+ 2. Write content to the specified file for pull operations
481
+ 3. Track both files for change detection
482
+ 4. **For JSON files**: Automatically process any `@include` directives within them
773
483
 
774
- 1. **Use underscore prefix**: Start custom keys with `_` to reserve the alphabetic namespace for future MetadataSync features
775
- 2. **Use arrays for multi-line comments**: `"_comments": ["Line 1", "Line 2"]` provides clean formatting
776
- 3. **Place comments near relevant content**: Add `_comments` inside related entity objects to document specific records
777
- 4. **Document complex configurations**: Use comments to explain lookup references, encryption settings, or business rules
778
- 5. **Version control friendly**: Comments make metadata files more readable in code reviews and git diffs
484
+ Examples:
485
+ - `@file:greeting.prompt.md` -- File in same directory as JSON
486
+ - `@file:./shared/common-prompt.md` -- Relative path
487
+ - `@file:../templates/standard-header.md` -- Parent directory reference
488
+ - `@file:spec.json` -- JSON file with `@include` directives (processed automatically)
779
489
 
780
- ## Resolution Tracking with `__mj_sync_notes`
490
+ ### @url: References
491
+ When a field value starts with `@url:`, the tool will:
492
+ 1. Fetch content from the URL during push operations
493
+ 2. Cache the content with appropriate headers
494
+ 3. Support both HTTP(S) and file:// protocols
781
495
 
782
- When you use `@lookup` or `@parent` references in your metadata files, MetadataSync can track how these references were resolved during push operations. This information is written to a `__mj_sync_notes` key in each record, providing transparency into the resolution process.
496
+ Examples:
497
+ - `@url:https://example.com/prompts/greeting.md` -- Remote content
498
+ - `@url:https://raw.githubusercontent.com/company/prompts/main/customer.md` -- GitHub raw content
783
499
 
784
- **Note:** This feature is **disabled by default** to keep metadata files clean. Enable it when you need to debug lookup resolutions or understand how references are being resolved.
785
-
786
- ### Enabling Resolution Tracking
787
-
788
- To enable `__mj_sync_notes`, add the `emitSyncNotes` setting to your `.mj-sync.json` configuration:
789
-
790
- **Root-level configuration** (applies to all entity directories):
791
- ```json
792
- {
793
- "version": "1.0",
794
- "emitSyncNotes": true,
795
- "directoryOrder": ["..."]
796
- }
797
- ```
798
-
799
- **Entity-level override** (in an entity directory's `.mj-sync.json`):
800
- ```json
801
- {
802
- "entity": "AI Prompts",
803
- "emitSyncNotes": true
804
- }
805
- ```
806
-
807
- The inheritance works as follows:
808
- - Entity-level `emitSyncNotes` takes precedence if explicitly set
809
- - If not set at entity level, inherits from root `.mj-sync.json`
810
- - Defaults to `false` if not set anywhere
811
-
812
- This allows you to enable tracking globally and disable it for specific entities, or vice versa.
813
-
814
- ### Purpose
815
-
816
- The `__mj_sync_notes` feature helps you:
817
- - **Debug lookup issues**: See exactly what value a `@lookup` resolved to
818
- - **Understand parent references**: Track how `@parent:` references were resolved
819
- - **Verify nested lookups**: View the resolution chain for nested `@lookup` expressions
820
- - **Document resolved values**: Provides a reference for what GUIDs correspond to which lookup expressions
821
-
822
- ### How It Works
823
-
824
- After each `mj sync push` operation, records with `@lookup` or `@parent` references will have a `__mj_sync_notes` section added automatically:
825
-
826
- ```json
827
- {
828
- "fields": {
829
- "Name": "ServerOnlyEncrypted",
830
- "Encrypt": true,
831
- "EncryptionKeyID": "@lookup:MJ: Encryption Keys.Name=Test Encryption Key"
832
- },
833
- "primaryKey": {
834
- "ID": "@lookup:Entity Fields.EntityID=@lookup:Entities.Name=Test Tables&Name=ServerOnlyEncrypted"
835
- },
836
- "sync": {
837
- "lastModified": "2025-12-25T16:14:32.605Z",
838
- "checksum": "7e989e08396f6cffb8b2d70958018b21..."
839
- },
840
- "__mj_sync_notes": [
841
- {
842
- "type": "lookup",
843
- "field": "primaryKey.ID",
844
- "expression": "@lookup:Entity Fields.EntityID=@lookup:Entities.Name=Test Tables&Name=ServerOnlyEncrypted",
845
- "resolved": "F501E294-5F5F-44C6-AD06-5C9754A13D29",
846
- "nested": [
847
- {
848
- "expression": "@lookup:Entities.Name=Test Tables",
849
- "resolved": "0fde4c2c-26b1-45e9-b504-5d4a6f4201cf"
850
- }
851
- ]
852
- },
853
- {
854
- "type": "lookup",
855
- "field": "fields.EncryptionKeyID",
856
- "expression": "@lookup:MJ: Encryption Keys.Name=Test Encryption Key",
857
- "resolved": "85B814C8-A01B-4AE3-A252-DC9D54C914C7"
858
- }
859
- ]
860
- }
861
- ```
862
-
863
- ### Note Structure
864
-
865
- Each resolution note contains:
866
-
867
- | Field | Description |
868
- |-------|-------------|
869
- | `type` | Resolution type: `"lookup"` for `@lookup` references, `"parent"` for `@parent` references |
870
- | `field` | Field path where the resolution occurred (e.g., `"primaryKey.ID"`, `"fields.CategoryID"`) |
871
- | `expression` | The original reference expression before resolution |
872
- | `resolved` | The resolved value (typically a GUID) |
873
- | `nested` | (Optional) Array of nested resolutions for expressions containing nested `@lookup` references |
874
-
875
- ### System-Managed Key
876
-
877
- The `__mj_sync_notes` key uses a double underscore prefix (`__`) to clearly indicate it is system-managed:
878
- - **Do not manually edit** this section - it is regenerated on each push when `emitSyncNotes` is enabled
879
- - When `emitSyncNotes` is disabled (the default), existing `__mj_sync_notes` keys are automatically removed on push
880
- - When enabled, the key is automatically removed if a record has no `@lookup` or `@parent` references
881
- - Key ordering is preserved - `__mj_sync_notes` appears after `sync` in the file
882
-
883
- ### Example: Parent Reference Resolution
884
-
885
- When using `@parent:` references in related entities:
886
-
887
- ```json
888
- {
889
- "fields": {
890
- "Name": "My Template"
891
- },
892
- "relatedEntities": {
893
- "Template Contents": [
894
- {
895
- "fields": {
896
- "TemplateID": "@parent:ID",
897
- "Content": "Hello World"
898
- },
899
- "__mj_sync_notes": [
900
- {
901
- "type": "parent",
902
- "field": "fields.TemplateID",
903
- "expression": "@parent:ID",
904
- "resolved": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
905
- }
906
- ]
907
- }
908
- ]
909
- }
910
- }
911
- ```
912
-
913
- ## Default Value Inheritance
914
-
915
- The tool implements a cascading inheritance system for field defaults, similar to CSS or OOP inheritance:
916
-
917
- 1. **Entity-level defaults** (in `.mj-sync.json`) - Base defaults for all records
918
- 2. **Folder-level defaults** (in `.mj-folder.json`) - Override/extend entity defaults
919
- 3. **Nested folder defaults** - Override/extend parent folder defaults
920
- 4. **Record-level values** - Override all inherited defaults
921
-
922
- ### Inheritance Example
923
- ```
924
- ai-prompts/.mj-sync.json → Temperature: 0.7, MaxTokens: 1500
925
- ├── customer-service/.mj-folder.json → Temperature: 0.8 (overrides)
926
- │ ├── greeting.json → Uses Temperature: 0.8, MaxTokens: 1500
927
- │ └── escalation/.mj-folder.json → Temperature: 0.6 (overrides again)
928
- │ └── urgent.json → Temperature: 0.9 (record override)
929
- ```
930
-
931
- Final values for `urgent.json`:
932
- - Temperature: 0.9 (from record)
933
- - MaxTokens: 1500 (from entity defaults)
934
- - All other fields from folder hierarchy
935
-
936
- ## Special Conventions
937
-
938
- The tool supports special reference types that can be used in ANY field that accepts text content. These references are processed during push/pull operations to handle external content, lookups, and environment-specific values.
939
-
940
- ### Primary Key Handling
941
- The tool automatically detects primary key fields from entity metadata:
942
- - **Single primary keys**: Most common, stored as `{"ID": "value"}` or `{"CustomKeyName": "value"}`
943
- - **Composite primary keys**: Multiple fields that together form the primary key
944
- - **Auto-detection**: Tool reads entity metadata to determine primary key structure
945
- - **No hardcoding**: Works with any primary key field name(s)
946
- - **Reference support**: Primary key values can use `@lookup`, `@parent`, and other reference types
947
-
948
- #### Using @lookup in Primary Keys
949
- You can use `@lookup` references in primary key fields to avoid hardcoding GUIDs. This is especially useful when decorating existing records:
950
-
951
- ```json
952
- {
953
- "fields": {
954
- "Encrypt": true,
955
- "AllowDecryptInAPI": false
956
- },
957
- "primaryKey": {
958
- "ID": "@lookup:Entity Fields.EntityID=@lookup:Entities.Name=Test Tables&Name=ServerOnlyEncrypted"
959
- }
960
- }
961
- ```
962
-
963
- In this example:
964
- 1. The inner `@lookup:Entities.Name=Test Tables` resolves to the Entity ID
965
- 2. That ID is used to find the Entity Field with the matching `EntityID` and `Name`
966
- 3. The resulting Entity Field ID becomes the primary key
967
-
968
- **Note:** Primary key lookups must resolve immediately - the `?allowDefer` flag is not supported in primary key fields since the primary key is needed to determine if a record exists.
969
-
970
- ### deleteRecord Directive
971
- 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:
972
-
973
- #### How to Delete a Record
974
- 1. Add a `deleteRecord` section to any record JSON file
975
- 2. Set `delete: true` to mark the record for deletion
976
- 3. Run `mj sync push` to execute the deletion
977
- 4. The tool will update the JSON with a deletion timestamp
978
-
979
- #### Syntax
980
- ```json
981
- {
982
- "fields": {
983
- "Name": "Obsolete Prompt",
984
- "Description": "This prompt is no longer needed"
985
- },
986
- "primaryKey": {
987
- "ID": "550e8400-e29b-41d4-a716-446655440000"
988
- },
989
- "deleteRecord": {
990
- "delete": true
991
- }
992
- }
993
- ```
994
-
995
- #### After Deletion
996
- After successfully deleting the record, the tool updates the JSON file:
997
- ```json
998
- {
999
- "fields": {
1000
- "Name": "Obsolete Prompt",
1001
- "Description": "This prompt is no longer needed"
1002
- },
1003
- "primaryKey": {
1004
- "ID": "550e8400-e29b-41d4-a716-446655440000"
1005
- },
1006
- "deleteRecord": {
1007
- "delete": true,
1008
- "deletedAt": "2024-01-15T14:30:00.000Z"
1009
- }
1010
- }
1011
- ```
1012
-
1013
- #### Important Notes
1014
- - **Primary key required**: You must specify the `primaryKey` to identify which record to delete
1015
- - **One-time operation**: Once `deletedAt` is set, the deletion won't be attempted again
1016
- - **SQL logging**: Delete operations are included in SQL logs when enabled
1017
- - **Foreign key constraints**: Deletions may fail if other records reference this record
1018
- - **Dry-run support**: Use `--dry-run` to preview what would be deleted
1019
- - **Takes precedence**: If `deleteRecord` is present, normal create/update operations are skipped
1020
-
1021
- #### Use Cases
1022
- - Removing deprecated prompts, templates, or configurations
1023
- - Cleaning up test data
1024
- - Synchronizing deletions across environments
1025
- - Maintaining clean metadata through version control
1026
-
1027
- ### @file: References
1028
- When a field value starts with `@file:`, the tool will:
1029
- 1. Read content from the specified file for push operations
1030
- 2. Write content to the specified file for pull operations
1031
- 3. Track both files for change detection
1032
- 4. **For JSON files**: Automatically process any `@include` directives within them
1033
-
1034
- Examples:
1035
- - `@file:greeting.prompt.md` - File in same directory as JSON
1036
- - `@file:./shared/common-prompt.md` - Relative path
1037
- - `@file:../templates/standard-header.md` - Parent directory reference
1038
- - `@file:spec.json` - JSON file with `@include` directives (processed automatically)
1039
-
1040
- ### @url: References
1041
- When a field value starts with `@url:`, the tool will:
1042
- 1. Fetch content from the URL during push operations
1043
- 2. Cache the content with appropriate headers
1044
- 3. Support both HTTP(S) and file:// protocols
1045
-
1046
- Examples:
1047
- - `@url:https://example.com/prompts/greeting.md` - Remote content
1048
- - `@url:https://raw.githubusercontent.com/company/prompts/main/customer.md` - GitHub raw content
1049
- - `@url:file:///shared/network/drive/prompts/standard.md` - Local file URL
1050
-
1051
- ### @lookup: References (ENHANCED)
500
+ ### @lookup: References
1052
501
  Enable entity relationships using human-readable values:
1053
502
  - Basic syntax: `@lookup:EntityName.FieldName=Value`
1054
503
  - Multi-field syntax: `@lookup:EntityName.Field1=Value1&Field2=Value2`
@@ -1058,11 +507,11 @@ Enable entity relationships using human-readable values:
1058
507
  - Combined flags: `@lookup:EntityName.FieldName=Value?create&allowDefer`
1059
508
 
1060
509
  Examples:
1061
- - `@lookup:AI Prompt Types.Name=Chat` - Single field lookup, fails if not found
1062
- - `@lookup:Users.Email=john@example.com&Department=Sales` - Multi-field lookup for precise matching
1063
- - `@lookup:AI Prompt Categories.Name=Examples?create` - Creates if missing
1064
- - `@lookup:AI Prompt Categories.Name=Examples?create&Description=Example prompts` - Creates with description
1065
- - `@lookup:Dashboards.Name=Data Explorer?allowDefer` - Defers lookup if not found, retries at end of push
510
+ - `@lookup:AI Prompt Types.Name=Chat` -- Single field lookup, fails if not found
511
+ - `@lookup:Users.Email=john@example.com&Department=Sales` -- Multi-field lookup for precise matching
512
+ - `@lookup:AI Prompt Categories.Name=Examples?create` -- Creates if missing
513
+ - `@lookup:AI Prompt Categories.Name=Examples?create&Description=Example prompts` -- Creates with description
514
+ - `@lookup:Dashboards.Name=Data Explorer?allowDefer` -- Defers lookup if not found, retries at end of push
1066
515
 
1067
516
  #### Multi-Field Lookups
1068
517
  When you need to match records based on multiple criteria, use the multi-field syntax:
@@ -1073,11 +522,9 @@ When you need to match records based on multiple criteria, use the multi-field s
1073
522
  }
1074
523
  ```
1075
524
 
1076
- This ensures you get the exact record you want when multiple records might have the same value in a single field.
1077
-
1078
525
  #### Deferred Lookups (?allowDefer)
1079
526
 
1080
- The `?allowDefer` flag enables handling of circular dependencies between entities during push operations. Use this when Entity A references Entity B and Entity B references Entity A - or any situation where a lookup target might not exist yet during initial processing.
527
+ The `?allowDefer` flag enables handling of circular dependencies between entities during push operations. Use this when Entity A references Entity B and Entity B references Entity A -- or any situation where a lookup target might not exist yet during initial processing.
1081
528
 
1082
529
  **How it works:**
1083
530
 
@@ -1095,11 +542,16 @@ flowchart TD
1095
542
  H --> I[Phase 2.5: Re-process entire record]
1096
543
  I -->|Success| J[Update record with resolved field]
1097
544
  I -->|Failure| F
545
+
546
+ style C fill:#2d8659,stroke:#1a5c3a,color:#fff
547
+ style F fill:#b8762f,stroke:#8a5722,color:#fff
548
+ style J fill:#2d8659,stroke:#1a5c3a,color:#fff
549
+ style I fill:#7c5295,stroke:#563a6b,color:#fff
1098
550
  ```
1099
551
 
1100
552
  **When to use `?allowDefer`:**
1101
553
  - When Entity A references Entity B, and Entity B references Entity A
1102
- - When you're creating related records that need to reference each other
554
+ - When you are creating related records that need to reference each other
1103
555
  - When the lookup target might not exist yet during initial processing
1104
556
 
1105
557
  **Processing phases:**
@@ -1109,7 +561,7 @@ flowchart TD
1109
561
  4. After all other records are processed, deferred records are re-processed using the exact same logic
1110
562
  5. If retry succeeds, the record is updated with the resolved field; if it fails, an error is reported and the transaction rolls back
1111
563
 
1112
- **Example: Application Dashboard circular reference**
564
+ **Example: Application / Dashboard circular reference**
1113
565
 
1114
566
  The Applications entity can have `DefaultNavItems` (a JSON field) that contains nested references to Dashboards, while Dashboards have an `ApplicationID` that references Applications.
1115
567
 
@@ -1140,37 +592,6 @@ Since Applications are processed before Dashboards (alphabetical order), the Das
1140
592
  }
1141
593
  ```
1142
594
 
1143
- **Processing order:**
1144
- 1. Applications are processed first (per `directoryOrder` in `.mj-sync.json`):
1145
- - The Dashboard lookup fails (Dashboard doesn't exist yet)
1146
- - Because `?allowDefer` is set, the `DefaultNavItems` field is skipped
1147
- - Application IS saved (without the `DefaultNavItems` value)
1148
- - Application record is queued for re-processing
1149
- 2. Dashboards are processed:
1150
- - Dashboard references Application via `ApplicationID` - this lookup succeeds because Application was saved in step 1
1151
- - Dashboard is created normally
1152
- 3. Deferred records are re-processed (Phase 2.5):
1153
- - The Application record is processed again using the exact same logic
1154
- - The Dashboard lookup now succeeds since Dashboard exists in the database
1155
- - Application is updated with the resolved `DefaultNavItems` field
1156
-
1157
- **Console output:**
1158
- ```
1159
- Processing Applications...
1160
- ⏳ Deferring lookup for Applications.DefaultNavItems -> Dashboards
1161
- 📋 Queued Applications for deferred processing (record saved, some fields pending)
1162
- ✓ Created: 1
1163
-
1164
- Processing Dashboards...
1165
- ✓ Created: 1
1166
-
1167
- ⏳ Processing 1 deferred record...
1168
- ✓ Applications (ID=867CB743-...) - updated
1169
- ✓ Resolved 1 deferred record (0 created, 1 updated)
1170
- ```
1171
-
1172
- **Important:** The `?allowDefer` flag queues the entire record for re-processing, not just the failed field. This ensures the exact same processing logic is used on retry, including proper handling of nested lookups within JSON structures, `@parent` references, and all other field processing.
1173
-
1174
595
  **Combining flags:**
1175
596
  You can combine `?allowDefer` with `?create`:
1176
597
  ```json
@@ -1181,19 +602,19 @@ This means: "Look up the category, create if missing, and if the lookup still fa
1181
602
  **Important notes:**
1182
603
  - Deferred records are processed before the final commit (Phase 2.5)
1183
604
  - If any deferred record fails on retry, the entire push transaction is rolled back
1184
- - Use sparingly - only for genuine circular dependencies
605
+ - Use sparingly -- only for genuine circular dependencies
1185
606
  - The record must have a primaryKey defined in the metadata file
1186
607
 
1187
- ### @parent: References
608
+ ### @parent: References
1188
609
  Reference fields from the immediate parent entity in embedded collections:
1189
- - `@parent:ID` - Get the parent's ID field
1190
- - `@parent:Name` - Get the parent's Name field
610
+ - `@parent:ID` -- Get the parent's ID field
611
+ - `@parent:Name` -- Get the parent's Name field
1191
612
  - Works with any field from the parent entity
1192
613
 
1193
614
  ### @root: References
1194
615
  Reference fields from the root entity in nested structures:
1195
- - `@root:ID` - Get the root entity's ID
1196
- - `@root:CategoryID` - Get the root's CategoryID
616
+ - `@root:ID` -- Get the root entity's ID
617
+ - `@root:CategoryID` -- Get the root's CategoryID
1197
618
  - Useful for deeply nested relationships
1198
619
 
1199
620
  ### @env: References
@@ -1201,37 +622,43 @@ Support environment-specific values:
1201
622
  - `@env:VARIABLE_NAME`
1202
623
  - Useful for different environments (dev/staging/prod)
1203
624
 
1204
- ### Automatic JSON Stringification with Reference Processing
1205
-
1206
- When a field value is an array or object, the tool automatically:
1207
- 1. **Recursively processes** all `@lookup:`, `@file:`, `@parent:`, `@root:` references inside the object
1208
- 2. **Converts to JSON string** with pretty formatting (2-space indentation) for database storage
1209
- 3. **Maintains clean structure** in source files while storing as strings in database
625
+ ### Primary Key Handling
626
+ The tool automatically detects primary key fields from entity metadata:
627
+ - **Single primary keys**: Most common, stored as `{"ID": "value"}` or `{"CustomKeyName": "value"}`
628
+ - **Composite primary keys**: Multiple fields that together form the primary key
629
+ - **Auto-detection**: Tool reads entity metadata to determine primary key structure
630
+ - **Reference support**: Primary key values can use `@lookup`, `@parent`, and other reference types
1210
631
 
1211
- This is extremely powerful for JSON-typed fields like `Configuration`, `Settings`, `Metadata`, etc.
632
+ #### Using @lookup in Primary Keys
633
+ You can use `@lookup` references in primary key fields to avoid hardcoding GUIDs. This is especially useful when decorating existing records:
1212
634
 
1213
- #### Basic Example
1214
635
  ```json
1215
636
  {
1216
637
  "fields": {
1217
- "Name": "My Entity",
1218
- "Configuration": {
1219
- "setting1": "value1",
1220
- "setting2": {
1221
- "nested": true,
1222
- "items": [1, 2, 3]
1223
- }
1224
- },
1225
- "Tags": ["tag1", "tag2", "tag3"]
638
+ "Encrypt": true,
639
+ "AllowDecryptInAPI": false
640
+ },
641
+ "primaryKey": {
642
+ "ID": "@lookup:Entity Fields.EntityID=@lookup:Entities.Name=Test Tables&Name=ServerOnlyEncrypted"
1226
643
  }
1227
644
  }
1228
645
  ```
1229
646
 
1230
- The `Configuration` and `Tags` fields will automatically be converted to JSON strings when pushed to the database.
647
+ In this example:
648
+ 1. The inner `@lookup:Entities.Name=Test Tables` resolves to the Entity ID
649
+ 2. That ID is used to find the Entity Field with the matching `EntityID` and `Name`
650
+ 3. The resulting Entity Field ID becomes the primary key
1231
651
 
1232
- #### Advanced Example: References Inside JSON Fields
652
+ **Note:** Primary key lookups must resolve immediately -- the `?allowDefer` flag is not supported in primary key fields since the primary key is needed to determine if a record exists.
653
+
654
+ ### Automatic JSON Stringification with Reference Processing
655
+
656
+ When a field value is an array or object, the tool automatically:
657
+ 1. **Recursively processes** all `@lookup:`, `@file:`, `@parent:`, `@root:` references inside the object
658
+ 2. **Converts to JSON string** with pretty formatting (2-space indentation) for database storage
659
+ 3. **Maintains clean structure** in source files while storing as strings in database
1233
660
 
1234
- **This is the powerful part** - you can use `@lookup:` and other references INSIDE object-typed fields:
661
+ This is useful for JSON-typed fields like `Configuration`, `Settings`, `Metadata`, etc.
1235
662
 
1236
663
  ```json
1237
664
  {
@@ -1251,59 +678,35 @@ The `Configuration` and `Tags` fields will automatically be converted to JSON st
1251
678
  }
1252
679
  ```
1253
680
 
1254
- When pushed to the database, this becomes:
1255
- ```json
1256
- {
1257
- "Configuration": "{\"AgentID\":\"actual-uuid-here\",\"InitialMessage\":\"Analyze recent conversations\",\"Settings\":{\"MaxNotes\":5,\"Strategy\":\"Relevant\",\"TargetAgentID\":\"another-uuid-here\"}}"
1258
- }
1259
- ```
1260
-
1261
- **Benefits:**
1262
- - ✅ **Human-readable**: Use agent names, not UUIDs in your metadata
1263
- - ✅ **Maintainable**: Changes to entity names don't break references
1264
- - ✅ **Type-safe**: Structured objects in source, properly stringified for DB
1265
- - ✅ **Nested support**: References work at any depth in the object tree
681
+ When pushed to the database, the `Configuration` field is resolved (lookups become GUIDs) and stringified as JSON for storage.
1266
682
 
1267
- #### Common Use Cases
683
+ ### deleteRecord Directive
684
+ The tool supports deleting records from the database using a special `deleteRecord` directive in JSON files:
1268
685
 
1269
- **Scheduled Job Configuration:**
1270
686
  ```json
1271
687
  {
1272
- "Configuration": {
1273
- "AgentID": "@lookup:AI Agents.Name=Report Generator",
1274
- "Schedule": "daily",
1275
- "Recipients": "@lookup:Users.Email=admin@company.com"
688
+ "fields": {
689
+ "Name": "Obsolete Prompt",
690
+ "Description": "This prompt is no longer needed"
691
+ },
692
+ "primaryKey": {
693
+ "ID": "550e8400-e29b-41d4-a716-446655440000"
694
+ },
695
+ "deleteRecord": {
696
+ "delete": true
1276
697
  }
1277
698
  }
1278
699
  ```
1279
700
 
1280
- **Action Parameters:**
1281
- ```json
1282
- {
1283
- "DefaultParameters": {
1284
- "TargetEntityID": "@lookup:Entities.Name=Customers",
1285
- "TemplateID": "@lookup:Templates.Name=Welcome Email",
1286
- "Settings": {
1287
- "SendImmediate": true,
1288
- "Priority": "High"
1289
- }
1290
- }
1291
- }
1292
- ```
701
+ After successfully deleting the record, the tool updates the JSON file with a `deletedAt` timestamp. Important notes:
702
+ - **Primary key required**: You must specify the `primaryKey` to identify which record to delete
703
+ - **One-time operation**: Once `deletedAt` is set, the deletion will not be attempted again
704
+ - **SQL logging**: Delete operations are included in SQL logs when enabled
705
+ - **Foreign key constraints**: Deletions may fail if other records reference this record
706
+ - **Dry-run support**: Use `--dry-run` to preview what would be deleted
707
+ - **Takes precedence**: If `deleteRecord` is present, normal create/update operations are skipped
1293
708
 
1294
- **AI Configuration:**
1295
- ```json
1296
- {
1297
- "AIConfig": {
1298
- "PreferredModelID": "@lookup:AI Models.Name=GPT 4.1",
1299
- "FallbackModels": [
1300
- "@lookup:AI Models.Name=Claude Sonnet 3.7",
1301
- "@lookup:AI Models.Name=Gemini Pro"
1302
- ],
1303
- "Temperature": 0.7
1304
- }
1305
- }
1306
- ```
709
+ ## Content Composition
1307
710
 
1308
711
  ### {@include} References in Files
1309
712
  Enable content composition within non-JSON files (like .md, .html, .txt) using JSDoc-style include syntax:
@@ -1313,14 +716,6 @@ Enable content composition within non-JSON files (like .md, .html, .txt) using J
1313
716
  - Circular reference detection prevents infinite loops
1314
717
  - Works seamlessly with `@file:` references
1315
718
 
1316
- #### How It Works
1317
- When a JSON metadata file uses `@file:` to reference an external file, the MetadataSync tool:
1318
- 1. Loads the referenced file
1319
- 2. Scans for `{@include}` patterns
1320
- 3. Recursively resolves all includes
1321
- 4. Returns the fully composed content
1322
-
1323
- #### Example Usage
1324
719
  ```markdown
1325
720
  # My Prompt Template
1326
721
 
@@ -1334,62 +729,11 @@ When a JSON metadata file uses `@file:` to reference an external file, the Metad
1334
729
  Please analyze the following...
1335
730
  ```
1336
731
 
1337
- #### Complex Example with Nested Includes
1338
- Directory structure:
1339
- ```
1340
- prompts/
1341
- ├── customer-service/
1342
- │ ├── greeting.json # Uses @file:greeting.md
1343
- │ ├── greeting.md # Contains {@include} references
1344
- │ └── shared/
1345
- │ ├── tone.md
1346
- │ └── guidelines.md
1347
- └── common/
1348
- ├── company-info.md
1349
- └── legal-disclaimer.md
1350
- ```
1351
-
1352
- greeting.json:
1353
- ```json
1354
- {
1355
- "fields": {
1356
- "Name": "Customer Greeting",
1357
- "Prompt": "@file:greeting.md"
1358
- }
1359
- }
1360
- ```
1361
-
1362
- greeting.md:
1363
- ```markdown
1364
- # Customer Service Greeting
1365
-
1366
- {@include ./shared/tone.md}
1367
-
1368
- ## Guidelines
1369
- {@include ./shared/guidelines.md}
1370
-
1371
- ## Company Information
1372
- {@include ../common/company-info.md}
1373
-
1374
- ## Legal
1375
- {@include ../common/legal-disclaimer.md}
1376
- ```
1377
-
1378
- The final content pushed to the database will have all includes fully resolved.
1379
-
1380
- Benefits:
1381
- - **DRY Principle**: Share common content across multiple files
1382
- - **Maintainability**: Update shared content in one place
1383
- - **Flexibility**: Build complex documents from modular parts
1384
- - **Validation**: Automatic checking of included file existence and circular references
1385
-
1386
732
  ### @include References in JSON Files
1387
733
 
1388
- Enable modular JSON composition by including external JSON files directly into your metadata files. This feature allows you to break large JSON configurations into smaller, reusable components.
1389
-
1390
- #### Syntax Options
734
+ Enable modular JSON composition by including external JSON files directly into your metadata files:
1391
735
 
1392
- **Object Context - Property Spreading (Default)**
736
+ **Object Context -- Property Spreading (Default)**
1393
737
  ```json
1394
738
  {
1395
739
  "name": "Parent Record",
@@ -1397,7 +741,6 @@ Enable modular JSON composition by including external JSON files directly into y
1397
741
  "description": "Additional fields"
1398
742
  }
1399
743
  ```
1400
- The included file's properties are spread into the parent object.
1401
744
 
1402
745
  **Multiple Includes with Dot Notation (Eliminates VS Code Warnings)**
1403
746
  ```json
@@ -1409,9 +752,9 @@ The included file's properties are spread into the parent object.
1409
752
  "status": "Active"
1410
753
  }
1411
754
  ```
1412
- Use dot notation (`@include.anything`) to include multiple files at different positions in your object. The part after the dot is ignored by the processor but makes each key unique, eliminating VS Code's duplicate key warnings. The includes are processed in the order they appear, allowing precise control over property ordering.
755
+ Use dot notation (`@include.anything`) to include multiple files at different positions in your object. The part after the dot is ignored by the processor but makes each key unique, eliminating VS Code's duplicate key warnings.
1413
756
 
1414
- **Array Context - Element Insertion**
757
+ **Array Context -- Element Insertion**
1415
758
  ```json
1416
759
  [
1417
760
  {"name": "First item"},
@@ -1419,219 +762,39 @@ Use dot notation (`@include.anything`) to include multiple files at different po
1419
762
  {"name": "Last item"}
1420
763
  ]
1421
764
  ```
1422
- The included file's content is inserted as array element(s).
1423
765
 
1424
766
  **Explicit Mode Control**
1425
767
  ```json
1426
768
  {
1427
769
  "@include": {
1428
770
  "file": "child.json",
1429
- "mode": "spread" // or "element"
771
+ "mode": "spread"
1430
772
  }
1431
773
  }
1432
774
  ```
1433
775
 
1434
- #### Modes Explained
776
+ #### Modes
1435
777
 
1436
- **"spread" mode**:
1437
- - Merges all properties from the included file into the parent object
1438
- - Only works when including an object into an object
1439
- - Parent properties override child properties if there are conflicts
1440
- - Default mode for objects
1441
-
1442
- **"element" mode**:
1443
- - Directly inserts the JSON content at that position
1444
- - Works with any JSON type (object, array, string, number, etc.)
1445
- - Replaces the @include directive with the actual content
1446
- - Default mode for arrays when using string syntax
778
+ - **"spread" mode**: Merges all properties from the included file into the parent object. Only works when including an object into an object. Parent properties override child properties on conflict. This is the default mode for objects.
779
+ - **"element" mode**: Directly inserts the JSON content at that position. Works with any JSON type (object, array, string, number, etc.). Default mode for arrays when using string syntax.
1447
780
 
1448
781
  #### Path Resolution
1449
782
  - All paths are relative to the file containing the @include
1450
783
  - Supports: `"child.json"`, `"./child.json"`, `"../shared/base.json"`, `"subfolder/config.json"`
1451
784
  - Circular references are detected and prevented
1452
785
 
1453
- #### Complex Example
1454
-
1455
- Directory structure:
1456
- ```
1457
- metadata/
1458
- ├── components/
1459
- │ ├── dashboard.json
1460
- │ ├── base-props.json
1461
- │ └── items/
1462
- │ └── dashboard-items.json
1463
- └── shared/
1464
- └── common-settings.json
1465
- ```
1466
-
1467
- dashboard.json:
1468
- ```json
1469
- {
1470
- "fields": {
1471
- "Name": "Analytics Dashboard",
1472
- "@include.common": "../shared/common-settings.json",
1473
- "Type": "Dashboard",
1474
- "@include.defaults": "../shared/default-values.json",
1475
- "Configuration": {
1476
- "@include": {"file": "./base-props.json", "mode": "element"}
1477
- }
1478
- },
1479
- "relatedEntities": {
1480
- "Dashboard Items": [
1481
- "@include:./items/dashboard-items.json"
1482
- ]
1483
- }
1484
- }
1485
- ```
1486
-
1487
- common-settings.json:
1488
- ```json
1489
- {
1490
- "CategoryID": "@lookup:Categories.Name=Analytics",
1491
- "Status": "Active",
1492
- "Priority": 1
1493
- }
1494
- ```
1495
-
1496
- base-props.json:
1497
- ```json
1498
- {
1499
- "refreshInterval": 60,
1500
- "theme": "dark",
1501
- "layout": "grid"
1502
- }
1503
- ```
1504
-
1505
- dashboard-items.json:
1506
- ```json
1507
- [
1508
- {"name": "Revenue Chart", "type": "chart"},
1509
- {"name": "User Stats", "type": "stats"},
1510
- {"name": "Activity Feed", "type": "feed"}
1511
- ]
1512
- ```
1513
-
1514
- Result after processing:
1515
- ```json
1516
- {
1517
- "fields": {
1518
- "Name": "Analytics Dashboard",
1519
- "CategoryID": "@lookup:Categories.Name=Analytics",
1520
- "Status": "Active",
1521
- "Priority": 1,
1522
- "Type": "Dashboard",
1523
- "Configuration": {
1524
- "refreshInterval": 60,
1525
- "theme": "dark",
1526
- "layout": "grid"
1527
- }
1528
- },
1529
- "relatedEntities": {
1530
- "Dashboard Items": [
1531
- {"name": "Revenue Chart", "type": "chart"},
1532
- {"name": "User Stats", "type": "stats"},
1533
- {"name": "Activity Feed", "type": "feed"}
1534
- ]
1535
- }
1536
- }
1537
- ```
1538
-
1539
- #### Use Cases
1540
- - **Shared Configurations**: Reuse common settings across multiple entities
1541
- - **Modular Records**: Build complex records from smaller components
1542
- - **Template Libraries**: Create libraries of reusable JSON fragments
1543
- - **Environment Configs**: Include environment-specific settings
1544
- - **Large Data Sets**: Break up large JSON files for better maintainability
1545
- - **VS Code Compatibility**: Use dot notation to avoid duplicate key warnings when including multiple files
1546
-
1547
- #### Practical Example: Component with Multiple Includes
1548
- ```json
1549
- {
1550
- "name": "DashboardComponent",
1551
- "type": "dashboard",
1552
- "@include.dataRequirements": "../shared/data-requirements.json",
1553
- "functionalRequirements": "Dashboard displays real-time metrics...",
1554
- "@include.libraries": "../shared/chart-libraries.json",
1555
- "technicalDesign": "Component uses React hooks for state...",
1556
- "@include.eventHandlers": "../shared/event-handlers.json",
1557
- "code": "const Dashboard = () => { ... }"
1558
- }
1559
- ```
1560
- In this example, data requirements, libraries, and event handlers are spread into the component definition at their specific positions, maintaining a logical property order while avoiding VS Code warnings about duplicate `@include` keys.
1561
-
1562
- #### @include in Referenced JSON Files
1563
- When using `@file:` to reference a JSON file, any `@include` directives within that JSON file are automatically processed:
1564
-
1565
- #### @file References in Included JSON Files
1566
- The system now automatically resolves `@file` references found within JSON files that are pulled in via `@include`. This allows for complete nesting of references:
1567
-
1568
- ```json
1569
- // main-entity.json
1570
- {
1571
- "fields": {
1572
- "Name": "MyComponent",
1573
- "Specification": "@file:files/component-spec.json"
1574
- }
1575
- }
1576
-
1577
- // files/component-spec.json
1578
- {
1579
- "name": "ComponentSpec",
1580
- "@include.base": "../shared/base-spec.json",
1581
- "customFields": {
1582
- "feature": "advanced"
1583
- },
1584
- "@include.libs": "../shared/libraries.json"
1585
- }
1586
- ```
1587
-
1588
- The `component-spec.json` file's `@include` directives are processed before the content is returned to the `Specification` field, ensuring all includes are resolved.
1589
-
1590
- #### Nested @file References in JSON Files
1591
- The system now recursively processes `@file` references within JSON files loaded via `@file`. This enables powerful composition patterns:
1592
-
1593
- ```json
1594
- // components.json
1595
- {
1596
- "fields": {
1597
- "Name": "RecentDealsList",
1598
- "Specification": "@file:spec/recent-deals-list.spec.json"
1599
- }
1600
- }
1601
-
1602
- // spec/recent-deals-list.spec.json
1603
- {
1604
- "name": "RecentDealsList",
1605
- "description": "List of recent deals",
1606
- "code": "@file:../code/recent-deals-list.js", // This nested @file is now resolved!
1607
- "style": "@file:../styles/deals.css",
1608
- "config": {
1609
- "template": "@file:../templates/deal-row.html" // Even deeply nested @file references work
1610
- }
1611
- }
1612
- ```
1613
-
1614
- All `@file` references are recursively resolved, regardless of nesting depth. The final result will have all file contents properly loaded and embedded.
1615
-
1616
786
  #### Processing Order
1617
- 1. @include directives are processed first (recursively)
1618
- 2. @file references are recursively resolved (including nested ones in JSON)
1619
- 3. Then @template references
1620
- 4. Finally, other @ references (@lookup, etc.)
787
+ 1. `@include` directives are processed first (recursively)
788
+ 2. `@file` references are recursively resolved (including nested ones in JSON)
789
+ 3. Then `@template` references
790
+ 4. Finally, other `@` references (`@lookup`, etc.)
1621
791
 
1622
792
  This ensures that included content can contain other special references that will be properly resolved.
1623
793
 
1624
- **New Feature**: @file references within @included JSON files are now automatically resolved. This means you can have:
1625
- - A main JSON file with `@include` directives
1626
- - The included JSON files can have `@file` references to load code, templates, etc.
1627
- - Those @file references are resolved to their actual content
1628
- - If the @file points to a JSON file with @include directives, those are also processed
1629
-
1630
794
  ### @template: References
1631
795
  Enable JSON template composition for reusable configurations:
1632
796
 
1633
- #### String Template Reference
1634
- Use `@template:` to replace any value with template content:
797
+ **String Template Reference:**
1635
798
  ```json
1636
799
  {
1637
800
  "relatedEntities": {
@@ -1640,20 +803,18 @@ Use `@template:` to replace any value with template content:
1640
803
  }
1641
804
  ```
1642
805
 
1643
- #### Object Template Merging
1644
- Use `@template` field within objects to merge template content:
806
+ **Object Template Merging:**
1645
807
  ```json
1646
808
  {
1647
809
  "fields": {
1648
810
  "Name": "My Prompt",
1649
811
  "@template": "templates/standard-prompt-settings.json",
1650
- "Temperature": 0.9 // Overrides template value
812
+ "Temperature": 0.9
1651
813
  }
1652
814
  }
1653
815
  ```
1654
816
 
1655
- #### Multiple Template Merging
1656
- Merge multiple templates in order (later templates override earlier ones):
817
+ **Multiple Template Merging** (later templates override earlier ones):
1657
818
  ```json
1658
819
  {
1659
820
  "fields": {
@@ -1661,482 +822,183 @@ Merge multiple templates in order (later templates override earlier ones):
1661
822
  "templates/base-settings.json",
1662
823
  "templates/customer-service-defaults.json"
1663
824
  ],
1664
- "Name": "Customer Bot" // Local fields override all templates
825
+ "Name": "Customer Bot"
1665
826
  }
1666
827
  }
1667
828
  ```
1668
829
 
1669
- #### Nested Templates
1670
- Templates can reference other templates:
1671
- ```json
1672
- // templates/high-performance-models.json
1673
- [
1674
- {
1675
- "fields": {
1676
- "@template": "../templates/model-defaults.json",
1677
- "ModelID": "@lookup:AI Models.Name=GPT 4o"
1678
- }
1679
- }
1680
- ]
1681
- ```
1682
-
1683
- #### Template Benefits
1684
- - **DRY Principle**: Define configurations once, use everywhere
1685
- - **Maintainability**: Update template to affect all uses
1686
- - **Flexibility**: Use at any JSON level
1687
- - **Composability**: Build complex configurations from simple parts
1688
- - **Override Support**: Local values always override template values
1689
-
1690
- ## CLI Commands
1691
-
1692
- All MetadataSync functionality is accessed through the MemberJunction CLI (`mj`) under the `sync` namespace. The commands previously available through `mj-sync` are now integrated as `mj sync` commands:
1693
-
1694
- ```bash
1695
- # Validate all metadata files
1696
- mj sync validate
1697
-
1698
- # Validate a specific directory
1699
- mj sync validate --dir="./metadata"
1700
-
1701
- # Validate with detailed output
1702
- mj sync validate --verbose
1703
-
1704
- # Validate with JSON output for CI/CD
1705
- mj sync validate --format=json
1706
-
1707
- # Save validation report to markdown file
1708
- mj sync validate --save-report
1709
-
1710
- # Initialize a directory for metadata sync
1711
- mj sync init
1712
-
1713
- # Pull all AI Prompts from database to ai-prompts directory
1714
- mj sync pull --entity="AI Prompts"
1715
-
1716
- # Pull specific records by filter
1717
- mj sync pull --entity="AI Prompts" --filter="CategoryID='customer-service-id'"
1718
-
1719
- # Pull multiple records into a single file
1720
- mj sync pull --entity="AI Prompts" --multi-file="all-prompts"
1721
- mj sync pull --entity="AI Prompts" --filter="Status='Active'" --multi-file="active-prompts.json"
1722
-
1723
- # Push all changes from current directory and subdirectories
1724
- mj sync push
1725
-
1726
- # Push only specific entity directory
1727
- mj sync push --dir="ai-prompts"
1728
-
1729
- # Push with verbose output
1730
- mj sync push -v
1731
- mj sync push --verbose
1732
-
1733
- # Dry run to see what would change
1734
- mj sync push --dry-run
1735
-
1736
- # Push with parallel processing
1737
- mj sync push --parallel-batch-size=20 # Process 20 records in parallel (default: 10, max: 50)
1738
-
1739
- # Directory filtering - exclude specific directories
1740
- mj sync push --exclude="actions" # Exclude single directory
1741
- mj sync push --exclude="actions,templates" # Exclude multiple directories (comma-separated)
1742
- mj sync push --exclude="*-test,*-old" # Exclude using glob patterns
1743
-
1744
- # Directory filtering - include only specific directories
1745
- mj sync push --include="prompts,agent-types" # Include only these directories
1746
- mj sync push --include="ai-*" # Include using glob patterns
1747
-
1748
- # Filtering works with other options
1749
- mj sync push --dir="metadata" --exclude="actions" --dry-run
1750
- mj sync status --exclude="actions,templates"
1751
-
1752
- # Show status of local vs database
1753
- mj sync status
1754
-
1755
- # Watch for changes and auto-push
1756
- mj sync watch
1757
-
1758
- # CI/CD mode (push with no prompts, fails on validation errors)
1759
- mj sync push --ci
1760
-
1761
- # Push/Pull without validation
1762
- mj sync push --no-validate
1763
- mj sync pull --entity="AI Prompts" --no-validate
1764
-
1765
- # Reset file checksums after manual edits
1766
- mj sync file-reset
1767
- ```
1768
-
1769
- ## Configuration
1770
-
1771
- The tool uses the existing `mj.config.cjs` for database configuration, eliminating the need for separate connection settings.
1772
-
1773
- Configuration follows a hierarchical structure:
1774
- - **Root config**: Global settings for all operations
1775
- - **Entity configs**: Each entity directory has its own config defining the entity type
1776
- - **Inheritance**: All files within an entity directory are treated as records of that entity type
1777
-
1778
- ### Push Configuration Options
1779
-
1780
- The push command supports several configuration options to control how records are synchronized to the database:
1781
-
1782
- #### autoCreateMissingRecords
1783
-
1784
- When set to `true`, the push command will automatically create new records when a primaryKey is specified but the record doesn't exist in the database. This is useful when:
1785
- - Migrating data between environments
1786
- - Restoring records from backups
1787
- - Initializing a new database with known IDs
830
+ ## Default Value Inheritance
1788
831
 
1789
- ```json
1790
- {
1791
- "push": {
1792
- "autoCreateMissingRecords": true
1793
- }
1794
- }
1795
- ```
832
+ The tool implements a cascading inheritance system for field defaults, similar to CSS or OOP inheritance:
1796
833
 
1797
- **Warning**: When enabled, you'll see: `🔧 WARNING: autoCreateMissingRecords is enabled - Missing records with primaryKey will be created`
834
+ 1. **Entity-level defaults** (in `.mj-sync.json`) -- Base defaults for all records
835
+ 2. **Folder-level defaults** (in `.mj-folder.json`) -- Override/extend entity defaults
836
+ 3. **Nested folder defaults** -- Override/extend parent folder defaults
837
+ 4. **Record-level values** -- Override all inherited defaults
1798
838
 
1799
- #### alwaysPush
839
+ ```mermaid
840
+ flowchart TD
841
+ ELD[".mj-sync.json\nTemperature: 0.7\nMaxTokens: 1500"] --> FD1[".mj-folder.json\nTemperature: 0.8\n(overrides entity)"]
842
+ FD1 --> FD2[".mj-folder.json\nTemperature: 0.6\n(overrides parent)"]
843
+ FD2 --> REC["urgent.json\nTemperature: 0.9\n(overrides all)"]
1800
844
 
1801
- When set to `true`, forces ALL records to be saved to the database regardless of their dirty state. This bypasses the normal dirty checking mechanism and ensures every record is written to the database.
845
+ ELD -..->|inherited| FD1
846
+ FD1 -..->|inherited| FD2
847
+ FD2 -..->|inherited| REC
1802
848
 
1803
- Use cases:
1804
- - **Ensuring complete synchronization** - When you need absolute certainty that all metadata is in sync
1805
- - **Bypassing dirty detection issues** - If file content changes aren't being detected properly
1806
- - **Force refresh** - When you want to refresh all database records with file content
1807
- - **After database restoration** - To ensure metadata matches file system after database operations
849
+ FINAL["Final Values:\nTemperature: 0.9 (record)\nMaxTokens: 1500 (entity)"]
850
+ REC --> FINAL
1808
851
 
1809
- ```json
1810
- {
1811
- "push": {
1812
- "alwaysPush": true
1813
- }
1814
- }
852
+ style ELD fill:#2d6a9f,stroke:#1a4971,color:#fff
853
+ style FD1 fill:#7c5295,stroke:#563a6b,color:#fff
854
+ style FD2 fill:#7c5295,stroke:#563a6b,color:#fff
855
+ style REC fill:#2d8659,stroke:#1a5c3a,color:#fff
856
+ style FINAL fill:#b8762f,stroke:#8a5722,color:#fff
1815
857
  ```
1816
858
 
1817
- **Warning**: When enabled, you'll see: `⚡ WARNING: alwaysPush is enabled - ALL records will be saved to database regardless of changes`
1818
-
1819
- **Note**: This flag should be used judiciously as it will cause database writes for all records, even those that haven't changed. It's recommended to enable this temporarily when needed, then disable it for normal operations.
1820
-
1821
- ### Parallel Processing
1822
-
1823
- MetadataSync now supports parallel processing of records during push operations, significantly improving performance for large datasets.
1824
-
1825
- #### How It Works
1826
-
1827
- Records are automatically grouped into dependency levels:
1828
- - **Level 0**: Records with no dependencies
1829
- - **Level 1**: Records that depend only on Level 0 records
1830
- - **Level 2**: Records that depend on Level 0 or Level 1 records
1831
- - And so on...
1832
-
1833
- Records within the same dependency level can be safely processed in parallel since they have no dependencies on each other.
1834
-
1835
- #### Configuration
1836
-
1837
- Use the `--parallel-batch-size` flag to control parallelism:
1838
-
1839
- ```bash
1840
- # Default: 10 records in parallel
1841
- mj sync push
1842
-
1843
- # Process 20 records in parallel
1844
- mj sync push --parallel-batch-size=20
1845
-
1846
- # Maximum parallelism (50 records)
1847
- mj sync push --parallel-batch-size=50
1848
-
1849
- # Conservative approach for debugging
1850
- mj sync push --parallel-batch-size=1
859
+ ### Inheritance Example
1851
860
  ```
1852
-
1853
- #### Performance Benefits
1854
-
1855
- - **2-3x faster** for typical metadata pushes
1856
- - **5-10x faster** for records with many file references (@file) or lookups (@lookup)
1857
- - Most beneficial when processing large numbers of independent records
1858
-
1859
- #### When to Use
1860
-
1861
- **Recommended for:**
1862
- - Large initial data imports
1863
- - Bulk metadata updates
1864
- - CI/CD pipelines with time constraints
1865
-
1866
- **Use conservative settings for:**
1867
- - Debugging sync issues
1868
- - Working with complex dependencies
1869
- - Limited database connection pools
1870
-
1871
- ### Directory Processing Order
1872
-
1873
- 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.
1874
-
1875
- #### Directory Order Configuration
1876
-
1877
- Directory order is configured in the root-level `.mj-sync.json` file only (not inherited by subdirectories):
1878
-
1879
- ```json
1880
- {
1881
- "version": "1.0.0",
1882
- "directoryOrder": [
1883
- "prompts",
1884
- "agent-types"
1885
- ]
1886
- }
861
+ ai-prompts/.mj-sync.json -> Temperature: 0.7, MaxTokens: 1500
862
+ +-- customer-service/.mj-folder.json -> Temperature: 0.8 (overrides)
863
+ | +-- greeting.json -> Uses Temperature: 0.8, MaxTokens: 1500
864
+ | +-- escalation/.mj-folder.json -> Temperature: 0.6 (overrides again)
865
+ | +-- urgent.json -> Temperature: 0.9 (record override)
1887
866
  ```
1888
867
 
1889
- #### How It Works
1890
-
1891
- - **Ordered Processing**: Directories listed in `directoryOrder` are processed first, in the specified order
1892
- - **Remaining Directories**: Any directories not listed are processed after the ordered ones, in alphabetical order
1893
- - **Dependency Management**: Ensures prompts are created before agent types that reference them
1894
- - **Flexible**: Only specify the directories that have order requirements
1895
-
1896
- #### Example Use Cases
1897
-
1898
- 1. **AI Prompts → Agent Types**: Create prompts before agent types that reference them
1899
- 2. **Categories → Items**: Create category records before items that reference them
1900
- 3. **Parent → Child**: Process parent entities before child entities with foreign key dependencies
868
+ ## Resolution Tracking with `__mj_sync_notes`
1901
869
 
1902
- ### Ignore Directories
870
+ When you use `@lookup` or `@parent` references in your metadata files, MetadataSync can track how these references were resolved during push operations. This information is written to a `__mj_sync_notes` key in each record, providing transparency into the resolution process.
1903
871
 
1904
- The MetadataSync tool supports ignoring specific directories during push/pull operations. This is useful for:
1905
- - Excluding output or example directories from processing
1906
- - Skipping temporary or build directories
1907
- - Organizing support files without them being processed as metadata
872
+ **Note:** This feature is **disabled by default** to keep metadata files clean. Enable it when you need to debug lookup resolutions or understand how references are being resolved.
1908
873
 
1909
- #### Configuration
874
+ ### Enabling Resolution Tracking
1910
875
 
1911
- Ignore directories are configured in `.mj-sync.json` files and are **cumulative** through the directory hierarchy:
876
+ Add `emitSyncNotes` to your `.mj-sync.json` configuration:
1912
877
 
878
+ **Root-level configuration** (applies to all entity directories):
1913
879
  ```json
1914
880
  {
1915
- "version": "1.0.0",
1916
- "ignoreDirectories": [
1917
- "output",
1918
- "examples",
1919
- "templates"
1920
- ]
881
+ "version": "1.0",
882
+ "emitSyncNotes": true,
883
+ "directoryOrder": ["..."]
1921
884
  }
1922
885
  ```
1923
886
 
1924
- #### How It Works
1925
-
1926
- - **Cumulative Inheritance**: Each directory inherits ignore patterns from its parent directories
1927
- - **Relative Paths**: Directory names are relative to the location of the `.mj-sync.json` file
1928
- - **Simple Patterns**: Supports exact directory names (e.g., "output", "temp")
1929
- - **Additive**: Child directories can add their own ignore patterns to parent patterns
1930
-
1931
- #### Example
1932
-
1933
- ```
1934
- metadata/.mj-sync.json → ignoreDirectories: ["output", "temp"]
1935
- ├── prompts/.mj-sync.json → ignoreDirectories: ["examples"]
1936
- │ ├── output/ → IGNORED (from root)
1937
- │ ├── examples/ → IGNORED (from prompts)
1938
- │ └── production/.mj-sync.json → ignoreDirectories: ["drafts"]
1939
- │ ├── drafts/ → IGNORED (from production)
1940
- │ └── output/ → IGNORED (inherited from root)
1941
- ```
1942
-
1943
- In this example:
1944
- - Root level ignores "output" and "temp" everywhere
1945
- - Prompts directory adds "examples" to the ignore list
1946
- - Production subdirectory further adds "drafts"
1947
- - All patterns are cumulative, so production inherits all parent ignores
1948
-
1949
- ### SQL Logging
1950
-
1951
- The MetadataSync tool now supports SQL logging for capturing all database operations during push commands. This feature is useful for:
1952
- - Creating migration files from MetadataSync operations
1953
- - Debugging database changes
1954
- - Understanding what SQL operations occur during push
1955
- - Creating migration scripts for deployment to other environments
1956
-
1957
- #### SQL Logging Configuration
1958
-
1959
- SQL logging is configured in the root-level `.mj-sync.json` file only (not inherited by subdirectories):
1960
-
887
+ **Entity-level override** (in an entity directory's `.mj-sync.json`):
1961
888
  ```json
1962
889
  {
1963
- "version": "1.0.0",
1964
- "sqlLogging": {
1965
- "enabled": true,
1966
- "outputDirectory": "./sql_logging",
1967
- "formatAsMigration": true
1968
- }
890
+ "entity": "AI Prompts",
891
+ "emitSyncNotes": true
1969
892
  }
1970
893
  ```
1971
894
 
1972
- #### SQL Logging Options
1973
-
1974
- | Option | Type | Default | Description |
1975
- |--------|------|---------|-------------|
1976
- | `enabled` | boolean | false | Whether to enable SQL logging during push operations |
1977
- | `outputDirectory` | string | "./sql_logging" | Directory to output SQL log files (relative to command execution directory) |
1978
- | `formatAsMigration` | boolean | false | Whether to format SQL as migration-ready files with Flyway schema placeholders |
1979
- | `filterPatterns` | string[] | undefined | Array of patterns to filter SQL statements (see below) |
1980
- | `filterType` | "exclude" \| "include" | "exclude" | How to apply filter patterns |
1981
-
1982
- #### SQL Log File Format
1983
-
1984
- When `formatAsMigration` is `false`, log files are named:
1985
- ```
1986
- metadatasync-push-YYYY-MM-DDTHH-MM-SS.sql
1987
- ```
1988
-
1989
- When `formatAsMigration` is `true`, log files are named as Flyway migrations:
1990
- ```
1991
- VYYYYMMDDHHMMSS__MetadataSync_Push.sql
1992
- ```
1993
-
1994
- Migration files include:
1995
- - Header comments with timestamp and description
1996
- - Schema placeholders that can be replaced during deployment
1997
- - Properly formatted SQL statements with parameters
1998
-
1999
- #### Example Usage
2000
-
2001
- 1. **Enable SQL logging** in your root `.mj-sync.json`:
2002
- ```json
2003
- {
2004
- "version": "1.0.0",
2005
- "sqlLogging": {
2006
- "enabled": true,
2007
- "outputDirectory": "./migrations",
2008
- "formatAsMigration": true
2009
- }
2010
- }
2011
- ```
2012
-
2013
- 2. **Run push command** as normal:
2014
- ```bash
2015
- mj sync push
2016
- ```
2017
-
2018
- 3. **Review generated SQL** in the output directory:
2019
- ```
2020
- migrations/
2021
- └── V20241215103045__MetadataSync_Push.sql
2022
- ```
2023
-
2024
- The SQL logging runs in parallel with the actual database operations, ensuring minimal performance impact while capturing all SQL statements for review and potential migration use.
2025
-
2026
- #### SQL Filtering Patterns
2027
-
2028
- The `filterPatterns` option allows you to include or exclude specific SQL statements from logging. It supports both regex patterns and simple wildcard patterns:
2029
-
2030
- **Pattern Types:**
2031
- - **Regex patterns**: Start with `/` and optionally end with flags (e.g., `/spCreate.*Run/i`)
2032
- - **Simple wildcards**: Use `*` as a wildcard (e.g., `*AIPrompt*`)
895
+ The inheritance works as follows:
896
+ - Entity-level `emitSyncNotes` takes precedence if explicitly set
897
+ - If not set at entity level, inherits from root `.mj-sync.json`
898
+ - Defaults to `false` if not set anywhere
2033
899
 
2034
- **Examples:**
900
+ ### Example Output
2035
901
 
2036
902
  ```json
2037
903
  {
2038
- "sqlLogging": {
2039
- "enabled": true,
2040
- "filterPatterns": [
2041
- "*AIPrompt*", // Exclude anything containing "AIPrompt"
2042
- "/^EXEC sp_/i", // Exclude stored procedures starting with "sp_"
2043
- "*EntityFieldValue*", // Exclude EntityFieldValue operations
2044
- "/INSERT INTO (__mj|mj)/i" // Exclude inserts to system tables
2045
- ],
2046
- "filterType": "exclude" // Default - exclude matching patterns
2047
- }
2048
- }
2049
- ```
2050
-
2051
- **Include Mode Example:**
2052
- ```json
2053
- {
2054
- "sqlLogging": {
2055
- "enabled": true,
2056
- "filterPatterns": [
2057
- "*User*", // Only log User-related SQL
2058
- "*Role*", // Only log Role-related SQL
2059
- "/sp_ChangePassword/i" // Include password change procedures
2060
- ],
2061
- "filterType": "include" // Only log statements matching patterns
2062
- }
904
+ "fields": {
905
+ "Name": "ServerOnlyEncrypted",
906
+ "Encrypt": true,
907
+ "EncryptionKeyID": "@lookup:MJ: Encryption Keys.Name=Test Encryption Key"
908
+ },
909
+ "primaryKey": {
910
+ "ID": "@lookup:Entity Fields.EntityID=@lookup:Entities.Name=Test Tables&Name=ServerOnlyEncrypted"
911
+ },
912
+ "sync": {
913
+ "lastModified": "2025-12-25T16:14:32.605Z",
914
+ "checksum": "7e989e08396f6cffb8b2d70958018b21..."
915
+ },
916
+ "__mj_sync_notes": [
917
+ {
918
+ "type": "lookup",
919
+ "field": "primaryKey.ID",
920
+ "expression": "@lookup:Entity Fields.EntityID=@lookup:Entities.Name=Test Tables&Name=ServerOnlyEncrypted",
921
+ "resolved": "F501E294-5F5F-44C6-AD06-5C9754A13D29",
922
+ "nested": [
923
+ {
924
+ "expression": "@lookup:Entities.Name=Test Tables",
925
+ "resolved": "0fde4c2c-26b1-45e9-b504-5d4a6f4201cf"
926
+ }
927
+ ]
928
+ },
929
+ {
930
+ "type": "lookup",
931
+ "field": "fields.EncryptionKeyID",
932
+ "expression": "@lookup:MJ: Encryption Keys.Name=Test Encryption Key",
933
+ "resolved": "85B814C8-A01B-4AE3-A252-DC9D54C914C7"
934
+ }
935
+ ]
2063
936
  }
2064
- ```
2065
-
2066
- **Simple Wildcard Syntax:**
2067
- - `*pattern*` - Contains pattern (case-insensitive)
2068
- - `pattern*` - Starts with pattern
2069
- - `*pattern` - Ends with pattern
2070
- - `pattern` - Exact match
2071
-
2072
- ### User Role Validation
937
+ ```
2073
938
 
2074
- 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.
939
+ ### Note Structure
2075
940
 
2076
- #### Configuration
941
+ | Field | Description |
942
+ |-------|-------------|
943
+ | `type` | Resolution type: `"lookup"` for `@lookup` references, `"parent"` for `@parent` references |
944
+ | `field` | Field path where the resolution occurred (e.g., `"primaryKey.ID"`, `"fields.CategoryID"`) |
945
+ | `expression` | The original reference expression before resolution |
946
+ | `resolved` | The resolved value (typically a GUID) |
947
+ | `nested` | (Optional) Array of nested resolutions for expressions containing nested `@lookup` references |
2077
948
 
2078
- Add the `userRoleValidation` configuration to your root `.mj-sync.json` file:
949
+ The `__mj_sync_notes` key uses a double underscore prefix (`__`) to clearly indicate it is system-managed. Do not manually edit this section -- it is regenerated on each push when `emitSyncNotes` is enabled.
2079
950
 
2080
- ```json
2081
- {
2082
- "version": "1.0.0",
2083
- "userRoleValidation": {
2084
- "enabled": true,
2085
- "allowedRoles": [
2086
- "Administrator",
2087
- "Developer",
2088
- "Content Manager"
2089
- ],
2090
- "allowUsersWithoutRoles": false
2091
- }
2092
- }
2093
- ```
951
+ ## CLI Commands
2094
952
 
2095
- #### Configuration Options
953
+ All MetadataSync functionality is accessed through the MemberJunction CLI (`mj`) under the `sync` namespace:
2096
954
 
2097
- | Option | Type | Default | Description |
2098
- |--------|------|---------|-------------|
2099
- | `enabled` | boolean | false | Enable user role validation for UserID fields |
2100
- | `allowedRoles` | string[] | [] | List of role names that are allowed |
2101
- | `allowUsersWithoutRoles` | boolean | false | Allow users without any assigned roles |
955
+ ```bash
956
+ # Initialize a directory for metadata sync
957
+ mj sync init
2102
958
 
2103
- #### How It Works
959
+ # Validate metadata files
960
+ mj sync validate
961
+ mj sync validate --dir="./metadata"
962
+ mj sync validate --verbose
963
+ mj sync validate --format=json
964
+ mj sync validate --save-report
2104
965
 
2105
- 1. During validation, all user roles are loaded from the database and cached
2106
- 2. For each UserID field in metadata files, the validator checks:
2107
- - If the user exists and has roles assigned
2108
- - If the user has at least one of the allowed roles
2109
- 3. Validation fails if:
2110
- - A UserID references a user without any roles (unless `allowUsersWithoutRoles` is true)
2111
- - A UserID references a user whose roles are not in the `allowedRoles` list
966
+ # Pull metadata from database to files
967
+ mj sync pull --entity="AI Prompts"
968
+ mj sync pull --entity="AI Prompts" --filter="CategoryID='customer-service-id'"
969
+ mj sync pull --entity="AI Prompts" --multi-file="all-prompts"
2112
970
 
2113
- #### Example
971
+ # Push local changes to database
972
+ mj sync push
973
+ mj sync push --dir="ai-prompts"
974
+ mj sync push -v
975
+ mj sync push --dry-run
976
+ mj sync push --parallel-batch-size=20
2114
977
 
2115
- Given a metadata file with a UserID field:
978
+ # Directory filtering
979
+ mj sync push --exclude="actions"
980
+ mj sync push --exclude="actions,templates"
981
+ mj sync push --exclude="*-test,*-old"
982
+ mj sync push --include="prompts,agent-types"
983
+ mj sync push --include="ai-*"
2116
984
 
2117
- ```json
2118
- {
2119
- "fields": {
2120
- "Name": "Admin Action",
2121
- "UserID": "user-123"
2122
- }
2123
- }
2124
- ```
985
+ # Status and watch
986
+ mj sync status
987
+ mj sync watch
2125
988
 
2126
- The validation will:
2127
- 1. Check if user-123 exists in the system
2128
- 2. Verify that user-123 has one of the allowed roles
2129
- 3. Report an error if the user doesn't have appropriate roles
989
+ # CI/CD mode
990
+ mj sync push --ci
2130
991
 
2131
- #### Error Messages
992
+ # Skip validation
993
+ mj sync push --no-validate
2132
994
 
995
+ # Reset file checksums
996
+ mj sync file-reset
2133
997
  ```
2134
- ✗ UserID 'user-123' does not have any assigned roles
2135
- Suggestion: User must have one of these roles: Administrator, Developer
2136
998
 
2137
- UserID 'user-456' has roles [Viewer] but none are in allowed list
2138
- Suggestion: Allowed roles: Administrator, Developer, Content Manager
2139
- ```
999
+ ## Configuration
1000
+
1001
+ The tool uses the existing `mj.config.cjs` for database configuration and a hierarchical structure of `.mj-sync.json` and `.mj-folder.json` files for sync behavior.
2140
1002
 
2141
1003
  ### Root Configuration (metadata/.mj-sync.json)
2142
1004
  ```json
@@ -2146,6 +1008,10 @@ The validation will:
2146
1008
  "prompts",
2147
1009
  "agent-types"
2148
1010
  ],
1011
+ "ignoreDirectories": [
1012
+ "output",
1013
+ "examples"
1014
+ ],
2149
1015
  "push": {
2150
1016
  "validateBeforePush": true,
2151
1017
  "requireConfirmation": true,
@@ -2155,7 +1021,9 @@ The validation will:
2155
1021
  "sqlLogging": {
2156
1022
  "enabled": true,
2157
1023
  "outputDirectory": "./sql_logging",
2158
- "formatAsMigration": false
1024
+ "formatAsMigration": false,
1025
+ "filterPatterns": ["*EntityFieldValue*"],
1026
+ "filterType": "exclude"
2159
1027
  },
2160
1028
  "userRoleValidation": {
2161
1029
  "enabled": true,
@@ -2165,7 +1033,8 @@ The validation will:
2165
1033
  "watch": {
2166
1034
  "debounceMs": 1000,
2167
1035
  "ignorePatterns": ["*.tmp", "*.bak"]
2168
- }
1036
+ },
1037
+ "emitSyncNotes": false
2169
1038
  }
2170
1039
  ```
2171
1040
 
@@ -2173,7 +1042,7 @@ The validation will:
2173
1042
  ```json
2174
1043
  {
2175
1044
  "entity": "AI Prompts",
2176
- "filePattern": ".*.json",
1045
+ "filePattern": "*.json",
2177
1046
  "defaults": {
2178
1047
  "TypeID": "@lookup:AI Prompt Types.Name=Chat",
2179
1048
  "Temperature": 0.7,
@@ -2181,7 +1050,7 @@ The validation will:
2181
1050
  "Status": "Active"
2182
1051
  },
2183
1052
  "pull": {
2184
- "filePattern": ".*.json",
1053
+ "filePattern": "*.json",
2185
1054
  "updateExistingRecords": true,
2186
1055
  "createNewFileIfNotFound": true,
2187
1056
  "mergeStrategy": "merge",
@@ -2214,139 +1083,136 @@ The validation will:
2214
1083
  }
2215
1084
  ```
2216
1085
 
2217
- ### Nested Folder Defaults (metadata/ai-prompts/customer-service/escalation/.mj-folder.json)
1086
+ ### Push Configuration Options
1087
+
1088
+ #### autoCreateMissingRecords
1089
+
1090
+ When set to `true`, the push command automatically creates records when a primaryKey is specified but the record does not exist in the database. Useful when migrating data between environments or restoring records from backups.
1091
+
2218
1092
  ```json
2219
1093
  {
2220
- "defaults": {
2221
- "Tags": ["customer-service", "support", "escalation", "priority"],
2222
- "MaxTokens": 2000,
2223
- "Temperature": 0.6
1094
+ "push": {
1095
+ "autoCreateMissingRecords": true
2224
1096
  }
2225
1097
  }
2226
1098
  ```
2227
1099
 
2228
- ## Embedded Collections
2229
-
2230
- 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.
1100
+ #### alwaysPush
2231
1101
 
2232
- ### Benefits
2233
- - **Single File Management**: Keep related data together
2234
- - **Atomic Operations**: Parent and children sync together
2235
- - **Cleaner Organization**: Fewer files to manage
2236
- - **Relationship Clarity**: Visual representation of data relationships
1102
+ When set to `true`, forces ALL records to be saved to the database regardless of their dirty state. This bypasses the normal dirty checking mechanism.
2237
1103
 
2238
- ## Recursive Patterns
1104
+ Use cases:
1105
+ - Ensuring complete synchronization after database restoration
1106
+ - Bypassing dirty detection issues
1107
+ - Force refreshing all database records with file content
2239
1108
 
2240
- The tool now supports automatic recursive patterns for self-referencing entities, eliminating the need to manually define each nesting level for hierarchical data structures.
1109
+ ```json
1110
+ {
1111
+ "push": {
1112
+ "alwaysPush": true
1113
+ }
1114
+ }
1115
+ ```
2241
1116
 
2242
- ### Benefits
2243
- - **Simplified Configuration**: No need to manually define each hierarchy level
2244
- - **Automatic Depth Handling**: Adapts to actual data depth dynamically
2245
- - **Reduced Maintenance**: Configuration stays simple regardless of data changes
2246
- - **Safeguards**: Built-in protection against infinite loops and excessive memory usage
1117
+ **Note**: This flag should be used judiciously as it causes database writes for all records. Enable temporarily when needed, then disable for normal operations.
2247
1118
 
2248
- ### Recursive Configuration
1119
+ ### Directory Processing Order
2249
1120
 
2250
- Enable recursive patterns for self-referencing entities:
1121
+ Directory order is configured in the root-level `.mj-sync.json` file only (not inherited by subdirectories):
2251
1122
 
2252
1123
  ```json
2253
1124
  {
2254
- "pull": {
2255
- "entities": {
2256
- "AI Agents": {
2257
- "relatedEntities": {
2258
- "AI Agents": {
2259
- "entity": "AI Agents",
2260
- "foreignKey": "ParentID",
2261
- "recursive": true, // Enable recursive fetching
2262
- "maxDepth": 10, // Optional depth limit (omit for default of 10)
2263
- "filter": "Status = 'Active'"
2264
- }
2265
- }
2266
- }
2267
- }
2268
- }
1125
+ "version": "1.0.0",
1126
+ "directoryOrder": [
1127
+ "prompts",
1128
+ "agent-types"
1129
+ ]
2269
1130
  }
2270
1131
  ```
2271
1132
 
2272
- ### How It Works
2273
-
2274
- When `recursive: true` is set:
1133
+ - Directories listed in `directoryOrder` are processed first, in the specified order
1134
+ - Remaining directories are processed after the ordered ones, in alphabetical order
1135
+ - Use this to ensure parent entities are created before children that reference them
2275
1136
 
2276
- 1. **Automatic Child Fetching**: The tool automatically fetches child records at each level
2277
- 2. **Dynamic Depth**: Continues until no more children are found or max depth is reached
2278
- 3. **Circular Reference Protection**: Prevents infinite loops by tracking processed record IDs
2279
- 4. **Consistent Configuration**: All recursive levels use the same `lookupFields`, `externalizeFields`, etc.
1137
+ ### Ignore Directories
2280
1138
 
2281
- ### Before vs After
1139
+ Ignore directories are configured in `.mj-sync.json` files and are **cumulative** through the directory hierarchy:
2282
1140
 
2283
- **Before (Manual Configuration):**
2284
1141
  ```json
2285
1142
  {
2286
- "pull": {
2287
- "relatedEntities": {
2288
- "AI Agents": {
2289
- "entity": "AI Agents",
2290
- "foreignKey": "ParentID",
2291
- "relatedEntities": {
2292
- "AI Agents": {
2293
- "entity": "AI Agents",
2294
- "foreignKey": "ParentID",
2295
- "relatedEntities": {
2296
- "AI Agents": {
2297
- "entity": "AI Agents",
2298
- "foreignKey": "ParentID"
2299
- // Must manually add more levels...
2300
- }
2301
- }
2302
- }
2303
- }
2304
- }
2305
- }
2306
- }
1143
+ "version": "1.0.0",
1144
+ "ignoreDirectories": [
1145
+ "output",
1146
+ "examples",
1147
+ "templates"
1148
+ ]
2307
1149
  }
2308
1150
  ```
2309
1151
 
2310
- **After (Recursive Configuration):**
1152
+ Child directories inherit parent ignore patterns and can add their own.
1153
+
1154
+ ### SQL Logging
1155
+
1156
+ SQL logging captures all database operations during push commands for creating migration files, debugging, and deployment:
1157
+
2311
1158
  ```json
2312
1159
  {
2313
- "pull": {
2314
- "relatedEntities": {
2315
- "AI Agents": {
2316
- "entity": "AI Agents",
2317
- "foreignKey": "ParentID",
2318
- "recursive": true,
2319
- "maxDepth": 10
2320
- }
2321
- }
1160
+ "sqlLogging": {
1161
+ "enabled": true,
1162
+ "outputDirectory": "./sql_logging",
1163
+ "formatAsMigration": true,
1164
+ "filterPatterns": ["*AIPrompt*", "/^EXEC sp_/i"],
1165
+ "filterType": "exclude"
2322
1166
  }
2323
1167
  }
2324
1168
  ```
2325
1169
 
2326
- ### Configuration Options
2327
-
2328
1170
  | Option | Type | Default | Description |
2329
1171
  |--------|------|---------|-------------|
2330
- | `recursive` | boolean | false | Enable automatic recursive fetching |
2331
- | `maxDepth` | number | 10 | Maximum recursion depth to prevent infinite loops |
1172
+ | `enabled` | boolean | false | Enable SQL logging during push operations |
1173
+ | `outputDirectory` | string | "./sql_logging" | Directory for SQL log files |
1174
+ | `formatAsMigration` | boolean | false | Format as Flyway migration files |
1175
+ | `filterPatterns` | string[] | undefined | Patterns to filter SQL statements |
1176
+ | `filterType` | "exclude" or "include" | "exclude" | How to apply filter patterns |
1177
+
1178
+ **Filter Pattern Types:**
1179
+ - **Regex patterns**: Start with `/` and optionally end with flags (e.g., `/spCreate.*Run/i`)
1180
+ - **Simple wildcards**: Use `*` as a wildcard (e.g., `*AIPrompt*`)
1181
+
1182
+ ### User Role Validation
1183
+
1184
+ Validates UserID fields against specific roles in the MemberJunction system:
2332
1185
 
2333
- ### Safeguards
1186
+ ```json
1187
+ {
1188
+ "userRoleValidation": {
1189
+ "enabled": true,
1190
+ "allowedRoles": [
1191
+ "Administrator",
1192
+ "Developer",
1193
+ "Content Manager"
1194
+ ],
1195
+ "allowUsersWithoutRoles": false
1196
+ }
1197
+ }
1198
+ ```
2334
1199
 
2335
- - **Circular Reference Detection**: Tracks processed record IDs to prevent infinite loops
2336
- - **Maximum Depth Limit**: Configurable depth limit (default: 10) prevents excessive memory usage
2337
- - **Performance Monitoring**: Verbose mode shows recursion depth and skipped circular references
2338
- - **Backward Compatibility**: Existing configurations continue to work unchanged
1200
+ | Option | Type | Default | Description |
1201
+ |--------|------|---------|-------------|
1202
+ | `enabled` | boolean | false | Enable user role validation |
1203
+ | `allowedRoles` | string[] | [] | Role names that are allowed |
1204
+ | `allowUsersWithoutRoles` | boolean | false | Allow users without any assigned roles |
2339
1205
 
2340
- ### Configuration for Pull
1206
+ ## Pull Configuration
2341
1207
 
2342
- The pull command now supports smart update capabilities with extensive configuration options:
1208
+ The pull command supports smart update capabilities with extensive configuration options:
2343
1209
 
2344
1210
  ```json
2345
1211
  {
2346
1212
  "entity": "AI Prompts",
2347
- "filePattern": ".*.json",
1213
+ "filePattern": "*.json",
2348
1214
  "pull": {
2349
- "filePattern": ".*.json",
1215
+ "filePattern": "*.json",
2350
1216
  "createNewFileIfNotFound": true,
2351
1217
  "newFileName": ".all-new.json",
2352
1218
  "appendRecordsToExistingFile": true,
@@ -2359,10 +1225,6 @@ The pull command now supports smart update capabilities with extensive configura
2359
1225
  {
2360
1226
  "field": "TemplateText",
2361
1227
  "pattern": "@file:{Name}.template.md"
2362
- },
2363
- {
2364
- "field": "PromptText",
2365
- "pattern": "@file:prompts/{Name}.prompt.md"
2366
1228
  }
2367
1229
  ],
2368
1230
  "excludeFields": ["InternalID", "TempField"],
@@ -2370,188 +1232,97 @@ The pull command now supports smart update capabilities with extensive configura
2370
1232
  "CategoryID": {
2371
1233
  "entity": "AI Prompt Categories",
2372
1234
  "field": "Name"
2373
- },
2374
- "TypeID": {
2375
- "entity": "AI Prompt Types",
2376
- "field": "Name"
2377
1235
  }
2378
1236
  },
2379
1237
  "relatedEntities": {
2380
1238
  "MJ: AI Prompt Models": {
2381
1239
  "entity": "MJ: AI Prompt Models",
2382
1240
  "foreignKey": "PromptID",
2383
- "filter": "Status = 'Active'",
2384
- "lookupFields": {
2385
- "ModelID": {
2386
- "entity": "AI Models",
2387
- "field": "Name"
2388
- }
2389
- }
1241
+ "filter": "Status = 'Active'"
2390
1242
  }
2391
- }
1243
+ },
1244
+ "ignoreNullFields": false,
1245
+ "ignoreVirtualFields": false
2392
1246
  }
2393
1247
  }
2394
1248
  ```
2395
1249
 
2396
- #### Pull Configuration Options
1250
+ ### Pull Configuration Options
2397
1251
 
2398
1252
  | Option | Type | Default | Description |
2399
1253
  |--------|------|---------|-------------|
2400
- | `filePattern` | string | Entity filePattern | Pattern for finding existing files to update |
1254
+ | `filePattern` | string | Entity filePattern | Pattern for finding existing files |
2401
1255
  | `createNewFileIfNotFound` | boolean | true | Create files for records not found locally |
2402
- | `newFileName` | string | - | Filename for new records when appending (see warning below) |
1256
+ | `newFileName` | string | -- | Filename for new records when appending |
2403
1257
  | `appendRecordsToExistingFile` | boolean | false | Append new records to a single file |
2404
- | `updateExistingRecords` | boolean | true | Update existing records found in local files |
2405
- | `preserveFields` | string[] | [] | Fields that retain local values during updates (see detailed explanation below) |
2406
- | `mergeStrategy` | string | "merge" | How to merge updates: "merge", "overwrite", or "skip" |
2407
- | `backupBeforeUpdate` | boolean | false | Create timestamped backups before updating files |
2408
- | `backupDirectory` | string | ".backups" | Directory name for backup files (relative to entity directory) |
2409
- | `filter` | string | - | SQL WHERE clause for filtering records |
2410
- | `externalizeFields` | array/object | - | Fields to save as external files with optional patterns |
2411
- | `excludeFields` | string[] | [] | Fields to completely omit from pulled data (see detailed explanation below) |
2412
- | `lookupFields` | object | - | Foreign keys to convert to @lookup references |
2413
- | `relatedEntities` | object | - | Related entities to pull as embedded collections |
2414
- | `ignoreNullFields` | boolean | false | Exclude fields with null values from pulled data |
2415
- | `ignoreVirtualFields` | boolean | false | Exclude virtual fields (view-only fields) from pulled data |
2416
-
2417
- > **⚠️ Important Configuration Warning**
2418
- >
2419
- > When both `appendRecordsToExistingFile: true` and `newFileName` are set, ALL new records will be appended to the single file specified by `newFileName`, effectively ignoring the standard per-record file pattern. This can lead to unexpected file organization:
2420
- >
2421
- > ```json
2422
- > // This configuration will put ALL new records in .all-new.json
2423
- > "pull": {
2424
- > "appendRecordsToExistingFile": true,
2425
- > "newFileName": ".all-new.json" // ⚠️ Overrides individual file creation
2426
- > }
2427
- > ```
2428
- >
2429
- > **Recommended configurations:**
2430
- > - For individual files per record: Set `appendRecordsToExistingFile: false` (or omit it)
2431
- > - For grouped new records: Set both `appendRecordsToExistingFile: true` and `newFileName`
2432
- > - For mixed approach: Omit `newFileName` to let new records follow the standard pattern
2433
-
2434
- #### Merge Strategies
1258
+ | `updateExistingRecords` | boolean | true | Update existing records in local files |
1259
+ | `preserveFields` | string[] | [] | Fields that retain local values during updates |
1260
+ | `mergeStrategy` | string | "merge" | How to merge: "merge", "overwrite", or "skip" |
1261
+ | `backupBeforeUpdate` | boolean | false | Create timestamped backups before updates |
1262
+ | `backupDirectory` | string | ".backups" | Directory for backup files |
1263
+ | `filter` | string | -- | SQL WHERE clause for filtering records |
1264
+ | `externalizeFields` | array/object | -- | Fields to save as external files |
1265
+ | `excludeFields` | string[] | [] | Fields to omit from pulled data |
1266
+ | `lookupFields` | object | -- | Foreign keys to convert to @lookup references |
1267
+ | `relatedEntities` | object | -- | Related entities to pull as embedded collections |
1268
+ | `ignoreNullFields` | boolean | false | Exclude fields with null values |
1269
+ | `ignoreVirtualFields` | boolean | false | Exclude virtual fields (view-only fields) |
1270
+
1271
+ ### Merge Strategies
2435
1272
 
2436
1273
  - **`merge`** (default): Combines fields from database and local file, with database values taking precedence for existing fields
2437
1274
  - **`overwrite`**: Completely replaces local record with database version (except preserved fields)
2438
1275
  - **`skip`**: Leaves existing records unchanged, only adds new records
2439
1276
 
2440
- #### Understanding excludeFields vs preserveFields
1277
+ ### Understanding excludeFields vs preserveFields
2441
1278
 
2442
- These two configuration options serve different purposes for managing fields during pull operations:
1279
+ | | excludeFields | preserveFields |
1280
+ |--|---------------|----------------|
1281
+ | **Purpose** | Completely omit fields from files | Protect local values from overwrite |
1282
+ | **Use Case** | Remove system/internal fields | Keep custom local modifications |
1283
+ | **Effect** | Fields never appear in JSON | Fields exist but retain local values |
1284
+ | **Example** | Internal IDs, timestamps | Custom file paths, local notes |
2443
1285
 
2444
- ##### excludeFields
2445
- - **Purpose**: Completely omit specified fields from your local files
2446
- - **Use Case**: Remove internal/system fields you don't want in version control
2447
- - **Effect**: Fields never appear in the JSON files
2448
- - **Example**: Excluding internal IDs, timestamps, or sensitive data
2449
-
2450
- ##### preserveFields
2451
- - **Purpose**: Protect local customizations from being overwritten during updates
2452
- - **Use Case**: Keep locally modified values while updating other fields
2453
- - **Effect**: Fields exist in files but retain their local values during pull
2454
- - **Example**: Preserving custom file paths, local notes, or environment-specific values
2455
- - **Special Behavior for @file: references**: When a preserved field contains a `@file:` reference, the tool will update the content at the existing file path rather than creating a new file with a generated name
2456
-
2457
- ##### Example Configuration
2458
- ```json
2459
- {
2460
- "pull": {
2461
- "excludeFields": ["TemplateID", "InternalNotes", "CreatedAt"],
2462
- "preserveFields": ["TemplateText", "OutputExample", "LocalConfig"]
2463
- }
2464
- }
2465
- ```
1286
+ When a preserved field contains a `@file:` reference, the tool updates the content at the existing file path rather than creating a new file with a generated name.
2466
1287
 
2467
- With this configuration:
2468
- - **TemplateID, InternalNotes, CreatedAt** → Never appear in local files
2469
- - **TemplateText, OutputExample, LocalConfig** → Keep their local values during updates
1288
+ ### Externalize Fields Patterns
2470
1289
 
2471
- ##### Common Scenario: Customized File References
2472
- When you customize file paths (e.g., changing `@file:templates/skip-conductor.md` to `@file:templates/conductor.md`), use `preserveFields` to protect these customizations:
1290
+ The `externalizeFields` configuration supports dynamic file naming with placeholders:
2473
1291
 
2474
1292
  ```json
2475
- {
2476
- "pull": {
2477
- "preserveFields": ["TemplateText", "OutputExample"],
2478
- "externalizeFields": [
2479
- {
2480
- "field": "TemplateText",
2481
- "pattern": "@file:templates/{Name}.template.md"
2482
- }
2483
- ]
1293
+ "externalizeFields": [
1294
+ {
1295
+ "field": "TemplateText",
1296
+ "pattern": "@file:{Name}.template.md"
1297
+ },
1298
+ {
1299
+ "field": "SQLQuery",
1300
+ "pattern": "@file:queries/{CategoryName}/{Name}.sql"
2484
1301
  }
2485
- }
2486
- ```
2487
-
2488
- This ensures your custom paths aren't overwritten when pulling updates from the database.
2489
-
2490
- **How it works:**
2491
- 1. **Without preserveFields**: Pull would create a new file using the pattern (e.g., `templates/skip-conductor.template.md`) and update the JSON to point to it
2492
- 2. **With preserveFields**: Pull keeps your custom path (e.g., `@file:templates/conductor.md`) in the JSON and updates the content at that existing location
2493
-
2494
- This is particularly useful when:
2495
- - You've reorganized your file structure after initial pull
2496
- - You've renamed files to follow your own naming conventions
2497
- - You want to maintain consistent paths across team members
2498
-
2499
- #### Backup Configuration
2500
-
2501
- When `backupBeforeUpdate` is enabled, the tool creates timestamped backups before updating existing files:
2502
-
2503
- - **Backup Location**: Files are backed up to the `backupDirectory` (default: `.backups`) within the entity directory
2504
- - **Backup Naming**: Original filename + timestamp + `.backup` extension (e.g., `.greeting.json` → `.greeting.2024-03-15T10-30-45-123Z.backup`)
2505
- - **Extension**: All backup files use the `.backup` extension, preventing them from being processed by push/pull/status commands
2506
- - **Deduplication**: Only one backup is created per file per pull operation, even if the file contains multiple records
2507
-
2508
- Example configuration:
2509
- ```json
2510
- "pull": {
2511
- "backupBeforeUpdate": true,
2512
- "backupDirectory": ".backups" // Custom backup directory name
2513
- }
2514
- ```
2515
-
2516
- #### Virtual Fields Configuration
2517
-
2518
- The `ignoreVirtualFields` option controls whether virtual fields are included in pulled data:
2519
-
2520
- ```json
2521
- "pull": {
2522
- "ignoreVirtualFields": true // Exclude virtual fields from pulled data
2523
- }
1302
+ ]
2524
1303
  ```
2525
1304
 
2526
- **What are Virtual Fields?**
2527
- Virtual fields are computed fields that exist only in database views, not in the underlying tables. They typically contain:
2528
- - Foreign key display names (e.g., `"User": "John Smith"` alongside `"UserID": "123"`)
2529
- - Computed/calculated values
2530
- - Aggregate data from related tables
2531
- - Derived fields from database functions
1305
+ Supported placeholders:
1306
+ - `{Name}` -- The entity's name field value
1307
+ - `{ID}` -- The entity's primary key
1308
+ - `{FieldName}` -- The field being externalized
1309
+ - `{AnyFieldName}` -- Any field from the entity record
2532
1310
 
2533
- **When to use `ignoreVirtualFields: true`:**
2534
- - **Cleaner JSON files**: Remove read-only display fields that don't need version control
2535
- - **Reducing file size**: Eliminate redundant data that's computed from other fields
2536
- - **Preventing confusion**: Avoid fields that can't be modified during push operations
2537
- - **Database-focused workflow**: When you only want to manage actual table columns
1311
+ All values are sanitized for filesystem compatibility (lowercase, spaces to hyphens, special characters removed).
2538
1312
 
2539
- **When to use `ignoreVirtualFields: false` (default):**
2540
- - **Complete data capture**: Include all available information for reference
2541
- - **Display purposes**: Keep human-readable field values for easy review
2542
- - **Documentation**: Maintain context about related entity names and computed values
1313
+ ### Virtual Fields Configuration
2543
1314
 
2544
- **Example difference:**
1315
+ The `ignoreVirtualFields` option controls whether virtual fields (computed view-only fields) are included in pulled data.
2545
1316
 
2546
- With `ignoreVirtualFields: false`:
1317
+ With `ignoreVirtualFields: false` (default):
2547
1318
  ```json
2548
1319
  {
2549
1320
  "fields": {
2550
1321
  "Name": "Test Action",
2551
1322
  "CategoryID": "@lookup:Action Categories.Name=System",
2552
- "Category": "System", // ← Virtual field (display name)
1323
+ "Category": "System",
2553
1324
  "UserID": "123",
2554
- "User": "John Smith" // ← Virtual field (display name)
1325
+ "User": "John Smith"
2555
1326
  }
2556
1327
  }
2557
1328
  ```
@@ -2560,41 +1331,17 @@ With `ignoreVirtualFields: true`:
2560
1331
  ```json
2561
1332
  {
2562
1333
  "fields": {
2563
- "Name": "Test Action",
1334
+ "Name": "Test Action",
2564
1335
  "CategoryID": "@lookup:Action Categories.Name=System",
2565
1336
  "UserID": "123"
2566
- // Virtual fields excluded
2567
1337
  }
2568
1338
  }
2569
1339
  ```
2570
1340
 
2571
- #### Externalize Fields Patterns
2572
-
2573
- The `externalizeFields` configuration supports dynamic file naming with placeholders:
2574
-
2575
- ```json
2576
- "externalizeFields": [
2577
- {
2578
- "field": "TemplateText",
2579
- "pattern": "@file:{Name}.template.md"
2580
- },
2581
- {
2582
- "field": "SQLQuery",
2583
- "pattern": "@file:queries/{CategoryName}/{Name}.sql"
2584
- }
2585
- ]
2586
- ```
2587
-
2588
- Supported placeholders:
2589
- - `{Name}` - The entity's name field value
2590
- - `{ID}` - The entity's primary key
2591
- - `{FieldName}` - The field being externalized
2592
- - `{AnyFieldName}` - Any field from the entity record
1341
+ ## Embedded Collections
2593
1342
 
2594
- All values are sanitized for filesystem compatibility (lowercase, spaces to hyphens, special characters removed).
1343
+ The tool supports managing related entities as embedded collections within parent JSON files:
2595
1344
 
2596
- ### Nested Related Entities
2597
- Support for multiple levels of nesting:
2598
1345
  ```json
2599
1346
  {
2600
1347
  "fields": {
@@ -2624,193 +1371,158 @@ Support for multiple levels of nesting:
2624
1371
  }
2625
1372
  ```
2626
1373
 
2627
- ## Console Output
1374
+ Benefits:
1375
+ - **Single File Management**: Keep related data together
1376
+ - **Atomic Operations**: Parent and children sync together
1377
+ - **Cleaner Organization**: Fewer files to manage
1378
+ - **Relationship Clarity**: Visual representation of data relationships
2628
1379
 
2629
- ### Normal Mode
2630
- Shows high-level progress:
2631
- ```
2632
- Processing AI Prompts in demo/ai-prompts
2633
- ↳ Processing 2 related MJ: AI Prompt Models records
2634
- Created: 1
2635
- Updated: 2
2636
- ```
1380
+ ## Recursive Patterns
2637
1381
 
2638
- ### Verbose Mode (-v flag)
2639
- Shows detailed field-level operations with hierarchical indentation:
2640
- ```
2641
- Processing AI Prompts in demo/ai-prompts
2642
- Setting Name: "Example Greeting Prompt 3" -> "Example Greeting Prompt 3"
2643
- Setting Description: "A simple example prompt..." -> "A simple example prompt..."
2644
- Processing 2 related MJ: AI Prompt Models records
2645
- Setting PromptID: "@parent:ID" -> "C2A1433E-F36B-1410-8DB0-00021F8B792E"
2646
- Setting ModelID: "@lookup:AI Models.Name=GPT 4.1" -> "123-456-789"
2647
- Setting Priority: 1 -> 1
2648
- ✓ Created MJ: AI Prompt Models record
1382
+ The tool supports automatic recursive patterns for self-referencing entities, eliminating the need to manually define each nesting level for hierarchical data structures.
1383
+
1384
+ ```json
1385
+ {
1386
+ "pull": {
1387
+ "relatedEntities": {
1388
+ "AI Agents": {
1389
+ "entity": "AI Agents",
1390
+ "foreignKey": "ParentID",
1391
+ "recursive": true,
1392
+ "maxDepth": 10
1393
+ }
1394
+ }
1395
+ }
1396
+ }
2649
1397
  ```
2650
1398
 
2651
- ## Use Cases
1399
+ | Option | Type | Default | Description |
1400
+ |--------|------|---------|-------------|
1401
+ | `recursive` | boolean | false | Enable automatic recursive fetching |
1402
+ | `maxDepth` | number | 10 | Maximum recursion depth |
2652
1403
 
2653
- ### Developer Workflow
2654
- 1. Install the MJ CLI: `npm install -g @memberjunction/cli`
2655
- 2. `mj sync pull --entity="AI Prompts"` to get latest prompts with their models
2656
- 3. Edit prompts and adjust model configurations in VS Code
2657
- 4. Test locally with `mj sync push --dry-run`
2658
- 5. Commit changes to Git
2659
- 6. PR review with diff visualization
2660
- 7. CI/CD runs `mj sync push --ci` on merge
1404
+ When `recursive: true` is set:
1405
+ 1. The tool automatically fetches child records at each level
1406
+ 2. Continues until no more children are found or max depth is reached
1407
+ 3. Circular reference protection prevents infinite loops by tracking processed record IDs
1408
+ 4. All recursive levels use the same `lookupFields`, `externalizeFields`, etc.
2661
1409
 
2662
- ### Content Team Workflow
2663
- 1. Pull prompts to local directory
2664
- 2. Edit in preferred markdown editor
2665
- 3. Adjust model priorities in JSON
2666
- 4. Preview changes
2667
- 5. Push updates back to database
1410
+ ## Parallel Processing
2668
1411
 
2669
- ### CI/CD Integration
2670
- ```yaml
2671
- - name: Push Metadata to Production
2672
- run: |
2673
- npm install -g @memberjunction/cli
2674
- mj sync push --ci --entity="AI Prompts"
2675
- ```
1412
+ MetadataSync supports parallel processing of records during push operations for improved performance with large datasets.
2676
1413
 
2677
- ## Benefits
1414
+ Records are automatically grouped into dependency levels:
1415
+ - **Level 0**: Records with no dependencies
1416
+ - **Level 1**: Records that depend only on Level 0 records
1417
+ - **Level 2**: Records that depend on Level 0 or Level 1 records
1418
+
1419
+ Records within the same dependency level can be safely processed in parallel.
1420
+
1421
+ ```bash
1422
+ # Default processing
1423
+ mj sync push
2678
1424
 
2679
- 1. **Version Control**: Full Git history for all metadata changes
2680
- 2. **Collaboration**: Standard PR workflows for metadata updates
2681
- 3. **Tooling**: Use any editor (VS Code, Sublime, etc.)
2682
- 4. **Backup**: File-based backups of critical metadata
2683
- 5. **Portability**: Easy migration between environments
2684
- 6. **Automation**: CI/CD pipeline integration
2685
- 7. **Related Data**: Manage parent-child relationships easily
1425
+ # Process 20 records in parallel
1426
+ mj sync push --parallel-batch-size=20
2686
1427
 
2687
- ## Technical Architecture
1428
+ # Maximum parallelism (50 records)
1429
+ mj sync push --parallel-batch-size=50
2688
1430
 
2689
- - Built with Node.js and TypeScript
2690
- - Uses oclif for CLI framework
2691
- - Integrates with MJ Core infrastructure
2692
- - Leverages existing data providers
2693
- - Supports watch mode via chokidar
2694
- - Checksums for change detection
2695
- - Dynamic primary key detection from entity metadata
2696
- - No hardcoded assumptions about entity structure
2697
- - Proper database connection cleanup
1431
+ # Conservative approach for debugging
1432
+ mj sync push --parallel-batch-size=1
1433
+ ```
2698
1434
 
2699
1435
  ## Validation System
2700
1436
 
2701
- The MetadataSync tool includes a comprehensive validation system that checks your metadata files for correctness before pushing to the database. This helps catch errors early and ensures data integrity.
1437
+ The MetadataSync tool includes a comprehensive validation system that checks metadata files for correctness before pushing to the database.
1438
+
1439
+ ```mermaid
1440
+ flowchart TD
1441
+ START["mj sync validate"] --> LOAD["Load .mj-sync.json"]
1442
+ LOAD --> DIRS["Discover Entity Directories"]
1443
+ DIRS --> LOOP["For Each Directory"]
1444
+
1445
+ LOOP --> ENT["Validate Entity Names"]
1446
+ LOOP --> FLD["Validate Field Names & Types"]
1447
+ LOOP --> REF["Validate References\n(@file, @lookup, @template)"]
1448
+ LOOP --> DEP["Analyze Dependencies"]
1449
+
1450
+ ENT --> COLLECT["Collect Errors & Warnings"]
1451
+ FLD --> COLLECT
1452
+ REF --> COLLECT
1453
+ DEP --> COLLECT
2702
1454
 
2703
- ### Validation Features
1455
+ COLLECT --> RESULT{Valid?}
1456
+ RESULT -->|Yes| PASS["Validation Passed"]
1457
+ RESULT -->|No| FAIL["Validation Failed\n(Errors Listed)"]
2704
1458
 
2705
- #### Automatic Validation
2706
- By default, validation runs automatically before push and pull operations:
1459
+ style START fill:#2d6a9f,stroke:#1a4971,color:#fff
1460
+ style PASS fill:#2d8659,stroke:#1a5c3a,color:#fff
1461
+ style FAIL fill:#b8762f,stroke:#8a5722,color:#fff
1462
+ style COLLECT fill:#7c5295,stroke:#563a6b,color:#fff
1463
+ ```
1464
+
1465
+ ### Automatic Validation
1466
+ By default, validation runs automatically before push operations:
2707
1467
  ```bash
2708
1468
  # These commands validate first, then proceed if valid
2709
1469
  mj sync push
2710
1470
  mj sync pull --entity="AI Prompts"
2711
1471
  ```
2712
1472
 
2713
- #### Manual Validation
2714
- Run validation without performing any sync operations:
1473
+ ### Manual Validation
2715
1474
  ```bash
2716
- # Validate current directory
2717
1475
  mj sync validate
2718
-
2719
- # Validate specific directory
2720
1476
  mj sync validate --dir="./metadata"
2721
-
2722
- # Verbose output shows all files checked
2723
1477
  mj sync validate --verbose
2724
- ```
2725
-
2726
- #### CI/CD Integration
2727
- Get JSON output for automated pipelines:
2728
- ```bash
2729
- # JSON output for parsing
2730
1478
  mj sync validate --format=json
2731
-
2732
- # In CI mode, validation failures cause immediate exit
2733
- mj sync push --ci
1479
+ mj sync validate --save-report
2734
1480
  ```
2735
1481
 
2736
- #### Validation During Push
2737
-
2738
- **Important:** The `push` command automatically validates your metadata before pushing to the database:
2739
- - ❌ **Push stops on any validation errors** - You cannot push invalid metadata
2740
- - 🛑 **In CI mode** - Push fails immediately without prompts
2741
- - 💬 **In interactive mode** - You'll be asked if you want to continue despite errors
2742
- - ✅ **Clean validation** - Push proceeds automatically
2743
-
2744
- #### Skip Validation
2745
- For emergency fixes or when you know validation will fail:
1482
+ ### Skip Validation
2746
1483
  ```bash
2747
- # Skip validation checks (USE WITH CAUTION!)
1484
+ # Skip validation checks (use with caution)
2748
1485
  mj sync push --no-validate
2749
1486
  mj sync pull --entity="AI Prompts" --no-validate
2750
1487
  ```
2751
1488
 
2752
- ⚠️ **Warning:** Using `--no-validate` may push invalid metadata to your database, potentially breaking your application. Only use this flag when absolutely necessary.
2753
-
2754
1489
  ### What Gets Validated
2755
1490
 
2756
- #### Entity Validation
2757
- - Entity names exist in database metadata
2758
- - Entity is accessible to current user
2759
- - Entity allows data modifications
2760
-
2761
- #### Field Validation
2762
- - Field names exist on the entity
2763
- - Virtual properties (getter/setter methods) are automatically detected
2764
- - Fields are settable (not system fields)
2765
- - Field values match expected data types
2766
- - Required fields are checked intelligently:
2767
- - Skips fields with default values
2768
- - Skips computed/virtual fields (e.g., `Action` derived from `ActionID`)
2769
- - Skips fields when related virtual property is used (e.g., `TemplateID` when `TemplateText` is provided)
2770
- - Skips ReadOnly and AutoUpdateOnly fields
2771
- - Foreign key relationships are valid
2772
-
2773
- #### Reference Validation
2774
- - `@file:` references point to existing files
2775
- - ✓ `@lookup:` references find matching records
2776
- - `@template:` references load valid JSON
2777
- - `@parent:` and `@root:` have proper context
2778
- - Circular references are detected
2779
-
2780
- #### Best Practice Checks
2781
- - ⚠️ Deep nesting (>10 levels) generates warnings
2782
- - ⚠️ Missing required fields are flagged
2783
- - ⚠️ Large file sizes trigger performance warnings
2784
- - ⚠️ Naming convention violations
2785
-
2786
- #### Dependency Order Validation
2787
- - ✓ Entities are processed in dependency order
2788
- - ✓ Parent entities exist before children
2789
- - ✓ Circular dependencies are detected
2790
- - ✓ Suggests corrected directory order
1491
+ **Entity Validation:**
1492
+ - Entity names exist in database metadata
1493
+ - Entity is accessible to current user
1494
+ - Entity allows data modifications
1495
+
1496
+ **Field Validation:**
1497
+ - Field names exist on the entity
1498
+ - Virtual properties (getter/setter methods) are automatically detected
1499
+ - Fields are settable (not system fields)
1500
+ - Field values match expected data types
1501
+ - Required fields are checked intelligently (skips fields with defaults, computed fields, ReadOnly fields)
1502
+ - Foreign key relationships are valid
1503
+
1504
+ **Reference Validation:**
1505
+ - `@file:` references point to existing files
1506
+ - `@lookup:` references find matching records
1507
+ - `@template:` references load valid JSON
1508
+ - `@parent:` and `@root:` have proper context
1509
+ - Circular references are detected
1510
+
1511
+ **Dependency Order Validation:**
1512
+ - Entities are processed in dependency order
1513
+ - Parent entities exist before children
1514
+ - Circular dependencies are detected and reported
2791
1515
 
2792
1516
  ### Validation Output
2793
1517
 
2794
1518
  #### Human-Readable Format (Default)
2795
1519
  ```
2796
- ════════════════════════════════════════════════════════════
2797
- ║ Validation Report ║
2798
- ════════════════════════════════════════════════════════════
2799
-
2800
- ┌────────────────────────────────────────────────┐
2801
- │ Files: 4 │
2802
- │ Entities: 29 │
2803
- │ Errors: 2 │
2804
- │ Warnings: 5 │
2805
- ├────────────────────────────────────────────────┤
2806
- │ Errors by Type: │
2807
- │ field: 1 │
2808
- │ reference: 1 │
2809
- ├────────────────────────────────────────────────┤
2810
- │ Warnings by Type: │
2811
- │ bestpractice: 3 │
2812
- │ nesting: 2 │
2813
- └────────────────────────────────────────────────┘
1520
+ Validation Report
1521
+
1522
+ Files: 4
1523
+ Entities: 29
1524
+ Errors: 2
1525
+ Warnings: 5
2814
1526
 
2815
1527
  Errors
2816
1528
 
@@ -2818,13 +1530,11 @@ Errors
2818
1530
  Entity: Templates
2819
1531
  Field: Status
2820
1532
  File: ./metadata/templates/.my-template.json
2821
- → Suggestion: Check spelling of 'Status'. Run 'mj sync list-entities' to see available entities.
2822
1533
 
2823
1534
  2. File not found: ./shared/footer.html
2824
1535
  Entity: Templates
2825
1536
  Field: FooterHTML
2826
1537
  File: ./metadata/templates/.my-template.json
2827
- → Suggestion: Ensure file './shared/footer.html' exists and path is relative to the metadata directory.
2828
1538
  ```
2829
1539
 
2830
1540
  #### JSON Format (CI/CD)
@@ -2835,15 +1545,7 @@ Errors
2835
1545
  "totalFiles": 4,
2836
1546
  "totalEntities": 29,
2837
1547
  "totalErrors": 2,
2838
- "totalWarnings": 5,
2839
- "errorsByType": {
2840
- "field": 1,
2841
- "reference": 1
2842
- },
2843
- "warningsByType": {
2844
- "bestpractice": 3,
2845
- "nesting": 2
2846
- }
1548
+ "totalWarnings": 5
2847
1549
  },
2848
1550
  "errors": [
2849
1551
  {
@@ -2852,69 +1554,9 @@ Errors
2852
1554
  "field": "Status",
2853
1555
  "file": "./metadata/templates/.my-template.json",
2854
1556
  "message": "Field \"Status\" does not exist on entity \"Templates\"",
2855
- "suggestion": "Check spelling of 'Status'. Run 'mj sync list-entities' to see available entities."
1557
+ "suggestion": "Check spelling of 'Status'."
2856
1558
  }
2857
- ],
2858
- "warnings": [...]
2859
- }
2860
- ```
2861
-
2862
- ### Virtual Properties Support
2863
-
2864
- Some MemberJunction entities include virtual properties - getter/setter methods that aren't database fields but provide convenient access to related data. The validation system automatically detects these properties.
2865
-
2866
- #### Example: TemplateText Virtual Property
2867
- The `Templates` entity includes a `TemplateText` virtual property that:
2868
- - Automatically manages `Template` and `TemplateContent` records
2869
- - Isn't a database field but appears as a property on the entity class
2870
- - Can be used in metadata files just like regular fields
2871
-
2872
- ```json
2873
- {
2874
- "fields": {
2875
- "Name": "My Template",
2876
- "TemplateText": "@file:template.html" // Virtual property - works!
2877
- }
2878
- }
2879
- ```
2880
-
2881
- The validator checks both database metadata AND entity class properties, ensuring virtual properties are properly recognized.
2882
-
2883
- ### Intelligent Required Field Validation
2884
-
2885
- The validator intelligently handles required fields to avoid false warnings:
2886
-
2887
- #### Fields with Default Values
2888
- Required fields that have database defaults are not flagged:
2889
- ```json
2890
- {
2891
- "fields": {
2892
- "Name": "My Entity"
2893
- // CreatedAt is required but has default value - no warning
2894
- }
2895
- }
2896
- ```
2897
-
2898
- #### Computed/Virtual Fields
2899
- Fields that are computed from other fields are skipped:
2900
- ```json
2901
- {
2902
- "fields": {
2903
- "ActionID": "123-456-789"
2904
- // Action field is computed from ActionID - no warning
2905
- }
2906
- }
2907
- ```
2908
-
2909
- #### Virtual Property Relationships
2910
- When using virtual properties, related required fields are skipped:
2911
- ```json
2912
- {
2913
- "fields": {
2914
- "Name": "My Prompt",
2915
- "TemplateText": "@file:template.md"
2916
- // TemplateID and Template are not required when TemplateText is used
2917
- }
1559
+ ]
2918
1560
  }
2919
1561
  ```
2920
1562
 
@@ -2926,72 +1568,38 @@ When using virtual properties, related required fields are skipped:
2926
1568
  | `Entity "X" not found` | Wrong entity name | Use exact entity name from database |
2927
1569
  | `File not found` | Bad @file: reference | Check file path is relative and exists |
2928
1570
  | `Lookup not found` | No matching record | Verify lookup value or use ?create |
2929
- | `Circular dependency` | ABA references | Restructure to avoid cycles |
1571
+ | `Circular dependency` | A -> B -> A references | Restructure to avoid cycles |
2930
1572
  | `Required field missing` | Missing required field | Add field with appropriate value |
2931
1573
 
2932
- ### Validation Configuration
2933
-
2934
- Control validation behavior in your workflow:
2935
-
2936
- ```json
2937
- {
2938
- "push": {
2939
- "validateBeforePush": true // Default: true
2940
- },
2941
- "pull": {
2942
- "validateBeforePull": false // Default: false
2943
- }
2944
- }
2945
- ```
2946
-
2947
- ### Best Practices
2948
-
2949
- 1. **Run validation during development**: `mj sync validate` frequently
2950
- 2. **Fix errors before warnings**: Errors block operations, warnings don't
2951
- 3. **Use verbose mode** to understand issues: `mj sync validate -v`
2952
- 4. **Include in CI/CD**: Parse JSON output for automated checks
2953
- 5. **Don't skip validation** unless absolutely necessary
2954
-
2955
- ## Troubleshooting
2956
-
2957
- ### Validation Errors
2958
-
2959
- If validation fails:
2960
-
2961
- 1. **Read the error message carefully** - It includes specific details
2962
- 2. **Check the suggestion** - Most errors include how to fix them
2963
- 3. **Use verbose mode** for more context: `mj sync validate -v`
2964
- 4. **Verify entity definitions** in generated entity files
2965
- 5. **Check file paths** are relative to the metadata directory
2966
-
2967
- ### Performance Issues
2968
-
2969
- For large metadata sets:
1574
+ ## Programmatic Usage
2970
1575
 
2971
- 1. **Disable best practice checks**: `mj sync validate --no-best-practices`
2972
- 2. **Validate specific directories**: `mj sync validate --dir="./prompts"`
2973
- 3. **Reduce nesting depth warning**: `mj sync validate --max-depth=20`
1576
+ ### Services Overview
2974
1577
 
2975
- ## Programmatic Usage
1578
+ The MetadataSync package exports several service classes for programmatic use:
2976
1579
 
2977
- ### Using ValidationService in Your Code
1580
+ | Service | Purpose |
1581
+ |---------|---------|
1582
+ | `InitService` | Initialize directory structure for metadata sync |
1583
+ | `PullService` | Pull metadata from database to local files |
1584
+ | `PushService` | Push local file changes to database |
1585
+ | `StatusService` | Compare local files with database state |
1586
+ | `WatchService` | Watch for file changes and auto-sync |
1587
+ | `ValidationService` | Validate metadata files for correctness |
1588
+ | `FileResetService` | Reset file checksums and primary keys |
1589
+ | `FormattingService` | Format validation results for display |
2978
1590
 
2979
- The MetadataSync validation can be used programmatically in any Node.js project:
1591
+ ### Using ValidationService
2980
1592
 
2981
1593
  ```typescript
2982
1594
  import { ValidationService, FormattingService } from '@memberjunction/metadata-sync';
2983
- import { ValidationOptions } from '@memberjunction/metadata-sync/dist/types/validation';
2984
1595
 
2985
- // Initialize validation options
2986
- const options: ValidationOptions = {
1596
+ // Create validator instance
1597
+ const validator = new ValidationService({
2987
1598
  verbose: false,
2988
1599
  outputFormat: 'human',
2989
1600
  maxNestingDepth: 10,
2990
1601
  checkBestPractices: true
2991
- };
2992
-
2993
- // Create validator instance
2994
- const validator = new ValidationService(options);
1602
+ });
2995
1603
 
2996
1604
  // Validate a directory
2997
1605
  const result = await validator.validateDirectory('/path/to/metadata');
@@ -3001,86 +1609,45 @@ if (result.isValid) {
3001
1609
  console.log('Validation passed!');
3002
1610
  } else {
3003
1611
  console.log(`Found ${result.errors.length} errors`);
3004
-
1612
+
3005
1613
  // Format results for display
3006
1614
  const formatter = new FormattingService();
3007
-
3008
- // Get human-readable output
3009
1615
  const humanOutput = formatter.formatValidationResult(result, true);
3010
1616
  console.log(humanOutput);
3011
-
3012
- // Get JSON output
1617
+
1618
+ // Or get JSON output
3013
1619
  const jsonOutput = formatter.formatValidationResultAsJson(result);
3014
-
3015
- // Get beautiful markdown report
1620
+
1621
+ // Or get markdown report
3016
1622
  const markdownReport = formatter.formatValidationResultAsMarkdown(result);
3017
1623
  }
3018
1624
  ```
3019
1625
 
3020
- ### ValidationResult Structure
3021
-
3022
- The validation service returns a structured object with complete details:
1626
+ ### Using PushService
3023
1627
 
3024
1628
  ```typescript
3025
- interface ValidationResult {
3026
- isValid: boolean;
3027
- errors: ValidationError[];
3028
- warnings: ValidationWarning[];
3029
- summary: {
3030
- totalFiles: number;
3031
- totalEntities: number;
3032
- totalErrors: number;
3033
- totalWarnings: number;
3034
- fileResults: Map<string, FileValidationResult>;
3035
- };
3036
- }
1629
+ import { SyncEngine, PushService } from '@memberjunction/metadata-sync';
1630
+ import { initializeProvider, getSystemUser } from '@memberjunction/metadata-sync';
3037
1631
 
3038
- interface ValidationError {
3039
- type: 'entity' | 'field' | 'reference' | 'circular' | 'dependency' | 'nesting' | 'bestpractice';
3040
- severity: 'error' | 'warning';
3041
- entity?: string;
3042
- field?: string;
3043
- file: string;
3044
- message: string;
3045
- suggestion?: string;
3046
- details?: any;
3047
- }
3048
- ```
1632
+ // Initialize the data provider
1633
+ await initializeProvider();
1634
+ const systemUser = await getSystemUser();
3049
1635
 
3050
- ### Integration Example
1636
+ // Create services
1637
+ const syncEngine = new SyncEngine(systemUser);
1638
+ const pushService = new PushService(syncEngine, systemUser);
3051
1639
 
3052
- ```typescript
3053
- import { ValidationService } from '@memberjunction/metadata-sync';
3054
-
3055
- export async function validateBeforeDeploy(metadataPath: string): Promise<boolean> {
3056
- const validator = new ValidationService({
3057
- checkBestPractices: true,
3058
- maxNestingDepth: 10
3059
- });
3060
-
3061
- const result = await validator.validateDirectory(metadataPath);
3062
-
3063
- if (!result.isValid) {
3064
- // Log errors to your monitoring system
3065
- result.errors.forEach(error => {
3066
- logger.error('Metadata validation error', {
3067
- type: error.type,
3068
- entity: error.entity,
3069
- field: error.field,
3070
- file: error.file,
3071
- message: error.message
3072
- });
3073
- });
3074
-
3075
- // Optionally save report
3076
- const report = new FormattingService().formatValidationResultAsMarkdown(result);
3077
- await fs.writeFile('validation-report.md', report);
3078
-
3079
- return false;
1640
+ // Push with callbacks
1641
+ const result = await pushService.push(
1642
+ { dir: './metadata', verbose: true },
1643
+ {
1644
+ onProgress: (msg) => console.log(msg),
1645
+ onSuccess: (msg) => console.log(msg),
1646
+ onError: (msg) => console.error(msg)
3080
1647
  }
3081
-
3082
- return true;
3083
- }
1648
+ );
1649
+
1650
+ console.log(`Created: ${result.created}, Updated: ${result.updated}`);
3084
1651
  ```
3085
1652
 
3086
1653
  ### CI/CD Integration
@@ -3091,7 +1658,7 @@ export async function validateBeforeDeploy(metadataPath: string): Promise<boolea
3091
1658
  run: |
3092
1659
  npm install @memberjunction/cli
3093
1660
  npx mj sync validate --dir=./metadata --format=json > validation-results.json
3094
-
1661
+
3095
1662
  - name: Check Validation Results
3096
1663
  run: |
3097
1664
  if [ $(jq '.isValid' validation-results.json) = "false" ]; then
@@ -3099,18 +1666,156 @@ export async function validateBeforeDeploy(metadataPath: string): Promise<boolea
3099
1666
  jq '.errors' validation-results.json
3100
1667
  exit 1
3101
1668
  fi
1669
+
1670
+ - name: Push Metadata to Production
1671
+ run: |
1672
+ npx mj sync push --ci --dir=./metadata
1673
+ ```
1674
+
1675
+ ## Creating Error-Free Entity Files
1676
+
1677
+ ### Quick Start Checklist
1678
+
1679
+ Before creating entity JSON files, follow this checklist:
1680
+
1681
+ 1. **Find the Entity Definition** -- Open `packages/MJCoreEntities/src/generated/entity_subclasses.ts` and search for the entity class
1682
+ 2. **Check Required Fields** -- Look for fields without `?` in TypeScript definitions
1683
+ 3. **Validate Field Names** -- Use exact field names from the BaseEntity class (case-sensitive)
1684
+ 4. **Use Correct File Naming** -- Configuration files must start with dot (`.mj-sync.json`)
1685
+ 5. **Set Up Directory Structure** -- Create `.mj-sync.json` with proper glob patterns
1686
+
1687
+ ### Step-by-Step Entity File Creation
1688
+
1689
+ #### Step 1: Research the Entity
1690
+ ```bash
1691
+ # Open in your IDE:
1692
+ packages/MJCoreEntities/src/generated/entity_subclasses.ts
1693
+
1694
+ # Search for your entity class (Ctrl+F):
1695
+ class TemplateEntity
1696
+ ```
1697
+
1698
+ #### Step 2: Create Directory Structure
1699
+ ```bash
1700
+ mkdir templates
1701
+ cd templates
1702
+
1703
+ # Create entity config (dot-prefixed configuration file)
1704
+ echo '{
1705
+ "entity": "Templates",
1706
+ "filePattern": "*.json"
1707
+ }' > .mj-sync.json
1708
+ ```
1709
+
1710
+ #### Step 3: Create Your First Entity File
1711
+ ```json
1712
+ {
1713
+ "fields": {
1714
+ "Name": "My First Template",
1715
+ "Description": "A test template",
1716
+ "UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"
1717
+ }
1718
+ }
1719
+ ```
1720
+
1721
+ #### Step 4: Test and Validate
1722
+ ```bash
1723
+ # Dry run to check for errors
1724
+ mj sync push --dir="templates" --dry-run
1725
+
1726
+ # If successful, do actual push
1727
+ mj sync push --dir="templates"
3102
1728
  ```
3103
1729
 
3104
- ## Future Enhancements
1730
+ ### Common Required Fields Pattern
3105
1731
 
3106
- - Plugin system for custom entity handlers
3107
- - Merge conflict resolution UI
3108
- - Bulk operations across entities
3109
- - Extended validation rules
3110
- - Schema migration support
3111
- - Team collaboration features
3112
- - Bidirectional sync for related entities
3113
- - Custom transformation pipelines
1732
+ **Always Required:**
1733
+ - `ID` -- Primary key (GUID, auto-generated if not provided)
1734
+ - `Name` -- Human-readable name
1735
+ - `UserID` -- Creator/owner (use System User: `ECAFCCEC-6A37-EF11-86D4-000D3A4E707E`)
1736
+
1737
+ **Be Careful With:**
1738
+ - `Status` fields -- Some entities have them, others do not
1739
+ - Enum fields -- Must match exact values from database
1740
+ - DateTime fields -- Use ISO format: `2024-01-15T10:30:00Z`
1741
+
1742
+ ### Troubleshooting Quick Reference
1743
+
1744
+ | Error Message | Cause | Solution |
1745
+ |---------------|-------|----------|
1746
+ | `No entity directories found` | Missing .mj-sync.json or wrong filePattern | Check .mj-sync.json exists and uses `"*.json"` |
1747
+ | `Field 'X' does not exist on entity 'Y'` | Using non-existent field | Check BaseEntity class in entity_subclasses.ts |
1748
+ | `User ID cannot be null` | Missing required UserID | Add `"UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"` |
1749
+ | `Processing 0 records` | Files do not match filePattern | Check files match pattern in .mj-sync.json |
1750
+ | Failed validation | Wrong data type or format | Check BaseEntity class for field types |
1751
+
1752
+ ## Console Output
1753
+
1754
+ ### Normal Mode
1755
+ Shows high-level progress:
1756
+ ```
1757
+ Processing AI Prompts in demo/ai-prompts
1758
+ -> Processing 2 related MJ: AI Prompt Models records
1759
+ Created: 1
1760
+ Updated: 2
1761
+ ```
1762
+
1763
+ ### Verbose Mode (-v flag)
1764
+ Shows detailed field-level operations with hierarchical indentation:
1765
+ ```
1766
+ Processing AI Prompts in demo/ai-prompts
1767
+ Setting Name: "Example Greeting Prompt 3" -> "Example Greeting Prompt 3"
1768
+ Setting Description: "A simple example prompt..." -> "A simple example prompt..."
1769
+ -> Processing 2 related MJ: AI Prompt Models records
1770
+ Setting PromptID: "@parent:ID" -> "C2A1433E-F36B-1410-8DB0-00021F8B792E"
1771
+ Setting ModelID: "@lookup:AI Models.Name=GPT 4.1" -> "123-456-789"
1772
+ Setting Priority: 1 -> 1
1773
+ Created MJ: AI Prompt Models record
1774
+ ```
1775
+
1776
+ ## Use Cases
1777
+
1778
+ ### Developer Workflow
1779
+ 1. Install the MJ CLI: `npm install -g @memberjunction/cli`
1780
+ 2. `mj sync pull --entity="AI Prompts"` to get latest prompts with their models
1781
+ 3. Edit prompts and adjust model configurations in VS Code
1782
+ 4. Test locally with `mj sync push --dry-run`
1783
+ 5. Commit changes to Git
1784
+ 6. PR review with diff visualization
1785
+ 7. CI/CD runs `mj sync push --ci` on merge
1786
+
1787
+ ### Content Team Workflow
1788
+ 1. Pull prompts to local directory
1789
+ 2. Edit in preferred markdown editor
1790
+ 3. Adjust model priorities in JSON
1791
+ 4. Preview changes
1792
+ 5. Push updates back to database
1793
+
1794
+ ### CI/CD Integration
1795
+ ```yaml
1796
+ - name: Push Metadata to Production
1797
+ run: |
1798
+ npm install -g @memberjunction/cli
1799
+ mj sync push --ci --entity="AI Prompts"
1800
+ ```
1801
+
1802
+ ## Dependencies
1803
+
1804
+ This package depends on:
1805
+ - [@memberjunction/core](../MJCore/README.md) -- Core MemberJunction framework (Metadata, RunView, BaseEntity)
1806
+ - [@memberjunction/core-entities](../MJCoreEntities/README.md) -- Generated entity subclasses
1807
+ - [@memberjunction/global](../MJGlobal/README.md) -- Global utilities and class factory
1808
+ - [@memberjunction/config](../Config/README.md) -- Configuration management
1809
+ - [@memberjunction/sqlserver-dataprovider](../SQLServerDataProvider/README.md) -- SQL Server data access
1810
+ - [@memberjunction/graphql-dataprovider](../GraphQLDataProvider/README.md) -- GraphQL data access
1811
+ - [@memberjunction/server-bootstrap-lite](../ServerBootstrapLite/README.md) -- Server-side class registration
1812
+
1813
+ Key third-party dependencies:
1814
+ - `chokidar` -- File watching for watch mode
1815
+ - `fast-glob` -- Fast file pattern matching
1816
+ - `cosmiconfig` -- Configuration file discovery
1817
+ - `zod` -- Runtime validation
1818
+ - `chalk` -- Terminal output formatting
3114
1819
 
3115
1820
  ## Migration from Standalone MetadataSync
3116
1821
 
@@ -3121,15 +1826,17 @@ If you were previously using the standalone `mj-sync` command:
3121
1826
  npm install -g @memberjunction/cli
3122
1827
  ```
3123
1828
 
3124
- 2. **Update your scripts**: Replace `mj-sync` with `mj sync` in all scripts and documentation
1829
+ 2. **Update your scripts**: Replace `mj-sync` with `mj sync` in all scripts
3125
1830
  ```bash
3126
1831
  # Old command (standalone package)
3127
1832
  mj-sync push --dir="metadata"
3128
-
1833
+
3129
1834
  # New command
3130
1835
  mj sync push --dir="metadata"
3131
1836
  ```
3132
1837
 
3133
1838
  3. **Configuration unchanged**: All `.mj-sync.json` configuration files work exactly the same
3134
1839
 
3135
- 4. **Same functionality**: The underlying MetadataSync library is identical, just accessed through the unified CLI
1840
+ ## Contributing
1841
+
1842
+ See the [MemberJunction Contributing Guide](../../CONTRIBUTING.md) for development setup and guidelines.