zero-http 0.2.5 → 0.3.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.
Files changed (89) hide show
  1. package/README.md +1250 -283
  2. package/documentation/config/db.js +25 -0
  3. package/documentation/config/middleware.js +44 -0
  4. package/documentation/config/tls.js +12 -0
  5. package/documentation/controllers/cookies.js +34 -0
  6. package/documentation/controllers/tasks.js +108 -0
  7. package/documentation/full-server.js +25 -184
  8. package/documentation/models/Task.js +21 -0
  9. package/documentation/public/data/api.json +404 -24
  10. package/documentation/public/data/docs.json +1139 -0
  11. package/documentation/public/data/examples.json +80 -2
  12. package/documentation/public/data/options.json +23 -8
  13. package/documentation/public/index.html +138 -99
  14. package/documentation/public/scripts/app.js +1 -3
  15. package/documentation/public/scripts/custom-select.js +189 -0
  16. package/documentation/public/scripts/data-sections.js +233 -250
  17. package/documentation/public/scripts/playground.js +270 -0
  18. package/documentation/public/scripts/ui.js +4 -3
  19. package/documentation/public/styles.css +56 -5
  20. package/documentation/public/vendor/icons/compress.svg +17 -17
  21. package/documentation/public/vendor/icons/database.svg +21 -0
  22. package/documentation/public/vendor/icons/env.svg +21 -0
  23. package/documentation/public/vendor/icons/fetch.svg +11 -14
  24. package/documentation/public/vendor/icons/security.svg +15 -0
  25. package/documentation/public/vendor/icons/sse.svg +12 -13
  26. package/documentation/public/vendor/icons/static.svg +12 -26
  27. package/documentation/public/vendor/icons/stream.svg +7 -13
  28. package/documentation/public/vendor/icons/validate.svg +17 -0
  29. package/documentation/routes/api.js +41 -0
  30. package/documentation/routes/core.js +20 -0
  31. package/documentation/routes/playground.js +29 -0
  32. package/documentation/routes/realtime.js +49 -0
  33. package/documentation/routes/uploads.js +71 -0
  34. package/index.js +62 -1
  35. package/lib/app.js +200 -8
  36. package/lib/body/json.js +28 -5
  37. package/lib/body/multipart.js +29 -1
  38. package/lib/body/raw.js +1 -1
  39. package/lib/body/sendError.js +1 -0
  40. package/lib/body/text.js +1 -1
  41. package/lib/body/typeMatch.js +6 -2
  42. package/lib/body/urlencoded.js +5 -2
  43. package/lib/debug.js +345 -0
  44. package/lib/env/index.js +440 -0
  45. package/lib/errors.js +231 -0
  46. package/lib/http/request.js +219 -1
  47. package/lib/http/response.js +410 -6
  48. package/lib/middleware/compress.js +39 -6
  49. package/lib/middleware/cookieParser.js +237 -0
  50. package/lib/middleware/cors.js +13 -2
  51. package/lib/middleware/csrf.js +135 -0
  52. package/lib/middleware/errorHandler.js +90 -0
  53. package/lib/middleware/helmet.js +176 -0
  54. package/lib/middleware/index.js +7 -2
  55. package/lib/middleware/rateLimit.js +12 -1
  56. package/lib/middleware/requestId.js +54 -0
  57. package/lib/middleware/static.js +95 -11
  58. package/lib/middleware/timeout.js +72 -0
  59. package/lib/middleware/validator.js +257 -0
  60. package/lib/orm/adapters/json.js +215 -0
  61. package/lib/orm/adapters/memory.js +383 -0
  62. package/lib/orm/adapters/mongo.js +444 -0
  63. package/lib/orm/adapters/mysql.js +272 -0
  64. package/lib/orm/adapters/postgres.js +394 -0
  65. package/lib/orm/adapters/sql-base.js +142 -0
  66. package/lib/orm/adapters/sqlite.js +311 -0
  67. package/lib/orm/index.js +276 -0
  68. package/lib/orm/model.js +895 -0
  69. package/lib/orm/query.js +807 -0
  70. package/lib/orm/schema.js +172 -0
  71. package/lib/router/index.js +136 -47
  72. package/lib/sse/stream.js +15 -3
  73. package/lib/ws/connection.js +19 -3
  74. package/lib/ws/handshake.js +3 -0
  75. package/lib/ws/index.js +3 -1
  76. package/lib/ws/room.js +222 -0
  77. package/package.json +15 -5
  78. package/types/app.d.ts +120 -0
  79. package/types/env.d.ts +80 -0
  80. package/types/errors.d.ts +147 -0
  81. package/types/fetch.d.ts +43 -0
  82. package/types/index.d.ts +135 -0
  83. package/types/middleware.d.ts +292 -0
  84. package/types/orm.d.ts +610 -0
  85. package/types/request.d.ts +99 -0
  86. package/types/response.d.ts +142 -0
  87. package/types/router.d.ts +78 -0
  88. package/types/sse.d.ts +78 -0
  89. package/types/websocket.d.ts +119 -0
@@ -1,319 +1,302 @@
1
1
  /**
2
2
  * data-sections.js
3
- * Fetches and renders the three data-driven documentation sections:
4
- * - API Reference (/data/api.json)
5
- * - Options Table (/data/options.json)
6
- * - Code Examples (/data/examples.json)
3
+ * Fetches the unified docs.json and renders hierarchical documentation sections
4
+ * with sidebar TOC population. Each section becomes a top-level sidebar category
5
+ * with expandable sub-items.
7
6
  *
8
- * Also populates the sidebar TOC with sub-items for the API reference and
9
- * examples sections.
10
- *
11
- * Depends on: helpers.js (provides $, escapeHtml, slugify, showJsonResult,
12
- * highlightAllPre)
7
+ * Depends on: helpers.js (provides $, escapeHtml, slugify, highlightAllPre)
13
8
  */
14
9
 
15
- /* -- TOC Helpers ------------------------------------------------------------- */
10
+ /* -- Section icon map -------------------------------------------------------- */
11
+ const SECTION_ICONS = {
12
+ rocket: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>',
13
+ box: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>',
14
+ parse: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 3H7a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2 2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h2"/><path d="M15 3h2a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2 2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2"/></svg>',
15
+ layers: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>',
16
+ shield: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>',
17
+ settings: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
18
+ database: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>',
19
+ zap: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
20
+ globe: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>'
21
+ };
22
+
23
+ /* -- Rendering helpers ------------------------------------------------------- */
24
+
25
+ function sectionSlug(sectionName)
26
+ {
27
+ return 'section-' + slugify(sectionName);
28
+ }
29
+
30
+ function itemSlug(sectionName, itemName)
31
+ {
32
+ return slugify(sectionName) + '-' + slugify(itemName);
33
+ }
16
34
 
17
35
  /**
18
- * Find a top-level `<li>` in the sidebar TOC whose link matches the given
19
- * href, then append a sub-list of items beneath it.
20
- * @param {string} href - Hash href to match (e.g. "#api-reference").
21
- * @param {Object[]} items - Array of `{ slug, label }` objects.
36
+ * Render a single documentation item as a <details> accordion.
22
37
  */
23
- function populateTocSub(href, items)
38
+ function renderDocItem(item, section)
24
39
  {
25
- const nav = document.querySelector('.toc-sidebar nav ul');
26
- if (!nav || !items || !items.length) return;
40
+ const slug = itemSlug(section, item.name);
41
+ const d = document.createElement('details');
42
+ d.className = 'acc nested doc-item';
43
+ d.id = slug;
27
44
 
28
- const parentLi = Array.from(nav.children).find(li =>
29
- {
30
- const a = li.querySelector && li.querySelector(`a[href="${href}"]`);
31
- return !!a;
32
- });
33
- if (!parentLi) return;
45
+ const s = document.createElement('summary');
46
+ s.innerHTML = `<strong>${escapeHtml(item.name)}</strong>`;
47
+ d.appendChild(s);
34
48
 
35
- /* Remove existing sub-list if present (re-render safe) */
36
- const existing = parentLi.querySelector('ul.toc-sub');
37
- if (existing) existing.remove();
49
+ const body = document.createElement('div');
50
+ body.className = 'acc-body';
38
51
 
39
- /* Ensure parent has collapsible behaviour */
40
- if (!parentLi.classList.contains('toc-collapsible'))
52
+ /* Description */
53
+ if (item.description)
41
54
  {
42
- parentLi.classList.add('toc-collapsible', 'toc-collapsed');
43
- parentLi.style.paddingLeft = '20px';
44
-
45
- const toggle = document.createElement('button');
46
- toggle.className = 'toc-collapse-btn';
47
- toggle.setAttribute('aria-label', 'Toggle section');
48
- toggle.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
49
- toggle.addEventListener('click', (e) =>
50
- {
51
- e.preventDefault();
52
- e.stopPropagation();
53
- parentLi.classList.toggle('toc-collapsed');
54
- });
55
- parentLi.insertBefore(toggle, parentLi.firstChild);
55
+ const p = document.createElement('p');
56
+ p.textContent = item.description;
57
+ body.appendChild(p);
56
58
  }
57
59
 
58
- const sub = document.createElement('ul');
59
- sub.className = 'toc-sub';
60
-
61
- for (const { slug, label } of items)
60
+ /* Options table */
61
+ if (Array.isArray(item.options) && item.options.length)
62
62
  {
63
- const li = document.createElement('li');
64
- li.className = 'toc-sub-item';
65
-
66
- const a = document.createElement('a');
67
- a.href = '#' + slug;
68
- a.textContent = label;
69
- a.addEventListener('click', () => document.body.classList.remove('toc-open'));
63
+ const h6 = document.createElement('h6');
64
+ h6.textContent = 'Options';
65
+ body.appendChild(h6);
70
66
 
71
- li.appendChild(a);
72
- sub.appendChild(li);
67
+ const table = document.createElement('table');
68
+ table.innerHTML = '<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>';
69
+ const tbody = document.createElement('tbody');
70
+ for (const opt of item.options)
71
+ {
72
+ const tr = document.createElement('tr');
73
+ tr.innerHTML =
74
+ `<td><code>${escapeHtml(opt.option)}</code></td>` +
75
+ `<td>${escapeHtml(opt.type || '')}</td>` +
76
+ `<td>${escapeHtml(opt.default != null ? String(opt.default) : '—')}</td>` +
77
+ `<td>${escapeHtml(opt.notes || '')}</td>`;
78
+ tbody.appendChild(tr);
79
+ }
80
+ table.appendChild(tbody);
81
+ body.appendChild(table);
73
82
  }
74
83
 
75
- parentLi.appendChild(sub);
76
- }
77
-
78
- /* -- API Reference ----------------------------------------------------------- */
79
-
80
- /**
81
- * Render a list of API items into the `#api-items` container and Prism-highlight
82
- * any code examples.
83
- * @param {HTMLElement} container - Target element.
84
- * @param {Object[]} list - API item descriptors.
85
- */
86
- function renderApiList(container, list)
87
- {
88
- container.innerHTML = '';
89
- if (!list || !list.length) { container.textContent = 'No API items'; return; }
90
-
91
- for (const it of list)
84
+ /* Methods table */
85
+ if (Array.isArray(item.methods) && item.methods.length)
92
86
  {
93
- const d = document.createElement('details');
94
- d.className = 'acc nested';
95
- d.id = 'api-' + slugify(it.name || '');
96
-
97
- const s = document.createElement('summary');
98
- s.innerHTML = `<strong>${escapeHtml(it.name)}</strong>`;
99
- d.appendChild(s);
100
-
101
- const body = document.createElement('div');
102
- body.className = 'acc-body';
87
+ const h6 = document.createElement('h6');
88
+ h6.textContent = 'Methods';
89
+ body.appendChild(h6);
103
90
 
104
- /* Description */
105
- if (it.description)
106
- {
107
- const p = document.createElement('p');
108
- p.innerHTML = escapeHtml(it.description);
109
- body.appendChild(p);
110
- }
111
-
112
- /* Options table */
113
- if (Array.isArray(it.options) && it.options.length)
91
+ const table = document.createElement('table');
92
+ table.innerHTML = '<thead><tr><th>Method</th><th>Signature</th><th>Description</th></tr></thead>';
93
+ const tbody = document.createElement('tbody');
94
+ for (const m of item.methods)
114
95
  {
115
- const table = document.createElement('table');
116
- table.innerHTML = '<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>';
117
- const tbody = document.createElement('tbody');
118
- for (const opt of it.options)
119
- {
120
- const tr = document.createElement('tr');
121
- tr.innerHTML =
122
- `<td><code>${escapeHtml(opt.option)}</code></td>` +
123
- `<td>${escapeHtml(opt.type || '')}</td>` +
124
- `<td>${escapeHtml(opt.default || '')}</td>` +
125
- `<td>${escapeHtml(opt.notes || '')}</td>`;
126
- tbody.appendChild(tr);
127
- }
128
- table.appendChild(tbody);
129
- body.appendChild(table);
96
+ const tr = document.createElement('tr');
97
+ tr.innerHTML =
98
+ `<td><code>${escapeHtml(m.method || '')}</code></td>` +
99
+ `<td><code>${escapeHtml(m.signature || '')}</code></td>` +
100
+ `<td>${escapeHtml(m.description || '')}</td>`;
101
+ tbody.appendChild(tr);
130
102
  }
103
+ table.appendChild(tbody);
104
+ body.appendChild(table);
105
+ }
131
106
 
132
- /* Methods table */
133
- if (Array.isArray(it.methods) && it.methods.length)
134
- {
135
- const table = document.createElement('table');
136
- table.innerHTML = '<thead><tr><th>Method</th><th>Signature</th><th>Description</th></tr></thead>';
137
- const tbody = document.createElement('tbody');
138
- for (const m of it.methods)
139
- {
140
- const tr = document.createElement('tr');
141
- tr.innerHTML =
142
- `<td><code>${escapeHtml(m.method || '')}</code></td>` +
143
- `<td><code>${escapeHtml(m.signature || '')}</code></td>` +
144
- `<td>${escapeHtml(m.description || '')}</td>`;
145
- tbody.appendChild(tr);
146
- }
147
- table.appendChild(tbody);
148
- body.appendChild(table);
149
- }
107
+ /* Example code block */
108
+ if (item.example)
109
+ {
110
+ const h6 = document.createElement('h6');
111
+ h6.textContent = 'Example';
112
+ body.appendChild(h6);
113
+
114
+ const lang = item.exampleLang || 'javascript';
115
+ const pre = document.createElement('pre');
116
+ pre.className = 'language-' + lang + ' code';
117
+ const code = document.createElement('code');
118
+ code.className = 'language-' + lang;
119
+ code.textContent = item.example;
120
+ pre.appendChild(code);
121
+ body.appendChild(pre);
122
+ }
150
123
 
151
- /* Example code block */
152
- if (it.example)
124
+ /* Tips */
125
+ if (Array.isArray(item.tips) && item.tips.length)
126
+ {
127
+ const tipsDiv = document.createElement('div');
128
+ tipsDiv.className = 'doc-tips';
129
+ const h6 = document.createElement('h6');
130
+ h6.className = 'doc-tips-heading';
131
+ h6.textContent = 'Tips';
132
+ tipsDiv.appendChild(h6);
133
+
134
+ const ul = document.createElement('ul');
135
+ ul.className = 'tips-list';
136
+ for (const tip of item.tips)
153
137
  {
154
- const h6 = document.createElement('h6');
155
- h6.textContent = 'Example';
156
- const pre = document.createElement('pre');
157
- pre.className = 'language-javascript code';
158
- const code = document.createElement('code');
159
- code.className = 'language-javascript';
160
- code.textContent = it.example;
161
- pre.appendChild(code);
162
- body.appendChild(h6);
163
- body.appendChild(pre);
138
+ const li = document.createElement('li');
139
+ li.textContent = tip;
140
+ ul.appendChild(li);
164
141
  }
165
-
166
- d.appendChild(body);
167
- container.appendChild(d);
142
+ tipsDiv.appendChild(ul);
143
+ body.appendChild(tipsDiv);
168
144
  }
169
145
 
170
- try { highlightAllPre(); } catch (e) { }
146
+ d.appendChild(body);
147
+ return d;
171
148
  }
172
149
 
173
150
  /**
174
- * Fetch the API reference JSON, render it, populate the sidebar TOC, and wire
175
- * the search / clear filter controls.
151
+ * Render a full documentation section (section heading card + item accordions).
176
152
  */
177
- async function loadApiReference()
153
+ function renderSection(section)
178
154
  {
179
- try
155
+ const slug = sectionSlug(section.section);
156
+ const wrapper = document.createElement('div');
157
+ wrapper.className = 'doc-section';
158
+ wrapper.id = slug;
159
+
160
+ /* Section header */
161
+ const header = document.createElement('div');
162
+ header.className = 'doc-section-header';
163
+
164
+ const iconHtml = SECTION_ICONS[section.icon] || '';
165
+ header.innerHTML = `<span class="doc-section-icon">${iconHtml}</span><h4 class="doc-section-title">${escapeHtml(section.section)}</h4>`;
166
+ wrapper.appendChild(header);
167
+
168
+ /* Section divider line */
169
+ const divider = document.createElement('div');
170
+ divider.className = 'doc-section-divider';
171
+ wrapper.appendChild(divider);
172
+
173
+ /* Items */
174
+ if (Array.isArray(section.items))
180
175
  {
181
- const res = await fetch('/data/api.json', { cache: 'no-store' });
182
- if (!res.ok) return;
183
-
184
- const items = await res.json();
185
- const container = document.getElementById('api-items');
186
- if (!container) return;
187
-
188
- window._apiItems = items;
189
- renderApiList(container, items);
190
-
191
- /* Sidebar TOC sub-items */
192
- populateTocSub('#api-reference', items.map(it => ({
193
- slug: 'api-' + slugify(it.name),
194
- label: it.name || '',
195
- })));
196
-
197
- /* Search / clear filter */
198
- const search = document.getElementById('api-search');
199
- const clearBtn = document.getElementById('api-clear');
200
-
201
- const doFilter = () =>
176
+ for (const item of section.items)
202
177
  {
203
- const q = (search && search.value || '').trim().toLowerCase();
204
- if (!q) return renderApiList(container, window._apiItems);
205
- const filtered = window._apiItems.filter(it =>
206
- (it.name || '').toLowerCase().includes(q) ||
207
- (it.description || '').toLowerCase().includes(q) ||
208
- JSON.stringify(it.options || []).toLowerCase().includes(q)
209
- );
210
- renderApiList(container, filtered);
211
- };
212
-
213
- if (search) search.addEventListener('input', doFilter);
214
- if (clearBtn) clearBtn.addEventListener('click', () => { if (search) search.value = ''; renderApiList(container, window._apiItems); });
215
- } catch (e) { }
178
+ wrapper.appendChild(renderDocItem(item, section.section));
179
+ }
180
+ }
181
+
182
+ return wrapper;
216
183
  }
217
184
 
218
- /* -- Options Table ----------------------------------------------------------- */
185
+ /* -- TOC population ---------------------------------------------------------- */
219
186
 
220
187
  /**
221
- * Fetch the options JSON and render a `<table>` into `#options-items`.
188
+ * Build the full sidebar TOC from the docs sections array.
222
189
  */
223
- async function loadOptions()
190
+ function populateToc(sections)
224
191
  {
225
- try
192
+ const nav = document.querySelector('.toc-sidebar nav ul');
193
+ if (!nav) return;
194
+
195
+ /* Keep static items (features, quickstart, playground) */
196
+ const staticItems = nav.querySelectorAll(':scope > li[data-static]');
197
+ const playgroundLi = nav.querySelector(':scope > li[data-static="playground"]');
198
+
199
+ /* Remove all non-static items */
200
+ Array.from(nav.children).forEach(li =>
226
201
  {
227
- const container = document.getElementById('options-items');
228
- if (!container) return;
202
+ if (!li.hasAttribute('data-static')) li.remove();
203
+ });
229
204
 
230
- const res = await fetch('/data/options.json', { cache: 'no-store' });
231
- if (!res.ok) { container.textContent = 'Error loading options: ' + res.status + ' ' + res.statusText; return; }
205
+ /* Insert section TOC items before playground */
206
+ for (const section of sections)
207
+ {
208
+ const sSlug = sectionSlug(section.section);
209
+ const li = document.createElement('li');
210
+ li.className = 'toc-collapsible toc-collapsed';
232
211
 
233
- let items;
234
- try { items = await res.json(); }
235
- catch (err) { container.textContent = 'Error parsing options JSON'; return; }
212
+ const a = document.createElement('a');
213
+ a.href = '#' + sSlug;
214
+ a.textContent = section.section;
215
+ a.addEventListener('click', () => document.body.classList.remove('toc-open'));
216
+ li.appendChild(a);
236
217
 
237
- const table = document.createElement('table');
238
- table.innerHTML = '<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>';
239
- const tbody = document.createElement('tbody');
218
+ /* Sub-items */
219
+ if (Array.isArray(section.items) && section.items.length)
220
+ {
221
+ const sub = document.createElement('ul');
222
+ sub.className = 'toc-sub';
223
+
224
+ for (const item of section.items)
225
+ {
226
+ const subLi = document.createElement('li');
227
+ subLi.className = 'toc-sub-item';
228
+ const subA = document.createElement('a');
229
+ subA.href = '#' + itemSlug(section.section, item.name);
230
+ subA.textContent = item.name;
231
+ subA.addEventListener('click', () => document.body.classList.remove('toc-open'));
232
+ subLi.appendChild(subA);
233
+ sub.appendChild(subLi);
234
+ }
235
+
236
+ li.appendChild(sub);
237
+ }
240
238
 
241
- for (const it of items)
239
+ if (playgroundLi)
242
240
  {
243
- const tr = document.createElement('tr');
244
- tr.innerHTML =
245
- `<td><strong>${escapeHtml(it.option || '')}</strong></td>` +
246
- `<td>${escapeHtml(it.type || '')}</td>` +
247
- `<td>${escapeHtml(it.default || '')}</td>` +
248
- `<td>${escapeHtml(it.notes || it.description || '')}</td>`;
249
- tbody.appendChild(tr);
241
+ nav.insertBefore(li, playgroundLi);
250
242
  }
243
+ else
244
+ {
245
+ nav.appendChild(li);
246
+ }
247
+ }
251
248
 
252
- table.appendChild(tbody);
253
- container.innerHTML = '';
254
- container.appendChild(table);
255
- } catch (e) { console.error('loadOptions error', e); }
249
+ /* Re-init collapsible toggles for new items */
250
+ if (typeof initTocCollapsible === 'function') initTocCollapsible();
256
251
  }
257
252
 
258
- /* -- Code Examples ----------------------------------------------------------- */
253
+ /* -- Main loader ------------------------------------------------------------- */
259
254
 
260
255
  /**
261
- * Fetch the examples JSON, render each as a collapsible accordion, and
262
- * populate the sidebar TOC.
256
+ * Fetch docs.json and render all sections + populate sidebar.
263
257
  */
264
- async function loadExamples()
258
+ async function loadDocs()
265
259
  {
266
260
  try
267
261
  {
268
- const container = document.getElementById('examples-items');
269
- if (!container) return;
262
+ const res = await fetch('/data/docs.json', { cache: 'no-store' });
263
+ if (!res.ok) return;
270
264
 
271
- const res = await fetch('/data/examples.json', { cache: 'no-store' });
272
- if (!res.ok) { container.textContent = 'Error loading examples: ' + res.status; return; }
265
+ const sections = await res.json();
266
+ window._docSections = sections;
267
+
268
+ const container = document.getElementById('doc-sections');
269
+ if (!container) return;
273
270
 
274
- const items = await res.json();
275
271
  container.innerHTML = '';
276
272
 
277
- for (const it of items)
273
+ for (const section of sections)
278
274
  {
279
- const id = 'example-' + slugify(it.title);
280
- const d = document.createElement('details');
281
- d.className = 'acc nested';
282
- d.id = id;
275
+ container.appendChild(renderSection(section));
276
+ }
283
277
 
284
- const s = document.createElement('summary');
285
- s.innerHTML = `<strong>${escapeHtml(it.title || '')}</strong>`;
286
- d.appendChild(s);
278
+ try { highlightAllPre(); } catch (e) { }
287
279
 
288
- const body = document.createElement('div');
289
- body.className = 'acc-body';
280
+ /* Populate sidebar */
281
+ populateToc(sections);
290
282
 
291
- if (it.description)
283
+ /* Wire accordion click handling for new items */
284
+ container.querySelectorAll('details.acc summary').forEach(summary =>
285
+ {
286
+ if (summary.dataset.wired === '1') return;
287
+ summary.dataset.wired = '1';
288
+ summary.addEventListener('click', (ev) =>
292
289
  {
293
- const p = document.createElement('p');
294
- p.className = 'muted';
295
- p.textContent = it.description;
296
- body.appendChild(p);
297
- }
298
-
299
- const pre = document.createElement('pre');
300
- pre.className = it.language ? 'language-' + it.language + ' code' : 'code';
301
- const code = document.createElement('code');
302
- if (it.language) code.className = 'language-' + it.language;
303
- code.textContent = it.code || '';
304
- pre.appendChild(code);
305
- body.appendChild(pre);
306
-
307
- d.appendChild(body);
308
- container.appendChild(d);
309
- }
310
-
311
- try { highlightAllPre(); } catch (e) { }
290
+ ev.preventDefault();
291
+ const details = summary.parentElement;
292
+ if (details) details.open = !details.open;
293
+ });
294
+ });
312
295
 
313
- /* Sidebar TOC sub-items */
314
- populateTocSub('#simple-examples', items.map(it => ({
315
- slug: 'example-' + slugify(it.title),
316
- label: it.title || '',
317
- })));
318
- } catch (e) { console.error('loadExamples error', e); }
296
+ } catch (e) { console.error('loadDocs error', e); }
319
297
  }
298
+
299
+ /* Legacy stubs for backwards compat (app.js may still call these) */
300
+ async function loadApiReference() { }
301
+ async function loadOptions() { }
302
+ async function loadExamples() { }