appwrite-utils-cli 1.6.3 → 1.6.5
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/collections/indexes.js +6 -1
- package/dist/collections/methods.js +4 -5
- 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 +14 -19
- 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/collections/indexes.ts +7 -1
- package/src/collections/methods.ts +4 -6
- 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 +14 -24
- 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,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";
|
package/src/interactiveCLI.ts
CHANGED
@@ -196,11 +196,11 @@ export class InteractiveCLI {
|
|
196
196
|
appwriteKey: string;
|
197
197
|
}): Promise<void> {
|
198
198
|
if (!this.controller) {
|
199
|
-
this.controller =
|
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
|
-
|
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
|
-
|
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 =
|
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 = {};
|
@@ -850,31 +850,21 @@ async function main() {
|
|
850
850
|
}
|
851
851
|
}
|
852
852
|
|
853
|
-
if (parsedArgv.push
|
853
|
+
if (parsedArgv.push) {
|
854
|
+
// PUSH: Use LOCAL config collections only (pass empty array to use config.collections)
|
854
855
|
const databases =
|
855
856
|
options.databases || (await fetchAllDatabases(controller.database!));
|
856
|
-
let collections: Models.Collection[] = [];
|
857
857
|
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
}
|
869
|
-
|
870
|
-
if (parsedArgv.push) {
|
871
|
-
await controller.syncDb(databases, collections);
|
872
|
-
operationStats.pushedDatabases = databases.length;
|
873
|
-
operationStats.pushedCollections = collections.length;
|
874
|
-
} else if (parsedArgv.sync) {
|
875
|
-
await controller.synchronizeConfigurations(databases);
|
876
|
-
operationStats.syncedDatabases = databases.length;
|
877
|
-
}
|
858
|
+
// Pass empty array - syncDb will use config.collections (local schema)
|
859
|
+
await controller.syncDb(databases, []);
|
860
|
+
operationStats.pushedDatabases = databases.length;
|
861
|
+
operationStats.pushedCollections = controller.config?.collections?.length || 0;
|
862
|
+
} else if (parsedArgv.sync) {
|
863
|
+
// SYNC: Pull from remote
|
864
|
+
const databases =
|
865
|
+
options.databases || (await fetchAllDatabases(controller.database!));
|
866
|
+
await controller.synchronizeConfigurations(databases);
|
867
|
+
operationStats.syncedDatabases = databases.length;
|
878
868
|
}
|
879
869
|
|
880
870
|
if (options.generateSchemas) {
|
@@ -76,7 +76,7 @@ export const clearProcessingState = () => {
|
|
76
76
|
processedAttributes.clear();
|
77
77
|
nameToIdMapping.clear();
|
78
78
|
|
79
|
-
|
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
|
+
}
|