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