@mostajs/setup 2.0.2 → 2.1.1

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.
@@ -0,0 +1,46 @@
1
+ import type { MostaSetupConfig } from '../types/index.js';
2
+ type NeedsSetupFn = () => Promise<boolean>;
3
+ type GetSetupConfigFn = () => Promise<MostaSetupConfig>;
4
+ export interface SetupRoutesConfig {
5
+ needsSetup: NeedsSetupFn;
6
+ getSetupConfig: GetSetupConfigFn;
7
+ }
8
+ /**
9
+ * Creates GET, POST, DELETE, PATCH handlers for a catch-all [...slug] route.
10
+ *
11
+ * Dispatch table (11 slugs):
12
+ * status GET
13
+ * test-db POST
14
+ * create-db POST
15
+ * preflight GET
16
+ * detect-modules GET
17
+ * install-modules POST
18
+ * setup-json GET POST
19
+ * upload-jar GET POST DELETE PATCH
20
+ * wire-module GET POST
21
+ * reconfig GET POST
22
+ * install POST
23
+ */
24
+ export declare function createSetupRoutes(config: SetupRoutesConfig): {
25
+ GET: (req: Request, { params }: {
26
+ params: Promise<{
27
+ slug: string[];
28
+ }>;
29
+ }) => Promise<Response>;
30
+ POST: (req: Request, { params }: {
31
+ params: Promise<{
32
+ slug: string[];
33
+ }>;
34
+ }) => Promise<Response>;
35
+ DELETE: (req: Request, { params }: {
36
+ params: Promise<{
37
+ slug: string[];
38
+ }>;
39
+ }) => Promise<Response>;
40
+ PATCH: (req: Request, { params }: {
41
+ params: Promise<{
42
+ slug: string[];
43
+ }>;
44
+ }) => Promise<Response>;
45
+ };
46
+ export {};
@@ -0,0 +1,121 @@
1
+ // @mostajs/setup — Catch-all route factory
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ //
4
+ // Replaces 6+ individual route files with a single [...slug] handler.
5
+ // Usage:
6
+ // export const { GET, POST, DELETE, PATCH } = createSetupRoutes({
7
+ // needsSetup: appNeedsSetup,
8
+ // getSetupConfig,
9
+ // })
10
+ /**
11
+ * Creates GET, POST, DELETE, PATCH handlers for a catch-all [...slug] route.
12
+ *
13
+ * Dispatch table (11 slugs):
14
+ * status GET
15
+ * test-db POST
16
+ * create-db POST
17
+ * preflight GET
18
+ * detect-modules GET
19
+ * install-modules POST
20
+ * setup-json GET POST
21
+ * upload-jar GET POST DELETE PATCH
22
+ * wire-module GET POST
23
+ * reconfig GET POST
24
+ * install POST
25
+ */
26
+ export function createSetupRoutes(config) {
27
+ const { needsSetup, getSetupConfig } = config;
28
+ // Lazy-init: handlers are created on first request, not at import time
29
+ let table = null;
30
+ async function buildTable() {
31
+ if (table)
32
+ return table;
33
+ // Import all factories
34
+ const [{ createStatusHandler }, { createTestDbHandler }, { createCreateDbHandler }, { createPreflightHandler }, { createDetectModulesHandler }, { createInstallModulesHandler }, { createSetupJsonHandler }, { createUploadJarHandlers }, { createWireModuleHandler }, { createReconfigHandlers }, { createInstallHandler },] = await Promise.all([
35
+ import('./status.route.js'),
36
+ import('./test-db.route.js'),
37
+ import('./create-db.route.js'),
38
+ import('./preflight.route.js'),
39
+ import('./detect-modules.route.js'),
40
+ import('./install-modules.route.js'),
41
+ import('./upload-setup-json.route.js'),
42
+ import('./upload-jar.route.js'),
43
+ import('./wire-module.route.js'),
44
+ import('./reconfig.route.js'),
45
+ import('./install.route.js'),
46
+ ]);
47
+ // Build handlers — pass needsSetup / config where required
48
+ const status = createStatusHandler(needsSetup);
49
+ const testDb = createTestDbHandler(needsSetup);
50
+ const createDb = createCreateDbHandler();
51
+ const preflight = createPreflightHandler();
52
+ const detectModules = createDetectModulesHandler();
53
+ const installModules = createInstallModulesHandler(needsSetup);
54
+ const setupJson = createSetupJsonHandler(needsSetup);
55
+ const uploadJar = createUploadJarHandlers();
56
+ const wireModule = createWireModuleHandler();
57
+ const reconfig = createReconfigHandlers();
58
+ // install handler needs async setupConfig — wrap it
59
+ const installHandlers = {
60
+ POST: async (req) => {
61
+ const setupConfig = await getSetupConfig();
62
+ const { POST: installPOST } = createInstallHandler(needsSetup, setupConfig);
63
+ return installPOST(req);
64
+ },
65
+ };
66
+ table = {
67
+ 'status': { GET: status.GET },
68
+ 'test-db': { POST: testDb.POST },
69
+ 'create-db': { POST: createDb.POST },
70
+ 'preflight': { GET: preflight.GET },
71
+ 'detect-modules': { GET: detectModules.GET },
72
+ 'install-modules': { POST: installModules.POST },
73
+ 'setup-json': { GET: setupJson.GET, POST: setupJson.POST },
74
+ 'upload-jar': { GET: uploadJar.GET, POST: uploadJar.POST, DELETE: uploadJar.DELETE, PATCH: uploadJar.PATCH },
75
+ 'wire-module': { GET: wireModule.GET, POST: wireModule.POST },
76
+ 'reconfig': { GET: reconfig.GET, POST: reconfig.POST },
77
+ 'install': installHandlers,
78
+ };
79
+ return table;
80
+ }
81
+ function notFound(slug, method) {
82
+ return Response.json({ error: `Setup route not found: ${method} /api/setup/${slug}` }, { status: 404 });
83
+ }
84
+ async function GET(req, { params }) {
85
+ const { slug } = await params;
86
+ const key = slug?.[0] || '';
87
+ const t = await buildTable();
88
+ const handler = t[key]?.GET;
89
+ if (!handler)
90
+ return notFound(key, 'GET');
91
+ return handler(req);
92
+ }
93
+ async function POST(req, { params }) {
94
+ const { slug } = await params;
95
+ const key = slug?.[0] || '';
96
+ const t = await buildTable();
97
+ const handler = t[key]?.POST;
98
+ if (!handler)
99
+ return notFound(key, 'POST');
100
+ return handler(req);
101
+ }
102
+ async function DELETE(req, { params }) {
103
+ const { slug } = await params;
104
+ const key = slug?.[0] || '';
105
+ const t = await buildTable();
106
+ const handler = t[key]?.DELETE;
107
+ if (!handler)
108
+ return notFound(key, 'DELETE');
109
+ return handler(req);
110
+ }
111
+ async function PATCH(req, { params }) {
112
+ const { slug } = await params;
113
+ const key = slug?.[0] || '';
114
+ const t = await buildTable();
115
+ const handler = t[key]?.PATCH;
116
+ if (!handler)
117
+ return notFound(key, 'PATCH');
118
+ return handler(req);
119
+ }
120
+ return { GET, POST, DELETE, PATCH };
121
+ }
@@ -425,9 +425,83 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
425
425
  }, [hydrated, persistState, currentStep, dialect, dbConfig, adminConfig, seedOptions, selectedModules]);
426
426
  // --- Detect modules ---
427
427
  useEffect(() => {
428
- // From setup.json (passed as prop)
428
+ const declaredKeys = new Set((declaredModules ?? []).map(m => m.key));
429
+ // If API endpoint available → fetch full catalog, enrich with declared info
430
+ if (ep.detectModules) {
431
+ fetch(ep.detectModules)
432
+ .then(r => r.json())
433
+ .then((data) => {
434
+ const apiModules = data.modules || [];
435
+ if (data.installed)
436
+ setDetectedModules(data.installed);
437
+ // Merge: API modules as base, enrich with setup.json overrides
438
+ if (declaredModules && declaredModules.length > 0) {
439
+ const apiByKey = new Map(apiModules.map(m => [m.key, m]));
440
+ // Enrich API modules with declared metadata
441
+ for (const dm of declaredModules) {
442
+ const existing = apiByKey.get(dm.key);
443
+ if (existing) {
444
+ if (dm.label)
445
+ existing.label = dm.label;
446
+ if (dm.description)
447
+ existing.description = dm.description;
448
+ if (dm.icon)
449
+ existing.icon = dm.icon;
450
+ }
451
+ else {
452
+ // Module declared in setup.json but not in API catalog — add it
453
+ apiModules.push({
454
+ key: dm.key,
455
+ label: dm.label ?? dm.key,
456
+ description: dm.description ?? '',
457
+ icon: dm.icon ?? '📦',
458
+ required: dm.required,
459
+ default: true,
460
+ dependsOn: dm.dependsOn,
461
+ });
462
+ }
463
+ }
464
+ setAvailableModules(apiModules);
465
+ // Pre-select: declared modules + required
466
+ const pre = new Set([
467
+ ...declaredKeys,
468
+ ...apiModules.filter(m => m.required).map(m => m.key),
469
+ ]);
470
+ setSelectedModules(Array.from(pre));
471
+ }
472
+ else {
473
+ setAvailableModules(apiModules);
474
+ if (selectedModules.length === 0) {
475
+ const pre = new Set([
476
+ ...apiModules.filter(m => m.required || m.default).map(m => m.key),
477
+ ...(data.installed || []),
478
+ ]);
479
+ setSelectedModules(Array.from(pre));
480
+ }
481
+ }
482
+ setModulesDetected(true);
483
+ })
484
+ .catch(() => {
485
+ // API failed — fallback to declared modules only
486
+ if (declaredModules && declaredModules.length > 0) {
487
+ setAvailableModules(declaredModules.map(m => ({
488
+ key: m.key,
489
+ label: m.label ?? m.key,
490
+ description: m.description ?? '',
491
+ icon: m.icon ?? '📦',
492
+ required: m.required,
493
+ default: true,
494
+ dependsOn: m.dependsOn,
495
+ })));
496
+ setSelectedModules(Array.from(declaredKeys));
497
+ }
498
+ setModulesDetected(true);
499
+ });
500
+ return;
501
+ }
502
+ // No API endpoint — use declared modules only (if any)
429
503
  if (declaredModules && declaredModules.length > 0) {
430
- const mods = declaredModules.map(m => ({
504
+ setAvailableModules(declaredModules.map(m => ({
431
505
  key: m.key,
432
506
  label: m.label ?? m.key,
433
507
  description: m.description ?? '',
@@ -435,36 +509,13 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
435
509
  required: m.required,
436
510
  default: true,
437
511
  dependsOn: m.dependsOn,
438
- }));
439
- setAvailableModules(mods);
440
- setSelectedModules(declaredModules.map(m => m.key));
441
- setModulesDetected(true);
442
- return;
443
- }
444
- // No modules and no endpoint → skip
445
- if (!ep.detectModules) {
512
+ })));
513
+ setSelectedModules(Array.from(declaredKeys));
446
514
  setModulesDetected(true);
447
515
  return;
448
516
  }
449
- // From API endpoint
450
- fetch(ep.detectModules)
451
- .then(r => r.json())
452
- .then((data) => {
453
- if (data.modules)
454
- setAvailableModules(data.modules);
455
- if (data.installed)
456
- setDetectedModules(data.installed);
457
- if (selectedModules.length === 0) {
458
- const mods = data.modules || [];
459
- const pre = new Set([
460
- ...mods.filter(m => m.required || m.default).map(m => m.key),
461
- ...(data.installed || []),
462
- ]);
463
- setSelectedModules(Array.from(pre));
464
- }
465
- setModulesDetected(true);
466
- })
467
- .catch(() => setModulesDetected(true));
517
+ // No endpoint, no declared modules → skip
518
+ setModulesDetected(true);
468
519
  // eslint-disable-next-line react-hooks/exhaustive-deps
469
520
  }, []);
470
521
  // --- Module toggle ---
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export { MODULES, resolveModuleDependencies } from './data/module-definitions.js
7
7
  export { discoverNpmModules } from './lib/discover-modules.js';
8
8
  export { loadSetupJson } from './lib/load-setup-json.js';
9
9
  export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json.js';
10
+ export { createSetupRoutes } from './api/routes.js';
11
+ export type { SetupRoutesConfig } from './api/routes.js';
10
12
  export { createTestDbHandler } from './api/test-db.route.js';
11
13
  export { createInstallHandler } from './api/install.route.js';
12
14
  export { createStatusHandler } from './api/status.route.js';
package/dist/index.js CHANGED
@@ -11,7 +11,9 @@ export { MODULES, resolveModuleDependencies } from './data/module-definitions.js
11
11
  // Lib
12
12
  export { discoverNpmModules } from './lib/discover-modules.js';
13
13
  export { loadSetupJson } from './lib/load-setup-json.js';
14
- // API route factories
14
+ // Catch-all route factory (replaces individual route files in host app)
15
+ export { createSetupRoutes } from './api/routes.js';
16
+ // API route factories (individual — still available for granular use)
15
17
  export { createTestDbHandler } from './api/test-db.route.js';
16
18
  export { createInstallHandler } from './api/install.route.js';
17
19
  export { createStatusHandler } from './api/status.route.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
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",
@@ -43,6 +43,11 @@
43
43
  "import": "./dist/components/SetupWizard.js",
44
44
  "default": "./dist/components/SetupWizard.js"
45
45
  },
46
+ "./api/routes": {
47
+ "types": "./dist/api/routes.d.ts",
48
+ "import": "./dist/api/routes.js",
49
+ "default": "./dist/api/routes.js"
50
+ },
46
51
  "./api/reconfig": {
47
52
  "types": "./dist/api/reconfig.route.d.ts",
48
53
  "import": "./dist/api/reconfig.route.js",