appwrite-utils-cli 1.6.3 → 1.6.4
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/CONFIG_TODO.md +1189 -0
- package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
- package/dist/cli/commands/configCommands.js +7 -1
- package/dist/collections/attributes.js +102 -30
- package/dist/config/ConfigManager.d.ts +445 -0
- package/dist/config/ConfigManager.js +625 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.js +7 -0
- package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
- package/dist/config/services/ConfigDiscoveryService.js +374 -0
- package/dist/config/services/ConfigLoaderService.d.ts +105 -0
- package/dist/config/services/ConfigLoaderService.js +410 -0
- package/dist/config/services/ConfigMergeService.d.ts +208 -0
- package/dist/config/services/ConfigMergeService.js +307 -0
- package/dist/config/services/ConfigValidationService.d.ts +214 -0
- package/dist/config/services/ConfigValidationService.js +310 -0
- package/dist/config/services/SessionAuthService.d.ts +225 -0
- package/dist/config/services/SessionAuthService.js +456 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
- package/dist/config/services/index.d.ts +13 -0
- package/dist/config/services/index.js +10 -0
- package/dist/interactiveCLI.js +8 -6
- package/dist/main.js +2 -2
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/shared/operationQueue.js +1 -1
- package/dist/utils/ClientFactory.d.ts +87 -0
- package/dist/utils/ClientFactory.js +164 -0
- package/dist/utils/getClientFromConfig.js +4 -3
- package/dist/utils/helperFunctions.d.ts +1 -0
- package/dist/utils/helperFunctions.js +21 -5
- package/dist/utils/yamlConverter.d.ts +2 -2
- package/dist/utils/yamlConverter.js +2 -2
- package/dist/utilsController.d.ts +18 -15
- package/dist/utilsController.js +83 -131
- package/package.json +1 -1
- package/src/cli/commands/configCommands.ts +8 -1
- package/src/collections/attributes.ts +118 -31
- package/src/config/ConfigManager.ts +808 -0
- package/src/config/index.ts +10 -0
- package/src/config/services/ConfigDiscoveryService.ts +463 -0
- package/src/config/services/ConfigLoaderService.ts +560 -0
- package/src/config/services/ConfigMergeService.ts +386 -0
- package/src/config/services/ConfigValidationService.ts +394 -0
- package/src/config/services/SessionAuthService.ts +565 -0
- package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
- package/src/config/services/index.ts +29 -0
- package/src/interactiveCLI.ts +9 -7
- package/src/main.ts +2 -2
- package/src/shared/operationQueue.ts +1 -1
- package/src/utils/ClientFactory.ts +186 -0
- package/src/utils/getClientFromConfig.ts +4 -3
- package/src/utils/helperFunctions.ts +27 -7
- package/src/utils/yamlConverter.ts +4 -4
- package/src/utilsController.ts +99 -187
@@ -0,0 +1,625 @@
|
|
1
|
+
import fs from "fs";
|
2
|
+
import path from "path";
|
3
|
+
import { ConfigDiscoveryService, ConfigLoaderService, ConfigMergeService, ConfigValidationService, SessionAuthService, } from "./services/index.js";
|
4
|
+
import { MessageFormatter } from "../shared/messageFormatter.js";
|
5
|
+
import { logger } from "../shared/logging.js";
|
6
|
+
/**
|
7
|
+
* Centralized configuration manager with intelligent caching and session management.
|
8
|
+
*
|
9
|
+
* This singleton provides a unified interface for:
|
10
|
+
* - Configuration discovery and loading (YAML, TypeScript, JSON)
|
11
|
+
* - Session authentication with automatic caching
|
12
|
+
* - Configuration validation and merging
|
13
|
+
* - Session preservation across reloads
|
14
|
+
* - File watching for configuration changes
|
15
|
+
*
|
16
|
+
* @example
|
17
|
+
* ```typescript
|
18
|
+
* const configManager = ConfigManager.getInstance();
|
19
|
+
*
|
20
|
+
* // Load configuration (cached after first call)
|
21
|
+
* const config = await configManager.loadConfig({
|
22
|
+
* validate: true,
|
23
|
+
* reportValidation: true,
|
24
|
+
* });
|
25
|
+
*
|
26
|
+
* // Get cached config synchronously
|
27
|
+
* const cachedConfig = configManager.getConfig();
|
28
|
+
*
|
29
|
+
* // Reload with session preservation
|
30
|
+
* const reloadedConfig = await configManager.reloadConfig();
|
31
|
+
*
|
32
|
+
* // Watch for config changes
|
33
|
+
* const unwatch = configManager.watchConfig(async (config) => {
|
34
|
+
* console.log("Config changed:", config);
|
35
|
+
* });
|
36
|
+
* ```
|
37
|
+
*/
|
38
|
+
export class ConfigManager {
|
39
|
+
// ──────────────────────────────────────────────────
|
40
|
+
// SINGLETON PATTERN
|
41
|
+
// ──────────────────────────────────────────────────
|
42
|
+
static instance = null;
|
43
|
+
/**
|
44
|
+
* Get the ConfigManager singleton instance
|
45
|
+
*
|
46
|
+
* @returns The singleton ConfigManager instance
|
47
|
+
*
|
48
|
+
* @example
|
49
|
+
* ```typescript
|
50
|
+
* const configManager = ConfigManager.getInstance();
|
51
|
+
* ```
|
52
|
+
*/
|
53
|
+
static getInstance() {
|
54
|
+
if (!ConfigManager.instance) {
|
55
|
+
ConfigManager.instance = new ConfigManager();
|
56
|
+
}
|
57
|
+
return ConfigManager.instance;
|
58
|
+
}
|
59
|
+
/**
|
60
|
+
* Reset the singleton instance (useful for testing)
|
61
|
+
*
|
62
|
+
* @example
|
63
|
+
* ```typescript
|
64
|
+
* ConfigManager.resetInstance();
|
65
|
+
* const newInstance = ConfigManager.getInstance();
|
66
|
+
* ```
|
67
|
+
*/
|
68
|
+
static resetInstance() {
|
69
|
+
ConfigManager.instance = null;
|
70
|
+
}
|
71
|
+
// ──────────────────────────────────────────────────
|
72
|
+
// STATE (Private)
|
73
|
+
// ──────────────────────────────────────────────────
|
74
|
+
cachedConfig = null;
|
75
|
+
cachedConfigPath = null;
|
76
|
+
cachedSession = null;
|
77
|
+
lastLoadTimestamp = 0;
|
78
|
+
isInitialized = false;
|
79
|
+
// Service dependencies
|
80
|
+
discoveryService;
|
81
|
+
loaderService;
|
82
|
+
validationService;
|
83
|
+
sessionService;
|
84
|
+
mergeService;
|
85
|
+
// ──────────────────────────────────────────────────
|
86
|
+
// CONSTRUCTOR
|
87
|
+
// ──────────────────────────────────────────────────
|
88
|
+
/**
|
89
|
+
* Private constructor to enforce singleton pattern.
|
90
|
+
* Use ConfigManager.getInstance() instead.
|
91
|
+
*/
|
92
|
+
constructor() {
|
93
|
+
// Initialize all services
|
94
|
+
this.discoveryService = new ConfigDiscoveryService();
|
95
|
+
this.loaderService = new ConfigLoaderService();
|
96
|
+
this.validationService = new ConfigValidationService();
|
97
|
+
this.sessionService = new SessionAuthService();
|
98
|
+
this.mergeService = new ConfigMergeService();
|
99
|
+
logger.debug("ConfigManager instance created", { prefix: "ConfigManager" });
|
100
|
+
}
|
101
|
+
// ──────────────────────────────────────────────────
|
102
|
+
// CORE CONFIGURATION METHODS
|
103
|
+
// ──────────────────────────────────────────────────
|
104
|
+
/**
|
105
|
+
* Load configuration with intelligent caching and session management.
|
106
|
+
*
|
107
|
+
* This method orchestrates the entire configuration loading flow:
|
108
|
+
* 1. Returns cached config if available (unless forceReload is true)
|
109
|
+
* 2. Discovers config file using ConfigDiscoveryService
|
110
|
+
* 3. Loads config from file using ConfigLoaderService
|
111
|
+
* 4. Loads and merges session authentication using SessionAuthService
|
112
|
+
* 5. Applies CLI/environment overrides using ConfigMergeService
|
113
|
+
* 6. Validates configuration if requested
|
114
|
+
* 7. Caches the result for future calls
|
115
|
+
*
|
116
|
+
* @param options - Configuration loading options
|
117
|
+
* @returns The loaded and processed configuration
|
118
|
+
* @throws Error if no configuration file is found
|
119
|
+
* @throws Error if validation fails in strict mode
|
120
|
+
*
|
121
|
+
* @example
|
122
|
+
* ```typescript
|
123
|
+
* const config = await configManager.loadConfig({
|
124
|
+
* configDir: process.cwd(),
|
125
|
+
* validate: true,
|
126
|
+
* strictMode: true,
|
127
|
+
* reportValidation: true,
|
128
|
+
* overrides: {
|
129
|
+
* appwriteEndpoint: "https://custom.endpoint.com/v1"
|
130
|
+
* }
|
131
|
+
* });
|
132
|
+
* ```
|
133
|
+
*/
|
134
|
+
async loadConfig(options = {}) {
|
135
|
+
// 1. Return cache if available and not forcing reload
|
136
|
+
if (this.cachedConfig && !options.forceReload) {
|
137
|
+
logger.debug("Returning cached config", { prefix: "ConfigManager" });
|
138
|
+
return this.getCachedConfigWithOverrides(options.overrides);
|
139
|
+
}
|
140
|
+
logger.debug("Loading config from file", { prefix: "ConfigManager", options });
|
141
|
+
// 2. Discover config file
|
142
|
+
const configPath = this.discoveryService.findConfig(options.configDir || process.cwd());
|
143
|
+
if (!configPath) {
|
144
|
+
const searchDir = options.configDir || process.cwd();
|
145
|
+
throw new Error(`No Appwrite configuration found in "${searchDir}".\n` +
|
146
|
+
`Searched for: YAML (.appwrite/config.yaml), TypeScript (appwriteConfig.ts), or JSON (appwrite.json).\n` +
|
147
|
+
`Suggestion: Create a configuration file using "npx appwrite-migrate --init" or refer to the documentation.`);
|
148
|
+
}
|
149
|
+
logger.debug(`Config discovered at: ${configPath}`, { prefix: "ConfigManager" });
|
150
|
+
// 3. Load config from file
|
151
|
+
let config = await this.loaderService.loadFromPath(configPath);
|
152
|
+
// 4. Load session authentication
|
153
|
+
const session = options.sessionOverride ||
|
154
|
+
(await this.sessionService.findSession(config.appwriteEndpoint, config.appwriteProject));
|
155
|
+
// 5. Merge session into config
|
156
|
+
if (session) {
|
157
|
+
logger.debug("Merging session authentication into config", { prefix: "ConfigManager" });
|
158
|
+
config = this.mergeService.mergeSession(config, session);
|
159
|
+
this.cachedSession = session;
|
160
|
+
}
|
161
|
+
// 6. Apply CLI/env overrides
|
162
|
+
if (options.overrides) {
|
163
|
+
logger.debug("Applying configuration overrides", { prefix: "ConfigManager" });
|
164
|
+
config = this.mergeService.applyOverrides(config, options.overrides);
|
165
|
+
}
|
166
|
+
// 7. Validate if requested
|
167
|
+
if (options.validate) {
|
168
|
+
logger.debug("Validating configuration", { prefix: "ConfigManager", strictMode: options.strictMode });
|
169
|
+
const validation = options.strictMode
|
170
|
+
? this.validationService.validateStrict(config)
|
171
|
+
: this.validationService.validate(config);
|
172
|
+
if (options.reportValidation) {
|
173
|
+
this.validationService.reportResults(validation, { verbose: false });
|
174
|
+
}
|
175
|
+
if (options.strictMode && !validation.isValid) {
|
176
|
+
throw new Error(`Configuration validation failed in strict mode.\n` +
|
177
|
+
`Errors: ${validation.errors.length}, Warnings: ${validation.warnings.length}\n` +
|
178
|
+
`Run with reportValidation: true to see details.`);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
// 8. Cache the config
|
182
|
+
this.cachedConfig = config;
|
183
|
+
this.cachedConfigPath = configPath;
|
184
|
+
this.lastLoadTimestamp = Date.now();
|
185
|
+
this.isInitialized = true;
|
186
|
+
logger.debug("Config loaded and cached successfully", {
|
187
|
+
prefix: "ConfigManager",
|
188
|
+
path: configPath,
|
189
|
+
hasSession: !!session,
|
190
|
+
});
|
191
|
+
return config;
|
192
|
+
}
|
193
|
+
/**
|
194
|
+
* Get the cached configuration synchronously.
|
195
|
+
*
|
196
|
+
* @returns The cached configuration
|
197
|
+
* @throws Error if configuration has not been loaded yet
|
198
|
+
*
|
199
|
+
* @example
|
200
|
+
* ```typescript
|
201
|
+
* const config = configManager.getConfig();
|
202
|
+
* ```
|
203
|
+
*/
|
204
|
+
getConfig() {
|
205
|
+
if (!this.cachedConfig) {
|
206
|
+
throw new Error("Configuration not loaded. Call loadConfig() first.\n" +
|
207
|
+
"Suggestion: await configManager.loadConfig();");
|
208
|
+
}
|
209
|
+
return this.cachedConfig;
|
210
|
+
}
|
211
|
+
/**
|
212
|
+
* Get the path to the loaded configuration file.
|
213
|
+
*
|
214
|
+
* @returns The configuration file path, or null if not loaded
|
215
|
+
*
|
216
|
+
* @example
|
217
|
+
* ```typescript
|
218
|
+
* const configPath = configManager.getConfigPath();
|
219
|
+
* console.log(`Config loaded from: ${configPath}`);
|
220
|
+
* ```
|
221
|
+
*/
|
222
|
+
getConfigPath() {
|
223
|
+
return this.cachedConfigPath;
|
224
|
+
}
|
225
|
+
/**
|
226
|
+
* Check if configuration has been loaded.
|
227
|
+
*
|
228
|
+
* @returns True if configuration is loaded and cached
|
229
|
+
*
|
230
|
+
* @example
|
231
|
+
* ```typescript
|
232
|
+
* if (!configManager.hasConfig()) {
|
233
|
+
* await configManager.loadConfig();
|
234
|
+
* }
|
235
|
+
* ```
|
236
|
+
*/
|
237
|
+
hasConfig() {
|
238
|
+
return this.cachedConfig !== null;
|
239
|
+
}
|
240
|
+
/**
|
241
|
+
* Check if the ConfigManager has been initialized.
|
242
|
+
*
|
243
|
+
* @returns True if initialized (config loaded at least once)
|
244
|
+
*
|
245
|
+
* @example
|
246
|
+
* ```typescript
|
247
|
+
* if (configManager.isConfigInitialized()) {
|
248
|
+
* console.log("Config ready");
|
249
|
+
* }
|
250
|
+
* ```
|
251
|
+
*/
|
252
|
+
isConfigInitialized() {
|
253
|
+
return this.isInitialized;
|
254
|
+
}
|
255
|
+
/**
|
256
|
+
* Invalidate the configuration cache.
|
257
|
+
* Next call to loadConfig() will reload from disk.
|
258
|
+
*
|
259
|
+
* @example
|
260
|
+
* ```typescript
|
261
|
+
* configManager.invalidateCache();
|
262
|
+
* const freshConfig = await configManager.loadConfig();
|
263
|
+
* ```
|
264
|
+
*/
|
265
|
+
invalidateCache() {
|
266
|
+
logger.debug("Invalidating config cache", { prefix: "ConfigManager" });
|
267
|
+
this.cachedConfig = null;
|
268
|
+
this.cachedConfigPath = null;
|
269
|
+
this.lastLoadTimestamp = 0;
|
270
|
+
// Note: session cache is preserved by SessionAuthService
|
271
|
+
}
|
272
|
+
/**
|
273
|
+
* Reload configuration from disk, preserving current session.
|
274
|
+
*
|
275
|
+
* This is a convenience method that combines invalidation and reloading
|
276
|
+
* while automatically preserving the current session authentication.
|
277
|
+
*
|
278
|
+
* @param options - Configuration loading options (forceReload is automatically set)
|
279
|
+
* @returns The reloaded configuration
|
280
|
+
*
|
281
|
+
* @example
|
282
|
+
* ```typescript
|
283
|
+
* // Reload config after manual file changes
|
284
|
+
* const config = await configManager.reloadConfig({
|
285
|
+
* validate: true,
|
286
|
+
* reportValidation: true
|
287
|
+
* });
|
288
|
+
* ```
|
289
|
+
*/
|
290
|
+
async reloadConfig(options = {}) {
|
291
|
+
logger.debug("Reloading config with session preservation", { prefix: "ConfigManager" });
|
292
|
+
// Preserve current session during reload
|
293
|
+
const currentSession = this.cachedSession;
|
294
|
+
return this.loadConfig({
|
295
|
+
...options,
|
296
|
+
forceReload: true,
|
297
|
+
sessionOverride: currentSession || undefined,
|
298
|
+
});
|
299
|
+
}
|
300
|
+
// ──────────────────────────────────────────────────
|
301
|
+
// SESSION MANAGEMENT
|
302
|
+
// ──────────────────────────────────────────────────
|
303
|
+
/**
|
304
|
+
* Get the current session information.
|
305
|
+
*
|
306
|
+
* @returns The cached session info, or null if no session
|
307
|
+
*
|
308
|
+
* @example
|
309
|
+
* ```typescript
|
310
|
+
* const session = configManager.getSession();
|
311
|
+
* if (session) {
|
312
|
+
* console.log(`Authenticated as: ${session.email}`);
|
313
|
+
* }
|
314
|
+
* ```
|
315
|
+
*/
|
316
|
+
getSession() {
|
317
|
+
return this.cachedSession;
|
318
|
+
}
|
319
|
+
/**
|
320
|
+
* Check if a session is available.
|
321
|
+
*
|
322
|
+
* @returns True if a session is cached
|
323
|
+
*
|
324
|
+
* @example
|
325
|
+
* ```typescript
|
326
|
+
* if (configManager.hasSession()) {
|
327
|
+
* console.log("Using session authentication");
|
328
|
+
* } else {
|
329
|
+
* console.log("Using API key authentication");
|
330
|
+
* }
|
331
|
+
* ```
|
332
|
+
*/
|
333
|
+
hasSession() {
|
334
|
+
return this.cachedSession !== null;
|
335
|
+
}
|
336
|
+
/**
|
337
|
+
* Refresh session from disk (bypasses session cache).
|
338
|
+
*
|
339
|
+
* This forces SessionAuthService to reload from ~/.appwrite/prefs.json
|
340
|
+
* and update the cached session.
|
341
|
+
*
|
342
|
+
* @returns The refreshed session, or null if no session found
|
343
|
+
*
|
344
|
+
* @example
|
345
|
+
* ```typescript
|
346
|
+
* // After logging in via appwrite CLI
|
347
|
+
* const session = await configManager.refreshSession();
|
348
|
+
* if (session) {
|
349
|
+
* console.log("Session refreshed successfully");
|
350
|
+
* }
|
351
|
+
* ```
|
352
|
+
*/
|
353
|
+
async refreshSession() {
|
354
|
+
if (!this.cachedConfig) {
|
355
|
+
logger.debug("Cannot refresh session - no config loaded", { prefix: "ConfigManager" });
|
356
|
+
return null;
|
357
|
+
}
|
358
|
+
logger.debug("Refreshing session from disk", { prefix: "ConfigManager" });
|
359
|
+
// Invalidate session cache
|
360
|
+
this.sessionService.invalidateCache();
|
361
|
+
// Reload session
|
362
|
+
const session = await this.sessionService.findSession(this.cachedConfig.appwriteEndpoint, this.cachedConfig.appwriteProject);
|
363
|
+
if (session) {
|
364
|
+
this.cachedSession = session;
|
365
|
+
logger.debug("Session refreshed successfully", { prefix: "ConfigManager" });
|
366
|
+
}
|
367
|
+
else {
|
368
|
+
this.cachedSession = null;
|
369
|
+
logger.debug("No session found after refresh", { prefix: "ConfigManager" });
|
370
|
+
}
|
371
|
+
return session;
|
372
|
+
}
|
373
|
+
/**
|
374
|
+
* Get authentication status for the current configuration.
|
375
|
+
*
|
376
|
+
* @returns Authentication status object with details about current auth method
|
377
|
+
* @throws Error if configuration has not been loaded
|
378
|
+
*
|
379
|
+
* @example
|
380
|
+
* ```typescript
|
381
|
+
* const authStatus = await configManager.getAuthStatus();
|
382
|
+
* console.log(`Auth method: ${authStatus.authMethod}`);
|
383
|
+
* console.log(`Has valid session: ${authStatus.hasValidSession}`);
|
384
|
+
* ```
|
385
|
+
*/
|
386
|
+
async getAuthStatus() {
|
387
|
+
if (!this.cachedConfig) {
|
388
|
+
throw new Error("Configuration not loaded. Call loadConfig() first.\n" +
|
389
|
+
"Suggestion: await configManager.loadConfig();");
|
390
|
+
}
|
391
|
+
return await this.sessionService.getAuthenticationStatus(this.cachedConfig.appwriteEndpoint, this.cachedConfig.appwriteProject, this.cachedConfig.appwriteKey, this.cachedSession);
|
392
|
+
}
|
393
|
+
/**
|
394
|
+
* Clear the cached session.
|
395
|
+
* Next load will attempt to find a new session.
|
396
|
+
*
|
397
|
+
* @example
|
398
|
+
* ```typescript
|
399
|
+
* configManager.clearSession();
|
400
|
+
* const config = await configManager.reloadConfig();
|
401
|
+
* ```
|
402
|
+
*/
|
403
|
+
clearSession() {
|
404
|
+
logger.debug("Clearing cached session", { prefix: "ConfigManager" });
|
405
|
+
this.cachedSession = null;
|
406
|
+
}
|
407
|
+
// ──────────────────────────────────────────────────
|
408
|
+
// VALIDATION
|
409
|
+
// ──────────────────────────────────────────────────
|
410
|
+
/**
|
411
|
+
* Validate the current configuration (warnings allowed).
|
412
|
+
*
|
413
|
+
* @param reportResults - Whether to report validation results to console
|
414
|
+
* @returns Validation result with errors and warnings
|
415
|
+
* @throws Error if configuration has not been loaded
|
416
|
+
*
|
417
|
+
* @example
|
418
|
+
* ```typescript
|
419
|
+
* const validation = configManager.validateConfig(true);
|
420
|
+
* if (!validation.isValid) {
|
421
|
+
* console.log(`Found ${validation.errors.length} errors`);
|
422
|
+
* }
|
423
|
+
* ```
|
424
|
+
*/
|
425
|
+
validateConfig(reportResults = false) {
|
426
|
+
if (!this.cachedConfig) {
|
427
|
+
throw new Error("Configuration not loaded. Call loadConfig() first.\n" +
|
428
|
+
"Suggestion: await configManager.loadConfig();");
|
429
|
+
}
|
430
|
+
const validation = this.validationService.validate(this.cachedConfig);
|
431
|
+
if (reportResults) {
|
432
|
+
this.validationService.reportResults(validation);
|
433
|
+
}
|
434
|
+
return validation;
|
435
|
+
}
|
436
|
+
/**
|
437
|
+
* Validate the current configuration in strict mode (warnings as errors).
|
438
|
+
*
|
439
|
+
* @param reportResults - Whether to report validation results to console
|
440
|
+
* @returns Validation result with errors and warnings (warnings treated as errors)
|
441
|
+
* @throws Error if configuration has not been loaded
|
442
|
+
*
|
443
|
+
* @example
|
444
|
+
* ```typescript
|
445
|
+
* const validation = configManager.validateConfigStrict(true);
|
446
|
+
* if (!validation.isValid) {
|
447
|
+
* console.log("Config validation failed (strict mode)");
|
448
|
+
* }
|
449
|
+
* ```
|
450
|
+
*/
|
451
|
+
validateConfigStrict(reportResults = false) {
|
452
|
+
if (!this.cachedConfig) {
|
453
|
+
throw new Error("Configuration not loaded. Call loadConfig() first.\n" +
|
454
|
+
"Suggestion: await configManager.loadConfig();");
|
455
|
+
}
|
456
|
+
const validation = this.validationService.validateStrict(this.cachedConfig);
|
457
|
+
if (reportResults) {
|
458
|
+
this.validationService.reportResults(validation);
|
459
|
+
}
|
460
|
+
return validation;
|
461
|
+
}
|
462
|
+
// ──────────────────────────────────────────────────
|
463
|
+
// ADVANCED / CONVENIENCE
|
464
|
+
// ──────────────────────────────────────────────────
|
465
|
+
/**
|
466
|
+
* Watch configuration file for changes and reload automatically.
|
467
|
+
*
|
468
|
+
* Returns a function to stop watching.
|
469
|
+
*
|
470
|
+
* @param callback - Function to call when config changes
|
471
|
+
* @returns Function to stop watching
|
472
|
+
*
|
473
|
+
* @example
|
474
|
+
* ```typescript
|
475
|
+
* const unwatch = configManager.watchConfig(async (config) => {
|
476
|
+
* console.log("Config changed, reloading...");
|
477
|
+
* // Handle config change
|
478
|
+
* });
|
479
|
+
*
|
480
|
+
* // Later, stop watching
|
481
|
+
* unwatch();
|
482
|
+
* ```
|
483
|
+
*/
|
484
|
+
watchConfig(callback) {
|
485
|
+
if (!this.cachedConfigPath) {
|
486
|
+
throw new Error("Cannot watch config - no config file loaded.\n" +
|
487
|
+
"Suggestion: Call loadConfig() before watchConfig().");
|
488
|
+
}
|
489
|
+
const configPath = this.cachedConfigPath;
|
490
|
+
logger.debug(`Setting up config file watcher: ${configPath}`, { prefix: "ConfigManager" });
|
491
|
+
let isProcessing = false;
|
492
|
+
const watcher = fs.watch(configPath, async (eventType) => {
|
493
|
+
if (eventType === "change" && !isProcessing) {
|
494
|
+
isProcessing = true;
|
495
|
+
logger.debug("Config file changed, reloading...", { prefix: "ConfigManager" });
|
496
|
+
try {
|
497
|
+
const newConfig = await this.reloadConfig();
|
498
|
+
await callback(newConfig);
|
499
|
+
}
|
500
|
+
catch (error) {
|
501
|
+
MessageFormatter.error("Failed to reload config after file change", error instanceof Error ? error : String(error), { prefix: "ConfigManager" });
|
502
|
+
}
|
503
|
+
finally {
|
504
|
+
isProcessing = false;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
});
|
508
|
+
// Return cleanup function
|
509
|
+
return () => {
|
510
|
+
logger.debug("Stopping config file watcher", { prefix: "ConfigManager" });
|
511
|
+
watcher.close();
|
512
|
+
};
|
513
|
+
}
|
514
|
+
/**
|
515
|
+
* Merge overrides into the current configuration without persisting.
|
516
|
+
*
|
517
|
+
* This creates a new config object with overrides applied, without
|
518
|
+
* affecting the cached configuration.
|
519
|
+
*
|
520
|
+
* @param overrides - Configuration overrides to apply
|
521
|
+
* @returns New configuration with overrides applied
|
522
|
+
* @throws Error if configuration has not been loaded
|
523
|
+
*
|
524
|
+
* @example
|
525
|
+
* ```typescript
|
526
|
+
* const customConfig = configManager.mergeOverrides({
|
527
|
+
* appwriteEndpoint: "https://test.endpoint.com/v1"
|
528
|
+
* });
|
529
|
+
* ```
|
530
|
+
*/
|
531
|
+
mergeOverrides(overrides) {
|
532
|
+
if (!this.cachedConfig) {
|
533
|
+
throw new Error("Configuration not loaded. Call loadConfig() first.\n" +
|
534
|
+
"Suggestion: await configManager.loadConfig();");
|
535
|
+
}
|
536
|
+
return this.mergeService.applyOverrides(this.cachedConfig, overrides);
|
537
|
+
}
|
538
|
+
/**
|
539
|
+
* Get collections from the configuration, optionally filtered.
|
540
|
+
*
|
541
|
+
* @param filter - Optional filter function for collections
|
542
|
+
* @returns Array of collections (or tables, depending on API mode)
|
543
|
+
*
|
544
|
+
* @example
|
545
|
+
* ```typescript
|
546
|
+
* // Get all collections
|
547
|
+
* const allCollections = configManager.getCollections();
|
548
|
+
*
|
549
|
+
* // Get enabled collections only
|
550
|
+
* const enabledCollections = configManager.getCollections(
|
551
|
+
* (c) => c.enabled !== false
|
552
|
+
* );
|
553
|
+
* ```
|
554
|
+
*/
|
555
|
+
getCollections(filter) {
|
556
|
+
const config = this.getConfig();
|
557
|
+
const collections = config.collections || config.tables || [];
|
558
|
+
if (filter) {
|
559
|
+
return collections.filter(filter);
|
560
|
+
}
|
561
|
+
return collections;
|
562
|
+
}
|
563
|
+
/**
|
564
|
+
* Get databases from the configuration.
|
565
|
+
*
|
566
|
+
* @returns Array of databases
|
567
|
+
*
|
568
|
+
* @example
|
569
|
+
* ```typescript
|
570
|
+
* const databases = configManager.getDatabases();
|
571
|
+
* console.log(`Found ${databases.length} databases`);
|
572
|
+
* ```
|
573
|
+
*/
|
574
|
+
getDatabases() {
|
575
|
+
const config = this.getConfig();
|
576
|
+
return config.databases || [];
|
577
|
+
}
|
578
|
+
/**
|
579
|
+
* Get buckets from the configuration.
|
580
|
+
*
|
581
|
+
* @returns Array of buckets
|
582
|
+
*
|
583
|
+
* @example
|
584
|
+
* ```typescript
|
585
|
+
* const buckets = configManager.getBuckets();
|
586
|
+
* console.log(`Found ${buckets.length} buckets`);
|
587
|
+
* ```
|
588
|
+
*/
|
589
|
+
getBuckets() {
|
590
|
+
const config = this.getConfig();
|
591
|
+
return config.buckets || [];
|
592
|
+
}
|
593
|
+
/**
|
594
|
+
* Get functions from the configuration.
|
595
|
+
*
|
596
|
+
* @returns Array of functions
|
597
|
+
*
|
598
|
+
* @example
|
599
|
+
* ```typescript
|
600
|
+
* const functions = configManager.getFunctions();
|
601
|
+
* console.log(`Found ${functions.length} functions`);
|
602
|
+
* ```
|
603
|
+
*/
|
604
|
+
getFunctions() {
|
605
|
+
const config = this.getConfig();
|
606
|
+
return config.functions || [];
|
607
|
+
}
|
608
|
+
// ──────────────────────────────────────────────────
|
609
|
+
// PRIVATE HELPERS
|
610
|
+
// ──────────────────────────────────────────────────
|
611
|
+
/**
|
612
|
+
* Get cached config with optional overrides applied.
|
613
|
+
* Used internally to avoid reloading when only overrides change.
|
614
|
+
*/
|
615
|
+
getCachedConfigWithOverrides(overrides) {
|
616
|
+
if (!this.cachedConfig) {
|
617
|
+
throw new Error("No cached config available");
|
618
|
+
}
|
619
|
+
if (!overrides) {
|
620
|
+
return this.cachedConfig;
|
621
|
+
}
|
622
|
+
logger.debug("Applying overrides to cached config", { prefix: "ConfigManager" });
|
623
|
+
return this.mergeService.applyOverrides(this.cachedConfig, overrides);
|
624
|
+
}
|
625
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
/**
|
2
|
+
* Configuration Management
|
3
|
+
*
|
4
|
+
* Centralized configuration manager with services for discovery, loading, validation, and merging.
|
5
|
+
*/
|
6
|
+
export { ConfigManager } from "./ConfigManager.js";
|
7
|
+
export type { ConfigLoadOptions, CollectionFilter } from "./ConfigManager.js";
|
8
|
+
export * from "./services/index.js";
|