zero-http 0.2.0

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -0
  3. package/documentation/controllers/cleanup.js +25 -0
  4. package/documentation/controllers/echo.js +14 -0
  5. package/documentation/controllers/headers.js +1 -0
  6. package/documentation/controllers/proxy.js +112 -0
  7. package/documentation/controllers/root.js +1 -0
  8. package/documentation/controllers/uploads.js +289 -0
  9. package/documentation/full-server.js +129 -0
  10. package/documentation/public/data/api.json +167 -0
  11. package/documentation/public/data/examples.json +62 -0
  12. package/documentation/public/data/options.json +13 -0
  13. package/documentation/public/index.html +414 -0
  14. package/documentation/public/prism-overrides.css +40 -0
  15. package/documentation/public/scripts/app.js +44 -0
  16. package/documentation/public/scripts/data-sections.js +300 -0
  17. package/documentation/public/scripts/helpers.js +166 -0
  18. package/documentation/public/scripts/playground.js +71 -0
  19. package/documentation/public/scripts/proxy.js +98 -0
  20. package/documentation/public/scripts/ui.js +210 -0
  21. package/documentation/public/scripts/uploads.js +459 -0
  22. package/documentation/public/styles.css +310 -0
  23. package/documentation/public/vendor/icons/fetch.svg +23 -0
  24. package/documentation/public/vendor/icons/plug.svg +27 -0
  25. package/documentation/public/vendor/icons/static.svg +35 -0
  26. package/documentation/public/vendor/icons/stream.svg +22 -0
  27. package/documentation/public/vendor/icons/zero.svg +21 -0
  28. package/documentation/public/vendor/prism-copy-to-clipboard.min.js +27 -0
  29. package/documentation/public/vendor/prism-javascript.min.js +1 -0
  30. package/documentation/public/vendor/prism-json.min.js +1 -0
  31. package/documentation/public/vendor/prism-okaidia.css +1 -0
  32. package/documentation/public/vendor/prism-toolbar.css +27 -0
  33. package/documentation/public/vendor/prism-toolbar.min.js +41 -0
  34. package/documentation/public/vendor/prism.min.js +1 -0
  35. package/index.js +43 -0
  36. package/lib/app.js +159 -0
  37. package/lib/body/index.js +14 -0
  38. package/lib/body/json.js +54 -0
  39. package/lib/body/multipart.js +310 -0
  40. package/lib/body/raw.js +40 -0
  41. package/lib/body/rawBuffer.js +74 -0
  42. package/lib/body/sendError.js +17 -0
  43. package/lib/body/text.js +43 -0
  44. package/lib/body/typeMatch.js +22 -0
  45. package/lib/body/urlencoded.js +166 -0
  46. package/lib/cors.js +72 -0
  47. package/lib/fetch.js +218 -0
  48. package/lib/logger.js +68 -0
  49. package/lib/rateLimit.js +64 -0
  50. package/lib/request.js +76 -0
  51. package/lib/response.js +165 -0
  52. package/lib/router.js +87 -0
  53. package/lib/static.js +196 -0
  54. package/package.json +44 -0
@@ -0,0 +1,62 @@
1
+ [
2
+ {
3
+ "title": "Minimal JSON API",
4
+ "description": "A concise REST API with CORS, JSON parsing, and a simple in-memory store.",
5
+ "language": "javascript",
6
+ "code": "const { createApp, json, cors } = require('zero-http')\nconst app = createApp()\n\napp.use(cors({ origin: ['https://example.com'] }))\napp.use(json({ limit: '10kb' }))\n\nconst items = []\n\napp.get('/items', (req, res) => res.json(items))\n\napp.post('/items', (req, res) => {\n\titems.push(req.body)\n\tres.status(201).json({ ok: true, count: items.length })\n})"
7
+ },
8
+ {
9
+ "title": "File Upload with Multipart Streaming",
10
+ "description": "Stream uploaded files to disk and return metadata. Swap the disk writer for an S3 stream to go cloud-native.",
11
+ "language": "javascript",
12
+ "code": "const path = require('path')\nconst os = require('os')\nconst { createApp, multipart } = require('zero-http')\nconst app = createApp()\n\nconst uploadsDir = path.join(os.tmpdir(), 'my-uploads')\n\napp.post('/upload', multipart({\n\tdir: uploadsDir,\n\tmaxFileSize: 10 * 1024 * 1024 // 10 MB\n}), (req, res) => {\n\tres.json({\n\t\tfiles: req.body.files,\n\t\tfields: req.body.fields\n\t})\n})"
13
+ },
14
+ {
15
+ "title": "Static File Server with Caching",
16
+ "description": "Serve a public directory with browser caching, dotfile protection, and HTML extension fallback.",
17
+ "language": "javascript",
18
+ "code": "const path = require('path')\nconst { createApp, static: serveStatic } = require('zero-http')\nconst app = createApp()\n\napp.use(serveStatic(path.join(__dirname, 'public'), {\n\tindex: 'index.html',\n\tmaxAge: 3600000, // 1 hour (in ms)\n\tdotfiles: 'ignore',\n\textensions: ['html', 'htm'],\n\tsetHeaders: (res, filePath) => {\n\t\tres.setHeader('X-Served-By', 'zero-http')\n\t}\n}))\n\napp.listen(3000)"
19
+ },
20
+ {
21
+ "title": "Rate-Limited Login Endpoint",
22
+ "description": "Protect a login route with per-IP rate limiting and a global fallback limiter for the whole app.",
23
+ "language": "javascript",
24
+ "code": "const { createApp, json, rateLimit } = require('zero-http')\nconst app = createApp()\n\n// Global: 200 req/min per IP\napp.use(rateLimit({ windowMs: 60000, max: 200 }))\napp.use(json())\n\n// Login: 5 attempts per minute\napp.post('/login', rateLimit({\n\twindowMs: 60000,\n\tmax: 5,\n\tmessage: 'Too many login attempts'\n}), (req, res) => {\n\tconst { username, password } = req.body\n\t// ... authenticate ...\n\tres.json({ ok: true })\n})"
25
+ },
26
+ {
27
+ "title": "Request Logging with Custom Output",
28
+ "description": "Use the built-in logger middleware for colorized dev output, or write short-format entries to a file.",
29
+ "language": "javascript",
30
+ "code": "const fs = require('fs')\nconst { createApp, logger } = require('zero-http')\nconst app = createApp()\n\n// Option A: colorized dev output to stdout\napp.use(logger({ format: 'dev' }))\n\n// Option B: append short-format lines to a log file\napp.use(logger({\n\tformat: 'short',\n\tcolors: false,\n\tlogger: (msg) => fs.appendFileSync('access.log', msg + '\\n')\n}))\n\napp.get('/', (req, res) => res.json({ ok: true }))\napp.listen(3000)"
31
+ },
32
+ {
33
+ "title": "Custom Error Handler",
34
+ "description": "Register a global error handler so thrown errors return structured JSON instead of a bare 500.",
35
+ "language": "javascript",
36
+ "code": "const { createApp, json } = require('zero-http')\nconst app = createApp()\napp.use(json())\n\napp.get('/fail', (req, res) => {\n\tthrow new Error('Something broke!')\n})\n\napp.onError((err, req, res, next) => {\n\tconsole.error(`[${new Date().toISOString()}]`, err.stack)\n\tres.status(err.statusCode || 500).json({\n\t\terror: err.message,\n\t\tpath: req.url\n\t})\n})\n\napp.listen(3000)"
37
+ },
38
+ {
39
+ "title": "Server-Side Fetch with Progress",
40
+ "description": "Use the built-in fetch client with timeout, abort support, and download progress tracking.",
41
+ "language": "javascript",
42
+ "code": "const { fetch } = require('zero-http')\n\nasync function download(url) {\n\tconst controller = new AbortController()\n\tsetTimeout(() => controller.abort(), 10000)\n\n\tconst res = await fetch(url, {\n\t\ttimeout: 5000,\n\t\tsignal: controller.signal,\n\t\tonDownloadProgress: ({ loaded, total }) =>\n\t\t\tconsole.log(`${loaded}/${total || '?'} bytes`)\n\t})\n\n\tconsole.log(res.ok, res.status, res.statusText)\n\tconst data = await res.json()\n\treturn data\n}\n\ndownload('https://jsonplaceholder.typicode.com/todos/1')\n\t.then(console.log)\n\t.catch(console.error)"
43
+ },
44
+ {
45
+ "title": "Path-Prefix Middleware (Sub-App)",
46
+ "description": "Mount middleware on a path prefix. The prefix is stripped from req.url so downstream handlers see relative paths.",
47
+ "language": "javascript",
48
+ "code": "const { createApp, json } = require('zero-http')\nconst app = createApp()\napp.use(json())\n\n// Everything under /api gets this middleware\napp.use('/api', (req, res, next) => {\n\tconsole.log('API request:', req.method, req.url)\n\t// req.url is now '/' instead of '/api/'\n\tnext()\n})\n\napp.get('/api/users', (req, res) => {\n\tres.json([{ id: 1, name: 'Alice' }])\n})\n\napp.listen(3000)"
49
+ },
50
+ {
51
+ "title": "CORS with Subdomain Matching",
52
+ "description": "Allow all subdomains of a root domain using the dot-prefix origin syntax, with credentials enabled.",
53
+ "language": "javascript",
54
+ "code": "const { createApp, cors, json } = require('zero-http')\nconst app = createApp()\n\napp.use(cors({\n\torigin: [\n\t\t'https://mysite.com', // exact match\n\t\t'.mysite.com' // suffix: matches api.mysite.com, www.mysite.com, etc.\n\t],\n\tcredentials: true,\n\tmethods: 'GET,POST,PUT,DELETE'\n}))\napp.use(json())\n\napp.get('/data', (req, res) => res.json({ secure: true }))\napp.listen(3000)"
55
+ },
56
+ {
57
+ "title": "Full-Featured Server Skeleton",
58
+ "description": "Combines logging, CORS, body parsing, rate limiting, static serving, error handling, and route parameters in one setup.",
59
+ "language": "javascript",
60
+ "code": "const path = require('path')\nconst { createApp, cors, json, urlencoded, text,\n\tstatic: serveStatic, logger, rateLimit, multipart } = require('zero-http')\n\nconst app = createApp()\n\n// Middleware stack\napp.use(logger({ format: 'dev' }))\napp.use(cors())\napp.use(rateLimit({ windowMs: 60000, max: 200 }))\napp.use(json({ limit: '1mb' }))\napp.use(urlencoded({ extended: true }))\napp.use(text())\napp.use(serveStatic(path.join(__dirname, 'public')))\n\n// Routes\napp.get('/health', (req, res) => res.json({ status: 'ok' }))\napp.get('/users/:id', (req, res) => res.json({ id: req.params.id }))\napp.post('/upload', multipart({ maxFileSize: 5 * 1024 * 1024 }),\n\t(req, res) => res.json(req.body))\n\n// Error handler\napp.onError((err, req, res) => {\n\tres.status(500).json({ error: err.message })\n})\n\napp.listen(3000, () => console.log('Server running on :3000'))"
61
+ }
62
+ ]
@@ -0,0 +1,13 @@
1
+ [
2
+ { "option": "createApp()", "type": "function", "default": "—", "notes": "Creates an app instance with use(), get(), post(), put(), delete(), patch(), options(), head(), all(), onError(), listen(), and handler." },
3
+ { "option": "json([opts])", "type": "function", "default": "—", "notes": "Parses JSON bodies into req.body. Options: limit, strict (true), reviver, type ('application/json')." },
4
+ { "option": "urlencoded([opts])", "type": "function", "default": "—", "notes": "Parses URL-encoded bodies. Options: extended (false), limit, type ('application/x-www-form-urlencoded')." },
5
+ { "option": "text([opts])", "type": "function", "default": "—", "notes": "Reads plain text into req.body. Options: type ('text/*'), limit, encoding ('utf8')." },
6
+ { "option": "raw([opts])", "type": "function", "default": "—", "notes": "Reads raw bytes into a Buffer on req.body. Options: type ('application/octet-stream'), limit." },
7
+ { "option": "multipart([opts])", "type": "function", "default": "—", "notes": "Streams file parts to disk; sets req.body to { fields, files }. Options: dir (os.tmpdir()/zero-http-uploads), maxFileSize." },
8
+ { "option": "static(rootPath, [opts])", "type": "function", "default": "—", "notes": "Serves static files with 60+ MIME types. Options: index ('index.html'), maxAge (0), dotfiles ('ignore'), extensions, setHeaders." },
9
+ { "option": "cors([opts])", "type": "function", "default": "—", "notes": "CORS middleware with preflight handling. Options: origin ('*'), methods, allowedHeaders, credentials (false)." },
10
+ { "option": "fetch(url, [opts])", "type": "function", "default": "—", "notes": "Node HTTP/HTTPS client. Response has ok, status, statusText, headers.get(), text(), json(), arrayBuffer(). Options: method, headers, body (auto-serialized), timeout, signal, agent, onDownloadProgress, onUploadProgress." },
11
+ { "option": "rateLimit([opts])", "type": "function", "default": "—", "notes": "Per-IP rate limiter. Sets X-RateLimit-* headers. Options: windowMs (60000), max (100), statusCode (429), message, keyGenerator." },
12
+ { "option": "logger([opts])", "type": "function", "default": "—", "notes": "Request logger with response timing. Options: format ('dev'|'short'|'tiny'), logger (console.log), colors (auto TTY)." }
13
+ ]
@@ -0,0 +1,414 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
7
+ <title>zero-http — demo & reference</title>
8
+ <meta name="description"
9
+ content="zero-http is a zero-dependency, Express-like HTTP server for Node.js with built-in parsers, static serving, and upload handling." />
10
+ <meta name="keywords"
11
+ content="nodejs,http,server,express,expressjs,middleware,body-parser,multipart,uploads,static files,fetch,http client" />
12
+ <meta name="author" content="Anthony Wiedman" />
13
+
14
+ <link rel="stylesheet" href="/styles.css" />
15
+ <link rel="stylesheet" href="/vendor/prism-okaidia.css" />
16
+ <link rel="stylesheet" href="/vendor/prism-toolbar.css" />
17
+ <link rel="stylesheet" href="/prism-overrides.css" />
18
+
19
+ <script src="/vendor/prism.min.js" async></script>
20
+ <script src="/vendor/prism-json.min.js" async></script>
21
+ <script src="/vendor/prism-javascript.min.js" async></script>
22
+ <script src="/vendor/prism-copy-to-clipboard.min.js" async></script>
23
+ <script src="/vendor/prism-toolbar.min.js" async></script>
24
+
25
+ <script src="/scripts/helpers.js" defer></script>
26
+ <script src="/scripts/uploads.js" defer></script>
27
+ <script src="/scripts/playground.js" defer></script>
28
+ <script src="/scripts/proxy.js" defer></script>
29
+ <script src="/scripts/data-sections.js" defer></script>
30
+ <script src="/scripts/ui.js" defer></script>
31
+ <script src="/scripts/app.js" defer></script>
32
+ </head>
33
+
34
+ <body>
35
+ <a id="top"></a>
36
+ <div class="ui-shell">
37
+ <header class="ui-header">
38
+ <button class="toc-toggle" aria-label="Toggle table of contents" title="Toggle table of contents">
39
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none" aria-hidden="true">
40
+ <path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
41
+ </svg>
42
+ </button>
43
+
44
+ <a class="brand" href="#top" id="brand-top" style="text-decoration:none;color:inherit;cursor:pointer">
45
+ <div class="logo">zero-http</div>
46
+ <div class="subtitle">Zero-dependency Express-like server</div>
47
+ </a>
48
+
49
+ <div class="repo-buttons" role="navigation" aria-label="Repository links">
50
+ <a class="icon-btn" href="https://github.com/tonywied17/zero-http-npm" target="_blank"
51
+ rel="noopener noreferrer" aria-label="View on GitHub">
52
+ <svg class="icon-svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
53
+ <path
54
+ d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.56 0-.28-.01-1.02-.02-2-3.2.7-3.88-1.54-3.88-1.54-.53-1.34-1.3-1.7-1.3-1.7-1.06-.72.08-.71.08-.71 1.17.08 1.79 1.2 1.79 1.2 1.04 1.78 2.73 1.27 3.4.97.11-.76.41-1.27.75-1.56-2.56-.29-5.25-1.28-5.25-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.45.11-3.02 0 0 .96-.31 3.15 1.18a10.9 10.9 0 0 1 2.87-.39c.98 0 1.97.13 2.87.39 2.18-1.49 3.14-1.18 3.14-1.18.63 1.57.24 2.73.12 3.02.74.81 1.18 1.84 1.18 3.1 0 4.43-2.7 5.4-5.27 5.69.42.36.8 1.07.8 2.15 0 1.55-.01 2.8-.01 3.18 0 .31.21.68.8.56C20.71 21.39 24 17.08 24 12c0-6.35-5.15-11.5-12-11.5z" />
55
+ </svg>
56
+ <span class="sr">GitHub</span>
57
+ </a>
58
+ <a class="icon-btn" href="https://www.npmjs.com/package/zero-http" target="_blank"
59
+ rel="noopener noreferrer" aria-label="View on npm">
60
+ <svg class="icon-svg" viewBox="0 0 48 22" aria-hidden="true" role="img">
61
+ <rect width="48" height="22" rx="4" fill="#CB3837"></rect>
62
+ <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle"
63
+ font-family="Segoe UI, Roboto, 'Helvetica Neue', Arial, sans-serif" font-weight="800"
64
+ font-size="14" fill="#fff">npm</text>
65
+ </svg>
66
+ <span class="sr">npm</span>
67
+ </a>
68
+ </div>
69
+ </header>
70
+
71
+ <aside class="toc-sidebar" aria-label="Table of contents">
72
+ <nav>
73
+ <div class="toc-toolbar">
74
+ <button class="toc-tool-btn" id="toc-top-btn" title="Back to top" aria-label="Scroll to top">
75
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 13V3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M3 7l5-5 5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
76
+ </button>
77
+ <button class="toc-tool-btn" id="toc-toggle-acc" title="Expand all" aria-label="Expand all sections">
78
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 4l6 4 6-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 9l6 4 6-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
79
+ </button>
80
+ </div>
81
+ <ul>
82
+ <li><a href="#features">Features</a></li>
83
+ <li><a href="#quickstart">Quickstart</a></li>
84
+ <li><a href="#options">Options</a></li>
85
+ <li><a href="#api-reference">API Reference</a></li>
86
+ <li><a href="#simple-examples">Simple Examples</a></li>
87
+ <li><a href="#playground">Playground</a>
88
+ <ul class="toc-sub">
89
+ <li class="toc-sub-item"><a href="#upload-testing">Upload multipart data</a></li>
90
+ <li class="toc-sub-item"><a href="#parser-tests">Parser tests</a></li>
91
+ <li class="toc-sub-item"><a href="#proxy-test">Proxy test</a></li>
92
+ </ul>
93
+ </li>
94
+ </ul>
95
+ </nav>
96
+ </aside>
97
+ <main class="ui-main">
98
+ <div class="card docs-card">
99
+
100
+ <div class="feature-tabs" id="features">
101
+ <div class="tabs-nav" role="tablist" aria-label="Docs sections tabs">
102
+ <button class="tab active" data-target="tab-features" role="tab"
103
+ aria-selected="true">Features</button>
104
+ <button class="tab" data-target="tab-behavior" role="tab" aria-selected="false">Server
105
+ model</button>
106
+ </div>
107
+ <div class="tabs-content">
108
+ <div id="tab-features" class="tab-panel active">
109
+ <div class="feature-grid">
110
+ <div class="feature-card">
111
+ <div class="feature-icon">
112
+ <img class="icon-svg" src="/vendor/icons/zero.svg" alt="" aria-hidden="true"
113
+ width="48" height="48" />
114
+ </div>
115
+ <h5>Zero-dependency</h5>
116
+ <p>Small, Express-like API without external deps.</p>
117
+ </div>
118
+ <div class="feature-card">
119
+ <div class="feature-icon">
120
+ <img class="icon-svg" src="/vendor/icons/plug.svg" alt="" aria-hidden="true"
121
+ width="48" height="48" />
122
+ </div>
123
+ <h5>Pluggable parsers</h5>
124
+ <p>json(), urlencoded(), text(), raw() built-in.</p>
125
+ </div>
126
+ <div class="feature-card">
127
+ <div class="feature-icon">
128
+ <img class="icon-svg" src="/vendor/icons/stream.svg" alt="" aria-hidden="true"
129
+ width="48" height="48" />
130
+ </div>
131
+ <h5>Streaming uploads</h5>
132
+ <p>Multipart streaming to disk for large files.</p>
133
+ </div>
134
+ <div class="feature-card">
135
+ <div class="feature-icon">
136
+ <img class="icon-svg" src="/vendor/icons/static.svg" alt="" aria-hidden="true"
137
+ width="48" height="48" />
138
+ </div>
139
+ <h5>Static serving</h5>
140
+ <p>Correct Content-Type handling and caching options.</p>
141
+ </div>
142
+ <div class="feature-card">
143
+ <div class="feature-icon">
144
+ <img class="icon-svg" src="/vendor/icons/fetch.svg" alt="" aria-hidden="true"
145
+ width="48" height="48" />
146
+ </div>
147
+ <h5>Built-in fetch</h5>
148
+ <p>Small server-side fetch replacement for HTTP requests.</p>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ <div id="tab-behavior" class="tab-panel">
153
+ <div class="behavior-list">
154
+ <div class="behavior-step">
155
+ <div class="step-num">1</div>
156
+ <div class="step-body">
157
+ <h5>Registration order</h5>
158
+ <p>Routing and middleware run in the order they're registered—add parsers before
159
+ handlers.</p>
160
+ </div>
161
+ </div>
162
+ <div class="behavior-step">
163
+ <div class="step-num">2</div>
164
+ <div class="step-body">
165
+ <h5>Body parsing</h5>
166
+ <p>Body parsers populate <code>req.body</code>; multipart uploads expose
167
+ <code>req.body.files</code> and <code>req.body.fields</code>.
168
+ </p>
169
+ </div>
170
+ </div>
171
+ <div class="behavior-step">
172
+ <div class="step-num">3</div>
173
+ <div class="step-body">
174
+ <h5>Handler model</h5>
175
+ <p>Handlers receive <code>req</code> and <code>res</code>. Use
176
+ <code>res.json()</code> or <code>res.send()</code> to respond.
177
+ </p>
178
+ </div>
179
+ </div>
180
+ <div class="behavior-step">
181
+ <div class="step-num">4</div>
182
+ <div class="step-body">
183
+ <h5>Static middleware</h5>
184
+ <p>Serves files with Content-Type derived from extension and supports caching
185
+ options.</p>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <div class="acc-body">
194
+ <h4 id="quickstart">Quickstart</h4>
195
+
196
+ <p class="muted">Install from npm and create a simple server:</p>
197
+ <pre class="language-bash"><code class="language-bash">npm install zero-http</code></pre>
198
+
199
+ <pre class="language-javascript"><code class="language-javascript">const { createApp, json, static } = require('zero-http')
200
+ const path = require('path')
201
+ const app = createApp()
202
+
203
+ app.use(json({ limit: '10kb' }))
204
+ app.use(static(path.join(__dirname, 'public'), { index: 'index.html' }))
205
+
206
+ app.get('/ping', (req, res) =&gt; res.json({ pong: true }))
207
+
208
+ app.listen(3000, () =&gt; {
209
+ console.log('Server listening on http://localhost:3000')
210
+ })
211
+ </code></pre>
212
+
213
+ <p class="muted">To run this demo server from the repo root and open
214
+ <code>http://localhost:3000</code>.
215
+ </p>
216
+ <pre class="language-bash"><code class="language-bash">npm install zero-http
217
+ npm run docs
218
+ # open http://localhost:3000
219
+ </code></pre>
220
+
221
+ <h4>API, Options & Examples</h4>
222
+
223
+ <div class="muted">Comprehensive reference for configuration options, runtime behavior, and API
224
+ examples. Expand each section for details and runnable snippets.</div>
225
+
226
+ <details class="acc" id="options">
227
+ <summary>Options (middleware & parser settings)</summary>
228
+ <div class="acc-body">
229
+ <p class="muted">Common option shapes and defaults for parsers and upload middleware.</p>
230
+ <div id="options-items">Loading options…</div>
231
+ </div>
232
+ </details>
233
+
234
+ <details class="acc" id="api-reference">
235
+ <summary>API Reference</summary>
236
+ <div class="acc-body">
237
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:8px">
238
+ <input id="api-search" placeholder="Search API (name, description)"
239
+ style="flex:1;padding:8px;border-radius:8px;border:1px solid rgba(0,0,0,0.06);background:transparent;color:inherit" />
240
+ <button id="api-clear" class="btn small" type="button">Clear</button>
241
+ </div>
242
+ <div id="api-items">Loading API reference…</div>
243
+ </div>
244
+ </details>
245
+
246
+ <details class="acc" id="simple-examples">
247
+ <summary>Simple Examples</summary>
248
+ <div class="acc-body">
249
+ <p class="muted">Snippets showing how to compose middleware for common
250
+ tasks.</p>
251
+ <div id="examples-items">Loading examples…</div>
252
+ </div>
253
+ </details>
254
+
255
+ <details class="acc">
256
+ <summary>Installation & tests</summary>
257
+ <div class="acc-body">
258
+
259
+ <pre class="language-bash"><code class="language-shell">#clone and install dependencies
260
+ git clone https://github.com/tonywied17/zero-http-npm.git
261
+ npm install
262
+
263
+ # run demo server from repo root
264
+ npm run docs
265
+
266
+ # or directly:
267
+ node documentation/full-server.js
268
+
269
+ # run tests (project-specific)
270
+ node test/test.js
271
+ </code></pre>
272
+
273
+ </div>
274
+ </details>
275
+
276
+ <h4>Extending</h4>
277
+ <p>Use the provided middlewares and routing helpers to build controllers, add auth, or swap in
278
+ different storage backends. The multipart parser exposes file streams and writes each part to
279
+ disk so you can replace disk writes with S3 streams if desired.</p>
280
+
281
+ </div>
282
+ </div>
283
+
284
+ <div class="section-divider constrained" id="playground">
285
+ <h2 class="section-title">Playground</h2>
286
+ <p class="muted">Interactive tools to test uploads, body parsers, and the built-in proxy.</p>
287
+ </div>
288
+
289
+ <div class="card upload-card constrained">
290
+ <div class="card-head">
291
+ <h3 id="upload-testing">Upload multipart data</h3>
292
+ <div class="small muted">Drag, drop or pick a file</div>
293
+ </div>
294
+ <form id="uploadForm" class="uploadForm" enctype="multipart/form-data">
295
+ <div class="fileDrop" id="fileDrop">
296
+ <input id="fileInput" name="file" type="file" class="fileInput" />
297
+ <div class="fileDrop-inner">Drop files here or <label for="fileInput" class="linkish">choose
298
+ file</label></div>
299
+ </div>
300
+ <input name="desc" class="textInput" placeholder="Description (optional)" />
301
+
302
+ <div class="upload-button-wrap">
303
+ <button type="submit" class="btn primary">Upload</button>
304
+ </div>
305
+
306
+ <div id="uploadResult" class="result mono"></div>
307
+
308
+ <div class="controls">
309
+
310
+ <div class="left-controls">
311
+
312
+ <button id="delAllBtn" type="button" class="btn warn">Delete All</button>
313
+ <button id="delKeepBtn" type="button" class="btn">Delete (keep 1)</button>
314
+ </div>
315
+ <div class="right-controls">
316
+ <label class="selectWrap">
317
+ <select id="sortSelect">
318
+ <option value="mtime">Newest</option>
319
+ <option value="name">Name</option>
320
+ <option value="size">Size</option>
321
+ </select>
322
+ <select id="sortOrder" title="Sort order" aria-label="Sort order">
323
+ <option value="desc">Desc</option>
324
+ <option value="asc">Asc</option>
325
+ </select>
326
+ </label>
327
+ <div class="pager"><button id="prevPage" type="button" class="btn small">◀</button><span
328
+ id="pageInfo">Page 1</span><button id="nextPage" type="button"
329
+ class="btn small">▶</button></div>
330
+ </div>
331
+ </div>
332
+ <progress id="uploadProgress" value="0" max="100" style="display:none"></progress>
333
+ </form>
334
+
335
+ <div class="files-wrap combined-files" style="margin-top:18px">
336
+ <div class="files-column">
337
+ <div class="card files-card">
338
+ <div class="card-head">
339
+ <h3>Uploaded Files</h3>
340
+ <div class="small muted">Thumbnails and metadata</div>
341
+ </div>
342
+ <div id="uploadsList" class="file-grid">(loading...)</div>
343
+ </div>
344
+ </div>
345
+
346
+ <div class="trash-column">
347
+ <div class="card trash-card">
348
+ <div class="card-head">
349
+ <h3>Trash</h3>
350
+ </div>
351
+ <div id="trashList" class="file-grid muted">(loading...)</div>
352
+ <div style="margin-top:12px"><button id="emptyTrashBtn" type="button" class="btn">Empty
353
+ Trash</button></div>
354
+ </div>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ <div class="card play-card constrained">
359
+ <div class="card-head">
360
+ <h3 id="parser-tests">Quick parser tests</h3>
361
+ </div>
362
+ <div class="card-body">
363
+ <div class="playgrid">
364
+ <form id="jsonPlay" class="play">
365
+ <label>JSON (raw)
366
+ <textarea name="json" class="textInput">{"foo":"bar"}</textarea>
367
+ </label>
368
+ <button class="btn">Send JSON</button>
369
+ </form>
370
+ <form id="urlPlay" class="play">
371
+ <label>URL-encoded (body)
372
+ <textarea name="url" class="textInput">foo=bar&amp;baz=qux</textarea>
373
+ </label>
374
+ <button class="btn">Send urlencoded</button>
375
+ </form>
376
+ <form id="textPlay" class="play">
377
+ <label>Text (raw)
378
+ <textarea name="txt" class="textInput">hello world</textarea>
379
+ </label>
380
+ <button class="btn">Send text</button>
381
+ </form>
382
+ </div>
383
+ <pre id="playResult" class="result mono" style="margin-top:12px"></pre>
384
+ </div>
385
+ </div>
386
+
387
+ <div class="card proxy-card constrained">
388
+ <div class="card-head">
389
+ <h3 id="proxy-test">Proxy Test</h3>
390
+ </div>
391
+ <div class="card-body">
392
+ <form id="proxyForm" class="play">
393
+ <label>External URL to proxy
394
+ <input id="proxyUrl" name="proxyUrl" class="textInput" type="url"
395
+ placeholder="https://download.samplelib.com/mp4/sample-5s.mp4" required
396
+ style="width:100%" />
397
+ </label>
398
+ <button class="btn">Proxy Fetch</button>
399
+ </form>
400
+ <pre id="proxyResult" class="result mono" style="margin-top:12px"></pre>
401
+ </div>
402
+ </div>
403
+
404
+ <div class="card footnote" style="max-width:1300px;margin:18px auto;">
405
+ <p class="muted">This demo doubles as a readable API reference and playground — try uploading files,
406
+ inspect the returned JSON, and use the playground to test parsers.</p>
407
+ </div>
408
+ </main>
409
+
410
+ </div>
411
+
412
+ </body>
413
+
414
+ </html>
@@ -0,0 +1,40 @@
1
+ /* Prism overrides loaded after vendor prism.css to ensure theme precedence */
2
+ :root{--prism-pre-bg: linear-gradient(180deg, rgba(11,13,16,0.96), rgba(6,8,10,0.96));}
3
+
4
+ /* Stronger specificity and !important to override vendor rules */
5
+ body pre[class*="language-"], body code[class*="language-"] {
6
+ background: var(--prism-pre-bg) !important;
7
+ padding: 18px !important;
8
+ border-radius: 10px !important;
9
+ overflow: auto !important;
10
+ border: 1px solid rgba(255,255,255,0.04) !important;
11
+ color: var(--text) !important;
12
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.02) !important;
13
+ }
14
+
15
+ /* Ensure code text uses the demo monospace and sizing */
16
+ body code[class*="language-"] { font-family: Menlo, Monaco, Consolas, 'Courier New', monospace !important; font-size:14px !important; }
17
+
18
+ /* Token color overrides (important to beat vendor palette) */
19
+ .token.comment, .token.prolog, .token.doctype, .token.cdata { color: var(--muted) !important; font-style: italic !important; }
20
+ .token.punctuation { color: rgba(255,255,255,0.6) !important; }
21
+ .token.namespace { opacity: .7 !important; }
22
+ .token.property, .token.tag, .token.constant, .token.symbol, .token.deleted { color: var(--accent-2) !important; }
23
+ .token.boolean, .token.number { color: #f6c177 !important; }
24
+ .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: var(--success) !important; }
25
+ .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #9fe6a0 !important; }
26
+ .token.atrule, .token.attr-value, .token.keyword { color: var(--accent) !important; font-weight:700 !important; }
27
+ .token.function { color: #8ad1ff !important; }
28
+ .token.regex, .token.important { color: #ffd580 !important; }
29
+ .token.variable { color: #ffd580 !important; }
30
+
31
+ /* Inline code readability tweak */
32
+ code.inline, :not(pre) > code { background: rgba(255,255,255,0.02) !important; padding:2px 6px !important; border-radius:6px !important; font-size:0.95em !important; }
33
+
34
+ @media (max-width:640px) {
35
+ body pre[class*="language-"], body code[class*="language-"] {
36
+ padding: 8px !important;
37
+ font-size:13px !important;
38
+ margin: 0;
39
+ }
40
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * app.js (entry point)
3
+ * Single DOMContentLoaded handler that bootstraps every documentation feature.
4
+ * Each section is initialised by calling into the module that owns it:
5
+ *
6
+ * helpers.js → DOM selectors, formatters, Prism helpers
7
+ * uploads.js → initUploads()
8
+ * playground.js → initPlayground()
9
+ * proxy.js → initProxy()
10
+ * data-sections.js → loadApiReference(), loadOptions(), loadExamples()
11
+ */
12
+
13
+ document.addEventListener('DOMContentLoaded', () =>
14
+ {
15
+ /* De-indent and highlight static <pre> blocks */
16
+ try { dedentAllPre(); } catch (e) { }
17
+ try { highlightAllPre(); } catch (e) { }
18
+
19
+ /* Accordion click handling (open/close <details> manually) */
20
+ try
21
+ {
22
+ document.querySelectorAll('details.acc summary').forEach(summary =>
23
+ {
24
+ if (summary.dataset.miniExpressSummary === '1') return;
25
+ summary.dataset.miniExpressSummary = '1';
26
+ summary.addEventListener('click', (ev) =>
27
+ {
28
+ ev.preventDefault();
29
+ const details = summary.parentElement;
30
+ if (details) details.open = !details.open;
31
+ });
32
+ });
33
+ } catch (e) { }
34
+
35
+ /* Feature modules */
36
+ initUploads();
37
+ initPlayground();
38
+ initProxy();
39
+
40
+ /* Data-driven documentation sections */
41
+ loadApiReference().catch(() => {});
42
+ loadOptions().catch(() => {});
43
+ loadExamples().catch(() => {});
44
+ });