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.
- package/CONFIG_TODO.md +1189 -0
- package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
- package/dist/cli/commands/configCommands.js +7 -1
- package/dist/cli/commands/databaseCommands.js +23 -15
- package/dist/collections/attributes.d.ts +1 -1
- package/dist/collections/attributes.js +163 -66
- package/dist/collections/indexes.js +3 -17
- package/dist/collections/methods.js +38 -0
- 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 -0
- package/dist/utils/yamlConverter.js +21 -4
- 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/cli/commands/databaseCommands.ts +34 -20
- package/src/collections/attributes.ts +195 -150
- package/src/collections/indexes.ts +4 -19
- package/src/collections/methods.ts +46 -0
- 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 +28 -2
- package/src/utilsController.ts +99 -187
@@ -0,0 +1,271 @@
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
|
2
|
+
import { ConfigMergeService } from "../ConfigMergeService.js";
|
3
|
+
describe("ConfigMergeService", () => {
|
4
|
+
let service;
|
5
|
+
let baseConfig;
|
6
|
+
let sessionInfo;
|
7
|
+
// Store original env vars
|
8
|
+
const originalEnv = { ...process.env };
|
9
|
+
beforeEach(() => {
|
10
|
+
service = new ConfigMergeService();
|
11
|
+
baseConfig = {
|
12
|
+
appwriteEndpoint: "https://cloud.appwrite.io/v1",
|
13
|
+
appwriteProject: "test-project",
|
14
|
+
appwriteKey: "test-api-key",
|
15
|
+
databases: [],
|
16
|
+
buckets: [],
|
17
|
+
functions: [],
|
18
|
+
};
|
19
|
+
sessionInfo = {
|
20
|
+
projectId: "test-project",
|
21
|
+
endpoint: "https://cloud.appwrite.io/v1",
|
22
|
+
cookie: "eyJhbGc.test.cookie",
|
23
|
+
email: "test@example.com",
|
24
|
+
};
|
25
|
+
});
|
26
|
+
afterEach(() => {
|
27
|
+
// Restore original env vars
|
28
|
+
process.env = { ...originalEnv };
|
29
|
+
});
|
30
|
+
describe("mergeSession", () => {
|
31
|
+
it("should merge session cookie into config", () => {
|
32
|
+
const result = service.mergeSession(baseConfig, sessionInfo);
|
33
|
+
expect(result.sessionCookie).toBe("eyJhbGc.test.cookie");
|
34
|
+
expect(result.authMethod).toBe("session");
|
35
|
+
});
|
36
|
+
it("should not mutate original config", () => {
|
37
|
+
const original = { ...baseConfig };
|
38
|
+
service.mergeSession(baseConfig, sessionInfo);
|
39
|
+
expect(baseConfig).toEqual(original);
|
40
|
+
});
|
41
|
+
it("should preserve all existing config properties", () => {
|
42
|
+
const result = service.mergeSession(baseConfig, sessionInfo);
|
43
|
+
expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
|
44
|
+
expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
|
45
|
+
expect(result.appwriteKey).toBe(baseConfig.appwriteKey);
|
46
|
+
});
|
47
|
+
});
|
48
|
+
describe("applyOverrides", () => {
|
49
|
+
it("should apply all provided overrides", () => {
|
50
|
+
const overrides = {
|
51
|
+
appwriteEndpoint: "https://custom.appwrite.io/v1",
|
52
|
+
appwriteProject: "override-project",
|
53
|
+
appwriteKey: "override-key",
|
54
|
+
sessionCookie: "override-cookie",
|
55
|
+
authMethod: "apikey",
|
56
|
+
};
|
57
|
+
const result = service.applyOverrides(baseConfig, overrides);
|
58
|
+
expect(result.appwriteEndpoint).toBe("https://custom.appwrite.io/v1");
|
59
|
+
expect(result.appwriteProject).toBe("override-project");
|
60
|
+
expect(result.appwriteKey).toBe("override-key");
|
61
|
+
expect(result.sessionCookie).toBe("override-cookie");
|
62
|
+
expect(result.authMethod).toBe("apikey");
|
63
|
+
});
|
64
|
+
it("should skip undefined and null values", () => {
|
65
|
+
const overrides = {
|
66
|
+
appwriteEndpoint: undefined,
|
67
|
+
appwriteProject: null,
|
68
|
+
appwriteKey: "new-key",
|
69
|
+
};
|
70
|
+
const result = service.applyOverrides(baseConfig, overrides);
|
71
|
+
expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
|
72
|
+
expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
|
73
|
+
expect(result.appwriteKey).toBe("new-key");
|
74
|
+
});
|
75
|
+
it("should not mutate original config", () => {
|
76
|
+
const original = { ...baseConfig };
|
77
|
+
const overrides = {
|
78
|
+
appwriteEndpoint: "https://custom.appwrite.io/v1",
|
79
|
+
};
|
80
|
+
service.applyOverrides(baseConfig, overrides);
|
81
|
+
expect(baseConfig).toEqual(original);
|
82
|
+
});
|
83
|
+
});
|
84
|
+
describe("mergeEnvironmentVariables", () => {
|
85
|
+
it("should merge environment variables when config values are not set", () => {
|
86
|
+
process.env.APPWRITE_ENDPOINT = "https://env.appwrite.io/v1";
|
87
|
+
process.env.APPWRITE_PROJECT = "env-project";
|
88
|
+
process.env.APPWRITE_API_KEY = "env-api-key";
|
89
|
+
process.env.APPWRITE_SESSION_COOKIE = "env-cookie";
|
90
|
+
const emptyConfig = {
|
91
|
+
databases: [],
|
92
|
+
buckets: [],
|
93
|
+
functions: [],
|
94
|
+
};
|
95
|
+
const result = service.mergeEnvironmentVariables(emptyConfig);
|
96
|
+
expect(result.appwriteEndpoint).toBe("https://env.appwrite.io/v1");
|
97
|
+
expect(result.appwriteProject).toBe("env-project");
|
98
|
+
expect(result.appwriteKey).toBe("env-api-key");
|
99
|
+
expect(result.sessionCookie).toBe("env-cookie");
|
100
|
+
});
|
101
|
+
it("should not override existing config values", () => {
|
102
|
+
process.env.APPWRITE_ENDPOINT = "https://env.appwrite.io/v1";
|
103
|
+
process.env.APPWRITE_PROJECT = "env-project";
|
104
|
+
const result = service.mergeEnvironmentVariables(baseConfig);
|
105
|
+
expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
|
106
|
+
expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
|
107
|
+
});
|
108
|
+
it("should ignore empty environment variables", () => {
|
109
|
+
process.env.APPWRITE_ENDPOINT = "";
|
110
|
+
process.env.APPWRITE_PROJECT = "";
|
111
|
+
const emptyConfig = {
|
112
|
+
databases: [],
|
113
|
+
buckets: [],
|
114
|
+
functions: [],
|
115
|
+
};
|
116
|
+
const result = service.mergeEnvironmentVariables(emptyConfig);
|
117
|
+
expect(result.appwriteEndpoint).toBeUndefined();
|
118
|
+
expect(result.appwriteProject).toBeUndefined();
|
119
|
+
});
|
120
|
+
});
|
121
|
+
describe("mergeSources", () => {
|
122
|
+
it("should merge multiple sources in priority order", () => {
|
123
|
+
const source1 = {
|
124
|
+
appwriteEndpoint: "https://source1.appwrite.io/v1",
|
125
|
+
appwriteProject: "source1-project",
|
126
|
+
};
|
127
|
+
const source2 = {
|
128
|
+
appwriteProject: "source2-project",
|
129
|
+
appwriteKey: "source2-key",
|
130
|
+
};
|
131
|
+
const source3 = {
|
132
|
+
appwriteKey: "source3-key",
|
133
|
+
sessionCookie: "source3-cookie",
|
134
|
+
};
|
135
|
+
const result = service.mergeSources([source1, source2, source3]);
|
136
|
+
expect(result.appwriteEndpoint).toBe("https://source1.appwrite.io/v1");
|
137
|
+
expect(result.appwriteProject).toBe("source2-project");
|
138
|
+
expect(result.appwriteKey).toBe("source3-key");
|
139
|
+
expect(result.sessionCookie).toBe("source3-cookie");
|
140
|
+
});
|
141
|
+
it("should handle arrays by replacing, not merging", () => {
|
142
|
+
const source1 = {
|
143
|
+
appwriteEndpoint: "https://cloud.appwrite.io/v1",
|
144
|
+
appwriteProject: "test",
|
145
|
+
databases: [{ $id: "db1", name: "db1" }],
|
146
|
+
};
|
147
|
+
const source2 = {
|
148
|
+
databases: [{ $id: "db2", name: "db2" }, { $id: "db3", name: "db3" }],
|
149
|
+
};
|
150
|
+
const result = service.mergeSources([source1, source2]);
|
151
|
+
expect(result.databases).toHaveLength(2);
|
152
|
+
expect(result.databases).toEqual([{ $id: "db2", name: "db2" }, { $id: "db3", name: "db3" }]);
|
153
|
+
});
|
154
|
+
it("should skip undefined and null sources", () => {
|
155
|
+
const source1 = {
|
156
|
+
appwriteEndpoint: "https://cloud.appwrite.io/v1",
|
157
|
+
appwriteProject: "test",
|
158
|
+
};
|
159
|
+
const result = service.mergeSources([
|
160
|
+
source1,
|
161
|
+
undefined,
|
162
|
+
null,
|
163
|
+
{ appwriteKey: "key" },
|
164
|
+
]);
|
165
|
+
expect(result.appwriteEndpoint).toBe("https://cloud.appwrite.io/v1");
|
166
|
+
expect(result.appwriteProject).toBe("test");
|
167
|
+
expect(result.appwriteKey).toBe("key");
|
168
|
+
});
|
169
|
+
it("should throw error when no valid sources provided", () => {
|
170
|
+
expect(() => service.mergeSources([])).toThrow("No valid configuration sources provided for merging");
|
171
|
+
expect(() => service.mergeSources([undefined, null])).toThrow("No valid configuration sources provided for merging");
|
172
|
+
});
|
173
|
+
it("should throw error when merged config lacks required fields", () => {
|
174
|
+
const invalidSource = {
|
175
|
+
appwriteKey: "key",
|
176
|
+
};
|
177
|
+
expect(() => service.mergeSources([invalidSource])).toThrow("Merged configuration is missing required fields");
|
178
|
+
});
|
179
|
+
});
|
180
|
+
describe("mergeAllSources", () => {
|
181
|
+
it("should merge all sources in correct priority order", () => {
|
182
|
+
process.env.APPWRITE_ENDPOINT = "https://env.appwrite.io/v1";
|
183
|
+
process.env.APPWRITE_API_KEY = "env-key";
|
184
|
+
const overrides = {
|
185
|
+
appwriteKey: "override-key",
|
186
|
+
};
|
187
|
+
const result = service.mergeAllSources(baseConfig, {
|
188
|
+
session: sessionInfo,
|
189
|
+
overrides,
|
190
|
+
includeEnv: true,
|
191
|
+
});
|
192
|
+
// Base config endpoint
|
193
|
+
expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
|
194
|
+
// Session should set session cookie
|
195
|
+
expect(result.sessionCookie).toBe(sessionInfo.cookie);
|
196
|
+
// Override should win for API key
|
197
|
+
expect(result.appwriteKey).toBe("override-key");
|
198
|
+
});
|
199
|
+
it("should exclude environment variables when includeEnv is false", () => {
|
200
|
+
process.env.APPWRITE_SESSION_COOKIE = "env-cookie";
|
201
|
+
const configWithoutKey = {
|
202
|
+
...baseConfig,
|
203
|
+
appwriteKey: undefined,
|
204
|
+
};
|
205
|
+
const result = service.mergeAllSources(configWithoutKey, {
|
206
|
+
includeEnv: false,
|
207
|
+
});
|
208
|
+
expect(result.sessionCookie).toBeUndefined();
|
209
|
+
});
|
210
|
+
it("should work with only base config", () => {
|
211
|
+
const result = service.mergeAllSources(baseConfig, {});
|
212
|
+
expect(result.appwriteEndpoint).toBe(baseConfig.appwriteEndpoint);
|
213
|
+
expect(result.appwriteProject).toBe(baseConfig.appwriteProject);
|
214
|
+
});
|
215
|
+
it("should apply priority: overrides > session > env > base", () => {
|
216
|
+
process.env.APPWRITE_KEY = "env-key";
|
217
|
+
const configWithoutKey = {
|
218
|
+
...baseConfig,
|
219
|
+
appwriteKey: undefined,
|
220
|
+
sessionCookie: undefined,
|
221
|
+
};
|
222
|
+
// Test with session (should override env)
|
223
|
+
const withSession = service.mergeAllSources(configWithoutKey, {
|
224
|
+
session: sessionInfo,
|
225
|
+
includeEnv: true,
|
226
|
+
});
|
227
|
+
expect(withSession.sessionCookie).toBe(sessionInfo.cookie);
|
228
|
+
expect(withSession.authMethod).toBe("session");
|
229
|
+
// Test with override (should override session)
|
230
|
+
const withOverride = service.mergeAllSources(configWithoutKey, {
|
231
|
+
session: sessionInfo,
|
232
|
+
overrides: { authMethod: "apikey" },
|
233
|
+
includeEnv: true,
|
234
|
+
});
|
235
|
+
expect(withOverride.authMethod).toBe("apikey");
|
236
|
+
});
|
237
|
+
});
|
238
|
+
describe("deep merge behavior", () => {
|
239
|
+
it("should deeply merge nested objects", () => {
|
240
|
+
const source1 = {
|
241
|
+
appwriteEndpoint: "https://cloud.appwrite.io/v1",
|
242
|
+
appwriteProject: "test",
|
243
|
+
databases: [
|
244
|
+
{
|
245
|
+
$id: "db1",
|
246
|
+
name: "Database 1",
|
247
|
+
},
|
248
|
+
],
|
249
|
+
};
|
250
|
+
const source2 = {
|
251
|
+
databases: [
|
252
|
+
{
|
253
|
+
$id: "db1",
|
254
|
+
name: "Updated Database",
|
255
|
+
},
|
256
|
+
],
|
257
|
+
};
|
258
|
+
const result = service.mergeSources([source1, source2]);
|
259
|
+
// Arrays are replaced, not merged
|
260
|
+
expect(result.databases).toHaveLength(1);
|
261
|
+
expect(result.databases[0].name).toBe("Updated Database");
|
262
|
+
});
|
263
|
+
it("should preserve immutability throughout merge chain", () => {
|
264
|
+
const original1 = { ...baseConfig };
|
265
|
+
const original2 = { appwriteKey: "new-key" };
|
266
|
+
service.mergeSources([baseConfig, original2]);
|
267
|
+
expect(baseConfig).toEqual(original1);
|
268
|
+
expect(original2).toEqual({ appwriteKey: "new-key" });
|
269
|
+
});
|
270
|
+
});
|
271
|
+
});
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* Configuration Services
|
3
|
+
*
|
4
|
+
* Modular services for configuration management, discovery, loading, validation, and merging.
|
5
|
+
*/
|
6
|
+
export { ConfigDiscoveryService } from "./ConfigDiscoveryService.js";
|
7
|
+
export type { DiscoveryResult } from "./ConfigDiscoveryService.js";
|
8
|
+
export { ConfigLoaderService } from "./ConfigLoaderService.js";
|
9
|
+
export type { CollectionLoadOptions } from "./ConfigLoaderService.js";
|
10
|
+
export { ConfigMergeService } from "./ConfigMergeService.js";
|
11
|
+
export type { ConfigOverrides } from "./ConfigMergeService.js";
|
12
|
+
export { SessionAuthService, type SessionAuthInfo, type AppwriteSessionPrefs, type AuthenticationStatus } from "./SessionAuthService.js";
|
13
|
+
export { ConfigValidationService, type ValidationResult, type ValidationError, type ValidationWarning, type ValidationReportOptions } from "./ConfigValidationService.js";
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/**
|
2
|
+
* Configuration Services
|
3
|
+
*
|
4
|
+
* Modular services for configuration management, discovery, loading, validation, and merging.
|
5
|
+
*/
|
6
|
+
export { ConfigDiscoveryService } from "./ConfigDiscoveryService.js";
|
7
|
+
export { ConfigLoaderService } from "./ConfigLoaderService.js";
|
8
|
+
export { ConfigMergeService } from "./ConfigMergeService.js";
|
9
|
+
export { SessionAuthService } from "./SessionAuthService.js";
|
10
|
+
export { ConfigValidationService } from "./ConfigValidationService.js";
|
package/dist/interactiveCLI.js
CHANGED
@@ -160,12 +160,12 @@ export class InteractiveCLI {
|
|
160
160
|
}
|
161
161
|
async initControllerIfNeeded(directConfig) {
|
162
162
|
if (!this.controller) {
|
163
|
-
this.controller =
|
163
|
+
this.controller = UtilsController.getInstance(this.currentDir, directConfig);
|
164
164
|
await this.controller.init();
|
165
165
|
}
|
166
166
|
else {
|
167
167
|
// Extract session info from existing controller before reinitializing
|
168
|
-
const sessionInfo = this.controller.getSessionInfo();
|
168
|
+
const sessionInfo = await this.controller.getSessionInfo();
|
169
169
|
if (sessionInfo.hasSession && directConfig) {
|
170
170
|
// Create enhanced directConfig with session preservation
|
171
171
|
const enhancedDirectConfig = {
|
@@ -174,12 +174,14 @@ export class InteractiveCLI {
|
|
174
174
|
sessionMetadata: this.controller.sessionMetadata
|
175
175
|
};
|
176
176
|
// Reinitialize with session preservation
|
177
|
-
|
177
|
+
UtilsController.clearInstance();
|
178
|
+
this.controller = UtilsController.getInstance(this.currentDir, enhancedDirectConfig);
|
178
179
|
await this.controller.init();
|
179
180
|
}
|
180
181
|
else if (directConfig) {
|
181
182
|
// Standard reinitialize without session
|
182
|
-
|
183
|
+
UtilsController.clearInstance();
|
184
|
+
this.controller = UtilsController.getInstance(this.currentDir, directConfig);
|
183
185
|
await this.controller.init();
|
184
186
|
}
|
185
187
|
// If no directConfig provided, keep existing controller
|
@@ -776,11 +778,11 @@ export class InteractiveCLI {
|
|
776
778
|
/**
|
777
779
|
* Extract session information from current controller for preservation
|
778
780
|
*/
|
779
|
-
extractSessionFromController() {
|
781
|
+
async extractSessionFromController() {
|
780
782
|
if (!this.controller?.config) {
|
781
783
|
return undefined;
|
782
784
|
}
|
783
|
-
const sessionInfo = this.controller.getSessionInfo();
|
785
|
+
const sessionInfo = await this.controller.getSessionInfo();
|
784
786
|
const config = this.controller.config;
|
785
787
|
if (!config.appwriteEndpoint || !config.appwriteProject) {
|
786
788
|
return undefined;
|
package/dist/main.js
CHANGED
@@ -353,8 +353,8 @@ async function main() {
|
|
353
353
|
argv.useSession = true;
|
354
354
|
}
|
355
355
|
}
|
356
|
-
// Create controller with session authentication support
|
357
|
-
const controller =
|
356
|
+
// Create controller with session authentication support using singleton
|
357
|
+
const controller = UtilsController.getInstance(process.cwd(), finalDirectConfig);
|
358
358
|
// Pass session authentication options to the controller
|
359
359
|
const initOptions = {};
|
360
360
|
if (argv.useSession || argv.sessionCookie) {
|
@@ -55,7 +55,7 @@ export const clearProcessingState = () => {
|
|
55
55
|
processedCollections.clear();
|
56
56
|
processedAttributes.clear();
|
57
57
|
nameToIdMapping.clear();
|
58
|
-
|
58
|
+
logger.debug("Cleared processing state caches", { operation: "clearProcessingState", sizeBefore });
|
59
59
|
logger.info('Processing state cleared', {
|
60
60
|
sizeBefore,
|
61
61
|
operation: 'clearProcessingState'
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import { Client } from "node-appwrite";
|
2
|
+
import type { AppwriteConfig } from "appwrite-utils";
|
3
|
+
import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
|
4
|
+
/**
|
5
|
+
* Factory for creating authenticated Appwrite clients and database adapters.
|
6
|
+
*
|
7
|
+
* This factory provides a clean separation of concerns by taking pre-configured
|
8
|
+
* AppwriteConfig objects (with authentication already resolved by ConfigManager)
|
9
|
+
* and creating the necessary client and adapter instances.
|
10
|
+
*
|
11
|
+
* Key features:
|
12
|
+
* - Takes pre-authenticated config from ConfigManager
|
13
|
+
* - Creates client with appropriate authentication method
|
14
|
+
* - Creates adapter with automatic version detection
|
15
|
+
* - Leverages AdapterFactory's internal caching for performance
|
16
|
+
*
|
17
|
+
* @example
|
18
|
+
* ```typescript
|
19
|
+
* const configManager = ConfigManager.getInstance();
|
20
|
+
* const config = await configManager.loadConfig();
|
21
|
+
*
|
22
|
+
* // Config already has session or API key resolved
|
23
|
+
* const { client, adapter } = await ClientFactory.createFromConfig(config);
|
24
|
+
* ```
|
25
|
+
*/
|
26
|
+
export declare class ClientFactory {
|
27
|
+
/**
|
28
|
+
* Create authenticated client and database adapter from configuration.
|
29
|
+
*
|
30
|
+
* This method expects the config to have authentication already resolved:
|
31
|
+
* - Either `sessionCookie` is set (from ConfigManager's session loading)
|
32
|
+
* - Or `appwriteKey` is set (from config file or CLI overrides)
|
33
|
+
*
|
34
|
+
* The ConfigManager handles all session discovery and authentication priority,
|
35
|
+
* so this factory simply applies the resolved authentication to the client.
|
36
|
+
*
|
37
|
+
* @param config - AppwriteConfig with resolved authentication
|
38
|
+
* @returns Object containing authenticated client and database adapter
|
39
|
+
* @throws Error if no authentication method is available in config
|
40
|
+
*
|
41
|
+
* @example
|
42
|
+
* ```typescript
|
43
|
+
* const config = await ConfigManager.getInstance().loadConfig();
|
44
|
+
* const { client, adapter } = await ClientFactory.createFromConfig(config);
|
45
|
+
*
|
46
|
+
* // Client is now authenticated and ready to use
|
47
|
+
* const databases = new Databases(client);
|
48
|
+
* const collections = await adapter.listCollections(databaseId);
|
49
|
+
* ```
|
50
|
+
*/
|
51
|
+
static createFromConfig(config: AppwriteConfig): Promise<{
|
52
|
+
client: Client;
|
53
|
+
adapter: DatabaseAdapter;
|
54
|
+
}>;
|
55
|
+
/**
|
56
|
+
* Create client and adapter from individual parameters.
|
57
|
+
*
|
58
|
+
* This is a lower-level method for cases where you don't have a full
|
59
|
+
* AppwriteConfig object. For most use cases, prefer createFromConfig().
|
60
|
+
*
|
61
|
+
* @param endpoint - Appwrite endpoint URL
|
62
|
+
* @param project - Appwrite project ID
|
63
|
+
* @param options - Authentication and API mode options
|
64
|
+
* @returns Object containing authenticated client and database adapter
|
65
|
+
* @throws Error if no authentication method is provided
|
66
|
+
*
|
67
|
+
* @example
|
68
|
+
* ```typescript
|
69
|
+
* const { client, adapter } = await ClientFactory.create(
|
70
|
+
* "https://cloud.appwrite.io/v1",
|
71
|
+
* "my-project-id",
|
72
|
+
* {
|
73
|
+
* apiKey: "my-api-key",
|
74
|
+
* apiMode: "auto"
|
75
|
+
* }
|
76
|
+
* );
|
77
|
+
* ```
|
78
|
+
*/
|
79
|
+
static create(endpoint: string, project: string, options?: {
|
80
|
+
apiKey?: string;
|
81
|
+
sessionCookie?: string;
|
82
|
+
apiMode?: "auto" | "legacy" | "tablesdb";
|
83
|
+
}): Promise<{
|
84
|
+
client: Client;
|
85
|
+
adapter: DatabaseAdapter;
|
86
|
+
}>;
|
87
|
+
}
|
@@ -0,0 +1,164 @@
|
|
1
|
+
import { Client } from "node-appwrite";
|
2
|
+
import { AdapterFactory } from "../adapters/AdapterFactory.js";
|
3
|
+
import { logger } from "../shared/logging.js";
|
4
|
+
/**
|
5
|
+
* Factory for creating authenticated Appwrite clients and database adapters.
|
6
|
+
*
|
7
|
+
* This factory provides a clean separation of concerns by taking pre-configured
|
8
|
+
* AppwriteConfig objects (with authentication already resolved by ConfigManager)
|
9
|
+
* and creating the necessary client and adapter instances.
|
10
|
+
*
|
11
|
+
* Key features:
|
12
|
+
* - Takes pre-authenticated config from ConfigManager
|
13
|
+
* - Creates client with appropriate authentication method
|
14
|
+
* - Creates adapter with automatic version detection
|
15
|
+
* - Leverages AdapterFactory's internal caching for performance
|
16
|
+
*
|
17
|
+
* @example
|
18
|
+
* ```typescript
|
19
|
+
* const configManager = ConfigManager.getInstance();
|
20
|
+
* const config = await configManager.loadConfig();
|
21
|
+
*
|
22
|
+
* // Config already has session or API key resolved
|
23
|
+
* const { client, adapter } = await ClientFactory.createFromConfig(config);
|
24
|
+
* ```
|
25
|
+
*/
|
26
|
+
export class ClientFactory {
|
27
|
+
/**
|
28
|
+
* Create authenticated client and database adapter from configuration.
|
29
|
+
*
|
30
|
+
* This method expects the config to have authentication already resolved:
|
31
|
+
* - Either `sessionCookie` is set (from ConfigManager's session loading)
|
32
|
+
* - Or `appwriteKey` is set (from config file or CLI overrides)
|
33
|
+
*
|
34
|
+
* The ConfigManager handles all session discovery and authentication priority,
|
35
|
+
* so this factory simply applies the resolved authentication to the client.
|
36
|
+
*
|
37
|
+
* @param config - AppwriteConfig with resolved authentication
|
38
|
+
* @returns Object containing authenticated client and database adapter
|
39
|
+
* @throws Error if no authentication method is available in config
|
40
|
+
*
|
41
|
+
* @example
|
42
|
+
* ```typescript
|
43
|
+
* const config = await ConfigManager.getInstance().loadConfig();
|
44
|
+
* const { client, adapter } = await ClientFactory.createFromConfig(config);
|
45
|
+
*
|
46
|
+
* // Client is now authenticated and ready to use
|
47
|
+
* const databases = new Databases(client);
|
48
|
+
* const collections = await adapter.listCollections(databaseId);
|
49
|
+
* ```
|
50
|
+
*/
|
51
|
+
static async createFromConfig(config) {
|
52
|
+
logger.debug("Creating client from config", {
|
53
|
+
prefix: "ClientFactory",
|
54
|
+
hasSession: !!config.sessionCookie,
|
55
|
+
hasApiKey: !!config.appwriteKey,
|
56
|
+
endpoint: config.appwriteEndpoint,
|
57
|
+
project: config.appwriteProject,
|
58
|
+
});
|
59
|
+
// Create base client with endpoint and project
|
60
|
+
const client = new Client()
|
61
|
+
.setEndpoint(config.appwriteEndpoint)
|
62
|
+
.setProject(config.appwriteProject);
|
63
|
+
// Apply authentication (priority already resolved by ConfigManager)
|
64
|
+
if (config.sessionCookie) {
|
65
|
+
// Session authentication (from ConfigManager's session loading)
|
66
|
+
client.setSession(config.sessionCookie);
|
67
|
+
logger.debug("Applied session authentication to client", {
|
68
|
+
prefix: "ClientFactory",
|
69
|
+
email: config.sessionMetadata?.email,
|
70
|
+
});
|
71
|
+
}
|
72
|
+
else if (config.appwriteKey) {
|
73
|
+
// API key authentication (from config file or overrides)
|
74
|
+
client.setKey(config.appwriteKey);
|
75
|
+
logger.debug("Applied API key authentication to client", {
|
76
|
+
prefix: "ClientFactory",
|
77
|
+
});
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
// No authentication available - this should have been caught by ConfigManager
|
81
|
+
const error = new Error("No authentication method available in configuration.\n\n" +
|
82
|
+
"This should have been resolved by ConfigManager during config loading.\n" +
|
83
|
+
"Expected either:\n" +
|
84
|
+
" - config.sessionCookie (from session authentication)\n" +
|
85
|
+
" - config.appwriteKey (from config file or CLI overrides)\n\n" +
|
86
|
+
"Suggestion: Ensure ConfigManager.loadConfig() was called before ClientFactory.createFromConfig().");
|
87
|
+
logger.error("Failed to create client - no authentication", { prefix: "ClientFactory" });
|
88
|
+
throw error;
|
89
|
+
}
|
90
|
+
// Create adapter with version detection
|
91
|
+
// AdapterFactory uses internal caching, so repeated calls are fast
|
92
|
+
logger.debug("Creating database adapter", {
|
93
|
+
prefix: "ClientFactory",
|
94
|
+
apiMode: config.apiMode || "auto",
|
95
|
+
});
|
96
|
+
const { adapter } = await AdapterFactory.createFromConfig(config);
|
97
|
+
logger.debug("Client and adapter created successfully", {
|
98
|
+
prefix: "ClientFactory",
|
99
|
+
adapterType: adapter.getApiMode(),
|
100
|
+
});
|
101
|
+
return { client, adapter };
|
102
|
+
}
|
103
|
+
/**
|
104
|
+
* Create client and adapter from individual parameters.
|
105
|
+
*
|
106
|
+
* This is a lower-level method for cases where you don't have a full
|
107
|
+
* AppwriteConfig object. For most use cases, prefer createFromConfig().
|
108
|
+
*
|
109
|
+
* @param endpoint - Appwrite endpoint URL
|
110
|
+
* @param project - Appwrite project ID
|
111
|
+
* @param options - Authentication and API mode options
|
112
|
+
* @returns Object containing authenticated client and database adapter
|
113
|
+
* @throws Error if no authentication method is provided
|
114
|
+
*
|
115
|
+
* @example
|
116
|
+
* ```typescript
|
117
|
+
* const { client, adapter } = await ClientFactory.create(
|
118
|
+
* "https://cloud.appwrite.io/v1",
|
119
|
+
* "my-project-id",
|
120
|
+
* {
|
121
|
+
* apiKey: "my-api-key",
|
122
|
+
* apiMode: "auto"
|
123
|
+
* }
|
124
|
+
* );
|
125
|
+
* ```
|
126
|
+
*/
|
127
|
+
static async create(endpoint, project, options = {}) {
|
128
|
+
logger.debug("Creating client from parameters", {
|
129
|
+
prefix: "ClientFactory",
|
130
|
+
hasSession: !!options.sessionCookie,
|
131
|
+
hasApiKey: !!options.apiKey,
|
132
|
+
endpoint,
|
133
|
+
project,
|
134
|
+
});
|
135
|
+
// Create minimal config object
|
136
|
+
const config = {
|
137
|
+
appwriteEndpoint: endpoint,
|
138
|
+
appwriteProject: project,
|
139
|
+
appwriteKey: options.apiKey || "",
|
140
|
+
sessionCookie: options.sessionCookie,
|
141
|
+
apiMode: options.apiMode || "auto",
|
142
|
+
// Minimal required fields
|
143
|
+
appwriteClient: null,
|
144
|
+
authMethod: options.sessionCookie ? "session" : "apikey",
|
145
|
+
enableBackups: false,
|
146
|
+
backupInterval: 0,
|
147
|
+
backupRetention: 0,
|
148
|
+
enableBackupCleanup: false,
|
149
|
+
enableMockData: false,
|
150
|
+
documentBucketId: "",
|
151
|
+
usersCollectionName: "",
|
152
|
+
databases: [],
|
153
|
+
buckets: [],
|
154
|
+
functions: [],
|
155
|
+
logging: {
|
156
|
+
enabled: false,
|
157
|
+
level: "info",
|
158
|
+
console: false,
|
159
|
+
},
|
160
|
+
};
|
161
|
+
// Use main createFromConfig method
|
162
|
+
return this.createFromConfig(config);
|
163
|
+
}
|
164
|
+
}
|
@@ -3,6 +3,7 @@ import { Client } from "node-appwrite";
|
|
3
3
|
import { AdapterFactory } from "../adapters/AdapterFactory.js";
|
4
4
|
import { findSessionByEndpointAndProject, hasSessionAuth, isValidSessionCookie } from "./sessionAuth.js";
|
5
5
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
6
|
+
import { logger } from "../shared/logging.js";
|
6
7
|
/**
|
7
8
|
* Enhanced client creation from config with session authentication support
|
8
9
|
* @deprecated Use getAdapterFromConfig for dual API support with session auth
|
@@ -28,7 +29,7 @@ export const getClientWithAuth = (endpoint, project, key, sessionCookie) => {
|
|
28
29
|
if (sessionCookie) {
|
29
30
|
if (isValidSessionCookie(sessionCookie)) {
|
30
31
|
client.setSession(sessionCookie);
|
31
|
-
|
32
|
+
logger.debug("Using explicit session authentication", { prefix: "Auth", project });
|
32
33
|
return client;
|
33
34
|
}
|
34
35
|
else {
|
@@ -41,7 +42,7 @@ export const getClientWithAuth = (endpoint, project, key, sessionCookie) => {
|
|
41
42
|
if (sessionAuth) {
|
42
43
|
if (isValidSessionCookie(sessionAuth.sessionCookie)) {
|
43
44
|
client.setSession(sessionAuth.sessionCookie);
|
44
|
-
|
45
|
+
logger.debug("Using session authentication", { prefix: "Auth", project, email: sessionAuth.email || 'unknown user' });
|
45
46
|
return client;
|
46
47
|
}
|
47
48
|
else {
|
@@ -56,7 +57,7 @@ export const getClientWithAuth = (endpoint, project, key, sessionCookie) => {
|
|
56
57
|
}
|
57
58
|
else {
|
58
59
|
client.setKey(key);
|
59
|
-
|
60
|
+
logger.debug("Using API key authentication", { prefix: "Auth", project });
|
60
61
|
return client;
|
61
62
|
}
|
62
63
|
}
|
@@ -38,6 +38,7 @@ export declare const finalizeByAttributeMap: (appwriteFolderPath: string, collec
|
|
38
38
|
export declare let numTimesFailedTotal: number;
|
39
39
|
/**
|
40
40
|
* Tries to execute the given createFunction and retries up to 5 times if it fails.
|
41
|
+
* Only retries on transient errors (network failures, 5xx errors). Does NOT retry validation errors (4xx).
|
41
42
|
*
|
42
43
|
* @param {() => Promise<any>} createFunction - The function to be executed.
|
43
44
|
* @param {number} [attemptNum=0] - The number of attempts made so far (default: 0).
|