@erwininteractive/mvc 0.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.
Files changed (34) hide show
  1. package/README.md +174 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +61 -0
  4. package/dist/framework/App.d.ts +38 -0
  5. package/dist/framework/App.js +100 -0
  6. package/dist/framework/Auth.d.ts +27 -0
  7. package/dist/framework/Auth.js +67 -0
  8. package/dist/framework/Router.d.ts +29 -0
  9. package/dist/framework/Router.js +125 -0
  10. package/dist/framework/db.d.ts +13 -0
  11. package/dist/framework/db.js +41 -0
  12. package/dist/framework/index.d.ts +5 -0
  13. package/dist/framework/index.js +22 -0
  14. package/dist/generators/generateController.d.ts +7 -0
  15. package/dist/generators/generateController.js +110 -0
  16. package/dist/generators/generateModel.d.ts +7 -0
  17. package/dist/generators/generateModel.js +77 -0
  18. package/dist/generators/initApp.d.ts +8 -0
  19. package/dist/generators/initApp.js +113 -0
  20. package/dist/generators/paths.d.ts +17 -0
  21. package/dist/generators/paths.js +55 -0
  22. package/package.json +72 -0
  23. package/prisma/schema.prisma +19 -0
  24. package/templates/appScaffold/README.md +297 -0
  25. package/templates/appScaffold/package.json +23 -0
  26. package/templates/appScaffold/public/favicon.svg +16 -0
  27. package/templates/appScaffold/src/controllers/HomeController.ts +9 -0
  28. package/templates/appScaffold/src/middleware/auth.ts +8 -0
  29. package/templates/appScaffold/src/server.ts +24 -0
  30. package/templates/appScaffold/src/views/index.ejs +300 -0
  31. package/templates/appScaffold/tsconfig.json +16 -0
  32. package/templates/controller.ts.ejs +98 -0
  33. package/templates/model.prisma.ejs +7 -0
  34. package/templates/view.ejs.ejs +42 -0
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # @erwininteractive/mvc
2
+
3
+ A lightweight, full-featured MVC framework for Node.js 20+ built with TypeScript.
4
+
5
+ ## Features
6
+
7
+ - **Express** - Fast, minimal web framework
8
+ - **Prisma** - Modern database ORM with PostgreSQL
9
+ - **EJS + Alpine.js** - Server-side templating with reactive client-side components
10
+ - **Redis Sessions** - Scalable session management
11
+ - **JWT Authentication** - Secure token-based auth with bcrypt password hashing
12
+ - **CLI Tools** - Scaffold apps and generate models/controllers
13
+
14
+ ## Quick Start
15
+
16
+ ### Create a New Application
17
+
18
+ ```bash
19
+ npx @erwininteractive/mvc init myapp
20
+ cd myapp
21
+ cp .env.example .env
22
+ # Edit .env with your database configuration
23
+ npx prisma migrate dev --name init
24
+ npm run dev
25
+ ```
26
+
27
+ ### Generate a Model
28
+
29
+ ```bash
30
+ npx erwinmvc generate model Post
31
+ ```
32
+
33
+ This creates a Prisma model and runs migrations.
34
+
35
+ ### Generate a Controller
36
+
37
+ ```bash
38
+ npx erwinmvc generate controller Post
39
+ ```
40
+
41
+ This creates a CRUD controller with routes:
42
+ - `GET /posts` - List all posts
43
+ - `GET /posts/:id` - Show a single post
44
+ - `POST /posts` - Create a post
45
+ - `PUT /posts/:id` - Update a post
46
+ - `DELETE /posts/:id` - Delete a post
47
+
48
+ ## CLI Commands
49
+
50
+ | Command | Description |
51
+ |---------|-------------|
52
+ | `erwinmvc init <dir>` | Scaffold a new MVC application |
53
+ | `erwinmvc generate model <name>` | Generate a Prisma model |
54
+ | `erwinmvc generate controller <name>` | Generate a CRUD controller |
55
+
56
+ ### Options
57
+
58
+ **init:**
59
+ - `--skip-install` - Skip running npm install
60
+ - `--skip-prisma` - Skip Prisma client generation
61
+
62
+ **generate model:**
63
+ - `--skip-migrate` - Skip running Prisma migrate
64
+
65
+ **generate controller:**
66
+ - `--no-views` - Skip generating EJS views
67
+
68
+ ## Framework API
69
+
70
+ ### App Factory
71
+
72
+ ```typescript
73
+ import { createMvcApp, startServer } from "@erwininteractive/mvc";
74
+
75
+ const { app, redisClient } = await createMvcApp({
76
+ viewsPath: "src/views",
77
+ publicPath: "public",
78
+ });
79
+
80
+ startServer(app);
81
+ ```
82
+
83
+ ### Database
84
+
85
+ ```typescript
86
+ import { getPrismaClient } from "@erwininteractive/mvc";
87
+
88
+ const prisma = getPrismaClient();
89
+ const users = await prisma.user.findMany();
90
+ ```
91
+
92
+ ### Authentication
93
+
94
+ ```typescript
95
+ import {
96
+ hashPassword,
97
+ verifyPassword,
98
+ signToken,
99
+ verifyToken,
100
+ authenticate,
101
+ } from "@erwininteractive/mvc";
102
+
103
+ // Hash a password
104
+ const hash = await hashPassword("secret123");
105
+
106
+ // Verify a password
107
+ const isValid = await verifyPassword("secret123", hash);
108
+
109
+ // Sign a JWT
110
+ const token = signToken({ userId: 1, email: "user@example.com" });
111
+
112
+ // Protect routes with middleware
113
+ app.get("/protected", authenticate, (req, res) => {
114
+ res.json({ user: req.user });
115
+ });
116
+ ```
117
+
118
+ ### Routing
119
+
120
+ ```typescript
121
+ import { registerControllers } from "@erwininteractive/mvc";
122
+
123
+ // Auto-register all *Controller.ts files
124
+ await registerControllers(app, path.resolve("src/controllers"));
125
+ ```
126
+
127
+ ## Environment Variables
128
+
129
+ Create a `.env` file with:
130
+
131
+ ```env
132
+ DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/yourdb?schema=public"
133
+ REDIS_URL="redis://localhost:6379"
134
+ JWT_SECRET="your-secret-key"
135
+ SESSION_SECRET="your-session-secret"
136
+ PORT=3000
137
+ NODE_ENV=development
138
+ ```
139
+
140
+ ## Project Structure
141
+
142
+ Generated applications follow this structure:
143
+
144
+ ```
145
+ myapp/
146
+ ├── src/
147
+ │ ├── controllers/ # Route controllers
148
+ │ ├── middleware/ # Express middleware
149
+ │ ├── views/ # EJS templates
150
+ │ └── server.ts # Entry point
151
+ ├── prisma/
152
+ │ └── schema.prisma # Database schema
153
+ ├── public/ # Static files
154
+ ├── .env.example
155
+ ├── package.json
156
+ └── tsconfig.json
157
+ ```
158
+
159
+ ## Controller Convention
160
+
161
+ Controllers export named functions that map to routes:
162
+
163
+ ```typescript
164
+ // src/controllers/PostController.ts
165
+ export async function index(req, res) { /* GET /posts */ }
166
+ export async function show(req, res) { /* GET /posts/:id */ }
167
+ export async function store(req, res) { /* POST /posts */ }
168
+ export async function update(req, res) { /* PUT /posts/:id */ }
169
+ export async function destroy(req, res) { /* DELETE /posts/:id */ }
170
+ ```
171
+
172
+ ## License
173
+
174
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const initApp_1 = require("./generators/initApp");
6
+ const generateModel_1 = require("./generators/generateModel");
7
+ const generateController_1 = require("./generators/generateController");
8
+ const program = new commander_1.Command();
9
+ program
10
+ .name("erwinmvc")
11
+ .description("CLI for @erwininteractive/mvc framework")
12
+ .version("0.1.1");
13
+ // Init command - scaffold a new application
14
+ program
15
+ .command("init <dir>")
16
+ .description("Scaffold a new MVC application")
17
+ .option("--skip-install", "Skip npm install")
18
+ .option("--with-database", "Include database/Prisma setup")
19
+ .action(async (dir, options) => {
20
+ try {
21
+ await (0, initApp_1.initApp)(dir, options);
22
+ }
23
+ catch (err) {
24
+ console.error("Error:", err instanceof Error ? err.message : err);
25
+ process.exit(1);
26
+ }
27
+ });
28
+ // Generate command group
29
+ const generate = program
30
+ .command("generate")
31
+ .alias("g")
32
+ .description("Generate models or controllers");
33
+ // Generate model
34
+ generate
35
+ .command("model <name>")
36
+ .description("Generate a Prisma model")
37
+ .option("--skip-migrate", "Skip running Prisma migrate")
38
+ .action(async (name, options) => {
39
+ try {
40
+ await (0, generateModel_1.generateModel)(name, options);
41
+ }
42
+ catch (err) {
43
+ console.error("Error:", err instanceof Error ? err.message : err);
44
+ process.exit(1);
45
+ }
46
+ });
47
+ // Generate controller
48
+ generate
49
+ .command("controller <name>")
50
+ .description("Generate a CRUD controller")
51
+ .option("--no-views", "Skip generating views")
52
+ .action(async (name, options) => {
53
+ try {
54
+ await (0, generateController_1.generateController)(name, options);
55
+ }
56
+ catch (err) {
57
+ console.error("Error:", err instanceof Error ? err.message : err);
58
+ process.exit(1);
59
+ }
60
+ });
61
+ program.parse();
@@ -0,0 +1,38 @@
1
+ import { Express } from "express";
2
+ import { RedisClientType } from "redis";
3
+ import helmet from "helmet";
4
+ import cors from "cors";
5
+ export interface MvcAppOptions {
6
+ /** Directory for EJS views (default: "src/views") */
7
+ viewsPath?: string;
8
+ /** Directory for static files (default: "public") */
9
+ publicPath?: string;
10
+ /** Directory for controllers (default: "src/controllers") */
11
+ controllersPath?: string;
12
+ /** Enable Redis sessions (default: true if REDIS_URL is set) */
13
+ enableRedis?: boolean;
14
+ /** Custom CORS options */
15
+ corsOptions?: cors.CorsOptions;
16
+ /** Custom Helmet options */
17
+ helmetOptions?: Parameters<typeof helmet>[0];
18
+ }
19
+ export interface MvcApp {
20
+ app: Express;
21
+ redisClient: RedisClientType | null;
22
+ }
23
+ /**
24
+ * Create and configure an Express MVC application.
25
+ *
26
+ * Includes:
27
+ * - Helmet for security headers
28
+ * - CORS support
29
+ * - JSON and URL-encoded body parsing
30
+ * - Redis-backed sessions (if REDIS_URL is set)
31
+ * - EJS view engine
32
+ * - Static file serving
33
+ */
34
+ export declare function createMvcApp(options?: MvcAppOptions): Promise<MvcApp>;
35
+ /**
36
+ * Start the Express server.
37
+ */
38
+ export declare function startServer(app: Express, port?: number): void;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createMvcApp = createMvcApp;
7
+ exports.startServer = startServer;
8
+ const express_1 = __importDefault(require("express"));
9
+ const express_session_1 = __importDefault(require("express-session"));
10
+ const redis_1 = require("redis");
11
+ const connect_redis_1 = __importDefault(require("connect-redis"));
12
+ const helmet_1 = __importDefault(require("helmet"));
13
+ const cors_1 = __importDefault(require("cors"));
14
+ const dotenv_1 = __importDefault(require("dotenv"));
15
+ const path_1 = __importDefault(require("path"));
16
+ // Load environment variables
17
+ dotenv_1.default.config();
18
+ /**
19
+ * Create and configure an Express MVC application.
20
+ *
21
+ * Includes:
22
+ * - Helmet for security headers
23
+ * - CORS support
24
+ * - JSON and URL-encoded body parsing
25
+ * - Redis-backed sessions (if REDIS_URL is set)
26
+ * - EJS view engine
27
+ * - Static file serving
28
+ */
29
+ async function createMvcApp(options = {}) {
30
+ const { viewsPath = "src/views", publicPath = "public", enableRedis = !!process.env.REDIS_URL, corsOptions = {}, helmetOptions = {}, } = options;
31
+ const app = (0, express_1.default)();
32
+ // Security middleware
33
+ app.use((0, helmet_1.default)(helmetOptions));
34
+ app.use((0, cors_1.default)(corsOptions));
35
+ // Body parsing
36
+ app.use(express_1.default.json());
37
+ app.use(express_1.default.urlencoded({ extended: true }));
38
+ // Static files
39
+ app.use(express_1.default.static(path_1.default.resolve(publicPath)));
40
+ // Session store (Redis if available, memory otherwise)
41
+ let redisClient = null;
42
+ if (enableRedis && process.env.REDIS_URL) {
43
+ try {
44
+ redisClient = (0, redis_1.createClient)({ url: process.env.REDIS_URL });
45
+ await redisClient.connect();
46
+ const store = new connect_redis_1.default({
47
+ client: redisClient,
48
+ prefix: "mvc:",
49
+ });
50
+ app.use((0, express_session_1.default)({
51
+ store,
52
+ secret: process.env.SESSION_SECRET || "default-secret-change-me",
53
+ resave: false,
54
+ saveUninitialized: false,
55
+ cookie: {
56
+ secure: process.env.NODE_ENV === "production",
57
+ httpOnly: true,
58
+ maxAge: 1000 * 60 * 60 * 24, // 24 hours
59
+ },
60
+ }));
61
+ console.log("Using Redis session store");
62
+ }
63
+ catch (err) {
64
+ console.warn("Failed to connect to Redis, using memory sessions:", err);
65
+ redisClient = null;
66
+ setupMemorySessions(app);
67
+ }
68
+ }
69
+ else {
70
+ // Use memory sessions when Redis is not configured
71
+ setupMemorySessions(app);
72
+ }
73
+ // View engine
74
+ app.set("view engine", "ejs");
75
+ app.set("views", path_1.default.resolve(viewsPath));
76
+ return { app, redisClient };
77
+ }
78
+ /**
79
+ * Setup memory-based sessions (for development without Redis).
80
+ */
81
+ function setupMemorySessions(app) {
82
+ app.use((0, express_session_1.default)({
83
+ secret: process.env.SESSION_SECRET || "default-secret-change-me",
84
+ resave: false,
85
+ saveUninitialized: false,
86
+ cookie: {
87
+ secure: process.env.NODE_ENV === "production",
88
+ httpOnly: true,
89
+ maxAge: 1000 * 60 * 60 * 24, // 24 hours
90
+ },
91
+ }));
92
+ }
93
+ /**
94
+ * Start the Express server.
95
+ */
96
+ function startServer(app, port = Number(process.env.PORT) || 3000) {
97
+ app.listen(port, () => {
98
+ console.log(`Server listening on port ${port}`);
99
+ });
100
+ }
@@ -0,0 +1,27 @@
1
+ import jwt from "jsonwebtoken";
2
+ import type { Request, Response, NextFunction } from "express";
3
+ /**
4
+ * Hash a plain text password using bcrypt.
5
+ */
6
+ export declare function hashPassword(plain: string): Promise<string>;
7
+ /**
8
+ * Verify a plain text password against a bcrypt hash.
9
+ */
10
+ export declare function verifyPassword(plain: string, hash: string): Promise<boolean>;
11
+ /**
12
+ * Sign a JWT token with the given payload.
13
+ * Token expires in 1 hour by default.
14
+ */
15
+ export declare function signToken(payload: object, expiresIn?: string | number): string;
16
+ /**
17
+ * Verify and decode a JWT token.
18
+ * Returns the decoded payload or throws an error if invalid.
19
+ */
20
+ export declare function verifyToken(token: string): jwt.JwtPayload | string;
21
+ /**
22
+ * Express middleware to authenticate requests using JWT Bearer tokens.
23
+ * Attaches the decoded user payload to req.user on success.
24
+ */
25
+ export declare function authenticate(req: Request & {
26
+ user?: jwt.JwtPayload | string;
27
+ }, res: Response, next: NextFunction): void;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hashPassword = hashPassword;
7
+ exports.verifyPassword = verifyPassword;
8
+ exports.signToken = signToken;
9
+ exports.verifyToken = verifyToken;
10
+ exports.authenticate = authenticate;
11
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
12
+ const bcryptjs_1 = __importDefault(require("bcryptjs"));
13
+ /**
14
+ * Hash a plain text password using bcrypt.
15
+ */
16
+ async function hashPassword(plain) {
17
+ const salt = await bcryptjs_1.default.genSalt(10);
18
+ return bcryptjs_1.default.hash(plain, salt);
19
+ }
20
+ /**
21
+ * Verify a plain text password against a bcrypt hash.
22
+ */
23
+ async function verifyPassword(plain, hash) {
24
+ return bcryptjs_1.default.compare(plain, hash);
25
+ }
26
+ /**
27
+ * Sign a JWT token with the given payload.
28
+ * Token expires in 1 hour by default.
29
+ */
30
+ function signToken(payload, expiresIn = "1h") {
31
+ const secret = process.env.JWT_SECRET;
32
+ if (!secret) {
33
+ throw new Error("JWT_SECRET environment variable is not set");
34
+ }
35
+ return jsonwebtoken_1.default.sign(payload, secret, { expiresIn });
36
+ }
37
+ /**
38
+ * Verify and decode a JWT token.
39
+ * Returns the decoded payload or throws an error if invalid.
40
+ */
41
+ function verifyToken(token) {
42
+ const secret = process.env.JWT_SECRET;
43
+ if (!secret) {
44
+ throw new Error("JWT_SECRET environment variable is not set");
45
+ }
46
+ return jsonwebtoken_1.default.verify(token, secret);
47
+ }
48
+ /**
49
+ * Express middleware to authenticate requests using JWT Bearer tokens.
50
+ * Attaches the decoded user payload to req.user on success.
51
+ */
52
+ function authenticate(req, res, next) {
53
+ const header = req.header("Authorization");
54
+ const token = header?.startsWith("Bearer ") ? header.slice(7) : null;
55
+ if (!token) {
56
+ res.status(401).json({ error: "Unauthorized" });
57
+ return;
58
+ }
59
+ try {
60
+ const decoded = verifyToken(token);
61
+ req.user = decoded;
62
+ next();
63
+ }
64
+ catch {
65
+ res.status(401).json({ error: "Invalid token" });
66
+ }
67
+ }
@@ -0,0 +1,29 @@
1
+ import type { Express, Request, Response } from "express";
2
+ type RouteHandler = (req: Request, res: Response) => Promise<void> | void;
3
+ interface ControllerModule {
4
+ index?: RouteHandler;
5
+ show?: RouteHandler;
6
+ store?: RouteHandler;
7
+ update?: RouteHandler;
8
+ destroy?: RouteHandler;
9
+ [key: string]: RouteHandler | undefined;
10
+ }
11
+ /**
12
+ * Register controllers from a directory with convention-based routing.
13
+ *
14
+ * Convention:
15
+ * - GET /<resource> -> index
16
+ * - GET /<resource>/:id -> show
17
+ * - POST /<resource> -> store
18
+ * - PUT /<resource>/:id -> update
19
+ * - DELETE /<resource>/:id -> destroy
20
+ *
21
+ * @param app - Express application instance
22
+ * @param controllersDir - Absolute path to the controllers directory
23
+ */
24
+ export declare function registerControllers(app: Express, controllersDir: string): Promise<void>;
25
+ /**
26
+ * Register a single controller with custom base path.
27
+ */
28
+ export declare function registerController(app: Express, basePath: string, controller: ControllerModule): void;
29
+ export {};
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.registerControllers = registerControllers;
40
+ exports.registerController = registerController;
41
+ const path_1 = __importDefault(require("path"));
42
+ const fs_1 = __importDefault(require("fs"));
43
+ /**
44
+ * Convert a controller name to a resource path.
45
+ * e.g., "UserController" -> "users", "PostController" -> "posts"
46
+ */
47
+ function controllerNameToResource(name) {
48
+ // Remove "Controller" suffix
49
+ const baseName = name.replace(/Controller$/, "");
50
+ // Convert to lowercase and simple pluralization
51
+ const lower = baseName.toLowerCase();
52
+ // Simple pluralization: add 's' if not already ending in 's'
53
+ return lower.endsWith("s") ? lower : lower + "s";
54
+ }
55
+ /**
56
+ * Register controllers from a directory with convention-based routing.
57
+ *
58
+ * Convention:
59
+ * - GET /<resource> -> index
60
+ * - GET /<resource>/:id -> show
61
+ * - POST /<resource> -> store
62
+ * - PUT /<resource>/:id -> update
63
+ * - DELETE /<resource>/:id -> destroy
64
+ *
65
+ * @param app - Express application instance
66
+ * @param controllersDir - Absolute path to the controllers directory
67
+ */
68
+ async function registerControllers(app, controllersDir) {
69
+ if (!fs_1.default.existsSync(controllersDir)) {
70
+ console.warn(`Controllers directory not found: ${controllersDir}`);
71
+ return;
72
+ }
73
+ const files = fs_1.default.readdirSync(controllersDir);
74
+ const controllerFiles = files.filter((f) => f.endsWith("Controller.ts") || f.endsWith("Controller.js"));
75
+ for (const file of controllerFiles) {
76
+ const controllerPath = path_1.default.join(controllersDir, file);
77
+ const controllerName = path_1.default.basename(file, path_1.default.extname(file));
78
+ const resource = controllerNameToResource(controllerName);
79
+ try {
80
+ // Dynamic import for ES modules
81
+ const controller = await Promise.resolve(`${controllerPath}`).then(s => __importStar(require(s)));
82
+ // Register routes based on exported functions
83
+ if (controller.index) {
84
+ app.get(`/${resource}`, controller.index);
85
+ }
86
+ if (controller.show) {
87
+ app.get(`/${resource}/:id`, controller.show);
88
+ }
89
+ if (controller.store) {
90
+ app.post(`/${resource}`, controller.store);
91
+ }
92
+ if (controller.update) {
93
+ app.put(`/${resource}/:id`, controller.update);
94
+ }
95
+ if (controller.destroy) {
96
+ app.delete(`/${resource}/:id`, controller.destroy);
97
+ }
98
+ console.log(`Registered controller: ${controllerName} -> /${resource}`);
99
+ }
100
+ catch (err) {
101
+ console.error(`Failed to load controller ${file}:`, err);
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Register a single controller with custom base path.
107
+ */
108
+ function registerController(app, basePath, controller) {
109
+ const resource = basePath.startsWith("/") ? basePath : `/${basePath}`;
110
+ if (controller.index) {
111
+ app.get(resource, controller.index);
112
+ }
113
+ if (controller.show) {
114
+ app.get(`${resource}/:id`, controller.show);
115
+ }
116
+ if (controller.store) {
117
+ app.post(resource, controller.store);
118
+ }
119
+ if (controller.update) {
120
+ app.put(`${resource}/:id`, controller.update);
121
+ }
122
+ if (controller.destroy) {
123
+ app.delete(`${resource}/:id`, controller.destroy);
124
+ }
125
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Get or create a singleton PrismaClient instance.
3
+ * Safe for use in long-running Node processes.
4
+ *
5
+ * Note: Only call this if your app uses a database.
6
+ * Throws an error if @prisma/client is not installed.
7
+ */
8
+ export declare function getPrismaClient(): any;
9
+ /**
10
+ * Disconnect the Prisma client.
11
+ * Useful for graceful shutdown.
12
+ */
13
+ export declare function disconnectPrisma(): Promise<void>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPrismaClient = getPrismaClient;
4
+ exports.disconnectPrisma = disconnectPrisma;
5
+ // Prisma is optional - only loaded when needed
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ let prisma = null;
8
+ let PrismaClient = null;
9
+ /**
10
+ * Get or create a singleton PrismaClient instance.
11
+ * Safe for use in long-running Node processes.
12
+ *
13
+ * Note: Only call this if your app uses a database.
14
+ * Throws an error if @prisma/client is not installed.
15
+ */
16
+ function getPrismaClient() {
17
+ if (!prisma) {
18
+ if (!PrismaClient) {
19
+ try {
20
+ // Dynamic import to make Prisma optional
21
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
22
+ PrismaClient = require("@prisma/client").PrismaClient;
23
+ }
24
+ catch {
25
+ throw new Error("Prisma is not installed. Run 'npm install @prisma/client prisma' to use database features.");
26
+ }
27
+ }
28
+ prisma = new PrismaClient();
29
+ }
30
+ return prisma;
31
+ }
32
+ /**
33
+ * Disconnect the Prisma client.
34
+ * Useful for graceful shutdown.
35
+ */
36
+ async function disconnectPrisma() {
37
+ if (prisma) {
38
+ await prisma.$disconnect();
39
+ prisma = null;
40
+ }
41
+ }
@@ -0,0 +1,5 @@
1
+ export { createMvcApp, startServer } from "./App";
2
+ export type { MvcAppOptions, MvcApp } from "./App";
3
+ export { getPrismaClient, disconnectPrisma } from "./db";
4
+ export { hashPassword, verifyPassword, signToken, verifyToken, authenticate, } from "./Auth";
5
+ export { registerControllers, registerController } from "./Router";