import { createDirectus, rest, readCollections, createCollection, readFields, createField, updateField, deleteField, staticToken } from '@directus/sdk';
export interface DirectusConfig {
url: string;
token?: string;
email?: string;
password?: string;
}
export interface CollectionDefinition {
collection: string;
meta: {
accountability?: string;
archive_app_filter?: boolean;
archive_field?: string;
archive_value?: string;
collapse?: string;
collection_divider?: boolean;
color?: string;
display_template?: string;
group?: string;
hidden?: boolean;
icon?: string;
item_duplication_fields?: string[];
note?: string;
preview_url?: string;
singleton?: boolean;
sort?: number;
sort_field?: string;
translations?: any[];
unarchive_value?: string;
versioning?: boolean;
};
schema?: {
name: string;
};
}
export interface FieldDefinition {
field: string;
type: string;
meta: {
collection?: string;
conditions?: any[];
display?: string;
display_options?: any;
field?: string;
group?: string;
hidden?: boolean;
interface?: string;
note?: string;
options?: any;
readonly?: boolean;
required?: boolean;
sort?: number;
special?: string[];
translations?: any[];
validation?: any;
validation_message?: string;
width?: string;
};
schema?: {
comment?: string;
data_type?: string;
default_value?: any;
foreign_key_column?: string;
foreign_key_table?: string;
generation_expression?: string;
has_auto_increment?: boolean;
is_generated?: boolean;
is_nullable?: boolean;
is_primary_key?: boolean;
is_unique?: boolean;
max_length?: number;
name?: string;
numeric_precision?: number;
numeric_scale?: number;
table?: string;
};
}
export class SchemaManager {
private directus: any;
private config: DirectusConfig;
constructor(config: DirectusConfig) {
this.config = config;
this.directus = createDirectus(config.url).with(rest());
}
async authenticate(): Promise<void> {
if (this.config.token) {
this.directus = this.directus.with(staticToken(this.config.token));
} else if (this.config.email && this.config.password) {
// For email/password authentication, we'd need to implement login
// This is a simplified version - in production you'd want proper auth handling
throw new Error('Email/password authentication not implemented in this example');
} else {
throw new Error('Either token or email/password must be provided');
}
}
async collectionExists(collectionName: string): Promise<boolean> {
try {
const collections = await this.directus.request(readCollections());
return collections.some((collection: any) => collection.collection === collectionName);
} catch (error) {
console.error('Error checking if collection exists:', error);
return false;
}
}
async createCollection(collectionDef: CollectionDefinition): Promise<void> {
try {
await this.directus.request(createCollection(collectionDef));
console.log(`Collection '${collectionDef.collection}' created successfully`);
} catch (error) {
console.error(`Error creating collection '${collectionDef.collection}':`, error);
throw error;
}
}
async createFields(collectionName: string, fields: FieldDefinition[]): Promise<void> {
try {
for (const field of fields) {
const fieldDef = {
...field,
meta: {
...field.meta,
collection: collectionName,
},
schema: {
...field.schema,
table: collectionName,
name: field.field,
},
};
await this.directus.request(createField(collectionName, fieldDef));
console.log(`Field '${field.field}' created in collection '${collectionName}'`);
}
} catch (error) {
console.error(`Error creating fields in collection '${collectionName}':`, error);
throw error;
}
}
async fieldExists(collectionName: string, fieldName: string): Promise<boolean> {
try {
const fields = await this.directus.request(readFields());
return fields.some((field: any) => field.field === fieldName);
} catch (error) {
console.error('Error checking if field exists:', error);
return false;
}
}
async updateField(collectionName: string, fieldName: string, fieldDef: Partial<FieldDefinition>): Promise<void> {
try {
await this.directus.request(updateField(collectionName, fieldName, fieldDef));
console.log(`Field '${fieldName}' updated in collection '${collectionName}'`);
} catch (error) {
console.error(`Error updating field '${fieldName}' in collection '${collectionName}':`, error);
throw error;
}
}
async ensureCollectionAndFields(
collectionName: string,
collectionMeta: CollectionDefinition['meta'],
fields: FieldDefinition[]
): Promise<void> {
// Check if collection exists, if not create it
if (!(await this.collectionExists(collectionName))) {
const collectionDef: CollectionDefinition = {
collection: collectionName,
meta: collectionMeta,
schema: {
name: collectionName,
},
};
await this.createCollection(collectionDef);
}
// Check and create/update fields
for (const field of fields) {
if (!(await this.fieldExists(collectionName, field.field))) {
await this.createFields(collectionName, [field]);
} else {
// Optionally update existing fields
console.log(`Field '${field.field}' already exists in collection '${collectionName}'`);
}
}
}
async syncModelToSchema(collectionName: string, collectionMeta: any, fields: FieldDefinition[]): Promise<void> {
try {
await this.authenticate();
await this.ensureCollectionAndFields(collectionName, collectionMeta, fields);
console.log(`Schema sync completed for collection '${collectionName}'`);
} catch (error) {
console.error(`Error syncing schema for collection '${collectionName}':`, error);
throw error;
}
}
}