lapeh 2.4.7 → 2.4.9

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/bin/index.js CHANGED
@@ -231,7 +231,7 @@ function runBuild() {
231
231
 
232
232
  // Compile TS
233
233
  try {
234
- execSync('npx tsc && npx tsc-alias', { stdio: 'inherit' });
234
+ execSync('npx tsc -p tsconfig.build.json && npx tsc-alias -p tsconfig.build.json', { stdio: 'inherit' });
235
235
  } catch (e) {
236
236
  console.error('❌ Build failed.');
237
237
  process.exit(1);
@@ -255,7 +255,7 @@ async function upgradeProject() {
255
255
 
256
256
  // Files/Folders to overwrite/copy
257
257
  const filesToSync = [
258
- 'bin', // Ensure CLI script is updated
258
+ // 'bin', // Removed: CLI script is managed by package
259
259
  'lib', // Ensure core framework files are updated
260
260
  'scripts',
261
261
  'docker-compose.yml',
@@ -268,7 +268,47 @@ async function upgradeProject() {
268
268
  'src/prisma.ts', // Core framework file
269
269
  ];
270
270
 
271
- // Helper to copy recursive
271
+ // Helper to sync directory (copy new/updated, delete removed)
272
+ function syncDirectory(src, dest) {
273
+ if (!fs.existsSync(src)) return;
274
+
275
+ // Ensure dest exists
276
+ if (!fs.existsSync(dest)) {
277
+ fs.mkdirSync(dest, { recursive: true });
278
+ }
279
+
280
+ // 1. Copy/Update files from src to dest
281
+ const srcEntries = fs.readdirSync(src, { withFileTypes: true });
282
+ const srcEntryNames = new Set();
283
+
284
+ for (const entry of srcEntries) {
285
+ srcEntryNames.add(entry.name);
286
+ const srcPath = path.join(src, entry.name);
287
+ const destPath = path.join(dest, entry.name);
288
+
289
+ if (entry.isDirectory()) {
290
+ syncDirectory(srcPath, destPath);
291
+ } else {
292
+ fs.copyFileSync(srcPath, destPath);
293
+ }
294
+ }
295
+
296
+ // 2. Delete files in dest that are not in src (only if we are syncing a folder)
297
+ const destEntries = fs.readdirSync(dest, { withFileTypes: true });
298
+ for (const entry of destEntries) {
299
+ if (!srcEntryNames.has(entry.name)) {
300
+ const destPath = path.join(dest, entry.name);
301
+ console.log(`🗑️ Removing obsolete file/directory: ${destPath}`);
302
+ if (entry.isDirectory()) {
303
+ fs.rmSync(destPath, { recursive: true, force: true });
304
+ } else {
305
+ fs.unlinkSync(destPath);
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ // Helper to copy recursive (legacy, kept for other uses if any, but replaced by syncDirectory for upgrade)
272
312
  function copyRecursive(src, dest) {
273
313
  if (!fs.existsSync(src)) return;
274
314
  const stats = fs.statSync(src);
@@ -309,8 +349,17 @@ async function upgradeProject() {
309
349
  const destPath = path.join(currentDir, item);
310
350
 
311
351
  if (fs.existsSync(srcPath)) {
312
- console.log(`🔄 Updating ${item}...`);
313
- copyRecursive(srcPath, destPath);
352
+ const stats = fs.statSync(srcPath);
353
+ if (stats.isDirectory()) {
354
+ console.log(`🔄 Syncing directory ${item}...`);
355
+ syncDirectory(srcPath, destPath);
356
+ } else {
357
+ console.log(`🔄 Updating file ${item}...`);
358
+ // Ensure dir exists
359
+ const destDir = path.dirname(destPath);
360
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
361
+ fs.copyFileSync(srcPath, destPath);
362
+ }
314
363
  }
315
364
  }
316
365
 
@@ -1,2 +1,3 @@
1
+ export declare function createApp(): Promise<import("express-serve-static-core").Express>;
1
2
  export declare function bootstrap(): Promise<void>;
2
3
  //# sourceMappingURL=bootstrap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../lib/bootstrap.ts"],"names":[],"mappings":"AAmBA,wBAAsB,SAAS,kBAmI9B"}
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../lib/bootstrap.ts"],"names":[],"mappings":"AAmBA,wBAAsB,SAAS,yDAyG9B;AAED,wBAAsB,SAAS,kBAwD9B"}
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createApp = createApp;
6
7
  exports.bootstrap = bootstrap;
7
8
  const dotenv_1 = __importDefault(require("dotenv"));
8
9
  dotenv_1.default.config();
@@ -21,18 +22,11 @@ const error_1 = require("./middleware/error");
21
22
  const rateLimit_1 = require("./middleware/rateLimit");
22
23
  const requestLogger_1 = require("./middleware/requestLogger");
23
24
  const response_1 = require("./utils/response");
24
- async function bootstrap() {
25
+ async function createApp() {
25
26
  // Register aliases for production runtime
26
27
  // Since user code (compiled JS) uses require('@lapeh/...')
27
28
  // We map '@lapeh' to the directory containing this file (lib/ or dist/lib/)
28
29
  module_alias_1.default.addAlias("@lapeh", __dirname);
29
- // Validasi Environment Variables
30
- const requiredEnvs = ["DATABASE_URL", "JWT_SECRET"];
31
- const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);
32
- if (missingEnvs.length > 0) {
33
- console.error(`❌ Missing required environment variables: ${missingEnvs.join(", ")}`);
34
- process.exit(1);
35
- }
36
30
  const app = (0, express_1.default)();
37
31
  app.disable("x-powered-by");
38
32
  app.use((0, compression_1.default)());
@@ -76,15 +70,48 @@ async function bootstrap() {
76
70
  ? path_1.default.join(process.cwd(), "dist", "src", "routes")
77
71
  : path_1.default.join(process.cwd(), "src", "routes");
78
72
  // Gunakan require agar sinkron dan mudah dicatch
79
- const { apiRouter } = require(userRoutesPath);
80
- app.use("/api", apiRouter);
81
- console.log(`✅ User routes loaded successfully from ${isProduction ? "dist/" : ""}src/routes`);
73
+ // Check if file exists before requiring to avoid crash in tests/clean env
74
+ try {
75
+ // In test environment, we might need to point to src/routes explicitly if not compiled
76
+ // const routesPath = process.env.NODE_ENV === 'test'
77
+ // ? path.join(process.cwd(), "src", "routes", "index.ts")
78
+ // : userRoutesPath;
79
+ // Note: For TS files in jest, we rely on ts-jest handling 'require' if it points to .ts or we need to use 'import'
80
+ // But 'require' in jest with ts-jest should work if configured.
81
+ // However, require(path) with .ts extension might be tricky.
82
+ // Let's stick to userRoutesPath but maybe adjust for test env.
83
+ // Check if we are in test environment and using ts-jest
84
+ // If so, we might need to import the TS file directly via relative path if alias is not working for require
85
+ const { apiRouter } = require(userRoutesPath);
86
+ app.use("/api", apiRouter);
87
+ console.log(`✅ User routes loaded successfully from ${isProduction ? "dist/" : ""}src/routes`);
88
+ }
89
+ catch (e) {
90
+ // If it's just missing module, maybe we are in test mode or fresh install
91
+ if (process.env.NODE_ENV !== "test") {
92
+ console.warn(`⚠️ Could not load user routes from ${userRoutesPath}. (This is expected during initial setup or if src/routes is missing)`);
93
+ }
94
+ else {
95
+ // In test mode, we really want to know if it failed to load
96
+ console.error(`Error loading routes in test mode from ${userRoutesPath}:`, e);
97
+ }
98
+ }
82
99
  }
83
100
  catch (error) {
84
- console.warn("⚠️ Could not load user routes. Make sure you export 'apiRouter'.");
85
101
  console.error(error);
86
102
  }
87
103
  app.use(error_1.errorHandler);
104
+ return app;
105
+ }
106
+ async function bootstrap() {
107
+ // Validasi Environment Variables
108
+ const requiredEnvs = ["DATABASE_URL", "JWT_SECRET"];
109
+ const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);
110
+ if (missingEnvs.length > 0) {
111
+ console.error(`❌ Missing required environment variables: ${missingEnvs.join(", ")}`);
112
+ process.exit(1);
113
+ }
114
+ const app = await createApp();
88
115
  const port = process.env.PORT ? Number(process.env.PORT) : 4000;
89
116
  const server = http_1.default.createServer(app);
90
117
  (0, realtime_1.initRealtime)(server);
@@ -10,11 +10,11 @@ export declare class Validator {
10
10
  /**
11
11
  * Create a new Validator instance
12
12
  * @param data The input data to validate
13
- * @param schema Zod schema or object of Zod schemas / Laravel-style rules
14
- * @param messages Optional custom error messages (Laravel style: 'field.rule' => 'message')
13
+ * @param schema Zod schema or object of Zod schemas / string-based rules
14
+ * @param messages Optional custom error messages (style: 'field.rule' => 'message')
15
15
  */
16
16
  static make(data: any, schema: ZodSchema<any> | Record<string, any>, messages?: Record<string, string>): Validator;
17
- private static parseLaravelRule;
17
+ private static parseStringRule;
18
18
  /**
19
19
  * Check if validation fails
20
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../lib/utils/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,SAAS,EAAsB,MAAM,KAAK,CAAC;AAGvD,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAkB;gBAG9B,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAavC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CACT,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAiDvC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAyN/B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAK/B;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC;;OAEG;IACH,MAAM;IAKN;;OAEG;IACG,SAAS;YAUD,GAAG;IAgBjB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,gBAAgB;CAoDzB"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../lib/utils/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,SAAS,EAAsB,MAAM,KAAK,CAAC;AAGvD,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAkB;gBAG9B,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAavC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CACT,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAiDvC,OAAO,CAAC,MAAM,CAAC,eAAe;IAyN9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAK/B;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC;;OAEG;IACH,MAAM;IAKN;;OAEG;IACG,SAAS;YAUD,GAAG;IAgBjB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,gBAAgB;CAoDzB"}
@@ -21,8 +21,8 @@ class Validator {
21
21
  /**
22
22
  * Create a new Validator instance
23
23
  * @param data The input data to validate
24
- * @param schema Zod schema or object of Zod schemas / Laravel-style rules
25
- * @param messages Optional custom error messages (Laravel style: 'field.rule' => 'message')
24
+ * @param schema Zod schema or object of Zod schemas / string-based rules
25
+ * @param messages Optional custom error messages (style: 'field.rule' => 'message')
26
26
  */
27
27
  static make(data, schema, messages = {}) {
28
28
  if (schema instanceof zod_1.ZodSchema) {
@@ -44,7 +44,7 @@ class Validator {
44
44
  sameRules.push({ field: key, target: args });
45
45
  }
46
46
  }
47
- parsedSchema[key] = Validator.parseLaravelRule(rule);
47
+ parsedSchema[key] = Validator.parseStringRule(rule);
48
48
  }
49
49
  else {
50
50
  parsedSchema[key] = rule;
@@ -66,7 +66,7 @@ class Validator {
66
66
  }
67
67
  return new Validator(data, objectSchema, messages);
68
68
  }
69
- static parseLaravelRule(rule) {
69
+ static parseStringRule(rule) {
70
70
  const rules = Array.isArray(rule)
71
71
  ? rule
72
72
  : rule.split("|").map((r) => r.trim());
@@ -107,7 +107,7 @@ async function register(req, res) {
107
107
  },
108
108
  });
109
109
  }
110
- (0, response_1.sendFastSuccess)(res, 200, registerSerializer, {
110
+ (0, response_1.sendFastSuccess)(res, 201, registerSerializer, {
111
111
  status: "success",
112
112
  message: "Registration successful",
113
113
  data: {
@@ -2,6 +2,21 @@
2
2
 
3
3
  File ini mencatat semua perubahan, pembaruan, dan perbaikan yang dilakukan pada framework Lapeh, diurutkan berdasarkan tanggal.
4
4
 
5
+ ## [2025-12-28] - Sunday, December 28, 2025 - Upgrade & Testing Improvements (v2.4.9)
6
+
7
+ ### 🚀 Features & Fixes
8
+
9
+ - **Smart Upgrade CLI**:
10
+
11
+ - Updated `npx lapeh upgrade` to perform full synchronization (mirroring).
12
+ - Files removed in the latest framework version are now automatically removed from user projects, keeping them clean.
13
+ - Removed `bin` folder from synchronization as it is managed by the package.
14
+
15
+ - **Comprehensive Testing Support**:
16
+ - Updated `tsconfig.json` to support `@lapeh/*` path aliases within the `tests` folder.
17
+ - The `tests` folder is now excluded from production builds (via `tsconfig.build.json`), resulting in a cleaner `dist/` folder.
18
+ - Jest documentation and configuration have been adjusted for seamless integration.
19
+
5
20
  ## [2025-12-28] - Minggu, 28 Desember 2025 - Perbaikan Kompatibilitas & Automasi
6
21
 
7
22
  ### 🛠️ Perbaikan Bug (Bug Fixes)
@@ -61,8 +76,8 @@ File ini mencatat semua perubahan, pembaruan, dan perbaikan yang dilakukan pada
61
76
 
62
77
  ### 🚀 Fitur Baru
63
78
 
64
- - **Laravel-style Validator**:
65
- - Implementasi utility `Validator` baru di `src/utils/validator.ts` yang meniru gaya validasi Laravel.
79
+ - **Expressive Validator**:
80
+ - Implementasi utility `Validator` baru di `src/utils/validator.ts` dengan gaya validasi yang lebih ekspresif.
66
81
  - Mendukung rule string seperti `required|string|min:3|email`.
67
82
  - Penambahan rule `unique` untuk pengecekan database otomatis (Prisma).
68
83
  - Penambahan rule `mimes`, `image`, `max` (file size) untuk validasi upload file.
@@ -18,7 +18,7 @@ Referensi cepat untuk perintah dan kode yang sering digunakan.
18
18
  | **`npm run db:seed`** | Isi data dummy. |
19
19
  | **`npm run db:reset`** | Hapus DB & mulai dari nol. |
20
20
 
21
- ## 🛡️ Validator Rules (Laravel-Style)
21
+ ## 🛡️ Validator Rules (Simple Syntax)
22
22
 
23
23
  Gunakan di `Validator.make(data, rules)`.
24
24
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  This document explains the key features of Lapeh Framework and how to use them in depth.
4
4
 
5
- ## 1. Data Validation (Laravel-Style)
5
+ ## 1. Data Validation (Simple & Powerful)
6
6
 
7
- The framework provides a `Validator` utility inspired by Laravel, using `zod` behind the scenes but with an API that is more string-based and readable.
7
+ The framework provides a `Validator` utility inspired by expressive modern validation styles, using `zod` behind the scenes but with an API that is more string-based and readable.
8
8
 
9
9
  **Location:** `@lapeh/utils/validator`
10
10
 
@@ -42,7 +42,7 @@ export async function createProduct(req: Request, res: Response) {
42
42
  - `image`: File must be an image (jpg, png, webp, etc).
43
43
  - `mimes:types`: File must be a specific type (e.g., `mimes:pdf,docx`).
44
44
 
45
- ## 2. High Performance Response (Fastify-Style)
45
+ ## 2. High Performance Response (Fastify-Like)
46
46
 
47
47
  For endpoints requiring high performance (e.g., large data lists), use schema-based serialization. This is much faster than standard Express `res.json`.
48
48
 
@@ -4,33 +4,35 @@
4
4
 
5
5
  **Lapeh** is a Backend Framework for Node.js built on top of **Express** and **TypeScript**.
6
6
 
7
- If you have ever used **Laravel** (PHP) or **NestJS** (Node.js), you will feel very familiar. Lapeh adopts the philosophy of ease-of-use & clean structure from Laravel, while maintaining the flexibility and speed of Express.
7
+ If you have ever used other modern frameworks, you will feel very familiar. Lapeh adopts the philosophy of ease-of-use & clean structure, while maintaining the flexibility and speed of Express.
8
8
 
9
9
  The name "Lapeh" comes from the Minang language which means "Loose" or "Free", symbolizing the freedom for developers to build applications quickly without being burdened by complicated configurations.
10
10
 
11
11
  ## Why was Lapeh Created?
12
12
 
13
13
  In the Node.js ecosystem, developers often experience "Decision Fatigue":
14
+
14
15
  - "Which ORM to use? Prisma, TypeORM, or Drizzle?"
15
16
  - "Validation using Joi, Zod, or express-validator?"
16
17
  - "How about the folder structure? MVC? Clean Architecture?"
17
18
  - "How to handle Auth?"
18
19
 
19
20
  Lapeh answers all of that with **Opinionated Defaults**:
21
+
20
22
  1. **ORM**: Prisma (Current industry standard).
21
- 2. **Validation**: Zod (with Laravel-style wrapper syntax).
23
+ 2. **Validation**: Zod (Powerful and readable schema validation).
22
24
  3. **Structure**: Modular MVC (Controller, Model, Route separated but cohesive).
23
25
  4. **Auth**: Ready-to-use JWT + RBAC (Role Based Access Control).
24
26
 
25
27
  ## Comparison with Other Frameworks
26
28
 
27
- | Feature | Express (Raw) | NestJS | Lapeh Framework |
28
- | :--- | :--- | :--- | :--- |
29
+ | Feature | Express (Raw) | NestJS | Lapeh Framework |
30
+ | :----------------- | :---------------------------- | :------------------------------- | :------------------------------------- |
29
31
  | **Learning Curve** | Low (but confusing structure) | High (Angular-style, Decorators) | **Medium** (Express + Clear Structure) |
30
- | **Boilerplate** | Empty | Very Heavy | **Just Right (Ready to use)** |
31
- | **Type Safety** | Manual | Strict | **Strict (Native TypeScript)** |
32
- | **Dev Speed** | Slow (manual setup) | Medium | **Fast (CLI Generator)** |
33
- | **Flexibility** | Very High | Rigid | **High** |
32
+ | **Boilerplate** | Empty | Very Heavy | **Just Right (Ready to use)** |
33
+ | **Type Safety** | Manual | Strict | **Strict (Native TypeScript)** |
34
+ | **Dev Speed** | Slow (manual setup) | Medium | **Fast (CLI Generator)** |
35
+ | **Flexibility** | Very High | Rigid | **High** |
34
36
 
35
37
  ## "The Lapeh Way" Philosophy
36
38
 
@@ -67,7 +67,7 @@ Built-in framework middleware.
67
67
 
68
68
  Built-in Helper functions.
69
69
 
70
- - `validator.ts`: Laravel-style input validation.
70
+ - `validator.ts`: Powerful and expressive input validation.
71
71
  - `response.ts`: Standard JSON response format (`sendFastSuccess`, `sendError`).
72
72
  - `logger.ts`: Logging system (Winston).
73
73
 
@@ -2,6 +2,21 @@
2
2
 
3
3
  File ini mencatat semua perubahan, pembaruan, dan perbaikan yang dilakukan pada framework Lapeh, diurutkan berdasarkan tanggal.
4
4
 
5
+ ## [2025-12-28] - Minggu, 28 Desember 2025 - Perbaikan Upgrade & Testing (v2.4.9)
6
+
7
+ ### 🚀 Fitur & Perbaikan
8
+
9
+ - **Smart Upgrade CLI**:
10
+
11
+ - Memperbarui perintah `npx lapeh upgrade` agar melakukan sinkronisasi penuh (mirroring).
12
+ - File yang dihapus di versi terbaru framework sekarang akan otomatis dihapus juga dari proyek pengguna, menjaga proyek tetap bersih.
13
+ - Menghapus folder `bin` dari proses sinkronisasi ke proyek pengguna karena folder tersebut dikelola oleh paket.
14
+
15
+ - **Dukungan Testing Komprehensif**:
16
+ - Konfigurasi `tsconfig.json` diperbarui untuk mendukung path alias `@lapeh/*` di dalam folder `tests`.
17
+ - Folder `tests` sekarang dikecualikan dari proses build produksi (via `tsconfig.build.json`), menghasilkan folder `dist/` yang lebih bersih.
18
+ - Dokumentasi dan konfigurasi Jest telah disesuaikan untuk integrasi yang mulus.
19
+
5
20
  ## [2025-12-28] - Minggu, 28 Desember 2025 - Perbaikan Kompatibilitas & Automasi
6
21
 
7
22
  ### 🛠️ Perbaikan Bug (Bug Fixes)
@@ -61,8 +76,8 @@ File ini mencatat semua perubahan, pembaruan, dan perbaikan yang dilakukan pada
61
76
 
62
77
  ### 🚀 Fitur Baru
63
78
 
64
- - **Laravel-style Validator**:
65
- - Implementasi utility `Validator` baru di `src/utils/validator.ts` yang meniru gaya validasi Laravel.
79
+ - **Expressive Validator**:
80
+ - Implementasi utility `Validator` baru di `src/utils/validator.ts` dengan gaya validasi yang lebih ekspresif.
66
81
  - Mendukung rule string seperti `required|string|min:3|email`.
67
82
  - Penambahan rule `unique` untuk pengecekan database otomatis (Prisma).
68
83
  - Penambahan rule `mimes`, `image`, `max` (file size) untuk validasi upload file.
@@ -18,7 +18,7 @@ Referensi cepat untuk perintah dan kode yang sering digunakan.
18
18
  | **`npm run db:seed`** | Isi data dummy. |
19
19
  | **`npm run db:reset`** | Hapus DB & mulai dari nol. |
20
20
 
21
- ## 🛡️ Validator Rules (Laravel-Style)
21
+ ## 🛡️ Validator Rules (Simple Syntax)
22
22
 
23
23
  Gunakan di `Validator.make(data, rules)`.
24
24
 
@@ -2,9 +2,11 @@
2
2
 
3
3
  Dokumen ini menjelaskan fitur-fitur utama Lapeh Framework dan cara penggunaannya secara mendalam.
4
4
 
5
- ## 1. Validasi Data (Laravel-Style)
5
+ 3. **Explicit is Better than Implicit**: Tidak ada "sihir" yang terlalu gelap. Kode controller Anda adalah kode Express biasa yang Anda mengerti.
6
6
 
7
- Framework ini menyediakan utility `Validator` yang terinspirasi dari Laravel, menggunakan `zod` di belakang layar namun dengan API yang lebih string-based dan mudah dibaca.
7
+ ## 1. Validasi Data (Simple & Powerful)
8
+
9
+ Framework ini menyediakan utility `Validator` yang terinspirasi dari gaya validasi modern yang ekspresif, menggunakan `zod` di belakang layar namun dengan API yang lebih string-based dan mudah dibaca.
8
10
 
9
11
  **Lokasi:** `@lapeh/utils/validator`
10
12
 
@@ -42,7 +44,7 @@ export async function createProduct(req: Request, res: Response) {
42
44
  - `image`: File harus berupa gambar (jpg, png, webp, dll).
43
45
  - `mimes:types`: File harus tipe tertentu (misal: `mimes:pdf,docx`).
44
46
 
45
- ## 2. High Performance Response (Fastify-Style)
47
+ ## 2. High Performance Response (Fastify-Like)
46
48
 
47
49
  Untuk endpoint yang membutuhkan performa tinggi (misalnya list data besar), gunakan serialisasi berbasis schema. Ini jauh lebih cepat daripada `res.json` standar Express.
48
50
 
@@ -4,33 +4,35 @@
4
4
 
5
5
  **Lapeh** adalah framework Backend untuk Node.js yang dibangun di atas **Express** dan **TypeScript**.
6
6
 
7
- Jika Anda pernah menggunakan **Laravel** (PHP) atau **NestJS** (Node.js), Anda akan merasa sangat familiar. Lapeh mengambil filosofi kemudahan & struktur rapi dari Laravel, namun tetap mempertahankan fleksibilitas dan kecepatan Express.
7
+ Jika Anda pernah menggunakan framework modern lainnya, Anda akan merasa sangat familiar. Lapeh mengambil filosofi kemudahan & struktur rapi, namun tetap mempertahankan fleksibilitas dan kecepatan Express.
8
8
 
9
9
  Nama "Lapeh" diambil dari bahasa Minang yang berarti "Lepas" atau "Bebas", melambangkan kebebasan developer untuk membangun aplikasi dengan cepat tanpa terbebani konfigurasi yang rumit.
10
10
 
11
11
  ## Mengapa Lapeh Dibuat?
12
12
 
13
13
  Di ekosistem Node.js, developer sering mengalami "Decision Fatigue" (Kelelahan memilih):
14
+
14
15
  - "Pakai ORM apa? Prisma, TypeORM, atau Drizzle?"
15
16
  - "Validasi pakai Joi, Zod, atau express-validator?"
16
17
  - "Struktur foldernya gimana? MVC? Clean Architecture?"
17
18
  - "Auth-nya gimana?"
18
19
 
19
20
  Lapeh menjawab semua itu dengan **Opinionated Defaults**:
21
+
20
22
  1. **ORM**: Prisma (Standar industri saat ini).
21
- 2. **Validasi**: Zod (dengan wrapper syntax ala Laravel).
23
+ 2. **Validasi**: Zod (Validasi skema yang kuat dan mudah dibaca).
22
24
  3. **Struktur**: MVC Modular (Controller, Model, Route terpisah tapi kohesif).
23
25
  4. **Auth**: JWT + RBAC (Role Based Access Control) siap pakai.
24
26
 
25
27
  ## Perbandingan dengan Framework Lain
26
28
 
27
- | Fitur | Express (Raw) | NestJS | Lapeh Framework |
28
- | :--- | :--- | :--- | :--- |
29
+ | Fitur | Express (Raw) | NestJS | Lapeh Framework |
30
+ | :----------------- | :----------------------------- | :--------------------------------- | :------------------------------------ |
29
31
  | **Learning Curve** | Rendah (tapi bingung struktur) | Tinggi (Angular-style, Decorators) | **Sedang** (Express + Struktur Jelas) |
30
- | **Boilerplate** | Kosong | Sangat Banyak | **Pas (Ready to use)** |
31
- | **Type Safety** | Manual | Strict | **Strict (TypeScript Native)** |
32
- | **Kecepatan Dev** | Lambat (setup manual) | Sedang | **Cepat (CLI Generator)** |
33
- | **Fleksibilitas** | Sangat Tinggi | Kaku | **Tinggi** |
32
+ | **Boilerplate** | Kosong | Sangat Banyak | **Pas (Ready to use)** |
33
+ | **Type Safety** | Manual | Strict | **Strict (TypeScript Native)** |
34
+ | **Kecepatan Dev** | Lambat (setup manual) | Sedang | **Cepat (CLI Generator)** |
35
+ | **Fleksibilitas** | Sangat Tinggi | Kaku | **Tinggi** |
34
36
 
35
37
  ## Filosofi "The Lapeh Way"
36
38
 
@@ -67,7 +67,7 @@ Middleware bawaan framework.
67
67
 
68
68
  Fungsi bantuan (Helper) bawaan.
69
69
 
70
- - `validator.ts`: Validasi input ala Laravel.
70
+ - `validator.ts`: Validasi input yang kuat dan ekspresif.
71
71
  - `response.ts`: Standar format JSON response (`sendFastSuccess`, `sendError`).
72
72
  - `logger.ts`: Sistem logging (Winston).
73
73
 
package/lib/bootstrap.ts CHANGED
@@ -17,22 +17,12 @@ import { apiLimiter } from "./middleware/rateLimit";
17
17
  import { requestLogger } from "./middleware/requestLogger";
18
18
  import { sendSuccess } from "./utils/response";
19
19
 
20
- export async function bootstrap() {
20
+ export async function createApp() {
21
21
  // Register aliases for production runtime
22
22
  // Since user code (compiled JS) uses require('@lapeh/...')
23
23
  // We map '@lapeh' to the directory containing this file (lib/ or dist/lib/)
24
24
  moduleAlias.addAlias("@lapeh", __dirname);
25
25
 
26
- // Validasi Environment Variables
27
- const requiredEnvs = ["DATABASE_URL", "JWT_SECRET"];
28
- const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);
29
- if (missingEnvs.length > 0) {
30
- console.error(
31
- `❌ Missing required environment variables: ${missingEnvs.join(", ")}`
32
- );
33
- process.exit(1);
34
- }
35
-
36
26
  const app = express();
37
27
 
38
28
  app.disable("x-powered-by");
@@ -88,22 +78,64 @@ export async function bootstrap() {
88
78
  : path.join(process.cwd(), "src", "routes");
89
79
 
90
80
  // Gunakan require agar sinkron dan mudah dicatch
91
- const { apiRouter } = require(userRoutesPath);
92
- app.use("/api", apiRouter);
93
- console.log(
94
- `✅ User routes loaded successfully from ${
95
- isProduction ? "dist/" : ""
96
- }src/routes`
97
- );
81
+ // Check if file exists before requiring to avoid crash in tests/clean env
82
+ try {
83
+ // In test environment, we might need to point to src/routes explicitly if not compiled
84
+ // const routesPath = process.env.NODE_ENV === 'test'
85
+ // ? path.join(process.cwd(), "src", "routes", "index.ts")
86
+ // : userRoutesPath;
87
+
88
+ // Note: For TS files in jest, we rely on ts-jest handling 'require' if it points to .ts or we need to use 'import'
89
+ // But 'require' in jest with ts-jest should work if configured.
90
+
91
+ // However, require(path) with .ts extension might be tricky.
92
+ // Let's stick to userRoutesPath but maybe adjust for test env.
93
+
94
+ // Check if we are in test environment and using ts-jest
95
+ // If so, we might need to import the TS file directly via relative path if alias is not working for require
96
+
97
+ const { apiRouter } = require(userRoutesPath);
98
+ app.use("/api", apiRouter);
99
+ console.log(
100
+ `✅ User routes loaded successfully from ${
101
+ isProduction ? "dist/" : ""
102
+ }src/routes`
103
+ );
104
+ } catch (e) {
105
+ // If it's just missing module, maybe we are in test mode or fresh install
106
+ if (process.env.NODE_ENV !== "test") {
107
+ console.warn(
108
+ `⚠️ Could not load user routes from ${userRoutesPath}. (This is expected during initial setup or if src/routes is missing)`
109
+ );
110
+ } else {
111
+ // In test mode, we really want to know if it failed to load
112
+ console.error(
113
+ `Error loading routes in test mode from ${userRoutesPath}:`,
114
+ e
115
+ );
116
+ }
117
+ }
98
118
  } catch (error) {
99
- console.warn(
100
- "⚠️ Could not load user routes. Make sure you export 'apiRouter'."
101
- );
102
119
  console.error(error);
103
120
  }
104
121
 
105
122
  app.use(errorHandler);
106
123
 
124
+ return app;
125
+ }
126
+
127
+ export async function bootstrap() {
128
+ // Validasi Environment Variables
129
+ const requiredEnvs = ["DATABASE_URL", "JWT_SECRET"];
130
+ const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);
131
+ if (missingEnvs.length > 0) {
132
+ console.error(
133
+ `❌ Missing required environment variables: ${missingEnvs.join(", ")}`
134
+ );
135
+ process.exit(1);
136
+ }
137
+
138
+ const app = await createApp();
107
139
  const port = process.env.PORT ? Number(process.env.PORT) : 4000;
108
140
  const server = http.createServer(app);
109
141
 
@@ -28,8 +28,8 @@ export class Validator {
28
28
  /**
29
29
  * Create a new Validator instance
30
30
  * @param data The input data to validate
31
- * @param schema Zod schema or object of Zod schemas / Laravel-style rules
32
- * @param messages Optional custom error messages (Laravel style: 'field.rule' => 'message')
31
+ * @param schema Zod schema or object of Zod schemas / string-based rules
32
+ * @param messages Optional custom error messages (style: 'field.rule' => 'message')
33
33
  */
34
34
  static make(
35
35
  data: any,
@@ -58,7 +58,7 @@ export class Validator {
58
58
  }
59
59
  }
60
60
 
61
- parsedSchema[key] = Validator.parseLaravelRule(rule);
61
+ parsedSchema[key] = Validator.parseStringRule(rule);
62
62
  } else {
63
63
  parsedSchema[key] = rule as ZodSchema;
64
64
  }
@@ -83,7 +83,7 @@ export class Validator {
83
83
  return new Validator(data, objectSchema, messages);
84
84
  }
85
85
 
86
- private static parseLaravelRule(rule: string | string[]): ZodSchema {
86
+ private static parseStringRule(rule: string | string[]): ZodSchema {
87
87
  const rules = Array.isArray(rule)
88
88
  ? rule
89
89
  : rule.split("|").map((r) => r.trim());