topsyde-utils 1.3.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/index.d.ts +2 -31
  2. package/dist/index.js +1 -27
  3. package/dist/index.js.map +1 -1
  4. package/dist/utils/Lib.d.ts +0 -12
  5. package/dist/utils/Lib.js +0 -65
  6. package/dist/utils/Lib.js.map +1 -1
  7. package/dist/websocket.shared.types.d.ts +25 -0
  8. package/dist/websocket.shared.types.js +4 -0
  9. package/dist/websocket.shared.types.js.map +1 -0
  10. package/package.json +1 -22
  11. package/src/index.ts +2 -51
  12. package/src/utils/Lib.ts +0 -77
  13. package/src/websocket.shared.types.ts +27 -0
  14. package/dist/application.d.ts +0 -18
  15. package/dist/application.js +0 -60
  16. package/dist/application.js.map +0 -1
  17. package/dist/server/base/base.database.d.ts +0 -10
  18. package/dist/server/base/base.database.js +0 -23
  19. package/dist/server/base/base.database.js.map +0 -1
  20. package/dist/server/base/index.d.ts +0 -2
  21. package/dist/server/base/index.js +0 -5
  22. package/dist/server/base/index.js.map +0 -1
  23. package/dist/server/bun/index.d.ts +0 -3
  24. package/dist/server/bun/index.js +0 -6
  25. package/dist/server/bun/index.js.map +0 -1
  26. package/dist/server/bun/router/controller-discovery.d.ts +0 -13
  27. package/dist/server/bun/router/controller-discovery.js +0 -83
  28. package/dist/server/bun/router/controller-discovery.js.map +0 -1
  29. package/dist/server/bun/router/index.d.ts +0 -6
  30. package/dist/server/bun/router/index.js +0 -9
  31. package/dist/server/bun/router/index.js.map +0 -1
  32. package/dist/server/bun/router/router.d.ts +0 -12
  33. package/dist/server/bun/router/router.internal.d.ts +0 -15
  34. package/dist/server/bun/router/router.internal.js +0 -51
  35. package/dist/server/bun/router/router.internal.js.map +0 -1
  36. package/dist/server/bun/router/router.js +0 -38
  37. package/dist/server/bun/router/router.js.map +0 -1
  38. package/dist/server/bun/router/routes.d.ts +0 -5
  39. package/dist/server/bun/router/routes.js +0 -2
  40. package/dist/server/bun/router/routes.js.map +0 -1
  41. package/dist/server/bun/websocket/Channel.d.ts +0 -68
  42. package/dist/server/bun/websocket/Channel.js +0 -263
  43. package/dist/server/bun/websocket/Channel.js.map +0 -1
  44. package/dist/server/bun/websocket/Client.d.ts +0 -87
  45. package/dist/server/bun/websocket/Client.js +0 -193
  46. package/dist/server/bun/websocket/Client.js.map +0 -1
  47. package/dist/server/bun/websocket/Message.d.ts +0 -10
  48. package/dist/server/bun/websocket/Message.js +0 -103
  49. package/dist/server/bun/websocket/Message.js.map +0 -1
  50. package/dist/server/bun/websocket/Websocket.d.ts +0 -171
  51. package/dist/server/bun/websocket/Websocket.js +0 -336
  52. package/dist/server/bun/websocket/Websocket.js.map +0 -1
  53. package/dist/server/bun/websocket/index.d.ts +0 -11
  54. package/dist/server/bun/websocket/index.js +0 -14
  55. package/dist/server/bun/websocket/index.js.map +0 -1
  56. package/dist/server/bun/websocket/websocket.enums.d.ts +0 -27
  57. package/dist/server/bun/websocket/websocket.enums.js +0 -31
  58. package/dist/server/bun/websocket/websocket.enums.js.map +0 -1
  59. package/dist/server/bun/websocket/websocket.guards.d.ts +0 -3
  60. package/dist/server/bun/websocket/websocket.guards.js +0 -17
  61. package/dist/server/bun/websocket/websocket.guards.js.map +0 -1
  62. package/dist/server/bun/websocket/websocket.types.d.ts +0 -235
  63. package/dist/server/bun/websocket/websocket.types.js +0 -2
  64. package/dist/server/bun/websocket/websocket.types.js.map +0 -1
  65. package/dist/server/controller.d.ts +0 -62
  66. package/dist/server/controller.js +0 -55
  67. package/dist/server/controller.js.map +0 -1
  68. package/dist/server/index.d.ts +0 -4
  69. package/dist/server/index.js +0 -7
  70. package/dist/server/index.js.map +0 -1
  71. package/dist/server/service.d.ts +0 -5
  72. package/dist/server/service.js +0 -38
  73. package/dist/server/service.js.map +0 -1
  74. package/src/application.ts +0 -73
  75. package/src/server/base/base.database.ts +0 -31
  76. package/src/server/base/index.ts +0 -5
  77. package/src/server/bun/index.ts +0 -6
  78. package/src/server/bun/router/controller-discovery.ts +0 -94
  79. package/src/server/bun/router/index.ts +0 -9
  80. package/src/server/bun/router/router.internal.ts +0 -64
  81. package/src/server/bun/router/router.ts +0 -51
  82. package/src/server/bun/router/routes.ts +0 -7
  83. package/src/server/bun/websocket/Channel.ts +0 -310
  84. package/src/server/bun/websocket/Client.ts +0 -243
  85. package/src/server/bun/websocket/ISSUES.md +0 -1175
  86. package/src/server/bun/websocket/Message.ts +0 -120
  87. package/src/server/bun/websocket/Websocket.ts +0 -402
  88. package/src/server/bun/websocket/index.ts +0 -14
  89. package/src/server/bun/websocket/websocket.enums.ts +0 -29
  90. package/src/server/bun/websocket/websocket.guards.ts +0 -22
  91. package/src/server/bun/websocket/websocket.types.ts +0 -252
  92. package/src/server/controller.ts +0 -121
  93. package/src/server/index.ts +0 -7
  94. package/src/server/service.ts +0 -36
@@ -1,193 +0,0 @@
1
- import { E_WebsocketMessageType, E_ClientState } from "./websocket.enums.js";
2
- import { Guards, Lib } from "../../../utils/index.js";
3
- import Message from "./Message.js";
4
- /**
5
- * Client - Connected WebSocket client with channel membership
6
- *
7
- * ## Channel Membership
8
- * - Maintains own channel list and handles Bun pub/sub subscriptions
9
- * - `joinChannel()` adds to channel, subscribes, and handles rollback on failure
10
- * - Always use `channel.addMember(client)` in application code, not `client.joinChannel()` directly
11
- *
12
- * @example
13
- * // ✅ Correct
14
- * channel.addMember(client);
15
- *
16
- * // ❌ Incorrect - internal use only
17
- * client.joinChannel(channel);
18
- */
19
- export default class Client {
20
- set ws(value) {
21
- this._ws = value;
22
- }
23
- get ws() {
24
- return this._ws;
25
- }
26
- set id(value) {
27
- this._id = value;
28
- }
29
- get id() {
30
- return this._id;
31
- }
32
- get name() {
33
- return this._name;
34
- }
35
- set name(value) {
36
- this._name = value;
37
- }
38
- set channels(value) {
39
- this._channels = value;
40
- }
41
- get channels() {
42
- return this._channels;
43
- }
44
- get state() {
45
- return this._state;
46
- }
47
- constructor(entity) {
48
- this._id = entity.id;
49
- this._name = entity.name;
50
- this._ws = entity.ws;
51
- this._channels = new Map();
52
- this._state = E_ClientState.CONNECTING;
53
- }
54
- canReceiveMessages() {
55
- return this._state === E_ClientState.CONNECTED || this._state === E_ClientState.DISCONNECTING;
56
- }
57
- markConnected() {
58
- this._state = E_ClientState.CONNECTED;
59
- this._connectedAt = new Date();
60
- }
61
- markDisconnecting() {
62
- this._state = E_ClientState.DISCONNECTING;
63
- }
64
- markDisconnected() {
65
- this._state = E_ClientState.DISCONNECTED;
66
- this._disconnectedAt = new Date();
67
- }
68
- getConnectionInfo() {
69
- return {
70
- id: this.id,
71
- name: this.name,
72
- state: this._state,
73
- connectedAt: this._connectedAt,
74
- disconnectedAt: this._disconnectedAt,
75
- uptime: this._connectedAt ? Date.now() - this._connectedAt.getTime() : 0,
76
- channelCount: this._channels.size,
77
- };
78
- }
79
- /**
80
- * HELPER: Track channel on client side (for channel.addMember coordination)
81
- * Allows channel to update client's internal channel map
82
- * @internal Used by channel.addMember()
83
- */
84
- trackChannel(channel) {
85
- this.channels.set(channel.getId(), channel);
86
- }
87
- /**
88
- * HELPER: Untrack channel on client side (for channel.addMember rollback)
89
- * Allows channel to remove from client's internal channel map during rollback
90
- * @internal Used by channel.addMember() error handling
91
- */
92
- untrackChannel(channel) {
93
- this.channels.delete(channel.getId());
94
- }
95
- /**
96
- * Join a channel (thin wrapper that delegates to channel.addMember)
97
- * channel.addMember() handles all coordination: membership + subscription + tracking + notification
98
- */
99
- joinChannel(channel, send = true) {
100
- const channel_id = channel.getId();
101
- // Check if already joined
102
- if (this.channels.has(channel_id)) {
103
- return { success: false, reason: "already_member" };
104
- }
105
- // Delegate to channel (which now handles full coordination)
106
- const result = channel.addMember(this, { notify: send });
107
- if (!result.success) {
108
- return { success: false, reason: result.reason };
109
- }
110
- return { success: true, reason: "" };
111
- }
112
- /**
113
- * Leave a channel (thin wrapper that delegates to channel.removeMember)
114
- * channel.removeMember() handles all coordination: membership removal + unsubscription + tracking removal + notification
115
- */
116
- leaveChannel(channel, send = true) {
117
- const channel_id = channel.getId();
118
- // Check if we're in the channel
119
- if (!this.channels.has(channel_id)) {
120
- return;
121
- }
122
- // Delegate to channel (which now handles full coordination)
123
- channel.removeMember(this, { notify: send });
124
- }
125
- joinChannels(channels, send = true) {
126
- channels.forEach((channel) => {
127
- this.joinChannel(channel, false);
128
- });
129
- if (send)
130
- this.send({ type: E_WebsocketMessageType.CLIENT_JOIN_CHANNELS, content: { channels }, client: this.whoami() });
131
- }
132
- leaveChannels(channels, send = true) {
133
- if (!channels)
134
- channels = Array.from(this.channels.values());
135
- channels.forEach((channel) => {
136
- this.leaveChannel(channel, false);
137
- });
138
- if (send)
139
- this.send({ type: E_WebsocketMessageType.CLIENT_LEAVE_CHANNELS, content: { channels }, client: this.whoami() });
140
- }
141
- whoami() {
142
- return { id: this.id, name: this.name };
143
- }
144
- send(message, options) {
145
- // Check state before sending
146
- if (!this.canReceiveMessages()) {
147
- Lib.Warn(`Cannot send to client ${this.id} in state ${this._state}`);
148
- return;
149
- }
150
- try {
151
- if (Guards.IsString(message)) {
152
- const msg = {
153
- type: "message",
154
- content: { message },
155
- };
156
- message = Message.Create(msg, options);
157
- }
158
- this.ws.send(JSON.stringify({ client: this.whoami(), ...message }));
159
- }
160
- catch (error) {
161
- Lib.Warn(`Failed to send message to client ${this.id}:`, error);
162
- if (error instanceof Error && error.message.includes("closed")) {
163
- this.markDisconnected();
164
- }
165
- }
166
- }
167
- subscribe(channel) {
168
- this.ws.subscribe(channel);
169
- }
170
- unsubscribe(channel) {
171
- this.ws.unsubscribe(channel);
172
- }
173
- static GetClientType(clients) {
174
- if (!clients)
175
- return Client;
176
- if (clients.size > 0) {
177
- const firstClient = clients.values().next().value;
178
- if (firstClient) {
179
- return firstClient.constructor;
180
- }
181
- }
182
- // Fallback to default Client class
183
- Lib.Warn("Clients map is empty, using default client class");
184
- return Client;
185
- }
186
- static System() {
187
- return {
188
- id: "system",
189
- name: "System",
190
- };
191
- }
192
- }
193
- //# sourceMappingURL=Client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Client.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Client.ts"],"names":[],"mappings":"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","sourcesContent":["import { ServerWebSocket } from \"bun\";\nimport type {\n\tI_WebsocketClient,\n\tWebsocketEntityData,\n\tWebsocketChannel,\n\tWebsocketStructuredMessage,\n\tI_WebsocketEntity,\n\tI_WebsocketChannel,\n\tWebsocketMessageOptions,\n\tWebsocketMessage,\n} from \"./websocket.types\";\nimport { E_WebsocketMessageType, E_ClientState } from \"./websocket.enums\";\nimport { Guards, Lib } from \"../../../utils\";\nimport Message from \"./Message\";\n\n/**\n * Client - Connected WebSocket client with channel membership\n *\n * ## Channel Membership\n * - Maintains own channel list and handles Bun pub/sub subscriptions\n * - `joinChannel()` adds to channel, subscribes, and handles rollback on failure\n * - Always use `channel.addMember(client)` in application code, not `client.joinChannel()` directly\n *\n * @example\n * // ✅ Correct\n * channel.addMember(client);\n *\n * // ❌ Incorrect - internal use only\n * client.joinChannel(channel);\n */\nexport default class Client implements I_WebsocketClient {\n\tprivate _id: string;\n\tprivate _name: string;\n\tprivate _ws: ServerWebSocket<WebsocketEntityData>;\n\tprivate _channels: WebsocketChannel<I_WebsocketChannel>;\n\tprivate _state: E_ClientState;\n\tprivate _connectedAt?: Date;\n\tprivate _disconnectedAt?: Date;\n\n\tprivate set ws(value: ServerWebSocket<WebsocketEntityData>) {\n\t\tthis._ws = value;\n\t}\n\n\tpublic get ws(): ServerWebSocket<WebsocketEntityData> {\n\t\treturn this._ws;\n\t}\n\n\tprivate set id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\tpublic get id(): string {\n\t\treturn this._id;\n\t}\n\n\tpublic get name(): string {\n\t\treturn this._name;\n\t}\n\n\tprivate set name(value: string) {\n\t\tthis._name = value;\n\t}\n\n\tprivate set channels(value: WebsocketChannel<I_WebsocketChannel>) {\n\t\tthis._channels = value;\n\t}\n\n\tpublic get channels(): WebsocketChannel<I_WebsocketChannel> {\n\t\treturn this._channels;\n\t}\n\n\tpublic get state(): E_ClientState {\n\t\treturn this._state;\n\t}\n\n\tconstructor(entity: I_WebsocketEntity) {\n\t\tthis._id = entity.id;\n\t\tthis._name = entity.name;\n\t\tthis._ws = entity.ws;\n\t\tthis._channels = new Map();\n\t\tthis._state = E_ClientState.CONNECTING;\n\t}\n\n\tpublic canReceiveMessages(): boolean {\n\t\treturn this._state === E_ClientState.CONNECTED || this._state === E_ClientState.DISCONNECTING;\n\t}\n\n\tpublic markConnected(): void {\n\t\tthis._state = E_ClientState.CONNECTED;\n\t\tthis._connectedAt = new Date();\n\t}\n\n\tpublic markDisconnecting(): void {\n\t\tthis._state = E_ClientState.DISCONNECTING;\n\t}\n\n\tpublic markDisconnected(): void {\n\t\tthis._state = E_ClientState.DISCONNECTED;\n\t\tthis._disconnectedAt = new Date();\n\t}\n\n\tpublic getConnectionInfo() {\n\t\treturn {\n\t\t\tid: this.id,\n\t\t\tname: this.name,\n\t\t\tstate: this._state,\n\t\t\tconnectedAt: this._connectedAt,\n\t\t\tdisconnectedAt: this._disconnectedAt,\n\t\t\tuptime: this._connectedAt ? Date.now() - this._connectedAt.getTime() : 0,\n\t\t\tchannelCount: this._channels.size,\n\t\t};\n\t}\n\n\t/**\n\t * HELPER: Track channel on client side (for channel.addMember coordination)\n\t * Allows channel to update client's internal channel map\n\t * @internal Used by channel.addMember()\n\t */\n\tpublic trackChannel(channel: I_WebsocketChannel): void {\n\t\tthis.channels.set(channel.getId(), channel);\n\t}\n\n\t/**\n\t * HELPER: Untrack channel on client side (for channel.addMember rollback)\n\t * Allows channel to remove from client's internal channel map during rollback\n\t * @internal Used by channel.addMember() error handling\n\t */\n\tpublic untrackChannel(channel: I_WebsocketChannel): void {\n\t\tthis.channels.delete(channel.getId());\n\t}\n\n\t/**\n\t * Join a channel (thin wrapper that delegates to channel.addMember)\n\t * channel.addMember() handles all coordination: membership + subscription + tracking + notification\n\t */\n\tpublic joinChannel(channel: I_WebsocketChannel, send: boolean = true): { success: boolean; reason: string } {\n\t\tconst channel_id = channel.getId();\n\n\t\t// Check if already joined\n\t\tif (this.channels.has(channel_id)) {\n\t\t\treturn { success: false, reason: \"already_member\" };\n\t\t}\n\n\t\t// Delegate to channel (which now handles full coordination)\n\t\tconst result = channel.addMember(this, { notify: send });\n\n\t\tif (!result.success) {\n\t\t\treturn { success: false, reason: result.reason };\n\t\t}\n\n\t\treturn { success: true, reason: \"\" };\n\t}\n\n\t/**\n\t * Leave a channel (thin wrapper that delegates to channel.removeMember)\n\t * channel.removeMember() handles all coordination: membership removal + unsubscription + tracking removal + notification\n\t */\n\tpublic leaveChannel(channel: I_WebsocketChannel, send: boolean = true) {\n\t\tconst channel_id = channel.getId();\n\n\t\t// Check if we're in the channel\n\t\tif (!this.channels.has(channel_id)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Delegate to channel (which now handles full coordination)\n\t\tchannel.removeMember(this, { notify: send });\n\t}\n\n\tpublic joinChannels(channels: I_WebsocketChannel[], send: boolean = true) {\n\t\tchannels.forEach((channel) => {\n\t\t\tthis.joinChannel(channel, false);\n\t\t});\n\t\tif (send) this.send({ type: E_WebsocketMessageType.CLIENT_JOIN_CHANNELS, content: { channels }, client: this.whoami() });\n\t}\n\n\tpublic leaveChannels(channels?: I_WebsocketChannel[], send: boolean = true) {\n\t\tif (!channels) channels = Array.from(this.channels.values());\n\t\tchannels.forEach((channel) => {\n\t\t\tthis.leaveChannel(channel, false);\n\t\t});\n\t\tif (send) this.send({ type: E_WebsocketMessageType.CLIENT_LEAVE_CHANNELS, content: { channels }, client: this.whoami() });\n\t}\n\n\tpublic whoami(): { id: string; name: string } {\n\t\treturn { id: this.id, name: this.name };\n\t}\n\n\tpublic send(message: string, options?: WebsocketMessageOptions): void;\n\tpublic send(message: WebsocketStructuredMessage): void;\n\tpublic send(message: WebsocketStructuredMessage | string, options?: WebsocketMessageOptions): void {\n\t\t// Check state before sending\n\t\tif (!this.canReceiveMessages()) {\n\t\t\tLib.Warn(`Cannot send to client ${this.id} in state ${this._state}`);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tif (Guards.IsString(message)) {\n\t\t\t\tconst msg: WebsocketMessage = {\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t\tcontent: { message },\n\t\t\t\t};\n\t\t\t\tmessage = Message.Create(msg, options);\n\t\t\t}\n\t\t\tthis.ws.send(JSON.stringify({ client: this.whoami(), ...message }));\n\t\t} catch (error) {\n\t\t\tLib.Warn(`Failed to send message to client ${this.id}:`, error);\n\t\t\tif (error instanceof Error && error.message.includes(\"closed\")) {\n\t\t\t\tthis.markDisconnected();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic subscribe(channel: string): void {\n\t\tthis.ws.subscribe(channel);\n\t}\n\n\tpublic unsubscribe(channel: string): void {\n\t\tthis.ws.unsubscribe(channel);\n\t}\n\n\tpublic static GetClientType(clients: Map<string, I_WebsocketClient> | undefined): typeof Client {\n\t\tif (!clients) return Client;\n\t\tif (clients.size > 0) {\n\t\t\tconst firstClient = clients.values().next().value;\n\t\t\tif (firstClient) {\n\t\t\t\treturn firstClient.constructor as typeof Client;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to default Client class\n\t\tLib.Warn(\"Clients map is empty, using default client class\");\n\t\treturn Client;\n\t}\n\n\tpublic static System() {\n\t\treturn <WebsocketEntityData>{\n\t\t\tid: \"system\",\n\t\t\tname: \"System\",\n\t\t};\n\t}\n}\n"]}
@@ -1,10 +0,0 @@
1
- import { WebsocketStructuredMessage, WebsocketMessage, WebsocketMessageOptions, I_WebsocketClient, WebsocketEntityData } from "./websocket.types";
2
- export default class Message {
3
- private static readonly MESSAGE_TEMPLATE;
4
- private constructor();
5
- static Create(message: WebsocketMessage, options?: WebsocketMessageOptions): WebsocketStructuredMessage;
6
- static CreateWhisper(message: Omit<WebsocketMessage, "type">, options?: WebsocketMessageOptions): WebsocketStructuredMessage;
7
- static Serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T): string | T;
8
- static Send(target: I_WebsocketClient, message: WebsocketMessage, options?: WebsocketMessageOptions): void;
9
- static Alert(target: I_WebsocketClient, reason: string, client?: WebsocketEntityData): void;
10
- }
@@ -1,103 +0,0 @@
1
- import Guards from "../../../utils/Guards.js";
2
- class Message {
3
- // Private constructor to prevent instantiation
4
- constructor() { }
5
- static Create(message, options) {
6
- // Clone the template
7
- const output = Object.assign({}, Message.MESSAGE_TEMPLATE);
8
- // Set the dynamic properties
9
- output.type = message.type;
10
- output.channel = message.channel || options?.channel || "N/A";
11
- // Process message content based on type
12
- if (typeof message.content === "string") {
13
- output.content = { message: message.content };
14
- }
15
- else if (typeof message.content === "object" && message.content !== null) {
16
- output.content = { ...message.content };
17
- }
18
- else {
19
- output.content = {};
20
- }
21
- // Process options if provided
22
- if (options) {
23
- // Add data if provided
24
- if (options.data !== undefined) {
25
- if (typeof options.data === "object" && options.data !== null && !Array.isArray(options.data)) {
26
- // Merge object data with content
27
- Object.assign(output.content, options.data);
28
- }
29
- else {
30
- // Set as data property for other types
31
- output.content.data = options.data;
32
- }
33
- }
34
- // Add client information if provided
35
- if (options.client && Guards.IsObject(options.client) && Guards.IsString(options.client.id, true)) {
36
- output.client = {
37
- id: options.client.id,
38
- name: options.client.name || "Unknown",
39
- };
40
- }
41
- // Include channel metadata if requested
42
- // Include channel metadata if provided as an object
43
- if (options.metadata && Guards.IsObject(options.metadata) && !Guards.IsArray(options.metadata)) {
44
- output.metadata = options.metadata;
45
- }
46
- // Add timestamp if requested (default: true)
47
- if (options.includeTimestamp !== false) {
48
- output.timestamp = new Date().toISOString();
49
- }
50
- else {
51
- // Remove timestamp if explicitly disabled
52
- delete output.timestamp;
53
- }
54
- // Add priority if specified
55
- if (options.priority !== undefined) {
56
- output.priority = options.priority;
57
- }
58
- // Add expiration if specified
59
- if (options.expiresAt !== undefined) {
60
- output.expiresAt = options.expiresAt;
61
- }
62
- // Add any custom fields to the root of the message
63
- if (options.customFields) {
64
- Object.assign(output, options.customFields);
65
- }
66
- // Apply custom transformation if provided
67
- if (options.transform) {
68
- return options.transform(output);
69
- }
70
- }
71
- else {
72
- output.timestamp = new Date().toISOString();
73
- }
74
- return output;
75
- }
76
- static CreateWhisper(message, options) {
77
- return Message.Create({ ...message, content: message.content, channel: message.channel, type: "whisper" }, options);
78
- }
79
- static Serialize(message, transform) {
80
- return transform ? transform(message) : JSON.stringify(message);
81
- }
82
- static Send(target, message, options) {
83
- target.send(Message.Create(message, options));
84
- }
85
- static Alert(target, reason, client) {
86
- target.send(Message.Create({
87
- content: {
88
- message: reason,
89
- },
90
- channel: "alert",
91
- type: "message",
92
- }, { client }));
93
- }
94
- }
95
- // Shared template for all messages
96
- Message.MESSAGE_TEMPLATE = {
97
- type: "",
98
- content: {},
99
- channel: "",
100
- timestamp: "",
101
- };
102
- export default Message;
103
- //# sourceMappingURL=Message.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Message.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Message.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAG3C,MAAqB,OAAO;IAS3B,+CAA+C;IAC/C,gBAAuB,CAAC;IAEjB,MAAM,CAAC,MAAM,CAAC,OAAyB,EAAE,OAAiC;QAChF,qBAAqB;QACrB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAE3D,6BAA6B;QAC7B,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC3B,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC;QAE9D,wCAAwC;QACxC,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QAC/C,CAAC;aAAM,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC5E,MAAM,CAAC,OAAO,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QACzC,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;QACrB,CAAC;QAED,8BAA8B;QAC9B,IAAI,OAAO,EAAE,CAAC;YACb,uBAAuB;YACvB,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAChC,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/F,iCAAiC;oBACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC7C,CAAC;qBAAM,CAAC;oBACP,uCAAuC;oBACvC,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;gBACpC,CAAC;YACF,CAAC;YAED,qCAAqC;YACrC,IAAI,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;gBACnG,MAAM,CAAC,MAAM,GAAG;oBACf,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;oBACrB,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,SAAS;iBACtC,CAAC;YACH,CAAC;YAED,wCAAwC;YACxC,oDAAoD;YACpD,IAAI,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAChG,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YACpC,CAAC;YAED,6CAA6C;YAC7C,IAAI,OAAO,CAAC,gBAAgB,KAAK,KAAK,EAAE,CAAC;gBACxC,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC7C,CAAC;iBAAM,CAAC;gBACP,0CAA0C;gBAC1C,OAAO,MAAM,CAAC,SAAS,CAAC;YACzB,CAAC;YAED,4BAA4B;YAC5B,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACpC,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YACpC,CAAC;YAED,8BAA8B;YAC9B,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBACrC,MAAM,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;YACtC,CAAC;YAED,mDAAmD;YACnD,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;gBAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;YAC7C,CAAC;YAED,0CAA0C;YAC1C,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACvB,OAAO,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC;IAEM,MAAM,CAAC,aAAa,CAAC,OAAuC,EAAE,OAAiC;QACrG,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;IACrH,CAAC;IAEM,MAAM,CAAC,SAAS,CAAa,OAAmC,EAAE,SAAsD;QAC9H,OAAO,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjE,CAAC;IAEM,MAAM,CAAC,IAAI,CAAC,MAAyB,EAAE,OAAyB,EAAE,OAAiC;QACzG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,MAAyB,EAAE,MAAc,EAAE,MAA4B;QAC1F,MAAM,CAAC,IAAI,CACV,OAAO,CAAC,MAAM,CACb;YACC,OAAO,EAAE;gBACR,OAAO,EAAE,MAAM;aACf;YACD,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,SAAS;SACf,EACD,EAAE,MAAM,EAAE,CACV,CACD,CAAC;IACH,CAAC;;AAlHD,mCAAmC;AACX,wBAAgB,GAAoC;IAC3E,IAAI,EAAE,EAAE;IACR,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,EAAE;IACX,SAAS,EAAE,EAAE;CACb,CAAC;eAPkB,OAAO","sourcesContent":["import Guards from \"../../../utils/Guards\";\nimport { WebsocketStructuredMessage, WebsocketMessage, WebsocketMessageOptions, I_WebsocketClient, WebsocketEntityData } from \"./websocket.types\";\n\nexport default class Message {\n\t// Shared template for all messages\n\tprivate static readonly MESSAGE_TEMPLATE: WebsocketStructuredMessage<any> = {\n\t\ttype: \"\",\n\t\tcontent: {},\n\t\tchannel: \"\",\n\t\ttimestamp: \"\",\n\t};\n\n\t// Private constructor to prevent instantiation\n\tprivate constructor() {}\n\n\tpublic static Create(message: WebsocketMessage, options?: WebsocketMessageOptions): WebsocketStructuredMessage {\n\t\t// Clone the template\n\t\tconst output = Object.assign({}, Message.MESSAGE_TEMPLATE);\n\n\t\t// Set the dynamic properties\n\t\toutput.type = message.type;\n\t\toutput.channel = message.channel || options?.channel || \"N/A\";\n\n\t\t// Process message content based on type\n\t\tif (typeof message.content === \"string\") {\n\t\t\toutput.content = { message: message.content };\n\t\t} else if (typeof message.content === \"object\" && message.content !== null) {\n\t\t\toutput.content = { ...message.content };\n\t\t} else {\n\t\t\toutput.content = {};\n\t\t}\n\n\t\t// Process options if provided\n\t\tif (options) {\n\t\t\t// Add data if provided\n\t\t\tif (options.data !== undefined) {\n\t\t\t\tif (typeof options.data === \"object\" && options.data !== null && !Array.isArray(options.data)) {\n\t\t\t\t\t// Merge object data with content\n\t\t\t\t\tObject.assign(output.content, options.data);\n\t\t\t\t} else {\n\t\t\t\t\t// Set as data property for other types\n\t\t\t\t\toutput.content.data = options.data;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add client information if provided\n\t\t\tif (options.client && Guards.IsObject(options.client) && Guards.IsString(options.client.id, true)) {\n\t\t\t\toutput.client = {\n\t\t\t\t\tid: options.client.id,\n\t\t\t\t\tname: options.client.name || \"Unknown\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Include channel metadata if requested\n\t\t\t// Include channel metadata if provided as an object\n\t\t\tif (options.metadata && Guards.IsObject(options.metadata) && !Guards.IsArray(options.metadata)) {\n\t\t\t\toutput.metadata = options.metadata;\n\t\t\t}\n\n\t\t\t// Add timestamp if requested (default: true)\n\t\t\tif (options.includeTimestamp !== false) {\n\t\t\t\toutput.timestamp = new Date().toISOString();\n\t\t\t} else {\n\t\t\t\t// Remove timestamp if explicitly disabled\n\t\t\t\tdelete output.timestamp;\n\t\t\t}\n\n\t\t\t// Add priority if specified\n\t\t\tif (options.priority !== undefined) {\n\t\t\t\toutput.priority = options.priority;\n\t\t\t}\n\n\t\t\t// Add expiration if specified\n\t\t\tif (options.expiresAt !== undefined) {\n\t\t\t\toutput.expiresAt = options.expiresAt;\n\t\t\t}\n\n\t\t\t// Add any custom fields to the root of the message\n\t\t\tif (options.customFields) {\n\t\t\t\tObject.assign(output, options.customFields);\n\t\t\t}\n\n\t\t\t// Apply custom transformation if provided\n\t\t\tif (options.transform) {\n\t\t\t\treturn options.transform(output);\n\t\t\t}\n\t\t} else {\n\t\t\toutput.timestamp = new Date().toISOString();\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tpublic static CreateWhisper(message: Omit<WebsocketMessage, \"type\">, options?: WebsocketMessageOptions): WebsocketStructuredMessage {\n\t\treturn Message.Create({ ...message, content: message.content, channel: message.channel, type: \"whisper\" }, options);\n\t}\n\n\tpublic static Serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T): string | T {\n\t\treturn transform ? transform(message) : JSON.stringify(message);\n\t}\n\n\tpublic static Send(target: I_WebsocketClient, message: WebsocketMessage, options?: WebsocketMessageOptions): void {\n\t\ttarget.send(Message.Create(message, options));\n\t}\n\n\tpublic static Alert(target: I_WebsocketClient, reason: string, client?: WebsocketEntityData): void {\n\t\ttarget.send(\n\t\t\tMessage.Create(\n\t\t\t\t{\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tmessage: reason,\n\t\t\t\t\t},\n\t\t\t\t\tchannel: \"alert\",\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t},\n\t\t\t\t{ client },\n\t\t\t),\n\t\t);\n\t}\n}\n"]}
@@ -1,171 +0,0 @@
1
- import { Server, ServerWebSocket, WebSocketHandler } from "bun";
2
- import Singleton from "../../../singleton";
3
- import Channel from "./Channel";
4
- import Client from "./Client";
5
- import type { I_WebsocketClient, I_WebsocketEntity, I_WebsocketInterface, WebsocketChannel, WebsocketEntityData, BunWebsocketMessage, WebsocketStructuredMessage } from "./websocket.types";
6
- export type WebsocketConstructorOptions = {
7
- debug?: boolean;
8
- global_channel_limit?: number;
9
- };
10
- export interface I_WebsocketConstructor {
11
- ws_interface?: I_WebsocketInterface;
12
- channels?: WebsocketChannel;
13
- clientClass?: typeof Client;
14
- channelClass?: typeof Channel;
15
- options?: WebsocketConstructorOptions;
16
- }
17
- /**
18
- * Websocket - Singleton managing clients, channels, and message routing
19
- *
20
- * ## API Design: Static vs Instance
21
- * - **Static methods**: Use in application code (e.g., `Websocket.Broadcast()`, `Websocket.GetClient()`)
22
- * - **Instance methods**: Use when extending the class (e.g., `protected createClient()`)
23
- *
24
- * Static methods are facades that call the singleton instance internally.
25
- *
26
- * @example
27
- * // Application code - use static methods
28
- * Websocket.Broadcast("lobby", { type: "chat", content: { message: "Hi!" } });
29
- *
30
- * // Extension - override instance methods
31
- * MyWebsocket extends Websocket:
32
- * protected createClient(entity) {
33
- * return new MyCustomClient(entity);
34
- * }
35
- */
36
- export default class Websocket extends Singleton {
37
- protected _channels: WebsocketChannel;
38
- protected _clients: Map<string, I_WebsocketClient>;
39
- protected _server: Server;
40
- protected _channelClass: typeof Channel;
41
- protected _clientClass: typeof Client;
42
- protected _ws_interface?: I_WebsocketInterface;
43
- protected _options: WebsocketConstructorOptions;
44
- protected _ws_interface_handlers: Partial<WebSocketHandler<WebsocketEntityData>>;
45
- protected _lastId: number;
46
- protected constructor(options?: I_WebsocketConstructor);
47
- protected set server(value: Server);
48
- get server(): Server;
49
- set(server: Server): void;
50
- /**
51
- * Create a new channel
52
- * @param id - The id of the channel
53
- * @param name - The name of the channel
54
- * @param limit - The limit of the channel
55
- * @returns The created channel
56
- */
57
- createChannel(id: string, name: string, limit?: number): Channel;
58
- /**
59
- * Remove a channel
60
- * @param id - The id of the channel
61
- */
62
- removeChannel(id: string): void;
63
- /**
64
- * Create a new channel
65
- * @param id - The id of the channel
66
- * @param name - The name of the channel
67
- * @param limit - The limit of the channel
68
- * @returns The created channel
69
- */
70
- static CreateChannel(id: string, name: string, limit?: number): Channel<Websocket>;
71
- handlers(): WebSocketHandler<WebsocketEntityData>;
72
- private clientMessageReceived;
73
- private clientConnected;
74
- private clientDisconnected;
75
- private handleHeartbeat;
76
- protected createClient(entity: I_WebsocketEntity): I_WebsocketClient;
77
- /**
78
- * Handle the heartbeat
79
- * @param ws - The websocket
80
- * @param message - The message
81
- * @returns True if the heartbeat was handled, false otherwise
82
- */
83
- static Heartbeat(ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage): boolean;
84
- /**
85
- * Get the server
86
- * @returns The server
87
- */
88
- static Server(): Server;
89
- /**
90
- * Broadcast a message to a channel
91
- * @param channel - The channel
92
- * @param message - The message
93
- * @param args - The arguments
94
- */
95
- static Broadcast(channel: string, message: WebsocketStructuredMessage, ...args: any[]): void;
96
- /**
97
- * Broadcast a message to all channels
98
- * @param message - The message
99
- * @param args - The arguments
100
- */
101
- static BroadCastAll(message: WebsocketStructuredMessage, ...args: any[]): void;
102
- /**
103
- * Join a channel
104
- * @param channel - The channel
105
- * @param entity - The entity
106
- */
107
- static Join(channel: string, entity: I_WebsocketEntity): void;
108
- /**
109
- * Leave a channel
110
- * @param channel - The channel
111
- * @param entity - The entity
112
- */
113
- static Leave(channel: string, entity: I_WebsocketEntity): void;
114
- /**
115
- * Get a client
116
- * @param id - The id of the client
117
- * @param throw_if_nil - Whether to throw an error if the client is not found
118
- * @returns The client
119
- */
120
- static GetClient(id: string, throw_if_nil?: true): I_WebsocketClient;
121
- static GetClient(id: string, throw_if_nil?: false): I_WebsocketClient | undefined;
122
- /**
123
- * Get a channel
124
- * @param id - The id of the channel
125
- * @returns The channel
126
- */
127
- static GetChannel(id: string): Channel<Websocket> | undefined;
128
- /**
129
- * Get all channels
130
- * @returns The channels
131
- */
132
- static GetChannels(): Channel<Websocket>[];
133
- /**
134
- * Get all clients
135
- * @returns The clients
136
- */
137
- static GetClients(): I_WebsocketClient[];
138
- static GetLastId(): number;
139
- /**
140
- * Get the number of clients
141
- * @returns The number of clients
142
- */
143
- static GetClientCount(): number;
144
- /**
145
- * Get the number of channels
146
- * @returns The number of channels
147
- */
148
- static GetChannelCount(): number;
149
- /**
150
- * Create a client
151
- * @param entity - The entity
152
- * @returns The created client
153
- */
154
- static CreateClient(entity: I_WebsocketEntity): I_WebsocketClient;
155
- /**
156
- * Get all connected clients (excluding connecting/disconnecting)
157
- * @returns Array of connected clients
158
- */
159
- static GetConnectedClients(): I_WebsocketClient[];
160
- /**
161
- * Get client statistics by state
162
- * @returns Object with counts by state
163
- */
164
- static GetClientStats(): {
165
- total: number;
166
- connecting: number;
167
- connected: number;
168
- disconnecting: number;
169
- disconnected: number;
170
- };
171
- }