@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/dist/index.mjs
CHANGED
|
@@ -1,6 +1,262 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ax from 'axios';
|
|
2
2
|
import { ref } from 'vue';
|
|
3
3
|
|
|
4
|
+
class StreamController {
|
|
5
|
+
constructor(cleanup) {
|
|
6
|
+
this.cleanup = cleanup;
|
|
7
|
+
}
|
|
8
|
+
handlers = /* @__PURE__ */ new Map();
|
|
9
|
+
_closed = false;
|
|
10
|
+
/**
|
|
11
|
+
* Register an event handler (fully typed!)
|
|
12
|
+
* @param event - Event type to listen for
|
|
13
|
+
* @param handler - Handler function (data type inferred from event)
|
|
14
|
+
* @returns this (for chaining)
|
|
15
|
+
*/
|
|
16
|
+
on(event, handler) {
|
|
17
|
+
if (!this.handlers.has(event)) {
|
|
18
|
+
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
19
|
+
}
|
|
20
|
+
this.handlers.get(event).add(handler);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Emit an event to all registered handlers
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
emit(event, data) {
|
|
28
|
+
const handlers = this.handlers.get(event);
|
|
29
|
+
if (handlers) {
|
|
30
|
+
handlers.forEach((handler) => {
|
|
31
|
+
try {
|
|
32
|
+
handler(data);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(`Error in ${event} handler:`, error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Close the stream
|
|
41
|
+
*/
|
|
42
|
+
close() {
|
|
43
|
+
if (!this._closed) {
|
|
44
|
+
this.cleanup();
|
|
45
|
+
this._closed = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function resolveHeaders(headers) {
|
|
51
|
+
return typeof headers === "function" ? await headers() : headers ?? {};
|
|
52
|
+
}
|
|
53
|
+
function createSSEStream(url, options = {}) {
|
|
54
|
+
const {
|
|
55
|
+
headers,
|
|
56
|
+
withCredentials = true,
|
|
57
|
+
lastEventId: initialLastEventId,
|
|
58
|
+
maxReconnects = 5,
|
|
59
|
+
reconnectDelayMs = 1e3
|
|
60
|
+
} = options;
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const stream = new StreamController(() => {
|
|
63
|
+
controller.abort();
|
|
64
|
+
});
|
|
65
|
+
let lastEventId = initialLastEventId;
|
|
66
|
+
let reconnectAttempts = 0;
|
|
67
|
+
async function connect() {
|
|
68
|
+
if (controller.signal.aborted) return;
|
|
69
|
+
const resolvedHeaders = await resolveHeaders(headers);
|
|
70
|
+
const requestHeaders = {
|
|
71
|
+
"Accept": "text/event-stream",
|
|
72
|
+
"Cache-Control": "no-cache",
|
|
73
|
+
...resolvedHeaders
|
|
74
|
+
};
|
|
75
|
+
if (lastEventId !== void 0) {
|
|
76
|
+
requestHeaders["Last-Event-ID"] = lastEventId;
|
|
77
|
+
}
|
|
78
|
+
let response;
|
|
79
|
+
try {
|
|
80
|
+
response = await fetch(url, {
|
|
81
|
+
method: "GET",
|
|
82
|
+
headers: requestHeaders,
|
|
83
|
+
credentials: withCredentials ? "include" : "same-origin",
|
|
84
|
+
signal: controller.signal,
|
|
85
|
+
keepalive: false
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error?.name === "AbortError") return;
|
|
89
|
+
return handleDisconnect(error);
|
|
90
|
+
}
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
stream.emit("error", new Error(`HTTP ${response.status}: ${response.statusText}`));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!response.body) {
|
|
96
|
+
stream.emit("error", new Error("Response body is null"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const reader = response.body.getReader();
|
|
100
|
+
const decoder = new TextDecoder();
|
|
101
|
+
let buffer = "";
|
|
102
|
+
let terminalEventReceived = false;
|
|
103
|
+
let currentEvent = "message";
|
|
104
|
+
let currentData = "";
|
|
105
|
+
let currentId;
|
|
106
|
+
try {
|
|
107
|
+
while (true) {
|
|
108
|
+
const { done, value } = await reader.read();
|
|
109
|
+
if (done) {
|
|
110
|
+
if (!controller.signal.aborted && !terminalEventReceived) {
|
|
111
|
+
return handleDisconnect(new Error("Stream ended before a terminal event"));
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
buffer += decoder.decode(value, { stream: true });
|
|
116
|
+
const lines = buffer.split("\n");
|
|
117
|
+
buffer = lines.pop() || "";
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
if (line.startsWith("id:")) {
|
|
120
|
+
currentId = line.slice(3).trim();
|
|
121
|
+
} else if (line.startsWith("event:")) {
|
|
122
|
+
currentEvent = line.slice(6).trim();
|
|
123
|
+
} else if (line.startsWith("data:")) {
|
|
124
|
+
currentData = currentData ? `${currentData}
|
|
125
|
+
${line.slice(5).trim()}` : line.slice(5).trim();
|
|
126
|
+
} else if (line === "" && currentData) {
|
|
127
|
+
if (currentId !== void 0) {
|
|
128
|
+
if (currentId !== lastEventId) reconnectAttempts = 0;
|
|
129
|
+
lastEventId = currentId;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(currentData);
|
|
133
|
+
stream.emit(currentEvent, parsed);
|
|
134
|
+
if (currentEvent === "done" || currentEvent === "error") {
|
|
135
|
+
terminalEventReceived = true;
|
|
136
|
+
stream.emit("complete", {});
|
|
137
|
+
controller.abort();
|
|
138
|
+
try {
|
|
139
|
+
await reader.cancel();
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
stream.emit(currentEvent, currentData);
|
|
146
|
+
}
|
|
147
|
+
currentEvent = "message";
|
|
148
|
+
currentData = "";
|
|
149
|
+
currentId = void 0;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error?.name !== "AbortError") {
|
|
155
|
+
return handleDisconnect(error);
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
try {
|
|
159
|
+
reader.releaseLock();
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function handleDisconnect(error) {
|
|
165
|
+
if (controller.signal.aborted) return;
|
|
166
|
+
if (reconnectAttempts >= maxReconnects) {
|
|
167
|
+
const msg = error?.message || "Stream connection lost";
|
|
168
|
+
stream.emit("error", new Error(`${msg} (gave up after ${maxReconnects} reconnects)`));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
reconnectAttempts++;
|
|
172
|
+
const delay = reconnectDelayMs * Math.pow(2, reconnectAttempts - 1);
|
|
173
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
174
|
+
if (!controller.signal.aborted) await connect();
|
|
175
|
+
}
|
|
176
|
+
connect().catch(() => {
|
|
177
|
+
});
|
|
178
|
+
return stream;
|
|
179
|
+
}
|
|
180
|
+
function createSSEStreamPost(url, body, options = {}) {
|
|
181
|
+
const { headers, withCredentials = true } = options;
|
|
182
|
+
const controller = new AbortController();
|
|
183
|
+
const stream = new StreamController(() => {
|
|
184
|
+
controller.abort();
|
|
185
|
+
});
|
|
186
|
+
resolveHeaders(headers).then((resolvedHeaders) => fetch(url, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: {
|
|
189
|
+
"Accept": "text/event-stream",
|
|
190
|
+
"Content-Type": "application/json",
|
|
191
|
+
"Cache-Control": "no-cache",
|
|
192
|
+
...resolvedHeaders
|
|
193
|
+
},
|
|
194
|
+
credentials: withCredentials ? "include" : "same-origin",
|
|
195
|
+
body: JSON.stringify(body),
|
|
196
|
+
signal: controller.signal,
|
|
197
|
+
// Disable keepalive for streaming
|
|
198
|
+
keepalive: false
|
|
199
|
+
})).then(async (response) => {
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
202
|
+
}
|
|
203
|
+
if (!response.body) {
|
|
204
|
+
throw new Error("Response body is null");
|
|
205
|
+
}
|
|
206
|
+
const reader = response.body.getReader();
|
|
207
|
+
const decoder = new TextDecoder();
|
|
208
|
+
let buffer = "";
|
|
209
|
+
let currentEvent = "message";
|
|
210
|
+
let currentData = "";
|
|
211
|
+
try {
|
|
212
|
+
while (true) {
|
|
213
|
+
const { done, value } = await reader.read();
|
|
214
|
+
if (done) {
|
|
215
|
+
stream.emit("done", {});
|
|
216
|
+
stream.emit("complete", {});
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
buffer += decoder.decode(value, { stream: true });
|
|
220
|
+
const lines = buffer.split("\n");
|
|
221
|
+
buffer = lines.pop() || "";
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
if (line.startsWith("event:")) {
|
|
224
|
+
currentEvent = line.slice(6).trim();
|
|
225
|
+
} else if (line.startsWith("data:")) {
|
|
226
|
+
currentData = currentData ? `${currentData}
|
|
227
|
+
${line.slice(5).trim()}` : line.slice(5).trim();
|
|
228
|
+
} else if (line === "" && currentData) {
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(currentData);
|
|
231
|
+
stream.emit(currentEvent, parsed);
|
|
232
|
+
} catch {
|
|
233
|
+
stream.emit(currentEvent, currentData);
|
|
234
|
+
}
|
|
235
|
+
currentEvent = "message";
|
|
236
|
+
currentData = "";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error?.name !== "AbortError") {
|
|
242
|
+
const errorMessage = error?.message || "Stream connection error";
|
|
243
|
+
stream.emit("error", new Error(errorMessage));
|
|
244
|
+
}
|
|
245
|
+
} finally {
|
|
246
|
+
try {
|
|
247
|
+
reader.releaseLock();
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}).catch((error) => {
|
|
252
|
+
if (error?.name !== "AbortError") {
|
|
253
|
+
const errorMessage = error?.message || "Failed to establish stream connection";
|
|
254
|
+
stream.emit("error", new Error(errorMessage));
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return stream;
|
|
258
|
+
}
|
|
259
|
+
|
|
4
260
|
function isSSEStream(operation) {
|
|
5
261
|
const responses = operation.responses || {};
|
|
6
262
|
for (const [statusCode, response] of Object.entries(responses)) {
|
|
@@ -605,7 +861,6 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
605
861
|
allParams = "";
|
|
606
862
|
}
|
|
607
863
|
const bodyVar = requestBodyPayload || "{}";
|
|
608
|
-
const baseUrlRef = "getStreamBaseURL()";
|
|
609
864
|
const streamTypeName = generateStreamTypeName(path);
|
|
610
865
|
if (eventTypes?.length) {
|
|
611
866
|
streamEventTypes[streamTypeName] = eventTypes;
|
|
@@ -615,7 +870,6 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
615
870
|
const eventTypesExample = eventTypes?.length ? eventTypes.map((t) => `
|
|
616
871
|
* .on('${t}', (data) => console.log(data))`).join("") : "\n * .on('message', (data) => console.log(data))";
|
|
617
872
|
const typeAnnotation = eventTypes?.length ? `StreamController<${streamTypeName}>` : "StreamController";
|
|
618
|
-
const pathForUrl = formattedPath.startsWith("`") ? formattedPath.slice(1, -1) : formattedPath.slice(1, -1);
|
|
619
873
|
const streamParams = allParams ? `${allParams}, options?: SSEStreamOptions` : `options?: SSEStreamOptions`;
|
|
620
874
|
const regularParams = allParams;
|
|
621
875
|
if (method === "post") {
|
|
@@ -632,14 +886,13 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
632
886
|
* stream.close()
|
|
633
887
|
*/
|
|
634
888
|
stream: (${streamParams}): ${typeAnnotation} => {
|
|
635
|
-
|
|
636
|
-
return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
|
|
889
|
+
return __transport.createSSEStreamPost<${streamTypeName}>(${formattedPath}, ${bodyVar}, options)
|
|
637
890
|
},
|
|
638
891
|
/**
|
|
639
892
|
* Regular POST request (non-streaming)
|
|
640
893
|
*/
|
|
641
894
|
post: async (${regularParams}): Promise<AxiosResponse<any>> => {
|
|
642
|
-
return axios.post(${formattedPath}, ${bodyVar})
|
|
895
|
+
return __transport.axios.post(${formattedPath}, ${bodyVar})
|
|
643
896
|
}
|
|
644
897
|
}`;
|
|
645
898
|
} else {
|
|
@@ -660,7 +913,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
660
913
|
].join("\n ");
|
|
661
914
|
}
|
|
662
915
|
const regularParamStr = queryParams?.length ? `, { params: {${queryParams.join(", ")}} }` : "";
|
|
663
|
-
const urlLine = queryParams?.length ? `const url =
|
|
916
|
+
const urlLine = queryParams?.length ? `const url = ${formattedPath} + __qsSuffix` : `const url = ${formattedPath}`;
|
|
664
917
|
const streamBody = queryParams?.length ? `
|
|
665
918
|
${queryStringCode}
|
|
666
919
|
${urlLine}` : `
|
|
@@ -678,13 +931,13 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
678
931
|
* stream.close()
|
|
679
932
|
*/
|
|
680
933
|
stream: (${streamParams}): ${typeAnnotation} => {${streamBody}
|
|
681
|
-
return createSSEStream<${streamTypeName}>(url, options)
|
|
934
|
+
return __transport.createSSEStream<${streamTypeName}>(url, options)
|
|
682
935
|
},
|
|
683
936
|
/**
|
|
684
937
|
* Regular GET request (non-streaming)
|
|
685
938
|
*/
|
|
686
939
|
get: async (${regularParams}): Promise<AxiosResponse<any>> => {
|
|
687
|
-
return axios.get(${formattedPath}${regularParamStr})
|
|
940
|
+
return __transport.axios.get(${formattedPath}${regularParamStr})
|
|
688
941
|
}
|
|
689
942
|
}`;
|
|
690
943
|
}
|
|
@@ -706,14 +959,14 @@ function generateAxiosFunction(method, formattedPath, allParams, responseTypeStr
|
|
|
706
959
|
const formData = new FormData()
|
|
707
960
|
formData.append('file', file)
|
|
708
961
|
formData.append('upload', file)
|
|
709
|
-
const response = await axios.${method}(${formattedPath}, formData, {
|
|
962
|
+
const response = await __transport.axios.${method}(${formattedPath}, formData, {
|
|
710
963
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
711
964
|
onUploadProgress: options?.onUploadProgress${paramStr ? `, ${paramStr}` : ""}
|
|
712
965
|
})
|
|
713
966
|
return ${returnExpression}`;
|
|
714
967
|
} else {
|
|
715
968
|
axiosFunction += `
|
|
716
|
-
const response = await axios.${method}(${formattedPath}, formData, {
|
|
969
|
+
const response = await __transport.axios.${method}(${formattedPath}, formData, {
|
|
717
970
|
headers: { 'Content-Type': 'multipart/form-data' }${paramStr ? `, ${paramStr}` : ""}
|
|
718
971
|
})
|
|
719
972
|
return ${returnExpression}`;
|
|
@@ -722,7 +975,7 @@ function generateAxiosFunction(method, formattedPath, allParams, responseTypeStr
|
|
|
722
975
|
const configParams = paramStr ? `, { ${paramStr} }` : "";
|
|
723
976
|
const bodyVar = requestBodyPayload || "{}";
|
|
724
977
|
axiosFunction += `
|
|
725
|
-
const response = await axios.${method}(${formattedPath}${["get", "delete"].includes(method) ? configParams : `, ${bodyVar}${configParams}`})
|
|
978
|
+
const response = await __transport.axios.${method}(${formattedPath}${["get", "delete"].includes(method) ? configParams : `, ${bodyVar}${configParams}`})
|
|
726
979
|
return ${returnExpression}`;
|
|
727
980
|
}
|
|
728
981
|
axiosFunction += "}";
|
|
@@ -860,9 +1113,15 @@ function handlePathSegment(path, operation, existingObj = {}) {
|
|
|
860
1113
|
}
|
|
861
1114
|
return { ...obj, ...existingObj };
|
|
862
1115
|
}
|
|
863
|
-
function
|
|
1116
|
+
function generateFunctionsInternal(paths, baseUrl, moduleFactoryName) {
|
|
1117
|
+
allTypes.length = 0;
|
|
1118
|
+
pathOperations.length = 0;
|
|
1119
|
+
for (const inventory of [functionsInventory, streamEventTypes, streamEventInfo, streamEventSchemas]) {
|
|
1120
|
+
for (const key of Object.keys(inventory)) delete inventory[key];
|
|
1121
|
+
}
|
|
864
1122
|
const body = {};
|
|
865
1123
|
const allPathsClean = Object.keys(paths).map(cleanPath);
|
|
1124
|
+
const httpMethods = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
866
1125
|
for (const [path, operation] of Object.entries(paths)) {
|
|
867
1126
|
const splitPath = path.split("/").filter((p) => p && !/\{|\}/.test(p));
|
|
868
1127
|
splitPath.reduce((acc, key, index, array) => {
|
|
@@ -870,7 +1129,8 @@ function generateFunctions(paths, baseUrl) {
|
|
|
870
1129
|
if (!objFuncKey) {
|
|
871
1130
|
return acc;
|
|
872
1131
|
}
|
|
873
|
-
const methods = Object.keys(operation);
|
|
1132
|
+
const methods = Object.keys(operation).filter((method) => httpMethods.has(method));
|
|
1133
|
+
if (methods.length === 0) return acc;
|
|
874
1134
|
const currentPathClean = cleanPath(path);
|
|
875
1135
|
const currentPathPrefix = splitPath.slice(0, index + 1).join("/");
|
|
876
1136
|
const prefixWithSlash = `${currentPathPrefix}/`;
|
|
@@ -891,14 +1151,14 @@ function generateFunctions(paths, baseUrl) {
|
|
|
891
1151
|
return acc[objFuncKey];
|
|
892
1152
|
}, body);
|
|
893
1153
|
}
|
|
894
|
-
let tsString = "";
|
|
1154
|
+
let tsString = "function createEndpointNamespaces(__transport: ApiTransport) {\n";
|
|
895
1155
|
const apiObjectEntries = [];
|
|
896
1156
|
for (const [parent, object] of Object.entries(body)) {
|
|
897
1157
|
tsString += `
|
|
898
1158
|
/**
|
|
899
1159
|
* API functions for ${parent} endpoints
|
|
900
1160
|
*/
|
|
901
|
-
|
|
1161
|
+
const ${parent} = ${JSON.stringify(object, void 0, 2)};
|
|
902
1162
|
`;
|
|
903
1163
|
apiObjectEntries.push(parent);
|
|
904
1164
|
}
|
|
@@ -906,14 +1166,22 @@ export const ${parent} = ${JSON.stringify(object, void 0, 2)};
|
|
|
906
1166
|
/**
|
|
907
1167
|
* Aggregated API object containing all endpoints
|
|
908
1168
|
*/
|
|
909
|
-
|
|
1169
|
+
const api = {
|
|
910
1170
|
${apiObjectEntries.map((name) => ` ${name},`).join("\n")}
|
|
911
1171
|
};
|
|
1172
|
+
return { api, ${apiObjectEntries.join(", ")} }
|
|
1173
|
+
}
|
|
912
1174
|
`;
|
|
913
1175
|
Object.entries(functionsInventory).forEach(([key, value]) => {
|
|
914
1176
|
tsString = tsString.replace(`"${key}"`, value);
|
|
915
1177
|
});
|
|
916
|
-
return fileTemplate(tsString, allTypes, baseUrl);
|
|
1178
|
+
return moduleFactoryName ? moduleFileTemplate(tsString, allTypes, moduleFactoryName) : fileTemplate(tsString, allTypes, baseUrl, apiObjectEntries);
|
|
1179
|
+
}
|
|
1180
|
+
function generateFunctions(paths, baseUrl) {
|
|
1181
|
+
return generateFunctionsInternal(paths, baseUrl);
|
|
1182
|
+
}
|
|
1183
|
+
function generateModuleFunctions(paths, baseUrl, moduleFactoryName) {
|
|
1184
|
+
return generateFunctionsInternal(paths, baseUrl, moduleFactoryName);
|
|
917
1185
|
}
|
|
918
1186
|
function generateStreamEventTypeDefinitions() {
|
|
919
1187
|
if (Object.keys(streamEventTypes).length === 0) {
|
|
@@ -998,18 +1266,16 @@ function generateStreamEventTypeDefinitions() {
|
|
|
998
1266
|
}
|
|
999
1267
|
return typeDefs;
|
|
1000
1268
|
}
|
|
1001
|
-
function fileTemplate(tsString, typeForImport, baseURL) {
|
|
1269
|
+
function fileTemplate(tsString, typeForImport, baseURL, apiObjectEntries) {
|
|
1002
1270
|
const streamTypeDefs = generateStreamEventTypeDefinitions();
|
|
1003
1271
|
const hasStreamEndpoints = Object.keys(streamEventTypes).length > 0;
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
const hasPostStreams = allFunctionBodies.includes("createSSEStreamPost<");
|
|
1007
|
-
const streamImports = hasStreamEndpoints ? `${hasPostStreams ? "createSSEStreamPost, " : ""}${hasGetStreams ? "createSSEStream, " : ""}type StreamController, type SSEStreamOptions,` : "";
|
|
1008
|
-
const templateCode = `import ax from 'axios';
|
|
1009
|
-
import type { AxiosResponse } from 'axios';
|
|
1272
|
+
const streamImports = hasStreamEndpoints ? `type StreamController, type SSEStreamOptions,` : "";
|
|
1273
|
+
const templateCode = `import type { AxiosResponse } from 'axios';
|
|
1010
1274
|
import type { ${typeForImport.join(", ")} } from './types.d';
|
|
1011
1275
|
import {
|
|
1012
1276
|
ApiResponse,
|
|
1277
|
+
createApiTransport,
|
|
1278
|
+
configureApiTransport,
|
|
1013
1279
|
parseApiError,
|
|
1014
1280
|
formatFieldErrors,
|
|
1015
1281
|
useApiRequest,
|
|
@@ -1024,9 +1290,10 @@ import {
|
|
|
1024
1290
|
type ApiRequestState,
|
|
1025
1291
|
type RequestInterceptor,
|
|
1026
1292
|
type ResponseInterceptor,
|
|
1293
|
+
type ApiTransport,
|
|
1027
1294
|
type Unwrapped
|
|
1028
1295
|
} from '@bagelink/sdk';
|
|
1029
|
-
import {
|
|
1296
|
+
import type { Ref } from 'vue';
|
|
1030
1297
|
|
|
1031
1298
|
/**
|
|
1032
1299
|
* Options for file upload operations
|
|
@@ -1042,22 +1309,29 @@ export interface UploadOptions {
|
|
|
1042
1309
|
|
|
1043
1310
|
${streamTypeDefs}
|
|
1044
1311
|
|
|
1045
|
-
|
|
1046
|
-
* Configured axios instance for API requests
|
|
1047
|
-
*/
|
|
1048
|
-
export const axios = ax.create({baseURL: '${baseURL}', withCredentials: true, paramsSerializer: { indexes: null }});
|
|
1312
|
+
const defaultTransport = createApiTransport({ baseURL: '${baseURL}' })
|
|
1049
1313
|
|
|
1050
1314
|
${tsString}
|
|
1051
1315
|
|
|
1052
|
-
|
|
1316
|
+
const defaultBindings = createEndpointNamespaces(defaultTransport)
|
|
1317
|
+
export const api = defaultBindings.api
|
|
1318
|
+
${apiObjectEntries.map((name) => `export const ${name} = defaultBindings.${name}`).join("\n")}
|
|
1319
|
+
export const axios = defaultTransport.axios
|
|
1320
|
+
|
|
1321
|
+
/** Bind all generated endpoint namespaces to an isolated transport. */
|
|
1322
|
+
export function createApiClient(transport: ApiTransport) {
|
|
1323
|
+
return createEndpointNamespaces(transport)
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/** Resolve the default client's SSE base URL. */
|
|
1327
|
+
export function getStreamBaseURL(): string {
|
|
1328
|
+
return defaultTransport.getStreamBaseURL()
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// ============================================================================
|
|
1053
1332
|
// API Instance & Configuration
|
|
1054
1333
|
// ============================================================================
|
|
1055
1334
|
|
|
1056
|
-
/**
|
|
1057
|
-
* Last response metadata (accessible via useApi().$lastResponse)
|
|
1058
|
-
*/
|
|
1059
|
-
const lastResponse = ref<AxiosResponse | null>(null)
|
|
1060
|
-
|
|
1061
1335
|
/**
|
|
1062
1336
|
* API instance type with enhanced response objects
|
|
1063
1337
|
*/
|
|
@@ -1071,13 +1345,6 @@ type ApiInstance = typeof api & {
|
|
|
1071
1345
|
|
|
1072
1346
|
// Global state
|
|
1073
1347
|
let apiInstance: ApiInstance | null = null
|
|
1074
|
-
let apiConfig = ref<ApiConfig | null>(null)
|
|
1075
|
-
let _streamBaseURL: string | undefined
|
|
1076
|
-
|
|
1077
|
-
/** Resolve base URL for SSE stream endpoints. Uses streamBaseURL if set, otherwise falls back to axios baseURL. */
|
|
1078
|
-
export function getStreamBaseURL(): string {
|
|
1079
|
-
return _streamBaseURL || axios.defaults.baseURL || ""
|
|
1080
|
-
}
|
|
1081
1348
|
|
|
1082
1349
|
/**
|
|
1083
1350
|
* Create and configure the API instance
|
|
@@ -1085,77 +1352,17 @@ export function getStreamBaseURL(): string {
|
|
|
1085
1352
|
* @returns Configured API instance
|
|
1086
1353
|
*/
|
|
1087
1354
|
export function createApi(config: ApiConfig = {}): ApiInstance {
|
|
1088
|
-
|
|
1089
|
-
apiConfig.value = config
|
|
1090
|
-
|
|
1091
|
-
// Configure axios defaults
|
|
1092
|
-
if (config.baseURL) {
|
|
1093
|
-
axios.defaults.baseURL = config.baseURL
|
|
1094
|
-
}
|
|
1095
|
-
_streamBaseURL = config.streamBaseURL
|
|
1096
|
-
if (config.withCredentials !== undefined) {
|
|
1097
|
-
axios.defaults.withCredentials = config.withCredentials
|
|
1098
|
-
}
|
|
1099
|
-
if (config.axiosConfig) {
|
|
1100
|
-
Object.assign(axios.defaults, config.axiosConfig)
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
// Clear existing interceptors
|
|
1104
|
-
axios.interceptors.request.clear()
|
|
1105
|
-
axios.interceptors.response.clear()
|
|
1106
|
-
|
|
1107
|
-
// Setup auth interceptor
|
|
1108
|
-
if (config.auth) {
|
|
1109
|
-
axios.interceptors.request.use(async (reqConfig) => {
|
|
1110
|
-
const token = await config.auth!.getToken?.()
|
|
1111
|
-
if (token) {
|
|
1112
|
-
const prefix = config.auth!.tokenPrefix ?? 'Bearer'
|
|
1113
|
-
reqConfig.headers.Authorization = \`\${prefix} \${token}\`
|
|
1114
|
-
}
|
|
1115
|
-
return reqConfig
|
|
1116
|
-
})
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
// Setup custom request interceptor
|
|
1120
|
-
if (config.interceptors?.request) {
|
|
1121
|
-
axios.interceptors.request.use(config.interceptors.request)
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
// Setup response interceptor for error handling
|
|
1125
|
-
axios.interceptors.response.use(
|
|
1126
|
-
(response) => {
|
|
1127
|
-
// Store last response for metadata access
|
|
1128
|
-
lastResponse.value = response
|
|
1129
|
-
|
|
1130
|
-
// Apply custom response interceptor if provided
|
|
1131
|
-
if (config.interceptors?.response) {
|
|
1132
|
-
return config.interceptors.response(response)
|
|
1133
|
-
}
|
|
1134
|
-
return response
|
|
1135
|
-
},
|
|
1136
|
-
async (error) => {
|
|
1137
|
-
// Handle 401 Unauthorized
|
|
1138
|
-
if (error.response?.status === 401) {
|
|
1139
|
-
await config.auth?.onUnauthorized?.()
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// Parse and handle error
|
|
1143
|
-
const parsedError = parseApiError(error)
|
|
1144
|
-
config.onError?.(parsedError, error)
|
|
1145
|
-
|
|
1146
|
-
throw error
|
|
1147
|
-
}
|
|
1148
|
-
)
|
|
1355
|
+
configureApiTransport(defaultTransport, config)
|
|
1149
1356
|
|
|
1150
1357
|
// API already returns ApiResponse directly!
|
|
1151
1358
|
// Just add the special properties
|
|
1152
1359
|
apiInstance = {
|
|
1153
1360
|
...api,
|
|
1154
1361
|
$raw: api,
|
|
1155
|
-
$lastResponse: lastResponse,
|
|
1362
|
+
$lastResponse: defaultTransport.lastResponse,
|
|
1156
1363
|
$axios: axios,
|
|
1157
1364
|
$ApiResponse: ApiResponse,
|
|
1158
|
-
$config:
|
|
1365
|
+
$config: defaultTransport.config
|
|
1159
1366
|
} as ApiInstance
|
|
1160
1367
|
|
|
1161
1368
|
return apiInstance
|
|
@@ -1202,6 +1409,35 @@ export {
|
|
|
1202
1409
|
`;
|
|
1203
1410
|
return templateCode.replace(/"([^"]+)":/g, "$1:");
|
|
1204
1411
|
}
|
|
1412
|
+
function moduleFileTemplate(tsString, typeForImport, moduleFactoryName) {
|
|
1413
|
+
const streamTypeDefs = generateStreamEventTypeDefinitions();
|
|
1414
|
+
const hasStreamEndpoints = Object.keys(streamEventTypes).length > 0;
|
|
1415
|
+
const streamImports = hasStreamEndpoints ? "type StreamController, type SSEStreamOptions," : "";
|
|
1416
|
+
const typeImport = typeForImport.length ? `import type { ${typeForImport.join(", ")} } from './types.d';` : "";
|
|
1417
|
+
const templateCode = `${typeImport}
|
|
1418
|
+
import {
|
|
1419
|
+
ApiResponse,
|
|
1420
|
+
${streamImports}
|
|
1421
|
+
type ApiTransport
|
|
1422
|
+
} from '@bagelink/sdk';
|
|
1423
|
+
|
|
1424
|
+
export interface UploadOptions {
|
|
1425
|
+
onUploadProgress?: (progressEvent: any) => void
|
|
1426
|
+
dirPath?: string
|
|
1427
|
+
tags?: string[]
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
${streamTypeDefs}
|
|
1431
|
+
|
|
1432
|
+
${tsString}
|
|
1433
|
+
|
|
1434
|
+
/** Bind this module's generated endpoint namespaces to the app transport. */
|
|
1435
|
+
export function ${moduleFactoryName}(transport: ApiTransport) {
|
|
1436
|
+
return createEndpointNamespaces(transport)
|
|
1437
|
+
}
|
|
1438
|
+
`;
|
|
1439
|
+
return templateCode.replace(/"([^"]+)":/g, "$1:");
|
|
1440
|
+
}
|
|
1205
1441
|
|
|
1206
1442
|
function generateTypes(schemas) {
|
|
1207
1443
|
if (!schemas) {
|
|
@@ -1245,263 +1481,122 @@ function generateTypes(schemas) {
|
|
|
1245
1481
|
}).join("\n");
|
|
1246
1482
|
}
|
|
1247
1483
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1484
|
+
const METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
1485
|
+
function isReference(value) {
|
|
1486
|
+
return Boolean(value && typeof value === "object" && "$ref" in value);
|
|
1487
|
+
}
|
|
1488
|
+
function operationOwners(path, operation, manifest) {
|
|
1489
|
+
return manifest.modules.filter(
|
|
1490
|
+
(module) => module.pathPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)) || operation.tags?.some((tag) => module.tags?.includes(tag))
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
function collectSchemaRefs(value, refs) {
|
|
1494
|
+
if (!value || typeof value !== "object") return;
|
|
1495
|
+
if (isReference(value) && value.$ref.startsWith("#/components/schemas/")) {
|
|
1496
|
+
refs.add(value.$ref.slice("#/components/schemas/".length));
|
|
1497
|
+
}
|
|
1498
|
+
for (const child of Object.values(value)) collectSchemaRefs(child, refs);
|
|
1499
|
+
}
|
|
1500
|
+
function schemaClosure(openApi, seed) {
|
|
1501
|
+
const schemas = openApi.components?.schemas ?? {};
|
|
1502
|
+
const result = new Set(seed);
|
|
1503
|
+
const pending = [...result];
|
|
1504
|
+
while (pending.length) {
|
|
1505
|
+
const name = pending.pop();
|
|
1506
|
+
const schema = schemas[name];
|
|
1507
|
+
if (!schema) continue;
|
|
1508
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1509
|
+
collectSchemaRefs(schema, refs);
|
|
1510
|
+
for (const ref of refs) {
|
|
1511
|
+
if (!result.has(ref)) {
|
|
1512
|
+
result.add(ref);
|
|
1513
|
+
pending.push(ref);
|
|
1514
|
+
}
|
|
1263
1515
|
}
|
|
1264
|
-
this.handlers.get(event).add(handler);
|
|
1265
|
-
return this;
|
|
1266
1516
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
handler(data);
|
|
1277
|
-
} catch (error) {
|
|
1278
|
-
console.error(`Error in ${event} handler:`, error);
|
|
1279
|
-
}
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1517
|
+
return [...result].sort();
|
|
1518
|
+
}
|
|
1519
|
+
function moduleFactoryName(name) {
|
|
1520
|
+
return `create${name.split("-").filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join("")}Api`;
|
|
1521
|
+
}
|
|
1522
|
+
function validateOwnershipManifest(openApi, manifest) {
|
|
1523
|
+
const issues = [];
|
|
1524
|
+
if (manifest.version !== 1) {
|
|
1525
|
+
issues.push({ kind: "invalid-manifest", message: `Unsupported manifest version: ${manifest.version}` });
|
|
1282
1526
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
close() {
|
|
1287
|
-
if (!this._closed) {
|
|
1288
|
-
this.cleanup();
|
|
1289
|
-
this._closed = true;
|
|
1290
|
-
}
|
|
1527
|
+
const names = manifest.modules.map((module) => module.name);
|
|
1528
|
+
for (const name of new Set(names.filter((name2, index) => names.indexOf(name2) !== index))) {
|
|
1529
|
+
issues.push({ kind: "invalid-manifest", message: `Duplicate module name: ${name}` });
|
|
1291
1530
|
}
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
headers = {},
|
|
1297
|
-
withCredentials = true,
|
|
1298
|
-
lastEventId: initialLastEventId,
|
|
1299
|
-
maxReconnects = 5,
|
|
1300
|
-
reconnectDelayMs = 1e3
|
|
1301
|
-
} = options;
|
|
1302
|
-
const controller = new AbortController();
|
|
1303
|
-
const stream = new StreamController(() => {
|
|
1304
|
-
controller.abort();
|
|
1305
|
-
});
|
|
1306
|
-
let lastEventId = initialLastEventId;
|
|
1307
|
-
let reconnectAttempts = 0;
|
|
1308
|
-
async function connect() {
|
|
1309
|
-
if (controller.signal.aborted) return;
|
|
1310
|
-
const requestHeaders = {
|
|
1311
|
-
"Accept": "text/event-stream",
|
|
1312
|
-
"Cache-Control": "no-cache",
|
|
1313
|
-
...headers
|
|
1314
|
-
};
|
|
1315
|
-
if (lastEventId !== void 0) {
|
|
1316
|
-
requestHeaders["Last-Event-ID"] = lastEventId;
|
|
1531
|
+
const schemas = openApi.components?.schemas ?? {};
|
|
1532
|
+
for (const module of manifest.modules) {
|
|
1533
|
+
if (!module.name || !/^[a-z][a-z0-9-]*$/.test(module.name)) {
|
|
1534
|
+
issues.push({ kind: "invalid-manifest", message: `Invalid module name: ${module.name || "(empty)"}` });
|
|
1317
1535
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
response = await fetch(url, {
|
|
1321
|
-
method: "GET",
|
|
1322
|
-
headers: requestHeaders,
|
|
1323
|
-
credentials: withCredentials ? "include" : "same-origin",
|
|
1324
|
-
signal: controller.signal,
|
|
1325
|
-
keepalive: false
|
|
1326
|
-
});
|
|
1327
|
-
} catch (error) {
|
|
1328
|
-
if (error?.name === "AbortError") return;
|
|
1329
|
-
return handleDisconnect(error);
|
|
1330
|
-
}
|
|
1331
|
-
if (!response.ok) {
|
|
1332
|
-
stream.emit("error", new Error(`HTTP ${response.status}: ${response.statusText}`));
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
if (!response.body) {
|
|
1336
|
-
stream.emit("error", new Error("Response body is null"));
|
|
1337
|
-
return;
|
|
1536
|
+
if (!(module.tags?.length || module.pathPrefixes?.length)) {
|
|
1537
|
+
issues.push({ kind: "invalid-manifest", message: `Module ${module.name} must own tags or pathPrefixes` });
|
|
1338
1538
|
}
|
|
1339
|
-
const
|
|
1340
|
-
|
|
1341
|
-
let buffer = "";
|
|
1342
|
-
let terminalEventReceived = false;
|
|
1343
|
-
let currentEvent = "message";
|
|
1344
|
-
let currentData = "";
|
|
1345
|
-
let currentId;
|
|
1346
|
-
try {
|
|
1347
|
-
while (true) {
|
|
1348
|
-
const { done, value } = await reader.read();
|
|
1349
|
-
if (done) {
|
|
1350
|
-
if (!controller.signal.aborted && !terminalEventReceived) {
|
|
1351
|
-
return handleDisconnect(new Error("Stream ended before a terminal event"));
|
|
1352
|
-
}
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1355
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1356
|
-
const lines = buffer.split("\n");
|
|
1357
|
-
buffer = lines.pop() || "";
|
|
1358
|
-
for (const line of lines) {
|
|
1359
|
-
if (line.startsWith("id:")) {
|
|
1360
|
-
currentId = line.slice(3).trim();
|
|
1361
|
-
} else if (line.startsWith("event:")) {
|
|
1362
|
-
currentEvent = line.slice(6).trim();
|
|
1363
|
-
} else if (line.startsWith("data:")) {
|
|
1364
|
-
currentData = currentData ? `${currentData}
|
|
1365
|
-
${line.slice(5).trim()}` : line.slice(5).trim();
|
|
1366
|
-
} else if (line === "" && currentData) {
|
|
1367
|
-
if (currentId !== void 0) {
|
|
1368
|
-
if (currentId !== lastEventId) reconnectAttempts = 0;
|
|
1369
|
-
lastEventId = currentId;
|
|
1370
|
-
}
|
|
1371
|
-
try {
|
|
1372
|
-
const parsed = JSON.parse(currentData);
|
|
1373
|
-
stream.emit(currentEvent, parsed);
|
|
1374
|
-
if (currentEvent === "done" || currentEvent === "error") {
|
|
1375
|
-
terminalEventReceived = true;
|
|
1376
|
-
stream.emit("complete", {});
|
|
1377
|
-
controller.abort();
|
|
1378
|
-
try {
|
|
1379
|
-
await reader.cancel();
|
|
1380
|
-
} catch {
|
|
1381
|
-
}
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
} catch {
|
|
1385
|
-
stream.emit(currentEvent, currentData);
|
|
1386
|
-
}
|
|
1387
|
-
currentEvent = "message";
|
|
1388
|
-
currentData = "";
|
|
1389
|
-
currentId = void 0;
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
} catch (error) {
|
|
1394
|
-
if (error?.name !== "AbortError") {
|
|
1395
|
-
return handleDisconnect(error);
|
|
1396
|
-
}
|
|
1397
|
-
} finally {
|
|
1398
|
-
try {
|
|
1399
|
-
reader.releaseLock();
|
|
1400
|
-
} catch {
|
|
1401
|
-
}
|
|
1539
|
+
for (const schema of module.schemas ?? []) {
|
|
1540
|
+
if (!schemas[schema]) issues.push({ kind: "unknown-schema", message: `Module ${module.name} owns unknown schema ${schema}` });
|
|
1402
1541
|
}
|
|
1403
1542
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1543
|
+
for (const [path, item] of Object.entries(openApi.paths ?? {})) {
|
|
1544
|
+
for (const method of METHODS) {
|
|
1545
|
+
const operation = item[method];
|
|
1546
|
+
if (!operation) continue;
|
|
1547
|
+
const owners = operationOwners(path, operation, manifest);
|
|
1548
|
+
if (owners.length === 0) {
|
|
1549
|
+
issues.push({ kind: "unowned-operation", message: `${method.toUpperCase()} ${path} is not owned` });
|
|
1550
|
+
} else if (owners.length > 1) {
|
|
1551
|
+
issues.push({
|
|
1552
|
+
kind: "multiply-owned-operation",
|
|
1553
|
+
message: `${method.toUpperCase()} ${path} is owned by ${owners.map((owner) => owner.name).join(", ")}`
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1410
1556
|
}
|
|
1411
|
-
reconnectAttempts++;
|
|
1412
|
-
const delay = reconnectDelayMs * Math.pow(2, reconnectAttempts - 1);
|
|
1413
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1414
|
-
if (!controller.signal.aborted) await connect();
|
|
1415
1557
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
...headers
|
|
1433
|
-
},
|
|
1434
|
-
credentials: withCredentials ? "include" : "same-origin",
|
|
1435
|
-
body: JSON.stringify(body),
|
|
1436
|
-
signal: controller.signal,
|
|
1437
|
-
// Disable keepalive for streaming
|
|
1438
|
-
keepalive: false
|
|
1439
|
-
}).then(async (response) => {
|
|
1440
|
-
if (!response.ok) {
|
|
1441
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1442
|
-
}
|
|
1443
|
-
if (!response.body) {
|
|
1444
|
-
throw new Error("Response body is null");
|
|
1445
|
-
}
|
|
1446
|
-
const reader = response.body.getReader();
|
|
1447
|
-
const decoder = new TextDecoder();
|
|
1448
|
-
let buffer = "";
|
|
1449
|
-
let currentEvent = "message";
|
|
1450
|
-
let currentData = "";
|
|
1451
|
-
try {
|
|
1452
|
-
while (true) {
|
|
1453
|
-
const { done, value } = await reader.read();
|
|
1454
|
-
if (done) {
|
|
1455
|
-
stream.emit("done", {});
|
|
1456
|
-
stream.emit("complete", {});
|
|
1457
|
-
break;
|
|
1458
|
-
}
|
|
1459
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1460
|
-
const lines = buffer.split("\n");
|
|
1461
|
-
buffer = lines.pop() || "";
|
|
1462
|
-
for (const line of lines) {
|
|
1463
|
-
if (line.startsWith("event:")) {
|
|
1464
|
-
currentEvent = line.slice(6).trim();
|
|
1465
|
-
} else if (line.startsWith("data:")) {
|
|
1466
|
-
currentData = currentData ? `${currentData}
|
|
1467
|
-
${line.slice(5).trim()}` : line.slice(5).trim();
|
|
1468
|
-
} else if (line === "" && currentData) {
|
|
1469
|
-
try {
|
|
1470
|
-
const parsed = JSON.parse(currentData);
|
|
1471
|
-
stream.emit(currentEvent, parsed);
|
|
1472
|
-
} catch {
|
|
1473
|
-
stream.emit(currentEvent, currentData);
|
|
1474
|
-
}
|
|
1475
|
-
currentEvent = "message";
|
|
1476
|
-
currentData = "";
|
|
1477
|
-
}
|
|
1558
|
+
return issues;
|
|
1559
|
+
}
|
|
1560
|
+
function generateModules(openApi, manifest, baseUrl) {
|
|
1561
|
+
const issues = validateOwnershipManifest(openApi, manifest);
|
|
1562
|
+
if (issues.length) throw new Error(issues.map((issue) => issue.message).join("\n"));
|
|
1563
|
+
const schemas = openApi.components?.schemas ?? {};
|
|
1564
|
+
return [...manifest.modules].sort((a, b) => a.name.localeCompare(b.name)).map((module) => {
|
|
1565
|
+
const paths = {};
|
|
1566
|
+
const refs = new Set(module.schemas ?? []);
|
|
1567
|
+
for (const [path, item] of Object.entries(openApi.paths ?? {})) {
|
|
1568
|
+
const ownedItem = {};
|
|
1569
|
+
for (const method of METHODS) {
|
|
1570
|
+
const operation = item[method];
|
|
1571
|
+
if (operation && operationOwners(path, operation, manifest)[0]?.name === module.name) {
|
|
1572
|
+
ownedItem[method] = operation;
|
|
1573
|
+
collectSchemaRefs(operation, refs);
|
|
1478
1574
|
}
|
|
1479
1575
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
stream.emit("error", new Error(errorMessage));
|
|
1484
|
-
}
|
|
1485
|
-
} finally {
|
|
1486
|
-
try {
|
|
1487
|
-
reader.releaseLock();
|
|
1488
|
-
} catch {
|
|
1576
|
+
if (Object.keys(ownedItem).length) {
|
|
1577
|
+
if (item.parameters) ownedItem.parameters = item.parameters;
|
|
1578
|
+
paths[path] = ownedItem;
|
|
1489
1579
|
}
|
|
1490
1580
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1581
|
+
const ownedSchemas = schemaClosure(openApi, refs);
|
|
1582
|
+
const subset = Object.fromEntries(
|
|
1583
|
+
ownedSchemas.map((name) => [name, schemas[name]]).filter((entry) => Boolean(entry[1]))
|
|
1584
|
+
);
|
|
1585
|
+
return {
|
|
1586
|
+
name: module.name,
|
|
1587
|
+
paths: Object.keys(paths).sort(),
|
|
1588
|
+
schemas: ownedSchemas.map(formatType),
|
|
1589
|
+
code: generateModuleFunctions(paths, baseUrl, moduleFactoryName(module.name)),
|
|
1590
|
+
types: generateTypes(subset)
|
|
1591
|
+
};
|
|
1496
1592
|
});
|
|
1497
|
-
return stream;
|
|
1498
1593
|
}
|
|
1499
1594
|
|
|
1500
1595
|
const basicAuthHeader = { Authorization: "Basic YmFnZWxfdXNlcm5hbWU6Tm90U2VjdXJlQGJhZ2Vs" };
|
|
1501
1596
|
const index = async (openApiUrl, baseUrl) => {
|
|
1502
1597
|
try {
|
|
1503
1598
|
console.log(`Fetching OpenAPI spec from: ${openApiUrl}`);
|
|
1504
|
-
const { data: openApi } = await
|
|
1599
|
+
const { data: openApi } = await ax.get(openApiUrl, { headers: basicAuthHeader });
|
|
1505
1600
|
const schemas = openApi.components?.schemas;
|
|
1506
1601
|
if (!schemas) {
|
|
1507
1602
|
throw new Error("No schemas found in OpenAPI document");
|
|
@@ -1736,6 +1831,107 @@ function parseApiError(error) {
|
|
|
1736
1831
|
function formatFieldErrors(fields) {
|
|
1737
1832
|
return Object.entries(fields).map(([field, errors]) => `${field}: ${errors.join(", ")}`).join("\n");
|
|
1738
1833
|
}
|
|
1834
|
+
function joinURL(baseURL, path) {
|
|
1835
|
+
if (/^https?:\/\//i.test(path)) return path;
|
|
1836
|
+
if (!baseURL) return path;
|
|
1837
|
+
return `${baseURL.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
|
|
1838
|
+
}
|
|
1839
|
+
async function getTransportHeaders(config, options) {
|
|
1840
|
+
const optionHeaders = typeof options.headers === "function" ? await options.headers() : options.headers ?? {};
|
|
1841
|
+
const configuredHeaders = config.axiosConfig?.headers;
|
|
1842
|
+
const headers = typeof configuredHeaders?.toJSON === "function" ? configuredHeaders.toJSON() : configuredHeaders ?? {};
|
|
1843
|
+
const token = await config.auth?.getToken?.();
|
|
1844
|
+
return {
|
|
1845
|
+
...headers,
|
|
1846
|
+
...token ? { Authorization: `${config.auth?.tokenPrefix ?? "Bearer"} ${token}` } : {},
|
|
1847
|
+
...optionHeaders
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
function createApiTransport(config = {}) {
|
|
1851
|
+
const configRef = ref(null);
|
|
1852
|
+
const lastResponse = ref(null);
|
|
1853
|
+
const axios2 = ax.create({
|
|
1854
|
+
baseURL: config.baseURL,
|
|
1855
|
+
withCredentials: config.withCredentials ?? config.axiosConfig?.withCredentials ?? true,
|
|
1856
|
+
paramsSerializer: { indexes: null },
|
|
1857
|
+
...config.axiosConfig
|
|
1858
|
+
});
|
|
1859
|
+
const streamOptions = async (options = {}) => {
|
|
1860
|
+
const currentConfig = configRef.value ?? {};
|
|
1861
|
+
return {
|
|
1862
|
+
...options,
|
|
1863
|
+
withCredentials: options.withCredentials ?? currentConfig.withCredentials,
|
|
1864
|
+
headers: await getTransportHeaders(currentConfig, options)
|
|
1865
|
+
};
|
|
1866
|
+
};
|
|
1867
|
+
const streamURL = (path) => joinURL(
|
|
1868
|
+
configRef.value?.streamBaseURL ?? axios2.defaults.baseURL ?? "",
|
|
1869
|
+
path
|
|
1870
|
+
);
|
|
1871
|
+
const transport = {
|
|
1872
|
+
axios: axios2,
|
|
1873
|
+
config: configRef,
|
|
1874
|
+
lastResponse,
|
|
1875
|
+
getStreamBaseURL: () => configRef.value?.streamBaseURL ?? axios2.defaults.baseURL ?? "",
|
|
1876
|
+
createSSEStream: (path, options = {}) => createSSEStream(streamURL(path), {
|
|
1877
|
+
...options,
|
|
1878
|
+
headers: async () => (await streamOptions(options)).headers,
|
|
1879
|
+
withCredentials: options.withCredentials ?? configRef.value?.withCredentials
|
|
1880
|
+
}),
|
|
1881
|
+
createSSEStreamPost: (path, body, options = {}) => createSSEStreamPost(streamURL(path), body, {
|
|
1882
|
+
...options,
|
|
1883
|
+
headers: async () => (await streamOptions(options)).headers,
|
|
1884
|
+
withCredentials: options.withCredentials ?? configRef.value?.withCredentials
|
|
1885
|
+
})
|
|
1886
|
+
};
|
|
1887
|
+
return configureApiTransport(transport, config);
|
|
1888
|
+
}
|
|
1889
|
+
function configureApiTransport(transport, config = {}) {
|
|
1890
|
+
const previousConfig = transport.config.value ?? {};
|
|
1891
|
+
const resolvedConfig = {
|
|
1892
|
+
...previousConfig,
|
|
1893
|
+
...config,
|
|
1894
|
+
axiosConfig: {
|
|
1895
|
+
...previousConfig.axiosConfig,
|
|
1896
|
+
...config.axiosConfig
|
|
1897
|
+
},
|
|
1898
|
+
withCredentials: config.withCredentials ?? config.axiosConfig?.withCredentials ?? previousConfig.withCredentials ?? true
|
|
1899
|
+
};
|
|
1900
|
+
transport.config.value = resolvedConfig;
|
|
1901
|
+
transport.axios.defaults.baseURL = resolvedConfig.baseURL;
|
|
1902
|
+
transport.axios.defaults.withCredentials = resolvedConfig.withCredentials;
|
|
1903
|
+
if (resolvedConfig.axiosConfig) {
|
|
1904
|
+
Object.assign(transport.axios.defaults, resolvedConfig.axiosConfig);
|
|
1905
|
+
}
|
|
1906
|
+
transport.axios.interceptors.request.clear();
|
|
1907
|
+
transport.axios.interceptors.response.clear();
|
|
1908
|
+
if (resolvedConfig.auth) {
|
|
1909
|
+
transport.axios.interceptors.request.use(async (requestConfig) => {
|
|
1910
|
+
const token = await resolvedConfig.auth.getToken?.();
|
|
1911
|
+
if (token) {
|
|
1912
|
+
requestConfig.headers.Authorization = `${resolvedConfig.auth.tokenPrefix ?? "Bearer"} ${token}`;
|
|
1913
|
+
}
|
|
1914
|
+
return requestConfig;
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
if (resolvedConfig.interceptors?.request) {
|
|
1918
|
+
transport.axios.interceptors.request.use(resolvedConfig.interceptors.request);
|
|
1919
|
+
}
|
|
1920
|
+
transport.axios.interceptors.response.use(
|
|
1921
|
+
(response) => {
|
|
1922
|
+
transport.lastResponse.value = response;
|
|
1923
|
+
return resolvedConfig.interceptors?.response?.(response) ?? response;
|
|
1924
|
+
},
|
|
1925
|
+
async (error) => {
|
|
1926
|
+
if (error.response?.status === 401) {
|
|
1927
|
+
await resolvedConfig.auth?.onUnauthorized?.();
|
|
1928
|
+
}
|
|
1929
|
+
resolvedConfig.onError?.(parseApiError(error), error);
|
|
1930
|
+
throw error;
|
|
1931
|
+
}
|
|
1932
|
+
);
|
|
1933
|
+
return transport;
|
|
1934
|
+
}
|
|
1739
1935
|
function useApiRequest(requestFn, options = {}) {
|
|
1740
1936
|
const loading = ref(false);
|
|
1741
1937
|
const error = ref(null);
|
|
@@ -1821,7 +2017,7 @@ function wrapApiForDirectReturn(apiObj) {
|
|
|
1821
2017
|
}
|
|
1822
2018
|
return apiObj;
|
|
1823
2019
|
}
|
|
1824
|
-
const axios =
|
|
2020
|
+
const axios = ax.create({
|
|
1825
2021
|
withCredentials: true
|
|
1826
2022
|
});
|
|
1827
2023
|
class DataRequest {
|
|
@@ -2091,4 +2287,4 @@ function createOptionsGetter(fetchFn, mapFn) {
|
|
|
2091
2287
|
};
|
|
2092
2288
|
}
|
|
2093
2289
|
|
|
2094
|
-
export { ApiResponse, Bagel, StreamController, createOptionsGetter, createSSEStream, createSSEStreamPost, dereference, extractSSEEventInfo, extractSSEEventSchemas, extractSSEEventTypes, formatAPIErrorMessage, formatFieldErrors, getPath, isReferenceObject, isSSEStream, isSchemaObject, index as openAPI, parseApiError, unwrap, useApiRequest, useCancellableRequest, wrapApiForDirectReturn };
|
|
2290
|
+
export { ApiResponse, Bagel, StreamController, configureApiTransport, createApiTransport, createOptionsGetter, createSSEStream, createSSEStreamPost, dereference, extractSSEEventInfo, extractSSEEventSchemas, extractSSEEventTypes, formatAPIErrorMessage, formatFieldErrors, generateModules, getPath, isReferenceObject, isSSEStream, isSchemaObject, index as openAPI, parseApiError, unwrap, useApiRequest, useCancellableRequest, validateOwnershipManifest, wrapApiForDirectReturn };
|