@memberjunction/metadata-sync 2.46.0 → 2.48.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.
@@ -1,13 +1,85 @@
1
+ /**
2
+ * @fileoverview Database provider utilities for MetadataSync
3
+ * @module provider-utils
4
+ *
5
+ * This module provides utilities for initializing and managing the database
6
+ * connection, accessing system users, and finding entity directories. It handles
7
+ * the TypeORM DataSource lifecycle and MemberJunction provider initialization.
8
+ */
1
9
  import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
2
10
  import type { MJConfig } from '../config';
3
11
  import { UserInfo } from '@memberjunction/core';
12
+ /**
13
+ * Initialize a SQLServerDataProvider with the given configuration
14
+ *
15
+ * Creates and initializes a TypeORM DataSource for SQL Server, then sets up
16
+ * the MemberJunction SQLServerDataProvider. The connection is stored globally
17
+ * for proper cleanup. Auto-detects Azure SQL databases for encryption settings.
18
+ *
19
+ * @param config - MemberJunction configuration with database connection details
20
+ * @returns Promise resolving to initialized SQLServerDataProvider instance
21
+ * @throws Error if database connection fails
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const config = loadMJConfig();
26
+ * const provider = await initializeProvider(config);
27
+ * // Provider is ready for use
28
+ * ```
29
+ */
4
30
  export declare function initializeProvider(config: MJConfig): Promise<SQLServerDataProvider>;
31
+ /**
32
+ * Clean up the global database connection
33
+ *
34
+ * Destroys the TypeORM DataSource if it exists and is initialized.
35
+ * Should be called when the CLI command completes to ensure proper cleanup.
36
+ *
37
+ * @returns Promise that resolves when cleanup is complete
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * try {
42
+ * // Do work with database
43
+ * } finally {
44
+ * await cleanupProvider();
45
+ * }
46
+ * ```
47
+ */
5
48
  export declare function cleanupProvider(): Promise<void>;
49
+ /**
50
+ * Get the system user from the UserCache
51
+ *
52
+ * Retrieves the "System" user from MemberJunction's UserCache. This user is
53
+ * typically used for CLI operations where no specific user context exists.
54
+ *
55
+ * @returns The System UserInfo object
56
+ * @throws Error if System user is not found in the cache
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const systemUser = getSystemUser();
61
+ * const syncEngine = new SyncEngine(systemUser);
62
+ * ```
63
+ */
6
64
  export declare function getSystemUser(): UserInfo;
7
65
  /**
8
- * Recursively find all entity directories with .mj-sync.json files
9
- * @param dir Directory to search
10
- * @param specificDir Optional specific directory to limit search to
11
- * @returns Array of directory paths containing .mj-sync.json files
66
+ * Find entity directories at the immediate level only
67
+ *
68
+ * Searches for directories containing .mj-sync.json files, which indicate
69
+ * entity data directories. Only searches immediate subdirectories, not recursive.
70
+ * If a specific directory is provided, only checks that directory.
71
+ *
72
+ * @param dir - Base directory to search from
73
+ * @param specificDir - Optional specific subdirectory name to check
74
+ * @returns Array of absolute directory paths containing .mj-sync.json files
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Find all entity directories
79
+ * const dirs = findEntityDirectories(process.cwd());
80
+ *
81
+ * // Check specific directory
82
+ * const dirs = findEntityDirectories(process.cwd(), 'ai-prompts');
83
+ * ```
12
84
  */
13
85
  export declare function findEntityDirectories(dir: string, specificDir?: string): string[];
@@ -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,47 +36,113 @@ 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;
41
+ /** Global provider instance to ensure single initialization */
42
+ let globalProvider = null;
43
+ /** Promise to track ongoing initialization */
44
+ let initializationPromise = null;
31
45
  /**
32
46
  * Initialize a SQLServerDataProvider with the given configuration
33
- * @param config MemberJunction configuration with database connection details
34
- * @returns Initialized SQLServerDataProvider instance
47
+ *
48
+ * Creates and initializes a TypeORM DataSource for SQL Server, then sets up
49
+ * the MemberJunction SQLServerDataProvider. The connection is stored globally
50
+ * for proper cleanup. Auto-detects Azure SQL databases for encryption settings.
51
+ *
52
+ * @param config - MemberJunction configuration with database connection details
53
+ * @returns Promise resolving to initialized SQLServerDataProvider instance
54
+ * @throws Error if database connection fails
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const config = loadMJConfig();
59
+ * const provider = await initializeProvider(config);
60
+ * // Provider is ready for use
61
+ * ```
35
62
  */
36
- let globalDataSource = null;
37
63
  async function initializeProvider(config) {
38
- // Create TypeORM DataSource
39
- const dataSource = new typeorm_1.DataSource({
40
- type: 'mssql',
41
- host: config.dbHost,
42
- port: config.dbPort ? Number(config.dbPort) : 1433,
43
- database: config.dbDatabase,
44
- username: config.dbUsername,
45
- password: config.dbPassword,
46
- synchronize: false,
47
- logging: false,
48
- options: {
49
- encrypt: config.dbTrustServerCertificate !== 'Y' ? false : true,
50
- trustServerCertificate: config.dbTrustServerCertificate === 'Y',
51
- instanceName: config.dbInstanceName
52
- }
53
- });
54
- // Initialize the data source
55
- await dataSource.initialize();
56
- // Store for cleanup
57
- globalDataSource = dataSource;
58
- // Create provider config
59
- const providerConfig = new sqlserver_dataprovider_1.SQLServerProviderConfigData(dataSource, 'system@sync.cli', // Default user for CLI
60
- config.mjCoreSchema || '__mj', 0);
61
- // Use setupSQLServerClient to properly initialize
62
- return await (0, sqlserver_dataprovider_1.setupSQLServerClient)(providerConfig);
64
+ // Return existing provider if already initialized
65
+ if (globalProvider) {
66
+ return globalProvider;
67
+ }
68
+ // Return ongoing initialization if in progress
69
+ if (initializationPromise) {
70
+ return initializationPromise;
71
+ }
72
+ // Start new initialization
73
+ initializationPromise = (async () => {
74
+ // Create TypeORM DataSource
75
+ const dataSource = new typeorm_1.DataSource({
76
+ type: 'mssql',
77
+ host: config.dbHost,
78
+ port: config.dbPort ? Number(config.dbPort) : 1433,
79
+ database: config.dbDatabase,
80
+ username: config.dbUsername,
81
+ password: config.dbPassword,
82
+ synchronize: false,
83
+ logging: false,
84
+ options: {
85
+ encrypt: config.dbEncrypt === 'Y' || config.dbEncrypt === 'true' ||
86
+ config.dbHost.includes('.database.windows.net'), // Auto-detect Azure SQL
87
+ trustServerCertificate: config.dbTrustServerCertificate === 'Y',
88
+ instanceName: config.dbInstanceName
89
+ }
90
+ });
91
+ // Initialize the data source
92
+ await dataSource.initialize();
93
+ // Store for cleanup
94
+ globalDataSource = dataSource;
95
+ // Create provider config
96
+ const providerConfig = new sqlserver_dataprovider_1.SQLServerProviderConfigData(dataSource, 'system@sync.cli', // Default user for CLI
97
+ config.mjCoreSchema || '__mj', 0);
98
+ // Use setupSQLServerClient to properly initialize
99
+ globalProvider = await (0, sqlserver_dataprovider_1.setupSQLServerClient)(providerConfig);
100
+ return globalProvider;
101
+ })();
102
+ return initializationPromise;
63
103
  }
64
104
  exports.initializeProvider = initializeProvider;
105
+ /**
106
+ * Clean up the global database connection
107
+ *
108
+ * Destroys the TypeORM DataSource if it exists and is initialized.
109
+ * Should be called when the CLI command completes to ensure proper cleanup.
110
+ *
111
+ * @returns Promise that resolves when cleanup is complete
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * try {
116
+ * // Do work with database
117
+ * } finally {
118
+ * await cleanupProvider();
119
+ * }
120
+ * ```
121
+ */
65
122
  async function cleanupProvider() {
66
123
  if (globalDataSource && globalDataSource.isInitialized) {
67
124
  await globalDataSource.destroy();
68
125
  globalDataSource = null;
69
126
  }
127
+ globalProvider = null;
128
+ initializationPromise = null;
70
129
  }
71
130
  exports.cleanupProvider = cleanupProvider;
131
+ /**
132
+ * Get the system user from the UserCache
133
+ *
134
+ * Retrieves the "System" user from MemberJunction's UserCache. This user is
135
+ * typically used for CLI operations where no specific user context exists.
136
+ *
137
+ * @returns The System UserInfo object
138
+ * @throws Error if System user is not found in the cache
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const systemUser = getSystemUser();
143
+ * const syncEngine = new SyncEngine(systemUser);
144
+ * ```
145
+ */
72
146
  function getSystemUser() {
73
147
  const sysUser = sqlserver_dataprovider_1.UserCache.Instance.UserByName("System", false);
74
148
  if (!sysUser) {
@@ -78,38 +152,48 @@ function getSystemUser() {
78
152
  }
79
153
  exports.getSystemUser = getSystemUser;
80
154
  /**
81
- * Recursively find all entity directories with .mj-sync.json files
82
- * @param dir Directory to search
83
- * @param specificDir Optional specific directory to limit search to
84
- * @returns Array of directory paths containing .mj-sync.json files
155
+ * Find entity directories at the immediate level only
156
+ *
157
+ * Searches for directories containing .mj-sync.json files, which indicate
158
+ * entity data directories. Only searches immediate subdirectories, not recursive.
159
+ * If a specific directory is provided, only checks that directory.
160
+ *
161
+ * @param dir - Base directory to search from
162
+ * @param specificDir - Optional specific subdirectory name to check
163
+ * @returns Array of absolute directory paths containing .mj-sync.json files
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * // Find all entity directories
168
+ * const dirs = findEntityDirectories(process.cwd());
169
+ *
170
+ * // Check specific directory
171
+ * const dirs = findEntityDirectories(process.cwd(), 'ai-prompts');
172
+ * ```
85
173
  */
86
174
  function findEntityDirectories(dir, specificDir) {
87
175
  const results = [];
88
- function search(currentDir) {
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
176
+ // If specific directory is provided, check if it's an entity directory
104
177
  if (specificDir) {
105
- // Handle both absolute and relative paths
106
178
  const targetDir = path.isAbsolute(specificDir) ? specificDir : path.join(dir, specificDir);
107
179
  if (fs.existsSync(targetDir)) {
108
- search(targetDir);
180
+ const hasSyncConfig = fs.existsSync(path.join(targetDir, '.mj-sync.json'));
181
+ if (hasSyncConfig) {
182
+ results.push(targetDir);
183
+ }
109
184
  }
185
+ return results;
110
186
  }
111
- else {
112
- search(dir);
187
+ // Otherwise, find all immediate subdirectories with .mj-sync.json
188
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
189
+ for (const entry of entries) {
190
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
191
+ const subDir = path.join(dir, entry.name);
192
+ const hasSyncConfig = fs.existsSync(path.join(subDir, '.mj-sync.json'));
193
+ if (hasSyncConfig) {
194
+ results.push(subDir);
195
+ }
196
+ }
113
197
  }
114
198
  return results;
115
199
  }
@@ -1 +1 @@
1
- {"version":3,"file":"provider-utils.js","sourceRoot":"","sources":["../../src/lib/provider-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { 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/**\n * Initialize a SQLServerDataProvider with the given configuration\n * @param config MemberJunction configuration with database connection details\n * @returns Initialized SQLServerDataProvider instance\n */\nlet globalDataSource: DataSource | null = null;\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.dbTrustServerCertificate !== 'Y' ? false : true,\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\nexport async function cleanupProvider(): Promise<void> {\n if (globalDataSource && globalDataSource.isInitialized) {\n await globalDataSource.destroy();\n globalDataSource = null;\n }\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 * Recursively find all entity directories with .mj-sync.json files\n * @param dir Directory to search\n * @param specificDir Optional specific directory to limit search to\n * @returns Array of directory paths containing .mj-sync.json files\n */\nexport function findEntityDirectories(dir: string, specificDir?: string): string[] {\n const results: string[] = [];\n \n function search(currentDir: string) {\n const entries = fs.readdirSync(currentDir, { withFileTypes: true });\n \n // Check if this directory has .mj-sync.json (and it's not the root)\n const hasSyncConfig = entries.some(e => e.name === '.mj-sync.json');\n const isRoot = currentDir === dir;\n \n if (hasSyncConfig && !isRoot) {\n results.push(currentDir);\n }\n \n // Recursively search subdirectories\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith('.')) {\n search(path.join(currentDir, entry.name));\n }\n }\n }\n \n // If specific directory is provided, only search within it\n if (specificDir) {\n // Handle both absolute and relative paths\n const targetDir = path.isAbsolute(specificDir) ? specificDir : path.join(dir, specificDir);\n if (fs.existsSync(targetDir)) {\n search(targetDir);\n }\n } else {\n search(dir);\n }\n \n return results;\n}"]}
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;AAI7B,qEAAqE;AACrE,IAAI,gBAAgB,GAAsB,IAAI,CAAC;AAE/C,+DAA+D;AAC/D,IAAI,cAAc,GAAiC,IAAI,CAAC;AAExD,8CAA8C;AAC9C,IAAI,qBAAqB,GAA0C,IAAI,CAAC;AAExE;;;;;;;;;;;;;;;;;GAiBG;AACI,KAAK,UAAU,kBAAkB,CAAC,MAAgB;IACvD,kDAAkD;IAClD,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,+CAA+C;IAC/C,IAAI,qBAAqB,EAAE,CAAC;QAC1B,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,2BAA2B;IAC3B,qBAAqB,GAAG,CAAC,KAAK,IAAI,EAAE;QAClC,4BAA4B;QAC5B,MAAM,UAAU,GAAG,IAAI,oBAAU,CAAC;YAChC,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,MAAM,CAAC,MAAM;YACnB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI;YAClD,QAAQ,EAAE,MAAM,CAAC,UAAU;YAC3B,QAAQ,EAAE,MAAM,CAAC,UAAU;YAC3B,QAAQ,EAAE,MAAM,CAAC,UAAU;YAC3B,WAAW,EAAE,KAAK;YAClB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE;gBACP,OAAO,EAAE,MAAM,CAAC,SAAS,KAAK,GAAG,IAAI,MAAM,CAAC,SAAS,KAAK,MAAM;oBACvD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,wBAAwB;gBAClF,sBAAsB,EAAE,MAAM,CAAC,wBAAwB,KAAK,GAAG;gBAC/D,YAAY,EAAE,MAAM,CAAC,cAAc;aACpC;SACF,CAAC,CAAC;QAEH,6BAA6B;QAC7B,MAAM,UAAU,CAAC,UAAU,EAAE,CAAC;QAE9B,oBAAoB;QACpB,gBAAgB,GAAG,UAAU,CAAC;QAE9B,yBAAyB;QACzB,MAAM,cAAc,GAAG,IAAI,oDAA2B,CACpD,UAAU,EACV,iBAAiB,EAAE,uBAAuB;QAC1C,MAAM,CAAC,YAAY,IAAI,MAAM,EAC7B,CAAC,CACF,CAAC;QAEF,kDAAkD;QAClD,cAAc,GAAG,MAAM,IAAA,6CAAoB,EAAC,cAAc,CAAC,CAAC;QAC5D,OAAO,cAAc,CAAC;IACxB,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,qBAAqB,CAAC;AAC/B,CAAC;AAnDD,gDAmDC;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;IACD,cAAc,GAAG,IAAI,CAAC;IACtB,qBAAqB,GAAG,IAAI,CAAC;AAC/B,CAAC;AAPD,0CAOC;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';\nimport { configManager } from './config-manager';\n\n/** Global DataSource instance for connection lifecycle management */\nlet globalDataSource: DataSource | null = null;\n\n/** Global provider instance to ensure single initialization */\nlet globalProvider: SQLServerDataProvider | null = null;\n\n/** Promise to track ongoing initialization */\nlet initializationPromise: Promise<SQLServerDataProvider> | 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 // Return existing provider if already initialized\n if (globalProvider) {\n return globalProvider;\n }\n \n // Return ongoing initialization if in progress\n if (initializationPromise) {\n return initializationPromise;\n }\n \n // Start new initialization\n initializationPromise = (async () => {\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 globalProvider = await setupSQLServerClient(providerConfig);\n return globalProvider;\n })();\n \n return initializationPromise;\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 globalProvider = null;\n initializationPromise = null;\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}"]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @fileoverview Singleton manager for shared instances across MetadataSync
3
+ * @module singleton-manager
4
+ *
5
+ * This module ensures that expensive resources like SyncEngine are only
6
+ * initialized once per process, preventing duplicate metadata refreshes
7
+ * and improving performance.
8
+ */
9
+ import { SyncEngine } from './sync-engine';
10
+ import { UserInfo } from '@memberjunction/core';
11
+ /**
12
+ * Get or create a singleton SyncEngine instance
13
+ *
14
+ * Ensures that only one SyncEngine is created and initialized per process,
15
+ * preventing duplicate metadata refreshes that cause the double GetAllMetadata()
16
+ * console output.
17
+ *
18
+ * @param contextUser - The user context for database operations
19
+ * @returns Promise resolving to initialized SyncEngine instance
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const syncEngine = await getSyncEngine(getSystemUser());
24
+ * // Use syncEngine for operations
25
+ * ```
26
+ */
27
+ export declare function getSyncEngine(contextUser: UserInfo): Promise<SyncEngine>;
28
+ /**
29
+ * Reset the singleton SyncEngine instance
30
+ *
31
+ * Should be called when cleaning up resources to ensure a fresh
32
+ * instance is created on the next request.
33
+ */
34
+ export declare function resetSyncEngine(): void;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Singleton manager for shared instances across MetadataSync
4
+ * @module singleton-manager
5
+ *
6
+ * This module ensures that expensive resources like SyncEngine are only
7
+ * initialized once per process, preventing duplicate metadata refreshes
8
+ * and improving performance.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.resetSyncEngine = exports.getSyncEngine = void 0;
12
+ const sync_engine_1 = require("./sync-engine");
13
+ /** Global SyncEngine instance */
14
+ let globalSyncEngine = null;
15
+ /** Promise to track ongoing SyncEngine initialization */
16
+ let syncEngineInitPromise = null;
17
+ /**
18
+ * Get or create a singleton SyncEngine instance
19
+ *
20
+ * Ensures that only one SyncEngine is created and initialized per process,
21
+ * preventing duplicate metadata refreshes that cause the double GetAllMetadata()
22
+ * console output.
23
+ *
24
+ * @param contextUser - The user context for database operations
25
+ * @returns Promise resolving to initialized SyncEngine instance
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const syncEngine = await getSyncEngine(getSystemUser());
30
+ * // Use syncEngine for operations
31
+ * ```
32
+ */
33
+ async function getSyncEngine(contextUser) {
34
+ // Return existing engine if already initialized
35
+ if (globalSyncEngine) {
36
+ return globalSyncEngine;
37
+ }
38
+ // Return ongoing initialization if in progress
39
+ if (syncEngineInitPromise) {
40
+ return syncEngineInitPromise;
41
+ }
42
+ // Start new initialization
43
+ syncEngineInitPromise = (async () => {
44
+ globalSyncEngine = new sync_engine_1.SyncEngine(contextUser);
45
+ await globalSyncEngine.initialize();
46
+ return globalSyncEngine;
47
+ })();
48
+ return syncEngineInitPromise;
49
+ }
50
+ exports.getSyncEngine = getSyncEngine;
51
+ /**
52
+ * Reset the singleton SyncEngine instance
53
+ *
54
+ * Should be called when cleaning up resources to ensure a fresh
55
+ * instance is created on the next request.
56
+ */
57
+ function resetSyncEngine() {
58
+ globalSyncEngine = null;
59
+ syncEngineInitPromise = null;
60
+ }
61
+ exports.resetSyncEngine = resetSyncEngine;
62
+ //# sourceMappingURL=singleton-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleton-manager.js","sourceRoot":"","sources":["../../src/lib/singleton-manager.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,+CAA2C;AAG3C,iCAAiC;AACjC,IAAI,gBAAgB,GAAsB,IAAI,CAAC;AAE/C,yDAAyD;AACzD,IAAI,qBAAqB,GAA+B,IAAI,CAAC;AAE7D;;;;;;;;;;;;;;;GAeG;AACI,KAAK,UAAU,aAAa,CAAC,WAAqB;IACvD,gDAAgD;IAChD,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED,+CAA+C;IAC/C,IAAI,qBAAqB,EAAE,CAAC;QAC1B,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,2BAA2B;IAC3B,qBAAqB,GAAG,CAAC,KAAK,IAAI,EAAE;QAClC,gBAAgB,GAAG,IAAI,wBAAU,CAAC,WAAW,CAAC,CAAC;QAC/C,MAAM,gBAAgB,CAAC,UAAU,EAAE,CAAC;QACpC,OAAO,gBAAgB,CAAC;IAC1B,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,qBAAqB,CAAC;AAC/B,CAAC;AAnBD,sCAmBC;AAED;;;;;GAKG;AACH,SAAgB,eAAe;IAC7B,gBAAgB,GAAG,IAAI,CAAC;IACxB,qBAAqB,GAAG,IAAI,CAAC;AAC/B,CAAC;AAHD,0CAGC","sourcesContent":["/**\n * @fileoverview Singleton manager for shared instances across MetadataSync\n * @module singleton-manager\n * \n * This module ensures that expensive resources like SyncEngine are only\n * initialized once per process, preventing duplicate metadata refreshes\n * and improving performance.\n */\n\nimport { SyncEngine } from './sync-engine';\nimport { UserInfo } from '@memberjunction/core';\n\n/** Global SyncEngine instance */\nlet globalSyncEngine: SyncEngine | null = null;\n\n/** Promise to track ongoing SyncEngine initialization */\nlet syncEngineInitPromise: Promise<SyncEngine> | null = null;\n\n/**\n * Get or create a singleton SyncEngine instance\n * \n * Ensures that only one SyncEngine is created and initialized per process,\n * preventing duplicate metadata refreshes that cause the double GetAllMetadata()\n * console output.\n * \n * @param contextUser - The user context for database operations\n * @returns Promise resolving to initialized SyncEngine instance\n * \n * @example\n * ```typescript\n * const syncEngine = await getSyncEngine(getSystemUser());\n * // Use syncEngine for operations\n * ```\n */\nexport async function getSyncEngine(contextUser: UserInfo): Promise<SyncEngine> {\n // Return existing engine if already initialized\n if (globalSyncEngine) {\n return globalSyncEngine;\n }\n \n // Return ongoing initialization if in progress\n if (syncEngineInitPromise) {\n return syncEngineInitPromise;\n }\n \n // Start new initialization\n syncEngineInitPromise = (async () => {\n globalSyncEngine = new SyncEngine(contextUser);\n await globalSyncEngine.initialize();\n return globalSyncEngine;\n })();\n \n return syncEngineInitPromise;\n}\n\n/**\n * Reset the singleton SyncEngine instance\n * \n * Should be called when cleaning up resources to ensure a fresh\n * instance is created on the next request.\n */\nexport function resetSyncEngine(): void {\n globalSyncEngine = null;\n syncEngineInitPromise = null;\n}"]}