@adhd/apigen-plugin-openapi 0.2.0 → 0.2.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/CHANGELOG.md +4 -0
- package/index.js +1 -1
- package/index.mjs +104 -72
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});require("ts-morph");require("node:fs");require("node:path");require("ts-json-schema-generator");const d=new Set(["string","number","boolean","integer"]);function
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});require("ts-morph");require("node:fs");require("node:path");require("ts-json-schema-generator");function m(e){return{raw:e,words:e.split(/(?=[A-Z])|[-_]/).map(t=>t.toLowerCase()).filter(Boolean)}}function j(e,t,n={}){const o=e.split("/");if(o.length<2||o.some(l=>l.length===0))throw new Error(`syntheticOp: id "${e}" must be "<namespace>/<path...>" with non-empty segments`);const[r,...i]=o,c=r.replace(/^_+/,""),p=n.kind??"action",s={id:e,host:t.host,namespace:m(c),path:i.map(m),kind:p,async:n.async??!1,streaming:n.streaming??!1,safe:n.safe??p==="query",input:n.input??{},output:n.output??{},envelope:n.envelope??{},typeText:n.typeText??null};return n.hasCtx!==void 0&&(s.hasCtx=n.hasCtx),n.transports!==void 0&&(s.transports=n.transports),s}const O={"decimal.js":"Decimal"};function w(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}Object.entries(O).map(([e,t])=>({pattern:new RegExp(`import\\(["'][^"']*${w(e)}[^"']*["']\\)\\.\\w+`,"g"),key:t}));const d=new Set(["string","number","boolean","integer"]);function q(e){if(typeof e!="object"||e===null)return!1;const t=e;if(t.$ref!==void 0||t.oneOf||t.anyOf||t.allOf)return!1;const{type:n}=t;return typeof n=="string"?d.has(n):Array.isArray(n)?n.every(o=>d.has(o)||o==="null"):Array.isArray(t.enum)&&t.enum.length>0?t.enum.every(o=>["string","number","boolean"].includes(typeof o)):!1}function A(e){if(!e||typeof e!="object")return!1;const t=e.properties;return t===void 0||typeof t!="object"||t===null?!1:Object.values(t).every(q)}const P={"decimal.js":"Decimal"};function k(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}Object.entries(P).map(([e,t])=>({pattern:new RegExp(`import\\(["'][^"']*${k(e)}[^"']*["']\\)\\.\\w+`,"g"),key:t}));function y(e){return e.words.join("-")}function h(e){return e.words.map(S).join("")}function g(e){return e.words.join("_")}function x(e,t={}){var n,o;const r=[e.namespace,...e.path],i=e.safe||A(e.input)?"GET":"POST",c=((o=(n=t.http)==null?void 0:n.verb)==null?void 0:o[e.id])??i,p="/"+r.map(a=>y(a)).join("/"),s=r.map(a=>g(a)).join("_"),l=r.map(a=>g(a)).slice(0,-1).join("."),f=h(r[r.length-2]??r[0]),u=h(r[r.length-1]),b=r.map(a=>y(a));return{http:{verb:c,route:p},mcp:{name:s},grpc:{package:l,service:f,method:u},cli:{path:b}}}function S(e){return e.length===0?e:e[0].toUpperCase()+e.slice(1)}function _(e,t={}){const n=t.title??"API",o=t.version??"0.0.0",r={};for(const i of e){const c=x(i),{verb:p,route:s}=c.http,l=p.toLowerCase(),f={operationId:i.id,summary:i.id,responses:{200:$(i.output)}};if(p==="GET")f.parameters=E(i.input);else{const u=i.input;typeof u=="object"&&u!==null&&Object.keys(u).length>0&&(f.requestBody={required:!0,content:{"application/json":{schema:u}}})}r[s]===void 0&&(r[s]={}),r[s][l]=f}return{openapi:"3.1.0",info:{title:n,version:o},paths:r}}function $(e){return typeof e=="object"&&e!==null&&Object.keys(e).length>0?{description:"Success",content:{"application/json":{schema:e}}}:{description:"Success"}}function E(e){if(typeof e!="object"||e===null||typeof e.properties!="object"||e.properties===null)return[];const t=Array.isArray(e.required)?e.required:[],n=e.properties;return Object.entries(n).map(([o,r])=>({name:o,in:"query",required:t.includes(o),schema:r}))}function T(e,t={}){return[{...j("_meta/openapi",e,{kind:"query",safe:!0,output:{type:"object"},transports:["http"]}),handler:n=>_(e.operations,{title:t.title,version:t.version})}]}const v={id:"openapi",description:"Mount plugin: serves the OpenAPI 3.1 doc at GET /meta/openapi",language:"ts",optionsSchema:{type:"object",properties:{title:{type:"string",description:"OpenAPI info.title"},version:{type:"string",description:"OpenAPI info.version"}},additionalProperties:!1},capabilities:{mount:{operations(e,t){return T(e,t)}}}};exports.default=v;exports.openapiPlugin=v;
|
package/index.mjs
CHANGED
|
@@ -2,31 +2,78 @@ import "ts-morph";
|
|
|
2
2
|
import "node:fs";
|
|
3
3
|
import "node:path";
|
|
4
4
|
import "ts-json-schema-generator";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
function m(e) {
|
|
6
|
+
return {
|
|
7
|
+
raw: e,
|
|
8
|
+
words: e.split(/(?=[A-Z])|[-_]/).map((t) => t.toLowerCase()).filter(Boolean)
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function b(e, t, n = {}) {
|
|
12
|
+
const o = e.split("/");
|
|
13
|
+
if (o.length < 2 || o.some((l) => l.length === 0))
|
|
14
|
+
throw new Error(
|
|
15
|
+
`syntheticOp: id "${e}" must be "<namespace>/<path...>" with non-empty segments`
|
|
16
|
+
);
|
|
17
|
+
const [r, ...i] = o, c = r.replace(/^_+/, ""), p = n.kind ?? "action", s = {
|
|
18
|
+
id: e,
|
|
19
|
+
host: t.host,
|
|
20
|
+
namespace: m(c),
|
|
21
|
+
path: i.map(m),
|
|
22
|
+
kind: p,
|
|
23
|
+
async: n.async ?? !1,
|
|
24
|
+
streaming: n.streaming ?? !1,
|
|
25
|
+
safe: n.safe ?? p === "query",
|
|
26
|
+
input: n.input ?? {},
|
|
27
|
+
output: n.output ?? {},
|
|
28
|
+
envelope: n.envelope ?? {},
|
|
29
|
+
typeText: n.typeText ?? null
|
|
30
|
+
};
|
|
31
|
+
return n.hasCtx !== void 0 && (s.hasCtx = n.hasCtx), n.transports !== void 0 && (s.transports = n.transports), s;
|
|
32
|
+
}
|
|
33
|
+
const j = {
|
|
34
|
+
"decimal.js": "Decimal"
|
|
35
|
+
};
|
|
36
|
+
function O(e) {
|
|
37
|
+
return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
38
|
+
}
|
|
39
|
+
Object.entries(j).map(([e, t]) => ({
|
|
40
|
+
// Matches: import("...{modSpec}...").<anyMemberName> (single or double quotes)
|
|
41
|
+
// The module specifier appears inside an absolute path, so we allow any
|
|
42
|
+
// characters between the quote and the specifier, and between the specifier
|
|
43
|
+
// and the closing quote. The trailing member accessor (`.default`, `.Decimal`,
|
|
44
|
+
// …) is matched generically (`\w+`) rather than anchored to one spelling —
|
|
45
|
+
// see the doc comment above for why.
|
|
46
|
+
pattern: new RegExp(
|
|
47
|
+
`import\\(["'][^"']*${O(e)}[^"']*["']\\)\\.\\w+`,
|
|
48
|
+
"g"
|
|
49
|
+
),
|
|
50
|
+
key: t
|
|
51
|
+
}));
|
|
52
|
+
const d = /* @__PURE__ */ new Set(["string", "number", "boolean", "integer"]);
|
|
53
|
+
function w(e) {
|
|
7
54
|
if (typeof e != "object" || e === null)
|
|
8
55
|
return !1;
|
|
9
56
|
const t = e;
|
|
10
57
|
if (t.$ref !== void 0 || t.oneOf || t.anyOf || t.allOf)
|
|
11
58
|
return !1;
|
|
12
|
-
const { type:
|
|
13
|
-
return typeof
|
|
59
|
+
const { type: n } = t;
|
|
60
|
+
return typeof n == "string" ? d.has(n) : Array.isArray(n) ? n.every((o) => d.has(o) || o === "null") : Array.isArray(t.enum) && t.enum.length > 0 ? t.enum.every((o) => ["string", "number", "boolean"].includes(typeof o)) : !1;
|
|
14
61
|
}
|
|
15
|
-
function
|
|
62
|
+
function A(e) {
|
|
16
63
|
if (!e || typeof e != "object")
|
|
17
64
|
return !1;
|
|
18
65
|
const t = e.properties;
|
|
19
66
|
return t === void 0 || typeof t != "object" || t === null ? !1 : Object.values(t).every(
|
|
20
|
-
|
|
67
|
+
w
|
|
21
68
|
);
|
|
22
69
|
}
|
|
23
|
-
const
|
|
70
|
+
const q = {
|
|
24
71
|
"decimal.js": "Decimal"
|
|
25
72
|
};
|
|
26
|
-
function
|
|
73
|
+
function x(e) {
|
|
27
74
|
return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28
75
|
}
|
|
29
|
-
Object.entries(
|
|
76
|
+
Object.entries(q).map(([e, t]) => ({
|
|
30
77
|
// Matches: import("...{modSpec}...").<anyMemberName> (single or double quotes)
|
|
31
78
|
// The module specifier appears inside an absolute path, so we allow any
|
|
32
79
|
// characters between the quote and the specifier, and between the specifier
|
|
@@ -34,65 +81,65 @@ Object.entries(O).map(([e, t]) => ({
|
|
|
34
81
|
// …) is matched generically (`\w+`) rather than anchored to one spelling —
|
|
35
82
|
// see the doc comment above for why.
|
|
36
83
|
pattern: new RegExp(
|
|
37
|
-
`import\\(["'][^"']*${
|
|
84
|
+
`import\\(["'][^"']*${x(e)}[^"']*["']\\)\\.\\w+`,
|
|
38
85
|
"g"
|
|
39
86
|
),
|
|
40
87
|
key: t
|
|
41
88
|
}));
|
|
42
|
-
function d(e) {
|
|
43
|
-
return e.words.join("-");
|
|
44
|
-
}
|
|
45
89
|
function y(e) {
|
|
46
|
-
return e.words.
|
|
90
|
+
return e.words.join("-");
|
|
47
91
|
}
|
|
48
92
|
function h(e) {
|
|
93
|
+
return e.words.map($).join("");
|
|
94
|
+
}
|
|
95
|
+
function g(e) {
|
|
49
96
|
return e.words.join("_");
|
|
50
97
|
}
|
|
51
|
-
function
|
|
52
|
-
var
|
|
53
|
-
const
|
|
98
|
+
function k(e, t = {}) {
|
|
99
|
+
var n, o;
|
|
100
|
+
const r = [e.namespace, ...e.path], i = e.safe || A(e.input) ? "GET" : "POST", c = ((o = (n = t.http) == null ? void 0 : n.verb) == null ? void 0 : o[e.id]) ?? i, p = "/" + r.map((a) => y(a)).join("/"), s = r.map((a) => g(a)).join("_"), l = r.map((a) => g(a)).slice(0, -1).join("."), f = h(r[r.length - 2] ?? r[0]), u = h(r[r.length - 1]), v = r.map((a) => y(a));
|
|
54
101
|
return {
|
|
55
|
-
http: { verb:
|
|
56
|
-
mcp: { name:
|
|
57
|
-
grpc: { package:
|
|
58
|
-
cli: { path:
|
|
102
|
+
http: { verb: c, route: p },
|
|
103
|
+
mcp: { name: s },
|
|
104
|
+
grpc: { package: l, service: f, method: u },
|
|
105
|
+
cli: { path: v }
|
|
59
106
|
};
|
|
60
107
|
}
|
|
61
|
-
function
|
|
108
|
+
function $(e) {
|
|
62
109
|
return e.length === 0 ? e : e[0].toUpperCase() + e.slice(1);
|
|
63
110
|
}
|
|
64
|
-
function
|
|
65
|
-
const
|
|
111
|
+
function E(e, t = {}) {
|
|
112
|
+
const n = t.title ?? "API", o = t.version ?? "0.0.0", r = {};
|
|
66
113
|
for (const i of e) {
|
|
67
|
-
const
|
|
114
|
+
const c = k(i), { verb: p, route: s } = c.http, l = p.toLowerCase(), f = {
|
|
68
115
|
operationId: i.id,
|
|
69
116
|
summary: i.id,
|
|
70
117
|
responses: {
|
|
71
|
-
200:
|
|
118
|
+
200: P(i.output)
|
|
72
119
|
}
|
|
73
120
|
};
|
|
74
|
-
if (
|
|
75
|
-
|
|
121
|
+
if (p === "GET")
|
|
122
|
+
f.parameters = C(i.input);
|
|
76
123
|
else {
|
|
77
|
-
const
|
|
78
|
-
typeof
|
|
124
|
+
const u = i.input;
|
|
125
|
+
typeof u == "object" && u !== null && Object.keys(u).length > 0 && (f.requestBody = {
|
|
79
126
|
required: !0,
|
|
80
127
|
content: {
|
|
81
128
|
"application/json": {
|
|
82
|
-
schema:
|
|
129
|
+
schema: u
|
|
83
130
|
}
|
|
84
131
|
}
|
|
85
132
|
});
|
|
86
133
|
}
|
|
87
|
-
|
|
134
|
+
r[s] === void 0 && (r[s] = {}), r[s][l] = f;
|
|
88
135
|
}
|
|
89
136
|
return {
|
|
90
137
|
openapi: "3.1.0",
|
|
91
|
-
info: { title:
|
|
92
|
-
paths:
|
|
138
|
+
info: { title: n, version: o },
|
|
139
|
+
paths: r
|
|
93
140
|
};
|
|
94
141
|
}
|
|
95
|
-
function
|
|
142
|
+
function P(e) {
|
|
96
143
|
return typeof e == "object" && e !== null && Object.keys(e).length > 0 ? {
|
|
97
144
|
description: "Success",
|
|
98
145
|
content: {
|
|
@@ -100,54 +147,39 @@ function S(e) {
|
|
|
100
147
|
}
|
|
101
148
|
} : { description: "Success" };
|
|
102
149
|
}
|
|
103
|
-
function
|
|
150
|
+
function C(e) {
|
|
104
151
|
if (typeof e != "object" || e === null || typeof e.properties != "object" || e.properties === null)
|
|
105
152
|
return [];
|
|
106
|
-
const t = Array.isArray(e.required) ? e.required : [],
|
|
107
|
-
return Object.entries(
|
|
108
|
-
name:
|
|
153
|
+
const t = Array.isArray(e.required) ? e.required : [], n = e.properties;
|
|
154
|
+
return Object.entries(n).map(([o, r]) => ({
|
|
155
|
+
name: o,
|
|
109
156
|
in: "query",
|
|
110
|
-
required: t.includes(
|
|
111
|
-
schema:
|
|
157
|
+
required: t.includes(o),
|
|
158
|
+
schema: r
|
|
112
159
|
}));
|
|
113
160
|
}
|
|
114
|
-
function
|
|
115
|
-
return {
|
|
116
|
-
raw: e,
|
|
117
|
-
words: e.split(/(?=[A-Z])|[-_]/).map((t) => t.toLowerCase()).filter(Boolean)
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function E(e, t = {}) {
|
|
161
|
+
function I(e, t = {}) {
|
|
121
162
|
return [
|
|
122
163
|
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Input: no domain params — this is a zero-arg metadata endpoint.
|
|
134
|
-
input: {},
|
|
135
|
-
// Output: an object (the full OpenAPI doc shape; not narrowed further
|
|
136
|
-
// here because the exact schema varies with the descriptor content).
|
|
137
|
-
output: { type: "object" },
|
|
138
|
-
envelope: {},
|
|
139
|
-
typeText: null,
|
|
140
|
-
// Expose only on HTTP — the doc is a tooling artefact (SPEC §7.2b).
|
|
141
|
-
transports: ["http"],
|
|
164
|
+
...b("_meta/openapi", e, {
|
|
165
|
+
// query + safe → GET /meta/openapi (SPEC §5)
|
|
166
|
+
kind: "query",
|
|
167
|
+
safe: !0,
|
|
168
|
+
// Output: an object (the full OpenAPI doc shape; not narrowed further
|
|
169
|
+
// here because the exact schema varies with the descriptor content).
|
|
170
|
+
output: { type: "object" },
|
|
171
|
+
// Expose only on HTTP — the doc is a tooling artefact (SPEC §7.2b).
|
|
172
|
+
transports: ["http"]
|
|
173
|
+
}),
|
|
142
174
|
// Handler: derive the doc from the descriptor at request time.
|
|
143
|
-
handler: (
|
|
175
|
+
handler: (n) => E(e.operations, {
|
|
144
176
|
title: t.title,
|
|
145
177
|
version: t.version
|
|
146
178
|
})
|
|
147
179
|
}
|
|
148
180
|
];
|
|
149
181
|
}
|
|
150
|
-
const
|
|
182
|
+
const B = {
|
|
151
183
|
id: "openapi",
|
|
152
184
|
description: "Mount plugin: serves the OpenAPI 3.1 doc at GET /meta/openapi",
|
|
153
185
|
language: "ts",
|
|
@@ -162,7 +194,7 @@ const x = {
|
|
|
162
194
|
capabilities: {
|
|
163
195
|
mount: {
|
|
164
196
|
operations(e, t) {
|
|
165
|
-
return
|
|
197
|
+
return I(
|
|
166
198
|
e,
|
|
167
199
|
t
|
|
168
200
|
);
|
|
@@ -171,6 +203,6 @@ const x = {
|
|
|
171
203
|
}
|
|
172
204
|
};
|
|
173
205
|
export {
|
|
174
|
-
|
|
175
|
-
|
|
206
|
+
B as default,
|
|
207
|
+
B as openapiPlugin
|
|
176
208
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/apigen-plugin-openapi",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"main": "./index.js",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@adhd/apigen-core-client": "^0.2.
|
|
7
|
-
"@adhd/apigen-codegen-openapi": "^0.2.
|
|
6
|
+
"@adhd/apigen-core-client": "^0.2.1",
|
|
7
|
+
"@adhd/apigen-codegen-openapi": "^0.2.1"
|
|
8
8
|
},
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|