@elysiumjs/core 0.3.2 → 0.4.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/index.ts +1 -1
- package/package.json +2 -2
- package/src/app.ts +21 -50
- package/src/cache.ts +1 -1
- package/src/console.ts +17 -11
- package/src/database.ts +131 -3
- package/src/event.ts +2 -0
- package/src/http.ts +74 -8
- package/src/middleware.ts +25 -2
- package/src/model.ts +33 -7
- package/src/repository.ts +52 -9
- package/src/service.ts +7 -5
- package/src/wamp.ts +40 -8
- package/tsconfig.json +47 -0
package/index.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import './macros';
|
|
16
16
|
|
|
17
|
-
export { type Elysia, t } from 'elysia';
|
|
17
|
+
export { type Elysia, type TSchema, type Static, t } from 'elysia';
|
|
18
18
|
export { type AppContext, type ElysiumPlugin, Application } from './src/app';
|
|
19
19
|
export { Cache } from './src/cache';
|
|
20
20
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elysiumjs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "A super-charged web framework for Bun, built on top of ElysiaJS.",
|
|
6
6
|
"private": false,
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@elysiajs/swagger": "^1.2.2",
|
|
30
30
|
"cache-manager": "^6.4.2",
|
|
31
31
|
"cacheable": "^1.8.10",
|
|
32
|
-
"drizzle-orm": "
|
|
32
|
+
"drizzle-orm": "0.43.1-4aa795a",
|
|
33
33
|
"elysia": "latest",
|
|
34
34
|
"radash": "^12.1.0",
|
|
35
35
|
"reflect-metadata": "^0.2.2",
|
package/src/app.ts
CHANGED
|
@@ -47,6 +47,9 @@ export type ElysiumPlugin = (app: Application) => Promise<AnyElysia>;
|
|
|
47
47
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
48
48
|
*/
|
|
49
49
|
export type AppProps = {
|
|
50
|
+
// Allows to pass custom options to the application properties.
|
|
51
|
+
[key: string]: any;
|
|
52
|
+
|
|
50
53
|
/**
|
|
51
54
|
* Enables or disables debug mode.
|
|
52
55
|
*/
|
|
@@ -349,6 +352,15 @@ export abstract class Application extends InteractsWithConsole {
|
|
|
349
352
|
return process.exit(0);
|
|
350
353
|
}
|
|
351
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Retrieves the configuration value for the given key.
|
|
357
|
+
* @param key The key to retrieve the configuration value for.
|
|
358
|
+
* @returns The configuration value for the given key, or `null` if not found.
|
|
359
|
+
*/
|
|
360
|
+
public getConfig<T>(key: keyof AppProps): T | null {
|
|
361
|
+
return Reflect.getMetadata(Symbols.app, this.constructor)?.[key] ?? null;
|
|
362
|
+
}
|
|
363
|
+
|
|
352
364
|
private async commandWork(argv: string[]) {
|
|
353
365
|
this.info('Starting worker process...');
|
|
354
366
|
|
|
@@ -480,13 +492,18 @@ export abstract class Application extends InteractsWithConsole {
|
|
|
480
492
|
.onError({ as: 'global' }, async (e) => {
|
|
481
493
|
if (await this.onError(e)) {
|
|
482
494
|
Event.emit('elysium:error', e);
|
|
495
|
+
this.trace({
|
|
496
|
+
name: 'HttpError',
|
|
497
|
+
message: 'Unexpected error while handling HTTP request',
|
|
498
|
+
// @ts-expect-error The stack property may not exist in the error.
|
|
499
|
+
stack: e.error.stack,
|
|
500
|
+
...e.error
|
|
501
|
+
});
|
|
483
502
|
|
|
484
503
|
let data = {};
|
|
485
504
|
|
|
486
505
|
if (typeof e.code === 'number') {
|
|
487
|
-
|
|
488
|
-
message: e.error.response
|
|
489
|
-
};
|
|
506
|
+
return e.error.response;
|
|
490
507
|
} else {
|
|
491
508
|
switch (e.code) {
|
|
492
509
|
case 'VALIDATION': {
|
|
@@ -505,54 +522,8 @@ export abstract class Application extends InteractsWithConsole {
|
|
|
505
522
|
}
|
|
506
523
|
}
|
|
507
524
|
|
|
508
|
-
const type = (() => {
|
|
509
|
-
if (typeof e.code === 'string') return e.code;
|
|
510
|
-
else {
|
|
511
|
-
switch (e.code) {
|
|
512
|
-
case 500:
|
|
513
|
-
return 'INTERNAL_SERVER_ERROR';
|
|
514
|
-
case 400:
|
|
515
|
-
return 'BAD_REQUEST';
|
|
516
|
-
case 401:
|
|
517
|
-
return 'UNAUTHORIZED';
|
|
518
|
-
case 403:
|
|
519
|
-
return 'FORBIDDEN';
|
|
520
|
-
case 404:
|
|
521
|
-
return 'NOT_FOUND';
|
|
522
|
-
case 405:
|
|
523
|
-
return 'METHOD_NOT_ALLOWED';
|
|
524
|
-
case 409:
|
|
525
|
-
return 'CONFLICT';
|
|
526
|
-
case 429:
|
|
527
|
-
return 'TOO_MANY_REQUESTS';
|
|
528
|
-
case 422:
|
|
529
|
-
return 'UNPROCESSABLE_ENTITY';
|
|
530
|
-
case 426:
|
|
531
|
-
return 'UPGRADE_REQUIRED';
|
|
532
|
-
case 428:
|
|
533
|
-
return 'PRECONDITION_REQUIRED';
|
|
534
|
-
case 431:
|
|
535
|
-
return 'REQUEST_HEADER_FIELDS_TOO_LARGE';
|
|
536
|
-
case 451:
|
|
537
|
-
return 'UNAVAILABLE_FOR_LEGAL_REASONS';
|
|
538
|
-
case 501:
|
|
539
|
-
return 'NOT_IMPLEMENTED';
|
|
540
|
-
case 502:
|
|
541
|
-
return 'BAD_GATEWAY';
|
|
542
|
-
case 503:
|
|
543
|
-
return 'SERVICE_UNAVAILABLE';
|
|
544
|
-
case 504:
|
|
545
|
-
return 'GATEWAY_TIMEOUT';
|
|
546
|
-
case 505:
|
|
547
|
-
return 'HTTP_VERSION_NOT_SUPPORTED';
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return 'UNKNOWN';
|
|
552
|
-
})();
|
|
553
|
-
|
|
554
525
|
return {
|
|
555
|
-
type,
|
|
526
|
+
type: e.code,
|
|
556
527
|
data
|
|
557
528
|
};
|
|
558
529
|
}
|
package/src/cache.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { KeyvRedis } from './redis';
|
|
|
25
25
|
* Interface for a cache storage.
|
|
26
26
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
27
27
|
*/
|
|
28
|
-
interface CacheInterface {
|
|
28
|
+
export interface CacheInterface {
|
|
29
29
|
/**
|
|
30
30
|
* Retrieves the cached data for a given key.
|
|
31
31
|
* @typeParam T The type of the cached data.
|
package/src/console.ts
CHANGED
|
@@ -142,22 +142,26 @@ export class InteractsWithConsole {
|
|
|
142
142
|
// Display the title if provided
|
|
143
143
|
if (title) {
|
|
144
144
|
this.title(title);
|
|
145
|
+
} else {
|
|
146
|
+
this.newLine();
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
// Display the error name and message in red
|
|
148
150
|
this.write(this.format(`${errorName}: ${errorMessage}`, ConsoleFormat.RED));
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
if (error.stack) {
|
|
153
|
+
// Display the stack trace in gray, each line prefixed by a tab for readability
|
|
154
|
+
this.write(this.format('Stack Trace:', ConsoleFormat.BOLD));
|
|
155
|
+
this.write(
|
|
156
|
+
this.format(
|
|
157
|
+
errorStack
|
|
158
|
+
.split('\n')
|
|
159
|
+
.map((line) => `\t${line}`)
|
|
160
|
+
.join('\n'),
|
|
161
|
+
ConsoleFormat.GRAY
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
161
165
|
|
|
162
166
|
// Display any other custom properties
|
|
163
167
|
const customProps = omit(error, ['name', 'message', 'stack']);
|
|
@@ -169,6 +173,8 @@ export class InteractsWithConsole {
|
|
|
169
173
|
this.write(`\t${prop}: ${JSON.stringify(value)}`);
|
|
170
174
|
});
|
|
171
175
|
}
|
|
176
|
+
|
|
177
|
+
this.newLine();
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
/**
|
package/src/database.ts
CHANGED
|
@@ -14,9 +14,14 @@
|
|
|
14
14
|
|
|
15
15
|
import type { DrizzleConfig } from 'drizzle-orm';
|
|
16
16
|
import type { BunSQLDatabase } from 'drizzle-orm/bun-sql';
|
|
17
|
+
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
|
|
18
|
+
import type { CacheInterface } from './cache';
|
|
17
19
|
|
|
20
|
+
import { getTableName, is, Table } from 'drizzle-orm';
|
|
18
21
|
import { drizzle } from 'drizzle-orm/bun-sql';
|
|
22
|
+
import { Cache as DrizzleCache } from 'drizzle-orm/cache/core';
|
|
19
23
|
|
|
24
|
+
import { Cache } from './cache';
|
|
20
25
|
import { Service } from './service';
|
|
21
26
|
|
|
22
27
|
/**
|
|
@@ -25,7 +30,7 @@ import { Service } from './service';
|
|
|
25
30
|
*/
|
|
26
31
|
export type DatabaseConnectionProps<
|
|
27
32
|
TSchema extends Record<string, unknown> = Record<string, never>
|
|
28
|
-
> = DrizzleConfig<TSchema> &
|
|
33
|
+
> = Omit<DrizzleConfig<TSchema>, 'cache'> &
|
|
29
34
|
(
|
|
30
35
|
| {
|
|
31
36
|
connection:
|
|
@@ -37,7 +42,40 @@ export type DatabaseConnectionProps<
|
|
|
37
42
|
| {
|
|
38
43
|
client: Bun.SQL;
|
|
39
44
|
}
|
|
40
|
-
)
|
|
45
|
+
) & {
|
|
46
|
+
/**
|
|
47
|
+
* Configures caching behavior for the database connection.
|
|
48
|
+
* Set it to `true` to enable caching with default configurations.
|
|
49
|
+
*/
|
|
50
|
+
cache?:
|
|
51
|
+
| true
|
|
52
|
+
| {
|
|
53
|
+
/**
|
|
54
|
+
* Whether to enable caching.
|
|
55
|
+
*/
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The default TTL for cached data, in seconds.
|
|
60
|
+
* @default 1
|
|
61
|
+
*/
|
|
62
|
+
ttl?: number;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The caching strategy.
|
|
66
|
+
* Set it to `all` to cache all queries automatically.
|
|
67
|
+
* Set it to `explicit` to cache queries manually.
|
|
68
|
+
* @default 'explicit'
|
|
69
|
+
*/
|
|
70
|
+
strategy?: 'explicit' | 'all';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The storage engine to use.
|
|
74
|
+
* @default 'redis'
|
|
75
|
+
*/
|
|
76
|
+
storage?: 'redis' | 'memory';
|
|
77
|
+
};
|
|
78
|
+
};
|
|
41
79
|
|
|
42
80
|
/**
|
|
43
81
|
* A database connection.
|
|
@@ -47,6 +85,83 @@ export type DatabaseConnection = BunSQLDatabase<Record<string, never>> & {
|
|
|
47
85
|
$client: Bun.SQL;
|
|
48
86
|
};
|
|
49
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Database cache implementation for Drizzle.
|
|
90
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
91
|
+
*/
|
|
92
|
+
export class DatabaseCache extends DrizzleCache {
|
|
93
|
+
#cache: Omit<CacheInterface, 'tags'>;
|
|
94
|
+
|
|
95
|
+
private usedTablesPerKey: Record<string, string[]> = {};
|
|
96
|
+
|
|
97
|
+
constructor(
|
|
98
|
+
private readonly cacheStrategy: 'explicit' | 'all' = 'explicit',
|
|
99
|
+
private readonly globalTtl: number = 1,
|
|
100
|
+
storageEngine: 'redis' | 'memory' = 'redis'
|
|
101
|
+
) {
|
|
102
|
+
super();
|
|
103
|
+
this.#cache = Cache[storageEngine].tags('database');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public override strategy(): 'explicit' | 'all' {
|
|
107
|
+
return this.cacheStrategy;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public override async get(key: string): Promise<any[] | undefined> {
|
|
111
|
+
const res = (await this.#cache.get<any[]>(key)) ?? undefined;
|
|
112
|
+
return res;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public override async put(
|
|
116
|
+
key: string,
|
|
117
|
+
response: any,
|
|
118
|
+
tables: string[],
|
|
119
|
+
isTag: boolean,
|
|
120
|
+
config?: CacheConfig
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
console.log({ key, response, tables, isTag, config });
|
|
123
|
+
await this.#cache.set(key, response, (config?.ex ?? this.globalTtl) * 1000);
|
|
124
|
+
for (const table of tables) {
|
|
125
|
+
const keys = this.usedTablesPerKey[table];
|
|
126
|
+
if (keys === undefined) {
|
|
127
|
+
this.usedTablesPerKey[table] = [key];
|
|
128
|
+
} else {
|
|
129
|
+
keys.push(key);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public override async onMutate(params: {
|
|
135
|
+
tags: string | string[];
|
|
136
|
+
tables: string | string[] | Table<any> | Table<any>[];
|
|
137
|
+
}): Promise<void> {
|
|
138
|
+
const tagsArray = params.tags ? (Array.isArray(params.tags) ? params.tags : [params.tags]) : [];
|
|
139
|
+
const tablesArray = params.tables
|
|
140
|
+
? Array.isArray(params.tables)
|
|
141
|
+
? params.tables
|
|
142
|
+
: [params.tables]
|
|
143
|
+
: [];
|
|
144
|
+
|
|
145
|
+
const keysToDelete = new Set<string>();
|
|
146
|
+
|
|
147
|
+
for (const table of tablesArray) {
|
|
148
|
+
const tableName = is(table, Table) ? getTableName(table) : (table as string);
|
|
149
|
+
const keys = this.usedTablesPerKey[tableName] ?? [];
|
|
150
|
+
for (const key of keys) keysToDelete.add(key);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (keysToDelete.size > 0 || tagsArray.length > 0) {
|
|
154
|
+
await this.#cache.mdel(tagsArray);
|
|
155
|
+
await this.#cache.mdel([...keysToDelete]);
|
|
156
|
+
|
|
157
|
+
for (const table of tablesArray) {
|
|
158
|
+
const tableName = is(table, Table) ? getTableName(table) : (table as string);
|
|
159
|
+
this.usedTablesPerKey[tableName] = [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
50
165
|
export namespace Database {
|
|
51
166
|
/**
|
|
52
167
|
* Creates a service name for a database connection.
|
|
@@ -94,7 +209,20 @@ export namespace Database {
|
|
|
94
209
|
process.exit(1);
|
|
95
210
|
}
|
|
96
211
|
|
|
97
|
-
return Service.instance(
|
|
212
|
+
return Service.instance(
|
|
213
|
+
getConnectionName(name),
|
|
214
|
+
drizzle({
|
|
215
|
+
...config,
|
|
216
|
+
cache:
|
|
217
|
+
config.cache === undefined
|
|
218
|
+
? undefined
|
|
219
|
+
: config.cache === true
|
|
220
|
+
? new DatabaseCache()
|
|
221
|
+
: config.cache.enabled
|
|
222
|
+
? new DatabaseCache(config.cache.strategy, config.cache.ttl, config.cache.storage)
|
|
223
|
+
: undefined
|
|
224
|
+
})
|
|
225
|
+
);
|
|
98
226
|
};
|
|
99
227
|
|
|
100
228
|
/**
|
package/src/event.ts
CHANGED
package/src/http.ts
CHANGED
|
@@ -33,6 +33,32 @@ import { nextTick, Symbols } from './utils';
|
|
|
33
33
|
*/
|
|
34
34
|
export type Route = `/${string}`;
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* A class for validating data against a schema.
|
|
38
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
39
|
+
*/
|
|
40
|
+
export type ValidatorClass<T extends TSchema = TSchema> = {
|
|
41
|
+
/**
|
|
42
|
+
* The validation schema. This schema is automatically checked against the input data during
|
|
43
|
+
* the validation stage of the HTTP request.
|
|
44
|
+
*/
|
|
45
|
+
readonly schema: T;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether the request should be authorized before validation. If the request is not authorized,
|
|
49
|
+
* the validation will be skipped.
|
|
50
|
+
*/
|
|
51
|
+
authorize(ctx: Context<T['static']>): Promise<boolean>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Processes custom validation logic. This method is called after the validation schema is checked
|
|
55
|
+
* against the input data. So it's safe to assume that the schema is valid.
|
|
56
|
+
* @param data The input data to validate.
|
|
57
|
+
* @returns Whether the input data is valid.
|
|
58
|
+
*/
|
|
59
|
+
validate(ctx: Context<T['static']>): Promise<boolean>;
|
|
60
|
+
};
|
|
61
|
+
|
|
36
62
|
/**
|
|
37
63
|
* A function that handles an HTTP request.
|
|
38
64
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
@@ -62,12 +88,12 @@ type HttpRequestHandlerMetadata = {
|
|
|
62
88
|
/**
|
|
63
89
|
* The response schema.
|
|
64
90
|
*/
|
|
65
|
-
response?: TSchema
|
|
91
|
+
response?: TSchema | Record<number, TSchema>;
|
|
66
92
|
|
|
67
93
|
/**
|
|
68
94
|
* The request body schema.
|
|
69
95
|
*/
|
|
70
|
-
body?: { schema?: TSchema; index: number };
|
|
96
|
+
body?: { schema?: TSchema; validator?: ValidatorClass; index: number };
|
|
71
97
|
|
|
72
98
|
/**
|
|
73
99
|
* The request query schema.
|
|
@@ -164,8 +190,14 @@ export type Singleton<TController = unknown, TModule extends Module = Module> =
|
|
|
164
190
|
* The Elysia context with the controller injected.
|
|
165
191
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
166
192
|
*/
|
|
167
|
-
export type Context<
|
|
168
|
-
|
|
193
|
+
export type Context<
|
|
194
|
+
TBody = unknown,
|
|
195
|
+
TController = unknown,
|
|
196
|
+
TModule extends Module = Module
|
|
197
|
+
> = ElysiaContext<
|
|
198
|
+
{
|
|
199
|
+
body: TBody;
|
|
200
|
+
},
|
|
169
201
|
Singleton<TController, TModule>
|
|
170
202
|
>;
|
|
171
203
|
|
|
@@ -271,7 +303,7 @@ export type RequestHandlerDecoratorProps = {
|
|
|
271
303
|
/**
|
|
272
304
|
* The schema of the response body.
|
|
273
305
|
*/
|
|
274
|
-
response?: TSchema
|
|
306
|
+
response?: TSchema | Record<number, TSchema>;
|
|
275
307
|
|
|
276
308
|
/**
|
|
277
309
|
* The operation ID for the handler.
|
|
@@ -373,6 +405,8 @@ export namespace Http {
|
|
|
373
405
|
const isGenerator = route.handler.constructor.name.includes('GeneratorFunction');
|
|
374
406
|
const isSSE = route.method === 'elysium:SSE';
|
|
375
407
|
const isTransactional = route.transactional ?? props.transactional ?? false;
|
|
408
|
+
const isValidator =
|
|
409
|
+
route.body && route.body.schema === undefined && route.body.validator !== undefined;
|
|
376
410
|
|
|
377
411
|
route.method = isSSE ? 'GET' : route.method;
|
|
378
412
|
|
|
@@ -439,6 +473,17 @@ export namespace Http {
|
|
|
439
473
|
};
|
|
440
474
|
}
|
|
441
475
|
|
|
476
|
+
if (isValidator) {
|
|
477
|
+
return async function (c: Context) {
|
|
478
|
+
if (await route.body!.validator!.validate(c)) {
|
|
479
|
+
const handler = initTransactedHandler(c);
|
|
480
|
+
return await handler(...(await getParameters(c)));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
throw c.error(400, 'Invalid request body');
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
442
487
|
return async function (c: Context) {
|
|
443
488
|
const handler = initTransactedHandler(c);
|
|
444
489
|
return await handler(...(await getParameters(c)));
|
|
@@ -456,6 +501,13 @@ export namespace Http {
|
|
|
456
501
|
app.route(route.method, route.path, getHandler(), {
|
|
457
502
|
// @ts-ignore
|
|
458
503
|
config: {},
|
|
504
|
+
async transform(c) {
|
|
505
|
+
if (isValidator && !(await route.body!.validator!.authorize(c as Context))) {
|
|
506
|
+
throw c.error(401, 'Unauthorized');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return executeMiddlewareChain(mi, c, 'onTransform');
|
|
510
|
+
},
|
|
459
511
|
beforeHandle(c) {
|
|
460
512
|
return executeMiddlewareChain(mi, c, 'onBeforeHandle');
|
|
461
513
|
},
|
|
@@ -470,7 +522,7 @@ export namespace Http {
|
|
|
470
522
|
description: route.description,
|
|
471
523
|
operationId: route.operationId
|
|
472
524
|
},
|
|
473
|
-
body: route.body?.schema,
|
|
525
|
+
body: isValidator ? route.body?.validator?.schema : route.body?.schema,
|
|
474
526
|
response: route.response,
|
|
475
527
|
params: isEmpty(params) ? undefined : t.Object(params)
|
|
476
528
|
});
|
|
@@ -647,9 +699,23 @@ export namespace Http {
|
|
|
647
699
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
648
700
|
* @param schema The schema of the request body.
|
|
649
701
|
*/
|
|
650
|
-
export const body = (schema?: TSchema): ParameterDecorator => {
|
|
702
|
+
export const body = (schema?: TSchema | ValidatorClass): ParameterDecorator => {
|
|
651
703
|
return function (target, propertyKey, parameterIndex) {
|
|
652
|
-
|
|
704
|
+
if (typeof schema === 'function') {
|
|
705
|
+
Reflect.defineMetadata(
|
|
706
|
+
'http:body',
|
|
707
|
+
{ validator: schema, index: parameterIndex },
|
|
708
|
+
target,
|
|
709
|
+
propertyKey!
|
|
710
|
+
);
|
|
711
|
+
} else {
|
|
712
|
+
Reflect.defineMetadata(
|
|
713
|
+
'http:body',
|
|
714
|
+
{ schema, index: parameterIndex },
|
|
715
|
+
target,
|
|
716
|
+
propertyKey!
|
|
717
|
+
);
|
|
718
|
+
}
|
|
653
719
|
};
|
|
654
720
|
};
|
|
655
721
|
|
package/src/middleware.ts
CHANGED
|
@@ -61,6 +61,14 @@ export const executeMiddlewareChain = (
|
|
|
61
61
|
export const applyMiddlewares = (middlewares: Class<Middleware>[], plugin: AnyElysia) => {
|
|
62
62
|
const mi = middlewares.map((middleware) => Service.make<Middleware>(middleware));
|
|
63
63
|
|
|
64
|
+
plugin.onRequest((c) => {
|
|
65
|
+
return executeMiddlewareChain(mi, c, 'onRequest');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
plugin.onTransform((c) => {
|
|
69
|
+
return executeMiddlewareChain(mi, c, 'onTransform');
|
|
70
|
+
});
|
|
71
|
+
|
|
64
72
|
plugin.onBeforeHandle((c) => {
|
|
65
73
|
return executeMiddlewareChain(mi, c, 'onBeforeHandle');
|
|
66
74
|
});
|
|
@@ -77,8 +85,9 @@ export const applyMiddlewares = (middlewares: Class<Middleware>[], plugin: AnyEl
|
|
|
77
85
|
/**
|
|
78
86
|
* Base class for middleware.
|
|
79
87
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
88
|
+
* @template TGuards The list of guards for the middleware.
|
|
80
89
|
*/
|
|
81
|
-
export abstract class Middleware {
|
|
90
|
+
export abstract class Middleware<TGuards extends string[] = []> {
|
|
82
91
|
/**
|
|
83
92
|
* Registers a list of middlewares to be applied on a controller or endpoint.
|
|
84
93
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
@@ -117,10 +126,24 @@ export abstract class Middleware {
|
|
|
117
126
|
/**
|
|
118
127
|
* Gets the list of guards for the middleware.
|
|
119
128
|
*/
|
|
120
|
-
public get guards():
|
|
129
|
+
public get guards(): TGuards {
|
|
121
130
|
return Reflect.getMetadata(Symbols.middlewareGuards, this.constructor) ?? [];
|
|
122
131
|
}
|
|
123
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Called first before everything else. This middleware hook works only when used on
|
|
135
|
+
* a controller, a module, or the application.
|
|
136
|
+
* @param ctx The request context.
|
|
137
|
+
*/
|
|
138
|
+
public onRequest(ctx: Context): any {}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Called before validation, but after the request has been parsed.
|
|
142
|
+
* Use this method to modify the request inputs before validation.
|
|
143
|
+
* @param ctx The request context.
|
|
144
|
+
*/
|
|
145
|
+
public onTransform(ctx: Context): any {}
|
|
146
|
+
|
|
124
147
|
/**
|
|
125
148
|
* Called after the request has been handled, but before the response is sent.
|
|
126
149
|
* @param ctx The request context.
|
package/src/model.ts
CHANGED
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
import type {
|
|
15
|
+
import type { BuildExtraConfigColumns } from 'drizzle-orm';
|
|
16
|
+
import type {
|
|
17
|
+
PgColumn,
|
|
18
|
+
PgColumnBuilderBase,
|
|
19
|
+
PgSchema,
|
|
20
|
+
PgTable,
|
|
21
|
+
PgTableExtraConfigValue
|
|
22
|
+
} from 'drizzle-orm/pg-core';
|
|
16
23
|
import type { TSchema } from 'elysia';
|
|
17
24
|
|
|
18
25
|
import { getTableConfig, pgSchema, pgTable } from 'drizzle-orm/pg-core';
|
|
@@ -65,7 +72,9 @@ const parseTypes = (element: PgColumn) => {
|
|
|
65
72
|
let type = (() => {
|
|
66
73
|
switch (element.dataType) {
|
|
67
74
|
case 'string':
|
|
68
|
-
return t.String(
|
|
75
|
+
return t.String({
|
|
76
|
+
format: element.columnType === 'PgUUID' ? 'uuid' : undefined
|
|
77
|
+
});
|
|
69
78
|
case 'number':
|
|
70
79
|
return t.Number();
|
|
71
80
|
case 'boolean':
|
|
@@ -89,6 +98,8 @@ const parseTypes = (element: PgColumn) => {
|
|
|
89
98
|
|
|
90
99
|
if (!element.notNull) {
|
|
91
100
|
type = t.Optional(type);
|
|
101
|
+
} else {
|
|
102
|
+
type = t.Required(type);
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
return type;
|
|
@@ -103,7 +114,7 @@ const parseTypes = (element: PgColumn) => {
|
|
|
103
114
|
export type ModelClass<
|
|
104
115
|
TTableName extends string,
|
|
105
116
|
TColumnsMap extends Record<string, PgColumnBuilderBase>,
|
|
106
|
-
TTable extends PgTable = ReturnType<typeof pgTable<
|
|
117
|
+
TTable extends PgTable = ReturnType<typeof pgTable<TTableName, TColumnsMap>>
|
|
107
118
|
> = {
|
|
108
119
|
/**
|
|
109
120
|
* The data type returned by the select queries.
|
|
@@ -136,10 +147,17 @@ export type ModelClass<
|
|
|
136
147
|
*/
|
|
137
148
|
readonly columns: TColumnsMap;
|
|
138
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The extra configuration for the table.
|
|
152
|
+
*/
|
|
153
|
+
readonly extraConfig?: (
|
|
154
|
+
self: BuildExtraConfigColumns<TTableName, TColumnsMap, 'pg'>
|
|
155
|
+
) => PgTableExtraConfigValue[];
|
|
156
|
+
|
|
139
157
|
/**
|
|
140
158
|
* The validation schema for creating records.
|
|
141
159
|
*/
|
|
142
|
-
readonly
|
|
160
|
+
readonly insertSchema: TSchema;
|
|
143
161
|
|
|
144
162
|
/**
|
|
145
163
|
* The validation schema for updating records.
|
|
@@ -179,9 +197,12 @@ export const Model = <
|
|
|
179
197
|
TColumnsMap extends Record<string, PgColumnBuilderBase>
|
|
180
198
|
>(
|
|
181
199
|
tableName: TTableName,
|
|
182
|
-
columns: TColumnsMap
|
|
200
|
+
columns: TColumnsMap,
|
|
201
|
+
extraConfig?: (
|
|
202
|
+
self: BuildExtraConfigColumns<TTableName, TColumnsMap, 'pg'>
|
|
203
|
+
) => PgTableExtraConfigValue[]
|
|
183
204
|
) => {
|
|
184
|
-
const table = pgTable(tableName, columns);
|
|
205
|
+
const table = pgTable(tableName, columns, extraConfig);
|
|
185
206
|
|
|
186
207
|
class M {
|
|
187
208
|
/**
|
|
@@ -227,10 +248,15 @@ export const Model = <
|
|
|
227
248
|
*/
|
|
228
249
|
public static readonly columns: TColumnsMap = columns;
|
|
229
250
|
|
|
251
|
+
/**
|
|
252
|
+
* The extra configuration for the table.
|
|
253
|
+
*/
|
|
254
|
+
public static readonly extraConfig = extraConfig;
|
|
255
|
+
|
|
230
256
|
/**
|
|
231
257
|
* The validation schema for creating records.
|
|
232
258
|
*/
|
|
233
|
-
public static readonly
|
|
259
|
+
public static readonly insertSchema = createSchemaFromDrizzle(table, { mode: 'create' });
|
|
234
260
|
|
|
235
261
|
/**
|
|
236
262
|
* The validation schema for updating records.
|
package/src/repository.ts
CHANGED
|
@@ -20,7 +20,6 @@ import type { DatabaseConnection } from './database';
|
|
|
20
20
|
import type { ModelClass } from './model';
|
|
21
21
|
|
|
22
22
|
import { eq } from 'drizzle-orm';
|
|
23
|
-
import { pgTable } from 'drizzle-orm/pg-core';
|
|
24
23
|
|
|
25
24
|
import { Application } from './app';
|
|
26
25
|
import { Database } from './database';
|
|
@@ -39,9 +38,13 @@ export type IdType = number | string;
|
|
|
39
38
|
* @template TColumnsMap The table columns config.
|
|
40
39
|
*/
|
|
41
40
|
export interface RepositoryInterface<
|
|
42
|
-
TModel extends ModelClass<TColumnsMap>,
|
|
41
|
+
TModel extends ModelClass<TTableName, TColumnsMap>,
|
|
43
42
|
TId extends IdType = string,
|
|
43
|
+
TTableName extends string = TModel extends ModelClass<infer TTableName, infer _>
|
|
44
|
+
? TTableName
|
|
45
|
+
: string,
|
|
44
46
|
TColumnsMap extends Record<string, PgColumnBuilderBase> = TModel extends ModelClass<
|
|
47
|
+
TTableName,
|
|
45
48
|
infer TColumnsMap
|
|
46
49
|
>
|
|
47
50
|
? TColumnsMap
|
|
@@ -104,14 +107,18 @@ export interface RepositoryInterface<
|
|
|
104
107
|
* @template TColumnsMap The table columns config.
|
|
105
108
|
*/
|
|
106
109
|
export type RepositoryClass<
|
|
107
|
-
TModel extends ModelClass<TColumnsMap>,
|
|
110
|
+
TModel extends ModelClass<TTableName, TColumnsMap>,
|
|
108
111
|
TId extends IdType = string,
|
|
112
|
+
TTableName extends string = TModel extends ModelClass<infer TTableName, infer _>
|
|
113
|
+
? TTableName
|
|
114
|
+
: string,
|
|
109
115
|
TColumnsMap extends Record<string, PgColumnBuilderBase> = TModel extends ModelClass<
|
|
116
|
+
TTableName,
|
|
110
117
|
infer TColumnsMap
|
|
111
118
|
>
|
|
112
119
|
? TColumnsMap
|
|
113
120
|
: Record<string, PgColumnBuilderBase>
|
|
114
|
-
> = Class<RepositoryInterface<TModel, TId, TColumnsMap>> & {
|
|
121
|
+
> = Class<RepositoryInterface<TModel, TId, TTableName, TColumnsMap>> & {
|
|
115
122
|
/**
|
|
116
123
|
* The drizzle's table schema wrapped by the repository.
|
|
117
124
|
*/
|
|
@@ -134,9 +141,13 @@ export type RepositoryClass<
|
|
|
134
141
|
* @template model The model class wrapped by the repository.
|
|
135
142
|
*/
|
|
136
143
|
export const Repository = <
|
|
137
|
-
TModel extends ModelClass<TColumnsMap>,
|
|
144
|
+
TModel extends ModelClass<TTableName, TColumnsMap>,
|
|
138
145
|
TId extends IdType = string,
|
|
146
|
+
TTableName extends string = TModel extends ModelClass<infer TTableName, infer _>
|
|
147
|
+
? TTableName
|
|
148
|
+
: string,
|
|
139
149
|
TColumnsMap extends Record<string, PgColumnBuilderBase> = TModel extends ModelClass<
|
|
150
|
+
TTableName,
|
|
140
151
|
infer TColumnsMap
|
|
141
152
|
>
|
|
142
153
|
? TColumnsMap
|
|
@@ -148,9 +159,7 @@ export const Repository = <
|
|
|
148
159
|
type TInsert = TModel['$inferInsert'];
|
|
149
160
|
type TUpdate = TModel['$inferUpdate'];
|
|
150
161
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class R implements RepositoryInterface<TModel, TId, TColumnsMap> {
|
|
162
|
+
class R implements RepositoryInterface<TModel, TId, TTableName, TColumnsMap> {
|
|
154
163
|
/**
|
|
155
164
|
* The drizzle's table schema wrapped by this repository.
|
|
156
165
|
*/
|
|
@@ -167,7 +176,8 @@ export const Repository = <
|
|
|
167
176
|
* The database connection used by this repository.
|
|
168
177
|
*/
|
|
169
178
|
public get db(): DatabaseConnection {
|
|
170
|
-
const connection = (this.constructor as RepositoryClass<TModel, TId, TColumnsMap>)
|
|
179
|
+
const connection = (this.constructor as RepositoryClass<TModel, TId, TTableName, TColumnsMap>)
|
|
180
|
+
.connection;
|
|
171
181
|
let db: DatabaseConnection | null = null;
|
|
172
182
|
|
|
173
183
|
if (connection === 'default') {
|
|
@@ -185,6 +195,25 @@ export const Repository = <
|
|
|
185
195
|
return await this.db.select().from(model.table);
|
|
186
196
|
}
|
|
187
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Get a paginated subset of the records in the database.
|
|
200
|
+
* @param page The page number to retrieve.
|
|
201
|
+
* @param count The number of records to retrieve per page.
|
|
202
|
+
* @returns A paginated response containing the records and the total number of records.
|
|
203
|
+
*/
|
|
204
|
+
public async paginate(
|
|
205
|
+
count: number,
|
|
206
|
+
page: number = 1
|
|
207
|
+
): Promise<{ page: number; data: TSelect[]; total: number }> {
|
|
208
|
+
const total = await this.db.$count(model.table);
|
|
209
|
+
const offset = Math.max(page - 1, 0) * count;
|
|
210
|
+
return {
|
|
211
|
+
page,
|
|
212
|
+
total,
|
|
213
|
+
data: await this.db.select().from(model.table).offset(offset).limit(count)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
188
217
|
/**
|
|
189
218
|
* Retrieves a record by its id.
|
|
190
219
|
* @param id The id of the record to retrieve.
|
|
@@ -195,6 +224,20 @@ export const Repository = <
|
|
|
195
224
|
return (row ?? null) as TSelect | null;
|
|
196
225
|
}
|
|
197
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Finds the first record with a column matching the given value.
|
|
229
|
+
* @param column The column to filter by.
|
|
230
|
+
* @param value The value the column should match.
|
|
231
|
+
* @returns The first record with a column matching the given value.
|
|
232
|
+
*/
|
|
233
|
+
public async findBy<TKey extends keyof TSelect>(
|
|
234
|
+
column: TKey,
|
|
235
|
+
value: TSelect[TKey]
|
|
236
|
+
): Promise<TSelect | null> {
|
|
237
|
+
const [row] = await this.db.select().from(model.table).where(eq(model.table[column], value));
|
|
238
|
+
return (row ?? null) as TSelect | null;
|
|
239
|
+
}
|
|
240
|
+
|
|
198
241
|
/**
|
|
199
242
|
* Inserts a new record in the database.
|
|
200
243
|
* @param data The data to insert.
|
package/src/service.ts
CHANGED
|
@@ -169,15 +169,17 @@ export namespace Service {
|
|
|
169
169
|
/**
|
|
170
170
|
* Resolves a registered service and set it as a parameter value.
|
|
171
171
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
172
|
-
* @param
|
|
172
|
+
* @param service An optional name for the service. If not set, the name of the parameter's type is used instead.
|
|
173
173
|
*/
|
|
174
|
-
export const inject = (
|
|
174
|
+
export const inject = (service?: string | ServiceClass): ParameterDecorator => {
|
|
175
175
|
return function (target, propertyKey, parameterIndex) {
|
|
176
176
|
const services = Reflect.getMetadata(Symbols.services, target, propertyKey!) ?? [];
|
|
177
|
-
const types
|
|
177
|
+
const types: Function[] =
|
|
178
|
+
Reflect.getMetadata('design:paramtypes', target, propertyKey!) ?? [];
|
|
179
|
+
const name = isString(service) ? service : (service?.name ?? types[parameterIndex].name);
|
|
178
180
|
|
|
179
181
|
services.push({
|
|
180
|
-
name
|
|
182
|
+
name,
|
|
181
183
|
index: parameterIndex
|
|
182
184
|
});
|
|
183
185
|
|
|
@@ -223,7 +225,7 @@ export namespace Service {
|
|
|
223
225
|
if (s === null) {
|
|
224
226
|
// TODO: Use the logger service here
|
|
225
227
|
console.error(
|
|
226
|
-
`Cannot inject
|
|
228
|
+
`Cannot inject service ${dependency.name}. No service was registered with the name: ${dependency.name}`
|
|
227
229
|
);
|
|
228
230
|
process.exit(1);
|
|
229
231
|
}
|
package/src/wamp.ts
CHANGED
|
@@ -90,22 +90,32 @@ export type WampProps = {
|
|
|
90
90
|
* The interval to wait between retries when connecting to the WAMP server.
|
|
91
91
|
*/
|
|
92
92
|
retryInterval?: number;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The authentication ID to use in challenges.
|
|
96
|
+
*/
|
|
97
|
+
authid?: string;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* An array of supported authentication methods.
|
|
101
|
+
*/
|
|
102
|
+
authmethods?: string[];
|
|
93
103
|
};
|
|
94
104
|
|
|
95
105
|
/**
|
|
96
106
|
* Data sent to a WAMP RPC.
|
|
97
107
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
98
108
|
*/
|
|
99
|
-
export type WampRegistrationHandlerArgs = {
|
|
109
|
+
export type WampRegistrationHandlerArgs<TList = any[], TDict = any> = {
|
|
100
110
|
/**
|
|
101
111
|
* The arguments passed to the RPC or subscription.
|
|
102
112
|
*/
|
|
103
|
-
argsList:
|
|
113
|
+
argsList: TList;
|
|
104
114
|
|
|
105
115
|
/**
|
|
106
116
|
* The keyword arguments passed to the RPC or subscription.
|
|
107
117
|
*/
|
|
108
|
-
argsDict:
|
|
118
|
+
argsDict: TDict;
|
|
109
119
|
|
|
110
120
|
/**
|
|
111
121
|
* The WAMP details object.
|
|
@@ -213,6 +223,7 @@ export namespace Wamp {
|
|
|
213
223
|
const w = new Wampy(options.url, {
|
|
214
224
|
ws: WampWebsocket,
|
|
215
225
|
...omit(options, ['url']),
|
|
226
|
+
onChallenge: metadata.challenge?.bind(controller),
|
|
216
227
|
onClose: metadata.close?.bind(controller),
|
|
217
228
|
onError() {
|
|
218
229
|
metadata.error?.call(controller);
|
|
@@ -316,11 +327,28 @@ export namespace Wamp {
|
|
|
316
327
|
};
|
|
317
328
|
|
|
318
329
|
/**
|
|
319
|
-
* Marks a method as the WAMP "
|
|
330
|
+
* Marks a method as the WAMP "challenge" event handler.
|
|
331
|
+
*
|
|
332
|
+
* This decorator should be used on a WAMP controller method. Only one "challenge" event handler
|
|
333
|
+
* can be defined per WAMP controller.
|
|
334
|
+
*
|
|
320
335
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
336
|
+
*/
|
|
337
|
+
export const onChallenge = (): MethodDecorator => {
|
|
338
|
+
return function (target, _propertyKey, descriptor) {
|
|
339
|
+
const metadata = Reflect.getMetadata(Symbols.wamp, target.constructor) ?? {};
|
|
340
|
+
metadata.challenge = descriptor.value;
|
|
341
|
+
Reflect.defineMetadata(Symbols.wamp, metadata, target.constructor);
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Marks a method as the WAMP "open" event handler.
|
|
321
347
|
*
|
|
322
348
|
* This decorator should be used on a WAMP controller method. Only one "open" event handler
|
|
323
349
|
* can be defined per WAMP controller.
|
|
350
|
+
*
|
|
351
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
324
352
|
*/
|
|
325
353
|
export const onOpen = (): MethodDecorator => {
|
|
326
354
|
return function (target, _propertyKey, descriptor) {
|
|
@@ -332,10 +360,11 @@ export namespace Wamp {
|
|
|
332
360
|
|
|
333
361
|
/**
|
|
334
362
|
* Marks a method as the WAMP "close" event handler.
|
|
335
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
336
363
|
*
|
|
337
364
|
* This decorator should be used on a WAMP controller method. Only one "close" event handler
|
|
338
365
|
* can be defined per WAMP controller.
|
|
366
|
+
*
|
|
367
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
339
368
|
*/
|
|
340
369
|
export const onClose = (): MethodDecorator => {
|
|
341
370
|
return function (target, _propertyKey, descriptor) {
|
|
@@ -347,10 +376,11 @@ export namespace Wamp {
|
|
|
347
376
|
|
|
348
377
|
/**
|
|
349
378
|
* Marks a method as the WAMP "error" event handler.
|
|
350
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
351
379
|
*
|
|
352
380
|
* This decorator should be used on a WAMP controller method. Only one "error" event handler
|
|
353
381
|
* can be defined per WAMP controller.
|
|
382
|
+
*
|
|
383
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
354
384
|
*/
|
|
355
385
|
export const onError = (): MethodDecorator => {
|
|
356
386
|
return function (target, _propertyKey, descriptor) {
|
|
@@ -362,10 +392,11 @@ export namespace Wamp {
|
|
|
362
392
|
|
|
363
393
|
/**
|
|
364
394
|
* Marks a method as the WAMP "reconnect" event handler.
|
|
365
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
366
395
|
*
|
|
367
396
|
* This decorator should be used on a WAMP controller method. Only one "reconnect" event handler
|
|
368
397
|
* can be defined per WAMP controller.
|
|
398
|
+
*
|
|
399
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
369
400
|
*/
|
|
370
401
|
export const onReconnect = (): MethodDecorator => {
|
|
371
402
|
return function (target, _propertyKey, descriptor) {
|
|
@@ -377,10 +408,11 @@ export namespace Wamp {
|
|
|
377
408
|
|
|
378
409
|
/**
|
|
379
410
|
* Marks a method as the WAMP "reconnectSuccess" event handler.
|
|
380
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
381
411
|
*
|
|
382
412
|
* This decorator should be used on a WAMP controller method. Only one "reconnectSuccess" event handler
|
|
383
413
|
* can be defined per WAMP controller.
|
|
414
|
+
*
|
|
415
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
384
416
|
*/
|
|
385
417
|
export const onReconnectSuccess = (): MethodDecorator => {
|
|
386
418
|
return function (target, _propertyKey, descriptor) {
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
{
|
|
15
|
+
"compilerOptions": {
|
|
16
|
+
// Environment setup & latest features
|
|
17
|
+
"target": "ESNext",
|
|
18
|
+
"module": "ESNext",
|
|
19
|
+
"moduleDetection": "force",
|
|
20
|
+
"allowJs": true,
|
|
21
|
+
"types": [
|
|
22
|
+
"bun-types",
|
|
23
|
+
"type-fest"
|
|
24
|
+
],
|
|
25
|
+
"experimentalDecorators": true,
|
|
26
|
+
"emitDecoratorMetadata": true,
|
|
27
|
+
// Bundler mode
|
|
28
|
+
"moduleResolution": "bundler",
|
|
29
|
+
"allowImportingTsExtensions": true,
|
|
30
|
+
"verbatimModuleSyntax": true,
|
|
31
|
+
"noEmit": true,
|
|
32
|
+
// Best practices
|
|
33
|
+
"strict": true,
|
|
34
|
+
"skipLibCheck": true,
|
|
35
|
+
"noFallthroughCasesInSwitch": true,
|
|
36
|
+
"noUncheckedIndexedAccess": false,
|
|
37
|
+
"forceConsistentCasingInFileNames": true,
|
|
38
|
+
// Some stricter flags (disabled by default)
|
|
39
|
+
"noUnusedLocals": true,
|
|
40
|
+
"noUnusedParameters": true,
|
|
41
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
42
|
+
},
|
|
43
|
+
"exclude": [
|
|
44
|
+
"node_modules",
|
|
45
|
+
"dist",
|
|
46
|
+
],
|
|
47
|
+
}
|