@connexis/testing 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Connexis Realtime Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,69 @@
1
+ import { Transport, TransportCapabilities, ConnectionState } from '@connexis/core';
2
+
3
+ declare class MockTransport implements Transport {
4
+ private caps;
5
+ readonly type = "mock";
6
+ private _state;
7
+ private messageCallback;
8
+ private stateCallback;
9
+ published: Array<{
10
+ topic: string;
11
+ data: any;
12
+ }>;
13
+ subscribed: Array<{
14
+ topic: string;
15
+ filter?: Record<string, any>;
16
+ }>;
17
+ unsubscribed: Array<{
18
+ topic: string;
19
+ filter?: Record<string, any>;
20
+ }>;
21
+ private failNextConnect;
22
+ private failError;
23
+ private connectDelay;
24
+ constructor(caps?: TransportCapabilities);
25
+ get state(): ConnectionState;
26
+ setFailNextConnect(fail: boolean, error?: Error): void;
27
+ setConnectDelay(delayMs: number): void;
28
+ connect(): Promise<void>;
29
+ disconnect(): Promise<void>;
30
+ publish(topic: string, data: any): Promise<void>;
31
+ subscribe(topic: string, filter?: Record<string, any>): Promise<void>;
32
+ unsubscribe(topic: string, filter?: Record<string, any>): Promise<void>;
33
+ capabilities(): TransportCapabilities;
34
+ onMessage(cb: (topic: string, data: any) => void): void;
35
+ onStateChange(cb: (state: ConnectionState, error?: Error) => void): void;
36
+ clone(): MockTransport;
37
+ simulateMessage(topic: string, data: any): void;
38
+ simulateStateChange(state: ConnectionState, error?: Error): void;
39
+ private updateState;
40
+ }
41
+
42
+ /**
43
+ * Benchmark Results Interface
44
+ */
45
+ interface BenchmarkReport {
46
+ operation: string;
47
+ count: number;
48
+ durationMs: number;
49
+ opsPerSecond: number;
50
+ }
51
+ /**
52
+ * Runs a performance benchmark for core client operations.
53
+ */
54
+ declare function runBenchmarks(): Promise<BenchmarkReport[]>;
55
+ /**
56
+ * Scenario: Stress test hybrid policy deduplication with 1,000 subscriptions.
57
+ */
58
+ declare function runHybridStressTest(): Promise<{
59
+ connectionsCount: number;
60
+ subscriptionsCount: number;
61
+ }>;
62
+ /**
63
+ * Scenario: Simulate 10 tabs and trigger random network online/offline events.
64
+ */
65
+ declare function runNetworkChaosStressTest(cycles?: number): Promise<{
66
+ success: boolean;
67
+ }>;
68
+
69
+ export { type BenchmarkReport, MockTransport, runBenchmarks, runHybridStressTest, runNetworkChaosStressTest };
@@ -0,0 +1,69 @@
1
+ import { Transport, TransportCapabilities, ConnectionState } from '@connexis/core';
2
+
3
+ declare class MockTransport implements Transport {
4
+ private caps;
5
+ readonly type = "mock";
6
+ private _state;
7
+ private messageCallback;
8
+ private stateCallback;
9
+ published: Array<{
10
+ topic: string;
11
+ data: any;
12
+ }>;
13
+ subscribed: Array<{
14
+ topic: string;
15
+ filter?: Record<string, any>;
16
+ }>;
17
+ unsubscribed: Array<{
18
+ topic: string;
19
+ filter?: Record<string, any>;
20
+ }>;
21
+ private failNextConnect;
22
+ private failError;
23
+ private connectDelay;
24
+ constructor(caps?: TransportCapabilities);
25
+ get state(): ConnectionState;
26
+ setFailNextConnect(fail: boolean, error?: Error): void;
27
+ setConnectDelay(delayMs: number): void;
28
+ connect(): Promise<void>;
29
+ disconnect(): Promise<void>;
30
+ publish(topic: string, data: any): Promise<void>;
31
+ subscribe(topic: string, filter?: Record<string, any>): Promise<void>;
32
+ unsubscribe(topic: string, filter?: Record<string, any>): Promise<void>;
33
+ capabilities(): TransportCapabilities;
34
+ onMessage(cb: (topic: string, data: any) => void): void;
35
+ onStateChange(cb: (state: ConnectionState, error?: Error) => void): void;
36
+ clone(): MockTransport;
37
+ simulateMessage(topic: string, data: any): void;
38
+ simulateStateChange(state: ConnectionState, error?: Error): void;
39
+ private updateState;
40
+ }
41
+
42
+ /**
43
+ * Benchmark Results Interface
44
+ */
45
+ interface BenchmarkReport {
46
+ operation: string;
47
+ count: number;
48
+ durationMs: number;
49
+ opsPerSecond: number;
50
+ }
51
+ /**
52
+ * Runs a performance benchmark for core client operations.
53
+ */
54
+ declare function runBenchmarks(): Promise<BenchmarkReport[]>;
55
+ /**
56
+ * Scenario: Stress test hybrid policy deduplication with 1,000 subscriptions.
57
+ */
58
+ declare function runHybridStressTest(): Promise<{
59
+ connectionsCount: number;
60
+ subscriptionsCount: number;
61
+ }>;
62
+ /**
63
+ * Scenario: Simulate 10 tabs and trigger random network online/offline events.
64
+ */
65
+ declare function runNetworkChaosStressTest(cycles?: number): Promise<{
66
+ success: boolean;
67
+ }>;
68
+
69
+ export { type BenchmarkReport, MockTransport, runBenchmarks, runHybridStressTest, runNetworkChaosStressTest };
package/dist/index.js ADDED
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ MockTransport: () => MockTransport,
24
+ runBenchmarks: () => runBenchmarks,
25
+ runHybridStressTest: () => runHybridStressTest,
26
+ runNetworkChaosStressTest: () => runNetworkChaosStressTest
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/mock-transport.ts
31
+ var MockTransport = class _MockTransport {
32
+ constructor(caps = { publish: true, subscribe: true, latency: "low" }) {
33
+ this.caps = caps;
34
+ }
35
+ caps;
36
+ type = "mock";
37
+ _state = "idle";
38
+ messageCallback = null;
39
+ stateCallback = null;
40
+ published = [];
41
+ subscribed = [];
42
+ unsubscribed = [];
43
+ failNextConnect = false;
44
+ failError = new Error("Mock connection failure");
45
+ connectDelay = 0;
46
+ get state() {
47
+ return this._state;
48
+ }
49
+ setFailNextConnect(fail, error) {
50
+ this.failNextConnect = fail;
51
+ if (error) this.failError = error;
52
+ }
53
+ setConnectDelay(delayMs) {
54
+ this.connectDelay = delayMs;
55
+ }
56
+ async connect() {
57
+ if (this._state === "connected") return;
58
+ this.updateState("connecting");
59
+ if (this.connectDelay > 0) {
60
+ await new Promise((resolve) => setTimeout(resolve, this.connectDelay));
61
+ }
62
+ if (this.failNextConnect) {
63
+ this.failNextConnect = false;
64
+ this.updateState("error", this.failError);
65
+ throw this.failError;
66
+ }
67
+ this.updateState("connected");
68
+ }
69
+ async disconnect() {
70
+ this.updateState("closed");
71
+ }
72
+ async publish(topic, data) {
73
+ if (this._state !== "connected") {
74
+ throw new Error("Transport not connected");
75
+ }
76
+ this.published.push({ topic, data });
77
+ }
78
+ async subscribe(topic, filter) {
79
+ this.subscribed.push({ topic, filter });
80
+ }
81
+ async unsubscribe(topic, filter) {
82
+ this.unsubscribed.push({ topic, filter });
83
+ }
84
+ capabilities() {
85
+ return this.caps;
86
+ }
87
+ onMessage(cb) {
88
+ this.messageCallback = cb;
89
+ }
90
+ onStateChange(cb) {
91
+ this.stateCallback = cb;
92
+ }
93
+ clone() {
94
+ const cloned = new _MockTransport(this.caps);
95
+ cloned.failNextConnect = this.failNextConnect;
96
+ cloned.failError = this.failError;
97
+ cloned.connectDelay = this.connectDelay;
98
+ return cloned;
99
+ }
100
+ // Simulator helper methods
101
+ simulateMessage(topic, data) {
102
+ if (this.messageCallback) {
103
+ this.messageCallback(topic, data);
104
+ }
105
+ }
106
+ simulateStateChange(state, error) {
107
+ this.updateState(state, error);
108
+ }
109
+ updateState(state, error) {
110
+ this._state = state;
111
+ if (this.stateCallback) {
112
+ this.stateCallback(state, error);
113
+ }
114
+ }
115
+ };
116
+
117
+ // src/stress-and-bench.ts
118
+ var import_core = require("@connexis/core");
119
+ async function runBenchmarks() {
120
+ const reports = [];
121
+ const transport = new MockTransport();
122
+ {
123
+ const client = (0, import_core.createRealtimeClient)({ transport });
124
+ const start = performance.now();
125
+ const count = 1e4;
126
+ const unsubs = [];
127
+ for (let i = 0; i < count; i++) {
128
+ const unsub = await client.subscribe(`topic_${i}`, () => {
129
+ });
130
+ unsubs.push(unsub);
131
+ }
132
+ for (const unsub of unsubs) {
133
+ await unsub();
134
+ }
135
+ const duration = performance.now() - start;
136
+ reports.push({
137
+ operation: "subscribe + unsubscribe",
138
+ count,
139
+ durationMs: duration,
140
+ opsPerSecond: count * 2 / (duration / 1e3)
141
+ });
142
+ await client.destroy();
143
+ }
144
+ {
145
+ const client = (0, import_core.createRealtimeClient)({ transport });
146
+ await client.subscribe("test", () => {
147
+ });
148
+ const count = 5e4;
149
+ const start = performance.now();
150
+ for (let i = 0; i < count; i++) {
151
+ await client.publish("test", { seq: i });
152
+ }
153
+ const duration = performance.now() - start;
154
+ reports.push({
155
+ operation: "publish message",
156
+ count,
157
+ durationMs: duration,
158
+ opsPerSecond: count / (duration / 1e3)
159
+ });
160
+ await client.destroy();
161
+ }
162
+ return reports;
163
+ }
164
+ async function runHybridStressTest() {
165
+ const transport = new MockTransport();
166
+ const manager = new import_core.ConnectionManager(transport, "hybrid");
167
+ const count = 1e3;
168
+ const filters = [
169
+ { region: "US" },
170
+ { region: "EU" },
171
+ { region: "AP" },
172
+ { region: "US" },
173
+ // duplicate of 0
174
+ { region: "EU" }
175
+ // duplicate of 1
176
+ ];
177
+ for (let i = 0; i < count; i++) {
178
+ const filter = filters[i % filters.length];
179
+ await manager.subscribe({ id: `sub_${i}`, topic: "orders", filter }, () => {
180
+ });
181
+ }
182
+ const connectionsCount = manager.getConnections().size;
183
+ const subscriptionsCount = manager.getActiveSubscriptionCount();
184
+ await manager.destroy();
185
+ return { connectionsCount, subscriptionsCount };
186
+ }
187
+ async function runNetworkChaosStressTest(cycles = 20) {
188
+ const transport = new MockTransport();
189
+ const connections = [];
190
+ for (let i = 0; i < 10; i++) {
191
+ const conn = (0, import_core.createRealtimeClient)({ transport });
192
+ await conn.subscribe("alerts", () => {
193
+ });
194
+ connections.push(conn);
195
+ }
196
+ for (let i = 0; i < cycles; i++) {
197
+ const shouldOffline = Math.random() > 0.5;
198
+ if (shouldOffline) {
199
+ transport.simulateStateChange("offline");
200
+ } else {
201
+ transport.simulateStateChange("connected");
202
+ }
203
+ }
204
+ transport.simulateStateChange("connected");
205
+ for (const conn of connections) {
206
+ await conn.destroy();
207
+ }
208
+ return { success: true };
209
+ }
210
+ // Annotate the CommonJS export names for ESM import in node:
211
+ 0 && (module.exports = {
212
+ MockTransport,
213
+ runBenchmarks,
214
+ runHybridStressTest,
215
+ runNetworkChaosStressTest
216
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,186 @@
1
+ // src/mock-transport.ts
2
+ var MockTransport = class _MockTransport {
3
+ constructor(caps = { publish: true, subscribe: true, latency: "low" }) {
4
+ this.caps = caps;
5
+ }
6
+ caps;
7
+ type = "mock";
8
+ _state = "idle";
9
+ messageCallback = null;
10
+ stateCallback = null;
11
+ published = [];
12
+ subscribed = [];
13
+ unsubscribed = [];
14
+ failNextConnect = false;
15
+ failError = new Error("Mock connection failure");
16
+ connectDelay = 0;
17
+ get state() {
18
+ return this._state;
19
+ }
20
+ setFailNextConnect(fail, error) {
21
+ this.failNextConnect = fail;
22
+ if (error) this.failError = error;
23
+ }
24
+ setConnectDelay(delayMs) {
25
+ this.connectDelay = delayMs;
26
+ }
27
+ async connect() {
28
+ if (this._state === "connected") return;
29
+ this.updateState("connecting");
30
+ if (this.connectDelay > 0) {
31
+ await new Promise((resolve) => setTimeout(resolve, this.connectDelay));
32
+ }
33
+ if (this.failNextConnect) {
34
+ this.failNextConnect = false;
35
+ this.updateState("error", this.failError);
36
+ throw this.failError;
37
+ }
38
+ this.updateState("connected");
39
+ }
40
+ async disconnect() {
41
+ this.updateState("closed");
42
+ }
43
+ async publish(topic, data) {
44
+ if (this._state !== "connected") {
45
+ throw new Error("Transport not connected");
46
+ }
47
+ this.published.push({ topic, data });
48
+ }
49
+ async subscribe(topic, filter) {
50
+ this.subscribed.push({ topic, filter });
51
+ }
52
+ async unsubscribe(topic, filter) {
53
+ this.unsubscribed.push({ topic, filter });
54
+ }
55
+ capabilities() {
56
+ return this.caps;
57
+ }
58
+ onMessage(cb) {
59
+ this.messageCallback = cb;
60
+ }
61
+ onStateChange(cb) {
62
+ this.stateCallback = cb;
63
+ }
64
+ clone() {
65
+ const cloned = new _MockTransport(this.caps);
66
+ cloned.failNextConnect = this.failNextConnect;
67
+ cloned.failError = this.failError;
68
+ cloned.connectDelay = this.connectDelay;
69
+ return cloned;
70
+ }
71
+ // Simulator helper methods
72
+ simulateMessage(topic, data) {
73
+ if (this.messageCallback) {
74
+ this.messageCallback(topic, data);
75
+ }
76
+ }
77
+ simulateStateChange(state, error) {
78
+ this.updateState(state, error);
79
+ }
80
+ updateState(state, error) {
81
+ this._state = state;
82
+ if (this.stateCallback) {
83
+ this.stateCallback(state, error);
84
+ }
85
+ }
86
+ };
87
+
88
+ // src/stress-and-bench.ts
89
+ import { createRealtimeClient, ConnectionManager } from "@connexis/core";
90
+ async function runBenchmarks() {
91
+ const reports = [];
92
+ const transport = new MockTransport();
93
+ {
94
+ const client = createRealtimeClient({ transport });
95
+ const start = performance.now();
96
+ const count = 1e4;
97
+ const unsubs = [];
98
+ for (let i = 0; i < count; i++) {
99
+ const unsub = await client.subscribe(`topic_${i}`, () => {
100
+ });
101
+ unsubs.push(unsub);
102
+ }
103
+ for (const unsub of unsubs) {
104
+ await unsub();
105
+ }
106
+ const duration = performance.now() - start;
107
+ reports.push({
108
+ operation: "subscribe + unsubscribe",
109
+ count,
110
+ durationMs: duration,
111
+ opsPerSecond: count * 2 / (duration / 1e3)
112
+ });
113
+ await client.destroy();
114
+ }
115
+ {
116
+ const client = createRealtimeClient({ transport });
117
+ await client.subscribe("test", () => {
118
+ });
119
+ const count = 5e4;
120
+ const start = performance.now();
121
+ for (let i = 0; i < count; i++) {
122
+ await client.publish("test", { seq: i });
123
+ }
124
+ const duration = performance.now() - start;
125
+ reports.push({
126
+ operation: "publish message",
127
+ count,
128
+ durationMs: duration,
129
+ opsPerSecond: count / (duration / 1e3)
130
+ });
131
+ await client.destroy();
132
+ }
133
+ return reports;
134
+ }
135
+ async function runHybridStressTest() {
136
+ const transport = new MockTransport();
137
+ const manager = new ConnectionManager(transport, "hybrid");
138
+ const count = 1e3;
139
+ const filters = [
140
+ { region: "US" },
141
+ { region: "EU" },
142
+ { region: "AP" },
143
+ { region: "US" },
144
+ // duplicate of 0
145
+ { region: "EU" }
146
+ // duplicate of 1
147
+ ];
148
+ for (let i = 0; i < count; i++) {
149
+ const filter = filters[i % filters.length];
150
+ await manager.subscribe({ id: `sub_${i}`, topic: "orders", filter }, () => {
151
+ });
152
+ }
153
+ const connectionsCount = manager.getConnections().size;
154
+ const subscriptionsCount = manager.getActiveSubscriptionCount();
155
+ await manager.destroy();
156
+ return { connectionsCount, subscriptionsCount };
157
+ }
158
+ async function runNetworkChaosStressTest(cycles = 20) {
159
+ const transport = new MockTransport();
160
+ const connections = [];
161
+ for (let i = 0; i < 10; i++) {
162
+ const conn = createRealtimeClient({ transport });
163
+ await conn.subscribe("alerts", () => {
164
+ });
165
+ connections.push(conn);
166
+ }
167
+ for (let i = 0; i < cycles; i++) {
168
+ const shouldOffline = Math.random() > 0.5;
169
+ if (shouldOffline) {
170
+ transport.simulateStateChange("offline");
171
+ } else {
172
+ transport.simulateStateChange("connected");
173
+ }
174
+ }
175
+ transport.simulateStateChange("connected");
176
+ for (const conn of connections) {
177
+ await conn.destroy();
178
+ }
179
+ return { success: true };
180
+ }
181
+ export {
182
+ MockTransport,
183
+ runBenchmarks,
184
+ runHybridStressTest,
185
+ runNetworkChaosStressTest
186
+ };
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@connexis/testing",
3
+ "version": "1.0.0",
4
+ "description": "Testing helpers and mocks for @connexis",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "@connexis/core": "1.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.5.2"
20
+ },
21
+ "scripts": {
22
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean"
23
+ }
24
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ runBenchmarks,
4
+ runHybridStressTest,
5
+ runNetworkChaosStressTest
6
+ } from '../stress-and-bench.js';
7
+
8
+ describe('Performance Benchmarks & Stress Tests', () => {
9
+ it('should run core performance benchmarks and yield throughput values', async () => {
10
+ const reports = await runBenchmarks();
11
+
12
+ // Print reports to stdout
13
+ console.table(reports);
14
+
15
+ expect(reports.length).toBe(2);
16
+ expect(reports[0].opsPerSecond).toBeGreaterThan(0);
17
+ expect(reports[1].opsPerSecond).toBeGreaterThan(0);
18
+ });
19
+
20
+ it('should run hybrid stress test and correctly deduplicate connection count', async () => {
21
+ // 1,000 subscriptions split across 5 unique filter groups -> should result in exactly 3 unique filter signatures
22
+ // (since region US, EU, AP are 3 distinct values, and the duplicates group onto them)
23
+ const { connectionsCount, subscriptionsCount } = await runHybridStressTest();
24
+
25
+ expect(subscriptionsCount).toBe(1000);
26
+ expect(connectionsCount).toBe(3); // 'orders' with region US, EU, AP
27
+ });
28
+
29
+ it('should run network chaos stress test without leaking subscriptions or crashing', async () => {
30
+ const { success } = await runNetworkChaosStressTest(10);
31
+ expect(success).toBe(true);
32
+ });
33
+ });
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { MockTransport } from './mock-transport.js';
2
+ export * from './stress-and-bench.js';
@@ -0,0 +1,107 @@
1
+ import { Transport, ConnectionState, TransportCapabilities } from '@connexis/core';
2
+
3
+ export class MockTransport implements Transport {
4
+ public readonly type = 'mock';
5
+ private _state: ConnectionState = 'idle';
6
+ private messageCallback: ((topic: string, data: any) => void) | null = null;
7
+ private stateCallback: ((state: ConnectionState, error?: Error) => void) | null = null;
8
+
9
+ public published: Array<{ topic: string; data: any }> = [];
10
+ public subscribed: Array<{ topic: string; filter?: Record<string, any> }> = [];
11
+ public unsubscribed: Array<{ topic: string; filter?: Record<string, any> }> = [];
12
+
13
+ private failNextConnect = false;
14
+ private failError: Error = new Error('Mock connection failure');
15
+ private connectDelay = 0;
16
+
17
+ constructor(
18
+ private caps: TransportCapabilities = { publish: true, subscribe: true, latency: 'low' }
19
+ ) {}
20
+
21
+ get state(): ConnectionState {
22
+ return this._state;
23
+ }
24
+
25
+ setFailNextConnect(fail: boolean, error?: Error): void {
26
+ this.failNextConnect = fail;
27
+ if (error) this.failError = error;
28
+ }
29
+
30
+ setConnectDelay(delayMs: number): void {
31
+ this.connectDelay = delayMs;
32
+ }
33
+
34
+ async connect(): Promise<void> {
35
+ if (this._state === 'connected') return;
36
+ this.updateState('connecting');
37
+
38
+ if (this.connectDelay > 0) {
39
+ await new Promise((resolve) => setTimeout(resolve, this.connectDelay));
40
+ }
41
+
42
+ if (this.failNextConnect) {
43
+ this.failNextConnect = false; // Reset
44
+ this.updateState('error', this.failError);
45
+ throw this.failError;
46
+ }
47
+
48
+ this.updateState('connected');
49
+ }
50
+
51
+ async disconnect(): Promise<void> {
52
+ this.updateState('closed');
53
+ }
54
+
55
+ async publish(topic: string, data: any): Promise<void> {
56
+ if (this._state !== 'connected') {
57
+ throw new Error('Transport not connected');
58
+ }
59
+ this.published.push({ topic, data });
60
+ }
61
+
62
+ async subscribe(topic: string, filter?: Record<string, any>): Promise<void> {
63
+ this.subscribed.push({ topic, filter });
64
+ }
65
+
66
+ async unsubscribe(topic: string, filter?: Record<string, any>): Promise<void> {
67
+ this.unsubscribed.push({ topic, filter });
68
+ }
69
+
70
+ capabilities(): TransportCapabilities {
71
+ return this.caps;
72
+ }
73
+
74
+ onMessage(cb: (topic: string, data: any) => void): void {
75
+ this.messageCallback = cb;
76
+ }
77
+
78
+ onStateChange(cb: (state: ConnectionState, error?: Error) => void): void {
79
+ this.stateCallback = cb;
80
+ }
81
+
82
+ clone(): MockTransport {
83
+ const cloned = new MockTransport(this.caps);
84
+ cloned.failNextConnect = this.failNextConnect;
85
+ cloned.failError = this.failError;
86
+ cloned.connectDelay = this.connectDelay;
87
+ return cloned;
88
+ }
89
+
90
+ // Simulator helper methods
91
+ simulateMessage(topic: string, data: any): void {
92
+ if (this.messageCallback) {
93
+ this.messageCallback(topic, data);
94
+ }
95
+ }
96
+
97
+ simulateStateChange(state: ConnectionState, error?: Error): void {
98
+ this.updateState(state, error);
99
+ }
100
+
101
+ private updateState(state: ConnectionState, error?: Error): void {
102
+ this._state = state;
103
+ if (this.stateCallback) {
104
+ this.stateCallback(state, error);
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,132 @@
1
+ import { createRealtimeClient, ConnectionManager } from '@connexis/core';
2
+ import { MockTransport } from './mock-transport.js';
3
+
4
+ /**
5
+ * Benchmark Results Interface
6
+ */
7
+ export interface BenchmarkReport {
8
+ operation: string;
9
+ count: number;
10
+ durationMs: number;
11
+ opsPerSecond: number;
12
+ }
13
+
14
+ /**
15
+ * Runs a performance benchmark for core client operations.
16
+ */
17
+ export async function runBenchmarks(): Promise<BenchmarkReport[]> {
18
+ const reports: BenchmarkReport[] = [];
19
+ const transport = new MockTransport();
20
+
21
+ // 1. Subscribe/Unsubscribe Benchmark
22
+ {
23
+ const client = createRealtimeClient({ transport });
24
+ const start = performance.now();
25
+ const count = 10000;
26
+ const unsubs: Array<() => Promise<void>> = [];
27
+
28
+ for (let i = 0; i < count; i++) {
29
+ const unsub = await client.subscribe(`topic_${i}`, () => {});
30
+ unsubs.push(unsub);
31
+ }
32
+ for (const unsub of unsubs) {
33
+ await unsub();
34
+ }
35
+ const duration = performance.now() - start;
36
+ reports.push({
37
+ operation: 'subscribe + unsubscribe',
38
+ count,
39
+ durationMs: duration,
40
+ opsPerSecond: (count * 2) / (duration / 1000)
41
+ });
42
+ await client.destroy();
43
+ }
44
+
45
+ // 2. Publish Throughput & Latency Benchmark
46
+ {
47
+ const client = createRealtimeClient({ transport });
48
+ // Spin up connection
49
+ await client.subscribe('test', () => {});
50
+
51
+ const count = 50000;
52
+ const start = performance.now();
53
+ for (let i = 0; i < count; i++) {
54
+ await client.publish('test', { seq: i });
55
+ }
56
+ const duration = performance.now() - start;
57
+ reports.push({
58
+ operation: 'publish message',
59
+ count,
60
+ durationMs: duration,
61
+ opsPerSecond: count / (duration / 1000)
62
+ });
63
+ await client.destroy();
64
+ }
65
+
66
+ return reports;
67
+ }
68
+
69
+ /**
70
+ * Scenario: Stress test hybrid policy deduplication with 1,000 subscriptions.
71
+ */
72
+ export async function runHybridStressTest(): Promise<{
73
+ connectionsCount: number;
74
+ subscriptionsCount: number;
75
+ }> {
76
+ const transport = new MockTransport();
77
+ const manager = new ConnectionManager(transport, 'hybrid');
78
+
79
+ const count = 1000;
80
+ // Deduplicate onto 5 unique filters
81
+ const filters = [
82
+ { region: 'US' },
83
+ { region: 'EU' },
84
+ { region: 'AP' },
85
+ { region: 'US' }, // duplicate of 0
86
+ { region: 'EU' } // duplicate of 1
87
+ ];
88
+
89
+ for (let i = 0; i < count; i++) {
90
+ const filter = filters[i % filters.length];
91
+ await manager.subscribe({ id: `sub_${i}`, topic: 'orders', filter }, () => {});
92
+ }
93
+
94
+ const connectionsCount = manager.getConnections().size;
95
+ const subscriptionsCount = manager.getActiveSubscriptionCount();
96
+
97
+ await manager.destroy();
98
+
99
+ return { connectionsCount, subscriptionsCount };
100
+ }
101
+
102
+ /**
103
+ * Scenario: Simulate 10 tabs and trigger random network online/offline events.
104
+ */
105
+ export async function runNetworkChaosStressTest(cycles = 20): Promise<{ success: boolean }> {
106
+ const transport = new MockTransport();
107
+ const connections: any[] = [];
108
+
109
+ for (let i = 0; i < 10; i++) {
110
+ const conn = createRealtimeClient({ transport });
111
+ await conn.subscribe('alerts', () => {});
112
+ connections.push(conn);
113
+ }
114
+
115
+ for (let i = 0; i < cycles; i++) {
116
+ const shouldOffline = Math.random() > 0.5;
117
+ if (shouldOffline) {
118
+ transport.simulateStateChange('offline');
119
+ } else {
120
+ transport.simulateStateChange('connected');
121
+ }
122
+ }
123
+
124
+ // Recover everything
125
+ transport.simulateStateChange('connected');
126
+
127
+ for (const conn of connections) {
128
+ await conn.destroy();
129
+ }
130
+
131
+ return { success: true };
132
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }