@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.
@@ -0,0 +1,178 @@
1
+ // @bun @bun-cjs
2
+ (function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+
30
+ // packages/client/websocket.ts
31
+ var exports_websocket = {};
32
+ __export(exports_websocket, {
33
+ createWebSocketClient: () => createWebSocketClient,
34
+ WebSocketClientValidationError: () => WebSocketClientValidationError
35
+ });
36
+ module.exports = __toCommonJS(exports_websocket);
37
+ var import_core = require("@richie-rpc/core");
38
+
39
+ class WebSocketClientValidationError extends Error {
40
+ messageType;
41
+ issues;
42
+ constructor(messageType, issues) {
43
+ super(`Validation failed for WebSocket message type: ${messageType}`);
44
+ this.messageType = messageType;
45
+ this.issues = issues;
46
+ this.name = "WebSocketClientValidationError";
47
+ }
48
+ }
49
+ function createTypedWebSocket(endpoint, url) {
50
+ let ws = null;
51
+ let isConnected = false;
52
+ const messageListeners = new Set;
53
+ const typedListeners = {};
54
+ const stateListeners = new Set;
55
+ const errorListeners = new Set;
56
+ for (const type of Object.keys(endpoint.serverMessages)) {
57
+ typedListeners[type] = new Set;
58
+ }
59
+ function notifyStateChange(connected) {
60
+ isConnected = connected;
61
+ stateListeners.forEach((h) => h(connected));
62
+ }
63
+ function notifyError(error) {
64
+ errorListeners.forEach((h) => h(error));
65
+ }
66
+ function handleMessage(event) {
67
+ try {
68
+ const message = JSON.parse(event.data);
69
+ messageListeners.forEach((h) => h(message));
70
+ const { type, payload } = message;
71
+ if (typedListeners[type]) {
72
+ typedListeners[type].forEach((h) => h(payload));
73
+ }
74
+ } catch (err) {
75
+ notifyError(new Error(`Failed to parse WebSocket message: ${err.message}`));
76
+ }
77
+ }
78
+ return {
79
+ connect() {
80
+ if (ws) {
81
+ return () => {
82
+ ws?.close();
83
+ ws = null;
84
+ };
85
+ }
86
+ ws = new WebSocket(url);
87
+ ws.onopen = () => {
88
+ notifyStateChange(true);
89
+ };
90
+ ws.onclose = () => {
91
+ notifyStateChange(false);
92
+ ws = null;
93
+ };
94
+ ws.onerror = () => {
95
+ notifyError(new Error("WebSocket connection error"));
96
+ };
97
+ ws.onmessage = handleMessage;
98
+ return () => {
99
+ ws?.close();
100
+ ws = null;
101
+ };
102
+ },
103
+ send(type, payload) {
104
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
105
+ throw new Error("WebSocket is not connected");
106
+ }
107
+ const messageDef = endpoint.clientMessages[type];
108
+ if (messageDef && messageDef.payload) {
109
+ const result = messageDef.payload.safeParse(payload);
110
+ if (!result.success) {
111
+ throw new WebSocketClientValidationError(type, result.error.issues);
112
+ }
113
+ }
114
+ ws.send(JSON.stringify({ type, payload }));
115
+ },
116
+ on(type, handler) {
117
+ const typeStr = type;
118
+ if (!typedListeners[typeStr]) {
119
+ typedListeners[typeStr] = new Set;
120
+ }
121
+ typedListeners[typeStr].add(handler);
122
+ return () => typedListeners[typeStr]?.delete(handler);
123
+ },
124
+ onMessage(handler) {
125
+ messageListeners.add(handler);
126
+ return () => messageListeners.delete(handler);
127
+ },
128
+ onStateChange(handler) {
129
+ stateListeners.add(handler);
130
+ return () => stateListeners.delete(handler);
131
+ },
132
+ onError(handler) {
133
+ errorListeners.add(handler);
134
+ return () => errorListeners.delete(handler);
135
+ },
136
+ get connected() {
137
+ return isConnected;
138
+ }
139
+ };
140
+ }
141
+ function resolveWebSocketUrl(baseUrl) {
142
+ if (baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://")) {
143
+ return baseUrl;
144
+ }
145
+ if (baseUrl.startsWith("http://")) {
146
+ return `ws://${baseUrl.slice(7)}`;
147
+ }
148
+ if (baseUrl.startsWith("https://")) {
149
+ return `wss://${baseUrl.slice(8)}`;
150
+ }
151
+ if (baseUrl.startsWith("/")) {
152
+ const g = globalThis;
153
+ if (g?.location) {
154
+ const protocol = g.location.protocol === "https:" ? "wss:" : "ws:";
155
+ return `${protocol}//${g.location.host}${baseUrl}`;
156
+ }
157
+ return `ws://localhost${baseUrl}`;
158
+ }
159
+ return `ws://${baseUrl}`;
160
+ }
161
+ function createWebSocketClient(contract, config) {
162
+ const resolvedBaseUrl = resolveWebSocketUrl(config.baseUrl);
163
+ const client = {};
164
+ for (const [name, endpoint] of Object.entries(contract)) {
165
+ client[name] = (options = {}) => {
166
+ let path = endpoint.path;
167
+ if (options.params) {
168
+ path = import_core.interpolatePath(path, options.params);
169
+ }
170
+ const url = import_core.buildUrl(resolvedBaseUrl, path, options.query);
171
+ return createTypedWebSocket(endpoint, url);
172
+ };
173
+ }
174
+ return client;
175
+ }
176
+ })
177
+
178
+ //# debugId=15DE3AD0993419A564756E2164756E21
@@ -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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAW0C,IAA1C;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,4BAAgB,MAAM,QAAQ,MAAyC;AAAA,MAChF;AAAA,MAEA,MAAM,MAAM,qBACV,iBACA,MACA,QAAQ,KACV;AAAA,MAEA,OAAO,qBAAqB,UAAU,GAAG;AAAA;AAAA,EAE7C;AAAA,EAEA,OAAO;AAAA;",
8
+ "debugId": "15DE3AD0993419A564756E2164756E21",
9
+ "names": []
10
+ }
@@ -60,6 +60,207 @@ function validateResponse(endpoint, status, data) {
60
60
  }
61
61
  }
62
62
  }
63
+ function extractFilename(contentDisposition) {
64
+ if (!contentDisposition)
65
+ return null;
66
+ const filenameStarMatch = contentDisposition.match(/filename\*=(?:UTF-8'')?([^;\s]+)/i);
67
+ if (filenameStarMatch && filenameStarMatch[1]) {
68
+ return decodeURIComponent(filenameStarMatch[1]);
69
+ }
70
+ const filenameMatch = contentDisposition.match(/filename=["']?([^"';\s]+)["']?/i);
71
+ if (filenameMatch && filenameMatch[1]) {
72
+ return filenameMatch[1];
73
+ }
74
+ return null;
75
+ }
76
+ function validateDownloadRequest(endpoint, options) {
77
+ if (endpoint.params && options.params) {
78
+ const result = endpoint.params.safeParse(options.params);
79
+ if (!result.success) {
80
+ throw new ClientValidationError("params", result.error.issues);
81
+ }
82
+ }
83
+ if (endpoint.query && options.query) {
84
+ const result = endpoint.query.safeParse(options.query);
85
+ if (!result.success) {
86
+ throw new ClientValidationError("query", result.error.issues);
87
+ }
88
+ }
89
+ if (endpoint.headers && options.headers) {
90
+ const result = endpoint.headers.safeParse(options.headers);
91
+ if (!result.success) {
92
+ throw new ClientValidationError("headers", result.error.issues);
93
+ }
94
+ }
95
+ }
96
+ function validateDownloadErrorResponse(endpoint, status, data) {
97
+ if (endpoint.errorResponses) {
98
+ const responseSchema = endpoint.errorResponses[status];
99
+ if (responseSchema) {
100
+ const result = responseSchema.safeParse(data);
101
+ if (!result.success) {
102
+ throw new ClientValidationError(`response[${status}]`, result.error.issues);
103
+ }
104
+ }
105
+ }
106
+ }
107
+ async function makeDownloadRequest(config, endpoint, options = {}) {
108
+ if (config.validateRequest !== false) {
109
+ validateDownloadRequest(endpoint, options);
110
+ }
111
+ let path = endpoint.path;
112
+ if (options.params) {
113
+ path = interpolatePath(path, options.params);
114
+ }
115
+ const url = buildUrl(config.baseUrl, path, options.query);
116
+ const headers = new Headers(config.headers);
117
+ if (options.headers) {
118
+ for (const [key, value] of Object.entries(options.headers)) {
119
+ headers.set(key, String(value));
120
+ }
121
+ }
122
+ const init = {
123
+ method: "GET",
124
+ headers
125
+ };
126
+ if (options.abortSignal) {
127
+ init.signal = options.abortSignal;
128
+ }
129
+ const response = await fetch(url, init);
130
+ if (response.status === 200) {
131
+ const contentLength = response.headers.get("content-length");
132
+ const total = contentLength ? parseInt(contentLength, 10) : 0;
133
+ let blob;
134
+ if (options.onDownloadProgress && response.body) {
135
+ const reader = response.body.getReader();
136
+ const chunks = [];
137
+ let loaded = 0;
138
+ while (true) {
139
+ const { done, value } = await reader.read();
140
+ if (done)
141
+ break;
142
+ chunks.push(value);
143
+ loaded += value.length;
144
+ options.onDownloadProgress({
145
+ loaded,
146
+ total,
147
+ progress: total > 0 ? loaded / total : NaN
148
+ });
149
+ }
150
+ blob = new Blob(chunks);
151
+ } else {
152
+ blob = await response.blob();
153
+ }
154
+ const contentDisposition = response.headers.get("content-disposition");
155
+ const filename = extractFilename(contentDisposition) || "download";
156
+ const contentType2 = response.headers.get("content-type") || "application/octet-stream";
157
+ const file = new File([blob], filename, { type: contentType2 });
158
+ return {
159
+ status: 200,
160
+ data: file
161
+ };
162
+ }
163
+ let data;
164
+ const contentType = response.headers.get("content-type") || "";
165
+ if (contentType.includes("application/json")) {
166
+ data = await response.json();
167
+ } else {
168
+ data = await response.text();
169
+ }
170
+ if (endpoint.errorResponses && !(response.status in endpoint.errorResponses)) {
171
+ throw new HTTPError(response.status, response.statusText, data);
172
+ }
173
+ if (config.validateResponse !== false) {
174
+ validateDownloadErrorResponse(endpoint, response.status, data);
175
+ }
176
+ return {
177
+ status: response.status,
178
+ data
179
+ };
180
+ }
181
+ function makeRequestWithXHR(config, endpoint, options, url) {
182
+ return new Promise((resolve, reject) => {
183
+ const xhr = new XMLHttpRequest;
184
+ xhr.open(endpoint.method, url);
185
+ if (config.headers) {
186
+ for (const [key, value] of Object.entries(config.headers)) {
187
+ xhr.setRequestHeader(key, value);
188
+ }
189
+ }
190
+ if (options.headers) {
191
+ for (const [key, value] of Object.entries(options.headers)) {
192
+ xhr.setRequestHeader(key, String(value));
193
+ }
194
+ }
195
+ if (options.onUploadProgress) {
196
+ xhr.upload.onprogress = (e) => {
197
+ if (e.lengthComputable && options.onUploadProgress) {
198
+ options.onUploadProgress({
199
+ loaded: e.loaded,
200
+ total: e.total,
201
+ progress: e.loaded / e.total
202
+ });
203
+ }
204
+ };
205
+ }
206
+ if (options.abortSignal) {
207
+ if (options.abortSignal.aborted) {
208
+ xhr.abort();
209
+ reject(new DOMException("Aborted", "AbortError"));
210
+ return;
211
+ }
212
+ options.abortSignal.addEventListener("abort", () => {
213
+ xhr.abort();
214
+ });
215
+ }
216
+ xhr.onload = () => {
217
+ let data;
218
+ const responseContentType = xhr.getResponseHeader("content-type") || "";
219
+ if (xhr.status === 204) {
220
+ data = {};
221
+ } else if (responseContentType.includes("application/json")) {
222
+ try {
223
+ data = JSON.parse(xhr.responseText);
224
+ } catch {
225
+ data = xhr.responseText || {};
226
+ }
227
+ } else if (responseContentType.includes("text/")) {
228
+ data = xhr.responseText;
229
+ } else {
230
+ data = xhr.responseText || {};
231
+ }
232
+ if (xhr.status >= 400 && !(xhr.status in endpoint.responses)) {
233
+ reject(new HTTPError(xhr.status, xhr.statusText, data));
234
+ return;
235
+ }
236
+ if (config.validateResponse !== false) {
237
+ try {
238
+ validateResponse(endpoint, xhr.status, data);
239
+ } catch (err) {
240
+ reject(err);
241
+ return;
242
+ }
243
+ }
244
+ resolve({
245
+ status: xhr.status,
246
+ data
247
+ });
248
+ };
249
+ xhr.onerror = () => reject(new Error("Network error"));
250
+ xhr.onabort = () => reject(new DOMException("Aborted", "AbortError"));
251
+ const contentType = endpoint.contentType ?? "application/json";
252
+ if (options.body !== undefined) {
253
+ if (contentType === "multipart/form-data") {
254
+ xhr.send(objectToFormData(options.body));
255
+ } else {
256
+ xhr.setRequestHeader("content-type", "application/json");
257
+ xhr.send(JSON.stringify(options.body));
258
+ }
259
+ } else {
260
+ xhr.send();
261
+ }
262
+ });
263
+ }
63
264
  async function makeRequest(config, endpoint, options) {
64
265
  if (config.validateRequest !== false) {
65
266
  validateRequest(endpoint, options);
@@ -69,6 +270,9 @@ async function makeRequest(config, endpoint, options) {
69
270
  path = interpolatePath(path, options.params);
70
271
  }
71
272
  const url = buildUrl(config.baseUrl, path, options.query);
273
+ if (options.onUploadProgress && options.body !== undefined) {
274
+ return makeRequestWithXHR(config, endpoint, options, url);
275
+ }
72
276
  const headers = new Headers(config.headers);
73
277
  if (options.headers) {
74
278
  for (const [key, value] of Object.entries(options.headers)) {
@@ -121,6 +325,189 @@ async function makeRequest(config, endpoint, options) {
121
325
  data
122
326
  };
123
327
  }
328
+ function createStreamingResult(response, controller) {
329
+ const listeners = {
330
+ chunk: new Set,
331
+ close: new Set,
332
+ error: new Set
333
+ };
334
+ (async () => {
335
+ const reader = response.body.getReader();
336
+ const decoder = new TextDecoder;
337
+ let buffer = "";
338
+ try {
339
+ while (true) {
340
+ const { done, value } = await reader.read();
341
+ if (done)
342
+ break;
343
+ buffer += decoder.decode(value, { stream: true });
344
+ const lines = buffer.split(`
345
+ `);
346
+ buffer = lines.pop() || "";
347
+ for (const line of lines) {
348
+ if (!line.trim())
349
+ continue;
350
+ try {
351
+ const parsed = JSON.parse(line);
352
+ if (parsed.__final__) {
353
+ listeners.close.forEach((h) => h(parsed.data));
354
+ } else {
355
+ listeners.chunk.forEach((h) => h(parsed));
356
+ }
357
+ } catch (parseErr) {
358
+ listeners.error.forEach((h) => h(parseErr));
359
+ }
360
+ }
361
+ }
362
+ if (buffer.trim()) {
363
+ try {
364
+ const parsed = JSON.parse(buffer);
365
+ if (parsed.__final__) {
366
+ listeners.close.forEach((h) => h(parsed.data));
367
+ } else {
368
+ listeners.chunk.forEach((h) => h(parsed));
369
+ }
370
+ } catch {}
371
+ }
372
+ listeners.close.forEach((h) => h());
373
+ } catch (err) {
374
+ if (err.name !== "AbortError") {
375
+ listeners.error.forEach((h) => h(err));
376
+ }
377
+ }
378
+ })();
379
+ return {
380
+ on(event, handler) {
381
+ listeners[event].add(handler);
382
+ return () => listeners[event].delete(handler);
383
+ },
384
+ abort() {
385
+ controller.abort();
386
+ },
387
+ get aborted() {
388
+ return controller.signal.aborted;
389
+ }
390
+ };
391
+ }
392
+ function validateStreamingRequest(endpoint, options) {
393
+ if (endpoint.params && options.params) {
394
+ const result = endpoint.params.safeParse(options.params);
395
+ if (!result.success) {
396
+ throw new ClientValidationError("params", result.error.issues);
397
+ }
398
+ }
399
+ if (endpoint.query && options.query) {
400
+ const result = endpoint.query.safeParse(options.query);
401
+ if (!result.success) {
402
+ throw new ClientValidationError("query", result.error.issues);
403
+ }
404
+ }
405
+ if (endpoint.headers && options.headers) {
406
+ const result = endpoint.headers.safeParse(options.headers);
407
+ if (!result.success) {
408
+ throw new ClientValidationError("headers", result.error.issues);
409
+ }
410
+ }
411
+ if (endpoint.body && options.body) {
412
+ const result = endpoint.body.safeParse(options.body);
413
+ if (!result.success) {
414
+ throw new ClientValidationError("body", result.error.issues);
415
+ }
416
+ }
417
+ }
418
+ async function makeStreamingRequest(config, endpoint, options) {
419
+ if (config.validateRequest !== false) {
420
+ validateStreamingRequest(endpoint, options);
421
+ }
422
+ let path = endpoint.path;
423
+ if (options.params) {
424
+ path = interpolatePath(path, options.params);
425
+ }
426
+ const url = buildUrl(config.baseUrl, path, options.query);
427
+ const headers = new Headers(config.headers);
428
+ if (options.headers) {
429
+ for (const [key, value] of Object.entries(options.headers)) {
430
+ headers.set(key, String(value));
431
+ }
432
+ }
433
+ const controller = new AbortController;
434
+ if (options.abortSignal) {
435
+ if (options.abortSignal.aborted) {
436
+ controller.abort();
437
+ } else {
438
+ options.abortSignal.addEventListener("abort", () => controller.abort());
439
+ }
440
+ }
441
+ const init = {
442
+ method: endpoint.method,
443
+ headers,
444
+ signal: controller.signal
445
+ };
446
+ if (options.body !== undefined) {
447
+ const contentType = endpoint.contentType ?? "application/json";
448
+ if (contentType === "multipart/form-data") {
449
+ init.body = objectToFormData(options.body);
450
+ } else {
451
+ headers.set("content-type", "application/json");
452
+ init.body = JSON.stringify(options.body);
453
+ }
454
+ }
455
+ const response = await fetch(url, init);
456
+ if (!response.ok) {
457
+ const contentType = response.headers.get("content-type") || "";
458
+ let data;
459
+ if (contentType.includes("application/json")) {
460
+ data = await response.json();
461
+ } else {
462
+ data = await response.text();
463
+ }
464
+ throw new HTTPError(response.status, response.statusText, data);
465
+ }
466
+ return createStreamingResult(response, controller);
467
+ }
468
+ function createSSEConnection(config, endpoint, options = {}) {
469
+ let path = endpoint.path;
470
+ if (options.params) {
471
+ path = interpolatePath(path, options.params);
472
+ }
473
+ const url = buildUrl(config.baseUrl, path, options.query);
474
+ const eventSource = new EventSource(url);
475
+ const listeners = {
476
+ error: new Set
477
+ };
478
+ const eventNames = Object.keys(endpoint.events);
479
+ for (const eventName of eventNames) {
480
+ listeners[eventName] = new Set;
481
+ eventSource.addEventListener(eventName, (e) => {
482
+ const messageEvent = e;
483
+ try {
484
+ const data = JSON.parse(messageEvent.data);
485
+ listeners[eventName].forEach((h) => h(data, messageEvent.lastEventId || undefined));
486
+ } catch (err) {
487
+ listeners.error.forEach((h) => h(new Error(`Failed to parse SSE data: ${err.message}`)));
488
+ }
489
+ });
490
+ }
491
+ eventSource.onerror = () => {
492
+ listeners.error.forEach((h) => h(new Error("SSE connection error")));
493
+ };
494
+ return {
495
+ on(event, handler) {
496
+ if (!listeners[event]) {
497
+ listeners[event] = new Set;
498
+ }
499
+ listeners[event].add(handler);
500
+ return () => listeners[event].delete(handler);
501
+ },
502
+ close() {
503
+ eventSource.close();
504
+ },
505
+ get state() {
506
+ const states = ["connecting", "open", "closed"];
507
+ return states[eventSource.readyState];
508
+ }
509
+ };
510
+ }
124
511
  function resolveBaseUrl(baseUrl) {
125
512
  if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) {
126
513
  return baseUrl;
@@ -139,9 +526,25 @@ function createClient(contract, config) {
139
526
  };
140
527
  const client = {};
141
528
  for (const [name, endpoint] of Object.entries(contract)) {
142
- client[name] = (options = {}) => {
143
- return makeRequest(resolvedConfig, endpoint, options);
144
- };
529
+ if (endpoint.type === "standard") {
530
+ client[name] = (options = {}) => {
531
+ return makeRequest(resolvedConfig, endpoint, options);
532
+ };
533
+ } else if (endpoint.type === "streaming") {
534
+ client[name] = (options = {}) => {
535
+ return makeStreamingRequest(resolvedConfig, endpoint, options);
536
+ };
537
+ } else if (endpoint.type === "sse") {
538
+ client[name] = (options = {}) => {
539
+ return createSSEConnection(resolvedConfig, endpoint, options);
540
+ };
541
+ } else if (endpoint.type === "download") {
542
+ client[name] = (options = {}) => {
543
+ return makeDownloadRequest(resolvedConfig, endpoint, options);
544
+ };
545
+ } else {
546
+ throw new Error(`Endpoint "${name}" has unknown type "${endpoint.type}".`);
547
+ }
145
548
  }
146
549
  return client;
147
550
  }
@@ -161,4 +564,4 @@ export {
161
564
  ClientValidationError
162
565
  };
163
566
 
164
- //# debugId=B663AC78F4A6E33F64756E2164756E21
567
+ //# debugId=50D6CA4EBD15A65664756E2164756E21