Files
core/src/features/admin/registry.js
T
hykocx 0106bc4ea0 feat(core)!: introduce runtime extension registry and flat module conventions
BREAKING CHANGE: sup config now derives entries from package.json#exports and a server/client glob instead of manual lists; module structure follows flat + barrel convention with .server.js/.client.js runtime suffixes
2026-04-22 14:13:30 -04:00

85 lines
2.8 KiB
JavaScript

/**
* Registre unique pour étendre l'admin sans modifier le core.
*
* Trois types d'extensions :
* - widget : une tuile du tableau de bord. Côté serveur on enregistre un fetcher
* (registerWidgetFetcher), côté client le Composant (registerWidget).
* - navItem : une entrée de la sidebar admin (section optionnelle pour grouper).
* - page : un composant rendu sous /admin/<slug>.
*
* Les instances de module sont séparées entre le bundle serveur et le bundle
* client de Next.js ; c'est attendu : les fetchers vivent côté serveur, les
* Composants côté client. Les navItems et les pages sont enregistrés côté
* neutre et visibles des deux côtés.
*/
const widgetFetchers = new Map(); // id -> async () => data
const widgetComponents = new Map(); // id -> { Component, order }
const navItems = new Map(); // id -> { id, label, icon, href, order, sectionId }
const navSections = new Map(); // id -> { id, title, icon, order }
const pages = new Map(); // slug -> { slug, Component, title? }
// ---- Widgets ---------------------------------------------------------------
export function registerWidgetFetcher(id, fetcher) {
widgetFetchers.set(id, fetcher);
}
export function registerWidget({ id, Component, order = 0 }) {
widgetComponents.set(id, { Component, order });
}
export function getWidgets() {
return [...widgetComponents.entries()]
.map(([id, v]) => ({ id, ...v }))
.sort((a, b) => a.order - b.order);
}
// Un fetcher qui échoue n'empêche pas les autres de produire leur donnée.
export async function collectWidgetData() {
const entries = [...widgetFetchers.entries()];
const results = await Promise.allSettled(
entries.map(async ([id, fetch]) => [id, await fetch()])
);
const out = {};
for (const r of results) {
if (r.status === 'fulfilled') {
const [id, data] = r.value;
out[id] = data;
}
}
return out;
}
// ---- Navigation ------------------------------------------------------------
export function registerNavSection({ id, title, icon, order = 0 }) {
navSections.set(id, { id, title, icon, order });
}
export function registerNavItem({ id, label, icon, href, order = 0, sectionId = 'main' }) {
navItems.set(id, { id, label, icon, href, order, sectionId });
}
export function getNavSections() {
return [...navSections.values()].sort((a, b) => a.order - b.order);
}
export function getNavItems() {
return [...navItems.values()].sort((a, b) => a.order - b.order);
}
// ---- Pages -----------------------------------------------------------------
export function registerPage({ slug, Component, title }) {
pages.set(slug, { slug, Component, title });
}
export function getPage(slug) {
return pages.get(slug);
}
export function getPages() {
return [...pages.values()];
}