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.
Files changed (74) hide show
  1. package/dist/application.js.map +1 -1
  2. package/dist/client/rxjs/index.js.map +1 -1
  3. package/dist/client/rxjs/rxjs.js.map +1 -1
  4. package/dist/client/rxjs/useRxjs.js.map +1 -1
  5. package/dist/client/vite/plugins/index.js.map +1 -1
  6. package/dist/client/vite/plugins/topsydeUtilsVitePlugin.js.map +1 -1
  7. package/dist/consts.js.map +1 -1
  8. package/dist/enums.js.map +1 -1
  9. package/dist/errors.js.map +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/initializable.js.map +1 -1
  12. package/dist/server/bun/index.js.map +1 -1
  13. package/dist/server/bun/router/controller-discovery.js.map +1 -1
  14. package/dist/server/bun/router/index.js.map +1 -1
  15. package/dist/server/bun/router/router.internal.js.map +1 -1
  16. package/dist/server/bun/router/router.js.map +1 -1
  17. package/dist/server/bun/router/routes.js.map +1 -1
  18. package/dist/server/bun/websocket/Channel.js.map +1 -1
  19. package/dist/server/bun/websocket/Client.js.map +1 -1
  20. package/dist/server/bun/websocket/Message.js.map +1 -1
  21. package/dist/server/bun/websocket/Websocket.js.map +1 -1
  22. package/dist/server/bun/websocket/index.js.map +1 -1
  23. package/dist/server/bun/websocket/websocket.enums.js.map +1 -1
  24. package/dist/server/bun/websocket/websocket.guards.js.map +1 -1
  25. package/dist/server/bun/websocket/websocket.types.js.map +1 -1
  26. package/dist/server/controller.js.map +1 -1
  27. package/dist/server/index.js.map +1 -1
  28. package/dist/server/service.js.map +1 -1
  29. package/dist/singleton.js.map +1 -1
  30. package/dist/throwable.js.map +1 -1
  31. package/dist/types.js.map +1 -1
  32. package/dist/utils/Console.js.map +1 -1
  33. package/dist/utils/Guards.js.map +1 -1
  34. package/dist/utils/Lib.js.map +1 -1
  35. package/dist/utils/index.js.map +1 -1
  36. package/package.json +4 -3
  37. package/src/__tests__/app.test.ts +205 -0
  38. package/src/__tests__/singleton.test.ts +402 -0
  39. package/src/__tests__/type-inference.test.ts +60 -0
  40. package/src/application.ts +43 -0
  41. package/src/client/rxjs/index.ts +5 -0
  42. package/src/client/rxjs/rxjs.ts +122 -0
  43. package/src/client/rxjs/useRxjs.ts +111 -0
  44. package/src/client/vite/plugins/index.ts +5 -0
  45. package/src/client/vite/plugins/topsydeUtilsVitePlugin.ts +80 -0
  46. package/src/consts.ts +48 -0
  47. package/src/enums.ts +14 -0
  48. package/src/errors.ts +56 -0
  49. package/src/index.ts +81 -0
  50. package/src/initializable.ts +375 -0
  51. package/src/server/bun/index.ts +6 -0
  52. package/src/server/bun/router/controller-discovery.ts +94 -0
  53. package/src/server/bun/router/index.ts +9 -0
  54. package/src/server/bun/router/router.internal.ts +64 -0
  55. package/src/server/bun/router/router.ts +51 -0
  56. package/src/server/bun/router/routes.ts +7 -0
  57. package/src/server/bun/websocket/Channel.ts +157 -0
  58. package/src/server/bun/websocket/Client.ts +129 -0
  59. package/src/server/bun/websocket/Message.ts +106 -0
  60. package/src/server/bun/websocket/Websocket.ts +221 -0
  61. package/src/server/bun/websocket/index.ts +14 -0
  62. package/src/server/bun/websocket/websocket.enums.ts +22 -0
  63. package/src/server/bun/websocket/websocket.guards.ts +6 -0
  64. package/src/server/bun/websocket/websocket.types.ts +186 -0
  65. package/src/server/controller.ts +121 -0
  66. package/src/server/index.ts +7 -0
  67. package/src/server/service.ts +36 -0
  68. package/src/singleton.ts +28 -0
  69. package/src/throwable.ts +87 -0
  70. package/src/types.ts +10 -0
  71. package/src/utils/Console.ts +85 -0
  72. package/src/utils/Guards.ts +61 -0
  73. package/src/utils/Lib.ts +506 -0
  74. 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,6 @@
1
+ // This file is auto-generated by scripts/generate-indexes.sh
2
+ // Do not edit this file directly
3
+
4
+ export * from './websocket/Websocket';
5
+ export { default as Websocket } from './websocket/Websocket';
6
+ export * from './websocket';
@@ -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;
@@ -0,0 +1,7 @@
1
+ import Controller from "../../controller";
2
+
3
+ export type Routes = {
4
+ [key: string]: new () => Controller;
5
+ };
6
+
7
+ export const routes: Routes = {};