@memberjunction/metadata-sync 2.47.0 → 2.49.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
@@ -98,16 +98,410 @@ The tool is intended for managing business-level metadata such as:
98
98
 
99
99
  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).
100
100
 
101
+ ## Creating Error-Free Entity Files
102
+
103
+ ### Quick Start Checklist
104
+
105
+ Before creating entity JSON files, follow this checklist to avoid common mistakes:
106
+
107
+ ✅ **1. Find the Entity Definition**
108
+ - Open `packages/MJCoreEntities/src/generated/entity_subclasses.ts` or `packages/GeneratedEntities/src/generated/entity_subclasses.ts`
109
+ - Search for `class [EntityName]Entity` (e.g., `class TemplateEntity`)
110
+ - Review JSDoc comments and property definitions to identify required vs optional fields
111
+
112
+ ✅ **2. Check Required Fields**
113
+ - Look for JSDoc comments with `@required` annotations
114
+ - Fields without `?` in TypeScript definitions are typically required
115
+ - Always include `Name` (almost always required)
116
+ - Always include `UserID` (use System User ID: `ECAFCCEC-6A37-EF11-86D4-000D3A4E707E`)
117
+
118
+ ✅ **3. Validate Field Names**
119
+ - Use exact field names from the BaseEntity class definition
120
+ - Field names are case-sensitive
121
+ - Don't assume fields exist (e.g., not all entities have `Status`)
122
+
123
+ ✅ **4. Use Correct File Naming**
124
+ - Configuration files (.mj-sync.json, .mj-folder.json) must start with dot
125
+ - Metadata files follow the `filePattern` in your .mj-sync.json
126
+ - Most common: `"filePattern": "*.json"` (matches any .json file)
127
+ - Alternative: `"filePattern": ".*.json"` (matches dot-prefixed .json files)
128
+
129
+ ✅ **5. Set Up Directory Structure**
130
+ - Create `.mj-sync.json` in the entity directory
131
+ - Use glob patterns: `"filePattern": "*.json"` (not regex: `".*.json"`)
132
+
133
+ ### Discovering Entity Structure
134
+
135
+ **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.
136
+
137
+ #### Finding Entity Definitions
138
+
139
+ The approach depends on whether you're working inside or outside the MemberJunction monorepo:
140
+
141
+ ##### Working Inside MJ Monorepo
142
+
143
+ Entity classes are located in:
144
+
145
+ - **Core MJ Entities**: `packages/MJCoreEntities/src/generated/entity_subclasses.ts`
146
+ - System entities like Users, Roles, EntityFields, etc.
147
+ - AI-related entities like AI Prompts, AI Models, etc.
148
+
149
+ - **Custom Entities**: `packages/GeneratedEntities/src/generated/entity_subclasses.ts`
150
+ - Your application-specific entities
151
+ - Business domain entities
152
+
153
+ ##### Working Outside MJ Monorepo (In Your Own Project)
154
+
155
+ Entity classes are located in:
156
+
157
+ - **Core MJ Entities**: `node_modules/@memberjunction/core-entities/dist/generated/entity_subclasses.js`
158
+ - Note: This is compiled JavaScript, but your IDE should provide IntelliSense
159
+ - For TypeScript definitions: `node_modules/@memberjunction/core-entities/dist/generated/entity_subclasses.d.ts`
160
+
161
+ - **Custom Entities**: Your project's generated entities location (varies by project structure)
162
+ - Common locations: `src/generated/`, `packages/entities/`, or similar
163
+ - Look for files containing your custom entity classes
164
+
165
+ ##### Best Practice: Use Your IDE's IntelliSense
166
+
167
+ **Recommended approach for all scenarios:**
168
+
169
+ 1. **Import the entity class** in your IDE:
170
+ ```typescript
171
+ import { TemplateEntity } from '@memberjunction/core-entities';
172
+ ```
173
+
174
+ 2. **Create an instance and explore with IntelliSense**:
175
+ ```typescript
176
+ const template = new TemplateEntity();
177
+ // Type "template." and let your IDE show available properties
178
+ ```
179
+
180
+ 3. **Check the class definition** (F12 or "Go to Definition") to see:
181
+ - JSDoc comments with field descriptions
182
+ - Required vs optional fields
183
+ - Field types and validation rules
184
+ - Relationships and constraints
185
+
186
+ #### How to Find Required Fields
187
+
188
+ 1. **Use IDE IntelliSense** (Recommended):
189
+ - Import the entity class
190
+ - Create an instance: `const entity = new TemplateEntity();`
191
+ - Use "Go to Definition" (F12) to see the BaseEntity class
192
+ - Look for JSDoc comments and field definitions
193
+
194
+ 2. **Examine the BaseEntity Class**:
195
+ - Find the entity class (e.g., `class TemplateEntity`)
196
+ - Look at property declarations with JSDoc comments
197
+ - Check for required vs optional field annotations
198
+ - Review any validation methods or constraints
199
+
200
+ 3. **Runtime Metadata Discovery**:
201
+ ```typescript
202
+ import { Metadata } from '@memberjunction/core';
203
+
204
+ const md = new Metadata();
205
+ const entityInfo = md.EntityByName('Templates');
206
+ console.log('Required fields:', entityInfo.Fields.filter(f => !f.AllowsNull));
207
+ ```
208
+
209
+ #### Example: Templates Entity Structure
210
+ ```typescript
211
+ // BaseEntity class (accessible via IDE IntelliSense)
212
+ export class TemplateEntity extends BaseEntity {
213
+ /**
214
+ * Primary key - auto-generated GUID
215
+ */
216
+ ID: string;
217
+
218
+ /**
219
+ * Template name - REQUIRED
220
+ * @required
221
+ */
222
+ Name: string;
223
+
224
+ /**
225
+ * Template description - optional
226
+ */
227
+ Description?: string;
228
+
229
+ /**
230
+ * User who created this template - REQUIRED
231
+ * Must be a valid User ID
232
+ * @required
233
+ * @foreignKey Users.ID
234
+ */
235
+ UserID: string;
236
+
237
+ /**
238
+ * Category for organizing templates - optional
239
+ * @foreignKey TemplateCategories.ID
240
+ */
241
+ CategoryID?: string;
242
+
243
+ // Note: Status field may not exist on all entities!
244
+ }
245
+ ```
246
+
247
+ #### Common Required Fields Pattern
248
+
249
+ Most MJ entities follow these patterns:
250
+
251
+ **Always Required:**
252
+ - `ID` - Primary key (GUID) - auto-generated if not provided
253
+ - `Name` - Human-readable name
254
+ - `UserID` - Creator/owner (use System User: `ECAFCCEC-6A37-EF11-86D4-000D3A4E707E`)
255
+
256
+ **Often Required:**
257
+ - `Description` - Usually optional but recommended
258
+ - Foreign key fields ending in `ID` - Check if they have `.optional()`
259
+
260
+ **Be Careful With:**
261
+ - `Status` fields - Some entities have them, others don't
262
+ - Enum fields - Must match exact values from database
263
+ - DateTime fields - Use ISO format: `2024-01-15T10:30:00Z`
264
+
265
+ ### Common Mistakes and Solutions
266
+
267
+ #### ❌ Mistake 1: Using Non-Existent Fields
268
+ ```json
269
+ {
270
+ "fields": {
271
+ "Name": "My Template",
272
+ "Status": "Active" // ❌ Templates entity may not have Status field
273
+ }
274
+ }
275
+ ```
276
+
277
+ **✅ Solution**: Check the BaseEntity class first
278
+ ```typescript
279
+ // In entity_subclasses.ts - if you don't see Status here, don't use it
280
+ export class TemplateEntity extends BaseEntity {
281
+ Name: string; // Required
282
+ Description?: string; // Optional (note the ?)
283
+ // No Status field defined
284
+ }
285
+ ```
286
+
287
+ #### ❌ Mistake 2: Missing Required Fields
288
+ ```json
289
+ {
290
+ "fields": {
291
+ "Name": "My Template"
292
+ // ❌ Missing required UserID
293
+ }
294
+ }
295
+ ```
296
+
297
+ **✅ Solution**: Include all required fields
298
+ ```json
299
+ {
300
+ "fields": {
301
+ "Name": "My Template",
302
+ "UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"
303
+ }
304
+ }
305
+ ```
306
+
307
+ #### ❌ Mistake 3: Wrong File Pattern in .mj-sync.json
308
+ ```json
309
+ {
310
+ "entity": "Templates",
311
+ "filePattern": ".*.json" // ❌ This is regex, not glob
312
+ }
313
+ ```
314
+
315
+ **✅ Solution**: Use glob patterns
316
+ ```json
317
+ {
318
+ "entity": "Templates",
319
+ "filePattern": "*.json" // ✅ Correct glob pattern
320
+ }
321
+ ```
322
+
323
+ #### ❌ Mistake 4: Incorrect Data Types
324
+ ```json
325
+ {
326
+ "fields": {
327
+ "Name": "My Template",
328
+ "CreatedAt": "2024-01-15", // ❌ Wrong datetime format
329
+ "Priority": "1" // ❌ Should be number, not string
330
+ }
331
+ }
332
+ ```
333
+
334
+ **✅ Solution**: Use correct data types
335
+ ```json
336
+ {
337
+ "fields": {
338
+ "Name": "My Template",
339
+ "CreatedAt": "2024-01-15T10:30:00Z", // ✅ ISO format
340
+ "Priority": 1 // ✅ Number type
341
+ }
342
+ }
343
+ ```
344
+
345
+ #### ❌ Mistake 5: Files Not Being Detected
346
+ ```
347
+ mydir/
348
+ ├── .mj-sync.json (with "filePattern": "*.json")
349
+ ├── template1.txt // ❌ Wrong extension
350
+ └── .template2.json // ❌ Dot prefix when pattern is "*.json"
351
+ ```
352
+
353
+ **✅ Solution**: Match your filePattern
354
+ ```
355
+ mydir/
356
+ ├── .mj-sync.json (with "filePattern": "*.json")
357
+ ├── template1.json // ✅ Matches *.json pattern
358
+ └── template2.json // ✅ Matches *.json pattern
359
+ ```
360
+
361
+ ### Step-by-Step Entity File Creation
362
+
363
+ #### Step 1: Research the Entity
364
+ ```bash
365
+ # Open in your IDE:
366
+ packages/MJCoreEntities/src/generated/entity_subclasses.ts
367
+
368
+ # Search for your entity class (Ctrl+F):
369
+ class TemplateEntity
370
+
371
+ # Note the required vs optional fields:
372
+ Name: string; // Required (no ?)
373
+ UserID: string; // Required (no ?)
374
+ Description?: string; // Optional (note the ?)
375
+ ```
376
+
377
+ #### Step 2: Create Directory Structure
378
+ ```bash
379
+ mkdir templates
380
+ cd templates
381
+
382
+ # Create entity config (dot-prefixed configuration file)
383
+ echo '{
384
+ "entity": "Templates",
385
+ "filePattern": "*.json"
386
+ }' > .mj-sync.json
387
+ ```
388
+
389
+ #### Step 3: Create Your First Entity File
390
+ ```bash
391
+ # Create metadata file (follows filePattern from .mj-sync.json)
392
+ echo '{
393
+ "fields": {
394
+ "Name": "My First Template",
395
+ "Description": "A test template",
396
+ "UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"
397
+ }
398
+ }' > my-first-template.json
399
+ ```
400
+
401
+ #### Step 4: Test and Validate
402
+ ```bash
403
+ # Dry run to check for errors
404
+ mj-sync push --dir="templates" --dry-run
405
+
406
+ # If successful, do actual push
407
+ mj-sync push --dir="templates"
408
+ ```
409
+
410
+ ### AI/LLM Guidelines
411
+
412
+ When using AI tools (like Claude, ChatGPT, etc.) to generate entity files:
413
+
414
+ **🤖 For AI Assistants:**
415
+
416
+ 1. **Always check entity definitions first** - Never assume field names or requirements
417
+ 2. **Look up the exact BaseEntity class** in the generated entity files
418
+ 3. **Use the System User ID** (`ECAFCCEC-6A37-EF11-86D4-000D3A4E707E`) for UserID fields
419
+ 4. **Include only fields that exist** in the entity definition
420
+ 5. **Use proper data types** as defined in the BaseEntity class
421
+ 6. **Remember file naming rules**:
422
+ - Configuration files (.mj-sync.json) must have dot prefix
423
+ - Metadata files follow the filePattern in .mj-sync.json
424
+ 7. **Use glob patterns** in .mj-sync.json, not regex patterns
425
+
426
+ **📝 Prompt Template for AI:**
427
+ ```
428
+ I need to create entity files for the [EntityName] entity in MemberJunction.
429
+
430
+ Please:
431
+ 1. First, check the entity definition in packages/MJCoreEntities/src/generated/entity_subclasses.ts
432
+ 2. Find the class [EntityName]Entity (e.g., class TemplateEntity)
433
+ 3. Review JSDoc comments and property definitions to identify required vs optional fields
434
+ 4. Create a .mj-sync.json file with correct glob pattern
435
+ 5. Create sample metadata JSON files following the filePattern
436
+ 6. Use UserID: "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E" for required UserID fields
437
+ 7. Follow the exact field names and data types from the BaseEntity class definition
438
+
439
+ CRITICAL: Configuration files (.mj-sync.json) must start with dot, but metadata files follow the filePattern specified in the configuration.
440
+ ```
441
+
442
+ ### Understanding File Naming Rules
443
+
444
+ **Configuration Files (Always Dot-Prefixed):**
445
+ - ✅ `.mj-sync.json` - Entity configuration
446
+ - ✅ `.mj-folder.json` - Folder defaults
447
+ - ❌ `mj-sync.json` - Won't be recognized
448
+
449
+ **Metadata Files (Follow filePattern):**
450
+ With `"filePattern": "*.json"`:
451
+ - ✅ `my-template.json` - Will be processed
452
+ - ✅ `greeting.json` - Will be processed
453
+ - ❌ `.my-template.json` - Won't match pattern
454
+ - ❌ `package.json` - Will be ignored (add to ignore list if needed)
455
+
456
+ With `"filePattern": ".*.json"`:
457
+ - ✅ `.my-template.json` - Will be processed
458
+ - ✅ `.greeting.json` - Will be processed
459
+ - ❌ `my-template.json` - Won't match pattern
460
+ - ❌ `package.json` - Won't match pattern
461
+
462
+ ### Troubleshooting Quick Reference
463
+
464
+ | Error Message | Cause | Solution |
465
+ |---------------|-------|----------|
466
+ | `No entity directories found` | Missing .mj-sync.json or wrong filePattern | Check .mj-sync.json exists and uses `"*.json"` |
467
+ | `Field 'X' does not exist on entity 'Y'` | Using non-existent field | Check BaseEntity class in entity_subclasses.ts |
468
+ | `User ID cannot be null` | Missing required UserID | Add `"UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"` |
469
+ | `Processing 0 records` | Files don't match filePattern | Check files match pattern in .mj-sync.json |
470
+ | Failed validation | Wrong data type or format | Check BaseEntity class for field types |
471
+
472
+ ### System User ID Reference
473
+
474
+ **Always use this GUID for UserID fields:**
475
+ ```
476
+ ECAFCCEC-6A37-EF11-86D4-000D3A4E707E
477
+ ```
478
+
479
+ 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.
480
+
101
481
  ## File Structure
102
482
 
103
483
  The tool uses a hierarchical directory structure with cascading defaults:
104
484
  - Each top-level directory represents an entity type
105
485
  - `.mj-sync.json` files define entities and base defaults
106
486
  - `.mj-folder.json` files define folder-specific defaults (optional)
107
- - All JSON files within are treated as records of that entity type
487
+ - Only dot-prefixed JSON files (e.g., `.prompt-template.json`, `.category.json`) are treated as metadata records
488
+ - Regular JSON files without the dot prefix are ignored, allowing package.json and other config files to coexist
108
489
  - External files (`.md`, `.html`, etc.) are referenced from the JSON files
109
490
  - Defaults cascade down through the folder hierarchy
110
491
 
492
+ ### File Naming Convention
493
+
494
+ **Metadata files must be prefixed with a dot (.)** to be recognized by the sync tool. This convention:
495
+ - Clearly distinguishes metadata files from regular configuration files
496
+ - Allows `package.json`, `tsconfig.json` and other standard files to coexist without being processed
497
+ - Follows established patterns like `.gitignore` and `.eslintrc.json`
498
+
499
+ Examples:
500
+ - ✅ `.greeting.json` - Will be processed as metadata
501
+ - ✅ `.customer-prompt.json` - Will be processed as metadata
502
+ - ❌ `greeting.json` - Will be ignored
503
+ - ❌ `package.json` - Will be ignored
504
+
111
505
  ### File Format Options
112
506
 
113
507
  #### Single Record per File (Default)
@@ -147,12 +541,12 @@ metadata/
147
541
  │ ├── .mj-sync.json # Defines entity: "AI Prompts"
148
542
  │ ├── customer-service/
149
543
  │ │ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
150
- │ │ ├── greeting.json # AI Prompt record with embedded models
544
+ │ │ ├── .greeting.json # AI Prompt record with embedded models
151
545
  │ │ ├── greeting.prompt.md # Prompt content (referenced)
152
546
  │ │ └── greeting.notes.md # Notes field (referenced)
153
547
  │ └── analytics/
154
548
  │ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
155
- │ ├── daily-report.json # AI Prompt record
549
+ │ ├── .daily-report.json # AI Prompt record
156
550
  │ └── daily-report.prompt.md # Prompt content (referenced)
157
551
  ├── templates/ # Reusable JSON templates
158
552
  │ ├── standard-prompt-settings.json # Common prompt configurations
@@ -163,17 +557,17 @@ metadata/
163
557
  ├── .mj-sync.json # Defines entity: "Templates"
164
558
  ├── email/
165
559
  │ ├── .mj-folder.json # Folder metadata
166
- │ ├── welcome.json # Template record
560
+ │ ├── .welcome.json # Template record (dot-prefixed)
167
561
  │ └── welcome.template.html # Template content (referenced)
168
562
  └── reports/
169
563
  ├── .mj-folder.json # Folder metadata
170
- ├── invoice.json # Template record
564
+ ├── .invoice.json # Template record (dot-prefixed)
171
565
  └── invoice.template.html # Template content (referenced)
172
566
  ```
173
567
 
174
568
  ## JSON Metadata Format
175
569
 
176
- ### Individual Record (e.g., ai-prompts/customer-service/greeting.json)
570
+ ### Individual Record (e.g., ai-prompts/customer-service/.greeting.json)
177
571
  ```json
178
572
  {
179
573
  "fields": {
@@ -457,14 +851,129 @@ Configuration follows a hierarchical structure:
457
851
  - **Entity configs**: Each entity directory has its own config defining the entity type
458
852
  - **Inheritance**: All files within an entity directory are treated as records of that entity type
459
853
 
854
+ ### Directory Processing Order (NEW)
855
+
856
+ 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.
857
+
858
+ #### Directory Order Configuration
859
+
860
+ Directory order is configured in the root-level `.mj-sync.json` file only (not inherited by subdirectories):
861
+
862
+ ```json
863
+ {
864
+ "version": "1.0.0",
865
+ "directoryOrder": [
866
+ "prompts",
867
+ "agent-types"
868
+ ]
869
+ }
870
+ ```
871
+
872
+ #### How It Works
873
+
874
+ - **Ordered Processing**: Directories listed in `directoryOrder` are processed first, in the specified order
875
+ - **Remaining Directories**: Any directories not listed are processed after the ordered ones, in alphabetical order
876
+ - **Dependency Management**: Ensures prompts are created before agent types that reference them
877
+ - **Flexible**: Only specify the directories that have order requirements
878
+
879
+ #### Example Use Cases
880
+
881
+ 1. **AI Prompts → Agent Types**: Create prompts before agent types that reference them
882
+ 2. **Categories → Items**: Create category records before items that reference them
883
+ 3. **Parent → Child**: Process parent entities before child entities with foreign key dependencies
884
+
885
+ ### SQL Logging (NEW)
886
+
887
+ The MetadataSync tool now supports SQL logging for capturing all database operations during push commands. This feature is useful for:
888
+ - Creating migration files from MetadataSync operations
889
+ - Debugging database changes
890
+ - Understanding what SQL operations occur during push
891
+ - Creating migration scripts for deployment to other environments
892
+
893
+ #### SQL Logging Configuration
894
+
895
+ SQL logging is configured in the root-level `.mj-sync.json` file only (not inherited by subdirectories):
896
+
897
+ ```json
898
+ {
899
+ "version": "1.0.0",
900
+ "sqlLogging": {
901
+ "enabled": true,
902
+ "outputDirectory": "./sql_logging",
903
+ "formatAsMigration": true
904
+ }
905
+ }
906
+ ```
907
+
908
+ #### SQL Logging Options
909
+
910
+ | Option | Type | Default | Description |
911
+ |--------|------|---------|-------------|
912
+ | `enabled` | boolean | false | Whether to enable SQL logging during push operations |
913
+ | `outputDirectory` | string | "./sql_logging" | Directory to output SQL log files (relative to command execution directory) |
914
+ | `formatAsMigration` | boolean | false | Whether to format SQL as migration-ready files with Flyway schema placeholders |
915
+
916
+ #### SQL Log File Format
917
+
918
+ When `formatAsMigration` is `false`, log files are named:
919
+ ```
920
+ metadatasync-push-YYYY-MM-DDTHH-MM-SS.sql
921
+ ```
922
+
923
+ When `formatAsMigration` is `true`, log files are named as Flyway migrations:
924
+ ```
925
+ VYYYYMMDDHHMMSS__MetadataSync_Push.sql
926
+ ```
927
+
928
+ Migration files include:
929
+ - Header comments with timestamp and description
930
+ - Schema placeholders that can be replaced during deployment
931
+ - Properly formatted SQL statements with parameters
932
+
933
+ #### Example Usage
934
+
935
+ 1. **Enable SQL logging** in your root `.mj-sync.json`:
936
+ ```json
937
+ {
938
+ "version": "1.0.0",
939
+ "sqlLogging": {
940
+ "enabled": true,
941
+ "outputDirectory": "./migrations",
942
+ "formatAsMigration": true
943
+ }
944
+ }
945
+ ```
946
+
947
+ 2. **Run push command** as normal:
948
+ ```bash
949
+ mj-sync push
950
+ ```
951
+
952
+ 3. **Review generated SQL** in the output directory:
953
+ ```
954
+ migrations/
955
+ └── V20241215103045__MetadataSync_Push.sql
956
+ ```
957
+
958
+ 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.
959
+
460
960
  ### Root Configuration (metadata/.mj-sync.json)
461
961
  ```json
462
962
  {
463
963
  "version": "1.0.0",
964
+ "directoryOrder": [
965
+ "prompts",
966
+ "agent-types"
967
+ ],
464
968
  "push": {
465
969
  "validateBeforePush": true,
466
970
  "requireConfirmation": true
467
971
  },
972
+ "sqlLogging": {
973
+ "enabled": true,
974
+ "outputDirectory": "./sql_logging",
975
+ "formatAsMigration": false
976
+ },
468
977
  "watch": {
469
978
  "debounceMs": 1000,
470
979
  "ignorePatterns": ["*.tmp", "*.bak"]
@@ -476,7 +985,7 @@ Configuration follows a hierarchical structure:
476
985
  ```json
477
986
  {
478
987
  "entity": "AI Prompts",
479
- "filePattern": "*.json",
988
+ "filePattern": ".*.json",
480
989
  "defaults": {
481
990
  "TypeID": "@lookup:AI Prompt Types.Name=Chat",
482
991
  "Temperature": 0.7,
@@ -484,11 +993,21 @@ Configuration follows a hierarchical structure:
484
993
  "Status": "Active"
485
994
  },
486
995
  "pull": {
996
+ "filePattern": ".*.json",
997
+ "updateExistingRecords": true,
998
+ "createNewFileIfNotFound": true,
999
+ "mergeStrategy": "merge",
487
1000
  "filter": "Status = 'Active'",
1001
+ "externalizeFields": [
1002
+ {
1003
+ "field": "Prompt",
1004
+ "pattern": "@file:{Name}.prompt.md"
1005
+ }
1006
+ ],
488
1007
  "relatedEntities": {
489
1008
  "MJ: AI Prompt Models": {
490
1009
  "entity": "MJ: AI Prompt Models",
491
- "foreignKey": "ID",
1010
+ "foreignKey": "PromptID",
492
1011
  "filter": "Status = 'Active'"
493
1012
  }
494
1013
  }
@@ -528,27 +1047,307 @@ The tool now supports managing related entities as embedded collections within p
528
1047
  - **Cleaner Organization**: Fewer files to manage
529
1048
  - **Relationship Clarity**: Visual representation of data relationships
530
1049
 
1050
+ ## Recursive Patterns (NEW)
1051
+
1052
+ The tool now supports automatic recursive patterns for self-referencing entities, eliminating the need to manually define each nesting level for hierarchical data structures.
1053
+
1054
+ ### Benefits
1055
+ - **Simplified Configuration**: No need to manually define each hierarchy level
1056
+ - **Automatic Depth Handling**: Adapts to actual data depth dynamically
1057
+ - **Reduced Maintenance**: Configuration stays simple regardless of data changes
1058
+ - **Safeguards**: Built-in protection against infinite loops and excessive memory usage
1059
+
1060
+ ### Recursive Configuration
1061
+
1062
+ Enable recursive patterns for self-referencing entities:
1063
+
1064
+ ```json
1065
+ {
1066
+ "pull": {
1067
+ "entities": {
1068
+ "AI Agents": {
1069
+ "relatedEntities": {
1070
+ "AI Agents": {
1071
+ "entity": "AI Agents",
1072
+ "foreignKey": "ParentID",
1073
+ "recursive": true, // Enable recursive fetching
1074
+ "maxDepth": 10, // Optional depth limit (omit for default of 10)
1075
+ "filter": "Status = 'Active'"
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1081
+ }
1082
+ ```
1083
+
1084
+ ### How It Works
1085
+
1086
+ When `recursive: true` is set:
1087
+
1088
+ 1. **Automatic Child Fetching**: The tool automatically fetches child records at each level
1089
+ 2. **Dynamic Depth**: Continues until no more children are found or max depth is reached
1090
+ 3. **Circular Reference Protection**: Prevents infinite loops by tracking processed record IDs
1091
+ 4. **Consistent Configuration**: All recursive levels use the same `lookupFields`, `externalizeFields`, etc.
1092
+
1093
+ ### Before vs After
1094
+
1095
+ **Before (Manual Configuration):**
1096
+ ```json
1097
+ {
1098
+ "pull": {
1099
+ "relatedEntities": {
1100
+ "AI Agents": {
1101
+ "entity": "AI Agents",
1102
+ "foreignKey": "ParentID",
1103
+ "relatedEntities": {
1104
+ "AI Agents": {
1105
+ "entity": "AI Agents",
1106
+ "foreignKey": "ParentID",
1107
+ "relatedEntities": {
1108
+ "AI Agents": {
1109
+ "entity": "AI Agents",
1110
+ "foreignKey": "ParentID"
1111
+ // Must manually add more levels...
1112
+ }
1113
+ }
1114
+ }
1115
+ }
1116
+ }
1117
+ }
1118
+ }
1119
+ }
1120
+ ```
1121
+
1122
+ **After (Recursive Configuration):**
1123
+ ```json
1124
+ {
1125
+ "pull": {
1126
+ "relatedEntities": {
1127
+ "AI Agents": {
1128
+ "entity": "AI Agents",
1129
+ "foreignKey": "ParentID",
1130
+ "recursive": true,
1131
+ "maxDepth": 10
1132
+ }
1133
+ }
1134
+ }
1135
+ }
1136
+ ```
1137
+
1138
+ ### Configuration Options
1139
+
1140
+ | Option | Type | Default | Description |
1141
+ |--------|------|---------|-------------|
1142
+ | `recursive` | boolean | false | Enable automatic recursive fetching |
1143
+ | `maxDepth` | number | 10 | Maximum recursion depth to prevent infinite loops |
1144
+
1145
+ ### Safeguards
1146
+
1147
+ - **Circular Reference Detection**: Tracks processed record IDs to prevent infinite loops
1148
+ - **Maximum Depth Limit**: Configurable depth limit (default: 10) prevents excessive memory usage
1149
+ - **Performance Monitoring**: Verbose mode shows recursion depth and skipped circular references
1150
+ - **Backward Compatibility**: Existing configurations continue to work unchanged
1151
+
531
1152
  ### Configuration for Pull
532
- Configure which related entities to pull in `.mj-sync.json`:
1153
+
1154
+ The pull command now supports smart update capabilities with extensive configuration options:
1155
+
533
1156
  ```json
534
1157
  {
535
1158
  "entity": "AI Prompts",
1159
+ "filePattern": ".*.json",
536
1160
  "pull": {
1161
+ "filePattern": ".*.json",
1162
+ "createNewFileIfNotFound": true,
1163
+ "newFileName": ".all-new.json",
1164
+ "appendRecordsToExistingFile": true,
1165
+ "updateExistingRecords": true,
1166
+ "preserveFields": ["customField", "localNotes"],
1167
+ "mergeStrategy": "merge",
1168
+ "backupBeforeUpdate": true,
1169
+ "filter": "Status = 'Active'",
1170
+ "externalizeFields": [
1171
+ {
1172
+ "field": "TemplateText",
1173
+ "pattern": "@file:{Name}.template.md"
1174
+ },
1175
+ {
1176
+ "field": "PromptText",
1177
+ "pattern": "@file:prompts/{Name}.prompt.md"
1178
+ }
1179
+ ],
1180
+ "excludeFields": ["InternalID", "TempField"],
1181
+ "lookupFields": {
1182
+ "CategoryID": {
1183
+ "entity": "AI Prompt Categories",
1184
+ "field": "Name"
1185
+ },
1186
+ "TypeID": {
1187
+ "entity": "AI Prompt Types",
1188
+ "field": "Name"
1189
+ }
1190
+ },
537
1191
  "relatedEntities": {
538
1192
  "MJ: AI Prompt Models": {
539
1193
  "entity": "MJ: AI Prompt Models",
540
- "foreignKey": "ID",
541
- "filter": "Status = 'Active'"
542
- },
543
- "AI Prompt Parameters": {
544
- "entity": "AI Prompt Parameters",
545
- "foreignKey": "ID"
1194
+ "foreignKey": "PromptID",
1195
+ "filter": "Status = 'Active'",
1196
+ "lookupFields": {
1197
+ "ModelID": {
1198
+ "entity": "AI Models",
1199
+ "field": "Name"
1200
+ }
1201
+ }
546
1202
  }
547
1203
  }
548
1204
  }
549
1205
  }
550
1206
  ```
551
1207
 
1208
+ #### Pull Configuration Options
1209
+
1210
+ | Option | Type | Default | Description |
1211
+ |--------|------|---------|-------------|
1212
+ | `filePattern` | string | Entity filePattern | Pattern for finding existing files to update |
1213
+ | `createNewFileIfNotFound` | boolean | true | Create files for records not found locally |
1214
+ | `newFileName` | string | - | Filename for new records when appending (see warning below) |
1215
+ | `appendRecordsToExistingFile` | boolean | false | Append new records to a single file |
1216
+ | `updateExistingRecords` | boolean | true | Update existing records found in local files |
1217
+ | `preserveFields` | string[] | [] | Fields that retain local values during updates (see detailed explanation below) |
1218
+ | `mergeStrategy` | string | "merge" | How to merge updates: "merge", "overwrite", or "skip" |
1219
+ | `backupBeforeUpdate` | boolean | false | Create timestamped backups before updating files |
1220
+ | `backupDirectory` | string | ".backups" | Directory name for backup files (relative to entity directory) |
1221
+ | `filter` | string | - | SQL WHERE clause for filtering records |
1222
+ | `externalizeFields` | array/object | - | Fields to save as external files with optional patterns |
1223
+ | `excludeFields` | string[] | [] | Fields to completely omit from pulled data (see detailed explanation below) |
1224
+ | `lookupFields` | object | - | Foreign keys to convert to @lookup references |
1225
+ | `relatedEntities` | object | - | Related entities to pull as embedded collections |
1226
+
1227
+ > **⚠️ Important Configuration Warning**
1228
+ >
1229
+ > 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:
1230
+ >
1231
+ > ```json
1232
+ > // This configuration will put ALL new records in .all-new.json
1233
+ > "pull": {
1234
+ > "appendRecordsToExistingFile": true,
1235
+ > "newFileName": ".all-new.json" // ⚠️ Overrides individual file creation
1236
+ > }
1237
+ > ```
1238
+ >
1239
+ > **Recommended configurations:**
1240
+ > - For individual files per record: Set `appendRecordsToExistingFile: false` (or omit it)
1241
+ > - For grouped new records: Set both `appendRecordsToExistingFile: true` and `newFileName`
1242
+ > - For mixed approach: Omit `newFileName` to let new records follow the standard pattern
1243
+
1244
+ #### Merge Strategies
1245
+
1246
+ - **`merge`** (default): Combines fields from database and local file, with database values taking precedence for existing fields
1247
+ - **`overwrite`**: Completely replaces local record with database version (except preserved fields)
1248
+ - **`skip`**: Leaves existing records unchanged, only adds new records
1249
+
1250
+ #### Understanding excludeFields vs preserveFields
1251
+
1252
+ These two configuration options serve different purposes for managing fields during pull operations:
1253
+
1254
+ ##### excludeFields
1255
+ - **Purpose**: Completely omit specified fields from your local files
1256
+ - **Use Case**: Remove internal/system fields you don't want in version control
1257
+ - **Effect**: Fields never appear in the JSON files
1258
+ - **Example**: Excluding internal IDs, timestamps, or sensitive data
1259
+
1260
+ ##### preserveFields
1261
+ - **Purpose**: Protect local customizations from being overwritten during updates
1262
+ - **Use Case**: Keep locally modified values while updating other fields
1263
+ - **Effect**: Fields exist in files but retain their local values during pull
1264
+ - **Example**: Preserving custom file paths, local notes, or environment-specific values
1265
+ - **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
1266
+
1267
+ ##### Example Configuration
1268
+ ```json
1269
+ {
1270
+ "pull": {
1271
+ "excludeFields": ["TemplateID", "InternalNotes", "CreatedAt"],
1272
+ "preserveFields": ["TemplateText", "OutputExample", "LocalConfig"]
1273
+ }
1274
+ }
1275
+ ```
1276
+
1277
+ With this configuration:
1278
+ - **TemplateID, InternalNotes, CreatedAt** → Never appear in local files
1279
+ - **TemplateText, OutputExample, LocalConfig** → Keep their local values during updates
1280
+
1281
+ ##### Common Scenario: Customized File References
1282
+ When you customize file paths (e.g., changing `@file:templates/skip-conductor.md` to `@file:templates/conductor.md`), use `preserveFields` to protect these customizations:
1283
+
1284
+ ```json
1285
+ {
1286
+ "pull": {
1287
+ "preserveFields": ["TemplateText", "OutputExample"],
1288
+ "externalizeFields": [
1289
+ {
1290
+ "field": "TemplateText",
1291
+ "pattern": "@file:templates/{Name}.template.md"
1292
+ }
1293
+ ]
1294
+ }
1295
+ }
1296
+ ```
1297
+
1298
+ This ensures your custom paths aren't overwritten when pulling updates from the database.
1299
+
1300
+ **How it works:**
1301
+ 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
1302
+ 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
1303
+
1304
+ This is particularly useful when:
1305
+ - You've reorganized your file structure after initial pull
1306
+ - You've renamed files to follow your own naming conventions
1307
+ - You want to maintain consistent paths across team members
1308
+
1309
+ #### Backup Configuration
1310
+
1311
+ When `backupBeforeUpdate` is enabled, the tool creates timestamped backups before updating existing files:
1312
+
1313
+ - **Backup Location**: Files are backed up to the `backupDirectory` (default: `.backups`) within the entity directory
1314
+ - **Backup Naming**: Original filename + timestamp + `.backup` extension (e.g., `.greeting.json` → `.greeting.2024-03-15T10-30-45-123Z.backup`)
1315
+ - **Extension**: All backup files use the `.backup` extension, preventing them from being processed by push/pull/status commands
1316
+ - **Deduplication**: Only one backup is created per file per pull operation, even if the file contains multiple records
1317
+
1318
+ Example configuration:
1319
+ ```json
1320
+ "pull": {
1321
+ "backupBeforeUpdate": true,
1322
+ "backupDirectory": ".backups" // Custom backup directory name
1323
+ }
1324
+ ```
1325
+
1326
+ #### Externalize Fields Patterns
1327
+
1328
+ The `externalizeFields` configuration supports dynamic file naming with placeholders:
1329
+
1330
+ ```json
1331
+ "externalizeFields": [
1332
+ {
1333
+ "field": "TemplateText",
1334
+ "pattern": "@file:{Name}.template.md"
1335
+ },
1336
+ {
1337
+ "field": "SQLQuery",
1338
+ "pattern": "@file:queries/{CategoryName}/{Name}.sql"
1339
+ }
1340
+ ]
1341
+ ```
1342
+
1343
+ Supported placeholders:
1344
+ - `{Name}` - The entity's name field value
1345
+ - `{ID}` - The entity's primary key
1346
+ - `{FieldName}` - The field being externalized
1347
+ - `{AnyFieldName}` - Any field from the entity record
1348
+
1349
+ All values are sanitized for filesystem compatibility (lowercase, spaces to hyphens, special characters removed).
1350
+
552
1351
  ### Nested Related Entities
553
1352
  Support for multiple levels of nesting:
554
1353
  ```json