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
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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()];
|
||||
}
|
||||
Reference in New Issue
Block a user