docs(core): update server boundary rules and fix db import paths

- document `.server.js` suffix requirement for node-only imports in DEV.md
- add client-safe subentries table and server-only barrel warnings in MODULES.md
- fix `crud.js` and `database/index.js` to import from `db.server.js`
- replace `createRequire` with `pathToFileURL` in `discover.server.js` for ESM-only modules
- update admin navigation and registry to use safe client-compatible imports
- bump version to 1.4.132
This commit is contained in:
2026-04-25 15:05:26 -04:00
parent 0b32e8aa97
commit cb547f6400
8 changed files with 61 additions and 21 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
* Provides convenient methods for Create, Read, Update, Delete operations
*/
import { query, queryOne, queryAll } from './db.js';
import { query, queryOne, queryAll } from './db.server.js';
/**
* Filter a data object to only the columns present in allowedColumns.
+1 -1
View File
@@ -14,7 +14,7 @@ export {
closePool,
testConnection,
tableExists
} from './db.js';
} from './db.server.js';
// CRUD helper functions
export {
+19 -8
View File
@@ -1,7 +1,7 @@
import { readFile } from 'node:fs/promises';
import { readFileSync } from 'node:fs';
import { resolve, join, dirname } from 'node:path';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
import { info, warn } from '@zen/core/shared/logger';
import { registerModule } from './registry.js';
@@ -132,6 +132,20 @@ export async function findInstalledModuleNames({ cwd = process.cwd() } = {}) {
return out.sort();
}
/**
* Résout l'URL ESM du main entry d'un module à partir de son `package.json`.
* On lit `exports['.'].import` puis `main`, sinon `./index.js` par défaut.
* Pas de `require.resolve` : un module `@zen/module-*` peut déclarer uniquement
* la condition `"import"` (ESM-only) — la résolution CJS échouerait alors avec
* "No exports main defined". Comme on a déjà localisé le `package.json` via
* `resolveModulePackageJson`, on construit l'URL nous-mêmes.
*/
function resolveModuleEntryUrl(found) {
const pkgDir = dirname(found.path);
const main = found.pkg?.exports?.['.']?.import ?? found.pkg?.main ?? './index.js';
return pathToFileURL(resolve(pkgDir, main)).href;
}
/**
* Variante "Node-only" du chargement de modules — utilisée par le CLI
* `zen-db init` qui ne passe jamais par un bundler. Charge dynamiquement
@@ -145,20 +159,17 @@ export async function findInstalledModuleNames({ cwd = process.cwd() } = {}) {
*/
export async function loadModulesForCli({ cwd = process.cwd() } = {}) {
const names = await findInstalledModuleNames({ cwd });
const require = createRequire(join(cwd, 'package.json'));
for (const name of names) {
let entryPath;
try {
entryPath = require.resolve(name);
} catch (err) {
warn(`zen-modules: cannot resolve "${name}" — ${err.message}`);
const found = resolveModulePackageJson(name, cwd);
if (!found) {
warn(`zen-modules: cannot find package "${name}" in node_modules`);
continue;
}
let mod;
try {
mod = await import(entryPath);
mod = await import(resolveModuleEntryUrl(found));
} catch (err) {
warn(`zen-modules: failed to import "${name}" — ${err.message}`);
continue;
+1 -1
View File
@@ -5,7 +5,7 @@ import {
getNavItems,
} from './registry.js';
import { isDevkitEnabled } from '../../shared/lib/appConfig.js';
import { PERMISSIONS } from '@zen/core/users';
import { PERMISSIONS } from '@zen/core/users/constants';
// Sections et items core — enregistrés à l'import de ce module.
registerNavSection({ id: 'dashboard', title: 'Tableau de bord', icon: 'DashboardSquare03Icon', order: 10 });
+18 -9
View File
@@ -7,17 +7,26 @@
* - 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.
* Les Maps sont stockées sur `globalThis` via `Symbol.for` pour survivre :
* 1. au hot-reload de Next.js dev (sinon les enregistrements disparaissent).
* 2. à la double-instanciation du fichier — l'instrumentation hook tourne en
* Node natif (require ESM), tandis que les Server Components passent par
* le bundle Turbopack/Webpack. Sans `globalThis`, les nav items poussés
* par `register-server.js` au boot ne seraient pas visibles côté Server
* Component qui rend la sidebar — la sidebar resterait vide.
*/
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? }
const REGISTRY_KEY = Symbol.for('__ZEN_ADMIN_REGISTRY__');
if (!globalThis[REGISTRY_KEY]) {
globalThis[REGISTRY_KEY] = {
widgetFetchers: new Map(), // id -> async () => data
widgetComponents: new Map(), // id -> { Component, order, permission }
navItems: new Map(), // id -> { id, label, icon, href, order, sectionId, position, permission }
navSections: new Map(), // id -> { id, title, icon, order }
pages: new Map(), // slug -> { slug, Component, title?, breadcrumbLabel? }
};
}
const { widgetFetchers, widgetComponents, navItems, navSections, pages } = globalThis[REGISTRY_KEY];
// ---- Widgets ---------------------------------------------------------------