create-phoenixjs 0.1.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.
Files changed (49) hide show
  1. package/index.ts +196 -0
  2. package/package.json +31 -0
  3. package/template/README.md +62 -0
  4. package/template/app/controllers/ExampleController.ts +61 -0
  5. package/template/artisan +2 -0
  6. package/template/bootstrap/app.ts +44 -0
  7. package/template/bunfig.toml +7 -0
  8. package/template/config/database.ts +25 -0
  9. package/template/config/plugins.ts +7 -0
  10. package/template/config/security.ts +158 -0
  11. package/template/framework/cli/Command.ts +17 -0
  12. package/template/framework/cli/ConsoleApplication.ts +55 -0
  13. package/template/framework/cli/artisan.ts +16 -0
  14. package/template/framework/cli/commands/MakeControllerCommand.ts +41 -0
  15. package/template/framework/cli/commands/MakeMiddlewareCommand.ts +41 -0
  16. package/template/framework/cli/commands/MakeModelCommand.ts +36 -0
  17. package/template/framework/cli/commands/MakeValidatorCommand.ts +42 -0
  18. package/template/framework/controller/Controller.ts +222 -0
  19. package/template/framework/core/Application.ts +208 -0
  20. package/template/framework/core/Container.ts +100 -0
  21. package/template/framework/core/Kernel.ts +297 -0
  22. package/template/framework/database/DatabaseAdapter.ts +18 -0
  23. package/template/framework/database/PrismaAdapter.ts +65 -0
  24. package/template/framework/database/SqlAdapter.ts +117 -0
  25. package/template/framework/gateway/Gateway.ts +109 -0
  26. package/template/framework/gateway/GatewayManager.ts +150 -0
  27. package/template/framework/gateway/WebSocketAdapter.ts +159 -0
  28. package/template/framework/gateway/WebSocketGateway.ts +182 -0
  29. package/template/framework/http/Request.ts +608 -0
  30. package/template/framework/http/Response.ts +525 -0
  31. package/template/framework/http/Server.ts +161 -0
  32. package/template/framework/http/UploadedFile.ts +145 -0
  33. package/template/framework/middleware/Middleware.ts +50 -0
  34. package/template/framework/middleware/Pipeline.ts +89 -0
  35. package/template/framework/plugin/Plugin.ts +26 -0
  36. package/template/framework/plugin/PluginManager.ts +61 -0
  37. package/template/framework/routing/RouteRegistry.ts +185 -0
  38. package/template/framework/routing/Router.ts +280 -0
  39. package/template/framework/security/CorsMiddleware.ts +151 -0
  40. package/template/framework/security/CsrfMiddleware.ts +121 -0
  41. package/template/framework/security/HelmetMiddleware.ts +138 -0
  42. package/template/framework/security/InputSanitizerMiddleware.ts +134 -0
  43. package/template/framework/security/RateLimiterMiddleware.ts +189 -0
  44. package/template/framework/security/SecurityManager.ts +128 -0
  45. package/template/framework/validation/Validator.ts +482 -0
  46. package/template/package.json +24 -0
  47. package/template/routes/api.ts +56 -0
  48. package/template/server.ts +29 -0
  49. package/template/tsconfig.json +49 -0
@@ -0,0 +1,482 @@
1
+ /**
2
+ * PhoenixJS - Base Validator
3
+ *
4
+ * Abstract base class for all validators.
5
+ * Provides a rule-based validation engine.
6
+ */
7
+
8
+ /**
9
+ * Validation result returned by validate()
10
+ */
11
+ export interface ValidationResult {
12
+ /** Whether all validations passed */
13
+ passes: boolean;
14
+ /** Whether any validation failed */
15
+ fails: boolean;
16
+ /** Validation errors keyed by field name */
17
+ errors: Record<string, string[]>;
18
+ /** Validated data (only if passes) */
19
+ validated: Record<string, unknown>;
20
+ }
21
+
22
+ /**
23
+ * Validation rules format
24
+ * Key is the field name, value is an array of rule strings
25
+ *
26
+ * Example:
27
+ * {
28
+ * name: ['required', 'string', 'min:2', 'max:100'],
29
+ * email: ['required', 'email'],
30
+ * age: ['number', 'min:0', 'max:150'],
31
+ * role: ['required', 'in:admin,user,guest']
32
+ * }
33
+ */
34
+ export type ValidationRules = Record<string, string[]>;
35
+
36
+ /**
37
+ * Custom validation messages
38
+ */
39
+ export type ValidationMessages = Record<string, string>;
40
+
41
+ /**
42
+ * Rule handler function type
43
+ */
44
+ type RuleHandler = (
45
+ field: string,
46
+ value: unknown,
47
+ param: string | undefined,
48
+ data: Record<string, unknown>
49
+ ) => string | null;
50
+
51
+ /**
52
+ * Base Validator class
53
+ *
54
+ * All application validators should extend this class.
55
+ * Provides a generic rule-based validation engine.
56
+ */
57
+ export abstract class Validator {
58
+ /**
59
+ * Define validation rules for fields
60
+ * Override this in subclasses
61
+ */
62
+ abstract rules(): ValidationRules;
63
+
64
+ /**
65
+ * Custom error messages (optional override)
66
+ */
67
+ messages(): ValidationMessages {
68
+ return {};
69
+ }
70
+
71
+ /**
72
+ * Custom attribute names (optional override)
73
+ */
74
+ attributes(): Record<string, string> {
75
+ return {};
76
+ }
77
+
78
+ /**
79
+ * Validate the given data against the rules
80
+ */
81
+ validate(data: Record<string, unknown>): ValidationResult {
82
+ const rules = this.rules();
83
+ const customMessages = this.messages();
84
+ const customAttributes = this.attributes();
85
+ const errors: Record<string, string[]> = {};
86
+ const validated: Record<string, unknown> = {};
87
+
88
+ for (const [field, fieldRules] of Object.entries(rules)) {
89
+ const value = this.getNestedValue(data, field);
90
+ const fieldErrors: string[] = [];
91
+ const displayName = customAttributes[field] || this.humanize(field);
92
+
93
+ for (const rule of fieldRules) {
94
+ const [ruleName, ruleParam] = this.parseRule(rule);
95
+ const handler = this.getHandler(ruleName);
96
+
97
+ if (handler) {
98
+ const errorMessage = handler(field, value, ruleParam, data);
99
+ if (errorMessage) {
100
+ // Check for custom message
101
+ const customKey = `${field}.${ruleName}`;
102
+ const message = customMessages[customKey]
103
+ || customMessages[ruleName]
104
+ || errorMessage.replace(':attribute', displayName);
105
+ fieldErrors.push(message);
106
+ }
107
+ }
108
+ }
109
+
110
+ if (fieldErrors.length > 0) {
111
+ errors[field] = fieldErrors;
112
+ } else if (value !== undefined) {
113
+ validated[field] = value;
114
+ }
115
+ }
116
+
117
+ const passes = Object.keys(errors).length === 0;
118
+
119
+ return {
120
+ passes,
121
+ fails: !passes,
122
+ errors,
123
+ validated: passes ? validated : {},
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Parse a rule string into name and parameter
129
+ */
130
+ private parseRule(rule: string): [string, string | undefined] {
131
+ const colonIndex = rule.indexOf(':');
132
+ if (colonIndex === -1) {
133
+ return [rule, undefined];
134
+ }
135
+ return [rule.substring(0, colonIndex), rule.substring(colonIndex + 1)];
136
+ }
137
+
138
+ /**
139
+ * Get nested value from object using dot notation
140
+ */
141
+ private getNestedValue(obj: Record<string, unknown>, path: string): unknown {
142
+ const keys = path.split('.');
143
+ let current: unknown = obj;
144
+
145
+ for (const key of keys) {
146
+ if (current === null || current === undefined || typeof current !== 'object') {
147
+ return undefined;
148
+ }
149
+ current = (current as Record<string, unknown>)[key];
150
+ }
151
+
152
+ return current;
153
+ }
154
+
155
+ /**
156
+ * Convert field name to human-readable format
157
+ */
158
+ private humanize(field: string): string {
159
+ return field
160
+ .replace(/([A-Z])/g, ' $1')
161
+ .replace(/[_-]/g, ' ')
162
+ .toLowerCase()
163
+ .trim()
164
+ .replace(/^\w/, (c) => c.toUpperCase());
165
+ }
166
+
167
+ /**
168
+ * Get the handler for a rule
169
+ */
170
+ private getHandler(ruleName: string): RuleHandler | null {
171
+ const handlers: Record<string, RuleHandler> = {
172
+ required: this.validateRequired.bind(this),
173
+ string: this.validateString.bind(this),
174
+ number: this.validateNumber.bind(this),
175
+ boolean: this.validateBoolean.bind(this),
176
+ array: this.validateArray.bind(this),
177
+ object: this.validateObject.bind(this),
178
+ email: this.validateEmail.bind(this),
179
+ url: this.validateUrl.bind(this),
180
+ min: this.validateMin.bind(this),
181
+ max: this.validateMax.bind(this),
182
+ between: this.validateBetween.bind(this),
183
+ in: this.validateIn.bind(this),
184
+ notIn: this.validateNotIn.bind(this),
185
+ regex: this.validateRegex.bind(this),
186
+ alpha: this.validateAlpha.bind(this),
187
+ alphaNum: this.validateAlphaNum.bind(this),
188
+ alphaDash: this.validateAlphaDash.bind(this),
189
+ numeric: this.validateNumeric.bind(this),
190
+ integer: this.validateInteger.bind(this),
191
+ date: this.validateDate.bind(this),
192
+ uuid: this.validateUuid.bind(this),
193
+ confirmed: this.validateConfirmed.bind(this),
194
+ same: this.validateSame.bind(this),
195
+ different: this.validateDifferent.bind(this),
196
+ nullable: this.validateNullable.bind(this),
197
+ };
198
+
199
+ return handlers[ruleName] || null;
200
+ }
201
+
202
+ // ==========================================
203
+ // Rule Validators
204
+ // ==========================================
205
+
206
+ private validateRequired(field: string, value: unknown): string | null {
207
+ if (value === undefined || value === null || value === '') {
208
+ return 'The :attribute field is required.';
209
+ }
210
+ if (Array.isArray(value) && value.length === 0) {
211
+ return 'The :attribute field is required.';
212
+ }
213
+ return null;
214
+ }
215
+
216
+ private validateString(field: string, value: unknown): string | null {
217
+ if (value === undefined || value === null) return null;
218
+ if (typeof value !== 'string') {
219
+ return 'The :attribute must be a string.';
220
+ }
221
+ return null;
222
+ }
223
+
224
+ private validateNumber(field: string, value: unknown): string | null {
225
+ if (value === undefined || value === null) return null;
226
+ if (typeof value !== 'number' || isNaN(value)) {
227
+ return 'The :attribute must be a number.';
228
+ }
229
+ return null;
230
+ }
231
+
232
+ private validateBoolean(field: string, value: unknown): string | null {
233
+ if (value === undefined || value === null) return null;
234
+ if (typeof value !== 'boolean' && value !== 0 && value !== 1 && value !== '0' && value !== '1') {
235
+ return 'The :attribute must be true or false.';
236
+ }
237
+ return null;
238
+ }
239
+
240
+ private validateArray(field: string, value: unknown): string | null {
241
+ if (value === undefined || value === null) return null;
242
+ if (!Array.isArray(value)) {
243
+ return 'The :attribute must be an array.';
244
+ }
245
+ return null;
246
+ }
247
+
248
+ private validateObject(field: string, value: unknown): string | null {
249
+ if (value === undefined || value === null) return null;
250
+ if (typeof value !== 'object' || Array.isArray(value)) {
251
+ return 'The :attribute must be an object.';
252
+ }
253
+ return null;
254
+ }
255
+
256
+ private validateEmail(field: string, value: unknown): string | null {
257
+ if (value === undefined || value === null || value === '') return null;
258
+ if (typeof value !== 'string') {
259
+ return 'The :attribute must be a valid email address.';
260
+ }
261
+ // RFC 5322 compliant email regex (simplified)
262
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
263
+ if (!emailRegex.test(value)) {
264
+ return 'The :attribute must be a valid email address.';
265
+ }
266
+ return null;
267
+ }
268
+
269
+ private validateUrl(field: string, value: unknown): string | null {
270
+ if (value === undefined || value === null || value === '') return null;
271
+ if (typeof value !== 'string') {
272
+ return 'The :attribute must be a valid URL.';
273
+ }
274
+ try {
275
+ new URL(value);
276
+ return null;
277
+ } catch {
278
+ return 'The :attribute must be a valid URL.';
279
+ }
280
+ }
281
+
282
+ private validateMin(field: string, value: unknown, param: string | undefined): string | null {
283
+ if (value === undefined || value === null) return null;
284
+ const min = Number(param);
285
+ if (isNaN(min)) return null;
286
+
287
+ if (typeof value === 'string') {
288
+ if (value.length < min) {
289
+ return `The :attribute must be at least ${min} characters.`;
290
+ }
291
+ } else if (typeof value === 'number') {
292
+ if (value < min) {
293
+ return `The :attribute must be at least ${min}.`;
294
+ }
295
+ } else if (Array.isArray(value)) {
296
+ if (value.length < min) {
297
+ return `The :attribute must have at least ${min} items.`;
298
+ }
299
+ }
300
+ return null;
301
+ }
302
+
303
+ private validateMax(field: string, value: unknown, param: string | undefined): string | null {
304
+ if (value === undefined || value === null) return null;
305
+ const max = Number(param);
306
+ if (isNaN(max)) return null;
307
+
308
+ if (typeof value === 'string') {
309
+ if (value.length > max) {
310
+ return `The :attribute must not be greater than ${max} characters.`;
311
+ }
312
+ } else if (typeof value === 'number') {
313
+ if (value > max) {
314
+ return `The :attribute must not be greater than ${max}.`;
315
+ }
316
+ } else if (Array.isArray(value)) {
317
+ if (value.length > max) {
318
+ return `The :attribute must not have more than ${max} items.`;
319
+ }
320
+ }
321
+ return null;
322
+ }
323
+
324
+ private validateBetween(field: string, value: unknown, param: string | undefined): string | null {
325
+ if (value === undefined || value === null) return null;
326
+ if (!param) return null;
327
+ const [minStr, maxStr] = param.split(',');
328
+ const min = Number(minStr);
329
+ const max = Number(maxStr);
330
+ if (isNaN(min) || isNaN(max)) return null;
331
+
332
+ if (typeof value === 'string') {
333
+ if (value.length < min || value.length > max) {
334
+ return `The :attribute must be between ${min} and ${max} characters.`;
335
+ }
336
+ } else if (typeof value === 'number') {
337
+ if (value < min || value > max) {
338
+ return `The :attribute must be between ${min} and ${max}.`;
339
+ }
340
+ } else if (Array.isArray(value)) {
341
+ if (value.length < min || value.length > max) {
342
+ return `The :attribute must have between ${min} and ${max} items.`;
343
+ }
344
+ }
345
+ return null;
346
+ }
347
+
348
+ private validateIn(field: string, value: unknown, param: string | undefined): string | null {
349
+ if (value === undefined || value === null) return null;
350
+ if (!param) return null;
351
+ const allowed = param.split(',');
352
+ if (!allowed.includes(String(value))) {
353
+ return `The selected :attribute is invalid.`;
354
+ }
355
+ return null;
356
+ }
357
+
358
+ private validateNotIn(field: string, value: unknown, param: string | undefined): string | null {
359
+ if (value === undefined || value === null) return null;
360
+ if (!param) return null;
361
+ const disallowed = param.split(',');
362
+ if (disallowed.includes(String(value))) {
363
+ return `The selected :attribute is invalid.`;
364
+ }
365
+ return null;
366
+ }
367
+
368
+ private validateRegex(field: string, value: unknown, param: string | undefined): string | null {
369
+ if (value === undefined || value === null || value === '') return null;
370
+ if (!param) return null;
371
+ if (typeof value !== 'string') {
372
+ return 'The :attribute format is invalid.';
373
+ }
374
+ try {
375
+ const regex = new RegExp(param);
376
+ if (!regex.test(value)) {
377
+ return 'The :attribute format is invalid.';
378
+ }
379
+ } catch {
380
+ return 'The :attribute format is invalid.';
381
+ }
382
+ return null;
383
+ }
384
+
385
+ private validateAlpha(field: string, value: unknown): string | null {
386
+ if (value === undefined || value === null || value === '') return null;
387
+ if (typeof value !== 'string' || !/^[a-zA-Z]+$/.test(value)) {
388
+ return 'The :attribute must only contain letters.';
389
+ }
390
+ return null;
391
+ }
392
+
393
+ private validateAlphaNum(field: string, value: unknown): string | null {
394
+ if (value === undefined || value === null || value === '') return null;
395
+ if (typeof value !== 'string' || !/^[a-zA-Z0-9]+$/.test(value)) {
396
+ return 'The :attribute must only contain letters and numbers.';
397
+ }
398
+ return null;
399
+ }
400
+
401
+ private validateAlphaDash(field: string, value: unknown): string | null {
402
+ if (value === undefined || value === null || value === '') return null;
403
+ if (typeof value !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(value)) {
404
+ return 'The :attribute must only contain letters, numbers, dashes and underscores.';
405
+ }
406
+ return null;
407
+ }
408
+
409
+ private validateNumeric(field: string, value: unknown): string | null {
410
+ if (value === undefined || value === null || value === '') return null;
411
+ if (isNaN(Number(value))) {
412
+ return 'The :attribute must be a number.';
413
+ }
414
+ return null;
415
+ }
416
+
417
+ private validateInteger(field: string, value: unknown): string | null {
418
+ if (value === undefined || value === null) return null;
419
+ const num = Number(value);
420
+ if (isNaN(num) || !Number.isInteger(num)) {
421
+ return 'The :attribute must be an integer.';
422
+ }
423
+ return null;
424
+ }
425
+
426
+ private validateDate(field: string, value: unknown): string | null {
427
+ if (value === undefined || value === null || value === '') return null;
428
+ const date = new Date(value as string | number);
429
+ if (isNaN(date.getTime())) {
430
+ return 'The :attribute must be a valid date.';
431
+ }
432
+ return null;
433
+ }
434
+
435
+ private validateUuid(field: string, value: unknown): string | null {
436
+ if (value === undefined || value === null || value === '') return null;
437
+ if (typeof value !== 'string') {
438
+ return 'The :attribute must be a valid UUID.';
439
+ }
440
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
441
+ if (!uuidRegex.test(value)) {
442
+ return 'The :attribute must be a valid UUID.';
443
+ }
444
+ return null;
445
+ }
446
+
447
+ private validateConfirmed(field: string, value: unknown, param: string | undefined, data: Record<string, unknown>): string | null {
448
+ if (value === undefined || value === null) return null;
449
+ const confirmationField = `${field}_confirmation`;
450
+ const confirmationValue = this.getNestedValue(data, confirmationField);
451
+ if (value !== confirmationValue) {
452
+ return 'The :attribute confirmation does not match.';
453
+ }
454
+ return null;
455
+ }
456
+
457
+ private validateSame(field: string, value: unknown, param: string | undefined, data: Record<string, unknown>): string | null {
458
+ if (value === undefined || value === null) return null;
459
+ if (!param) return null;
460
+ const otherValue = this.getNestedValue(data, param);
461
+ if (value !== otherValue) {
462
+ return `The :attribute and ${param} must match.`;
463
+ }
464
+ return null;
465
+ }
466
+
467
+ private validateDifferent(field: string, value: unknown, param: string | undefined, data: Record<string, unknown>): string | null {
468
+ if (value === undefined || value === null) return null;
469
+ if (!param) return null;
470
+ const otherValue = this.getNestedValue(data, param);
471
+ if (value === otherValue) {
472
+ return `The :attribute and ${param} must be different.`;
473
+ }
474
+ return null;
475
+ }
476
+
477
+ private validateNullable(): string | null {
478
+ // Nullable is a special rule that allows null/undefined values
479
+ // It doesn't produce errors, it just signals that null is allowed
480
+ return null;
481
+ }
482
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "my-phoenixjs-app",
3
+ "version": "0.1.0",
4
+ "description": "A PhoenixJS API project",
5
+ "type": "module",
6
+ "main": "server.ts",
7
+ "scripts": {
8
+ "dev": "bun run --watch server.ts",
9
+ "start": "bun run server.ts",
10
+ "test": "bun test"
11
+ },
12
+ "keywords": [
13
+ "api",
14
+ "typescript",
15
+ "bun",
16
+ "phoenixjs"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "devDependencies": {
21
+ "@types/bun": "latest",
22
+ "typescript": "^5.0.0"
23
+ }
24
+ }
@@ -0,0 +1,56 @@
1
+ import { Router } from '@framework/routing/Router';
2
+ import { FrameworkResponse as Response } from '@framework/http/Response';
3
+
4
+ /**
5
+ * API Routes
6
+ *
7
+ * Define your API routes here. These routes are loaded by the kernel
8
+ * and all routes here will have the /api prefix.
9
+ */
10
+ export function registerRoutes() {
11
+ // Health check endpoint
12
+ Router.get('/health', () => {
13
+ return Response.json({
14
+ status: 'ok',
15
+ timestamp: new Date().toISOString(),
16
+ });
17
+ });
18
+
19
+ // Welcome endpoint
20
+ Router.get('/hello', () => {
21
+ return Response.json({
22
+ message: 'Welcome to PhoenixJS! 🚀',
23
+ });
24
+ });
25
+
26
+ // Example API group with routes
27
+ Router.group({ prefix: '/api' }, () => {
28
+ // Ping endpoint
29
+ Router.get('/ping', () => Response.json({ message: 'pong' }));
30
+
31
+ // Example with route parameter
32
+ Router.get('/users/:id', () => {
33
+ return Response.json({
34
+ user: { id: '1', name: 'John Doe' },
35
+ });
36
+ });
37
+
38
+ // Example POST route (use Controller for real implementations)
39
+ Router.post('/users', () => {
40
+ return Response.json({
41
+ message: 'User created',
42
+ }, 201);
43
+ });
44
+ });
45
+
46
+ /**
47
+ * Route Groups Example
48
+ *
49
+ * You can use Controllers with the 'ControllerName@method' syntax:
50
+ *
51
+ * Router.group({ prefix: '/v1', middleware: [] }, () => {
52
+ * Router.get('/products', 'ProductController@index');
53
+ * Router.post('/products', 'ProductController@store');
54
+ * });
55
+ */
56
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * PhoenixJS - Server Entry Point
3
+ *
4
+ * This is the main entry point for the application.
5
+ * Run with: bun run server.ts
6
+ */
7
+
8
+ import { app, kernel } from '@/bootstrap/app';
9
+ import { Server } from '@framework/http/Server';
10
+
11
+ // Boot the application
12
+ app.boot();
13
+
14
+ // Create and start the server
15
+ const server = new Server(kernel);
16
+ server.start();
17
+
18
+ // Handle graceful shutdown
19
+ process.on('SIGINT', () => {
20
+ console.log('\nShutting down...');
21
+ server.stop();
22
+ process.exit(0);
23
+ });
24
+
25
+ process.on('SIGTERM', () => {
26
+ console.log('\nShutting down...');
27
+ server.stop();
28
+ process.exit(0);
29
+ });
@@ -0,0 +1,49 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "noEmit": true,
12
+ "declaration": false,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "lib": [
16
+ "ESNext"
17
+ ],
18
+ "types": [
19
+ "bun-types"
20
+ ],
21
+ "baseUrl": ".",
22
+ "paths": {
23
+ "@/*": [
24
+ "./*"
25
+ ],
26
+ "@framework/*": [
27
+ "framework/*"
28
+ ],
29
+ "@app/*": [
30
+ "app/*"
31
+ ],
32
+ "@config/*": [
33
+ "config/*"
34
+ ],
35
+ "@routes/*": [
36
+ "routes/*"
37
+ ],
38
+ "@database/*": [
39
+ "database/*"
40
+ ]
41
+ }
42
+ },
43
+ "include": [
44
+ "**/*.ts"
45
+ ],
46
+ "exclude": [
47
+ "node_modules"
48
+ ]
49
+ }