Skip to content

Commit 5aaa762

Browse files
committed
Fix various bugs
1 parent 917a1b9 commit 5aaa762

File tree

16 files changed

+598
-431
lines changed

16 files changed

+598
-431
lines changed

packages/appwrite-utils-cli/src/cli/commands/databaseCommands.ts

Lines changed: 185 additions & 155 deletions
Large diffs are not rendered by default.

packages/appwrite-utils-cli/src/collections/attributes.ts renamed to packages/appwrite-utils-cli/src/collections/columns.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const EXTREME_MAX_INTEGER = 9223372036854776000;
2626
const EXTREME_MIN_FLOAT = -1.7976931348623157e308;
2727
const EXTREME_MAX_FLOAT = 1.7976931348623157e308;
2828

29+
/** Get columns/attributes array, preferring modern 'columns' over legacy 'attributes' */
30+
function getColumns(obj: any): any[] {
31+
return obj.columns || obj.attributes || [];
32+
}
33+
2934
/**
3035
* Type guard to check if an attribute has min/max properties
3136
*/
@@ -833,7 +838,7 @@ const waitForAttributeAvailable = async (
833838
const collection = isDatabaseAdapter(db)
834839
? (await db.getTable({ databaseId: dbId, tableId: collectionId })).data
835840
: await db.getCollection(dbId, collectionId);
836-
const attribute = (collection.attributes as any[]).find(
841+
const attribute = (getColumns(collection) as any[]).find(
837842
(attr: AttributeWithStatus) => attr.key === attributeKey
838843
) as AttributeWithStatus | undefined;
839844

@@ -1449,7 +1454,7 @@ export const createOrUpdateAttribute = async (
14491454
const updateEnabled = true;
14501455
let finalAttribute: any = attribute;
14511456
try {
1452-
const collectionAttr = collection.attributes.find(
1457+
const collectionAttr = getColumns(collection).find(
14531458
(attr: any) => attr.key === attribute.key
14541459
) as unknown as any;
14551460
foundAttribute = parseAttribute(collectionAttr);
@@ -1478,9 +1483,11 @@ export const createOrUpdateAttribute = async (
14781483
await db.deleteAttribute(dbId, collection.$id, attribute.key);
14791484
}
14801485
// Remove from local collection metadata so downstream logic treats it as new
1481-
collection.attributes = collection.attributes.filter(
1486+
const filtered = getColumns(collection).filter(
14821487
(attr: any) => attr.key !== attribute.key
14831488
);
1489+
if ('columns' in collection) (collection as any).columns = filtered;
1490+
if ('attributes' in collection) (collection as any).attributes = filtered;
14841491
foundAttribute = undefined;
14851492
} catch (deleteError) {
14861493
MessageFormatter.error(
@@ -1715,7 +1722,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
17151722
attributes: Attribute[]
17161723
): Promise<boolean> => {
17171724
const existingAttributes: Attribute[] =
1718-
collection.attributes.map((attr) => parseAttribute(attr as any)) || [];
1725+
getColumns(collection).map((attr) => parseAttribute(attr as any)) || [];
17191726

17201727
const attributesToRemove = existingAttributes.filter(
17211728
(attr) => !attributes.some((a) => a.key === attr.key)
@@ -1935,7 +1942,7 @@ export const createUpdateCollectionAttributes = async (
19351942
);
19361943

19371944
const existingAttributes: Attribute[] =
1938-
collection.attributes.map((attr) => parseAttribute(attr as any)) || [];
1945+
getColumns(collection).map((attr) => parseAttribute(attr as any)) || [];
19391946

19401947
const attributesToRemove = existingAttributes.filter(
19411948
(attr) => !attributes.some((a) => a.key === attr.key)

packages/appwrite-utils-cli/src/collections/methods.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,21 @@ export const checkForCollection = async (
114114
collection: Partial<CollectionCreate>
115115
): Promise<Models.Collection | null> => {
116116
try {
117-
MessageFormatter.progress(`Checking for collection with name: ${collection.name}`, { prefix: "Collections" });
117+
const isLegacy = isLegacyDatabases(db);
118+
const entityType = isLegacy ? "Collection" : "Table";
119+
MessageFormatter.progress(`Checking for ${entityType.toLowerCase()} with name: ${collection.name}`, { prefix: entityType + "s" });
118120
const response = await tryAwaitWithRetry(
119-
async () => isLegacyDatabases(db) ?
121+
async () => isLegacy ?
120122
await db.listCollections(dbId, [Query.equal("name", collection.name!)]) :
121123
await db.listTables({ databaseId: dbId, queries: [Query.equal("name", collection.name!)] })
122124
);
123-
const items = isLegacyDatabases(db) ? response.collections : ((response as any).tables || response.collections);
125+
const items = isLegacy ? response.collections : ((response as any).tables || response.collections);
124126
if (items && items.length > 0) {
125-
MessageFormatter.info(`Collection found: ${items[0].$id}`, { prefix: "Collections" });
127+
MessageFormatter.info(`${entityType} found: ${items[0].$id}`, { prefix: entityType + "s" });
126128
// Return remote collection for update operations (don't merge local config over it)
127129
return items[0] as Models.Collection;
128130
} else {
129-
MessageFormatter.info(`No collection found with name: ${collection.name}`, { prefix: "Collections" });
131+
MessageFormatter.info(`No ${entityType.toLowerCase()} found with name: ${collection.name}`, { prefix: entityType + "s" });
130132
return null;
131133
}
132134
} catch (error) {
@@ -148,29 +150,31 @@ export const fetchAndCacheCollectionByName = async (
148150
dbId: string,
149151
collectionName: string
150152
): Promise<Models.Collection | undefined> => {
153+
const isLegacy = isLegacyDatabases(db);
154+
const entityType = isLegacy ? "Collection" : "Table";
151155
if (nameToIdMapping.has(collectionName)) {
152156
const collectionId = nameToIdMapping.get(collectionName);
153-
MessageFormatter.debug(`Collection found in cache: ${collectionId}`, undefined, { prefix: "Collections" });
157+
MessageFormatter.debug(`${entityType} found in cache: ${collectionId}`, undefined, { prefix: entityType + "s" });
154158
return await tryAwaitWithRetry(
155-
async () => isLegacyDatabases(db) ?
159+
async () => isLegacy ?
156160
await db.getCollection(dbId, collectionId!) :
157161
await db.getTable({ databaseId: dbId, tableId: collectionId! })
158162
) as Models.Collection;
159163
} else {
160-
MessageFormatter.progress(`Fetching collection by name: ${collectionName}`, { prefix: "Collections" });
164+
MessageFormatter.progress(`Fetching ${entityType.toLowerCase()} by name: ${collectionName}`, { prefix: entityType + "s" });
161165
const collectionsPulled = await tryAwaitWithRetry(
162-
async () => isLegacyDatabases(db) ?
166+
async () => isLegacy ?
163167
await db.listCollections(dbId, [Query.equal("name", collectionName)]) :
164168
await db.listTables({ databaseId: dbId, queries: [Query.equal("name", collectionName)] })
165169
);
166-
const items = isLegacyDatabases(db) ? collectionsPulled.collections : ((collectionsPulled as any).tables || collectionsPulled.collections);
170+
const items = isLegacy ? collectionsPulled.collections : ((collectionsPulled as any).tables || collectionsPulled.collections);
167171
if ((collectionsPulled.total || items?.length) > 0) {
168172
const collection = items[0];
169-
MessageFormatter.info(`Collection found: ${collection.$id}`, { prefix: "Collections" });
173+
MessageFormatter.info(`${entityType} found: ${collection.$id}`, { prefix: entityType + "s" });
170174
nameToIdMapping.set(collectionName, collection.$id);
171175
return collection;
172176
} else {
173-
MessageFormatter.warning(`Collection not found by name: ${collectionName}`, { prefix: "Collections" });
177+
MessageFormatter.warning(`${entityType} not found by name: ${collectionName}`, { prefix: entityType + "s" });
174178
return undefined;
175179
}
176180
}

packages/appwrite-utils-cli/src/interactiveCLI.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ enum CHOICES {
6363
COMPREHENSIVE_TRANSFER = "🚀 Comprehensive transfer (users → databases → buckets → functions)",
6464
BACKUP_DATABASE = "💾 Backup database",
6565
WIPE_DATABASE = "🧹 Wipe database",
66-
WIPE_COLLECTIONS = "🧹 Wipe collections",
66+
WIPE_COLLECTIONS = "🧹 Wipe tables",
6767
GENERATE_SCHEMAS = "🏗️ Generate schemas",
6868
GENERATE_CONSTANTS = "📋 Generate cross-language constants (TypeScript, Python, PHP, Dart, etc.)",
6969
IMPORT_DATA = "📥 Import data",
@@ -532,26 +532,26 @@ export class InteractiveCLI {
532532

533533
// Provide context about what's available
534534
if (collectionsCount > 0 && tablesCount > 0) {
535-
MessageFormatter.info(`\n📋 ${totalCount} total items available:`, { prefix: "Collections" });
536-
MessageFormatter.info(` Collections: ${collectionsCount} (from collections/ folder)`, { prefix: "Collections" });
537-
MessageFormatter.info(` Tables: ${tablesCount} (from tables/ folder)`, { prefix: "Collections" });
535+
MessageFormatter.info(`\n${totalCount} total tables available:`, { prefix: "Tables" });
536+
MessageFormatter.info(` From collections/ folder: ${collectionsCount}`, { prefix: "Tables" });
537+
MessageFormatter.info(` From tables/ folder: ${tablesCount}`, { prefix: "Tables" });
538538
} else if (collectionsCount > 0) {
539-
MessageFormatter.info(`📁 ${collectionsCount} collections available from collections/ folder`, { prefix: "Collections" });
539+
MessageFormatter.info(`${collectionsCount} tables available from collections/ folder`, { prefix: "Tables" });
540540
} else if (tablesCount > 0) {
541-
MessageFormatter.info(`📊 ${tablesCount} tables available from tables/ folder`, { prefix: "Collections" });
541+
MessageFormatter.info(`${tablesCount} tables available from tables/ folder`, { prefix: "Tables" });
542542
}
543543

544544
// Show current database context clearly before view mode selection
545-
MessageFormatter.info(`DB: ${database.name}`, { prefix: "Collections" });
545+
MessageFormatter.info(`DB: ${database.name}`, { prefix: "Tables" });
546546

547547
// Ask user if they want to filter by database, show all, or reuse previous selection
548548
const choices: { name: string; value: string }[] = [
549549
{
550-
name: `Show all available collections/tables (${totalCount} total) - You can push any collection to any database`,
550+
name: `Show all available tables (${totalCount} total) - You can push any table to any database`,
551551
value: "all"
552552
},
553553
{
554-
name: `Filter by database "${database.name}" - Show only related collections/tables`,
554+
name: `Filter by database "${database.name}" - Show only related tables`,
555555
value: "filter"
556556
}
557557
];
@@ -566,7 +566,7 @@ export class InteractiveCLI {
566566
{
567567
type: "list",
568568
name: "filterChoice",
569-
message: chalk.blue("How would you like to view collections/tables?"),
569+
message: chalk.blue("How would you like to view tables?"),
570570
choices,
571571
default: choices[0]?.value || "all"
572572
}
@@ -589,17 +589,17 @@ export class InteractiveCLI {
589589

590590
// Show appropriate informational message
591591
if (userWantsFiltering) {
592-
MessageFormatter.info(`ℹ️ Showing collections/tables related to database "${database.name}"`, { prefix: "Collections" });
592+
MessageFormatter.info(`Showing tables related to database "${database.name}"`, { prefix: "Tables" });
593593
if (tablesCount > 0) {
594594
const filteredTables = configCollections.filter(c =>
595595
c._isFromTablesDir && (!c.databaseId || c.databaseId === database.$id)
596596
).length;
597597
if (filteredTables !== tablesCount) {
598-
MessageFormatter.info(` ${filteredTables}/${tablesCount} tables match this database`, { prefix: "Collections" });
598+
MessageFormatter.info(` ${filteredTables}/${tablesCount} tables match this database`, { prefix: "Tables" });
599599
}
600600
}
601601
} else {
602-
MessageFormatter.info(`ℹ️ Showing all available collections/tables - you can push any collection to any database\n`, { prefix: "Collections" });
602+
MessageFormatter.info(`Showing all available tables - you can push any table to any database\n`, { prefix: "Tables" });
603603
}
604604

605605
const result = await this.selectCollections(

packages/appwrite-utils-cli/src/migrations/dataLoader.ts

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { convertObjectByAttributeMappings } from "appwrite-utils-helpers";
1818
import { z } from "zod";
1919
import { checkForCollection } from "../collections/methods.js";
2020
import { ID, Users, type Databases } from "node-appwrite";
21-
import { logger, LegacyAdapter, MessageFormatter } from "appwrite-utils-helpers";
21+
import { logger, AdapterFactory, type DatabaseAdapter, MessageFormatter } from "appwrite-utils-helpers";
2222
import { findOrCreateOperation, updateOperation } from "../shared/migrationHelpers.js";
2323
import { AuthUserCreateSchema } from "../schemas/authUser.js";
2424
import { UsersController } from "../users/methods.js";
@@ -67,6 +67,15 @@ export class DataLoader {
6767
private userIdSet = new Set<string>();
6868
userExistsMap = new Map<string, boolean>();
6969
private shouldWriteFile = false;
70+
private _adapter: DatabaseAdapter | null = null;
71+
72+
private async getAdapter(): Promise<DatabaseAdapter> {
73+
if (!this._adapter) {
74+
const { adapter } = await AdapterFactory.createFromConfig(this.config);
75+
this._adapter = adapter;
76+
}
77+
return this._adapter;
78+
}
7079

7180
// Constructor to initialize the DataLoader with necessary configurations
7281
constructor(
@@ -279,7 +288,7 @@ export class DataLoader {
279288
);
280289
}
281290

282-
async setupMaps(dbId: string) {
291+
async setupMaps(dbId: string, specificCollections?: string[]) {
283292
// Initialize the users collection in the import map
284293
this.importMap.set(this.getCollectionKey("users"), {
285294
data: [],
@@ -294,6 +303,11 @@ export class DataLoader {
294303
for (let index = 0; index < this.config.collections.length; index++) {
295304
const collectionConfig = this.config.collections[index];
296305
let collection = CollectionCreateSchema.parse(collectionConfig);
306+
// Skip collections not in the specific list if one was provided
307+
if (specificCollections && specificCollections.length > 0 &&
308+
!specificCollections.includes(collection.name)) {
309+
continue;
310+
}
297311
// Check if the collection exists in the database
298312
const collectionExists = await checkForCollection(
299313
this.database,
@@ -311,19 +325,25 @@ export class DataLoader {
311325
collectionConfig.$id = collectionExists.$id;
312326
collection.$id = collectionExists.$id;
313327
this.config.collections[index] = collectionConfig;
314-
// Find or create an import operation for the collection
315-
const adapter = new LegacyAdapter(this.database.client);
316-
const collectionImportOperation = await findOrCreateOperation(
317-
adapter,
318-
dbId,
319-
"importData",
320-
collection.$id!
321-
);
322-
// Store the operation ID in the map
323-
this.collectionImportOperations.set(
324-
this.getCollectionKey(collection.name),
325-
collectionImportOperation.$id
326-
);
328+
// Find or create an import operation for the collection (non-fatal)
329+
try {
330+
const adapter = await this.getAdapter();
331+
const collectionImportOperation = await findOrCreateOperation(
332+
adapter,
333+
dbId,
334+
"importData",
335+
collection.$id!
336+
);
337+
this.collectionImportOperations.set(
338+
this.getCollectionKey(collection.name),
339+
collectionImportOperation.$id
340+
);
341+
} catch (error) {
342+
MessageFormatter.warning(
343+
`Operations tracking unavailable for ${collection.name}, import will proceed without it`,
344+
{ prefix: "Import" }
345+
);
346+
}
327347
// Initialize the collection in the import map
328348
this.importMap.set(this.getCollectionKey(collection.name), {
329349
collection: collection,
@@ -373,17 +393,22 @@ export class DataLoader {
373393
}
374394

375395
// Main method to start the data loading process for a given database ID
376-
async start(dbId: string) {
396+
async start(dbId: string, specificCollections?: string[]) {
377397
MessageFormatter.divider();
378398
MessageFormatter.info(`Starting data setup for database: ${dbId}`, { prefix: "Data" });
379399
MessageFormatter.divider();
380-
await this.setupMaps(dbId);
381-
const allUsers = await this.getAllUsers();
382-
MessageFormatter.info(
383-
`Fetched ${allUsers.length} users, waiting a few seconds to let the program catch up...`,
384-
{ prefix: "Data" }
385-
);
386-
await new Promise((resolve) => setTimeout(resolve, 5000));
400+
// Only fetch users if we're importing users or no specific collections specified
401+
const needsUsers = !specificCollections || specificCollections.length === 0 ||
402+
specificCollections.some(c =>
403+
this.getCollectionKey(c) === this.getCollectionKey(this.config.usersCollectionName)
404+
);
405+
if (needsUsers) {
406+
const allUsers = await this.getAllUsers();
407+
MessageFormatter.info(
408+
`Fetched ${allUsers.length} users`,
409+
{ prefix: "Data" }
410+
);
411+
}
387412
// Iterate over the configured databases to find the matching one
388413
for (const db of this.config.databases) {
389414
if (db.$id !== dbId) {
@@ -395,6 +420,11 @@ export class DataLoader {
395420
// Iterate over the configured collections to process each
396421
for (const collectionConfig of this.config.collections) {
397422
const collection = collectionConfig;
423+
// Skip collections not in the specific list
424+
if (specificCollections && specificCollections.length > 0 &&
425+
!specificCollections.includes(collection.name)) {
426+
continue;
427+
}
398428
// Determine if this is the users collection
399429
let isUsersCollection =
400430
this.getCollectionKey(this.config.usersCollectionName) ===
@@ -954,7 +984,7 @@ export class DataLoader {
954984
this.oldIdToNewIdPerCollectionMap
955985
.set(this.getCollectionKey(collection.name), oldIdToNewIdMap)
956986
.get(this.getCollectionKey(collection.name));
957-
const adapter = new LegacyAdapter(this.database.client);
987+
const adapter = await this.getAdapter();
958988
if (!operationId) {
959989
const collectionImportOperation = await findOrCreateOperation(
960990
adapter,
@@ -971,7 +1001,7 @@ export class DataLoader {
9711001
}
9721002
if (operationId) {
9731003
await updateOperation(adapter, db.$id, operationId, {
974-
status: "ready",
1004+
status: "in_progress",
9751005
total: rawData.length,
9761006
});
9771007
}
@@ -1185,7 +1215,7 @@ export class DataLoader {
11851215
let operationId = this.collectionImportOperations.get(
11861216
this.getCollectionKey(collection.name)
11871217
);
1188-
const adapter = new LegacyAdapter(this.database.client);
1218+
const adapter = await this.getAdapter();
11891219
if (!operationId) {
11901220
const collectionImportOperation = await findOrCreateOperation(
11911221
adapter,
@@ -1202,7 +1232,7 @@ export class DataLoader {
12021232
}
12031233
if (operationId) {
12041234
await updateOperation(adapter, db.$id, operationId, {
1205-
status: "ready",
1235+
status: "in_progress",
12061236
total: rawData.length,
12071237
});
12081238
}

0 commit comments

Comments
 (0)