appwrite-utils-cli 1.6.2 → 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.
Files changed (62) hide show
  1. package/CONFIG_TODO.md +1189 -0
  2. package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
  3. package/dist/cli/commands/configCommands.js +7 -1
  4. package/dist/cli/commands/databaseCommands.js +23 -15
  5. package/dist/collections/attributes.d.ts +1 -1
  6. package/dist/collections/attributes.js +163 -66
  7. package/dist/collections/indexes.js +3 -17
  8. package/dist/collections/methods.js +38 -0
  9. package/dist/config/ConfigManager.d.ts +445 -0
  10. package/dist/config/ConfigManager.js +625 -0
  11. package/dist/config/index.d.ts +8 -0
  12. package/dist/config/index.js +7 -0
  13. package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
  14. package/dist/config/services/ConfigDiscoveryService.js +374 -0
  15. package/dist/config/services/ConfigLoaderService.d.ts +105 -0
  16. package/dist/config/services/ConfigLoaderService.js +410 -0
  17. package/dist/config/services/ConfigMergeService.d.ts +208 -0
  18. package/dist/config/services/ConfigMergeService.js +307 -0
  19. package/dist/config/services/ConfigValidationService.d.ts +214 -0
  20. package/dist/config/services/ConfigValidationService.js +310 -0
  21. package/dist/config/services/SessionAuthService.d.ts +225 -0
  22. package/dist/config/services/SessionAuthService.js +456 -0
  23. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
  24. package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
  25. package/dist/config/services/index.d.ts +13 -0
  26. package/dist/config/services/index.js +10 -0
  27. package/dist/interactiveCLI.js +8 -6
  28. package/dist/main.js +2 -2
  29. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
  30. package/dist/shared/operationQueue.js +1 -1
  31. package/dist/utils/ClientFactory.d.ts +87 -0
  32. package/dist/utils/ClientFactory.js +164 -0
  33. package/dist/utils/getClientFromConfig.js +4 -3
  34. package/dist/utils/helperFunctions.d.ts +1 -0
  35. package/dist/utils/helperFunctions.js +21 -5
  36. package/dist/utils/yamlConverter.d.ts +2 -0
  37. package/dist/utils/yamlConverter.js +21 -4
  38. package/dist/utilsController.d.ts +18 -15
  39. package/dist/utilsController.js +83 -131
  40. package/package.json +1 -1
  41. package/src/cli/commands/configCommands.ts +8 -1
  42. package/src/cli/commands/databaseCommands.ts +34 -20
  43. package/src/collections/attributes.ts +195 -150
  44. package/src/collections/indexes.ts +4 -19
  45. package/src/collections/methods.ts +46 -0
  46. package/src/config/ConfigManager.ts +808 -0
  47. package/src/config/index.ts +10 -0
  48. package/src/config/services/ConfigDiscoveryService.ts +463 -0
  49. package/src/config/services/ConfigLoaderService.ts +560 -0
  50. package/src/config/services/ConfigMergeService.ts +386 -0
  51. package/src/config/services/ConfigValidationService.ts +394 -0
  52. package/src/config/services/SessionAuthService.ts +565 -0
  53. package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
  54. package/src/config/services/index.ts +29 -0
  55. package/src/interactiveCLI.ts +9 -7
  56. package/src/main.ts +2 -2
  57. package/src/shared/operationQueue.ts +1 -1
  58. package/src/utils/ClientFactory.ts +186 -0
  59. package/src/utils/getClientFromConfig.ts +4 -3
  60. package/src/utils/helperFunctions.ts +27 -7
  61. package/src/utils/yamlConverter.ts +28 -2
  62. package/src/utilsController.ts +99 -187
@@ -0,0 +1,351 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
2
+ import { ConfigMergeService } from "../ConfigMergeService.js";
3
+ import type { ConfigOverrides } from "../ConfigMergeService.js";
4
+ import type { AppwriteConfig } from "appwrite-utils";
5
+ import type { SessionAuthInfo } from "../SessionAuthService.js";
6
+
7
+ describe("ConfigMergeService", () => {
8
+ let service: ConfigMergeService;
9
+ let baseConfig: AppwriteConfig;
10
+ let sessionInfo: SessionAuthInfo;
11
+
12
+ // Store original env vars
13
+ const originalEnv = { ...process.env };
14
+
15
+ beforeEach(() => {
16
+ service = new ConfigMergeService();
17
+
18
+ baseConfig = {
19
+ appwriteEndpoint: "https://cloud.appwrite.io/v1",
20
+ appwriteProject: "test-project",
21
+ appwriteKey: "test-api-key",
22
+ databases: [],
23
+ buckets: [],
24
+ functions: [],
25
+ } as unknown as AppwriteConfig;
26
+
27
+ sessionInfo = {
28
+ projectId: "test-project",
29
+ endpoint: "https://cloud.appwrite.io/v1",
30
+ cookie: "eyJhbGc.test.cookie",
31
+ email: "test@example.com",
32
+ };
33
+ });
34
+
35
+ afterEach(() => {
36
+ // Restore original env vars
37
+ process.env = { ...originalEnv };
38
+ });
39
+
40
+ describe("mergeSession", () => {
41
+ it("should merge session cookie into config", () => {
42
+ const result = service.mergeSession(baseConfig, sessionInfo);
43
+
44
+ expect(result.sessionCookie).toBe("eyJhbGc.test.cookie");
45
+ expect(result.authMethod).toBe("session");
46
+ });
47
+
48
+ it("should not mutate original config", () => {
49
+ const original = { ...baseConfig };
50
+ service.mergeSession(baseConfig, sessionInfo);
51
+
52
+ expect(baseConfig).toEqual(original);
53
+ });
54
+
55
+ it("should preserve all existing config properties", () => {
56
+ const result = service.mergeSession(baseConfig, sessionInfo);
57
+
58
+ expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
59
+ expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
60
+ expect(result.appwriteKey).toBe(baseConfig.appwriteKey);
61
+ });
62
+ });
63
+
64
+ describe("applyOverrides", () => {
65
+ it("should apply all provided overrides", () => {
66
+ const overrides: ConfigOverrides = {
67
+ appwriteEndpoint: "https://custom.appwrite.io/v1",
68
+ appwriteProject: "override-project",
69
+ appwriteKey: "override-key",
70
+ sessionCookie: "override-cookie",
71
+ authMethod: "apikey",
72
+ };
73
+
74
+ const result = service.applyOverrides(baseConfig, overrides);
75
+
76
+ expect(result.appwriteEndpoint).toBe("https://custom.appwrite.io/v1");
77
+ expect(result.appwriteProject).toBe("override-project");
78
+ expect(result.appwriteKey).toBe("override-key");
79
+ expect(result.sessionCookie).toBe("override-cookie");
80
+ expect(result.authMethod).toBe("apikey");
81
+ });
82
+
83
+ it("should skip undefined and null values", () => {
84
+ const overrides: ConfigOverrides = {
85
+ appwriteEndpoint: undefined,
86
+ appwriteProject: null as any,
87
+ appwriteKey: "new-key",
88
+ };
89
+
90
+ const result = service.applyOverrides(baseConfig, overrides);
91
+
92
+ expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
93
+ expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
94
+ expect(result.appwriteKey).toBe("new-key");
95
+ });
96
+
97
+ it("should not mutate original config", () => {
98
+ const original = { ...baseConfig };
99
+ const overrides: ConfigOverrides = {
100
+ appwriteEndpoint: "https://custom.appwrite.io/v1",
101
+ };
102
+
103
+ service.applyOverrides(baseConfig, overrides);
104
+
105
+ expect(baseConfig).toEqual(original);
106
+ });
107
+ });
108
+
109
+ describe("mergeEnvironmentVariables", () => {
110
+ it("should merge environment variables when config values are not set", () => {
111
+ process.env.APPWRITE_ENDPOINT = "https://env.appwrite.io/v1";
112
+ process.env.APPWRITE_PROJECT = "env-project";
113
+ process.env.APPWRITE_API_KEY = "env-api-key";
114
+ process.env.APPWRITE_SESSION_COOKIE = "env-cookie";
115
+
116
+ const emptyConfig = {
117
+ databases: [],
118
+ buckets: [],
119
+ functions: [],
120
+ } as unknown as AppwriteConfig;
121
+
122
+ const result = service.mergeEnvironmentVariables(emptyConfig);
123
+
124
+ expect(result.appwriteEndpoint).toBe("https://env.appwrite.io/v1");
125
+ expect(result.appwriteProject).toBe("env-project");
126
+ expect(result.appwriteKey).toBe("env-api-key");
127
+ expect(result.sessionCookie).toBe("env-cookie");
128
+ });
129
+
130
+ it("should not override existing config values", () => {
131
+ process.env.APPWRITE_ENDPOINT = "https://env.appwrite.io/v1";
132
+ process.env.APPWRITE_PROJECT = "env-project";
133
+
134
+ const result = service.mergeEnvironmentVariables(baseConfig);
135
+
136
+ expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
137
+ expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
138
+ });
139
+
140
+ it("should ignore empty environment variables", () => {
141
+ process.env.APPWRITE_ENDPOINT = "";
142
+ process.env.APPWRITE_PROJECT = "";
143
+
144
+ const emptyConfig = {
145
+ databases: [],
146
+ buckets: [],
147
+ functions: [],
148
+ } as unknown as AppwriteConfig;
149
+
150
+ const result = service.mergeEnvironmentVariables(emptyConfig);
151
+
152
+ expect(result.appwriteEndpoint).toBeUndefined();
153
+ expect(result.appwriteProject).toBeUndefined();
154
+ });
155
+ });
156
+
157
+ describe("mergeSources", () => {
158
+ it("should merge multiple sources in priority order", () => {
159
+ const source1 = {
160
+ appwriteEndpoint: "https://source1.appwrite.io/v1",
161
+ appwriteProject: "source1-project",
162
+ };
163
+
164
+ const source2 = {
165
+ appwriteProject: "source2-project",
166
+ appwriteKey: "source2-key",
167
+ };
168
+
169
+ const source3 = {
170
+ appwriteKey: "source3-key",
171
+ sessionCookie: "source3-cookie",
172
+ };
173
+
174
+ const result = service.mergeSources([source1, source2, source3]);
175
+
176
+ expect(result.appwriteEndpoint).toBe("https://source1.appwrite.io/v1");
177
+ expect(result.appwriteProject).toBe("source2-project");
178
+ expect(result.appwriteKey).toBe("source3-key");
179
+ expect(result.sessionCookie).toBe("source3-cookie");
180
+ });
181
+
182
+ it("should handle arrays by replacing, not merging", () => {
183
+ const source1 = {
184
+ appwriteEndpoint: "https://cloud.appwrite.io/v1",
185
+ appwriteProject: "test",
186
+ databases: [{ $id: "db1", name: "db1" }],
187
+ } as Partial<AppwriteConfig>;
188
+
189
+ const source2 = {
190
+ databases: [{ $id: "db2", name: "db2" }, { $id: "db3", name: "db3" }],
191
+ } as Partial<AppwriteConfig>;
192
+
193
+ const result = service.mergeSources([source1, source2]);
194
+
195
+ expect(result.databases).toHaveLength(2);
196
+ expect(result.databases).toEqual([{ $id: "db2", name: "db2" }, { $id: "db3", name: "db3" }]);
197
+ });
198
+
199
+ it("should skip undefined and null sources", () => {
200
+ const source1 = {
201
+ appwriteEndpoint: "https://cloud.appwrite.io/v1",
202
+ appwriteProject: "test",
203
+ };
204
+
205
+ const result = service.mergeSources([
206
+ source1,
207
+ undefined as any,
208
+ null as any,
209
+ { appwriteKey: "key" },
210
+ ]);
211
+
212
+ expect(result.appwriteEndpoint).toBe("https://cloud.appwrite.io/v1");
213
+ expect(result.appwriteProject).toBe("test");
214
+ expect(result.appwriteKey).toBe("key");
215
+ });
216
+
217
+ it("should throw error when no valid sources provided", () => {
218
+ expect(() => service.mergeSources([])).toThrow(
219
+ "No valid configuration sources provided for merging"
220
+ );
221
+
222
+ expect(() =>
223
+ service.mergeSources([undefined as any, null as any])
224
+ ).toThrow("No valid configuration sources provided for merging");
225
+ });
226
+
227
+ it("should throw error when merged config lacks required fields", () => {
228
+ const invalidSource = {
229
+ appwriteKey: "key",
230
+ };
231
+
232
+ expect(() => service.mergeSources([invalidSource])).toThrow(
233
+ "Merged configuration is missing required fields"
234
+ );
235
+ });
236
+ });
237
+
238
+ describe("mergeAllSources", () => {
239
+ it("should merge all sources in correct priority order", () => {
240
+ process.env.APPWRITE_ENDPOINT = "https://env.appwrite.io/v1";
241
+ process.env.APPWRITE_API_KEY = "env-key";
242
+
243
+ const overrides: ConfigOverrides = {
244
+ appwriteKey: "override-key",
245
+ };
246
+
247
+ const result = service.mergeAllSources(baseConfig, {
248
+ session: sessionInfo,
249
+ overrides,
250
+ includeEnv: true,
251
+ });
252
+
253
+ // Base config endpoint
254
+ expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
255
+ // Session should set session cookie
256
+ expect(result.sessionCookie).toBe(sessionInfo.cookie);
257
+ // Override should win for API key
258
+ expect(result.appwriteKey).toBe("override-key");
259
+ });
260
+
261
+ it("should exclude environment variables when includeEnv is false", () => {
262
+ process.env.APPWRITE_SESSION_COOKIE = "env-cookie";
263
+
264
+ const configWithoutKey = {
265
+ ...baseConfig,
266
+ appwriteKey: undefined,
267
+ } as unknown as AppwriteConfig;
268
+
269
+ const result = service.mergeAllSources(configWithoutKey, {
270
+ includeEnv: false,
271
+ });
272
+
273
+ expect(result.sessionCookie).toBeUndefined();
274
+ });
275
+
276
+ it("should work with only base config", () => {
277
+ const result = service.mergeAllSources(baseConfig, {});
278
+
279
+ expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
280
+ expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
281
+ });
282
+
283
+ it("should apply priority: overrides > session > env > base", () => {
284
+ process.env.APPWRITE_KEY = "env-key";
285
+
286
+ const configWithoutKey = {
287
+ ...baseConfig,
288
+ appwriteKey: undefined,
289
+ sessionCookie: undefined,
290
+ } as unknown as AppwriteConfig;
291
+
292
+ // Test with session (should override env)
293
+ const withSession = service.mergeAllSources(configWithoutKey, {
294
+ session: sessionInfo,
295
+ includeEnv: true,
296
+ });
297
+
298
+ expect(withSession.sessionCookie).toBe(sessionInfo.cookie);
299
+ expect(withSession.authMethod).toBe("session");
300
+
301
+ // Test with override (should override session)
302
+ const withOverride = service.mergeAllSources(configWithoutKey, {
303
+ session: sessionInfo,
304
+ overrides: { authMethod: "apikey" },
305
+ includeEnv: true,
306
+ });
307
+
308
+ expect(withOverride.authMethod).toBe("apikey");
309
+ });
310
+ });
311
+
312
+ describe("deep merge behavior", () => {
313
+ it("should deeply merge nested objects", () => {
314
+ const source1 = {
315
+ appwriteEndpoint: "https://cloud.appwrite.io/v1",
316
+ appwriteProject: "test",
317
+ databases: [
318
+ {
319
+ $id: "db1",
320
+ name: "Database 1",
321
+ },
322
+ ],
323
+ } as Partial<AppwriteConfig>;
324
+
325
+ const source2 = {
326
+ databases: [
327
+ {
328
+ $id: "db1",
329
+ name: "Updated Database",
330
+ },
331
+ ],
332
+ } as Partial<AppwriteConfig>;
333
+
334
+ const result = service.mergeSources([source1, source2]);
335
+
336
+ // Arrays are replaced, not merged
337
+ expect(result.databases).toHaveLength(1);
338
+ expect(result.databases![0].name).toBe("Updated Database");
339
+ });
340
+
341
+ it("should preserve immutability throughout merge chain", () => {
342
+ const original1 = { ...baseConfig };
343
+ const original2 = { appwriteKey: "new-key" };
344
+
345
+ service.mergeSources([baseConfig, original2]);
346
+
347
+ expect(baseConfig).toEqual(original1);
348
+ expect(original2).toEqual({ appwriteKey: "new-key" });
349
+ });
350
+ });
351
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Configuration Services
3
+ *
4
+ * Modular services for configuration management, discovery, loading, validation, and merging.
5
+ */
6
+
7
+ export { ConfigDiscoveryService } from "./ConfigDiscoveryService.js";
8
+ export type { DiscoveryResult } from "./ConfigDiscoveryService.js";
9
+
10
+ export { ConfigLoaderService } from "./ConfigLoaderService.js";
11
+ export type { CollectionLoadOptions } from "./ConfigLoaderService.js";
12
+
13
+ export { ConfigMergeService } from "./ConfigMergeService.js";
14
+ export type { ConfigOverrides } from "./ConfigMergeService.js";
15
+
16
+ export {
17
+ SessionAuthService,
18
+ type SessionAuthInfo,
19
+ type AppwriteSessionPrefs,
20
+ type AuthenticationStatus
21
+ } from "./SessionAuthService.js";
22
+
23
+ export {
24
+ ConfigValidationService,
25
+ type ValidationResult,
26
+ type ValidationError,
27
+ type ValidationWarning,
28
+ type ValidationReportOptions
29
+ } from "./ConfigValidationService.js";
@@ -196,11 +196,11 @@ export class InteractiveCLI {
196
196
  appwriteKey: string;
197
197
  }): Promise<void> {
198
198
  if (!this.controller) {
199
- this.controller = new UtilsController(this.currentDir, directConfig);
199
+ this.controller = UtilsController.getInstance(this.currentDir, directConfig);
200
200
  await this.controller.init();
201
201
  } else {
202
202
  // Extract session info from existing controller before reinitializing
203
- const sessionInfo = this.controller.getSessionInfo();
203
+ const sessionInfo = await this.controller.getSessionInfo();
204
204
  if (sessionInfo.hasSession && directConfig) {
205
205
  // Create enhanced directConfig with session preservation
206
206
  const enhancedDirectConfig = {
@@ -210,11 +210,13 @@ export class InteractiveCLI {
210
210
  };
211
211
 
212
212
  // Reinitialize with session preservation
213
- this.controller = new UtilsController(this.currentDir, enhancedDirectConfig);
213
+ UtilsController.clearInstance();
214
+ this.controller = UtilsController.getInstance(this.currentDir, enhancedDirectConfig);
214
215
  await this.controller.init();
215
216
  } else if (directConfig) {
216
217
  // Standard reinitialize without session
217
- this.controller = new UtilsController(this.currentDir, directConfig);
218
+ UtilsController.clearInstance();
219
+ this.controller = UtilsController.getInstance(this.currentDir, directConfig);
218
220
  await this.controller.init();
219
221
  }
220
222
  // If no directConfig provided, keep existing controller
@@ -985,18 +987,18 @@ export class InteractiveCLI {
985
987
  /**
986
988
  * Extract session information from current controller for preservation
987
989
  */
988
- private extractSessionFromController(): {
990
+ private async extractSessionFromController(): Promise<{
989
991
  appwriteEndpoint: string;
990
992
  appwriteProject: string;
991
993
  appwriteKey?: string;
992
994
  sessionCookie?: string;
993
995
  sessionMetadata?: any;
994
- } | undefined {
996
+ } | undefined> {
995
997
  if (!this.controller?.config) {
996
998
  return undefined;
997
999
  }
998
1000
 
999
- const sessionInfo = this.controller.getSessionInfo();
1001
+ const sessionInfo = await this.controller.getSessionInfo();
1000
1002
  const config = this.controller.config;
1001
1003
 
1002
1004
  if (!config.appwriteEndpoint || !config.appwriteProject) {
package/src/main.ts CHANGED
@@ -424,8 +424,8 @@ async function main() {
424
424
  }
425
425
  }
426
426
 
427
- // Create controller with session authentication support
428
- const controller = new UtilsController(process.cwd(), finalDirectConfig);
427
+ // Create controller with session authentication support using singleton
428
+ const controller = UtilsController.getInstance(process.cwd(), finalDirectConfig);
429
429
 
430
430
  // Pass session authentication options to the controller
431
431
  const initOptions: any = {};
@@ -76,7 +76,7 @@ export const clearProcessingState = () => {
76
76
  processedAttributes.clear();
77
77
  nameToIdMapping.clear();
78
78
 
79
- MessageFormatter.success("Cleared processing state caches");
79
+ logger.debug("Cleared processing state caches", { operation: "clearProcessingState", sizeBefore });
80
80
  logger.info('Processing state cleared', {
81
81
  sizeBefore,
82
82
  operation: 'clearProcessingState'
@@ -0,0 +1,186 @@
1
+ import { Client } from "node-appwrite";
2
+ import type { AppwriteConfig } from "appwrite-utils";
3
+ import { AdapterFactory } from "../adapters/AdapterFactory.js";
4
+ import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
5
+ import { logger } from "../shared/logging.js";
6
+
7
+ /**
8
+ * Factory for creating authenticated Appwrite clients and database adapters.
9
+ *
10
+ * This factory provides a clean separation of concerns by taking pre-configured
11
+ * AppwriteConfig objects (with authentication already resolved by ConfigManager)
12
+ * and creating the necessary client and adapter instances.
13
+ *
14
+ * Key features:
15
+ * - Takes pre-authenticated config from ConfigManager
16
+ * - Creates client with appropriate authentication method
17
+ * - Creates adapter with automatic version detection
18
+ * - Leverages AdapterFactory's internal caching for performance
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const configManager = ConfigManager.getInstance();
23
+ * const config = await configManager.loadConfig();
24
+ *
25
+ * // Config already has session or API key resolved
26
+ * const { client, adapter } = await ClientFactory.createFromConfig(config);
27
+ * ```
28
+ */
29
+ export class ClientFactory {
30
+ /**
31
+ * Create authenticated client and database adapter from configuration.
32
+ *
33
+ * This method expects the config to have authentication already resolved:
34
+ * - Either `sessionCookie` is set (from ConfigManager's session loading)
35
+ * - Or `appwriteKey` is set (from config file or CLI overrides)
36
+ *
37
+ * The ConfigManager handles all session discovery and authentication priority,
38
+ * so this factory simply applies the resolved authentication to the client.
39
+ *
40
+ * @param config - AppwriteConfig with resolved authentication
41
+ * @returns Object containing authenticated client and database adapter
42
+ * @throws Error if no authentication method is available in config
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const config = await ConfigManager.getInstance().loadConfig();
47
+ * const { client, adapter } = await ClientFactory.createFromConfig(config);
48
+ *
49
+ * // Client is now authenticated and ready to use
50
+ * const databases = new Databases(client);
51
+ * const collections = await adapter.listCollections(databaseId);
52
+ * ```
53
+ */
54
+ public static async createFromConfig(
55
+ config: AppwriteConfig
56
+ ): Promise<{ client: Client; adapter: DatabaseAdapter }> {
57
+ logger.debug("Creating client from config", {
58
+ prefix: "ClientFactory",
59
+ hasSession: !!config.sessionCookie,
60
+ hasApiKey: !!config.appwriteKey,
61
+ endpoint: config.appwriteEndpoint,
62
+ project: config.appwriteProject,
63
+ });
64
+
65
+ // Create base client with endpoint and project
66
+ const client = new Client()
67
+ .setEndpoint(config.appwriteEndpoint)
68
+ .setProject(config.appwriteProject);
69
+
70
+ // Apply authentication (priority already resolved by ConfigManager)
71
+ if (config.sessionCookie) {
72
+ // Session authentication (from ConfigManager's session loading)
73
+ client.setSession(config.sessionCookie);
74
+ logger.debug("Applied session authentication to client", {
75
+ prefix: "ClientFactory",
76
+ email: config.sessionMetadata?.email,
77
+ });
78
+ } else if (config.appwriteKey) {
79
+ // API key authentication (from config file or overrides)
80
+ client.setKey(config.appwriteKey);
81
+ logger.debug("Applied API key authentication to client", {
82
+ prefix: "ClientFactory",
83
+ });
84
+ } else {
85
+ // No authentication available - this should have been caught by ConfigManager
86
+ const error = new Error(
87
+ "No authentication method available in configuration.\n\n" +
88
+ "This should have been resolved by ConfigManager during config loading.\n" +
89
+ "Expected either:\n" +
90
+ " - config.sessionCookie (from session authentication)\n" +
91
+ " - config.appwriteKey (from config file or CLI overrides)\n\n" +
92
+ "Suggestion: Ensure ConfigManager.loadConfig() was called before ClientFactory.createFromConfig()."
93
+ );
94
+ logger.error("Failed to create client - no authentication", { prefix: "ClientFactory" });
95
+ throw error;
96
+ }
97
+
98
+ // Create adapter with version detection
99
+ // AdapterFactory uses internal caching, so repeated calls are fast
100
+ logger.debug("Creating database adapter", {
101
+ prefix: "ClientFactory",
102
+ apiMode: config.apiMode || "auto",
103
+ });
104
+
105
+ const { adapter } = await AdapterFactory.createFromConfig(config);
106
+
107
+ logger.debug("Client and adapter created successfully", {
108
+ prefix: "ClientFactory",
109
+ adapterType: adapter.getApiMode(),
110
+ });
111
+
112
+ return { client, adapter };
113
+ }
114
+
115
+ /**
116
+ * Create client and adapter from individual parameters.
117
+ *
118
+ * This is a lower-level method for cases where you don't have a full
119
+ * AppwriteConfig object. For most use cases, prefer createFromConfig().
120
+ *
121
+ * @param endpoint - Appwrite endpoint URL
122
+ * @param project - Appwrite project ID
123
+ * @param options - Authentication and API mode options
124
+ * @returns Object containing authenticated client and database adapter
125
+ * @throws Error if no authentication method is provided
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const { client, adapter } = await ClientFactory.create(
130
+ * "https://cloud.appwrite.io/v1",
131
+ * "my-project-id",
132
+ * {
133
+ * apiKey: "my-api-key",
134
+ * apiMode: "auto"
135
+ * }
136
+ * );
137
+ * ```
138
+ */
139
+ public static async create(
140
+ endpoint: string,
141
+ project: string,
142
+ options: {
143
+ apiKey?: string;
144
+ sessionCookie?: string;
145
+ apiMode?: "auto" | "legacy" | "tablesdb";
146
+ } = {}
147
+ ): Promise<{ client: Client; adapter: DatabaseAdapter }> {
148
+ logger.debug("Creating client from parameters", {
149
+ prefix: "ClientFactory",
150
+ hasSession: !!options.sessionCookie,
151
+ hasApiKey: !!options.apiKey,
152
+ endpoint,
153
+ project,
154
+ });
155
+
156
+ // Create minimal config object
157
+ const config: AppwriteConfig = {
158
+ appwriteEndpoint: endpoint,
159
+ appwriteProject: project,
160
+ appwriteKey: options.apiKey || "",
161
+ sessionCookie: options.sessionCookie,
162
+ apiMode: options.apiMode || "auto",
163
+ // Minimal required fields
164
+ appwriteClient: null,
165
+ authMethod: options.sessionCookie ? "session" : "apikey",
166
+ enableBackups: false,
167
+ backupInterval: 0,
168
+ backupRetention: 0,
169
+ enableBackupCleanup: false,
170
+ enableMockData: false,
171
+ documentBucketId: "",
172
+ usersCollectionName: "",
173
+ databases: [],
174
+ buckets: [],
175
+ functions: [],
176
+ logging: {
177
+ enabled: false,
178
+ level: "info",
179
+ console: false,
180
+ },
181
+ };
182
+
183
+ // Use main createFromConfig method
184
+ return this.createFromConfig(config);
185
+ }
186
+ }
@@ -4,6 +4,7 @@ import { AdapterFactory, type AdapterFactoryResult } from "../adapters/AdapterFa
4
4
  import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
5
5
  import { findSessionByEndpointAndProject, hasSessionAuth, isValidSessionCookie } from "./sessionAuth.js";
6
6
  import { MessageFormatter } from "../shared/messageFormatter.js";
7
+ import { logger } from "../shared/logging.js";
7
8
 
8
9
  /**
9
10
  * Enhanced client creation from config with session authentication support
@@ -43,7 +44,7 @@ export const getClientWithAuth = (
43
44
  if (sessionCookie) {
44
45
  if (isValidSessionCookie(sessionCookie)) {
45
46
  client.setSession(sessionCookie);
46
- MessageFormatter.info(`Using explicit session authentication for project ${project}`, { prefix: "Auth" });
47
+ logger.debug("Using explicit session authentication", { prefix: "Auth", project });
47
48
  return client;
48
49
  } else {
49
50
  authAttempts.push("explicit session cookie (invalid format)");
@@ -56,7 +57,7 @@ export const getClientWithAuth = (
56
57
  if (sessionAuth) {
57
58
  if (isValidSessionCookie(sessionAuth.sessionCookie)) {
58
59
  client.setSession(sessionAuth.sessionCookie);
59
- MessageFormatter.info(`Using session authentication for project ${project} (${sessionAuth.email || 'unknown user'})`, { prefix: "Auth" });
60
+ logger.debug("Using session authentication", { prefix: "Auth", project, email: sessionAuth.email || 'unknown user' });
60
61
  return client;
61
62
  } else {
62
63
  authAttempts.push("session from CLI prefs (invalid/expired)");
@@ -70,7 +71,7 @@ export const getClientWithAuth = (
70
71
  authAttempts.push("API key (empty)");
71
72
  } else {
72
73
  client.setKey(key);
73
- MessageFormatter.info(`Using API key authentication for project ${project}`, { prefix: "Auth" });
74
+ logger.debug("Using API key authentication", { prefix: "Auth", project });
74
75
  return client;
75
76
  }
76
77
  }