@bagelink/sdk 1.7.98 → 1.7.104
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/bin/index.ts +16 -0
- package/dist/index.cjs +578 -0
- package/dist/index.d.cts +171 -2
- package/dist/index.d.mts +171 -2
- package/dist/index.d.ts +171 -2
- package/dist/index.mjs +574 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/openAPITools/StreamController.ts +372 -0
- package/src/openAPITools/functionGenerator.ts +183 -1
- package/src/openAPITools/index.ts +2 -0
- package/src/openAPITools/streamClient.ts +247 -0
- package/src/openAPITools/streamDetector.ts +44 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/* eslint-disable ts/no-non-null-assertion */
|
|
2
|
+
/* eslint-disable ts/no-unnecessary-condition */
|
|
3
|
+
/**
|
|
4
|
+
* StreamController - Elegant event-based SSE stream management
|
|
5
|
+
* Provides a beautiful, type-safe API for consuming Server-Sent Events
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface SSEEvent<T = any> {
|
|
9
|
+
/** Event type (e.g., "token", "tool_call", "done") */
|
|
10
|
+
type: string
|
|
11
|
+
/** Event data payload */
|
|
12
|
+
data: T
|
|
13
|
+
/** Raw event string */
|
|
14
|
+
raw?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type EventHandler<T = any> = (data: T) => void
|
|
18
|
+
type ErrorHandler = (error: Error) => void
|
|
19
|
+
type VoidHandler = () => void
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type-safe event map for stream events
|
|
23
|
+
* Maps event names to their data types
|
|
24
|
+
*/
|
|
25
|
+
export type StreamEventMap = Record<string, any>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* StreamController - Chainable event emitter for SSE streams with full type safety
|
|
29
|
+
* @template TEventMap - Map of event names to their data types
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* type ChatEvents = {
|
|
33
|
+
* token: { content: string }
|
|
34
|
+
* tool_call: { name: string, args: any }
|
|
35
|
+
* done: { message: string }
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* const stream: StreamController<ChatEvents> = ...
|
|
39
|
+
* stream.on('token', (data) => {
|
|
40
|
+
* // `data` is typed as { content: string }
|
|
41
|
+
* console.log(data.content)
|
|
42
|
+
* })
|
|
43
|
+
*/
|
|
44
|
+
export class StreamController<TEventMap extends StreamEventMap = StreamEventMap> {
|
|
45
|
+
private handlers: Map<string, Set<EventHandler>> = new Map()
|
|
46
|
+
private errorHandlers: Set<ErrorHandler> = new Set()
|
|
47
|
+
private completeHandlers: Set<VoidHandler> = new Set()
|
|
48
|
+
private abortController: AbortController
|
|
49
|
+
private _closed = false
|
|
50
|
+
private _promise: Promise<any> | null = null
|
|
51
|
+
private _resolvePromise: ((value: any) => void) | null = null
|
|
52
|
+
private _rejectPromise: ((error: Error) => void) | null = null
|
|
53
|
+
|
|
54
|
+
constructor(
|
|
55
|
+
private streamFn: (
|
|
56
|
+
onEvent: (event: SSEEvent) => void,
|
|
57
|
+
onError: (error: Error) => void,
|
|
58
|
+
onComplete: () => void
|
|
59
|
+
) => () => void
|
|
60
|
+
) {
|
|
61
|
+
this.abortController = new AbortController()
|
|
62
|
+
this.start()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private start() {
|
|
66
|
+
const cleanup = this.streamFn(
|
|
67
|
+
(event) => { this.emit(event.type, event.data) },
|
|
68
|
+
(error) => { this.emitError(error) },
|
|
69
|
+
() => { this.emitComplete() }
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
// Store cleanup function
|
|
73
|
+
this.abortController.signal.addEventListener('abort', () => {
|
|
74
|
+
cleanup()
|
|
75
|
+
this._closed = true
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Register an event handler (fully typed!)
|
|
81
|
+
* @param event - Event type to listen for
|
|
82
|
+
* @param handler - Handler function (data type inferred from event)
|
|
83
|
+
* @returns this (for chaining)
|
|
84
|
+
*/
|
|
85
|
+
on<K extends keyof TEventMap | 'error' | 'complete'>(
|
|
86
|
+
event: K,
|
|
87
|
+
handler: K extends 'error'
|
|
88
|
+
? ErrorHandler
|
|
89
|
+
: K extends 'complete'
|
|
90
|
+
? VoidHandler
|
|
91
|
+
: K extends keyof TEventMap
|
|
92
|
+
? (data: TEventMap[K]) => void
|
|
93
|
+
: EventHandler
|
|
94
|
+
): this {
|
|
95
|
+
if (event === 'error') {
|
|
96
|
+
this.errorHandlers.add(handler as ErrorHandler)
|
|
97
|
+
} else if (event === 'complete') {
|
|
98
|
+
this.completeHandlers.add(handler as VoidHandler)
|
|
99
|
+
} else {
|
|
100
|
+
if (!this.handlers.has(event as string)) {
|
|
101
|
+
this.handlers.set(event as string, new Set())
|
|
102
|
+
}
|
|
103
|
+
this.handlers.get(event as string)!.add(handler as EventHandler)
|
|
104
|
+
}
|
|
105
|
+
return this
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Register a one-time event handler (fully typed!)
|
|
110
|
+
* @param event - Event type to listen for
|
|
111
|
+
* @param handler - Handler function (called once then removed, data type inferred from event)
|
|
112
|
+
* @returns this (for chaining)
|
|
113
|
+
*/
|
|
114
|
+
once<K extends keyof TEventMap | 'error' | 'complete'>(
|
|
115
|
+
event: K,
|
|
116
|
+
handler: K extends 'error'
|
|
117
|
+
? ErrorHandler
|
|
118
|
+
: K extends 'complete'
|
|
119
|
+
? VoidHandler
|
|
120
|
+
: K extends keyof TEventMap
|
|
121
|
+
? (data: TEventMap[K]) => void
|
|
122
|
+
: EventHandler
|
|
123
|
+
): this {
|
|
124
|
+
const wrappedHandler = (data: any) => {
|
|
125
|
+
(handler as any)(data)
|
|
126
|
+
this.off(event, wrappedHandler as any)
|
|
127
|
+
}
|
|
128
|
+
return this.on(event, wrappedHandler as any)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove an event handler (fully typed!)
|
|
133
|
+
* @param event - Event type
|
|
134
|
+
* @param handler - Handler to remove
|
|
135
|
+
* @returns this (for chaining)
|
|
136
|
+
*/
|
|
137
|
+
off<K extends keyof TEventMap | 'error' | 'complete'>(
|
|
138
|
+
event: K,
|
|
139
|
+
handler: K extends 'error'
|
|
140
|
+
? ErrorHandler
|
|
141
|
+
: K extends 'complete'
|
|
142
|
+
? VoidHandler
|
|
143
|
+
: K extends keyof TEventMap
|
|
144
|
+
? (data: TEventMap[K]) => void
|
|
145
|
+
: EventHandler
|
|
146
|
+
): this {
|
|
147
|
+
if (event === 'error') {
|
|
148
|
+
this.errorHandlers.delete(handler as ErrorHandler)
|
|
149
|
+
} else if (event === 'complete') {
|
|
150
|
+
this.completeHandlers.delete(handler as VoidHandler)
|
|
151
|
+
} else {
|
|
152
|
+
this.handlers.get(event as string)?.delete(handler as EventHandler)
|
|
153
|
+
}
|
|
154
|
+
return this
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Remove all handlers for an event (or all events if no event specified)
|
|
159
|
+
*/
|
|
160
|
+
removeAllListeners(event?: keyof TEventMap | 'error' | 'complete'): this {
|
|
161
|
+
if (event === undefined) {
|
|
162
|
+
this.handlers.clear()
|
|
163
|
+
this.errorHandlers.clear()
|
|
164
|
+
this.completeHandlers.clear()
|
|
165
|
+
} else if (event === 'error') {
|
|
166
|
+
this.errorHandlers.clear()
|
|
167
|
+
} else if (event === 'complete') {
|
|
168
|
+
this.completeHandlers.clear()
|
|
169
|
+
} else {
|
|
170
|
+
this.handlers.delete(event as string)
|
|
171
|
+
}
|
|
172
|
+
return this
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private emit(event: string, data: any) {
|
|
176
|
+
const handlers = this.handlers.get(event)
|
|
177
|
+
if (handlers) {
|
|
178
|
+
handlers.forEach((handler) => {
|
|
179
|
+
try {
|
|
180
|
+
handler(data)
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error(`Error in handler for event "${event}":`, error)
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Resolve promise on 'done' event
|
|
188
|
+
if (event === 'done' && this._resolvePromise) {
|
|
189
|
+
this._resolvePromise(data)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private emitError(error: Error) {
|
|
194
|
+
this.errorHandlers.forEach((handler) => {
|
|
195
|
+
try {
|
|
196
|
+
handler(error)
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.error('Error in error handler:', err)
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// Reject promise on error
|
|
203
|
+
if (this._rejectPromise) {
|
|
204
|
+
this._rejectPromise(error)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private emitComplete() {
|
|
209
|
+
this.completeHandlers.forEach((handler) => {
|
|
210
|
+
try {
|
|
211
|
+
handler()
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error('Error in complete handler:', error)
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Close the stream
|
|
220
|
+
*/
|
|
221
|
+
close(): void {
|
|
222
|
+
if (!this._closed) {
|
|
223
|
+
this.abortController.abort()
|
|
224
|
+
this._closed = true
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if stream is closed
|
|
230
|
+
*/
|
|
231
|
+
get closed(): boolean {
|
|
232
|
+
return this._closed
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Convert stream to a Promise that resolves when 'done' event fires
|
|
237
|
+
* @returns Promise that resolves with the 'done' event data
|
|
238
|
+
*/
|
|
239
|
+
toPromise<T = any>(): Promise<T> {
|
|
240
|
+
if (!this._promise) {
|
|
241
|
+
this._promise = new Promise<T>((resolve, reject) => {
|
|
242
|
+
this._resolvePromise = resolve as any
|
|
243
|
+
this._rejectPromise = reject
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
return this._promise
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Make the stream async iterable
|
|
251
|
+
* Usage: for await (const event of stream) { ... }
|
|
252
|
+
*/
|
|
253
|
+
async* [Symbol.asyncIterator](): AsyncIterableIterator<SSEEvent> {
|
|
254
|
+
const events: SSEEvent[] = []
|
|
255
|
+
let resolveNext: ((event: SSEEvent | null) => void) | null = null
|
|
256
|
+
let done = false
|
|
257
|
+
|
|
258
|
+
// Capture all event types
|
|
259
|
+
const eventHandler = (type: string) => (data: any) => {
|
|
260
|
+
const event: SSEEvent = { type, data }
|
|
261
|
+
if (resolveNext) {
|
|
262
|
+
resolveNext(event)
|
|
263
|
+
resolveNext = null
|
|
264
|
+
} else {
|
|
265
|
+
events.push(event)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Listen to all events by capturing them
|
|
270
|
+
const originalEmit = this.emit.bind(this)
|
|
271
|
+
const capturedEvents: string[] = []
|
|
272
|
+
this.emit = (event: string, data: any) => {
|
|
273
|
+
if (!capturedEvents.includes(event)) {
|
|
274
|
+
capturedEvents.push(event)
|
|
275
|
+
|
|
276
|
+
this.on(event as any, eventHandler(event))
|
|
277
|
+
}
|
|
278
|
+
originalEmit(event, data)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.on('complete', () => {
|
|
282
|
+
done = true
|
|
283
|
+
if (resolveNext) {
|
|
284
|
+
resolveNext(null)
|
|
285
|
+
resolveNext = null
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
// eslint-disable-next-line no-unmodified-loop-condition
|
|
291
|
+
while (!done) {
|
|
292
|
+
if (events.length > 0) {
|
|
293
|
+
const event = events.shift()
|
|
294
|
+
if (event) yield event
|
|
295
|
+
} else {
|
|
296
|
+
// eslint-disable-next-line no-await-in-loop
|
|
297
|
+
const event = await new Promise<SSEEvent | null>((resolve) => {
|
|
298
|
+
resolveNext = resolve
|
|
299
|
+
})
|
|
300
|
+
if (event === null) break
|
|
301
|
+
yield event
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} finally {
|
|
305
|
+
this.close()
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Collect all events into an array until stream completes
|
|
311
|
+
* @returns Promise<SSEEvent[]>
|
|
312
|
+
*/
|
|
313
|
+
async toArray(): Promise<SSEEvent[]> {
|
|
314
|
+
const events: SSEEvent[] = []
|
|
315
|
+
for await (const event of this) {
|
|
316
|
+
events.push(event)
|
|
317
|
+
}
|
|
318
|
+
return events
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Pipe stream events through a transform function
|
|
323
|
+
*/
|
|
324
|
+
map<R>(transform: (event: SSEEvent) => R): StreamController<TEventMap> {
|
|
325
|
+
const mappedController = new StreamController<TEventMap>(
|
|
326
|
+
(onEvent, onError, onComplete) => {
|
|
327
|
+
this.on('error' as any, onError)
|
|
328
|
+
this.on('complete' as any, onComplete)
|
|
329
|
+
|
|
330
|
+
// Forward all events through transform
|
|
331
|
+
this.handlers.forEach((_, eventType) => {
|
|
332
|
+
this.on(eventType as any, (data: any) => {
|
|
333
|
+
try {
|
|
334
|
+
const transformed = transform({ type: eventType, data })
|
|
335
|
+
onEvent({ type: eventType, data: transformed, raw: undefined })
|
|
336
|
+
} catch (error) {
|
|
337
|
+
onError(error as Error)
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
return () => { this.close() }
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
return mappedController
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Filter events based on a predicate
|
|
350
|
+
*/
|
|
351
|
+
filter(predicate: (event: SSEEvent) => boolean): StreamController<TEventMap> {
|
|
352
|
+
const filteredController = new StreamController<TEventMap>(
|
|
353
|
+
(onEvent, onError, onComplete) => {
|
|
354
|
+
this.on('error' as any, onError)
|
|
355
|
+
this.on('complete' as any, onComplete)
|
|
356
|
+
|
|
357
|
+
// Forward filtered events
|
|
358
|
+
this.handlers.forEach((_, eventType) => {
|
|
359
|
+
this.on(eventType as any, (data: any) => {
|
|
360
|
+
const event = { type: eventType, data }
|
|
361
|
+
if (predicate(event)) {
|
|
362
|
+
onEvent(event)
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
return () => { this.close() }
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
return filteredController
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/* eslint-disable ts/strict-boolean-expressions */
|
|
2
|
+
/* eslint-disable ts/no-unnecessary-condition */
|
|
3
|
+
/* eslint-disable jsdoc/check-param-names */
|
|
4
|
+
/* eslint-disable ts/no-non-null-assertion */
|
|
5
|
+
/* eslint-disable prefer-destructuring */
|
|
1
6
|
import type {
|
|
2
7
|
OperationObject as OpenAPIOperation,
|
|
3
8
|
PathItemObject as OpenAPIPath,
|
|
@@ -8,8 +13,9 @@ import type {
|
|
|
8
13
|
ReferenceObject,
|
|
9
14
|
SchemaObject,
|
|
10
15
|
} from './types'
|
|
11
|
-
import {
|
|
16
|
+
import { isSSEStream, extractSSEEventTypes } from './streamDetector'
|
|
12
17
|
|
|
18
|
+
import { dereference, isReferenceObject } from './types/utils'
|
|
13
19
|
import {
|
|
14
20
|
cleanPath,
|
|
15
21
|
formatType,
|
|
@@ -56,6 +62,7 @@ export interface PathOperation {
|
|
|
56
62
|
// Tracking for function generation
|
|
57
63
|
const functionsInventory: Record<string, string> = {}
|
|
58
64
|
const pathOperations: PathOperation[] = []
|
|
65
|
+
const streamEventTypes: Record<string, string[]> = {} // Track stream endpoints and their event types
|
|
59
66
|
|
|
60
67
|
/**
|
|
61
68
|
* Collects non-primitive types for import statements
|
|
@@ -339,6 +346,115 @@ function combineAllParams(
|
|
|
339
346
|
return `{ ${destructuredNames} }: { ${typeDefinition} } = {}`
|
|
340
347
|
}
|
|
341
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Generates a unique type name for a stream endpoint
|
|
351
|
+
*/
|
|
352
|
+
function generateStreamTypeName(path: string): string {
|
|
353
|
+
return `${toPascalCase(
|
|
354
|
+
path
|
|
355
|
+
.split('/')
|
|
356
|
+
.filter(p => p && !/\{|\}/.test(p))
|
|
357
|
+
.join('_')
|
|
358
|
+
)}StreamEvents`
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Generates a Stream function for SSE endpoints with full type safety
|
|
363
|
+
* @param method - The HTTP method
|
|
364
|
+
* @param path - The original API path
|
|
365
|
+
* @param formattedPath - The formatted API path
|
|
366
|
+
* @param allParams - The combined parameter string
|
|
367
|
+
* @param requestBodyPayload - The request body payload
|
|
368
|
+
* @param eventTypes - Array of SSE event types
|
|
369
|
+
* @returns A string with the SSE stream function
|
|
370
|
+
*/
|
|
371
|
+
function generateStreamFunction(
|
|
372
|
+
method: string,
|
|
373
|
+
path: string,
|
|
374
|
+
formattedPath: string,
|
|
375
|
+
allParams: string,
|
|
376
|
+
requestBodyPayload: string,
|
|
377
|
+
eventTypes?: string[]
|
|
378
|
+
): string {
|
|
379
|
+
if (allParams === 'undefined') { allParams = '' }
|
|
380
|
+
|
|
381
|
+
// Extract parameter names for the stream call (unused but kept for future use)
|
|
382
|
+
// const paramNames = allParams ? allParams.match(/\w+(?=\s*[?:])/g) || [] : []
|
|
383
|
+
|
|
384
|
+
const bodyVar = requestBodyPayload || '{}'
|
|
385
|
+
const baseUrlRef = 'axios.defaults.baseURL || ""'
|
|
386
|
+
|
|
387
|
+
// Generate type name for this stream
|
|
388
|
+
const streamTypeName = generateStreamTypeName(path)
|
|
389
|
+
|
|
390
|
+
// Store event types for later type generation
|
|
391
|
+
if (eventTypes?.length) {
|
|
392
|
+
streamEventTypes[streamTypeName] = eventTypes
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const eventTypesComment = eventTypes?.length
|
|
396
|
+
? `\n * Event types: ${eventTypes.map(t => `"${t}"`).join(', ')}`
|
|
397
|
+
: ''
|
|
398
|
+
|
|
399
|
+
const eventTypesExample = eventTypes?.length
|
|
400
|
+
? eventTypes.map(t => `\n * .on('${t}', (data) => console.log(data))`).join('')
|
|
401
|
+
: '\n * .on(\'message\', (data) => console.log(data))'
|
|
402
|
+
|
|
403
|
+
const typeAnnotation = eventTypes?.length
|
|
404
|
+
? `StreamController<${streamTypeName}>`
|
|
405
|
+
: 'StreamController'
|
|
406
|
+
|
|
407
|
+
if (method === 'post') {
|
|
408
|
+
return `{
|
|
409
|
+
/**
|
|
410
|
+
* Stream SSE events from this endpoint (returns StreamController)${eventTypesComment}
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* const stream = api.endpoint.stream(params)${eventTypesExample}
|
|
414
|
+
* .on('error', (err) => console.error(err))
|
|
415
|
+
* .on('complete', () => console.log('Done!'))
|
|
416
|
+
*
|
|
417
|
+
* // Close stream when needed
|
|
418
|
+
* stream.close()
|
|
419
|
+
*/
|
|
420
|
+
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
421
|
+
const url = \`\${${baseUrlRef}}${formattedPath}\`
|
|
422
|
+
return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
|
|
423
|
+
},
|
|
424
|
+
/**
|
|
425
|
+
* Regular POST request (non-streaming)
|
|
426
|
+
*/
|
|
427
|
+
post: async (${allParams}): Promise<AxiosResponse<any>> => {
|
|
428
|
+
return axios.post(${formattedPath}, ${bodyVar})
|
|
429
|
+
}
|
|
430
|
+
}`
|
|
431
|
+
} else {
|
|
432
|
+
return `{
|
|
433
|
+
/**
|
|
434
|
+
* Stream SSE events from this endpoint (returns StreamController)${eventTypesComment}
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* const stream = api.endpoint.stream(params)${eventTypesExample}
|
|
438
|
+
* .on('error', (err) => console.error(err))
|
|
439
|
+
* .on('complete', () => console.log('Done!'))
|
|
440
|
+
*
|
|
441
|
+
* // Close stream when needed
|
|
442
|
+
* stream.close()
|
|
443
|
+
*/
|
|
444
|
+
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
445
|
+
const url = \`\${${baseUrlRef}}${formattedPath}\`
|
|
446
|
+
return createSSEStream<${streamTypeName}>(url, options)
|
|
447
|
+
},
|
|
448
|
+
/**
|
|
449
|
+
* Regular GET request (non-streaming)
|
|
450
|
+
*/
|
|
451
|
+
get: async (${allParams}): Promise<AxiosResponse<any>> => {
|
|
452
|
+
return axios.get(${formattedPath})
|
|
453
|
+
}
|
|
454
|
+
}`
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
342
458
|
/**
|
|
343
459
|
* Generates an Axios function call as a string
|
|
344
460
|
* @param method - The HTTP method
|
|
@@ -444,6 +560,9 @@ function generateFunctionForOperation(
|
|
|
444
560
|
): string {
|
|
445
561
|
if (!operation) { return '' }
|
|
446
562
|
|
|
563
|
+
// Check if this is an SSE stream endpoint
|
|
564
|
+
const isStream = isSSEStream(operation)
|
|
565
|
+
|
|
447
566
|
// Validate: GET and DELETE requests should not have request bodies
|
|
448
567
|
const methodLower = method.toLowerCase()
|
|
449
568
|
if (['get', 'delete'].includes(methodLower) && operation.requestBody) {
|
|
@@ -493,6 +612,19 @@ function generateFunctionForOperation(
|
|
|
493
612
|
// Create JSDoc comment with OpenAPI documentation
|
|
494
613
|
const functionComment = buildJSDocComment(operation, method, path)
|
|
495
614
|
|
|
615
|
+
// Generate stream function for SSE endpoints
|
|
616
|
+
if (isStream) {
|
|
617
|
+
const eventTypes = extractSSEEventTypes(operation)
|
|
618
|
+
return functionComment + generateStreamFunction(
|
|
619
|
+
method,
|
|
620
|
+
path,
|
|
621
|
+
formatPathWithParams(path),
|
|
622
|
+
allParams,
|
|
623
|
+
requestBodyPayload,
|
|
624
|
+
eventTypes
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
|
|
496
628
|
return functionComment + generateAxiosFunction(
|
|
497
629
|
method,
|
|
498
630
|
formatPathWithParams(path),
|
|
@@ -667,6 +799,35 @@ export const ${parent} = ${JSON.stringify(object, undefined, 2)};\n`
|
|
|
667
799
|
return fileTemplate(tsString, allTypes, baseUrl)
|
|
668
800
|
}
|
|
669
801
|
|
|
802
|
+
/**
|
|
803
|
+
* Generates TypeScript type definitions for stream events
|
|
804
|
+
* @returns TypeScript type definitions string
|
|
805
|
+
*/
|
|
806
|
+
function generateStreamEventTypeDefinitions(): string {
|
|
807
|
+
if (Object.keys(streamEventTypes).length === 0) {
|
|
808
|
+
return ''
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
let typeDefs = '\n// ============================================================================\n'
|
|
812
|
+
typeDefs += '// Stream Event Type Definitions (Fully Typed!)\n'
|
|
813
|
+
typeDefs += '// ============================================================================\n\n'
|
|
814
|
+
|
|
815
|
+
for (const [typeName, events] of Object.entries(streamEventTypes)) {
|
|
816
|
+
typeDefs += `/**\n * Event types for ${typeName.replace('StreamEvents', '')} stream\n`
|
|
817
|
+
typeDefs += ` * Events: ${events.map(e => `"${e}"`).join(', ')}\n */\n`
|
|
818
|
+
typeDefs += `export interface ${typeName} {\n`
|
|
819
|
+
|
|
820
|
+
for (const event of events) {
|
|
821
|
+
typeDefs += ` /** ${event} event data */\n`
|
|
822
|
+
typeDefs += ` ${event}: any // TODO: Define specific type from OpenAPI schema\n`
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
typeDefs += '}\n\n'
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return typeDefs
|
|
829
|
+
}
|
|
830
|
+
|
|
670
831
|
/**
|
|
671
832
|
* Generates the TypeScript file template
|
|
672
833
|
* @param tsString - The generated TypeScript code
|
|
@@ -679,9 +840,12 @@ function fileTemplate(
|
|
|
679
840
|
typeForImport: string[],
|
|
680
841
|
baseURL: string
|
|
681
842
|
): string {
|
|
843
|
+
const streamTypeDefs = generateStreamEventTypeDefinitions()
|
|
844
|
+
|
|
682
845
|
const templateCode = `import ax from 'axios';
|
|
683
846
|
import type { AxiosResponse } from 'axios';
|
|
684
847
|
import type { ${typeForImport.join(', ')} } from './types.d';
|
|
848
|
+
import { createSSEStream, createSSEStreamPost, StreamController, type SSEStreamOptions, type SSEEvent } from './streamClient';
|
|
685
849
|
|
|
686
850
|
/**
|
|
687
851
|
* Options for file upload operations
|
|
@@ -695,6 +859,24 @@ export interface UploadOptions {
|
|
|
695
859
|
tags?: string[]
|
|
696
860
|
}
|
|
697
861
|
|
|
862
|
+
/**
|
|
863
|
+
* Export SSE stream utilities for direct use
|
|
864
|
+
*
|
|
865
|
+
* @example Chainable event handlers
|
|
866
|
+
* const stream = createSSEStream(url)
|
|
867
|
+
* .on('token', (data) => console.log(data))
|
|
868
|
+
* .on('done', () => console.log('Complete!'))
|
|
869
|
+
*
|
|
870
|
+
* @example Async iteration
|
|
871
|
+
* for await (const event of createSSEStream(url)) {
|
|
872
|
+
* console.log(event.type, event.data)
|
|
873
|
+
* }
|
|
874
|
+
*
|
|
875
|
+
* @example Promise-based
|
|
876
|
+
* const result = await createSSEStream(url).toPromise()
|
|
877
|
+
*/
|
|
878
|
+
export { createSSEStream, createSSEStreamPost, StreamController, type SSEStreamOptions, type SSEEvent } from './streamClient';
|
|
879
|
+
${streamTypeDefs}
|
|
698
880
|
/**
|
|
699
881
|
* Configured axios instance for API requests
|
|
700
882
|
* @example
|