lapeeh 1.0.0
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/.env.example +14 -0
- package/LICENSE +21 -0
- package/bin/index.js +934 -0
- package/doc/en/ARCHITECTURE_GUIDE.md +79 -0
- package/doc/en/CHANGELOG.md +203 -0
- package/doc/en/CHEATSHEET.md +90 -0
- package/doc/en/CLI.md +111 -0
- package/doc/en/CONTRIBUTING.md +119 -0
- package/doc/en/DEPLOYMENT.md +171 -0
- package/doc/en/FAQ.md +69 -0
- package/doc/en/FEATURES.md +99 -0
- package/doc/en/GETTING_STARTED.md +84 -0
- package/doc/en/INTRODUCTION.md +62 -0
- package/doc/en/PACKAGES.md +63 -0
- package/doc/en/PERFORMANCE.md +98 -0
- package/doc/en/ROADMAP.md +104 -0
- package/doc/en/SECURITY.md +95 -0
- package/doc/en/STRUCTURE.md +79 -0
- package/doc/en/TUTORIAL.md +145 -0
- package/doc/id/ARCHITECTURE_GUIDE.md +76 -0
- package/doc/id/CHANGELOG.md +203 -0
- package/doc/id/CHEATSHEET.md +90 -0
- package/doc/id/CLI.md +139 -0
- package/doc/id/CONTRIBUTING.md +119 -0
- package/doc/id/DEPLOYMENT.md +171 -0
- package/doc/id/FAQ.md +69 -0
- package/doc/id/FEATURES.md +169 -0
- package/doc/id/GETTING_STARTED.md +91 -0
- package/doc/id/INTRODUCTION.md +62 -0
- package/doc/id/PACKAGES.md +63 -0
- package/doc/id/PERFORMANCE.md +100 -0
- package/doc/id/ROADMAP.md +107 -0
- package/doc/id/SECURITY.md +94 -0
- package/doc/id/STRUCTURE.md +79 -0
- package/doc/id/TUTORIAL.md +145 -0
- package/docker-compose.yml +24 -0
- package/ecosystem.config.js +17 -0
- package/eslint.config.mjs +26 -0
- package/gitignore.template +30 -0
- package/lib/bootstrap.ts +210 -0
- package/lib/core/realtime.ts +34 -0
- package/lib/core/redis.ts +139 -0
- package/lib/core/serializer.ts +63 -0
- package/lib/core/server.ts +70 -0
- package/lib/core/store.ts +116 -0
- package/lib/middleware/auth.ts +63 -0
- package/lib/middleware/error.ts +50 -0
- package/lib/middleware/multipart.ts +13 -0
- package/lib/middleware/rateLimit.ts +14 -0
- package/lib/middleware/requestLogger.ts +27 -0
- package/lib/middleware/visitor.ts +178 -0
- package/lib/utils/logger.ts +100 -0
- package/lib/utils/pagination.ts +56 -0
- package/lib/utils/response.ts +88 -0
- package/lib/utils/validator.ts +394 -0
- package/nodemon.json +6 -0
- package/package.json +126 -0
- package/readme.md +357 -0
- package/scripts/check-update.js +92 -0
- package/scripts/config-clear.js +45 -0
- package/scripts/generate-jwt-secret.js +38 -0
- package/scripts/init-project.js +84 -0
- package/scripts/make-module.js +89 -0
- package/scripts/release.js +494 -0
- package/scripts/seed-json.js +158 -0
- package/scripts/verify-rbac-functional.js +187 -0
- package/src/config/app.ts +9 -0
- package/src/config/cors.ts +5 -0
- package/src/modules/Auth/auth.controller.ts +519 -0
- package/src/modules/Rbac/rbac.controller.ts +533 -0
- package/src/routes/auth.ts +74 -0
- package/src/routes/index.ts +7 -0
- package/src/routes/rbac.ts +42 -0
- package/storage/logs/.gitkeep +0 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { z, ZodSchema, ZodError, ZodIssue } from "zod";
|
|
2
|
+
|
|
3
|
+
export class Validator {
|
|
4
|
+
private data: any;
|
|
5
|
+
private schema: ZodSchema<any>;
|
|
6
|
+
private customMessages: Record<string, string>;
|
|
7
|
+
private result: any = null;
|
|
8
|
+
private errorResult: any = null;
|
|
9
|
+
private hasRun: boolean = false;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
data: any,
|
|
13
|
+
schema: ZodSchema<any> | Record<string, any>,
|
|
14
|
+
messages: Record<string, string> = {}
|
|
15
|
+
) {
|
|
16
|
+
this.data = data;
|
|
17
|
+
this.customMessages = messages;
|
|
18
|
+
|
|
19
|
+
// If it's a raw object, wrap it in z.object()
|
|
20
|
+
if (schema instanceof ZodSchema) {
|
|
21
|
+
this.schema = schema;
|
|
22
|
+
} else {
|
|
23
|
+
this.schema = z.object(schema as any);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a new Validator instance
|
|
29
|
+
* @param data The input data to validate
|
|
30
|
+
* @param schema Zod schema or object of Zod schemas / string-based rules
|
|
31
|
+
* @param messages Optional custom error messages (style: 'field.rule' => 'message')
|
|
32
|
+
*/
|
|
33
|
+
static make(
|
|
34
|
+
data: any,
|
|
35
|
+
schema: ZodSchema<any> | Record<string, any>,
|
|
36
|
+
messages: Record<string, string> = {}
|
|
37
|
+
) {
|
|
38
|
+
if (schema instanceof ZodSchema) {
|
|
39
|
+
return new Validator(data, schema, messages);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const parsedSchema: Record<string, ZodSchema> = {};
|
|
43
|
+
const sameRules: { field: string; target: string }[] = [];
|
|
44
|
+
|
|
45
|
+
for (const [key, rule] of Object.entries(schema)) {
|
|
46
|
+
if (rule instanceof ZodSchema) {
|
|
47
|
+
parsedSchema[key] = rule;
|
|
48
|
+
} else if (typeof rule === "string" || Array.isArray(rule)) {
|
|
49
|
+
const ruleStr = Array.isArray(rule) ? rule.join("|") : rule;
|
|
50
|
+
|
|
51
|
+
// Check for 'same:target'
|
|
52
|
+
const parts = ruleStr.split("|");
|
|
53
|
+
for (const part of parts) {
|
|
54
|
+
const [name, args] = part.split(":");
|
|
55
|
+
if (name === "same" && args) {
|
|
56
|
+
sameRules.push({ field: key, target: args });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
parsedSchema[key] = Validator.parseStringRule(rule);
|
|
61
|
+
} else {
|
|
62
|
+
parsedSchema[key] = rule as ZodSchema;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let objectSchema: ZodSchema = z.object(parsedSchema);
|
|
67
|
+
|
|
68
|
+
if (sameRules.length > 0) {
|
|
69
|
+
objectSchema = (objectSchema as any).superRefine((val: any, ctx: any) => {
|
|
70
|
+
sameRules.forEach(({ field, target }) => {
|
|
71
|
+
if (val[field] !== val[target]) {
|
|
72
|
+
ctx.addIssue({
|
|
73
|
+
code: z.ZodIssueCode.custom,
|
|
74
|
+
message: `The ${field} and ${target} must match.`,
|
|
75
|
+
path: [field],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return new Validator(data, objectSchema, messages);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private static parseStringRule(rule: string | string[]): ZodSchema {
|
|
86
|
+
const rules = Array.isArray(rule)
|
|
87
|
+
? rule
|
|
88
|
+
: rule.split("|").map((r) => r.trim());
|
|
89
|
+
|
|
90
|
+
let schema: any = z.string(); // Default base
|
|
91
|
+
let isOptional = false;
|
|
92
|
+
let isNullable = false;
|
|
93
|
+
|
|
94
|
+
// 1. Determine Base Type
|
|
95
|
+
const isFile =
|
|
96
|
+
rules.includes("file") ||
|
|
97
|
+
rules.includes("image") ||
|
|
98
|
+
rules.includes("mimes");
|
|
99
|
+
|
|
100
|
+
if (rules.includes("numeric") || rules.includes("integer")) {
|
|
101
|
+
schema = z.coerce.number();
|
|
102
|
+
if (rules.includes("integer")) schema = schema.int();
|
|
103
|
+
} else if (rules.includes("boolean")) {
|
|
104
|
+
schema = z.boolean();
|
|
105
|
+
} else if (rules.includes("array")) {
|
|
106
|
+
schema = z.array(z.any());
|
|
107
|
+
} else if (rules.includes("date")) {
|
|
108
|
+
schema = z.coerce.date();
|
|
109
|
+
} else if (isFile) {
|
|
110
|
+
schema = z.any().refine(
|
|
111
|
+
(val) => {
|
|
112
|
+
// Check if it looks like a Multer file object
|
|
113
|
+
return (
|
|
114
|
+
typeof val === "object" &&
|
|
115
|
+
val !== null &&
|
|
116
|
+
"fieldname" in val &&
|
|
117
|
+
"originalname" in val &&
|
|
118
|
+
"encoding" in val &&
|
|
119
|
+
"mimetype" in val &&
|
|
120
|
+
"size" in val
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
{ message: "The field must be a file." }
|
|
124
|
+
);
|
|
125
|
+
} else {
|
|
126
|
+
schema = z.string();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 2. Constraints & Modifiers
|
|
130
|
+
for (const r of rules) {
|
|
131
|
+
const [name, args] = r.split(":");
|
|
132
|
+
const params = args ? args.split(",") : [];
|
|
133
|
+
|
|
134
|
+
switch (name) {
|
|
135
|
+
case "email":
|
|
136
|
+
if (schema instanceof z.ZodString) schema = schema.email();
|
|
137
|
+
break;
|
|
138
|
+
case "url":
|
|
139
|
+
if (schema instanceof z.ZodString) schema = schema.url();
|
|
140
|
+
break;
|
|
141
|
+
case "uuid":
|
|
142
|
+
if (schema instanceof z.ZodString) schema = schema.uuid();
|
|
143
|
+
break;
|
|
144
|
+
case "min":
|
|
145
|
+
if (!isNaN(Number(params[0]))) schema = schema.min(Number(params[0]));
|
|
146
|
+
break;
|
|
147
|
+
case "max":
|
|
148
|
+
if (!isNaN(Number(params[0]))) {
|
|
149
|
+
if (isFile) {
|
|
150
|
+
schema = schema.refine(
|
|
151
|
+
(val: any) => {
|
|
152
|
+
if (!val) return true; // Let nullable/optional handle nulls
|
|
153
|
+
// But wait, if we are in strict mode (not nullable), we should fail?
|
|
154
|
+
// No, max() usually doesn't enforce existence. required() does.
|
|
155
|
+
// But here val is the file object.
|
|
156
|
+
// If val is missing, and it's required, Base Type would have failed (if I revert Base Type).
|
|
157
|
+
// So here val is likely a file object.
|
|
158
|
+
return val.size <= Number(params[0]) * 1024;
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
message: `The file may not be greater than ${params[0]} kilobytes.`,
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
schema = schema.max(Number(params[0]));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
case "image":
|
|
170
|
+
schema = schema.refine(
|
|
171
|
+
(val: any) => {
|
|
172
|
+
if (!val || typeof val !== "object" || !val.mimetype)
|
|
173
|
+
return false;
|
|
174
|
+
return val.mimetype.startsWith("image/");
|
|
175
|
+
},
|
|
176
|
+
{ message: "The field must be an image." }
|
|
177
|
+
);
|
|
178
|
+
break;
|
|
179
|
+
case "in":
|
|
180
|
+
if (params.length > 0) {
|
|
181
|
+
// For strings, we can use regex or refine
|
|
182
|
+
schema = schema.refine((val: any) => params.includes(String(val)), {
|
|
183
|
+
message: `The selected ${name} is invalid.`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
case "not_in":
|
|
188
|
+
if (params.length > 0) {
|
|
189
|
+
schema = schema.refine(
|
|
190
|
+
(val: any) => !params.includes(String(val)),
|
|
191
|
+
{
|
|
192
|
+
message: `The selected ${name} is invalid.`,
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
case "alpha":
|
|
198
|
+
if (schema instanceof z.ZodString)
|
|
199
|
+
schema = schema.regex(
|
|
200
|
+
/^[a-zA-Z]+$/,
|
|
201
|
+
"The field must only contain letters."
|
|
202
|
+
);
|
|
203
|
+
break;
|
|
204
|
+
case "alpha_dash":
|
|
205
|
+
if (schema instanceof z.ZodString)
|
|
206
|
+
schema = schema.regex(
|
|
207
|
+
/^[a-zA-Z0-9_-]+$/,
|
|
208
|
+
"The field must only contain letters, numbers, dashes, and underscores."
|
|
209
|
+
);
|
|
210
|
+
break;
|
|
211
|
+
case "alpha_num":
|
|
212
|
+
if (schema instanceof z.ZodString)
|
|
213
|
+
schema = schema.regex(
|
|
214
|
+
/^[a-zA-Z0-9]+$/,
|
|
215
|
+
"The field must only contain letters and numbers."
|
|
216
|
+
);
|
|
217
|
+
break;
|
|
218
|
+
case "mimes":
|
|
219
|
+
// For file validation
|
|
220
|
+
schema = schema.refine(
|
|
221
|
+
(val: any) => {
|
|
222
|
+
if (!val || typeof val !== "object" || !val.mimetype)
|
|
223
|
+
return false;
|
|
224
|
+
// params are extensions (jpg, png). We need to map to mimetypes or check loosely.
|
|
225
|
+
// Simplification: check if mimetype includes one of the params
|
|
226
|
+
// Or better: map extensions to mimetypes? Too big.
|
|
227
|
+
// Let's assume params are extensions, and we check if mimetype matches.
|
|
228
|
+
// Actually, typically mimes:jpg,png means we check extension or mime.
|
|
229
|
+
// For now, let's just check if mimetype contains the string (imperfect but simple)
|
|
230
|
+
// OR check originalname extension.
|
|
231
|
+
const extension = val.originalname
|
|
232
|
+
?.split(".")
|
|
233
|
+
.pop()
|
|
234
|
+
?.toLowerCase();
|
|
235
|
+
return params.includes(extension);
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
message: `The file must be a file of type: ${params.join(", ")}.`,
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
break;
|
|
242
|
+
case "unique":
|
|
243
|
+
// unique:table,column,ignore,idColumn
|
|
244
|
+
// NOTE: Unique check requires Database implementation.
|
|
245
|
+
// Since v3.0.0 (No-ORM), this rule is disabled by default.
|
|
246
|
+
// You should implement your own uniqueness check manually in the controller.
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 3. Modifiers
|
|
252
|
+
const isRequired = rules.includes("required");
|
|
253
|
+
isNullable = rules.includes("nullable");
|
|
254
|
+
isOptional = rules.includes("sometimes") || !isRequired;
|
|
255
|
+
|
|
256
|
+
if (isNullable) {
|
|
257
|
+
schema = schema.nullable();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (isOptional) {
|
|
261
|
+
schema = schema.optional();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return schema;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if validation fails
|
|
269
|
+
*/
|
|
270
|
+
async fails(): Promise<boolean> {
|
|
271
|
+
await this.run();
|
|
272
|
+
return this.errorResult !== null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Check if validation passes
|
|
277
|
+
*/
|
|
278
|
+
async passes(): Promise<boolean> {
|
|
279
|
+
return !(await this.fails());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get the validation errors
|
|
284
|
+
*/
|
|
285
|
+
errors() {
|
|
286
|
+
// Should be called after fails()
|
|
287
|
+
return this.errorResult || {};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get the validated data
|
|
292
|
+
*/
|
|
293
|
+
async validated() {
|
|
294
|
+
await this.run();
|
|
295
|
+
if (this.errorResult) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
"Validation failed. Check errors() before calling validated()."
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return this.result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private async run() {
|
|
304
|
+
if (this.hasRun) return;
|
|
305
|
+
this.hasRun = true;
|
|
306
|
+
|
|
307
|
+
// safeParseAsync to handle async refinements (like unique)
|
|
308
|
+
const parseResult = await this.schema.safeParseAsync(this.data);
|
|
309
|
+
|
|
310
|
+
if (parseResult.success) {
|
|
311
|
+
this.result = parseResult.data;
|
|
312
|
+
this.errorResult = null;
|
|
313
|
+
} else {
|
|
314
|
+
this.result = null;
|
|
315
|
+
this.errorResult = this.formatErrors(parseResult.error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private formatErrors(error: ZodError) {
|
|
320
|
+
// If we have custom messages, we try to apply them
|
|
321
|
+
if (Object.keys(this.customMessages).length > 0) {
|
|
322
|
+
error.issues = error.issues.map((issue) => {
|
|
323
|
+
const message = this.getCustomMessage(issue);
|
|
324
|
+
if (message) {
|
|
325
|
+
return { ...issue, message };
|
|
326
|
+
}
|
|
327
|
+
return issue;
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const flattened = error.flatten();
|
|
332
|
+
const errors: any = flattened.fieldErrors;
|
|
333
|
+
|
|
334
|
+
// Add formErrors if any
|
|
335
|
+
if (flattened.formErrors.length > 0) {
|
|
336
|
+
errors.formErrors = flattened.formErrors;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return errors;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private getCustomMessage(issue: ZodIssue): string | undefined {
|
|
343
|
+
const path = issue.path.join(".");
|
|
344
|
+
let rule = "";
|
|
345
|
+
|
|
346
|
+
// Map Zod issue codes to "Laravel-like" rules for key lookup
|
|
347
|
+
switch (issue.code) {
|
|
348
|
+
case "invalid_type":
|
|
349
|
+
if (issue.received === "undefined" || issue.received === "null") {
|
|
350
|
+
rule = "required";
|
|
351
|
+
} else {
|
|
352
|
+
// e.g. string, number, boolean
|
|
353
|
+
rule = issue.expected;
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
case "invalid_string":
|
|
357
|
+
if (typeof issue.validation === "string") {
|
|
358
|
+
rule = issue.validation; // email, url, uuid, etc.
|
|
359
|
+
} else {
|
|
360
|
+
rule = "string";
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
case "too_small":
|
|
364
|
+
rule = "min";
|
|
365
|
+
break;
|
|
366
|
+
case "too_big":
|
|
367
|
+
rule = "max";
|
|
368
|
+
break;
|
|
369
|
+
case "custom":
|
|
370
|
+
// Custom rules often have messages already, but we can override
|
|
371
|
+
// For unique, mimes, etc.
|
|
372
|
+
// We might need to inspect the message to guess the rule?
|
|
373
|
+
// Or just use the path.
|
|
374
|
+
rule = "custom";
|
|
375
|
+
break;
|
|
376
|
+
// Add more mappings as needed
|
|
377
|
+
default:
|
|
378
|
+
rule = issue.code;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Try keys: "field.rule" -> "field"
|
|
382
|
+
const specificKey = `${path}.${rule}`;
|
|
383
|
+
if (this.customMessages[specificKey]) {
|
|
384
|
+
return this.customMessages[specificKey];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Fallback for generic field message "field"
|
|
388
|
+
if (this.customMessages[path]) {
|
|
389
|
+
return this.customMessages[path];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
}
|
package/nodemon.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lapeeh",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "lapeeh Framework (Standardized by lapeeh)",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=18.0.0",
|
|
7
|
+
"npm": ">=9.0.0"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/lib/bootstrap.js",
|
|
10
|
+
"bin": {
|
|
11
|
+
"lapeeh": "bin/index.js"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/lapeeh/lapeeh.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://lapeeh.vercel.app",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/lapeeh/lapeeh/issues"
|
|
20
|
+
},
|
|
21
|
+
"types": "dist/src/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"bin",
|
|
24
|
+
"dist",
|
|
25
|
+
"lib",
|
|
26
|
+
"scripts",
|
|
27
|
+
"src",
|
|
28
|
+
"storage",
|
|
29
|
+
"doc",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"tsconfig.json",
|
|
33
|
+
"nodemon.json",
|
|
34
|
+
"docker-compose.yml",
|
|
35
|
+
".env.example",
|
|
36
|
+
"eslint.config.mjs",
|
|
37
|
+
"gitignore.template",
|
|
38
|
+
"ecosystem.config.js",
|
|
39
|
+
"tsconfig.build.json"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "node bin/index.js dev",
|
|
43
|
+
"first": "node scripts/init-project.js",
|
|
44
|
+
"build": "node bin/index.js build",
|
|
45
|
+
"start": "node bin/index.js start",
|
|
46
|
+
"start:prod": "node bin/index.js start",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "eslint .",
|
|
49
|
+
"lint:fix": "eslint . --fix",
|
|
50
|
+
"test": "jest",
|
|
51
|
+
"generate:jwt": "node scripts/generate-jwt-secret.js",
|
|
52
|
+
"make:module": "node scripts/make-module.js",
|
|
53
|
+
"make:modul": "node scripts/make-module.js",
|
|
54
|
+
"config:clear": "node scripts/config-clear.js",
|
|
55
|
+
"release": "node scripts/release.js"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"nodejs-framework",
|
|
59
|
+
"typescript-framework",
|
|
60
|
+
"express-framework",
|
|
61
|
+
"backend-framework",
|
|
62
|
+
"rest-api",
|
|
63
|
+
"production-ready",
|
|
64
|
+
"api-generator",
|
|
65
|
+
"boilerplate",
|
|
66
|
+
"starter-kit",
|
|
67
|
+
"mvc",
|
|
68
|
+
"cli",
|
|
69
|
+
"lapeeh",
|
|
70
|
+
"roby-ajo",
|
|
71
|
+
"ajo-roby",
|
|
72
|
+
"robyajo",
|
|
73
|
+
"lapeeh"
|
|
74
|
+
],
|
|
75
|
+
"author": "lapeeh <lapeehframework.dev@gmail.com>",
|
|
76
|
+
"license": "MIT",
|
|
77
|
+
"type": "commonjs",
|
|
78
|
+
"peerDependencies": {},
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"@types/bcryptjs": "2.4.6",
|
|
81
|
+
"@types/compression": "^1.8.1",
|
|
82
|
+
"@types/cors": "2.8.19",
|
|
83
|
+
"@types/express": "5.0.6",
|
|
84
|
+
"@types/jsonwebtoken": "9.0.10",
|
|
85
|
+
"@types/multer": "^2.0.0",
|
|
86
|
+
"@types/node": "25.0.3",
|
|
87
|
+
"@types/pg": "8.16.0",
|
|
88
|
+
"@types/uuid": "10.0.0",
|
|
89
|
+
"bcryptjs": "^2.4.3",
|
|
90
|
+
"compression": "^1.8.1",
|
|
91
|
+
"cors": "2.8.5",
|
|
92
|
+
"dotenv": "17.2.3",
|
|
93
|
+
"express": "5.2.1",
|
|
94
|
+
"express-rate-limit": "8.2.1",
|
|
95
|
+
"fast-json-stringify": "^6.1.1",
|
|
96
|
+
"helmet": "8.1.0",
|
|
97
|
+
"ioredis": "5.8.2",
|
|
98
|
+
"ioredis-mock": "^8.13.1",
|
|
99
|
+
"jsonwebtoken": "9.0.3",
|
|
100
|
+
"module-alias": "^2.2.3",
|
|
101
|
+
"multer": "2.0.2",
|
|
102
|
+
"pg": "8.16.3",
|
|
103
|
+
"slugify": "1.6.6",
|
|
104
|
+
"socket.io": "4.8.3",
|
|
105
|
+
"ts-node": "^10.9.2",
|
|
106
|
+
"tsconfig-paths": "^4.2.0",
|
|
107
|
+
"typescript": "^5.9.3",
|
|
108
|
+
"uuid": "13.0.0",
|
|
109
|
+
"winston": "^3.19.0",
|
|
110
|
+
"winston-daily-rotate-file": "^5.0.0",
|
|
111
|
+
"zod": "3.23.8"
|
|
112
|
+
},
|
|
113
|
+
"devDependencies": {
|
|
114
|
+
"@eslint/js": "^9.39.2",
|
|
115
|
+
"@types/bcrypt": "^5.0.2",
|
|
116
|
+
"@types/jest": "^30.0.0",
|
|
117
|
+
"@types/module-alias": "^2.0.4",
|
|
118
|
+
"@types/supertest": "^6.0.3",
|
|
119
|
+
"eslint": "^9.39.2",
|
|
120
|
+
"globals": "^16.5.0",
|
|
121
|
+
"jest": "^30.2.0",
|
|
122
|
+
"supertest": "^7.1.4",
|
|
123
|
+
"ts-jest": "^29.4.6",
|
|
124
|
+
"typescript-eslint": "^8.50.1"
|
|
125
|
+
}
|
|
126
|
+
}
|