@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 +907 -2200
- package/dist/lib/deletion-auditor.d.ts +15 -1
- package/dist/lib/deletion-auditor.d.ts.map +1 -1
- package/dist/lib/deletion-auditor.js +102 -5
- package/dist/lib/deletion-auditor.js.map +1 -1
- package/dist/lib/record-dependency-analyzer.d.ts +39 -1
- package/dist/lib/record-dependency-analyzer.d.ts.map +1 -1
- package/dist/lib/record-dependency-analyzer.js +82 -9
- package/dist/lib/record-dependency-analyzer.js.map +1 -1
- package/dist/services/PushService.d.ts +1 -0
- package/dist/services/PushService.d.ts.map +1 -1
- package/dist/services/PushService.js +85 -27
- package/dist/services/PushService.js.map +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
|
306
|
+
### Individual Record
|
|
595
307
|
```json
|
|
596
308
|
{
|
|
597
309
|
"fields": {
|
|
@@ -683,34 +395,23 @@ metadata/
|
|
|
683
395
|
}
|
|
684
396
|
```
|
|
685
397
|
|
|
686
|
-
|
|
398
|
+
### Reserved Keys
|
|
687
399
|
|
|
688
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
1062
|
-
- `@lookup:Users.Email=john@example.com&Department=Sales`
|
|
1063
|
-
- `@lookup:AI Prompt Categories.Name=Examples?create`
|
|
1064
|
-
- `@lookup:AI Prompt Categories.Name=Examples?create&Description=Example prompts`
|
|
1065
|
-
- `@lookup:Dashboards.Name=Data Explorer?allowDefer`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
1190
|
-
- `@parent:Name`
|
|
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`
|
|
1196
|
-
- `@root: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
|
-
###
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
1218
|
-
"
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
"
|
|
1273
|
-
"
|
|
1274
|
-
"
|
|
1275
|
-
|
|
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
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
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
|
|
1389
|
-
|
|
1390
|
-
#### Syntax Options
|
|
734
|
+
Enable modular JSON composition by including external JSON files directly into your metadata files:
|
|
1391
735
|
|
|
1392
|
-
**Object Context
|
|
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.
|
|
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
|
|
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"
|
|
771
|
+
"mode": "spread"
|
|
1430
772
|
}
|
|
1431
773
|
}
|
|
1432
774
|
```
|
|
1433
775
|
|
|
1434
|
-
#### Modes
|
|
776
|
+
#### Modes
|
|
1435
777
|
|
|
1436
|
-
**"spread" mode**:
|
|
1437
|
-
-
|
|
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.
|
|
1618
|
-
2.
|
|
1619
|
-
3. Then
|
|
1620
|
-
4. Finally, other
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
812
|
+
"Temperature": 0.9
|
|
1651
813
|
}
|
|
1652
814
|
}
|
|
1653
815
|
```
|
|
1654
816
|
|
|
1655
|
-
|
|
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"
|
|
825
|
+
"Name": "Customer Bot"
|
|
1665
826
|
}
|
|
1666
827
|
}
|
|
1667
828
|
```
|
|
1668
829
|
|
|
1669
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
845
|
+
ELD -..->|inherited| FD1
|
|
846
|
+
FD1 -..->|inherited| FD2
|
|
847
|
+
FD2 -..->|inherited| REC
|
|
1802
848
|
|
|
1803
|
-
|
|
1804
|
-
|
|
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
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
874
|
+
### Enabling Resolution Tracking
|
|
1910
875
|
|
|
1911
|
-
|
|
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
|
|
1916
|
-
"
|
|
1917
|
-
|
|
1918
|
-
"examples",
|
|
1919
|
-
"templates"
|
|
1920
|
-
]
|
|
881
|
+
"version": "1.0",
|
|
882
|
+
"emitSyncNotes": true,
|
|
883
|
+
"directoryOrder": ["..."]
|
|
1921
884
|
}
|
|
1922
885
|
```
|
|
1923
886
|
|
|
1924
|
-
|
|
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
|
-
"
|
|
1964
|
-
"
|
|
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
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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
|
-
|
|
900
|
+
### Example Output
|
|
2035
901
|
|
|
2036
902
|
```json
|
|
2037
903
|
{
|
|
2038
|
-
"
|
|
2039
|
-
"
|
|
2040
|
-
"
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
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
|
-
|
|
939
|
+
### Note Structure
|
|
2075
940
|
|
|
2076
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
953
|
+
All MetadataSync functionality is accessed through the MemberJunction CLI (`mj`) under the `sync` namespace:
|
|
2096
954
|
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
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
|
-
|
|
2127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2138
|
-
|
|
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": "
|
|
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": "
|
|
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
|
-
###
|
|
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
|
-
"
|
|
2221
|
-
"
|
|
2222
|
-
"MaxTokens": 2000,
|
|
2223
|
-
"Temperature": 0.6
|
|
1094
|
+
"push": {
|
|
1095
|
+
"autoCreateMissingRecords": true
|
|
2224
1096
|
}
|
|
2225
1097
|
}
|
|
2226
1098
|
```
|
|
2227
1099
|
|
|
2228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1109
|
+
```json
|
|
1110
|
+
{
|
|
1111
|
+
"push": {
|
|
1112
|
+
"alwaysPush": true
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
```
|
|
2241
1116
|
|
|
2242
|
-
|
|
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
|
-
###
|
|
1119
|
+
### Directory Processing Order
|
|
2249
1120
|
|
|
2250
|
-
|
|
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
|
-
"
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
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
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
2314
|
-
"
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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
|
-
| `
|
|
2331
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
-
|
|
1206
|
+
## Pull Configuration
|
|
2341
1207
|
|
|
2342
|
-
The pull command
|
|
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": "
|
|
1213
|
+
"filePattern": "*.json",
|
|
2348
1214
|
"pull": {
|
|
2349
|
-
"filePattern": "
|
|
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
|
-
|
|
1250
|
+
### Pull Configuration Options
|
|
2397
1251
|
|
|
2398
1252
|
| Option | Type | Default | Description |
|
|
2399
1253
|
|--------|------|---------|-------------|
|
|
2400
|
-
| `filePattern` | string | Entity filePattern | Pattern for finding existing files
|
|
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 |
|
|
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
|
|
2405
|
-
| `preserveFields` | string[] | [] | Fields that retain local values during updates
|
|
2406
|
-
| `mergeStrategy` | string | "merge" | How to merge
|
|
2407
|
-
| `backupBeforeUpdate` | boolean | false | Create timestamped backups before
|
|
2408
|
-
| `backupDirectory` | string | ".backups" | Directory
|
|
2409
|
-
| `filter` | string |
|
|
2410
|
-
| `externalizeFields` | array/object |
|
|
2411
|
-
| `excludeFields` | string[] | [] | Fields to
|
|
2412
|
-
| `lookupFields` | object |
|
|
2413
|
-
| `relatedEntities` | object |
|
|
2414
|
-
| `ignoreNullFields` | boolean | false | Exclude fields with null values
|
|
2415
|
-
| `ignoreVirtualFields` | boolean | false | Exclude virtual fields (view-only fields)
|
|
2416
|
-
|
|
2417
|
-
|
|
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
|
-
|
|
1277
|
+
### Understanding excludeFields vs preserveFields
|
|
2441
1278
|
|
|
2442
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2477
|
-
"
|
|
2478
|
-
"
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
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
|
-
|
|
2527
|
-
|
|
2528
|
-
-
|
|
2529
|
-
-
|
|
2530
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
1323
|
+
"Category": "System",
|
|
2553
1324
|
"UserID": "123",
|
|
2554
|
-
"User": "John Smith"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2639
|
-
|
|
2640
|
-
```
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
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
|
-
|
|
1399
|
+
| Option | Type | Default | Description |
|
|
1400
|
+
|--------|------|---------|-------------|
|
|
1401
|
+
| `recursive` | boolean | false | Enable automatic recursive fetching |
|
|
1402
|
+
| `maxDepth` | number | 10 | Maximum recursion depth |
|
|
2652
1403
|
|
|
2653
|
-
|
|
2654
|
-
1.
|
|
2655
|
-
2.
|
|
2656
|
-
3.
|
|
2657
|
-
4.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2680
|
-
|
|
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
|
-
|
|
1428
|
+
# Maximum parallelism (50 records)
|
|
1429
|
+
mj sync push --parallel-batch-size=50
|
|
2688
1430
|
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
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
|
|
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
|
-
|
|
1455
|
+
COLLECT --> RESULT{Valid?}
|
|
1456
|
+
RESULT -->|Yes| PASS["Validation Passed"]
|
|
1457
|
+
RESULT -->|No| FAIL["Validation Failed\n(Errors Listed)"]
|
|
2704
1458
|
|
|
2705
|
-
|
|
2706
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
2757
|
-
-
|
|
2758
|
-
-
|
|
2759
|
-
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
-
|
|
2763
|
-
-
|
|
2764
|
-
-
|
|
2765
|
-
-
|
|
2766
|
-
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
-
|
|
2778
|
-
-
|
|
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
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
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'.
|
|
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` | A
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1578
|
+
The MetadataSync package exports several service classes for programmatic use:
|
|
2976
1579
|
|
|
2977
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2986
|
-
const
|
|
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
|
-
//
|
|
1617
|
+
|
|
1618
|
+
// Or get JSON output
|
|
3013
1619
|
const jsonOutput = formatter.formatValidationResultAsJson(result);
|
|
3014
|
-
|
|
3015
|
-
//
|
|
1620
|
+
|
|
1621
|
+
// Or get markdown report
|
|
3016
1622
|
const markdownReport = formatter.formatValidationResultAsMarkdown(result);
|
|
3017
1623
|
}
|
|
3018
1624
|
```
|
|
3019
1625
|
|
|
3020
|
-
###
|
|
3021
|
-
|
|
3022
|
-
The validation service returns a structured object with complete details:
|
|
1626
|
+
### Using PushService
|
|
3023
1627
|
|
|
3024
1628
|
```typescript
|
|
3025
|
-
|
|
3026
|
-
|
|
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
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
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
|
-
|
|
1636
|
+
// Create services
|
|
1637
|
+
const syncEngine = new SyncEngine(systemUser);
|
|
1638
|
+
const pushService = new PushService(syncEngine, systemUser);
|
|
3051
1639
|
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1730
|
+
### Common Required Fields Pattern
|
|
3105
1731
|
|
|
3106
|
-
|
|
3107
|
-
-
|
|
3108
|
-
-
|
|
3109
|
-
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
-
|
|
3113
|
-
-
|
|
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
|
|
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
|
-
|
|
1840
|
+
## Contributing
|
|
1841
|
+
|
|
1842
|
+
See the [MemberJunction Contributing Guide](../../CONTRIBUTING.md) for development setup and guidelines.
|