dead-simple-orm
Version:
A simple ORM package.
92 lines • 4.46 kB
JavaScript
/**
* A renderer function that returns an HTML input string for a given column.
*/
export class AdminUI {
declaration;
orm;
renderers;
constructor(declaration, orm) {
this.declaration = declaration;
this.orm = orm;
this.renderers = {};
this.registerDefaultRenderers();
}
/**
* Allow user to override or add custom renderers for specific types.
*/
registerRenderers(custom) {
Object.assign(this.renderers, custom);
}
/**
* Get the input HTML for a single column by delegating to the appropriate renderer.
*/
async getInput(columnName, columnType) {
const typeKey = typeof columnType === "object" ? columnType.type : columnType;
const renderer = this.renderers[typeKey] || this.renderers["default"];
return await renderer(columnName, columnType, this.orm);
}
/**
* Generate an HTML form for a given table using default or custom renderers.
* @param tableName Name of the table to build the form for
* @param apiEndpoint The form's action URL
*/
async generateForm(tableName, apiEndpoint) {
const tableDecl = this.declaration[tableName];
if (!tableDecl)
throw new Error(`Unknown table: ${tableName}`);
let formHtml = `<style>
form { max-width: 600px; margin: 1em auto; padding: 1em; border: 1px solid #ccc; border-radius: 4px; }
label { display: block; margin-top: 0.75em; font-weight: bold; }
input, textarea, select { width: 100%; padding: 0.5em; margin-top: 0.25em; box-sizing: border-box; }
button { margin-top: 1em; padding: 0.75em 1.5em; border: none; border-radius: 4px; background: #007bff; color: white; cursor: pointer; }
button:hover { background: #0056b3; }
</style>
`;
formHtml += `<form action="${apiEndpoint}" method="post">
`;
for (const [columnName, columnType] of Object.entries(tableDecl)) {
const inputHtml = await this.getInput(columnName, columnType);
formHtml += ` <label for="${columnName}">${columnName}</label>\n ${inputHtml}\n`;
}
formHtml += ` <button type="submit">Submit</button>\n</form>`;
return formHtml;
}
/**
* Registers default renderers for primitive and relation types.
*/
registerDefaultRenderers() {
// text: single-line input
this.renderers["text"] = async (name) => `\<input type="text" id="${name}" name="${name}" \/>`;
// long text: textarea
this.renderers["long text"] = async (name) => `\<textarea id="${name}" name="${name}" rows="4"\>\<\/textarea\>`;
// image: single-line input (URL or path)
this.renderers["image"] = async (name) => `\<input type="text" id="${name}" name="${name}" \/>`;
// json: textarea
this.renderers["json"] = async (name) => `\<textarea id="${name}" name="${name}" rows="4"\>\<\/textarea\>`;
// date
this.renderers["date"] = async (name) => `\<input type="date" id="${name}" name="${name}" \/>`;
// number
this.renderers["number"] = async (name) => `\<input type="number" id="${name}" name="${name}" \/>`;
// bool
this.renderers["bool"] = async (name) => `\<input type="checkbox" id="${name}" name="${name}" \/>`;
// one-to-one relationship select
this.renderers["one-to-one"] = async (name, type, orm) => {
if (typeof type !== "object" || !type.ref)
throw new Error("Invalid one-to-one relation type");
const rows = await orm.list(type.ref);
const options = rows.map((r) => `\<option value="${r.id}"\>${r.id}\<\/option\>`).join("\n");
return `\<select id="${name}" name="${name}"\>\n${options}\n\<\/select\>`;
};
// one-to-many relationship multiple select
this.renderers["one-to-many"] = async (name, type, orm) => {
if (typeof type !== "object" || !type.ref)
throw new Error("Invalid one-to-many relation type");
const rows = await orm.list(type.ref);
const options = rows.map((r) => `\<option value="${r.id}"\>${r.id}\<\/option\>`).join("\n");
return `\<select id="${name}" name="${name}" multiple\>\n${options}\n\<\/select\>`;
};
// fallback
this.renderers["default"] = async (name) => `\<input type="text" id="${name}" name="${name}" \/>`;
}
}
//# sourceMappingURL=adminUi.js.map