appwrite-utils-cli
Version:
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.
201 lines (184 loc) • 6.59 kB
text/typescript
import { Databases, Query, type Models } from "node-appwrite";
import { createOrUpdateAttribute } from "../collections/attributes.js";
import { getMigrationCollectionSchemas } from "../storage/schemas.js";
import {
areCollectionNamesSame,
delay,
toCamelCase,
tryAwaitWithRetry,
} from "../utils/index.js";
import { type AppwriteConfig } from "appwrite-utils";
import { ulid } from "ulidx";
export const setupMigrationDatabase = async (config: AppwriteConfig) => {
if (!config.useMigrations) {
console.log("Migrations database disabled, skipping setup");
return;
}
console.log("---------------------------------");
console.log("Starting Migrations Setup");
console.log("---------------------------------");
const database = new Databases(config.appwriteClient);
if (!config.appwriteClient) {
throw new Error("Appwrite client is not initialized in the config");
}
let db: Models.Database | undefined;
const migrationCollectionsSetup = getMigrationCollectionSchemas();
try {
db = await tryAwaitWithRetry(
async () => await database.get("migrations"),
undefined,
true
);
console.log("Migrations database found");
} catch (e) {
db = await tryAwaitWithRetry(
async () => await database.create("migrations", "Migrations", true)
);
console.log("Migrations database created");
}
if (!db) {
console.error("Failed to create or retrieve the migrations database");
return;
}
for (const [collectionName, { collection, attributes }] of Object.entries(
migrationCollectionsSetup
)) {
const collectionId = toCamelCase(collectionName);
let collectionFound: Models.Collection | undefined;
try {
collectionFound = await tryAwaitWithRetry(
async () => await database.getCollection(db.$id, collectionId),
undefined,
true
);
console.log(`Collection found: ${collectionId}`);
} catch (e) {
console.log(`Collection not found: ${collectionId}`);
try {
collectionFound = await tryAwaitWithRetry(
async () =>
await database.createCollection(
db.$id,
collectionId,
collectionName,
undefined,
collection.documentSecurity,
collection.enabled
),
undefined,
true
);
console.log(`Collection created: ${collectionId}`);
} catch (createError) {
console.error(
`Failed to create collection: ${collectionId}`,
createError
);
continue;
}
}
if (!collectionFound) {
console.error(`Failed to create or retrieve collection: ${collectionId}`);
continue;
}
for (const attribute of attributes) {
try {
await createOrUpdateAttribute(
database,
db.$id,
collectionFound,
attribute
);
await delay(100);
console.log(`Attribute created/updated: ${attribute.key}`);
} catch (attrError) {
console.error(
`Failed to create/update attribute: ${attribute.key}`,
attrError
);
}
}
}
console.log("---------------------------------");
console.log("Migrations Setup Complete");
console.log("---------------------------------");
};
export const ensureDatabasesExist = async (config: AppwriteConfig, databasesToEnsure?: Models.Database[]) => {
if (!config.appwriteClient) {
throw new Error("Appwrite client is not initialized in the config");
}
const database = new Databases(config.appwriteClient);
const databasesToCreate = databasesToEnsure || config.databases || [];
if (!databasesToCreate.length) {
console.log("No databases to create");
return;
}
const existingDatabases = await tryAwaitWithRetry(
async () => await database.list([Query.limit(500)])
);
const migrationsDatabase = existingDatabases.databases.find(
(d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations"
);
if (config.useMigrations && existingDatabases.databases.length !== 0 && migrationsDatabase) {
console.log("Creating all databases including migrations");
databasesToCreate.push(migrationsDatabase);
}
for (const db of databasesToCreate) {
if (!existingDatabases.databases.some((d) => d.name === db.name)) {
await tryAwaitWithRetry(
async () => await database.create(db.$id || ulid(), db.name, true)
);
console.log(`${db.name} database created`);
}
}
};
export const wipeOtherDatabases = async (
database: Databases,
databasesToKeep: Models.Database[],
useMigrations: boolean = true
) => {
console.log(`Databases to keep: ${databasesToKeep.map(db => db.name).join(", ")}`);
const allDatabases = await tryAwaitWithRetry(
async () => await database.list([Query.limit(500)])
);
const migrationsDatabase = allDatabases.databases.find(
(d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations"
);
if (useMigrations && allDatabases.databases.length !== 0 && migrationsDatabase) {
console.log("Wiping all databases except migrations");
databasesToKeep.push(migrationsDatabase);
}
for (const db of allDatabases.databases) {
if (!databasesToKeep.some((d) => d.name === db.name)) {
await tryAwaitWithRetry(async () => await database.delete(db.$id));
console.log(`Deleted database: ${db.name}`);
}
}
};
export const ensureCollectionsExist = async (
config: AppwriteConfig,
database: Models.Database,
collectionsToEnsure?: Models.Collection[]
) => {
const databaseClient = new Databases(config.appwriteClient!);
const collectionsToCreate = collectionsToEnsure ||
(config.collections ? config.collections : []);
const existingCollections = await tryAwaitWithRetry(
async () => await databaseClient.listCollections(database.$id, [Query.limit(500)])
);
for (const collection of collectionsToCreate) {
if (!existingCollections.collections.some((c) => c.name === collection.name)) {
await tryAwaitWithRetry(
async () => await databaseClient.createCollection(
database.$id,
ulid(),
collection.name,
undefined,
true,
true
)
);
console.log(`${collection.name} collection created in ${database.name}`);
}
}
};