@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/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
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
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
+ }