@bagelink/sdk 1.7.101 → 1.7.104

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.mjs CHANGED
@@ -1,5 +1,25 @@
1
1
  import axios$1 from 'axios';
2
2
 
3
+ function isSSEStream(operation) {
4
+ const description = operation.description?.toLowerCase() || "";
5
+ const summary = operation.summary?.toLowerCase() || "";
6
+ const hasSSEKeywords = description.includes("sse") || description.includes("server-sent events") || description.includes("event stream") || summary.includes("stream");
7
+ const responses = operation.responses || {};
8
+ const hasEventStreamContentType = Object.values(responses).some((response) => {
9
+ if ("content" in response) {
10
+ return "text/event-stream" in (response.content || {});
11
+ }
12
+ return false;
13
+ });
14
+ return hasSSEKeywords || hasEventStreamContentType;
15
+ }
16
+ function extractSSEEventTypes(operation) {
17
+ const description = operation.description || "";
18
+ const typeMatches = description.matchAll(/type:\s*"([^"]+)"/g);
19
+ const types = Array.from(typeMatches).map((match) => match[1]);
20
+ return types.length > 0 ? types : void 0;
21
+ }
22
+
3
23
  function getPath(pathsObject, path) {
4
24
  return pathsObject ? pathsObject[path] : void 0;
5
25
  }
@@ -278,6 +298,7 @@ const primitiveTypes = [
278
298
  ];
279
299
  const functionsInventory = {};
280
300
  const pathOperations = [];
301
+ const streamEventTypes = {};
281
302
  function collectTypeForImportStatement(typeName) {
282
303
  typeName = typeName.trim().replace("[]", "");
283
304
  if (typeName.includes("|")) {
@@ -441,6 +462,76 @@ function combineAllParams(parameters, requestBodyParam) {
441
462
  }).join(", ");
442
463
  return `{ ${destructuredNames} }: { ${typeDefinition} } = {}`;
443
464
  }
465
+ function generateStreamTypeName(path) {
466
+ return `${toPascalCase(
467
+ path.split("/").filter((p) => p && !/\{|\}/.test(p)).join("_")
468
+ )}StreamEvents`;
469
+ }
470
+ function generateStreamFunction(method, path, formattedPath, allParams, requestBodyPayload, eventTypes) {
471
+ if (allParams === "undefined") {
472
+ allParams = "";
473
+ }
474
+ const bodyVar = requestBodyPayload || "{}";
475
+ const baseUrlRef = 'axios.defaults.baseURL || ""';
476
+ const streamTypeName = generateStreamTypeName(path);
477
+ if (eventTypes?.length) {
478
+ streamEventTypes[streamTypeName] = eventTypes;
479
+ }
480
+ const eventTypesComment = eventTypes?.length ? `
481
+ * Event types: ${eventTypes.map((t) => `"${t}"`).join(", ")}` : "";
482
+ const eventTypesExample = eventTypes?.length ? eventTypes.map((t) => `
483
+ * .on('${t}', (data) => console.log(data))`).join("") : "\n * .on('message', (data) => console.log(data))";
484
+ const typeAnnotation = eventTypes?.length ? `StreamController<${streamTypeName}>` : "StreamController";
485
+ if (method === "post") {
486
+ return `{
487
+ /**
488
+ * Stream SSE events from this endpoint (returns StreamController)${eventTypesComment}
489
+ *
490
+ * @example
491
+ * const stream = api.endpoint.stream(params)${eventTypesExample}
492
+ * .on('error', (err) => console.error(err))
493
+ * .on('complete', () => console.log('Done!'))
494
+ *
495
+ * // Close stream when needed
496
+ * stream.close()
497
+ */
498
+ stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
499
+ const url = \`\${${baseUrlRef}}${formattedPath}\`
500
+ return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
501
+ },
502
+ /**
503
+ * Regular POST request (non-streaming)
504
+ */
505
+ post: async (${allParams}): Promise<AxiosResponse<any>> => {
506
+ return axios.post(${formattedPath}, ${bodyVar})
507
+ }
508
+ }`;
509
+ } else {
510
+ return `{
511
+ /**
512
+ * Stream SSE events from this endpoint (returns StreamController)${eventTypesComment}
513
+ *
514
+ * @example
515
+ * const stream = api.endpoint.stream(params)${eventTypesExample}
516
+ * .on('error', (err) => console.error(err))
517
+ * .on('complete', () => console.log('Done!'))
518
+ *
519
+ * // Close stream when needed
520
+ * stream.close()
521
+ */
522
+ stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
523
+ const url = \`\${${baseUrlRef}}${formattedPath}\`
524
+ return createSSEStream<${streamTypeName}>(url, options)
525
+ },
526
+ /**
527
+ * Regular GET request (non-streaming)
528
+ */
529
+ get: async (${allParams}): Promise<AxiosResponse<any>> => {
530
+ return axios.get(${formattedPath})
531
+ }
532
+ }`;
533
+ }
534
+ }
444
535
  function generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload) {
445
536
  if (allParams === "undefined") {
446
537
  allParams = "";
@@ -498,6 +589,7 @@ function generateFunctionForOperation(method, path, operation) {
498
589
  if (!operation) {
499
590
  return "";
500
591
  }
592
+ const isStream = isSSEStream(operation);
501
593
  const methodLower = method.toLowerCase();
502
594
  if (["get", "delete"].includes(methodLower) && operation.requestBody) {
503
595
  const requestBodyDeref = dereference(operation.requestBody);
@@ -526,6 +618,17 @@ function generateFunctionForOperation(method, path, operation) {
526
618
  const responseType = generateResponseType(operation.responses);
527
619
  const responseTypeStr = responseType ? `: Promise<AxiosResponse<${responseType}>>` : "";
528
620
  const functionComment = buildJSDocComment(operation, method, path);
621
+ if (isStream) {
622
+ const eventTypes = extractSSEEventTypes(operation);
623
+ return functionComment + generateStreamFunction(
624
+ method,
625
+ path,
626
+ formatPathWithParams(path),
627
+ allParams,
628
+ requestBodyPayload,
629
+ eventTypes
630
+ );
631
+ }
529
632
  return functionComment + generateAxiosFunction(
530
633
  method,
531
634
  formatPathWithParams(path),
@@ -621,10 +724,38 @@ export const ${parent} = ${JSON.stringify(object, void 0, 2)};
621
724
  });
622
725
  return fileTemplate(tsString, allTypes, baseUrl);
623
726
  }
727
+ function generateStreamEventTypeDefinitions() {
728
+ if (Object.keys(streamEventTypes).length === 0) {
729
+ return "";
730
+ }
731
+ let typeDefs = "\n// ============================================================================\n";
732
+ typeDefs += "// Stream Event Type Definitions (Fully Typed!)\n";
733
+ typeDefs += "// ============================================================================\n\n";
734
+ for (const [typeName, events] of Object.entries(streamEventTypes)) {
735
+ typeDefs += `/**
736
+ * Event types for ${typeName.replace("StreamEvents", "")} stream
737
+ `;
738
+ typeDefs += ` * Events: ${events.map((e) => `"${e}"`).join(", ")}
739
+ */
740
+ `;
741
+ typeDefs += `export interface ${typeName} {
742
+ `;
743
+ for (const event of events) {
744
+ typeDefs += ` /** ${event} event data */
745
+ `;
746
+ typeDefs += ` ${event}: any // TODO: Define specific type from OpenAPI schema
747
+ `;
748
+ }
749
+ typeDefs += "}\n\n";
750
+ }
751
+ return typeDefs;
752
+ }
624
753
  function fileTemplate(tsString, typeForImport, baseURL) {
754
+ const streamTypeDefs = generateStreamEventTypeDefinitions();
625
755
  const templateCode = `import ax from 'axios';
626
756
  import type { AxiosResponse } from 'axios';
627
757
  import type { ${typeForImport.join(", ")} } from './types.d';
758
+ import { createSSEStream, createSSEStreamPost, StreamController, type SSEStreamOptions, type SSEEvent } from './streamClient';
628
759
 
629
760
  /**
630
761
  * Options for file upload operations
@@ -638,6 +769,24 @@ export interface UploadOptions {
638
769
  tags?: string[]
639
770
  }
640
771
 
772
+ /**
773
+ * Export SSE stream utilities for direct use
774
+ *
775
+ * @example Chainable event handlers
776
+ * const stream = createSSEStream(url)
777
+ * .on('token', (data) => console.log(data))
778
+ * .on('done', () => console.log('Complete!'))
779
+ *
780
+ * @example Async iteration
781
+ * for await (const event of createSSEStream(url)) {
782
+ * console.log(event.type, event.data)
783
+ * }
784
+ *
785
+ * @example Promise-based
786
+ * const result = await createSSEStream(url).toPromise()
787
+ */
788
+ export { createSSEStream, createSSEStreamPost, StreamController, type SSEStreamOptions, type SSEEvent } from './streamClient';
789
+ ${streamTypeDefs}
641
790
  /**
642
791
  * Configured axios instance for API requests
643
792
  * @example
@@ -691,6 +840,430 @@ function generateTypes(schemas) {
691
840
  }).join("\n");
692
841
  }
693
842
 
843
+ class StreamController {
844
+ constructor(streamFn) {
845
+ this.streamFn = streamFn;
846
+ this.abortController = new AbortController();
847
+ this.start();
848
+ }
849
+ handlers = /* @__PURE__ */ new Map();
850
+ errorHandlers = /* @__PURE__ */ new Set();
851
+ completeHandlers = /* @__PURE__ */ new Set();
852
+ abortController;
853
+ _closed = false;
854
+ _promise = null;
855
+ _resolvePromise = null;
856
+ _rejectPromise = null;
857
+ start() {
858
+ const cleanup = this.streamFn(
859
+ (event) => {
860
+ this.emit(event.type, event.data);
861
+ },
862
+ (error) => {
863
+ this.emitError(error);
864
+ },
865
+ () => {
866
+ this.emitComplete();
867
+ }
868
+ );
869
+ this.abortController.signal.addEventListener("abort", () => {
870
+ cleanup();
871
+ this._closed = true;
872
+ });
873
+ }
874
+ /**
875
+ * Register an event handler (fully typed!)
876
+ * @param event - Event type to listen for
877
+ * @param handler - Handler function (data type inferred from event)
878
+ * @returns this (for chaining)
879
+ */
880
+ on(event, handler) {
881
+ if (event === "error") {
882
+ this.errorHandlers.add(handler);
883
+ } else if (event === "complete") {
884
+ this.completeHandlers.add(handler);
885
+ } else {
886
+ if (!this.handlers.has(event)) {
887
+ this.handlers.set(event, /* @__PURE__ */ new Set());
888
+ }
889
+ this.handlers.get(event).add(handler);
890
+ }
891
+ return this;
892
+ }
893
+ /**
894
+ * Register a one-time event handler (fully typed!)
895
+ * @param event - Event type to listen for
896
+ * @param handler - Handler function (called once then removed, data type inferred from event)
897
+ * @returns this (for chaining)
898
+ */
899
+ once(event, handler) {
900
+ const wrappedHandler = (data) => {
901
+ handler(data);
902
+ this.off(event, wrappedHandler);
903
+ };
904
+ return this.on(event, wrappedHandler);
905
+ }
906
+ /**
907
+ * Remove an event handler (fully typed!)
908
+ * @param event - Event type
909
+ * @param handler - Handler to remove
910
+ * @returns this (for chaining)
911
+ */
912
+ off(event, handler) {
913
+ if (event === "error") {
914
+ this.errorHandlers.delete(handler);
915
+ } else if (event === "complete") {
916
+ this.completeHandlers.delete(handler);
917
+ } else {
918
+ this.handlers.get(event)?.delete(handler);
919
+ }
920
+ return this;
921
+ }
922
+ /**
923
+ * Remove all handlers for an event (or all events if no event specified)
924
+ */
925
+ removeAllListeners(event) {
926
+ if (event === void 0) {
927
+ this.handlers.clear();
928
+ this.errorHandlers.clear();
929
+ this.completeHandlers.clear();
930
+ } else if (event === "error") {
931
+ this.errorHandlers.clear();
932
+ } else if (event === "complete") {
933
+ this.completeHandlers.clear();
934
+ } else {
935
+ this.handlers.delete(event);
936
+ }
937
+ return this;
938
+ }
939
+ emit(event, data) {
940
+ const handlers = this.handlers.get(event);
941
+ if (handlers) {
942
+ handlers.forEach((handler) => {
943
+ try {
944
+ handler(data);
945
+ } catch (error) {
946
+ console.error(`Error in handler for event "${event}":`, error);
947
+ }
948
+ });
949
+ }
950
+ if (event === "done" && this._resolvePromise) {
951
+ this._resolvePromise(data);
952
+ }
953
+ }
954
+ emitError(error) {
955
+ this.errorHandlers.forEach((handler) => {
956
+ try {
957
+ handler(error);
958
+ } catch (err) {
959
+ console.error("Error in error handler:", err);
960
+ }
961
+ });
962
+ if (this._rejectPromise) {
963
+ this._rejectPromise(error);
964
+ }
965
+ }
966
+ emitComplete() {
967
+ this.completeHandlers.forEach((handler) => {
968
+ try {
969
+ handler();
970
+ } catch (error) {
971
+ console.error("Error in complete handler:", error);
972
+ }
973
+ });
974
+ }
975
+ /**
976
+ * Close the stream
977
+ */
978
+ close() {
979
+ if (!this._closed) {
980
+ this.abortController.abort();
981
+ this._closed = true;
982
+ }
983
+ }
984
+ /**
985
+ * Check if stream is closed
986
+ */
987
+ get closed() {
988
+ return this._closed;
989
+ }
990
+ /**
991
+ * Convert stream to a Promise that resolves when 'done' event fires
992
+ * @returns Promise that resolves with the 'done' event data
993
+ */
994
+ toPromise() {
995
+ if (!this._promise) {
996
+ this._promise = new Promise((resolve, reject) => {
997
+ this._resolvePromise = resolve;
998
+ this._rejectPromise = reject;
999
+ });
1000
+ }
1001
+ return this._promise;
1002
+ }
1003
+ /**
1004
+ * Make the stream async iterable
1005
+ * Usage: for await (const event of stream) { ... }
1006
+ */
1007
+ async *[Symbol.asyncIterator]() {
1008
+ const events = [];
1009
+ let resolveNext = null;
1010
+ let done = false;
1011
+ const eventHandler = (type) => (data) => {
1012
+ const event = { type, data };
1013
+ if (resolveNext) {
1014
+ resolveNext(event);
1015
+ resolveNext = null;
1016
+ } else {
1017
+ events.push(event);
1018
+ }
1019
+ };
1020
+ const originalEmit = this.emit.bind(this);
1021
+ const capturedEvents = [];
1022
+ this.emit = (event, data) => {
1023
+ if (!capturedEvents.includes(event)) {
1024
+ capturedEvents.push(event);
1025
+ this.on(event, eventHandler(event));
1026
+ }
1027
+ originalEmit(event, data);
1028
+ };
1029
+ this.on("complete", () => {
1030
+ done = true;
1031
+ if (resolveNext) {
1032
+ resolveNext(null);
1033
+ resolveNext = null;
1034
+ }
1035
+ });
1036
+ try {
1037
+ while (!done) {
1038
+ if (events.length > 0) {
1039
+ const event = events.shift();
1040
+ if (event) yield event;
1041
+ } else {
1042
+ const event = await new Promise((resolve) => {
1043
+ resolveNext = resolve;
1044
+ });
1045
+ if (event === null) break;
1046
+ yield event;
1047
+ }
1048
+ }
1049
+ } finally {
1050
+ this.close();
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Collect all events into an array until stream completes
1055
+ * @returns Promise<SSEEvent[]>
1056
+ */
1057
+ async toArray() {
1058
+ const events = [];
1059
+ for await (const event of this) {
1060
+ events.push(event);
1061
+ }
1062
+ return events;
1063
+ }
1064
+ /**
1065
+ * Pipe stream events through a transform function
1066
+ */
1067
+ map(transform) {
1068
+ const mappedController = new StreamController(
1069
+ (onEvent, onError, onComplete) => {
1070
+ this.on("error", onError);
1071
+ this.on("complete", onComplete);
1072
+ this.handlers.forEach((_, eventType) => {
1073
+ this.on(eventType, (data) => {
1074
+ try {
1075
+ const transformed = transform({ type: eventType, data });
1076
+ onEvent({ type: eventType, data: transformed, raw: void 0 });
1077
+ } catch (error) {
1078
+ onError(error);
1079
+ }
1080
+ });
1081
+ });
1082
+ return () => {
1083
+ this.close();
1084
+ };
1085
+ }
1086
+ );
1087
+ return mappedController;
1088
+ }
1089
+ /**
1090
+ * Filter events based on a predicate
1091
+ */
1092
+ filter(predicate) {
1093
+ const filteredController = new StreamController(
1094
+ (onEvent, onError, onComplete) => {
1095
+ this.on("error", onError);
1096
+ this.on("complete", onComplete);
1097
+ this.handlers.forEach((_, eventType) => {
1098
+ this.on(eventType, (data) => {
1099
+ const event = { type: eventType, data };
1100
+ if (predicate(event)) {
1101
+ onEvent(event);
1102
+ }
1103
+ });
1104
+ });
1105
+ return () => {
1106
+ this.close();
1107
+ };
1108
+ }
1109
+ );
1110
+ return filteredController;
1111
+ }
1112
+ }
1113
+
1114
+ function createSSEStream(url, options = {}) {
1115
+ const { headers = {}, withCredentials = true } = options;
1116
+ return new StreamController((onEvent, onError, onComplete) => {
1117
+ const controller = new AbortController();
1118
+ const { signal } = controller;
1119
+ fetch(url, {
1120
+ method: "GET",
1121
+ headers: {
1122
+ Accept: "text/event-stream",
1123
+ ...headers
1124
+ },
1125
+ credentials: withCredentials ? "include" : "same-origin",
1126
+ signal
1127
+ }).then(async (response) => {
1128
+ if (!response.ok) {
1129
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1130
+ }
1131
+ if (!response.body) {
1132
+ throw new Error("Response body is null");
1133
+ }
1134
+ const reader = response.body.getReader();
1135
+ const decoder = new TextDecoder();
1136
+ let buffer = "";
1137
+ try {
1138
+ while (true) {
1139
+ const { done, value } = await reader.read();
1140
+ if (done) {
1141
+ onComplete();
1142
+ break;
1143
+ }
1144
+ buffer += decoder.decode(value, { stream: true });
1145
+ const lines = buffer.split("\n");
1146
+ buffer = lines.pop() || "";
1147
+ let eventType = "";
1148
+ let eventData = "";
1149
+ for (const line of lines) {
1150
+ if (line.startsWith("event:")) {
1151
+ eventType = line.slice(6).trim();
1152
+ } else if (line.startsWith("data:")) {
1153
+ eventData = line.slice(5).trim();
1154
+ } else if (line === "" && eventData) {
1155
+ try {
1156
+ const parsedData = JSON.parse(eventData);
1157
+ onEvent({
1158
+ type: eventType || "message",
1159
+ data: parsedData,
1160
+ raw: eventData
1161
+ });
1162
+ } catch {
1163
+ onEvent({
1164
+ type: eventType || "message",
1165
+ data: eventData,
1166
+ raw: eventData
1167
+ });
1168
+ }
1169
+ eventType = "";
1170
+ eventData = "";
1171
+ }
1172
+ }
1173
+ }
1174
+ } catch (error) {
1175
+ if (error instanceof Error && error.name !== "AbortError") {
1176
+ onError(error);
1177
+ }
1178
+ }
1179
+ }).catch((error) => {
1180
+ if (error.name !== "AbortError") {
1181
+ onError(error);
1182
+ }
1183
+ });
1184
+ return () => {
1185
+ controller.abort();
1186
+ };
1187
+ });
1188
+ }
1189
+ function createSSEStreamPost(url, body, options = {}) {
1190
+ const { headers = {}, withCredentials = true } = options;
1191
+ return new StreamController((onEvent, onError, onComplete) => {
1192
+ const controller = new AbortController();
1193
+ const { signal } = controller;
1194
+ fetch(url, {
1195
+ method: "POST",
1196
+ headers: {
1197
+ "Accept": "text/event-stream",
1198
+ "Content-Type": "application/json",
1199
+ ...headers
1200
+ },
1201
+ credentials: withCredentials ? "include" : "same-origin",
1202
+ body: JSON.stringify(body),
1203
+ signal
1204
+ }).then(async (response) => {
1205
+ if (!response.ok) {
1206
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1207
+ }
1208
+ if (!response.body) {
1209
+ throw new Error("Response body is null");
1210
+ }
1211
+ const reader = response.body.getReader();
1212
+ const decoder = new TextDecoder();
1213
+ let buffer = "";
1214
+ try {
1215
+ while (true) {
1216
+ const { done, value } = await reader.read();
1217
+ if (done) {
1218
+ onComplete();
1219
+ break;
1220
+ }
1221
+ buffer += decoder.decode(value, { stream: true });
1222
+ const lines = buffer.split("\n");
1223
+ buffer = lines.pop() || "";
1224
+ let eventType = "";
1225
+ let eventData = "";
1226
+ for (const line of lines) {
1227
+ if (line.startsWith("event:")) {
1228
+ eventType = line.slice(6).trim();
1229
+ } else if (line.startsWith("data:")) {
1230
+ eventData = line.slice(5).trim();
1231
+ } else if (line === "" && eventData) {
1232
+ try {
1233
+ const parsedData = JSON.parse(eventData);
1234
+ onEvent({
1235
+ type: eventType || "message",
1236
+ data: parsedData,
1237
+ raw: eventData
1238
+ });
1239
+ } catch {
1240
+ onEvent({
1241
+ type: eventType || "message",
1242
+ data: eventData,
1243
+ raw: eventData
1244
+ });
1245
+ }
1246
+ eventType = "";
1247
+ eventData = "";
1248
+ }
1249
+ }
1250
+ }
1251
+ } catch (error) {
1252
+ if (error instanceof Error && error.name !== "AbortError") {
1253
+ onError(error);
1254
+ }
1255
+ }
1256
+ }).catch((error) => {
1257
+ if (error.name !== "AbortError") {
1258
+ onError(error);
1259
+ }
1260
+ });
1261
+ return () => {
1262
+ controller.abort();
1263
+ };
1264
+ });
1265
+ }
1266
+
694
1267
  const basicAuthHeader = { Authorization: "Basic YmFnZWxfdXNlcm5hbWU6Tm90U2VjdXJlQGJhZ2Vs" };
695
1268
  const index = async (openApiUrl, baseUrl) => {
696
1269
  try {
@@ -1000,4 +1573,4 @@ class Bagel {
1000
1573
  }
1001
1574
  }
1002
1575
 
1003
- export { Bagel, dereference, formatAPIErrorMessage, getPath, isReferenceObject, isSchemaObject, index as openAPI };
1576
+ export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, index as openAPI };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/sdk",
3
3
  "type": "module",
4
- "version": "1.7.101",
4
+ "version": "1.7.104",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import ax from 'axios'
2
2
 
3
3
  export * from './openAPITools'
4
4
  export { default as openAPI } from './openAPITools'
5
+ export { createSSEStream, createSSEStreamPost, type SSEEvent, type SSEStreamOptions, StreamController, type StreamEventMap } from './openAPITools/streamClient'
5
6
 
6
7
  export type Tables = ''
7
8
  export type TableToTypeMapping = Record<Tables, any>