@richie-rpc/client 1.2.3 → 1.2.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.
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n Contract,\n EndpointDefinition,\n ExtractBody,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n} from '@richie-rpc/core';\nimport { buildUrl, interpolatePath, objectToFormData } from '@richie-rpc/core';\nimport type { z } from 'zod';\n\n// Client configuration\nexport interface ClientConfig {\n baseUrl: string;\n headers?: Record<string, string>;\n validateRequest?: boolean;\n validateResponse?: boolean;\n}\n\n// Request options for an endpoint\nexport type EndpointRequestOptions<T extends EndpointDefinition> = {\n params?: ExtractParams<T> extends never ? never : ExtractParams<T>;\n query?: ExtractQuery<T> extends never ? never : ExtractQuery<T>;\n headers?: ExtractHeaders<T> extends never ? never : ExtractHeaders<T>;\n body?: ExtractBody<T> extends never ? never : ExtractBody<T>;\n abortSignal?: AbortSignal;\n};\n\n// Response type for an endpoint (union of all possible responses)\nexport type EndpointResponse<T extends EndpointDefinition> = {\n [Status in keyof T['responses']]: {\n status: Status;\n data: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;\n };\n}[keyof T['responses']];\n\n// Client method type for an endpoint\nexport type ClientMethod<T extends EndpointDefinition> = (\n options: EndpointRequestOptions<T>,\n) => Promise<EndpointResponse<T>>;\n\n// Client type for a contract\nexport type Client<T extends Contract> = {\n [K in keyof T]: ClientMethod<T[K]>;\n};\n\n// Validation error\nexport class ClientValidationError extends Error {\n constructor(\n public field: string,\n public issues: z.ZodIssue[],\n ) {\n super(`Validation failed for ${field}`);\n this.name = 'ClientValidationError';\n }\n}\n\n// HTTP error\nexport class HTTPError extends Error {\n constructor(\n public status: number,\n public statusText: string,\n public body: unknown,\n ) {\n super(`HTTP Error ${status}: ${statusText}`);\n this.name = 'HTTPError';\n }\n}\n\n/**\n * Validate request data before sending\n */\nfunction validateRequest<T extends EndpointDefinition>(\n endpoint: T,\n options: EndpointRequestOptions<T>,\n): void {\n // Validate params\n if (endpoint.params && options.params) {\n const result = endpoint.params.safeParse(options.params);\n if (!result.success) {\n throw new ClientValidationError('params', result.error.issues);\n }\n }\n\n // Validate query\n if (endpoint.query && options.query) {\n const result = endpoint.query.safeParse(options.query);\n if (!result.success) {\n throw new ClientValidationError('query', result.error.issues);\n }\n }\n\n // Validate headers\n if (endpoint.headers && options.headers) {\n const result = endpoint.headers.safeParse(options.headers);\n if (!result.success) {\n throw new ClientValidationError('headers', result.error.issues);\n }\n }\n\n // Validate body\n if (endpoint.body && options.body) {\n const result = endpoint.body.safeParse(options.body);\n if (!result.success) {\n throw new ClientValidationError('body', result.error.issues);\n }\n }\n}\n\n/**\n * Validate response data after receiving\n */\nfunction validateResponse<T extends EndpointDefinition>(\n endpoint: T,\n status: number,\n data: unknown,\n): void {\n const responseSchema = endpoint.responses[status];\n if (responseSchema) {\n const result = responseSchema.safeParse(data);\n if (!result.success) {\n throw new ClientValidationError(`response[${status}]`, result.error.issues);\n }\n }\n}\n\n/**\n * Make a request to an endpoint\n */\nasync function makeRequest<T extends EndpointDefinition>(\n config: ClientConfig,\n endpoint: T,\n options: EndpointRequestOptions<T>,\n): Promise<EndpointResponse<T>> {\n // Validate request if enabled\n if (config.validateRequest !== false) {\n validateRequest(endpoint, options);\n }\n\n // Build URL\n let path = endpoint.path;\n if (options.params) {\n path = interpolatePath(path, options.params as Record<string, string | number>);\n }\n\n const url = buildUrl(\n config.baseUrl,\n path,\n options.query as Record<string, string | number | boolean | string[]> | undefined,\n );\n\n // Build headers\n const headers = new Headers(config.headers);\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n headers.set(key, String(value));\n }\n }\n\n // Build request init\n const init: RequestInit = {\n method: endpoint.method,\n headers,\n };\n\n // Add abort signal if present\n if (options.abortSignal) {\n init.signal = options.abortSignal;\n }\n\n // Add body if present\n if (options.body !== undefined) {\n const contentType = endpoint.contentType ?? 'application/json';\n\n if (contentType === 'multipart/form-data') {\n // Don't set Content-Type header - browser sets boundary automatically\n init.body = objectToFormData(options.body as Record<string, unknown>);\n } else {\n headers.set('content-type', 'application/json');\n init.body = JSON.stringify(options.body);\n }\n }\n\n // Make request\n const response = await fetch(url, init);\n\n // Parse response\n let data: unknown;\n\n // Handle 204 No Content\n if (response.status === 204) {\n data = {};\n } else {\n const contentType = response.headers.get('content-type') || '';\n\n if (contentType.includes('application/json')) {\n data = await response.json();\n } else if (contentType.includes('text/')) {\n data = await response.text();\n } else {\n // Check if there's any content\n const text = await response.text();\n if (text) {\n data = text;\n } else {\n data = {};\n }\n }\n }\n\n // Check for HTTP errors\n if (!response.ok && !(response.status in endpoint.responses)) {\n throw new HTTPError(response.status, response.statusText, data);\n }\n\n // Validate response if enabled\n if (config.validateResponse !== false) {\n validateResponse(endpoint, response.status, data);\n }\n\n return {\n status: response.status,\n data,\n } as EndpointResponse<T>;\n}\n\n/**\n * Resolve relative baseUrl to absolute URL in browser contexts\n */\nfunction resolveBaseUrl(baseUrl: string): string {\n // If baseUrl is already absolute, return as-is\n if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) {\n return baseUrl;\n }\n\n // If baseUrl is relative (starts with /), resolve it using window.location in browser\n if (baseUrl.startsWith('/')) {\n const g = globalThis as unknown as { location?: { origin?: string } };\n const origin = g?.location?.origin || 'http://localhost';\n return origin + baseUrl;\n }\n\n // Otherwise, assume it's a full URL\n return baseUrl;\n}\n\n/**\n * Create a typesafe client for a contract\n */\nexport function createClient<T extends Contract>(contract: T, config: ClientConfig): Client<T> {\n // Resolve relative baseUrl to absolute URL\n const resolvedConfig = {\n ...config,\n baseUrl: resolveBaseUrl(config.baseUrl),\n };\n\n const client: Record<string, unknown> = {};\n\n for (const [name, endpoint] of Object.entries(contract)) {\n client[name] = (options: EndpointRequestOptions<EndpointDefinition> = {}) => {\n return makeRequest(resolvedConfig, endpoint, options);\n };\n }\n\n return client as Client<T>;\n}\n\n/**\n * Create a client without providing the contract at runtime\n * Useful when you only need types and want a lighter bundle\n */\nexport function createTypedClient<T extends Contract>(_config: ClientConfig): Client<T> {\n return new Proxy({} as Client<T>, {\n get(_target, _prop: string) {\n return async (_options: EndpointRequestOptions<EndpointDefinition> = {}) => {\n // Without the contract, we can't validate or infer the endpoint\n // This is just a basic fetch wrapper with typing\n throw new Error(\n 'createTypedClient requires contract at runtime for validation. Use createClient instead.',\n );\n };\n },\n });\n}\n"
5
+ "/* eslint-disable @typescript-eslint/no-non-null-assertion */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n Contract,\n DownloadEndpointDefinition,\n DownloadProgressEvent,\n EndpointDefinition,\n ExtractBody,\n ExtractChunk,\n ExtractFinalResponse,\n ExtractHeaders,\n ExtractParams,\n ExtractQuery,\n ExtractSSEEventData,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n UploadProgressEvent,\n} from '@richie-rpc/core';\nimport { buildUrl, interpolatePath, objectToFormData } from '@richie-rpc/core';\n\n// Re-export for convenience\nexport type { UploadProgressEvent, DownloadProgressEvent };\nimport type { z } from 'zod';\n\n// Client configuration\nexport interface ClientConfig {\n baseUrl: string;\n headers?: Record<string, string>;\n validateRequest?: boolean;\n validateResponse?: boolean;\n}\n\n// Request options for an endpoint\nexport type EndpointRequestOptions<T extends EndpointDefinition> = {\n params?: ExtractParams<T> extends never ? never : ExtractParams<T>;\n query?: ExtractQuery<T> extends never ? never : ExtractQuery<T>;\n headers?: ExtractHeaders<T> extends never ? never : ExtractHeaders<T>;\n body?: ExtractBody<T> extends never ? never : ExtractBody<T>;\n abortSignal?: AbortSignal;\n /** Upload progress callback (uses XHR for progress tracking) */\n onUploadProgress?: (event: UploadProgressEvent) => void;\n};\n\n// Response type for a standard endpoint (union of all possible responses)\nexport type EndpointResponse<T extends StandardEndpointDefinition> = {\n [Status in keyof T['responses']]: {\n status: Status;\n data: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;\n };\n}[keyof T['responses']];\n\n// Client method type for a standard endpoint\nexport type ClientMethod<T extends StandardEndpointDefinition> = (\n options: EndpointRequestOptions<T>,\n) => Promise<EndpointResponse<T>>;\n\n// ============================================\n// Streaming Endpoint Client Types\n// ============================================\n\n/**\n * Result object for streaming endpoints - event-based API\n */\nexport interface StreamingResult<T extends StreamingEndpointDefinition> {\n /** Subscribe to chunks */\n on(event: 'chunk', handler: (chunk: ExtractChunk<T>) => void): () => void;\n /** Subscribe to stream close (with optional final response) */\n on(event: 'close', handler: (final?: ExtractFinalResponse<T>) => void): () => void;\n /** Subscribe to errors */\n on(event: 'error', handler: (error: Error) => void): () => void;\n /** Abort the stream */\n abort(): void;\n /** Check if aborted */\n readonly aborted: boolean;\n}\n\n/**\n * Client method type for streaming endpoints\n */\nexport type StreamingClientMethod<T extends StreamingEndpointDefinition> = (\n options: EndpointRequestOptions<T>,\n) => Promise<StreamingResult<T>>;\n\n// ============================================\n// SSE Endpoint Client Types\n// ============================================\n\n/**\n * Connection object for SSE endpoints - event-based API\n */\nexport interface SSEConnection<T extends SSEEndpointDefinition> {\n /** Subscribe to a specific event type */\n on<K extends keyof T['events']>(\n event: K,\n handler: (data: ExtractSSEEventData<T, K>, id?: string) => void,\n ): () => void;\n /** Subscribe to errors */\n on(event: 'error', handler: (error: Error) => void): () => void;\n /** Close the connection */\n close(): void;\n /** Current connection state */\n readonly state: 'connecting' | 'open' | 'closed';\n}\n\n/**\n * Client method type for SSE endpoints\n */\nexport type SSEClientMethod<T extends SSEEndpointDefinition> = (\n options?: Omit<EndpointRequestOptions<T>, 'body' | 'onUploadProgress'>,\n) => SSEConnection<T>;\n\n// ============================================\n// Download Endpoint Client Types\n// ============================================\n\n/**\n * Request options for download endpoints\n */\nexport type DownloadRequestOptions<T extends DownloadEndpointDefinition> = {\n params?: ExtractParams<T> extends never ? never : ExtractParams<T>;\n query?: ExtractQuery<T> extends never ? never : ExtractQuery<T>;\n headers?: ExtractHeaders<T> extends never ? never : ExtractHeaders<T>;\n abortSignal?: AbortSignal;\n /** Download progress callback */\n onDownloadProgress?: (event: DownloadProgressEvent) => void;\n};\n\n/**\n * Response type for download endpoints\n * Success (200) returns File, errors return typed error response\n */\nexport type DownloadResponse<T extends DownloadEndpointDefinition> =\n | { status: 200; data: File }\n | (T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? {\n [S in keyof T['errorResponses']]: {\n status: S;\n data: T['errorResponses'][S] extends z.ZodTypeAny\n ? z.infer<T['errorResponses'][S]>\n : never;\n };\n }[keyof T['errorResponses']]\n : never);\n\n/**\n * Client method type for download endpoints\n */\nexport type DownloadClientMethod<T extends DownloadEndpointDefinition> = (\n options?: DownloadRequestOptions<T>,\n) => Promise<DownloadResponse<T>>;\n\n// Client type for a contract (supports all endpoint types)\nexport type Client<T extends Contract> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? ClientMethod<T[K]>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingClientMethod<T[K]>\n : T[K] extends SSEEndpointDefinition\n ? SSEClientMethod<T[K]>\n : T[K] extends DownloadEndpointDefinition\n ? DownloadClientMethod<T[K]>\n : never;\n};\n\n// Validation error\nexport class ClientValidationError extends Error {\n constructor(\n public field: string,\n public issues: z.ZodIssue[],\n ) {\n super(`Validation failed for ${field}`);\n this.name = 'ClientValidationError';\n }\n}\n\n// HTTP error\nexport class HTTPError extends Error {\n constructor(\n public status: number,\n public statusText: string,\n public body: unknown,\n ) {\n super(`HTTP Error ${status}: ${statusText}`);\n this.name = 'HTTPError';\n }\n}\n\n/**\n * Validate request data before sending\n */\nfunction validateRequest<T extends StandardEndpointDefinition>(\n endpoint: T,\n options: EndpointRequestOptions<T>,\n): void {\n // Validate params\n if (endpoint.params && options.params) {\n const result = endpoint.params.safeParse(options.params);\n if (!result.success) {\n throw new ClientValidationError('params', result.error.issues);\n }\n }\n\n // Validate query\n if (endpoint.query && options.query) {\n const result = endpoint.query.safeParse(options.query);\n if (!result.success) {\n throw new ClientValidationError('query', result.error.issues);\n }\n }\n\n // Validate headers\n if (endpoint.headers && options.headers) {\n const result = endpoint.headers.safeParse(options.headers);\n if (!result.success) {\n throw new ClientValidationError('headers', result.error.issues);\n }\n }\n\n // Validate body\n if (endpoint.body && options.body) {\n const result = endpoint.body.safeParse(options.body);\n if (!result.success) {\n throw new ClientValidationError('body', result.error.issues);\n }\n }\n}\n\n/**\n * Validate response data after receiving\n */\nfunction validateResponse<T extends StandardEndpointDefinition>(\n endpoint: T,\n status: number,\n data: unknown,\n): void {\n const responseSchema = endpoint.responses[status];\n if (responseSchema) {\n const result = responseSchema.safeParse(data);\n if (!result.success) {\n throw new ClientValidationError(`response[${status}]`, result.error.issues);\n }\n }\n}\n\n/**\n * Extract filename from Content-Disposition header\n */\nfunction extractFilename(contentDisposition: string | null): string | null {\n if (!contentDisposition) return null;\n // Try filename*= (RFC 5987) first\n const filenameStarMatch = contentDisposition.match(/filename\\*=(?:UTF-8'')?([^;\\s]+)/i);\n if (filenameStarMatch && filenameStarMatch[1]) {\n return decodeURIComponent(filenameStarMatch[1]);\n }\n // Try filename= (standard)\n const filenameMatch = contentDisposition.match(/filename=[\"']?([^\"';\\s]+)[\"']?/i);\n if (filenameMatch && filenameMatch[1]) {\n return filenameMatch[1];\n }\n return null;\n}\n\n/**\n * Validate download request data before sending\n */\nfunction validateDownloadRequest<T extends DownloadEndpointDefinition>(\n endpoint: T,\n options: DownloadRequestOptions<T>,\n): void {\n // Validate params\n if (endpoint.params && options.params) {\n const result = endpoint.params.safeParse(options.params);\n if (!result.success) {\n throw new ClientValidationError('params', result.error.issues);\n }\n }\n\n // Validate query\n if (endpoint.query && options.query) {\n const result = endpoint.query.safeParse(options.query);\n if (!result.success) {\n throw new ClientValidationError('query', result.error.issues);\n }\n }\n\n // Validate headers\n if (endpoint.headers && options.headers) {\n const result = endpoint.headers.safeParse(options.headers);\n if (!result.success) {\n throw new ClientValidationError('headers', result.error.issues);\n }\n }\n}\n\n/**\n * Validate download error response data\n */\nfunction validateDownloadErrorResponse<T extends DownloadEndpointDefinition>(\n endpoint: T,\n status: number,\n data: unknown,\n): void {\n if (endpoint.errorResponses) {\n const responseSchema = endpoint.errorResponses[status];\n if (responseSchema) {\n const result = responseSchema.safeParse(data);\n if (!result.success) {\n throw new ClientValidationError(`response[${status}]`, result.error.issues);\n }\n }\n }\n}\n\n/**\n * Make a download request using fetch with progress support\n */\nasync function makeDownloadRequest<T extends DownloadEndpointDefinition>(\n config: ClientConfig,\n endpoint: T,\n options: DownloadRequestOptions<T> = {},\n): Promise<DownloadResponse<T>> {\n // Validate request if enabled\n if (config.validateRequest !== false) {\n validateDownloadRequest(endpoint, options);\n }\n\n // Build URL\n let path = endpoint.path;\n if (options.params) {\n path = interpolatePath(path, options.params as Record<string, string | number>);\n }\n\n const url = buildUrl(\n config.baseUrl,\n path,\n options.query as Record<string, string | number | boolean | string[]> | undefined,\n );\n\n // Build headers\n const headers = new Headers(config.headers);\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n headers.set(key, String(value));\n }\n }\n\n // Build request init\n const init: RequestInit = {\n method: 'GET',\n headers,\n };\n\n // Add abort signal if present\n if (options.abortSignal) {\n init.signal = options.abortSignal;\n }\n\n // Make request\n const response = await fetch(url, init);\n\n // Handle success (200) - return File\n if (response.status === 200) {\n const contentLength = response.headers.get('content-length');\n const total = contentLength ? parseInt(contentLength, 10) : 0;\n\n let blob: Blob;\n\n if (options.onDownloadProgress && response.body) {\n // Stream the response to track progress\n const reader = response.body.getReader();\n const chunks: BlobPart[] = [];\n let loaded = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n chunks.push(value);\n loaded += value.length;\n\n options.onDownloadProgress({\n loaded,\n total,\n progress: total > 0 ? loaded / total : NaN,\n });\n }\n\n blob = new Blob(chunks);\n } else {\n blob = await response.blob();\n }\n\n const contentDisposition = response.headers.get('content-disposition');\n const filename = extractFilename(contentDisposition) || 'download';\n const contentType = response.headers.get('content-type') || 'application/octet-stream';\n\n const file = new File([blob], filename, { type: contentType });\n\n return {\n status: 200,\n data: file,\n } as DownloadResponse<T>;\n }\n\n // Handle error responses\n let data: unknown;\n const contentType = response.headers.get('content-type') || '';\n\n if (contentType.includes('application/json')) {\n data = await response.json();\n } else {\n data = await response.text();\n }\n\n // Check for HTTP errors not in errorResponses\n if (endpoint.errorResponses && !(response.status in endpoint.errorResponses)) {\n throw new HTTPError(response.status, response.statusText, data);\n }\n\n // Validate error response if enabled\n if (config.validateResponse !== false) {\n validateDownloadErrorResponse(endpoint, response.status, data);\n }\n\n return {\n status: response.status,\n data,\n } as DownloadResponse<T>;\n}\n\n/**\n * Make a request using XMLHttpRequest for upload progress support\n */\nfunction makeRequestWithXHR<T extends StandardEndpointDefinition>(\n config: ClientConfig,\n endpoint: T,\n options: EndpointRequestOptions<T>,\n url: string,\n): Promise<EndpointResponse<T>> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open(endpoint.method, url);\n\n // Set base headers from config\n if (config.headers) {\n for (const [key, value] of Object.entries(config.headers)) {\n xhr.setRequestHeader(key, value);\n }\n }\n\n // Set request-specific headers\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n xhr.setRequestHeader(key, String(value));\n }\n }\n\n // Upload progress callback\n if (options.onUploadProgress) {\n xhr.upload.onprogress = (e) => {\n if (e.lengthComputable && options.onUploadProgress) {\n options.onUploadProgress({\n loaded: e.loaded,\n total: e.total,\n progress: e.loaded / e.total,\n });\n }\n };\n }\n\n // Handle abort signal\n if (options.abortSignal) {\n if (options.abortSignal.aborted) {\n xhr.abort();\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n options.abortSignal.addEventListener('abort', () => {\n xhr.abort();\n });\n }\n\n xhr.onload = () => {\n let data: unknown;\n const responseContentType = xhr.getResponseHeader('content-type') || '';\n\n if (xhr.status === 204) {\n data = {};\n } else if (responseContentType.includes('application/json')) {\n try {\n data = JSON.parse(xhr.responseText);\n } catch {\n data = xhr.responseText || {};\n }\n } else if (responseContentType.includes('text/')) {\n data = xhr.responseText;\n } else {\n data = xhr.responseText || {};\n }\n\n // Check for HTTP errors\n if (xhr.status >= 400 && !(xhr.status in endpoint.responses)) {\n reject(new HTTPError(xhr.status, xhr.statusText, data));\n return;\n }\n\n // Validate response if enabled\n if (config.validateResponse !== false) {\n try {\n validateResponse(endpoint, xhr.status, data);\n } catch (err) {\n reject(err);\n return;\n }\n }\n\n resolve({\n status: xhr.status,\n data,\n } as EndpointResponse<T>);\n };\n\n xhr.onerror = () => reject(new Error('Network error'));\n xhr.onabort = () => reject(new DOMException('Aborted', 'AbortError'));\n\n // Prepare and send body\n const contentType = endpoint.contentType ?? 'application/json';\n if (options.body !== undefined) {\n if (contentType === 'multipart/form-data') {\n // Don't set Content-Type header - browser sets boundary automatically\n xhr.send(objectToFormData(options.body as Record<string, unknown>));\n } else {\n xhr.setRequestHeader('content-type', 'application/json');\n xhr.send(JSON.stringify(options.body));\n }\n } else {\n xhr.send();\n }\n });\n}\n\n/**\n * Make a request to a standard endpoint\n */\nasync function makeRequest<T extends StandardEndpointDefinition>(\n config: ClientConfig,\n endpoint: T,\n options: EndpointRequestOptions<T>,\n): Promise<EndpointResponse<T>> {\n // Validate request if enabled\n if (config.validateRequest !== false) {\n validateRequest(endpoint, options);\n }\n\n // Build URL\n let path = endpoint.path;\n if (options.params) {\n path = interpolatePath(path, options.params as Record<string, string | number>);\n }\n\n const url = buildUrl(\n config.baseUrl,\n path,\n options.query as Record<string, string | number | boolean | string[]> | undefined,\n );\n\n // Use XHR for upload progress support\n if (options.onUploadProgress && options.body !== undefined) {\n return makeRequestWithXHR(config, endpoint, options, url);\n }\n\n // Build headers\n const headers = new Headers(config.headers);\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n headers.set(key, String(value));\n }\n }\n\n // Build request init\n const init: RequestInit = {\n method: endpoint.method,\n headers,\n };\n\n // Add abort signal if present\n if (options.abortSignal) {\n init.signal = options.abortSignal;\n }\n\n // Add body if present\n if (options.body !== undefined) {\n const contentType = endpoint.contentType ?? 'application/json';\n\n if (contentType === 'multipart/form-data') {\n // Don't set Content-Type header - browser sets boundary automatically\n init.body = objectToFormData(options.body as Record<string, unknown>);\n } else {\n headers.set('content-type', 'application/json');\n init.body = JSON.stringify(options.body);\n }\n }\n\n // Make request\n const response = await fetch(url, init);\n\n // Parse response\n let data: unknown;\n\n // Handle 204 No Content\n if (response.status === 204) {\n data = {};\n } else {\n const contentType = response.headers.get('content-type') || '';\n\n if (contentType.includes('application/json')) {\n data = await response.json();\n } else if (contentType.includes('text/')) {\n data = await response.text();\n } else {\n // Check if there's any content\n const text = await response.text();\n if (text) {\n data = text;\n } else {\n data = {};\n }\n }\n }\n\n // Check for HTTP errors\n if (!response.ok && !(response.status in endpoint.responses)) {\n throw new HTTPError(response.status, response.statusText, data);\n }\n\n // Validate response if enabled\n if (config.validateResponse !== false) {\n validateResponse(endpoint, response.status, data);\n }\n\n return {\n status: response.status,\n data,\n } as EndpointResponse<T>;\n}\n\n/**\n * Create a streaming result from an NDJSON response\n */\nfunction createStreamingResult<T extends StreamingEndpointDefinition>(\n response: Response,\n controller: AbortController,\n): StreamingResult<T> {\n type ChunkHandler = (chunk: ExtractChunk<T>) => void;\n type CloseHandler = (final?: ExtractFinalResponse<T>) => void;\n type ErrorHandler = (error: Error) => void;\n\n const listeners = {\n chunk: new Set<ChunkHandler>(),\n close: new Set<CloseHandler>(),\n error: new Set<ErrorHandler>(),\n };\n\n // Start reading in background\n (async () => {\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line);\n if (parsed.__final__) {\n listeners.close.forEach((h) => h(parsed.data));\n } else {\n listeners.chunk.forEach((h) => h(parsed as ExtractChunk<T>));\n }\n } catch (parseErr) {\n listeners.error.forEach((h) => h(parseErr as Error));\n }\n }\n }\n\n // Process any remaining buffer content\n if (buffer.trim()) {\n try {\n const parsed = JSON.parse(buffer);\n if (parsed.__final__) {\n listeners.close.forEach((h) => h(parsed.data));\n } else {\n listeners.chunk.forEach((h) => h(parsed as ExtractChunk<T>));\n }\n } catch {\n // Ignore incomplete JSON at end\n }\n }\n\n // Stream ended without final message\n listeners.close.forEach((h) => h());\n } catch (err) {\n if ((err as Error).name !== 'AbortError') {\n listeners.error.forEach((h) => h(err as Error));\n }\n }\n })();\n\n return {\n on(event: 'chunk' | 'close' | 'error', handler: ChunkHandler | CloseHandler | ErrorHandler) {\n (listeners[event] as Set<typeof handler>).add(handler);\n return () => (listeners[event] as Set<typeof handler>).delete(handler);\n },\n abort() {\n controller.abort();\n },\n get aborted() {\n return controller.signal.aborted;\n },\n } as StreamingResult<T>;\n}\n\n/**\n * Validate streaming request data before sending\n */\nfunction validateStreamingRequest<T extends StreamingEndpointDefinition>(\n endpoint: T,\n options: EndpointRequestOptions<T>,\n): void {\n // Validate params\n if (endpoint.params && options.params) {\n const result = endpoint.params.safeParse(options.params);\n if (!result.success) {\n throw new ClientValidationError('params', result.error.issues);\n }\n }\n\n // Validate query\n if (endpoint.query && options.query) {\n const result = endpoint.query.safeParse(options.query);\n if (!result.success) {\n throw new ClientValidationError('query', result.error.issues);\n }\n }\n\n // Validate headers\n if (endpoint.headers && options.headers) {\n const result = endpoint.headers.safeParse(options.headers);\n if (!result.success) {\n throw new ClientValidationError('headers', result.error.issues);\n }\n }\n\n // Validate body\n if (endpoint.body && options.body) {\n const result = endpoint.body.safeParse(options.body);\n if (!result.success) {\n throw new ClientValidationError('body', result.error.issues);\n }\n }\n}\n\n/**\n * Make a streaming request to an endpoint\n */\nasync function makeStreamingRequest<T extends StreamingEndpointDefinition>(\n config: ClientConfig,\n endpoint: T,\n options: EndpointRequestOptions<T>,\n): Promise<StreamingResult<T>> {\n // Validate request if enabled\n if (config.validateRequest !== false) {\n validateStreamingRequest(endpoint, options);\n }\n\n // Build URL\n let path = endpoint.path;\n if (options.params) {\n path = interpolatePath(path, options.params as Record<string, string | number>);\n }\n\n const url = buildUrl(\n config.baseUrl,\n path,\n options.query as Record<string, string | number | boolean | string[]> | undefined,\n );\n\n // Build headers\n const headers = new Headers(config.headers);\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n headers.set(key, String(value));\n }\n }\n\n // Build request init - create our own controller for abort() method\n const controller = new AbortController();\n\n // Link to external abort signal if provided\n if (options.abortSignal) {\n if (options.abortSignal.aborted) {\n controller.abort();\n } else {\n options.abortSignal.addEventListener('abort', () => controller.abort());\n }\n }\n\n const init: RequestInit = {\n method: endpoint.method,\n headers,\n signal: controller.signal,\n };\n\n // Add body if present\n if (options.body !== undefined) {\n const contentType = endpoint.contentType ?? 'application/json';\n\n if (contentType === 'multipart/form-data') {\n init.body = objectToFormData(options.body as Record<string, unknown>);\n } else {\n headers.set('content-type', 'application/json');\n init.body = JSON.stringify(options.body);\n }\n }\n\n // Make request\n const response = await fetch(url, init);\n\n // Check for error responses before streaming\n if (!response.ok) {\n const contentType = response.headers.get('content-type') || '';\n let data: unknown;\n\n if (contentType.includes('application/json')) {\n data = await response.json();\n } else {\n data = await response.text();\n }\n\n throw new HTTPError(response.status, response.statusText, data);\n }\n\n // Return streaming result\n return createStreamingResult<T>(response, controller);\n}\n\n/**\n * Create an SSE connection\n */\nfunction createSSEConnection<T extends SSEEndpointDefinition>(\n config: ClientConfig,\n endpoint: T,\n options: Omit<EndpointRequestOptions<T>, 'body' | 'onUploadProgress'> = {},\n): SSEConnection<T> {\n // Build URL\n let path = endpoint.path;\n if (options.params) {\n path = interpolatePath(path, options.params as Record<string, string | number>);\n }\n\n const url = buildUrl(\n config.baseUrl,\n path,\n options.query as Record<string, string | number | boolean | string[]> | undefined,\n );\n\n // EventSource doesn't support custom headers, but we can include query params\n // Note: If auth headers are needed, consider using fetch-based SSE or passing auth in query\n const eventSource = new EventSource(url);\n\n type EventHandler = (data: unknown, id?: string) => void;\n type ErrorHandler = (error: Error) => void;\n\n const listeners: Record<string, Set<EventHandler | ErrorHandler>> = {\n error: new Set<ErrorHandler>(),\n };\n\n // Get event names from the endpoint\n const eventNames = Object.keys(endpoint.events);\n\n // Register listeners for each event type\n for (const eventName of eventNames) {\n listeners[eventName] = new Set<EventHandler>();\n eventSource.addEventListener(eventName, (e) => {\n const messageEvent = e as MessageEvent;\n try {\n const data = JSON.parse(messageEvent.data);\n (listeners[eventName] as Set<EventHandler>).forEach((h) =>\n h(data, messageEvent.lastEventId || undefined),\n );\n } catch (err) {\n (listeners.error as Set<ErrorHandler>).forEach((h) =>\n h(new Error(`Failed to parse SSE data: ${(err as Error).message}`)),\n );\n }\n });\n }\n\n // Handle errors\n eventSource.onerror = () => {\n (listeners.error as Set<ErrorHandler>).forEach((h) => h(new Error('SSE connection error')));\n };\n\n return {\n on(event: string, handler: EventHandler | ErrorHandler) {\n if (!listeners[event]) {\n listeners[event] = new Set();\n }\n (listeners[event] as Set<typeof handler>).add(handler);\n return () => (listeners[event] as Set<typeof handler>).delete(handler);\n },\n close() {\n eventSource.close();\n },\n get state() {\n const states = ['connecting', 'open', 'closed'] as const;\n return states[eventSource.readyState];\n },\n } as SSEConnection<T>;\n}\n\n/**\n * Resolve relative baseUrl to absolute URL in browser contexts\n */\nfunction resolveBaseUrl(baseUrl: string): string {\n // If baseUrl is already absolute, return as-is\n if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) {\n return baseUrl;\n }\n\n // If baseUrl is relative (starts with /), resolve it using window.location in browser\n if (baseUrl.startsWith('/')) {\n const g = globalThis as unknown as { location?: { origin?: string } };\n const origin = g?.location?.origin || 'http://localhost';\n return origin + baseUrl;\n }\n\n // Otherwise, assume it's a full URL\n return baseUrl;\n}\n\n/**\n * Create a typesafe client for a contract\n */\nexport function createClient<T extends Contract>(contract: T, config: ClientConfig): Client<T> {\n // Resolve relative baseUrl to absolute URL\n const resolvedConfig = {\n ...config,\n baseUrl: resolveBaseUrl(config.baseUrl),\n };\n\n const client: Record<string, unknown> = {};\n\n for (const [name, endpoint] of Object.entries(contract)) {\n if (endpoint.type === 'standard') {\n client[name] = (options: EndpointRequestOptions<StandardEndpointDefinition> = {}) => {\n return makeRequest(resolvedConfig, endpoint, options);\n };\n } else if (endpoint.type === 'streaming') {\n client[name] = (options: EndpointRequestOptions<StreamingEndpointDefinition> = {}) => {\n return makeStreamingRequest(resolvedConfig, endpoint, options);\n };\n } else if (endpoint.type === 'sse') {\n client[name] = (\n options: Omit<\n EndpointRequestOptions<SSEEndpointDefinition>,\n 'body' | 'onUploadProgress'\n > = {},\n ) => {\n return createSSEConnection(resolvedConfig, endpoint, options);\n };\n } else if (endpoint.type === 'download') {\n client[name] = (options: DownloadRequestOptions<DownloadEndpointDefinition> = {}) => {\n return makeDownloadRequest(resolvedConfig, endpoint, options);\n };\n } else {\n throw new Error(`Endpoint \"${name}\" has unknown type \"${(endpoint as any).type}\".`);\n }\n }\n\n return client as Client<T>;\n}\n\n/**\n * Create a client without providing the contract at runtime\n * Useful when you only need types and want a lighter bundle\n */\nexport function createTypedClient<T extends Contract>(_config: ClientConfig): Client<T> {\n return new Proxy({} as Client<T>, {\n get(_target, _prop: string) {\n return async (_options: EndpointRequestOptions<EndpointDefinition> = {}) => {\n // Without the contract, we can't validate or infer the endpoint\n // This is just a basic fetch wrapper with typing\n throw new Error(\n 'createTypedClient requires contract at runtime for validation. Use createClient instead.',\n );\n };\n },\n });\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "B663AC78F4A6E33F64756E2164756E21",
7
+ "mappings": ";;AAmBA;AAAA;AAmJO,MAAM,8BAA8B,MAAM;AAAA,EAEtC;AAAA,EACA;AAAA,EAFT,WAAW,CACF,OACA,QACP;AAAA,IACA,MAAM,yBAAyB,OAAO;AAAA,IAH/B;AAAA,IACA;AAAA,IAGP,KAAK,OAAO;AAAA;AAEhB;AAAA;AAGO,MAAM,kBAAkB,MAAM;AAAA,EAE1B;AAAA,EACA;AAAA,EACA;AAAA,EAHT,WAAW,CACF,QACA,YACA,MACP;AAAA,IACA,MAAM,cAAc,WAAW,YAAY;AAAA,IAJpC;AAAA,IACA;AAAA,IACA;AAAA,IAGP,KAAK,OAAO;AAAA;AAEhB;AAKA,SAAS,eAAqD,CAC5D,UACA,SACM;AAAA,EAEN,IAAI,SAAS,UAAU,QAAQ,QAAQ;AAAA,IACrC,MAAM,SAAS,SAAS,OAAO,UAAU,QAAQ,MAAM;AAAA,IACvD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,UAAU,OAAO,MAAM,MAAM;AAAA,IAC/D;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,SAAS,QAAQ,OAAO;AAAA,IACnC,MAAM,SAAS,SAAS,MAAM,UAAU,QAAQ,KAAK;AAAA,IACrD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,SAAS,OAAO,MAAM,MAAM;AAAA,IAC9D;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,WAAW,QAAQ,SAAS;AAAA,IACvC,MAAM,SAAS,SAAS,QAAQ,UAAU,QAAQ,OAAO;AAAA,IACzD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,WAAW,OAAO,MAAM,MAAM;AAAA,IAChE;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,QAAQ,QAAQ,MAAM;AAAA,IACjC,MAAM,SAAS,SAAS,KAAK,UAAU,QAAQ,IAAI;AAAA,IACnD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,QAAQ,OAAO,MAAM,MAAM;AAAA,IAC7D;AAAA,EACF;AAAA;AAMF,SAAS,gBAAsD,CAC7D,UACA,QACA,MACM;AAAA,EACN,MAAM,iBAAiB,SAAS,UAAU;AAAA,EAC1C,IAAI,gBAAgB;AAAA,IAClB,MAAM,SAAS,eAAe,UAAU,IAAI;AAAA,IAC5C,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,YAAY,WAAW,OAAO,MAAM,MAAM;AAAA,IAC5E;AAAA,EACF;AAAA;AAMF,SAAS,eAAe,CAAC,oBAAkD;AAAA,EACzE,IAAI,CAAC;AAAA,IAAoB,OAAO;AAAA,EAEhC,MAAM,oBAAoB,mBAAmB,MAAM,mCAAmC;AAAA,EACtF,IAAI,qBAAqB,kBAAkB,IAAI;AAAA,IAC7C,OAAO,mBAAmB,kBAAkB,EAAE;AAAA,EAChD;AAAA,EAEA,MAAM,gBAAgB,mBAAmB,MAAM,iCAAiC;AAAA,EAChF,IAAI,iBAAiB,cAAc,IAAI;AAAA,IACrC,OAAO,cAAc;AAAA,EACvB;AAAA,EACA,OAAO;AAAA;AAMT,SAAS,uBAA6D,CACpE,UACA,SACM;AAAA,EAEN,IAAI,SAAS,UAAU,QAAQ,QAAQ;AAAA,IACrC,MAAM,SAAS,SAAS,OAAO,UAAU,QAAQ,MAAM;AAAA,IACvD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,UAAU,OAAO,MAAM,MAAM;AAAA,IAC/D;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,SAAS,QAAQ,OAAO;AAAA,IACnC,MAAM,SAAS,SAAS,MAAM,UAAU,QAAQ,KAAK;AAAA,IACrD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,SAAS,OAAO,MAAM,MAAM;AAAA,IAC9D;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,WAAW,QAAQ,SAAS;AAAA,IACvC,MAAM,SAAS,SAAS,QAAQ,UAAU,QAAQ,OAAO;AAAA,IACzD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,WAAW,OAAO,MAAM,MAAM;AAAA,IAChE;AAAA,EACF;AAAA;AAMF,SAAS,6BAAmE,CAC1E,UACA,QACA,MACM;AAAA,EACN,IAAI,SAAS,gBAAgB;AAAA,IAC3B,MAAM,iBAAiB,SAAS,eAAe;AAAA,IAC/C,IAAI,gBAAgB;AAAA,MAClB,MAAM,SAAS,eAAe,UAAU,IAAI;AAAA,MAC5C,IAAI,CAAC,OAAO,SAAS;AAAA,QACnB,MAAM,IAAI,sBAAsB,YAAY,WAAW,OAAO,MAAM,MAAM;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AAAA;AAMF,eAAe,mBAAyD,CACtE,QACA,UACA,UAAqC,CAAC,GACR;AAAA,EAE9B,IAAI,OAAO,oBAAoB,OAAO;AAAA,IACpC,wBAAwB,UAAU,OAAO;AAAA,EAC3C;AAAA,EAGA,IAAI,OAAO,SAAS;AAAA,EACpB,IAAI,QAAQ,QAAQ;AAAA,IAClB,OAAO,gBAAgB,MAAM,QAAQ,MAAyC;AAAA,EAChF;AAAA,EAEA,MAAM,MAAM,SACV,OAAO,SACP,MACA,QAAQ,KACV;AAAA,EAGA,MAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAAA,EAC1C,IAAI,QAAQ,SAAS;AAAA,IACnB,YAAY,KAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,GAAG;AAAA,MAC1D,QAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAChC;AAAA,EACF;AAAA,EAGA,MAAM,OAAoB;AAAA,IACxB,QAAQ;AAAA,IACR;AAAA,EACF;AAAA,EAGA,IAAI,QAAQ,aAAa;AAAA,IACvB,KAAK,SAAS,QAAQ;AAAA,EACxB;AAAA,EAGA,MAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AAAA,EAGtC,IAAI,SAAS,WAAW,KAAK;AAAA,IAC3B,MAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAAA,IAC3D,MAAM,QAAQ,gBAAgB,SAAS,eAAe,EAAE,IAAI;AAAA,IAE5D,IAAI;AAAA,IAEJ,IAAI,QAAQ,sBAAsB,SAAS,MAAM;AAAA,MAE/C,MAAM,SAAS,SAAS,KAAK,UAAU;AAAA,MACvC,MAAM,SAAqB,CAAC;AAAA,MAC5B,IAAI,SAAS;AAAA,MAEb,OAAO,MAAM;AAAA,QACX,QAAQ,MAAM,UAAU,MAAM,OAAO,KAAK;AAAA,QAC1C,IAAI;AAAA,UAAM;AAAA,QAEV,OAAO,KAAK,KAAK;AAAA,QACjB,UAAU,MAAM;AAAA,QAEhB,QAAQ,mBAAmB;AAAA,UACzB;AAAA,UACA;AAAA,UACA,UAAU,QAAQ,IAAI,SAAS,QAAQ;AAAA,QACzC,CAAC;AAAA,MACH;AAAA,MAEA,OAAO,IAAI,KAAK,MAAM;AAAA,IACxB,EAAO;AAAA,MACL,OAAO,MAAM,SAAS,KAAK;AAAA;AAAA,IAG7B,MAAM,qBAAqB,SAAS,QAAQ,IAAI,qBAAqB;AAAA,IACrE,MAAM,WAAW,gBAAgB,kBAAkB,KAAK;AAAA,IACxD,MAAM,eAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,IAE5D,MAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,UAAU,EAAE,MAAM,aAAY,CAAC;AAAA,IAE7D,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EAGA,IAAI;AAAA,EACJ,MAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,EAE5D,IAAI,YAAY,SAAS,kBAAkB,GAAG;AAAA,IAC5C,OAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,EAAO;AAAA,IACL,OAAO,MAAM,SAAS,KAAK;AAAA;AAAA,EAI7B,IAAI,SAAS,kBAAkB,EAAE,SAAS,UAAU,SAAS,iBAAiB;AAAA,IAC5E,MAAM,IAAI,UAAU,SAAS,QAAQ,SAAS,YAAY,IAAI;AAAA,EAChE;AAAA,EAGA,IAAI,OAAO,qBAAqB,OAAO;AAAA,IACrC,8BAA8B,UAAU,SAAS,QAAQ,IAAI;AAAA,EAC/D;AAAA,EAEA,OAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IACjB;AAAA,EACF;AAAA;AAMF,SAAS,kBAAwD,CAC/D,QACA,UACA,SACA,KAC8B;AAAA,EAC9B,OAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAAA,IACtC,MAAM,MAAM,IAAI;AAAA,IAEhB,IAAI,KAAK,SAAS,QAAQ,GAAG;AAAA,IAG7B,IAAI,OAAO,SAAS;AAAA,MAClB,YAAY,KAAK,UAAU,OAAO,QAAQ,OAAO,OAAO,GAAG;AAAA,QACzD,IAAI,iBAAiB,KAAK,KAAK;AAAA,MACjC;AAAA,IACF;AAAA,IAGA,IAAI,QAAQ,SAAS;AAAA,MACnB,YAAY,KAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,GAAG;AAAA,QAC1D,IAAI,iBAAiB,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAAA,IACF;AAAA,IAGA,IAAI,QAAQ,kBAAkB;AAAA,MAC5B,IAAI,OAAO,aAAa,CAAC,MAAM;AAAA,QAC7B,IAAI,EAAE,oBAAoB,QAAQ,kBAAkB;AAAA,UAClD,QAAQ,iBAAiB;AAAA,YACvB,QAAQ,EAAE;AAAA,YACV,OAAO,EAAE;AAAA,YACT,UAAU,EAAE,SAAS,EAAE;AAAA,UACzB,CAAC;AAAA,QACH;AAAA;AAAA,IAEJ;AAAA,IAGA,IAAI,QAAQ,aAAa;AAAA,MACvB,IAAI,QAAQ,YAAY,SAAS;AAAA,QAC/B,IAAI,MAAM;AAAA,QACV,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC;AAAA,QAChD;AAAA,MACF;AAAA,MACA,QAAQ,YAAY,iBAAiB,SAAS,MAAM;AAAA,QAClD,IAAI,MAAM;AAAA,OACX;AAAA,IACH;AAAA,IAEA,IAAI,SAAS,MAAM;AAAA,MACjB,IAAI;AAAA,MACJ,MAAM,sBAAsB,IAAI,kBAAkB,cAAc,KAAK;AAAA,MAErE,IAAI,IAAI,WAAW,KAAK;AAAA,QACtB,OAAO,CAAC;AAAA,MACV,EAAO,SAAI,oBAAoB,SAAS,kBAAkB,GAAG;AAAA,QAC3D,IAAI;AAAA,UACF,OAAO,KAAK,MAAM,IAAI,YAAY;AAAA,UAClC,MAAM;AAAA,UACN,OAAO,IAAI,gBAAgB,CAAC;AAAA;AAAA,MAEhC,EAAO,SAAI,oBAAoB,SAAS,OAAO,GAAG;AAAA,QAChD,OAAO,IAAI;AAAA,MACb,EAAO;AAAA,QACL,OAAO,IAAI,gBAAgB,CAAC;AAAA;AAAA,MAI9B,IAAI,IAAI,UAAU,OAAO,EAAE,IAAI,UAAU,SAAS,YAAY;AAAA,QAC5D,OAAO,IAAI,UAAU,IAAI,QAAQ,IAAI,YAAY,IAAI,CAAC;AAAA,QACtD;AAAA,MACF;AAAA,MAGA,IAAI,OAAO,qBAAqB,OAAO;AAAA,QACrC,IAAI;AAAA,UACF,iBAAiB,UAAU,IAAI,QAAQ,IAAI;AAAA,UAC3C,OAAO,KAAK;AAAA,UACZ,OAAO,GAAG;AAAA,UACV;AAAA;AAAA,MAEJ;AAAA,MAEA,QAAQ;AAAA,QACN,QAAQ,IAAI;AAAA,QACZ;AAAA,MACF,CAAwB;AAAA;AAAA,IAG1B,IAAI,UAAU,MAAM,OAAO,IAAI,MAAM,eAAe,CAAC;AAAA,IACrD,IAAI,UAAU,MAAM,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC;AAAA,IAGpE,MAAM,cAAc,SAAS,eAAe;AAAA,IAC5C,IAAI,QAAQ,SAAS,WAAW;AAAA,MAC9B,IAAI,gBAAgB,uBAAuB;AAAA,QAEzC,IAAI,KAAK,iBAAiB,QAAQ,IAA+B,CAAC;AAAA,MACpE,EAAO;AAAA,QACL,IAAI,iBAAiB,gBAAgB,kBAAkB;AAAA,QACvD,IAAI,KAAK,KAAK,UAAU,QAAQ,IAAI,CAAC;AAAA;AAAA,IAEzC,EAAO;AAAA,MACL,IAAI,KAAK;AAAA;AAAA,GAEZ;AAAA;AAMH,eAAe,WAAiD,CAC9D,QACA,UACA,SAC8B;AAAA,EAE9B,IAAI,OAAO,oBAAoB,OAAO;AAAA,IACpC,gBAAgB,UAAU,OAAO;AAAA,EACnC;AAAA,EAGA,IAAI,OAAO,SAAS;AAAA,EACpB,IAAI,QAAQ,QAAQ;AAAA,IAClB,OAAO,gBAAgB,MAAM,QAAQ,MAAyC;AAAA,EAChF;AAAA,EAEA,MAAM,MAAM,SACV,OAAO,SACP,MACA,QAAQ,KACV;AAAA,EAGA,IAAI,QAAQ,oBAAoB,QAAQ,SAAS,WAAW;AAAA,IAC1D,OAAO,mBAAmB,QAAQ,UAAU,SAAS,GAAG;AAAA,EAC1D;AAAA,EAGA,MAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAAA,EAC1C,IAAI,QAAQ,SAAS;AAAA,IACnB,YAAY,KAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,GAAG;AAAA,MAC1D,QAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAChC;AAAA,EACF;AAAA,EAGA,MAAM,OAAoB;AAAA,IACxB,QAAQ,SAAS;AAAA,IACjB;AAAA,EACF;AAAA,EAGA,IAAI,QAAQ,aAAa;AAAA,IACvB,KAAK,SAAS,QAAQ;AAAA,EACxB;AAAA,EAGA,IAAI,QAAQ,SAAS,WAAW;AAAA,IAC9B,MAAM,cAAc,SAAS,eAAe;AAAA,IAE5C,IAAI,gBAAgB,uBAAuB;AAAA,MAEzC,KAAK,OAAO,iBAAiB,QAAQ,IAA+B;AAAA,IACtE,EAAO;AAAA,MACL,QAAQ,IAAI,gBAAgB,kBAAkB;AAAA,MAC9C,KAAK,OAAO,KAAK,UAAU,QAAQ,IAAI;AAAA;AAAA,EAE3C;AAAA,EAGA,MAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AAAA,EAGtC,IAAI;AAAA,EAGJ,IAAI,SAAS,WAAW,KAAK;AAAA,IAC3B,OAAO,CAAC;AAAA,EACV,EAAO;AAAA,IACL,MAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,IAE5D,IAAI,YAAY,SAAS,kBAAkB,GAAG;AAAA,MAC5C,OAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,EAAO,SAAI,YAAY,SAAS,OAAO,GAAG;AAAA,MACxC,OAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,EAAO;AAAA,MAEL,MAAM,OAAO,MAAM,SAAS,KAAK;AAAA,MACjC,IAAI,MAAM;AAAA,QACR,OAAO;AAAA,MACT,EAAO;AAAA,QACL,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA,EAMd,IAAI,CAAC,SAAS,MAAM,EAAE,SAAS,UAAU,SAAS,YAAY;AAAA,IAC5D,MAAM,IAAI,UAAU,SAAS,QAAQ,SAAS,YAAY,IAAI;AAAA,EAChE;AAAA,EAGA,IAAI,OAAO,qBAAqB,OAAO;AAAA,IACrC,iBAAiB,UAAU,SAAS,QAAQ,IAAI;AAAA,EAClD;AAAA,EAEA,OAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IACjB;AAAA,EACF;AAAA;AAMF,SAAS,qBAA4D,CACnE,UACA,YACoB;AAAA,EAKpB,MAAM,YAAY;AAAA,IAChB,OAAO,IAAI;AAAA,IACX,OAAO,IAAI;AAAA,IACX,OAAO,IAAI;AAAA,EACb;AAAA,GAGC,YAAY;AAAA,IACX,MAAM,SAAS,SAAS,KAAM,UAAU;AAAA,IACxC,MAAM,UAAU,IAAI;AAAA,IACpB,IAAI,SAAS;AAAA,IAEb,IAAI;AAAA,MACF,OAAO,MAAM;AAAA,QACX,QAAQ,MAAM,UAAU,MAAM,OAAO,KAAK;AAAA,QAC1C,IAAI;AAAA,UAAM;AAAA,QAEV,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAAA,QAChD,MAAM,QAAQ,OAAO,MAAM;AAAA,CAAI;AAAA,QAC/B,SAAS,MAAM,IAAI,KAAK;AAAA,QAExB,WAAW,QAAQ,OAAO;AAAA,UACxB,IAAI,CAAC,KAAK,KAAK;AAAA,YAAG;AAAA,UAClB,IAAI;AAAA,YACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAAA,YAC9B,IAAI,OAAO,WAAW;AAAA,cACpB,UAAU,MAAM,QAAQ,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC;AAAA,YAC/C,EAAO;AAAA,cACL,UAAU,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAyB,CAAC;AAAA;AAAA,YAE7D,OAAO,UAAU;AAAA,YACjB,UAAU,MAAM,QAAQ,CAAC,MAAM,EAAE,QAAiB,CAAC;AAAA;AAAA,QAEvD;AAAA,MACF;AAAA,MAGA,IAAI,OAAO,KAAK,GAAG;AAAA,QACjB,IAAI;AAAA,UACF,MAAM,SAAS,KAAK,MAAM,MAAM;AAAA,UAChC,IAAI,OAAO,WAAW;AAAA,YACpB,UAAU,MAAM,QAAQ,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC;AAAA,UAC/C,EAAO;AAAA,YACL,UAAU,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAyB,CAAC;AAAA;AAAA,UAE7D,MAAM;AAAA,MAGV;AAAA,MAGA,UAAU,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;AAAA,MAClC,OAAO,KAAK;AAAA,MACZ,IAAK,IAAc,SAAS,cAAc;AAAA,QACxC,UAAU,MAAM,QAAQ,CAAC,MAAM,EAAE,GAAY,CAAC;AAAA,MAChD;AAAA;AAAA,KAED;AAAA,EAEH,OAAO;AAAA,IACL,EAAE,CAAC,OAAoC,SAAqD;AAAA,MACzF,UAAU,OAA+B,IAAI,OAAO;AAAA,MACrD,OAAO,MAAO,UAAU,OAA+B,OAAO,OAAO;AAAA;AAAA,IAEvE,KAAK,GAAG;AAAA,MACN,WAAW,MAAM;AAAA;AAAA,QAEf,OAAO,GAAG;AAAA,MACZ,OAAO,WAAW,OAAO;AAAA;AAAA,EAE7B;AAAA;AAMF,SAAS,wBAA+D,CACtE,UACA,SACM;AAAA,EAEN,IAAI,SAAS,UAAU,QAAQ,QAAQ;AAAA,IACrC,MAAM,SAAS,SAAS,OAAO,UAAU,QAAQ,MAAM;AAAA,IACvD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,UAAU,OAAO,MAAM,MAAM;AAAA,IAC/D;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,SAAS,QAAQ,OAAO;AAAA,IACnC,MAAM,SAAS,SAAS,MAAM,UAAU,QAAQ,KAAK;AAAA,IACrD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,SAAS,OAAO,MAAM,MAAM;AAAA,IAC9D;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,WAAW,QAAQ,SAAS;AAAA,IACvC,MAAM,SAAS,SAAS,QAAQ,UAAU,QAAQ,OAAO;AAAA,IACzD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,WAAW,OAAO,MAAM,MAAM;AAAA,IAChE;AAAA,EACF;AAAA,EAGA,IAAI,SAAS,QAAQ,QAAQ,MAAM;AAAA,IACjC,MAAM,SAAS,SAAS,KAAK,UAAU,QAAQ,IAAI;AAAA,IACnD,IAAI,CAAC,OAAO,SAAS;AAAA,MACnB,MAAM,IAAI,sBAAsB,QAAQ,OAAO,MAAM,MAAM;AAAA,IAC7D;AAAA,EACF;AAAA;AAMF,eAAe,oBAA2D,CACxE,QACA,UACA,SAC6B;AAAA,EAE7B,IAAI,OAAO,oBAAoB,OAAO;AAAA,IACpC,yBAAyB,UAAU,OAAO;AAAA,EAC5C;AAAA,EAGA,IAAI,OAAO,SAAS;AAAA,EACpB,IAAI,QAAQ,QAAQ;AAAA,IAClB,OAAO,gBAAgB,MAAM,QAAQ,MAAyC;AAAA,EAChF;AAAA,EAEA,MAAM,MAAM,SACV,OAAO,SACP,MACA,QAAQ,KACV;AAAA,EAGA,MAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAAA,EAC1C,IAAI,QAAQ,SAAS;AAAA,IACnB,YAAY,KAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,GAAG;AAAA,MAC1D,QAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAChC;AAAA,EACF;AAAA,EAGA,MAAM,aAAa,IAAI;AAAA,EAGvB,IAAI,QAAQ,aAAa;AAAA,IACvB,IAAI,QAAQ,YAAY,SAAS;AAAA,MAC/B,WAAW,MAAM;AAAA,IACnB,EAAO;AAAA,MACL,QAAQ,YAAY,iBAAiB,SAAS,MAAM,WAAW,MAAM,CAAC;AAAA;AAAA,EAE1E;AAAA,EAEA,MAAM,OAAoB;AAAA,IACxB,QAAQ,SAAS;AAAA,IACjB;AAAA,IACA,QAAQ,WAAW;AAAA,EACrB;AAAA,EAGA,IAAI,QAAQ,SAAS,WAAW;AAAA,IAC9B,MAAM,cAAc,SAAS,eAAe;AAAA,IAE5C,IAAI,gBAAgB,uBAAuB;AAAA,MACzC,KAAK,OAAO,iBAAiB,QAAQ,IAA+B;AAAA,IACtE,EAAO;AAAA,MACL,QAAQ,IAAI,gBAAgB,kBAAkB;AAAA,MAC9C,KAAK,OAAO,KAAK,UAAU,QAAQ,IAAI;AAAA;AAAA,EAE3C;AAAA,EAGA,MAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AAAA,EAGtC,IAAI,CAAC,SAAS,IAAI;AAAA,IAChB,MAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,IAC5D,IAAI;AAAA,IAEJ,IAAI,YAAY,SAAS,kBAAkB,GAAG;AAAA,MAC5C,OAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,EAAO;AAAA,MACL,OAAO,MAAM,SAAS,KAAK;AAAA;AAAA,IAG7B,MAAM,IAAI,UAAU,SAAS,QAAQ,SAAS,YAAY,IAAI;AAAA,EAChE;AAAA,EAGA,OAAO,sBAAyB,UAAU,UAAU;AAAA;AAMtD,SAAS,mBAAoD,CAC3D,QACA,UACA,UAAwE,CAAC,GACvD;AAAA,EAElB,IAAI,OAAO,SAAS;AAAA,EACpB,IAAI,QAAQ,QAAQ;AAAA,IAClB,OAAO,gBAAgB,MAAM,QAAQ,MAAyC;AAAA,EAChF;AAAA,EAEA,MAAM,MAAM,SACV,OAAO,SACP,MACA,QAAQ,KACV;AAAA,EAIA,MAAM,cAAc,IAAI,YAAY,GAAG;AAAA,EAKvC,MAAM,YAA8D;AAAA,IAClE,OAAO,IAAI;AAAA,EACb;AAAA,EAGA,MAAM,aAAa,OAAO,KAAK,SAAS,MAAM;AAAA,EAG9C,WAAW,aAAa,YAAY;AAAA,IAClC,UAAU,aAAa,IAAI;AAAA,IAC3B,YAAY,iBAAiB,WAAW,CAAC,MAAM;AAAA,MAC7C,MAAM,eAAe;AAAA,MACrB,IAAI;AAAA,QACF,MAAM,OAAO,KAAK,MAAM,aAAa,IAAI;AAAA,QACxC,UAAU,WAAiC,QAAQ,CAAC,MACnD,EAAE,MAAM,aAAa,eAAe,SAAS,CAC/C;AAAA,QACA,OAAO,KAAK;AAAA,QACX,UAAU,MAA4B,QAAQ,CAAC,MAC9C,EAAE,IAAI,MAAM,6BAA8B,IAAc,SAAS,CAAC,CACpE;AAAA;AAAA,KAEH;AAAA,EACH;AAAA,EAGA,YAAY,UAAU,MAAM;AAAA,IACzB,UAAU,MAA4B,QAAQ,CAAC,MAAM,EAAE,IAAI,MAAM,sBAAsB,CAAC,CAAC;AAAA;AAAA,EAG5F,OAAO;AAAA,IACL,EAAE,CAAC,OAAe,SAAsC;AAAA,MACtD,IAAI,CAAC,UAAU,QAAQ;AAAA,QACrB,UAAU,SAAS,IAAI;AAAA,MACzB;AAAA,MACC,UAAU,OAA+B,IAAI,OAAO;AAAA,MACrD,OAAO,MAAO,UAAU,OAA+B,OAAO,OAAO;AAAA;AAAA,IAEvE,KAAK,GAAG;AAAA,MACN,YAAY,MAAM;AAAA;AAAA,QAEhB,KAAK,GAAG;AAAA,MACV,MAAM,SAAS,CAAC,cAAc,QAAQ,QAAQ;AAAA,MAC9C,OAAO,OAAO,YAAY;AAAA;AAAA,EAE9B;AAAA;AAMF,SAAS,cAAc,CAAC,SAAyB;AAAA,EAE/C,IAAI,QAAQ,WAAW,SAAS,KAAK,QAAQ,WAAW,UAAU,GAAG;AAAA,IACnE,OAAO;AAAA,EACT;AAAA,EAGA,IAAI,QAAQ,WAAW,GAAG,GAAG;AAAA,IAC3B,MAAM,IAAI;AAAA,IACV,MAAM,SAAS,GAAG,UAAU,UAAU;AAAA,IACtC,OAAO,SAAS;AAAA,EAClB;AAAA,EAGA,OAAO;AAAA;AAMF,SAAS,YAAgC,CAAC,UAAa,QAAiC;AAAA,EAE7F,MAAM,iBAAiB;AAAA,OAClB;AAAA,IACH,SAAS,eAAe,OAAO,OAAO;AAAA,EACxC;AAAA,EAEA,MAAM,SAAkC,CAAC;AAAA,EAEzC,YAAY,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;AAAA,IACvD,IAAI,SAAS,SAAS,YAAY;AAAA,MAChC,OAAO,QAAQ,CAAC,UAA8D,CAAC,MAAM;AAAA,QACnF,OAAO,YAAY,gBAAgB,UAAU,OAAO;AAAA;AAAA,IAExD,EAAO,SAAI,SAAS,SAAS,aAAa;AAAA,MACxC,OAAO,QAAQ,CAAC,UAA+D,CAAC,MAAM;AAAA,QACpF,OAAO,qBAAqB,gBAAgB,UAAU,OAAO;AAAA;AAAA,IAEjE,EAAO,SAAI,SAAS,SAAS,OAAO;AAAA,MAClC,OAAO,QAAQ,CACb,UAGI,CAAC,MACF;AAAA,QACH,OAAO,oBAAoB,gBAAgB,UAAU,OAAO;AAAA;AAAA,IAEhE,EAAO,SAAI,SAAS,SAAS,YAAY;AAAA,MACvC,OAAO,QAAQ,CAAC,UAA8D,CAAC,MAAM;AAAA,QACnF,OAAO,oBAAoB,gBAAgB,UAAU,OAAO;AAAA;AAAA,IAEhE,EAAO;AAAA,MACL,MAAM,IAAI,MAAM,aAAa,2BAA4B,SAAiB,QAAQ;AAAA;AAAA,EAEtF;AAAA,EAEA,OAAO;AAAA;AAOF,SAAS,iBAAqC,CAAC,SAAkC;AAAA,EACtF,OAAO,IAAI,MAAM,CAAC,GAAgB;AAAA,IAChC,GAAG,CAAC,SAAS,OAAe;AAAA,MAC1B,OAAO,OAAO,WAAuD,CAAC,MAAM;AAAA,QAG1E,MAAM,IAAI,MACR,0FACF;AAAA;AAAA;AAAA,EAGN,CAAC;AAAA;",
8
+ "debugId": "50D6CA4EBD15A65664756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/client",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "type": "module"
5
5
  }
@@ -0,0 +1,147 @@
1
+ // @bun
2
+ // packages/client/websocket.ts
3
+ import { buildUrl, interpolatePath } from "@richie-rpc/core";
4
+
5
+ class WebSocketClientValidationError extends Error {
6
+ messageType;
7
+ issues;
8
+ constructor(messageType, issues) {
9
+ super(`Validation failed for WebSocket message type: ${messageType}`);
10
+ this.messageType = messageType;
11
+ this.issues = issues;
12
+ this.name = "WebSocketClientValidationError";
13
+ }
14
+ }
15
+ function createTypedWebSocket(endpoint, url) {
16
+ let ws = null;
17
+ let isConnected = false;
18
+ const messageListeners = new Set;
19
+ const typedListeners = {};
20
+ const stateListeners = new Set;
21
+ const errorListeners = new Set;
22
+ for (const type of Object.keys(endpoint.serverMessages)) {
23
+ typedListeners[type] = new Set;
24
+ }
25
+ function notifyStateChange(connected) {
26
+ isConnected = connected;
27
+ stateListeners.forEach((h) => h(connected));
28
+ }
29
+ function notifyError(error) {
30
+ errorListeners.forEach((h) => h(error));
31
+ }
32
+ function handleMessage(event) {
33
+ try {
34
+ const message = JSON.parse(event.data);
35
+ messageListeners.forEach((h) => h(message));
36
+ const { type, payload } = message;
37
+ if (typedListeners[type]) {
38
+ typedListeners[type].forEach((h) => h(payload));
39
+ }
40
+ } catch (err) {
41
+ notifyError(new Error(`Failed to parse WebSocket message: ${err.message}`));
42
+ }
43
+ }
44
+ return {
45
+ connect() {
46
+ if (ws) {
47
+ return () => {
48
+ ws?.close();
49
+ ws = null;
50
+ };
51
+ }
52
+ ws = new WebSocket(url);
53
+ ws.onopen = () => {
54
+ notifyStateChange(true);
55
+ };
56
+ ws.onclose = () => {
57
+ notifyStateChange(false);
58
+ ws = null;
59
+ };
60
+ ws.onerror = () => {
61
+ notifyError(new Error("WebSocket connection error"));
62
+ };
63
+ ws.onmessage = handleMessage;
64
+ return () => {
65
+ ws?.close();
66
+ ws = null;
67
+ };
68
+ },
69
+ send(type, payload) {
70
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
71
+ throw new Error("WebSocket is not connected");
72
+ }
73
+ const messageDef = endpoint.clientMessages[type];
74
+ if (messageDef && messageDef.payload) {
75
+ const result = messageDef.payload.safeParse(payload);
76
+ if (!result.success) {
77
+ throw new WebSocketClientValidationError(type, result.error.issues);
78
+ }
79
+ }
80
+ ws.send(JSON.stringify({ type, payload }));
81
+ },
82
+ on(type, handler) {
83
+ const typeStr = type;
84
+ if (!typedListeners[typeStr]) {
85
+ typedListeners[typeStr] = new Set;
86
+ }
87
+ typedListeners[typeStr].add(handler);
88
+ return () => typedListeners[typeStr]?.delete(handler);
89
+ },
90
+ onMessage(handler) {
91
+ messageListeners.add(handler);
92
+ return () => messageListeners.delete(handler);
93
+ },
94
+ onStateChange(handler) {
95
+ stateListeners.add(handler);
96
+ return () => stateListeners.delete(handler);
97
+ },
98
+ onError(handler) {
99
+ errorListeners.add(handler);
100
+ return () => errorListeners.delete(handler);
101
+ },
102
+ get connected() {
103
+ return isConnected;
104
+ }
105
+ };
106
+ }
107
+ function resolveWebSocketUrl(baseUrl) {
108
+ if (baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://")) {
109
+ return baseUrl;
110
+ }
111
+ if (baseUrl.startsWith("http://")) {
112
+ return `ws://${baseUrl.slice(7)}`;
113
+ }
114
+ if (baseUrl.startsWith("https://")) {
115
+ return `wss://${baseUrl.slice(8)}`;
116
+ }
117
+ if (baseUrl.startsWith("/")) {
118
+ const g = globalThis;
119
+ if (g?.location) {
120
+ const protocol = g.location.protocol === "https:" ? "wss:" : "ws:";
121
+ return `${protocol}//${g.location.host}${baseUrl}`;
122
+ }
123
+ return `ws://localhost${baseUrl}`;
124
+ }
125
+ return `ws://${baseUrl}`;
126
+ }
127
+ function createWebSocketClient(contract, config) {
128
+ const resolvedBaseUrl = resolveWebSocketUrl(config.baseUrl);
129
+ const client = {};
130
+ for (const [name, endpoint] of Object.entries(contract)) {
131
+ client[name] = (options = {}) => {
132
+ let path = endpoint.path;
133
+ if (options.params) {
134
+ path = interpolatePath(path, options.params);
135
+ }
136
+ const url = buildUrl(resolvedBaseUrl, path, options.query);
137
+ return createTypedWebSocket(endpoint, url);
138
+ };
139
+ }
140
+ return client;
141
+ }
142
+ export {
143
+ createWebSocketClient,
144
+ WebSocketClientValidationError
145
+ };
146
+
147
+ //# debugId=0C267D8CB4E1D91364756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../websocket.ts"],
4
+ "sourcesContent": [
5
+ "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ExtractClientMessagePayload,\n ExtractServerMessage,\n ExtractServerMessagePayload,\n ExtractWSHeaders,\n ExtractWSParams,\n ExtractWSQuery,\n WebSocketContract,\n WebSocketContractDefinition,\n} from '@richie-rpc/core';\nimport { buildUrl, interpolatePath } from '@richie-rpc/core';\n\n/**\n * Validation error for WebSocket messages\n */\nexport class WebSocketClientValidationError extends Error {\n constructor(\n public messageType: string,\n public issues: unknown[],\n ) {\n super(`Validation failed for WebSocket message type: ${messageType}`);\n this.name = 'WebSocketClientValidationError';\n }\n}\n\n/**\n * Options for creating a WebSocket connection\n */\nexport type WebSocketConnectionOptions<T extends WebSocketContractDefinition> = {\n params?: ExtractWSParams<T> extends never ? never : ExtractWSParams<T>;\n query?: ExtractWSQuery<T> extends never ? never : ExtractWSQuery<T>;\n headers?: ExtractWSHeaders<T> extends never ? never : ExtractWSHeaders<T>;\n};\n\n/**\n * Typed WebSocket connection interface\n */\nexport interface TypedWebSocket<T extends WebSocketContractDefinition> {\n /** Connect to WebSocket server, returns disconnect function */\n connect(): () => void;\n\n /** Send a typed message (validates before sending) */\n send<K extends keyof T['clientMessages']>(\n type: K,\n payload: ExtractClientMessagePayload<T, K>,\n ): void;\n\n /** Subscribe to specific message type, returns unsubscribe function */\n on<K extends keyof T['serverMessages']>(\n type: K,\n handler: (payload: ExtractServerMessagePayload<T, K>) => void,\n ): () => void;\n\n /** Subscribe to all messages, returns unsubscribe function */\n onMessage(handler: (message: ExtractServerMessage<T>) => void): () => void;\n\n /** Subscribe to connection state changes */\n onStateChange(handler: (connected: boolean) => void): () => void;\n\n /** Subscribe to connection errors (network failures, etc.) */\n onError(handler: (error: Error) => void): () => void;\n\n /** Current connection state */\n readonly connected: boolean;\n}\n\n/**\n * WebSocket client type for a contract\n */\nexport type WebSocketClient<T extends WebSocketContract> = {\n [K in keyof T]: (options?: WebSocketConnectionOptions<T[K]>) => TypedWebSocket<T[K]>;\n};\n\n/**\n * WebSocket client configuration\n */\nexport interface WebSocketClientConfig {\n /** Base URL for WebSocket connections (ws:// or wss://) */\n baseUrl: string;\n}\n\n/**\n * Create a typed WebSocket connection for a specific endpoint\n */\nfunction createTypedWebSocket<T extends WebSocketContractDefinition>(\n endpoint: T,\n url: string,\n): TypedWebSocket<T> {\n let ws: WebSocket | null = null;\n let isConnected = false;\n\n type MessageHandler = (message: ExtractServerMessage<T>) => void;\n type TypedHandler<K extends keyof T['serverMessages']> = (\n payload: ExtractServerMessagePayload<T, K>,\n ) => void;\n type StateHandler = (connected: boolean) => void;\n type ErrorHandler = (error: Error) => void;\n\n const messageListeners = new Set<MessageHandler>();\n const typedListeners: Record<string, Set<TypedHandler<any>>> = {};\n const stateListeners = new Set<StateHandler>();\n const errorListeners = new Set<ErrorHandler>();\n\n // Initialize typed listener sets for each server message type\n for (const type of Object.keys(endpoint.serverMessages)) {\n typedListeners[type] = new Set();\n }\n\n function notifyStateChange(connected: boolean) {\n isConnected = connected;\n stateListeners.forEach((h) => h(connected));\n }\n\n function notifyError(error: Error) {\n errorListeners.forEach((h) => h(error));\n }\n\n function handleMessage(event: MessageEvent) {\n try {\n const message = JSON.parse(event.data) as ExtractServerMessage<T>;\n\n // Notify all-message listeners\n messageListeners.forEach((h) => h(message));\n\n // Notify type-specific listeners\n const { type, payload } = message as { type: string; payload: unknown };\n if (typedListeners[type]) {\n typedListeners[type].forEach((h) => h(payload as any));\n }\n } catch (err) {\n notifyError(new Error(`Failed to parse WebSocket message: ${(err as Error).message}`));\n }\n }\n\n return {\n connect() {\n if (ws) {\n // Already connected or connecting\n return () => {\n ws?.close();\n ws = null;\n };\n }\n\n ws = new WebSocket(url);\n\n ws.onopen = () => {\n notifyStateChange(true);\n };\n\n ws.onclose = () => {\n notifyStateChange(false);\n ws = null;\n };\n\n ws.onerror = () => {\n notifyError(new Error('WebSocket connection error'));\n };\n\n ws.onmessage = handleMessage;\n\n // Return disconnect function\n return () => {\n ws?.close();\n ws = null;\n };\n },\n\n send(type, payload) {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error('WebSocket is not connected');\n }\n\n // Validate payload against schema\n const messageDef = endpoint.clientMessages[type as string];\n if (messageDef && messageDef.payload) {\n const result = messageDef.payload.safeParse(payload);\n if (!result.success) {\n throw new WebSocketClientValidationError(type as string, result.error.issues);\n }\n }\n\n // Send message\n ws.send(JSON.stringify({ type, payload }));\n },\n\n on(type, handler) {\n const typeStr = type as string;\n if (!typedListeners[typeStr]) {\n typedListeners[typeStr] = new Set();\n }\n typedListeners[typeStr].add(handler);\n return () => typedListeners[typeStr]?.delete(handler);\n },\n\n onMessage(handler) {\n messageListeners.add(handler);\n return () => messageListeners.delete(handler);\n },\n\n onStateChange(handler) {\n stateListeners.add(handler);\n return () => stateListeners.delete(handler);\n },\n\n onError(handler) {\n errorListeners.add(handler);\n return () => errorListeners.delete(handler);\n },\n\n get connected() {\n return isConnected;\n },\n };\n}\n\n/**\n * Resolve HTTP URL to WebSocket URL\n */\nfunction resolveWebSocketUrl(baseUrl: string): string {\n // If already a WebSocket URL, return as-is\n if (baseUrl.startsWith('ws://') || baseUrl.startsWith('wss://')) {\n return baseUrl;\n }\n\n // Convert http:// to ws:// and https:// to wss://\n if (baseUrl.startsWith('http://')) {\n return `ws://${baseUrl.slice(7)}`;\n }\n if (baseUrl.startsWith('https://')) {\n return `wss://${baseUrl.slice(8)}`;\n }\n\n // If relative URL, resolve using window.location\n if (baseUrl.startsWith('/')) {\n const g = globalThis as unknown as { location?: { protocol?: string; host?: string } };\n if (g?.location) {\n const protocol = g.location.protocol === 'https:' ? 'wss:' : 'ws:';\n return `${protocol}//${g.location.host}${baseUrl}`;\n }\n return `ws://localhost${baseUrl}`;\n }\n\n // Assume ws:// by default\n return `ws://${baseUrl}`;\n}\n\n/**\n * Create a typed WebSocket client for a contract\n *\n * @param contract - The WebSocket contract definition\n * @param config - Client configuration with baseUrl\n * @returns Client object with methods for each endpoint\n *\n * @example\n * ```typescript\n * const wsContract = defineWebSocketContract({\n * chat: {\n * path: '/ws/chat/:roomId',\n * params: z.object({ roomId: z.string() }),\n * clientMessages: {\n * sendMessage: { payload: z.object({ text: z.string() }) },\n * },\n * serverMessages: {\n * message: { payload: z.object({ userId: z.string(), text: z.string() }) },\n * },\n * },\n * });\n *\n * const wsClient = createWebSocketClient(wsContract, { baseUrl: 'ws://localhost:3000' });\n *\n * // Create connection instance\n * const chat = wsClient.chat({ params: { roomId: 'room1' } });\n *\n * // Connect and get disconnect function\n * const disconnect = chat.connect();\n *\n * // Subscribe to state changes\n * chat.onStateChange((connected) => {\n * console.log('Connected:', connected);\n * });\n *\n * // Subscribe to specific message types\n * chat.on('message', (payload) => {\n * console.log(`${payload.userId}: ${payload.text}`);\n * });\n *\n * // Send messages\n * chat.send('sendMessage', { text: 'Hello!' });\n *\n * // Disconnect when done\n * disconnect();\n * ```\n */\nexport function createWebSocketClient<T extends WebSocketContract>(\n contract: T,\n config: WebSocketClientConfig,\n): WebSocketClient<T> {\n const resolvedBaseUrl = resolveWebSocketUrl(config.baseUrl);\n\n const client: Record<string, unknown> = {};\n\n for (const [name, endpoint] of Object.entries(contract)) {\n client[name] = (options: WebSocketConnectionOptions<WebSocketContractDefinition> = {}) => {\n // Build URL\n let path = endpoint.path;\n if (options.params) {\n path = interpolatePath(path, options.params as Record<string, string | number>);\n }\n\n const url = buildUrl(\n resolvedBaseUrl,\n path,\n options.query as Record<string, string | number | boolean | string[]> | undefined,\n );\n\n return createTypedWebSocket(endpoint, url);\n };\n }\n\n return client as WebSocketClient<T>;\n}\n"
6
+ ],
7
+ "mappings": ";;AAWA;AAAA;AAKO,MAAM,uCAAuC,MAAM;AAAA,EAE/C;AAAA,EACA;AAAA,EAFT,WAAW,CACF,aACA,QACP;AAAA,IACA,MAAM,iDAAiD,aAAa;AAAA,IAH7D;AAAA,IACA;AAAA,IAGP,KAAK,OAAO;AAAA;AAEhB;AA6DA,SAAS,oBAA2D,CAClE,UACA,KACmB;AAAA,EACnB,IAAI,KAAuB;AAAA,EAC3B,IAAI,cAAc;AAAA,EASlB,MAAM,mBAAmB,IAAI;AAAA,EAC7B,MAAM,iBAAyD,CAAC;AAAA,EAChE,MAAM,iBAAiB,IAAI;AAAA,EAC3B,MAAM,iBAAiB,IAAI;AAAA,EAG3B,WAAW,QAAQ,OAAO,KAAK,SAAS,cAAc,GAAG;AAAA,IACvD,eAAe,QAAQ,IAAI;AAAA,EAC7B;AAAA,EAEA,SAAS,iBAAiB,CAAC,WAAoB;AAAA,IAC7C,cAAc;AAAA,IACd,eAAe,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA;AAAA,EAG5C,SAAS,WAAW,CAAC,OAAc;AAAA,IACjC,eAAe,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA;AAAA,EAGxC,SAAS,aAAa,CAAC,OAAqB;AAAA,IAC1C,IAAI;AAAA,MACF,MAAM,UAAU,KAAK,MAAM,MAAM,IAAI;AAAA,MAGrC,iBAAiB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;AAAA,MAG1C,QAAQ,MAAM,YAAY;AAAA,MAC1B,IAAI,eAAe,OAAO;AAAA,QACxB,eAAe,MAAM,QAAQ,CAAC,MAAM,EAAE,OAAc,CAAC;AAAA,MACvD;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,YAAY,IAAI,MAAM,sCAAuC,IAAc,SAAS,CAAC;AAAA;AAAA;AAAA,EAIzF,OAAO;AAAA,IACL,OAAO,GAAG;AAAA,MACR,IAAI,IAAI;AAAA,QAEN,OAAO,MAAM;AAAA,UACX,IAAI,MAAM;AAAA,UACV,KAAK;AAAA;AAAA,MAET;AAAA,MAEA,KAAK,IAAI,UAAU,GAAG;AAAA,MAEtB,GAAG,SAAS,MAAM;AAAA,QAChB,kBAAkB,IAAI;AAAA;AAAA,MAGxB,GAAG,UAAU,MAAM;AAAA,QACjB,kBAAkB,KAAK;AAAA,QACvB,KAAK;AAAA;AAAA,MAGP,GAAG,UAAU,MAAM;AAAA,QACjB,YAAY,IAAI,MAAM,4BAA4B,CAAC;AAAA;AAAA,MAGrD,GAAG,YAAY;AAAA,MAGf,OAAO,MAAM;AAAA,QACX,IAAI,MAAM;AAAA,QACV,KAAK;AAAA;AAAA;AAAA,IAIT,IAAI,CAAC,MAAM,SAAS;AAAA,MAClB,IAAI,CAAC,MAAM,GAAG,eAAe,UAAU,MAAM;AAAA,QAC3C,MAAM,IAAI,MAAM,4BAA4B;AAAA,MAC9C;AAAA,MAGA,MAAM,aAAa,SAAS,eAAe;AAAA,MAC3C,IAAI,cAAc,WAAW,SAAS;AAAA,QACpC,MAAM,SAAS,WAAW,QAAQ,UAAU,OAAO;AAAA,QACnD,IAAI,CAAC,OAAO,SAAS;AAAA,UACnB,MAAM,IAAI,+BAA+B,MAAgB,OAAO,MAAM,MAAM;AAAA,QAC9E;AAAA,MACF;AAAA,MAGA,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC,CAAC;AAAA;AAAA,IAG3C,EAAE,CAAC,MAAM,SAAS;AAAA,MAChB,MAAM,UAAU;AAAA,MAChB,IAAI,CAAC,eAAe,UAAU;AAAA,QAC5B,eAAe,WAAW,IAAI;AAAA,MAChC;AAAA,MACA,eAAe,SAAS,IAAI,OAAO;AAAA,MACnC,OAAO,MAAM,eAAe,UAAU,OAAO,OAAO;AAAA;AAAA,IAGtD,SAAS,CAAC,SAAS;AAAA,MACjB,iBAAiB,IAAI,OAAO;AAAA,MAC5B,OAAO,MAAM,iBAAiB,OAAO,OAAO;AAAA;AAAA,IAG9C,aAAa,CAAC,SAAS;AAAA,MACrB,eAAe,IAAI,OAAO;AAAA,MAC1B,OAAO,MAAM,eAAe,OAAO,OAAO;AAAA;AAAA,IAG5C,OAAO,CAAC,SAAS;AAAA,MACf,eAAe,IAAI,OAAO;AAAA,MAC1B,OAAO,MAAM,eAAe,OAAO,OAAO;AAAA;AAAA,QAGxC,SAAS,GAAG;AAAA,MACd,OAAO;AAAA;AAAA,EAEX;AAAA;AAMF,SAAS,mBAAmB,CAAC,SAAyB;AAAA,EAEpD,IAAI,QAAQ,WAAW,OAAO,KAAK,QAAQ,WAAW,QAAQ,GAAG;AAAA,IAC/D,OAAO;AAAA,EACT;AAAA,EAGA,IAAI,QAAQ,WAAW,SAAS,GAAG;AAAA,IACjC,OAAO,QAAQ,QAAQ,MAAM,CAAC;AAAA,EAChC;AAAA,EACA,IAAI,QAAQ,WAAW,UAAU,GAAG;AAAA,IAClC,OAAO,SAAS,QAAQ,MAAM,CAAC;AAAA,EACjC;AAAA,EAGA,IAAI,QAAQ,WAAW,GAAG,GAAG;AAAA,IAC3B,MAAM,IAAI;AAAA,IACV,IAAI,GAAG,UAAU;AAAA,MACf,MAAM,WAAW,EAAE,SAAS,aAAa,WAAW,SAAS;AAAA,MAC7D,OAAO,GAAG,aAAa,EAAE,SAAS,OAAO;AAAA,IAC3C;AAAA,IACA,OAAO,iBAAiB;AAAA,EAC1B;AAAA,EAGA,OAAO,QAAQ;AAAA;AAkDV,SAAS,qBAAkD,CAChE,UACA,QACoB;AAAA,EACpB,MAAM,kBAAkB,oBAAoB,OAAO,OAAO;AAAA,EAE1D,MAAM,SAAkC,CAAC;AAAA,EAEzC,YAAY,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;AAAA,IACvD,OAAO,QAAQ,CAAC,UAAmE,CAAC,MAAM;AAAA,MAExF,IAAI,OAAO,SAAS;AAAA,MACpB,IAAI,QAAQ,QAAQ;AAAA,QAClB,OAAO,gBAAgB,MAAM,QAAQ,MAAyC;AAAA,MAChF;AAAA,MAEA,MAAM,MAAM,SACV,iBACA,MACA,QAAQ,KACV;AAAA,MAEA,OAAO,qBAAqB,UAAU,GAAG;AAAA;AAAA,EAE7C;AAAA,EAEA,OAAO;AAAA;",
8
+ "debugId": "0C267D8CB4E1D91364756E2164756E21",
9
+ "names": []
10
+ }
@@ -1,4 +1,5 @@
1
- import type { Contract, EndpointDefinition, ExtractBody, ExtractHeaders, ExtractParams, ExtractQuery } from '@richie-rpc/core';
1
+ import type { Contract, DownloadEndpointDefinition, DownloadProgressEvent, EndpointDefinition, ExtractBody, ExtractChunk, ExtractFinalResponse, ExtractHeaders, ExtractParams, ExtractQuery, ExtractSSEEventData, SSEEndpointDefinition, StandardEndpointDefinition, StreamingEndpointDefinition, UploadProgressEvent } from '@richie-rpc/core';
2
+ export type { UploadProgressEvent, DownloadProgressEvent };
2
3
  import type { z } from 'zod';
3
4
  export interface ClientConfig {
4
5
  baseUrl: string;
@@ -12,16 +13,82 @@ export type EndpointRequestOptions<T extends EndpointDefinition> = {
12
13
  headers?: ExtractHeaders<T> extends never ? never : ExtractHeaders<T>;
13
14
  body?: ExtractBody<T> extends never ? never : ExtractBody<T>;
14
15
  abortSignal?: AbortSignal;
16
+ /** Upload progress callback (uses XHR for progress tracking) */
17
+ onUploadProgress?: (event: UploadProgressEvent) => void;
15
18
  };
16
- export type EndpointResponse<T extends EndpointDefinition> = {
19
+ export type EndpointResponse<T extends StandardEndpointDefinition> = {
17
20
  [Status in keyof T['responses']]: {
18
21
  status: Status;
19
22
  data: T['responses'][Status] extends z.ZodTypeAny ? z.infer<T['responses'][Status]> : never;
20
23
  };
21
24
  }[keyof T['responses']];
22
- export type ClientMethod<T extends EndpointDefinition> = (options: EndpointRequestOptions<T>) => Promise<EndpointResponse<T>>;
25
+ export type ClientMethod<T extends StandardEndpointDefinition> = (options: EndpointRequestOptions<T>) => Promise<EndpointResponse<T>>;
26
+ /**
27
+ * Result object for streaming endpoints - event-based API
28
+ */
29
+ export interface StreamingResult<T extends StreamingEndpointDefinition> {
30
+ /** Subscribe to chunks */
31
+ on(event: 'chunk', handler: (chunk: ExtractChunk<T>) => void): () => void;
32
+ /** Subscribe to stream close (with optional final response) */
33
+ on(event: 'close', handler: (final?: ExtractFinalResponse<T>) => void): () => void;
34
+ /** Subscribe to errors */
35
+ on(event: 'error', handler: (error: Error) => void): () => void;
36
+ /** Abort the stream */
37
+ abort(): void;
38
+ /** Check if aborted */
39
+ readonly aborted: boolean;
40
+ }
41
+ /**
42
+ * Client method type for streaming endpoints
43
+ */
44
+ export type StreamingClientMethod<T extends StreamingEndpointDefinition> = (options: EndpointRequestOptions<T>) => Promise<StreamingResult<T>>;
45
+ /**
46
+ * Connection object for SSE endpoints - event-based API
47
+ */
48
+ export interface SSEConnection<T extends SSEEndpointDefinition> {
49
+ /** Subscribe to a specific event type */
50
+ on<K extends keyof T['events']>(event: K, handler: (data: ExtractSSEEventData<T, K>, id?: string) => void): () => void;
51
+ /** Subscribe to errors */
52
+ on(event: 'error', handler: (error: Error) => void): () => void;
53
+ /** Close the connection */
54
+ close(): void;
55
+ /** Current connection state */
56
+ readonly state: 'connecting' | 'open' | 'closed';
57
+ }
58
+ /**
59
+ * Client method type for SSE endpoints
60
+ */
61
+ export type SSEClientMethod<T extends SSEEndpointDefinition> = (options?: Omit<EndpointRequestOptions<T>, 'body' | 'onUploadProgress'>) => SSEConnection<T>;
62
+ /**
63
+ * Request options for download endpoints
64
+ */
65
+ export type DownloadRequestOptions<T extends DownloadEndpointDefinition> = {
66
+ params?: ExtractParams<T> extends never ? never : ExtractParams<T>;
67
+ query?: ExtractQuery<T> extends never ? never : ExtractQuery<T>;
68
+ headers?: ExtractHeaders<T> extends never ? never : ExtractHeaders<T>;
69
+ abortSignal?: AbortSignal;
70
+ /** Download progress callback */
71
+ onDownloadProgress?: (event: DownloadProgressEvent) => void;
72
+ };
73
+ /**
74
+ * Response type for download endpoints
75
+ * Success (200) returns File, errors return typed error response
76
+ */
77
+ export type DownloadResponse<T extends DownloadEndpointDefinition> = {
78
+ status: 200;
79
+ data: File;
80
+ } | (T['errorResponses'] extends Record<number, z.ZodTypeAny> ? {
81
+ [S in keyof T['errorResponses']]: {
82
+ status: S;
83
+ data: T['errorResponses'][S] extends z.ZodTypeAny ? z.infer<T['errorResponses'][S]> : never;
84
+ };
85
+ }[keyof T['errorResponses']] : never);
86
+ /**
87
+ * Client method type for download endpoints
88
+ */
89
+ export type DownloadClientMethod<T extends DownloadEndpointDefinition> = (options?: DownloadRequestOptions<T>) => Promise<DownloadResponse<T>>;
23
90
  export type Client<T extends Contract> = {
24
- [K in keyof T]: ClientMethod<T[K]>;
91
+ [K in keyof T]: T[K] extends StandardEndpointDefinition ? ClientMethod<T[K]> : T[K] extends StreamingEndpointDefinition ? StreamingClientMethod<T[K]> : T[K] extends SSEEndpointDefinition ? SSEClientMethod<T[K]> : T[K] extends DownloadEndpointDefinition ? DownloadClientMethod<T[K]> : never;
25
92
  };
26
93
  export declare class ClientValidationError extends Error {
27
94
  field: string;
@@ -0,0 +1,97 @@
1
+ import type { ExtractClientMessagePayload, ExtractServerMessage, ExtractServerMessagePayload, ExtractWSHeaders, ExtractWSParams, ExtractWSQuery, WebSocketContract, WebSocketContractDefinition } from '@richie-rpc/core';
2
+ /**
3
+ * Validation error for WebSocket messages
4
+ */
5
+ export declare class WebSocketClientValidationError extends Error {
6
+ messageType: string;
7
+ issues: unknown[];
8
+ constructor(messageType: string, issues: unknown[]);
9
+ }
10
+ /**
11
+ * Options for creating a WebSocket connection
12
+ */
13
+ export type WebSocketConnectionOptions<T extends WebSocketContractDefinition> = {
14
+ params?: ExtractWSParams<T> extends never ? never : ExtractWSParams<T>;
15
+ query?: ExtractWSQuery<T> extends never ? never : ExtractWSQuery<T>;
16
+ headers?: ExtractWSHeaders<T> extends never ? never : ExtractWSHeaders<T>;
17
+ };
18
+ /**
19
+ * Typed WebSocket connection interface
20
+ */
21
+ export interface TypedWebSocket<T extends WebSocketContractDefinition> {
22
+ /** Connect to WebSocket server, returns disconnect function */
23
+ connect(): () => void;
24
+ /** Send a typed message (validates before sending) */
25
+ send<K extends keyof T['clientMessages']>(type: K, payload: ExtractClientMessagePayload<T, K>): void;
26
+ /** Subscribe to specific message type, returns unsubscribe function */
27
+ on<K extends keyof T['serverMessages']>(type: K, handler: (payload: ExtractServerMessagePayload<T, K>) => void): () => void;
28
+ /** Subscribe to all messages, returns unsubscribe function */
29
+ onMessage(handler: (message: ExtractServerMessage<T>) => void): () => void;
30
+ /** Subscribe to connection state changes */
31
+ onStateChange(handler: (connected: boolean) => void): () => void;
32
+ /** Subscribe to connection errors (network failures, etc.) */
33
+ onError(handler: (error: Error) => void): () => void;
34
+ /** Current connection state */
35
+ readonly connected: boolean;
36
+ }
37
+ /**
38
+ * WebSocket client type for a contract
39
+ */
40
+ export type WebSocketClient<T extends WebSocketContract> = {
41
+ [K in keyof T]: (options?: WebSocketConnectionOptions<T[K]>) => TypedWebSocket<T[K]>;
42
+ };
43
+ /**
44
+ * WebSocket client configuration
45
+ */
46
+ export interface WebSocketClientConfig {
47
+ /** Base URL for WebSocket connections (ws:// or wss://) */
48
+ baseUrl: string;
49
+ }
50
+ /**
51
+ * Create a typed WebSocket client for a contract
52
+ *
53
+ * @param contract - The WebSocket contract definition
54
+ * @param config - Client configuration with baseUrl
55
+ * @returns Client object with methods for each endpoint
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const wsContract = defineWebSocketContract({
60
+ * chat: {
61
+ * path: '/ws/chat/:roomId',
62
+ * params: z.object({ roomId: z.string() }),
63
+ * clientMessages: {
64
+ * sendMessage: { payload: z.object({ text: z.string() }) },
65
+ * },
66
+ * serverMessages: {
67
+ * message: { payload: z.object({ userId: z.string(), text: z.string() }) },
68
+ * },
69
+ * },
70
+ * });
71
+ *
72
+ * const wsClient = createWebSocketClient(wsContract, { baseUrl: 'ws://localhost:3000' });
73
+ *
74
+ * // Create connection instance
75
+ * const chat = wsClient.chat({ params: { roomId: 'room1' } });
76
+ *
77
+ * // Connect and get disconnect function
78
+ * const disconnect = chat.connect();
79
+ *
80
+ * // Subscribe to state changes
81
+ * chat.onStateChange((connected) => {
82
+ * console.log('Connected:', connected);
83
+ * });
84
+ *
85
+ * // Subscribe to specific message types
86
+ * chat.on('message', (payload) => {
87
+ * console.log(`${payload.userId}: ${payload.text}`);
88
+ * });
89
+ *
90
+ * // Send messages
91
+ * chat.send('sendMessage', { text: 'Hello!' });
92
+ *
93
+ * // Disconnect when done
94
+ * disconnect();
95
+ * ```
96
+ */
97
+ export declare function createWebSocketClient<T extends WebSocketContract>(contract: T, config: WebSocketClientConfig): WebSocketClient<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richie-rpc/client",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "main": "./dist/cjs/index.cjs",
5
5
  "exports": {
6
6
  ".": {
@@ -10,7 +10,7 @@
10
10
  }
11
11
  },
12
12
  "peerDependencies": {
13
- "@richie-rpc/core": "^1.2.2",
13
+ "@richie-rpc/core": "^1.2.4",
14
14
  "typescript": "^5",
15
15
  "zod": "^4.1.12"
16
16
  },