@rlanz/socket 0.0.1-3 → 0.0.1-5

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 (55) hide show
  1. package/README.md +467 -41
  2. package/build/chunk-ANT2E3LT.js +136 -0
  3. package/build/chunk-ANT2E3LT.js.map +1 -0
  4. package/build/chunk-B4Y3TNDI.js +159 -0
  5. package/build/chunk-B4Y3TNDI.js.map +1 -0
  6. package/build/{chunk-GKAD2UOA.js → chunk-D3HUBCBW.js} +1 -1
  7. package/build/chunk-D3HUBCBW.js.map +1 -0
  8. package/build/chunk-SFAY2ZA4.js +28 -0
  9. package/build/chunk-SFAY2ZA4.js.map +1 -0
  10. package/build/{chunk-XUDFDJME.js → chunk-SHH6U4CI.js} +12 -9
  11. package/build/chunk-SHH6U4CI.js.map +1 -0
  12. package/build/chunk-YAX5EHHB.js +453 -0
  13. package/build/chunk-YAX5EHHB.js.map +1 -0
  14. package/build/framework-CvxgHwbv.d.ts +12 -0
  15. package/build/index.d.ts +11 -3
  16. package/build/index.js +32 -6
  17. package/build/index.js.map +1 -1
  18. package/build/providers/socket_provider.d.ts +4 -3
  19. package/build/providers/socket_provider.js +1774 -57
  20. package/build/providers/socket_provider.js.map +1 -1
  21. package/build/services/socket.d.ts +4 -3
  22. package/build/socket_service-mybqv1hj.d.ts +105 -0
  23. package/build/src/assembler_hook.d.ts +13 -0
  24. package/build/src/assembler_hook.js +249 -0
  25. package/build/src/assembler_hook.js.map +1 -0
  26. package/build/src/client/index.d.ts +46 -12
  27. package/build/src/client/index.js +223 -81
  28. package/build/src/client/index.js.map +1 -1
  29. package/build/src/client/react.d.ts +27 -0
  30. package/build/src/client/react.js +69 -0
  31. package/build/src/client/react.js.map +1 -0
  32. package/build/src/client/svelte.d.ts +22 -0
  33. package/build/src/client/svelte.js +82 -0
  34. package/build/src/client/svelte.js.map +1 -0
  35. package/build/src/client/types.d.ts +68 -10
  36. package/build/src/client/vue.d.ts +25 -0
  37. package/build/src/client/vue.js +81 -0
  38. package/build/src/client/vue.js.map +1 -0
  39. package/build/src/decorators.d.ts +4 -4
  40. package/build/src/decorators.js +1 -1
  41. package/build/src/health_check.d.ts +4 -3
  42. package/build/src/otel.js +4 -5
  43. package/build/src/otel.js.map +1 -1
  44. package/build/src/testing.d.ts +54 -0
  45. package/build/src/testing.js +7 -0
  46. package/build/src/testing.js.map +1 -0
  47. package/build/src/types.d.ts +2 -2
  48. package/build/{types-BnOi46bE.d.ts → types-C7Q36ryo.d.ts} +144 -79
  49. package/package.json +43 -8
  50. package/build/chunk-GKAD2UOA.js.map +0 -1
  51. package/build/chunk-NRWRLZAO.js +0 -1794
  52. package/build/chunk-NRWRLZAO.js.map +0 -1
  53. package/build/chunk-XUDFDJME.js.map +0 -1
  54. package/build/shared_types-Dw9AphfO.d.ts +0 -21
  55. package/build/socket_service-BN1g3ENk.d.ts +0 -144
@@ -0,0 +1,136 @@
1
+ import {
2
+ callClientHandler
3
+ } from "./chunk-SFAY2ZA4.js";
4
+
5
+ // src/client/framework.ts
6
+ var socketEntries = /* @__PURE__ */ new WeakMap();
7
+ var socketOwners = /* @__PURE__ */ new WeakMap();
8
+ function reportSubscriptionError(error) {
9
+ queueMicrotask(() => {
10
+ console.error(error);
11
+ });
12
+ }
13
+ function attachEvents(entry) {
14
+ for (const [event, managed] of entry.events) {
15
+ entry.channel.$listen(event, managed.dispatch);
16
+ }
17
+ }
18
+ function getEntry(socket, name) {
19
+ let entries = socketEntries.get(socket);
20
+ if (!entries) {
21
+ entries = /* @__PURE__ */ new Map();
22
+ socketEntries.set(socket, entries);
23
+ }
24
+ const channel = socket.channel(name);
25
+ let entry = entries.get(name);
26
+ if (!entry || entry.channel !== channel) {
27
+ entry = {
28
+ channel,
29
+ socket,
30
+ leases: /* @__PURE__ */ new Set(),
31
+ events: /* @__PURE__ */ new Map(),
32
+ lifecycle: void 0
33
+ };
34
+ entries.set(name, entry);
35
+ }
36
+ return entry;
37
+ }
38
+ var ChannelLease = class {
39
+ entry;
40
+ #released = false;
41
+ #listeners = [];
42
+ constructor(entry) {
43
+ this.entry = entry;
44
+ }
45
+ listen(event, handler) {
46
+ if (this.#released) return;
47
+ let managed = this.entry.events.get(event);
48
+ if (!managed) {
49
+ const handlers = /* @__PURE__ */ new Set();
50
+ managed = {
51
+ handlers,
52
+ dispatch: (data) => {
53
+ for (const listener of handlers) callClientHandler(listener, data);
54
+ }
55
+ };
56
+ this.entry.events.set(event, managed);
57
+ this.entry.channel.$listen(event, managed.dispatch);
58
+ }
59
+ const registration = (data) => handler(data);
60
+ managed.handlers.add(registration);
61
+ this.#listeners.push({ event, handler: registration });
62
+ }
63
+ release() {
64
+ if (this.#released) return;
65
+ this.#released = true;
66
+ for (const { event, handler } of this.#listeners) {
67
+ const managed = this.entry.events.get(event);
68
+ managed?.handlers.delete(handler);
69
+ if (managed?.handlers.size === 0) {
70
+ this.entry.channel.$stopListening(event, managed.dispatch);
71
+ this.entry.events.delete(event);
72
+ }
73
+ }
74
+ this.#listeners = [];
75
+ this.entry.leases.delete(this);
76
+ queueMicrotask(() => {
77
+ if (this.entry.leases.size > 0) return;
78
+ const lifecycle = this.entry.lifecycle;
79
+ this.entry.lifecycle = void 0;
80
+ void lifecycle?.release().then(() => {
81
+ if (this.entry.leases.size > 0 || this.entry.lifecycle) return;
82
+ const entries = socketEntries.get(this.entry.socket);
83
+ if (entries?.get(this.entry.channel.name) === this.entry) {
84
+ entries.delete(this.entry.channel.name);
85
+ }
86
+ });
87
+ });
88
+ }
89
+ };
90
+ function acquireChannel(socket, name) {
91
+ const entry = getEntry(socket, name);
92
+ const lease = new ChannelLease(entry);
93
+ entry.leases.add(lease);
94
+ if (!entry.lifecycle) {
95
+ const lifecycle = entry.channel.$acquire();
96
+ entry.lifecycle = lifecycle;
97
+ attachEvents(entry);
98
+ void lifecycle.ready.then(() => {
99
+ if (entry.lifecycle === lifecycle) attachEvents(entry);
100
+ }).catch((error) => {
101
+ if (entry.lifecycle === lifecycle && entry.leases.size > 0 && entry.socket.connected) {
102
+ reportSubscriptionError(error);
103
+ }
104
+ });
105
+ }
106
+ return { channel: entry.channel, lease };
107
+ }
108
+ function acquireSocketOwnership(socket) {
109
+ let owner = socketOwners.get(socket);
110
+ if (!owner) {
111
+ owner = { count: 0, generation: 0 };
112
+ socketOwners.set(socket, owner);
113
+ socket.connect().catch(reportSubscriptionError);
114
+ }
115
+ owner.count++;
116
+ owner.generation++;
117
+ let released = false;
118
+ return () => {
119
+ if (released) return;
120
+ released = true;
121
+ owner.count--;
122
+ const generation = ++owner.generation;
123
+ queueMicrotask(() => {
124
+ if (owner.count === 0 && owner.generation === generation) {
125
+ socket.disconnect();
126
+ socketOwners.delete(socket);
127
+ }
128
+ });
129
+ };
130
+ }
131
+
132
+ export {
133
+ acquireChannel,
134
+ acquireSocketOwnership
135
+ };
136
+ //# sourceMappingURL=chunk-ANT2E3LT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/framework.ts"],"sourcesContent":["import type { Channel } from './channel.js'\nimport type { Socket } from './socket.js'\nimport type { ChannelContract, ChannelContractFor, EventHandler } from './types.js'\nimport { callClientHandler } from './callback.js'\n\ntype AnySocket = Socket<any>\ntype UnknownContract = ChannelContract<unknown, unknown>\ntype ServerEvents<Contract> =\n Contract extends ChannelContract<unknown, infer Events> ? Events : unknown\ntype IsUnknown<Value> = unknown extends Value ? true : false\n\nexport type FrameworkChannel<Registry, Name extends string> = string extends Name\n ? Channel<UnknownContract>\n : [Registry] extends [never]\n ? Channel<UnknownContract>\n : ChannelContractFor<Registry, Name> extends infer Contract\n ? [Contract] extends [never]\n ? never\n : Channel<Contract>\n : never\n\nexport type FrameworkChannelName<Registry, Name extends string> = Name &\n (FrameworkChannel<Registry, Name> extends never ? never : unknown)\n\nexport type FrameworkChannelEvent<Registry, Name extends string> =\n FrameworkChannel<Registry, Name> extends Channel<infer Contract>\n ? IsUnknown<ServerEvents<Contract>> extends true\n ? string\n : Extract<keyof ServerEvents<Contract>, string>\n : never\n\nexport type FrameworkChannelEventPayload<\n Registry,\n Name extends string,\n Event extends FrameworkChannelEvent<Registry, Name>,\n> =\n FrameworkChannel<Registry, Name> extends Channel<infer Contract>\n ? IsUnknown<ServerEvents<Contract>> extends true\n ? unknown\n : Event extends keyof ServerEvents<Contract>\n ? ServerEvents<Contract>[Event]\n : never\n : never\n\ninterface ManagedEvent {\n handlers: Set<EventHandler<any>>\n dispatch: EventHandler<any>\n}\n\ninterface ManagedEntry {\n channel: Channel<any>\n socket: AnySocket\n leases: Set<ChannelLease>\n events: Map<string, ManagedEvent>\n lifecycle: { ready: Promise<Channel<any>>; release: () => Promise<void> } | undefined\n}\n\nconst socketEntries = new WeakMap<AnySocket, Map<string, ManagedEntry>>()\nconst socketOwners = new WeakMap<AnySocket, { count: number; generation: number }>()\n\nfunction reportSubscriptionError(error: unknown): void {\n queueMicrotask(() => {\n console.error(error)\n })\n}\n\nfunction attachEvents(entry: ManagedEntry): void {\n for (const [event, managed] of entry.events) {\n entry.channel.$listen(event, managed.dispatch)\n }\n}\n\nfunction getEntry(socket: AnySocket, name: string): ManagedEntry {\n let entries = socketEntries.get(socket)\n if (!entries) {\n entries = new Map()\n socketEntries.set(socket, entries)\n }\n\n const channel = socket.channel(name)\n let entry = entries.get(name)\n if (!entry || entry.channel !== channel) {\n entry = {\n channel,\n socket,\n leases: new Set(),\n events: new Map(),\n lifecycle: undefined,\n }\n entries.set(name, entry)\n }\n\n return entry\n}\n\nexport class ChannelLease {\n readonly entry: ManagedEntry\n #released = false\n #listeners: Array<{ event: string; handler: EventHandler<any> }> = []\n\n constructor(entry: ManagedEntry) {\n this.entry = entry\n }\n\n listen(event: string, handler: EventHandler<any>): void {\n if (this.#released) return\n\n let managed = this.entry.events.get(event)\n if (!managed) {\n const handlers = new Set<EventHandler<any>>()\n managed = {\n handlers,\n dispatch: (data) => {\n for (const listener of handlers) callClientHandler(listener, data)\n },\n }\n this.entry.events.set(event, managed)\n this.entry.channel.$listen(event, managed.dispatch)\n }\n const registration: EventHandler<any> = (data) => handler(data)\n managed.handlers.add(registration)\n this.#listeners.push({ event, handler: registration })\n }\n\n release(): void {\n if (this.#released) return\n this.#released = true\n\n for (const { event, handler } of this.#listeners) {\n const managed = this.entry.events.get(event)\n managed?.handlers.delete(handler)\n if (managed?.handlers.size === 0) {\n this.entry.channel.$stopListening(event, managed.dispatch)\n this.entry.events.delete(event)\n }\n }\n this.#listeners = []\n this.entry.leases.delete(this)\n\n queueMicrotask(() => {\n if (this.entry.leases.size > 0) return\n const lifecycle = this.entry.lifecycle\n this.entry.lifecycle = undefined\n void lifecycle?.release().then(() => {\n if (this.entry.leases.size > 0 || this.entry.lifecycle) return\n const entries = socketEntries.get(this.entry.socket)\n if (entries?.get(this.entry.channel.name) === this.entry) {\n entries.delete(this.entry.channel.name)\n }\n })\n })\n }\n}\n\nexport function acquireChannel<Registry, const Name extends string>(\n socket: Socket<Registry>,\n name: FrameworkChannelName<Registry, Name>\n): { channel: FrameworkChannel<Registry, Name>; lease: ChannelLease } {\n const entry = getEntry(socket, name)\n const lease = new ChannelLease(entry)\n entry.leases.add(lease)\n\n if (!entry.lifecycle) {\n const lifecycle = entry.channel.$acquire()\n entry.lifecycle = lifecycle\n attachEvents(entry)\n void lifecycle.ready\n .then(() => {\n if (entry.lifecycle === lifecycle) attachEvents(entry)\n })\n .catch((error) => {\n if (entry.lifecycle === lifecycle && entry.leases.size > 0 && entry.socket.connected) {\n reportSubscriptionError(error)\n }\n })\n }\n\n return { channel: entry.channel as FrameworkChannel<Registry, Name>, lease }\n}\n\n/** Connects an exclusively owned socket and coalesces development-mode remounts. */\nexport function acquireSocketOwnership(socket: AnySocket): () => void {\n let owner = socketOwners.get(socket)\n if (!owner) {\n owner = { count: 0, generation: 0 }\n socketOwners.set(socket, owner)\n socket.connect().catch(reportSubscriptionError)\n }\n owner.count++\n owner.generation++\n\n let released = false\n return () => {\n if (released) return\n released = true\n owner.count--\n const generation = ++owner.generation\n queueMicrotask(() => {\n if (owner.count === 0 && owner.generation === generation) {\n socket.disconnect()\n socketOwners.delete(socket)\n }\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,159 @@
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
+ assertEmittedToUser(userId, event, query) {
60
+ this.#assertEmitted(
61
+ (emission) => emission.target === "user" && emission.userId === userId && emission.event === event,
62
+ query,
63
+ `Expected socket event "${event}" to have been emitted to user "${String(userId)}"`
64
+ );
65
+ }
66
+ assertNotEmittedToUser(userId, event, query) {
67
+ this.#assertNotEmitted(
68
+ (emission) => emission.target === "user" && emission.userId === userId && emission.event === event,
69
+ query,
70
+ `Expected socket event "${event}" not to have been emitted to user "${String(userId)}"`
71
+ );
72
+ }
73
+ assertEmittedCount(count, query) {
74
+ const actual = this.#matching(query).length;
75
+ if (actual !== count) {
76
+ throw new AssertionError({
77
+ message: `Expected ${count} socket event(s) to have been emitted, but found ${actual}`,
78
+ actual,
79
+ expected: count,
80
+ operator: "strictEqual"
81
+ });
82
+ }
83
+ }
84
+ assertNothingEmitted() {
85
+ this.assertEmittedCount(0);
86
+ }
87
+ #assertEmitted(predicate, query, message) {
88
+ const matching = this.#emissions.filter((emission) => {
89
+ return predicate(emission) && this.#matchesQuery(emission, query);
90
+ });
91
+ if (matching.length === 0) {
92
+ throw new AssertionError({
93
+ message,
94
+ actual: this.#emissions,
95
+ expected: query,
96
+ operator: "includes"
97
+ });
98
+ }
99
+ }
100
+ #assertNotEmitted(predicate, query, message) {
101
+ const matching = this.#emissions.filter((emission) => {
102
+ return predicate(emission) && this.#matchesQuery(emission, query);
103
+ });
104
+ if (matching.length > 0) {
105
+ throw new AssertionError({
106
+ message,
107
+ actual: matching,
108
+ expected: [],
109
+ operator: "doesNotInclude"
110
+ });
111
+ }
112
+ }
113
+ #matching(query) {
114
+ return this.#emissions.filter((emission) => {
115
+ if (query?.target && emission.target !== query.target) {
116
+ return false;
117
+ }
118
+ if (query?.event && emission.event !== query.event) {
119
+ return false;
120
+ }
121
+ if (query?.channel && (!("channel" in emission) || emission.channel !== query.channel)) {
122
+ return false;
123
+ }
124
+ if (query?.userId !== void 0 && (!("userId" in emission) || emission.userId !== query.userId)) {
125
+ return false;
126
+ }
127
+ return this.#matchesQuery(emission, query);
128
+ });
129
+ }
130
+ #matchesQuery(emission, query) {
131
+ if (!query) {
132
+ return true;
133
+ }
134
+ if ("data" in query) {
135
+ if (typeof query.data === "function") {
136
+ if (!query.data(emission.data)) {
137
+ return false;
138
+ }
139
+ } else if (!isDeepStrictEqual(emission.data, query.data)) {
140
+ return false;
141
+ }
142
+ }
143
+ if ("except" in query) {
144
+ if (typeof query.except === "function") {
145
+ if (!query.except("except" in emission ? emission.except : void 0)) {
146
+ return false;
147
+ }
148
+ } else if (!isDeepStrictEqual(("except" in emission ? emission.except : void 0) ?? [], query.except)) {
149
+ return false;
150
+ }
151
+ }
152
+ return true;
153
+ }
154
+ };
155
+
156
+ export {
157
+ SocketFake
158
+ };
159
+ //# sourceMappingURL=chunk-B4Y3TNDI.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\nexport interface SocketFakeEmissionQuery {\n data?: unknown | ((data: unknown) => boolean)\n except?: string[] | ((except: string[] | undefined) => boolean)\n}\n\nexport interface SocketFakeCountQuery extends SocketFakeEmissionQuery {\n target?: SocketFakeTarget\n event?: string\n channel?: string\n userId?: string | number\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 assertEmittedToUser(\n userId: string | number,\n event: string,\n query?: SocketFakeEmissionQuery\n ): void {\n this.#assertEmitted(\n (emission) =>\n emission.target === 'user' && emission.userId === userId && emission.event === event,\n query,\n `Expected socket event \"${event}\" to have been emitted to user \"${String(userId)}\"`\n )\n }\n\n assertNotEmittedToUser(\n userId: string | number,\n event: string,\n query?: SocketFakeEmissionQuery\n ): void {\n this.#assertNotEmitted(\n (emission) =>\n emission.target === 'user' && emission.userId === userId && emission.event === event,\n query,\n `Expected socket event \"${event}\" not to have been emitted to user \"${String(userId)}\"`\n )\n }\n\n assertEmittedCount(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 assertNothingEmitted(): void {\n this.assertEmittedCount(0)\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?.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 if (\n query?.userId !== undefined &&\n (!('userId' in emission) || emission.userId !== query.userId)\n ) {\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":[]}
@@ -15,4 +15,4 @@ export {
15
15
  channelMessageChannel,
16
16
  broadcastChannel
17
17
  };
18
- //# sourceMappingURL=chunk-GKAD2UOA.js.map
18
+ //# sourceMappingURL=chunk-D3HUBCBW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tracing_channels.ts"],"sourcesContent":["import diagnostics_channel from 'node:diagnostics_channel'\nimport type {\n SocketBroadcastMessage,\n SocketChannelMessage,\n SocketConnectMessage,\n SocketDisconnectMessage,\n SocketSubscribeMessage,\n SocketUnsubscribeMessage,\n} from './types/tracing_channels.js'\n\nexport const connectChannel = diagnostics_channel.tracingChannel<\n SocketConnectMessage,\n SocketConnectMessage\n>('rlanz.socket.connect')\n\nexport const disconnectChannel = diagnostics_channel.tracingChannel<\n SocketDisconnectMessage,\n SocketDisconnectMessage\n>('rlanz.socket.disconnect')\n\nexport const subscribeChannel = diagnostics_channel.tracingChannel<\n SocketSubscribeMessage,\n SocketSubscribeMessage\n>('rlanz.socket.subscribe')\n\nexport const unsubscribeChannel = diagnostics_channel.tracingChannel<\n SocketUnsubscribeMessage,\n SocketUnsubscribeMessage\n>('rlanz.socket.unsubscribe')\n\nexport const channelMessageChannel = diagnostics_channel.tracingChannel<\n SocketChannelMessage,\n SocketChannelMessage\n>('rlanz.socket.message')\n\nexport const broadcastChannel = diagnostics_channel.tracingChannel<\n SocketBroadcastMessage,\n SocketBroadcastMessage\n>('rlanz.socket.broadcast')\n"],"mappings":";AAAA,OAAO,yBAAyB;AAUzB,IAAM,iBAAiB,oBAAoB,eAGhD,sBAAsB;AAEjB,IAAM,oBAAoB,oBAAoB,eAGnD,yBAAyB;AAEpB,IAAM,mBAAmB,oBAAoB,eAGlD,wBAAwB;AAEnB,IAAM,qBAAqB,oBAAoB,eAGpD,0BAA0B;AAErB,IAAM,wBAAwB,oBAAoB,eAGvD,sBAAsB;AAEjB,IAAM,mBAAmB,oBAAoB,eAGlD,wBAAwB;","names":[]}
@@ -0,0 +1,28 @@
1
+ // src/client/callback.ts
2
+ function reportClientError(error) {
3
+ try {
4
+ const reportError = globalThis.reportError;
5
+ if (typeof reportError === "function") {
6
+ reportError(error);
7
+ } else {
8
+ console.error(error);
9
+ }
10
+ } catch {
11
+ }
12
+ }
13
+ function callClientHandler(handler, data) {
14
+ try {
15
+ const result = Reflect.apply(handler, void 0, [data]);
16
+ if (result && typeof result.then === "function") {
17
+ void Promise.resolve(result).catch(reportClientError);
18
+ }
19
+ } catch (error) {
20
+ reportClientError(error);
21
+ }
22
+ }
23
+
24
+ export {
25
+ reportClientError,
26
+ callClientHandler
27
+ };
28
+ //# sourceMappingURL=chunk-SFAY2ZA4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/callback.ts"],"sourcesContent":["type Callable = (...args: never[]) => unknown\n\nexport function reportClientError(error: unknown): void {\n try {\n const reportError = (\n globalThis as typeof globalThis & { reportError?: (error: unknown) => void }\n ).reportError\n\n if (typeof reportError === 'function') {\n reportError(error)\n } else {\n console.error(error)\n }\n } catch {\n // Error reporting must never affect socket state or create another rejected promise.\n }\n}\n\nexport function callClientHandler(handler: Callable, data?: unknown): void {\n try {\n const result = Reflect.apply(handler, undefined, [data])\n if (result && typeof (result as PromiseLike<unknown>).then === 'function') {\n void Promise.resolve(result).catch(reportClientError)\n }\n } catch (error) {\n reportClientError(error)\n }\n}\n"],"mappings":";AAEO,SAAS,kBAAkB,OAAsB;AACtD,MAAI;AACF,UAAM,cACJ,WACA;AAEF,QAAI,OAAO,gBAAgB,YAAY;AACrC,kBAAY,KAAK;AAAA,IACnB,OAAO;AACL,cAAQ,MAAM,KAAK;AAAA,IACrB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,kBAAkB,SAAmB,MAAsB;AACzE,MAAI;AACF,UAAM,SAAS,QAAQ,MAAM,SAAS,QAAW,CAAC,IAAI,CAAC;AACvD,QAAI,UAAU,OAAQ,OAAgC,SAAS,YAAY;AACzE,WAAK,QAAQ,QAAQ,MAAM,EAAE,MAAM,iBAAiB;AAAA,IACtD;AAAA,EACF,SAAS,OAAO;AACd,sBAAkB,KAAK;AAAA,EACzB;AACF;","names":[]}
@@ -1,31 +1,34 @@
1
1
  // src/decorators.ts
2
- var CHANNEL_MESSAGE_HANDLERS = /* @__PURE__ */ Symbol("channel_message_handlers");
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 ctor = target.constructor;
9
- const handlers = ctor[CHANNEL_MESSAGE_HANDLERS] ? [...ctor[CHANNEL_MESSAGE_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
- ctor[CHANNEL_MESSAGE_HANDLERS] = handlers;
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
- constructors.unshift(proto.constructor);
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 ctor of constructors) {
26
- const definitions = ctor[CHANNEL_MESSAGE_HANDLERS] ?? [];
28
+ for (const constructor of constructors) {
29
+ const definitions = decoratedHandlers.get(constructor) ?? [];
27
30
  for (const definition of definitions) {
28
- const handler = instance[definition.methodName];
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-XUDFDJME.js.map
45
+ //# sourceMappingURL=chunk-SHH6U4CI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/decorators.ts"],"sourcesContent":["import type { ChannelHandlerMap } from './types.js'\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): ChannelHandlerMap<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 ChannelHandlerMap<User>\n}\n"],"mappings":";AAOA,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,UACyB;AACzB,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":[]}