@bagelink/sdk 1.7.104 → 1.8.3
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 +151 -16
- package/dist/index.d.cts +42 -3
- package/dist/index.d.mts +42 -3
- package/dist/index.d.ts +42 -3
- package/dist/index.mjs +151 -17
- package/package.json +1 -1
- package/src/openAPITools/functionGenerator.ts +64 -8
- package/src/openAPITools/streamDetector.ts +182 -19
package/dist/index.cjs
CHANGED
|
@@ -7,23 +7,109 @@ 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
|
|
15
|
-
if ("
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
26
|
-
|
|
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
|
+
const [, fieldName, fieldDescription] = fieldMatch;
|
|
74
|
+
fields.push({
|
|
75
|
+
name: fieldName,
|
|
76
|
+
description: fieldDescription.trim()
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
eventInfos.push({
|
|
80
|
+
name: eventName,
|
|
81
|
+
description: eventDescription,
|
|
82
|
+
fields: fields.length > 0 ? fields : void 0
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (eventInfos.length === 0) {
|
|
86
|
+
const responses = operation.responses || {};
|
|
87
|
+
for (const response of Object.values(responses)) {
|
|
88
|
+
if ("content" in response) {
|
|
89
|
+
const content = response.content || {};
|
|
90
|
+
const eventStream = content["text/event-stream"];
|
|
91
|
+
if (eventStream?.example) {
|
|
92
|
+
const exampleMatches = eventStream.example.matchAll(/data:\s*(\{[^}]+\})/g);
|
|
93
|
+
const seenEvents = /* @__PURE__ */ new Set();
|
|
94
|
+
for (const match of exampleMatches) {
|
|
95
|
+
try {
|
|
96
|
+
const data = JSON.parse(match[1]);
|
|
97
|
+
if (data.type && !seenEvents.has(data.type)) {
|
|
98
|
+
seenEvents.add(data.type);
|
|
99
|
+
const fields = Object.keys(data).filter((key) => key !== "type" && key !== "sequence").map((key) => ({ name: key }));
|
|
100
|
+
eventInfos.push({
|
|
101
|
+
name: data.type,
|
|
102
|
+
fields: fields.length > 0 ? fields : void 0
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return eventInfos.length > 0 ? eventInfos : void 0;
|
|
27
113
|
}
|
|
28
114
|
|
|
29
115
|
function getPath(pathsObject, path) {
|
|
@@ -305,6 +391,7 @@ const primitiveTypes = [
|
|
|
305
391
|
const functionsInventory = {};
|
|
306
392
|
const pathOperations = [];
|
|
307
393
|
const streamEventTypes = {};
|
|
394
|
+
const streamEventInfo = {};
|
|
308
395
|
function collectTypeForImportStatement(typeName) {
|
|
309
396
|
typeName = typeName.trim().replace("[]", "");
|
|
310
397
|
if (typeName.includes("|")) {
|
|
@@ -488,6 +575,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
488
575
|
const eventTypesExample = eventTypes?.length ? eventTypes.map((t) => `
|
|
489
576
|
* .on('${t}', (data) => console.log(data))`).join("") : "\n * .on('message', (data) => console.log(data))";
|
|
490
577
|
const typeAnnotation = eventTypes?.length ? `StreamController<${streamTypeName}>` : "StreamController";
|
|
578
|
+
const pathForUrl = formattedPath.startsWith("`") ? formattedPath.slice(1, -1) : formattedPath.slice(1, -1);
|
|
491
579
|
if (method === "post") {
|
|
492
580
|
return `{
|
|
493
581
|
/**
|
|
@@ -502,7 +590,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
502
590
|
* stream.close()
|
|
503
591
|
*/
|
|
504
592
|
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
505
|
-
const url = \`\${${baseUrlRef}}${
|
|
593
|
+
const url = \`\${${baseUrlRef}}${pathForUrl}\`
|
|
506
594
|
return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
|
|
507
595
|
},
|
|
508
596
|
/**
|
|
@@ -526,7 +614,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
526
614
|
* stream.close()
|
|
527
615
|
*/
|
|
528
616
|
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
529
|
-
const url = \`\${${baseUrlRef}}${
|
|
617
|
+
const url = \`\${${baseUrlRef}}${pathForUrl}\`
|
|
530
618
|
return createSSEStream<${streamTypeName}>(url, options)
|
|
531
619
|
},
|
|
532
620
|
/**
|
|
@@ -626,6 +714,11 @@ function generateFunctionForOperation(method, path, operation) {
|
|
|
626
714
|
const functionComment = buildJSDocComment(operation, method, path);
|
|
627
715
|
if (isStream) {
|
|
628
716
|
const eventTypes = extractSSEEventTypes(operation);
|
|
717
|
+
const eventInfo = extractSSEEventInfo(operation);
|
|
718
|
+
const streamTypeName = generateStreamTypeName(path);
|
|
719
|
+
if (eventInfo?.length) {
|
|
720
|
+
streamEventInfo[streamTypeName] = eventInfo;
|
|
721
|
+
}
|
|
629
722
|
return functionComment + generateStreamFunction(
|
|
630
723
|
method,
|
|
631
724
|
path,
|
|
@@ -738,19 +831,60 @@ function generateStreamEventTypeDefinitions() {
|
|
|
738
831
|
typeDefs += "// Stream Event Type Definitions (Fully Typed!)\n";
|
|
739
832
|
typeDefs += "// ============================================================================\n\n";
|
|
740
833
|
for (const [typeName, events] of Object.entries(streamEventTypes)) {
|
|
834
|
+
const eventInfo = streamEventInfo[typeName];
|
|
741
835
|
typeDefs += `/**
|
|
742
836
|
* Event types for ${typeName.replace("StreamEvents", "")} stream
|
|
743
837
|
`;
|
|
744
838
|
typeDefs += ` * Events: ${events.map((e) => `"${e}"`).join(", ")}
|
|
745
|
-
|
|
839
|
+
`;
|
|
840
|
+
if (eventInfo?.length) {
|
|
841
|
+
typeDefs += ` *
|
|
842
|
+
`;
|
|
843
|
+
for (const info of eventInfo) {
|
|
844
|
+
typeDefs += ` * - **${info.name}**: ${info.description || "Event data"}
|
|
845
|
+
`;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
typeDefs += ` */
|
|
746
849
|
`;
|
|
747
850
|
typeDefs += `export interface ${typeName} {
|
|
748
851
|
`;
|
|
749
|
-
|
|
750
|
-
|
|
852
|
+
if (eventInfo?.length) {
|
|
853
|
+
for (const info of eventInfo) {
|
|
854
|
+
typeDefs += ` /**
|
|
855
|
+
* ${info.description || info.name}
|
|
856
|
+
`;
|
|
857
|
+
if (info.fields?.length) {
|
|
858
|
+
info.fields.forEach((field) => {
|
|
859
|
+
typeDefs += ` * - \`${field.name}\`: ${field.description || "Field data"}
|
|
860
|
+
`;
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
typeDefs += ` */
|
|
864
|
+
`;
|
|
865
|
+
if (info.fields?.length) {
|
|
866
|
+
typeDefs += ` ${info.name}: {
|
|
867
|
+
`;
|
|
868
|
+
for (const field of info.fields) {
|
|
869
|
+
typeDefs += ` /** ${field.description || field.name} */
|
|
870
|
+
`;
|
|
871
|
+
typeDefs += ` ${field.name}: any
|
|
872
|
+
`;
|
|
873
|
+
}
|
|
874
|
+
typeDefs += ` }
|
|
751
875
|
`;
|
|
752
|
-
|
|
876
|
+
} else {
|
|
877
|
+
typeDefs += ` ${info.name}: any
|
|
753
878
|
`;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
} else {
|
|
882
|
+
for (const event of events) {
|
|
883
|
+
typeDefs += ` /** ${event} event data */
|
|
884
|
+
`;
|
|
885
|
+
typeDefs += ` ${event}: any
|
|
886
|
+
`;
|
|
887
|
+
}
|
|
754
888
|
}
|
|
755
889
|
typeDefs += "}\n\n";
|
|
756
890
|
}
|
|
@@ -1584,6 +1718,7 @@ exports.StreamController = StreamController;
|
|
|
1584
1718
|
exports.createSSEStream = createSSEStream;
|
|
1585
1719
|
exports.createSSEStreamPost = createSSEStreamPost;
|
|
1586
1720
|
exports.dereference = dereference;
|
|
1721
|
+
exports.extractSSEEventInfo = extractSSEEventInfo;
|
|
1587
1722
|
exports.extractSSEEventTypes = extractSSEEventTypes;
|
|
1588
1723
|
exports.formatAPIErrorMessage = formatAPIErrorMessage;
|
|
1589
1724
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,109 @@
|
|
|
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
|
|
9
|
-
if ("
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
20
|
-
|
|
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
|
+
const [, fieldName, fieldDescription] = fieldMatch;
|
|
68
|
+
fields.push({
|
|
69
|
+
name: fieldName,
|
|
70
|
+
description: fieldDescription.trim()
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
eventInfos.push({
|
|
74
|
+
name: eventName,
|
|
75
|
+
description: eventDescription,
|
|
76
|
+
fields: fields.length > 0 ? fields : void 0
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (eventInfos.length === 0) {
|
|
80
|
+
const responses = operation.responses || {};
|
|
81
|
+
for (const response of Object.values(responses)) {
|
|
82
|
+
if ("content" in response) {
|
|
83
|
+
const content = response.content || {};
|
|
84
|
+
const eventStream = content["text/event-stream"];
|
|
85
|
+
if (eventStream?.example) {
|
|
86
|
+
const exampleMatches = eventStream.example.matchAll(/data:\s*(\{[^}]+\})/g);
|
|
87
|
+
const seenEvents = /* @__PURE__ */ new Set();
|
|
88
|
+
for (const match of exampleMatches) {
|
|
89
|
+
try {
|
|
90
|
+
const data = JSON.parse(match[1]);
|
|
91
|
+
if (data.type && !seenEvents.has(data.type)) {
|
|
92
|
+
seenEvents.add(data.type);
|
|
93
|
+
const fields = Object.keys(data).filter((key) => key !== "type" && key !== "sequence").map((key) => ({ name: key }));
|
|
94
|
+
eventInfos.push({
|
|
95
|
+
name: data.type,
|
|
96
|
+
fields: fields.length > 0 ? fields : void 0
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return eventInfos.length > 0 ? eventInfos : void 0;
|
|
21
107
|
}
|
|
22
108
|
|
|
23
109
|
function getPath(pathsObject, path) {
|
|
@@ -299,6 +385,7 @@ const primitiveTypes = [
|
|
|
299
385
|
const functionsInventory = {};
|
|
300
386
|
const pathOperations = [];
|
|
301
387
|
const streamEventTypes = {};
|
|
388
|
+
const streamEventInfo = {};
|
|
302
389
|
function collectTypeForImportStatement(typeName) {
|
|
303
390
|
typeName = typeName.trim().replace("[]", "");
|
|
304
391
|
if (typeName.includes("|")) {
|
|
@@ -482,6 +569,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
482
569
|
const eventTypesExample = eventTypes?.length ? eventTypes.map((t) => `
|
|
483
570
|
* .on('${t}', (data) => console.log(data))`).join("") : "\n * .on('message', (data) => console.log(data))";
|
|
484
571
|
const typeAnnotation = eventTypes?.length ? `StreamController<${streamTypeName}>` : "StreamController";
|
|
572
|
+
const pathForUrl = formattedPath.startsWith("`") ? formattedPath.slice(1, -1) : formattedPath.slice(1, -1);
|
|
485
573
|
if (method === "post") {
|
|
486
574
|
return `{
|
|
487
575
|
/**
|
|
@@ -496,7 +584,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
496
584
|
* stream.close()
|
|
497
585
|
*/
|
|
498
586
|
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
499
|
-
const url = \`\${${baseUrlRef}}${
|
|
587
|
+
const url = \`\${${baseUrlRef}}${pathForUrl}\`
|
|
500
588
|
return createSSEStreamPost<${streamTypeName}>(url, ${bodyVar}, options)
|
|
501
589
|
},
|
|
502
590
|
/**
|
|
@@ -520,7 +608,7 @@ function generateStreamFunction(method, path, formattedPath, allParams, requestB
|
|
|
520
608
|
* stream.close()
|
|
521
609
|
*/
|
|
522
610
|
stream: (${allParams}, options?: SSEStreamOptions): ${typeAnnotation} => {
|
|
523
|
-
const url = \`\${${baseUrlRef}}${
|
|
611
|
+
const url = \`\${${baseUrlRef}}${pathForUrl}\`
|
|
524
612
|
return createSSEStream<${streamTypeName}>(url, options)
|
|
525
613
|
},
|
|
526
614
|
/**
|
|
@@ -620,6 +708,11 @@ function generateFunctionForOperation(method, path, operation) {
|
|
|
620
708
|
const functionComment = buildJSDocComment(operation, method, path);
|
|
621
709
|
if (isStream) {
|
|
622
710
|
const eventTypes = extractSSEEventTypes(operation);
|
|
711
|
+
const eventInfo = extractSSEEventInfo(operation);
|
|
712
|
+
const streamTypeName = generateStreamTypeName(path);
|
|
713
|
+
if (eventInfo?.length) {
|
|
714
|
+
streamEventInfo[streamTypeName] = eventInfo;
|
|
715
|
+
}
|
|
623
716
|
return functionComment + generateStreamFunction(
|
|
624
717
|
method,
|
|
625
718
|
path,
|
|
@@ -732,19 +825,60 @@ function generateStreamEventTypeDefinitions() {
|
|
|
732
825
|
typeDefs += "// Stream Event Type Definitions (Fully Typed!)\n";
|
|
733
826
|
typeDefs += "// ============================================================================\n\n";
|
|
734
827
|
for (const [typeName, events] of Object.entries(streamEventTypes)) {
|
|
828
|
+
const eventInfo = streamEventInfo[typeName];
|
|
735
829
|
typeDefs += `/**
|
|
736
830
|
* Event types for ${typeName.replace("StreamEvents", "")} stream
|
|
737
831
|
`;
|
|
738
832
|
typeDefs += ` * Events: ${events.map((e) => `"${e}"`).join(", ")}
|
|
739
|
-
|
|
833
|
+
`;
|
|
834
|
+
if (eventInfo?.length) {
|
|
835
|
+
typeDefs += ` *
|
|
836
|
+
`;
|
|
837
|
+
for (const info of eventInfo) {
|
|
838
|
+
typeDefs += ` * - **${info.name}**: ${info.description || "Event data"}
|
|
839
|
+
`;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
typeDefs += ` */
|
|
740
843
|
`;
|
|
741
844
|
typeDefs += `export interface ${typeName} {
|
|
742
845
|
`;
|
|
743
|
-
|
|
744
|
-
|
|
846
|
+
if (eventInfo?.length) {
|
|
847
|
+
for (const info of eventInfo) {
|
|
848
|
+
typeDefs += ` /**
|
|
849
|
+
* ${info.description || info.name}
|
|
850
|
+
`;
|
|
851
|
+
if (info.fields?.length) {
|
|
852
|
+
info.fields.forEach((field) => {
|
|
853
|
+
typeDefs += ` * - \`${field.name}\`: ${field.description || "Field data"}
|
|
854
|
+
`;
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
typeDefs += ` */
|
|
858
|
+
`;
|
|
859
|
+
if (info.fields?.length) {
|
|
860
|
+
typeDefs += ` ${info.name}: {
|
|
861
|
+
`;
|
|
862
|
+
for (const field of info.fields) {
|
|
863
|
+
typeDefs += ` /** ${field.description || field.name} */
|
|
864
|
+
`;
|
|
865
|
+
typeDefs += ` ${field.name}: any
|
|
866
|
+
`;
|
|
867
|
+
}
|
|
868
|
+
typeDefs += ` }
|
|
745
869
|
`;
|
|
746
|
-
|
|
870
|
+
} else {
|
|
871
|
+
typeDefs += ` ${info.name}: any
|
|
747
872
|
`;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
} else {
|
|
876
|
+
for (const event of events) {
|
|
877
|
+
typeDefs += ` /** ${event} event data */
|
|
878
|
+
`;
|
|
879
|
+
typeDefs += ` ${event}: any
|
|
880
|
+
`;
|
|
881
|
+
}
|
|
748
882
|
}
|
|
749
883
|
typeDefs += "}\n\n";
|
|
750
884
|
}
|
|
@@ -1573,4 +1707,4 @@ class Bagel {
|
|
|
1573
1707
|
}
|
|
1574
1708
|
}
|
|
1575
1709
|
|
|
1576
|
-
export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, index as openAPI };
|
|
1710
|
+
export { Bagel, StreamController, createSSEStream, createSSEStreamPost, dereference, extractSSEEventInfo, extractSSEEventTypes, formatAPIErrorMessage, getPath, isReferenceObject, isSSEStream, isSchemaObject, index as openAPI };
|
package/package.json
CHANGED
|
@@ -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}}${
|
|
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}}${
|
|
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
|
|
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
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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,206 @@ 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
|
|
10
|
-
const summary = operation.summary?.toLowerCase() || ''
|
|
13
|
+
const responses = operation.responses || {}
|
|
11
14
|
|
|
12
|
-
// Check
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
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
|
+
}>
|
|
29
76
|
}
|
|
30
77
|
|
|
31
78
|
/**
|
|
32
|
-
* Extracts SSE event types from operation description
|
|
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>()
|
|
38
92
|
|
|
39
|
-
//
|
|
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
|
+
}
|
|
99
|
+
|
|
100
|
+
// Method 2: Legacy format - type: "event_name"
|
|
40
101
|
const typeMatches = description.matchAll(/type:\s*"([^"]+)"/g)
|
|
41
|
-
const
|
|
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
|
+
const [, fieldName, fieldDescription] = fieldMatch
|
|
158
|
+
fields.push({
|
|
159
|
+
name: fieldName,
|
|
160
|
+
description: fieldDescription.trim(),
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
eventInfos.push({
|
|
165
|
+
name: eventName,
|
|
166
|
+
description: eventDescription,
|
|
167
|
+
fields: fields.length > 0 ? fields : undefined,
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If no markdown events found, try to extract from examples
|
|
172
|
+
if (eventInfos.length === 0) {
|
|
173
|
+
const responses = operation.responses || {}
|
|
174
|
+
for (const response of Object.values(responses)) {
|
|
175
|
+
if ('content' in response) {
|
|
176
|
+
const content = (response as any).content || {}
|
|
177
|
+
const eventStream = content['text/event-stream']
|
|
178
|
+
if (eventStream?.example) {
|
|
179
|
+
// Parse example JSON to extract field names
|
|
180
|
+
const exampleMatches = eventStream.example.matchAll(/data:\s*(\{[^}]+\})/g)
|
|
181
|
+
const seenEvents = new Set<string>()
|
|
182
|
+
|
|
183
|
+
for (const match of exampleMatches) {
|
|
184
|
+
try {
|
|
185
|
+
const data = JSON.parse(match[1])
|
|
186
|
+
if (data.type && !seenEvents.has(data.type)) {
|
|
187
|
+
seenEvents.add(data.type)
|
|
188
|
+
const fields = Object.keys(data)
|
|
189
|
+
.filter(key => key !== 'type' && key !== 'sequence')
|
|
190
|
+
.map(key => ({ name: key }))
|
|
191
|
+
|
|
192
|
+
eventInfos.push({
|
|
193
|
+
name: data.type,
|
|
194
|
+
fields: fields.length > 0 ? fields : undefined,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
// Skip invalid JSON
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
42
205
|
|
|
43
|
-
return
|
|
206
|
+
return eventInfos.length > 0 ? eventInfos : undefined
|
|
44
207
|
}
|