@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 +155 -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 +155 -17
- package/package.json +1 -1
- package/src/openAPITools/functionGenerator.ts +64 -8
- package/src/openAPITools/streamDetector.ts +190 -19
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
|
|
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
|
+
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}}${
|
|
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}}${
|
|
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
|
-
|
|
750
|
-
|
|
856
|
+
if (eventInfo?.length) {
|
|
857
|
+
for (const info of eventInfo) {
|
|
858
|
+
typeDefs += ` /**
|
|
859
|
+
* ${info.description || info.name}
|
|
751
860
|
`;
|
|
752
|
-
|
|
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
|
-
*
|
|
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,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
|
|
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
|
+
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}}${
|
|
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}}${
|
|
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
|
-
|
|
744
|
-
|
|
850
|
+
if (eventInfo?.length) {
|
|
851
|
+
for (const info of eventInfo) {
|
|
852
|
+
typeDefs += ` /**
|
|
853
|
+
* ${info.description || info.name}
|
|
745
854
|
`;
|
|
746
|
-
|
|
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
|
@@ -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,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
|
|
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
|
|
29
60
|
}
|
|
30
61
|
|
|
31
62
|
/**
|
|
32
|
-
*
|
|
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
|
-
//
|
|
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
|
+
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
|
|
214
|
+
return eventInfos.length > 0 ? eventInfos : undefined
|
|
44
215
|
}
|