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,8 +1,95 @@
|
|
|
1
1
|
import yaml from "js-yaml";
|
|
2
2
|
import type { Collection, CollectionCreate } from "appwrite-utils";
|
|
3
|
+
import { Decimal } from "decimal.js";
|
|
3
4
|
|
|
4
|
-
//
|
|
5
|
-
const
|
|
5
|
+
// Extreme values that Appwrite may return, which should be treated as undefined
|
|
6
|
+
const EXTREME_MIN_INTEGER = -9223372036854776000;
|
|
7
|
+
const EXTREME_MAX_INTEGER = 9223372036854776000;
|
|
8
|
+
const EXTREME_MIN_FLOAT = -1.7976931348623157e+308;
|
|
9
|
+
const EXTREME_MAX_FLOAT = 1.7976931348623157e+308;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Type guard to check if an attribute has min/max properties
|
|
13
|
+
*/
|
|
14
|
+
const hasMinMaxProperties = (yamlAttr: any): boolean => {
|
|
15
|
+
return yamlAttr.type === 'integer' || yamlAttr.type === 'double' || yamlAttr.type === 'float';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Normalizes min/max values for integer and float attributes using Decimal.js for precision
|
|
20
|
+
* Validates that min < max and handles extreme database values
|
|
21
|
+
*/
|
|
22
|
+
const normalizeMinMaxValues = (yamlAttr: any): { min?: number; max?: number } => {
|
|
23
|
+
if (!hasMinMaxProperties(yamlAttr)) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { type, min, max, key } = yamlAttr;
|
|
28
|
+
let normalizedMin = min;
|
|
29
|
+
let normalizedMax = max;
|
|
30
|
+
|
|
31
|
+
// Handle min value - only filter out extreme database values
|
|
32
|
+
if (normalizedMin !== undefined && normalizedMin !== null) {
|
|
33
|
+
const minValue = Number(normalizedMin);
|
|
34
|
+
const originalMin = normalizedMin;
|
|
35
|
+
|
|
36
|
+
// Check if it's an extreme database value (but don't filter out large numbers)
|
|
37
|
+
if (type === 'integer') {
|
|
38
|
+
if (minValue === EXTREME_MIN_INTEGER) {
|
|
39
|
+
console.debug(`Min value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
|
|
40
|
+
normalizedMin = undefined;
|
|
41
|
+
}
|
|
42
|
+
} else { // float/double
|
|
43
|
+
if (minValue === EXTREME_MIN_FLOAT) {
|
|
44
|
+
console.debug(`Min value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
|
|
45
|
+
normalizedMin = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle max value - only filter out extreme database values
|
|
51
|
+
if (normalizedMax !== undefined && normalizedMax !== null) {
|
|
52
|
+
const maxValue = Number(normalizedMax);
|
|
53
|
+
const originalMax = normalizedMax;
|
|
54
|
+
|
|
55
|
+
// Check if it's an extreme database value (but don't filter out large numbers)
|
|
56
|
+
if (type === 'integer') {
|
|
57
|
+
if (maxValue === EXTREME_MAX_INTEGER) {
|
|
58
|
+
console.debug(`Max value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
|
|
59
|
+
normalizedMax = undefined;
|
|
60
|
+
}
|
|
61
|
+
} else { // float/double
|
|
62
|
+
if (maxValue === EXTREME_MAX_FLOAT) {
|
|
63
|
+
console.debug(`Max value normalized to undefined for attribute '${yamlAttr.key}': extreme database value`);
|
|
64
|
+
normalizedMax = undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Validate that min < max using Decimal.js for safe comparison
|
|
70
|
+
if (normalizedMin !== undefined && normalizedMax !== undefined &&
|
|
71
|
+
normalizedMin !== null && normalizedMax !== null) {
|
|
72
|
+
try {
|
|
73
|
+
const minDecimal = new Decimal(normalizedMin.toString());
|
|
74
|
+
const maxDecimal = new Decimal(normalizedMax.toString());
|
|
75
|
+
|
|
76
|
+
if (minDecimal.greaterThanOrEqualTo(maxDecimal)) {
|
|
77
|
+
// Swap values to ensure min < max (graceful handling)
|
|
78
|
+
console.warn(`Swapping min/max values for attribute '${yamlAttr.key}' to fix validation: min (${normalizedMin}) must be less than max (${normalizedMax})`);
|
|
79
|
+
const temp = normalizedMin;
|
|
80
|
+
normalizedMin = normalizedMax;
|
|
81
|
+
normalizedMax = temp;
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(`Error comparing min/max values for attribute '${yamlAttr.key}':`, error);
|
|
85
|
+
// If Decimal comparison fails, set both to undefined to avoid API errors
|
|
86
|
+
normalizedMin = undefined;
|
|
87
|
+
normalizedMax = undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { min: normalizedMin, max: normalizedMax };
|
|
92
|
+
};
|
|
6
93
|
|
|
7
94
|
export interface YamlCollectionData {
|
|
8
95
|
name: string;
|
|
@@ -125,21 +212,11 @@ export function collectionToYaml(
|
|
|
125
212
|
|
|
126
213
|
if ('xdefault' in attr && attr.xdefault !== undefined) yamlAttr.default = attr.xdefault;
|
|
127
214
|
|
|
128
|
-
// Normalize min/max values
|
|
129
|
-
if ('min' in attr
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
yamlAttr.min = attr.min;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if ('max' in attr && attr.max !== undefined) {
|
|
138
|
-
const maxValue = Number(attr.max);
|
|
139
|
-
// Only include max if it's within reasonable range (< 1 trillion)
|
|
140
|
-
if (Math.abs(maxValue) < MIN_MAX_THRESHOLD) {
|
|
141
|
-
yamlAttr.max = attr.max;
|
|
142
|
-
}
|
|
215
|
+
// Normalize min/max values using Decimal.js precision
|
|
216
|
+
if ('min' in attr || 'max' in attr) {
|
|
217
|
+
const { min, max } = normalizeMinMaxValues(attr);
|
|
218
|
+
if (min !== undefined) yamlAttr.min = min;
|
|
219
|
+
if (max !== undefined) yamlAttr.max = max;
|
|
143
220
|
}
|
|
144
221
|
if ('elements' in attr && attr.elements !== undefined) yamlAttr.elements = attr.elements;
|
|
145
222
|
if ('relatedCollection' in attr && attr.relatedCollection !== undefined) yamlAttr.relatedCollection = attr.relatedCollection;
|
package/src/utils/yamlLoader.ts
CHANGED
|
@@ -9,7 +9,10 @@ import {
|
|
|
9
9
|
type YamlCollectionData,
|
|
10
10
|
type YamlTerminologyConfig
|
|
11
11
|
} from "./yamlConverter.js";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
CollectionCreateSchema,
|
|
14
|
+
type CollectionCreate,
|
|
15
|
+
} from "appwrite-utils";
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Enhanced YAML loader with dual terminology support
|
|
@@ -149,7 +152,7 @@ export class YamlLoader {
|
|
|
149
152
|
// Always normalize to ensure consistent attribute terminology
|
|
150
153
|
const normalized = normalizeYamlData(yamlData);
|
|
151
154
|
|
|
152
|
-
|
|
155
|
+
const collectionInput: CollectionCreate = {
|
|
153
156
|
name: normalized.name,
|
|
154
157
|
$id: normalized.id || normalized.name.toLowerCase().replace(/\s+/g, '_'),
|
|
155
158
|
enabled: normalized.enabled !== false,
|
|
@@ -173,7 +176,9 @@ export class YamlLoader {
|
|
|
173
176
|
twoWay: attr.twoWay,
|
|
174
177
|
twoWayKey: attr.twoWayKey,
|
|
175
178
|
onDelete: attr.onDelete as any,
|
|
176
|
-
side: attr.side as any
|
|
179
|
+
side: attr.side as any,
|
|
180
|
+
encrypt: (attr as any).encrypt,
|
|
181
|
+
format: (attr as any).format
|
|
177
182
|
})) || [],
|
|
178
183
|
indexes: normalized.indexes?.map(idx => ({
|
|
179
184
|
key: idx.key,
|
|
@@ -183,6 +188,8 @@ export class YamlLoader {
|
|
|
183
188
|
})) || [],
|
|
184
189
|
importDefs: normalized.importDefs || []
|
|
185
190
|
};
|
|
191
|
+
|
|
192
|
+
return CollectionCreateSchema.parse(collectionInput);
|
|
186
193
|
}
|
|
187
194
|
|
|
188
195
|
/**
|
|
@@ -361,4 +368,4 @@ export class YamlLoader {
|
|
|
361
368
|
*/
|
|
362
369
|
export function createYamlLoader(baseDirectory: string): YamlLoader {
|
|
363
370
|
return new YamlLoader(baseDirectory);
|
|
364
|
-
}
|
|
371
|
+
}
|
package/src/utilsController.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "./databases/setup.js";
|
|
28
28
|
import {
|
|
29
29
|
createOrUpdateCollections,
|
|
30
|
+
createOrUpdateCollectionsViaAdapter,
|
|
30
31
|
wipeDatabase,
|
|
31
32
|
generateSchemas,
|
|
32
33
|
fetchAllCollections,
|
|
@@ -273,6 +274,13 @@ export class UtilsController {
|
|
|
273
274
|
this.appwriteServer = client;
|
|
274
275
|
this.adapter = adapter;
|
|
275
276
|
this.config = config;
|
|
277
|
+
|
|
278
|
+
// Update config.apiMode from adapter if it's auto or not set
|
|
279
|
+
if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
|
|
280
|
+
this.config.apiMode = adapter.getApiMode();
|
|
281
|
+
logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
|
|
282
|
+
}
|
|
283
|
+
|
|
276
284
|
this.database = new Databases(this.appwriteServer);
|
|
277
285
|
this.storage = new Storage(this.appwriteServer);
|
|
278
286
|
this.config.appwriteClient = this.appwriteServer;
|
|
@@ -280,7 +288,8 @@ export class UtilsController {
|
|
|
280
288
|
// Log only on FIRST initialization to avoid spam
|
|
281
289
|
if (!this.isInitialized) {
|
|
282
290
|
const apiMode = adapter.getApiMode();
|
|
283
|
-
|
|
291
|
+
const configApiMode = this.config.apiMode;
|
|
292
|
+
MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
|
|
284
293
|
this.isInitialized = true;
|
|
285
294
|
} else {
|
|
286
295
|
logger.debug("Adapter reused from cache", { prefix: "UtilsController" });
|
|
@@ -601,22 +610,46 @@ export class UtilsController {
|
|
|
601
610
|
}
|
|
602
611
|
}
|
|
603
612
|
|
|
604
|
-
async createOrUpdateCollections(
|
|
605
|
-
database: Models.Database,
|
|
606
|
-
deletedCollections?: { collectionId: string; collectionName: string }[],
|
|
607
|
-
collections: Models.Collection[] = []
|
|
608
|
-
) {
|
|
613
|
+
async createOrUpdateCollections(
|
|
614
|
+
database: Models.Database,
|
|
615
|
+
deletedCollections?: { collectionId: string; collectionName: string }[],
|
|
616
|
+
collections: Models.Collection[] = []
|
|
617
|
+
) {
|
|
609
618
|
await this.init();
|
|
610
619
|
if (!this.database || !this.config)
|
|
611
620
|
throw new Error("Database or config not initialized");
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
this.config
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
621
|
+
|
|
622
|
+
// Ensure apiMode is properly set from adapter
|
|
623
|
+
if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
|
|
624
|
+
this.config.apiMode = this.adapter.getApiMode();
|
|
625
|
+
logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
|
|
629
|
+
if (this.adapter) {
|
|
630
|
+
logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
|
|
631
|
+
prefix: "UtilsController",
|
|
632
|
+
apiMode: this.adapter.getApiMode()
|
|
633
|
+
});
|
|
634
|
+
await createOrUpdateCollectionsViaAdapter(
|
|
635
|
+
this.adapter,
|
|
636
|
+
database.$id,
|
|
637
|
+
this.config,
|
|
638
|
+
deletedCollections,
|
|
639
|
+
collections
|
|
640
|
+
);
|
|
641
|
+
} else {
|
|
642
|
+
// Fallback if adapter is unavailable for some reason
|
|
643
|
+
logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
|
|
644
|
+
await createOrUpdateCollections(
|
|
645
|
+
this.database,
|
|
646
|
+
database.$id,
|
|
647
|
+
this.config,
|
|
648
|
+
deletedCollections,
|
|
649
|
+
collections
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
620
653
|
|
|
621
654
|
async generateSchemas() {
|
|
622
655
|
// Schema generation doesn't need Appwrite connection, just config
|
|
@@ -863,36 +896,53 @@ export class UtilsController {
|
|
|
863
896
|
}
|
|
864
897
|
|
|
865
898
|
// PUSH OPERATION: Push local configuration to Appwrite
|
|
866
|
-
// Build
|
|
867
|
-
const
|
|
899
|
+
// Build database-specific collection mappings from databaseSelections
|
|
900
|
+
const databaseCollectionsMap = new Map<string, any[]>();
|
|
868
901
|
|
|
869
902
|
// Get all collections/tables from config (they're at the root level, not nested in databases)
|
|
870
903
|
const allCollections = this.config?.collections || this.config?.tables || [];
|
|
871
904
|
|
|
872
|
-
//
|
|
873
|
-
const selectedTableIds = new Set<string>();
|
|
905
|
+
// Create database-specific collection mapping to preserve relationships
|
|
874
906
|
for (const dbSelection of databaseSelections) {
|
|
875
|
-
|
|
876
|
-
selectedTableIds.add(tableId);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
907
|
+
const collectionsForDatabase: any[] = [];
|
|
879
908
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
909
|
+
MessageFormatter.info(`Processing collections for database: ${dbSelection.databaseId}`, { prefix: "Controller" });
|
|
910
|
+
|
|
911
|
+
// Filter collections that were selected for THIS specific database
|
|
912
|
+
for (const collection of allCollections) {
|
|
913
|
+
const collectionId = collection.$id || (collection as any).id;
|
|
914
|
+
|
|
915
|
+
// Check if this collection was selected for THIS database
|
|
916
|
+
if (dbSelection.tableIds.includes(collectionId)) {
|
|
917
|
+
collectionsForDatabase.push(collection);
|
|
918
|
+
MessageFormatter.info(` - Selected collection: ${collection.name || collectionId} for database ${dbSelection.databaseId}`, { prefix: "Controller" });
|
|
919
|
+
}
|
|
885
920
|
}
|
|
921
|
+
|
|
922
|
+
databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
|
|
923
|
+
MessageFormatter.info(`Database ${dbSelection.databaseId}: ${collectionsForDatabase.length} collections selected`, { prefix: "Controller" });
|
|
886
924
|
}
|
|
887
925
|
|
|
888
|
-
|
|
926
|
+
// Calculate total collections for logging
|
|
927
|
+
const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
|
|
928
|
+
.reduce((total, collections) => total + collections.length, 0);
|
|
929
|
+
|
|
930
|
+
MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables/collections to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
|
|
889
931
|
|
|
890
932
|
// Ensure databases exist
|
|
891
933
|
await this.ensureDatabasesExist(selectedDatabases);
|
|
892
934
|
await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
|
|
893
935
|
|
|
894
|
-
// Create/update
|
|
895
|
-
|
|
936
|
+
// Create/update collections with database-specific context
|
|
937
|
+
for (const database of selectedDatabases) {
|
|
938
|
+
const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
|
|
939
|
+
if (collectionsForThisDatabase.length > 0) {
|
|
940
|
+
MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} collections to database ${database.$id} (${database.name})`, { prefix: "Controller" });
|
|
941
|
+
await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
|
|
942
|
+
} else {
|
|
943
|
+
MessageFormatter.info(`No collections selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
|
|
944
|
+
}
|
|
945
|
+
}
|
|
896
946
|
|
|
897
947
|
MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
|
|
898
948
|
}
|