recker 1.0.83 → 1.0.84-next.ca0ad49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ### Multi-Protocol SDK for the AI Era
6
6
 
7
- Nine protocols unified: HTTP, WebSocket, DNS, WHOIS, RDAP, FTP, SFTP, Telnet, HLS.
7
+ Ten protocols unified: HTTP, WebSocket, DNS, WHOIS, RDAP, FTP, SFTP, Telnet, HLS, Raffel.
8
8
  <br>
9
9
  AI-native: OpenAI, Anthropic, Google, Ollama + MCP server with 70 tools.
10
10
  <br>
@@ -108,13 +108,14 @@ await recker.whois('github.com'); // WHOIS
108
108
  await recker.dns('google.com'); // DNS
109
109
  await recker.ai.chat('Hello!'); // AI
110
110
  recker.ws('wss://example.com/socket'); // WebSocket
111
+ recker.raffel('ws://game:9999'); // Raffel
111
112
  ```
112
113
 
113
114
  ## What's Inside
114
115
 
115
116
  | Category | Features |
116
117
  |:---------|:---------|
117
- | **Protocols** | HTTP/2, WebSocket, DNS, WHOIS, RDAP, FTP, SFTP, Telnet, HLS |
118
+ | **Protocols** | HTTP/2, WebSocket, DNS, WHOIS, RDAP, FTP, SFTP, Telnet, HLS, Raffel |
118
119
  | **AI** | OpenAI, Anthropic, Google, Ollama, Groq, Mistral + streaming |
119
120
  | **Resilience** | Retry, circuit breaker, rate limiting, request deduplication |
120
121
  | **Auth** | Basic, Bearer, OAuth2, AWS SigV4, Digest, API Key + 15 providers |
@@ -197,6 +198,23 @@ await spider.crawl('https://protected-site.com');
197
198
 
198
199
  [📖 Anti-blocking docs](https://forattini-dev.github.io/recker/#/scraping/06-anti-blocking)
199
200
 
201
+ ### Raffel Protocol
202
+
203
+ ```typescript
204
+ import { createRaffelClient } from 'recker';
205
+
206
+ const client = createRaffelClient('ws://api:3000', { reconnect: true });
207
+ await client.connect();
208
+
209
+ const user = await client.call<User>('users.get', { id: 42 });
210
+ client.subscribe('notifications', (event, data) => console.log(event, data));
211
+ client.notify('analytics.track', { event: 'page_view' });
212
+ ```
213
+
214
+ Recker connects. Raffel serves. One protocol, zero glue.
215
+
216
+ [→ Raffel Documentation](https://forattini-dev.github.io/recker/#/protocols/10-raffel)
217
+
200
218
  ### 48 API Presets
201
219
 
202
220
  Pre-configured clients for popular services:
@@ -161,6 +161,17 @@ export class ResourceRegistry {
161
161
  mimeType: 'text/markdown',
162
162
  text: this.getBenchmarkSummary(),
163
163
  }]);
164
+ this.registerResource({
165
+ uri: 'recker://docs/raffel',
166
+ name: 'Raffel Protocol Client',
167
+ description: 'Connect to Raffel servers: RPC calls, channels, events, error handling',
168
+ mimeType: 'text/markdown',
169
+ }, () => [{
170
+ type: 'resource',
171
+ uri: 'recker://docs/raffel',
172
+ mimeType: 'text/markdown',
173
+ text: RAFFEL_DOCS,
174
+ }]);
164
175
  this.registerResource({
165
176
  uri: 'recker://seo/checklist',
166
177
  name: 'SEO Technical Checklist',
@@ -1091,3 +1102,122 @@ function getErrorMessage(error) {
1091
1102
  return String(error);
1092
1103
  }
1093
1104
  }
1105
+ const RAFFEL_DOCS = `# Raffel Protocol Client
1106
+
1107
+ Connect to Raffel servers with type-safe RPC, channels, and events over WebSocket.
1108
+
1109
+ ## Setup
1110
+
1111
+ \`\`\`typescript
1112
+ import { createRaffelClient, RaffelError } from 'recker';
1113
+ // or: import { RaffelClient } from 'recker';
1114
+ // or: recker.raffel(url, options)
1115
+ \`\`\`
1116
+
1117
+ ## Connect & Close
1118
+
1119
+ \`\`\`typescript
1120
+ // Transport auto-detected from URL: ws://, http://, tcp://, udp://, http://host/rpc
1121
+ const client = createRaffelClient('ws://localhost:3000', {
1122
+ defaultTimeout: 10000,
1123
+ channels: ['lobby'],
1124
+ ws: { // WebSocket-specific options
1125
+ reconnect: true,
1126
+ reconnectDelay: 1000,
1127
+ maxReconnectAttempts: 5,
1128
+ heartbeatInterval: 30000,
1129
+ headers: { Authorization: 'Bearer token' },
1130
+ },
1131
+ });
1132
+ // HTTP: createRaffelClient('http://api:3000', { http: { timeout: 10000 } })
1133
+ // TCP: createRaffelClient('tcp://svc:9000', { tcp: { reconnect: true } })
1134
+ // UDP: createRaffelClient('udp://svc:9000')
1135
+ // JSON-RPC: createRaffelClient('http://api:3000/rpc')
1136
+ await client.connect();
1137
+ client.close();
1138
+ \`\`\`
1139
+
1140
+ ## RPC Calls
1141
+
1142
+ \`\`\`typescript
1143
+ const user = await client.call<User>('users.get', { id: 42 });
1144
+ const data = await client.call('slow.op', payload, { timeout: 60000 });
1145
+ const res = await client.call('search', query, { signal: abortController.signal });
1146
+ client.cancel('req-3'); // Cancel in-flight call
1147
+ \`\`\`
1148
+
1149
+ ## Fire-and-Forget
1150
+
1151
+ \`\`\`typescript
1152
+ client.notify('analytics.track', { event: 'click' });
1153
+ \`\`\`
1154
+
1155
+ ## Channels
1156
+
1157
+ \`\`\`typescript
1158
+ client.subscribe('chat', (event, data) => console.log(event, data));
1159
+ client.publish('chat', 'message', { text: 'Hello' });
1160
+ client.unsubscribe('chat');
1161
+ \`\`\`
1162
+
1163
+ ## Events
1164
+
1165
+ | Event | Args | Description |
1166
+ |---|---|---|
1167
+ | raffel:connected | — | Connected |
1168
+ | raffel:disconnected | — | Disconnected |
1169
+ | raffel:event | (procedure, payload) | Server event |
1170
+ | raffel:channel:subscribed | (channel, members?) | Joined channel |
1171
+ | raffel:channel:unsubscribed | (channel) | Left channel |
1172
+ | raffel:channel:event | (channel, event, data) | Channel event |
1173
+ | ws:reconnecting | (attempt, delay) | Reconnecting |
1174
+ | ws:error | (error) | WebSocket error |
1175
+
1176
+ ## Error Handling
1177
+
1178
+ \`\`\`typescript
1179
+ try {
1180
+ await client.call('users.get', { id: 999 });
1181
+ } catch (err) {
1182
+ if (err instanceof RaffelError) {
1183
+ err.code; // "NOT_FOUND"
1184
+ err.status; // 404
1185
+ err.procedure; // "users.get"
1186
+ err.details; // optional server details
1187
+ }
1188
+ }
1189
+ \`\`\`
1190
+
1191
+ ## Properties
1192
+
1193
+ - \`client.isConnected\` — boolean
1194
+ - \`client.raw\` — underlying RaffelTransport
1195
+ - \`client.rawWs\` — underlying ReckerWebSocket (null for non-WS transports)
1196
+
1197
+ ## Capability Matrix
1198
+
1199
+ | Method | WS | HTTP | TCP | UDP | JSON-RPC |
1200
+ |---|:---:|:---:|:---:|:---:|:---:|
1201
+ | call() | ✅ | ✅ | ✅ | ✅ | ✅ |
1202
+ | callStream() | ✅ | ✅ (SSE) | ✅ | ❌ | ❌ |
1203
+ | notify() | ✅ | ✅ | ✅ | ✅ | ✅ |
1204
+ | subscribe() | ✅ | ❌ | ✅ | ❌ | ❌ |
1205
+ | publish() | ✅ | ❌ | ✅ | ❌ | ❌ |
1206
+ | cancel() | ✅ | ❌ | ✅ | ❌ | ❌ |
1207
+
1208
+ ## Example
1209
+
1210
+ \`\`\`typescript
1211
+ const api = createRaffelClient('ws://api:3000', {
1212
+ channels: ['notifications'],
1213
+ channelHandlers: {
1214
+ notifications: (event, data) => showToast(data),
1215
+ },
1216
+ ws: { reconnect: true },
1217
+ });
1218
+ await api.connect();
1219
+
1220
+ const user = await api.call('auth.verify', { token });
1221
+ api.notify('presence.online', { userId: user.id });
1222
+ \`\`\`
1223
+ `;
@@ -1,5 +1,6 @@
1
1
  import { ReckerWebSocket } from '../websocket/client.js';
2
2
  import type { RaffelClientOptions, RaffelCallOptions, ChannelEventHandler } from './types.js';
3
+ import type { RaffelTransport } from './transport.js';
3
4
  type Listener = (...args: any[]) => void;
4
5
  declare class SimpleEmitter {
5
6
  private listeners;
@@ -9,30 +10,37 @@ declare class SimpleEmitter {
9
10
  emit(event: string, ...args: any[]): boolean;
10
11
  }
11
12
  export declare class RaffelClient extends SimpleEmitter {
12
- private ws;
13
+ private transport;
13
14
  private pendingCalls;
14
15
  private subscribedChannels;
15
16
  private idCounter;
16
17
  private defaultTimeout;
17
18
  private onEvent?;
19
+ private url;
20
+ private options;
18
21
  constructor(url: string, options?: RaffelClientOptions);
22
+ private ensureTransport;
23
+ _setTransport(transport: RaffelTransport): void;
24
+ private wireTransportEvents;
19
25
  connect(): Promise<void>;
20
26
  close(code?: number, reason?: string): void;
21
27
  get isConnected(): boolean;
22
- get raw(): ReckerWebSocket;
28
+ get raw(): RaffelTransport;
29
+ get rawWs(): ReckerWebSocket | null;
23
30
  call<T = unknown>(procedure: string, payload?: unknown, options?: RaffelCallOptions): Promise<T>;
31
+ callStream<T = unknown>(procedure: string, payload?: unknown): AsyncIterable<T>;
24
32
  notify(procedure: string, payload?: unknown): void;
25
33
  subscribe(channel: string, handler?: ChannelEventHandler): void;
26
34
  unsubscribe(channel: string): void;
27
35
  publish(channel: string, event: string, data?: unknown): void;
28
36
  cancel(id: string): void;
29
- private handleMessage;
37
+ private executeWithTimeout;
38
+ private handleIncoming;
30
39
  private handleEnvelope;
31
40
  private handleChannelMessage;
32
41
  private nextId;
33
42
  private extractBaseId;
34
43
  private sendChannelSubscribe;
35
- private sendRaw;
36
44
  }
37
45
  export declare function createRaffelClient(url: string, options?: RaffelClientOptions): RaffelClient;
38
46
  export {};
@@ -1,5 +1,8 @@
1
- import { ReckerWebSocket } from '../websocket/client.js';
1
+ import { UnsupportedError } from '../core/errors.js';
2
2
  import { RaffelError } from './types.js';
3
+ import { TransportCapability, hasExecute, hasCapability } from './transport.js';
4
+ import { WsTransport } from './transport-ws.js';
5
+ import { createTransport, detectTransportType } from './transport-factory.js';
3
6
  class SimpleEmitter {
4
7
  listeners = new Map();
5
8
  on(event, listener) {
@@ -35,38 +38,52 @@ class SimpleEmitter {
35
38
  }
36
39
  }
37
40
  export class RaffelClient extends SimpleEmitter {
38
- ws;
41
+ transport;
39
42
  pendingCalls = new Map();
40
43
  subscribedChannels = new Map();
41
44
  idCounter = 0;
42
45
  defaultTimeout;
43
46
  onEvent;
47
+ url;
48
+ options;
44
49
  constructor(url, options = {}) {
45
50
  super();
46
- const { channels, channelHandlers, onEvent, defaultTimeout, ...wsOptions } = options;
47
- this.defaultTimeout = defaultTimeout ?? 30_000;
48
- this.onEvent = onEvent;
49
- this.ws = new ReckerWebSocket(url, wsOptions);
50
- if (channels) {
51
- for (const ch of channels) {
52
- this.subscribedChannels.set(ch, channelHandlers?.[ch] ?? null);
51
+ this.url = url;
52
+ this.options = options;
53
+ this.defaultTimeout = options.defaultTimeout ?? 30_000;
54
+ this.onEvent = options.onEvent;
55
+ if (options.channels) {
56
+ for (const ch of options.channels) {
57
+ this.subscribedChannels.set(ch, options.channelHandlers?.[ch] ?? null);
53
58
  }
54
59
  }
55
- if (channelHandlers) {
56
- for (const [ch, handler] of Object.entries(channelHandlers)) {
60
+ if (options.channelHandlers) {
61
+ for (const [ch, handler] of Object.entries(options.channelHandlers)) {
57
62
  if (!this.subscribedChannels.has(ch)) {
58
63
  this.subscribedChannels.set(ch, handler);
59
64
  }
60
65
  }
61
66
  }
62
- this.ws.on('message', (msg) => this.handleMessage(msg));
63
- this.ws.on('open', () => {
67
+ }
68
+ async ensureTransport() {
69
+ if (this.transport)
70
+ return;
71
+ this.transport = await createTransport(this.url, this.options);
72
+ this.wireTransportEvents();
73
+ }
74
+ _setTransport(transport) {
75
+ this.transport = transport;
76
+ this.wireTransportEvents();
77
+ }
78
+ wireTransportEvents() {
79
+ this.transport.on('message', (data) => this.handleIncoming(data));
80
+ this.transport.on('connected', () => {
64
81
  this.emit('raffel:connected');
65
82
  for (const [channel] of this.subscribedChannels) {
66
83
  this.sendChannelSubscribe(channel);
67
84
  }
68
85
  });
69
- this.ws.on('close', (_code, _reason) => {
86
+ this.transport.on('disconnected', () => {
70
87
  this.emit('raffel:disconnected');
71
88
  for (const [id, pending] of this.pendingCalls) {
72
89
  if (pending.timer)
@@ -75,15 +92,16 @@ export class RaffelClient extends SimpleEmitter {
75
92
  }
76
93
  this.pendingCalls.clear();
77
94
  });
78
- this.ws.on('reconnecting', (attempt, delay) => {
95
+ this.transport.on('reconnecting', (attempt, delay) => {
79
96
  this.emit('ws:reconnecting', attempt, delay);
80
97
  });
81
- this.ws.on('error', (err) => {
98
+ this.transport.on('error', (err) => {
82
99
  this.emit('ws:error', err);
83
100
  });
84
101
  }
85
102
  async connect() {
86
- return this.ws.connect();
103
+ await this.ensureTransport();
104
+ return this.transport.connect();
87
105
  }
88
106
  close(code, reason) {
89
107
  for (const [id, pending] of this.pendingCalls) {
@@ -92,13 +110,18 @@ export class RaffelClient extends SimpleEmitter {
92
110
  pending.reject(new Error(`Connection closed while waiting for response to ${id}`));
93
111
  }
94
112
  this.pendingCalls.clear();
95
- this.ws.close(code, reason);
113
+ if (this.transport) {
114
+ this.transport.close(code, reason);
115
+ }
96
116
  }
97
117
  get isConnected() {
98
- return this.ws.isConnected;
118
+ return this.transport?.isConnected ?? false;
99
119
  }
100
120
  get raw() {
101
- return this.ws;
121
+ return this.transport;
122
+ }
123
+ get rawWs() {
124
+ return this.transport instanceof WsTransport ? this.transport.socket : null;
102
125
  }
103
126
  async call(procedure, payload, options) {
104
127
  const id = this.nextId();
@@ -110,6 +133,9 @@ export class RaffelClient extends SimpleEmitter {
110
133
  payload: payload ?? {},
111
134
  metadata: {},
112
135
  };
136
+ if (hasExecute(this.transport)) {
137
+ return this.executeWithTimeout(this.transport, envelope, timeout, procedure, options?.signal);
138
+ }
113
139
  return new Promise((resolve, reject) => {
114
140
  let timer = null;
115
141
  const cleanup = () => {
@@ -122,7 +148,7 @@ export class RaffelClient extends SimpleEmitter {
122
148
  };
123
149
  const onAbort = () => {
124
150
  cleanup();
125
- this.sendRaw({ id, type: 'cancel' });
151
+ this.transport.send({ id, type: 'cancel' });
126
152
  reject(new Error(`Call to ${procedure} was aborted`));
127
153
  };
128
154
  if (timeout > 0) {
@@ -139,9 +165,75 @@ export class RaffelClient extends SimpleEmitter {
139
165
  options.signal.addEventListener('abort', onAbort, { once: true });
140
166
  }
141
167
  this.pendingCalls.set(id, { resolve, reject, timer });
142
- this.ws.sendJSON(envelope);
168
+ this.transport.send(envelope);
143
169
  });
144
170
  }
171
+ async *callStream(procedure, payload) {
172
+ if (!hasCapability(this.transport, TransportCapability.STREAM)) {
173
+ throw new UnsupportedError(`callStream() not supported over ${detectTransportType(this.url, this.options)}. Use WebSocket, HTTP, or TCP.`);
174
+ }
175
+ if (hasExecute(this.transport) && this.transport.executeStream) {
176
+ const envelope = {
177
+ id: this.nextId(),
178
+ procedure,
179
+ type: 'stream:start',
180
+ payload: payload ?? {},
181
+ metadata: {},
182
+ };
183
+ yield* this.transport.executeStream(envelope);
184
+ return;
185
+ }
186
+ const id = this.nextId();
187
+ const envelope = {
188
+ id,
189
+ procedure,
190
+ type: 'stream:start',
191
+ payload: payload ?? {},
192
+ metadata: {},
193
+ };
194
+ const queue = [];
195
+ let resolve = null;
196
+ const push = (item) => {
197
+ queue.push(item);
198
+ if (resolve) {
199
+ resolve();
200
+ resolve = null;
201
+ }
202
+ };
203
+ const streamHandler = (data) => {
204
+ if (!data.id || !data.id.startsWith(id))
205
+ return;
206
+ if (data.type === 'stream:data') {
207
+ push({ value: data.payload });
208
+ }
209
+ else if (data.type === 'stream:end') {
210
+ push({ done: true });
211
+ this.transport.off('message', streamHandler);
212
+ }
213
+ else if (data.type === 'error') {
214
+ push({ error: new RaffelError(data.payload, procedure) });
215
+ this.transport.off('message', streamHandler);
216
+ }
217
+ };
218
+ this.transport.on('message', streamHandler);
219
+ this.transport.send(envelope);
220
+ try {
221
+ while (true) {
222
+ if (queue.length === 0) {
223
+ await new Promise((r) => { resolve = r; });
224
+ }
225
+ const item = queue.shift();
226
+ if (item.done)
227
+ return;
228
+ if (item.error)
229
+ throw item.error;
230
+ yield item.value;
231
+ }
232
+ }
233
+ finally {
234
+ this.transport.off('message', streamHandler);
235
+ }
236
+ }
145
237
  notify(procedure, payload) {
146
238
  const envelope = {
147
239
  id: this.nextId(),
@@ -150,26 +242,43 @@ export class RaffelClient extends SimpleEmitter {
150
242
  payload: payload ?? {},
151
243
  metadata: {},
152
244
  };
153
- this.ws.sendJSON(envelope);
245
+ if (hasExecute(this.transport)) {
246
+ this.transport.execute({
247
+ ...envelope,
248
+ type: 'notify',
249
+ }).catch(() => { });
250
+ }
251
+ else {
252
+ this.transport.send(envelope);
253
+ }
154
254
  }
155
255
  subscribe(channel, handler) {
256
+ if (!hasCapability(this.transport, TransportCapability.SUBSCRIBE)) {
257
+ throw new UnsupportedError(`subscribe() not supported over ${detectTransportType(this.url, this.options)}. Use WebSocket or TCP.`);
258
+ }
156
259
  this.subscribedChannels.set(channel, handler ?? null);
157
- if (this.ws.isConnected) {
260
+ if (this.transport.isConnected) {
158
261
  this.sendChannelSubscribe(channel);
159
262
  }
160
263
  }
161
264
  unsubscribe(channel) {
265
+ if (!hasCapability(this.transport, TransportCapability.SUBSCRIBE)) {
266
+ throw new UnsupportedError(`unsubscribe() not supported over ${detectTransportType(this.url, this.options)}. Use WebSocket or TCP.`);
267
+ }
162
268
  this.subscribedChannels.delete(channel);
163
- if (this.ws.isConnected) {
269
+ if (this.transport.isConnected) {
164
270
  const msg = {
165
271
  id: this.nextId(),
166
272
  type: 'unsubscribe',
167
273
  channel,
168
274
  };
169
- this.ws.sendJSON(msg);
275
+ this.transport.send(msg);
170
276
  }
171
277
  }
172
278
  publish(channel, event, data) {
279
+ if (!hasCapability(this.transport, TransportCapability.PUBLISH)) {
280
+ throw new UnsupportedError(`publish() not supported over ${detectTransportType(this.url, this.options)}. Use WebSocket or TCP.`);
281
+ }
173
282
  const msg = {
174
283
  id: this.nextId(),
175
284
  type: 'publish',
@@ -177,9 +286,12 @@ export class RaffelClient extends SimpleEmitter {
177
286
  event,
178
287
  data,
179
288
  };
180
- this.ws.sendJSON(msg);
289
+ this.transport.send(msg);
181
290
  }
182
291
  cancel(id) {
292
+ if (!hasCapability(this.transport, TransportCapability.CANCEL)) {
293
+ throw new UnsupportedError(`cancel() not supported over ${detectTransportType(this.url, this.options)}. Use WebSocket or TCP.`);
294
+ }
183
295
  const pending = this.pendingCalls.get(id);
184
296
  if (pending) {
185
297
  if (pending.timer)
@@ -187,26 +299,36 @@ export class RaffelClient extends SimpleEmitter {
187
299
  pending.reject(new Error(`Call ${id} was cancelled`));
188
300
  this.pendingCalls.delete(id);
189
301
  }
190
- this.sendRaw({ id, type: 'cancel' });
302
+ this.transport.send({ id, type: 'cancel' });
191
303
  }
192
- handleMessage(msg) {
193
- if (typeof msg.data !== 'string')
194
- return;
195
- let parsed;
196
- try {
197
- parsed = JSON.parse(msg.data);
304
+ async executeWithTimeout(transport, envelope, timeout, procedure, signal) {
305
+ if (signal?.aborted) {
306
+ throw new Error(`Call to ${procedure} was aborted`);
198
307
  }
199
- catch {
200
- return;
308
+ const promises = [
309
+ transport.execute(envelope),
310
+ ];
311
+ if (timeout > 0) {
312
+ promises.push(new Promise((_, reject) => {
313
+ setTimeout(() => reject(new Error(`Call to ${procedure} timed out after ${timeout}ms`)), timeout);
314
+ }));
201
315
  }
202
- if (parsed.channel) {
203
- this.handleChannelMessage(parsed);
316
+ if (signal) {
317
+ promises.push(new Promise((_, reject) => {
318
+ signal.addEventListener('abort', () => reject(new Error(`Call to ${procedure} was aborted`)), { once: true });
319
+ }));
204
320
  }
205
- else if (parsed.procedure || parsed.type === 'cancel') {
206
- this.handleEnvelope(parsed);
321
+ return Promise.race(promises);
322
+ }
323
+ handleIncoming(data) {
324
+ if (data.channel) {
325
+ this.handleChannelMessage(data);
326
+ }
327
+ else if (data.procedure || data.type === 'cancel') {
328
+ this.handleEnvelope(data);
207
329
  }
208
330
  else {
209
- this.emit('raffel:unknown', parsed);
331
+ this.emit('raffel:unknown', data);
210
332
  }
211
333
  }
212
334
  handleEnvelope(envelope) {
@@ -271,10 +393,7 @@ export class RaffelClient extends SimpleEmitter {
271
393
  type: 'subscribe',
272
394
  channel,
273
395
  };
274
- this.ws.sendJSON(msg);
275
- }
276
- sendRaw(data) {
277
- this.ws.sendJSON(data);
396
+ this.transport.send(msg);
278
397
  }
279
398
  }
280
399
  export function createRaffelClient(url, options) {
@@ -1,2 +1,9 @@
1
1
  export * from './client.js';
2
2
  export * from './types.js';
3
+ export * from './transport.js';
4
+ export * from './transport-ws.js';
5
+ export * from './transport-http.js';
6
+ export * from './transport-tcp.js';
7
+ export * from './transport-udp.js';
8
+ export * from './transport-jsonrpc.js';
9
+ export { detectTransportType, createTransport } from './transport-factory.js';
@@ -1,2 +1,9 @@
1
1
  export * from './client.js';
2
2
  export * from './types.js';
3
+ export * from './transport.js';
4
+ export * from './transport-ws.js';
5
+ export * from './transport-http.js';
6
+ export * from './transport-tcp.js';
7
+ export * from './transport-udp.js';
8
+ export * from './transport-jsonrpc.js';
9
+ export { detectTransportType, createTransport } from './transport-factory.js';
@@ -0,0 +1,4 @@
1
+ import type { RaffelTransport } from './transport.js';
2
+ import type { RaffelClientOptions, RaffelTransportType } from './types.js';
3
+ export declare function detectTransportType(url: string, options?: RaffelClientOptions): RaffelTransportType;
4
+ export declare function createTransport(url: string, options?: RaffelClientOptions): Promise<RaffelTransport>;
@@ -0,0 +1,52 @@
1
+ import { WsTransport } from './transport-ws.js';
2
+ export function detectTransportType(url, options) {
3
+ if (options?.transport)
4
+ return options.transport;
5
+ const lower = url.toLowerCase();
6
+ if (lower.startsWith('ws://') || lower.startsWith('wss://')) {
7
+ return 'websocket';
8
+ }
9
+ if (lower.startsWith('tcp://')) {
10
+ return 'tcp';
11
+ }
12
+ if (lower.startsWith('udp://')) {
13
+ return 'udp';
14
+ }
15
+ if (lower.startsWith('http://') || lower.startsWith('https://')) {
16
+ try {
17
+ const parsed = new URL(url);
18
+ if (parsed.pathname === '/rpc' || parsed.pathname.endsWith('/rpc')) {
19
+ return 'jsonrpc';
20
+ }
21
+ }
22
+ catch {
23
+ }
24
+ return 'http';
25
+ }
26
+ return 'websocket';
27
+ }
28
+ export async function createTransport(url, options = {}) {
29
+ const type = detectTransportType(url, options);
30
+ switch (type) {
31
+ case 'websocket':
32
+ return new WsTransport(url, options.ws);
33
+ case 'http': {
34
+ const { HttpTransport } = await import('./transport-http.js');
35
+ return new HttpTransport(url, options.http);
36
+ }
37
+ case 'tcp': {
38
+ const { TcpTransport } = await import('./transport-tcp.js');
39
+ return new TcpTransport(url, options.tcp);
40
+ }
41
+ case 'udp': {
42
+ const { UdpTransport } = await import('./transport-udp.js');
43
+ return new UdpTransport(url, options.udp);
44
+ }
45
+ case 'jsonrpc': {
46
+ const { JsonRpcTransport } = await import('./transport-jsonrpc.js');
47
+ return new JsonRpcTransport(url, options.jsonrpc);
48
+ }
49
+ default:
50
+ throw new Error(`Unknown transport type: ${type}`);
51
+ }
52
+ }
@@ -0,0 +1,25 @@
1
+ import type { RaffelExecutableTransport, RaffelTransportEvent } from './transport.js';
2
+ import type { RaffelEnvelope, RaffelHttpOptions } from './types.js';
3
+ type Listener = (...args: any[]) => void;
4
+ export declare class HttpTransport implements RaffelExecutableTransport {
5
+ readonly capabilities: number;
6
+ private client;
7
+ private connected;
8
+ private listeners;
9
+ private baseUrl;
10
+ constructor(url: string, options?: RaffelHttpOptions);
11
+ get isConnected(): boolean;
12
+ connect(): Promise<void>;
13
+ close(): void;
14
+ send(_data: unknown): void;
15
+ execute(envelope: RaffelEnvelope): Promise<unknown>;
16
+ executeStream(envelope: RaffelEnvelope): AsyncIterable<unknown>;
17
+ private parseSSE;
18
+ private getReader;
19
+ private parseBody;
20
+ on(event: RaffelTransportEvent, listener: Listener): void;
21
+ off(event: RaffelTransportEvent, listener: Listener): void;
22
+ once(event: RaffelTransportEvent, listener: Listener): void;
23
+ private emit;
24
+ }
25
+ export {};