appwrite-utils-cli 1.6.4 → 1.6.6
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/dist/collections/indexes.js +36 -9
- package/dist/collections/methods.js +11 -7
- package/dist/main.js +12 -17
- package/dist/setupCommands.d.ts +57 -0
- package/dist/setupCommands.js +484 -1
- package/package.json +1 -1
- package/src/collections/indexes.ts +45 -15
- package/src/collections/methods.ts +22 -8
- package/src/main.ts +12 -22
- package/src/setupCommands.ts +597 -0
@@ -3,6 +3,8 @@ import { Databases, IndexType, Query } from "node-appwrite";
|
|
3
3
|
import { delay, tryAwaitWithRetry, calculateExponentialBackoff } from "../utils/helperFunctions.js";
|
4
4
|
import { isLegacyDatabases } from "../utils/typeGuards.js";
|
5
5
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
6
|
+
// System attributes that are always available for indexing in Appwrite
|
7
|
+
const SYSTEM_ATTRIBUTES = ['$id', '$createdAt', '$updatedAt', '$permissions'];
|
6
8
|
/**
|
7
9
|
* Wait for index to become available, with retry logic for stuck indexes and exponential backoff
|
8
10
|
*/
|
@@ -72,10 +74,12 @@ export const createOrUpdateIndexWithStatusCheck = async (dbId, db, collectionId,
|
|
72
74
|
// First, validate that all required attributes exist
|
73
75
|
const freshCollection = await db.getCollection(dbId, collectionId);
|
74
76
|
const existingAttributeKeys = freshCollection.attributes.map((attr) => attr.key);
|
75
|
-
|
77
|
+
// Include system attributes that are always available
|
78
|
+
const allAvailableAttributes = [...existingAttributeKeys, ...SYSTEM_ATTRIBUTES];
|
79
|
+
const missingAttributes = index.attributes.filter(attr => !allAvailableAttributes.includes(attr));
|
76
80
|
if (missingAttributes.length > 0) {
|
77
81
|
MessageFormatter.error(`Index '${index.key}' cannot be created: missing attributes [${missingAttributes.join(', ')}] (type: ${index.type})`);
|
78
|
-
MessageFormatter.error(`Available attributes: [${existingAttributeKeys.join(', ')}]`);
|
82
|
+
MessageFormatter.error(`Available attributes: [${existingAttributeKeys.join(', ')}, ${SYSTEM_ATTRIBUTES.join(', ')}]`);
|
79
83
|
return false; // Don't retry if attributes are missing
|
80
84
|
}
|
81
85
|
// Try to create/update the index using existing logic
|
@@ -169,15 +173,38 @@ export const createOrUpdateIndex = async (dbId, db, collectionId, index) => {
|
|
169
173
|
// No existing index, create it
|
170
174
|
createIndex = true;
|
171
175
|
}
|
172
|
-
else
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
176
|
+
else {
|
177
|
+
const existing = existingIndex.indexes[0];
|
178
|
+
// Check key and type
|
179
|
+
const keyMatches = existing.key === index.key;
|
180
|
+
const typeMatches = existing.type === index.type;
|
181
|
+
// Compare attributes as SETS (order doesn't matter, only content)
|
182
|
+
const existingAttrsSet = new Set(existing.attributes);
|
183
|
+
const newAttrsSet = new Set(index.attributes);
|
184
|
+
const attributesMatch = existingAttrsSet.size === newAttrsSet.size &&
|
185
|
+
[...existingAttrsSet].every(attr => newAttrsSet.has(attr));
|
186
|
+
// Compare orders as SETS if both exist (order doesn't matter)
|
187
|
+
let ordersMatch = true;
|
188
|
+
if (index.orders && existing.orders) {
|
189
|
+
const existingOrdersSet = new Set(existing.orders);
|
190
|
+
const newOrdersSet = new Set(index.orders);
|
191
|
+
ordersMatch =
|
192
|
+
existingOrdersSet.size === newOrdersSet.size &&
|
193
|
+
[...existingOrdersSet].every(ord => newOrdersSet.has(ord));
|
194
|
+
}
|
195
|
+
// Only recreate if something genuinely changed
|
196
|
+
if (!keyMatches || !typeMatches || !attributesMatch || !ordersMatch) {
|
197
|
+
await db.deleteIndex(dbId, collectionId, existing.key);
|
198
|
+
createIndex = true;
|
199
|
+
}
|
178
200
|
}
|
179
201
|
if (createIndex) {
|
180
|
-
|
202
|
+
// Ensure orders array exists and matches attributes length
|
203
|
+
// Default to "asc" for each attribute if not specified
|
204
|
+
const orders = index.orders && index.orders.length === index.attributes.length
|
205
|
+
? index.orders
|
206
|
+
: index.attributes.map(() => "asc");
|
207
|
+
newIndex = await db.createIndex(dbId, collectionId, index.key, index.type, index.attributes, orders);
|
181
208
|
}
|
182
209
|
return newIndex;
|
183
210
|
};
|
@@ -68,7 +68,8 @@ export const checkForCollection = async (db, dbId, collection) => {
|
|
68
68
|
const items = isLegacyDatabases(db) ? response.collections : (response.tables || response.collections);
|
69
69
|
if (items && items.length > 0) {
|
70
70
|
MessageFormatter.info(`Collection found: ${items[0].$id}`, { prefix: "Collections" });
|
71
|
-
|
71
|
+
// Return remote collection for update operations (don't merge local config over it)
|
72
|
+
return items[0];
|
72
73
|
}
|
73
74
|
else {
|
74
75
|
MessageFormatter.info(`No collection found with name: ${collection.name}`, { prefix: "Collections" });
|
@@ -223,12 +224,14 @@ export const createOrUpdateCollections = async (database, databaseId, config, de
|
|
223
224
|
attributes);
|
224
225
|
// Add delay after creating attributes
|
225
226
|
await delay(250);
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
?.indexes ?? [];
|
227
|
+
// ALWAYS use indexes from local config, NEVER from server
|
228
|
+
const localCollectionConfig = config.collections?.find(c => c.name === collectionData.name || c.$id === collectionData.$id);
|
229
|
+
const indexesToUse = localCollectionConfig?.indexes || [];
|
230
230
|
MessageFormatter.progress("Creating Indexes", { prefix: "Collections" });
|
231
231
|
await createOrUpdateIndexesWithStatusCheck(databaseId, database, collectionToUse.$id, collectionToUse, indexesToUse);
|
232
|
+
// Delete indexes that exist on server but not in local config
|
233
|
+
const { deleteObsoleteIndexes } = await import('../shared/indexManager.js');
|
234
|
+
await deleteObsoleteIndexes(database, databaseId, collectionToUse, { indexes: indexesToUse }, { verbose: true });
|
232
235
|
// Mark this collection as fully processed to prevent re-processing
|
233
236
|
markCollectionProcessed(collectionToUse.$id, collectionData.name);
|
234
237
|
// Add delay after creating indexes
|
@@ -425,8 +428,9 @@ export const createOrUpdateCollectionsViaAdapter = async (adapter, databaseId, c
|
|
425
428
|
}
|
426
429
|
}
|
427
430
|
}
|
428
|
-
//
|
429
|
-
const
|
431
|
+
// ALWAYS use indexes from local config, NEVER from server (TablesDB path)
|
432
|
+
const localTableConfig = config.collections?.find(c => c.name === collectionData.name || c.$id === collectionData.$id);
|
433
|
+
const idxs = (localTableConfig?.indexes || []);
|
430
434
|
for (const idx of idxs) {
|
431
435
|
try {
|
432
436
|
await adapter.createIndex({
|
package/dist/main.js
CHANGED
@@ -697,24 +697,19 @@ async function main() {
|
|
697
697
|
operationStats.wipedBuckets = wipeStats.buckets;
|
698
698
|
}
|
699
699
|
}
|
700
|
-
if (parsedArgv.push
|
700
|
+
if (parsedArgv.push) {
|
701
|
+
// PUSH: Use LOCAL config collections only (pass empty array to use config.collections)
|
701
702
|
const databases = options.databases || (await fetchAllDatabases(controller.database));
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
operationStats.pushedCollections = collections.length;
|
713
|
-
}
|
714
|
-
else if (parsedArgv.sync) {
|
715
|
-
await controller.synchronizeConfigurations(databases);
|
716
|
-
operationStats.syncedDatabases = databases.length;
|
717
|
-
}
|
703
|
+
// Pass empty array - syncDb will use config.collections (local schema)
|
704
|
+
await controller.syncDb(databases, []);
|
705
|
+
operationStats.pushedDatabases = databases.length;
|
706
|
+
operationStats.pushedCollections = controller.config?.collections?.length || 0;
|
707
|
+
}
|
708
|
+
else if (parsedArgv.sync) {
|
709
|
+
// SYNC: Pull from remote
|
710
|
+
const databases = options.databases || (await fetchAllDatabases(controller.database));
|
711
|
+
await controller.synchronizeConfigurations(databases);
|
712
|
+
operationStats.syncedDatabases = databases.length;
|
718
713
|
}
|
719
714
|
if (options.generateSchemas) {
|
720
715
|
await controller.generateSchemas();
|
package/dist/setupCommands.d.ts
CHANGED
@@ -1 +1,58 @@
|
|
1
|
+
import { type ApiMode } from "./utils/versionDetection.js";
|
2
|
+
/**
|
3
|
+
* Terminology configuration for API mode-specific naming
|
4
|
+
*/
|
5
|
+
interface TerminologyConfig {
|
6
|
+
container: "table" | "collection";
|
7
|
+
containerName: "Table" | "Collection";
|
8
|
+
fields: "columns" | "attributes";
|
9
|
+
fieldName: "Column" | "Attribute";
|
10
|
+
security: "rowSecurity" | "documentSecurity";
|
11
|
+
schemaRef: "table.schema.json" | "collection.schema.json";
|
12
|
+
items: "rows" | "documents";
|
13
|
+
}
|
14
|
+
/**
|
15
|
+
* Detection result with source information
|
16
|
+
*/
|
17
|
+
interface ApiModeDetectionResult {
|
18
|
+
apiMode: ApiMode;
|
19
|
+
useTables: boolean;
|
20
|
+
detectionSource: "appwrite.json" | "server-version" | "default";
|
21
|
+
serverVersion?: string;
|
22
|
+
}
|
23
|
+
/**
|
24
|
+
* Get terminology configuration based on API mode
|
25
|
+
*/
|
26
|
+
export declare function getTerminologyConfig(useTables: boolean): TerminologyConfig;
|
27
|
+
/**
|
28
|
+
* Detect API mode using multiple detection sources
|
29
|
+
* Priority: appwrite.json > server version > default (collections)
|
30
|
+
*/
|
31
|
+
export declare function detectApiMode(basePath: string): Promise<ApiModeDetectionResult>;
|
32
|
+
/**
|
33
|
+
* Create directory structure for Appwrite project
|
34
|
+
*/
|
35
|
+
export declare function createProjectDirectories(basePath: string, useTables: boolean): {
|
36
|
+
appwriteFolder: string;
|
37
|
+
containerFolder: string;
|
38
|
+
schemaFolder: string;
|
39
|
+
yamlSchemaFolder: string;
|
40
|
+
dataFolder: string;
|
41
|
+
};
|
42
|
+
/**
|
43
|
+
* Create example YAML schema file with correct terminology
|
44
|
+
*/
|
45
|
+
export declare function createExampleSchema(containerFolder: string, terminology: TerminologyConfig): string;
|
46
|
+
/**
|
47
|
+
* Create JSON schema for YAML validation
|
48
|
+
*/
|
49
|
+
export declare function createYamlValidationSchema(yamlSchemaFolder: string, useTables: boolean): string;
|
50
|
+
/**
|
51
|
+
* Initialize a new Appwrite project with correct directory structure and terminology
|
52
|
+
*/
|
53
|
+
export declare function initProject(basePath?: string, forceApiMode?: 'legacy' | 'tablesdb'): Promise<void>;
|
54
|
+
/**
|
55
|
+
* Create a new collection or table schema file
|
56
|
+
*/
|
57
|
+
export declare function createSchema(name: string, basePath?: string, forceApiMode?: 'legacy' | 'tablesdb'): Promise<void>;
|
1
58
|
export {};
|