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