@bagelink/sdk 1.7.101 → 1.8.3
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 +713 -0
- package/dist/index.d.cts +210 -2
- package/dist/index.d.mts +210 -2
- package/dist/index.d.ts +210 -2
- package/dist/index.mjs +708 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/openAPITools/StreamController.ts +372 -0
- package/src/openAPITools/functionGenerator.ts +239 -1
- package/src/openAPITools/index.ts +2 -0
- package/src/openAPITools/streamClient.ts +247 -0
- package/src/openAPITools/streamDetector.ts +207 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import ax from 'axios'
|
|
|
2
2
|
|
|
3
3
|
export * from './openAPITools'
|
|
4
4
|
export { default as openAPI } from './openAPITools'
|
|
5
|
+
export { createSSEStream, createSSEStreamPost, type SSEEvent, type SSEStreamOptions, StreamController, type StreamEventMap } from './openAPITools/streamClient'
|
|
5
6
|
|
|
6
7
|
export type Tables = ''
|
|
7
8
|
export type TableToTypeMapping = Record<Tables, any>
|
|
@@ -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,9 @@
|
|
|
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 */
|
|
6
|
+
import type { SSEEventTypeInfo } from './streamDetector'
|
|
1
7
|
import type {
|
|
2
8
|
OperationObject as OpenAPIOperation,
|
|
3
9
|
PathItemObject as OpenAPIPath,
|
|
@@ -8,8 +14,8 @@ import type {
|
|
|
8
14
|
ReferenceObject,
|
|
9
15
|
SchemaObject,
|
|
10
16
|
} from './types'
|
|
17
|
+
import { isSSEStream, extractSSEEventTypes, extractSSEEventInfo } from './streamDetector'
|
|
11
18
|
import { dereference, isReferenceObject } from './types/utils'
|
|
12
|
-
|
|
13
19
|
import {
|
|
14
20
|
cleanPath,
|
|
15
21
|
formatType,
|
|
@@ -56,6 +62,8 @@ 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
|
|
66
|
+
const streamEventInfo: Record<string, SSEEventTypeInfo[]> = {} // Track detailed event info for type generation
|
|
59
67
|
|
|
60
68
|
/**
|
|
61
69
|
* Collects non-primitive types for import statements
|
|
@@ -339,6 +347,121 @@ function combineAllParams(
|
|
|
339
347
|
return `{ ${destructuredNames} }: { ${typeDefinition} } = {}`
|
|
340
348
|
}
|
|
341
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Generates a unique type name for a stream endpoint
|
|
352
|
+
*/
|
|
353
|
+
function generateStreamTypeName(path: string): string {
|
|
354
|
+
return `${toPascalCase(
|
|
355
|
+
path
|
|
356
|
+
.split('/')
|
|
357
|
+
.filter(p => p && !/\{|\}/.test(p))
|
|
358
|
+
.join('_')
|
|
359
|
+
)}StreamEvents`
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Generates a Stream function for SSE endpoints with full type safety
|
|
364
|
+
* @param method - The HTTP method
|
|
365
|
+
* @param path - The original API path
|
|
366
|
+
* @param formattedPath - The formatted API path
|
|
367
|
+
* @param allParams - The combined parameter string
|
|
368
|
+
* @param requestBodyPayload - The request body payload
|
|
369
|
+
* @param eventTypes - Array of SSE event types
|
|
370
|
+
* @returns A string with the SSE stream function
|
|
371
|
+
*/
|
|
372
|
+
function generateStreamFunction(
|
|
373
|
+
method: string,
|
|
374
|
+
path: string,
|
|
375
|
+
formattedPath: string,
|
|
376
|
+
allParams: string,
|
|
377
|
+
requestBodyPayload: string,
|
|
378
|
+
eventTypes?: string[]
|
|
379
|
+
): string {
|
|
380
|
+
if (allParams === 'undefined') { allParams = '' }
|
|
381
|
+
|
|
382
|
+
// Extract parameter names for the stream call (unused but kept for future use)
|
|
383
|
+
// const paramNames = allParams ? allParams.match(/\w+(?=\s*[?:])/g) || [] : []
|
|
384
|
+
|
|
385
|
+
const bodyVar = requestBodyPayload || '{}'
|
|
386
|
+
const baseUrlRef = 'axios.defaults.baseURL || ""'
|
|
387
|
+
|
|
388
|
+
// Generate type name for this stream
|
|
389
|
+
const streamTypeName = generateStreamTypeName(path)
|
|
390
|
+
|
|
391
|
+
// Store event types for later type generation
|
|
392
|
+
if (eventTypes?.length) {
|
|
393
|
+
streamEventTypes[streamTypeName] = eventTypes
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const eventTypesComment = eventTypes?.length
|
|
397
|
+
? `\n * Event types: ${eventTypes.map(t => `"${t}"`).join(', ')}`
|
|
398
|
+
: ''
|
|
399
|
+
|
|
400
|
+
const eventTypesExample = eventTypes?.length
|
|
401
|
+
? eventTypes.map(t => `\n * .on('${t}', (data) => console.log(data))`).join('')
|
|
402
|
+
: '\n * .on(\'message\', (data) => console.log(data))'
|
|
403
|
+
|
|
404
|
+
const typeAnnotation = eventTypes?.length
|
|
405
|
+
? `StreamController<${streamTypeName}>`
|
|
406
|
+
: 'StreamController'
|
|
407
|
+
|
|
408
|
+
// Convert formattedPath from string representation to actual path for URL construction
|
|
409
|
+
// formattedPath can be either 'path' or `path/${param}` (as strings)
|
|
410
|
+
const pathForUrl = formattedPath.startsWith('`')
|
|
411
|
+
? formattedPath.slice(1, -1) // Remove backticks to get the content
|
|
412
|
+
: formattedPath.slice(1, -1) // Remove quotes to get the content
|
|
413
|
+
|
|
414
|
+
if (method === 'post') {
|
|
415
|
+
return `{
|
|
416
|
+
/**
|
|
417
|
+
* Stream SSE events from this endpoint (returns StreamController)${eventTypesComment}
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* const stream = api.endpoint.stream(params)${eventTypesExample}
|
|
421
|
+
* .on('error', (err) => console.error(err))
|
|
422
|
+
* .on('complete', () => console.log('Done!'))
|
|
423
|
+
*
|
|
424
|
+
* // Close stream when needed
|
|
425
|
+
* stream.close()
|
|
426
|
+
*/
|
|
427
|
+
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
428
|
+
const url = \`\${${baseUrlRef}}${pathForUrl}\`
|
|
429
|
+
return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
|
|
430
|
+
},
|
|
431
|
+
/**
|
|
432
|
+
* Regular POST request (non-streaming)
|
|
433
|
+
*/
|
|
434
|
+
post: async (${allParams}): Promise<AxiosResponse<any>> => {
|
|
435
|
+
return axios.post(${formattedPath}, ${bodyVar})
|
|
436
|
+
}
|
|
437
|
+
}`
|
|
438
|
+
} else {
|
|
439
|
+
return `{
|
|
440
|
+
/**
|
|
441
|
+
* Stream SSE events from this endpoint (returns StreamController)${eventTypesComment}
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* const stream = api.endpoint.stream(params)${eventTypesExample}
|
|
445
|
+
* .on('error', (err) => console.error(err))
|
|
446
|
+
* .on('complete', () => console.log('Done!'))
|
|
447
|
+
*
|
|
448
|
+
* // Close stream when needed
|
|
449
|
+
* stream.close()
|
|
450
|
+
*/
|
|
451
|
+
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
452
|
+
const url = \`\${${baseUrlRef}}${pathForUrl}\`
|
|
453
|
+
return createSSEStream<${streamTypeName}>(url, options)
|
|
454
|
+
},
|
|
455
|
+
/**
|
|
456
|
+
* Regular GET request (non-streaming)
|
|
457
|
+
*/
|
|
458
|
+
get: async (${allParams}): Promise<AxiosResponse<any>> => {
|
|
459
|
+
return axios.get(${formattedPath})
|
|
460
|
+
}
|
|
461
|
+
}`
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
342
465
|
/**
|
|
343
466
|
* Generates an Axios function call as a string
|
|
344
467
|
* @param method - The HTTP method
|
|
@@ -444,6 +567,9 @@ function generateFunctionForOperation(
|
|
|
444
567
|
): string {
|
|
445
568
|
if (!operation) { return '' }
|
|
446
569
|
|
|
570
|
+
// Check if this is an SSE stream endpoint
|
|
571
|
+
const isStream = isSSEStream(operation)
|
|
572
|
+
|
|
447
573
|
// Validate: GET and DELETE requests should not have request bodies
|
|
448
574
|
const methodLower = method.toLowerCase()
|
|
449
575
|
if (['get', 'delete'].includes(methodLower) && operation.requestBody) {
|
|
@@ -493,6 +619,27 @@ function generateFunctionForOperation(
|
|
|
493
619
|
// Create JSDoc comment with OpenAPI documentation
|
|
494
620
|
const functionComment = buildJSDocComment(operation, method, path)
|
|
495
621
|
|
|
622
|
+
// Generate stream function for SSE endpoints
|
|
623
|
+
if (isStream) {
|
|
624
|
+
const eventTypes = extractSSEEventTypes(operation)
|
|
625
|
+
const eventInfo = extractSSEEventInfo(operation)
|
|
626
|
+
|
|
627
|
+
// Store detailed event info for type generation
|
|
628
|
+
const streamTypeName = generateStreamTypeName(path)
|
|
629
|
+
if (eventInfo?.length) {
|
|
630
|
+
streamEventInfo[streamTypeName] = eventInfo
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return functionComment + generateStreamFunction(
|
|
634
|
+
method,
|
|
635
|
+
path,
|
|
636
|
+
formatPathWithParams(path),
|
|
637
|
+
allParams,
|
|
638
|
+
requestBodyPayload,
|
|
639
|
+
eventTypes
|
|
640
|
+
)
|
|
641
|
+
}
|
|
642
|
+
|
|
496
643
|
return functionComment + generateAxiosFunction(
|
|
497
644
|
method,
|
|
498
645
|
formatPathWithParams(path),
|
|
@@ -667,6 +814,76 @@ export const ${parent} = ${JSON.stringify(object, undefined, 2)};\n`
|
|
|
667
814
|
return fileTemplate(tsString, allTypes, baseUrl)
|
|
668
815
|
}
|
|
669
816
|
|
|
817
|
+
/**
|
|
818
|
+
* Generates TypeScript type definitions for stream events
|
|
819
|
+
* Uses detailed event info if available, falls back to simple event list
|
|
820
|
+
* @returns TypeScript type definitions string
|
|
821
|
+
*/
|
|
822
|
+
function generateStreamEventTypeDefinitions(): string {
|
|
823
|
+
if (Object.keys(streamEventTypes).length === 0) {
|
|
824
|
+
return ''
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
let typeDefs = '\n// ============================================================================\n'
|
|
828
|
+
typeDefs += '// Stream Event Type Definitions (Fully Typed!)\n'
|
|
829
|
+
typeDefs += '// ============================================================================\n\n'
|
|
830
|
+
|
|
831
|
+
for (const [typeName, events] of Object.entries(streamEventTypes)) {
|
|
832
|
+
const eventInfo = streamEventInfo[typeName]
|
|
833
|
+
|
|
834
|
+
typeDefs += `/**\n * Event types for ${typeName.replace('StreamEvents', '')} stream\n`
|
|
835
|
+
typeDefs += ` * Events: ${events.map(e => `"${e}"`).join(', ')}\n`
|
|
836
|
+
|
|
837
|
+
// Add event descriptions if available
|
|
838
|
+
if (eventInfo?.length) {
|
|
839
|
+
typeDefs += ` *\n`
|
|
840
|
+
for (const info of eventInfo) {
|
|
841
|
+
typeDefs += ` * - **${info.name}**: ${info.description || 'Event data'}\n`
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
typeDefs += ` */\n`
|
|
846
|
+
typeDefs += `export interface ${typeName} {\n`
|
|
847
|
+
|
|
848
|
+
// Generate event type definitions with field information if available
|
|
849
|
+
if (eventInfo?.length) {
|
|
850
|
+
for (const info of eventInfo) {
|
|
851
|
+
typeDefs += ` /**\n * ${info.description || info.name}\n`
|
|
852
|
+
|
|
853
|
+
if (info.fields?.length) {
|
|
854
|
+
info.fields.forEach((field) => {
|
|
855
|
+
typeDefs += ` * - \`${field.name}\`: ${field.description || 'Field data'}\n`
|
|
856
|
+
})
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
typeDefs += ` */\n`
|
|
860
|
+
|
|
861
|
+
// Generate interface for this event
|
|
862
|
+
if (info.fields?.length) {
|
|
863
|
+
typeDefs += ` ${info.name}: {\n`
|
|
864
|
+
for (const field of info.fields) {
|
|
865
|
+
typeDefs += ` /** ${field.description || field.name} */\n`
|
|
866
|
+
typeDefs += ` ${field.name}: any\n`
|
|
867
|
+
}
|
|
868
|
+
typeDefs += ` }\n`
|
|
869
|
+
} else {
|
|
870
|
+
typeDefs += ` ${info.name}: any\n`
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
} else {
|
|
874
|
+
// Fallback: simple event list without field info
|
|
875
|
+
for (const event of events) {
|
|
876
|
+
typeDefs += ` /** ${event} event data */\n`
|
|
877
|
+
typeDefs += ` ${event}: any\n`
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
typeDefs += '}\n\n'
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return typeDefs
|
|
885
|
+
}
|
|
886
|
+
|
|
670
887
|
/**
|
|
671
888
|
* Generates the TypeScript file template
|
|
672
889
|
* @param tsString - The generated TypeScript code
|
|
@@ -679,9 +896,12 @@ function fileTemplate(
|
|
|
679
896
|
typeForImport: string[],
|
|
680
897
|
baseURL: string
|
|
681
898
|
): string {
|
|
899
|
+
const streamTypeDefs = generateStreamEventTypeDefinitions()
|
|
900
|
+
|
|
682
901
|
const templateCode = `import ax from 'axios';
|
|
683
902
|
import type { AxiosResponse } from 'axios';
|
|
684
903
|
import type { ${typeForImport.join(', ')} } from './types.d';
|
|
904
|
+
import { createSSEStream, createSSEStreamPost, StreamController, type SSEStreamOptions, type SSEEvent } from './streamClient';
|
|
685
905
|
|
|
686
906
|
/**
|
|
687
907
|
* Options for file upload operations
|
|
@@ -695,6 +915,24 @@ export interface UploadOptions {
|
|
|
695
915
|
tags?: string[]
|
|
696
916
|
}
|
|
697
917
|
|
|
918
|
+
/**
|
|
919
|
+
* Export SSE stream utilities for direct use
|
|
920
|
+
*
|
|
921
|
+
* @example Chainable event handlers
|
|
922
|
+
* const stream = createSSEStream(url)
|
|
923
|
+
* .on('token', (data) => console.log(data))
|
|
924
|
+
* .on('done', () => console.log('Complete!'))
|
|
925
|
+
*
|
|
926
|
+
* @example Async iteration
|
|
927
|
+
* for await (const event of createSSEStream(url)) {
|
|
928
|
+
* console.log(event.type, event.data)
|
|
929
|
+
* }
|
|
930
|
+
*
|
|
931
|
+
* @example Promise-based
|
|
932
|
+
* const result = await createSSEStream(url).toPromise()
|
|
933
|
+
*/
|
|
934
|
+
export { createSSEStream, createSSEStreamPost, StreamController, type SSEStreamOptions, type SSEEvent } from './streamClient';
|
|
935
|
+
${streamTypeDefs}
|
|
698
936
|
/**
|
|
699
937
|
* Configured axios instance for API requests
|
|
700
938
|
* @example
|