@databehave/admin 0.0.0-stage → 0.1.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 (63) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +39 -2
  4. package/assets/ui.css +1 -0
  5. package/assets/ui.html +13 -0
  6. package/assets/ui.js +233 -0
  7. package/dist/admin-plugin.d.ts +23 -0
  8. package/dist/admin-plugin.d.ts.map +1 -0
  9. package/dist/admin-plugin.js +71 -0
  10. package/dist/admin-plugin.js.map +1 -0
  11. package/dist/admin-routes.d.ts +22 -0
  12. package/dist/admin-routes.d.ts.map +1 -0
  13. package/dist/admin-routes.js +316 -0
  14. package/dist/admin-routes.js.map +1 -0
  15. package/dist/admin-types.d.ts +98 -0
  16. package/dist/admin-types.d.ts.map +1 -0
  17. package/dist/admin-types.js +2 -0
  18. package/dist/admin-types.js.map +1 -0
  19. package/dist/banner.d.ts +10 -0
  20. package/dist/banner.d.ts.map +1 -0
  21. package/dist/banner.js +5 -0
  22. package/dist/banner.js.map +1 -0
  23. package/dist/fault-plugin.d.ts +29 -0
  24. package/dist/fault-plugin.d.ts.map +1 -0
  25. package/dist/fault-plugin.js +32 -0
  26. package/dist/fault-plugin.js.map +1 -0
  27. package/dist/header-parser.d.ts +14 -0
  28. package/dist/header-parser.d.ts.map +1 -0
  29. package/dist/header-parser.js +171 -0
  30. package/dist/header-parser.js.map +1 -0
  31. package/dist/index.d.ts +10 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +5 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/inject.d.ts +35 -0
  36. package/dist/inject.d.ts.map +1 -0
  37. package/dist/inject.js +155 -0
  38. package/dist/inject.js.map +1 -0
  39. package/dist/open-browser-plugin.d.ts +6 -0
  40. package/dist/open-browser-plugin.d.ts.map +1 -0
  41. package/dist/open-browser-plugin.js +32 -0
  42. package/dist/open-browser-plugin.js.map +1 -0
  43. package/dist/overrides-store.d.ts +21 -0
  44. package/dist/overrides-store.d.ts.map +1 -0
  45. package/dist/overrides-store.js +114 -0
  46. package/dist/overrides-store.js.map +1 -0
  47. package/dist/resolve-cors.d.ts +6 -0
  48. package/dist/resolve-cors.d.ts.map +1 -0
  49. package/dist/resolve-cors.js +39 -0
  50. package/dist/resolve-cors.js.map +1 -0
  51. package/dist/scenarios-store.d.ts +8 -0
  52. package/dist/scenarios-store.d.ts.map +1 -0
  53. package/dist/scenarios-store.js +136 -0
  54. package/dist/scenarios-store.js.map +1 -0
  55. package/dist/scenarios.d.ts +9 -0
  56. package/dist/scenarios.d.ts.map +1 -0
  57. package/dist/scenarios.js +2 -0
  58. package/dist/scenarios.js.map +1 -0
  59. package/dist/validation.d.ts +17 -0
  60. package/dist/validation.d.ts.map +1 -0
  61. package/dist/validation.js +169 -0
  62. package/dist/validation.js.map +1 -0
  63. package/package.json +52 -3
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Admin REST + UI routes. Mounted under a configurable `basePath`
3
+ * (default `/databehave`).
4
+ *
5
+ * GET {basePath} — admin HTML page (UI shell)
6
+ * GET {basePath}/ — same (trailing-slash equivalent)
7
+ * GET {basePath}/ui.js — admin UI script bundle
8
+ * GET {basePath}/ui.css — admin UI stylesheet
9
+ * GET {basePath}/openapi-routes — `{method, path, summary}[]`
10
+ * GET {basePath}/openapi.json — raw OpenAPI document (404 when not configured)
11
+ * GET {basePath}/overrides — list active sticky overrides
12
+ * POST {basePath}/overrides — add a sticky override (201)
13
+ * DELETE {basePath}/overrides — clear all
14
+ * DELETE {basePath}/overrides/:id — remove by id (404 if missing)
15
+ * GET {basePath}/scenarios — list saved scenarios
16
+ * POST {basePath}/scenarios — save current overrides as a scenario
17
+ * GET {basePath}/scenarios/:name — fetch a saved scenario
18
+ * DELETE {basePath}/scenarios/:name — delete a saved scenario
19
+ * POST {basePath}/scenarios/:name/load — replace active overrides with the scenario
20
+ *
21
+ * CORS for these routes is controlled by `admin.cors`; the kit's
22
+ * server integration injects the resolved header bag here via
23
+ * {@link AdminRoutesOptions.corsHeaders}.
24
+ */
25
+ import { readFile } from 'node:fs/promises';
26
+ import { dirname, resolve } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { isValidScenarioName, validateScenario, isPlainObject, validateMatcher, validateMode, validateOverrides, validateScenarioVersion, ScenarioValidationError } from './validation.js';
29
+ const HERE = dirname(fileURLToPath(import.meta.url));
30
+ const UI_HTML_PATH = resolve(HERE, '../assets/ui.html');
31
+ const UI_JS_PATH = resolve(HERE, '../assets/ui.js');
32
+ const UI_CSS_PATH = resolve(HERE, '../assets/ui.css');
33
+ // Cached on first read — the admin bundle is static for the process lifetime.
34
+ // HTML is cached per basePath because the served body is templated with the
35
+ // base so the UI bundle and the asset <script>/<link> tags know where to
36
+ // fetch from when the UI is mounted at a non-default path.
37
+ const uiHtmlCacheByBase = new Map();
38
+ let uiJsCache;
39
+ let uiCssCache;
40
+ /**
41
+ * Inject the resolved `basePath` into the prebuilt UI shell HTML so
42
+ * the `<script>` / `<link>` tags resolve correctly when the UI is
43
+ * mounted at the bare base path (browsers resolve `./ui.js` against
44
+ * the parent of the current URL, which is wrong when the URL has no
45
+ * trailing slash) and so the UI bundle can read
46
+ * `window.__DATABEHAVE_BASE__` to build its fetch URLs.
47
+ */
48
+ const templateHtml = (raw, basePath) => {
49
+ const baseJson = JSON.stringify(basePath);
50
+ return raw
51
+ .replace(/src="\.\/ui\.js"/g, `src="${basePath}/ui.js"`)
52
+ .replace(/href="\.\/ui\.css"/g, `href="${basePath}/ui.css"`)
53
+ .replace(/<script type="module"/, `<script>window.__DATABEHAVE_BASE__=${baseJson};</script>\n <script type="module"`);
54
+ };
55
+ export const createAdminRoutes = (opts) => {
56
+ const { store, openapiBody } = opts;
57
+ const basePath = opts.basePath ?? '/databehave';
58
+ const ui = opts.ui !== false;
59
+ const corsHeaders = opts.corsHeaders ?? {};
60
+ if (!basePath.startsWith('/')) {
61
+ throw new Error(`@databehave/admin: admin.path must start with "/" (got ${JSON.stringify(basePath)})`);
62
+ }
63
+ const json = (status, body) => ({
64
+ status,
65
+ json: body,
66
+ headers: { ...corsHeaders },
67
+ });
68
+ const uiHandler = async () => {
69
+ try {
70
+ let html = uiHtmlCacheByBase.get(basePath);
71
+ if (html === undefined) {
72
+ const raw = await readFile(UI_HTML_PATH, 'utf8');
73
+ html = templateHtml(raw, basePath);
74
+ uiHtmlCacheByBase.set(basePath, html);
75
+ }
76
+ return { status: 200, html, headers: { ...corsHeaders } };
77
+ /* v8 ignore start -- ui.html ships with the package; missing only on a broken install */
78
+ }
79
+ catch (err) {
80
+ return json(500, {
81
+ error: 'failed to read ui.html',
82
+ detail: err instanceof Error ? err.message : String(err),
83
+ });
84
+ }
85
+ /* v8 ignore stop */
86
+ };
87
+ const uiJsHandler = async () => {
88
+ try {
89
+ if (uiJsCache === undefined) {
90
+ uiJsCache = await readFile(UI_JS_PATH, 'utf8');
91
+ }
92
+ return {
93
+ status: 200,
94
+ text: uiJsCache,
95
+ headers: {
96
+ ...corsHeaders,
97
+ 'Content-Type': 'application/javascript; charset=utf-8',
98
+ },
99
+ };
100
+ /* v8 ignore start -- ui.js ships with the package; missing only on a broken install */
101
+ }
102
+ catch (err) {
103
+ return json(500, {
104
+ error: 'failed to read ui.js',
105
+ detail: err instanceof Error ? err.message : String(err),
106
+ });
107
+ }
108
+ /* v8 ignore stop */
109
+ };
110
+ const uiCssHandler = async () => {
111
+ try {
112
+ if (uiCssCache === undefined) {
113
+ uiCssCache = await readFile(UI_CSS_PATH, 'utf8');
114
+ }
115
+ return {
116
+ status: 200,
117
+ text: uiCssCache,
118
+ headers: {
119
+ ...corsHeaders,
120
+ 'Content-Type': 'text/css; charset=utf-8',
121
+ },
122
+ };
123
+ /* v8 ignore start -- ui.css ships with the package; missing only on a broken install */
124
+ }
125
+ catch (err) {
126
+ return json(500, {
127
+ error: 'failed to read ui.css',
128
+ detail: err instanceof Error ? err.message : String(err),
129
+ });
130
+ }
131
+ /* v8 ignore stop */
132
+ };
133
+ const listRoutesHandler = () => json(200, {
134
+ features: { scenarios: opts.scenarios !== undefined },
135
+ routes: (opts.catalog?.() ?? [])
136
+ .filter((route) => route.path !== basePath && !route.path.startsWith(`${basePath}/`))
137
+ .map((route) => ({ method: route.method.toUpperCase(), path: route.path,
138
+ source: route.source === 'openapi' ? 'openapi' : 'handler',
139
+ ...(typeof route.metadata?.summary === 'string' ? { summary: route.metadata.summary } : {}),
140
+ })).sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method)),
141
+ discoveredAt: new Date().toISOString(),
142
+ });
143
+ const openApiDocHandler = () => {
144
+ const document = openapiBody?.();
145
+ if (document === undefined) {
146
+ return json(404, { error: 'no openapi document configured' });
147
+ }
148
+ return {
149
+ status: 200,
150
+ text: document,
151
+ headers: {
152
+ ...corsHeaders,
153
+ 'Content-Type': 'application/json',
154
+ 'Cache-Control': 'no-store',
155
+ },
156
+ };
157
+ };
158
+ const listOverridesHandler = () => json(200, { overrides: store.list() });
159
+ const addOverrideHandler = async (req) => {
160
+ let body;
161
+ try {
162
+ body = await req.json();
163
+ }
164
+ catch (err) {
165
+ return json(400, {
166
+ error: 'invalid JSON body',
167
+ detail: err instanceof Error ? err.message : String(err),
168
+ });
169
+ }
170
+ if (!isPlainObject(body))
171
+ return json(400, { error: 'body must be a JSON object' });
172
+ const [matcher, mErr] = validateMatcher(body.matcher);
173
+ if (!matcher)
174
+ return json(400, { error: `matcher invalid: ${mErr}` });
175
+ const [mode, modeErr] = validateMode(body.mode);
176
+ if (!mode)
177
+ return json(400, { error: `mode invalid: ${modeErr}` });
178
+ const description = typeof body.description === 'string' ? body.description : undefined;
179
+ const override = store.add({
180
+ matcher,
181
+ mode,
182
+ ...(description !== undefined ? { description } : {}),
183
+ });
184
+ return {
185
+ status: 201,
186
+ json: { id: override.id, override },
187
+ headers: { ...corsHeaders },
188
+ };
189
+ };
190
+ const removeByIdHandler = (req) => {
191
+ const id = req.params.id;
192
+ if (!id)
193
+ return json(400, { error: 'missing :id' });
194
+ const ok = store.remove(id);
195
+ if (!ok)
196
+ return json(404, { error: 'override not found', id });
197
+ return json(200, { deleted: true, id });
198
+ };
199
+ const clearAllHandler = () => {
200
+ const n = store.clear();
201
+ return json(200, { cleared: n });
202
+ };
203
+ const routes = {
204
+ [`GET ${basePath}/openapi-routes`]: listRoutesHandler,
205
+ [`GET ${basePath}/openapi.json`]: openApiDocHandler,
206
+ [`GET ${basePath}/overrides`]: listOverridesHandler,
207
+ [`POST ${basePath}/overrides`]: addOverrideHandler,
208
+ [`DELETE ${basePath}/overrides`]: clearAllHandler,
209
+ [`DELETE ${basePath}/overrides/:id`]: removeByIdHandler,
210
+ };
211
+ if (ui) {
212
+ // Serve the HTML shell at the bare base path AND its trailing-slash
213
+ // variant so both `/databehave` and `/databehave/` work in a browser.
214
+ routes[`GET ${basePath}`] = uiHandler;
215
+ if (!basePath.endsWith('/')) {
216
+ routes[`GET ${basePath}/`] = uiHandler;
217
+ }
218
+ routes[`GET ${basePath}/ui.js`] = uiJsHandler;
219
+ routes[`GET ${basePath}/ui.css`] = uiCssHandler;
220
+ }
221
+ // Scenarios (snapshots persisted by the supplied store). Mounted only
222
+ // when a scenarios store is provided — pure REST CRUD plus a
223
+ // `:name/load` action that atomically replaces the active overrides.
224
+ const scenarios = opts.scenarios;
225
+ if (scenarios) {
226
+ const listScenariosHandler = async () => json(200, { scenarios: await scenarios.list() });
227
+ const getScenarioHandler = async (req) => {
228
+ const name = req.params.name ?? '';
229
+ if (!isValidScenarioName(name))
230
+ return json(400, { error: 'invalid scenario name' });
231
+ const s = await scenarios.get(name);
232
+ if (s === null)
233
+ return json(404, { error: 'scenario not found', name });
234
+ return json(200, validateScenario(s, name));
235
+ };
236
+ const createScenarioHandler = async (req) => {
237
+ let body;
238
+ try {
239
+ body = await req.json();
240
+ }
241
+ catch (err) {
242
+ return json(400, {
243
+ error: 'invalid JSON body',
244
+ detail: err instanceof Error ? err.message : String(err),
245
+ });
246
+ }
247
+ if (!isPlainObject(body))
248
+ return json(400, { error: 'body must be a JSON object' });
249
+ const name = typeof body.name === 'string' ? body.name : '';
250
+ if (!isValidScenarioName(name))
251
+ return json(400, {
252
+ error: 'invalid scenario name (only [A-Za-z0-9_-], max 64 chars)',
253
+ });
254
+ let overrides;
255
+ try {
256
+ validateScenarioVersion(body.version);
257
+ overrides = validateOverrides(body.overrides === undefined ? store.list() : body.overrides);
258
+ }
259
+ catch (error) {
260
+ if (error instanceof ScenarioValidationError)
261
+ return json(400, { error: error.message });
262
+ throw error;
263
+ }
264
+ const saved = validateScenario(await scenarios.save(name, overrides), name);
265
+ return {
266
+ status: 201,
267
+ json: saved,
268
+ headers: { ...corsHeaders },
269
+ };
270
+ };
271
+ const deleteScenarioHandler = async (req) => {
272
+ const name = req.params.name ?? '';
273
+ if (!isValidScenarioName(name))
274
+ return json(400, { error: 'invalid scenario name' });
275
+ const ok = await scenarios.remove(name);
276
+ if (!ok)
277
+ return json(404, { error: 'scenario not found', name });
278
+ return json(200, { deleted: true, name });
279
+ };
280
+ const loadScenarioHandler = async (req) => {
281
+ const name = req.params.name ?? '';
282
+ if (!isValidScenarioName(name))
283
+ return json(400, { error: 'invalid scenario name' });
284
+ const s = await scenarios.get(name);
285
+ if (s === null)
286
+ return json(404, { error: 'scenario not found', name });
287
+ const validated = validateScenario(s, name);
288
+ const loaded = store.replace(validated.overrides).map((override) => override.id);
289
+ return json(200, { loaded: loaded.length, name });
290
+ };
291
+ routes[`GET ${basePath}/scenarios`] =
292
+ listScenariosHandler;
293
+ routes[`GET ${basePath}/scenarios/:name`] =
294
+ getScenarioHandler;
295
+ routes[`POST ${basePath}/scenarios`] =
296
+ createScenarioHandler;
297
+ routes[`DELETE ${basePath}/scenarios/:name`] = deleteScenarioHandler;
298
+ routes[`POST ${basePath}/scenarios/:name/load`] = loadScenarioHandler;
299
+ }
300
+ for (const key of Object.keys(routes)) {
301
+ const handler = routes[key];
302
+ if (handler)
303
+ routes[key] = async (request) => {
304
+ try {
305
+ return await handler(request);
306
+ }
307
+ catch (error) {
308
+ if (error instanceof ScenarioValidationError)
309
+ return json(400, { error: error.message });
310
+ throw error;
311
+ }
312
+ };
313
+ }
314
+ return routes;
315
+ };
316
+ //# sourceMappingURL=admin-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-routes.js","sourceRoot":"","sources":["../src/admin-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Discriminated union of every error mode the admin layer can inject.
3
+ * 1. `http-status` — non-2xx → typical HTTP error
4
+ * 2. `business-failure` — 2xx + `{success:false, message}`
5
+ * 3. `custom-body` — arbitrary status + arbitrary JSON / text
6
+ * 4. `empty-body` — empty body
7
+ * 5. `malformed-json` — body is literally `{` so JSON.parse blows up
8
+ * 6. `delay` — sleep `ms` then either run `then` or pass through
9
+ * 7. `hang` — never resolve (FE timeout / hang detection)
10
+ * 8. `destroy` — drop the socket without a response
11
+ */
12
+ export type ErrorMode = {
13
+ kind: 'http-status';
14
+ status: number;
15
+ } | {
16
+ kind: 'business-failure';
17
+ message: string;
18
+ extra?: Record<string, unknown>;
19
+ } | {
20
+ kind: 'custom-body';
21
+ status?: number;
22
+ body: unknown;
23
+ contentType?: string;
24
+ } | {
25
+ kind: 'empty-body';
26
+ status?: number;
27
+ } | {
28
+ kind: 'malformed-json';
29
+ status?: number;
30
+ } | {
31
+ kind: 'delay';
32
+ ms: number;
33
+ then?: Exclude<ErrorMode, {
34
+ kind: 'delay';
35
+ } | {
36
+ kind: 'hang';
37
+ } | {
38
+ kind: 'destroy';
39
+ }>;
40
+ } | {
41
+ kind: 'hang';
42
+ } | {
43
+ kind: 'destroy';
44
+ };
45
+ /**
46
+ * Resolution priority (handled by `OverridesStore.resolve`):
47
+ * 1. `exact` (METHOD + path)
48
+ * 2. `path` (path only, any method)
49
+ * 3. `global` (every route)
50
+ */
51
+ export type OverrideMatcher = {
52
+ kind: 'exact';
53
+ method: string;
54
+ path: string;
55
+ } | {
56
+ kind: 'path';
57
+ path: string;
58
+ } | {
59
+ kind: 'global';
60
+ methods?: readonly string[];
61
+ };
62
+ /** Alias kept for parity with the legacy reference; prefer {@link OverrideMatcher}. */
63
+ export type StickyMatcher = OverrideMatcher;
64
+ /** A normalized override snapshot; mutation belongs to the fault-control capability. */
65
+ export interface StickyOverride {
66
+ readonly id: string;
67
+ readonly matcher: OverrideMatcher;
68
+ readonly mode: ErrorMode;
69
+ readonly createdAt: string;
70
+ readonly description?: string;
71
+ }
72
+ /**
73
+ * Admin-mode CORS policy.
74
+ *
75
+ * - `'auto'` — `'*'` when bind is loopback-only, `'same-origin'`
76
+ * when `bind === 'any'` (defensive default for
77
+ * LAN-exposed servers).
78
+ * - `'any'` — `Access-Control-Allow-Origin: *`.
79
+ * - `'same-origin'` — no CORS headers added; browser enforces same-origin.
80
+ * - `{ origin }` — explicit allowlist (string | string[]).
81
+ */
82
+ export type AdminModeCors = 'auto' | 'any' | 'same-origin' | {
83
+ origin: string | readonly string[];
84
+ };
85
+ /** A persisted scenario — a named bundle of sticky overrides. */
86
+ export interface Scenario {
87
+ readonly version?: 1;
88
+ readonly name: string;
89
+ readonly overrides: readonly StickyOverride[];
90
+ readonly created?: string;
91
+ }
92
+ /** Lightweight scenario summary returned by the list endpoint. */
93
+ export interface ScenarioSummary {
94
+ readonly name: string;
95
+ readonly count: number;
96
+ readonly created: string;
97
+ }
98
+ //# sourceMappingURL=admin-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-types.d.ts","sourceRoot":"","sources":["../src/admin-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IACE,IAAI,EAAE,kBAAkB,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC,GACD;IACE,IAAI,EAAE,aAAa,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C;IACE,IAAI,EAAE,OAAO,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,OAAO,CACZ,SAAS,EACT;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,CAC3D,CAAA;CACF,GACD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAEvB;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAEnD,uFAAuF;AACvF,MAAM,MAAM,aAAa,GAAG,eAAe,CAAA;AAE3C,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,KAAK,GACL,aAAa,GACb;IAAE,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAE1C,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAA;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=admin-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-types.js","sourceRoot":"","sources":["../src/admin-types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * stdout notice emitted by the explicit browser-opening plugin. Splitting it out keeps the banner format unit-testable
3
+ * (no need to spin up a real http server just to assert a string).
4
+ */
5
+ export interface AdminBannerLogger {
6
+ info(message: string): void;
7
+ }
8
+ export declare const formatAdminBanner: (host: string, port: number, path: string) => string;
9
+ export declare const emitAdminBanner: (host: string, port: number, path: string, logger?: AdminBannerLogger) => void;
10
+ //# sourceMappingURL=banner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.d.ts","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,MAAM,MAAM,KACX,MAC0G,CAAA;AAE7G,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,SAAQ,iBAA2B,KAClC,IAEF,CAAA"}
package/dist/banner.js ADDED
@@ -0,0 +1,5 @@
1
+ export const formatAdminBanner = (host, port, path) => `[@databehave/admin] admin panel ready at http://${host}:${port}${path} (dev mock — disable in production)`;
2
+ export const emitAdminBanner = (host, port, path, logger = console) => {
3
+ logger.info(formatAdminBanner(host, port, path));
4
+ };
5
+ //# sourceMappingURL=banner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.js","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,IAAY,EACZ,IAAY,EACZ,IAAY,EACJ,EAAE,CACV,mDAAmD,IAAI,IAAI,IAAI,GAAG,IAAI,qCAAqC,CAAA;AAE7G,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,IAAY,EACZ,IAAY,EACZ,IAAY,EACZ,SAA4B,OAAO,EAC7B,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAClD,CAAC,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { type Plugin } from '@databehave/core';
2
+ import type { ErrorMode, OverrideMatcher, StickyOverride } from './admin-types.js';
3
+ /** Typed input for one new sticky fault. */
4
+ export interface FaultOverrideInput {
5
+ readonly matcher: OverrideMatcher;
6
+ readonly mode: ErrorMode;
7
+ readonly description?: string;
8
+ }
9
+ /** Narrow state boundary used by admin and alternative control plugins. */
10
+ export interface FaultControl {
11
+ add(input: FaultOverrideInput): StickyOverride;
12
+ remove(id: string): boolean;
13
+ list(): readonly StickyOverride[];
14
+ clear(): number;
15
+ /** Validates the entire input before a single atomic replacement. */
16
+ replace(input: unknown): readonly StickyOverride[];
17
+ /** Registration only; prevents control endpoints from injecting themselves. */
18
+ excludePathPrefix(path: string): void;
19
+ }
20
+ export declare const faultControlCapability: import("@databehave/core").Capability<FaultControl>;
21
+ /** Per-request fault policy; sticky state is instance-local and owned by this plugin. */
22
+ export interface FaultInjectionOptions {
23
+ readonly headers?: boolean;
24
+ readonly allowDestroy?: boolean;
25
+ readonly bypassPathPrefixes?: readonly string[];
26
+ }
27
+ /** Enable fault interception independently of the admin UI and persistence. */
28
+ export declare const faultInjectionPlugin: (options?: FaultInjectionOptions) => Plugin;
29
+ //# sourceMappingURL=fault-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fault-plugin.d.ts","sourceRoot":"","sources":["../src/fault-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAIlF,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,kBAAkB,GAAG,cAAc,CAAA;IAC9C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,IAAI,IAAI,SAAS,cAAc,EAAE,CAAA;IACjC,KAAK,IAAI,MAAM,CAAA;IACf,qEAAqE;IACrE,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,cAAc,EAAE,CAAA;IAClD,+EAA+E;IAC/E,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACtC;AAED,eAAO,MAAM,sBAAsB,qDAA0D,CAAA;AAE7F,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAChD;AAED,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,GAAI,UAAS,qBAA0B,KAAG,MAuBzE,CAAA"}
@@ -0,0 +1,32 @@
1
+ import { capability } from '@databehave/core';
2
+ import { httpCapability } from '@databehave/server';
3
+ import { createOverridesStore } from './overrides-store.js';
4
+ import { createInjectHook } from './inject.js';
5
+ export const faultControlCapability = capability('databehave.fault-control', 1);
6
+ /** Enable fault interception independently of the admin UI and persistence. */
7
+ export const faultInjectionPlugin = (options = {}) => ({
8
+ id: 'databehave.fault-injection', apiVersion: 1,
9
+ requires: [httpCapability], provides: [faultControlCapability],
10
+ setup(ctx) {
11
+ const store = createOverridesStore();
12
+ const bypass = [...(options.bypassPathPrefixes ?? [])];
13
+ let sealed = false;
14
+ ctx.onSeal(() => { sealed = true; });
15
+ ctx.provide(faultControlCapability, {
16
+ add: store.add, remove: store.remove, list: store.list, clear: store.clear, replace: store.replace,
17
+ excludePathPrefix(path) {
18
+ if (sealed)
19
+ throw new Error('Fault exclusions are sealed');
20
+ bypass.push(path);
21
+ },
22
+ });
23
+ ctx.use(httpCapability).registerMiddleware({
24
+ owner: 'databehave.fault-injection', order: ctx.order, stage: 'interception',
25
+ handle: createInjectHook({ store, bypassPathPrefixes: bypass,
26
+ ...(options.headers !== undefined ? { headers: options.headers } : {}),
27
+ ...(options.allowDestroy !== undefined ? { allowDestroy: options.allowDestroy } : {}),
28
+ }),
29
+ });
30
+ },
31
+ });
32
+ //# sourceMappingURL=fault-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fault-plugin.js","sourceRoot":"","sources":["../src/fault-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAqB9C,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAe,0BAA0B,EAAE,CAAC,CAAC,CAAA;AAS7F,+EAA+E;AAC/E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,UAAiC,EAAE,EAAU,EAAE,CAAC,CAAC;IACpF,EAAE,EAAE,4BAA4B,EAAE,UAAU,EAAE,CAAC;IAC/C,QAAQ,EAAE,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,CAAC,sBAAsB,CAAC;IAC9D,KAAK,CAAC,GAAG;QACP,MAAM,KAAK,GAAG,oBAAoB,EAAE,CAAA;QACpC,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,CAAA;QACtD,IAAI,MAAM,GAAG,KAAK,CAAA;QAClB,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAA,CAAC,CAAC,CAAC,CAAA;QACnC,GAAG,CAAC,OAAO,CAAC,sBAAsB,EAAE;YAClC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO;YAClG,iBAAiB,CAAC,IAAI;gBACpB,IAAI,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;gBAC1D,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACnB,CAAC;SACF,CAAC,CAAA;QACF,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,kBAAkB,CAAC;YACzC,KAAK,EAAE,4BAA4B,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc;YAC5E,MAAM,EAAE,gBAAgB,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM;gBAC1D,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtE,GAAG,CAAC,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtF,CAAC;SACH,CAAC,CAAA;IACJ,CAAC;CACF,CAAC,CAAA"}
@@ -0,0 +1,14 @@
1
+ import type { ErrorMode } from './admin-types.js';
2
+ export type HeaderParseResult = {
3
+ kind: 'none';
4
+ } | {
5
+ kind: 'mode';
6
+ mode: ErrorMode;
7
+ } | {
8
+ kind: 'error';
9
+ message: string;
10
+ };
11
+ export declare const parseHeaderMode: (headers: Readonly<Record<string, string>>) => HeaderParseResult;
12
+ /** Public alias of {@link parseHeaderMode}. */
13
+ export declare const parseMockHeaders: (headers: Readonly<Record<string, string>>) => HeaderParseResult;
14
+ //# sourceMappingURL=header-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-parser.d.ts","sourceRoot":"","sources":["../src/header-parser.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAkJtC,eAAO,MAAM,eAAe,GAC1B,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KACxC,iBAqCF,CAAA;AAGD,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,YA1ClB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KACxC,iBAyC4C,CAAA"}