@avleon/core 0.0.28 → 0.0.29
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/dist/application.js +1 -1
- package/dist/cache.d.ts +1 -1
- package/dist/cache.js +2 -2
- package/dist/collection.d.ts +25 -32
- package/dist/collection.js +50 -6
- package/dist/collection.test.d.ts +1 -0
- package/dist/collection.test.js +59 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +30 -5
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +40 -0
- package/dist/controller.js +2 -2
- package/dist/environment-variables.js +42 -5
- package/dist/event-dispatcher.d.ts +23 -0
- package/dist/event-dispatcher.js +102 -0
- package/dist/event-subscriber.d.ts +15 -0
- package/dist/event-subscriber.js +96 -0
- package/dist/exceptions/http-exceptions.js +1 -1
- package/dist/exceptions/index.d.ts +1 -1
- package/dist/exceptions/system-exception.js +3 -1
- package/dist/file-storage.js +1 -1
- package/dist/helpers.js +1 -1
- package/dist/icore.d.ts +5 -0
- package/dist/icore.js +53 -18
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/optional-require.js +2 -2
- package/dist/validation.d.ts +1 -1
- package/dist/websocket.d.ts +7 -0
- package/dist/websocket.js +20 -0
- package/dist/websocket.test.d.ts +0 -0
- package/dist/websocket.test.js +1 -0
- package/package.json +3 -1
- package/src/config.test.ts +2 -2
- package/src/config.ts +2 -2
- package/src/event-dispatcher.ts +100 -0
- package/src/event-subscriber.ts +79 -0
- package/src/icore.ts +1106 -1060
- package/src/index.ts +3 -1
- package/src/middleware.ts +6 -4
- package/src/websocket.ts +47 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EventSubscriberRegistry = void 0;
|
|
13
|
+
exports.Private = Private;
|
|
14
|
+
exports.isPrivate = isPrivate;
|
|
15
|
+
exports.getPrivateChannelResolver = getPrivateChannelResolver;
|
|
16
|
+
exports.registerSocketSubscriber = registerSocketSubscriber;
|
|
17
|
+
exports.getSocketSubscribers = getSocketSubscribers;
|
|
18
|
+
exports.Subscribe = Subscribe;
|
|
19
|
+
exports.PrivateSubscribe = PrivateSubscribe;
|
|
20
|
+
const typedi_1 = require("typedi");
|
|
21
|
+
const event_dispatcher_1 = require("./event-dispatcher");
|
|
22
|
+
require("reflect-metadata");
|
|
23
|
+
const PRIVATE_META_KEY = 'avleon:private';
|
|
24
|
+
function Private(channelResolver) {
|
|
25
|
+
return function (target, propertyKey) {
|
|
26
|
+
Reflect.defineMetadata(PRIVATE_META_KEY, true, target, propertyKey);
|
|
27
|
+
Reflect.defineMetadata(`private:channel:${propertyKey}`, channelResolver, target);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function isPrivate(target, propertyKey) {
|
|
31
|
+
return Reflect.getMetadata(PRIVATE_META_KEY, target, propertyKey) || false;
|
|
32
|
+
}
|
|
33
|
+
function getPrivateChannelResolver(target, propertyKey) {
|
|
34
|
+
return Reflect.getMetadata(`private:channel:${propertyKey}`, target);
|
|
35
|
+
}
|
|
36
|
+
const socketSubscriberClasses = new Set();
|
|
37
|
+
function registerSocketSubscriber(target) {
|
|
38
|
+
socketSubscriberClasses.add(target);
|
|
39
|
+
}
|
|
40
|
+
function getSocketSubscribers() {
|
|
41
|
+
return Array.from(socketSubscriberClasses);
|
|
42
|
+
}
|
|
43
|
+
// decorators/Subscribe.ts
|
|
44
|
+
function Subscribe(event) {
|
|
45
|
+
return (target, propertyKey) => {
|
|
46
|
+
Reflect.defineMetadata('socket:event', event, target, propertyKey);
|
|
47
|
+
registerSocketSubscriber(target.constructor);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// decorators/Private.ts
|
|
51
|
+
function PrivateSubscribe(resolver) {
|
|
52
|
+
return function (target, propertyKey) {
|
|
53
|
+
Reflect.defineMetadata('avleon:private', true, target, propertyKey);
|
|
54
|
+
Reflect.defineMetadata(`private:channel:${propertyKey}`, resolver, target);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
let EventSubscriberRegistry = class EventSubscriberRegistry {
|
|
58
|
+
constructor(socketContext) {
|
|
59
|
+
this.socketContext = socketContext;
|
|
60
|
+
}
|
|
61
|
+
register(socket) {
|
|
62
|
+
const subscriberClasses = getSocketSubscribers();
|
|
63
|
+
for (const SubscriberClass of subscriberClasses) {
|
|
64
|
+
const instance = typedi_1.Container.get(SubscriberClass);
|
|
65
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
66
|
+
const methodNames = Object.getOwnPropertyNames(prototype)
|
|
67
|
+
.filter(name => typeof prototype[name] === 'function');
|
|
68
|
+
for (const methodName of methodNames) {
|
|
69
|
+
const event = Reflect.getMetadata('socket:event', prototype, methodName);
|
|
70
|
+
const isPrivateListener = isPrivate(instance, methodName);
|
|
71
|
+
const channelResolver = getPrivateChannelResolver(instance, methodName);
|
|
72
|
+
//const event = Reflect.getMetadata(`subscribe:event:${method}`, instance);
|
|
73
|
+
if (event) {
|
|
74
|
+
const channel = isPrivateListener && channelResolver ? channelResolver(socket) : event;
|
|
75
|
+
console.log('Channel', channel);
|
|
76
|
+
socket.on(channel, (payload) => {
|
|
77
|
+
this.socketContext.run(socket, async () => {
|
|
78
|
+
if (isPrivateListener) {
|
|
79
|
+
const user = socket.data.user;
|
|
80
|
+
if (!user)
|
|
81
|
+
return; // unauthorized
|
|
82
|
+
// optionally add more validation here
|
|
83
|
+
}
|
|
84
|
+
await instance[methodName](payload, socket.data);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
exports.EventSubscriberRegistry = EventSubscriberRegistry;
|
|
93
|
+
exports.EventSubscriberRegistry = EventSubscriberRegistry = __decorate([
|
|
94
|
+
(0, typedi_1.Service)(),
|
|
95
|
+
__metadata("design:paramtypes", [event_dispatcher_1.SocketContextService])
|
|
96
|
+
], EventSubscriberRegistry);
|
|
@@ -79,5 +79,5 @@ exports.HttpExceptions = {
|
|
|
79
79
|
badRequest: (message = "") => new BadRequestException(message),
|
|
80
80
|
unauthorized: (message = "") => new UnauthorizedException(message),
|
|
81
81
|
forbidden: (message = "") => new ForbiddenException(message),
|
|
82
|
-
internalError: (message = "") => new InternalErrorException(message)
|
|
82
|
+
internalError: (message = "") => new InternalErrorException(message),
|
|
83
83
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./http-exceptions";
|
|
@@ -11,7 +11,9 @@ class DuplicateRouteException extends Error {
|
|
|
11
11
|
constructor(params) {
|
|
12
12
|
let sameController = params.controller == params.inverseController;
|
|
13
13
|
let message = `Duplicate route found for method ${params.method.toUpperCase()}:${params.path == "" ? "'/'" : params.path} `;
|
|
14
|
-
message += sameController
|
|
14
|
+
message += sameController
|
|
15
|
+
? `in ${params.controller}`
|
|
16
|
+
: `both in ${params.controller} and ${params.inverseController}`;
|
|
15
17
|
super(message);
|
|
16
18
|
}
|
|
17
19
|
}
|
package/dist/file-storage.js
CHANGED
|
@@ -90,7 +90,7 @@ let FileStorage = class FileStorage {
|
|
|
90
90
|
overwrite: options && options.overwrite ? options.overwrite : true,
|
|
91
91
|
};
|
|
92
92
|
for (let f of files) {
|
|
93
|
-
let uploadPath =
|
|
93
|
+
let uploadPath = "public";
|
|
94
94
|
if (options === null || options === void 0 ? void 0 : options.to) {
|
|
95
95
|
uploadPath = `public/${options.to}`;
|
|
96
96
|
}
|
package/dist/helpers.js
CHANGED
|
@@ -40,7 +40,7 @@ function inject(cls) {
|
|
|
40
40
|
return container_1.default.get(cls);
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
|
-
throw new system_exception_1.SystemUseError(
|
|
43
|
+
throw new system_exception_1.SystemUseError("Not a project class. Maybe you wanna register it first.");
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
function isConstructor(func) {
|
package/dist/icore.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { IConfig } from "./config";
|
|
|
15
15
|
import { FastifyCorsOptions } from "@fastify/cors";
|
|
16
16
|
import { FastifyMultipartOptions } from "@fastify/multipart";
|
|
17
17
|
import { MultipartFile } from "./multipart";
|
|
18
|
+
import { ServerOptions } from 'socket.io';
|
|
18
19
|
export type FuncRoute = {
|
|
19
20
|
handler: any;
|
|
20
21
|
middlewares?: any[];
|
|
@@ -124,6 +125,7 @@ export declare class AvleonApplication {
|
|
|
124
125
|
private middlewares;
|
|
125
126
|
private rMap;
|
|
126
127
|
private hasSwagger;
|
|
128
|
+
private _hasWebsocket;
|
|
127
129
|
private globalSwaggerOptions;
|
|
128
130
|
private dataSourceOptions?;
|
|
129
131
|
private controllers;
|
|
@@ -142,6 +144,8 @@ export declare class AvleonApplication {
|
|
|
142
144
|
private initSwagger;
|
|
143
145
|
private _isConfigClass;
|
|
144
146
|
useCors<T = FastifyCorsOptions>(corsOptions?: ConfigInput<T>): void;
|
|
147
|
+
useWebSocket<T = Partial<ServerOptions>>(socketOptions: ConfigInput<T>): void;
|
|
148
|
+
private _initWebSocket;
|
|
145
149
|
useOpenApi<T = OpenApiUiOptions>(configOrClass: OpenApiConfigInput<T>): void;
|
|
146
150
|
useMultipart<T extends MultipartOptions>(options: ConfigInput<T>): void;
|
|
147
151
|
useDataSource<T extends DataSourceOptions>(options: ConfigInput<T>): void;
|
|
@@ -197,6 +201,7 @@ export declare class AvleonApplication {
|
|
|
197
201
|
};
|
|
198
202
|
private _mapFeatures;
|
|
199
203
|
initializeDatabase(): Promise<void>;
|
|
204
|
+
handleSocket(socket: any): void;
|
|
200
205
|
run(port?: number, fn?: CallableFunction): Promise<void>;
|
|
201
206
|
getTestApp(buildOptions?: any): TestApplication;
|
|
202
207
|
}
|
package/dist/icore.js
CHANGED
|
@@ -58,10 +58,13 @@ const cors_1 = __importDefault(require("@fastify/cors"));
|
|
|
58
58
|
const multipart_1 = __importDefault(require("@fastify/multipart"));
|
|
59
59
|
const validation_1 = require("./validation");
|
|
60
60
|
const utils_1 = require("./utils");
|
|
61
|
+
const socket_io_1 = require("socket.io");
|
|
62
|
+
const event_subscriber_1 = require("./event-subscriber");
|
|
61
63
|
const isTsNode = process.env.TS_NODE_DEV ||
|
|
62
64
|
process.env.TS_NODE_PROJECT ||
|
|
63
65
|
process[Symbol.for("ts-node.register.instance")];
|
|
64
66
|
const controllerDir = path_1.default.join(process.cwd(), isTsNode ? "./src/controllers" : "./dist/cotrollers");
|
|
67
|
+
const subscriberRegistry = typedi_1.default.get(event_subscriber_1.EventSubscriberRegistry);
|
|
65
68
|
// InternalApplication
|
|
66
69
|
class AvleonApplication {
|
|
67
70
|
constructor() {
|
|
@@ -71,6 +74,7 @@ class AvleonApplication {
|
|
|
71
74
|
this.middlewares = new Map();
|
|
72
75
|
this.rMap = new Map();
|
|
73
76
|
this.hasSwagger = false;
|
|
77
|
+
this._hasWebsocket = false;
|
|
74
78
|
this.globalSwaggerOptions = {};
|
|
75
79
|
this.dataSourceOptions = undefined;
|
|
76
80
|
this.controllers = [];
|
|
@@ -78,7 +82,7 @@ class AvleonApplication {
|
|
|
78
82
|
this.dataSource = undefined;
|
|
79
83
|
this.isMapFeatures = false;
|
|
80
84
|
this.registerControllerAuto = false;
|
|
81
|
-
this.registerControllerPath =
|
|
85
|
+
this.registerControllerPath = "./src";
|
|
82
86
|
this.metaCache = new Map();
|
|
83
87
|
this.app = (0, fastify_1.default)();
|
|
84
88
|
this.appConfig = new config_1.AppConfig();
|
|
@@ -122,7 +126,7 @@ class AvleonApplication {
|
|
|
122
126
|
if (options.ui && options.ui == "scalar") {
|
|
123
127
|
const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
|
|
124
128
|
failOnMissing: true,
|
|
125
|
-
customMessage:
|
|
129
|
+
customMessage: "Install \"@scalar/fastify-api-reference\" to enable API docs.\n\n npm install @scalar/fastify-api-reference",
|
|
126
130
|
});
|
|
127
131
|
await this.app.register(scalarPlugin, {
|
|
128
132
|
routePrefix: rPrefix,
|
|
@@ -141,7 +145,7 @@ class AvleonApplication {
|
|
|
141
145
|
else {
|
|
142
146
|
const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
|
|
143
147
|
failOnMissing: true,
|
|
144
|
-
customMessage:
|
|
148
|
+
customMessage: "Install \"@fastify/swagger-ui\" to enable API docs.\n\n npm install @fastify/swagger-ui",
|
|
145
149
|
});
|
|
146
150
|
await this.app.register(fastifySwaggerUi, {
|
|
147
151
|
logo: logo ? logo : null,
|
|
@@ -152,8 +156,8 @@ class AvleonApplication {
|
|
|
152
156
|
}
|
|
153
157
|
_isConfigClass(input) {
|
|
154
158
|
var _a;
|
|
155
|
-
return (typeof input ===
|
|
156
|
-
typeof input.prototype ===
|
|
159
|
+
return (typeof input === "function" &&
|
|
160
|
+
typeof input.prototype === "object" &&
|
|
157
161
|
((_a = input.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === input);
|
|
158
162
|
}
|
|
159
163
|
useCors(corsOptions) {
|
|
@@ -168,6 +172,19 @@ class AvleonApplication {
|
|
|
168
172
|
}
|
|
169
173
|
this.app.register(cors_1.default, coptions);
|
|
170
174
|
}
|
|
175
|
+
useWebSocket(socketOptions) {
|
|
176
|
+
this._hasWebsocket = true;
|
|
177
|
+
this._initWebSocket(socketOptions);
|
|
178
|
+
}
|
|
179
|
+
async _initWebSocket(options) {
|
|
180
|
+
const fsSocketIO = (0, utils_1.optionalRequire)("fastify-socket.io", {
|
|
181
|
+
failOnMissing: true,
|
|
182
|
+
customMessage: "Install \"fastify-socket.io\" to enable socket.io.\n\n run pnpm install fastify-socket.io",
|
|
183
|
+
});
|
|
184
|
+
await this.app.register(fsSocketIO, options);
|
|
185
|
+
const socketIO = await this.app.io;
|
|
186
|
+
typedi_1.default.set(socket_io_1.Server, socketIO);
|
|
187
|
+
}
|
|
171
188
|
useOpenApi(configOrClass) {
|
|
172
189
|
let openApiConfig;
|
|
173
190
|
if (this._isConfigClass(configOrClass)) {
|
|
@@ -211,8 +228,7 @@ class AvleonApplication {
|
|
|
211
228
|
this.dataSource = datasource;
|
|
212
229
|
typedi_1.default.set("idatasource", datasource);
|
|
213
230
|
}
|
|
214
|
-
_useCache(options) {
|
|
215
|
-
}
|
|
231
|
+
_useCache(options) { }
|
|
216
232
|
useMiddlewares(mclasses) {
|
|
217
233
|
for (const mclass of mclasses) {
|
|
218
234
|
const cls = typedi_1.default.get(mclass);
|
|
@@ -317,7 +333,7 @@ class AvleonApplication {
|
|
|
317
333
|
const args = await this._mapArgs(reqClone, allMeta);
|
|
318
334
|
for (let paramMeta of allMeta.params) {
|
|
319
335
|
if (paramMeta.required) {
|
|
320
|
-
(0, validation_1.validateOrThrow)({ [paramMeta.key]: args[paramMeta.index] }, { [paramMeta.key]: { type: paramMeta.dataType } }, { location:
|
|
336
|
+
(0, validation_1.validateOrThrow)({ [paramMeta.key]: args[paramMeta.index] }, { [paramMeta.key]: { type: paramMeta.dataType } }, { location: "param" });
|
|
321
337
|
}
|
|
322
338
|
}
|
|
323
339
|
for (let queryMeta of allMeta.query) {
|
|
@@ -333,7 +349,7 @@ class AvleonApplication {
|
|
|
333
349
|
}
|
|
334
350
|
}
|
|
335
351
|
if (queryMeta.required) {
|
|
336
|
-
(0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location:
|
|
352
|
+
(0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: "queryparam" });
|
|
337
353
|
}
|
|
338
354
|
}
|
|
339
355
|
for (let bodyMeta of allMeta.body) {
|
|
@@ -443,7 +459,7 @@ class AvleonApplication {
|
|
|
443
459
|
process.env.TS_NODE_PROJECT ||
|
|
444
460
|
process[Symbol.for("ts-node.register.instance")];
|
|
445
461
|
const controllerDir = path_1.default.join(process.cwd(), this.registerControllerPath);
|
|
446
|
-
return isTsNode ? controllerDir : controllerDir.replace(
|
|
462
|
+
return isTsNode ? controllerDir : controllerDir.replace("src", "dist");
|
|
447
463
|
}
|
|
448
464
|
async autoControllers(controllersPath) {
|
|
449
465
|
const conDir = this._resolveControllerDir(controllersPath);
|
|
@@ -457,8 +473,8 @@ class AvleonApplication {
|
|
|
457
473
|
const module = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
|
|
458
474
|
for (const exported of Object.values(module)) {
|
|
459
475
|
if (typeof exported === "function" && (0, container_1.isApiController)(exported)) {
|
|
460
|
-
console.log(
|
|
461
|
-
if (!this.controllers.some(con => exported.name == con.name)) {
|
|
476
|
+
console.log("adding", exported.name);
|
|
477
|
+
if (!this.controllers.some((con) => exported.name == con.name)) {
|
|
462
478
|
this.controllers.push(exported);
|
|
463
479
|
}
|
|
464
480
|
//this.buildController(exported);
|
|
@@ -470,7 +486,7 @@ class AvleonApplication {
|
|
|
470
486
|
useControllers(controllers) {
|
|
471
487
|
if (Array.isArray(controllers)) {
|
|
472
488
|
this.controllers = controllers;
|
|
473
|
-
controllers.forEach(controller => {
|
|
489
|
+
controllers.forEach((controller) => {
|
|
474
490
|
if (!this.controllers.includes(controller)) {
|
|
475
491
|
this.controllers.push(controller);
|
|
476
492
|
}
|
|
@@ -593,14 +609,17 @@ class AvleonApplication {
|
|
|
593
609
|
return this._routeHandler(path, "DELETE", fn);
|
|
594
610
|
}
|
|
595
611
|
_mapFeatures() {
|
|
596
|
-
const features = typedi_1.default.get(
|
|
597
|
-
console.log(
|
|
612
|
+
const features = typedi_1.default.get("features");
|
|
613
|
+
console.log("Features", features);
|
|
598
614
|
}
|
|
599
615
|
async initializeDatabase() {
|
|
600
616
|
if (this.dataSourceOptions && this.dataSource) {
|
|
601
617
|
await this.dataSource.initialize();
|
|
602
618
|
}
|
|
603
619
|
}
|
|
620
|
+
handleSocket(socket) {
|
|
621
|
+
subscriberRegistry.register(socket);
|
|
622
|
+
}
|
|
604
623
|
async run(port = 4000, fn) {
|
|
605
624
|
if (this.alreadyRun)
|
|
606
625
|
throw new system_exception_1.SystemUseError("App already running");
|
|
@@ -631,7 +650,8 @@ class AvleonApplication {
|
|
|
631
650
|
});
|
|
632
651
|
this.app.setErrorHandler(async (error, req, res) => {
|
|
633
652
|
const handledErr = this._handleError(error);
|
|
634
|
-
if (error instanceof exceptions_1.ValidationErrorException ||
|
|
653
|
+
if (error instanceof exceptions_1.ValidationErrorException ||
|
|
654
|
+
error instanceof exceptions_1.BadRequestException) {
|
|
635
655
|
return res.status(handledErr.code).send({
|
|
636
656
|
code: handledErr.code,
|
|
637
657
|
error: handledErr.error,
|
|
@@ -641,6 +661,21 @@ class AvleonApplication {
|
|
|
641
661
|
return res.status(handledErr.code).send(handledErr);
|
|
642
662
|
});
|
|
643
663
|
await this.app.ready();
|
|
664
|
+
if (this._hasWebsocket) {
|
|
665
|
+
await this.app.io.on('connection', this.handleSocket);
|
|
666
|
+
await this.app.io.use((socket, next) => {
|
|
667
|
+
const token = socket.handshake.auth.token;
|
|
668
|
+
try {
|
|
669
|
+
console.log('token', token);
|
|
670
|
+
const user = { id: 1, name: "tareq" };
|
|
671
|
+
socket.data.user = user; // this powers @AuthUser()
|
|
672
|
+
next();
|
|
673
|
+
}
|
|
674
|
+
catch (_a) {
|
|
675
|
+
next(new Error('Unauthorized'));
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
644
679
|
await this.app.listen({ port });
|
|
645
680
|
console.log(`Application running on http://127.0.0.1:${port}`);
|
|
646
681
|
}
|
|
@@ -683,7 +718,7 @@ class AvleonApplication {
|
|
|
683
718
|
delete: async (url, options) => this.app.inject({ method: "DELETE", url, ...options }),
|
|
684
719
|
options: async (url, options) => this.app.inject({ method: "OPTIONS", url, ...options }),
|
|
685
720
|
getController: (controller, deps = []) => {
|
|
686
|
-
const paramTypes = Reflect.getMetadata(
|
|
721
|
+
const paramTypes = Reflect.getMetadata("design:paramtypes", controller) || [];
|
|
687
722
|
deps.forEach((dep, i) => {
|
|
688
723
|
typedi_1.default.set(paramTypes[i], dep);
|
|
689
724
|
});
|
|
@@ -704,7 +739,7 @@ class AvleonTest {
|
|
|
704
739
|
process.env.NODE_ENV = "test";
|
|
705
740
|
}
|
|
706
741
|
static getController(controller, deps = []) {
|
|
707
|
-
const paramTypes = Reflect.getMetadata(
|
|
742
|
+
const paramTypes = Reflect.getMetadata("design:paramtypes", controller) || [];
|
|
708
743
|
deps.forEach((dep, i) => {
|
|
709
744
|
typedi_1.default.set(paramTypes[i], dep);
|
|
710
745
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -24,5 +24,7 @@ export * from "./utils/hash";
|
|
|
24
24
|
export * from "./multipart";
|
|
25
25
|
export * from "./file-storage";
|
|
26
26
|
export * from "./logger";
|
|
27
|
+
export * from "./event-dispatcher";
|
|
28
|
+
export { Subscribe, Private, PrivateSubscribe } from "./event-subscriber";
|
|
27
29
|
export declare const GetSchema: typeof sw.generateSwaggerSchema;
|
|
28
30
|
export { default as Container } from "./container";
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.Container = exports.GetSchema = exports.exclude = exports.pick = exports.validateRequestBody = exports.inject = void 0;
|
|
42
|
+
exports.Container = exports.GetSchema = exports.PrivateSubscribe = exports.Private = exports.Subscribe = exports.exclude = exports.pick = exports.validateRequestBody = exports.inject = void 0;
|
|
43
43
|
/**
|
|
44
44
|
* @copyright 2024
|
|
45
45
|
* @author Tareq Hossain
|
|
@@ -70,6 +70,11 @@ __exportStar(require("./utils/hash"), exports);
|
|
|
70
70
|
__exportStar(require("./multipart"), exports);
|
|
71
71
|
__exportStar(require("./file-storage"), exports);
|
|
72
72
|
__exportStar(require("./logger"), exports);
|
|
73
|
+
__exportStar(require("./event-dispatcher"), exports);
|
|
74
|
+
var event_subscriber_1 = require("./event-subscriber");
|
|
75
|
+
Object.defineProperty(exports, "Subscribe", { enumerable: true, get: function () { return event_subscriber_1.Subscribe; } });
|
|
76
|
+
Object.defineProperty(exports, "Private", { enumerable: true, get: function () { return event_subscriber_1.Private; } });
|
|
77
|
+
Object.defineProperty(exports, "PrivateSubscribe", { enumerable: true, get: function () { return event_subscriber_1.PrivateSubscribe; } });
|
|
73
78
|
exports.GetSchema = sw.generateSwaggerSchema;
|
|
74
79
|
var container_1 = require("./container");
|
|
75
80
|
Object.defineProperty(exports, "Container", { enumerable: true, get: function () { return __importDefault(container_1).default; } });
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./hash";
|
|
2
|
+
export * from "./optional-require";
|
|
@@ -37,7 +37,6 @@ exports.optionalRequire = optionalRequire;
|
|
|
37
37
|
exports.optionalImport = optionalImport;
|
|
38
38
|
function optionalRequire(moduleName, options = {}) {
|
|
39
39
|
try {
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
41
40
|
return require(moduleName);
|
|
42
41
|
}
|
|
43
42
|
catch (err) {
|
|
@@ -57,7 +56,8 @@ async function optionalImport(moduleName, options = {}) {
|
|
|
57
56
|
return mod;
|
|
58
57
|
}
|
|
59
58
|
catch (err) {
|
|
60
|
-
if ((err.code === "ERR_MODULE_NOT_FOUND" ||
|
|
59
|
+
if ((err.code === "ERR_MODULE_NOT_FOUND" ||
|
|
60
|
+
err.code === "MODULE_NOT_FOUND") &&
|
|
61
61
|
err.message.includes(moduleName)) {
|
|
62
62
|
if (options.failOnMissing) {
|
|
63
63
|
throw new Error(options.customMessage ||
|
package/dist/validation.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type ValidationProps = {
|
|
|
26
26
|
[key: string]: ValidationRule;
|
|
27
27
|
};
|
|
28
28
|
export type ValidateOptons = {
|
|
29
|
-
location?:
|
|
29
|
+
location?: "header" | "queryparam" | "param" | "body" | "custom";
|
|
30
30
|
};
|
|
31
31
|
export declare function validateOrThrow<T extends {}>(obj: T, rules: ValidationProps, options?: ValidateOptons): any;
|
|
32
32
|
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.AvleonSocketIo = void 0;
|
|
10
|
+
const typedi_1 = require("typedi");
|
|
11
|
+
let AvleonSocketIo = class AvleonSocketIo {
|
|
12
|
+
sendToAll() { }
|
|
13
|
+
sendOnly() { }
|
|
14
|
+
sendRoom() { }
|
|
15
|
+
receive(channel) { }
|
|
16
|
+
};
|
|
17
|
+
exports.AvleonSocketIo = AvleonSocketIo;
|
|
18
|
+
exports.AvleonSocketIo = AvleonSocketIo = __decorate([
|
|
19
|
+
(0, typedi_1.Service)()
|
|
20
|
+
], AvleonSocketIo);
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avleon/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -56,10 +56,12 @@
|
|
|
56
56
|
"cross-env": "^7.0.3",
|
|
57
57
|
"dotenv": "^16.4.7",
|
|
58
58
|
"fastify": "^5.1.0",
|
|
59
|
+
"fastify-socket.io": "^5.1.0",
|
|
59
60
|
"highlight.js": "^11.9.0",
|
|
60
61
|
"pino": "^9.6.0",
|
|
61
62
|
"pino-pretty": "^13.0.0",
|
|
62
63
|
"reflect-metadata": "^0.2.2",
|
|
64
|
+
"socket.io": "^4.8.1",
|
|
63
65
|
"typedi": "^0.10.0"
|
|
64
66
|
},
|
|
65
67
|
"peerDependencies": {
|
package/src/config.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
-
import {
|
|
2
|
+
import { AppConfig, CreateConfig, GetConfig, IConfig } from "./config";
|
|
3
3
|
import { Environment } from "./environment-variables";
|
|
4
4
|
|
|
5
5
|
type AppConfig = { name: string; os: string };
|
|
@@ -7,7 +7,7 @@ type AppConfig = { name: string; os: string };
|
|
|
7
7
|
describe("Config", () => {
|
|
8
8
|
describe("class", () => {
|
|
9
9
|
it("should be call by get config", () => {
|
|
10
|
-
@
|
|
10
|
+
@AppConfig
|
|
11
11
|
class MyConfig {
|
|
12
12
|
config(env: Environment) {
|
|
13
13
|
return {
|
package/src/config.ts
CHANGED
|
@@ -12,11 +12,11 @@ export interface IConfig<T = any> {
|
|
|
12
12
|
config(env: Environment): T;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function
|
|
15
|
+
export function AppConfig<T extends IConfig>(target: Constructable<T>) {
|
|
16
16
|
Container.set({ id: target, type: target });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export class
|
|
19
|
+
export class AvleonConfig {
|
|
20
20
|
get<T extends IConfig<R>, R>(configClass: Constructable<T>): R {
|
|
21
21
|
const instance = Container.get(configClass);
|
|
22
22
|
if (!instance) {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Service, Container } from "typedi";
|
|
2
|
+
import { Server as SocketIOServer, Socket, ServerOptions } from "socket.io";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@Service()
|
|
7
|
+
export class SocketContextService {
|
|
8
|
+
private readonly storage = new AsyncLocalStorage<{ socket: Socket }>();
|
|
9
|
+
|
|
10
|
+
run(socket: Socket, fn: () => void | Promise<void>) {
|
|
11
|
+
this.storage.run({ socket }, fn);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getSocket(): Socket | undefined {
|
|
15
|
+
return this.storage.getStore()?.socket;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type DispatchOptions = {
|
|
20
|
+
room?: string;
|
|
21
|
+
broadcast?: boolean;
|
|
22
|
+
transports?: ("socket" | "kafka" | "rabbitmq")[];
|
|
23
|
+
retry?: number;
|
|
24
|
+
retryDelay?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function sleep(ms: number) {
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Service()
|
|
32
|
+
export class EventDispatcher {
|
|
33
|
+
constructor(private readonly _context: SocketContextService) {}
|
|
34
|
+
|
|
35
|
+
async dispatch<T = any>(
|
|
36
|
+
event: string,
|
|
37
|
+
data: T,
|
|
38
|
+
options: DispatchOptions = {}
|
|
39
|
+
) {
|
|
40
|
+
const retryCount = options.retry ?? 0;
|
|
41
|
+
const delay = options.retryDelay ?? 300;
|
|
42
|
+
|
|
43
|
+
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
|
44
|
+
try {
|
|
45
|
+
await this.dispatchToTransports(event, data, options);
|
|
46
|
+
break;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (attempt === retryCount) throw err;
|
|
49
|
+
await sleep(delay * (attempt + 1));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async dispatchToTransports(event: string, data: any, options: DispatchOptions) {
|
|
55
|
+
const transports = options.transports ?? ["socket"];
|
|
56
|
+
|
|
57
|
+
for (const transport of transports) {
|
|
58
|
+
if (transport === "socket") {
|
|
59
|
+
const io = Container.get(SocketIOServer);
|
|
60
|
+
|
|
61
|
+
//console.log('SOckert', Container.get(SocketContextService));
|
|
62
|
+
|
|
63
|
+
const context = Container.get(SocketContextService);
|
|
64
|
+
const socket = context.getSocket();
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if (options.broadcast && socket) {
|
|
69
|
+
if (options.room) {
|
|
70
|
+
socket.broadcast.to(options.room).emit(event, data);
|
|
71
|
+
} else {
|
|
72
|
+
socket.broadcast.emit(event, data);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
if (options.room) {
|
|
76
|
+
io.to(options.room).emit(event, data);
|
|
77
|
+
} else {
|
|
78
|
+
io.emit(event, data);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
export function Dispatch(event: string, options?: Omit<DispatchOptions, "transports"> & { transports?: DispatchOptions["transports"] }) {
|
|
88
|
+
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
89
|
+
const original = descriptor.value;
|
|
90
|
+
|
|
91
|
+
descriptor.value = async function (...args: any[]) {
|
|
92
|
+
const result = await original.apply(this, args);
|
|
93
|
+
|
|
94
|
+
const dispatcher = Container.get(EventDispatcher);
|
|
95
|
+
await dispatcher.dispatch(event, result, options);
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
}
|