topsyde-utils 1.0.149 → 1.0.151
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.map +1 -1
- package/dist/client/rxjs/index.js.map +1 -1
- package/dist/client/rxjs/rxjs.js.map +1 -1
- package/dist/client/rxjs/useRxjs.js.map +1 -1
- package/dist/client/vite/plugins/index.js.map +1 -1
- package/dist/client/vite/plugins/topsydeUtilsVitePlugin.js.map +1 -1
- package/dist/consts.js.map +1 -1
- package/dist/enums.js.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/initializable.js.map +1 -1
- package/dist/server/bun/index.js.map +1 -1
- package/dist/server/bun/router/controller-discovery.js.map +1 -1
- package/dist/server/bun/router/index.js.map +1 -1
- package/dist/server/bun/router/router.internal.js.map +1 -1
- package/dist/server/bun/router/router.js.map +1 -1
- package/dist/server/bun/router/routes.js.map +1 -1
- package/dist/server/bun/websocket/Channel.js.map +1 -1
- package/dist/server/bun/websocket/Client.js.map +1 -1
- package/dist/server/bun/websocket/Message.js.map +1 -1
- package/dist/server/bun/websocket/Websocket.js.map +1 -1
- package/dist/server/bun/websocket/index.js.map +1 -1
- package/dist/server/bun/websocket/websocket.enums.js.map +1 -1
- package/dist/server/bun/websocket/websocket.guards.js.map +1 -1
- package/dist/server/bun/websocket/websocket.types.js.map +1 -1
- package/dist/server/controller.js.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/service.js.map +1 -1
- package/dist/singleton.js.map +1 -1
- package/dist/throwable.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/Console.js.map +1 -1
- package/dist/utils/Guards.js.map +1 -1
- package/dist/utils/Lib.js.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +4 -3
- package/src/__tests__/app.test.ts +205 -0
- package/src/__tests__/singleton.test.ts +402 -0
- package/src/__tests__/type-inference.test.ts +60 -0
- package/src/application.ts +43 -0
- package/src/client/rxjs/index.ts +5 -0
- package/src/client/rxjs/rxjs.ts +122 -0
- package/src/client/rxjs/useRxjs.ts +111 -0
- package/src/client/vite/plugins/index.ts +5 -0
- package/src/client/vite/plugins/topsydeUtilsVitePlugin.ts +80 -0
- package/src/consts.ts +48 -0
- package/src/enums.ts +14 -0
- package/src/errors.ts +56 -0
- package/src/index.ts +81 -0
- package/src/initializable.ts +375 -0
- package/src/server/bun/index.ts +6 -0
- package/src/server/bun/router/controller-discovery.ts +94 -0
- package/src/server/bun/router/index.ts +9 -0
- package/src/server/bun/router/router.internal.ts +64 -0
- package/src/server/bun/router/router.ts +51 -0
- package/src/server/bun/router/routes.ts +7 -0
- package/src/server/bun/websocket/Channel.ts +157 -0
- package/src/server/bun/websocket/Client.ts +129 -0
- package/src/server/bun/websocket/Message.ts +106 -0
- package/src/server/bun/websocket/Websocket.ts +221 -0
- package/src/server/bun/websocket/index.ts +14 -0
- package/src/server/bun/websocket/websocket.enums.ts +22 -0
- package/src/server/bun/websocket/websocket.guards.ts +6 -0
- package/src/server/bun/websocket/websocket.types.ts +186 -0
- package/src/server/controller.ts +121 -0
- package/src/server/index.ts +7 -0
- package/src/server/service.ts +36 -0
- package/src/singleton.ts +28 -0
- package/src/throwable.ts +87 -0
- package/src/types.ts +10 -0
- package/src/utils/Console.ts +85 -0
- package/src/utils/Guards.ts +61 -0
- package/src/utils/Lib.ts +506 -0
- package/src/utils/index.ts +9 -0
@@ -0,0 +1,375 @@
|
|
1
|
+
/* class Initializable {
|
2
|
+
retries: number;
|
3
|
+
initialized: boolean;
|
4
|
+
constructor(retries: number = 25) {
|
5
|
+
this.retries = retries;
|
6
|
+
this.initialized = false;
|
7
|
+
this.initialize();
|
8
|
+
}
|
9
|
+
|
10
|
+
protected initialize() {
|
11
|
+
this.initialized = true;
|
12
|
+
}
|
13
|
+
|
14
|
+
protected async isInitialized(): Promise<boolean> {
|
15
|
+
let retries = this.retries; // 5 seconds / 200ms = 25 retries
|
16
|
+
|
17
|
+
while (!this.initialized && retries > 0) {
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
19
|
+
retries--;
|
20
|
+
}
|
21
|
+
|
22
|
+
if (!this.initialized) {
|
23
|
+
throw new Error("Initialization failed after 5 seconds");
|
24
|
+
}
|
25
|
+
|
26
|
+
return true;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
export default Initializable;
|
31
|
+
*/
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Configuration options for initialization
|
35
|
+
*/
|
36
|
+
export interface InitializableOptions {
|
37
|
+
/** Number of retry attempts (default: 25) */
|
38
|
+
retries?: number;
|
39
|
+
|
40
|
+
/** Time in milliseconds between retry attempts (default: 200) */
|
41
|
+
retryInterval?: number;
|
42
|
+
|
43
|
+
/** Whether to initialize automatically in constructor (default: false) */
|
44
|
+
autoInitialize?: boolean;
|
45
|
+
|
46
|
+
/** Custom timeout in milliseconds (overrides retries * retryInterval) */
|
47
|
+
timeout?: number;
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Events emitted by Initializable instances
|
52
|
+
*/
|
53
|
+
export type InitializableEvent = "initializing" | "initialized" | "failed" | "timeout";
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Base class for objects that require asynchronous initialization
|
57
|
+
*
|
58
|
+
* @example
|
59
|
+
* class Database extends Initializable {
|
60
|
+
* private connection: any;
|
61
|
+
*
|
62
|
+
* constructor() {
|
63
|
+
* super({ retries: 10, retryInterval: 500 });
|
64
|
+
* }
|
65
|
+
*
|
66
|
+
* protected async doInitialize(): Promise<void> {
|
67
|
+
* this.connection = await connectToDatabase();
|
68
|
+
* }
|
69
|
+
* }
|
70
|
+
*
|
71
|
+
* // Usage
|
72
|
+
* const db = new Database();
|
73
|
+
* await db.initialize();
|
74
|
+
* // or check status
|
75
|
+
* if (await db.isInitialized()) {
|
76
|
+
* // use the database
|
77
|
+
* }
|
78
|
+
*/
|
79
|
+
class Initializable {
|
80
|
+
/** Number of retry attempts */
|
81
|
+
private retries: number;
|
82
|
+
|
83
|
+
/** Time in milliseconds between retry attempts */
|
84
|
+
private retryInterval: number;
|
85
|
+
|
86
|
+
/** Whether initialization has completed successfully */
|
87
|
+
private _initialized = false;
|
88
|
+
|
89
|
+
/** Whether initialization is in progress */
|
90
|
+
private initializing = false;
|
91
|
+
|
92
|
+
/** Whether initialization has failed */
|
93
|
+
private failed = false;
|
94
|
+
|
95
|
+
/** Optional timeout in milliseconds */
|
96
|
+
private timeout?: number;
|
97
|
+
|
98
|
+
/** Event listeners */
|
99
|
+
private listeners: Map<InitializableEvent, Function[]> = new Map();
|
100
|
+
|
101
|
+
/** Abort controller for cancellation */
|
102
|
+
private abortController: AbortController | null = null;
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Creates a new Initializable instance
|
106
|
+
* @param options Configuration options
|
107
|
+
*/
|
108
|
+
constructor(options: InitializableOptions = {}) {
|
109
|
+
this.retries = options.retries ?? 25;
|
110
|
+
this.retryInterval = options.retryInterval ?? 200;
|
111
|
+
this.timeout = options.timeout;
|
112
|
+
|
113
|
+
if (options.autoInitialize) {
|
114
|
+
// Schedule initialization on next tick to allow subclass construction to complete
|
115
|
+
setTimeout(() => this.initialize(), 0);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Initialize the object
|
121
|
+
* @returns Promise that resolves when initialization is complete
|
122
|
+
* @throws Error if initialization fails
|
123
|
+
*/
|
124
|
+
public async initialize(): Promise<void> {
|
125
|
+
// If already initialized, return immediately
|
126
|
+
if (this._initialized) {
|
127
|
+
return;
|
128
|
+
}
|
129
|
+
|
130
|
+
// If already initializing, wait for it to complete
|
131
|
+
if (this.initializing) {
|
132
|
+
return this.waitForInitialization();
|
133
|
+
}
|
134
|
+
|
135
|
+
// Start initialization
|
136
|
+
this.initializing = true;
|
137
|
+
this.failed = false;
|
138
|
+
this.abortController = new AbortController();
|
139
|
+
|
140
|
+
try {
|
141
|
+
// Emit initializing event
|
142
|
+
this.emit("initializing");
|
143
|
+
|
144
|
+
// Call the implementation-specific initialization
|
145
|
+
await this.doInitialize();
|
146
|
+
|
147
|
+
// Mark as initialized
|
148
|
+
this._initialized = true;
|
149
|
+
this.initializing = false;
|
150
|
+
|
151
|
+
// Emit initialized event
|
152
|
+
this.emit("initialized");
|
153
|
+
} catch (error) {
|
154
|
+
// Mark as failed
|
155
|
+
this.failed = true;
|
156
|
+
this.initializing = false;
|
157
|
+
|
158
|
+
// Emit failed event
|
159
|
+
this.emit("failed", error);
|
160
|
+
|
161
|
+
// Re-throw the error
|
162
|
+
throw error instanceof Error ? error : new Error(`Initialization failed: ${String(error)}`);
|
163
|
+
} finally {
|
164
|
+
this.abortController = null;
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Implementation-specific initialization logic
|
170
|
+
* Override this method in subclasses to provide custom initialization
|
171
|
+
*/
|
172
|
+
protected async doInitialize(): Promise<void> {
|
173
|
+
// Default implementation does nothing
|
174
|
+
// Subclasses should override this method
|
175
|
+
this._initialized = true;
|
176
|
+
}
|
177
|
+
|
178
|
+
/**
|
179
|
+
* Check if the object is initialized
|
180
|
+
* @param waitForIt Whether to wait for initialization to complete
|
181
|
+
* @returns Promise that resolves to true if initialized, false otherwise
|
182
|
+
*/
|
183
|
+
public async isInitialized(waitForIt = false): Promise<boolean> {
|
184
|
+
// If already initialized, return immediately
|
185
|
+
if (this._initialized) {
|
186
|
+
return true;
|
187
|
+
}
|
188
|
+
|
189
|
+
// If not waiting or already failed, return current status
|
190
|
+
if (!waitForIt || this.failed) {
|
191
|
+
return this._initialized;
|
192
|
+
}
|
193
|
+
|
194
|
+
// Wait for initialization to complete
|
195
|
+
try {
|
196
|
+
await this.waitForInitialization();
|
197
|
+
return true;
|
198
|
+
} catch (error) {
|
199
|
+
return false;
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
/**
|
204
|
+
* Wait for initialization to complete
|
205
|
+
* @returns Promise that resolves when initialization is complete
|
206
|
+
* @throws Error if initialization fails or times out
|
207
|
+
*/
|
208
|
+
private async waitForInitialization(): Promise<void> {
|
209
|
+
// If already initialized, return immediately
|
210
|
+
if (this._initialized) {
|
211
|
+
return;
|
212
|
+
}
|
213
|
+
|
214
|
+
// If not initializing, start initialization
|
215
|
+
if (!this.initializing) {
|
216
|
+
return this.initialize();
|
217
|
+
}
|
218
|
+
|
219
|
+
const className = this.constructor.name;
|
220
|
+
const maxTime = this.timeout ?? this.retries * this.retryInterval;
|
221
|
+
let retries = this.retries;
|
222
|
+
|
223
|
+
// Create a promise that resolves when initialization completes
|
224
|
+
return new Promise<void>((resolve, reject) => {
|
225
|
+
// One-time event listeners for completion
|
226
|
+
const onInitialized = () => {
|
227
|
+
this.off("initialized", onInitialized);
|
228
|
+
this.off("failed", onFailed);
|
229
|
+
this.off("timeout", onTimeout);
|
230
|
+
resolve();
|
231
|
+
};
|
232
|
+
|
233
|
+
const onFailed = (error: Error) => {
|
234
|
+
this.off("initialized", onInitialized);
|
235
|
+
this.off("failed", onFailed);
|
236
|
+
this.off("timeout", onTimeout);
|
237
|
+
reject(error);
|
238
|
+
};
|
239
|
+
|
240
|
+
const onTimeout = () => {
|
241
|
+
this.off("initialized", onInitialized);
|
242
|
+
this.off("failed", onFailed);
|
243
|
+
this.off("timeout", onTimeout);
|
244
|
+
reject(new Error(`Initialization of ${className} timed out after ${maxTime}ms`));
|
245
|
+
};
|
246
|
+
|
247
|
+
// Register event listeners
|
248
|
+
this.on("initialized", onInitialized);
|
249
|
+
this.on("failed", onFailed);
|
250
|
+
this.on("timeout", onTimeout);
|
251
|
+
|
252
|
+
// Set up polling to check for timeout
|
253
|
+
const checkInterval = setInterval(() => {
|
254
|
+
retries--;
|
255
|
+
|
256
|
+
if (this._initialized) {
|
257
|
+
clearInterval(checkInterval);
|
258
|
+
// Will be handled by event
|
259
|
+
} else if (retries <= 0) {
|
260
|
+
clearInterval(checkInterval);
|
261
|
+
this.emit("timeout");
|
262
|
+
}
|
263
|
+
}, this.retryInterval);
|
264
|
+
});
|
265
|
+
}
|
266
|
+
|
267
|
+
/**
|
268
|
+
* Cancel initialization if in progress
|
269
|
+
*/
|
270
|
+
public cancel(): void {
|
271
|
+
if (this.initializing && this.abortController) {
|
272
|
+
this.abortController.abort();
|
273
|
+
this.initializing = false;
|
274
|
+
this.failed = true;
|
275
|
+
this.emit("failed", new Error("Initialization cancelled"));
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Reset initialization state
|
281
|
+
* Allows re-initialization after failure
|
282
|
+
*/
|
283
|
+
public reset(): void {
|
284
|
+
if (this.initializing) {
|
285
|
+
this.cancel();
|
286
|
+
}
|
287
|
+
|
288
|
+
this._initialized = false;
|
289
|
+
this.initializing = false;
|
290
|
+
this.failed = false;
|
291
|
+
}
|
292
|
+
|
293
|
+
/**
|
294
|
+
* Register an event listener
|
295
|
+
* @param event Event name
|
296
|
+
* @param callback Function to call when event is emitted
|
297
|
+
* @returns this for chaining
|
298
|
+
*/
|
299
|
+
public on(event: InitializableEvent, callback: Function): this {
|
300
|
+
if (!this.listeners.has(event)) {
|
301
|
+
this.listeners.set(event, []);
|
302
|
+
}
|
303
|
+
|
304
|
+
this.listeners.get(event)!.push(callback);
|
305
|
+
return this;
|
306
|
+
}
|
307
|
+
|
308
|
+
/**
|
309
|
+
* Remove an event listener
|
310
|
+
* @param event Event name
|
311
|
+
* @param callback Function to remove
|
312
|
+
* @returns this for chaining
|
313
|
+
*/
|
314
|
+
public off(event: InitializableEvent, callback: Function): this {
|
315
|
+
if (this.listeners.has(event)) {
|
316
|
+
const callbacks = this.listeners.get(event)!;
|
317
|
+
const index = callbacks.indexOf(callback);
|
318
|
+
|
319
|
+
if (index !== -1) {
|
320
|
+
callbacks.splice(index, 1);
|
321
|
+
}
|
322
|
+
}
|
323
|
+
|
324
|
+
return this;
|
325
|
+
}
|
326
|
+
|
327
|
+
/**
|
328
|
+
* Emit an event
|
329
|
+
* @param event Event name
|
330
|
+
* @param args Arguments to pass to listeners
|
331
|
+
*/
|
332
|
+
protected emit(event: InitializableEvent, ...args: any[]): void {
|
333
|
+
if (this.listeners.has(event)) {
|
334
|
+
const callbacks = [...this.listeners.get(event)!];
|
335
|
+
callbacks.forEach((callback) => {
|
336
|
+
try {
|
337
|
+
callback(...args);
|
338
|
+
} catch (error) {
|
339
|
+
console.error(`Error in ${event} event listener:`, error);
|
340
|
+
}
|
341
|
+
});
|
342
|
+
}
|
343
|
+
}
|
344
|
+
|
345
|
+
/**
|
346
|
+
* Get the abort signal for cancellation
|
347
|
+
* Can be passed to fetch or other cancellable operations
|
348
|
+
*/
|
349
|
+
protected get abortSignal(): AbortSignal | undefined {
|
350
|
+
return this.abortController?.signal;
|
351
|
+
}
|
352
|
+
|
353
|
+
/**
|
354
|
+
* Check if initialization has been completed
|
355
|
+
*/
|
356
|
+
public get initialized(): boolean {
|
357
|
+
return this._initialized;
|
358
|
+
}
|
359
|
+
|
360
|
+
/**
|
361
|
+
* Check if initialization is in progress
|
362
|
+
*/
|
363
|
+
public get isInitializing(): boolean {
|
364
|
+
return this.initializing;
|
365
|
+
}
|
366
|
+
|
367
|
+
/**
|
368
|
+
* Check if initialization has failed
|
369
|
+
*/
|
370
|
+
public get hasFailed(): boolean {
|
371
|
+
return this.failed;
|
372
|
+
}
|
373
|
+
}
|
374
|
+
|
375
|
+
export default Initializable;
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import { join } from 'path';
|
2
|
+
import { readdirSync, statSync } from 'fs';
|
3
|
+
import { Lib } from '../../../utils';
|
4
|
+
import { Routes } from './routes';
|
5
|
+
|
6
|
+
const fallbackRoutes: Routes = {};
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Dynamically discovers and loads controllers from the components directory
|
10
|
+
*/
|
11
|
+
export class ControllerDiscovery {
|
12
|
+
public static async DiscoverRoutes(componentPaths: string[]) {
|
13
|
+
try {
|
14
|
+
const allDiscoveredRoutes: Routes = {};
|
15
|
+
|
16
|
+
// Discover controllers in all specified component paths
|
17
|
+
for (const path of componentPaths) {
|
18
|
+
const discoveredRoutes = await ControllerDiscovery.Find(path);
|
19
|
+
|
20
|
+
// Merge discovered routes
|
21
|
+
Object.assign(allDiscoveredRoutes, discoveredRoutes);
|
22
|
+
}
|
23
|
+
|
24
|
+
// Use discovered routes if any were found, otherwise use fallback
|
25
|
+
if (Object.keys(allDiscoveredRoutes).length > 0) {
|
26
|
+
Lib.Log(`Using auto-discovered routes from paths: ${componentPaths.join(', ')}`);
|
27
|
+
return allDiscoveredRoutes;
|
28
|
+
} else {
|
29
|
+
Lib.Log('No routes discovered, using fallback routes');
|
30
|
+
return fallbackRoutes;
|
31
|
+
}
|
32
|
+
} catch (error) {
|
33
|
+
// If auto-discovery fails, use fallback routes
|
34
|
+
Lib.Warn('Controller auto-discovery failed, using fallback routes:', error);
|
35
|
+
return fallbackRoutes;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Discovers controllers in the specified directory
|
41
|
+
* @param componentsPath Optional custom path to components directory (relative to project root)
|
42
|
+
* @returns Routes object with discovered controllers
|
43
|
+
*/
|
44
|
+
public static async Find(componentsPath?: string): Promise<Routes> {
|
45
|
+
const routes: Routes = {};
|
46
|
+
|
47
|
+
// Get project root - use process.cwd() to get the root of the project using this library
|
48
|
+
const projectRoot = process.cwd();
|
49
|
+
|
50
|
+
// Use provided path or default to components directory
|
51
|
+
const componentsDir = componentsPath
|
52
|
+
? join(projectRoot, componentsPath) // From project root
|
53
|
+
: join(projectRoot, 'src', 'components'); // Default location
|
54
|
+
|
55
|
+
Lib.Log(`Looking for controllers in: ${componentsDir}`);
|
56
|
+
|
57
|
+
try {
|
58
|
+
// Get all component directories
|
59
|
+
const componentFolders = readdirSync(componentsDir).filter(folder =>
|
60
|
+
statSync(join(componentsDir, folder)).isDirectory()
|
61
|
+
);
|
62
|
+
|
63
|
+
// Process each component folder
|
64
|
+
for (const componentName of componentFolders) {
|
65
|
+
const controllerPath = join(componentsDir, componentName, `${componentName}.controller.ts`);
|
66
|
+
|
67
|
+
try {
|
68
|
+
// Check if controller file exists
|
69
|
+
const controllerFile = Bun.file(controllerPath);
|
70
|
+
if (await controllerFile.exists()) {
|
71
|
+
// Import the controller
|
72
|
+
try {
|
73
|
+
const module = await import(controllerPath);
|
74
|
+
const Controller = module.default;
|
75
|
+
|
76
|
+
if (Controller && typeof Controller === 'function') {
|
77
|
+
routes[componentName] = Controller;
|
78
|
+
Lib.Log(`Registered controller: ${componentName}`);
|
79
|
+
}
|
80
|
+
} catch (err) {
|
81
|
+
Lib.Warn(`Failed to import controller for ${componentName}:`, err);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
} catch (err) {
|
85
|
+
Lib.Warn(`Error processing component ${componentName}:`, err);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
} catch (err) {
|
89
|
+
Lib.Warn(`Error discovering controllers in ${componentsDir}:`, err);
|
90
|
+
}
|
91
|
+
|
92
|
+
return routes;
|
93
|
+
}
|
94
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
// This file is auto-generated by scripts/generate-indexes.ts
|
2
|
+
// Do not edit this file directly
|
3
|
+
|
4
|
+
export * from './controller-discovery';
|
5
|
+
export * from './routes';
|
6
|
+
export * from './router';
|
7
|
+
export * from './router.internal';
|
8
|
+
export { default as Router } from './router';
|
9
|
+
export { default as Router_Internal } from './router.internal';
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import { Throwable } from "../../..";
|
2
|
+
import { ERROR_CODE } from "../../../errors";
|
3
|
+
import Controller from "../../controller";
|
4
|
+
import { Debug } from "../../../utils/Lib";
|
5
|
+
import { Routes } from "./routes";
|
6
|
+
|
7
|
+
class Router_Internal {
|
8
|
+
private registry = new Map<string, Controller>();
|
9
|
+
private routes: Routes;
|
10
|
+
|
11
|
+
constructor(routes?: Routes) {
|
12
|
+
this.routes = routes ?? {};
|
13
|
+
}
|
14
|
+
|
15
|
+
async post<T>(req: Request): Promise<T> {
|
16
|
+
return await this.handleRequest(req);
|
17
|
+
}
|
18
|
+
|
19
|
+
async get<T>(req: Request): Promise<T> {
|
20
|
+
return await this.handleRequest(req);
|
21
|
+
}
|
22
|
+
|
23
|
+
private async handleRequest<T>(request: Request): Promise<T> {
|
24
|
+
const path = this.getPath(request);
|
25
|
+
const output = await this.resolve(path).call<T>(request);
|
26
|
+
return output;
|
27
|
+
}
|
28
|
+
|
29
|
+
private getPath(request: Request): string {
|
30
|
+
return new URL(request.url).pathname;
|
31
|
+
}
|
32
|
+
|
33
|
+
private resolve(path: string): Controller {
|
34
|
+
const controllerKey = path.split("/")[1];
|
35
|
+
return this.register(controllerKey, () => this.controllerFactory(controllerKey));
|
36
|
+
}
|
37
|
+
|
38
|
+
private register(controllerKey: string, factory: () => Controller): Controller {
|
39
|
+
if (!this.registry.has(controllerKey)) {
|
40
|
+
this.registry.set(controllerKey, factory());
|
41
|
+
Debug.Log(`Caching controller: /${controllerKey}`);
|
42
|
+
}
|
43
|
+
return this.registry.get(controllerKey) as Controller;
|
44
|
+
}
|
45
|
+
|
46
|
+
public setRoutes(routes: Routes): void {
|
47
|
+
this.routes = routes;
|
48
|
+
}
|
49
|
+
|
50
|
+
private controllerFactory(controllerKey: string): Controller {
|
51
|
+
try {
|
52
|
+
if (!(controllerKey in this.routes)) throw new Throwable(`${ERROR_CODE.INVALID_CONTROLLER}: ${controllerKey}`, { logError: false });
|
53
|
+
|
54
|
+
const ControllerClass = this.routes[controllerKey as keyof typeof this.routes];
|
55
|
+
|
56
|
+
return new ControllerClass();
|
57
|
+
} catch (err) {
|
58
|
+
console.error("controllerFactory", err);
|
59
|
+
throw err;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
export default Router_Internal;
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { ERROR_CODE } from "../../../errors";
|
2
|
+
import Singleton from "../../../singleton";
|
3
|
+
import Guards from "../../../utils/Guards";
|
4
|
+
import Router_Internal from "./router.internal";
|
5
|
+
import { Routes } from "./routes";
|
6
|
+
|
7
|
+
type MethodMap<T> = {
|
8
|
+
[method: string]: (req: Request) => Promise<T>;
|
9
|
+
};
|
10
|
+
|
11
|
+
class Router extends Singleton {
|
12
|
+
private internal: Router_Internal;
|
13
|
+
|
14
|
+
public constructor(routes?: Routes) {
|
15
|
+
super();
|
16
|
+
this.internal = new Router_Internal(routes);
|
17
|
+
}
|
18
|
+
|
19
|
+
private setRoutes(routes: Routes): void {
|
20
|
+
this.internal.setRoutes(routes);
|
21
|
+
}
|
22
|
+
|
23
|
+
public static async Call<T>(request: Request): Promise<T> {
|
24
|
+
if (Guards.IsNil(request)) throw ERROR_CODE.NO_REQUEST;
|
25
|
+
const methods: MethodMap<T> = this.getMethodMap();
|
26
|
+
const method = methods[request.method];
|
27
|
+
|
28
|
+
if (Guards.IsNil(method)) throw ERROR_CODE.INVALID_METHOD;
|
29
|
+
|
30
|
+
return await method(request);
|
31
|
+
}
|
32
|
+
|
33
|
+
public static SetRoutes(routes: Routes) {
|
34
|
+
this.GetInstance<Router>().setRoutes(routes);
|
35
|
+
}
|
36
|
+
|
37
|
+
private static getMethodMap<T>(): MethodMap<T> {
|
38
|
+
const router = this.GetInstance<Router>();
|
39
|
+
return {
|
40
|
+
GET: async (req) => await router.internal.get(req),
|
41
|
+
POST: async (req) => await router.internal.post(req),
|
42
|
+
};
|
43
|
+
}
|
44
|
+
|
45
|
+
public static GetQueryParams(request: Request): URLSearchParams {
|
46
|
+
const url = new URL(request.url);
|
47
|
+
return url.searchParams;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
export default Router;
|