@bagelink/sdk 1.15.558 → 1.15.562
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/dist/index.cjs +544 -344
- package/dist/index.d.cts +53 -4
- package/dist/index.d.mts +53 -4
- package/dist/index.d.ts +53 -4
- package/dist/index.mjs +540 -344
- package/package.json +1 -1
- package/src/index.ts +157 -1
- package/src/openAPITools/functionGenerator.test.ts +44 -0
- package/src/openAPITools/functionGenerator.ts +115 -114
- package/src/openAPITools/index.ts +1 -0
- package/src/openAPITools/moduleGenerator.test.ts +89 -0
- package/src/openAPITools/moduleGenerator.ts +174 -0
- package/src/openAPITools/streamClient.ts +25 -19
- package/src/transport.test.ts +71 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import type { AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosError } from 'axios'
|
|
1
|
+
import type { AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosError, AxiosInstance } from 'axios'
|
|
2
2
|
import type { Ref } from 'vue'
|
|
3
3
|
import ax from 'axios'
|
|
4
4
|
import { ref } from 'vue'
|
|
5
|
+
import type { StreamEventMap } from './openAPITools/StreamController'
|
|
6
|
+
import {
|
|
7
|
+
createSSEStream,
|
|
8
|
+
createSSEStreamPost,
|
|
9
|
+
type SSEStreamOptions,
|
|
10
|
+
type StreamController
|
|
11
|
+
} from './openAPITools/streamClient'
|
|
5
12
|
|
|
6
13
|
export * from './openAPITools'
|
|
7
14
|
export { default as openAPI } from './openAPITools'
|
|
@@ -324,6 +331,155 @@ export interface ApiConfig {
|
|
|
324
331
|
axiosConfig?: AxiosRequestConfig
|
|
325
332
|
}
|
|
326
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Instance-scoped HTTP and SSE transport used by generated clients.
|
|
336
|
+
*/
|
|
337
|
+
export interface ApiTransport {
|
|
338
|
+
axios: AxiosInstance
|
|
339
|
+
config: Ref<ApiConfig | null>
|
|
340
|
+
lastResponse: Ref<AxiosResponse | null>
|
|
341
|
+
getStreamBaseURL: () => string
|
|
342
|
+
createSSEStream: <TEventMap extends StreamEventMap = StreamEventMap>(
|
|
343
|
+
path: string,
|
|
344
|
+
options?: SSEStreamOptions
|
|
345
|
+
) => StreamController<TEventMap>
|
|
346
|
+
createSSEStreamPost: <TEventMap extends StreamEventMap = StreamEventMap, TBody = any>(
|
|
347
|
+
path: string,
|
|
348
|
+
body: TBody,
|
|
349
|
+
options?: SSEStreamOptions
|
|
350
|
+
) => StreamController<TEventMap>
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function joinURL(baseURL: string, path: string): string {
|
|
354
|
+
if (/^https?:\/\//i.test(path)) return path
|
|
355
|
+
if (!baseURL) return path
|
|
356
|
+
return `${baseURL.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function getTransportHeaders(config: ApiConfig, options: SSEStreamOptions): Promise<Record<string, string>> {
|
|
360
|
+
const optionHeaders = typeof options.headers === 'function'
|
|
361
|
+
? await options.headers()
|
|
362
|
+
: (options.headers ?? {})
|
|
363
|
+
const configuredHeaders = config.axiosConfig?.headers
|
|
364
|
+
const headers = typeof configuredHeaders?.toJSON === 'function'
|
|
365
|
+
? configuredHeaders.toJSON()
|
|
366
|
+
: (configuredHeaders ?? {})
|
|
367
|
+
const token = await config.auth?.getToken?.()
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
...(headers as Record<string, string>),
|
|
371
|
+
...(token ? { Authorization: `${config.auth?.tokenPrefix ?? 'Bearer'} ${token}` } : {}),
|
|
372
|
+
...optionHeaders,
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Create an isolated transport. Multiple generated clients can safely use
|
|
378
|
+
* different base URLs, credentials, auth providers, and interceptors.
|
|
379
|
+
*/
|
|
380
|
+
export function createApiTransport(config: ApiConfig = {}): ApiTransport {
|
|
381
|
+
const configRef = ref<ApiConfig | null>(null)
|
|
382
|
+
const lastResponse = ref<AxiosResponse | null>(null)
|
|
383
|
+
const axios = ax.create({
|
|
384
|
+
baseURL: config.baseURL,
|
|
385
|
+
withCredentials: config.withCredentials ?? config.axiosConfig?.withCredentials ?? true,
|
|
386
|
+
paramsSerializer: { indexes: null },
|
|
387
|
+
...config.axiosConfig,
|
|
388
|
+
})
|
|
389
|
+
const streamOptions = async (options: SSEStreamOptions = {}): Promise<SSEStreamOptions> => {
|
|
390
|
+
const currentConfig = configRef.value ?? {}
|
|
391
|
+
return {
|
|
392
|
+
...options,
|
|
393
|
+
withCredentials: options.withCredentials ?? currentConfig.withCredentials,
|
|
394
|
+
headers: await getTransportHeaders(currentConfig, options),
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const streamURL = (path: string) => joinURL(
|
|
398
|
+
configRef.value?.streamBaseURL ?? axios.defaults.baseURL ?? '',
|
|
399
|
+
path
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
const transport: ApiTransport = {
|
|
403
|
+
axios,
|
|
404
|
+
config: configRef,
|
|
405
|
+
lastResponse,
|
|
406
|
+
getStreamBaseURL: () => configRef.value?.streamBaseURL ?? axios.defaults.baseURL ?? '',
|
|
407
|
+
createSSEStream: <TEventMap extends StreamEventMap = StreamEventMap>(
|
|
408
|
+
path: string,
|
|
409
|
+
options: SSEStreamOptions = {}
|
|
410
|
+
) => createSSEStream<TEventMap>(streamURL(path), {
|
|
411
|
+
...options,
|
|
412
|
+
headers: async () => (await streamOptions(options)).headers as Record<string, string>,
|
|
413
|
+
withCredentials: options.withCredentials ?? configRef.value?.withCredentials,
|
|
414
|
+
}),
|
|
415
|
+
createSSEStreamPost: <TEventMap extends StreamEventMap = StreamEventMap, TBody = any>(
|
|
416
|
+
path: string,
|
|
417
|
+
body: TBody,
|
|
418
|
+
options: SSEStreamOptions = {}
|
|
419
|
+
) => createSSEStreamPost<TEventMap, TBody>(streamURL(path), body, {
|
|
420
|
+
...options,
|
|
421
|
+
headers: async () => (await streamOptions(options)).headers as Record<string, string>,
|
|
422
|
+
withCredentials: options.withCredentials ?? configRef.value?.withCredentials,
|
|
423
|
+
}),
|
|
424
|
+
}
|
|
425
|
+
return configureApiTransport(transport, config)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Reconfigure an existing transport without changing its Axios identity.
|
|
430
|
+
* This supports legacy generated `axios` exports while keeping bindings isolated.
|
|
431
|
+
*/
|
|
432
|
+
export function configureApiTransport(transport: ApiTransport, config: ApiConfig = {}): ApiTransport {
|
|
433
|
+
const previousConfig = transport.config.value ?? {}
|
|
434
|
+
const resolvedConfig: ApiConfig = {
|
|
435
|
+
...previousConfig,
|
|
436
|
+
...config,
|
|
437
|
+
axiosConfig: {
|
|
438
|
+
...previousConfig.axiosConfig,
|
|
439
|
+
...config.axiosConfig,
|
|
440
|
+
},
|
|
441
|
+
withCredentials: config.withCredentials
|
|
442
|
+
?? config.axiosConfig?.withCredentials
|
|
443
|
+
?? previousConfig.withCredentials
|
|
444
|
+
?? true,
|
|
445
|
+
}
|
|
446
|
+
transport.config.value = resolvedConfig
|
|
447
|
+
transport.axios.defaults.baseURL = resolvedConfig.baseURL
|
|
448
|
+
transport.axios.defaults.withCredentials = resolvedConfig.withCredentials
|
|
449
|
+
if (resolvedConfig.axiosConfig) {
|
|
450
|
+
Object.assign(transport.axios.defaults, resolvedConfig.axiosConfig)
|
|
451
|
+
}
|
|
452
|
+
transport.axios.interceptors.request.clear()
|
|
453
|
+
transport.axios.interceptors.response.clear()
|
|
454
|
+
|
|
455
|
+
if (resolvedConfig.auth) {
|
|
456
|
+
transport.axios.interceptors.request.use(async (requestConfig) => {
|
|
457
|
+
const token = await resolvedConfig.auth!.getToken?.()
|
|
458
|
+
if (token) {
|
|
459
|
+
requestConfig.headers.Authorization = `${resolvedConfig.auth!.tokenPrefix ?? 'Bearer'} ${token}`
|
|
460
|
+
}
|
|
461
|
+
return requestConfig
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
if (resolvedConfig.interceptors?.request) {
|
|
465
|
+
transport.axios.interceptors.request.use(resolvedConfig.interceptors.request)
|
|
466
|
+
}
|
|
467
|
+
transport.axios.interceptors.response.use(
|
|
468
|
+
(response) => {
|
|
469
|
+
transport.lastResponse.value = response
|
|
470
|
+
return resolvedConfig.interceptors?.response?.(response) ?? response
|
|
471
|
+
},
|
|
472
|
+
async (error) => {
|
|
473
|
+
if (error.response?.status === 401) {
|
|
474
|
+
await resolvedConfig.auth?.onUnauthorized?.()
|
|
475
|
+
}
|
|
476
|
+
resolvedConfig.onError?.(parseApiError(error), error)
|
|
477
|
+
throw error
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
return transport
|
|
481
|
+
}
|
|
482
|
+
|
|
327
483
|
/**
|
|
328
484
|
* Request state for useApiRequest composable
|
|
329
485
|
*/
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { generateFunctions } from './functionGenerator'
|
|
3
|
+
|
|
4
|
+
describe('generateFunctions transport bindings', () => {
|
|
5
|
+
it('preserves direct exports and emits transport-bound client factories', () => {
|
|
6
|
+
const code = generateFunctions({
|
|
7
|
+
'/items': {
|
|
8
|
+
get: {
|
|
9
|
+
responses: {
|
|
10
|
+
'200': {
|
|
11
|
+
description: 'OK',
|
|
12
|
+
content: {
|
|
13
|
+
'application/json': {
|
|
14
|
+
schema: { type: 'array', items: { type: 'string' } },
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
'/events': {
|
|
22
|
+
get: {
|
|
23
|
+
responses: {
|
|
24
|
+
'200': {
|
|
25
|
+
description: 'SSE',
|
|
26
|
+
content: {
|
|
27
|
+
'text/event-stream': {
|
|
28
|
+
schema: { type: 'string' },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}, '/api')
|
|
36
|
+
|
|
37
|
+
expect(code).toContain('export function createApiClient(transport: ApiTransport)')
|
|
38
|
+
expect(code).toContain('export const items = defaultBindings.items')
|
|
39
|
+
expect(code).toContain('export const axios = defaultTransport.axios')
|
|
40
|
+
expect(code).toContain('__transport.axios.get')
|
|
41
|
+
expect(code).toContain('__transport.createSSEStream')
|
|
42
|
+
expect(code).toContain('return defaultTransport.getStreamBaseURL()')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -419,8 +419,6 @@ function generateStreamFunction(
|
|
|
419
419
|
// const paramNames = allParams ? allParams.match(/\w+(?=\s*[?:])/g) || [] : []
|
|
420
420
|
|
|
421
421
|
const bodyVar = requestBodyPayload || '{}'
|
|
422
|
-
const baseUrlRef = 'getStreamBaseURL()'
|
|
423
|
-
|
|
424
422
|
// Generate type name for this stream
|
|
425
423
|
const streamTypeName = generateStreamTypeName(path)
|
|
426
424
|
|
|
@@ -441,12 +439,6 @@ function generateStreamFunction(
|
|
|
441
439
|
? `StreamController<${streamTypeName}>`
|
|
442
440
|
: 'StreamController'
|
|
443
441
|
|
|
444
|
-
// Convert formattedPath from string representation to actual path for URL construction
|
|
445
|
-
// formattedPath can be either 'path' or `path/${param}` (as strings)
|
|
446
|
-
const pathForUrl = formattedPath.startsWith('`')
|
|
447
|
-
? formattedPath.slice(1, -1) // Remove backticks to get the content
|
|
448
|
-
: formattedPath.slice(1, -1) // Remove quotes to get the content
|
|
449
|
-
|
|
450
442
|
const streamParams = allParams ? `${allParams}, options?: SSEStreamOptions` : `options?: SSEStreamOptions`
|
|
451
443
|
const regularParams = allParams
|
|
452
444
|
|
|
@@ -464,14 +456,13 @@ function generateStreamFunction(
|
|
|
464
456
|
* stream.close()
|
|
465
457
|
*/
|
|
466
458
|
stream: (${streamParams}): ${typeAnnotation} => {
|
|
467
|
-
|
|
468
|
-
return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
|
|
459
|
+
return __transport.createSSEStreamPost<${streamTypeName}>(${formattedPath}, ${bodyVar}, options)
|
|
469
460
|
},
|
|
470
461
|
/**
|
|
471
462
|
* Regular POST request (non-streaming)
|
|
472
463
|
*/
|
|
473
464
|
post: async (${regularParams}): Promise<AxiosResponse<any>> => {
|
|
474
|
-
return axios.post(${formattedPath}, ${bodyVar})
|
|
465
|
+
return __transport.axios.post(${formattedPath}, ${bodyVar})
|
|
475
466
|
}
|
|
476
467
|
}`
|
|
477
468
|
} else {
|
|
@@ -500,8 +491,8 @@ function generateStreamFunction(
|
|
|
500
491
|
|
|
501
492
|
// For GET streams with query params, build URL with suffix; otherwise plain URL
|
|
502
493
|
const urlLine = queryParams?.length
|
|
503
|
-
? `const url =
|
|
504
|
-
: `const url =
|
|
494
|
+
? `const url = ${formattedPath} + __qsSuffix`
|
|
495
|
+
: `const url = ${formattedPath}`
|
|
505
496
|
|
|
506
497
|
const streamBody = queryParams?.length
|
|
507
498
|
? `\n ${queryStringCode}\n ${urlLine}`
|
|
@@ -520,13 +511,13 @@ function generateStreamFunction(
|
|
|
520
511
|
* stream.close()
|
|
521
512
|
*/
|
|
522
513
|
stream: (${streamParams}): ${typeAnnotation} => {${streamBody}
|
|
523
|
-
return createSSEStream<${streamTypeName}>(url, options)
|
|
514
|
+
return __transport.createSSEStream<${streamTypeName}>(url, options)
|
|
524
515
|
},
|
|
525
516
|
/**
|
|
526
517
|
* Regular GET request (non-streaming)
|
|
527
518
|
*/
|
|
528
519
|
get: async (${regularParams}): Promise<AxiosResponse<any>> => {
|
|
529
|
-
return axios.get(${formattedPath}${regularParamStr})
|
|
520
|
+
return __transport.axios.get(${formattedPath}${regularParamStr})
|
|
530
521
|
}
|
|
531
522
|
}`
|
|
532
523
|
}
|
|
@@ -574,7 +565,7 @@ function generateAxiosFunction(
|
|
|
574
565
|
const formData = new FormData()
|
|
575
566
|
formData.append('file', file)
|
|
576
567
|
formData.append('upload', file)
|
|
577
|
-
const response = await axios.${method}(${formattedPath}, formData, {
|
|
568
|
+
const response = await __transport.axios.${method}(${formattedPath}, formData, {
|
|
578
569
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
579
570
|
onUploadProgress: options?.onUploadProgress${paramStr ? `, ${paramStr}` : ''}
|
|
580
571
|
})
|
|
@@ -582,7 +573,7 @@ function generateAxiosFunction(
|
|
|
582
573
|
} else {
|
|
583
574
|
// Generic FormData handling
|
|
584
575
|
axiosFunction += `
|
|
585
|
-
const response = await axios.${method}(${formattedPath}, formData, {
|
|
576
|
+
const response = await __transport.axios.${method}(${formattedPath}, formData, {
|
|
586
577
|
headers: { 'Content-Type': 'multipart/form-data' }${paramStr ? `, ${paramStr}` : ''}
|
|
587
578
|
})
|
|
588
579
|
return ${returnExpression}`
|
|
@@ -591,7 +582,7 @@ function generateAxiosFunction(
|
|
|
591
582
|
const configParams = paramStr ? `, { ${paramStr} }` : ''
|
|
592
583
|
const bodyVar = requestBodyPayload || '{}'
|
|
593
584
|
axiosFunction += `
|
|
594
|
-
const response = await axios.${method}(${formattedPath}${
|
|
585
|
+
const response = await __transport.axios.${method}(${formattedPath}${
|
|
595
586
|
['get', 'delete'].includes(method)
|
|
596
587
|
? configParams
|
|
597
588
|
: `, ${bodyVar}${configParams}`
|
|
@@ -847,9 +838,22 @@ function handlePathSegment(
|
|
|
847
838
|
* @param baseUrl - The base URL for the API
|
|
848
839
|
* @returns The generated TypeScript code
|
|
849
840
|
*/
|
|
850
|
-
|
|
841
|
+
function generateFunctionsInternal(
|
|
842
|
+
paths: OpenAPIPaths,
|
|
843
|
+
baseUrl: string,
|
|
844
|
+
moduleFactoryName?: string,
|
|
845
|
+
): string {
|
|
846
|
+
// Generation can run more than once in the same process (for example, one
|
|
847
|
+
// invocation per explicitly owned module). Keep each result independent.
|
|
848
|
+
allTypes.length = 0
|
|
849
|
+
pathOperations.length = 0
|
|
850
|
+
for (const inventory of [functionsInventory, streamEventTypes, streamEventInfo, streamEventSchemas]) {
|
|
851
|
+
for (const key of Object.keys(inventory)) delete inventory[key]
|
|
852
|
+
}
|
|
853
|
+
|
|
851
854
|
const body: Record<string, any> = {}
|
|
852
855
|
const allPathsClean = Object.keys(paths).map(cleanPath)
|
|
856
|
+
const httpMethods = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'])
|
|
853
857
|
|
|
854
858
|
// Process all paths to generate functions
|
|
855
859
|
for (const [path, operation] of Object.entries(paths)) {
|
|
@@ -859,7 +863,9 @@ export function generateFunctions(paths: OpenAPIPaths, baseUrl: string): string
|
|
|
859
863
|
const objFuncKey = toCamelCaseSafe(key)
|
|
860
864
|
if (!objFuncKey) { return acc }
|
|
861
865
|
|
|
862
|
-
const methods = Object.keys(operation)
|
|
866
|
+
const methods = Object.keys(operation)
|
|
867
|
+
.filter(method => httpMethods.has(method)) as Array<keyof OpenAPIPath>
|
|
868
|
+
if (methods.length === 0) return acc
|
|
863
869
|
const currentPathClean = cleanPath(path)
|
|
864
870
|
const currentPathPrefix = splitPath.slice(0, index + 1).join('/')
|
|
865
871
|
|
|
@@ -899,7 +905,7 @@ export function generateFunctions(paths: OpenAPIPaths, baseUrl: string): string
|
|
|
899
905
|
}
|
|
900
906
|
|
|
901
907
|
// Generate TypeScript code from processed paths
|
|
902
|
-
let tsString = ''
|
|
908
|
+
let tsString = 'function createEndpointNamespaces(__transport: ApiTransport) {\n'
|
|
903
909
|
const apiObjectEntries: string[] = []
|
|
904
910
|
|
|
905
911
|
// Convert object structure to TypeScript
|
|
@@ -908,7 +914,7 @@ export function generateFunctions(paths: OpenAPIPaths, baseUrl: string): string
|
|
|
908
914
|
/**
|
|
909
915
|
* API functions for ${parent} endpoints
|
|
910
916
|
*/
|
|
911
|
-
|
|
917
|
+
const ${parent} = ${JSON.stringify(object, undefined, 2)};\n`
|
|
912
918
|
apiObjectEntries.push(parent)
|
|
913
919
|
}
|
|
914
920
|
|
|
@@ -917,9 +923,11 @@ export const ${parent} = ${JSON.stringify(object, undefined, 2)};\n`
|
|
|
917
923
|
/**
|
|
918
924
|
* Aggregated API object containing all endpoints
|
|
919
925
|
*/
|
|
920
|
-
|
|
926
|
+
const api = {
|
|
921
927
|
${apiObjectEntries.map(name => ` ${name},`).join('\n')}
|
|
922
|
-
}
|
|
928
|
+
};
|
|
929
|
+
return { api, ${apiObjectEntries.join(', ')} }
|
|
930
|
+
}\n`
|
|
923
931
|
|
|
924
932
|
// Replace placeholders with actual function bodies
|
|
925
933
|
Object.entries(functionsInventory).forEach(([key, value]) => {
|
|
@@ -927,7 +935,22 @@ ${apiObjectEntries.map(name => ` ${name},`).join('\n')}
|
|
|
927
935
|
})
|
|
928
936
|
|
|
929
937
|
// Generate the final TypeScript file
|
|
930
|
-
return
|
|
938
|
+
return moduleFactoryName
|
|
939
|
+
? moduleFileTemplate(tsString, allTypes, moduleFactoryName)
|
|
940
|
+
: fileTemplate(tsString, allTypes, baseUrl, apiObjectEntries)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export function generateFunctions(paths: OpenAPIPaths, baseUrl: string): string {
|
|
944
|
+
return generateFunctionsInternal(paths, baseUrl)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/** Generate a transport-bound module client without legacy singleton state. */
|
|
948
|
+
export function generateModuleFunctions(
|
|
949
|
+
paths: OpenAPIPaths,
|
|
950
|
+
baseUrl: string,
|
|
951
|
+
moduleFactoryName: string,
|
|
952
|
+
): string {
|
|
953
|
+
return generateFunctionsInternal(paths, baseUrl, moduleFactoryName)
|
|
931
954
|
}
|
|
932
955
|
|
|
933
956
|
/**
|
|
@@ -1018,26 +1041,23 @@ function generateStreamEventTypeDefinitions(): string {
|
|
|
1018
1041
|
function fileTemplate(
|
|
1019
1042
|
tsString: string,
|
|
1020
1043
|
typeForImport: string[],
|
|
1021
|
-
baseURL: string
|
|
1044
|
+
baseURL: string,
|
|
1045
|
+
apiObjectEntries: string[]
|
|
1022
1046
|
): string {
|
|
1023
1047
|
const streamTypeDefs = generateStreamEventTypeDefinitions()
|
|
1024
1048
|
|
|
1025
1049
|
const hasStreamEndpoints = Object.keys(streamEventTypes).length > 0
|
|
1026
1050
|
|
|
1027
|
-
// Determine which stream functions are actually used by checking functionsInventory
|
|
1028
|
-
const allFunctionBodies = Object.values(functionsInventory).join('\n')
|
|
1029
|
-
const hasGetStreams = allFunctionBodies.includes('createSSEStream<')
|
|
1030
|
-
const hasPostStreams = allFunctionBodies.includes('createSSEStreamPost<')
|
|
1031
|
-
|
|
1032
1051
|
const streamImports = hasStreamEndpoints
|
|
1033
|
-
?
|
|
1052
|
+
? `type StreamController, type SSEStreamOptions,`
|
|
1034
1053
|
: ''
|
|
1035
1054
|
|
|
1036
|
-
const templateCode = `import
|
|
1037
|
-
import type { AxiosResponse } from 'axios';
|
|
1055
|
+
const templateCode = `import type { AxiosResponse } from 'axios';
|
|
1038
1056
|
import type { ${typeForImport.join(', ')} } from './types.d';
|
|
1039
1057
|
import {
|
|
1040
1058
|
ApiResponse,
|
|
1059
|
+
createApiTransport,
|
|
1060
|
+
configureApiTransport,
|
|
1041
1061
|
parseApiError,
|
|
1042
1062
|
formatFieldErrors,
|
|
1043
1063
|
useApiRequest,
|
|
@@ -1052,9 +1072,10 @@ import {
|
|
|
1052
1072
|
type ApiRequestState,
|
|
1053
1073
|
type RequestInterceptor,
|
|
1054
1074
|
type ResponseInterceptor,
|
|
1075
|
+
type ApiTransport,
|
|
1055
1076
|
type Unwrapped
|
|
1056
1077
|
} from '@bagelink/sdk';
|
|
1057
|
-
import {
|
|
1078
|
+
import type { Ref } from 'vue';
|
|
1058
1079
|
|
|
1059
1080
|
/**
|
|
1060
1081
|
* Options for file upload operations
|
|
@@ -1070,22 +1091,29 @@ export interface UploadOptions {
|
|
|
1070
1091
|
|
|
1071
1092
|
${streamTypeDefs}
|
|
1072
1093
|
|
|
1073
|
-
|
|
1074
|
-
* Configured axios instance for API requests
|
|
1075
|
-
*/
|
|
1076
|
-
export const axios = ax.create({baseURL: '${baseURL}', withCredentials: true, paramsSerializer: { indexes: null }});
|
|
1094
|
+
const defaultTransport = createApiTransport({ baseURL: '${baseURL}' })
|
|
1077
1095
|
|
|
1078
1096
|
${tsString}
|
|
1079
1097
|
|
|
1098
|
+
const defaultBindings = createEndpointNamespaces(defaultTransport)
|
|
1099
|
+
export const api = defaultBindings.api
|
|
1100
|
+
${apiObjectEntries.map(name => `export const ${name} = defaultBindings.${name}`).join('\n')}
|
|
1101
|
+
export const axios = defaultTransport.axios
|
|
1102
|
+
|
|
1103
|
+
/** Bind all generated endpoint namespaces to an isolated transport. */
|
|
1104
|
+
export function createApiClient(transport: ApiTransport) {
|
|
1105
|
+
return createEndpointNamespaces(transport)
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/** Resolve the default client's SSE base URL. */
|
|
1109
|
+
export function getStreamBaseURL(): string {
|
|
1110
|
+
return defaultTransport.getStreamBaseURL()
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1080
1113
|
// ============================================================================
|
|
1081
1114
|
// API Instance & Configuration
|
|
1082
1115
|
// ============================================================================
|
|
1083
1116
|
|
|
1084
|
-
/**
|
|
1085
|
-
* Last response metadata (accessible via useApi().$lastResponse)
|
|
1086
|
-
*/
|
|
1087
|
-
const lastResponse = ref<AxiosResponse | null>(null)
|
|
1088
|
-
|
|
1089
1117
|
/**
|
|
1090
1118
|
* API instance type with enhanced response objects
|
|
1091
1119
|
*/
|
|
@@ -1099,13 +1127,6 @@ type ApiInstance = typeof api & {
|
|
|
1099
1127
|
|
|
1100
1128
|
// Global state
|
|
1101
1129
|
let apiInstance: ApiInstance | null = null
|
|
1102
|
-
let apiConfig = ref<ApiConfig | null>(null)
|
|
1103
|
-
let _streamBaseURL: string | undefined
|
|
1104
|
-
|
|
1105
|
-
/** Resolve base URL for SSE stream endpoints. Uses streamBaseURL if set, otherwise falls back to axios baseURL. */
|
|
1106
|
-
export function getStreamBaseURL(): string {
|
|
1107
|
-
return _streamBaseURL || axios.defaults.baseURL || ""
|
|
1108
|
-
}
|
|
1109
1130
|
|
|
1110
1131
|
/**
|
|
1111
1132
|
* Create and configure the API instance
|
|
@@ -1113,77 +1134,17 @@ export function getStreamBaseURL(): string {
|
|
|
1113
1134
|
* @returns Configured API instance
|
|
1114
1135
|
*/
|
|
1115
1136
|
export function createApi(config: ApiConfig = {}): ApiInstance {
|
|
1116
|
-
|
|
1117
|
-
apiConfig.value = config
|
|
1118
|
-
|
|
1119
|
-
// Configure axios defaults
|
|
1120
|
-
if (config.baseURL) {
|
|
1121
|
-
axios.defaults.baseURL = config.baseURL
|
|
1122
|
-
}
|
|
1123
|
-
_streamBaseURL = config.streamBaseURL
|
|
1124
|
-
if (config.withCredentials !== undefined) {
|
|
1125
|
-
axios.defaults.withCredentials = config.withCredentials
|
|
1126
|
-
}
|
|
1127
|
-
if (config.axiosConfig) {
|
|
1128
|
-
Object.assign(axios.defaults, config.axiosConfig)
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// Clear existing interceptors
|
|
1132
|
-
axios.interceptors.request.clear()
|
|
1133
|
-
axios.interceptors.response.clear()
|
|
1134
|
-
|
|
1135
|
-
// Setup auth interceptor
|
|
1136
|
-
if (config.auth) {
|
|
1137
|
-
axios.interceptors.request.use(async (reqConfig) => {
|
|
1138
|
-
const token = await config.auth!.getToken?.()
|
|
1139
|
-
if (token) {
|
|
1140
|
-
const prefix = config.auth!.tokenPrefix ?? 'Bearer'
|
|
1141
|
-
reqConfig.headers.Authorization = \`\${prefix} \${token}\`
|
|
1142
|
-
}
|
|
1143
|
-
return reqConfig
|
|
1144
|
-
})
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
// Setup custom request interceptor
|
|
1148
|
-
if (config.interceptors?.request) {
|
|
1149
|
-
axios.interceptors.request.use(config.interceptors.request)
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
// Setup response interceptor for error handling
|
|
1153
|
-
axios.interceptors.response.use(
|
|
1154
|
-
(response) => {
|
|
1155
|
-
// Store last response for metadata access
|
|
1156
|
-
lastResponse.value = response
|
|
1157
|
-
|
|
1158
|
-
// Apply custom response interceptor if provided
|
|
1159
|
-
if (config.interceptors?.response) {
|
|
1160
|
-
return config.interceptors.response(response)
|
|
1161
|
-
}
|
|
1162
|
-
return response
|
|
1163
|
-
},
|
|
1164
|
-
async (error) => {
|
|
1165
|
-
// Handle 401 Unauthorized
|
|
1166
|
-
if (error.response?.status === 401) {
|
|
1167
|
-
await config.auth?.onUnauthorized?.()
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
// Parse and handle error
|
|
1171
|
-
const parsedError = parseApiError(error)
|
|
1172
|
-
config.onError?.(parsedError, error)
|
|
1173
|
-
|
|
1174
|
-
throw error
|
|
1175
|
-
}
|
|
1176
|
-
)
|
|
1137
|
+
configureApiTransport(defaultTransport, config)
|
|
1177
1138
|
|
|
1178
1139
|
// API already returns ApiResponse directly!
|
|
1179
1140
|
// Just add the special properties
|
|
1180
1141
|
apiInstance = {
|
|
1181
1142
|
...api,
|
|
1182
1143
|
$raw: api,
|
|
1183
|
-
$lastResponse: lastResponse,
|
|
1144
|
+
$lastResponse: defaultTransport.lastResponse,
|
|
1184
1145
|
$axios: axios,
|
|
1185
1146
|
$ApiResponse: ApiResponse,
|
|
1186
|
-
$config:
|
|
1147
|
+
$config: defaultTransport.config
|
|
1187
1148
|
} as ApiInstance
|
|
1188
1149
|
|
|
1189
1150
|
return apiInstance
|
|
@@ -1232,3 +1193,43 @@ export {
|
|
|
1232
1193
|
// Remove potential double quotes around property names
|
|
1233
1194
|
return templateCode.replace(/"([^"]+)":/g, '$1:')
|
|
1234
1195
|
}
|
|
1196
|
+
|
|
1197
|
+
function moduleFileTemplate(
|
|
1198
|
+
tsString: string,
|
|
1199
|
+
typeForImport: string[],
|
|
1200
|
+
moduleFactoryName: string,
|
|
1201
|
+
): string {
|
|
1202
|
+
const streamTypeDefs = generateStreamEventTypeDefinitions()
|
|
1203
|
+
const hasStreamEndpoints = Object.keys(streamEventTypes).length > 0
|
|
1204
|
+
const streamImports = hasStreamEndpoints
|
|
1205
|
+
? 'type StreamController, type SSEStreamOptions,'
|
|
1206
|
+
: ''
|
|
1207
|
+
const typeImport = typeForImport.length
|
|
1208
|
+
? `import type { ${typeForImport.join(', ')} } from './types.d';`
|
|
1209
|
+
: ''
|
|
1210
|
+
|
|
1211
|
+
const templateCode = `${typeImport}
|
|
1212
|
+
import {
|
|
1213
|
+
\tApiResponse,
|
|
1214
|
+
\t${streamImports}
|
|
1215
|
+
\ttype ApiTransport
|
|
1216
|
+
} from '@bagelink/sdk';
|
|
1217
|
+
|
|
1218
|
+
export interface UploadOptions {
|
|
1219
|
+
\tonUploadProgress?: (progressEvent: any) => void
|
|
1220
|
+
\tdirPath?: string
|
|
1221
|
+
\ttags?: string[]
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
${streamTypeDefs}
|
|
1225
|
+
|
|
1226
|
+
${tsString}
|
|
1227
|
+
|
|
1228
|
+
/** Bind this module's generated endpoint namespaces to the app transport. */
|
|
1229
|
+
export function ${moduleFactoryName}(transport: ApiTransport) {
|
|
1230
|
+
\treturn createEndpointNamespaces(transport)
|
|
1231
|
+
}
|
|
1232
|
+
`
|
|
1233
|
+
|
|
1234
|
+
return templateCode.replace(/"([^"]+)":/g, '$1:')
|
|
1235
|
+
}
|