@zap-socket/server 0.0.2 → 0.0.6
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/events.d.ts +16 -11
- package/dist/events.js +14 -0
- package/dist/server.d.ts +19 -9
- package/dist/server.js +164 -19
- package/package.json +2 -1
package/dist/events.d.ts
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
import { z } from "zod";
|
2
|
-
|
3
|
-
export type ZapEvent<T extends EventInput, R = any> = T extends z.ZodTypeAny ? {
|
4
|
-
input: T;
|
5
|
-
process: (input: z.infer<T>) => R;
|
6
|
-
} : {
|
7
|
-
input: z.ZodVoid;
|
8
|
-
process: () => R;
|
9
|
-
};
|
10
|
-
export type EventMap = Record<string, ZapEvent<any, any>>;
|
2
|
+
import type { EventInput, Context, ZapEvent, ZapStream, ZapServerEvent, MiddlewareType } from "@zap-socket/types";
|
11
3
|
export declare const zapEvent: <T extends EventInput, R>(eventObj: T extends z.ZodTypeAny ? {
|
12
4
|
input: T;
|
13
|
-
|
5
|
+
middleware?: MiddlewareType[];
|
6
|
+
process: (input: z.infer<T>, ctx: Context) => R;
|
14
7
|
} : {
|
15
|
-
|
8
|
+
middleware?: MiddlewareType[];
|
9
|
+
process: (ctx: Context) => R;
|
16
10
|
}) => ZapEvent<T, R>;
|
11
|
+
export declare const zapStream: <T extends EventInput, R>(eventObj: T extends z.ZodTypeAny ? {
|
12
|
+
input: T;
|
13
|
+
middleware?: MiddlewareType[];
|
14
|
+
process: (input: z.infer<T>, ctx: Context) => AsyncGenerator<R, void, unknown>;
|
15
|
+
} : {
|
16
|
+
middleware?: MiddlewareType[];
|
17
|
+
process: (ctx: Context) => AsyncGenerator<R, void, unknown>;
|
18
|
+
}) => ZapStream<T, R>;
|
19
|
+
export declare const zapServerEvent: <T extends z.ZodTypeAny>(eventObj: {
|
20
|
+
data: T;
|
21
|
+
}) => ZapServerEvent<T>;
|
package/dist/events.js
CHANGED
@@ -8,3 +8,17 @@ export const zapEvent = (eventObj) => {
|
|
8
8
|
process: eventObj.process
|
9
9
|
};
|
10
10
|
};
|
11
|
+
export const zapStream = (eventObj) => {
|
12
|
+
if ("input" in eventObj) {
|
13
|
+
return eventObj;
|
14
|
+
}
|
15
|
+
return {
|
16
|
+
input: z.void(),
|
17
|
+
process: eventObj.process
|
18
|
+
};
|
19
|
+
};
|
20
|
+
export const zapServerEvent = (eventObj) => {
|
21
|
+
return {
|
22
|
+
data: eventObj.data
|
23
|
+
};
|
24
|
+
};
|
package/dist/server.d.ts
CHANGED
@@ -1,17 +1,27 @@
|
|
1
|
-
import
|
1
|
+
import { WebSocketServer, WebSocket } from "ws";
|
2
|
+
import type { EventMap, ZapServerEvent } from "@zap-socket/types";
|
2
3
|
interface ZapServerConstructorT {
|
3
4
|
port: number;
|
5
|
+
events?: EventMap;
|
4
6
|
}
|
5
|
-
export declare class ZapServer {
|
6
|
-
|
7
|
+
export declare class ZapServer<T extends EventMap> {
|
8
|
+
wss: WebSocketServer;
|
7
9
|
private wsToId;
|
8
10
|
private idToWs;
|
9
|
-
private
|
10
|
-
constructor({ port }: ZapServerConstructorT);
|
11
|
+
private _events;
|
12
|
+
constructor({ port, events }: ZapServerConstructorT, callback?: () => void);
|
11
13
|
private removeClient;
|
12
|
-
|
14
|
+
sendMessageRaw(clientId: string, data: any): void;
|
15
|
+
sendMessage(event: keyof T, clientId: string, data: any): void;
|
16
|
+
broadcastRaw(data: any): void;
|
17
|
+
broadcast(event: keyof T, data: any): void;
|
18
|
+
selectiveBroascast(event: string, data: any, connections: string[]): void;
|
19
|
+
get event(): { [K in keyof T as T[K] extends ZapServerEvent<any> ? K : never]: {
|
20
|
+
send: (clientId: string, data?: (T[K] extends ZapServerEvent<any> ? T[K]["data"] : never)) => void;
|
21
|
+
broadcast: (data?: (T[K] extends ZapServerEvent<any> ? T[K]["data"] : never)) => void;
|
22
|
+
}; };
|
23
|
+
get clients(): Set<string>;
|
24
|
+
get socketMap(): Map<string, WebSocket>;
|
13
25
|
}
|
14
|
-
export declare const createZapServer: ({ port }:
|
15
|
-
port: number;
|
16
|
-
}) => ZapServer;
|
26
|
+
export declare const createZapServer: <T extends EventMap>({ port, events }: ZapServerConstructorT, callback?: () => void) => ZapServer<T>;
|
17
27
|
export {};
|
package/dist/server.js
CHANGED
@@ -1,18 +1,24 @@
|
|
1
1
|
import { WebSocketServer } from "ws";
|
2
2
|
import { serialize, deserialize, generateId } from "./utils";
|
3
|
+
const isClientEvent = (event) => {
|
4
|
+
return "process" in event; // both zapEvent and zapStream have process in them.
|
5
|
+
};
|
3
6
|
export class ZapServer {
|
4
7
|
wss;
|
5
8
|
wsToId;
|
6
9
|
idToWs;
|
7
|
-
|
8
|
-
constructor({ port }) {
|
10
|
+
_events = {};
|
11
|
+
constructor({ port, events = {} }, callback) {
|
9
12
|
this.wss = new WebSocketServer({ port });
|
10
13
|
this.wsToId = new Map();
|
11
14
|
this.idToWs = new Map();
|
12
|
-
this.
|
15
|
+
this._events = events;
|
16
|
+
this.wss.on("listening", () => {
|
17
|
+
if (callback)
|
18
|
+
callback();
|
19
|
+
});
|
20
|
+
this.wss.on("connection", (ws, req) => {
|
13
21
|
ws.on("message", (message) => {
|
14
|
-
const parsedMessage = deserialize(message.toString());
|
15
|
-
console.log("got message: ", parsedMessage);
|
16
22
|
if (!this.wsToId.get(ws)) {
|
17
23
|
const id = generateId();
|
18
24
|
this.wsToId.set(ws, id);
|
@@ -21,18 +27,63 @@ export class ZapServer {
|
|
21
27
|
console.log(`client ${id} connected.`);
|
22
28
|
return;
|
23
29
|
}
|
24
|
-
|
30
|
+
const clientId = this.wsToId.get(ws);
|
31
|
+
for (const [event, eventObj] of Object.entries(this._events)) {
|
32
|
+
if (!isClientEvent(eventObj))
|
33
|
+
continue;
|
34
|
+
const { process, middleware } = eventObj;
|
25
35
|
const parsedMessage = deserialize(message.toString());
|
26
36
|
if (parsedMessage &&
|
27
|
-
parsedMessage["event"] === event) {
|
28
|
-
const { data, requestId } = parsedMessage;
|
29
|
-
|
30
|
-
const
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
37
|
+
(parsedMessage["event"] === event || parsedMessage["stream"] === event)) {
|
38
|
+
const { data, requestId, streamId } = parsedMessage;
|
39
|
+
// Do middleware checks
|
40
|
+
const ctx = {};
|
41
|
+
if (middleware) {
|
42
|
+
middleware.forEach((m) => {
|
43
|
+
const metadata = {
|
44
|
+
id: clientId,
|
45
|
+
ip: req.socket.remoteAddress,
|
46
|
+
timestamp: Date.now(),
|
47
|
+
size: message.toString().length
|
48
|
+
};
|
49
|
+
const msg = {
|
50
|
+
event,
|
51
|
+
data: parsedMessage,
|
52
|
+
metadata
|
53
|
+
};
|
54
|
+
if (!m(ctx, msg)) {
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
});
|
58
|
+
}
|
59
|
+
// By this point all the middlewares allow to pass through
|
60
|
+
if (requestId) {
|
61
|
+
const result = process(data, { server: this, id: this.wsToId.get(ws), buffer: ctx });
|
62
|
+
const serialized = serialize(requestId ? { requestId, event, data: result } : { event, data: result });
|
63
|
+
// TODO: throw some nice error: only return stuff that is serializable
|
64
|
+
// i.e. primary data types and objects
|
65
|
+
if (!serialized)
|
66
|
+
return;
|
67
|
+
ws.send(serialized);
|
68
|
+
}
|
69
|
+
else if (streamId) {
|
70
|
+
const consumeStream = async () => {
|
71
|
+
const result = process(data, { server: this, id: this.wsToId.get(ws), buffer: ctx });
|
72
|
+
for await (const x of result) {
|
73
|
+
const packet = {
|
74
|
+
streamId,
|
75
|
+
fragment: x
|
76
|
+
};
|
77
|
+
this.sendMessageRaw(clientId, packet);
|
78
|
+
}
|
79
|
+
this.sendMessageRaw(clientId, {
|
80
|
+
streamId,
|
81
|
+
done: true
|
82
|
+
});
|
83
|
+
};
|
84
|
+
consumeStream();
|
85
|
+
}
|
86
|
+
return; // finally return to avoid looping through rest of events unneccessarily
|
36
87
|
}
|
37
88
|
}
|
38
89
|
});
|
@@ -51,10 +102,104 @@ export class ZapServer {
|
|
51
102
|
this.idToWs.delete(clientId);
|
52
103
|
}
|
53
104
|
}
|
54
|
-
|
55
|
-
|
105
|
+
sendMessageRaw(clientId, data) {
|
106
|
+
const ws = this.idToWs.get(clientId);
|
107
|
+
// TODO: throw a nice error
|
108
|
+
if (!ws)
|
109
|
+
return;
|
110
|
+
const serializedData = serialize(data);
|
111
|
+
// TODO: throw a nice error
|
112
|
+
if (!serializedData)
|
113
|
+
return;
|
114
|
+
ws.send(serializedData);
|
115
|
+
}
|
116
|
+
sendMessage(event, clientId, data) {
|
117
|
+
const ws = this.idToWs.get(clientId);
|
118
|
+
// TODO: throw a nice error
|
119
|
+
if (!ws)
|
120
|
+
return;
|
121
|
+
const packet = {
|
122
|
+
event,
|
123
|
+
data
|
124
|
+
};
|
125
|
+
const serializedPacket = serialize(packet);
|
126
|
+
// TODO: throw a nice error
|
127
|
+
if (!serializedPacket)
|
128
|
+
return;
|
129
|
+
ws.send(serializedPacket);
|
130
|
+
}
|
131
|
+
broadcastRaw(data) {
|
132
|
+
const serializedData = serialize(data);
|
133
|
+
if (!serializedData) {
|
134
|
+
// TODO: throw a nice error
|
135
|
+
return;
|
136
|
+
}
|
137
|
+
this.idToWs.forEach((ws) => {
|
138
|
+
ws.send(serializedData);
|
139
|
+
});
|
140
|
+
}
|
141
|
+
broadcast(event, data) {
|
142
|
+
const packet = {
|
143
|
+
event,
|
144
|
+
data
|
145
|
+
};
|
146
|
+
const serializedPacket = serialize(packet);
|
147
|
+
if (!serializedPacket)
|
148
|
+
return;
|
149
|
+
this.idToWs.forEach((ws) => {
|
150
|
+
ws.send(serializedPacket);
|
151
|
+
});
|
152
|
+
}
|
153
|
+
selectiveBroascast(event, data, connections) {
|
154
|
+
const serialized = serialize(data);
|
155
|
+
if (!serialized) {
|
156
|
+
// TODO: throw a nice error
|
157
|
+
return;
|
158
|
+
}
|
159
|
+
const packet = {
|
160
|
+
event,
|
161
|
+
data
|
162
|
+
};
|
163
|
+
const serializedPacket = serialize(packet); // if data is serializable then packet is too, so no need to check
|
164
|
+
connections
|
165
|
+
.flatMap(x => this.idToWs.get(x) ?? [])
|
166
|
+
.forEach((ws) => {
|
167
|
+
ws.send(serializedPacket);
|
168
|
+
});
|
169
|
+
}
|
170
|
+
get event() {
|
171
|
+
return Object.fromEntries(Object.keys(this._events).map((eventName) => {
|
172
|
+
// HACK: use a better method to determine the type of event.
|
173
|
+
if ("data" in this._events[eventName]) {
|
174
|
+
// event is server event
|
175
|
+
return [eventName, {
|
176
|
+
send: (clientId, data) => {
|
177
|
+
const packet = {
|
178
|
+
event: eventName,
|
179
|
+
data
|
180
|
+
};
|
181
|
+
this.sendMessageRaw(clientId, packet);
|
182
|
+
},
|
183
|
+
broadcast: (data) => {
|
184
|
+
const packet = {
|
185
|
+
event: eventName,
|
186
|
+
data
|
187
|
+
};
|
188
|
+
this.broadcastRaw(packet);
|
189
|
+
}
|
190
|
+
}];
|
191
|
+
}
|
192
|
+
return null;
|
193
|
+
}).filter(entry => entry !== null));
|
194
|
+
}
|
195
|
+
get clients() {
|
196
|
+
return new Set(this.idToWs.keys());
|
197
|
+
}
|
198
|
+
get socketMap() {
|
199
|
+
return this.idToWs;
|
56
200
|
}
|
57
201
|
}
|
58
|
-
export const createZapServer = ({ port }) => {
|
59
|
-
|
202
|
+
export const createZapServer = ({ port, events }, callback) => {
|
203
|
+
const server = new ZapServer({ port, events }, callback);
|
204
|
+
return server;
|
60
205
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@zap-socket/server",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.6",
|
4
4
|
"description": "A fully typesafe tRPC-inspired WebSocket library with Zod validation, req-res model, and native subscriptions.",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"types": "dist/index.d.ts",
|
@@ -23,6 +23,7 @@
|
|
23
23
|
"vitest": "^3.0.9"
|
24
24
|
},
|
25
25
|
"dependencies": {
|
26
|
+
"@zap-socket/types": "^0.0.3",
|
26
27
|
"zod": "^3.24.2"
|
27
28
|
}
|
28
29
|
}
|