@avtechno/sfr 2.0.9 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +34 -0
- package/dist/sfr-pipeline.mjs +91 -2
- package/dist/types/sfr-pipeline.d.mts +9 -0
- package/package.json +1 -1
- package/src/index.mts +41 -0
- package/src/sfr-pipeline.mts +106 -3
package/dist/index.mjs
CHANGED
|
@@ -22,6 +22,8 @@ async function write_service_discovery(cfg, documents) {
|
|
|
22
22
|
//Setup files in case they do not exist.
|
|
23
23
|
//Setup output folder
|
|
24
24
|
await fs.mkdir(path.join(cwd, cfg.out), { recursive: true });
|
|
25
|
+
// Write combined OpenAPI index file containing all protocols
|
|
26
|
+
await write_combined_openapi_index(cfg, documents);
|
|
25
27
|
// Loop over each protocol
|
|
26
28
|
for (let [protocol, documentation] of Object.entries(documents)) {
|
|
27
29
|
//Strictly await for setup to create dirs for each protocol.
|
|
@@ -51,6 +53,38 @@ async function write_service_discovery(cfg, documents) {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
}
|
|
56
|
+
// Writes a combined openapi.yml index file containing all generated documentation
|
|
57
|
+
async function write_combined_openapi_index(cfg, documents) {
|
|
58
|
+
const combined = {
|
|
59
|
+
openapi: "3.1.0",
|
|
60
|
+
info: {},
|
|
61
|
+
paths: {},
|
|
62
|
+
components: {
|
|
63
|
+
schemas: {}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
for (const [protocol, documentation] of Object.entries(documents)) {
|
|
67
|
+
switch (protocol) {
|
|
68
|
+
case "REST":
|
|
69
|
+
{
|
|
70
|
+
const rest_doc = documentation;
|
|
71
|
+
// Merge REST OpenAPI info, paths, and components into combined doc
|
|
72
|
+
combined.info = rest_doc.info;
|
|
73
|
+
combined.paths = rest_doc.paths;
|
|
74
|
+
// Merge component schemas if they exist
|
|
75
|
+
if (rest_doc.components?.schemas) {
|
|
76
|
+
combined.components.schemas = {
|
|
77
|
+
...combined.components.schemas,
|
|
78
|
+
...rest_doc.components.schemas
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Write the combined index file
|
|
86
|
+
await fs.writeFile(path.join(cwd, cfg.out, "openapi.yml"), yaml.dump(combined, { lineWidth: -1, indent: 2, noRefs: true }), { flag: "w+" });
|
|
87
|
+
}
|
|
54
88
|
//Tasked with recursively creating directories and spec files.
|
|
55
89
|
async function write_to_file(cfg, dir, data) {
|
|
56
90
|
//Path is a slash(/) delimited string, with the end delimiter indicating the filename to be used.
|
package/dist/sfr-pipeline.mjs
CHANGED
|
@@ -509,7 +509,10 @@ export class SFRPipeline {
|
|
|
509
509
|
const spec = {
|
|
510
510
|
openapi: "3.0.0",
|
|
511
511
|
info: Object.assign({}, this.oas_cfg),
|
|
512
|
-
paths: {}
|
|
512
|
+
paths: {},
|
|
513
|
+
components: {
|
|
514
|
+
schemas: {}
|
|
515
|
+
}
|
|
513
516
|
};
|
|
514
517
|
// Iterate through each protocol (e.g., REST, WS, MQ)
|
|
515
518
|
Object.entries(declaration).forEach(([namespace, module]) => {
|
|
@@ -623,9 +626,30 @@ export class SFRPipeline {
|
|
|
623
626
|
document.description = (document.description || "") + rate_limit_note;
|
|
624
627
|
}
|
|
625
628
|
}
|
|
629
|
+
// Generate schema name from namespace and endpoint
|
|
630
|
+
const schema_name = this.generate_schema_name(namespace, endpoint, method);
|
|
626
631
|
//Insert either a parameter or a requestBody according to the method type
|
|
627
632
|
if (validator_type === "joi") {
|
|
628
|
-
|
|
633
|
+
const swagger_schema = j2s(validator).swagger;
|
|
634
|
+
if (method === "get") {
|
|
635
|
+
// For GET requests, convert schema properties to query parameters
|
|
636
|
+
document.parameters = this.convert_schema_to_query_parameters(swagger_schema, schema_name, spec);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
// For POST/PUT/PATCH/DELETE, use requestBody with application/json
|
|
640
|
+
// Store schema in components
|
|
641
|
+
spec.components.schemas[schema_name] = swagger_schema;
|
|
642
|
+
document.requestBody = {
|
|
643
|
+
required: true,
|
|
644
|
+
content: {
|
|
645
|
+
"application/json": {
|
|
646
|
+
schema: {
|
|
647
|
+
$ref: `#/components/schemas/${schema_name}`
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
629
653
|
}
|
|
630
654
|
// Convert Multer validators to multipart/form-data OpenAPI schema
|
|
631
655
|
if (validator_type === "multer") {
|
|
@@ -644,6 +668,71 @@ export class SFRPipeline {
|
|
|
644
668
|
spec.info.meta = Object.fromEntries(Object.entries(spec.info.meta).map(([k, v]) => [`x-${k}`, v]));
|
|
645
669
|
return spec;
|
|
646
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* Generates a unique schema name from namespace, endpoint, and method.
|
|
673
|
+
*/
|
|
674
|
+
generate_schema_name(namespace, endpoint, method) {
|
|
675
|
+
// Convert to PascalCase and create a descriptive schema name
|
|
676
|
+
const pascal_case = (str) => str
|
|
677
|
+
.split(/[-_]/)
|
|
678
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
679
|
+
.join('');
|
|
680
|
+
const method_prefix = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
|
681
|
+
const namespace_part = pascal_case(namespace);
|
|
682
|
+
const endpoint_part = pascal_case(endpoint);
|
|
683
|
+
return `${method_prefix}${namespace_part}${endpoint_part}Request`;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Converts a JSON schema to OpenAPI query parameters array.
|
|
687
|
+
* Stores complex schemas in components and references them.
|
|
688
|
+
*/
|
|
689
|
+
convert_schema_to_query_parameters(schema, base_name, spec) {
|
|
690
|
+
const parameters = [];
|
|
691
|
+
if (!schema || !schema.properties) {
|
|
692
|
+
return parameters;
|
|
693
|
+
}
|
|
694
|
+
const required_fields = schema.required || [];
|
|
695
|
+
for (const [prop_name, prop_schema] of Object.entries(schema.properties)) {
|
|
696
|
+
const param_schema = prop_schema;
|
|
697
|
+
const is_required = required_fields.includes(prop_name);
|
|
698
|
+
// Check if the property is a complex type (object or array of objects)
|
|
699
|
+
const is_complex = param_schema.type === 'object' ||
|
|
700
|
+
(param_schema.type === 'array' && param_schema.items?.type === 'object');
|
|
701
|
+
if (is_complex) {
|
|
702
|
+
// Store complex schemas in components and reference them
|
|
703
|
+
const component_name = `${base_name}${prop_name.charAt(0).toUpperCase() + prop_name.slice(1)}`;
|
|
704
|
+
spec.components.schemas[component_name] = param_schema;
|
|
705
|
+
parameters.push({
|
|
706
|
+
name: prop_name,
|
|
707
|
+
in: "query",
|
|
708
|
+
required: is_required,
|
|
709
|
+
description: param_schema.description || "",
|
|
710
|
+
content: {
|
|
711
|
+
"application/json": {
|
|
712
|
+
schema: {
|
|
713
|
+
$ref: `#/components/schemas/${component_name}`
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
// Simple types can be defined inline
|
|
721
|
+
const param = {
|
|
722
|
+
name: prop_name,
|
|
723
|
+
in: "query",
|
|
724
|
+
required: is_required,
|
|
725
|
+
schema: { ...param_schema }
|
|
726
|
+
};
|
|
727
|
+
if (param_schema.description) {
|
|
728
|
+
param.description = param_schema.description;
|
|
729
|
+
delete param.schema.description;
|
|
730
|
+
}
|
|
731
|
+
parameters.push(param);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return parameters;
|
|
735
|
+
}
|
|
647
736
|
// Method to generate an AsyncAPI document from a WSNamespaceDeclaration
|
|
648
737
|
generate_ws_async_api_document(declaration) {
|
|
649
738
|
const spec = {
|
|
@@ -68,6 +68,15 @@ export declare class SFRPipeline {
|
|
|
68
68
|
private bind_mq_fns;
|
|
69
69
|
private spec_generation;
|
|
70
70
|
private generate_open_api_document;
|
|
71
|
+
/**
|
|
72
|
+
* Generates a unique schema name from namespace, endpoint, and method.
|
|
73
|
+
*/
|
|
74
|
+
private generate_schema_name;
|
|
75
|
+
/**
|
|
76
|
+
* Converts a JSON schema to OpenAPI query parameters array.
|
|
77
|
+
* Stores complex schemas in components and references them.
|
|
78
|
+
*/
|
|
79
|
+
private convert_schema_to_query_parameters;
|
|
71
80
|
private generate_ws_async_api_document;
|
|
72
81
|
private generate_mq_async_api_document;
|
|
73
82
|
private print_to_console;
|
package/package.json
CHANGED
package/src/index.mts
CHANGED
|
@@ -31,6 +31,9 @@ async function write_service_discovery(cfg: ParserCFG, documents: ServiceDocumen
|
|
|
31
31
|
//Setup output folder
|
|
32
32
|
await fs.mkdir(path.join(cwd, cfg.out), { recursive: true });
|
|
33
33
|
|
|
34
|
+
// Write combined OpenAPI index file containing all protocols
|
|
35
|
+
await write_combined_openapi_index(cfg, documents);
|
|
36
|
+
|
|
34
37
|
// Loop over each protocol
|
|
35
38
|
for (let [protocol, documentation] of Object.entries(documents)) {
|
|
36
39
|
//Strictly await for setup to create dirs for each protocol.
|
|
@@ -60,6 +63,44 @@ async function write_service_discovery(cfg: ParserCFG, documents: ServiceDocumen
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
// Writes a combined openapi.yml index file containing all generated documentation
|
|
67
|
+
async function write_combined_openapi_index(cfg: ParserCFG, documents: ServiceDocuments) {
|
|
68
|
+
const combined: any = {
|
|
69
|
+
openapi: "3.1.0",
|
|
70
|
+
info: {},
|
|
71
|
+
paths: {},
|
|
72
|
+
components: {
|
|
73
|
+
schemas: {}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (const [protocol, documentation] of Object.entries(documents)) {
|
|
78
|
+
switch (protocol) {
|
|
79
|
+
case "REST": {
|
|
80
|
+
const rest_doc = documentation as OAPI_Document;
|
|
81
|
+
// Merge REST OpenAPI info, paths, and components into combined doc
|
|
82
|
+
combined.info = rest_doc.info;
|
|
83
|
+
combined.paths = rest_doc.paths;
|
|
84
|
+
|
|
85
|
+
// Merge component schemas if they exist
|
|
86
|
+
if (rest_doc.components?.schemas) {
|
|
87
|
+
combined.components.schemas = {
|
|
88
|
+
...combined.components.schemas,
|
|
89
|
+
...rest_doc.components.schemas
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
} break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Write the combined index file
|
|
97
|
+
await fs.writeFile(
|
|
98
|
+
path.join(cwd, cfg.out, "openapi.yml"),
|
|
99
|
+
yaml.dump(combined, { lineWidth: -1, indent: 2, noRefs: true }),
|
|
100
|
+
{ flag: "w+" }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
63
104
|
//Tasked with recursively creating directories and spec files.
|
|
64
105
|
async function write_to_file(cfg: ParserCFG, dir: string, data: object) {
|
|
65
106
|
//Path is a slash(/) delimited string, with the end delimiter indicating the filename to be used.
|
package/src/sfr-pipeline.mts
CHANGED
|
@@ -562,7 +562,10 @@ export class SFRPipeline {
|
|
|
562
562
|
const spec: OAPI_Document = {
|
|
563
563
|
openapi: "3.0.0",
|
|
564
564
|
info : Object.assign({}, this.oas_cfg),
|
|
565
|
-
paths: {}
|
|
565
|
+
paths: {},
|
|
566
|
+
components: {
|
|
567
|
+
schemas: {}
|
|
568
|
+
}
|
|
566
569
|
};
|
|
567
570
|
|
|
568
571
|
// Iterate through each protocol (e.g., REST, WS, MQ)
|
|
@@ -686,9 +689,32 @@ export class SFRPipeline {
|
|
|
686
689
|
}
|
|
687
690
|
}
|
|
688
691
|
|
|
692
|
+
// Generate schema name from namespace and endpoint
|
|
693
|
+
const schema_name = this.generate_schema_name(namespace, endpoint, method);
|
|
694
|
+
|
|
689
695
|
//Insert either a parameter or a requestBody according to the method type
|
|
690
|
-
if(validator_type === "joi"){
|
|
691
|
-
|
|
696
|
+
if (validator_type === "joi") {
|
|
697
|
+
const swagger_schema = j2s(validator).swagger;
|
|
698
|
+
|
|
699
|
+
if (method === "get") {
|
|
700
|
+
// For GET requests, convert schema properties to query parameters
|
|
701
|
+
document.parameters = this.convert_schema_to_query_parameters(swagger_schema, schema_name, spec);
|
|
702
|
+
} else {
|
|
703
|
+
// For POST/PUT/PATCH/DELETE, use requestBody with application/json
|
|
704
|
+
// Store schema in components
|
|
705
|
+
spec.components.schemas[schema_name] = swagger_schema;
|
|
706
|
+
|
|
707
|
+
document.requestBody = {
|
|
708
|
+
required: true,
|
|
709
|
+
content: {
|
|
710
|
+
"application/json": {
|
|
711
|
+
schema: {
|
|
712
|
+
$ref: `#/components/schemas/${schema_name}`
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
}
|
|
692
718
|
}
|
|
693
719
|
|
|
694
720
|
// Convert Multer validators to multipart/form-data OpenAPI schema
|
|
@@ -710,6 +736,83 @@ export class SFRPipeline {
|
|
|
710
736
|
return spec;
|
|
711
737
|
}
|
|
712
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Generates a unique schema name from namespace, endpoint, and method.
|
|
741
|
+
*/
|
|
742
|
+
private generate_schema_name(namespace: string, endpoint: string, method: string): string {
|
|
743
|
+
// Convert to PascalCase and create a descriptive schema name
|
|
744
|
+
const pascal_case = (str: string) => str
|
|
745
|
+
.split(/[-_]/)
|
|
746
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
747
|
+
.join('');
|
|
748
|
+
|
|
749
|
+
const method_prefix = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
|
750
|
+
const namespace_part = pascal_case(namespace);
|
|
751
|
+
const endpoint_part = pascal_case(endpoint);
|
|
752
|
+
|
|
753
|
+
return `${method_prefix}${namespace_part}${endpoint_part}Request`;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Converts a JSON schema to OpenAPI query parameters array.
|
|
758
|
+
* Stores complex schemas in components and references them.
|
|
759
|
+
*/
|
|
760
|
+
private convert_schema_to_query_parameters(schema: any, base_name: string, spec: OAPI_Document): any[] {
|
|
761
|
+
const parameters: any[] = [];
|
|
762
|
+
|
|
763
|
+
if (!schema || !schema.properties) {
|
|
764
|
+
return parameters;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const required_fields = schema.required || [];
|
|
768
|
+
|
|
769
|
+
for (const [prop_name, prop_schema] of Object.entries(schema.properties)) {
|
|
770
|
+
const param_schema: any = prop_schema;
|
|
771
|
+
const is_required = required_fields.includes(prop_name);
|
|
772
|
+
|
|
773
|
+
// Check if the property is a complex type (object or array of objects)
|
|
774
|
+
const is_complex = param_schema.type === 'object' ||
|
|
775
|
+
(param_schema.type === 'array' && param_schema.items?.type === 'object');
|
|
776
|
+
|
|
777
|
+
if (is_complex) {
|
|
778
|
+
// Store complex schemas in components and reference them
|
|
779
|
+
const component_name = `${base_name}${prop_name.charAt(0).toUpperCase() + prop_name.slice(1)}`;
|
|
780
|
+
spec.components.schemas[component_name] = param_schema;
|
|
781
|
+
|
|
782
|
+
parameters.push({
|
|
783
|
+
name: prop_name,
|
|
784
|
+
in: "query",
|
|
785
|
+
required: is_required,
|
|
786
|
+
description: param_schema.description || "",
|
|
787
|
+
content: {
|
|
788
|
+
"application/json": {
|
|
789
|
+
schema: {
|
|
790
|
+
$ref: `#/components/schemas/${component_name}`
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
} else {
|
|
796
|
+
// Simple types can be defined inline
|
|
797
|
+
const param: any = {
|
|
798
|
+
name: prop_name,
|
|
799
|
+
in: "query",
|
|
800
|
+
required: is_required,
|
|
801
|
+
schema: { ...param_schema }
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
if (param_schema.description) {
|
|
805
|
+
param.description = param_schema.description;
|
|
806
|
+
delete param.schema.description;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
parameters.push(param);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return parameters;
|
|
814
|
+
}
|
|
815
|
+
|
|
713
816
|
// Method to generate an AsyncAPI document from a WSNamespaceDeclaration
|
|
714
817
|
private generate_ws_async_api_document(declaration: WSNamespaceDeclaration): AsyncAPIDocument {
|
|
715
818
|
const spec: any = {
|