@rlanz/socket 0.0.1-1 → 0.0.1-10
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/README.md +572 -81
- package/build/base_channel-DKmR3Z0l.d.ts +476 -0
- package/build/chunk-55EIQ2VV.js +152 -0
- package/build/chunk-55EIQ2VV.js.map +1 -0
- package/build/chunk-6PJ4ALJD.js +115 -0
- package/build/chunk-6PJ4ALJD.js.map +1 -0
- package/build/chunk-BLRAKPXW.js +7 -0
- package/build/chunk-BLRAKPXW.js.map +1 -0
- package/build/chunk-E6ILCRL3.js +182 -0
- package/build/chunk-E6ILCRL3.js.map +1 -0
- package/build/{chunk-XUDFDJME.js → chunk-G75J233Z.js} +12 -9
- package/build/chunk-G75J233Z.js.map +1 -0
- package/build/chunk-IUQ3GYZZ.js +140 -0
- package/build/chunk-IUQ3GYZZ.js.map +1 -0
- package/build/chunk-K243S3QP.js +131 -0
- package/build/chunk-K243S3QP.js.map +1 -0
- package/build/chunk-NUPBNOP3.js +61 -0
- package/build/chunk-NUPBNOP3.js.map +1 -0
- package/build/chunk-O6UYCGTV.js +914 -0
- package/build/chunk-O6UYCGTV.js.map +1 -0
- package/build/framework-16ypv4Ss.d.ts +13 -0
- package/build/index-8jB5RG6v.d.ts +150 -0
- package/build/index.d.ts +29 -8
- package/build/index.js +39 -15
- package/build/index.js.map +1 -1
- package/build/providers/socket_provider.d.ts +6 -3
- package/build/providers/socket_provider.js +2156 -45
- package/build/providers/socket_provider.js.map +1 -1
- package/build/services/socket.d.ts +6 -3
- package/build/{shared_types-Dw9AphfO.d.ts → shared_types-DE86M136.d.ts} +5 -4
- package/build/socket_service-C1JM64_9.d.ts +106 -0
- package/build/src/assembler_hook.d.ts +15 -0
- package/build/src/assembler_hook.js +238 -0
- package/build/src/assembler_hook.js.map +1 -0
- package/build/src/client/index.d.ts +3 -118
- package/build/src/client/index.js +6 -696
- package/build/src/client/index.js.map +1 -1
- package/build/src/client/react.d.ts +30 -0
- package/build/src/client/react.js +86 -0
- package/build/src/client/react.js.map +1 -0
- package/build/src/client/types.d.ts +62 -9
- package/build/src/client/vue.d.ts +32 -0
- package/build/src/client/vue.js +100 -0
- package/build/src/client/vue.js.map +1 -0
- package/build/src/decorators.d.ts +7 -4
- package/build/src/decorators.js +1 -1
- package/build/src/health_check.d.ts +6 -3
- package/build/src/health_check.js +14 -3
- package/build/src/health_check.js.map +1 -1
- package/build/src/otel.d.ts +1 -0
- package/build/src/otel.js +187 -32
- package/build/src/otel.js.map +1 -1
- package/build/src/testing.d.ts +45 -0
- package/build/src/testing.js +7 -0
- package/build/src/testing.js.map +1 -0
- package/build/src/types/tracing_channels.d.ts +1 -2
- package/build/src/types.d.ts +4 -2
- package/package.json +36 -11
- package/build/chunk-5X4SKZQS.js +0 -1787
- package/build/chunk-5X4SKZQS.js.map +0 -1
- package/build/chunk-GKAD2UOA.js +0 -18
- package/build/chunk-GKAD2UOA.js.map +0 -1
- package/build/chunk-HK7Z65DA.js +0 -19
- package/build/chunk-HK7Z65DA.js.map +0 -1
- package/build/chunk-XUDFDJME.js.map +0 -1
- package/build/socket_service-jxIaH5Rs.d.ts +0 -144
- package/build/types-C0rDwbry.d.ts +0 -393
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChannelPatternSyntax
|
|
3
|
+
} from "./chunk-E6ILCRL3.js";
|
|
4
|
+
|
|
5
|
+
// src/bindable_channel_pattern.ts
|
|
6
|
+
var BindableChannelPattern = class _BindableChannelPattern extends ChannelPatternSyntax {
|
|
7
|
+
constructor(source) {
|
|
8
|
+
super(source);
|
|
9
|
+
this.source = source;
|
|
10
|
+
const bindingSegments = [];
|
|
11
|
+
const parameters = [];
|
|
12
|
+
const parameterNames = /* @__PURE__ */ new Set();
|
|
13
|
+
const serializedSegments = [];
|
|
14
|
+
let bindable = true;
|
|
15
|
+
if (this.canonicalPattern !== "/" && (this.segments.length === 0 || this.segments.length !== this.canonicalPattern.split("/").length)) {
|
|
16
|
+
bindable = false;
|
|
17
|
+
}
|
|
18
|
+
for (const [index, segment] of this.segments.entries()) {
|
|
19
|
+
const final = index === this.segments.length - 1;
|
|
20
|
+
if (segment.kind === "static") {
|
|
21
|
+
if (segment.value.length === 0 || /[:*?]/.test(segment.value)) {
|
|
22
|
+
bindable = false;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
bindingSegments.push({ kind: "static", value: segment.value });
|
|
26
|
+
serializedSegments.push(segment.value);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (segment.kind === "parameter") {
|
|
30
|
+
const unsupportedParameter = segment.suffix !== "" || !/^[^.:?/*]+$/.test(segment.name) || segment.optional && !final || segment.name === "wildcard" || parameterNames.has(segment.name);
|
|
31
|
+
if (unsupportedParameter) {
|
|
32
|
+
bindable = false;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
parameterNames.add(segment.name);
|
|
36
|
+
parameters.push({ name: segment.name, optional: segment.optional });
|
|
37
|
+
bindingSegments.push({
|
|
38
|
+
kind: "parameter",
|
|
39
|
+
name: segment.name,
|
|
40
|
+
optional: segment.optional,
|
|
41
|
+
wildcard: false
|
|
42
|
+
});
|
|
43
|
+
serializedSegments.push(`:${segment.name}${segment.optional ? "?" : ""}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!final || segment.name !== "*" || parameterNames.has("wildcard")) {
|
|
47
|
+
bindable = false;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
parameterNames.add("wildcard");
|
|
51
|
+
parameters.push({ name: "wildcard", optional: false });
|
|
52
|
+
bindingSegments.push({
|
|
53
|
+
kind: "parameter",
|
|
54
|
+
name: "wildcard",
|
|
55
|
+
optional: false,
|
|
56
|
+
wildcard: true
|
|
57
|
+
});
|
|
58
|
+
serializedSegments.push(segment.name);
|
|
59
|
+
}
|
|
60
|
+
if (this.canonicalPattern !== "/" && serializedSegments.join("/") !== this.canonicalPattern) {
|
|
61
|
+
bindable = false;
|
|
62
|
+
}
|
|
63
|
+
this.#bindable = bindable;
|
|
64
|
+
this.parameters = Object.freeze(parameters.map((parameter) => Object.freeze(parameter)));
|
|
65
|
+
this.#bindingSegments = Object.freeze(bindingSegments.map((segment) => Object.freeze(segment)));
|
|
66
|
+
Object.freeze(this);
|
|
67
|
+
}
|
|
68
|
+
parameters;
|
|
69
|
+
#bindable;
|
|
70
|
+
#bindingSegments;
|
|
71
|
+
/**
|
|
72
|
+
* Parse and validate the pattern subset that generated clients can bind.
|
|
73
|
+
*/
|
|
74
|
+
static parse(value) {
|
|
75
|
+
const pattern = new _BindableChannelPattern(value);
|
|
76
|
+
return pattern.#bindable ? pattern : null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Bind all required parameters and return one concrete channel name.
|
|
80
|
+
*/
|
|
81
|
+
bind(parameters = {}) {
|
|
82
|
+
if (this.canonicalPattern === "/") {
|
|
83
|
+
return "/";
|
|
84
|
+
}
|
|
85
|
+
const values = [];
|
|
86
|
+
for (const segment of this.#bindingSegments) {
|
|
87
|
+
if (segment.kind === "static") {
|
|
88
|
+
values.push(segment.value);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const value = Reflect.get(parameters, segment.name);
|
|
92
|
+
if (value === void 0 && segment.optional) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (value === void 0) {
|
|
96
|
+
const label = segment.wildcard ? "wildcard parameter" : `"${segment.name}" parameter`;
|
|
97
|
+
throw new Error(`Missing ${label} for channel: ${this.source}`);
|
|
98
|
+
}
|
|
99
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
100
|
+
throw new Error(`Invalid "${segment.name}" parameter for channel: ${this.source}`);
|
|
101
|
+
}
|
|
102
|
+
const bound = String(value);
|
|
103
|
+
if (bound.length === 0 || !segment.wildcard && bound.includes("/")) {
|
|
104
|
+
throw new Error(`Invalid "${segment.name}" parameter for channel: ${this.source}`);
|
|
105
|
+
}
|
|
106
|
+
values.push(bound);
|
|
107
|
+
}
|
|
108
|
+
return values.join("/") || "/";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
BindableChannelPattern
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=chunk-6PJ4ALJD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bindable_channel_pattern.ts"],"sourcesContent":["import { ChannelPatternSyntax } from './channel_pattern_syntax.js'\n\nexport interface BindableChannelParameter {\n readonly name: string\n readonly optional: boolean\n}\n\ntype BindableChannelSegment =\n | { readonly kind: 'static'; readonly value: string }\n | {\n readonly kind: 'parameter'\n readonly name: string\n readonly optional: boolean\n readonly wildcard: boolean\n }\n\n/**\n * The deliberately small subset of channel patterns that generated clients can bind.\n * Runtime routing keeps using the complete grammar through ChannelPattern.\n */\nexport class BindableChannelPattern extends ChannelPatternSyntax {\n readonly parameters: readonly BindableChannelParameter[]\n readonly #bindable: boolean\n readonly #bindingSegments: readonly BindableChannelSegment[]\n\n private constructor(private readonly source: string) {\n super(source)\n\n const bindingSegments: BindableChannelSegment[] = []\n const parameters: BindableChannelParameter[] = []\n const parameterNames = new Set<string>()\n const serializedSegments: string[] = []\n let bindable = true\n\n // Reject syntax that does not map to one complete segment per slash.\n if (\n this.canonicalPattern !== '/' &&\n (this.segments.length === 0 ||\n this.segments.length !== this.canonicalPattern.split('/').length)\n ) {\n bindable = false\n }\n\n for (const [index, segment] of this.segments.entries()) {\n const final = index === this.segments.length - 1\n\n if (segment.kind === 'static') {\n if (segment.value.length === 0 || /[:*?]/.test(segment.value)) {\n bindable = false\n break\n }\n bindingSegments.push({ kind: 'static', value: segment.value })\n serializedSegments.push(segment.value)\n continue\n }\n\n if (segment.kind === 'parameter') {\n // Generated parameters do not support suffixes or intermediate optional values.\n // The wildcard name is reserved for the final wildcard segment.\n const unsupportedParameter =\n segment.suffix !== '' ||\n !/^[^.:?/*]+$/.test(segment.name) ||\n (segment.optional && !final) ||\n segment.name === 'wildcard' ||\n parameterNames.has(segment.name)\n\n if (unsupportedParameter) {\n bindable = false\n break\n }\n\n parameterNames.add(segment.name)\n parameters.push({ name: segment.name, optional: segment.optional })\n bindingSegments.push({\n kind: 'parameter',\n name: segment.name,\n optional: segment.optional,\n wildcard: false,\n })\n serializedSegments.push(`:${segment.name}${segment.optional ? '?' : ''}`)\n continue\n }\n\n // A generated wildcard must be the final segment and must use the reserved key.\n if (!final || segment.name !== '*' || parameterNames.has('wildcard')) {\n bindable = false\n break\n }\n\n parameterNames.add('wildcard')\n parameters.push({ name: 'wildcard', optional: false })\n bindingSegments.push({\n kind: 'parameter',\n name: 'wildcard',\n optional: false,\n wildcard: true,\n })\n serializedSegments.push(segment.name)\n }\n\n // Reject syntax that the parser cannot reproduce without a change.\n if (this.canonicalPattern !== '/' && serializedSegments.join('/') !== this.canonicalPattern) {\n bindable = false\n }\n\n this.#bindable = bindable\n this.parameters = Object.freeze(parameters.map((parameter) => Object.freeze(parameter)))\n this.#bindingSegments = Object.freeze(bindingSegments.map((segment) => Object.freeze(segment)))\n Object.freeze(this)\n }\n\n /**\n * Parse and validate the pattern subset that generated clients can bind.\n */\n static parse(value: string): BindableChannelPattern | null {\n const pattern = new BindableChannelPattern(value)\n return pattern.#bindable ? pattern : null\n }\n\n /**\n * Bind all required parameters and return one concrete channel name.\n */\n bind(parameters: object = {}): string {\n if (this.canonicalPattern === '/') {\n return '/'\n }\n\n const values: string[] = []\n for (const segment of this.#bindingSegments) {\n if (segment.kind === 'static') {\n values.push(segment.value)\n continue\n }\n\n const value = Reflect.get(parameters, segment.name) as unknown\n\n // Omit only an optional parameter. All other segments need one value.\n if (value === undefined && segment.optional) {\n continue\n }\n if (value === undefined) {\n const label = segment.wildcard ? 'wildcard parameter' : `\"${segment.name}\" parameter`\n throw new Error(`Missing ${label} for channel: ${this.source}`)\n }\n if (typeof value !== 'string' && typeof value !== 'number') {\n throw new Error(`Invalid \"${segment.name}\" parameter for channel: ${this.source}`)\n }\n\n const bound = String(value)\n\n // An ordinary parameter must stay in one segment. A wildcard can contain slashes.\n if (bound.length === 0 || (!segment.wildcard && bound.includes('/'))) {\n throw new Error(`Invalid \"${segment.name}\" parameter for channel: ${this.source}`)\n }\n values.push(bound)\n }\n\n return values.join('/') || '/'\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared_types.ts"],"sourcesContent":["/**\n * Types shared between the server and the client.\n */\n\n/** WebSocket close code used when the server deliberately ends a client session. */\nexport const SERVER_DISCONNECT_CODE = 4000\n\n/**\n * Message sent by the client to a channel.\n */\nexport interface ChannelMessage {\n channel: string\n event: string\n data?: unknown\n}\n\n/**\n * Channel handler acknowledgement response.\n */\nexport interface ChannelAck<T = unknown> {\n ok: boolean\n data?: T\n error?: string\n}\n\n/** Extracts the server-to-client event map declared by a channel instance. */\nexport type ChannelServerEventsOf<T> = T extends { readonly $serverEvents: infer Events }\n ? Events\n : unknown\n\n/**\n * Client-to-server JSON protocol messages.\n */\nexport type ClientProtocolMessage =\n | { id?: string; type: 'subscribe'; channel: string }\n | { id?: string; type: 'unsubscribe'; channel: string }\n | { id?: string; type: 'message'; channel: string; event: string; data?: unknown }\n | { id?: string; type: 'whisper'; channel: string; event: string; data?: unknown }\n | { id?: string; type: 'ping' }\n\n/**\n * Server-to-client JSON protocol messages.\n */\nexport type ServerProtocolMessage =\n | { id?: string; type: 'ack'; ok: boolean; data?: unknown; error?: string }\n | { type: 'event'; channel?: string; event: string; data?: unknown }\n | { type: 'error'; error: string }\n | { id?: string; type: 'pong' }\n"],"mappings":";AAKO,IAAM,yBAAyB;","names":[]}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// src/channel_pattern_syntax.ts
|
|
2
|
+
var SEPARATOR = "/";
|
|
3
|
+
var SLASH_CODE = 47;
|
|
4
|
+
var COLON_CODE = 58;
|
|
5
|
+
var ASTERISK_CODE = 42;
|
|
6
|
+
var QUESTION_MARK_CODE = 63;
|
|
7
|
+
var ChannelPatternSyntax = class _ChannelPatternSyntax {
|
|
8
|
+
canonicalPattern;
|
|
9
|
+
segments;
|
|
10
|
+
#specificity;
|
|
11
|
+
constructor(value) {
|
|
12
|
+
const canonicalPattern = _ChannelPatternSyntax.#normalize(value);
|
|
13
|
+
if (canonicalPattern === SEPARATOR) {
|
|
14
|
+
this.canonicalPattern = canonicalPattern;
|
|
15
|
+
this.segments = Object.freeze([Object.freeze({ kind: "static", value: SEPARATOR })]);
|
|
16
|
+
} else {
|
|
17
|
+
const segments = [];
|
|
18
|
+
let remaining = canonicalPattern;
|
|
19
|
+
let index = -1;
|
|
20
|
+
let start = 0;
|
|
21
|
+
let length = remaining.length;
|
|
22
|
+
while (++index < length) {
|
|
23
|
+
const character = remaining.charCodeAt(index);
|
|
24
|
+
if (character === COLON_CODE) {
|
|
25
|
+
start = index + 1;
|
|
26
|
+
let optional = false;
|
|
27
|
+
let marker = 0;
|
|
28
|
+
let suffix = "";
|
|
29
|
+
while (index < length && remaining.charCodeAt(index) !== SLASH_CODE) {
|
|
30
|
+
const parameterCharacter = remaining.charCodeAt(index);
|
|
31
|
+
if (parameterCharacter === QUESTION_MARK_CODE) {
|
|
32
|
+
marker = index;
|
|
33
|
+
optional = true;
|
|
34
|
+
} else if (parameterCharacter === 46 && suffix.length === 0) {
|
|
35
|
+
marker = index;
|
|
36
|
+
suffix = remaining.slice(index);
|
|
37
|
+
}
|
|
38
|
+
index++;
|
|
39
|
+
}
|
|
40
|
+
segments.push({
|
|
41
|
+
kind: "parameter",
|
|
42
|
+
name: remaining.slice(start, marker || index),
|
|
43
|
+
optional,
|
|
44
|
+
suffix
|
|
45
|
+
});
|
|
46
|
+
remaining = remaining.slice(index);
|
|
47
|
+
length -= index;
|
|
48
|
+
index = 0;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (character === ASTERISK_CODE) {
|
|
52
|
+
segments.push({ kind: "wildcard", name: remaining.slice(index) });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
start = index;
|
|
56
|
+
while (index < length && remaining.charCodeAt(index) !== SLASH_CODE) {
|
|
57
|
+
index++;
|
|
58
|
+
}
|
|
59
|
+
segments.push({ kind: "static", value: remaining.slice(start, index) });
|
|
60
|
+
remaining = remaining.slice(index);
|
|
61
|
+
length -= index;
|
|
62
|
+
index = 0;
|
|
63
|
+
start = 0;
|
|
64
|
+
}
|
|
65
|
+
this.canonicalPattern = canonicalPattern;
|
|
66
|
+
this.segments = Object.freeze(segments.map((segment) => Object.freeze(segment)));
|
|
67
|
+
}
|
|
68
|
+
this.#specificity = Object.freeze(
|
|
69
|
+
this.segments.map((segment) => {
|
|
70
|
+
if (segment.kind === "static") return 40;
|
|
71
|
+
if (segment.kind === "wildcard") return 0;
|
|
72
|
+
return segment.optional ? 20 : 30;
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse one pattern and return its immutable syntax.
|
|
78
|
+
*/
|
|
79
|
+
static from(value) {
|
|
80
|
+
const syntax = new _ChannelPatternSyntax(value);
|
|
81
|
+
Object.freeze(syntax);
|
|
82
|
+
return syntax;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Match one channel name and return its decoded parameters.
|
|
86
|
+
*/
|
|
87
|
+
match(channelName) {
|
|
88
|
+
return this.#matchValues(_ChannelPatternSyntax.#split(channelName));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Compare this syntax with another syntax for runtime routing.
|
|
92
|
+
* Return a negative value when this syntax is more specific.
|
|
93
|
+
*/
|
|
94
|
+
compareSpecificity(other) {
|
|
95
|
+
const length = Math.max(this.#specificity.length, other.#specificity.length);
|
|
96
|
+
for (let index = 0; index < length; index++) {
|
|
97
|
+
const difference = (other.#specificity[index] ?? -1) - (this.#specificity[index] ?? -1);
|
|
98
|
+
if (difference !== 0) {
|
|
99
|
+
return difference;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return other.#specificity.length - this.#specificity.length;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Return the first matching syntax without splitting the channel name again.
|
|
106
|
+
*/
|
|
107
|
+
static firstMatch(patterns, channelName) {
|
|
108
|
+
const values = _ChannelPatternSyntax.#split(channelName);
|
|
109
|
+
for (const [index, pattern] of patterns.entries()) {
|
|
110
|
+
const params = pattern.#matchValues(values);
|
|
111
|
+
if (params) return { index, params };
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
#matchValues(values) {
|
|
116
|
+
const finalSegment = this.segments.at(-1);
|
|
117
|
+
const compatibleLength = this.segments.length === values.length || this.segments.length < values.length && finalSegment?.kind === "wildcard" || this.segments.length > values.length && finalSegment?.kind === "parameter" && finalSegment.optional;
|
|
118
|
+
if (!compatibleLength || !this.segments.every(
|
|
119
|
+
(segment, index) => _ChannelPatternSyntax.#segmentMatches(segment, values[index])
|
|
120
|
+
)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const parameters = {};
|
|
124
|
+
for (const [index, segment] of this.segments.entries()) {
|
|
125
|
+
if (values[index] === SEPARATOR) continue;
|
|
126
|
+
if (segment.kind === "wildcard") {
|
|
127
|
+
if (segment.name === "*") {
|
|
128
|
+
parameters[segment.name] = values.slice(index).map(_ChannelPatternSyntax.#decode).join(SEPARATOR);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
if (segment.kind === "parameter" && values[index] !== void 0) {
|
|
133
|
+
parameters[segment.name] = _ChannelPatternSyntax.#decode(
|
|
134
|
+
values[index].replace(segment.suffix, "")
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return parameters;
|
|
139
|
+
}
|
|
140
|
+
static #normalize(value) {
|
|
141
|
+
if (value === SEPARATOR) return value;
|
|
142
|
+
let normalized = value;
|
|
143
|
+
if (normalized.charCodeAt(0) === SLASH_CODE) {
|
|
144
|
+
normalized = normalized.slice(1);
|
|
145
|
+
}
|
|
146
|
+
if (normalized.charCodeAt(normalized.length - 1) === SLASH_CODE) {
|
|
147
|
+
normalized = normalized.slice(0, -1);
|
|
148
|
+
}
|
|
149
|
+
return normalized;
|
|
150
|
+
}
|
|
151
|
+
static #split(value) {
|
|
152
|
+
const normalized = _ChannelPatternSyntax.#normalize(value);
|
|
153
|
+
return normalized === SEPARATOR ? [SEPARATOR] : normalized.split(SEPARATOR);
|
|
154
|
+
}
|
|
155
|
+
static #decode(value) {
|
|
156
|
+
try {
|
|
157
|
+
return decodeURIComponent(value);
|
|
158
|
+
} catch {
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
static #segmentMatches(segment, value) {
|
|
163
|
+
if (segment.kind === "static") {
|
|
164
|
+
return segment.value === value;
|
|
165
|
+
}
|
|
166
|
+
if (value === SEPARATOR) {
|
|
167
|
+
return segment.kind === "wildcard" || segment.optional;
|
|
168
|
+
}
|
|
169
|
+
if (value === "") {
|
|
170
|
+
return segment.kind === "wildcard" || segment.suffix === "";
|
|
171
|
+
}
|
|
172
|
+
if (value === void 0) {
|
|
173
|
+
return segment.kind === "wildcard" || segment.suffix === "";
|
|
174
|
+
}
|
|
175
|
+
return segment.kind === "wildcard" || value.endsWith(segment.suffix);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
ChannelPatternSyntax
|
|
181
|
+
};
|
|
182
|
+
//# sourceMappingURL=chunk-E6ILCRL3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/channel_pattern_syntax.ts"],"sourcesContent":["/**\n * Pattern parsing and matching semantics are adapted from @poppinss/matchit.\n * Copyright 2020 Harminder Virk and contributors, used under the MIT license.\n * See the repository LICENSE.md for the license text.\n */\n\nconst SEPARATOR = '/'\nconst SLASH_CODE = 47\nconst COLON_CODE = 58\nconst ASTERISK_CODE = 42\nconst QUESTION_MARK_CODE = 63\n\ntype ChannelPatternSegment =\n | { readonly kind: 'static'; readonly value: string }\n | {\n readonly kind: 'parameter'\n readonly name: string\n readonly optional: boolean\n readonly suffix: string\n }\n | { readonly kind: 'wildcard'; readonly name: string }\n\nexport interface ChannelPatternSyntaxMatch {\n readonly index: number\n readonly params: Record<string, string>\n}\n\n/**\n * Immutable parsed syntax shared by runtime matching and generated binding.\n */\nexport class ChannelPatternSyntax {\n readonly canonicalPattern: string\n protected readonly segments: readonly ChannelPatternSegment[]\n readonly #specificity: readonly number[]\n\n protected constructor(value: string) {\n const canonicalPattern = ChannelPatternSyntax.#normalize(value)\n if (canonicalPattern === SEPARATOR) {\n this.canonicalPattern = canonicalPattern\n this.segments = Object.freeze([Object.freeze({ kind: 'static', value: SEPARATOR })])\n } else {\n const segments: ChannelPatternSegment[] = []\n let remaining = canonicalPattern\n let index = -1\n let start = 0\n let length = remaining.length\n\n while (++index < length) {\n const character = remaining.charCodeAt(index)\n\n if (character === COLON_CODE) {\n // Read the parameter name, its optional marker, and its suffix.\n start = index + 1\n let optional = false\n let marker = 0\n let suffix = ''\n\n while (index < length && remaining.charCodeAt(index) !== SLASH_CODE) {\n const parameterCharacter = remaining.charCodeAt(index)\n if (parameterCharacter === QUESTION_MARK_CODE) {\n marker = index\n optional = true\n } else if (parameterCharacter === 46 && suffix.length === 0) {\n marker = index\n suffix = remaining.slice(index)\n }\n index++\n }\n\n segments.push({\n kind: 'parameter',\n name: remaining.slice(start, marker || index),\n optional,\n suffix,\n })\n remaining = remaining.slice(index)\n length -= index\n index = 0\n continue\n }\n\n if (character === ASTERISK_CODE) {\n // A wildcard consumes the rest of the pattern.\n segments.push({ kind: 'wildcard', name: remaining.slice(index) })\n continue\n }\n\n start = index\n while (index < length && remaining.charCodeAt(index) !== SLASH_CODE) {\n index++\n }\n segments.push({ kind: 'static', value: remaining.slice(start, index) })\n remaining = remaining.slice(index)\n length -= index\n index = 0\n start = 0\n }\n\n this.canonicalPattern = canonicalPattern\n this.segments = Object.freeze(segments.map((segment) => Object.freeze(segment)))\n }\n\n this.#specificity = Object.freeze(\n this.segments.map((segment) => {\n if (segment.kind === 'static') return 40\n if (segment.kind === 'wildcard') return 0\n return segment.optional ? 20 : 30\n })\n )\n }\n\n /**\n * Parse one pattern and return its immutable syntax.\n */\n static from(value: string): ChannelPatternSyntax {\n const syntax = new ChannelPatternSyntax(value)\n Object.freeze(syntax)\n return syntax\n }\n\n /**\n * Match one channel name and return its decoded parameters.\n */\n match(channelName: string): Record<string, string> | null {\n return this.#matchValues(ChannelPatternSyntax.#split(channelName))\n }\n\n /**\n * Compare this syntax with another syntax for runtime routing.\n * Return a negative value when this syntax is more specific.\n */\n compareSpecificity(other: ChannelPatternSyntax): number {\n const length = Math.max(this.#specificity.length, other.#specificity.length)\n\n for (let index = 0; index < length; index++) {\n const difference = (other.#specificity[index] ?? -1) - (this.#specificity[index] ?? -1)\n if (difference !== 0) {\n return difference\n }\n }\n\n return other.#specificity.length - this.#specificity.length\n }\n\n /**\n * Return the first matching syntax without splitting the channel name again.\n */\n static firstMatch(\n patterns: readonly ChannelPatternSyntax[],\n channelName: string\n ): ChannelPatternSyntaxMatch | null {\n const values = ChannelPatternSyntax.#split(channelName)\n for (const [index, pattern] of patterns.entries()) {\n const params = pattern.#matchValues(values)\n if (params) return { index, params }\n }\n return null\n }\n\n #matchValues(values: string[]): Record<string, string> | null {\n const finalSegment = this.segments.at(-1)\n\n // A wildcard can consume extra values. A final optional parameter can be absent.\n const compatibleLength =\n this.segments.length === values.length ||\n (this.segments.length < values.length && finalSegment?.kind === 'wildcard') ||\n (this.segments.length > values.length &&\n finalSegment?.kind === 'parameter' &&\n finalSegment.optional)\n\n if (\n !compatibleLength ||\n !this.segments.every((segment, index) =>\n ChannelPatternSyntax.#segmentMatches(segment, values[index])\n )\n ) {\n return null\n }\n\n const parameters: Record<string, string> = {}\n for (const [index, segment] of this.segments.entries()) {\n // The root separator does not produce a parameter.\n if (values[index] === SEPARATOR) continue\n\n if (segment.kind === 'wildcard') {\n // Decode each wildcard value before the values are joined again.\n if (segment.name === '*') {\n parameters[segment.name] = values\n .slice(index)\n .map(ChannelPatternSyntax.#decode)\n .join(SEPARATOR)\n }\n break\n }\n\n if (segment.kind === 'parameter' && values[index] !== undefined) {\n // Remove the static suffix before the parameter is decoded.\n parameters[segment.name] = ChannelPatternSyntax.#decode(\n values[index].replace(segment.suffix, '')\n )\n }\n }\n return parameters\n }\n\n static #normalize(value: string): string {\n if (value === SEPARATOR) return value\n\n // Remove one leading slash and one trailing slash. This keeps Matchit behavior.\n let normalized = value\n if (normalized.charCodeAt(0) === SLASH_CODE) {\n normalized = normalized.slice(1)\n }\n if (normalized.charCodeAt(normalized.length - 1) === SLASH_CODE) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n }\n\n static #split(value: string): string[] {\n const normalized = ChannelPatternSyntax.#normalize(value)\n return normalized === SEPARATOR ? [SEPARATOR] : normalized.split(SEPARATOR)\n }\n\n static #decode(value: string): string {\n try {\n return decodeURIComponent(value)\n } catch {\n // Keep invalid encoded input unchanged. Matchit uses the same fallback.\n return value\n }\n }\n\n static #segmentMatches(segment: ChannelPatternSegment, value: string | undefined): boolean {\n if (segment.kind === 'static') {\n return segment.value === value\n }\n if (value === SEPARATOR) {\n return segment.kind === 'wildcard' || segment.optional\n }\n if (value === '') {\n return segment.kind === 'wildcard' || segment.suffix === ''\n }\n if (value === undefined) {\n return segment.kind === 'wildcard' || segment.suffix === ''\n }\n return segment.kind === 'wildcard' || value.endsWith(segment.suffix)\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
// src/decorators.ts
|
|
2
|
-
var
|
|
2
|
+
var decoratedHandlers = /* @__PURE__ */ new WeakMap();
|
|
3
3
|
function onMessage(event) {
|
|
4
4
|
return (target, propertyKey, descriptor) => {
|
|
5
5
|
if (typeof descriptor?.value !== "function") {
|
|
6
6
|
throw new Error(`@onMessage("${event}") can only be used on methods`);
|
|
7
7
|
}
|
|
8
|
-
const
|
|
9
|
-
const handlers =
|
|
8
|
+
const constructor = target.constructor;
|
|
9
|
+
const handlers = [...decoratedHandlers.get(constructor) ?? []];
|
|
10
10
|
handlers.push({
|
|
11
11
|
event,
|
|
12
12
|
methodName: propertyKey
|
|
13
13
|
});
|
|
14
|
-
|
|
14
|
+
decoratedHandlers.set(constructor, handlers);
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
function getDecoratedChannelHandlers(instance) {
|
|
18
18
|
const constructors = [];
|
|
19
19
|
let proto = Object.getPrototypeOf(instance);
|
|
20
20
|
while (proto && proto !== Object.prototype) {
|
|
21
|
-
|
|
21
|
+
const constructor = Reflect.get(proto, "constructor");
|
|
22
|
+
if (typeof constructor === "function") {
|
|
23
|
+
constructors.unshift(constructor);
|
|
24
|
+
}
|
|
22
25
|
proto = Object.getPrototypeOf(proto);
|
|
23
26
|
}
|
|
24
27
|
const handlers = {};
|
|
25
|
-
for (const
|
|
26
|
-
const definitions =
|
|
28
|
+
for (const constructor of constructors) {
|
|
29
|
+
const definitions = decoratedHandlers.get(constructor) ?? [];
|
|
27
30
|
for (const definition of definitions) {
|
|
28
|
-
const handler = instance
|
|
31
|
+
const handler = Reflect.get(instance, definition.methodName);
|
|
29
32
|
if (typeof handler !== "function") {
|
|
30
33
|
continue;
|
|
31
34
|
}
|
|
@@ -39,4 +42,4 @@ export {
|
|
|
39
42
|
onMessage,
|
|
40
43
|
getDecoratedChannelHandlers
|
|
41
44
|
};
|
|
42
|
-
//# sourceMappingURL=chunk-
|
|
45
|
+
//# sourceMappingURL=chunk-G75J233Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/decorators.ts"],"sourcesContent":["import type { AuthenticatedSocket } from './types.js'\n\ntype DecoratedChannelHandler<User> = (socket: AuthenticatedSocket<User>, data: unknown) => unknown\n\ntype ChannelMessageHandlerDefinition = {\n event: string\n methodName: string | symbol\n}\n\nconst decoratedHandlers = new WeakMap<object, ChannelMessageHandlerDefinition[]>()\n\n/**\n * Method decorator used to bind an incoming channel event to a method.\n */\nexport function onMessage<EventName extends string>(event: EventName): MethodDecorator {\n return (target, propertyKey, descriptor) => {\n if (typeof descriptor?.value !== 'function') {\n throw new Error(`@onMessage(\"${event}\") can only be used on methods`)\n }\n\n const constructor = target.constructor\n const handlers = [...(decoratedHandlers.get(constructor) ?? [])]\n\n handlers.push({\n event,\n methodName: propertyKey,\n })\n\n decoratedHandlers.set(constructor, handlers)\n }\n}\n\n/**\n * Resolve decorated handlers for a channel instance, including inherited ones.\n * Child classes override parent handlers when the event name is the same.\n */\nexport function getDecoratedChannelHandlers<User = unknown>(\n instance: object\n): Record<string, DecoratedChannelHandler<User>> {\n const constructors: object[] = []\n\n let proto = Object.getPrototypeOf(instance)\n while (proto && proto !== Object.prototype) {\n const constructor = Reflect.get(proto, 'constructor')\n if (typeof constructor === 'function') {\n constructors.unshift(constructor)\n }\n proto = Object.getPrototypeOf(proto)\n }\n\n const handlers: Record<string, unknown> = {}\n\n for (const constructor of constructors) {\n const definitions = decoratedHandlers.get(constructor) ?? []\n\n for (const definition of definitions) {\n const handler = Reflect.get(instance, definition.methodName)\n if (typeof handler !== 'function') {\n continue\n }\n\n handlers[definition.event] = handler.bind(instance)\n }\n }\n\n return handlers as Record<string, DecoratedChannelHandler<User>>\n}\n"],"mappings":";AASA,IAAM,oBAAoB,oBAAI,QAAmD;AAK1E,SAAS,UAAoC,OAAmC;AACrF,SAAO,CAAC,QAAQ,aAAa,eAAe;AAC1C,QAAI,OAAO,YAAY,UAAU,YAAY;AAC3C,YAAM,IAAI,MAAM,eAAe,KAAK,gCAAgC;AAAA,IACtE;AAEA,UAAM,cAAc,OAAO;AAC3B,UAAM,WAAW,CAAC,GAAI,kBAAkB,IAAI,WAAW,KAAK,CAAC,CAAE;AAE/D,aAAS,KAAK;AAAA,MACZ;AAAA,MACA,YAAY;AAAA,IACd,CAAC;AAED,sBAAkB,IAAI,aAAa,QAAQ;AAAA,EAC7C;AACF;AAMO,SAAS,4BACd,UAC+C;AAC/C,QAAM,eAAyB,CAAC;AAEhC,MAAI,QAAQ,OAAO,eAAe,QAAQ;AAC1C,SAAO,SAAS,UAAU,OAAO,WAAW;AAC1C,UAAM,cAAc,QAAQ,IAAI,OAAO,aAAa;AACpD,QAAI,OAAO,gBAAgB,YAAY;AACrC,mBAAa,QAAQ,WAAW;AAAA,IAClC;AACA,YAAQ,OAAO,eAAe,KAAK;AAAA,EACrC;AAEA,QAAM,WAAoC,CAAC;AAE3C,aAAW,eAAe,cAAc;AACtC,UAAM,cAAc,kBAAkB,IAAI,WAAW,KAAK,CAAC;AAE3D,eAAW,cAAc,aAAa;AACpC,YAAM,UAAU,QAAQ,IAAI,UAAU,WAAW,UAAU;AAC3D,UAAI,OAAO,YAAY,YAAY;AACjC;AAAA,MACF;AAEA,eAAS,WAAW,KAAK,IAAI,QAAQ,KAAK,QAAQ;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/fake_socket.ts
|
|
2
|
+
import { AssertionError } from "assert";
|
|
3
|
+
import { isDeepStrictEqual } from "util";
|
|
4
|
+
var SocketFake = class {
|
|
5
|
+
#emissions = [];
|
|
6
|
+
#restore;
|
|
7
|
+
constructor(restore) {
|
|
8
|
+
this.#restore = restore;
|
|
9
|
+
}
|
|
10
|
+
get emissions() {
|
|
11
|
+
return [...this.#emissions];
|
|
12
|
+
}
|
|
13
|
+
record(emission) {
|
|
14
|
+
this.#emissions.push({
|
|
15
|
+
...emission,
|
|
16
|
+
..."except" in emission && emission.except ? { except: [...emission.except] } : {}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
dispatch(emission) {
|
|
20
|
+
this.record(emission);
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.#emissions = [];
|
|
24
|
+
}
|
|
25
|
+
restore() {
|
|
26
|
+
this.#restore();
|
|
27
|
+
}
|
|
28
|
+
[Symbol.dispose]() {
|
|
29
|
+
this.restore();
|
|
30
|
+
}
|
|
31
|
+
assertBroadcasted(event, query) {
|
|
32
|
+
this.#assertEmitted(
|
|
33
|
+
(emission) => emission.target === "global" && emission.event === event,
|
|
34
|
+
query,
|
|
35
|
+
`Expected socket event "${event}" to have been broadcasted`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
assertNotBroadcasted(event, query) {
|
|
39
|
+
this.#assertNotEmitted(
|
|
40
|
+
(emission) => emission.target === "global" && emission.event === event,
|
|
41
|
+
query,
|
|
42
|
+
`Expected socket event "${event}" not to have been broadcasted`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
assertEmittedTo(channel, event, query) {
|
|
46
|
+
this.#assertEmitted(
|
|
47
|
+
(emission) => emission.target === "channel" && emission.channel === channel && emission.event === event,
|
|
48
|
+
query,
|
|
49
|
+
`Expected socket event "${event}" to have been emitted to channel "${channel}"`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
assertNotEmittedTo(channel, event, query) {
|
|
53
|
+
this.#assertNotEmitted(
|
|
54
|
+
(emission) => emission.target === "channel" && emission.channel === channel && emission.event === event,
|
|
55
|
+
query,
|
|
56
|
+
`Expected socket event "${event}" not to have been emitted to channel "${channel}"`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
assertCount(count, query) {
|
|
60
|
+
const actual = this.#matching(query).length;
|
|
61
|
+
if (actual !== count) {
|
|
62
|
+
throw new AssertionError({
|
|
63
|
+
message: `Expected ${count} socket event(s) to have been emitted, but found ${actual}`,
|
|
64
|
+
actual,
|
|
65
|
+
expected: count,
|
|
66
|
+
operator: "strictEqual"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
#assertEmitted(predicate, query, message) {
|
|
71
|
+
const matching = this.#emissions.filter((emission) => {
|
|
72
|
+
return predicate(emission) && this.#matchesQuery(emission, query);
|
|
73
|
+
});
|
|
74
|
+
if (matching.length === 0) {
|
|
75
|
+
throw new AssertionError({
|
|
76
|
+
message,
|
|
77
|
+
actual: this.#emissions,
|
|
78
|
+
expected: query,
|
|
79
|
+
operator: "includes"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
#assertNotEmitted(predicate, query, message) {
|
|
84
|
+
const matching = this.#emissions.filter((emission) => {
|
|
85
|
+
return predicate(emission) && this.#matchesQuery(emission, query);
|
|
86
|
+
});
|
|
87
|
+
if (matching.length > 0) {
|
|
88
|
+
throw new AssertionError({
|
|
89
|
+
message,
|
|
90
|
+
actual: matching,
|
|
91
|
+
expected: [],
|
|
92
|
+
operator: "doesNotInclude"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
#matching(query) {
|
|
97
|
+
return this.#emissions.filter((emission) => {
|
|
98
|
+
if (!query) return true;
|
|
99
|
+
if (query.target && emission.target !== query.target) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (query?.event && emission.event !== query.event) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (query.channel && (!("channel" in emission) || emission.channel !== query.channel)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return this.#matchesQuery(emission, query);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
#matchesQuery(emission, query) {
|
|
112
|
+
if (!query) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if ("data" in query) {
|
|
116
|
+
if (typeof query.data === "function") {
|
|
117
|
+
if (!query.data(emission.data)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
} else if (!isDeepStrictEqual(emission.data, query.data)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if ("except" in query) {
|
|
125
|
+
if (typeof query.except === "function") {
|
|
126
|
+
if (!query.except("except" in emission ? emission.except : void 0)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
} else if (!isDeepStrictEqual(("except" in emission ? emission.except : void 0) ?? [], query.except)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
SocketFake
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=chunk-IUQ3GYZZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fake_socket.ts"],"sourcesContent":["import { AssertionError } from 'node:assert'\nimport { isDeepStrictEqual } from 'node:util'\nimport type { SocketEmission, SocketEmissionSink, SocketEmissionTarget } from './broadcasting.js'\n\nexport type SocketFakeTarget = SocketEmissionTarget\nexport type SocketFakeEmission = SocketEmission\n\ntype SocketFakeMatcher<Value> = Value | ((value: Value) => boolean)\n\nexport interface SocketFakeEmissionQuery {\n data?: unknown | ((data: unknown) => boolean)\n except?: SocketFakeMatcher<string[] | undefined>\n}\n\nexport interface SocketFakeCountQuery extends SocketFakeEmissionQuery {\n target?: SocketFakeTarget\n event?: string\n channel?: string\n}\n\nexport class SocketFake implements SocketEmissionSink {\n #emissions: SocketFakeEmission[] = []\n #restore: () => void\n\n constructor(restore: () => void) {\n this.#restore = restore\n }\n\n get emissions(): SocketFakeEmission[] {\n return [...this.#emissions]\n }\n\n record(emission: SocketFakeEmission): void {\n this.#emissions.push({\n ...emission,\n ...('except' in emission && emission.except ? { except: [...emission.except] } : {}),\n })\n }\n\n dispatch(emission: SocketFakeEmission): void {\n this.record(emission)\n }\n\n clear(): void {\n this.#emissions = []\n }\n\n restore(): void {\n this.#restore()\n }\n\n [Symbol.dispose](): void {\n this.restore()\n }\n\n assertBroadcasted(event: string, query?: SocketFakeEmissionQuery): void {\n this.#assertEmitted(\n (emission) => emission.target === 'global' && emission.event === event,\n query,\n `Expected socket event \"${event}\" to have been broadcasted`\n )\n }\n\n assertNotBroadcasted(event: string, query?: SocketFakeEmissionQuery): void {\n this.#assertNotEmitted(\n (emission) => emission.target === 'global' && emission.event === event,\n query,\n `Expected socket event \"${event}\" not to have been broadcasted`\n )\n }\n\n assertEmittedTo(channel: string, event: string, query?: SocketFakeEmissionQuery): void {\n this.#assertEmitted(\n (emission) =>\n emission.target === 'channel' && emission.channel === channel && emission.event === event,\n query,\n `Expected socket event \"${event}\" to have been emitted to channel \"${channel}\"`\n )\n }\n\n assertNotEmittedTo(channel: string, event: string, query?: SocketFakeEmissionQuery): void {\n this.#assertNotEmitted(\n (emission) =>\n emission.target === 'channel' && emission.channel === channel && emission.event === event,\n query,\n `Expected socket event \"${event}\" not to have been emitted to channel \"${channel}\"`\n )\n }\n\n assertCount(count: number, query?: SocketFakeCountQuery): void {\n const actual = this.#matching(query).length\n\n if (actual !== count) {\n throw new AssertionError({\n message: `Expected ${count} socket event(s) to have been emitted, but found ${actual}`,\n actual,\n expected: count,\n operator: 'strictEqual',\n })\n }\n }\n\n #assertEmitted(\n predicate: (emission: SocketFakeEmission) => boolean,\n query: SocketFakeEmissionQuery | undefined,\n message: string\n ): void {\n const matching = this.#emissions.filter((emission) => {\n return predicate(emission) && this.#matchesQuery(emission, query)\n })\n\n if (matching.length === 0) {\n throw new AssertionError({\n message,\n actual: this.#emissions,\n expected: query,\n operator: 'includes',\n })\n }\n }\n\n #assertNotEmitted(\n predicate: (emission: SocketFakeEmission) => boolean,\n query: SocketFakeEmissionQuery | undefined,\n message: string\n ): void {\n const matching = this.#emissions.filter((emission) => {\n return predicate(emission) && this.#matchesQuery(emission, query)\n })\n\n if (matching.length > 0) {\n throw new AssertionError({\n message,\n actual: matching,\n expected: [],\n operator: 'doesNotInclude',\n })\n }\n }\n\n #matching(query: SocketFakeCountQuery | undefined): SocketFakeEmission[] {\n return this.#emissions.filter((emission) => {\n if (!query) return true\n\n if (query.target && emission.target !== query.target) {\n return false\n }\n\n if (query?.event && emission.event !== query.event) {\n return false\n }\n\n if (query.channel && (!('channel' in emission) || emission.channel !== query.channel)) {\n return false\n }\n\n return this.#matchesQuery(emission, query)\n })\n }\n\n #matchesQuery(emission: SocketFakeEmission, query: SocketFakeEmissionQuery | undefined): boolean {\n if (!query) {\n return true\n }\n\n if ('data' in query) {\n if (typeof query.data === 'function') {\n if (!query.data(emission.data)) {\n return false\n }\n } else if (!isDeepStrictEqual(emission.data, query.data)) {\n return false\n }\n }\n\n if ('except' in query) {\n if (typeof query.except === 'function') {\n if (!query.except('except' in emission ? emission.except : undefined)) {\n return false\n }\n } else if (\n !isDeepStrictEqual(('except' in emission ? emission.except : undefined) ?? [], query.except)\n ) {\n return false\n }\n }\n\n return true\n }\n}\n"],"mappings":"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","names":[]}
|