topsyde-utils 1.0.150 → 1.0.152
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.d.ts +1 -0
- package/dist/application.js +12 -8
- 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.d.ts +1 -1
- package/dist/index.js +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 +5 -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 +48 -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,402 @@
|
|
1
|
+
import Singleton from "../singleton";
|
2
|
+
import Channel from "../server/bun/websocket/Channel";
|
3
|
+
import { BroadcastOptions, WebsocketStructuredMessage } from "../server/bun/websocket/websocket.types";
|
4
|
+
import * as app from "../server/bun/websocket";
|
5
|
+
import { Client } from "../server/bun/websocket";
|
6
|
+
import { Server } from "bun";
|
7
|
+
|
8
|
+
// Base class that extends Singleton
|
9
|
+
class BaseClass extends Singleton {
|
10
|
+
protected constructor() {
|
11
|
+
super();
|
12
|
+
}
|
13
|
+
public value: string = "base";
|
14
|
+
|
15
|
+
public getValue(): string {
|
16
|
+
return this.value;
|
17
|
+
}
|
18
|
+
|
19
|
+
public static staticMethod(): string {
|
20
|
+
return "base-static";
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
// Derived class that extends BaseClass
|
25
|
+
class DerivedClass extends BaseClass {
|
26
|
+
protected constructor() {
|
27
|
+
super();
|
28
|
+
}
|
29
|
+
public override value: string = "derived";
|
30
|
+
|
31
|
+
public override getValue(): string {
|
32
|
+
return "derived-" + this.value;
|
33
|
+
}
|
34
|
+
|
35
|
+
public static override staticMethod(): string {
|
36
|
+
return "derived-static";
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
// Another class that extends BaseClass
|
41
|
+
class AnotherDerivedClass extends BaseClass {
|
42
|
+
public override value: string = "another";
|
43
|
+
|
44
|
+
public static testStaticMethod(): string {
|
45
|
+
// This should call the staticMethod on the class it's called on
|
46
|
+
return this.staticMethod();
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
// Class with constructor parameters
|
51
|
+
class ParameterizedSingleton extends Singleton {
|
52
|
+
public readonly config: { name: string; id: number };
|
53
|
+
|
54
|
+
constructor(name: string, id: number) {
|
55
|
+
super();
|
56
|
+
this.config = { name, id };
|
57
|
+
}
|
58
|
+
|
59
|
+
public getConfig(): { name: string; id: number } {
|
60
|
+
return this.config;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
// Multiple level inheritance
|
65
|
+
class Level1 extends Singleton {
|
66
|
+
protected constructor() {
|
67
|
+
super();
|
68
|
+
}
|
69
|
+
public level: number = 1;
|
70
|
+
}
|
71
|
+
|
72
|
+
class Level2 extends Level1 {
|
73
|
+
protected constructor() {
|
74
|
+
super();
|
75
|
+
}
|
76
|
+
public override level: number = 2;
|
77
|
+
}
|
78
|
+
|
79
|
+
class Level3 extends Level2 {
|
80
|
+
protected constructor() {
|
81
|
+
super();
|
82
|
+
}
|
83
|
+
public override level: number = 3;
|
84
|
+
}
|
85
|
+
|
86
|
+
describe("Singleton", () => {
|
87
|
+
beforeEach(() => {
|
88
|
+
// Reset the singleton instances before each test
|
89
|
+
Singleton.ResetInstances();
|
90
|
+
});
|
91
|
+
|
92
|
+
test("BaseClass should return a singleton instance", () => {
|
93
|
+
const instance1 = BaseClass.GetInstance<BaseClass>();
|
94
|
+
const instance2 = BaseClass.GetInstance<BaseClass>();
|
95
|
+
|
96
|
+
expect(instance1).toBe(instance2);
|
97
|
+
expect(instance1.getValue()).toBe("base");
|
98
|
+
});
|
99
|
+
|
100
|
+
test("Each class should have its own singleton instance", () => {
|
101
|
+
const baseInstance = BaseClass.GetInstance<BaseClass>();
|
102
|
+
const derivedInstance = DerivedClass.GetInstance<DerivedClass>();
|
103
|
+
|
104
|
+
// Each class should have its own instance
|
105
|
+
expect(derivedInstance).not.toBe(baseInstance);
|
106
|
+
|
107
|
+
// But the instances should have the correct class behavior
|
108
|
+
expect(baseInstance.getValue()).toBe("base");
|
109
|
+
expect(derivedInstance.getValue()).toBe("derived-derived");
|
110
|
+
});
|
111
|
+
|
112
|
+
test("Static methods should be called on the correct class", () => {
|
113
|
+
expect(BaseClass.staticMethod()).toBe("base-static");
|
114
|
+
expect(DerivedClass.staticMethod()).toBe("derived-static");
|
115
|
+
|
116
|
+
// In JavaScript, 'this' in a static method refers to the class it was called on
|
117
|
+
expect(AnotherDerivedClass.testStaticMethod()).toBe("base-static");
|
118
|
+
});
|
119
|
+
|
120
|
+
test("Multiple level inheritance should maintain separate instances", () => {
|
121
|
+
const level1 = Level1.GetInstance<Level1>();
|
122
|
+
const level2 = Level2.GetInstance<Level2>();
|
123
|
+
const level3 = Level3.GetInstance<Level3>();
|
124
|
+
|
125
|
+
// Each level should have its own instance
|
126
|
+
expect(level1).not.toBe(level2);
|
127
|
+
expect(level2).not.toBe(level3);
|
128
|
+
expect(level1).not.toBe(level3);
|
129
|
+
|
130
|
+
// Each instance should have the correct level value
|
131
|
+
expect(level1.level).toBe(1);
|
132
|
+
expect(level2.level).toBe(2);
|
133
|
+
expect(level3.level).toBe(3);
|
134
|
+
});
|
135
|
+
|
136
|
+
test("Singleton instances should be created only once per class", () => {
|
137
|
+
// Create a mock class that extends Singleton and counts constructor calls
|
138
|
+
class CounterSingleton extends Singleton {
|
139
|
+
public static constructorCallCount = 0;
|
140
|
+
|
141
|
+
constructor() {
|
142
|
+
super();
|
143
|
+
CounterSingleton.constructorCallCount++;
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
// Get the instance multiple times
|
148
|
+
const instance1 = CounterSingleton.GetInstance();
|
149
|
+
const instance2 = CounterSingleton.GetInstance();
|
150
|
+
const instance3 = CounterSingleton.GetInstance();
|
151
|
+
|
152
|
+
// Constructor should be called only once
|
153
|
+
expect(CounterSingleton.constructorCallCount).toBe(1);
|
154
|
+
|
155
|
+
// All instances should be the same
|
156
|
+
expect(instance1).toBe(instance2);
|
157
|
+
expect(instance2).toBe(instance3);
|
158
|
+
});
|
159
|
+
|
160
|
+
test("Singleton should handle property modifications correctly", () => {
|
161
|
+
const instance1 = BaseClass.GetInstance<BaseClass>();
|
162
|
+
|
163
|
+
// Modify a property
|
164
|
+
instance1.value = "modified";
|
165
|
+
|
166
|
+
// Get the instance again
|
167
|
+
const instance2 = BaseClass.GetInstance<BaseClass>();
|
168
|
+
|
169
|
+
// The modification should persist
|
170
|
+
expect(instance2.value).toBe("modified");
|
171
|
+
|
172
|
+
// Reset for other tests
|
173
|
+
instance1.value = "base";
|
174
|
+
});
|
175
|
+
|
176
|
+
test("ResetInstances should clear all singleton instances", () => {
|
177
|
+
// Get instances of multiple classes
|
178
|
+
const baseInstance1 = BaseClass.GetInstance<BaseClass>();
|
179
|
+
const derivedInstance1 = DerivedClass.GetInstance<DerivedClass>();
|
180
|
+
|
181
|
+
// Reset all instances
|
182
|
+
Singleton.ResetInstances();
|
183
|
+
|
184
|
+
// Get new instances
|
185
|
+
const baseInstance2 = BaseClass.GetInstance<BaseClass>();
|
186
|
+
const derivedInstance2 = DerivedClass.GetInstance<DerivedClass>();
|
187
|
+
|
188
|
+
// The new instances should be different from the original ones
|
189
|
+
expect(baseInstance2).not.toBe(baseInstance1);
|
190
|
+
expect(derivedInstance2).not.toBe(derivedInstance1);
|
191
|
+
});
|
192
|
+
|
193
|
+
test("ResetInstance should clear a specific singleton instance", () => {
|
194
|
+
// Get instances of multiple classes
|
195
|
+
const baseInstance1 = BaseClass.GetInstance<BaseClass>();
|
196
|
+
const derivedInstance1 = DerivedClass.GetInstance<DerivedClass>();
|
197
|
+
|
198
|
+
// Reset only the BaseClass instance
|
199
|
+
Singleton.ResetInstance("BaseClass");
|
200
|
+
|
201
|
+
// Get new instances
|
202
|
+
const baseInstance2 = BaseClass.GetInstance<BaseClass>();
|
203
|
+
const derivedInstance2 = DerivedClass.GetInstance<DerivedClass>();
|
204
|
+
|
205
|
+
// Only the BaseClass instance should be different
|
206
|
+
expect(baseInstance2).not.toBe(baseInstance1);
|
207
|
+
expect(derivedInstance2).toBe(derivedInstance1);
|
208
|
+
});
|
209
|
+
|
210
|
+
test("Special handling for Websocket classes", () => {
|
211
|
+
// Create classes that include "Websocket" in their name
|
212
|
+
class MockWebsocket extends Singleton {
|
213
|
+
constructor() {
|
214
|
+
super();
|
215
|
+
}
|
216
|
+
}
|
217
|
+
class MockWebsocketExtended extends MockWebsocket {}
|
218
|
+
class OtherSingleton extends Singleton {
|
219
|
+
constructor() {
|
220
|
+
super();
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
// Get instances
|
225
|
+
const instance1 = MockWebsocket.GetInstance();
|
226
|
+
const instance2 = MockWebsocketExtended.GetInstance();
|
227
|
+
const instance3 = OtherSingleton.GetInstance();
|
228
|
+
|
229
|
+
// Each class should have its own singleton instance
|
230
|
+
expect(instance1).toBe(instance1); // Same class gets same instance
|
231
|
+
expect(instance2).toBe(instance2); // Same class gets same instance
|
232
|
+
expect(instance1).not.toBe(instance3); // Different classes get different instances
|
233
|
+
expect(instance2).not.toBe(instance3); // Different classes get different instances
|
234
|
+
});
|
235
|
+
|
236
|
+
test("GetInstance should infer consturctor params for the class", () => {
|
237
|
+
class ClassWithConstructorParams extends Singleton {
|
238
|
+
public readonly config: { name: string; id: number };
|
239
|
+
|
240
|
+
protected constructor(name: string, id: number) {
|
241
|
+
super();
|
242
|
+
this.config = { name, id };
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
// Passing required parameters
|
247
|
+
const instance = ClassWithConstructorParams.GetInstance<ClassWithConstructorParams>("test", 1);
|
248
|
+
expect(instance.config).toEqual({ name: "test", id: 1 });
|
249
|
+
});
|
250
|
+
|
251
|
+
test("GetInstance can be overriden with a custom implementation", () => {
|
252
|
+
class CustomSingleton extends Singleton {
|
253
|
+
protected constructor(param1: string, param2: number) {
|
254
|
+
super();
|
255
|
+
}
|
256
|
+
|
257
|
+
public static override GetInstance<T extends CustomSingleton>(param1: string, param2: number): T {
|
258
|
+
return super.GetInstance<T>(param1, param2);
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
const instance = CustomSingleton.GetInstance("test", 2);
|
263
|
+
expect(instance).toBeDefined();
|
264
|
+
});
|
265
|
+
|
266
|
+
it("should allow custom channel map implementation", () => {
|
267
|
+
class CustomChannel extends Channel {
|
268
|
+
public broadcast(message: WebsocketStructuredMessage) {
|
269
|
+
console.log("CONSOLE LOG");
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
// Create a map with our custom channel
|
274
|
+
// Create a new Websocket instance with our custom channels
|
275
|
+
const ws = app.Websocket.GetInstance<app.Websocket>({
|
276
|
+
channelClass: CustomChannel, // Explicitly set the channel class
|
277
|
+
});
|
278
|
+
|
279
|
+
// Create a new channel - it should be a CustomChannel instance
|
280
|
+
const newChannel = ws.createChannel("test", "Test Channel", 5);
|
281
|
+
expect(newChannel).toBeInstanceOf(CustomChannel);
|
282
|
+
|
283
|
+
// Verify that broadcast uses our custom implementation
|
284
|
+
const spy = jest.spyOn(console, "log");
|
285
|
+
newChannel.broadcast({ type: "test", content: { message: "test message" } });
|
286
|
+
expect(spy).toHaveBeenCalledWith("CONSOLE LOG");
|
287
|
+
spy.mockRestore();
|
288
|
+
|
289
|
+
// Verify that the global channel is also a CustomChannel
|
290
|
+
const globalChannel = app.Websocket.GetChannel("global");
|
291
|
+
expect(globalChannel).toBeInstanceOf(CustomChannel);
|
292
|
+
});
|
293
|
+
it("should allow custom client implementation", () => {
|
294
|
+
class CustomClient extends Client {
|
295
|
+
public send(message: WebsocketStructuredMessage) {
|
296
|
+
console.log("CONSOLE LOG");
|
297
|
+
}
|
298
|
+
}
|
299
|
+
const ws = app.Websocket.GetInstance<app.Websocket>({ clientClass: CustomClient });
|
300
|
+
const client = app.Websocket.CreateClient({ id: "test", name: "Test Client", ws: {} as any });
|
301
|
+
expect(client).toBeInstanceOf(CustomClient);
|
302
|
+
});
|
303
|
+
it("should allow custom channel implementation", () => {
|
304
|
+
class CustomChannel extends Channel {
|
305
|
+
public broadcast(message: WebsocketStructuredMessage) {
|
306
|
+
console.log("CONSOLE LOG");
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
// Create a map with our custom channel
|
311
|
+
const ws = app.Websocket.GetInstance<app.Websocket>({ channelClass: CustomChannel });
|
312
|
+
|
313
|
+
// Create a new channel - it should be a CustomChannel instance
|
314
|
+
const newChannel = ws.createChannel("test", "Test Channel", 5);
|
315
|
+
expect(newChannel).toBeInstanceOf(CustomChannel);
|
316
|
+
|
317
|
+
// Verify that broadcast uses our custom implementation
|
318
|
+
const spy = jest.spyOn(console, "log");
|
319
|
+
newChannel.broadcast({ type: "test", content: { message: "test message" } });
|
320
|
+
expect(spy).toHaveBeenCalledWith("CONSOLE LOG");
|
321
|
+
spy.mockRestore();
|
322
|
+
|
323
|
+
// Verify that the global channel is also a CustomChannel
|
324
|
+
const globalChannel = app.Websocket.GetChannel("global");
|
325
|
+
expect(globalChannel).toBeInstanceOf(CustomChannel);
|
326
|
+
});
|
327
|
+
|
328
|
+
it("should handle derived channel broadcast with server correctly", () => {
|
329
|
+
// Mock server setup
|
330
|
+
const mockPublish = jest.fn();
|
331
|
+
const server = {
|
332
|
+
publish: mockPublish,
|
333
|
+
} as unknown as Server;
|
334
|
+
|
335
|
+
// Create derived channel class
|
336
|
+
class GameChannel<T extends app.Websocket = GameWebsocket> extends Channel<T> {
|
337
|
+
public override broadcast(message: WebsocketStructuredMessage, options?: BroadcastOptions): void {
|
338
|
+
// Log the message and options for testing
|
339
|
+
console.log("GameChannel");
|
340
|
+
// Call the parent implementation
|
341
|
+
super.broadcast(message, options);
|
342
|
+
}
|
343
|
+
}
|
344
|
+
|
345
|
+
class GameWebsocket extends app.Websocket {
|
346
|
+
constructor() {
|
347
|
+
super({
|
348
|
+
channelClass: GameChannel,
|
349
|
+
});
|
350
|
+
}
|
351
|
+
}
|
352
|
+
|
353
|
+
// Create and configure the Websocket instance
|
354
|
+
const ws = GameWebsocket.GetInstance<GameWebsocket>({
|
355
|
+
channelClass: GameChannel,
|
356
|
+
});
|
357
|
+
|
358
|
+
ws.set(server);
|
359
|
+
|
360
|
+
// Create and test channel
|
361
|
+
const channel = ws.createChannel("game", "Game Channel");
|
362
|
+
expect(channel).toBeInstanceOf(GameChannel);
|
363
|
+
|
364
|
+
// Test broadcast
|
365
|
+
const spy = jest.spyOn(console, "log");
|
366
|
+
const message = { type: "test", content: { message: "test message" } };
|
367
|
+
const extraData = { param1: "param1", extra: { data: "data" } };
|
368
|
+
|
369
|
+
// Call broadcast with the new options-based API
|
370
|
+
channel.broadcast(message, { data: extraData, client: { id: "test", name: "Test Client" } });
|
371
|
+
|
372
|
+
// Verify console.log was called
|
373
|
+
expect(spy).toHaveBeenCalled();
|
374
|
+
|
375
|
+
// Verify server publish was called correctly
|
376
|
+
expect(mockPublish).toHaveBeenCalledWith(channel.id, expect.any(String));
|
377
|
+
|
378
|
+
// Verify the JSON structure
|
379
|
+
const lastCall = mockPublish.mock.calls[0];
|
380
|
+
const parsedJson = JSON.parse(lastCall[1]);
|
381
|
+
|
382
|
+
// Update expectations to match actual structure - we don't care about exact format
|
383
|
+
// as long as it contains the message
|
384
|
+
expect(parsedJson).toHaveProperty("type", message.type);
|
385
|
+
expect(parsedJson).toHaveProperty("channel", channel.id);
|
386
|
+
|
387
|
+
spy.mockRestore();
|
388
|
+
});
|
389
|
+
it("should make sure broadcast structured message is correct", () => {
|
390
|
+
const mockPublish = jest.fn();
|
391
|
+
const server = {
|
392
|
+
publish: mockPublish,
|
393
|
+
} as unknown as Server;
|
394
|
+
const ws = app.Websocket.GetInstance<app.Websocket>();
|
395
|
+
ws.set(server);
|
396
|
+
|
397
|
+
const channel = ws.createChannel("test", "Test Channel");
|
398
|
+
const message = { type: "test", content: { message: "test message" }, };
|
399
|
+
channel.broadcast(message, { debug: true, client: { id: "test", name: "Test Client" } });
|
400
|
+
expect(mockPublish).toHaveBeenCalledWith(channel.id, expect.any(String));
|
401
|
+
});
|
402
|
+
});
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import Singleton from "../singleton";
|
2
|
+
|
3
|
+
// This file is not meant to be run as a test, but to demonstrate
|
4
|
+
// type inference capabilities of the GetInstance method
|
5
|
+
|
6
|
+
// Class with typed constructor parameters
|
7
|
+
class TypedSingleton extends Singleton {
|
8
|
+
public readonly name: string;
|
9
|
+
public readonly id: number;
|
10
|
+
public readonly options: { debug: boolean };
|
11
|
+
|
12
|
+
constructor(name: string, id: number, options: { debug: boolean }) {
|
13
|
+
super();
|
14
|
+
this.name = name;
|
15
|
+
this.id = id;
|
16
|
+
this.options = options;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
// TypeScript should infer the parameter types correctly
|
21
|
+
// This would cause a type error if the parameters don't match
|
22
|
+
const instance = TypedSingleton.GetInstance<TypedSingleton>(
|
23
|
+
"test-name", // string
|
24
|
+
123, // number
|
25
|
+
{ debug: true } // { debug: boolean }
|
26
|
+
);
|
27
|
+
|
28
|
+
// This would cause a type error (wrong type for id)
|
29
|
+
// const badInstance = TypedSingleton.GetInstance(
|
30
|
+
// "test-name",
|
31
|
+
// "not-a-number", // Type error: Argument of type 'string' is not assignable to parameter of type 'number'
|
32
|
+
// { debug: true }
|
33
|
+
// );
|
34
|
+
|
35
|
+
// This would cause a type error (missing parameter)
|
36
|
+
// const missingParamInstance = TypedSingleton.GetInstance(
|
37
|
+
// "test-name",
|
38
|
+
// 123
|
39
|
+
// // Missing options parameter
|
40
|
+
// );
|
41
|
+
|
42
|
+
// This would cause a type error (extra parameter)
|
43
|
+
// const extraParamInstance = TypedSingleton.GetInstance(
|
44
|
+
// "test-name",
|
45
|
+
// 123,
|
46
|
+
// { debug: true },
|
47
|
+
// "extra" // Extra parameter not expected
|
48
|
+
// );
|
49
|
+
|
50
|
+
// The instance should have the correct types for its properties
|
51
|
+
const name: string = instance.name;
|
52
|
+
const id: number = instance.id;
|
53
|
+
const debug: boolean = instance.options.debug;
|
54
|
+
|
55
|
+
// This test file doesn't contain actual tests, just type checking
|
56
|
+
describe("Type Inference", () => {
|
57
|
+
test("This is a placeholder test to avoid test runner errors", () => {
|
58
|
+
expect(true).toBe(true);
|
59
|
+
});
|
60
|
+
});
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { I_ApplicationResponse } from "./types";
|
2
|
+
|
3
|
+
export const RESPONSE_INIT = (status?: number, headers?: HeadersInit): ResponseInit => {
|
4
|
+
return {
|
5
|
+
status: status ?? 200,
|
6
|
+
headers: HEADERS_INIT(headers),
|
7
|
+
};
|
8
|
+
};
|
9
|
+
|
10
|
+
export const HEADERS_INIT = (headers?: HeadersInit): HeadersInit => {
|
11
|
+
return {
|
12
|
+
"Access-Control-Allow-Origin": "*", // Specific origin
|
13
|
+
"Content-Type": "application/json",
|
14
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, DELETE",
|
15
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With, Origin, Accept",
|
16
|
+
"Access-Control-Allow-Credentials": "true",
|
17
|
+
"Access-Control-Max-Age": "86400", // 24 hours
|
18
|
+
...headers,
|
19
|
+
};
|
20
|
+
};
|
21
|
+
|
22
|
+
export const RESPONSE_METHOD_OPTIONS = {
|
23
|
+
name: "Access-Control-Max-Age",
|
24
|
+
value: "86400",
|
25
|
+
};
|
26
|
+
|
27
|
+
function isApplicationResponse<T>(data: T | I_ApplicationResponse<T>): data is I_ApplicationResponse<T> {
|
28
|
+
return typeof data === "object" && data !== null && "status" in data;
|
29
|
+
}
|
30
|
+
|
31
|
+
class Application {
|
32
|
+
public static Response<T>(data: T | I_ApplicationResponse<T>, status = 200, headers?: HeadersInit): Response {
|
33
|
+
const response = isApplicationResponse(data) ? data : { status: true, data };
|
34
|
+
return Response.json(response, RESPONSE_INIT(status, headers));
|
35
|
+
}
|
36
|
+
|
37
|
+
public static Error<T extends BodyInit | unknown | Error>(error: T | I_ApplicationResponse<T>, status = 200, headers?: HeadersInit): Response {
|
38
|
+
const response = isApplicationResponse(error) ? error : { status: false, data: error, error };
|
39
|
+
return Response.json(response, RESPONSE_INIT(status, headers));
|
40
|
+
}
|
41
|
+
|
42
|
+
public static Throw<T extends BodyInit | unknown | Error>(error: T | I_ApplicationResponse<T>, status = 400, headers?: HeadersInit): Response {
|
43
|
+
const response = isApplicationResponse(error) ? error : { status: false, data: error, error };
|
44
|
+
return Response.json(response, RESPONSE_INIT(status, headers));
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
export default Application;
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { AsyncSubject, BehaviorSubject, ReplaySubject, Subject, Subscription } from "rxjs";
|
2
|
+
import { Lib } from "../../utils";
|
3
|
+
import { Singleton } from "../..";
|
4
|
+
|
5
|
+
export enum E_SUBJET_TYPE {
|
6
|
+
SUBJECT = "subject",
|
7
|
+
ASYNC_SUBJECT = "asyncSubject",
|
8
|
+
BEHAVIOR_SUBJECT = "behaviorSubject",
|
9
|
+
REPLAY_SUBJECT = "replaySubject",
|
10
|
+
}
|
11
|
+
|
12
|
+
export type I_RxjsPayload<T> = {
|
13
|
+
cta: string;
|
14
|
+
data: T;
|
15
|
+
source?: string;
|
16
|
+
};
|
17
|
+
|
18
|
+
export type RxjsNamespaces<T extends string> = T;
|
19
|
+
|
20
|
+
export class Rxjs<T extends string> extends Singleton {
|
21
|
+
public namespaces = new Map<string, RxjsInstance>();
|
22
|
+
|
23
|
+
create(namespace: string, subjectType: E_SUBJET_TYPE = E_SUBJET_TYPE.SUBJECT) {
|
24
|
+
console.log("Creating namespace", namespace, subjectType);
|
25
|
+
if (!this.namespaces.has(namespace)) {
|
26
|
+
this.namespaces.set(namespace, new RxjsInstance(subjectType));
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
has(namespace: string) {
|
31
|
+
return this.namespaces.has(namespace);
|
32
|
+
}
|
33
|
+
|
34
|
+
next<U>(namespace: RxjsNamespaces<T>, rxjsPayload: I_RxjsPayload<U>): void {
|
35
|
+
this.namespaces.get(namespace)?.next(rxjsPayload);
|
36
|
+
}
|
37
|
+
|
38
|
+
subscribe(namespace: RxjsNamespaces<T>, listener: (payload: I_RxjsPayload<T>) => any): Subscription {
|
39
|
+
const instance = this.namespaces.get(namespace);
|
40
|
+
if (!instance) {
|
41
|
+
throw new Error(`Namespace ${namespace} not found`);
|
42
|
+
}
|
43
|
+
return instance.subscribe((payload: I_RxjsPayload<T>) => listener(payload));
|
44
|
+
}
|
45
|
+
|
46
|
+
clear(namespace: RxjsNamespaces<T>) {
|
47
|
+
this.namespaces.get(namespace)?.clear();
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
export class RxjsInstance {
|
52
|
+
subscriptions: Subscription;
|
53
|
+
subject: Subject<any>;
|
54
|
+
|
55
|
+
constructor(subjectType: E_SUBJET_TYPE) {
|
56
|
+
this.subscriptions = new Subscription();
|
57
|
+
switch (subjectType) {
|
58
|
+
case E_SUBJET_TYPE.SUBJECT:
|
59
|
+
this.subject = new Subject();
|
60
|
+
break;
|
61
|
+
case E_SUBJET_TYPE.ASYNC_SUBJECT:
|
62
|
+
this.subject = new AsyncSubject();
|
63
|
+
break;
|
64
|
+
case E_SUBJET_TYPE.BEHAVIOR_SUBJECT:
|
65
|
+
this.subject = new BehaviorSubject(null);
|
66
|
+
break;
|
67
|
+
case E_SUBJET_TYPE.REPLAY_SUBJECT:
|
68
|
+
this.subject = new ReplaySubject();
|
69
|
+
break;
|
70
|
+
default:
|
71
|
+
this.subject = new Subject();
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
next(data: any) {
|
76
|
+
this.subject.next(data);
|
77
|
+
}
|
78
|
+
|
79
|
+
asObservable() {
|
80
|
+
return this.subject.asObservable();
|
81
|
+
}
|
82
|
+
|
83
|
+
clear() {
|
84
|
+
if (this.subject instanceof ReplaySubject) {
|
85
|
+
// Get the current subscribers
|
86
|
+
const currentSubscribers = (this.subject as any)._subscribers;
|
87
|
+
|
88
|
+
// Create a new ReplaySubject with same buffer config
|
89
|
+
const bufferSize = (this.subject as any)._bufferSize;
|
90
|
+
const windowTime = (this.subject as any)._windowTime;
|
91
|
+
const newSubject = new ReplaySubject(bufferSize, windowTime);
|
92
|
+
|
93
|
+
// Transfer subscribers to new subject
|
94
|
+
newSubject.subscribe(currentSubscribers);
|
95
|
+
|
96
|
+
// Replace the old subject
|
97
|
+
this.subject = newSubject;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
set add(subscription: Subscription) {
|
102
|
+
this.subscriptions.add(subscription);
|
103
|
+
}
|
104
|
+
|
105
|
+
subscribe<U>(callback: (payload: I_RxjsPayload<U>) => void): Subscription {
|
106
|
+
if (typeof callback !== "function") {
|
107
|
+
throw "Failed to subscribe. Please provide a callback function";
|
108
|
+
}
|
109
|
+
|
110
|
+
const subscriber = this.asObservable().subscribe((payload: I_RxjsPayload<U>) => {
|
111
|
+
if (Lib.IsNumpty(payload) || !payload.hasOwnProperty("cta")) return;
|
112
|
+
callback(payload);
|
113
|
+
});
|
114
|
+
|
115
|
+
this.subscriptions.add(subscriber);
|
116
|
+
return subscriber;
|
117
|
+
}
|
118
|
+
|
119
|
+
unsubscribe() {
|
120
|
+
this.subscriptions.unsubscribe();
|
121
|
+
}
|
122
|
+
}
|