Skip to content

Commit 2ed93f5

Browse files
committed
add datastream ws client
1 parent 0348d0f commit 2ed93f5

File tree

8 files changed

+7406
-792
lines changed

8 files changed

+7406
-792
lines changed

package-lock.json

Lines changed: 5937 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,39 @@
1313
"build:legacy": "tsc"
1414
},
1515
"dependencies": {
16-
"url-join": "4.0.1",
16+
"@types/ws": "^8.18.1",
1717
"form-data": "^4.0.4",
1818
"formdata-node": "^6.0.3",
1919
"node-fetch": "^2.7.0",
2020
"qs": "^6.13.1",
21-
"readable-stream": "^4.5.2"
21+
"readable-stream": "^4.5.2",
22+
"url-join": "4.0.1",
23+
"ws": "^8.18.3"
2224
},
2325
"devDependencies": {
24-
"@types/url-join": "4.0.1",
25-
"@types/qs": "^6.9.17",
26+
"@types/jest": "^29.5.14",
27+
"@types/node": "^18.19.70",
2628
"@types/node-fetch": "^2.6.12",
29+
"@types/qs": "^6.9.17",
2730
"@types/readable-stream": "^4.0.18",
28-
"webpack": "^5.97.1",
29-
"ts-loader": "^9.5.1",
31+
"@types/url-join": "4.0.1",
32+
"esbuild": "^0.25.9",
3033
"jest": "^29.7.0",
31-
"@types/jest": "^29.5.14",
32-
"ts-jest": "^29.1.1",
3334
"jest-environment-jsdom": "^29.7.0",
34-
"@types/node": "^18.19.70",
3535
"prettier": "^3.4.2",
36+
"ts-jest": "^29.1.1",
37+
"ts-loader": "^9.5.1",
3638
"typescript": "~5.7.2",
37-
"esbuild": "^0.25.9"
39+
"webpack": "^5.97.1"
3840
},
3941
"browser": {
4042
"fs": false,
4143
"os": false,
4244
"path": false,
4345
"crypto": false,
44-
"timers": false
46+
"timers": false,
47+
"url": false,
48+
"ws": false
4549
},
4650
"packageManager": "yarn@1.22.22",
4751
"files": [

src/datastream/client.test.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { DatastreamClient, Logger, MessageHandlerFunc, ConnectionReadyHandlerFunc, DataStreamResp, EntityType, Action } from '../datastream';
2+
3+
// Mock logger implementation
4+
class MockLogger implements Logger {
5+
debug(ctx: any, message: string, ...args: any[]): void {
6+
console.log(`[DEBUG] ${message}`, ...args);
7+
}
8+
9+
info(ctx: any, message: string, ...args: any[]): void {
10+
console.log(`[INFO] ${message}`, ...args);
11+
}
12+
13+
warn(ctx: any, message: string, ...args: any[]): void {
14+
console.log(`[WARN] ${message}`, ...args);
15+
}
16+
17+
error(ctx: any, message: string, ...args: any[]): void {
18+
console.error(`[ERROR] ${message}`, ...args);
19+
}
20+
}
21+
22+
describe('DatastreamClient', () => {
23+
let client: DatastreamClient;
24+
const mockLogger = new MockLogger();
25+
26+
const mockMessageHandler: MessageHandlerFunc = async (ctx: any, message: DataStreamResp) => {
27+
console.log('Received message:', message);
28+
};
29+
30+
const mockConnectionReadyHandler: ConnectionReadyHandlerFunc = async (ctx: any) => {
31+
console.log('Connection ready');
32+
};
33+
34+
afterEach(() => {
35+
if (client) {
36+
client.close();
37+
}
38+
});
39+
40+
test('should create client with required options', () => {
41+
expect(() => {
42+
client = new DatastreamClient({
43+
url: 'wss://datastream.example.com/datastream',
44+
apiKey: 'test-key',
45+
messageHandler: mockMessageHandler,
46+
logger: mockLogger,
47+
});
48+
}).not.toThrow();
49+
50+
expect(client.isConnected()).toBe(false);
51+
expect(client.isReady()).toBe(false);
52+
});
53+
54+
test('should throw error when URL is missing', () => {
55+
expect(() => {
56+
new DatastreamClient({
57+
url: '',
58+
apiKey: 'test-key',
59+
messageHandler: mockMessageHandler,
60+
});
61+
}).toThrow('URL is required');
62+
});
63+
64+
test('should throw error when API key is missing', () => {
65+
expect(() => {
66+
new DatastreamClient({
67+
url: 'wss://example.com',
68+
apiKey: '',
69+
messageHandler: mockMessageHandler,
70+
});
71+
}).toThrow('ApiKey is required');
72+
});
73+
74+
test('should throw error when message handler is missing', () => {
75+
expect(() => {
76+
new DatastreamClient({
77+
url: 'wss://example.com',
78+
apiKey: 'test-key',
79+
messageHandler: undefined as any,
80+
});
81+
}).toThrow('MessageHandler is required');
82+
});
83+
84+
test('should convert HTTP URL to WebSocket URL', () => {
85+
client = new DatastreamClient({
86+
url: 'https://api.schematichq.com',
87+
apiKey: 'test-key',
88+
messageHandler: mockMessageHandler,
89+
logger: mockLogger,
90+
});
91+
92+
// The URL conversion is tested internally - we can't directly access the private url property
93+
// But we can verify the client was created successfully
94+
expect(client).toBeDefined();
95+
expect(client.isConnected()).toBe(false);
96+
});
97+
98+
test('should convert HTTP localhost URL to WebSocket URL', () => {
99+
client = new DatastreamClient({
100+
url: 'http://localhost:8080',
101+
apiKey: 'test-key',
102+
messageHandler: mockMessageHandler,
103+
logger: mockLogger,
104+
});
105+
106+
expect(client).toBeDefined();
107+
expect(client.isConnected()).toBe(false);
108+
});
109+
110+
test('should handle WebSocket URL directly', () => {
111+
client = new DatastreamClient({
112+
url: 'wss://datastream.example.com/datastream',
113+
apiKey: 'test-key',
114+
messageHandler: mockMessageHandler,
115+
logger: mockLogger,
116+
});
117+
118+
expect(client).toBeDefined();
119+
expect(client.isConnected()).toBe(false);
120+
});
121+
122+
test('should set default options when not provided', () => {
123+
client = new DatastreamClient({
124+
url: 'wss://example.com',
125+
apiKey: 'test-key',
126+
messageHandler: mockMessageHandler,
127+
});
128+
129+
// Defaults are applied internally - we verify by successful construction
130+
expect(client).toBeDefined();
131+
});
132+
133+
test('should use custom options when provided', () => {
134+
client = new DatastreamClient({
135+
url: 'wss://example.com',
136+
apiKey: 'test-key',
137+
messageHandler: mockMessageHandler,
138+
connectionReadyHandler: mockConnectionReadyHandler,
139+
logger: mockLogger,
140+
maxReconnectAttempts: 5,
141+
minReconnectDelay: 2000,
142+
maxReconnectDelay: 60000,
143+
});
144+
145+
expect(client).toBeDefined();
146+
});
147+
148+
test('should emit events', (done) => {
149+
client = new DatastreamClient({
150+
url: 'wss://example.com',
151+
apiKey: 'test-key',
152+
messageHandler: mockMessageHandler,
153+
logger: mockLogger,
154+
});
155+
156+
// Test that the client can emit events
157+
client.on('error', (error) => {
158+
expect(error).toBeDefined();
159+
done();
160+
});
161+
162+
// Trigger an error to test event emission
163+
client.emit('error', new Error('Test error'));
164+
});
165+
166+
test('should reject sendMessage when not connected', async () => {
167+
client = new DatastreamClient({
168+
url: 'wss://example.com',
169+
apiKey: 'test-key',
170+
messageHandler: mockMessageHandler,
171+
logger: mockLogger,
172+
});
173+
174+
await expect(client.sendMessage({ test: 'message' })).rejects.toThrow('WebSocket connection is not available!');
175+
});
176+
});

0 commit comments

Comments
 (0)