@bagelink/sdk 1.7.104 → 1.8.5

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
@@ -7,23 +7,113 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
7
7
  const axios__default = /*#__PURE__*/_interopDefaultCompat(axios$1);
8
8
 
9
9
  function isSSEStream(operation) {
10
- const description = operation.description?.toLowerCase() || "";
11
- const summary = operation.summary?.toLowerCase() || "";
12
- const hasSSEKeywords = description.includes("sse") || description.includes("server-sent events") || description.includes("event stream") || summary.includes("stream");
13
10
  const responses = operation.responses || {};
14
- const hasEventStreamContentType = Object.values(responses).some((response) => {
15
- if ("content" in response) {
16
- return "text/event-stream" in (response.content || {});
11
+ for (const [statusCode, response] of Object.entries(responses)) {
12
+ if (!statusCode.startsWith("2")) continue;
13
+ if ("$ref" in response) continue;
14
+ const content = response.content || {};
15
+ const contentTypes = Object.keys(content);
16
+ if (contentTypes.length > 0) {
17
+ if ("text/event-stream" in content) {
18
+ return true;
19
+ }
17
20
  }
18
- return false;
21
+ }
22
+ const hasDefinedContentTypes = Object.entries(responses).some(([statusCode, response]) => {
23
+ if (!statusCode.startsWith("2")) return false;
24
+ if ("$ref" in response) return false;
25
+ const content = response.content || {};
26
+ return Object.keys(content).length > 0;
19
27
  });
20
- return hasSSEKeywords || hasEventStreamContentType;
28
+ if (hasDefinedContentTypes) {
29
+ return false;
30
+ }
31
+ const description = operation.description?.toLowerCase() || "";
32
+ const summary = operation.summary?.toLowerCase() || "";
33
+ const hasExplicitSSEMention = /\bsse\b/.test(description) || /server-sent events/i.test(description) || /text\/event-stream/i.test(description) || /\bsse\b/.test(summary);
34
+ return hasExplicitSSEMention;
21
35
  }
22
36
  function extractSSEEventTypes(operation) {
23
37
  const description = operation.description || "";
38
+ const types = /* @__PURE__ */ new Set();
39
+ const markdownMatches = description.matchAll(/^\d+\.\s+\*\*([a-z_]+)\*\*/gm);
40
+ for (const match of markdownMatches) {
41
+ types.add(match[1]);
42
+ }
24
43
  const typeMatches = description.matchAll(/type:\s*"([^"]+)"/g);
25
- const types = Array.from(typeMatches).map((match) => match[1]);
26
- return types.length > 0 ? types : void 0;
44
+ for (const match of typeMatches) {
45
+ types.add(match[1]);
46
+ }
47
+ const responses = operation.responses || {};
48
+ for (const response of Object.values(responses)) {
49
+ if ("content" in response) {
50
+ const content = response.content || {};
51
+ const eventStream = content["text/event-stream"];
52
+ if (eventStream?.example) {
53
+ const exampleMatches = eventStream.example.matchAll(/^event:\s*([a-z_]+)/gm);
54
+ for (const match of exampleMatches) {
55
+ types.add(match[1]);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ return types.size > 0 ? Array.from(types).sort() : void 0;
61
+ }
62
+ function extractSSEEventInfo(operation) {
63
+ const description = operation.description || "";
64
+ const eventInfos = [];
65
+ const eventSections = description.split(/(?=^\d+\.\s+\*\*)/m);
66
+ for (const section of eventSections) {
67
+ const eventMatch = section.match(/^\d+\.\s+\*\*([a-z_]+)\*\*:\s*([^\n]+)/m);
68
+ if (!eventMatch) continue;
69
+ const [, eventName, eventDescription] = eventMatch;
70
+ const fields = [];
71
+ const fieldMatches = section.matchAll(/^\s+[-*]\s+`([^`]+)`:\s*([^\n]+)/gm);
72
+ for (const fieldMatch of fieldMatches) {
73
+ let [, fieldName, fieldDescription] = fieldMatch;
74
+ if (fieldName.includes(".")) {
75
+ const parts = fieldName.split(".");
76
+ fieldName = parts[parts.length - 1];
77
+ }
78
+ fields.push({
79
+ name: fieldName,
80
+ description: fieldDescription.trim()
81
+ });
82
+ }
83
+ eventInfos.push({
84
+ name: eventName,
85
+ description: eventDescription,
86
+ fields: fields.length > 0 ? fields : void 0
87
+ });
88
+ }
89
+ if (eventInfos.length === 0) {
90
+ const responses = operation.responses || {};
91
+ for (const response of Object.values(responses)) {
92
+ if ("content" in response) {
93
+ const content = response.content || {};
94
+ const eventStream = content["text/event-stream"];
95
+ if (eventStream?.example) {
96
+ const exampleMatches = eventStream.example.matchAll(/data:\s*(\{[^}]+\})/g);
97
+ const seenEvents = /* @__PURE__ */ new Set();
98
+ for (const match of exampleMatches) {
99
+ try {
100
+ const data = JSON.parse(match[1]);
101
+ if (data.type && !seenEvents.has(data.type)) {
102
+ seenEvents.add(data.type);
103
+ const fields = Object.keys(data).filter((key) => key !== "type" && key !== "sequence").map((key) => ({ name: key }));
104
+ eventInfos.push({
105
+ name: data.type,
106
+ fields: fields.length > 0 ? fields : void 0
107
+ });
108
+ }
109
+ } catch {
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ return eventInfos.length > 0 ? eventInfos : void 0;
27
117
  }
28
118
 
29
119
  function getPath(pathsObject, path) {
@@ -305,6 +395,7 @@ const primitiveTypes = [
305
395
  const functionsInventory = {};
306
396
  const pathOperations = [];
307
397
  const streamEventTypes = {};
398
+ const streamEventInfo = {};
308
399
  function collectTypeForImportStatement(typeName) {
309
400
  typeName = typeName.trim().replace("[]", "");
310
401
  if (typeName.includes("|")) {
@@ -488,6 +579,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
488
579
  const eventTypesExample = eventTypes?.length ? eventTypes.map((t) => `
489
580
  * .on('${t}', (data) => console.log(data))`).join("") : "\n * .on('message', (data) => console.log(data))";
490
581
  const typeAnnotation = eventTypes?.length ? `StreamController<${streamTypeName}>` : "StreamController";
582
+ const pathForUrl = formattedPath.startsWith("`") ? formattedPath.slice(1, -1) : formattedPath.slice(1, -1);
491
583
  if (method === "post") {
492
584
  return `{
493
585
  /**
@@ -502,7 +594,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
502
594
  * stream.close()
503
595
  */
504
596
  stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
505
- const url = \`\${${baseUrlRef}}${formattedPath}\`
597
+ const url = \`\${${baseUrlRef}}${pathForUrl}\`
506
598
  return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
507
599
  },
508
600
  /**
@@ -526,7 +618,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
526
618
  * stream.close()
527
619
  */
528
620
  stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
529
- const url = \`\${${baseUrlRef}}${formattedPath}\`
621
+ const url = \`\${${baseUrlRef}}${pathForUrl}\`
530
622
  return createSSEStream<${streamTypeName}>(url, options)
531
623
  },
532
624
  /**
@@ -626,6 +718,11 @@ function generateFunctionForOperation(method, path, operation) {
626
718
  const functionComment = buildJSDocComment(operation, method, path);
627
719
  if (isStream) {
628
720
  const eventTypes = extractSSEEventTypes(operation);
721
+ const eventInfo = extractSSEEventInfo(operation);
722
+ const streamTypeName = generateStreamTypeName(path);
723
+ if (eventInfo?.length) {
724
+ streamEventInfo[streamTypeName] = eventInfo;
725
+ }
629
726
  return functionComment + generateStreamFunction(
630
727
  method,
631
728
  path,
@@ -738,19 +835,60 @@ function generateStreamEventTypeDefinitions() {
738
835
  typeDefs += "// Stream Event Type Definitions (Fully Typed!)\n";
739
836
  typeDefs += "// ============================================================================\n\n";
740
837
  for (const [typeName, events] of Object.entries(streamEventTypes)) {
838
+ const eventInfo = streamEventInfo[typeName];
741
839
  typeDefs += `/**
742
840
  * Event types for ${typeName.replace("StreamEvents", "")} stream
743
841
  `;
744
842
  typeDefs += ` * Events: ${events.map((e) => `"${e}"`).join(", ")}
745
- */
843
+ `;
844
+ if (eventInfo?.length) {
845
+ typeDefs += ` *
846
+ `;
847
+ for (const info of eventInfo) {
848
+ typeDefs += ` * - **${info.name}**: ${info.description || "Event data"}
849
+ `;
850
+ }
851
+ }
852
+ typeDefs += ` */
746
853
  `;
747
854
  typeDefs += `export interface ${typeName} {
748
855
  `;
749
- for (const event of events) {
750
- typeDefs += ` /** ${event} event data */
856
+ if (eventInfo?.length) {
857
+ for (const info of eventInfo) {
858
+ typeDefs += ` /**
859
+ * ${info.description || info.name}
751
860
  `;
752
- typeDefs += ` ${event}: any // TODO: Define specific type from OpenAPI schema
861
+ if (info.fields?.length) {
862
+ info.fields.forEach((field) => {
863
+ typeDefs += ` * - \`${field.name}\`: ${field.description || "Field data"}
753
864
  `;
865
+ });
866
+ }
867
+ typeDefs += ` */
868
+ `;
869
+ if (info.fields?.length) {
870
+ typeDefs += ` ${info.name}: {
871
+ `;
872
+ for (const field of info.fields) {
873
+ typeDefs += ` /** ${field.description || field.name} */
874
+ `;
875
+ typeDefs += ` ${field.name}: any
876
+ `;
877
+ }
878
+ typeDefs += ` }
879
+ `;
880
+ } else {
881
+ typeDefs += ` ${info.name}: any
882
+ `;
883
+ }
884
+ }
885
+ } else {
886
+ for (const event of events) {
887
+ typeDefs += ` /** ${event} event data */
888
+ `;
889
+ typeDefs += ` ${event}: any
890
+ `;
891
+ }
754
892
  }
755
893
  typeDefs += "}\n\n";
756
894
  }
@@ -1584,6 +1722,7 @@ exports.StreamController = StreamController;
1584
1722
  exports.createSSEStream = createSSEStream;
1585
1723
  exports.createSSEStreamPost = createSSEStreamPost;
1586
1724
  exports.dereference = dereference;
1725
+ exports.extractSSEEventInfo = extractSSEEventInfo;
1587
1726
  exports.extractSSEEventTypes = extractSSEEventTypes;
1588
1727
  exports.formatAPIErrorMessage = formatAPIErrorMessage;
1589
1728
  exports.getPath = getPath;
package/dist/index.d.cts CHANGED
@@ -508,16 +508,55 @@ interface SecurityRequirementObject {
508
508
 
509
509
  /**
510
510
  * Detects if an operation is an SSE (Server-Sent Events) stream endpoint
511
+ *
512
+ * Primary detection: Checks for text/event-stream content type in responses
513
+ * Fallback: Only if no content type is specified, check for explicit SSE keywords
514
+ *
511
515
  * @param operation - The OpenAPI operation object
512
516
  * @returns Whether the operation is an SSE stream
513
517
  */
514
518
  declare function isSSEStream(operation: OperationObject): boolean;
515
519
  /**
516
- * Extracts SSE event types from operation description
520
+ * Information about an SSE event type including its fields
521
+ */
522
+ interface SSEEventTypeInfo {
523
+ /** Event type name */
524
+ name: string;
525
+ /** Event description */
526
+ description?: string;
527
+ /** Fields in the event data */
528
+ fields?: Array<{
529
+ name: string;
530
+ description?: string;
531
+ type?: string;
532
+ }>;
533
+ }
534
+ /**
535
+ * Extracts SSE event types from operation description and examples
536
+ *
537
+ * Supports multiple documentation formats:
538
+ * 1. Markdown format: "1. **event_name**: Description"
539
+ * 2. Legacy format: type: "event_name"
540
+ * 3. SSE examples: event: event_name
541
+ *
517
542
  * @param operation - The OpenAPI operation object
518
543
  * @returns Array of event types or undefined
519
544
  */
520
545
  declare function extractSSEEventTypes(operation: OperationObject): string[] | undefined;
546
+ /**
547
+ * Extracts detailed SSE event information including field definitions
548
+ *
549
+ * Parses markdown documentation format:
550
+ * ```
551
+ * 1. **event_name**: Event description
552
+ * - `field_name`: Field description
553
+ * - `another_field`: Another description
554
+ * ```
555
+ *
556
+ * @param operation - The OpenAPI operation object
557
+ * @returns Array of event type information with fields
558
+ */
559
+ declare function extractSSEEventInfo(operation: OperationObject): SSEEventTypeInfo[] | undefined;
521
560
 
522
561
  interface OpenAPIResponse {
523
562
  types: string;
@@ -629,5 +668,5 @@ declare class Bagel {
629
668
  uploadFile<T>(file: File, options?: UploadOptions): Promise<T>;
630
669
  }
631
670
 
632
- export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, _default as openAPI };
633
- export type { BaseParameterObject, CallbackObject, CallbacksObject, ComponentsObject, ContactObject, ContentObject, DiscriminatorObject, EncodingObject, EncodingPropertyObject, ExampleObject, ExamplesObject, ExternalDocumentationObject, HeaderObject, HeadersObject, InfoObject, LicenseObject, LinkObject, LinkParametersObject, LinksObject, MediaTypeObject, OAuthFlowObject, OAuthFlowsObject, OpenAPIObject, OperationObject, ParameterLocation, ParameterObject, ParameterStyle, PathItemObject, PathsObject, ReferenceObject, RequestBodyObject, ResponseObject, ResponsesObject, SSEEvent, SSEStreamOptions, SchemaObject, SchemaObjectType, SchemasObject, ScopesObject, SecurityRequirementObject, SecuritySchemeObject, SecuritySchemeType, ServerObject, ServerVariableObject, StreamEventMap, TableToTypeMapping, Tables, TagObject, TypeFormatT, UploadOptions, User, XmlObject };
671
+ export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventInfo, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, _default as openAPI };
672
+ export type { BaseParameterObject, CallbackObject, CallbacksObject, ComponentsObject, ContactObject, ContentObject, DiscriminatorObject, EncodingObject, EncodingPropertyObject, ExampleObject, ExamplesObject, ExternalDocumentationObject, HeaderObject, HeadersObject, InfoObject, LicenseObject, LinkObject, LinkParametersObject, LinksObject, MediaTypeObject, OAuthFlowObject, OAuthFlowsObject, OpenAPIObject, OperationObject, ParameterLocation, ParameterObject, ParameterStyle, PathItemObject, PathsObject, ReferenceObject, RequestBodyObject, ResponseObject, ResponsesObject, SSEEvent, SSEEventTypeInfo, SSEStreamOptions, SchemaObject, SchemaObjectType, SchemasObject, ScopesObject, SecurityRequirementObject, SecuritySchemeObject, SecuritySchemeType, ServerObject, ServerVariableObject, StreamEventMap, TableToTypeMapping, Tables, TagObject, TypeFormatT, UploadOptions, User, XmlObject };
package/dist/index.d.mts CHANGED
@@ -508,16 +508,55 @@ interface SecurityRequirementObject {
508
508
 
509
509
  /**
510
510
  * Detects if an operation is an SSE (Server-Sent Events) stream endpoint
511
+ *
512
+ * Primary detection: Checks for text/event-stream content type in responses
513
+ * Fallback: Only if no content type is specified, check for explicit SSE keywords
514
+ *
511
515
  * @param operation - The OpenAPI operation object
512
516
  * @returns Whether the operation is an SSE stream
513
517
  */
514
518
  declare function isSSEStream(operation: OperationObject): boolean;
515
519
  /**
516
- * Extracts SSE event types from operation description
520
+ * Information about an SSE event type including its fields
521
+ */
522
+ interface SSEEventTypeInfo {
523
+ /** Event type name */
524
+ name: string;
525
+ /** Event description */
526
+ description?: string;
527
+ /** Fields in the event data */
528
+ fields?: Array<{
529
+ name: string;
530
+ description?: string;
531
+ type?: string;
532
+ }>;
533
+ }
534
+ /**
535
+ * Extracts SSE event types from operation description and examples
536
+ *
537
+ * Supports multiple documentation formats:
538
+ * 1. Markdown format: "1. **event_name**: Description"
539
+ * 2. Legacy format: type: "event_name"
540
+ * 3. SSE examples: event: event_name
541
+ *
517
542
  * @param operation - The OpenAPI operation object
518
543
  * @returns Array of event types or undefined
519
544
  */
520
545
  declare function extractSSEEventTypes(operation: OperationObject): string[] | undefined;
546
+ /**
547
+ * Extracts detailed SSE event information including field definitions
548
+ *
549
+ * Parses markdown documentation format:
550
+ * ```
551
+ * 1. **event_name**: Event description
552
+ * - `field_name`: Field description
553
+ * - `another_field`: Another description
554
+ * ```
555
+ *
556
+ * @param operation - The OpenAPI operation object
557
+ * @returns Array of event type information with fields
558
+ */
559
+ declare function extractSSEEventInfo(operation: OperationObject): SSEEventTypeInfo[] | undefined;
521
560
 
522
561
  interface OpenAPIResponse {
523
562
  types: string;
@@ -629,5 +668,5 @@ declare class Bagel {
629
668
  uploadFile<T>(file: File, options?: UploadOptions): Promise<T>;
630
669
  }
631
670
 
632
- export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, _default as openAPI };
633
- export type { BaseParameterObject, CallbackObject, CallbacksObject, ComponentsObject, ContactObject, ContentObject, DiscriminatorObject, EncodingObject, EncodingPropertyObject, ExampleObject, ExamplesObject, ExternalDocumentationObject, HeaderObject, HeadersObject, InfoObject, LicenseObject, LinkObject, LinkParametersObject, LinksObject, MediaTypeObject, OAuthFlowObject, OAuthFlowsObject, OpenAPIObject, OperationObject, ParameterLocation, ParameterObject, ParameterStyle, PathItemObject, PathsObject, ReferenceObject, RequestBodyObject, ResponseObject, ResponsesObject, SSEEvent, SSEStreamOptions, SchemaObject, SchemaObjectType, SchemasObject, ScopesObject, SecurityRequirementObject, SecuritySchemeObject, SecuritySchemeType, ServerObject, ServerVariableObject, StreamEventMap, TableToTypeMapping, Tables, TagObject, TypeFormatT, UploadOptions, User, XmlObject };
671
+ export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventInfo, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, _default as openAPI };
672
+ export type { BaseParameterObject, CallbackObject, CallbacksObject, ComponentsObject, ContactObject, ContentObject, DiscriminatorObject, EncodingObject, EncodingPropertyObject, ExampleObject, ExamplesObject, ExternalDocumentationObject, HeaderObject, HeadersObject, InfoObject, LicenseObject, LinkObject, LinkParametersObject, LinksObject, MediaTypeObject, OAuthFlowObject, OAuthFlowsObject, OpenAPIObject, OperationObject, ParameterLocation, ParameterObject, ParameterStyle, PathItemObject, PathsObject, ReferenceObject, RequestBodyObject, ResponseObject, ResponsesObject, SSEEvent, SSEEventTypeInfo, SSEStreamOptions, SchemaObject, SchemaObjectType, SchemasObject, ScopesObject, SecurityRequirementObject, SecuritySchemeObject, SecuritySchemeType, ServerObject, ServerVariableObject, StreamEventMap, TableToTypeMapping, Tables, TagObject, TypeFormatT, UploadOptions, User, XmlObject };
package/dist/index.d.ts CHANGED
@@ -508,16 +508,55 @@ interface SecurityRequirementObject {
508
508
 
509
509
  /**
510
510
  * Detects if an operation is an SSE (Server-Sent Events) stream endpoint
511
+ *
512
+ * Primary detection: Checks for text/event-stream content type in responses
513
+ * Fallback: Only if no content type is specified, check for explicit SSE keywords
514
+ *
511
515
  * @param operation - The OpenAPI operation object
512
516
  * @returns Whether the operation is an SSE stream
513
517
  */
514
518
  declare function isSSEStream(operation: OperationObject): boolean;
515
519
  /**
516
- * Extracts SSE event types from operation description
520
+ * Information about an SSE event type including its fields
521
+ */
522
+ interface SSEEventTypeInfo {
523
+ /** Event type name */
524
+ name: string;
525
+ /** Event description */
526
+ description?: string;
527
+ /** Fields in the event data */
528
+ fields?: Array<{
529
+ name: string;
530
+ description?: string;
531
+ type?: string;
532
+ }>;
533
+ }
534
+ /**
535
+ * Extracts SSE event types from operation description and examples
536
+ *
537
+ * Supports multiple documentation formats:
538
+ * 1. Markdown format: "1. **event_name**: Description"
539
+ * 2. Legacy format: type: "event_name"
540
+ * 3. SSE examples: event: event_name
541
+ *
517
542
  * @param operation - The OpenAPI operation object
518
543
  * @returns Array of event types or undefined
519
544
  */
520
545
  declare function extractSSEEventTypes(operation: OperationObject): string[] | undefined;
546
+ /**
547
+ * Extracts detailed SSE event information including field definitions
548
+ *
549
+ * Parses markdown documentation format:
550
+ * ```
551
+ * 1. **event_name**: Event description
552
+ * - `field_name`: Field description
553
+ * - `another_field`: Another description
554
+ * ```
555
+ *
556
+ * @param operation - The OpenAPI operation object
557
+ * @returns Array of event type information with fields
558
+ */
559
+ declare function extractSSEEventInfo(operation: OperationObject): SSEEventTypeInfo[] | undefined;
521
560
 
522
561
  interface OpenAPIResponse {
523
562
  types: string;
@@ -629,5 +668,5 @@ declare class Bagel {
629
668
  uploadFile<T>(file: File, options?: UploadOptions): Promise<T>;
630
669
  }
631
670
 
632
- export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, _default as openAPI };
633
- export type { BaseParameterObject, CallbackObject, CallbacksObject, ComponentsObject, ContactObject, ContentObject, DiscriminatorObject, EncodingObject, EncodingPropertyObject, ExampleObject, ExamplesObject, ExternalDocumentationObject, HeaderObject, HeadersObject, InfoObject, LicenseObject, LinkObject, LinkParametersObject, LinksObject, MediaTypeObject, OAuthFlowObject, OAuthFlowsObject, OpenAPIObject, OperationObject, ParameterLocation, ParameterObject, ParameterStyle, PathItemObject, PathsObject, ReferenceObject, RequestBodyObject, ResponseObject, ResponsesObject, SSEEvent, SSEStreamOptions, SchemaObject, SchemaObjectType, SchemasObject, ScopesObject, SecurityRequirementObject, SecuritySchemeObject, SecuritySchemeType, ServerObject, ServerVariableObject, StreamEventMap, TableToTypeMapping, Tables, TagObject, TypeFormatT, UploadOptions, User, XmlObject };
671
+ export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventInfo, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, _default as openAPI };
672
+ export type { BaseParameterObject, CallbackObject, CallbacksObject, ComponentsObject, ContactObject, ContentObject, DiscriminatorObject, EncodingObject, EncodingPropertyObject, ExampleObject, ExamplesObject, ExternalDocumentationObject, HeaderObject, HeadersObject, InfoObject, LicenseObject, LinkObject, LinkParametersObject, LinksObject, MediaTypeObject, OAuthFlowObject, OAuthFlowsObject, OpenAPIObject, OperationObject, ParameterLocation, ParameterObject, ParameterStyle, PathItemObject, PathsObject, ReferenceObject, RequestBodyObject, ResponseObject, ResponsesObject, SSEEvent, SSEEventTypeInfo, SSEStreamOptions, SchemaObject, SchemaObjectType, SchemasObject, ScopesObject, SecurityRequirementObject, SecuritySchemeObject, SecuritySchemeType, ServerObject, ServerVariableObject, StreamEventMap, TableToTypeMapping, Tables, TagObject, TypeFormatT, UploadOptions, User, XmlObject };
package/dist/index.mjs CHANGED
@@ -1,23 +1,113 @@
1
1
  import axios$1 from 'axios';
2
2
 
3
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
4
  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 || {});
5
+ for (const [statusCode, response] of Object.entries(responses)) {
6
+ if (!statusCode.startsWith("2")) continue;
7
+ if ("$ref" in response) continue;
8
+ const content = response.content || {};
9
+ const contentTypes = Object.keys(content);
10
+ if (contentTypes.length > 0) {
11
+ if ("text/event-stream" in content) {
12
+ return true;
13
+ }
11
14
  }
12
- return false;
15
+ }
16
+ const hasDefinedContentTypes = Object.entries(responses).some(([statusCode, response]) => {
17
+ if (!statusCode.startsWith("2")) return false;
18
+ if ("$ref" in response) return false;
19
+ const content = response.content || {};
20
+ return Object.keys(content).length > 0;
13
21
  });
14
- return hasSSEKeywords || hasEventStreamContentType;
22
+ if (hasDefinedContentTypes) {
23
+ return false;
24
+ }
25
+ const description = operation.description?.toLowerCase() || "";
26
+ const summary = operation.summary?.toLowerCase() || "";
27
+ const hasExplicitSSEMention = /\bsse\b/.test(description) || /server-sent events/i.test(description) || /text\/event-stream/i.test(description) || /\bsse\b/.test(summary);
28
+ return hasExplicitSSEMention;
15
29
  }
16
30
  function extractSSEEventTypes(operation) {
17
31
  const description = operation.description || "";
32
+ const types = /* @__PURE__ */ new Set();
33
+ const markdownMatches = description.matchAll(/^\d+\.\s+\*\*([a-z_]+)\*\*/gm);
34
+ for (const match of markdownMatches) {
35
+ types.add(match[1]);
36
+ }
18
37
  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;
38
+ for (const match of typeMatches) {
39
+ types.add(match[1]);
40
+ }
41
+ const responses = operation.responses || {};
42
+ for (const response of Object.values(responses)) {
43
+ if ("content" in response) {
44
+ const content = response.content || {};
45
+ const eventStream = content["text/event-stream"];
46
+ if (eventStream?.example) {
47
+ const exampleMatches = eventStream.example.matchAll(/^event:\s*([a-z_]+)/gm);
48
+ for (const match of exampleMatches) {
49
+ types.add(match[1]);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ return types.size > 0 ? Array.from(types).sort() : void 0;
55
+ }
56
+ function extractSSEEventInfo(operation) {
57
+ const description = operation.description || "";
58
+ const eventInfos = [];
59
+ const eventSections = description.split(/(?=^\d+\.\s+\*\*)/m);
60
+ for (const section of eventSections) {
61
+ const eventMatch = section.match(/^\d+\.\s+\*\*([a-z_]+)\*\*:\s*([^\n]+)/m);
62
+ if (!eventMatch) continue;
63
+ const [, eventName, eventDescription] = eventMatch;
64
+ const fields = [];
65
+ const fieldMatches = section.matchAll(/^\s+[-*]\s+`([^`]+)`:\s*([^\n]+)/gm);
66
+ for (const fieldMatch of fieldMatches) {
67
+ let [, fieldName, fieldDescription] = fieldMatch;
68
+ if (fieldName.includes(".")) {
69
+ const parts = fieldName.split(".");
70
+ fieldName = parts[parts.length - 1];
71
+ }
72
+ fields.push({
73
+ name: fieldName,
74
+ description: fieldDescription.trim()
75
+ });
76
+ }
77
+ eventInfos.push({
78
+ name: eventName,
79
+ description: eventDescription,
80
+ fields: fields.length > 0 ? fields : void 0
81
+ });
82
+ }
83
+ if (eventInfos.length === 0) {
84
+ const responses = operation.responses || {};
85
+ for (const response of Object.values(responses)) {
86
+ if ("content" in response) {
87
+ const content = response.content || {};
88
+ const eventStream = content["text/event-stream"];
89
+ if (eventStream?.example) {
90
+ const exampleMatches = eventStream.example.matchAll(/data:\s*(\{[^}]+\})/g);
91
+ const seenEvents = /* @__PURE__ */ new Set();
92
+ for (const match of exampleMatches) {
93
+ try {
94
+ const data = JSON.parse(match[1]);
95
+ if (data.type && !seenEvents.has(data.type)) {
96
+ seenEvents.add(data.type);
97
+ const fields = Object.keys(data).filter((key) => key !== "type" && key !== "sequence").map((key) => ({ name: key }));
98
+ eventInfos.push({
99
+ name: data.type,
100
+ fields: fields.length > 0 ? fields : void 0
101
+ });
102
+ }
103
+ } catch {
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ return eventInfos.length > 0 ? eventInfos : void 0;
21
111
  }
22
112
 
23
113
  function getPath(pathsObject, path) {
@@ -299,6 +389,7 @@ const primitiveTypes = [
299
389
  const functionsInventory = {};
300
390
  const pathOperations = [];
301
391
  const streamEventTypes = {};
392
+ const streamEventInfo = {};
302
393
  function collectTypeForImportStatement(typeName) {
303
394
  typeName = typeName.trim().replace("[]", "");
304
395
  if (typeName.includes("|")) {
@@ -482,6 +573,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
482
573
  const eventTypesExample = eventTypes?.length ? eventTypes.map((t) => `
483
574
  * .on('${t}', (data) => console.log(data))`).join("") : "\n * .on('message', (data) => console.log(data))";
484
575
  const typeAnnotation = eventTypes?.length ? `StreamController<${streamTypeName}>` : "StreamController";
576
+ const pathForUrl = formattedPath.startsWith("`") ? formattedPath.slice(1, -1) : formattedPath.slice(1, -1);
485
577
  if (method === "post") {
486
578
  return `{
487
579
  /**
@@ -496,7 +588,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
496
588
  * stream.close()
497
589
  */
498
590
  stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
499
- const url = \`\${${baseUrlRef}}${formattedPath}\`
591
+ const url = \`\${${baseUrlRef}}${pathForUrl}\`
500
592
  return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
501
593
  },
502
594
  /**
@@ -520,7 +612,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
520
612
  * stream.close()
521
613
  */
522
614
  stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
523
- const url = \`\${${baseUrlRef}}${formattedPath}\`
615
+ const url = \`\${${baseUrlRef}}${pathForUrl}\`
524
616
  return createSSEStream<${streamTypeName}>(url, options)
525
617
  },
526
618
  /**
@@ -620,6 +712,11 @@ function generateFunctionForOperation(method, path, operation) {
620
712
  const functionComment = buildJSDocComment(operation, method, path);
621
713
  if (isStream) {
622
714
  const eventTypes = extractSSEEventTypes(operation);
715
+ const eventInfo = extractSSEEventInfo(operation);
716
+ const streamTypeName = generateStreamTypeName(path);
717
+ if (eventInfo?.length) {
718
+ streamEventInfo[streamTypeName] = eventInfo;
719
+ }
623
720
  return functionComment + generateStreamFunction(
624
721
  method,
625
722
  path,
@@ -732,19 +829,60 @@ function generateStreamEventTypeDefinitions() {
732
829
  typeDefs += "// Stream Event Type Definitions (Fully Typed!)\n";
733
830
  typeDefs += "// ============================================================================\n\n";
734
831
  for (const [typeName, events] of Object.entries(streamEventTypes)) {
832
+ const eventInfo = streamEventInfo[typeName];
735
833
  typeDefs += `/**
736
834
  * Event types for ${typeName.replace("StreamEvents", "")} stream
737
835
  `;
738
836
  typeDefs += ` * Events: ${events.map((e) => `"${e}"`).join(", ")}
739
- */
837
+ `;
838
+ if (eventInfo?.length) {
839
+ typeDefs += ` *
840
+ `;
841
+ for (const info of eventInfo) {
842
+ typeDefs += ` * - **${info.name}**: ${info.description || "Event data"}
843
+ `;
844
+ }
845
+ }
846
+ typeDefs += ` */
740
847
  `;
741
848
  typeDefs += `export interface ${typeName} {
742
849
  `;
743
- for (const event of events) {
744
- typeDefs += ` /** ${event} event data */
850
+ if (eventInfo?.length) {
851
+ for (const info of eventInfo) {
852
+ typeDefs += ` /**
853
+ * ${info.description || info.name}
745
854
  `;
746
- typeDefs += ` ${event}: any // TODO: Define specific type from OpenAPI schema
855
+ if (info.fields?.length) {
856
+ info.fields.forEach((field) => {
857
+ typeDefs += ` * - \`${field.name}\`: ${field.description || "Field data"}
747
858
  `;
859
+ });
860
+ }
861
+ typeDefs += ` */
862
+ `;
863
+ if (info.fields?.length) {
864
+ typeDefs += ` ${info.name}: {
865
+ `;
866
+ for (const field of info.fields) {
867
+ typeDefs += ` /** ${field.description || field.name} */
868
+ `;
869
+ typeDefs += ` ${field.name}: any
870
+ `;
871
+ }
872
+ typeDefs += ` }
873
+ `;
874
+ } else {
875
+ typeDefs += ` ${info.name}: any
876
+ `;
877
+ }
878
+ }
879
+ } else {
880
+ for (const event of events) {
881
+ typeDefs += ` /** ${event} event data */
882
+ `;
883
+ typeDefs += ` ${event}: any
884
+ `;
885
+ }
748
886
  }
749
887
  typeDefs += "}\n\n";
750
888
  }
@@ -1573,4 +1711,4 @@ class Bagel {
1573
1711
  }
1574
1712
  }
1575
1713
 
1576
- export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, index as openAPI };
1714
+ export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventInfo, 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.104",
4
+ "version": "1.8.5",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable jsdoc/check-param-names */
4
4
  /* eslint-disable ts/no-non-null-assertion */
5
5
  /* eslint-disable prefer-destructuring */
6
+ import type { SSEEventTypeInfo } from './streamDetector'
6
7
  import type {
7
8
  OperationObject as OpenAPIOperation,
8
9
  PathItemObject as OpenAPIPath,
@@ -13,8 +14,7 @@ import type {
13
14
  ReferenceObject,
14
15
  SchemaObject,
15
16
  } from './types'
16
- import { isSSEStream, extractSSEEventTypes } from './streamDetector'
17
-
17
+ import { isSSEStream, extractSSEEventTypes, extractSSEEventInfo } from './streamDetector'
18
18
  import { dereference, isReferenceObject } from './types/utils'
19
19
  import {
20
20
  cleanPath,
@@ -63,6 +63,7 @@ export interface PathOperation {
63
63
  const functionsInventory: Record<string, string> = {}
64
64
  const pathOperations: PathOperation[] = []
65
65
  const streamEventTypes: Record<string, string[]> = {} // Track stream endpoints and their event types
66
+ const streamEventInfo: Record<string, SSEEventTypeInfo[]> = {} // Track detailed event info for type generation
66
67
 
67
68
  /**
68
69
  * Collects non-primitive types for import statements
@@ -404,6 +405,12 @@ function generateStreamFunction(
404
405
  ? `StreamController<${streamTypeName}>`
405
406
  : 'StreamController'
406
407
 
408
+ // Convert formattedPath from string representation to actual path for URL construction
409
+ // formattedPath can be either 'path' or `path/${param}` (as strings)
410
+ const pathForUrl = formattedPath.startsWith('`')
411
+ ? formattedPath.slice(1, -1) // Remove backticks to get the content
412
+ : formattedPath.slice(1, -1) // Remove quotes to get the content
413
+
407
414
  if (method === 'post') {
408
415
  return `{
409
416
  /**
@@ -418,7 +425,7 @@ function generateStreamFunction(
418
425
  * stream.close()
419
426
  */
420
427
  stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
421
- const url = \`\${${baseUrlRef}}${formattedPath}\`
428
+ const url = \`\${${baseUrlRef}}${pathForUrl}\`
422
429
  return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
423
430
  },
424
431
  /**
@@ -442,7 +449,7 @@ function generateStreamFunction(
442
449
  * stream.close()
443
450
  */
444
451
  stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
445
- const url = \`\${${baseUrlRef}}${formattedPath}\`
452
+ const url = \`\${${baseUrlRef}}${pathForUrl}\`
446
453
  return createSSEStream<${streamTypeName}>(url, options)
447
454
  },
448
455
  /**
@@ -615,6 +622,14 @@ function generateFunctionForOperation(
615
622
  // Generate stream function for SSE endpoints
616
623
  if (isStream) {
617
624
  const eventTypes = extractSSEEventTypes(operation)
625
+ const eventInfo = extractSSEEventInfo(operation)
626
+
627
+ // Store detailed event info for type generation
628
+ const streamTypeName = generateStreamTypeName(path)
629
+ if (eventInfo?.length) {
630
+ streamEventInfo[streamTypeName] = eventInfo
631
+ }
632
+
618
633
  return functionComment + generateStreamFunction(
619
634
  method,
620
635
  path,
@@ -801,6 +816,7 @@ export const ${parent} = ${JSON.stringify(object, undefined, 2)};\n`
801
816
 
802
817
  /**
803
818
  * Generates TypeScript type definitions for stream events
819
+ * Uses detailed event info if available, falls back to simple event list
804
820
  * @returns TypeScript type definitions string
805
821
  */
806
822
  function generateStreamEventTypeDefinitions(): string {
@@ -813,13 +829,53 @@ function generateStreamEventTypeDefinitions(): string {
813
829
  typeDefs += '// ============================================================================\n\n'
814
830
 
815
831
  for (const [typeName, events] of Object.entries(streamEventTypes)) {
832
+ const eventInfo = streamEventInfo[typeName]
833
+
816
834
  typeDefs += `/**\n * Event types for ${typeName.replace('StreamEvents', '')} stream\n`
817
- typeDefs += ` * Events: ${events.map(e => `"${e}"`).join(', ')}\n */\n`
835
+ typeDefs += ` * Events: ${events.map(e => `"${e}"`).join(', ')}\n`
836
+
837
+ // Add event descriptions if available
838
+ if (eventInfo?.length) {
839
+ typeDefs += ` *\n`
840
+ for (const info of eventInfo) {
841
+ typeDefs += ` * - **${info.name}**: ${info.description || 'Event data'}\n`
842
+ }
843
+ }
844
+
845
+ typeDefs += ` */\n`
818
846
  typeDefs += `export interface ${typeName} {\n`
819
847
 
820
- for (const event of events) {
821
- typeDefs += ` /** ${event} event data */\n`
822
- typeDefs += ` ${event}: any // TODO: Define specific type from OpenAPI schema\n`
848
+ // Generate event type definitions with field information if available
849
+ if (eventInfo?.length) {
850
+ for (const info of eventInfo) {
851
+ typeDefs += ` /**\n * ${info.description || info.name}\n`
852
+
853
+ if (info.fields?.length) {
854
+ info.fields.forEach((field) => {
855
+ typeDefs += ` * - \`${field.name}\`: ${field.description || 'Field data'}\n`
856
+ })
857
+ }
858
+
859
+ typeDefs += ` */\n`
860
+
861
+ // Generate interface for this event
862
+ if (info.fields?.length) {
863
+ typeDefs += ` ${info.name}: {\n`
864
+ for (const field of info.fields) {
865
+ typeDefs += ` /** ${field.description || field.name} */\n`
866
+ typeDefs += ` ${field.name}: any\n`
867
+ }
868
+ typeDefs += ` }\n`
869
+ } else {
870
+ typeDefs += ` ${info.name}: any\n`
871
+ }
872
+ }
873
+ } else {
874
+ // Fallback: simple event list without field info
875
+ for (const event of events) {
876
+ typeDefs += ` /** ${event} event data */\n`
877
+ typeDefs += ` ${event}: any\n`
878
+ }
823
879
  }
824
880
 
825
881
  typeDefs += '}\n\n'
@@ -2,43 +2,214 @@ import type { OperationObject as OpenAPIOperation } from './types'
2
2
 
3
3
  /**
4
4
  * Detects if an operation is an SSE (Server-Sent Events) stream endpoint
5
+ *
6
+ * Primary detection: Checks for text/event-stream content type in responses
7
+ * Fallback: Only if no content type is specified, check for explicit SSE keywords
8
+ *
5
9
  * @param operation - The OpenAPI operation object
6
10
  * @returns Whether the operation is an SSE stream
7
11
  */
8
12
  export function isSSEStream(operation: OpenAPIOperation): boolean {
9
- const description = operation.description?.toLowerCase() || ''
10
- const summary = operation.summary?.toLowerCase() || ''
13
+ const responses = operation.responses || {}
11
14
 
12
- // Check for SSE indicators in description or summary
13
- const hasSSEKeywords
14
- = description.includes('sse')
15
- || description.includes('server-sent events')
16
- || description.includes('event stream')
17
- || summary.includes('stream')
15
+ // Check all response codes (200, 201, etc.)
16
+ for (const [statusCode, response] of Object.entries(responses)) {
17
+ // Only check successful responses (2xx)
18
+ if (!statusCode.startsWith('2')) continue
18
19
 
19
- // Check for text/event-stream content type in responses
20
- const responses = operation.responses || {}
21
- const hasEventStreamContentType = Object.values(responses).some((response: any) => {
22
- if ('content' in response) {
23
- return 'text/event-stream' in (response.content || {})
20
+ // Skip reference objects for now
21
+ if ('$ref' in response) continue
22
+
23
+ const content = (response as any).content || {}
24
+ const contentTypes = Object.keys(content)
25
+
26
+ // If response has content types defined
27
+ if (contentTypes.length > 0) {
28
+ // It's a stream if text/event-stream is present
29
+ // (even if application/json is also present as a fallback)
30
+ if ('text/event-stream' in content) {
31
+ return true
32
+ }
24
33
  }
25
- return false
34
+ }
35
+
36
+ // If we checked responses and found only application/json (no text/event-stream), it's not a stream
37
+ const hasDefinedContentTypes = Object.entries(responses).some(([statusCode, response]) => {
38
+ if (!statusCode.startsWith('2')) return false
39
+ if ('$ref' in response) return false
40
+ const content = (response as any).content || {}
41
+ return Object.keys(content).length > 0
26
42
  })
27
43
 
28
- return hasSSEKeywords || hasEventStreamContentType
44
+ if (hasDefinedContentTypes) {
45
+ return false
46
+ }
47
+
48
+ // Fallback: If no content types are defined at all, check description
49
+ // (Some OpenAPI specs might not properly define response content types)
50
+ const description = operation.description?.toLowerCase() || ''
51
+ const summary = operation.summary?.toLowerCase() || ''
52
+
53
+ const hasExplicitSSEMention
54
+ = /\bsse\b/.test(description)
55
+ || /server-sent events/i.test(description)
56
+ || /text\/event-stream/i.test(description)
57
+ || /\bsse\b/.test(summary)
58
+
59
+ return hasExplicitSSEMention
29
60
  }
30
61
 
31
62
  /**
32
- * Extracts SSE event types from operation description
63
+ * Information about an SSE event type including its fields
64
+ */
65
+ export interface SSEEventTypeInfo {
66
+ /** Event type name */
67
+ name: string
68
+ /** Event description */
69
+ description?: string
70
+ /** Fields in the event data */
71
+ fields?: Array<{
72
+ name: string
73
+ description?: string
74
+ type?: string
75
+ }>
76
+ }
77
+
78
+ /**
79
+ * Extracts SSE event types from operation description and examples
80
+ *
81
+ * Supports multiple documentation formats:
82
+ * 1. Markdown format: "1. **event_name**: Description"
83
+ * 2. Legacy format: type: "event_name"
84
+ * 3. SSE examples: event: event_name
85
+ *
33
86
  * @param operation - The OpenAPI operation object
34
87
  * @returns Array of event types or undefined
35
88
  */
36
89
  export function extractSSEEventTypes(operation: OpenAPIOperation): string[] | undefined {
37
90
  const description = operation.description || ''
91
+ const types = new Set<string>()
92
+
93
+ // Method 1: Extract from markdown numbered lists with bold event names
94
+ // Pattern: "1. **event_name**: Description"
95
+ const markdownMatches = description.matchAll(/^\d+\.\s+\*\*([a-z_]+)\*\*/gm)
96
+ for (const match of markdownMatches) {
97
+ types.add(match[1])
98
+ }
38
99
 
39
- // Look for patterns like: type: "token", type: "done", etc.
100
+ // Method 2: Legacy format - type: "event_name"
40
101
  const typeMatches = description.matchAll(/type:\s*"([^"]+)"/g)
41
- const types = Array.from(typeMatches).map(match => match[1])
102
+ for (const match of typeMatches) {
103
+ types.add(match[1])
104
+ }
105
+
106
+ // Method 3: Extract from SSE examples in response content
107
+ const responses = operation.responses || {}
108
+ for (const response of Object.values(responses)) {
109
+ if ('content' in response) {
110
+ const content = (response as any).content || {}
111
+ const eventStream = content['text/event-stream']
112
+ if (eventStream?.example) {
113
+ // Parse SSE format: event: event_name
114
+ const exampleMatches = eventStream.example.matchAll(/^event:\s*([a-z_]+)/gm)
115
+ for (const match of exampleMatches) {
116
+ types.add(match[1])
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ return types.size > 0 ? Array.from(types).sort() : undefined
123
+ }
124
+
125
+ /**
126
+ * Extracts detailed SSE event information including field definitions
127
+ *
128
+ * Parses markdown documentation format:
129
+ * ```
130
+ * 1. **event_name**: Event description
131
+ * - `field_name`: Field description
132
+ * - `another_field`: Another description
133
+ * ```
134
+ *
135
+ * @param operation - The OpenAPI operation object
136
+ * @returns Array of event type information with fields
137
+ */
138
+ export function extractSSEEventInfo(operation: OpenAPIOperation): SSEEventTypeInfo[] | undefined {
139
+ const description = operation.description || ''
140
+ const eventInfos: SSEEventTypeInfo[] = []
141
+
142
+ // Split description into sections by numbered events
143
+ const eventSections = description.split(/(?=^\d+\.\s+\*\*)/m)
144
+
145
+ for (const section of eventSections) {
146
+ // Extract event name and description
147
+ const eventMatch = section.match(/^\d+\.\s+\*\*([a-z_]+)\*\*:\s*([^\n]+)/m)
148
+ if (!eventMatch) continue
149
+
150
+ const [, eventName, eventDescription] = eventMatch
151
+
152
+ // Extract fields from the section
153
+ const fields: Array<{ name: string, description?: string, type?: string }> = []
154
+ const fieldMatches = section.matchAll(/^\s+[-*]\s+`([^`]+)`:\s*([^\n]+)/gm)
155
+
156
+ for (const fieldMatch of fieldMatches) {
157
+ let [, fieldName, fieldDescription] = fieldMatch
158
+
159
+ // Handle nested field names like "error.code" → extract just "code"
160
+ // This handles documentation that shows nested object paths
161
+ if (fieldName.includes('.')) {
162
+ const parts = fieldName.split('.')
163
+ fieldName = parts[parts.length - 1] // Take the last part
164
+ }
165
+
166
+ fields.push({
167
+ name: fieldName,
168
+ description: fieldDescription.trim(),
169
+ })
170
+ }
171
+
172
+ eventInfos.push({
173
+ name: eventName,
174
+ description: eventDescription,
175
+ fields: fields.length > 0 ? fields : undefined,
176
+ })
177
+ }
178
+
179
+ // If no markdown events found, try to extract from examples
180
+ if (eventInfos.length === 0) {
181
+ const responses = operation.responses || {}
182
+ for (const response of Object.values(responses)) {
183
+ if ('content' in response) {
184
+ const content = (response as any).content || {}
185
+ const eventStream = content['text/event-stream']
186
+ if (eventStream?.example) {
187
+ // Parse example JSON to extract field names
188
+ const exampleMatches = eventStream.example.matchAll(/data:\s*(\{[^}]+\})/g)
189
+ const seenEvents = new Set<string>()
190
+
191
+ for (const match of exampleMatches) {
192
+ try {
193
+ const data = JSON.parse(match[1])
194
+ if (data.type && !seenEvents.has(data.type)) {
195
+ seenEvents.add(data.type)
196
+ const fields = Object.keys(data)
197
+ .filter(key => key !== 'type' && key !== 'sequence')
198
+ .map(key => ({ name: key }))
199
+
200
+ eventInfos.push({
201
+ name: data.type,
202
+ fields: fields.length > 0 ? fields : undefined,
203
+ })
204
+ }
205
+ } catch {
206
+ // Skip invalid JSON
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
42
213
 
43
- return types.length > 0 ? types : undefined
214
+ return eventInfos.length > 0 ? eventInfos : undefined
44
215
  }