@erenthedeveloper0/zen-openapi 0.1.0-alpha.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/LICENSE +9 -0
- package/README.md +76 -0
- package/dist/diff.d.ts +37 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +320 -0
- package/dist/diff.js.map +1 -0
- package/dist/document.d.ts +75 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +689 -0
- package/dist/document.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +29 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +84 -0
- package/dist/plugin.js.map +1 -0
- package/dist/schema.d.ts +103 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +434 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +154 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +19 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +192 -0
- package/dist/ui.js.map +1 -0
- package/package.json +65 -0
- package/src/diff.ts +439 -0
- package/src/document.ts +882 -0
- package/src/index.ts +20 -0
- package/src/plugin.ts +135 -0
- package/src/schema.ts +497 -0
- package/src/types.ts +166 -0
- package/src/ui.ts +198 -0
package/dist/ui.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A self-contained API reference — rfcs/0001 §29.2.
|
|
3
|
+
*
|
|
4
|
+
* Scalar, Swagger UI and Redoc are all better-looking than this, and all three
|
|
5
|
+
* are normally wired in with a `<script src="https://cdn…">`. That is a fine
|
|
6
|
+
* default for a public docs site and a bad one for a framework: it makes an
|
|
7
|
+
* internal endpoint phone a third party on every page load, it breaks behind a
|
|
8
|
+
* strict CSP, it breaks in an air-gapped deployment, and it breaks on a laptop
|
|
9
|
+
* on a train. So the built-in viewer has **no external requests at all** — the
|
|
10
|
+
* document is inlined at boot and the page is HTML, CSS and ~90 lines of script.
|
|
11
|
+
*
|
|
12
|
+
* `ui: false` turns it off; serving Scalar or Redoc instead is a five-line route
|
|
13
|
+
* in application code, and the README shows it.
|
|
14
|
+
*/
|
|
15
|
+
export function renderReference(document, options = { jsonPath: null }) {
|
|
16
|
+
const title = escapeHtml(document.info.title);
|
|
17
|
+
// `</script` inside a JSON island would end the block early. Escaping `<` is
|
|
18
|
+
// the standard, complete fix — `<` is the same character to JSON.parse.
|
|
19
|
+
const inlined = JSON.stringify(document).replace(/</g, '\\u003c');
|
|
20
|
+
const jsonLink = options.jsonPath === null
|
|
21
|
+
? ''
|
|
22
|
+
: `<a class="json-link" href="${escapeHtml(options.jsonPath)}">openapi.json</a>`;
|
|
23
|
+
return `<!doctype html>
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<head>
|
|
26
|
+
<meta charset="utf-8">
|
|
27
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
28
|
+
<meta name="robots" content="noindex">
|
|
29
|
+
<title>${title} — API reference</title>
|
|
30
|
+
<style>${STYLE}</style>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<header>
|
|
34
|
+
<h1>${title}</h1>
|
|
35
|
+
<p class="version">v${escapeHtml(document.info.version)}${jsonLink}</p>
|
|
36
|
+
</header>
|
|
37
|
+
<main id="root"></main>
|
|
38
|
+
<script type="application/json" id="spec">${inlined}</script>
|
|
39
|
+
<script>${SCRIPT}</script>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
function escapeHtml(raw) {
|
|
45
|
+
return raw.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char] ?? char);
|
|
46
|
+
}
|
|
47
|
+
const HTML_ESCAPES = {
|
|
48
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
49
|
+
};
|
|
50
|
+
const STYLE = `
|
|
51
|
+
:root{--bg:#fff;--fg:#1a1a1a;--muted:#666;--line:#e3e3e3;--code:#f6f6f7;--accent:#3060d0}
|
|
52
|
+
@media(prefers-color-scheme:dark){:root{--bg:#16171a;--fg:#e8e8ea;--muted:#9a9aa2;--line:#2c2d33;--code:#1e1f24;--accent:#7aa2f7}}
|
|
53
|
+
*{box-sizing:border-box}
|
|
54
|
+
body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.55 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
|
|
55
|
+
header{padding:28px 32px 18px;border-bottom:1px solid var(--line)}
|
|
56
|
+
h1{margin:0;font-size:22px;font-weight:650}
|
|
57
|
+
.version{margin:6px 0 0;color:var(--muted);font-size:13px}
|
|
58
|
+
.json-link{margin-left:12px;color:var(--accent);text-decoration:none}
|
|
59
|
+
.json-link:hover{text-decoration:underline}
|
|
60
|
+
main{padding:24px 32px 64px;max-width:1000px}
|
|
61
|
+
h2{margin:32px 0 4px;font-size:15px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
|
|
62
|
+
.tag-desc{margin:0 0 10px;color:var(--muted);font-size:13.5px}
|
|
63
|
+
details{border:1px solid var(--line);border-radius:8px;margin:8px 0;background:var(--bg)}
|
|
64
|
+
details[open]{background:var(--code)}
|
|
65
|
+
summary{cursor:pointer;padding:11px 14px;display:flex;align-items:center;gap:10px;list-style:none;font-size:14px}
|
|
66
|
+
summary::-webkit-details-marker{display:none}
|
|
67
|
+
.method{font:600 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.05em;padding:5px 7px;border-radius:4px;color:#fff;min-width:56px;text-align:center}
|
|
68
|
+
.get{background:#2b7a4b}.post{background:#2f5fb3}.put{background:#a86100}.patch{background:#7a4bb3}.delete{background:#b33a3a}.head,.options{background:#5a5a66}
|
|
69
|
+
.path{font:13.5px ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
70
|
+
.summary{color:var(--muted);font-size:13px;margin-left:auto;text-align:right}
|
|
71
|
+
.dep{text-decoration:line-through;opacity:.65}
|
|
72
|
+
.body{padding:2px 14px 16px;border-top:1px solid var(--line)}
|
|
73
|
+
h3{margin:16px 0 6px;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
|
|
74
|
+
table{border-collapse:collapse;width:100%;font-size:13.5px}
|
|
75
|
+
td,th{border-bottom:1px solid var(--line);padding:6px 8px;text-align:left;vertical-align:top}
|
|
76
|
+
th{color:var(--muted);font-weight:500;font-size:12px}
|
|
77
|
+
code{font:12.5px ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--bg);border:1px solid var(--line);border-radius:4px;padding:1px 5px}
|
|
78
|
+
pre{background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:10px 12px;overflow:auto;font:12.5px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;margin:6px 0 0}
|
|
79
|
+
.req{color:#b33a3a;font-size:11px;margin-left:4px}
|
|
80
|
+
.status{font:600 12px ui-monospace,SFMono-Regular,Menlo,monospace;margin-right:8px}
|
|
81
|
+
`;
|
|
82
|
+
const SCRIPT = `
|
|
83
|
+
const spec = JSON.parse(document.getElementById('spec').textContent)
|
|
84
|
+
const root = document.getElementById('root')
|
|
85
|
+
const METHODS = ['get','post','put','patch','delete','head','options']
|
|
86
|
+
|
|
87
|
+
function resolve(schema, depth) {
|
|
88
|
+
if (!schema || depth > 6) return schema || {}
|
|
89
|
+
if (schema.$ref) {
|
|
90
|
+
const name = schema.$ref.split('/').pop()
|
|
91
|
+
const target = (spec.components && spec.components.schemas || {})[name]
|
|
92
|
+
return target ? Object.assign({ 'x-name': name }, resolve(target, depth + 1)) : {}
|
|
93
|
+
}
|
|
94
|
+
return schema
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function typeOf(schema) {
|
|
98
|
+
const s = resolve(schema, 0)
|
|
99
|
+
if (s['x-name']) return s['x-name']
|
|
100
|
+
if (Array.isArray(s.type)) return s.type.join(' | ')
|
|
101
|
+
if (s.type === 'array') return typeOf(s.items || {}) + '[]'
|
|
102
|
+
if (s.enum) return s.enum.map(v => JSON.stringify(v)).join(' | ')
|
|
103
|
+
if (s.const !== undefined) return JSON.stringify(s.const)
|
|
104
|
+
return s.type || 'any'
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function el(tag, cls, text) {
|
|
108
|
+
const node = document.createElement(tag)
|
|
109
|
+
if (cls) node.className = cls
|
|
110
|
+
if (text !== undefined) node.textContent = text
|
|
111
|
+
return node
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function paramTable(params) {
|
|
115
|
+
const table = el('table')
|
|
116
|
+
table.innerHTML = '<tr><th>Name</th><th>In</th><th>Type</th><th>Description</th></tr>'
|
|
117
|
+
for (const p of params) {
|
|
118
|
+
const row = table.insertRow()
|
|
119
|
+
const name = row.insertCell()
|
|
120
|
+
name.appendChild(el('code', null, p.name))
|
|
121
|
+
if (p.required) name.appendChild(el('span', 'req', 'required'))
|
|
122
|
+
row.insertCell().textContent = p.in
|
|
123
|
+
row.insertCell().appendChild(el('code', null, typeOf(p.schema)))
|
|
124
|
+
row.insertCell().textContent = p.description || ''
|
|
125
|
+
}
|
|
126
|
+
return table
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function schemaBlock(schema) {
|
|
130
|
+
return el('pre', null, JSON.stringify(schema, null, 2))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function operationNode(path, method, op) {
|
|
134
|
+
const details = el('details')
|
|
135
|
+
const summary = el('summary')
|
|
136
|
+
summary.appendChild(el('span', 'method ' + method, method.toUpperCase()))
|
|
137
|
+
summary.appendChild(el('span', 'path' + (op.deprecated ? ' dep' : ''), path))
|
|
138
|
+
if (op.summary) summary.appendChild(el('span', 'summary', op.summary))
|
|
139
|
+
details.appendChild(summary)
|
|
140
|
+
|
|
141
|
+
const body = el('div', 'body')
|
|
142
|
+
if (op.description) body.appendChild(el('p', 'tag-desc', op.description))
|
|
143
|
+
body.appendChild(el('h3', null, 'operationId'))
|
|
144
|
+
body.appendChild(el('code', null, op.operationId))
|
|
145
|
+
|
|
146
|
+
if (op.parameters && op.parameters.length) {
|
|
147
|
+
body.appendChild(el('h3', null, 'Parameters'))
|
|
148
|
+
body.appendChild(paramTable(op.parameters))
|
|
149
|
+
}
|
|
150
|
+
if (op.requestBody) {
|
|
151
|
+
body.appendChild(el('h3', null, 'Request body'))
|
|
152
|
+
for (const [media, content] of Object.entries(op.requestBody.content || {})) {
|
|
153
|
+
body.appendChild(el('code', null, media))
|
|
154
|
+
body.appendChild(schemaBlock(content.schema))
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
body.appendChild(el('h3', null, 'Responses'))
|
|
158
|
+
for (const [status, response] of Object.entries(op.responses || {})) {
|
|
159
|
+
const line = el('div')
|
|
160
|
+
line.appendChild(el('span', 'status', status))
|
|
161
|
+
line.appendChild(el('span', null, response.description || ''))
|
|
162
|
+
body.appendChild(line)
|
|
163
|
+
for (const content of Object.values(response.content || {})) {
|
|
164
|
+
body.appendChild(schemaBlock(content.schema))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
details.appendChild(body)
|
|
168
|
+
return details
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const groups = new Map()
|
|
172
|
+
for (const [path, item] of Object.entries(spec.paths || {})) {
|
|
173
|
+
for (const method of METHODS) {
|
|
174
|
+
const op = item[method]
|
|
175
|
+
if (!op) continue
|
|
176
|
+
const tag = (op.tags && op.tags[0]) || 'default'
|
|
177
|
+
if (!groups.has(tag)) groups.set(tag, [])
|
|
178
|
+
groups.get(tag).push([path, method, op])
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const described = new Map((spec.tags || []).map(t => [t.name, t.description]))
|
|
183
|
+
for (const tag of [...groups.keys()].sort()) {
|
|
184
|
+
root.appendChild(el('h2', null, tag))
|
|
185
|
+
const description = described.get(tag)
|
|
186
|
+
if (description) root.appendChild(el('p', 'tag-desc', description))
|
|
187
|
+
for (const [path, method, op] of groups.get(tag)) {
|
|
188
|
+
root.appendChild(operationNode(path, method, op))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
//# sourceMappingURL=ui.js.map
|
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,QAAyB,EAAE,UAAuC,EAAE,QAAQ,EAAE,IAAI,EAAE;IAClH,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7C,6EAA6E;IAC7E,wEAAwE;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,KAAK,IAAI;QACxC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,8BAA8B,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAA;IAElF,OAAO;;;;;;SAMA,KAAK;SACL,KAAK;;;;QAIN,KAAK;wBACW,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,QAAQ;;;4CAGxB,OAAO;UACzC,MAAM;;;CAGf,CAAA;AACD,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA;AACtE,CAAC;AAED,MAAM,YAAY,GAAqC;IACrD,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO;CACpE,CAAA;AAED,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Bb,CAAA;AAED,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6Gd,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@erenthedeveloper0/zen-openapi",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Zen OpenAPI: the application graph as an OAS 3.1 document, $ref deduplication, breaking-change detection and a dependency-free viewer.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openapi",
|
|
7
|
+
"openapi-3.1",
|
|
8
|
+
"swagger",
|
|
9
|
+
"api-docs",
|
|
10
|
+
"json-schema",
|
|
11
|
+
"zen"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/erenthedeveloper0/zen/tree/main/packages/openapi#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/erenthedeveloper0/zen/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/erenthedeveloper0/zen.git",
|
|
20
|
+
"directory": "packages/openapi"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": {
|
|
24
|
+
"name": "Eren Sümer",
|
|
25
|
+
"url": "https://github.com/erenthedeveloper0"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"src",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22.6.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -b",
|
|
52
|
+
"test": "node --test \"test/**/*.test.ts\""
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@erenthedeveloper0/zen-core": "0.1.0-alpha.1"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"typescript": ">=5.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"typescript": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|