@memberjunction/metadata-sync 2.46.0 → 2.47.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 +124 -13
- package/dist/commands/pull/index.d.ts +135 -0
- package/dist/commands/pull/index.js +187 -18
- package/dist/commands/pull/index.js.map +1 -1
- package/dist/commands/push/index.d.ts +1 -0
- package/dist/commands/push/index.js +75 -33
- package/dist/commands/push/index.js.map +1 -1
- package/dist/commands/status/index.js +35 -2
- package/dist/commands/status/index.js.map +1 -1
- package/dist/commands/watch/index.js +1 -1
- package/dist/commands/watch/index.js.map +1 -1
- package/dist/config.d.ts +141 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -1
- package/dist/hooks/init.js +6 -1
- package/dist/hooks/init.js.map +1 -1
- package/dist/lib/provider-utils.d.ts +76 -4
- package/dist/lib/provider-utils.js +93 -28
- package/dist/lib/provider-utils.js.map +1 -1
- package/dist/lib/sync-engine.d.ts +239 -5
- package/dist/lib/sync-engine.js +314 -5
- package/dist/lib/sync-engine.js.map +1 -1
- package/oclif.manifest.json +8 -1
- package/package.json +6 -6
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Database provider utilities for MetadataSync
|
|
4
|
+
* @module provider-utils
|
|
5
|
+
*
|
|
6
|
+
* This module provides utilities for initializing and managing the database
|
|
7
|
+
* connection, accessing system users, and finding entity directories. It handles
|
|
8
|
+
* the TypeORM DataSource lifecycle and MemberJunction provider initialization.
|
|
9
|
+
*/
|
|
2
10
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
11
|
if (k2 === undefined) k2 = k;
|
|
4
12
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -28,12 +36,26 @@ const typeorm_1 = require("typeorm");
|
|
|
28
36
|
const sqlserver_dataprovider_1 = require("@memberjunction/sqlserver-dataprovider");
|
|
29
37
|
const fs = __importStar(require("fs"));
|
|
30
38
|
const path = __importStar(require("path"));
|
|
39
|
+
/** Global DataSource instance for connection lifecycle management */
|
|
40
|
+
let globalDataSource = null;
|
|
31
41
|
/**
|
|
32
42
|
* Initialize a SQLServerDataProvider with the given configuration
|
|
33
|
-
*
|
|
34
|
-
*
|
|
43
|
+
*
|
|
44
|
+
* Creates and initializes a TypeORM DataSource for SQL Server, then sets up
|
|
45
|
+
* the MemberJunction SQLServerDataProvider. The connection is stored globally
|
|
46
|
+
* for proper cleanup. Auto-detects Azure SQL databases for encryption settings.
|
|
47
|
+
*
|
|
48
|
+
* @param config - MemberJunction configuration with database connection details
|
|
49
|
+
* @returns Promise resolving to initialized SQLServerDataProvider instance
|
|
50
|
+
* @throws Error if database connection fails
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const config = loadMJConfig();
|
|
55
|
+
* const provider = await initializeProvider(config);
|
|
56
|
+
* // Provider is ready for use
|
|
57
|
+
* ```
|
|
35
58
|
*/
|
|
36
|
-
let globalDataSource = null;
|
|
37
59
|
async function initializeProvider(config) {
|
|
38
60
|
// Create TypeORM DataSource
|
|
39
61
|
const dataSource = new typeorm_1.DataSource({
|
|
@@ -46,7 +68,8 @@ async function initializeProvider(config) {
|
|
|
46
68
|
synchronize: false,
|
|
47
69
|
logging: false,
|
|
48
70
|
options: {
|
|
49
|
-
encrypt: config.
|
|
71
|
+
encrypt: config.dbEncrypt === 'Y' || config.dbEncrypt === 'true' ||
|
|
72
|
+
config.dbHost.includes('.database.windows.net'), // Auto-detect Azure SQL
|
|
50
73
|
trustServerCertificate: config.dbTrustServerCertificate === 'Y',
|
|
51
74
|
instanceName: config.dbInstanceName
|
|
52
75
|
}
|
|
@@ -62,6 +85,23 @@ async function initializeProvider(config) {
|
|
|
62
85
|
return await (0, sqlserver_dataprovider_1.setupSQLServerClient)(providerConfig);
|
|
63
86
|
}
|
|
64
87
|
exports.initializeProvider = initializeProvider;
|
|
88
|
+
/**
|
|
89
|
+
* Clean up the global database connection
|
|
90
|
+
*
|
|
91
|
+
* Destroys the TypeORM DataSource if it exists and is initialized.
|
|
92
|
+
* Should be called when the CLI command completes to ensure proper cleanup.
|
|
93
|
+
*
|
|
94
|
+
* @returns Promise that resolves when cleanup is complete
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* try {
|
|
99
|
+
* // Do work with database
|
|
100
|
+
* } finally {
|
|
101
|
+
* await cleanupProvider();
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
65
105
|
async function cleanupProvider() {
|
|
66
106
|
if (globalDataSource && globalDataSource.isInitialized) {
|
|
67
107
|
await globalDataSource.destroy();
|
|
@@ -69,6 +109,21 @@ async function cleanupProvider() {
|
|
|
69
109
|
}
|
|
70
110
|
}
|
|
71
111
|
exports.cleanupProvider = cleanupProvider;
|
|
112
|
+
/**
|
|
113
|
+
* Get the system user from the UserCache
|
|
114
|
+
*
|
|
115
|
+
* Retrieves the "System" user from MemberJunction's UserCache. This user is
|
|
116
|
+
* typically used for CLI operations where no specific user context exists.
|
|
117
|
+
*
|
|
118
|
+
* @returns The System UserInfo object
|
|
119
|
+
* @throws Error if System user is not found in the cache
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const systemUser = getSystemUser();
|
|
124
|
+
* const syncEngine = new SyncEngine(systemUser);
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
72
127
|
function getSystemUser() {
|
|
73
128
|
const sysUser = sqlserver_dataprovider_1.UserCache.Instance.UserByName("System", false);
|
|
74
129
|
if (!sysUser) {
|
|
@@ -78,38 +133,48 @@ function getSystemUser() {
|
|
|
78
133
|
}
|
|
79
134
|
exports.getSystemUser = getSystemUser;
|
|
80
135
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
136
|
+
* Find entity directories at the immediate level only
|
|
137
|
+
*
|
|
138
|
+
* Searches for directories containing .mj-sync.json files, which indicate
|
|
139
|
+
* entity data directories. Only searches immediate subdirectories, not recursive.
|
|
140
|
+
* If a specific directory is provided, only checks that directory.
|
|
141
|
+
*
|
|
142
|
+
* @param dir - Base directory to search from
|
|
143
|
+
* @param specificDir - Optional specific subdirectory name to check
|
|
144
|
+
* @returns Array of absolute directory paths containing .mj-sync.json files
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* // Find all entity directories
|
|
149
|
+
* const dirs = findEntityDirectories(process.cwd());
|
|
150
|
+
*
|
|
151
|
+
* // Check specific directory
|
|
152
|
+
* const dirs = findEntityDirectories(process.cwd(), 'ai-prompts');
|
|
153
|
+
* ```
|
|
85
154
|
*/
|
|
86
155
|
function findEntityDirectories(dir, specificDir) {
|
|
87
156
|
const results = [];
|
|
88
|
-
|
|
89
|
-
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
90
|
-
// Check if this directory has .mj-sync.json (and it's not the root)
|
|
91
|
-
const hasSyncConfig = entries.some(e => e.name === '.mj-sync.json');
|
|
92
|
-
const isRoot = currentDir === dir;
|
|
93
|
-
if (hasSyncConfig && !isRoot) {
|
|
94
|
-
results.push(currentDir);
|
|
95
|
-
}
|
|
96
|
-
// Recursively search subdirectories
|
|
97
|
-
for (const entry of entries) {
|
|
98
|
-
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
99
|
-
search(path.join(currentDir, entry.name));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// If specific directory is provided, only search within it
|
|
157
|
+
// If specific directory is provided, check if it's an entity directory
|
|
104
158
|
if (specificDir) {
|
|
105
|
-
// Handle both absolute and relative paths
|
|
106
159
|
const targetDir = path.isAbsolute(specificDir) ? specificDir : path.join(dir, specificDir);
|
|
107
160
|
if (fs.existsSync(targetDir)) {
|
|
108
|
-
|
|
161
|
+
const hasSyncConfig = fs.existsSync(path.join(targetDir, '.mj-sync.json'));
|
|
162
|
+
if (hasSyncConfig) {
|
|
163
|
+
results.push(targetDir);
|
|
164
|
+
}
|
|
109
165
|
}
|
|
166
|
+
return results;
|
|
110
167
|
}
|
|
111
|
-
|
|
112
|
-
|
|
168
|
+
// Otherwise, find all immediate subdirectories with .mj-sync.json
|
|
169
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
172
|
+
const subDir = path.join(dir, entry.name);
|
|
173
|
+
const hasSyncConfig = fs.existsSync(path.join(subDir, '.mj-sync.json'));
|
|
174
|
+
if (hasSyncConfig) {
|
|
175
|
+
results.push(subDir);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
113
178
|
}
|
|
114
179
|
return results;
|
|
115
180
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-utils.js","sourceRoot":"","sources":["../../src/lib/provider-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"provider-utils.js","sourceRoot":"","sources":["../../src/lib/provider-utils.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,qCAAqC;AACrC,mFAA6I;AAE7I,uCAAyB;AACzB,2CAA6B;AAG7B,qEAAqE;AACrE,IAAI,gBAAgB,GAAsB,IAAI,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACI,KAAK,UAAU,kBAAkB,CAAC,MAAgB;IACvD,4BAA4B;IAC5B,MAAM,UAAU,GAAG,IAAI,oBAAU,CAAC;QAChC,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,MAAM,CAAC,MAAM;QACnB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI;QAClD,QAAQ,EAAE,MAAM,CAAC,UAAU;QAC3B,QAAQ,EAAE,MAAM,CAAC,UAAU;QAC3B,QAAQ,EAAE,MAAM,CAAC,UAAU;QAC3B,WAAW,EAAE,KAAK;QAClB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE;YACP,OAAO,EAAE,MAAM,CAAC,SAAS,KAAK,GAAG,IAAI,MAAM,CAAC,SAAS,KAAK,MAAM;gBACvD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,wBAAwB;YAClF,sBAAsB,EAAE,MAAM,CAAC,wBAAwB,KAAK,GAAG;YAC/D,YAAY,EAAE,MAAM,CAAC,cAAc;SACpC;KACF,CAAC,CAAC;IAEH,6BAA6B;IAC7B,MAAM,UAAU,CAAC,UAAU,EAAE,CAAC;IAE9B,oBAAoB;IACpB,gBAAgB,GAAG,UAAU,CAAC;IAE9B,yBAAyB;IACzB,MAAM,cAAc,GAAG,IAAI,oDAA2B,CACpD,UAAU,EACV,iBAAiB,EAAE,uBAAuB;IAC1C,MAAM,CAAC,YAAY,IAAI,MAAM,EAC7B,CAAC,CACF,CAAC;IAEF,kDAAkD;IAClD,OAAO,MAAM,IAAA,6CAAoB,EAAC,cAAc,CAAC,CAAC;AACpD,CAAC;AAnCD,gDAmCC;AAED;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,UAAU,eAAe;IACnC,IAAI,gBAAgB,IAAI,gBAAgB,CAAC,aAAa,EAAE,CAAC;QACvD,MAAM,gBAAgB,CAAC,OAAO,EAAE,CAAC;QACjC,gBAAgB,GAAG,IAAI,CAAC;IAC1B,CAAC;AACH,CAAC;AALD,0CAKC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,aAAa;IAC3B,MAAM,OAAO,GAAG,kCAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,gFAAgF,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAND,sCAMC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,qBAAqB,CAAC,GAAW,EAAE,WAAoB;IACrE,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,uEAAuE;IACvE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAC3F,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,MAAM,aAAa,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC;YAC3E,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,kEAAkE;IAClE,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC1C,MAAM,aAAa,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;YAExE,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AA9BD,sDA8BC","sourcesContent":["/**\n * @fileoverview Database provider utilities for MetadataSync\n * @module provider-utils\n * \n * This module provides utilities for initializing and managing the database\n * connection, accessing system users, and finding entity directories. It handles\n * the TypeORM DataSource lifecycle and MemberJunction provider initialization.\n */\n\nimport { DataSource } from 'typeorm';\nimport { SQLServerDataProvider, SQLServerProviderConfigData, UserCache, setupSQLServerClient } from '@memberjunction/sqlserver-dataprovider';\nimport type { MJConfig } from '../config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { UserInfo } from '@memberjunction/core';\n\n/** Global DataSource instance for connection lifecycle management */\nlet globalDataSource: DataSource | null = null;\n\n/**\n * Initialize a SQLServerDataProvider with the given configuration\n * \n * Creates and initializes a TypeORM DataSource for SQL Server, then sets up\n * the MemberJunction SQLServerDataProvider. The connection is stored globally\n * for proper cleanup. Auto-detects Azure SQL databases for encryption settings.\n * \n * @param config - MemberJunction configuration with database connection details\n * @returns Promise resolving to initialized SQLServerDataProvider instance\n * @throws Error if database connection fails\n * \n * @example\n * ```typescript\n * const config = loadMJConfig();\n * const provider = await initializeProvider(config);\n * // Provider is ready for use\n * ```\n */\nexport async function initializeProvider(config: MJConfig): Promise<SQLServerDataProvider> {\n // Create TypeORM DataSource\n const dataSource = new DataSource({\n type: 'mssql',\n host: config.dbHost,\n port: config.dbPort ? Number(config.dbPort) : 1433,\n database: config.dbDatabase,\n username: config.dbUsername,\n password: config.dbPassword,\n synchronize: false,\n logging: false,\n options: {\n encrypt: config.dbEncrypt === 'Y' || config.dbEncrypt === 'true' || \n config.dbHost.includes('.database.windows.net'), // Auto-detect Azure SQL\n trustServerCertificate: config.dbTrustServerCertificate === 'Y',\n instanceName: config.dbInstanceName\n }\n });\n \n // Initialize the data source\n await dataSource.initialize();\n \n // Store for cleanup\n globalDataSource = dataSource;\n \n // Create provider config\n const providerConfig = new SQLServerProviderConfigData(\n dataSource,\n 'system@sync.cli', // Default user for CLI\n config.mjCoreSchema || '__mj',\n 0\n );\n \n // Use setupSQLServerClient to properly initialize\n return await setupSQLServerClient(providerConfig);\n}\n\n/**\n * Clean up the global database connection\n * \n * Destroys the TypeORM DataSource if it exists and is initialized.\n * Should be called when the CLI command completes to ensure proper cleanup.\n * \n * @returns Promise that resolves when cleanup is complete\n * \n * @example\n * ```typescript\n * try {\n * // Do work with database\n * } finally {\n * await cleanupProvider();\n * }\n * ```\n */\nexport async function cleanupProvider(): Promise<void> {\n if (globalDataSource && globalDataSource.isInitialized) {\n await globalDataSource.destroy();\n globalDataSource = null;\n }\n}\n\n/**\n * Get the system user from the UserCache\n * \n * Retrieves the \"System\" user from MemberJunction's UserCache. This user is\n * typically used for CLI operations where no specific user context exists.\n * \n * @returns The System UserInfo object\n * @throws Error if System user is not found in the cache\n * \n * @example\n * ```typescript\n * const systemUser = getSystemUser();\n * const syncEngine = new SyncEngine(systemUser);\n * ```\n */\nexport function getSystemUser(): UserInfo {\n const sysUser = UserCache.Instance.UserByName(\"System\", false);\n if (!sysUser) {\n throw new Error(\"System user not found in cache. Ensure the system user exists in the database.\"); \n }\n return sysUser;\n}\n\n/**\n * Find entity directories at the immediate level only\n * \n * Searches for directories containing .mj-sync.json files, which indicate\n * entity data directories. Only searches immediate subdirectories, not recursive.\n * If a specific directory is provided, only checks that directory.\n * \n * @param dir - Base directory to search from\n * @param specificDir - Optional specific subdirectory name to check\n * @returns Array of absolute directory paths containing .mj-sync.json files\n * \n * @example\n * ```typescript\n * // Find all entity directories\n * const dirs = findEntityDirectories(process.cwd());\n * \n * // Check specific directory\n * const dirs = findEntityDirectories(process.cwd(), 'ai-prompts');\n * ```\n */\nexport function findEntityDirectories(dir: string, specificDir?: string): string[] {\n const results: string[] = [];\n \n // If specific directory is provided, check if it's an entity directory\n if (specificDir) {\n const targetDir = path.isAbsolute(specificDir) ? specificDir : path.join(dir, specificDir);\n if (fs.existsSync(targetDir)) {\n const hasSyncConfig = fs.existsSync(path.join(targetDir, '.mj-sync.json'));\n if (hasSyncConfig) {\n results.push(targetDir);\n }\n }\n return results;\n }\n \n // Otherwise, find all immediate subdirectories with .mj-sync.json\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith('.')) {\n const subDir = path.join(dir, entry.name);\n const hasSyncConfig = fs.existsSync(path.join(subDir, '.mj-sync.json'));\n \n if (hasSyncConfig) {\n results.push(subDir);\n }\n }\n }\n \n return results;\n}"]}
|
|
@@ -1,49 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core synchronization engine for MemberJunction metadata
|
|
3
|
+
* @module sync-engine
|
|
4
|
+
*
|
|
5
|
+
* This module provides the core functionality for synchronizing metadata between
|
|
6
|
+
* the MemberJunction database and local file system representations. It handles
|
|
7
|
+
* special reference types (@file, @url, @lookup, @env, @parent, @root, @template),
|
|
8
|
+
* manages entity operations, and provides utilities for data transformation.
|
|
9
|
+
*/
|
|
1
10
|
import { EntityInfo, BaseEntity, UserInfo } from '@memberjunction/core';
|
|
2
11
|
import { EntityConfig } from '../config';
|
|
12
|
+
/**
|
|
13
|
+
* Represents the structure of a metadata record with optional sync tracking
|
|
14
|
+
*/
|
|
3
15
|
export interface RecordData {
|
|
16
|
+
/** Primary key field(s) and their values */
|
|
4
17
|
primaryKey?: Record<string, any>;
|
|
18
|
+
/** Entity field names and their values */
|
|
5
19
|
fields: Record<string, any>;
|
|
20
|
+
/** Related entities organized by entity name */
|
|
6
21
|
relatedEntities?: Record<string, RecordData[]>;
|
|
22
|
+
/** Synchronization metadata for change tracking */
|
|
7
23
|
sync?: {
|
|
24
|
+
/** ISO timestamp of last modification */
|
|
8
25
|
lastModified: string;
|
|
26
|
+
/** SHA256 checksum of the fields object */
|
|
9
27
|
checksum: string;
|
|
10
28
|
};
|
|
11
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Core engine for synchronizing MemberJunction metadata between database and files
|
|
32
|
+
*
|
|
33
|
+
* @class SyncEngine
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const syncEngine = new SyncEngine(systemUser);
|
|
37
|
+
* await syncEngine.initialize();
|
|
38
|
+
*
|
|
39
|
+
* // Process a field value with special references
|
|
40
|
+
* const value = await syncEngine.processFieldValue('@lookup:Users.Email=admin@example.com', '/path/to/base');
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
12
43
|
export declare class SyncEngine {
|
|
13
44
|
private metadata;
|
|
14
45
|
private contextUser;
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new SyncEngine instance
|
|
48
|
+
* @param contextUser - The user context for database operations
|
|
49
|
+
*/
|
|
15
50
|
constructor(contextUser: UserInfo);
|
|
51
|
+
/**
|
|
52
|
+
* Initializes the sync engine by refreshing metadata cache
|
|
53
|
+
* @returns Promise that resolves when initialization is complete
|
|
54
|
+
*/
|
|
16
55
|
initialize(): Promise<void>;
|
|
17
56
|
/**
|
|
18
57
|
* Process special references in field values
|
|
58
|
+
*
|
|
59
|
+
* Handles the following reference types:
|
|
60
|
+
* - `@parent:fieldName` - References a field from the parent record
|
|
61
|
+
* - `@root:fieldName` - References a field from the root record
|
|
62
|
+
* - `@file:path` - Reads content from an external file
|
|
63
|
+
* - `@url:address` - Fetches content from a URL
|
|
64
|
+
* - `@lookup:Entity.Field=Value` - Looks up an entity ID by field value
|
|
65
|
+
* - `@env:VARIABLE` - Reads an environment variable
|
|
66
|
+
*
|
|
67
|
+
* @param value - The field value to process
|
|
68
|
+
* @param baseDir - Base directory for resolving relative file paths
|
|
69
|
+
* @param parentRecord - Optional parent entity for @parent references
|
|
70
|
+
* @param rootRecord - Optional root entity for @root references
|
|
71
|
+
* @returns The processed value with all references resolved
|
|
72
|
+
* @throws Error if a reference cannot be resolved
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* // File reference
|
|
77
|
+
* const content = await processFieldValue('@file:template.md', '/path/to/dir');
|
|
78
|
+
*
|
|
79
|
+
* // Lookup with auto-create
|
|
80
|
+
* const userId = await processFieldValue('@lookup:Users.Email=john@example.com?create', '/path');
|
|
81
|
+
* ```
|
|
19
82
|
*/
|
|
20
83
|
processFieldValue(value: any, baseDir: string, parentRecord?: BaseEntity | null, rootRecord?: BaseEntity | null): Promise<any>;
|
|
21
84
|
/**
|
|
22
85
|
* Resolve a lookup reference to an ID, optionally creating the record if it doesn't exist
|
|
86
|
+
*
|
|
87
|
+
* @param entityName - Name of the entity to search in
|
|
88
|
+
* @param fieldName - Field to match against
|
|
89
|
+
* @param fieldValue - Value to search for
|
|
90
|
+
* @param autoCreate - Whether to create the record if not found
|
|
91
|
+
* @param createFields - Additional fields to set when creating
|
|
92
|
+
* @returns The ID of the found or created record
|
|
93
|
+
* @throws Error if lookup fails and autoCreate is false
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* // Simple lookup
|
|
98
|
+
* const categoryId = await resolveLookup('Categories', 'Name', 'Technology');
|
|
99
|
+
*
|
|
100
|
+
* // Lookup with auto-create
|
|
101
|
+
* const tagId = await resolveLookup('Tags', 'Name', 'New Tag', true, {
|
|
102
|
+
* Description: 'Auto-created tag',
|
|
103
|
+
* Status: 'Active'
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
23
106
|
*/
|
|
24
107
|
resolveLookup(entityName: string, fieldName: string, fieldValue: string, autoCreate?: boolean, createFields?: Record<string, any>): Promise<string>;
|
|
25
108
|
/**
|
|
26
109
|
* Build cascading defaults for a file path and process field values
|
|
110
|
+
*
|
|
111
|
+
* Walks up the directory tree from the file location, collecting defaults from
|
|
112
|
+
* entity config and folder configs, with deeper folders overriding parent values.
|
|
113
|
+
* All default values are processed for special references.
|
|
114
|
+
*
|
|
115
|
+
* @param filePath - Path to the file being processed
|
|
116
|
+
* @param entityConfig - Entity configuration containing base defaults
|
|
117
|
+
* @returns Processed defaults with all references resolved
|
|
118
|
+
* @throws Error if any default value processing fails
|
|
27
119
|
*/
|
|
28
120
|
buildDefaults(filePath: string, entityConfig: EntityConfig): Promise<Record<string, any>>;
|
|
29
121
|
/**
|
|
30
|
-
* Load folder configuration
|
|
122
|
+
* Load folder configuration from .mj-folder.json file
|
|
123
|
+
*
|
|
124
|
+
* @param dir - Directory to check for configuration
|
|
125
|
+
* @returns Folder configuration or null if not found/invalid
|
|
126
|
+
* @private
|
|
31
127
|
*/
|
|
32
128
|
private loadFolderConfig;
|
|
33
129
|
/**
|
|
34
|
-
* Calculate checksum for data
|
|
130
|
+
* Calculate SHA256 checksum for data
|
|
131
|
+
*
|
|
132
|
+
* Generates a deterministic hash of the provided data by converting it to
|
|
133
|
+
* formatted JSON and calculating a SHA256 digest. Used for change detection
|
|
134
|
+
* in sync operations.
|
|
135
|
+
*
|
|
136
|
+
* @param data - Any data structure to calculate checksum for
|
|
137
|
+
* @returns Hexadecimal string representation of the SHA256 hash
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const checksum = syncEngine.calculateChecksum({
|
|
142
|
+
* name: 'Test Record',
|
|
143
|
+
* value: 42,
|
|
144
|
+
* tags: ['a', 'b']
|
|
145
|
+
* });
|
|
146
|
+
* // Returns consistent hash for same data structure
|
|
147
|
+
* ```
|
|
35
148
|
*/
|
|
36
149
|
calculateChecksum(data: any): string;
|
|
37
150
|
/**
|
|
38
|
-
* Get entity
|
|
151
|
+
* Get entity metadata information by name
|
|
152
|
+
*
|
|
153
|
+
* Retrieves the EntityInfo object containing schema metadata for the specified entity.
|
|
154
|
+
* Returns null if the entity is not found in the metadata cache.
|
|
155
|
+
*
|
|
156
|
+
* @param entityName - Name of the entity to look up
|
|
157
|
+
* @returns EntityInfo object with schema details or null if not found
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const entityInfo = syncEngine.getEntityInfo('AI Prompts');
|
|
162
|
+
* if (entityInfo) {
|
|
163
|
+
* console.log(`Primary keys: ${entityInfo.PrimaryKeys.map(pk => pk.Name).join(', ')}`);
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
39
166
|
*/
|
|
40
167
|
getEntityInfo(entityName: string): EntityInfo | null;
|
|
41
168
|
/**
|
|
42
|
-
* Create a new entity object
|
|
169
|
+
* Create a new entity object instance
|
|
170
|
+
*
|
|
171
|
+
* Uses the MemberJunction metadata system to properly instantiate an entity object.
|
|
172
|
+
* This ensures correct class registration and respects any custom entity subclasses.
|
|
173
|
+
*
|
|
174
|
+
* @param entityName - Name of the entity to create
|
|
175
|
+
* @returns Promise resolving to the new BaseEntity instance
|
|
176
|
+
* @throws Error if entity creation fails
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* const entity = await syncEngine.createEntityObject('AI Prompts');
|
|
181
|
+
* entity.NewRecord();
|
|
182
|
+
* entity.Set('Name', 'My Prompt');
|
|
183
|
+
* await entity.Save();
|
|
184
|
+
* ```
|
|
43
185
|
*/
|
|
44
186
|
createEntityObject(entityName: string): Promise<BaseEntity>;
|
|
45
187
|
/**
|
|
46
|
-
* Load an entity by primary key
|
|
188
|
+
* Load an entity record by primary key
|
|
189
|
+
*
|
|
190
|
+
* Retrieves an existing entity record from the database using its primary key values.
|
|
191
|
+
* Supports both single and composite primary keys. Returns null if the record is not found.
|
|
192
|
+
*
|
|
193
|
+
* @param entityName - Name of the entity to load
|
|
194
|
+
* @param primaryKey - Object containing primary key field names and values
|
|
195
|
+
* @returns Promise resolving to the loaded entity or null if not found
|
|
196
|
+
* @throws Error if entity metadata is not found
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* // Single primary key
|
|
201
|
+
* const entity = await syncEngine.loadEntity('Users', { ID: '123-456' });
|
|
202
|
+
*
|
|
203
|
+
* // Composite primary key
|
|
204
|
+
* const entity = await syncEngine.loadEntity('UserRoles', {
|
|
205
|
+
* UserID: '123-456',
|
|
206
|
+
* RoleID: '789-012'
|
|
207
|
+
* });
|
|
208
|
+
* ```
|
|
47
209
|
*/
|
|
48
210
|
loadEntity(entityName: string, primaryKey: Record<string, any>): Promise<BaseEntity | null>;
|
|
211
|
+
/**
|
|
212
|
+
* Process JSON object with template references
|
|
213
|
+
*
|
|
214
|
+
* Recursively processes JSON data structures to resolve `@template` references.
|
|
215
|
+
* Templates can be defined at any level and support:
|
|
216
|
+
* - Single template references: `"@template:path/to/template.json"`
|
|
217
|
+
* - Object with @template field: `{ "@template": "file.json", "override": "value" }`
|
|
218
|
+
* - Array of templates for merging: `{ "@template": ["base.json", "overrides.json"] }`
|
|
219
|
+
* - Nested template references within templates
|
|
220
|
+
*
|
|
221
|
+
* @param data - JSON data structure to process
|
|
222
|
+
* @param baseDir - Base directory for resolving relative template paths
|
|
223
|
+
* @returns Promise resolving to the processed data with all templates resolved
|
|
224
|
+
* @throws Error if template file is not found or contains invalid JSON
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* // Input data with template reference
|
|
229
|
+
* const data = {
|
|
230
|
+
* "@template": "defaults/ai-prompt.json",
|
|
231
|
+
* "Name": "Custom Prompt",
|
|
232
|
+
* "Prompt": "Override the template prompt"
|
|
233
|
+
* };
|
|
234
|
+
*
|
|
235
|
+
* // Resolves template and merges with overrides
|
|
236
|
+
* const result = await syncEngine.processTemplates(data, '/path/to/dir');
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
processTemplates(data: any, baseDir: string): Promise<any>;
|
|
240
|
+
/**
|
|
241
|
+
* Load and process a template file
|
|
242
|
+
*
|
|
243
|
+
* Loads a JSON template file from the filesystem and recursively processes any
|
|
244
|
+
* nested template references within it. Template paths are resolved relative to
|
|
245
|
+
* the template file's directory, enabling template composition.
|
|
246
|
+
*
|
|
247
|
+
* @param templatePath - Path to the template file (relative or absolute)
|
|
248
|
+
* @param baseDir - Base directory for resolving relative paths
|
|
249
|
+
* @returns Promise resolving to the processed template content
|
|
250
|
+
* @throws Error if template file not found or contains invalid JSON
|
|
251
|
+
* @private
|
|
252
|
+
*/
|
|
253
|
+
private loadAndProcessTemplate;
|
|
254
|
+
/**
|
|
255
|
+
* Deep merge two objects with target taking precedence
|
|
256
|
+
*
|
|
257
|
+
* Recursively merges two objects, with values from the target object overriding
|
|
258
|
+
* values from the source object. Arrays and primitive values are not merged but
|
|
259
|
+
* replaced entirely by the target value. Undefined values in target are skipped.
|
|
260
|
+
*
|
|
261
|
+
* @param source - Base object to merge from
|
|
262
|
+
* @param target - Object with values that override source
|
|
263
|
+
* @returns New object with merged values
|
|
264
|
+
* @private
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* const source = {
|
|
269
|
+
* a: 1,
|
|
270
|
+
* b: { x: 10, y: 20 },
|
|
271
|
+
* c: [1, 2, 3]
|
|
272
|
+
* };
|
|
273
|
+
* const target = {
|
|
274
|
+
* a: 2,
|
|
275
|
+
* b: { y: 30, z: 40 },
|
|
276
|
+
* d: 'new'
|
|
277
|
+
* };
|
|
278
|
+
* const result = deepMerge(source, target);
|
|
279
|
+
* // Result: { a: 2, b: { x: 10, y: 30, z: 40 }, c: [1, 2, 3], d: 'new' }
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
private deepMerge;
|
|
49
283
|
}
|