@mostajs/setup 2.1.27 → 2.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,1181 +1,84 @@
1
1
  # @mostajs/setup
2
2
 
3
- > Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner, module discovery.
3
+ > Setup wizard — multi-dialect DB config, schema management, seed runner, module discovery.
4
+ > Author: Dr Hamid MADANI drmdh@msn.com
4
5
 
5
- [![npm version](https://img.shields.io/npm/v/@mostajs/setup.svg)](https://www.npmjs.com/package/@mostajs/setup)
6
- [![license](https://img.shields.io/npm/l/@mostajs/setup.svg)](LICENSE)
7
-
8
- Part of the [@mosta suite](https://mostajs.dev).
9
-
10
- ---
11
-
12
- ## Table des matieres
13
-
14
- 1. [Installation](#1-installation)
15
- 2. [Architecture du package](#2-architecture-du-package)
16
- 3. [Didacticiel : integrer @mostajs/setup dans une nouvelle app](#3-didacticiel--integrer-mostajssetup-dans-une-nouvelle-app)
17
- - [Etape 1 — Installation des dependances](#etape-1--installation-des-dependances)
18
- - [Etape 2 — Configurer le setup (lib/setup.ts)](#etape-2--configurer-le-setup-libsetupts)
19
- - [Etape 3 — Creer les routes API](#etape-3--creer-les-routes-api)
20
- - [Etape 4 — Creer la page Setup (frontend)](#etape-4--creer-la-page-setup-frontend)
21
- - [Etape 5 — Middleware : rediriger vers /setup](#etape-5--middleware--rediriger-vers-setup)
22
- - [Etape 6 — Tester le flux complet](#etape-6--tester-le-flux-complet)
23
- 4. [API Reference](#4-api-reference)
24
- 5. [Les 13 dialectes supportes](#5-les-13-dialectes-supportes)
25
- 6. [Systeme de modules](#6-systeme-de-modules)
26
- 7. [Exemples avances](#7-exemples-avances)
27
- 8. [Reconfiguration (post-installation)](#8-reconfiguration-post-installation)
28
- 9. [Mode declaratif : setup.json](#9-mode-declaratif--setupjson)
29
- 10. [FAQ / Troubleshooting](#10-faq--troubleshooting)
30
-
31
- ---
32
-
33
- ## 1. Installation
6
+ ## Install
34
7
 
35
8
  ```bash
36
9
  npm install @mostajs/setup @mostajs/orm
37
10
  ```
38
11
 
39
- `@mostajs/orm` est un peer dependency requis (il fournit les dialectes DB).
40
-
41
- ---
42
-
43
- ## 2. Architecture du package
44
-
45
- ```
46
- @mostajs/setup
47
- ├── data/
48
- │ ├── dialects.ts # Metadata des 13 SGBD (icone, port, driver)
49
- │ └── module-definitions.ts # Liste statique des modules @mostajs/*
50
- ├── lib/
51
- │ ├── setup.ts # needsSetup() + runInstall()
52
- │ ├── compose-uri.ts # Compose URI de connexion (mongo, pg, mysql...)
53
- │ ├── db-test.ts # Test de connexion ephemere
54
- │ ├── env-writer.ts # Ecriture/maj de .env.local
55
- │ └── discover-modules.ts # Decouverte npm dynamique
56
- ├── api/
57
- │ ├── status.route.ts # Factory GET /api/setup/status
58
- │ ├── test-db.route.ts # Factory POST /api/setup/test-db
59
- │ ├── install.route.ts # Factory POST /api/setup/install
60
- │ ├── detect-modules.route.ts # Factory GET /api/setup/detect-modules
61
- │ ├── install-modules.route.ts # Factory POST /api/setup/install-modules
62
- │ ├── reconfig.route.ts # Factory GET+POST /api/setup/reconfig
63
- │ └── upload-jar.route.ts # Factory GET+POST+DELETE /api/setup/upload-jar
64
- ├── components/
65
- │ ├── SetupWizard.tsx # Wizard d'installation complet (6 etapes)
66
- │ └── ReconfigPanel.tsx # UI reconfiguration (modules + DB)
67
- ├── types/
68
- │ └── index.ts # Tous les types TypeScript
69
- └── index.ts # Barrel exports
70
- ```
71
-
72
- **Principe** : le package fournit des **factory functions** qui retournent des handlers `{ GET }` ou `{ POST }`. Votre app Next.js les expose en une ligne par route.
73
-
74
- ---
75
-
76
- ## 3. Didacticiel : integrer @mostajs/setup dans une nouvelle app
77
-
78
- ### Prerequis
79
-
80
- - Next.js 14+ (App Router)
81
- - Node.js >= 18
82
- - `@mostajs/orm` installe
83
-
84
- ### Etape 1 — Installation des dependances
85
-
86
- ```bash
87
- npm install @mostajs/setup @mostajs/orm bcryptjs
88
- npm install -D @types/bcryptjs
89
- ```
90
-
91
- ### Etape 2 — Configurer le setup (`lib/setup.ts`)
92
-
93
- Ce fichier est le **pont** entre le package generique et votre application. Il definit :
94
- - Comment compter les utilisateurs (pour `needsSetup`)
95
- - Comment seeder les roles/permissions
96
- - Comment creer l'admin
97
- - Les seeds optionnels propres a votre app
98
-
99
- ```typescript
100
- // src/lib/setup.ts
101
- import {
102
- needsSetup as _needsSetup,
103
- runInstall as _runInstall,
104
- } from '@mostajs/setup/lib/setup'
105
- import type {
106
- DialectType,
107
- MostaSetupConfig,
108
- SeedDefinition,
109
- InstallConfig,
110
- } from '@mostajs/setup'
111
-
112
- export type { DialectType }
113
-
114
- // ─── needsSetup : verifie si la DB a 0 users ──────────────
115
- export async function needsSetup(): Promise<boolean> {
116
- return _needsSetup(async () => {
117
- // Adaptez cette ligne a votre couche d'acces aux donnees
118
- const { userRepo } = await import('@/dal/service')
119
- const repo = await userRepo()
120
- return repo.count()
121
- })
122
- }
123
-
124
- // ─── Seeds optionnels (propres a votre app) ────────────────
125
- const optionalSeeds: SeedDefinition[] = [
126
- {
127
- key: 'demoData',
128
- label: 'Donnees de demonstration',
129
- description: 'Quelques enregistrements pour tester',
130
- run: async () => {
131
- // Votre logique de seed
132
- const { productRepo } = await import('@/dal/service')
133
- const repo = await productRepo()
134
- await repo.create({ name: 'Produit A', price: 1000 })
135
- await repo.create({ name: 'Produit B', price: 2000 })
136
- },
137
- },
138
- ]
139
-
140
- // ─── Configuration du setup ────────────────────────────────
141
- const SETUP_CONFIG: MostaSetupConfig = {
142
- appName: 'Mon Application',
143
- defaultPort: 3000,
144
-
145
- // Callback pour seeder les roles et permissions
146
- seedRBAC: async () => {
147
- const { roleRepo, permissionRepo } = await import('@/dal/service')
148
- const pRepo = await permissionRepo()
149
- const rRepo = await roleRepo()
150
-
151
- // Creer les permissions
152
- const readPerm = await pRepo.upsert(
153
- { name: 'read' },
154
- { name: 'read', description: 'Lecture' },
155
- )
156
- const writePerm = await pRepo.upsert(
157
- { name: 'write' },
158
- { name: 'write', description: 'Ecriture' },
159
- )
160
-
161
- // Creer les roles avec permissions
162
- await rRepo.upsert(
163
- { name: 'admin' },
164
- { name: 'admin', description: 'Administrateur', permissions: [readPerm.id, writePerm.id] },
165
- )
166
- await rRepo.upsert(
167
- { name: 'viewer' },
168
- { name: 'viewer', description: 'Lecteur', permissions: [readPerm.id] },
169
- )
170
- },
171
-
172
- // Callback pour creer le premier administrateur
173
- // Le mot de passe est DEJA hashe par le package (bcrypt, 12 rounds)
174
- createAdmin: async ({ email, hashedPassword, firstName, lastName }) => {
175
- const { userRepo, roleRepo } = await import('@/dal/service')
176
- const uRepo = await userRepo()
177
- const rRepo = await roleRepo()
178
-
179
- const adminRole = await rRepo.findOne({ name: 'admin' })
180
- await uRepo.upsert(
181
- { email: email.toLowerCase() },
182
- {
183
- email: email.toLowerCase(),
184
- password: hashedPassword,
185
- firstName,
186
- lastName,
187
- roles: adminRole ? [adminRole.id] : [],
188
- status: 'active',
189
- },
190
- )
191
- },
192
-
193
- optionalSeeds,
194
- }
195
-
196
- // ─── runInstall : expose au route handler ──────────────────
197
- export async function runInstall(config: InstallConfig) {
198
- return _runInstall(config, SETUP_CONFIG)
199
- }
200
- ```
201
-
202
- **Points cles :**
203
- - `needsSetup()` retourne `true` si 0 users en base → l'app doit s'installer
204
- - `runInstall()` execute les 6 etapes : ecriture `.env.local`, connexion DB, seed RBAC, creation admin, seeds optionnels
205
- - Le mot de passe admin est **automatiquement hashe** par le package (bcrypt, 12 rounds). Ne le hachez PAS vous-meme.
206
- - `MOSTAJS_MODULES` est ecrit dans `.env.local` si `config.modules` est fourni
207
-
208
- ### Etape 3 — Creer les routes API
209
-
210
- Creez 5 fichiers sous `src/app/api/setup/` :
211
-
212
- #### 3.1 — Status (GET)
213
-
214
- ```typescript
215
- // src/app/api/setup/status/route.ts
216
- import { createStatusHandler } from '@mostajs/setup'
217
- import { needsSetup } from '@/lib/setup'
218
-
219
- export const { GET } = createStatusHandler(needsSetup)
220
- ```
221
-
222
- Reponse : `{ "needsSetup": true }` ou `{ "needsSetup": false }`
12
+ ## How to Use
223
13
 
224
- #### 3.2 Test connexion DB (POST)
14
+ ### 1. Setup Route (Next.js catch-all)
225
15
 
226
16
  ```typescript
227
- // src/app/api/setup/test-db/route.ts
228
- import { createTestDbHandler } from '@mostajs/setup'
229
- import { needsSetup } from '@/lib/setup'
230
-
231
- export const { POST } = createTestDbHandler(needsSetup)
232
- ```
233
-
234
- Body attendu :
235
- ```json
236
- {
237
- "dialect": "postgres",
238
- "host": "localhost",
239
- "port": 5432,
240
- "name": "mydb",
241
- "user": "postgres",
242
- "password": "secret"
243
- }
244
- ```
245
-
246
- Reponse : `{ "ok": true }` ou `{ "ok": false, "error": "..." }`
247
-
248
- #### 3.3 — Installation (POST)
249
-
250
- ```typescript
251
- // src/app/api/setup/install/route.ts
252
- import { NextRequest, NextResponse } from 'next/server'
253
- import { needsSetup, runInstall } from '@/lib/setup'
254
- import { z } from 'zod'
255
-
256
- const ALL_DIALECTS = [
257
- 'mongodb', 'sqlite', 'postgres', 'mysql', 'mariadb',
258
- 'oracle', 'mssql', 'cockroachdb', 'db2', 'hana',
259
- 'hsqldb', 'spanner', 'sybase',
260
- ] as const
261
-
262
- const installSchema = z.object({
263
- dialect: z.enum(ALL_DIALECTS),
264
- db: z.object({
265
- host: z.string().default('localhost'),
266
- port: z.number().int().min(0).max(65535).default(0),
267
- name: z.string().min(1),
268
- user: z.string().default(''),
269
- password: z.string().default(''),
270
- }),
271
- admin: z.object({
272
- email: z.string().email(),
273
- password: z.string().min(6),
274
- firstName: z.string().min(1),
275
- lastName: z.string().min(1),
276
- }),
277
- seed: z.record(z.boolean()).optional(),
278
- modules: z.array(z.string()).optional(),
17
+ // src/app/api/setup/[...slug]/route.ts
18
+ import { createSetupRoutes } from '@mostajs/setup'
19
+ export const { GET, POST, DELETE, PATCH } = createSetupRoutes({
20
+ needsSetup: appNeedsSetup,
21
+ getSetupConfig,
279
22
  })
280
-
281
- export async function POST(req: NextRequest) {
282
- const setupNeeded = await needsSetup()
283
- if (!setupNeeded) {
284
- return NextResponse.json(
285
- { error: { code: 'FORBIDDEN', message: 'Installation deja effectuee' } },
286
- { status: 403 },
287
- )
288
- }
289
-
290
- const body = await req.json()
291
- const parsed = installSchema.safeParse(body)
292
- if (!parsed.success) {
293
- return NextResponse.json(
294
- { error: { code: 'VALIDATION_ERROR', details: parsed.error.flatten() } },
295
- { status: 400 },
296
- )
297
- }
298
-
299
- const result = await runInstall(parsed.data)
300
- if (!result.ok) {
301
- return NextResponse.json(
302
- { error: { code: 'INSTALL_ERROR', message: result.error } },
303
- { status: 500 },
304
- )
305
- }
306
-
307
- return NextResponse.json({
308
- data: { ok: true, needsRestart: result.needsRestart, seeded: result.seeded },
309
- })
310
- }
311
- ```
312
-
313
- #### 3.4 — Detection des modules (GET)
314
-
315
- ```typescript
316
- // src/app/api/setup/detect-modules/route.ts
317
- import { createDetectModulesHandler } from '@mostajs/setup'
318
-
319
- export const { GET } = createDetectModulesHandler()
320
- ```
321
-
322
- Reponse :
323
- ```json
324
- {
325
- "modules": [
326
- { "key": "orm", "packageName": "@mostajs/orm", "label": "ORM", "required": true, ... },
327
- { "key": "auth", "packageName": "@mostajs/auth", ... },
328
- { "key": "new-plugin", "discovered": true, "icon": "📦", ... }
329
- ],
330
- "installed": ["orm", "auth", "setup"]
331
- }
332
- ```
333
-
334
- #### 3.5 — Installation des modules (POST)
335
-
336
- ```typescript
337
- // src/app/api/setup/install-modules/route.ts
338
- import { createInstallModulesHandler } from '@mostajs/setup'
339
- import { needsSetup } from '@/lib/setup'
340
-
341
- export const { POST } = createInstallModulesHandler(needsSetup)
342
23
  ```
343
24
 
344
- Body : `{ "modules": ["orm", "auth", "audit", "rbac"] }`
345
-
346
- Ce handler :
347
- 1. Resout les dependances transitives (`rbac` → `auth` + `audit`)
348
- 2. Installe via `npm install` (local `file:./packages/...` si present, sinon npm registry)
349
- 3. Ecrit `MOSTAJS_MODULES=orm,auth,audit,rbac` dans `.env.local`
350
-
351
- ### Etape 4 — Creer la page Setup (frontend)
352
-
353
- Le module fournit un composant **`SetupWizard`** pret a l'emploi avec inline styles (aucune dependance Tailwind/shadcn).
354
- Votre page Next.js n'est qu'un **wrapper** d'une vingtaine de lignes :
25
+ ### 2. Setup Page
355
26
 
356
27
  ```tsx
357
28
  // src/app/setup/page.tsx
358
- 'use client'
359
-
360
- import { useRouter } from 'next/navigation'
361
- import SetupWizard from '@mostajs/setup/components/SetupWizard'
362
- import { t } from '@/i18n' // ou toute fonction de traduction
363
-
364
- export default function SetupPage() {
365
- const router = useRouter()
366
-
367
- return (
368
- <SetupWizard
369
- t={t}
370
- onComplete={() => router.push('/login')}
371
- dbNamePrefix="secuaccessdb"
372
- endpoints={{
373
- detectModules: '/api/setup/detect-modules',
374
- testDb: '/api/setup/test-db',
375
- installModules: '/api/setup/install-modules',
376
- install: '/api/setup/install',
377
- uploadJar: '/api/setup/upload-jar',
378
- }}
379
- />
380
- )
381
- }
382
- ```
383
-
384
- #### Props de SetupWizard
385
-
386
- | Prop | Type | Default | Description |
387
- |------|------|---------|-------------|
388
- | `t` | `(key: string) => string` | `(k) => k` | Fonction de traduction (recoit des cles `setup.xxx`) |
389
- | `onComplete` | `() => void` | — | Callback apres installation reussie (ex: `router.push('/login')`) |
390
- | `dbNamePrefix` | `string` | `'mydb'` | Prefixe pour le nom de base par defaut |
391
- | `persistState` | `boolean` | `true` | Persister l'etat du wizard dans `sessionStorage` |
392
- | `endpoints` | `object` | voir ci-dessous | URLs des routes API |
393
-
394
- **Endpoints par defaut :**
395
- ```json
396
- {
397
- "detectModules": "/api/setup/detect-modules",
398
- "testDb": "/api/setup/test-db",
399
- "installModules": "/api/setup/install-modules",
400
- "install": "/api/setup/install",
401
- "uploadJar": "/api/setup/upload-jar"
402
- }
403
- ```
404
-
405
- **Cles i18n attendues :** `setup.steps.*`, `setup.welcome.*`, `setup.modules.*`, `setup.dialect.*`, `setup.database.*`, `setup.admin.*`, `setup.summary.*`, `setup.back`, `setup.next`.
406
-
407
- #### Le wizard inclut :
408
- - **6 etapes** : Accueil → Modules → Dialecte → Base de donnees → Admin → Recapitulatif
409
- - **13 dialectes** avec distinction Premium (grises, non cliquables) et badges JDBC
410
- - **Upload JAR** integre pour les dialectes JDBC (hsqldb, oracle, db2, hana, sybase)
411
- - **Persistence sessionStorage** pour survivre aux hot-reloads Next.js
412
- - **Resolution des dependances** entre modules
413
- - **Retry automatique** apres npm install (le serveur peut redemarrer)
414
-
415
- ### Etape 5 — Middleware : rediriger vers /setup
416
-
417
- ```typescript
418
- // src/middleware.ts
419
- import { NextResponse } from 'next/server'
420
- import type { NextRequest } from 'next/server'
421
-
422
- export async function middleware(req: NextRequest) {
423
- const { pathname } = req.nextUrl
424
-
425
- // Laisser passer les routes publiques
426
- if (
427
- pathname.startsWith('/setup') ||
428
- pathname.startsWith('/api/setup') ||
429
- pathname.startsWith('/api/auth') ||
430
- pathname.startsWith('/_next')
431
- ) {
432
- return NextResponse.next()
433
- }
434
-
435
- // Verifier si l'app a besoin du setup
436
- try {
437
- const res = await fetch(new URL('/api/setup/status', req.url))
438
- const data = await res.json()
439
- if (data.needsSetup) {
440
- return NextResponse.redirect(new URL('/setup', req.url))
441
- }
442
- } catch {
443
- // Si la DB n'est pas accessible, rediriger vers setup
444
- return NextResponse.redirect(new URL('/setup', req.url))
445
- }
446
-
447
- return NextResponse.next()
448
- }
449
-
450
- export const config = {
451
- matcher: ['/((?!_next|favicon.ico|icons|manifest.json).*)'],
452
- }
453
- ```
454
-
455
- ### Etape 6 — Tester le flux complet
456
-
457
- ```bash
458
- # 1. Demarrer le serveur
459
- npm run dev
460
-
461
- # 2. Ouvrir http://localhost:3000
462
- # → Redirige automatiquement vers /setup
463
-
464
- # 3. Suivre le wizard :
465
- # - Choisir les modules
466
- # - Selectionner le dialecte (ex: MongoDB, SQLite, Postgres...)
467
- # - Tester la connexion
468
- # - Renseigner l'admin
469
- # - Lancer l'installation
470
-
471
- # 4. Verifier les resultats :
472
- # - .env.local contient DB_DIALECT, SGBD_URI, MOSTAJS_MODULES
473
- # - La DB contient les roles, permissions et l'admin
474
- # - /api/setup/status retourne { needsSetup: false }
475
- ```
476
-
477
- ---
478
-
479
- ## 4. API Reference
480
-
481
- ### Fonctions principales
482
-
483
- | Export | Signature | Description |
484
- |--------|-----------|-------------|
485
- | `needsSetup` | `(countFn: () => Promise<number>) => Promise<boolean>` | Retourne `true` si 0 users en base |
486
- | `runInstall` | `(config: InstallConfig, setup: MostaSetupConfig) => Promise<Result>` | Flux complet : env → DB → seed → admin |
487
- | `testDbConnection` | `(config: DbTestConfig) => Promise<{ ok, error? }>` | Test ephemere (ne modifie pas la connexion globale) |
488
- | `composeDbUri` | `(dialect: DialectType, db: DbConfig) => string` | Compose l'URI de connexion |
489
- | `writeEnvLocal` | `(options: EnvWriterOptions) => Promise<boolean>` | Ecrit `.env.local`, retourne `true` si dialect change |
490
-
491
- ### Factories API routes
492
-
493
- | Export | Route | Methode | Description |
494
- |--------|-------|---------|-------------|
495
- | `createStatusHandler(needsSetup)` | `/api/setup/status` | GET | Retourne `{ needsSetup: boolean }` |
496
- | `createTestDbHandler(needsSetup)` | `/api/setup/test-db` | POST | Teste la connexion DB |
497
- | `createInstallHandler(needsSetup, config)` | `/api/setup/install` | POST | Lance l'installation complete |
498
- | `createDetectModulesHandler()` | `/api/setup/detect-modules` | GET | Liste modules (statiques + npm) + installes |
499
- | `createInstallModulesHandler(needsSetup)` | `/api/setup/install-modules` | POST | Installe les modules npm selectionnes |
500
-
501
- ### setup.json (declaratif)
502
-
503
- | Export | Signature | Description |
504
- |--------|-----------|-------------|
505
- | `loadSetupJson` | `(source?, repoFactory?) => Promise<MostaSetupConfig>` | Charge `setup.json` et retourne un config complet |
506
- | `createSetupJsonHandler` | `(needsSetup) => { GET, POST }` | Route API pour verifier/uploader `setup.json` |
507
-
508
- ### Data exports
509
-
510
- | Export | Description |
511
- |--------|-------------|
512
- | `DIALECT_INFO` | `Record<DialectType, DialectInfo>` — metadata de chaque SGBD |
513
- | `ALL_DIALECTS` | `DialectType[]` — liste des 13 dialectes |
514
- | `MODULES` | `ModuleDefinition[]` — liste statique des 7 modules connus |
515
- | `resolveModuleDependencies(selected, modules?)` | Resout les dependances transitives |
516
- | `discoverNpmModules()` | Decouvre les packages `@mostajs/*` sur npm |
517
-
518
- ### Types
519
-
520
- ```typescript
521
- interface InstallConfig {
522
- dialect: DialectType
523
- db: DbConfig
524
- admin: { email: string; password: string; firstName: string; lastName: string }
525
- seed?: SeedOptions // { activities: true, demoData: false, ... }
526
- modules?: string[] // ['orm', 'auth', 'rbac'] → ecrit MOSTAJS_MODULES
527
- }
528
-
529
- interface MostaSetupConfig {
530
- appName: string
531
- defaultPort?: number
532
- seedRBAC?: () => Promise<void>
533
- createAdmin?: (admin: { email: string; hashedPassword: string; firstName: string; lastName: string }) => Promise<void>
534
- optionalSeeds?: SeedDefinition[]
535
- extraEnvVars?: Record<string, string>
536
- }
537
-
538
- interface SeedDefinition {
539
- key: string
540
- label: string
541
- description: string
542
- icon?: string
543
- default?: boolean
544
- run: (repos: any) => Promise<void>
545
- }
546
-
547
- interface ModuleDefinition {
548
- key: string
549
- packageName: string // '@mostajs/auth'
550
- localDir?: string // 'mosta-auth' (sous packages/)
551
- label: string
552
- description: string
553
- icon: string
554
- required?: boolean
555
- default?: boolean
556
- dependsOn?: string[]
557
- discovered?: boolean // true si trouve via npm search
558
- }
559
- ```
560
-
561
- ---
562
-
563
- ## 5. Les 13 dialectes supportes
564
-
565
- | Dialecte | Icone | Port | Categorie | Driver |
566
- |----------|-------|------|-----------|--------|
567
- | MongoDB | 🍃 | 27017 | document | mongoose |
568
- | SQLite | 📁 | — | file | better-sqlite3 |
569
- | PostgreSQL | 🐘 | 5432 | sql | pg |
570
- | MySQL | 🐬 | 3306 | sql | mysql2 |
571
- | MariaDB | 🦭 | 3306 | sql | mariadb |
572
- | Oracle | 🔴 | 1521 | enterprise | oracledb |
573
- | SQL Server | 🟦 | 1433 | enterprise | tedious |
574
- | CockroachDB | 🪳 | 26257 | distributed | pg |
575
- | IBM DB2 | 🏢 | 50000 | enterprise | ibm_db |
576
- | SAP HANA | 💎 | 39013 | enterprise | @sap/hana-client |
577
- | HyperSQL | ⚡ | 9001 | legacy | — |
578
- | Cloud Spanner | ☁️ | — | distributed | @google-cloud/spanner |
579
- | Sybase ASE | 🔷 | 5000 | legacy | sybase |
580
-
581
- Les URI sont composees automatiquement par `composeDbUri()` :
582
-
583
- ```
584
- mongodb://user:pass@host:27017/mydb
585
- postgresql://user:pass@host:5432/mydb
586
- mysql://user:pass@host:3306/mydb
587
- ./data/mydb.db (SQLite)
588
- spanner://projects/my-project (Cloud Spanner)
589
- ```
590
-
591
- ---
592
-
593
- ## 6. Systeme de modules
594
-
595
- ### Catalogue des 7 modules connus
596
-
597
- | Module | Package | Requis | Depend de | Standalone | Description |
598
- |--------|---------|--------|-----------|------------|-------------|
599
- | **orm** | `@mostajs/orm` | oui | — | — | ORM multi-dialecte (13 SGBD), pattern Hibernate |
600
- | **auth** | `@mostajs/auth` | oui | orm | non | NextAuth, sessions, hashage mots de passe |
601
- | **audit** | `@mostajs/audit` | non | orm | non | Journalisation des actions, tracabilite |
602
- | **rbac** | `@mostajs/rbac` | non | auth, audit | non | Roles, permissions, matrice RBAC |
603
- | **settings** | `@mostajs/settings` | non | orm | non | Parametres cle-valeur, formulaire auto, provider React |
604
- | **face** | `@mostajs/face` | non | **aucune** | **oui** | Detection de visage, descripteurs, matching 1:N |
605
- | **setup** | `@mostajs/setup` | oui | orm | non | Wizard d'installation, test DB, seed runner |
606
-
607
- ### Module standalone : @mostajs/face
608
-
609
- `@mostajs/face` est **100% independant** — il n'importe aucun package `@mostajs/*` et peut etre utilise dans n'importe quelle application React >= 18 sans `@mostajs/orm` ni base de donnees.
610
-
611
- **Dependance unique** : `@vladmandic/face-api` (reconnaissance faciale TensorFlow.js)
612
-
613
- ```bash
614
- npm install @mostajs/face
615
- ```
616
-
617
- ```tsx
618
- import { useCamera, useFaceDetection, compareFaces } from '@mostajs/face'
619
-
620
- // Hooks React pour camera et detection
621
- const { videoRef, start, stop } = useCamera()
622
- const { detect, result } = useFaceDetection()
623
-
624
- // API bas niveau
625
- import { loadModels, detectFace, extractDescriptor } from '@mostajs/face'
626
- import { findMatch, descriptorToArray, arrayToDescriptor } from '@mostajs/face'
627
- ```
628
-
629
- Exports : `loadModels`, `detectFace`, `detectAllFaces`, `extractDescriptor`, `compareFaces`, `findMatch`, `findAllMatches`, `descriptorToArray`, `arrayToDescriptor`, `isValidDescriptor`, `drawDetection`, `useCamera`, `useFaceDetection`.
630
-
631
- ### Graphe de dependances
632
-
633
- ```
634
- ┌──────────┐
635
- │ orm (R) │ R = requis
636
- └────┬─────┘
637
- ┌────────┼────────┬──────────┐
638
- v v v v
639
- ┌──────────┐ ┌──────┐ ┌──────────┐ ┌───────┐
640
- │ auth (R) │ │audit │ │ settings │ │setup(R)│
641
- └────┬─────┘ └──┬───┘ └──────────┘ └───────┘
642
- │ │
643
- v v
644
- ┌──────────────────┐
645
- │ rbac │
646
- └──────────────────┘
647
-
648
- ┌──────────────────────────┐
649
- │ face (100% standalone) │ ← aucune dependance @mostajs
650
- └──────────────────────────┘
651
- ```
652
-
653
- ### Liste statique vs decouverte dynamique
654
-
655
- Le package maintient une **liste statique** des 7 modules ci-dessus avec metadata riches (required, dependsOn, icon, description).
656
-
657
- Au runtime, `GET /api/setup/detect-modules` interroge aussi **npm** (`npm search @mostajs --json`) pour trouver des packages publies apres le deploiement. Ces modules decouverts sont ajoutes avec `discovered: true` et l'icone 📦.
658
-
659
- ### Resolution des dependances
660
-
661
- ```typescript
662
- import { resolveModuleDependencies } from '@mostajs/setup'
663
-
664
- resolveModuleDependencies(['rbac'])
665
- // → ['rbac', 'auth', 'audit', 'orm', 'setup']
666
- // (rbac depend de auth + audit, auth depend de orm, setup est requis)
667
-
668
- resolveModuleDependencies(['face'])
669
- // → ['face', 'orm', 'auth', 'setup']
670
- // (face n'a pas de dependance @mostajs, mais orm/auth/setup sont requis)
671
- ```
672
-
673
- ### Installation hybride (local + npm)
674
-
675
- Le handler `install-modules` utilise une strategie hybride :
676
-
677
- 1. **Skip** : si deja dans `node_modules/@mostajs/xxx` → pas de `npm install` (evite hot-reload)
678
- 2. **Local** : si `packages/mosta-xxx/` existe → `npm install file:./packages/mosta-xxx`
679
- 3. **npm registry** : sinon → `npm install @mostajs/xxx`
680
-
681
- Cela evite les 404 npm pour les packages non encore publies et les hot-reloads Next.js inutiles.
682
-
683
- ---
684
-
685
- ## 7. Exemples avances
686
-
687
- ### Utiliser runInstall sans le wizard UI
688
-
689
- ```typescript
690
- import { runInstall } from '@mostajs/setup'
691
- import { setupConfig } from './my-setup-config'
692
-
693
- const result = await runInstall(
694
- {
695
- dialect: 'postgres',
696
- db: { host: 'localhost', port: 5432, name: 'mydb', user: 'pg', password: 'secret' },
697
- admin: { email: 'admin@example.com', password: 'Admin@123', firstName: 'Admin', lastName: 'User' },
698
- seed: { demoData: true },
699
- modules: ['orm', 'auth', 'rbac'],
700
- },
701
- setupConfig,
702
- )
703
-
704
- console.log(result)
705
- // { ok: true, needsRestart: false, seeded: ['categories', 'permissions', 'roles', 'admin', 'demoData'] }
706
- ```
707
-
708
- ### Ajouter des variables d'environnement personnalisees
709
-
710
- ```typescript
711
- const config: MostaSetupConfig = {
712
- appName: 'MyApp',
713
- extraEnvVars: {
714
- SMTP_HOST: 'smtp.example.com',
715
- STRIPE_KEY: 'sk_test_...',
716
- },
717
- }
718
- // → .env.local contiendra aussi SMTP_HOST et STRIPE_KEY
719
- ```
720
-
721
- ### Tester la connexion DB programmatiquement
722
-
723
- ```typescript
724
- import { testDbConnection } from '@mostajs/setup'
725
-
726
- const result = await testDbConnection({
727
- dialect: 'mongodb',
728
- host: 'localhost',
729
- port: 27017,
730
- name: 'testdb',
731
- user: '',
732
- password: '',
733
- })
734
- // { ok: true } ou { ok: false, error: 'Connection refused' }
735
- ```
736
-
737
- ### Composer une URI manuellement
738
-
739
- ```typescript
740
- import { composeDbUri } from '@mostajs/setup'
741
-
742
- composeDbUri('postgres', { host: 'db.example.com', port: 5432, name: 'prod', user: 'app', password: 's3cret' })
743
- // → 'postgresql://app:s3cret@db.example.com:5432/prod'
744
-
745
- composeDbUri('sqlite', { host: '', port: 0, name: 'myapp', user: '', password: '' })
746
- // → './data/myapp.db'
747
- ```
748
-
749
- ### Ecrire .env.local directement
750
-
751
- ```typescript
752
- import { writeEnvLocal } from '@mostajs/setup'
753
-
754
- const dialectChanged = await writeEnvLocal({
755
- dialect: 'mongodb',
756
- uri: 'mongodb://localhost:27017/mydb',
757
- port: 3000,
758
- extraVars: { MOSTAJS_MODULES: 'orm,auth,rbac' },
759
- })
760
- // dialectChanged = true si le dialecte a change (necessite un redemarrage)
761
- ```
762
-
763
- ---
764
-
765
- ## 8. Reconfiguration (post-installation)
766
-
767
- Apres l'installation initiale, le module fournit un **panneau de reconfiguration** permettant de :
768
- - Changer de base de donnees (dialecte, connexion, test)
769
- - Activer / desactiver des modules @mostajs
770
- - Optionnellement re-seeder la nouvelle base
771
-
772
- ### Integration dans le projet hote
773
-
774
- Ce module exporte un **composant React** (`ReconfigPanel`) et une **factory API** (`createReconfigHandlers`),
775
- mais ne cree pas de pages Next.js. Le projet hote doit creer la route API et la page.
776
-
777
- #### 1. Route API
778
-
779
- **`src/app/api/setup/reconfig/route.ts`**
780
- ```typescript
781
- import { createReconfigHandlers } from '@mostajs/setup/api/reconfig'
782
-
783
- const { GET, POST } = createReconfigHandlers()
784
- export { GET, POST }
785
- ```
786
-
787
- #### 2. Route API JAR Upload (drivers JDBC)
788
-
789
- **`src/app/api/setup/upload-jar/route.ts`**
790
- ```typescript
791
- // Author: Dr Hamid MADANI drmdh@msn.com
792
- import { createUploadJarHandlers } from '@mostajs/setup/api/upload-jar'
793
-
794
- const { GET, POST, DELETE } = createUploadJarHandlers()
795
- export { GET, POST, DELETE }
796
- ```
797
-
798
- > La logique d'upload est dans `@mostajs/orm` (`saveJarFile`, `deleteJarFile`, `listJarFiles`).
799
- > La route factory dans `@mostajs/setup` ne fait que la deleguer.
800
- >
801
- > - **GET** — liste les JARs et le statut des dialects JDBC
802
- > - **POST** — upload un fichier `.jar` (multipart/form-data, champ `jar`)
803
- > - **DELETE** — supprime un JAR (`{ "fileName": "hsqldb-2.7.2.jar" }`)
804
-
805
- #### 3. Page de reconfiguration
806
-
807
- **`src/app/dashboard/settings/reconfig/page.tsx`**
808
- ```tsx
809
- 'use client'
810
- import ReconfigPanel from '@mostajs/setup/components/ReconfigPanel'
811
-
812
- export default function ReconfigPage() {
813
- return (
814
- <div className="space-y-6">
815
- <h1 className="text-2xl font-bold">Reconfiguration</h1>
816
- <ReconfigPanel
817
- apiEndpoint="/api/setup/reconfig"
818
- detectEndpoint="/api/setup/detect-modules"
819
- jarEndpoint="/api/setup/upload-jar"
820
- showSeedOption
821
- onDbChanged={() => window.location.reload()}
822
- onSeedRequested={async () => {
823
- await fetch('/api/setup/install', {
824
- method: 'POST',
825
- headers: { 'Content-Type': 'application/json' },
826
- body: JSON.stringify({ action: 'seed-only' }),
827
- })
828
- }}
829
- />
830
- </div>
831
- )
832
- }
833
- ```
834
-
835
- #### 3. Menu dynamique
836
-
837
- Le module exporte `setupMenuContribution` qui declare la route `/dashboard/settings/reconfig`
838
- dans le groupe "Administration". Importez-le via le deep import :
839
-
840
- ```tsx
841
- import { setupMenuContribution } from '@mostajs/setup/lib/menu'
842
- ```
843
-
844
- #### 4. Props de ReconfigPanel
845
-
846
- | Prop | Type | Description |
847
- |------|------|-------------|
848
- | `apiEndpoint` | `string` | URL de l'API reconfig (ex: `/api/setup/reconfig`) |
849
- | `detectEndpoint` | `string` | URL de l'API detect-modules (ex: `/api/setup/detect-modules`) |
850
- | `t` | `(key: string) => string` | Fonction de traduction (optionnel) |
851
- | `showSeedOption` | `boolean` | Afficher la checkbox "Re-seeder" lors d'un changement de DB |
852
- | `onDbChanged` | `() => void` | Callback apres changement de DB reussi |
853
- | `onModulesChanged` | `(modules: string[]) => void` | Callback apres maj des modules |
854
- | `onSeedRequested` | `() => Promise<void>` | Callback pour executer le seed |
855
-
856
- #### 5. Pourquoi le projet hote doit creer les pages ?
857
-
858
- Les modules `@mostajs/*` sont des **bibliotheques npm** (composants, hooks, utilitaires), pas des applications.
859
- Next.js App Router exige que les fichiers `page.tsx` soient dans le dossier `src/app/` du projet.
860
- Un package npm ne peut pas injecter de pages dans le routeur — c'est donc au projet hote de creer ces fichiers wrapper, meme s'ils ne font qu'importer et afficher un composant du module.
861
-
862
- ---
863
-
864
- ## 9. Mode declaratif : setup.json
865
-
866
- > **Nouveau** — Depuis v1.5, le setup peut etre entierement configure via un fichier JSON declaratif.
867
-
868
- ### Principe
869
-
870
- Au lieu d'ecrire du TypeScript pour definir les categories, permissions, roles et seeds,
871
- vous declarez tout dans un fichier `setup.json` a la racine du projet. Le module le lit
872
- et genere automatiquement les callbacks `seedRBAC`, `createAdmin` et `optionalSeeds`.
873
-
874
- ### Creer un setup.json
875
-
876
- **3 methodes :**
877
-
878
- | Methode | Commande | Pour qui |
879
- |---------|----------|----------|
880
- | **Studio visuel** | Ouvrir [MostaSetup Studio](https://github.com/apolocine/mosta-setup-studio) | Non-developpeurs, design RBAC |
881
- | **CLI interactif** | `npx mosta-setup` | Developpeurs en terminal |
882
- | **CLI rapide** | `npx mosta-setup --quick --name MonApp --port 3000` | CI/CD, scripts |
883
-
884
- ### Structure du fichier
885
-
886
- ```json
887
- {
888
- "$schema": "https://mostajs.dev/schemas/setup.v1.json",
889
- "app": {
890
- "name": "MonApp",
891
- "port": 3000,
892
- "dbNamePrefix": "monappdb"
893
- },
894
- "env": {
895
- "MOSTAJS_MODULES": "orm,auth,audit,rbac,settings,setup"
896
- },
897
- "rbac": {
898
- "categories": [
899
- { "name": "admin", "label": "Administration", "icon": "Settings", "order": 0 }
900
- ],
901
- "permissions": [
902
- { "code": "admin:access", "description": "Acceder au panneau", "category": "admin" }
903
- ],
904
- "roles": [
905
- { "name": "admin", "description": "Administrateur", "permissions": ["*"] }
906
- ]
907
- },
908
- "seeds": [
909
- {
910
- "key": "products",
911
- "label": "Produits demo",
912
- "collection": "product",
913
- "match": "slug",
914
- "default": true,
915
- "data": [
916
- { "name": "Produit A", "slug": "produit-a", "price": 1000 }
917
- ]
918
- }
919
- ]
920
- }
29
+ import { SetupWizard } from '@mostajs/setup'
30
+ export default function Setup() { return <SetupWizard /> }
921
31
  ```
922
32
 
923
- Le champ `$schema` active l'**autocompletion dans VS Code** (types, descriptions, exemples).
924
-
925
- ### Utiliser loadSetupJson()
33
+ ### 3. Config from setup.json (generated by Studio)
926
34
 
927
35
  ```typescript
928
- // src/lib/setup-config.ts
929
36
  import { loadSetupJson } from '@mostajs/setup'
930
- import type { MostaSetupConfig } from '@mostajs/setup'
931
-
932
- // repoFactory : adapte a votre couche d'acces aux donnees
933
- async function repoFactory(collection: string) {
934
- const service = await import('@/dal/service')
935
- const factories: Record<string, () => Promise<unknown>> = {
936
- permissionCategory: service.permissionCategoryRepo,
937
- permission: service.permissionRepo,
938
- role: service.roleRepo,
939
- user: service.userRepo,
940
- activity: service.activityRepo,
941
- }
942
- return factories[collection]() as Promise<any>
943
- }
944
-
945
- export async function getSetupConfig(): Promise<MostaSetupConfig> {
946
- return loadSetupJson('./setup.json', repoFactory)
947
- }
948
- ```
949
-
950
- ```typescript
951
- // src/app/api/setup/install/route.ts
952
- import { runInstall } from '@mostajs/setup'
953
- import type { InstallConfig } from '@mostajs/setup'
954
- import { appNeedsSetup, getSetupConfig } from '@/lib/setup-config'
955
-
956
- export async function POST(req: Request) {
957
- if (!(await appNeedsSetup())) {
958
- return Response.json({ error: 'Already installed' }, { status: 400 })
959
- }
960
- const body: InstallConfig = await req.json()
961
- const config = await getSetupConfig()
962
- return Response.json(await runInstall(body, config))
963
- }
964
- ```
965
-
966
- ### Fonctionnalites des seeds JSON
967
-
968
- | Champ | Type | Description |
969
- |-------|------|-------------|
970
- | `key` | `string` | Identifiant unique du seed |
971
- | `label` | `string` | Label affiche dans le wizard (checkbox) |
972
- | `collection` | `string` | Collection/table cible (doit matcher un schema enregistre) |
973
- | `match` | `string` | Champ pour upsert idempotent (ex: `slug`, `email`) |
974
- | `hashField` | `string` | Champ a hasher avec bcrypt avant insertion (ex: `password`) |
975
- | `roleField` | `string` | Champ contenant un nom de role — resolu en ID a l'execution |
976
- | `defaults` | `object` | Valeurs par defaut fusionnees dans chaque ligne |
977
- | `default` | `boolean` | Si `true`, la checkbox est cochee par defaut dans le wizard |
978
- | `data` | `array` | Tableau d'objets a seeder |
979
-
980
- **Exemple : seed utilisateurs avec hash + resolution de role :**
981
- ```json
982
- {
983
- "key": "demoUsers",
984
- "collection": "user",
985
- "match": "email",
986
- "hashField": "password",
987
- "roleField": "role",
988
- "defaults": { "status": "active" },
989
- "data": [
990
- { "email": "agent@app.dz", "password": "Agent@123", "firstName": "Karim", "role": "agent_accueil" }
991
- ]
992
- }
993
- ```
994
-
995
- A l'execution :
996
- 1. `password` est hashe avec bcrypt (12 rounds)
997
- 2. `role: "agent_accueil"` est resolu en `roles: ["<id-du-role>"]`
998
- 3. `defaults.status` est fusionne → `status: "active"`
999
- 4. Si `match: "email"` et l'email existe deja → upsert (pas de doublon)
1000
-
1001
- ### setup.json manquant : upload automatique
1002
-
1003
- Si le projet accede a `/setup` et que `setup.json` n'existe pas, la page affiche
1004
- automatiquement un formulaire d'upload (drag & drop ou selection de fichier).
1005
-
1006
- Pour activer cette fonctionnalite, ajoutez la route API :
1007
-
1008
- ```typescript
1009
- // src/app/api/setup/setup-json/route.ts
1010
- import { createSetupJsonHandler } from '@mostajs/setup'
1011
- import { appNeedsSetup } from '@/lib/setup-config'
1012
-
1013
- export const { GET, POST } = createSetupJsonHandler(appNeedsSetup)
1014
- ```
1015
-
1016
- - **GET** `/api/setup/setup-json` → `{ exists: boolean, config?: {...} }`
1017
- - **POST** `/api/setup/setup-json` → recoit le JSON, ecrit `./setup.json`
1018
-
1019
- ### Mixer JSON + code TypeScript
1020
-
1021
- Les seeds simples (insert de donnees) vont dans `setup.json`. Les seeds complexes
1022
- (relations, logique conditionnelle) restent en TypeScript :
1023
-
1024
- ```typescript
1025
- const config = await loadSetupJson('./setup.json', repoFactory)
1026
- // Ajouter un seed code-only
1027
- config.optionalSeeds = [
1028
- ...(config.optionalSeeds ?? []),
1029
- { key: 'demoData', label: 'Donnees complexes', run: async () => { /* ... */ } },
1030
- ]
1031
- ```
1032
-
1033
- ### Validation
1034
-
1035
- `loadSetupJson()` valide automatiquement :
1036
- - `app.name` est requis
1037
- - Chaque permission reference une categorie existante
1038
- - Chaque role reference des permissions existantes (sauf `*`)
1039
- - Erreur descriptive en cas de reference croisee invalide
1040
-
1041
- ### CLI : npx mosta-setup
1042
-
1043
- ```bash
1044
- # Mode interactif (terminal)
1045
- npx mosta-setup
1046
-
1047
- # Mode rapide (CI, scripts, Dockerfile)
1048
- npx mosta-setup --quick --name MonApp --port 4567 --db monappdb
1049
-
1050
- # Avec modules
1051
- npx mosta-setup --quick --name MonApp --modules "orm,auth,audit,rbac,settings,setup"
1052
-
1053
- # Sortie stdout (pour pipe)
1054
- npx mosta-setup --quick --name MonApp --stdout | jq .
37
+ const config = await loadSetupJson('./setup.json', dalRepoFactory)
1055
38
  ```
1056
39
 
1057
- ---
1058
-
1059
- ## 10. FAQ / Troubleshooting
1060
-
1061
- ### L'installation tourne en boucle (GET /setup se repete)
1062
-
1063
- **Cause** : `npm install` modifie `package.json` / `node_modules`, ce qui declenche un hot-reload Next.js et reinitialise le state React.
1064
-
1065
- **Solution** : Le handler `install-modules` **skip les packages deja installes** dans `node_modules/`. Si le probleme persiste, ajoutez une persistence du state wizard dans `sessionStorage` (voir l'implementation de SecuAccess Pro).
1066
-
1067
- ### E11000 duplicate key error (MongoDB)
1068
-
1069
- **Cause** : Un champ `unique: true` sans `required: true` dans le schema. MongoDB indexe les `null` et refuse le doublon.
1070
-
1071
- **Solution** : Ajoutez `sparse: true` au champ dans votre `EntitySchema`. Cela fonctionne avec MongoDB (index sparse) et est ignore sans risque par les dialectes SQL.
40
+ ### 4. Module Discovery
1072
41
 
1073
42
  ```typescript
1074
- clientNumber: { type: 'string', unique: true, sparse: true }
1075
- ```
1076
-
1077
- ### JSON.parse: unexpected character at line 1 column 1
43
+ import { discoverModules, collectSchemas } from '@mostajs/setup/lib/module-registry'
1078
44
 
1079
- **Cause** : La reponse de l'API est du HTML (page 404 Next.js) au lieu de JSON, typiquement pendant un hot-reload du serveur.
45
+ const modules = await discoverModules(['rbac', 'audit', 'settings'])
46
+ const allSchemas = collectSchemas(modules)
1080
47
 
1081
- **Solution** : Utilisez un `safeJson()` helper dans le frontend :
1082
-
1083
- ```typescript
1084
- async function safeJson(res: Response) {
1085
- try { return JSON.parse(await res.text()) }
1086
- catch { return null }
48
+ // Seed each module
49
+ for (const mod of modules) {
50
+ await mod.seed(setupJson[mod.name])
1087
51
  }
1088
- ```
1089
-
1090
- ### npm search timeout (pas d'internet)
1091
-
1092
- Le handler `detect-modules` a un timeout de 10 secondes sur `npm search`. En cas d'echec (offline, timeout), il retourne la **liste statique** des 7 modules connus. Aucune action necessaire.
1093
-
1094
- ### Comment ajouter un nouveau module a la liste statique ?
1095
-
1096
- Editez `packages/mosta-setup/data/module-definitions.ts` et ajoutez une entree a `MODULES` :
1097
52
 
1098
- ```typescript
1099
- {
1100
- key: 'notifications',
1101
- packageName: '@mostajs/notifications',
1102
- localDir: 'mosta-notifications', // si disponible localement
1103
- label: 'Notifications',
1104
- description: 'Push, email, SMS',
1105
- icon: '🔔',
1106
- default: false,
1107
- dependsOn: ['orm'],
1108
- }
53
+ // Create admin via rbac
54
+ const rbac = modules.find(m => m.createAdmin)
55
+ await rbac.createAdmin({ email, password, firstName, lastName })
1109
56
  ```
1110
57
 
1111
- Puis recompilez : `cd packages/mosta-setup && npx tsc`
1112
-
1113
- ---
58
+ ### 5. Dual ORM/NET Mode
1114
59
 
1115
- ## Related Packages
60
+ The wizard supports both:
61
+ - **ORM mode**: direct DB connection (dialect selection, URI, create DB, apply schema)
62
+ - **NET mode**: remote @mostajs/net server (test connection, upload schemas, wait restart)
1116
63
 
1117
- | Package | Depend de orm | Standalone | Description |
1118
- |---------|:---:|:---:|-------------|
1119
- | [@mostajs/orm](https://www.npmjs.com/package/@mostajs/orm) | — | — | Multi-dialect ORM, 13 SGBD (requis) |
1120
- | [@mostajs/auth](https://www.npmjs.com/package/@mostajs/auth) | oui | non | Authentication NextAuth, sessions |
1121
- | [@mostajs/audit](https://www.npmjs.com/package/@mostajs/audit) | oui | non | Audit logging, tracabilite |
1122
- | [@mostajs/rbac](https://www.npmjs.com/package/@mostajs/rbac) | oui | non | Roles & Permissions RBAC |
1123
- | [@mostajs/settings](https://www.npmjs.com/package/@mostajs/settings) | oui | non | Parametres cle-valeur |
1124
- | [@mostajs/face](https://www.npmjs.com/package/@mostajs/face) | **non** | **oui** | Reconnaissance faciale (independant) |
64
+ ### 6. Setup Wizard Features
1125
65
 
1126
- ## Catch-all Route Factory (createSetupRoutes)
66
+ - Welcome: choose ORM or NET mode
67
+ - Modules: select @mostajs modules
68
+ - Dialect/Database or NET config
69
+ - Admin user creation (delegates to @mostajs/rbac)
70
+ - Schema upload (JSON, ZIP, scan directory)
71
+ - Seeds (from setup.json)
72
+ - Summary with config review
1127
73
 
1128
- Replace 6+ individual route files with a single `[...slug]` handler :
74
+ ### 7. API Routes
1129
75
 
1130
- ```typescript
1131
- // src/app/api/setup/[...slug]/route.ts — 7 lines replaces 6 files
1132
- import { createSetupRoutes } from '@mostajs/setup'
1133
- import { appNeedsSetup, getSetupConfig } from '@/lib/setup-config'
1134
-
1135
- export const { GET, POST, DELETE, PATCH } = createSetupRoutes({
1136
- needsSetup: appNeedsSetup,
1137
- getSetupConfig,
1138
- })
1139
76
  ```
1140
-
1141
- Handles all 11 setup endpoints : status, test-db, create-db, preflight, detect-modules, install-modules, setup-json, upload-jar, wire-module, reconfig, install.
1142
-
1143
- ## createAdmin (auto-generated)
1144
-
1145
- `loadSetupJson()` automatically generates a `createAdmin` callback that :
1146
- 1. Gets the `user` repo via `repoFactory`
1147
- 2. Gets the `role` repo to resolve the `admin` role
1148
- 3. Creates the admin user with bcrypt-hashed password and admin role
1149
-
1150
- No need to define `createAdmin` manually — it comes from `loadSetupJson()`.
1151
-
1152
- ## lookupFields (cross-entity references in seeds)
1153
-
1154
- Resolve a field value from another collection before inserting seed data :
1155
-
1156
- ```json
1157
- {
1158
- "key": "demoClients",
1159
- "collection": "client",
1160
- "lookupFields": {
1161
- "createdBy": { "collection": "user", "match": "status", "value": "active" }
1162
- },
1163
- "data": [
1164
- { "firstName": "Samir", "lastName": "Boudjema", "phone": "0550100001" }
1165
- ]
1166
- }
77
+ GET /api/setup/status — needsSetup check
78
+ POST /api/setup/install run full installation
79
+ POST /api/setup/test-db — test DB connection
80
+ POST /api/setup/create-db — create database
81
+ POST /api/setup/create-admin — create admin (delegates to rbac)
82
+ GET /api/setup/detect-modules list available modules
83
+ POST /api/setup/net-test — test NET server connection
1167
84
  ```
1168
-
1169
- `lookupFields.createdBy` : find the first `user` where `status = "active"`, inject its `id` as `createdBy` in every data item.
1170
-
1171
- Useful for seeds that reference entities created by earlier seeds (RBAC roles, admin user, etc.).
1172
-
1173
- ## Dynamic Seeds in SetupWizard
1174
-
1175
- The wizard automatically loads available seeds from the `/api/setup/setup-json` endpoint and displays them as checkboxes. Seeds with `"default": true` in setup.json are pre-checked.
1176
-
1177
- No need to hardcode seed checkboxes — add a seed to `setup.json` and it appears in the wizard automatically.
1178
-
1179
- ## License
1180
-
1181
- MIT — (c) 2025-2026 Dr Hamid MADANI <drmdh@msn.com>
@@ -85,7 +85,31 @@ export function createSetupRoutes(config) {
85
85
  const { NetClient } = await import('../lib/net-client.js');
86
86
  const client = new NetClient({ url: body.url });
87
87
  const health = await client.health();
88
- return Response.json({ ok: true, ...health });
88
+ // Auto-save MOSTA_DATA, MOSTA_NET_URL, MOSTA_NET_TRANSPORT in .env.local
89
+ try {
90
+ const { readFileSync, writeFileSync, existsSync } = await import('fs');
91
+ const { resolve } = await import('path');
92
+ const envPath = resolve(process.cwd(), '.env.local');
93
+ let content = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
94
+ const updates = {
95
+ 'MOSTA_DATA': 'net',
96
+ 'MOSTA_NET_URL': body.url,
97
+ 'MOSTA_NET_TRANSPORT': body.transport || 'rest',
98
+ };
99
+ for (const [key, val] of Object.entries(updates)) {
100
+ const regex = new RegExp(`^${key}=.*$`, 'm');
101
+ if (regex.test(content)) {
102
+ content = content.replace(regex, `${key}=${val}`);
103
+ }
104
+ else {
105
+ content += `\n${key}=${val}`;
106
+ }
107
+ process.env[key] = val;
108
+ }
109
+ writeFileSync(envPath, content);
110
+ }
111
+ catch { }
112
+ return Response.json({ ok: true, ...health, saved: true });
89
113
  }
90
114
  catch (e) {
91
115
  return Response.json({ ok: false, error: e instanceof Error ? e.message : 'Connexion echouee' });
@@ -836,7 +836,7 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
836
836
  const res = await fetch(ep.setupJson.replace('setup-json', 'net-test'), {
837
837
  method: 'POST',
838
838
  headers: { 'Content-Type': 'application/json' },
839
- body: JSON.stringify({ url: netUrl }),
839
+ body: JSON.stringify({ url: netUrl, transport: netTransport }),
840
840
  });
841
841
  const data = await res.json();
842
842
  setNetTestResult(data);
@@ -93,7 +93,7 @@ function buildConfig(json, repoFactory) {
93
93
  if (json.rbac.roles?.length) {
94
94
  const roleRepo = await getRepo('role');
95
95
  const allPermIds = Object.values(permissionMap);
96
- for (const roleDef of json.rbac.roles.filter(r => r.name)) {
96
+ for (const roleDef of json.rbac.roles.filter(r => r.name && r.name.trim() !== '')) {
97
97
  const permissionIds = roleDef.permissions.includes('*')
98
98
  ? allPermIds
99
99
  : roleDef.permissions.map(code => permissionMap[code]).filter(Boolean);
package/dist/lib/setup.js CHANGED
@@ -179,16 +179,18 @@ async function runNetInstall(installConfig, setupConfig) {
179
179
  extraVars['MOSTAJS_MODULES'] = installConfig.modules.join(',');
180
180
  }
181
181
  const seeded = [];
182
- // 2. Verify NET server is reachable (retry up to 10s if just restarted)
182
+ // 2. Verify NET server is reachable (retry with backoff, max 20s)
183
183
  let health = null;
184
- for (let i = 0; i < 10; i++) {
184
+ let retryDelay = 500;
185
+ for (let i = 0; i < 15; i++) {
185
186
  try {
186
187
  health = await net.health();
187
188
  if (health.entities?.length > 0)
188
189
  break;
189
190
  }
190
191
  catch { }
191
- await new Promise(r => setTimeout(r, 1000));
192
+ await new Promise(r => setTimeout(r, retryDelay));
193
+ retryDelay = Math.min(retryDelay * 1.5, 3000);
192
194
  }
193
195
  if (!health) {
194
196
  return { ok: false, error: 'Serveur NET non joignable', needsRestart: false };
@@ -237,8 +239,24 @@ async function runNetInstall(installConfig, setupConfig) {
237
239
  // Si le serveur redémarre, attendre qu'il soit de retour
238
240
  if (result.needsRestart) {
239
241
  console.log('[Setup] Serveur NET redémarre pour charger les schemas...');
240
- await new Promise(r => setTimeout(r, 4000)); // Laisser le temps au process.exit + restart
241
- // Poll health jusqu'à ce que le serveur soit de retour (max 30s)
242
+ // Phase 1 : attendre que le serveur soit DOWN (max 10s)
243
+ let serverDown = false;
244
+ for (let i = 0; i < 20; i++) {
245
+ try {
246
+ await net.health();
247
+ // Encore up — attendre
248
+ }
249
+ catch {
250
+ serverDown = true;
251
+ break;
252
+ }
253
+ await new Promise(r => setTimeout(r, 500));
254
+ }
255
+ if (!serverDown) {
256
+ console.log('[Setup] Serveur n\'a pas redémarré — continue quand même');
257
+ }
258
+ // Phase 2 : attendre que le serveur soit UP avec schemas (max 60s, backoff)
259
+ let backoff = 500;
242
260
  for (let i = 0; i < 30; i++) {
243
261
  try {
244
262
  const h = await net.health();
@@ -248,7 +266,8 @@ async function runNetInstall(installConfig, setupConfig) {
248
266
  }
249
267
  }
250
268
  catch { }
251
- await new Promise(r => setTimeout(r, 1000));
269
+ await new Promise(r => setTimeout(r, backoff));
270
+ backoff = Math.min(backoff * 1.5, 3000); // backoff: 500→750→1125→...→3000ms max
252
271
  }
253
272
  // Recharger la collection map
254
273
  await net.loadCollectionMap();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.1.27",
3
+ "version": "2.1.28",
4
4
  "description": "Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "MIT",