@bagelink/sdk 1.15.556 → 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 CHANGED
@@ -1,11 +1,267 @@
1
1
  'use strict';
2
2
 
3
- const axios$1 = require('axios');
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 axios__default = /*#__PURE__*/_interopDefaultCompat(axios$1);
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
- const url = \`\${${baseUrlRef}}${pathForUrl}\`
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 = \`\${${baseUrlRef}}${pathForUrl}\` + __qsSuffix` : `const url = \`\${${baseUrlRef}}${pathForUrl}\``;
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 generateFunctions(paths, baseUrl) {
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
- export const ${parent} = ${JSON.stringify(object, void 0, 2)};
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
- export const api = {
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 allFunctionBodies = Object.values(functionsInventory).join("\n");
1011
- const hasGetStreams = allFunctionBodies.includes("createSSEStream<");
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 { ref, type Ref } from 'vue';
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
- // Store config for access via useApi().$config
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: apiConfig
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
- class StreamController {
1255
- constructor(cleanup) {
1256
- this.cleanup = cleanup;
1257
- }
1258
- handlers = /* @__PURE__ */ new Map();
1259
- _closed = false;
1260
- /**
1261
- * Register an event handler (fully typed!)
1262
- * @param event - Event type to listen for
1263
- * @param handler - Handler function (data type inferred from event)
1264
- * @returns this (for chaining)
1265
- */
1266
- on(event, handler) {
1267
- if (!this.handlers.has(event)) {
1268
- this.handlers.set(event, /* @__PURE__ */ new Set());
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
- * Emit an event to all registered handlers
1275
- * @internal
1276
- */
1277
- emit(event, data) {
1278
- const handlers = this.handlers.get(event);
1279
- if (handlers) {
1280
- handlers.forEach((handler) => {
1281
- try {
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
- * Close the stream
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
- function createSSEStream(url, options = {}) {
1301
- const {
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
- let response;
1325
- try {
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 reader = response.body.getReader();
1346
- const decoder = new TextDecoder();
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
- async function handleDisconnect(error) {
1411
- if (controller.signal.aborted) return;
1412
- if (reconnectAttempts >= maxReconnects) {
1413
- const msg = error?.message || "Stream connection lost";
1414
- stream.emit("error", new Error(`${msg} (gave up after ${maxReconnects} reconnects)`));
1415
- return;
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
- connect().catch(() => {
1423
- });
1424
- return stream;
1425
- }
1426
- function createSSEStreamPost(url, body, options = {}) {
1427
- const { headers = {}, withCredentials = true } = options;
1428
- const controller = new AbortController();
1429
- const stream = new StreamController(() => {
1430
- controller.abort();
1431
- });
1432
- fetch(url, {
1433
- method: "POST",
1434
- headers: {
1435
- "Accept": "text/event-stream",
1436
- "Content-Type": "application/json",
1437
- "Cache-Control": "no-cache",
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
- } catch (error) {
1487
- if (error?.name !== "AbortError") {
1488
- const errorMessage = error?.message || "Stream connection error";
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
- }).catch((error) => {
1498
- if (error?.name !== "AbortError") {
1499
- const errorMessage = error?.message || "Failed to establish stream connection";
1500
- stream.emit("error", new Error(errorMessage));
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 axios__default.get(openApiUrl, { headers: basicAuthHeader });
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 = axios__default.create({
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;