@jaredwray/mockhttp 1.9.0 → 2.0.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.
- package/README.md +29 -7
- package/dist/index.d.mts +90 -11
- package/dist/index.mjs +202 -240
- package/package.json +17 -13
- package/site/dist/api/index.html +17983 -0
- package/site/dist/api/swagger.json +5487 -0
- package/site/dist/changelog/index.html +767 -0
- package/site/dist/changelog/page/2/index.html +578 -0
- package/site/dist/changelog/v0-1-0/index.html +453 -0
- package/site/dist/changelog/v0-2-0/index.html +437 -0
- package/site/dist/changelog/v0-2-1/index.html +438 -0
- package/site/dist/changelog/v0-2-2/index.html +436 -0
- package/site/dist/changelog/v0-2-3/index.html +440 -0
- package/site/dist/changelog/v0-3-0/index.html +443 -0
- package/site/dist/changelog/v0-4-0/index.html +437 -0
- package/site/dist/changelog/v0-4-1/index.html +441 -0
- package/site/dist/changelog/v0-4-2/index.html +446 -0
- package/site/dist/changelog/v0-4-3/index.html +437 -0
- package/site/dist/changelog/v0-5-0/index.html +443 -0
- package/site/dist/changelog/v0-6-0/index.html +444 -0
- package/site/dist/changelog/v0-6-1/index.html +441 -0
- package/site/dist/changelog/v0-7-0/index.html +445 -0
- package/site/dist/changelog/v0-8-0/index.html +440 -0
- package/site/dist/changelog/v0-9-0/index.html +437 -0
- package/site/dist/changelog/v1-0-0/index.html +446 -0
- package/site/dist/changelog/v1-0-1/index.html +437 -0
- package/site/dist/changelog/v1-1-0/index.html +445 -0
- package/site/dist/changelog/v1-2-0/index.html +443 -0
- package/site/dist/changelog/v1-3-0/index.html +448 -0
- package/site/dist/changelog/v1-4-0/index.html +445 -0
- package/site/dist/changelog/v1-5-0/index.html +439 -0
- package/site/dist/changelog/v1-5-1/index.html +443 -0
- package/site/dist/changelog/v1-6-0/index.html +467 -0
- package/site/dist/changelog/v1-7-0/index.html +462 -0
- package/site/dist/changelog/v1-8-0/index.html +481 -0
- package/site/dist/changelog/v1-8-1/index.html +457 -0
- package/site/dist/changelog/v1-9-0/index.html +495 -0
- package/site/dist/changelog/v2-0-0/index.html +500 -0
- package/site/dist/changelog-latest.json +269 -0
- package/site/dist/changelog.json +399 -0
- package/site/dist/css/api.css +1113 -0
- package/site/dist/css/highlight/highlight.min.js +1433 -0
- package/site/dist/css/highlight/styles/base16/docula.css +123 -0
- package/site/dist/css/home.css +133 -0
- package/site/dist/css/styles.css +1227 -0
- package/site/dist/css/variables.css +32 -0
- package/site/dist/docs/bins/index.html +942 -0
- package/site/dist/docs/configuration/index.html +771 -0
- package/site/dist/docs/hosted/index.html +544 -0
- package/site/dist/docs/https/index.html +758 -0
- package/site/dist/docs/index.html +601 -0
- package/site/dist/docs/library/index.html +780 -0
- package/site/dist/docs/taps/index.html +708 -0
- package/site/dist/favicon.svg +14 -0
- package/site/dist/feed.xml +1 -0
- package/site/dist/index.html +601 -0
- package/site/dist/js/api.js +363 -0
- package/site/dist/js/search.js +436 -0
- package/site/dist/llms-full.txt +7415 -0
- package/site/dist/llms.txt +40 -0
- package/site/dist/logo.svg +21 -0
- package/site/dist/robots.txt +2 -0
- package/site/dist/search-index.json +1 -0
- package/site/dist/sitemap.xml +1 -0
|
@@ -0,0 +1,780 @@
|
|
|
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.0">
|
|
7
|
+
<meta name="description" content="HTTP mock server and httpbin replacement for API testing. Run it at mockhttp.org, in Docker, or in Node.js.">
|
|
8
|
+
<link rel="stylesheet" href="/css/variables.css">
|
|
9
|
+
<link rel="stylesheet" href="/css/styles.css">
|
|
10
|
+
<link rel="stylesheet" href="/css/highlight/styles/base16/docula.css">
|
|
11
|
+
<link rel="icon" href="/logo.svg">
|
|
12
|
+
<script>
|
|
13
|
+
(function(){
|
|
14
|
+
window.__doculaThemeKey = 'docula:theme:' + ('https://mockhttp.org').replace(/^https?:\/\//, '');
|
|
15
|
+
var mode = localStorage.getItem(window.__doculaThemeKey) || 'light';
|
|
16
|
+
var resolved = mode === 'system'
|
|
17
|
+
? (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
|
|
18
|
+
: mode;
|
|
19
|
+
if (resolved === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
|
20
|
+
try {
|
|
21
|
+
var authState = JSON.parse(localStorage.getItem('docula-auth-state'));
|
|
22
|
+
if (authState) {
|
|
23
|
+
window.__doculaAuth = authState;
|
|
24
|
+
if (authState.loggedIn) {
|
|
25
|
+
document.documentElement.classList.add('docula-auth-logged-in');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// Ignore localStorage parsing errors
|
|
30
|
+
}
|
|
31
|
+
if (!window.__doculaAuth) {
|
|
32
|
+
window.__doculaAuth = { loggedIn: false, displayName: null };
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
</script>
|
|
36
|
+
<title>MockHTTP - Library API</title>
|
|
37
|
+
<script type="application/ld+json">
|
|
38
|
+
{"@context":"https://schema.org","@type":"TechArticle","headline":"Library API","description":"","url":"https://mockhttp.org/docs/library/","publisher":{"@type":"Organization","name":"MockHTTP"},"dateModified":"2026-09-18","keywords":[]}
|
|
39
|
+
</script>
|
|
40
|
+
</head>
|
|
41
|
+
|
|
42
|
+
<body>
|
|
43
|
+
<header class="header">
|
|
44
|
+
<div class="header-top">
|
|
45
|
+
<div class="header-top__content">
|
|
46
|
+
<button class="mobile-menu-toggle" id="mobile-menu-toggle" aria-label="Toggle navigation menu">
|
|
47
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
|
|
48
|
+
</button>
|
|
49
|
+
<a href="/" class="logo-link">
|
|
50
|
+
<img alt="MockHTTP" class="logo__img" src="/logo.svg">
|
|
51
|
+
</a>
|
|
52
|
+
<nav class="header-bottom__nav">
|
|
53
|
+
<a class="header-bottom__item" href="/docs/" id="nav-docs">
|
|
54
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
|
|
55
|
+
<span>Documentation</span>
|
|
56
|
+
</a>
|
|
57
|
+
<a class="header-bottom__item" href="/api" id="nav-api">
|
|
58
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg>
|
|
59
|
+
<span>API Reference</span>
|
|
60
|
+
</a>
|
|
61
|
+
<a class="header-bottom__item" href="/changelog/" id="nav-changelog">
|
|
62
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"/><path d="M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14"/><path d="M8 6v8"/></svg>
|
|
63
|
+
<span>Changelog</span>
|
|
64
|
+
</a>
|
|
65
|
+
</nav>
|
|
66
|
+
<div class="header-actions">
|
|
67
|
+
<button class="search-button" id="search-button" type="button" aria-label="Search" aria-keyshortcuts="Meta+K Control+K">
|
|
68
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
69
|
+
<span class="search-button__text">Search</span>
|
|
70
|
+
<span class="search-button__shortcut">⌘K</span>
|
|
71
|
+
</button>
|
|
72
|
+
<button class="theme-button" id="theme-toggle" aria-label="Theme: system (following system)">
|
|
73
|
+
<svg class="theme-button__icon theme-button__icon--system" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>
|
|
74
|
+
<svg class="theme-button__icon theme-button__icon--sun theme-button__icon--hidden" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
|
75
|
+
<svg class="theme-button__icon theme-button__icon--moon theme-button__icon--hidden" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</header>
|
|
81
|
+
<aside class="mobile-sidebar" id="mobile-sidebar">
|
|
82
|
+
<nav class="mobile-nav">
|
|
83
|
+
<a class="mobile-nav__item" href="/docs/">
|
|
84
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
|
|
85
|
+
<span>Documentation</span>
|
|
86
|
+
</a>
|
|
87
|
+
<a class="mobile-nav__item" href="/api">
|
|
88
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg>
|
|
89
|
+
<span>API Reference</span>
|
|
90
|
+
</a>
|
|
91
|
+
<a class="mobile-nav__item" href="/changelog/">
|
|
92
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"/><path d="M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14"/><path d="M8 6v8"/></svg>
|
|
93
|
+
<span>Changelog</span>
|
|
94
|
+
</a>
|
|
95
|
+
</nav>
|
|
96
|
+
<div class="mobile-sidebar__docs" id="mobile-sidebar-docs"></div>
|
|
97
|
+
</aside>
|
|
98
|
+
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
|
|
99
|
+
<div class="search-modal" id="search-modal" hidden>
|
|
100
|
+
<div class="search-modal__backdrop" data-search-close></div>
|
|
101
|
+
<div class="search-modal__panel" role="dialog" aria-modal="true" aria-label="Search">
|
|
102
|
+
<div class="search-modal__header">
|
|
103
|
+
<svg class="search-modal__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
104
|
+
<input
|
|
105
|
+
type="search"
|
|
106
|
+
id="search-input"
|
|
107
|
+
class="search-modal__input"
|
|
108
|
+
placeholder="Search"
|
|
109
|
+
autocomplete="off"
|
|
110
|
+
autocorrect="off"
|
|
111
|
+
autocapitalize="off"
|
|
112
|
+
spellcheck="false"
|
|
113
|
+
aria-label="Search"
|
|
114
|
+
aria-controls="search-results"
|
|
115
|
+
aria-activedescendant=""
|
|
116
|
+
role="combobox"
|
|
117
|
+
aria-expanded="true"
|
|
118
|
+
/>
|
|
119
|
+
<button class="search-modal__clear" id="search-clear" type="button" aria-label="Clear search" hidden>
|
|
120
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="search-modal__body">
|
|
124
|
+
<ul class="search-results" id="search-results" role="listbox" aria-label="Search results"></ul>
|
|
125
|
+
<div class="search-modal__empty" id="search-empty" hidden>
|
|
126
|
+
No results for "<span class="search-modal__empty-query" id="search-empty-query"></span>"
|
|
127
|
+
</div>
|
|
128
|
+
<div class="search-modal__initial" id="search-initial">
|
|
129
|
+
Type to search the documentation.
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="search-modal__footer">
|
|
133
|
+
<span class="search-modal__hint"><kbd class="search-modal__kbd">↑</kbd><kbd class="search-modal__kbd">↓</kbd> to navigate</span>
|
|
134
|
+
<span class="search-modal__hint"><kbd class="search-modal__kbd">↵</kbd> to select</span>
|
|
135
|
+
<span class="search-modal__hint"><kbd class="search-modal__kbd">esc</kbd> to close</span>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
<script>
|
|
140
|
+
window.__doculaSearch = { indexUrl: "/search-index.json" };
|
|
141
|
+
</script>
|
|
142
|
+
<script src="/js/search.js" defer></script>
|
|
143
|
+
<div class="page-dropdown" id="page-dropdown">
|
|
144
|
+
<button class="page-dropdown__toggle" id="page-dropdown-toggle">
|
|
145
|
+
<span class="page-dropdown__title">Library API</span>
|
|
146
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
|
|
147
|
+
</button>
|
|
148
|
+
<div class="page-dropdown__list" id="page-dropdown-list">
|
|
149
|
+
<section class="nav-sidebar__section">
|
|
150
|
+
<ul class="nav-sidebar__list">
|
|
151
|
+
<li>
|
|
152
|
+
<a href="/docs/" class="nav-sidebar__item">Getting Started</a>
|
|
153
|
+
</li>
|
|
154
|
+
</ul>
|
|
155
|
+
</section>
|
|
156
|
+
<section class="nav-sidebar__section">
|
|
157
|
+
<ul class="nav-sidebar__list">
|
|
158
|
+
<li>
|
|
159
|
+
<a href="/docs/https/" class="nav-sidebar__item">HTTPS and HTTP/2</a>
|
|
160
|
+
</li>
|
|
161
|
+
</ul>
|
|
162
|
+
</section>
|
|
163
|
+
<section class="nav-sidebar__section">
|
|
164
|
+
<ul class="nav-sidebar__list">
|
|
165
|
+
<li>
|
|
166
|
+
<a href="/docs/taps/" class="nav-sidebar__item">Taps</a>
|
|
167
|
+
</li>
|
|
168
|
+
</ul>
|
|
169
|
+
</section>
|
|
170
|
+
<section class="nav-sidebar__section">
|
|
171
|
+
<ul class="nav-sidebar__list">
|
|
172
|
+
<li>
|
|
173
|
+
<a href="/docs/bins/" class="nav-sidebar__item">Bins</a>
|
|
174
|
+
</li>
|
|
175
|
+
</ul>
|
|
176
|
+
</section>
|
|
177
|
+
<section class="nav-sidebar__section">
|
|
178
|
+
<ul class="nav-sidebar__list">
|
|
179
|
+
<li>
|
|
180
|
+
<a href="/docs/configuration/" class="nav-sidebar__item">Configuration</a>
|
|
181
|
+
</li>
|
|
182
|
+
</ul>
|
|
183
|
+
</section>
|
|
184
|
+
<section class="nav-sidebar__section">
|
|
185
|
+
<ul class="nav-sidebar__list">
|
|
186
|
+
<li>
|
|
187
|
+
<a href="/docs/library/" class="nav-sidebar__item">Library API</a>
|
|
188
|
+
</li>
|
|
189
|
+
</ul>
|
|
190
|
+
</section>
|
|
191
|
+
<section class="nav-sidebar__section">
|
|
192
|
+
<ul class="nav-sidebar__list">
|
|
193
|
+
<li>
|
|
194
|
+
<a href="/docs/hosted/" class="nav-sidebar__item">Hosted Service</a>
|
|
195
|
+
</li>
|
|
196
|
+
</ul>
|
|
197
|
+
</section>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="content">
|
|
201
|
+
<aside class="nav-sidebar" id="nav-sidebar">
|
|
202
|
+
<section class="nav-sidebar__section">
|
|
203
|
+
<ul class="nav-sidebar__list">
|
|
204
|
+
<li>
|
|
205
|
+
<a href="/docs/" class="nav-sidebar__item">Getting Started</a>
|
|
206
|
+
</li>
|
|
207
|
+
</ul>
|
|
208
|
+
</section>
|
|
209
|
+
<section class="nav-sidebar__section">
|
|
210
|
+
<ul class="nav-sidebar__list">
|
|
211
|
+
<li>
|
|
212
|
+
<a href="/docs/https/" class="nav-sidebar__item">HTTPS and HTTP/2</a>
|
|
213
|
+
</li>
|
|
214
|
+
</ul>
|
|
215
|
+
</section>
|
|
216
|
+
<section class="nav-sidebar__section">
|
|
217
|
+
<ul class="nav-sidebar__list">
|
|
218
|
+
<li>
|
|
219
|
+
<a href="/docs/taps/" class="nav-sidebar__item">Taps</a>
|
|
220
|
+
</li>
|
|
221
|
+
</ul>
|
|
222
|
+
</section>
|
|
223
|
+
<section class="nav-sidebar__section">
|
|
224
|
+
<ul class="nav-sidebar__list">
|
|
225
|
+
<li>
|
|
226
|
+
<a href="/docs/bins/" class="nav-sidebar__item">Bins</a>
|
|
227
|
+
</li>
|
|
228
|
+
</ul>
|
|
229
|
+
</section>
|
|
230
|
+
<section class="nav-sidebar__section">
|
|
231
|
+
<ul class="nav-sidebar__list">
|
|
232
|
+
<li>
|
|
233
|
+
<a href="/docs/configuration/" class="nav-sidebar__item">Configuration</a>
|
|
234
|
+
</li>
|
|
235
|
+
</ul>
|
|
236
|
+
</section>
|
|
237
|
+
<section class="nav-sidebar__section">
|
|
238
|
+
<ul class="nav-sidebar__list">
|
|
239
|
+
<li>
|
|
240
|
+
<a href="/docs/library/" class="nav-sidebar__item">Library API</a>
|
|
241
|
+
</li>
|
|
242
|
+
</ul>
|
|
243
|
+
</section>
|
|
244
|
+
<section class="nav-sidebar__section">
|
|
245
|
+
<ul class="nav-sidebar__list">
|
|
246
|
+
<li>
|
|
247
|
+
<a href="/docs/hosted/" class="nav-sidebar__item">Hosted Service</a>
|
|
248
|
+
</li>
|
|
249
|
+
</ul>
|
|
250
|
+
</section>
|
|
251
|
+
</aside>
|
|
252
|
+
<article class="article">
|
|
253
|
+
<header class="article__header">
|
|
254
|
+
<h1 class="article__title">Library API</h1>
|
|
255
|
+
</header>
|
|
256
|
+
<div class="article__body">
|
|
257
|
+
<main class="article__main">
|
|
258
|
+
<h2 id="table-of-contents">Table of Contents</h2>
|
|
259
|
+
<ul>
|
|
260
|
+
<li><a href="#mockhttp-class">MockHttp Class</a>
|
|
261
|
+
<ul>
|
|
262
|
+
<li><a href="#constructor">Constructor</a></li>
|
|
263
|
+
<li><a href="#properties">Properties</a></li>
|
|
264
|
+
<li><a href="#methods">Methods</a>
|
|
265
|
+
<ul>
|
|
266
|
+
<li><a href="#async-initialize"><code>async initialize()</code></a></li>
|
|
267
|
+
<li><a href="#async-start"><code>async start()</code></a></li>
|
|
268
|
+
<li><a href="#async-close"><code>async close()</code></a></li>
|
|
269
|
+
<li><a href="#async-detectport"><code>async detectPort()</code></a></li>
|
|
270
|
+
<li><a href="#async-registerapidocsfastifyinstance"><code>async registerApiDocs(fastifyInstance?)</code></a></li>
|
|
271
|
+
<li><a href="#async-registerswaggerfastifyinstance"><code>async registerSwagger(fastifyInstance?)</code></a></li>
|
|
272
|
+
<li><a href="#async-registersitefastifyinstance"><code>async registerSite(fastifyInstance?)</code></a></li>
|
|
273
|
+
<li><a href="#async-registerhttpmethodsfastifyinstance"><code>async registerHttpMethods(fastifyInstance?)</code></a></li>
|
|
274
|
+
<li><a href="#async-registerstatuscoderoutesfastifyinstance"><code>async registerStatusCodeRoutes(fastifyInstance?)</code></a></li>
|
|
275
|
+
<li><a href="#async-registerrequestinspectionroutesfastifyinstance"><code>async registerRequestInspectionRoutes(fastifyInstance?)</code></a></li>
|
|
276
|
+
<li><a href="#async-registerresponseinspectionroutesfastifyinstance"><code>async registerResponseInspectionRoutes(fastifyInstance?)</code></a></li>
|
|
277
|
+
<li><a href="#async-registerresponseformatroutesfastifyinstance"><code>async registerResponseFormatRoutes(fastifyInstance?)</code></a></li>
|
|
278
|
+
<li><a href="#async-registerredirectroutesfastifyinstance"><code>async registerRedirectRoutes(fastifyInstance?)</code></a></li>
|
|
279
|
+
<li><a href="#async-registercookieroutesfastifyinstance"><code>async registerCookieRoutes(fastifyInstance?)</code></a></li>
|
|
280
|
+
<li><a href="#async-registeranythingroutesfastifyinstance"><code>async registerAnythingRoutes(fastifyInstance?)</code></a></li>
|
|
281
|
+
<li><a href="#async-registerauthroutesfastifyinstance"><code>async registerAuthRoutes(fastifyInstance?)</code></a></li>
|
|
282
|
+
<li><a href="#async-registerimageroutesfastifyinstance"><code>async registerImageRoutes(fastifyInstance?)</code></a></li>
|
|
283
|
+
<li><a href="#async-registerbinroutesfastifyinstance"><code>async registerBinRoutes(fastifyInstance?)</code></a></li>
|
|
284
|
+
</ul>
|
|
285
|
+
</li>
|
|
286
|
+
</ul>
|
|
287
|
+
</li>
|
|
288
|
+
<li><a href="#bins-request-capture">Bins (Request Capture)</a>
|
|
289
|
+
<ul>
|
|
290
|
+
<li><a href="#binscreatebin"><code>bins.createBin()</code></a></li>
|
|
291
|
+
<li><a href="#binsgetbinid"><code>bins.getBin(id)</code></a></li>
|
|
292
|
+
<li><a href="#binslistbins"><code>bins.listBins()</code></a></li>
|
|
293
|
+
<li><a href="#binsdeletebinid"><code>bins.deleteBin(id)</code></a></li>
|
|
294
|
+
<li><a href="#binsgetrequestsbinid"><code>bins.getRequests(binId)</code></a></li>
|
|
295
|
+
<li><a href="#binsgetrequestbinid-reqid"><code>bins.getRequest(binId, reqId)</code></a></li>
|
|
296
|
+
<li><a href="#binsclearrequestsbinid"><code>bins.clearRequests(binId)</code></a></li>
|
|
297
|
+
<li><a href="#binsrecordrequestbinid-raw"><code>bins.recordRequest(binId, raw)</code></a></li>
|
|
298
|
+
<li><a href="#binsstart--binsstop"><code>bins.start()</code> / <code>bins.stop()</code></a></li>
|
|
299
|
+
<li><a href="#binsmaxbodysize--binsmaxrequestsperbin--binsdefaultttlms"><code>bins.maxBodySize</code> / <code>bins.maxRequestsPerBin</code> / <code>bins.defaultTtlMs</code></a></li>
|
|
300
|
+
</ul>
|
|
301
|
+
</li>
|
|
302
|
+
<li><a href="#taps-response-injection">Taps (Response Injection)</a>
|
|
303
|
+
<ul>
|
|
304
|
+
<li><a href="#tapsinjectresponse-matcher"><code>taps.inject(response, matcher?)</code></a></li>
|
|
305
|
+
</ul>
|
|
306
|
+
</li>
|
|
307
|
+
<li><a href="#tapsremoveinjectiontaporid"><code>taps.removeInjection(tapOrId)</code></a>
|
|
308
|
+
<ul>
|
|
309
|
+
<li><a href="#tapsinjections"><code>taps.injections</code></a></li>
|
|
310
|
+
</ul>
|
|
311
|
+
</li>
|
|
312
|
+
<li><a href="#tapsclear"><code>taps.clear()</code></a></li>
|
|
313
|
+
<li><a href="#tapshasinjections"><code>taps.hasInjections</code></a></li>
|
|
314
|
+
</ul>
|
|
315
|
+
<h2 id="mockhttp-class">MockHttp Class</h2>
|
|
316
|
+
<h3 id="constructor">Constructor</h3>
|
|
317
|
+
<pre><code class="hljs language-javascript"><span class="hljs-keyword">new</span> <span class="hljs-title class_">MockHttp</span>(options?)
|
|
318
|
+
</code></pre>
|
|
319
|
+
<p><strong>Parameters:</strong></p>
|
|
320
|
+
<ul>
|
|
321
|
+
<li><code>options?</code> (MockHttpOptions):
|
|
322
|
+
<ul>
|
|
323
|
+
<li><code>port?</code>: number - The port to listen on (default: 3000)</li>
|
|
324
|
+
<li><code>host?</code>: string - The host to listen on (default: '0.0.0.0')</li>
|
|
325
|
+
<li><code>autoDetectPort?</code>: boolean - Auto-detect next available port if in use (default: true)</li>
|
|
326
|
+
<li><code>helmet?</code>: boolean - Use Helmet for security headers (default: true)</li>
|
|
327
|
+
<li><code>apiDocs?</code>: boolean - Serve the Docula documentation site and OpenAPI spec (default: true)</li>
|
|
328
|
+
<li><code>siteDistPath?</code>: string - Path to the built Docula site (default: the package <code>site/dist</code> directory)</li>
|
|
329
|
+
<li><code>rateLimit?</code>: RateLimitPluginOptions - Configure rate limiting (default: 1000 req/min, localhost excluded)</li>
|
|
330
|
+
<li><code>logging?</code>: boolean - Enable logging (default: true)</li>
|
|
331
|
+
<li><code>staticFiles?</code>: boolean - Serve files from the package <code>public/</code> directory (default: true)</li>
|
|
332
|
+
<li><code>pluginTimeout?</code>: number - Fastify plugin boot timeout in milliseconds; <code>0</code> disables it (default: Fastify's timeout)</li>
|
|
333
|
+
<li><code>startBins?</code>: boolean - Start the request-bin cleanup timer during <code>initialize()</code> (default: true)</li>
|
|
334
|
+
<li><code>httpBin?</code>: HttpBinOptions - Configure which httpbin routes to enable
|
|
335
|
+
<ul>
|
|
336
|
+
<li><code>httpMethods?</code>: boolean - Enable HTTP method routes (default: true)</li>
|
|
337
|
+
<li><code>redirects?</code>: boolean - Enable redirect routes (default: true)</li>
|
|
338
|
+
<li><code>requestInspection?</code>: boolean - Enable request inspection routes (default: true)</li>
|
|
339
|
+
<li><code>responseInspection?</code>: boolean - Enable response inspection routes (default: true)</li>
|
|
340
|
+
<li><code>statusCodes?</code>: boolean - Enable status code routes (default: true)</li>
|
|
341
|
+
<li><code>responseFormats?</code>: boolean - Enable response format routes (default: true)</li>
|
|
342
|
+
<li><code>cookies?</code>: boolean - Enable cookie routes (default: true)</li>
|
|
343
|
+
<li><code>anything?</code>: boolean - Enable anything routes (default: true)</li>
|
|
344
|
+
<li><code>auth?</code>: boolean - Enable authentication routes (default: true)</li>
|
|
345
|
+
<li><code>images?</code>: boolean - Enable image routes (default: true)</li>
|
|
346
|
+
<li><code>bins?</code>: boolean - Enable request bin routes /bins and /b/:id (default: true)</li>
|
|
347
|
+
</ul>
|
|
348
|
+
</li>
|
|
349
|
+
<li><code>https?</code>: boolean | HttpsOptions - Enable HTTPS with auto-generated or custom certificates (default: undefined/disabled)</li>
|
|
350
|
+
<li><code>http2?</code>: boolean - Enable HTTP/2 support (default: false)</li>
|
|
351
|
+
<li><code>http1?</code>: boolean - Allow HTTP/1.1 fallback when using HTTP/2 with HTTPS (default: true)</li>
|
|
352
|
+
<li><code>hookOptions?</code>: HookifiedOptions - Hookified options</li>
|
|
353
|
+
</ul>
|
|
354
|
+
</li>
|
|
355
|
+
</ul>
|
|
356
|
+
<h3 id="properties">Properties</h3>
|
|
357
|
+
<ul>
|
|
358
|
+
<li><code>port</code>: number - Get/set the server port</li>
|
|
359
|
+
<li><code>host</code>: string - Get/set the server host</li>
|
|
360
|
+
<li><code>autoDetectPort</code>: boolean - Get/set auto-detect port behavior</li>
|
|
361
|
+
<li><code>helmet</code>: boolean - Get/set Helmet security headers</li>
|
|
362
|
+
<li><code>apiDocs</code>: boolean - Get/set whether the documentation site and OpenAPI spec are served</li>
|
|
363
|
+
<li><code>siteDistPath</code>: string - Get/set the path to the built Docula site</li>
|
|
364
|
+
<li><code>logging</code>: boolean - Get/set logging enabled state</li>
|
|
365
|
+
<li><code>staticFiles</code>: boolean - Get/set whether files from <code>public/</code> are served</li>
|
|
366
|
+
<li><code>pluginTimeout</code>: number | undefined - Get/set Fastify plugin boot timeout</li>
|
|
367
|
+
<li><code>startBins</code>: boolean - Get/set whether bin cleanup starts during <code>initialize()</code></li>
|
|
368
|
+
<li><code>rateLimit</code>: RateLimitPluginOptions | undefined - Get/set rate limiting options</li>
|
|
369
|
+
<li><code>httpBin</code>: HttpBinOptions - Get/set httpbin route options</li>
|
|
370
|
+
<li><code>https</code>: HttpsOptions | undefined - Get/set HTTPS configuration</li>
|
|
371
|
+
<li><code>isHttps</code>: boolean - Whether the server is running with HTTPS</li>
|
|
372
|
+
<li><code>http2</code>: boolean - Get/set HTTP/2 support</li>
|
|
373
|
+
<li><code>http1</code>: boolean - Get/set HTTP/1.1 fallback for HTTP/2 with HTTPS</li>
|
|
374
|
+
<li><code>server</code>: FastifyInstance - Get/set the Fastify server instance</li>
|
|
375
|
+
<li><code>taps</code>: TapManager - Get/set the TapManager instance</li>
|
|
376
|
+
<li><code>bins</code>: BinManager - Get/set the BinManager instance for request bins</li>
|
|
377
|
+
</ul>
|
|
378
|
+
<h3 id="methods">Methods</h3>
|
|
379
|
+
<h4 id="async-initialize"><code>async initialize()</code></h4>
|
|
380
|
+
<p>Register plugins and routes without listening. Use <code>server.inject()</code> afterwards, or call <code>start()</code> to bind a port.</p>
|
|
381
|
+
<h4 id="async-start"><code>async start()</code></h4>
|
|
382
|
+
<p>Start the Fastify server. If already running, it will be closed and restarted.</p>
|
|
383
|
+
<h4 id="async-close"><code>async close()</code></h4>
|
|
384
|
+
<p>Stop the Fastify server.</p>
|
|
385
|
+
<h4 id="async-detectport"><code>async detectPort()</code></h4>
|
|
386
|
+
<p>Detect the next available port.</p>
|
|
387
|
+
<p><strong>Returns:</strong> number - The available port</p>
|
|
388
|
+
<h4 id="async-registerapidocsfastifyinstance"><code>async registerApiDocs(fastifyInstance?)</code></h4>
|
|
389
|
+
<p>Register OpenAPI generation and the Docula documentation site.</p>
|
|
390
|
+
<h4 id="async-registerswaggerfastifyinstance"><code>async registerSwagger(fastifyInstance?)</code></h4>
|
|
391
|
+
<p>Register <code>@fastify/swagger</code> and the live <code>/openapi.json</code> spec route.</p>
|
|
392
|
+
<h4 id="async-registersitefastifyinstance"><code>async registerSite(fastifyInstance?)</code></h4>
|
|
393
|
+
<p>Serve the built Docula site from <code>siteDistPath</code> when the directory exists.</p>
|
|
394
|
+
<h4 id="async-registerhttpmethodsfastifyinstance"><code>async registerHttpMethods(fastifyInstance?)</code></h4>
|
|
395
|
+
<p>Register HTTP method routes (GET, POST, PUT, PATCH, DELETE).</p>
|
|
396
|
+
<h4 id="async-registerstatuscoderoutesfastifyinstance"><code>async registerStatusCodeRoutes(fastifyInstance?)</code></h4>
|
|
397
|
+
<p>Register status code routes.</p>
|
|
398
|
+
<h4 id="async-registerrequestinspectionroutesfastifyinstance"><code>async registerRequestInspectionRoutes(fastifyInstance?)</code></h4>
|
|
399
|
+
<p>Register request inspection routes (headers, ip, user-agent).</p>
|
|
400
|
+
<h4 id="async-registerresponseinspectionroutesfastifyinstance"><code>async registerResponseInspectionRoutes(fastifyInstance?)</code></h4>
|
|
401
|
+
<p>Register response inspection routes (cache, etag, response-headers).</p>
|
|
402
|
+
<h4 id="async-registerresponseformatroutesfastifyinstance"><code>async registerResponseFormatRoutes(fastifyInstance?)</code></h4>
|
|
403
|
+
<p>Register response format routes (json, xml, html, etc.).</p>
|
|
404
|
+
<h4 id="async-registerredirectroutesfastifyinstance"><code>async registerRedirectRoutes(fastifyInstance?)</code></h4>
|
|
405
|
+
<p>Register redirect routes (absolute, relative, redirect-to).</p>
|
|
406
|
+
<h4 id="async-registercookieroutesfastifyinstance"><code>async registerCookieRoutes(fastifyInstance?)</code></h4>
|
|
407
|
+
<p>Register cookie routes (get, set, delete).</p>
|
|
408
|
+
<h4 id="async-registeranythingroutesfastifyinstance"><code>async registerAnythingRoutes(fastifyInstance?)</code></h4>
|
|
409
|
+
<p>Register "anything" catch-all routes.</p>
|
|
410
|
+
<h4 id="async-registerauthroutesfastifyinstance"><code>async registerAuthRoutes(fastifyInstance?)</code></h4>
|
|
411
|
+
<p>Register authentication routes (basic, bearer, digest, hidden-basic).</p>
|
|
412
|
+
<h4 id="async-registerimageroutesfastifyinstance"><code>async registerImageRoutes(fastifyInstance?)</code></h4>
|
|
413
|
+
<p>Register image routes (jpeg, png, svg, webp) with content negotiation support.</p>
|
|
414
|
+
<h4 id="async-registerbinroutesfastifyinstance"><code>async registerBinRoutes(fastifyInstance?)</code></h4>
|
|
415
|
+
<p>Register the request bin routes — management at <code>/bins</code> and capture at <code>/b/:id</code>.</p>
|
|
416
|
+
<h2 id="bins-request-capture">Bins (Request Capture)</h2>
|
|
417
|
+
<p>Access the BinManager via <code>mockHttp.bins</code> to manage request bins programmatically. See <a href="#request-bins">Request Bins</a> for usage examples.</p>
|
|
418
|
+
<h3 id="binscreatebin"><code>bins.createBin()</code></h3>
|
|
419
|
+
<p>Create a new bin with the configured TTL.</p>
|
|
420
|
+
<p><strong>Returns:</strong> <code>Bin</code> — <code>{ id, createdAt, expiresAt, requestCount }</code></p>
|
|
421
|
+
<h3 id="binsgetbinid"><code>bins.getBin(id)</code></h3>
|
|
422
|
+
<p>Look up a bin by id. Returns <code>undefined</code> if the bin does not exist or has expired (and lazily removes the expired entry).</p>
|
|
423
|
+
<p><strong>Returns:</strong> <code>Bin | undefined</code></p>
|
|
424
|
+
<h3 id="binslistbins"><code>bins.listBins()</code></h3>
|
|
425
|
+
<p>List all non-expired bins.</p>
|
|
426
|
+
<p><strong>Returns:</strong> <code>Bin[]</code></p>
|
|
427
|
+
<h3 id="binsdeletebinid"><code>bins.deleteBin(id)</code></h3>
|
|
428
|
+
<p>Delete a bin and all its captured requests.</p>
|
|
429
|
+
<p><strong>Returns:</strong> <code>boolean</code> — <code>true</code> if the bin existed</p>
|
|
430
|
+
<h3 id="binsgetrequestsbinid"><code>bins.getRequests(binId)</code></h3>
|
|
431
|
+
<p>List captured requests for a bin, newest first.</p>
|
|
432
|
+
<p><strong>Returns:</strong> <code>CapturedRequest[]</code></p>
|
|
433
|
+
<h3 id="binsgetrequestbinid-reqid"><code>bins.getRequest(binId, reqId)</code></h3>
|
|
434
|
+
<p>Look up a single captured request.</p>
|
|
435
|
+
<p><strong>Returns:</strong> <code>CapturedRequest | undefined</code></p>
|
|
436
|
+
<h3 id="binsclearrequestsbinid"><code>bins.clearRequests(binId)</code></h3>
|
|
437
|
+
<p>Clear all captured requests in a bin (the bin itself is preserved).</p>
|
|
438
|
+
<h3 id="binsrecordrequestbinid-raw"><code>bins.recordRequest(binId, raw)</code></h3>
|
|
439
|
+
<p>Manually record a captured request. Returns <code>undefined</code> if the bin does not exist or is expired. Normally invoked by the capture route, but available for programmatic use.</p>
|
|
440
|
+
<p><strong>Returns:</strong> <code>CapturedRequest | undefined</code></p>
|
|
441
|
+
<h3 id="binsstart--binsstop"><code>bins.start()</code> / <code>bins.stop()</code></h3>
|
|
442
|
+
<p>Start or stop the periodic cleanup of expired bins. <code>MockHttp.start()</code> and <code>MockHttp.close()</code> call these automatically. The interval is <code>unref()</code>'d so it never keeps the Node process alive.</p>
|
|
443
|
+
<h3 id="binsmaxbodysize--binsmaxrequestsperbin--binsdefaultttlms"><code>bins.maxBodySize</code> / <code>bins.maxRequestsPerBin</code> / <code>bins.defaultTtlMs</code></h3>
|
|
444
|
+
<p>Read-only getters for the currently-configured limits.</p>
|
|
445
|
+
<h2 id="taps-response-injection">Taps (Response Injection)</h2>
|
|
446
|
+
<p>Access the TapManager via <code>mockHttp.taps</code> to inject custom responses.</p>
|
|
447
|
+
<h3 id="tapsinjectresponse-matcher"><code>taps.inject(response, matcher?)</code></h3>
|
|
448
|
+
<p>Injects a custom response for requests matching the criteria.</p>
|
|
449
|
+
<p><strong>Parameters:</strong></p>
|
|
450
|
+
<ul>
|
|
451
|
+
<li>
|
|
452
|
+
<p><code>response</code> (InjectionResponse | InjectionResponseFunction):</p>
|
|
453
|
+
<ul>
|
|
454
|
+
<li><strong>Static Response</strong> (InjectionResponse):
|
|
455
|
+
<ul>
|
|
456
|
+
<li><code>response</code>: string | object | Buffer - The response body</li>
|
|
457
|
+
<li><code>statusCode?</code>: number - HTTP status code (default: 200)</li>
|
|
458
|
+
<li><code>headers?</code>: object - Response headers</li>
|
|
459
|
+
</ul>
|
|
460
|
+
</li>
|
|
461
|
+
<li><strong>Function Response</strong> (InjectionResponseFunction):
|
|
462
|
+
<ul>
|
|
463
|
+
<li>A function that receives the Fastify request object and returns an InjectionResponse</li>
|
|
464
|
+
<li><code>(request: FastifyRequest) => InjectionResponse</code></li>
|
|
465
|
+
<li>Allows dynamic response generation based on request properties (url, method, headers, etc.)</li>
|
|
466
|
+
</ul>
|
|
467
|
+
</li>
|
|
468
|
+
</ul>
|
|
469
|
+
</li>
|
|
470
|
+
<li>
|
|
471
|
+
<p><code>matcher?</code> (InjectionMatcher) - Optional matching criteria:</p>
|
|
472
|
+
<ul>
|
|
473
|
+
<li><code>url?</code>: string - URL path (supports wildcards with <code>*</code>)</li>
|
|
474
|
+
<li><code>method?</code>: string - HTTP method (GET, POST, etc.)</li>
|
|
475
|
+
<li><code>hostname?</code>: string - Hostname to match</li>
|
|
476
|
+
<li><code>headers?</code>: object - Headers that must be present</li>
|
|
477
|
+
</ul>
|
|
478
|
+
</li>
|
|
479
|
+
</ul>
|
|
480
|
+
<p><strong>Returns:</strong> <code>InjectionTap</code> - A tap object with a unique <code>id</code> that can be used to remove the injection</p>
|
|
481
|
+
<h2 id="tapsremoveinjectiontaporid"><code>taps.removeInjection(tapOrId)</code></h2>
|
|
482
|
+
<p>Removes an injection.</p>
|
|
483
|
+
<p><strong>Parameters:</strong></p>
|
|
484
|
+
<ul>
|
|
485
|
+
<li><code>tapOrId</code>: InjectionTap | string - The tap object or tap ID to remove</li>
|
|
486
|
+
</ul>
|
|
487
|
+
<p><strong>Returns:</strong> boolean - <code>true</code> if removed, <code>false</code> if not found</p>
|
|
488
|
+
<h3 id="tapsinjections"><code>taps.injections</code></h3>
|
|
489
|
+
<p>A getter that returns a Map of all active injection taps.</p>
|
|
490
|
+
<p><strong>Returns:</strong> <code>Map<string, InjectionTap></code> - Map of all active injections with tap IDs as keys</p>
|
|
491
|
+
<h2 id="tapsclear"><code>taps.clear()</code></h2>
|
|
492
|
+
<p>Removes all injections.</p>
|
|
493
|
+
<h2 id="tapshasinjections"><code>taps.hasInjections</code></h2>
|
|
494
|
+
<p>A getter that returns whether there are any active injections.</p>
|
|
495
|
+
<p><strong>Returns:</strong> boolean - <code>true</code> if there are active injections, <code>false</code> otherwise</p>
|
|
496
|
+
</main>
|
|
497
|
+
<aside class="content-aside">
|
|
498
|
+
<h2 class="content-aside__title">TABLE OF CONTENTS</h2>
|
|
499
|
+
<ul class="content-aside__list" id="toc-list">
|
|
500
|
+
</ul>
|
|
501
|
+
</aside>
|
|
502
|
+
</div>
|
|
503
|
+
<a class="article__edit-link" href="https://github.com/jaredwray/mockhttp/edit/main/site/docs/library.md" target="_blank" rel="noopener noreferrer">
|
|
504
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
|
505
|
+
Edit this page
|
|
506
|
+
</a>
|
|
507
|
+
</article>
|
|
508
|
+
</div>
|
|
509
|
+
<footer>
|
|
510
|
+
<a href="/llms.txt" class="footer__link">llms.txt</a>
|
|
511
|
+
<a href="/feed.xml" class="footer__link" aria-label="RSS Feed">
|
|
512
|
+
<svg class="footer__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="6.18" cy="17.82" r="2.18"/><path d="M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z"/></svg>
|
|
513
|
+
</a>
|
|
514
|
+
|
|
515
|
+
<a href="https://docula.org/" target="_blank" rel="noopener noreferrer" class="footer__link" aria-label="Docula">
|
|
516
|
+
<svg class="footer__bat" xmlns="http://www.w3.org/2000/svg" viewBox="70 110 360 205" fill="currentColor"><path d="M420,173c-16.76-18-72.78-36.65-131.59-30.58A160.15,160.15,0,0,0,269,120.55c-3.78,4.52-11.15,9.45-19,9.45s-15.23-4.93-19-9.45a160.15,160.15,0,0,0-19.4,21.83C152.78,136.31,96.76,154.94,80,173c48.86,5.86,79.5,34.09,79.5,72.06,52.37,0,77,28,90.5,59.94C263.54,273,288.13,245,340.5,245,340.5,207,371.14,178.8,420,173Z"/><circle cx="239.19" cy="145.36" r="3.32" fill="#fff"/><circle cx="260.81" cy="145.36" r="3.32" fill="#fff"/></svg>
|
|
517
|
+
</a>
|
|
518
|
+
<span class="footer__updated">updated: 2026-09-18</span>
|
|
519
|
+
</footer>
|
|
520
|
+
<script src="/css/highlight/highlight.min.js"></script>
|
|
521
|
+
<script>
|
|
522
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
523
|
+
document.querySelectorAll('pre code').forEach(el => { el.textContent = el.textContent.trimEnd(); });
|
|
524
|
+
hljs.highlightAll();
|
|
525
|
+
var kbd = document.querySelector('.search-button__shortcut');
|
|
526
|
+
if (kbd) kbd.textContent = navigator.platform.indexOf('Mac') > -1 ? '⌘K' : 'Ctrl K';
|
|
527
|
+
|
|
528
|
+
// Copy code buttons
|
|
529
|
+
const copyIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
530
|
+
const checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
531
|
+
document.querySelectorAll('pre').forEach(function(pre) {
|
|
532
|
+
const btn = document.createElement('button');
|
|
533
|
+
btn.className = 'copy-code-btn';
|
|
534
|
+
btn.innerHTML = copyIcon;
|
|
535
|
+
btn.setAttribute('aria-label', 'Copy code');
|
|
536
|
+
btn.addEventListener('click', function() {
|
|
537
|
+
const code = pre.querySelector('code');
|
|
538
|
+
const text = code ? code.textContent : pre.textContent;
|
|
539
|
+
navigator.clipboard.writeText(text || '').then(function() {
|
|
540
|
+
btn.innerHTML = checkIcon;
|
|
541
|
+
setTimeout(function() { btn.innerHTML = copyIcon; }, 2000);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
pre.appendChild(btn);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
</script>
|
|
548
|
+
<script>
|
|
549
|
+
(function() {
|
|
550
|
+
var themeKey = window.__doculaThemeKey;
|
|
551
|
+
function getMode() {
|
|
552
|
+
return localStorage.getItem(themeKey) || 'light';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function resolveTheme(mode) {
|
|
556
|
+
if (mode === 'system') {
|
|
557
|
+
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
558
|
+
}
|
|
559
|
+
return mode;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
var cachedEls = null;
|
|
563
|
+
function getThemeEls() {
|
|
564
|
+
if (!cachedEls) {
|
|
565
|
+
cachedEls = {
|
|
566
|
+
systemIcon: document.querySelector('.theme-button__icon--system'),
|
|
567
|
+
sunIcon: document.querySelector('.theme-button__icon--sun'),
|
|
568
|
+
moonIcon: document.querySelector('.theme-button__icon--moon'),
|
|
569
|
+
toggle: document.getElementById('theme-toggle')
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
return cachedEls;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function applyTheme(mode) {
|
|
576
|
+
var resolved = resolveTheme(mode);
|
|
577
|
+
if (resolved === 'light') {
|
|
578
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
579
|
+
} else {
|
|
580
|
+
document.documentElement.removeAttribute('data-theme');
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
var els = getThemeEls();
|
|
584
|
+
if (els.systemIcon && els.sunIcon && els.moonIcon) {
|
|
585
|
+
els.systemIcon.classList.add('theme-button__icon--hidden');
|
|
586
|
+
els.sunIcon.classList.add('theme-button__icon--hidden');
|
|
587
|
+
els.moonIcon.classList.add('theme-button__icon--hidden');
|
|
588
|
+
if (mode === 'system') {
|
|
589
|
+
els.systemIcon.classList.remove('theme-button__icon--hidden');
|
|
590
|
+
} else if (mode === 'light') {
|
|
591
|
+
els.sunIcon.classList.remove('theme-button__icon--hidden');
|
|
592
|
+
} else {
|
|
593
|
+
els.moonIcon.classList.remove('theme-button__icon--hidden');
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (els.toggle) {
|
|
598
|
+
var labels = { system: 'Theme: system (following system)', light: 'Theme: light', dark: 'Theme: dark' };
|
|
599
|
+
els.toggle.setAttribute('aria-label', labels[mode] || 'Toggle theme');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function nextMode(current) {
|
|
604
|
+
var cycle = { system: 'light', light: 'dark', dark: 'system' };
|
|
605
|
+
return cycle[current] || 'system';
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
var currentMode = getMode();
|
|
609
|
+
|
|
610
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
611
|
+
applyTheme(currentMode);
|
|
612
|
+
|
|
613
|
+
var els = getThemeEls();
|
|
614
|
+
if (els.toggle) {
|
|
615
|
+
els.toggle.addEventListener('click', function() {
|
|
616
|
+
currentMode = nextMode(currentMode);
|
|
617
|
+
localStorage.setItem(themeKey, currentMode);
|
|
618
|
+
applyTheme(currentMode);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function() {
|
|
624
|
+
if (getMode() === 'system') {
|
|
625
|
+
applyTheme('system');
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
})();
|
|
629
|
+
</script>
|
|
630
|
+
<script defer>
|
|
631
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
632
|
+
const menuToggle = document.getElementById('mobile-menu-toggle');
|
|
633
|
+
const mobileSidebar = document.getElementById('mobile-sidebar');
|
|
634
|
+
const backdrop = document.getElementById('sidebar-backdrop');
|
|
635
|
+
if (menuToggle && mobileSidebar) {
|
|
636
|
+
menuToggle.addEventListener('click', () => {
|
|
637
|
+
mobileSidebar.classList.toggle('mobile-sidebar--open');
|
|
638
|
+
if (backdrop) backdrop.classList.toggle('sidebar-backdrop--visible');
|
|
639
|
+
});
|
|
640
|
+
if (backdrop) {
|
|
641
|
+
backdrop.addEventListener('click', () => {
|
|
642
|
+
mobileSidebar.classList.remove('mobile-sidebar--open');
|
|
643
|
+
backdrop.classList.remove('sidebar-backdrop--visible');
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Page dropdown toggle (docs pages)
|
|
649
|
+
const dropdownToggle = document.getElementById('page-dropdown-toggle');
|
|
650
|
+
const dropdownList = document.getElementById('page-dropdown-list');
|
|
651
|
+
if (dropdownToggle && dropdownList) {
|
|
652
|
+
dropdownToggle.addEventListener('click', () => {
|
|
653
|
+
dropdownList.classList.toggle('page-dropdown__list--open');
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Active sidebar link highlighting
|
|
658
|
+
const sidebarLinks = document.querySelectorAll('.nav-sidebar__item');
|
|
659
|
+
const currentPath = window.location.pathname;
|
|
660
|
+
sidebarLinks.forEach((link) => {
|
|
661
|
+
if (link.getAttribute('href') === currentPath || link.getAttribute('href') === currentPath.replace(/\/$/, '')) {
|
|
662
|
+
link.classList.add('nav-sidebar__item--active');
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// Sidebar section collapse/expand
|
|
667
|
+
const SIDEBAR_STORAGE_KEY = 'docula:sidebar-sections';
|
|
668
|
+
const collapsibleSections = document.querySelectorAll('.nav-sidebar__section--collapsible');
|
|
669
|
+
if (collapsibleSections.length > 0) {
|
|
670
|
+
let storedSectionState = {};
|
|
671
|
+
try {
|
|
672
|
+
storedSectionState = JSON.parse(localStorage.getItem(SIDEBAR_STORAGE_KEY) || '{}');
|
|
673
|
+
} catch (e) { storedSectionState = {}; }
|
|
674
|
+
|
|
675
|
+
const setSectionOpen = (section, toggle, open) => {
|
|
676
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
677
|
+
section.classList.toggle('nav-sidebar__section--collapsed', !open);
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
collapsibleSections.forEach((section, idx) => {
|
|
681
|
+
const toggle = section.querySelector('.nav-sidebar__toggle');
|
|
682
|
+
const list = section.querySelector('.nav-sidebar__list');
|
|
683
|
+
if (!toggle || !list) return;
|
|
684
|
+
const listId = 'nav-sidebar-section-' + idx;
|
|
685
|
+
list.id = listId;
|
|
686
|
+
toggle.setAttribute('aria-controls', listId);
|
|
687
|
+
|
|
688
|
+
const key = 'section-' + idx;
|
|
689
|
+
const hasStored = Object.prototype.hasOwnProperty.call(storedSectionState, key);
|
|
690
|
+
const isOpen = hasStored ? !!storedSectionState[key] : true;
|
|
691
|
+
setSectionOpen(section, toggle, isOpen);
|
|
692
|
+
|
|
693
|
+
toggle.addEventListener('click', () => {
|
|
694
|
+
const next = toggle.getAttribute('aria-expanded') !== 'true';
|
|
695
|
+
setSectionOpen(section, toggle, next);
|
|
696
|
+
storedSectionState[key] = next;
|
|
697
|
+
try {
|
|
698
|
+
localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(storedSectionState));
|
|
699
|
+
} catch (e) { /* storage unavailable */ }
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Active nav link highlighting (header + mobile). Highlight only the
|
|
705
|
+
// single most-specific link: the one whose path is the longest prefix of
|
|
706
|
+
// the current path. A plain startsWith() check lights up every link whose
|
|
707
|
+
// path merely prefixes the current one, so when the site is embedded under
|
|
708
|
+
// a base path (e.g. served at /developer with docs at that root) the
|
|
709
|
+
// Documentation link — whose path is a prefix of every page — would stay
|
|
710
|
+
// highlighted everywhere. Trailing slashes are normalized so matches stop
|
|
711
|
+
// at a path boundary. We read the anchor's resolved origin/pathname so
|
|
712
|
+
// absolute, relative, and same-origin hrefs all work, while cross-origin
|
|
713
|
+
// links and non-anchor items (e.g. the mobile logout button) are skipped.
|
|
714
|
+
const withTrailingSlash = (p) => (p.endsWith('/') ? p : p + '/');
|
|
715
|
+
const here = withTrailingSlash(currentPath);
|
|
716
|
+
const markActiveNav = (selector, activeClass) => {
|
|
717
|
+
let bestLink = null;
|
|
718
|
+
let bestLength = -1;
|
|
719
|
+
document.querySelectorAll(selector).forEach((link) => {
|
|
720
|
+
// Only same-origin anchors can match the current pathname.
|
|
721
|
+
if (!link.href || link.origin !== window.location.origin) return;
|
|
722
|
+
const base = withTrailingSlash(link.pathname);
|
|
723
|
+
if (here.startsWith(base) && base.length > bestLength) {
|
|
724
|
+
bestLink = link;
|
|
725
|
+
bestLength = base.length;
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
if (bestLink) bestLink.classList.add(activeClass);
|
|
729
|
+
};
|
|
730
|
+
markActiveNav('.header-bottom__item', 'header-bottom__item--active');
|
|
731
|
+
markActiveNav('.mobile-nav__item', 'mobile-nav__item--active');
|
|
732
|
+
|
|
733
|
+
// TOC extraction: move the inline TOC into the right sidebar and hide the original
|
|
734
|
+
const tocHeading = document.getElementById('table-of-contents');
|
|
735
|
+
if (tocHeading) {
|
|
736
|
+
const tocList = tocHeading.nextElementSibling;
|
|
737
|
+
const tocTarget = document.getElementById('toc-list');
|
|
738
|
+
if (tocList && tocList.tagName === 'UL' && tocTarget) {
|
|
739
|
+
tocTarget.innerHTML = tocList.innerHTML;
|
|
740
|
+
tocHeading.style.display = 'none';
|
|
741
|
+
tocList.style.display = 'none';
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Hide the TOC aside if it has no entries
|
|
746
|
+
const tocSidebar = document.getElementById('toc-list');
|
|
747
|
+
if (tocSidebar && tocSidebar.children.length === 0) {
|
|
748
|
+
const aside = tocSidebar.closest('.content-aside');
|
|
749
|
+
if (aside) { aside.style.display = 'none'; }
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Image lightbox
|
|
753
|
+
const lightboxOverlay = document.createElement('div');
|
|
754
|
+
lightboxOverlay.className = 'lightbox-overlay';
|
|
755
|
+
lightboxOverlay.addEventListener('click', function(e) {
|
|
756
|
+
if (e.target !== lightboxImg) lightboxOverlay.classList.remove('lightbox-overlay--visible');
|
|
757
|
+
});
|
|
758
|
+
const lightboxClose = document.createElement('button');
|
|
759
|
+
lightboxClose.className = 'lightbox-close';
|
|
760
|
+
lightboxClose.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
761
|
+
lightboxClose.addEventListener('click', function() {
|
|
762
|
+
lightboxOverlay.classList.remove('lightbox-overlay--visible');
|
|
763
|
+
});
|
|
764
|
+
const lightboxImg = document.createElement('img');
|
|
765
|
+
lightboxOverlay.appendChild(lightboxClose);
|
|
766
|
+
lightboxOverlay.appendChild(lightboxImg);
|
|
767
|
+
document.body.appendChild(lightboxOverlay);
|
|
768
|
+
|
|
769
|
+
document.querySelectorAll('.article__main img, .changelog-entry-body img').forEach(function(img) {
|
|
770
|
+
if (img.closest('a')) return;
|
|
771
|
+
img.addEventListener('click', function() {
|
|
772
|
+
lightboxImg.src = img.src;
|
|
773
|
+
lightboxOverlay.classList.add('lightbox-overlay--visible');
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
</script>
|
|
778
|
+
</body>
|
|
779
|
+
|
|
780
|
+
</html>
|