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.
- package/CHANGELOG.md +14 -199
- package/README.md +87 -30
- package/dist/adapters/AdapterFactory.js +5 -25
- package/dist/adapters/DatabaseAdapter.d.ts +17 -2
- package/dist/adapters/LegacyAdapter.d.ts +2 -1
- package/dist/adapters/LegacyAdapter.js +212 -16
- package/dist/adapters/TablesDBAdapter.d.ts +2 -12
- package/dist/adapters/TablesDBAdapter.js +261 -57
- package/dist/cli/commands/databaseCommands.js +4 -3
- package/dist/cli/commands/functionCommands.js +17 -8
- package/dist/collections/attributes.js +447 -125
- package/dist/collections/methods.js +197 -186
- package/dist/collections/tableOperations.d.ts +86 -0
- package/dist/collections/tableOperations.js +434 -0
- package/dist/collections/transferOperations.d.ts +3 -2
- package/dist/collections/transferOperations.js +93 -12
- package/dist/config/yamlConfig.d.ts +221 -88
- package/dist/examples/yamlTerminologyExample.d.ts +1 -1
- package/dist/examples/yamlTerminologyExample.js +6 -3
- package/dist/functions/fnConfigDiscovery.d.ts +3 -0
- package/dist/functions/fnConfigDiscovery.js +108 -0
- package/dist/interactiveCLI.js +18 -15
- package/dist/main.js +211 -73
- package/dist/migrations/appwriteToX.d.ts +88 -23
- package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
- package/dist/migrations/comprehensiveTransfer.js +83 -6
- package/dist/migrations/dataLoader.d.ts +227 -69
- package/dist/migrations/dataLoader.js +3 -3
- package/dist/migrations/importController.js +3 -3
- package/dist/migrations/relationships.d.ts +8 -2
- package/dist/migrations/services/ImportOrchestrator.js +3 -3
- package/dist/migrations/transfer.js +159 -37
- package/dist/shared/attributeMapper.d.ts +20 -0
- package/dist/shared/attributeMapper.js +203 -0
- package/dist/shared/selectionDialogs.js +8 -4
- package/dist/storage/schemas.d.ts +354 -92
- package/dist/utils/configDiscovery.js +4 -3
- package/dist/utils/versionDetection.d.ts +0 -4
- package/dist/utils/versionDetection.js +41 -173
- package/dist/utils/yamlConverter.js +89 -16
- package/dist/utils/yamlLoader.d.ts +1 -1
- package/dist/utils/yamlLoader.js +6 -2
- package/dist/utilsController.js +56 -19
- package/package.json +4 -4
- package/src/adapters/AdapterFactory.ts +119 -143
- package/src/adapters/DatabaseAdapter.ts +18 -3
- package/src/adapters/LegacyAdapter.ts +236 -105
- package/src/adapters/TablesDBAdapter.ts +773 -643
- package/src/cli/commands/databaseCommands.ts +13 -12
- package/src/cli/commands/functionCommands.ts +23 -14
- package/src/collections/attributes.ts +2054 -1611
- package/src/collections/methods.ts +208 -293
- package/src/collections/tableOperations.ts +506 -0
- package/src/collections/transferOperations.ts +218 -144
- package/src/examples/yamlTerminologyExample.ts +10 -5
- package/src/functions/fnConfigDiscovery.ts +103 -0
- package/src/interactiveCLI.ts +25 -20
- package/src/main.ts +549 -194
- package/src/migrations/comprehensiveTransfer.ts +126 -50
- package/src/migrations/dataLoader.ts +3 -3
- package/src/migrations/importController.ts +3 -3
- package/src/migrations/services/ImportOrchestrator.ts +3 -3
- package/src/migrations/transfer.ts +148 -131
- package/src/shared/attributeMapper.ts +229 -0
- package/src/shared/selectionDialogs.ts +29 -25
- package/src/utils/configDiscovery.ts +9 -3
- package/src/utils/versionDetection.ts +74 -228
- package/src/utils/yamlConverter.ts +94 -17
- package/src/utils/yamlLoader.ts +11 -4
- 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
|
|
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
|
-
|
|
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:
|
|
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
|
|
49
|
+
logger.debug('Attempting SDK-based TablesDB probe', {
|
|
50
50
|
endpoint: cleanEndpoint,
|
|
51
|
-
serverVersion: serverVersion || 'unknown',
|
|
52
51
|
operation: 'detectAppwriteVersion'
|
|
53
52
|
});
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
97
|
+
// Final fallback: default to tablesdb for modern environments when version unknown
|
|
109
98
|
const fallbackResult = {
|
|
110
|
-
apiMode: '
|
|
99
|
+
apiMode: 'tablesdb',
|
|
111
100
|
detectionMethod: 'fallback',
|
|
112
|
-
confidence: 'low'
|
|
101
|
+
confidence: 'low',
|
|
102
|
+
serverVersion: serverVersion || undefined
|
|
113
103
|
};
|
|
114
|
-
logger.info('
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
|
55
|
-
if ('min' in attr
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
2
|
+
import { type CollectionCreate } from "appwrite-utils";
|
|
3
3
|
/**
|
|
4
4
|
* Enhanced YAML loader with dual terminology support
|
|
5
5
|
*/
|
package/dist/utils/yamlLoader.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/utilsController.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
632
|
-
const
|
|
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
|
-
//
|
|
636
|
-
const selectedTableIds = new Set();
|
|
658
|
+
// Create database-specific collection mapping to preserve relationships
|
|
637
659
|
for (const dbSelection of databaseSelections) {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
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
|
|
654
|
-
|
|
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.
|
|
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": "
|
|
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": "^
|
|
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",
|