@agentuity/frontend 1.0.48 → 2.0.0-beta.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.
Files changed (44) hide show
  1. package/AGENTS.md +7 -31
  2. package/dist/beacon-script.js +1 -1
  3. package/dist/beacon.js +1 -1
  4. package/dist/dev-ws.d.ts +11 -0
  5. package/dist/dev-ws.d.ts.map +1 -0
  6. package/dist/dev-ws.js +34 -0
  7. package/dist/dev-ws.js.map +1 -0
  8. package/dist/index.d.ts +1 -3
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -2
  11. package/dist/index.js.map +1 -1
  12. package/package.json +3 -3
  13. package/src/dev-ws.ts +33 -0
  14. package/src/index.ts +1 -18
  15. package/dist/client/eventstream.d.ts +0 -12
  16. package/dist/client/eventstream.d.ts.map +0 -1
  17. package/dist/client/eventstream.js +0 -39
  18. package/dist/client/eventstream.js.map +0 -1
  19. package/dist/client/index.d.ts +0 -33
  20. package/dist/client/index.d.ts.map +0 -1
  21. package/dist/client/index.js +0 -230
  22. package/dist/client/index.js.map +0 -1
  23. package/dist/client/stream.d.ts +0 -6
  24. package/dist/client/stream.d.ts.map +0 -1
  25. package/dist/client/stream.js +0 -49
  26. package/dist/client/stream.js.map +0 -1
  27. package/dist/client/types.d.ts +0 -168
  28. package/dist/client/types.d.ts.map +0 -1
  29. package/dist/client/types.js +0 -5
  30. package/dist/client/types.js.map +0 -1
  31. package/dist/client/websocket.d.ts +0 -6
  32. package/dist/client/websocket.d.ts.map +0 -1
  33. package/dist/client/websocket.js +0 -49
  34. package/dist/client/websocket.js.map +0 -1
  35. package/dist/types.d.ts +0 -42
  36. package/dist/types.d.ts.map +0 -1
  37. package/dist/types.js +0 -2
  38. package/dist/types.js.map +0 -1
  39. package/src/client/eventstream.ts +0 -52
  40. package/src/client/index.ts +0 -267
  41. package/src/client/stream.ts +0 -61
  42. package/src/client/types.ts +0 -237
  43. package/src/client/websocket.ts +0 -61
  44. package/src/types.ts +0 -56
@@ -1,52 +0,0 @@
1
- import type { EventHandler, EventStreamClient } from './types';
2
-
3
- /**
4
- * Create an EventSource (SSE) client wrapper with event-based API.
5
- *
6
- * Note: Native EventSource has limited authentication support.
7
- * - withCredentials: true will send cookies and HTTP auth headers
8
- * - For custom Authorization headers, consider using @microsoft/fetch-event-source
9
- */
10
- export function createEventStreamClient(
11
- url: string,
12
- options?: { withCredentials?: boolean }
13
- ): EventStreamClient {
14
- const eventSource = new EventSource(url, {
15
- withCredentials: options?.withCredentials ?? false,
16
- });
17
- const handlers: {
18
- message: Set<EventHandler<MessageEvent>>;
19
- open: Set<EventHandler<Event>>;
20
- error: Set<EventHandler<Event>>;
21
- } = {
22
- message: new Set(),
23
- open: new Set(),
24
- error: new Set(),
25
- };
26
-
27
- // Set up native EventSource event listeners
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
- eventSource.onmessage = (event: any) => {
30
- handlers.message.forEach((handler) => handler(event));
31
- };
32
-
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- eventSource.onopen = (event: any) => {
35
- handlers.open.forEach((handler) => handler(event));
36
- };
37
-
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- eventSource.onerror = (event: any) => {
40
- handlers.error.forEach((handler) => handler(event));
41
- };
42
-
43
- return {
44
- on: ((event: 'message' | 'open' | 'error', handler: EventHandler) => {
45
- handlers[event].add(handler);
46
- }) as EventStreamClient['on'],
47
-
48
- close() {
49
- eventSource.close();
50
- },
51
- };
52
- }
@@ -1,267 +0,0 @@
1
- import type { Client, ClientOptions } from './types';
2
- import { createWebSocketClient } from './websocket';
3
- import { createEventStreamClient } from './eventstream';
4
- import { createStreamClient } from './stream';
5
-
6
- /**
7
- * Default base URL (empty = relative URLs).
8
- */
9
- const defaultBaseUrl = '';
10
-
11
- /**
12
- * Resolve baseUrl - if it's a function, call it; otherwise return the string.
13
- */
14
- function resolveBaseUrl(baseUrl: string | (() => string)): string {
15
- return typeof baseUrl === 'function' ? baseUrl() : baseUrl;
16
- }
17
-
18
- /**
19
- * Resolve headers - if it's a function, call it; otherwise return the object.
20
- */
21
- function resolveHeaders(
22
- headers: Record<string, string> | (() => Record<string, string>)
23
- ): Record<string, string> {
24
- return typeof headers === 'function' ? headers() : headers;
25
- }
26
-
27
- /**
28
- * Escape special regex characters in a string.
29
- */
30
- function escapeRegExp(str: string): string {
31
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
32
- }
33
-
34
- /**
35
- * Substitute path parameters in a URL path template.
36
- * E.g., '/api/users/:id' with { id: '123' } becomes '/api/users/123'
37
- */
38
- function substitutePathParams(pathTemplate: string, pathParams?: Record<string, string>): string {
39
- if (!pathParams) return pathTemplate;
40
-
41
- let result = pathTemplate;
42
- for (const [key, value] of Object.entries(pathParams)) {
43
- const escapedKey = escapeRegExp(key);
44
- result = result.replace(new RegExp(`:${escapedKey}\\??`, 'g'), encodeURIComponent(value));
45
- result = result.replace(new RegExp(`\\*${escapedKey}`, 'g'), encodeURIComponent(value));
46
- }
47
- return result;
48
- }
49
-
50
- /**
51
- * Build URL with query params.
52
- */
53
- function buildUrlWithQuery(baseUrl: string, path: string, query?: Record<string, string>): string {
54
- const url = `${baseUrl}${path}`;
55
- if (!query || Object.keys(query).length === 0) return url;
56
-
57
- const params = new URLSearchParams(query);
58
- return `${url}?${params.toString()}`;
59
- }
60
-
61
- /**
62
- * Create a type-safe API client from a RouteRegistry.
63
- *
64
- * Uses a Proxy to build up the path as you navigate the object,
65
- * then executes the request when you call a terminal method (.post(), .get(), .websocket(), etc.).
66
- *
67
- * @example
68
- * ```typescript
69
- * import { createClient } from '@agentuity/frontend';
70
- * import type { RPCRouteRegistry } from './generated/routes';
71
- *
72
- * const client = createClient<RPCRouteRegistry>();
73
- *
74
- * // Type-safe API call
75
- * const result = await client.hello.post({ name: 'World' });
76
- *
77
- * // WebSocket
78
- * const ws = client.chat.websocket();
79
- * ws.on('message', (msg) => console.log(msg));
80
- *
81
- * // Server-Sent Events
82
- * const es = client.events.eventstream();
83
- * es.on('message', (event) => console.log(event.data));
84
- *
85
- * // Streaming response
86
- * const stream = await client.data.stream({ query: 'foo' });
87
- * stream.on('chunk', (chunk) => console.log(chunk));
88
- * ```
89
- */
90
- export function createClient<R>(options: ClientOptions = {}, metadata?: unknown): Client<R> {
91
- const { baseUrl = defaultBaseUrl, headers, contentType = 'application/json', signal } = options;
92
-
93
- // Default headers to empty object if not provided
94
- const defaultHeaders = headers || {};
95
-
96
- const handler: ProxyHandler<{ __path: string[]; __type?: string }> = {
97
- get(target, prop: string) {
98
- const currentPath = target.__path;
99
- const newPath = [...currentPath, prop];
100
-
101
- // Check if this is a terminal method
102
- const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
103
- const streamMethods = ['websocket', 'eventstream', 'stream'];
104
- const isHttpMethod = httpMethods.includes(prop);
105
- const isStreamMethod = streamMethods.includes(prop);
106
- const isTerminalMethod = isHttpMethod || isStreamMethod;
107
-
108
- // Terminal: method at the end (minimum 1 path segment required)
109
- // Examples: client.hello.post(), client.echo.websocket(), client.events.eventstream()
110
- if (isTerminalMethod && currentPath.length >= 1) {
111
- const method = prop;
112
- const pathSegments = currentPath;
113
-
114
- // Look up route metadata
115
- let routeType = 'api';
116
- let routePath: string | undefined;
117
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
- let metaNode: any = metadata;
119
-
120
- if (isStreamMethod) {
121
- // Stream methods directly specify the route type
122
- if (method === 'websocket') routeType = 'websocket';
123
- else if (method === 'eventstream') routeType = 'sse';
124
- else if (method === 'stream') routeType = 'stream';
125
- }
126
-
127
- if (metadata) {
128
- for (const segment of pathSegments) {
129
- if (metaNode && typeof metaNode === 'object') {
130
- metaNode = metaNode[segment];
131
- }
132
- }
133
- if (metaNode && typeof metaNode === 'object' && metaNode[method]) {
134
- if (metaNode[method].type) {
135
- routeType = metaNode[method].type;
136
- }
137
- if (metaNode[method].path) {
138
- routePath = metaNode[method].path;
139
- }
140
- }
141
- }
142
-
143
- // Fallback URL path if no metadata
144
- const fallbackPath = '/api/' + pathSegments.join('/');
145
-
146
- return (...args: unknown[]) => {
147
- const resolvedBaseUrl = resolveBaseUrl(baseUrl);
148
- const resolvedHeaders = resolveHeaders(defaultHeaders);
149
-
150
- // Get path param names from metadata if available
151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
- const pathParamNames: string[] | undefined = (metaNode as any)?.[method]?.pathParams;
153
- const hasPathParams = pathParamNames && pathParamNames.length > 0;
154
-
155
- let pathParams: Record<string, string> | undefined;
156
- let input: unknown;
157
- let query: Record<string, string> | undefined;
158
-
159
- if (hasPathParams) {
160
- // Route has path params - positional arguments API
161
- // Args are: param1, param2, ..., [options?]
162
- // Example: client.user.get('123', 12) or client.user.get('123', 12, { query: {...} })
163
- pathParams = {};
164
-
165
- for (let i = 0; i < pathParamNames.length; i++) {
166
- const paramName = pathParamNames[i];
167
- if (paramName === undefined) continue;
168
- const arg = args[i];
169
- if (arg === undefined || arg === null) {
170
- throw new Error(
171
- `Missing required path parameter '${paramName}' at position ${i + 1}. ` +
172
- `Expected ${pathParamNames.length} path parameter(s): ${pathParamNames.join(', ')}`
173
- );
174
- }
175
- pathParams[paramName] = String(arg);
176
- }
177
-
178
- // Check if there's an options object after the path params
179
- const optionsArg = args[pathParamNames.length];
180
- if (optionsArg && typeof optionsArg === 'object') {
181
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
- const opts = optionsArg as any;
183
- input = opts.input;
184
- query = opts.query;
185
- }
186
- } else {
187
- // No path params - use existing behavior
188
- const options = args[0];
189
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
- const opts = options as any;
191
- const isOptionsObject =
192
- opts && typeof opts === 'object' && ('input' in opts || 'query' in opts);
193
-
194
- input = isOptionsObject ? opts.input : options;
195
- query = isOptionsObject ? opts.query : undefined;
196
- }
197
-
198
- // Substitute path params in the route path
199
- const basePath = routePath || fallbackPath;
200
- const urlPath = substitutePathParams(basePath, pathParams);
201
-
202
- // WebSocket endpoint
203
- if (routeType === 'websocket') {
204
- const wsBaseUrl = resolvedBaseUrl.replace(/^http/, 'ws');
205
- const wsUrl = buildUrlWithQuery(wsBaseUrl, urlPath, query);
206
- const ws = createWebSocketClient(wsUrl);
207
- if (input) {
208
- ws.on('open', () => ws.send(input));
209
- }
210
- return ws;
211
- }
212
-
213
- // SSE endpoint
214
- if (routeType === 'sse') {
215
- const sseUrl = buildUrlWithQuery(resolvedBaseUrl, urlPath, query);
216
- return createEventStreamClient(sseUrl, {
217
- withCredentials: Object.keys(resolvedHeaders).length > 0,
218
- });
219
- }
220
-
221
- // Stream endpoint
222
- if (routeType === 'stream') {
223
- const streamUrl = buildUrlWithQuery(resolvedBaseUrl, urlPath, query);
224
- return fetch(streamUrl, {
225
- method: method.toUpperCase(),
226
- headers: { 'Content-Type': contentType, ...resolvedHeaders },
227
- body: input ? JSON.stringify(input) : undefined,
228
- signal,
229
- }).then((res) => {
230
- if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
231
- return createStreamClient(res);
232
- });
233
- }
234
-
235
- // Regular API endpoint
236
- const apiUrl = buildUrlWithQuery(resolvedBaseUrl, urlPath, query);
237
- return fetch(apiUrl, {
238
- method: method.toUpperCase(),
239
- headers: { 'Content-Type': contentType, ...resolvedHeaders },
240
- body: method.toUpperCase() !== 'GET' && input ? JSON.stringify(input) : undefined,
241
- signal,
242
- }).then(async (res) => {
243
- if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
244
- if (res.status === 204) return undefined;
245
- return res.json();
246
- });
247
- };
248
- }
249
-
250
- // Otherwise, return a new proxy with extended path
251
- return new Proxy({ __path: newPath }, handler);
252
- },
253
- };
254
-
255
- return new Proxy({ __path: [] }, handler) as unknown as Client<R>;
256
- }
257
-
258
- // Re-export types
259
- export type {
260
- ClientOptions,
261
- Client,
262
- RouteEndpoint,
263
- WebSocketClient,
264
- EventStreamClient,
265
- StreamClient,
266
- EventHandler,
267
- } from './types';
@@ -1,61 +0,0 @@
1
- import type { EventHandler, StreamClient } from './types';
2
-
3
- /**
4
- * Create a streaming response reader with event-based API.
5
- */
6
- export function createStreamClient(response: Response): StreamClient {
7
- const handlers: {
8
- chunk: Set<EventHandler<Uint8Array>>;
9
- close: Set<EventHandler<void>>;
10
- error: Set<EventHandler<Error>>;
11
- } = {
12
- chunk: new Set(),
13
- close: new Set(),
14
- error: new Set(),
15
- };
16
-
17
- const reader = response.body?.getReader();
18
- let cancelled = false;
19
-
20
- // Start reading the stream
21
- const readStream = async () => {
22
- if (!reader) {
23
- const error = new Error('Response body is not readable');
24
- handlers.error.forEach((handler) => handler(error));
25
- return;
26
- }
27
-
28
- try {
29
- while (!cancelled) {
30
- const { done, value } = await reader.read();
31
-
32
- if (done) {
33
- handlers.close.forEach((handler) => handler());
34
- break;
35
- }
36
-
37
- if (value) {
38
- handlers.chunk.forEach((handler) => handler(value));
39
- }
40
- }
41
- } catch (error) {
42
- const err = error instanceof Error ? error : new Error(String(error));
43
- handlers.error.forEach((handler) => handler(err));
44
- }
45
- };
46
-
47
- // Start reading immediately
48
- readStream();
49
-
50
- return {
51
- on: ((event: 'chunk' | 'close' | 'error', handler: EventHandler) => {
52
- handlers[event].add(handler as EventHandler<Uint8Array | void | Error>);
53
- }) as StreamClient['on'],
54
-
55
- async cancel() {
56
- cancelled = true;
57
- await reader?.cancel();
58
- handlers.close.forEach((handler) => handler());
59
- },
60
- };
61
- }
@@ -1,237 +0,0 @@
1
- /**
2
- * Type-safe API client types for RPC-style invocations.
3
- */
4
-
5
- /**
6
- * Options for creating a client.
7
- */
8
- export interface ClientOptions {
9
- /**
10
- * Base URL for API requests (e.g., "https://p1234567890.agenuity.run").
11
- * Defaults to empty string (relative URLs).
12
- * Can be a string or a function that returns a string for lazy resolution.
13
- */
14
- baseUrl?: string | (() => string);
15
-
16
- /**
17
- * Default headers to include in all requests.
18
- * Can be a static object or a function that returns headers for dynamic resolution (e.g., auth tokens).
19
- */
20
- headers?: Record<string, string> | (() => Record<string, string>);
21
-
22
- /**
23
- * Content-Type header for request bodies.
24
- * @default "application/json"
25
- */
26
- contentType?: string;
27
-
28
- /**
29
- * AbortSignal for request cancellation.
30
- */
31
- signal?: AbortSignal;
32
- }
33
-
34
- /**
35
- * Event handler for streaming responses.
36
- */
37
- export type EventHandler<T = unknown> = (data: T) => void;
38
-
39
- /**
40
- * WebSocket wrapper with event-based API.
41
- */
42
- export interface WebSocketClient {
43
- /**
44
- * Register an event handler.
45
- */
46
- on: {
47
- (event: 'message', handler: EventHandler<unknown>): void;
48
- (event: 'open', handler: EventHandler<Event>): void;
49
- (event: 'close', handler: EventHandler<CloseEvent>): void;
50
- (event: 'error', handler: EventHandler<Event>): void;
51
- };
52
-
53
- /**
54
- * Send data through the WebSocket.
55
- */
56
- send(data: unknown): void;
57
-
58
- /**
59
- * Close the WebSocket connection.
60
- */
61
- close(code?: number, reason?: string): void;
62
- }
63
-
64
- /**
65
- * Server-Sent Events (SSE) client with event-based API.
66
- */
67
- export interface EventStreamClient {
68
- /**
69
- * Register an event handler.
70
- */
71
- on: {
72
- (event: 'message', handler: EventHandler<MessageEvent>): void;
73
- (event: 'open', handler: EventHandler<Event>): void;
74
- (event: 'error', handler: EventHandler<Event>): void;
75
- };
76
-
77
- /**
78
- * Close the EventSource connection.
79
- */
80
- close(): void;
81
- }
82
-
83
- /**
84
- * Streaming response reader with event-based API.
85
- */
86
- export interface StreamClient {
87
- /**
88
- * Register an event handler.
89
- */
90
- on: {
91
- (event: 'chunk', handler: EventHandler<Uint8Array>): void;
92
- (event: 'close', handler: EventHandler<void>): void;
93
- (event: 'error', handler: EventHandler<Error>): void;
94
- };
95
-
96
- /**
97
- * Cancel the stream.
98
- */
99
- cancel(): Promise<void>;
100
- }
101
-
102
- /**
103
- * Options object for endpoints without path params (optional query support).
104
- */
105
- export interface EndpointOptionsWithQuery<Input = unknown, Query = Record<string, string>> {
106
- input?: Input;
107
- query?: Query;
108
- }
109
-
110
- /**
111
- * Additional options that can be passed after positional path params.
112
- */
113
- export interface EndpointExtraOptions<Input = unknown, Query = Record<string, string>> {
114
- input?: Input;
115
- query?: Query;
116
- }
117
-
118
- /**
119
- * Convert a path params object to a tuple of its value types.
120
- * Used for positional argument typing.
121
- * Note: For proper ordering, we rely on the generated PathParamsTuple type.
122
- */
123
- export type PathParamsToTuple<P> = P extends readonly [infer First, ...infer Rest]
124
- ? [First, ...PathParamsToTuple<Rest>]
125
- : P extends readonly []
126
- ? []
127
- : string[];
128
-
129
- /**
130
- * API endpoint - callable function for regular HTTP calls.
131
- * - Without path params: accepts input directly OR options object with query
132
- * - With path params: accepts positional arguments followed by optional options object
133
- *
134
- * @example
135
- * // No path params
136
- * client.hello.post({ name: 'World' })
137
- *
138
- * // Single path param
139
- * client.users.userId.get('123')
140
- *
141
- * // Multiple path params
142
- * client.orgs.orgId.members.memberId.get('org-1', 'user-2')
143
- *
144
- * // With additional options
145
- * client.users.userId.get('123', { query: { include: 'posts' } })
146
- */
147
- export type APIEndpoint<
148
- Input = unknown,
149
- Output = unknown,
150
- PathParams = never,
151
- PathParamsTuple extends unknown[] = string[],
152
- > = [PathParams] extends [never]
153
- ? (inputOrOptions?: Input | EndpointOptionsWithQuery<Input>) => Promise<Output>
154
- : (...args: [...PathParamsTuple, options?: EndpointExtraOptions<Input>]) => Promise<Output>;
155
-
156
- /**
157
- * WebSocket endpoint - callable function that returns WebSocket client.
158
- */
159
- export type WebSocketEndpoint<
160
- Input = unknown,
161
- _Output = unknown,
162
- PathParams = never,
163
- PathParamsTuple extends unknown[] = string[],
164
- > = [PathParams] extends [never]
165
- ? (inputOrOptions?: Input | EndpointOptionsWithQuery<Input>) => WebSocketClient
166
- : (...args: [...PathParamsTuple, options?: EndpointExtraOptions<Input>]) => WebSocketClient;
167
-
168
- /**
169
- * Server-Sent Events endpoint - callable function that returns EventStream client.
170
- */
171
- export type SSEEndpoint<
172
- Input = unknown,
173
- _Output = unknown,
174
- PathParams = never,
175
- PathParamsTuple extends unknown[] = string[],
176
- > = [PathParams] extends [never]
177
- ? (inputOrOptions?: Input | EndpointOptionsWithQuery<Input>) => EventStreamClient
178
- : (...args: [...PathParamsTuple, options?: EndpointExtraOptions<Input>]) => EventStreamClient;
179
-
180
- /**
181
- * Streaming endpoint - callable function that returns Stream client.
182
- */
183
- export type StreamEndpoint<
184
- Input = unknown,
185
- _Output = unknown,
186
- PathParams = never,
187
- PathParamsTuple extends unknown[] = string[],
188
- > = [PathParams] extends [never]
189
- ? (inputOrOptions?: Input | EndpointOptionsWithQuery<Input>) => StreamClient
190
- : (...args: [...PathParamsTuple, options?: EndpointExtraOptions<Input>]) => StreamClient;
191
-
192
- /**
193
- * Route endpoint - discriminated union based on route type.
194
- */
195
- export type RouteEndpoint<
196
- Input = unknown,
197
- Output = unknown,
198
- Type extends string = 'api',
199
- PathParams = never,
200
- PathParamsTuple extends unknown[] = [],
201
- > = Type extends 'websocket'
202
- ? WebSocketEndpoint<Input, Output, PathParams, PathParamsTuple>
203
- : Type extends 'sse'
204
- ? SSEEndpoint<Input, Output, PathParams, PathParamsTuple>
205
- : Type extends 'stream'
206
- ? StreamEndpoint<Input, Output, PathParams, PathParamsTuple>
207
- : APIEndpoint<Input, Output, PathParams, PathParamsTuple>;
208
-
209
- /**
210
- * Recursively build the client proxy type from a RouteRegistry.
211
- */
212
- export type Client<R> = {
213
- [K in keyof R]: R[K] extends {
214
- input: infer I;
215
- output: infer O;
216
- type: infer T;
217
- params: infer P;
218
- paramsTuple: infer PT;
219
- }
220
- ? PT extends unknown[]
221
- ? RouteEndpoint<I, O, T extends string ? T : 'api', P, PT>
222
- : RouteEndpoint<I, O, T extends string ? T : 'api', P>
223
- : R[K] extends {
224
- input: infer I;
225
- output: infer O;
226
- type: infer T;
227
- params: infer P;
228
- }
229
- ? RouteEndpoint<I, O, T extends string ? T : 'api', P>
230
- : R[K] extends { input: infer I; output: infer O; type: infer T }
231
- ? RouteEndpoint<I, O, T extends string ? T : 'api'>
232
- : R[K] extends { input: infer I; output: infer O }
233
- ? RouteEndpoint<I, O, 'api'>
234
- : R[K] extends object
235
- ? Client<R[K]>
236
- : never;
237
- };
@@ -1,61 +0,0 @@
1
- import type { EventHandler, WebSocketClient } from './types';
2
-
3
- /**
4
- * Create a WebSocket client wrapper with event-based API.
5
- */
6
- export function createWebSocketClient(url: string, protocols?: string | string[]): WebSocketClient {
7
- const ws = new WebSocket(url, protocols);
8
- const handlers: {
9
- message: Set<EventHandler<unknown>>;
10
- open: Set<EventHandler<Event>>;
11
- close: Set<EventHandler<CloseEvent>>;
12
- error: Set<EventHandler<Event>>;
13
- } = {
14
- message: new Set(),
15
- open: new Set(),
16
- close: new Set(),
17
- error: new Set(),
18
- };
19
-
20
- // Set up native WebSocket event listeners
21
- ws.addEventListener('message', (event: MessageEvent) => {
22
- let data: unknown = event.data;
23
- // Try to parse JSON if data is a string
24
- if (typeof event.data === 'string') {
25
- try {
26
- data = JSON.parse(event.data);
27
- } catch {
28
- // Keep as string if not valid JSON
29
- data = event.data;
30
- }
31
- }
32
- handlers.message.forEach((handler) => handler(data));
33
- });
34
-
35
- ws.addEventListener('open', (event: Event) => {
36
- handlers.open.forEach((handler) => handler(event));
37
- });
38
-
39
- ws.addEventListener('close', (event: CloseEvent) => {
40
- handlers.close.forEach((handler) => handler(event));
41
- });
42
-
43
- ws.addEventListener('error', (event: Event) => {
44
- handlers.error.forEach((handler) => handler(event));
45
- });
46
-
47
- return {
48
- on: ((event: 'message' | 'open' | 'close' | 'error', handler: EventHandler) => {
49
- handlers[event].add(handler as EventHandler<unknown | Event | CloseEvent>);
50
- }) as WebSocketClient['on'],
51
-
52
- send(data: unknown) {
53
- const payload = typeof data === 'string' ? data : JSON.stringify(data);
54
- ws.send(payload);
55
- },
56
-
57
- close(code?: number, reason?: string) {
58
- ws.close(code, reason);
59
- },
60
- };
61
- }