appwrite-utils-cli 1.7.9 → 1.8.2

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 (70) hide show
  1. package/CHANGELOG.md +14 -199
  2. package/README.md +87 -30
  3. package/dist/adapters/AdapterFactory.js +5 -25
  4. package/dist/adapters/DatabaseAdapter.d.ts +17 -2
  5. package/dist/adapters/LegacyAdapter.d.ts +2 -1
  6. package/dist/adapters/LegacyAdapter.js +212 -16
  7. package/dist/adapters/TablesDBAdapter.d.ts +2 -12
  8. package/dist/adapters/TablesDBAdapter.js +261 -57
  9. package/dist/cli/commands/databaseCommands.js +4 -3
  10. package/dist/cli/commands/functionCommands.js +17 -8
  11. package/dist/collections/attributes.js +447 -125
  12. package/dist/collections/methods.js +197 -186
  13. package/dist/collections/tableOperations.d.ts +86 -0
  14. package/dist/collections/tableOperations.js +434 -0
  15. package/dist/collections/transferOperations.d.ts +3 -2
  16. package/dist/collections/transferOperations.js +93 -12
  17. package/dist/config/yamlConfig.d.ts +221 -88
  18. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  19. package/dist/examples/yamlTerminologyExample.js +6 -3
  20. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  21. package/dist/functions/fnConfigDiscovery.js +108 -0
  22. package/dist/interactiveCLI.js +18 -15
  23. package/dist/main.js +211 -73
  24. package/dist/migrations/appwriteToX.d.ts +88 -23
  25. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  26. package/dist/migrations/comprehensiveTransfer.js +83 -6
  27. package/dist/migrations/dataLoader.d.ts +227 -69
  28. package/dist/migrations/dataLoader.js +3 -3
  29. package/dist/migrations/importController.js +3 -3
  30. package/dist/migrations/relationships.d.ts +8 -2
  31. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  32. package/dist/migrations/transfer.js +159 -37
  33. package/dist/shared/attributeMapper.d.ts +20 -0
  34. package/dist/shared/attributeMapper.js +203 -0
  35. package/dist/shared/selectionDialogs.js +8 -4
  36. package/dist/storage/schemas.d.ts +354 -92
  37. package/dist/utils/configDiscovery.js +4 -3
  38. package/dist/utils/versionDetection.d.ts +0 -4
  39. package/dist/utils/versionDetection.js +41 -173
  40. package/dist/utils/yamlConverter.js +89 -16
  41. package/dist/utils/yamlLoader.d.ts +1 -1
  42. package/dist/utils/yamlLoader.js +6 -2
  43. package/dist/utilsController.js +56 -19
  44. package/package.json +4 -4
  45. package/src/adapters/AdapterFactory.ts +119 -143
  46. package/src/adapters/DatabaseAdapter.ts +18 -3
  47. package/src/adapters/LegacyAdapter.ts +236 -105
  48. package/src/adapters/TablesDBAdapter.ts +773 -643
  49. package/src/cli/commands/databaseCommands.ts +13 -12
  50. package/src/cli/commands/functionCommands.ts +23 -14
  51. package/src/collections/attributes.ts +2054 -1611
  52. package/src/collections/methods.ts +208 -293
  53. package/src/collections/tableOperations.ts +506 -0
  54. package/src/collections/transferOperations.ts +218 -144
  55. package/src/examples/yamlTerminologyExample.ts +10 -5
  56. package/src/functions/fnConfigDiscovery.ts +103 -0
  57. package/src/interactiveCLI.ts +25 -20
  58. package/src/main.ts +549 -194
  59. package/src/migrations/comprehensiveTransfer.ts +126 -50
  60. package/src/migrations/dataLoader.ts +3 -3
  61. package/src/migrations/importController.ts +3 -3
  62. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  63. package/src/migrations/transfer.ts +148 -131
  64. package/src/shared/attributeMapper.ts +229 -0
  65. package/src/shared/selectionDialogs.ts +29 -25
  66. package/src/utils/configDiscovery.ts +9 -3
  67. package/src/utils/versionDetection.ts +74 -228
  68. package/src/utils/yamlConverter.ts +94 -17
  69. package/src/utils/yamlLoader.ts +11 -4
  70. package/src/utilsController.ts +80 -30
@@ -0,0 +1,229 @@
1
+ import type { CreateAttributeParams, UpdateAttributeParams } from "../adapters/DatabaseAdapter.js";
2
+ import type { Attribute } from "appwrite-utils";
3
+
4
+ function ensureNumber(n: any): number | undefined {
5
+ if (n === null || n === undefined) return undefined;
6
+ const num = Number(n);
7
+ return Number.isFinite(num) ? num : undefined;
8
+ }
9
+
10
+ /**
11
+ * Map a schema Attribute into DatabaseAdapter CreateAttributeParams
12
+ * Only includes fields valid for the specific type to satisfy TS unions.
13
+ * Also normalizes min/max ordering for numeric types to avoid server errors.
14
+ */
15
+ export function mapToCreateAttributeParams(
16
+ attr: Attribute,
17
+ base: { databaseId: string; tableId: string }
18
+ ): CreateAttributeParams {
19
+ const type = String((attr as any).type || "").toLowerCase();
20
+ const required = !!(attr as any).required;
21
+ const array = !!(attr as any).array;
22
+ const xdefault = (attr as any).xdefault;
23
+ const encrypt = (attr as any).encrypted ?? (attr as any).encrypt;
24
+
25
+ // Numeric helpers
26
+ const rawMin = ensureNumber((attr as any).min);
27
+ const rawMax = ensureNumber((attr as any).max);
28
+ let min = rawMin;
29
+ let max = rawMax;
30
+ if (min !== undefined && max !== undefined && min >= max) {
31
+ // Swap to satisfy server-side validation
32
+ const tmp = min;
33
+ min = Math.min(min, max);
34
+ max = Math.max(tmp, max);
35
+ if (min === max) {
36
+ // If still equal, unset max to avoid error
37
+ max = undefined;
38
+ }
39
+ }
40
+
41
+ switch (type) {
42
+ case "string":
43
+ return {
44
+ databaseId: base.databaseId,
45
+ tableId: base.tableId,
46
+ key: attr.key,
47
+ type,
48
+ size: (attr as any).size ?? 255,
49
+ required,
50
+ default: xdefault,
51
+ array,
52
+ encrypt: !!encrypt,
53
+ };
54
+
55
+ case "integer":
56
+ return {
57
+ databaseId: base.databaseId,
58
+ tableId: base.tableId,
59
+ key: attr.key,
60
+ type,
61
+ required,
62
+ default: xdefault,
63
+ array,
64
+ min,
65
+ max,
66
+ };
67
+
68
+ case "double":
69
+ case "float":
70
+ return {
71
+ databaseId: base.databaseId,
72
+ tableId: base.tableId,
73
+ key: attr.key,
74
+ type,
75
+ required,
76
+ default: xdefault,
77
+ array,
78
+ min,
79
+ max,
80
+ };
81
+
82
+ case "boolean":
83
+ return {
84
+ databaseId: base.databaseId,
85
+ tableId: base.tableId,
86
+ key: attr.key,
87
+ type,
88
+ required,
89
+ default: xdefault,
90
+ array,
91
+ };
92
+
93
+ case "datetime":
94
+ case "email":
95
+ case "ip":
96
+ case "url":
97
+ return {
98
+ databaseId: base.databaseId,
99
+ tableId: base.tableId,
100
+ key: attr.key,
101
+ type,
102
+ required,
103
+ default: xdefault,
104
+ array,
105
+ };
106
+
107
+ case "enum":
108
+ {
109
+ const elements = (attr as any).elements;
110
+ if (!Array.isArray(elements) || elements.length === 0) {
111
+ // Creating an enum without elements is invalid – fail fast with clear messaging
112
+ throw new Error(
113
+ `Enum attribute '${(attr as any).key}' requires a non-empty 'elements' array for creation`
114
+ );
115
+ }
116
+ }
117
+ return {
118
+ databaseId: base.databaseId,
119
+ tableId: base.tableId,
120
+ key: attr.key,
121
+ type,
122
+ required,
123
+ default: xdefault,
124
+ array,
125
+ elements: (attr as any).elements,
126
+ };
127
+
128
+ case "relationship": {
129
+ // Relationship attributes require related collection and metadata
130
+ return {
131
+ databaseId: base.databaseId,
132
+ tableId: base.tableId,
133
+ key: attr.key,
134
+ type,
135
+ relatedCollection: (attr as any).relatedCollection,
136
+ relationType: (attr as any).relationType,
137
+ twoWay: (attr as any).twoWay,
138
+ twoWayKey: (attr as any).twoWayKey,
139
+ onDelete: (attr as any).onDelete,
140
+ side: (attr as any).side,
141
+ } as unknown as CreateAttributeParams;
142
+ }
143
+
144
+ default:
145
+ return {
146
+ databaseId: base.databaseId,
147
+ tableId: base.tableId,
148
+ key: attr.key,
149
+ type,
150
+ required,
151
+ } as CreateAttributeParams;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Map a schema Attribute into DatabaseAdapter UpdateAttributeParams
157
+ * Omits fields that are not explicitly provided, and guards enum updates
158
+ * so we never send an empty elements array (preserve existing on server).
159
+ */
160
+ export function mapToUpdateAttributeParams(
161
+ attr: Attribute,
162
+ base: { databaseId: string; tableId: string }
163
+ ): UpdateAttributeParams {
164
+ const type = String((attr as any).type || "").toLowerCase();
165
+ const params: UpdateAttributeParams = {
166
+ databaseId: base.databaseId,
167
+ tableId: base.tableId,
168
+ key: (attr as any).key,
169
+ };
170
+
171
+ const setIfDefined = <K extends keyof UpdateAttributeParams>(
172
+ key: K,
173
+ value: UpdateAttributeParams[K]
174
+ ) => {
175
+ if (value !== undefined) (params as any)[key] = value;
176
+ };
177
+
178
+ // Common fields
179
+ setIfDefined("type", type);
180
+ setIfDefined("required", (attr as any).required);
181
+ // Only send default if explicitly provided and not on required
182
+ if (!(attr as any).required && (attr as any).xdefault !== undefined) {
183
+ setIfDefined("default", (attr as any).xdefault as any);
184
+ }
185
+ setIfDefined("array", (attr as any).array);
186
+ // encrypt only applies to string types
187
+ if (type === "string") setIfDefined("encrypt", (attr as any).encrypted ?? (attr as any).encrypt);
188
+
189
+ // Numeric normalization
190
+ const toNum = (n: any) => (n === null || n === undefined ? undefined : (Number(n)));
191
+ let min = toNum((attr as any).min);
192
+ let max = toNum((attr as any).max);
193
+ if (min !== undefined && max !== undefined && min >= max) {
194
+ const tmp = min;
195
+ min = Math.min(min, max);
196
+ max = Math.max(tmp, max);
197
+ if (min === max) max = undefined;
198
+ }
199
+
200
+ switch (type) {
201
+ case "string":
202
+ setIfDefined("size", (attr as any).size);
203
+ break;
204
+ case "integer":
205
+ case "float":
206
+ case "double":
207
+ setIfDefined("min", min);
208
+ setIfDefined("max", max);
209
+ break;
210
+ case "enum": {
211
+ const elements = (attr as any).elements;
212
+ if (Array.isArray(elements) && elements.length > 0) {
213
+ // Only include when non-empty; otherwise preserve existing on server
214
+ setIfDefined("elements", elements as string[]);
215
+ }
216
+ break;
217
+ }
218
+ case "relationship": {
219
+ setIfDefined("relatedCollection", (attr as any).relatedCollection);
220
+ setIfDefined("relationType", (attr as any).relationType);
221
+ setIfDefined("twoWay", (attr as any).twoWay);
222
+ setIfDefined("twoWayKey", (attr as any).twoWayKey);
223
+ setIfDefined("onDelete", (attr as any).onDelete);
224
+ break;
225
+ }
226
+ }
227
+
228
+ return params;
229
+ }
@@ -244,12 +244,13 @@ export class SelectionDialogs {
244
244
  return; // Skip configured databases if only allowing new ones
245
245
  }
246
246
 
247
- choices.push({
248
- name,
249
- value: database.$id,
250
- short: database.name,
251
- checked: defaultSelected.includes(database.$id) || (!allowNewOnly && isConfigured)
252
- });
247
+ choices.push({
248
+ name,
249
+ value: database.$id,
250
+ short: database.name,
251
+ // Do not preselect anything unless explicitly provided
252
+ checked: defaultSelected.includes(database.$id)
253
+ });
253
254
  });
254
255
 
255
256
  if (choices.length === 0) {
@@ -327,12 +328,13 @@ export class SelectionDialogs {
327
328
  return; // Skip configured tables if only allowing new ones
328
329
  }
329
330
 
330
- choices.push({
331
- name,
332
- value: table.$id,
333
- short: table.name,
334
- checked: defaultSelected.includes(table.$id) || (!allowNewOnly && isConfigured)
335
- });
331
+ choices.push({
332
+ name,
333
+ value: table.$id,
334
+ short: table.name,
335
+ // Do not preselect anything unless explicitly provided
336
+ checked: defaultSelected.includes(table.$id)
337
+ });
336
338
  });
337
339
 
338
340
  if (choices.length === 0) {
@@ -436,12 +438,13 @@ export class SelectionDialogs {
436
438
  return; // Skip configured buckets if only allowing new ones
437
439
  }
438
440
 
439
- choices.push({
440
- name: ` ${name}`,
441
- value: bucket.$id,
442
- short: bucket.name,
443
- checked: defaultSelected.includes(bucket.$id) || (!allowNewOnly && isConfigured)
444
- });
441
+ choices.push({
442
+ name: ` ${name}`,
443
+ value: bucket.$id,
444
+ short: bucket.name,
445
+ // Do not preselect anything unless explicitly provided
446
+ checked: defaultSelected.includes(bucket.$id)
447
+ });
445
448
  });
446
449
  }
447
450
  });
@@ -480,12 +483,13 @@ export class SelectionDialogs {
480
483
  return; // Skip configured buckets if only allowing new ones
481
484
  }
482
485
 
483
- choices.push({
484
- name,
485
- value: bucket.$id,
486
- short: bucket.name,
487
- checked: defaultSelected.includes(bucket.$id) || (!allowNewOnly && isConfigured)
488
- });
486
+ choices.push({
487
+ name,
488
+ value: bucket.$id,
489
+ short: bucket.name,
490
+ // Do not preselect anything unless explicitly provided
491
+ checked: defaultSelected.includes(bucket.$id)
492
+ });
489
493
  });
490
494
  }
491
495
 
@@ -742,4 +746,4 @@ export class SelectionDialogs {
742
746
  MessageFormatter.success(message, { skipLogging: true });
743
747
  logger.info(`Selection dialog success: ${message}`);
744
748
  }
745
- }
749
+ }
@@ -1,6 +1,10 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import { type CollectionCreate, type Collection } from "appwrite-utils";
3
+ import {
4
+ CollectionCreateSchema,
5
+ type CollectionCreate,
6
+ type Collection
7
+ } from "appwrite-utils";
4
8
  import { register } from "tsx/esm/api";
5
9
  import { pathToFileURL } from "node:url";
6
10
  import yaml from "js-yaml";
@@ -216,7 +220,7 @@ export const loadYamlCollection = (filePath: string): CollectionCreate | null =>
216
220
  const parsedCollection = YamlCollectionSchema.parse(yamlData);
217
221
 
218
222
  // Convert YAML collection to CollectionCreate format
219
- const collection: CollectionCreate = {
223
+ const collectionInput: CollectionCreate = {
220
224
  name: parsedCollection.name,
221
225
  $id: parsedCollection.id || parsedCollection.name.toLowerCase().replace(/\s+/g, '_'),
222
226
  documentSecurity: parsedCollection.documentSecurity,
@@ -241,7 +245,7 @@ export const loadYamlCollection = (filePath: string): CollectionCreate | null =>
241
245
  twoWayKey: attr.twoWayKey,
242
246
  onDelete: attr.onDelete as any,
243
247
  side: attr.side as any,
244
- encrypted: (attr as any).encrypt,
248
+ encrypt: (attr as any).encrypt,
245
249
  format: (attr as any).format
246
250
  })),
247
251
  indexes: parsedCollection.indexes.map(idx => ({
@@ -253,6 +257,8 @@ export const loadYamlCollection = (filePath: string): CollectionCreate | null =>
253
257
  importDefs: parsedCollection.importDefs && Array.isArray(parsedCollection.importDefs) && parsedCollection.importDefs.length > 0 ? parsedCollection.importDefs : []
254
258
  };
255
259
 
260
+ const collection = CollectionCreateSchema.parse(collectionInput);
261
+
256
262
  return collection;
257
263
  } catch (error) {
258
264
  MessageFormatter.error(`Error loading YAML collection from ${filePath}`, error as Error, { prefix: "Config" });
@@ -10,8 +10,9 @@
10
10
  * 3. Fallback: Default to legacy mode for safety
11
11
  */
12
12
 
13
- import { logger } from '../shared/logging.js';
14
- import { MessageFormatter } from '../shared/messageFormatter.js';
13
+ import { logger } from '../shared/logging.js';
14
+ import { MessageFormatter } from '../shared/messageFormatter.js';
15
+ import { Client, Databases, TablesDB, Query } from 'node-appwrite';
15
16
 
16
17
  export type ApiMode = 'legacy' | 'tablesdb';
17
18
 
@@ -30,11 +31,11 @@ export interface VersionDetectionResult {
30
31
  * @param apiKey - API key for authentication
31
32
  * @returns Promise resolving to version detection result
32
33
  */
33
- export async function detectAppwriteVersion(
34
- endpoint: string,
35
- project: string,
36
- apiKey: string
37
- ): Promise<VersionDetectionResult> {
34
+ export async function detectAppwriteVersion(
35
+ endpoint: string,
36
+ project: string,
37
+ apiKey: string
38
+ ): Promise<VersionDetectionResult> {
38
39
  const startTime = Date.now();
39
40
  // Clean endpoint URL
40
41
  const cleanEndpoint = endpoint.replace(/\/$/, '');
@@ -62,199 +63,72 @@ export async function detectAppwriteVersion(
62
63
  };
63
64
  }
64
65
 
65
- // STEP 2: Only proceed with endpoint probe if version >= 1.8.0 or version unknown
66
- // Try primary detection method: TablesDB endpoint probe
67
- try {
68
- logger.debug('Attempting TablesDB endpoint probe', {
69
- endpoint: cleanEndpoint,
70
- serverVersion: serverVersion || 'unknown',
71
- operation: 'detectAppwriteVersion'
72
- });
73
-
74
- const probeStartTime = Date.now();
75
- const tablesDbResult = await probeTablesDbEndpoint(cleanEndpoint, project, apiKey);
76
- const probeDuration = Date.now() - probeStartTime;
77
-
78
- if (tablesDbResult.apiMode === 'tablesdb') {
79
- logger.info('TablesDB detected via endpoint probe', {
80
- endpoint: cleanEndpoint,
81
- detectionMethod: tablesDbResult.detectionMethod,
82
- confidence: tablesDbResult.confidence,
83
- probeDuration,
84
- totalDuration: Date.now() - startTime,
85
- operation: 'detectAppwriteVersion'
86
- });
87
- return tablesDbResult;
88
- }
89
- } catch (error) {
90
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
91
- MessageFormatter.warning(`TablesDB endpoint probe failed: ${errorMessage}`, { prefix: "Version Detection" });
92
- logger.warn('TablesDB endpoint probe failed', {
93
- endpoint: cleanEndpoint,
94
- error: errorMessage,
95
- operation: 'detectAppwriteVersion'
96
- });
97
- }
98
-
99
- // Try secondary detection method: SDK feature detection
100
- try {
101
- logger.debug('Attempting SDK capability probe', {
102
- endpoint: cleanEndpoint,
103
- operation: 'detectAppwriteVersion'
104
- });
105
-
106
- const sdkProbeStartTime = Date.now();
107
- const sdkResult = await probeSdkCapabilities();
108
- const sdkProbeDuration = Date.now() - sdkProbeStartTime;
109
-
110
- if (sdkResult.apiMode === 'tablesdb') {
111
- logger.info('TablesDB detected via SDK capability probe', {
112
- endpoint: cleanEndpoint,
113
- detectionMethod: sdkResult.detectionMethod,
114
- confidence: sdkResult.confidence,
115
- sdkProbeDuration,
116
- totalDuration: Date.now() - startTime,
117
- operation: 'detectAppwriteVersion'
118
- });
119
- return sdkResult;
120
- }
121
- } catch (error) {
122
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
123
- MessageFormatter.warning(`SDK capability probe failed: ${errorMessage}`, { prefix: "Version Detection" });
124
- logger.warn('SDK capability probe failed', {
125
- endpoint: cleanEndpoint,
126
- error: errorMessage,
127
- operation: 'detectAppwriteVersion'
128
- });
129
- }
130
-
131
- // Fallback to legacy mode
132
- const fallbackResult = {
133
- apiMode: 'legacy' as ApiMode,
134
- detectionMethod: 'fallback' as const,
135
- confidence: 'low' as const
136
- };
66
+ // STEP 2: If version is unknown, use SDK probe (no fake HTTP endpoints)
67
+ try {
68
+ logger.debug('Attempting SDK-based TablesDB probe', {
69
+ endpoint: cleanEndpoint,
70
+ operation: 'detectAppwriteVersion'
71
+ });
72
+
73
+ const client = new Client().setEndpoint(cleanEndpoint).setProject(project);
74
+ if (apiKey && apiKey.trim().length > 0) client.setKey(apiKey);
75
+
76
+ const databases = new Databases(client);
77
+ // Try to get a database id to probe tables listing
78
+ let dbId: string | undefined;
79
+ try {
80
+ const dbList: any = await databases.list([Query.limit(1)]);
81
+ dbId = dbList?.databases?.[0]?.$id || dbList?.databases?.[0]?.id || dbList?.[0]?.$id;
82
+ } catch (e) {
83
+ // Ignore, we'll still attempt a conservative probe
84
+ logger.debug('Databases.list probe failed or returned no items', { operation: 'detectAppwriteVersion' });
85
+ }
86
+
87
+ const tables = new TablesDB(client);
88
+ if (dbId) {
89
+ // Probe listTables for the first database (limit 1)
90
+ await tables.listTables({ databaseId: dbId, queries: [Query.limit(1)] });
91
+ } else {
92
+ // No databases to probe; assume TablesDB available (cannot falsify-positively without a db)
93
+ logger.debug('No databases found to probe tables; assuming TablesDB if SDK available', { operation: 'detectAppwriteVersion' });
94
+ }
95
+
96
+ const result: VersionDetectionResult = {
97
+ apiMode: 'tablesdb',
98
+ detectionMethod: 'endpoint_probe', // repurpose label for SDK probe
99
+ confidence: 'medium',
100
+ serverVersion: serverVersion || undefined
101
+ };
102
+ logger.info('TablesDB detected via SDK probe', {
103
+ endpoint: cleanEndpoint,
104
+ result,
105
+ operation: 'detectAppwriteVersion'
106
+ });
107
+ return result;
108
+ } catch (error) {
109
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
110
+ logger.warn('SDK TablesDB probe failed; defaulting conservatively', {
111
+ endpoint: cleanEndpoint,
112
+ error: errorMessage,
113
+ operation: 'detectAppwriteVersion'
114
+ });
115
+ }
116
+
117
+ // Final fallback: default to tablesdb for modern environments when version unknown
118
+ const fallbackResult: VersionDetectionResult = {
119
+ apiMode: 'tablesdb',
120
+ detectionMethod: 'fallback',
121
+ confidence: 'low',
122
+ serverVersion: serverVersion || undefined
123
+ };
124
+ logger.info('Defaulting to TablesDB mode (fallback)', {
125
+ endpoint: cleanEndpoint,
126
+ result: fallbackResult,
127
+ operation: 'detectAppwriteVersion'
128
+ });
129
+ return fallbackResult;
130
+ }
137
131
 
138
- logger.info('Falling back to legacy mode', {
139
- endpoint: cleanEndpoint,
140
- totalDuration: Date.now() - startTime,
141
- result: fallbackResult,
142
- operation: 'detectAppwriteVersion'
143
- });
144
-
145
- return fallbackResult;
146
- }
147
-
148
- /**
149
- * Test TablesDB endpoint availability - most reliable detection method
150
- */
151
- async function probeTablesDbEndpoint(
152
- endpoint: string,
153
- project: string,
154
- apiKey: string
155
- ): Promise<VersionDetectionResult> {
156
- const startTime = Date.now();
157
- const url = `${endpoint}/tablesdb/`;
158
-
159
- logger.debug('Probing TablesDB endpoint', {
160
- url,
161
- project,
162
- operation: 'probeTablesDbEndpoint'
163
- });
164
-
165
- const response = await fetch(url, {
166
- method: 'GET',
167
- headers: {
168
- 'Content-Type': 'application/json',
169
- 'X-Appwrite-Project': project,
170
- 'X-Appwrite-Key': apiKey
171
- },
172
- // Short timeout for faster detection
173
- signal: AbortSignal.timeout(5000)
174
- });
175
-
176
- const duration = Date.now() - startTime;
177
-
178
- logger.debug('TablesDB endpoint response received', {
179
- url,
180
- status: response.status,
181
- statusText: response.statusText,
182
- duration,
183
- operation: 'probeTablesDbEndpoint'
184
- });
185
-
186
- if (response.ok) {
187
- // ONLY 200 OK means TablesDB available
188
- // 404 means endpoint doesn't exist (server < 1.8.0)
189
- const result = {
190
- apiMode: 'tablesdb' as ApiMode,
191
- detectionMethod: 'endpoint_probe' as const,
192
- confidence: 'high' as const
193
- };
194
-
195
- logger.info('TablesDB endpoint probe successful', {
196
- url,
197
- status: response.status,
198
- result,
199
- duration,
200
- operation: 'probeTablesDbEndpoint'
201
- });
202
-
203
- return result;
204
- }
205
-
206
- // 501 Not Implemented or other errors = no TablesDB support
207
- const error = new Error(`TablesDB endpoint returned ${response.status}: ${response.statusText}`);
208
- logger.debug('TablesDB endpoint probe failed', {
209
- url,
210
- status: response.status,
211
- statusText: response.statusText,
212
- duration,
213
- operation: 'probeTablesDbEndpoint'
214
- });
215
- throw error;
216
- }
217
-
218
- /**
219
- * SDK capability detection as secondary method
220
- */
221
- async function probeSdkCapabilities(): Promise<VersionDetectionResult> {
222
- try {
223
- // Try to import TablesDB SDK
224
- let TablesDBModule;
225
- try {
226
- TablesDBModule = await import('node-appwrite-tablesdb');
227
- } catch (importError) {
228
- // TablesDB SDK not available, will fall back to legacy
229
- }
230
-
231
- if (TablesDBModule?.TablesDB) {
232
- return {
233
- apiMode: 'tablesdb',
234
- detectionMethod: 'endpoint_probe',
235
- confidence: 'medium'
236
- };
237
- }
238
- } catch (error) {
239
- // TablesDB SDK not available, assume legacy
240
- }
241
-
242
- // Check for legacy SDK availability
243
- try {
244
- const { Databases } = await import('node-appwrite');
245
- if (Databases) {
246
- return {
247
- apiMode: 'legacy',
248
- detectionMethod: 'endpoint_probe',
249
- confidence: 'medium'
250
- };
251
- }
252
- } catch (error) {
253
- throw new Error('No Appwrite SDK available');
254
- }
255
-
256
- throw new Error('Unable to determine SDK capabilities');
257
- }
258
132
 
259
133
  /**
260
134
  * Cached version detection to avoid repeated API calls
@@ -381,35 +255,7 @@ export function isCloudAppwriteEndpoint(endpoint: string): boolean {
381
255
  * SDK feature detection as a fallback method
382
256
  * Attempts to dynamically import TablesDB to check availability
383
257
  */
384
- export async function detectSdkSupport(): Promise<{
385
- tablesDbAvailable: boolean;
386
- legacyAvailable: boolean;
387
- }> {
388
- const result = {
389
- tablesDbAvailable: false,
390
- legacyAvailable: false
391
- };
392
-
393
- // Test TablesDB SDK availability
394
- try {
395
- const tablesModule = await import('node-appwrite-tablesdb');
396
- if (tablesModule) {
397
- result.tablesDbAvailable = true;
398
- }
399
- } catch (error) {
400
- // TablesDB SDK not available
401
- }
402
-
403
- // Test legacy SDK availability
404
- try {
405
- await import('node-appwrite');
406
- result.legacyAvailable = true;
407
- } catch (error) {
408
- // Legacy SDK not available
409
- }
410
-
411
- return result;
412
- }
258
+ // Removed dynamic SDK capability checks to avoid confusion and side effects.
413
259
 
414
260
  /**
415
261
  * Clear version detection cache (useful for testing)