@znight/websocket 0.0.0 → 0.6.1

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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/dist/bun.d.mts +4 -0
  4. package/dist/bun.mjs +112 -0
  5. package/dist/bun.mjs.map +1 -0
  6. package/dist/compiler.d.mts +90 -0
  7. package/dist/compiler.mjs +864 -0
  8. package/dist/compiler.mjs.map +1 -0
  9. package/dist/index.d.mts +501 -0
  10. package/dist/index.mjs +2088 -0
  11. package/dist/index.mjs.map +1 -0
  12. package/dist/server-CI_Bh9o1.d.mts +331 -0
  13. package/dist/server-D40_bC88.mjs +72 -0
  14. package/dist/server-D40_bC88.mjs.map +1 -0
  15. package/dist/testing.d.mts +60 -0
  16. package/dist/testing.mjs +189 -0
  17. package/dist/testing.mjs.map +1 -0
  18. package/dist/transport-xnJqvB93.d.mts +63 -0
  19. package/dist/ws.d.mts +9 -0
  20. package/dist/ws.mjs +143 -0
  21. package/dist/ws.mjs.map +1 -0
  22. package/package.json +102 -1
  23. package/src/client.ts +246 -0
  24. package/src/compiler/analyze.ts +850 -0
  25. package/src/compiler/constants.ts +102 -0
  26. package/src/compiler/data.ts +78 -0
  27. package/src/compiler/generate.ts +262 -0
  28. package/src/compiler/index.ts +53 -0
  29. package/src/compiler/validate.ts +81 -0
  30. package/src/connection.ts +455 -0
  31. package/src/context.ts +43 -0
  32. package/src/decorators.ts +83 -0
  33. package/src/dispatcher.ts +343 -0
  34. package/src/envelope.ts +136 -0
  35. package/src/exceptions.ts +91 -0
  36. package/src/index.ts +89 -0
  37. package/src/metadata.ts +74 -0
  38. package/src/options.ts +343 -0
  39. package/src/plugin.ts +820 -0
  40. package/src/rate-limit.ts +27 -0
  41. package/src/rooms.ts +185 -0
  42. package/src/server.ts +125 -0
  43. package/src/testing/index.ts +279 -0
  44. package/src/tokens.ts +23 -0
  45. package/src/transport.ts +72 -0
  46. package/src/transports/bun.ts +163 -0
  47. package/src/transports/memory.ts +174 -0
  48. package/src/transports/ws.ts +237 -0
  49. package/src/validate.ts +31 -0
@@ -0,0 +1,864 @@
1
+ import { readRouteMeta, requiredRouteGroups } from "@znight/http/compiler";
2
+ import { checkRoutePath, normalizePath, parseRoutePath } from "@znight/http/path";
3
+ // src/compiler/constants.ts
4
+ /** Name of the extension, also the key of `metadata.extensions.websocket`. */
5
+ const EXTENSION_NAME = "websocket";
6
+ /** Module the generated code imports its helpers and types from. */
7
+ const RUNTIME_MODULE = "@znight/websocket";
8
+ /** Canonical identities of the decorators owned by the extension. */
9
+ const WsDecorator = Object.freeze({
10
+ Gateway: "@znight/websocket#Gateway",
11
+ OnConnect: "@znight/websocket#OnConnect",
12
+ OnMessage: "@znight/websocket#OnMessage",
13
+ OnDisconnect: "@znight/websocket#OnDisconnect",
14
+ Payload: "@znight/websocket#Payload",
15
+ Conn: "@znight/websocket#Conn",
16
+ Ctx: "@znight/websocket#Ctx",
17
+ UseHandshakeGuard: "@znight/websocket#UseHandshakeGuard",
18
+ UseMessageGuard: "@znight/websocket#UseMessageGuard"
19
+ });
20
+ /** Decorators giving a method a role. */
21
+ const ROLE_DECORATORS = /* @__PURE__ */ new Set([
22
+ WsDecorator.OnConnect,
23
+ WsDecorator.OnMessage,
24
+ WsDecorator.OnDisconnect
25
+ ]);
26
+ /** Decorators applied to methods (roles and message guards). */
27
+ const METHOD_DECORATORS = /* @__PURE__ */ new Set([...ROLE_DECORATORS, WsDecorator.UseMessageGuard]);
28
+ /** Decorators applied to handler parameters. */
29
+ const PARAMETER_DECORATORS = /* @__PURE__ */ new Set([
30
+ WsDecorator.Payload,
31
+ WsDecorator.Conn,
32
+ WsDecorator.Ctx
33
+ ]);
34
+ /** ZNIGHT005: invalid decorator usage. */
35
+ const INVALID_DECORATOR = "ZNIGHT005";
36
+ /** ZNIGHT007: invalid or conflicting gateway path. */
37
+ const INVALID_ROUTE = "ZNIGHT007";
38
+ /** ZNIGHT017: non-primitive `@Payload()` without schema. */
39
+ const UNVALIDATED_INPUT = "ZNIGHT017";
40
+ /** ZNIGHT023: a gateway without a decision of a required route decorator group (RFC 0001). */
41
+ const MISSING_ROUTE_DECISION = "ZNIGHT023";
42
+ /** ZNIGHT050: invalid gateway. */
43
+ const INVALID_GATEWAY = "ZNIGHT050";
44
+ /** Event names of the `events` protocol (the runtime `EVENT_NAME`, checked by the tests). */
45
+ const EVENT_NAME = /^[A-Za-z][\w.:/-]{0,127}$/;
46
+ /** RFC 7230 token: subprotocol names. */
47
+ const SUBPROTOCOL = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
48
+ /** Deterministic string comparison (never `localeCompare`). */
49
+ function compareStrings(a, b) {
50
+ if (a === b) return 0;
51
+ return a < b ? -1 : 1;
52
+ }
53
+ const UNITS = {
54
+ b: 1,
55
+ k: 1024,
56
+ kb: 1024,
57
+ kib: 1024,
58
+ m: 1024 ** 2,
59
+ mb: 1024 ** 2,
60
+ mib: 1024 ** 2,
61
+ g: 1024 ** 3,
62
+ gb: 1024 ** 3,
63
+ gib: 1024 ** 3
64
+ };
65
+ const SIZE = /^(\d+(?:\.\d+)?|\.\d+)\s*([a-z]*)$/i;
66
+ /** A positive byte size (`'64kb'`, `65536`), the grammar of `ByteSize`; `undefined` otherwise. */
67
+ function parseSize(value) {
68
+ if (typeof value === "number") return Number.isSafeInteger(value) && value > 0 ? value : void 0;
69
+ if (typeof value !== "string") return void 0;
70
+ const match = SIZE.exec(value.trim());
71
+ if (match === null) return void 0;
72
+ const unit = match[2].toLowerCase();
73
+ const multiplier = unit === "" ? 1 : UNITS[unit];
74
+ if (multiplier === void 0) return void 0;
75
+ const bytes = Math.floor(Number(match[1]) * multiplier);
76
+ return Number.isSafeInteger(bytes) && bytes > 0 ? bytes : void 0;
77
+ }
78
+ // src/compiler/analyze.ts
79
+ /** Types a `@Payload()` without schema may have (RFC 0003 §5.5). */
80
+ const BINARY_TYPES = /* @__PURE__ */ new Set([
81
+ "globalThis#Uint8Array",
82
+ "globalThis#ArrayBuffer",
83
+ "globalThis#Buffer",
84
+ "node:buffer#Buffer",
85
+ "buffer#Buffer"
86
+ ]);
87
+ const OPTION_KEYS = /* @__PURE__ */ new Set([
88
+ "path",
89
+ "scope",
90
+ "protocol",
91
+ "protocols",
92
+ "maxPayload",
93
+ "concurrency",
94
+ "rateLimit",
95
+ "backpressure"
96
+ ]);
97
+ /** Maximum depth of the base class chain walked for inherited handlers. */
98
+ const MAX_CHAIN = 32;
99
+ /** Analyses a class carrying at least one decorator of the extension. */
100
+ function analyzeWebSocketClass(cls, ctx) {
101
+ const gateways = cls.decorators.filter((decorator) => decorator.identity === WsDecorator.Gateway);
102
+ const gateway = gateways[0];
103
+ if (gateway === void 0) {
104
+ analyzeMembers(cls, ctx);
105
+ const decorators = [...cls.decorators.filter(isOwnClassDecorator), ...cls.methods.flatMap((method) => method.decorators.filter(isOwnMethodDecorator))].map((decorator) => ({
106
+ name: decorator.name,
107
+ location: decorator.location
108
+ }));
109
+ if (decorators.length > 0) ctx.store({
110
+ kind: "members",
111
+ identity: cls.identity,
112
+ displayName: cls.displayName,
113
+ abstract: cls.abstract,
114
+ decorators
115
+ });
116
+ return;
117
+ }
118
+ for (const duplicate of gateways.slice(1)) report(ctx, INVALID_DECORATOR, duplicate.location, `@Gateway is applied several times on ${cls.displayName}`, ["Keep a single @Gateway: a class serves one path"]);
119
+ const read = readGatewayOptions(gateway, cls, ctx);
120
+ const token = ctx.registerProvider(cls, { scope: read.scope });
121
+ const chain = classChain(cls, ctx);
122
+ const merged = /* @__PURE__ */ new Map();
123
+ for (let index = chain.length - 1; index >= 0; index--) {
124
+ const owner = chain[index];
125
+ for (const [name, member] of analyzeMembers(owner, ctx, index === 0 ? void 0 : cls)) merged.set(name, member);
126
+ }
127
+ const members = [...merged.values()];
128
+ const handlers = members.flatMap((member) => member.handlers);
129
+ const onConnect = single(ctx, cls, members, "onConnect", "@OnConnect");
130
+ const onDisconnect = single(ctx, cls, members, "onDisconnect", "@OnDisconnect");
131
+ checkHandlers(cls, read.options.protocol, handlers, ctx);
132
+ if (!members.some((member) => member.role)) report(ctx, INVALID_GATEWAY, gateway.location, `@Gateway ${cls.displayName} has no handler`, ["Decorate a public method with @OnMessage(\"event\"), @OnConnect() or @OnDisconnect()"], "warning");
133
+ ctx.store({
134
+ kind: "gateway",
135
+ token,
136
+ identity: cls.identity,
137
+ displayName: cls.displayName,
138
+ path: read.path,
139
+ options: read.options,
140
+ meta: readRouteMeta(ctx, cls),
141
+ handshakeGuards: guardRefs(ctx, cls, cls.decorators, WsDecorator.UseHandshakeGuard),
142
+ messageGuards: guardRefs(ctx, cls, cls.decorators, WsDecorator.UseMessageGuard),
143
+ onConnect,
144
+ onDisconnect,
145
+ handlers,
146
+ chain: chain.map((owner) => owner.identity),
147
+ location: gateway.location
148
+ });
149
+ }
150
+ function isOwnClassDecorator(decorator) {
151
+ return decorator.identity === WsDecorator.UseHandshakeGuard || decorator.identity === WsDecorator.UseMessageGuard;
152
+ }
153
+ function isOwnMethodDecorator(decorator) {
154
+ return decorator.identity !== void 0 && METHOD_DECORATORS.has(decorator.identity);
155
+ }
156
+ function report(ctx, code, location, message, hints, severity = "error", related) {
157
+ ctx.report({
158
+ code,
159
+ severity,
160
+ message,
161
+ location,
162
+ ...hints === void 0 ? {} : { hints },
163
+ ...related === void 0 ? {} : { related }
164
+ });
165
+ }
166
+ function isRecord(value) {
167
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
168
+ const prototype = Object.getPrototypeOf(value);
169
+ return prototype === Object.prototype || prototype === null;
170
+ }
171
+ function describe(value) {
172
+ if (value === void 0) return "undefined";
173
+ if (value === null) return "null";
174
+ if (typeof value === "string") return JSON.stringify(value);
175
+ if (typeof value !== "object") return String(value);
176
+ if (Array.isArray(value)) return "an array";
177
+ return isRecord(value) ? "an object" : "a reference";
178
+ }
179
+ function isPositiveInteger(value) {
180
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
181
+ }
182
+ /** `@Gateway()`, `@Gateway('/chat')` or `@Gateway({ path, scope, protocol, ... })`. */
183
+ function readGatewayOptions(decorator, cls, ctx) {
184
+ const prefix = `@Gateway ${cls.displayName}`;
185
+ const fallback = {
186
+ path: void 0,
187
+ scope: "singleton",
188
+ options: { protocol: "events" }
189
+ };
190
+ const arg = decorator.args[0];
191
+ if (!decorator.called) return fallback;
192
+ if (arg === void 0) return {
193
+ ...fallback,
194
+ path: "/"
195
+ };
196
+ if (!arg.evaluated) return fallback;
197
+ const value = arg.value;
198
+ if (typeof value === "string") return {
199
+ ...fallback,
200
+ path: checkPath(ctx, prefix, value, arg.location)
201
+ };
202
+ if (value === void 0) return {
203
+ ...fallback,
204
+ path: "/"
205
+ };
206
+ if (!isRecord(value)) {
207
+ report(ctx, INVALID_DECORATOR, arg.location, `${prefix}: expected a path or an options object, got ${describe(value)}`, ["Write @Gateway('/chat') or @Gateway({ path: '/chat', protocol: 'raw' })"]);
208
+ return fallback;
209
+ }
210
+ const fail = (message, hints) => {
211
+ report(ctx, INVALID_GATEWAY, arg.location, `${prefix}: ${message}`, hints);
212
+ };
213
+ for (const key of Object.keys(value)) if (!OPTION_KEYS.has(key)) fail(`unknown option "${key}"`, [`Known options: ${[...OPTION_KEYS].join(", ")}`]);
214
+ let path = "/";
215
+ const rawPath = value["path"];
216
+ if (rawPath !== void 0) {
217
+ if (typeof rawPath === "string") path = checkPath(ctx, prefix, rawPath, arg.location);
218
+ else {
219
+ fail(`"path" must be a string, got ${describe(rawPath)}`);
220
+ path = void 0;
221
+ }
222
+ }
223
+ let scope = "singleton";
224
+ const rawScope = value["scope"];
225
+ if (rawScope === "request") scope = "request";
226
+ else if (rawScope === "transient") fail("a gateway cannot be 'transient'", ["Use scope: 'request' for one instance per connection, or the default singleton"]);
227
+ else if (rawScope !== void 0 && rawScope !== "singleton") fail(`"scope" must be 'singleton' or 'request', got ${describe(rawScope)}`);
228
+ let protocol = "events";
229
+ const rawProtocol = value["protocol"];
230
+ if (rawProtocol === "raw") protocol = "raw";
231
+ else if (rawProtocol !== void 0 && rawProtocol !== "events") fail(`"protocol" must be 'events' or 'raw', got ${describe(rawProtocol)}`);
232
+ const options = { protocol };
233
+ const protocols = value["protocols"];
234
+ if (protocols !== void 0) {
235
+ if (!Array.isArray(protocols) || protocols.length === 0 || !protocols.every((item) => typeof item === "string" && SUBPROTOCOL.test(item)) || new Set(protocols).size !== protocols.length) fail(`"protocols" must be a non-empty list of distinct subprotocol tokens, got ${describe(protocols)}`, ["protocols: ['chat.v1']"]);
236
+ else options.protocols = protocols;
237
+ }
238
+ const maxPayload = value["maxPayload"];
239
+ if (maxPayload !== void 0) {
240
+ const bytes = parseSize(maxPayload);
241
+ if (bytes === void 0) fail(`"maxPayload" must be a positive byte size ('64kb', 65536), got ${describe(maxPayload)}`);
242
+ else options.maxPayload = bytes;
243
+ }
244
+ const concurrency = value["concurrency"];
245
+ if (concurrency !== void 0) {
246
+ if (isPositiveInteger(concurrency)) options.concurrency = concurrency;
247
+ else fail(`"concurrency" must be a positive integer, got ${describe(concurrency)}`);
248
+ }
249
+ const rateLimit = value["rateLimit"];
250
+ if (rateLimit !== void 0) {
251
+ const onLimit = isRecord(rateLimit) ? rateLimit["onLimit"] ?? "reject" : void 0;
252
+ if (!isRecord(rateLimit) || !isPositiveInteger(rateLimit["messages"]) || !isPositiveInteger(rateLimit["intervalMs"]) || onLimit !== "reject" && onLimit !== "close" || Object.keys(rateLimit).some((key) => key !== "messages" && key !== "intervalMs" && key !== "onLimit")) fail(`"rateLimit" must be { messages, intervalMs, onLimit?: 'reject' | 'close' }, got ${describe(rateLimit)}`, ["rateLimit: { messages: 30, intervalMs: 1000 }"]);
253
+ else options.rateLimit = {
254
+ messages: rateLimit["messages"],
255
+ intervalMs: rateLimit["intervalMs"],
256
+ onLimit
257
+ };
258
+ }
259
+ const backpressure = value["backpressure"];
260
+ if (backpressure !== void 0) {
261
+ const limit = isRecord(backpressure) ? backpressure["limit"] : void 0;
262
+ const onLimit = isRecord(backpressure) ? backpressure["onLimit"] : void 0;
263
+ const bytes = limit === void 0 ? void 0 : parseSize(limit);
264
+ if (!isRecord(backpressure) || limit !== void 0 && bytes === void 0 || onLimit !== void 0 && onLimit !== "close" && onLimit !== "drop" || Object.keys(backpressure).some((key) => key !== "limit" && key !== "onLimit")) fail(`"backpressure" must be { limit?: ByteSize, onLimit?: 'close' | 'drop' }, got ${describe(backpressure)}`);
265
+ else options.backpressure = {
266
+ ...bytes === void 0 ? {} : { limit: bytes },
267
+ ...onLimit === void 0 ? {} : { onLimit }
268
+ };
269
+ }
270
+ return {
271
+ path,
272
+ scope,
273
+ options
274
+ };
275
+ }
276
+ /** The normalised path, or `undefined` when it breaks the route grammar (ZNIGHT007). */
277
+ function checkPath(ctx, prefix, raw, location) {
278
+ const path = normalizePath(raw);
279
+ const issue = checkRoutePath(path);
280
+ if (issue === void 0) return path;
281
+ report(ctx, INVALID_ROUTE, location, `${prefix}: invalid path "${raw}": ${issue.message}`, ["Gateway paths use the route grammar: '/chat', '/docs/:docId', '/feeds/*'"]);
282
+ }
283
+ /** The class and its analysable base classes (nearest first). */
284
+ function classChain(cls, ctx) {
285
+ const chain = [cls];
286
+ const seen = /* @__PURE__ */ new Set([cls.identity]);
287
+ let current = cls;
288
+ while (current.superClass !== void 0 && chain.length < MAX_CHAIN) {
289
+ const base = ctx.classInfo(current.superClass.identity);
290
+ if (base === void 0 || seen.has(base.identity)) break;
291
+ chain.push(base);
292
+ seen.add(base.identity);
293
+ current = base;
294
+ }
295
+ return chain;
296
+ }
297
+ /** `@UseHandshakeGuard` / `@UseMessageGuard` arguments, bound with a gateway → guard edge. */
298
+ function guardRefs(ctx, from, decorators, identity) {
299
+ const refs = [];
300
+ for (const decorator of decorators) {
301
+ if (decorator.identity !== identity) continue;
302
+ if (decorator.called && decorator.args.length === 0) report(ctx, INVALID_DECORATOR, decorator.location, `@${decorator.name} requires at least one guard`, [`@${decorator.name}(MyGuard)`]);
303
+ for (const arg of decorator.args) {
304
+ const ref = ctx.resolveClassRef(arg, {
305
+ from,
306
+ role: "guard"
307
+ });
308
+ if (ref) refs.push(ref);
309
+ }
310
+ }
311
+ return refs;
312
+ }
313
+ /** The single `@OnConnect` / `@OnDisconnect` of a gateway (ZNIGHT050 for several). */
314
+ function single(ctx, cls, members, key, name) {
315
+ const found = members.flatMap((member) => member[key] === void 0 ? [] : [member[key]]);
316
+ const first = found[0];
317
+ for (const extra of found.slice(1)) report(ctx, INVALID_GATEWAY, extra.location, `${cls.displayName} has several ${name} methods: ${found.map((item) => `${item.declaredBy}.${item.method}`).join(", ")}`, [`Keep a single ${name} method per gateway`], "error", [{
318
+ message: `First ${name} method`,
319
+ location: first.location
320
+ }]);
321
+ return first;
322
+ }
323
+ /** Events handled twice, several default handlers, events on raw gateways (ZNIGHT050). */
324
+ function checkHandlers(cls, protocol, handlers, ctx) {
325
+ const label = (handler) => `${handler.declaredBy}.${handler.method}`;
326
+ if (protocol === "raw") {
327
+ for (const handler of handlers) {
328
+ if (handler.event === void 0) continue;
329
+ report(ctx, INVALID_GATEWAY, handler.location, `@OnMessage("${handler.event}") on the raw gateway ${cls.displayName}`, ["A raw gateway has no events: use @OnMessage() for its single handler, or protocol: \"events\""]);
330
+ }
331
+ const first = handlers[0];
332
+ for (const extra of handlers.slice(1)) report(ctx, INVALID_GATEWAY, extra.location, `The raw gateway ${cls.displayName} has several handlers: ${handlers.map(label).join(", ")}`, ["A raw gateway has a single @OnMessage() handler receiving every frame"], "error", [{
333
+ message: "First handler",
334
+ location: first.location
335
+ }]);
336
+ return;
337
+ }
338
+ let fallback;
339
+ const byEvent = /* @__PURE__ */ new Map();
340
+ for (const handler of handlers) {
341
+ if (handler.event === void 0) {
342
+ if (fallback === void 0) {
343
+ fallback = handler;
344
+ continue;
345
+ }
346
+ report(ctx, INVALID_GATEWAY, handler.location, `${cls.displayName} has several default handlers: ${label(fallback)} and ${label(handler)}`, ["Give one of them an event: @OnMessage(\"message.send\")"], "error", [{
347
+ message: "First default handler",
348
+ location: fallback.location
349
+ }]);
350
+ continue;
351
+ }
352
+ const first = byEvent.get(handler.event);
353
+ if (first === void 0) {
354
+ byEvent.set(handler.event, handler);
355
+ continue;
356
+ }
357
+ report(ctx, INVALID_GATEWAY, handler.location, `${cls.displayName} handles "${handler.event}" twice: ${label(first)} and ${label(handler)}`, ["An event has a single handler per gateway: merge the methods, or rename one of the events"], "error", [{
358
+ message: "first handled here",
359
+ location: first.location
360
+ }]);
361
+ }
362
+ }
363
+ /**
364
+ * Websocket methods declared by `cls` itself, by name. `host` is the gateway when `cls` is one of
365
+ * its bases (its methods were already reported when the base was analysed: no second report).
366
+ */
367
+ function analyzeMembers(cls, ctx, host) {
368
+ const members = /* @__PURE__ */ new Map();
369
+ const quiet = host === void 0 ? ctx : silenced(ctx);
370
+ for (const method of cls.methods) {
371
+ const member = analyzeMethod(cls, method, quiet, host ?? cls);
372
+ if (member !== void 0) members.set(method.name, member);
373
+ }
374
+ return members;
375
+ }
376
+ /** The context without reports (a base class analysed again for a gateway). */
377
+ function silenced(ctx) {
378
+ return new Proxy(ctx, { get(target, key, receiver) {
379
+ if (key === "report") return () => void 0;
380
+ const value = Reflect.get(target, key, receiver);
381
+ return typeof value === "function" ? value.bind(target) : value;
382
+ } });
383
+ }
384
+ function analyzeMethod(cls, method, ctx, host) {
385
+ const onConnect = method.decorators.filter((decorator) => decorator.identity === WsDecorator.OnConnect);
386
+ const onDisconnect = method.decorators.filter((decorator) => decorator.identity === WsDecorator.OnDisconnect);
387
+ const onMessage = method.decorators.filter((decorator) => decorator.identity === WsDecorator.OnMessage);
388
+ const guards = method.decorators.filter((decorator) => decorator.identity === WsDecorator.UseMessageGuard);
389
+ const decoratedParams = method.params.some((param) => param.decorators.some(isParameterDecorator));
390
+ if (onConnect.length + onDisconnect.length + onMessage.length + guards.length === 0 && !decoratedParams) return;
391
+ const where = `${cls.displayName}.${method.name}`;
392
+ const roles = [
393
+ onConnect[0],
394
+ onDisconnect[0],
395
+ onMessage[0]
396
+ ].filter((item) => item !== void 0);
397
+ const primary = roles[0];
398
+ if (primary === void 0) {
399
+ for (const guard of guards) report(ctx, INVALID_DECORATOR, guard.location, `@UseMessageGuard has no effect on ${where}: it is not an @OnMessage handler`, ["Add @OnMessage(\"event\") to the method, or put the guard on the gateway class"]);
400
+ reportStrayParameters(method, where, ctx);
401
+ return {
402
+ method: method.name,
403
+ declaredBy: cls.displayName,
404
+ onConnect: void 0,
405
+ onDisconnect: void 0,
406
+ handlers: [],
407
+ role: false
408
+ };
409
+ }
410
+ checkMethodShape(method, primary, where, ctx);
411
+ if (roles.length > 1) report(ctx, INVALID_GATEWAY, roles[1].location, `${where} has several roles (${roles.map((role) => `@${role.name}`).join(", ")})`, ["A method is either @OnConnect, @OnDisconnect or an @OnMessage handler: split it"]);
412
+ for (const duplicate of [...onConnect.slice(1), ...onDisconnect.slice(1)]) report(ctx, INVALID_DECORATOR, duplicate.location, `@${duplicate.name} is applied several times on ${where}`, [`Keep a single @${duplicate.name}`]);
413
+ const location = primary.location;
414
+ if (onMessage.length === 0) {
415
+ for (const guard of guards) report(ctx, INVALID_DECORATOR, guard.location, `@UseMessageGuard has no effect on ${where}: it is not an @OnMessage handler`, ["Message guards run before @OnMessage handlers; guard the handshake with @UseHandshakeGuard on the class"]);
416
+ const lifecycle = {
417
+ method: method.name,
418
+ params: analyzeParams(method, where, "lifecycle", ctx),
419
+ declaredBy: cls.displayName,
420
+ location
421
+ };
422
+ return {
423
+ method: method.name,
424
+ declaredBy: cls.displayName,
425
+ onConnect: onConnect.length > 0 ? lifecycle : void 0,
426
+ onDisconnect: onConnect.length === 0 ? lifecycle : void 0,
427
+ handlers: [],
428
+ role: true
429
+ };
430
+ }
431
+ const params = analyzeParams(method, where, "message", ctx);
432
+ const meta = readRouteMeta(ctx, cls, method);
433
+ const methodGuards = guardRefs(ctx, host, method.decorators, WsDecorator.UseMessageGuard);
434
+ const handlers = onMessage.flatMap((decorator) => {
435
+ const event = eventOf(decorator, where, ctx);
436
+ if (event === false) return [];
437
+ return [Object.freeze({
438
+ method: method.name,
439
+ event,
440
+ meta,
441
+ guards: methodGuards,
442
+ params,
443
+ declaredBy: cls.displayName,
444
+ location: decorator.location
445
+ })];
446
+ });
447
+ return {
448
+ method: method.name,
449
+ declaredBy: cls.displayName,
450
+ onConnect: void 0,
451
+ onDisconnect: void 0,
452
+ handlers,
453
+ role: true
454
+ };
455
+ }
456
+ /** The event of `@OnMessage(event?)`: a string, `undefined` (default handler) or `false` (invalid). */
457
+ function eventOf(decorator, where, ctx) {
458
+ const arg = decorator.args[0];
459
+ if (arg === void 0 || arg.evaluated && arg.value === void 0) return void 0;
460
+ if (!arg.evaluated) return false;
461
+ const value = arg.value;
462
+ if (typeof value !== "string") {
463
+ report(ctx, INVALID_DECORATOR, arg.location, `@OnMessage on ${where} expects an event name (a string), got ${describe(value)}`, ["Use @OnMessage() for the default handler"]);
464
+ return false;
465
+ }
466
+ if (value.startsWith("$")) {
467
+ report(ctx, INVALID_GATEWAY, arg.location, `Event name "${value}" is reserved (${where})`, [`Names starting with "$" are protocol events${value === "$ping" ? "; \"$ping\" is answered by Znight itself" : ""}`]);
468
+ return false;
469
+ }
470
+ if (!EVENT_NAME.test(value)) {
471
+ report(ctx, INVALID_GATEWAY, arg.location, `Invalid event name "${value}" (${where})`, ["An event name starts with a letter, then up to 127 letters, digits or _ . : / -"]);
472
+ return false;
473
+ }
474
+ return value;
475
+ }
476
+ /** Handlers are called by the generated code: `gateway.method(...)`. */
477
+ function checkMethodShape(method, decorator, where, ctx) {
478
+ const name = `@${decorator.name}`;
479
+ let problem;
480
+ if (method.kind !== "method") problem = [`${name} cannot be applied to the accessor ${where}`, "Use a regular method"];
481
+ else if (method.static) problem = [`${name} cannot be applied to the static method ${where}`, "Remove `static`"];
482
+ else if (method.privateName) problem = [`${name} cannot be applied to the #private method ${where}`, "Use a public method"];
483
+ else if (method.visibility !== "public") problem = [`${where} must be public: ${name} methods are called by the generated code`, `Remove the '${method.visibility}' modifier`];
484
+ else if (method.computed) problem = [`${name} cannot be applied to ${where}: computed names are not supported`, "Use a plain method name"];
485
+ else if (method.generator) problem = [`${name} cannot be applied to the generator method ${where}`, "Use a regular (or async) method"];
486
+ if (problem !== void 0) report(ctx, INVALID_DECORATOR, decorator.location, problem[0], [problem[1]]);
487
+ }
488
+ function analyzeParams(method, where, role, ctx) {
489
+ let payload;
490
+ return Object.freeze(method.params.map((param) => {
491
+ const data = analyzeParam(param, where, role, ctx);
492
+ if (data.kind === "payload" || data.kind === "validated") {
493
+ const decorator = parameterDecorators(param)[0];
494
+ if (payload !== void 0) report(ctx, INVALID_GATEWAY, decorator.location, `${where} has two @Payload parameters`, ["A message has one payload: keep a single @Payload()"]);
495
+ payload ??= decorator;
496
+ }
497
+ return data;
498
+ }));
499
+ }
500
+ function analyzeParam(param, where, role, ctx) {
501
+ const absent = {
502
+ kind: "undefined",
503
+ optional: param.optional || param.hasDefault
504
+ };
505
+ const decorators = parameterDecorators(param);
506
+ const decorator = decorators[0];
507
+ if (decorator === void 0) {
508
+ const foreign = foreignParameterDecorator(param);
509
+ if (foreign !== void 0) {
510
+ report(ctx, INVALID_DECORATOR, foreign.decorator.location, `@${foreign.decorator.name} on parameter '${param.name}' of ${where} is imported from '${foreign.module}': gateway handlers use ${foreign.name} from '${RUNTIME_MODULE}'`, [`Import it from '${RUNTIME_MODULE}': import { ${foreign.name} } from '${RUNTIME_MODULE}'`]);
511
+ return absent;
512
+ }
513
+ if (param.optional || param.hasDefault || param.rest) return absent;
514
+ report(ctx, INVALID_GATEWAY, param.location, `Parameter '${param.name}' of ${where} has no @Payload(), @Conn() or @Ctx() decorator`, [role === "message" ? "Every handler parameter is extracted from the message: @Payload(schema?), @Conn() or @Ctx()" : "@OnConnect and @OnDisconnect methods receive the connection: @Conn()"]);
515
+ return absent;
516
+ }
517
+ if (decorators.length > 1) report(ctx, INVALID_DECORATOR, decorators[1].location, `Parameter '${param.name}' of ${where} has several websocket decorators`, ["Keep a single one"]);
518
+ if (param.rest) {
519
+ report(ctx, INVALID_DECORATOR, decorator.location, `@${decorator.name} cannot be applied to the rest parameter '...${param.name}' of ${where}`);
520
+ return absent;
521
+ }
522
+ if (decorator.identity === WsDecorator.Conn) return { kind: "connection" };
523
+ if (role === "lifecycle") {
524
+ report(ctx, INVALID_GATEWAY, decorator.location, `@${decorator.name} on ${where}: only @OnMessage handlers receive a message`, ["@OnConnect and @OnDisconnect methods take @Conn() only"]);
525
+ return absent;
526
+ }
527
+ if (decorator.identity === WsDecorator.Ctx) return { kind: "context" };
528
+ return payloadParam(param, decorator, where, ctx);
529
+ }
530
+ /** `@Payload()` / `@Payload(schema)`. */
531
+ function payloadParam(param, decorator, where, ctx) {
532
+ const arg = decorator.args[0];
533
+ if (arg === void 0 || arg.evaluated && arg.value === void 0) {
534
+ checkUnvalidated(param, decorator, where, ctx);
535
+ return { kind: "payload" };
536
+ }
537
+ if (arg.ref !== void 0) {
538
+ if (isClassReference(arg.ref)) {
539
+ report(ctx, INVALID_DECORATOR, arg.location, `@Payload(${arg.text}) on ${where}: '${arg.ref.name}' is a class, not a Standard Schema`, ["Pass a Standard Schema (Zod, Valibot, ArkType...): export const SendMessage = z.object({ ... })"]);
540
+ return {
541
+ kind: "undefined",
542
+ optional: false
543
+ };
544
+ }
545
+ return {
546
+ kind: "validated",
547
+ schema: arg.ref
548
+ };
549
+ }
550
+ if (!arg.evaluated) return {
551
+ kind: "undefined",
552
+ optional: false
553
+ };
554
+ report(ctx, INVALID_DECORATOR, arg.location, `@Payload on ${where} expects a Standard Schema (Zod, Valibot, ArkType...), got ${describe(arg.value)}`, ["Declare the schema with `export const SendMessage = z.object({ ... })` and pass it: @Payload(SendMessage)"]);
555
+ return {
556
+ kind: "undefined",
557
+ optional: false
558
+ };
559
+ }
560
+ /** ZNIGHT017: a structured payload injected without schema is trusted blindly. */
561
+ function checkUnvalidated(param, decorator, where, ctx) {
562
+ const { type } = ctx.types.stripNullish(param.type);
563
+ if (isSafePayloadType(type)) return;
564
+ const typed = type.kind === "opaque" && type.reason === "missing-annotation" ? "has no type annotation" : `is typed '${ctx.types.format(param.type)}'`;
565
+ ctx.report({
566
+ code: UNVALIDATED_INPUT,
567
+ severity: ctx.options.strictValidation ? "error" : "warning",
568
+ message: `Unvalidated message payload: parameter '${param.name}' of ${where} ${typed} but @Payload() has no schema`,
569
+ location: decorator.location,
570
+ hints: ["Validate it with a Standard Schema (Zod, Valibot, ArkType...): @Payload(SendMessage)", "Or declare the parameter as 'unknown' (or 'string | Uint8Array') and check it in the handler"]
571
+ });
572
+ }
573
+ /** Parameter decorators of a method that has no websocket role. */
574
+ function reportStrayParameters(method, where, ctx) {
575
+ for (const param of method.params) for (const decorator of parameterDecorators(param)) report(ctx, INVALID_DECORATOR, decorator.location, `@${decorator.name} has no effect on parameter '${param.name}' of ${where}: it is not a gateway handler`, ["Add @OnMessage(\"event\"), @OnConnect() or @OnDisconnect() to the method, or remove the decorator"]);
576
+ }
577
+ /** A parameter decorator named like a websocket one (`Ctx`, `Payload`) imported from another package. */
578
+ function foreignParameterDecorator(param) {
579
+ for (const decorator of param.decorators) {
580
+ const identity = decorator.identity;
581
+ const separator = identity?.lastIndexOf("#") ?? -1;
582
+ if (identity === void 0 || separator <= 0) continue;
583
+ const module = identity.slice(0, separator);
584
+ const name = identity.slice(separator + 1);
585
+ if (module === "@znight/websocket" || !module.startsWith("@znight/")) continue;
586
+ if (PARAMETER_DECORATORS.has(`@znight/websocket#${name}`)) return {
587
+ decorator,
588
+ module,
589
+ name
590
+ };
591
+ }
592
+ }
593
+ /** Websocket decorators of a parameter, de-duplicated by position. */
594
+ function parameterDecorators(param) {
595
+ const seen = /* @__PURE__ */ new Set();
596
+ return param.decorators.filter((decorator) => {
597
+ if (!isParameterDecorator(decorator) || seen.has(decorator.location.start)) return false;
598
+ seen.add(decorator.location.start);
599
+ return true;
600
+ });
601
+ }
602
+ function isParameterDecorator(decorator) {
603
+ return decorator.identity !== void 0 && PARAMETER_DECORATORS.has(decorator.identity);
604
+ }
605
+ function isClassReference(ref) {
606
+ const symbol = ref.symbol;
607
+ return symbol.kind === "declaration" && symbol.declaration.kind === "class";
608
+ }
609
+ /** `string`, `Uint8Array`, `string | Uint8Array`, literals and `unknown` need no schema. */
610
+ function isSafePayloadType(type) {
611
+ switch (type.kind) {
612
+ case "primitive": return type.name !== "any" && type.name !== "object";
613
+ case "literal": return true;
614
+ case "union": return type.types.every(isSafePayloadType);
615
+ case "reference": return BINARY_TYPES.has(type.identity);
616
+ default: return false;
617
+ }
618
+ }
619
+ // src/compiler/generate.ts
620
+ /** Builds the websocket section; `undefined` (no section) without gateways. */
621
+ function generateWebSocket(ctx) {
622
+ const gateways = ctx.stored.filter((data) => data.kind === "gateway").sort((a, b) => compareStrings(a.token, b.token));
623
+ if (gateways.length === 0) return void 0;
624
+ const helpers = lazyHelpers(ctx);
625
+ const entries = [["gateways", ctx.arr(gateways.map((gateway) => gatewayNode(ctx, gateway, helpers)))]];
626
+ const groups = groupsNode(ctx, gateways);
627
+ if (groups !== void 0) entries.push(["groups", groups]);
628
+ return ctx.obj(entries);
629
+ }
630
+ function gatewayNode(ctx, gateway, helpers) {
631
+ let classType;
632
+ const gatewayType = () => {
633
+ classType ??= ctx.importType(gateway.identity);
634
+ return classType;
635
+ };
636
+ const options = gateway.options;
637
+ const entries = [
638
+ ["token", gateway.token],
639
+ ["path", gateway.path ?? "/"],
640
+ ["protocol", options.protocol]
641
+ ];
642
+ if (options.protocols !== void 0) entries.push(["protocols", ctx.lit([...options.protocols])]);
643
+ if (options.maxPayload !== void 0) entries.push(["maxPayload", options.maxPayload]);
644
+ if (options.concurrency !== void 0) entries.push(["concurrency", options.concurrency]);
645
+ if (options.rateLimit !== void 0) entries.push(["rateLimit", ctx.lit({ ...options.rateLimit })]);
646
+ if (options.backpressure !== void 0) entries.push(["backpressure", ctx.lit({ ...options.backpressure })]);
647
+ entries.push(["meta", metaNode(ctx, gateway.meta)], ["handshakeGuards", tokens(ctx, gateway.handshakeGuards)], ["messageGuards", tokens(ctx, gateway.messageGuards)]);
648
+ if (gateway.onConnect !== void 0) entries.push(["onConnect", lifecycleNode(ctx, gateway.onConnect, gatewayType, helpers)]);
649
+ if (gateway.onDisconnect !== void 0) entries.push(["onDisconnect", lifecycleNode(ctx, gateway.onDisconnect, gatewayType, helpers)]);
650
+ entries.push(["handlers", ctx.arr(gateway.handlers.map((handler) => handlerNode(ctx, handler, gatewayType, helpers)))]);
651
+ return ctx.obj(entries);
652
+ }
653
+ function metaNode(ctx, meta) {
654
+ return ctx.obj(meta.map((entry) => [entry.key, ctx.lit(entry.value)]));
655
+ }
656
+ function tokens(ctx, refs) {
657
+ return ctx.arr(refs.map((ref) => ctx.tokenOf(ref) ?? ref.token));
658
+ }
659
+ /** Trailing parameters left `undefined` are not passed. */
660
+ function trimmed(params) {
661
+ const list = [...params];
662
+ while (list.length > 0 && list[list.length - 1]?.kind === "undefined") list.pop();
663
+ return list;
664
+ }
665
+ /** `{ method, invoke: (g: Gateway, c: WsConnection) => g.method(c as never) }`. */
666
+ function lifecycleNode(ctx, lifecycle, gatewayType, helpers) {
667
+ const params = trimmed(lifecycle.params);
668
+ const invoke = params.length === 0 ? ctx.arrow([["g", gatewayType()]], (g) => ctx.call(ctx.member(g, lifecycle.method))) : ctx.arrow([["g", gatewayType()], ["c", helpers.connection()]], (g, c) => ctx.call(ctx.member(g, lifecycle.method), params.map((param) => param.kind === "connection" ? ctx.code`${c} as never` : ctx.lit(void 0))));
669
+ return ctx.obj([["method", lifecycle.method], ["invoke", invoke]]);
670
+ }
671
+ function handlerNode(ctx, handler, gatewayType, helpers) {
672
+ const entries = [["method", handler.method]];
673
+ if (handler.event !== void 0) entries.push(["event", handler.event]);
674
+ entries.push(["meta", metaNode(ctx, handler.meta)], ["guards", tokens(ctx, handler.guards)], ["params", ctx.arr(handler.params.flatMap((param, index) => paramNode(ctx, param, index)))], ["invoke", invokeNode(ctx, handler, gatewayType, helpers)]);
675
+ return ctx.obj(entries);
676
+ }
677
+ /** The informational `params` entry of a parameter (none for a parameter left `undefined`). */
678
+ function paramNode(ctx, param, index) {
679
+ switch (param.kind) {
680
+ case "payload": return [ctx.obj([["index", index], ["source", "payload"]])];
681
+ case "validated": return [ctx.obj([
682
+ ["index", index],
683
+ ["source", "payload"],
684
+ ["schema", true]
685
+ ])];
686
+ case "connection": return [ctx.obj([["index", index], ["source", "connection"]])];
687
+ case "context": return [ctx.obj([["index", index], ["source", "context"]])];
688
+ case "undefined": return [];
689
+ }
690
+ }
691
+ /** `(g: Gateway, m: WsMessageContext) => g.method(...args)`, `async` when a payload is validated. */
692
+ function invokeNode(ctx, handler, gatewayType, helpers) {
693
+ const params = trimmed(handler.params);
694
+ if (params.length === 0) return ctx.arrow([["g", gatewayType()]], (g) => ctx.call(ctx.member(g, handler.method)));
695
+ return ctx.arrow([["g", gatewayType()], ["m", helpers.messageContext()]], (g, m) => ctx.call(ctx.member(g, handler.method), params.map((param) => argumentNode(ctx, param, m, helpers))), { async: params.some((param) => param.kind === "validated") });
696
+ }
697
+ function argumentNode(ctx, param, m, helpers) {
698
+ switch (param.kind) {
699
+ case "payload": return ctx.code`${ctx.member(m, "data")} as never`;
700
+ case "validated": return ctx.code`await ${ctx.call(helpers.validatePayload(), [ctx.importValue(param.schema), ctx.member(m, "data")])}`;
701
+ case "connection": return ctx.code`${ctx.member(m, "connection")} as never`;
702
+ case "context": return m;
703
+ case "undefined": return ctx.lit(void 0);
704
+ }
705
+ }
706
+ /**
707
+ * `groups`: the route decorator groups the runtime needs (required, holding several keys, or named
708
+ * differently from their key), sorted; `undefined` when there is none (as `extensions.http`).
709
+ */
710
+ function groupsNode(ctx, gateways) {
711
+ const groups = /* @__PURE__ */ new Map();
712
+ const visit = (entries) => {
713
+ for (const entry of entries) {
714
+ const group = groups.get(entry.group);
715
+ if (group === void 0) groups.set(entry.group, {
716
+ keys: /* @__PURE__ */ new Set([entry.key]),
717
+ required: entry.required
718
+ });
719
+ else {
720
+ group.keys.add(entry.key);
721
+ group.required ||= entry.required;
722
+ }
723
+ }
724
+ };
725
+ for (const gateway of gateways) {
726
+ visit(gateway.meta);
727
+ for (const handler of gateway.handlers) visit(handler.meta);
728
+ }
729
+ const needed = [...groups].filter(([name, group]) => group.required || group.keys.size > 1 || !group.keys.has(name)).sort(([a], [b]) => compareStrings(a, b));
730
+ if (needed.length === 0) return void 0;
731
+ return ctx.obj(needed.map(([name, group]) => [name, ctx.obj([["keys", ctx.lit([...group.keys].sort(compareStrings))], ["required", group.required]])]));
732
+ }
733
+ function lazyHelpers(ctx) {
734
+ const cache = /* @__PURE__ */ new Map();
735
+ const get = (name, type) => {
736
+ let node = cache.get(name);
737
+ if (node === void 0) {
738
+ node = ctx.importFrom(RUNTIME_MODULE, name, type ? { type: true } : {});
739
+ cache.set(name, node);
740
+ }
741
+ return node;
742
+ };
743
+ return {
744
+ connection: () => get("WsConnection", true),
745
+ messageContext: () => get("WsMessageContext", true),
746
+ validatePayload: () => get("validatePayload", false)
747
+ };
748
+ }
749
+ // src/compiler/validate.ts
750
+ /** Validates the gateways of the application. */
751
+ function validateWebSocket(ctx) {
752
+ const gateways = ctx.stored.filter((data) => data.kind === "gateway").sort((a, b) => compareStrings(a.token, b.token));
753
+ const byShape = /* @__PURE__ */ new Map();
754
+ for (const gateway of gateways) {
755
+ if (gateway.path === void 0) continue;
756
+ const shape = parseRoutePath(gateway.path).shape;
757
+ const first = byShape.get(shape);
758
+ if (first === void 0) {
759
+ byShape.set(shape, gateway);
760
+ continue;
761
+ }
762
+ ctx.report({
763
+ code: INVALID_ROUTE,
764
+ severity: "error",
765
+ message: `${first.displayName} (${first.path}) and ${gateway.displayName} (${gateway.path}) serve the same path`,
766
+ location: gateway.location,
767
+ hints: ["Two gateways cannot match the same handshake: change one of the paths"],
768
+ related: [{
769
+ message: `${first.displayName} serves ${first.path}`,
770
+ location: first.location
771
+ }]
772
+ });
773
+ }
774
+ const bases = new Set(gateways.flatMap((gateway) => gateway.chain));
775
+ for (const data of ctx.stored) {
776
+ if (data.kind !== "members" || data.abstract || bases.has(data.identity)) continue;
777
+ for (const decorator of data.decorators) ctx.report({
778
+ code: INVALID_DECORATOR,
779
+ severity: "error",
780
+ message: `@${decorator.name} has no effect: ${data.displayName} is not a @Gateway`,
781
+ location: decorator.location,
782
+ hints: [`Add @Gateway('/path') to ${data.displayName}, or extend it from a @Gateway class`, "Base classes holding shared handlers should be abstract"]
783
+ });
784
+ }
785
+ const applied = [];
786
+ for (const gateway of gateways) {
787
+ applied.push(...gateway.meta);
788
+ for (const handler of gateway.handlers) applied.push(...handler.meta);
789
+ }
790
+ const required = requiredRouteGroups(ctx, applied);
791
+ if (required.length === 0) return;
792
+ for (const gateway of gateways) {
793
+ const decided = new Set(gateway.meta.map((entry) => entry.group));
794
+ for (const group of required) {
795
+ if (decided.has(group.group)) continue;
796
+ const definition = group.decorators[0];
797
+ ctx.report({
798
+ code: MISSING_ROUTE_DECISION,
799
+ severity: "error",
800
+ message: `Gateway ${gateway.displayName} (${gateway.path ?? "?"}) has no "${group.group}" decision`,
801
+ location: gateway.location,
802
+ hints: [`Add ${group.suggestion} to ${gateway.displayName}: the handshake is an HTTP request`],
803
+ ...definition?.definition === void 0 ? {} : { related: [{
804
+ message: `The "${group.group}" group is required`,
805
+ location: definition.definition
806
+ }] }
807
+ });
808
+ }
809
+ }
810
+ }
811
+ // src/compiler/index.ts
812
+ /** Decorators owned by the extension (static / value arguments, spec §4.6). */
813
+ const decorators = Object.freeze({
814
+ [WsDecorator.Gateway]: {
815
+ targets: ["class"],
816
+ params: ["static"]
817
+ },
818
+ [WsDecorator.OnConnect]: {
819
+ targets: ["method"],
820
+ params: []
821
+ },
822
+ [WsDecorator.OnDisconnect]: {
823
+ targets: ["method"],
824
+ params: []
825
+ },
826
+ [WsDecorator.OnMessage]: {
827
+ targets: ["method"],
828
+ params: ["static"]
829
+ },
830
+ [WsDecorator.Payload]: {
831
+ targets: ["parameter"],
832
+ params: ["value"]
833
+ },
834
+ [WsDecorator.Conn]: {
835
+ targets: ["parameter"],
836
+ params: []
837
+ },
838
+ [WsDecorator.Ctx]: {
839
+ targets: ["parameter"],
840
+ params: []
841
+ },
842
+ [WsDecorator.UseHandshakeGuard]: {
843
+ targets: ["class"],
844
+ params: [],
845
+ rest: "value"
846
+ },
847
+ [WsDecorator.UseMessageGuard]: {
848
+ targets: ["class", "method"],
849
+ params: [],
850
+ rest: "value"
851
+ }
852
+ });
853
+ /** The `@znight/websocket` compiler extension. */
854
+ const webSocketCompilerExtension = Object.freeze({
855
+ apiVersion: 1,
856
+ name: EXTENSION_NAME,
857
+ decorators,
858
+ analyzeClass: analyzeWebSocketClass,
859
+ validate: validateWebSocket,
860
+ generate: generateWebSocket
861
+ });
862
+ export { webSocketCompilerExtension as default, webSocketCompilerExtension };
863
+
864
+ //# sourceMappingURL=compiler.mjs.map