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
@@ -1,6 +1,6 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import {} from "appwrite-utils";
3
+ import { CollectionCreateSchema } from "appwrite-utils";
4
4
  import { register } from "tsx/esm/api";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import yaml from "js-yaml";
@@ -185,7 +185,7 @@ export const loadYamlCollection = (filePath) => {
185
185
  const yamlData = yaml.load(fileContent);
186
186
  const parsedCollection = YamlCollectionSchema.parse(yamlData);
187
187
  // Convert YAML collection to CollectionCreate format
188
- const collection = {
188
+ const collectionInput = {
189
189
  name: parsedCollection.name,
190
190
  $id: parsedCollection.id || parsedCollection.name.toLowerCase().replace(/\s+/g, '_'),
191
191
  documentSecurity: parsedCollection.documentSecurity,
@@ -210,7 +210,7 @@ export const loadYamlCollection = (filePath) => {
210
210
  twoWayKey: attr.twoWayKey,
211
211
  onDelete: attr.onDelete,
212
212
  side: attr.side,
213
- encrypted: attr.encrypt,
213
+ encrypt: attr.encrypt,
214
214
  format: attr.format
215
215
  })),
216
216
  indexes: parsedCollection.indexes.map(idx => ({
@@ -221,6 +221,7 @@ export const loadYamlCollection = (filePath) => {
221
221
  })),
222
222
  importDefs: parsedCollection.importDefs && Array.isArray(parsedCollection.importDefs) && parsedCollection.importDefs.length > 0 ? parsedCollection.importDefs : []
223
223
  };
224
+ const collection = CollectionCreateSchema.parse(collectionInput);
224
225
  return collection;
225
226
  }
226
227
  catch (error) {
@@ -46,10 +46,6 @@ export declare function isCloudAppwriteEndpoint(endpoint: string): boolean;
46
46
  * SDK feature detection as a fallback method
47
47
  * Attempts to dynamically import TablesDB to check availability
48
48
  */
49
- export declare function detectSdkSupport(): Promise<{
50
- tablesDbAvailable: boolean;
51
- legacyAvailable: boolean;
52
- }>;
53
49
  /**
54
50
  * Clear version detection cache (useful for testing)
55
51
  */
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { logger } from '../shared/logging.js';
13
13
  import { MessageFormatter } from '../shared/messageFormatter.js';
14
+ import { Client, Databases, TablesDB, Query } from 'node-appwrite';
14
15
  /**
15
16
  * Detects Appwrite API version and TablesDB support
16
17
  *
@@ -43,179 +44,70 @@ export async function detectAppwriteVersion(endpoint, project, apiKey) {
43
44
  confidence: 'high'
44
45
  };
45
46
  }
46
- // STEP 2: Only proceed with endpoint probe if version >= 1.8.0 or version unknown
47
- // Try primary detection method: TablesDB endpoint probe
47
+ // STEP 2: If version is unknown, use SDK probe (no fake HTTP endpoints)
48
48
  try {
49
- logger.debug('Attempting TablesDB endpoint probe', {
49
+ logger.debug('Attempting SDK-based TablesDB probe', {
50
50
  endpoint: cleanEndpoint,
51
- serverVersion: serverVersion || 'unknown',
52
51
  operation: 'detectAppwriteVersion'
53
52
  });
54
- const probeStartTime = Date.now();
55
- const tablesDbResult = await probeTablesDbEndpoint(cleanEndpoint, project, apiKey);
56
- const probeDuration = Date.now() - probeStartTime;
57
- if (tablesDbResult.apiMode === 'tablesdb') {
58
- logger.info('TablesDB detected via endpoint probe', {
59
- endpoint: cleanEndpoint,
60
- detectionMethod: tablesDbResult.detectionMethod,
61
- confidence: tablesDbResult.confidence,
62
- probeDuration,
63
- totalDuration: Date.now() - startTime,
64
- operation: 'detectAppwriteVersion'
65
- });
66
- return tablesDbResult;
53
+ const client = new Client().setEndpoint(cleanEndpoint).setProject(project);
54
+ if (apiKey && apiKey.trim().length > 0)
55
+ client.setKey(apiKey);
56
+ const databases = new Databases(client);
57
+ // Try to get a database id to probe tables listing
58
+ let dbId;
59
+ try {
60
+ const dbList = await databases.list([Query.limit(1)]);
61
+ dbId = dbList?.databases?.[0]?.$id || dbList?.databases?.[0]?.id || dbList?.[0]?.$id;
67
62
  }
68
- }
69
- catch (error) {
70
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
71
- MessageFormatter.warning(`TablesDB endpoint probe failed: ${errorMessage}`, { prefix: "Version Detection" });
72
- logger.warn('TablesDB endpoint probe failed', {
73
- endpoint: cleanEndpoint,
74
- error: errorMessage,
75
- operation: 'detectAppwriteVersion'
76
- });
77
- }
78
- // Try secondary detection method: SDK feature detection
79
- try {
80
- logger.debug('Attempting SDK capability probe', {
63
+ catch (e) {
64
+ // Ignore, we'll still attempt a conservative probe
65
+ logger.debug('Databases.list probe failed or returned no items', { operation: 'detectAppwriteVersion' });
66
+ }
67
+ const tables = new TablesDB(client);
68
+ if (dbId) {
69
+ // Probe listTables for the first database (limit 1)
70
+ await tables.listTables({ databaseId: dbId, queries: [Query.limit(1)] });
71
+ }
72
+ else {
73
+ // No databases to probe; assume TablesDB available (cannot falsify-positively without a db)
74
+ logger.debug('No databases found to probe tables; assuming TablesDB if SDK available', { operation: 'detectAppwriteVersion' });
75
+ }
76
+ const result = {
77
+ apiMode: 'tablesdb',
78
+ detectionMethod: 'endpoint_probe', // repurpose label for SDK probe
79
+ confidence: 'medium',
80
+ serverVersion: serverVersion || undefined
81
+ };
82
+ logger.info('TablesDB detected via SDK probe', {
81
83
  endpoint: cleanEndpoint,
84
+ result,
82
85
  operation: 'detectAppwriteVersion'
83
86
  });
84
- const sdkProbeStartTime = Date.now();
85
- const sdkResult = await probeSdkCapabilities();
86
- const sdkProbeDuration = Date.now() - sdkProbeStartTime;
87
- if (sdkResult.apiMode === 'tablesdb') {
88
- logger.info('TablesDB detected via SDK capability probe', {
89
- endpoint: cleanEndpoint,
90
- detectionMethod: sdkResult.detectionMethod,
91
- confidence: sdkResult.confidence,
92
- sdkProbeDuration,
93
- totalDuration: Date.now() - startTime,
94
- operation: 'detectAppwriteVersion'
95
- });
96
- return sdkResult;
97
- }
87
+ return result;
98
88
  }
99
89
  catch (error) {
100
90
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
101
- MessageFormatter.warning(`SDK capability probe failed: ${errorMessage}`, { prefix: "Version Detection" });
102
- logger.warn('SDK capability probe failed', {
91
+ logger.warn('SDK TablesDB probe failed; defaulting conservatively', {
103
92
  endpoint: cleanEndpoint,
104
93
  error: errorMessage,
105
94
  operation: 'detectAppwriteVersion'
106
95
  });
107
96
  }
108
- // Fallback to legacy mode
97
+ // Final fallback: default to tablesdb for modern environments when version unknown
109
98
  const fallbackResult = {
110
- apiMode: 'legacy',
99
+ apiMode: 'tablesdb',
111
100
  detectionMethod: 'fallback',
112
- confidence: 'low'
101
+ confidence: 'low',
102
+ serverVersion: serverVersion || undefined
113
103
  };
114
- logger.info('Falling back to legacy mode', {
104
+ logger.info('Defaulting to TablesDB mode (fallback)', {
115
105
  endpoint: cleanEndpoint,
116
- totalDuration: Date.now() - startTime,
117
106
  result: fallbackResult,
118
107
  operation: 'detectAppwriteVersion'
119
108
  });
120
109
  return fallbackResult;
121
110
  }
122
- /**
123
- * Test TablesDB endpoint availability - most reliable detection method
124
- */
125
- async function probeTablesDbEndpoint(endpoint, project, apiKey) {
126
- const startTime = Date.now();
127
- const url = `${endpoint}/tablesdb/`;
128
- logger.debug('Probing TablesDB endpoint', {
129
- url,
130
- project,
131
- operation: 'probeTablesDbEndpoint'
132
- });
133
- const response = await fetch(url, {
134
- method: 'GET',
135
- headers: {
136
- 'Content-Type': 'application/json',
137
- 'X-Appwrite-Project': project,
138
- 'X-Appwrite-Key': apiKey
139
- },
140
- // Short timeout for faster detection
141
- signal: AbortSignal.timeout(5000)
142
- });
143
- const duration = Date.now() - startTime;
144
- logger.debug('TablesDB endpoint response received', {
145
- url,
146
- status: response.status,
147
- statusText: response.statusText,
148
- duration,
149
- operation: 'probeTablesDbEndpoint'
150
- });
151
- if (response.ok) {
152
- // ONLY 200 OK means TablesDB available
153
- // 404 means endpoint doesn't exist (server < 1.8.0)
154
- const result = {
155
- apiMode: 'tablesdb',
156
- detectionMethod: 'endpoint_probe',
157
- confidence: 'high'
158
- };
159
- logger.info('TablesDB endpoint probe successful', {
160
- url,
161
- status: response.status,
162
- result,
163
- duration,
164
- operation: 'probeTablesDbEndpoint'
165
- });
166
- return result;
167
- }
168
- // 501 Not Implemented or other errors = no TablesDB support
169
- const error = new Error(`TablesDB endpoint returned ${response.status}: ${response.statusText}`);
170
- logger.debug('TablesDB endpoint probe failed', {
171
- url,
172
- status: response.status,
173
- statusText: response.statusText,
174
- duration,
175
- operation: 'probeTablesDbEndpoint'
176
- });
177
- throw error;
178
- }
179
- /**
180
- * SDK capability detection as secondary method
181
- */
182
- async function probeSdkCapabilities() {
183
- try {
184
- // Try to import TablesDB SDK
185
- let TablesDBModule;
186
- try {
187
- TablesDBModule = await import('node-appwrite-tablesdb');
188
- }
189
- catch (importError) {
190
- // TablesDB SDK not available, will fall back to legacy
191
- }
192
- if (TablesDBModule?.TablesDB) {
193
- return {
194
- apiMode: 'tablesdb',
195
- detectionMethod: 'endpoint_probe',
196
- confidence: 'medium'
197
- };
198
- }
199
- }
200
- catch (error) {
201
- // TablesDB SDK not available, assume legacy
202
- }
203
- // Check for legacy SDK availability
204
- try {
205
- const { Databases } = await import('node-appwrite');
206
- if (Databases) {
207
- return {
208
- apiMode: 'legacy',
209
- detectionMethod: 'endpoint_probe',
210
- confidence: 'medium'
211
- };
212
- }
213
- }
214
- catch (error) {
215
- throw new Error('No Appwrite SDK available');
216
- }
217
- throw new Error('Unable to determine SDK capabilities');
218
- }
219
111
  /**
220
112
  * Cached version detection to avoid repeated API calls
221
113
  */
@@ -320,31 +212,7 @@ export function isCloudAppwriteEndpoint(endpoint) {
320
212
  * SDK feature detection as a fallback method
321
213
  * Attempts to dynamically import TablesDB to check availability
322
214
  */
323
- export async function detectSdkSupport() {
324
- const result = {
325
- tablesDbAvailable: false,
326
- legacyAvailable: false
327
- };
328
- // Test TablesDB SDK availability
329
- try {
330
- const tablesModule = await import('node-appwrite-tablesdb');
331
- if (tablesModule) {
332
- result.tablesDbAvailable = true;
333
- }
334
- }
335
- catch (error) {
336
- // TablesDB SDK not available
337
- }
338
- // Test legacy SDK availability
339
- try {
340
- await import('node-appwrite');
341
- result.legacyAvailable = true;
342
- }
343
- catch (error) {
344
- // Legacy SDK not available
345
- }
346
- return result;
347
- }
215
+ // Removed dynamic SDK capability checks to avoid confusion and side effects.
348
216
  /**
349
217
  * Clear version detection cache (useful for testing)
350
218
  */
@@ -1,6 +1,86 @@
1
1
  import yaml from "js-yaml";
2
- // Threshold for treating min/max values as undefined (1 trillion)
3
- const MIN_MAX_THRESHOLD = 1_000_000_000_000;
2
+ import { Decimal } from "decimal.js";
3
+ // Extreme values that Appwrite may return, which should be treated as undefined
4
+ const EXTREME_MIN_INTEGER = -9223372036854776000;
5
+ const EXTREME_MAX_INTEGER = 9223372036854776000;
6
+ const EXTREME_MIN_FLOAT = -1.7976931348623157e+308;
7
+ const EXTREME_MAX_FLOAT = 1.7976931348623157e+308;
8
+ /**
9
+ * Type guard to check if an attribute has min/max properties
10
+ */
11
+ const hasMinMaxProperties = (yamlAttr) => {
12
+ return yamlAttr.type === 'integer' || yamlAttr.type === 'double' || yamlAttr.type === 'float';
13
+ };
14
+ /**
15
+ * Normalizes min/max values for integer and float attributes using Decimal.js for precision
16
+ * Validates that min < max and handles extreme database values
17
+ */
18
+ const normalizeMinMaxValues = (yamlAttr) => {
19
+ if (!hasMinMaxProperties(yamlAttr)) {
20
+ return {};
21
+ }
22
+ const { type, min, max, key } = yamlAttr;
23
+ let normalizedMin = min;
24
+ let normalizedMax = max;
25
+ // Handle min value - only filter out extreme database values
26
+ if (normalizedMin !== undefined && normalizedMin !== null) {
27
+ const minValue = Number(normalizedMin);
28
+ const originalMin = normalizedMin;
29
+ // Check if it's an extreme database value (but don't filter out large numbers)
30
+ if (type === 'integer') {
31
+ if (minValue === EXTREME_MIN_INTEGER) {
32
+ console.debug(`Min value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
33
+ normalizedMin = undefined;
34
+ }
35
+ }
36
+ else { // float/double
37
+ if (minValue === EXTREME_MIN_FLOAT) {
38
+ console.debug(`Min value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
39
+ normalizedMin = undefined;
40
+ }
41
+ }
42
+ }
43
+ // Handle max value - only filter out extreme database values
44
+ if (normalizedMax !== undefined && normalizedMax !== null) {
45
+ const maxValue = Number(normalizedMax);
46
+ const originalMax = normalizedMax;
47
+ // Check if it's an extreme database value (but don't filter out large numbers)
48
+ if (type === 'integer') {
49
+ if (maxValue === EXTREME_MAX_INTEGER) {
50
+ console.debug(`Max value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
51
+ normalizedMax = undefined;
52
+ }
53
+ }
54
+ else { // float/double
55
+ if (maxValue === EXTREME_MAX_FLOAT) {
56
+ console.debug(`Max value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
57
+ normalizedMax = undefined;
58
+ }
59
+ }
60
+ }
61
+ // Validate that min < max using Decimal.js for safe comparison
62
+ if (normalizedMin !== undefined && normalizedMax !== undefined &&
63
+ normalizedMin !== null && normalizedMax !== null) {
64
+ try {
65
+ const minDecimal = new Decimal(normalizedMin.toString());
66
+ const maxDecimal = new Decimal(normalizedMax.toString());
67
+ if (minDecimal.greaterThanOrEqualTo(maxDecimal)) {
68
+ // Swap values to ensure min < max (graceful handling)
69
+ console.warn(`Swapping min/max values for attribute '${yamlAttr.key}' to fix validation: min (${normalizedMin}) must be less than max (${normalizedMax})`);
70
+ const temp = normalizedMin;
71
+ normalizedMin = normalizedMax;
72
+ normalizedMax = temp;
73
+ }
74
+ }
75
+ catch (error) {
76
+ console.error(`Error comparing min/max values for attribute '${yamlAttr.key}':`, error);
77
+ // If Decimal comparison fails, set both to undefined to avoid API errors
78
+ normalizedMin = undefined;
79
+ normalizedMax = undefined;
80
+ }
81
+ }
82
+ return { min: normalizedMin, max: normalizedMax };
83
+ };
4
84
  /**
5
85
  * Converts a Collection object to YAML format with proper schema reference
6
86
  * Supports both collection and table terminology based on configuration
@@ -51,20 +131,13 @@ export function collectionToYaml(collection, config = {
51
131
  }
52
132
  if ('xdefault' in attr && attr.xdefault !== undefined)
53
133
  yamlAttr.default = attr.xdefault;
54
- // Normalize min/max values - filter out extreme database values
55
- if ('min' in attr && attr.min !== undefined) {
56
- const minValue = Number(attr.min);
57
- // Only include min if it's within reasonable range (< 1 trillion)
58
- if (Math.abs(minValue) < MIN_MAX_THRESHOLD) {
59
- yamlAttr.min = attr.min;
60
- }
61
- }
62
- if ('max' in attr && attr.max !== undefined) {
63
- const maxValue = Number(attr.max);
64
- // Only include max if it's within reasonable range (< 1 trillion)
65
- if (Math.abs(maxValue) < MIN_MAX_THRESHOLD) {
66
- yamlAttr.max = attr.max;
67
- }
134
+ // Normalize min/max values using Decimal.js precision
135
+ if ('min' in attr || 'max' in attr) {
136
+ const { min, max } = normalizeMinMaxValues(attr);
137
+ if (min !== undefined)
138
+ yamlAttr.min = min;
139
+ if (max !== undefined)
140
+ yamlAttr.max = max;
68
141
  }
69
142
  if ('elements' in attr && attr.elements !== undefined)
70
143
  yamlAttr.elements = attr.elements;
@@ -1,5 +1,5 @@
1
1
  import { type YamlCollectionData, type YamlTerminologyConfig } from "./yamlConverter.js";
2
- import type { CollectionCreate } from "appwrite-utils";
2
+ import { type CollectionCreate } from "appwrite-utils";
3
3
  /**
4
4
  * Enhanced YAML loader with dual terminology support
5
5
  */
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import { logger } from "../shared/logging.js";
5
5
  import { normalizeYamlData, usesTableTerminology, convertTerminology } from "./yamlConverter.js";
6
+ import { CollectionCreateSchema, } from "appwrite-utils";
6
7
  /**
7
8
  * Enhanced YAML loader with dual terminology support
8
9
  */
@@ -99,7 +100,7 @@ export class YamlLoader {
99
100
  yamlToCollectionCreate(yamlData) {
100
101
  // Always normalize to ensure consistent attribute terminology
101
102
  const normalized = normalizeYamlData(yamlData);
102
- return {
103
+ const collectionInput = {
103
104
  name: normalized.name,
104
105
  $id: normalized.id || normalized.name.toLowerCase().replace(/\s+/g, '_'),
105
106
  enabled: normalized.enabled !== false,
@@ -123,7 +124,9 @@ export class YamlLoader {
123
124
  twoWay: attr.twoWay,
124
125
  twoWayKey: attr.twoWayKey,
125
126
  onDelete: attr.onDelete,
126
- side: attr.side
127
+ side: attr.side,
128
+ encrypt: attr.encrypt,
129
+ format: attr.format
127
130
  })) || [],
128
131
  indexes: normalized.indexes?.map(idx => ({
129
132
  key: idx.key,
@@ -133,6 +136,7 @@ export class YamlLoader {
133
136
  })) || [],
134
137
  importDefs: normalized.importDefs || []
135
138
  };
139
+ return CollectionCreateSchema.parse(collectionInput);
136
140
  }
137
141
  /**
138
142
  * Saves YAML data with specified terminology
@@ -7,7 +7,7 @@ import { AppwriteToX } from "./migrations/appwriteToX.js";
7
7
  import { ImportController } from "./migrations/importController.js";
8
8
  import { ImportDataActions } from "./migrations/importDataActions.js";
9
9
  import { ensureDatabasesExist, wipeOtherDatabases, ensureCollectionsExist, } from "./databases/setup.js";
10
- import { createOrUpdateCollections, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
10
+ import { createOrUpdateCollections, createOrUpdateCollectionsViaAdapter, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
11
11
  import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
12
12
  import { backupDatabase, ensureDatabaseConfigBucketsExist, wipeDocumentStorage, } from "./storage/methods.js";
13
13
  import path from "path";
@@ -172,13 +172,19 @@ export class UtilsController {
172
172
  this.appwriteServer = client;
173
173
  this.adapter = adapter;
174
174
  this.config = config;
175
+ // Update config.apiMode from adapter if it's auto or not set
176
+ if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
177
+ this.config.apiMode = adapter.getApiMode();
178
+ logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
179
+ }
175
180
  this.database = new Databases(this.appwriteServer);
176
181
  this.storage = new Storage(this.appwriteServer);
177
182
  this.config.appwriteClient = this.appwriteServer;
178
183
  // Log only on FIRST initialization to avoid spam
179
184
  if (!this.isInitialized) {
180
185
  const apiMode = adapter.getApiMode();
181
- MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, { prefix: "Adapter" });
186
+ const configApiMode = this.config.apiMode;
187
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
182
188
  this.isInitialized = true;
183
189
  }
184
190
  else {
@@ -443,7 +449,24 @@ export class UtilsController {
443
449
  await this.init();
444
450
  if (!this.database || !this.config)
445
451
  throw new Error("Database or config not initialized");
446
- await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
452
+ // Ensure apiMode is properly set from adapter
453
+ if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
454
+ this.config.apiMode = this.adapter.getApiMode();
455
+ logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
456
+ }
457
+ // Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
458
+ if (this.adapter) {
459
+ logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
460
+ prefix: "UtilsController",
461
+ apiMode: this.adapter.getApiMode()
462
+ });
463
+ await createOrUpdateCollectionsViaAdapter(this.adapter, database.$id, this.config, deletedCollections, collections);
464
+ }
465
+ else {
466
+ // Fallback if adapter is unavailable for some reason
467
+ logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
468
+ await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
469
+ }
447
470
  }
448
471
  async generateSchemas() {
449
472
  // Schema generation doesn't need Appwrite connection, just config
@@ -628,30 +651,44 @@ export class UtilsController {
628
651
  }
629
652
  }
630
653
  // PUSH OPERATION: Push local configuration to Appwrite
631
- // Build selected collections/tables from databaseSelections
632
- const selectedCollections = [];
654
+ // Build database-specific collection mappings from databaseSelections
655
+ const databaseCollectionsMap = new Map();
633
656
  // Get all collections/tables from config (they're at the root level, not nested in databases)
634
657
  const allCollections = this.config?.collections || this.config?.tables || [];
635
- // Collect all selected table IDs from all database selections
636
- const selectedTableIds = new Set();
658
+ // Create database-specific collection mapping to preserve relationships
637
659
  for (const dbSelection of databaseSelections) {
638
- for (const tableId of dbSelection.tableIds) {
639
- selectedTableIds.add(tableId);
640
- }
641
- }
642
- // Filter to only the selected table IDs
643
- for (const collection of allCollections) {
644
- const collectionId = collection.$id || collection.id;
645
- if (selectedTableIds.has(collectionId)) {
646
- selectedCollections.push(collection);
660
+ const collectionsForDatabase = [];
661
+ MessageFormatter.info(`Processing collections for database: ${dbSelection.databaseId}`, { prefix: "Controller" });
662
+ // Filter collections that were selected for THIS specific database
663
+ for (const collection of allCollections) {
664
+ const collectionId = collection.$id || collection.id;
665
+ // Check if this collection was selected for THIS database
666
+ if (dbSelection.tableIds.includes(collectionId)) {
667
+ collectionsForDatabase.push(collection);
668
+ MessageFormatter.info(` - Selected collection: ${collection.name || collectionId} for database ${dbSelection.databaseId}`, { prefix: "Controller" });
669
+ }
647
670
  }
671
+ databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
672
+ MessageFormatter.info(`Database ${dbSelection.databaseId}: ${collectionsForDatabase.length} collections selected`, { prefix: "Controller" });
648
673
  }
649
- MessageFormatter.info(`Pushing ${selectedCollections.length} selected tables/collections to Appwrite`, { prefix: "Controller" });
674
+ // Calculate total collections for logging
675
+ const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
676
+ .reduce((total, collections) => total + collections.length, 0);
677
+ MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables/collections to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
650
678
  // Ensure databases exist
651
679
  await this.ensureDatabasesExist(selectedDatabases);
652
680
  await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
653
- // Create/update ONLY the selected collections/tables
654
- await this.createOrUpdateCollectionsForDatabases(selectedDatabases, selectedCollections);
681
+ // Create/update collections with database-specific context
682
+ for (const database of selectedDatabases) {
683
+ const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
684
+ if (collectionsForThisDatabase.length > 0) {
685
+ MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} collections to database ${database.$id} (${database.name})`, { prefix: "Controller" });
686
+ await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
687
+ }
688
+ else {
689
+ MessageFormatter.info(`No collections selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
690
+ }
691
+ }
655
692
  MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
656
693
  }
657
694
  async syncDb(databases = [], collections = []) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "1.7.9",
4
+ "version": "1.8.2",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -39,10 +39,11 @@
39
39
  "@types/inquirer": "^9.0.8",
40
40
  "@types/json-schema": "^7.0.15",
41
41
  "@types/yargs": "^17.0.33",
42
- "appwrite-utils": "^1.6.1",
42
+ "appwrite-utils": "latest",
43
43
  "chalk": "^5.4.1",
44
44
  "cli-progress": "^3.12.0",
45
45
  "commander": "^12.1.0",
46
+ "decimal.js": "^10.6.0",
46
47
  "es-toolkit": "^1.39.4",
47
48
  "ignore": "^6.0.2",
48
49
  "inquirer": "^9.3.7",
@@ -50,8 +51,7 @@
50
51
  "jszip": "^3.10.1",
51
52
  "luxon": "^3.6.1",
52
53
  "nanostores": "^0.10.3",
53
- "node-appwrite": "^17",
54
- "node-appwrite-tablesdb": "npm:node-appwrite@^18.0.0",
54
+ "node-appwrite": "^20.2.1",
55
55
  "p-limit": "^6.2.0",
56
56
  "tar": "^7.4.3",
57
57
  "tsx": "^4.20.3",