@http-client-toolkit/dashboard 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.cjs CHANGED
@@ -6,19 +6,20 @@ var path = require('path');
6
6
  var url = require('url');
7
7
  var http = require('http');
8
8
 
9
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
+ var _documentCurrentScript =
10
+ typeof document !== 'undefined' ? document.currentScript : null;
10
11
  // src/adapters/cache/generic.ts
11
12
  function createGenericCacheAdapter(store) {
12
13
  return {
13
- type: "generic",
14
+ type: 'generic',
14
15
  capabilities: {
15
16
  canList: false,
16
17
  canDelete: true,
17
18
  canClear: true,
18
- canGetStats: false
19
+ canGetStats: false,
19
20
  },
20
21
  async getStats() {
21
- return { message: "Stats not available for this store type" };
22
+ return { message: 'Stats not available for this store type' };
22
23
  },
23
24
  async listEntries() {
24
25
  return { entries: [] };
@@ -31,7 +32,7 @@ function createGenericCacheAdapter(store) {
31
32
  },
32
33
  async clearAll() {
33
34
  await store.clear();
34
- }
35
+ },
35
36
  };
36
37
  }
37
38
 
@@ -39,12 +40,12 @@ function createGenericCacheAdapter(store) {
39
40
  function createMemoryCacheAdapter(store) {
40
41
  const memStore = store;
41
42
  return {
42
- type: "memory",
43
+ type: 'memory',
43
44
  capabilities: {
44
45
  canList: true,
45
46
  canDelete: true,
46
47
  canClear: true,
47
- canGetStats: true
48
+ canGetStats: true,
48
49
  },
49
50
  async getStats() {
50
51
  return memStore.getStats();
@@ -62,7 +63,7 @@ function createMemoryCacheAdapter(store) {
62
63
  },
63
64
  async clearAll() {
64
65
  await memStore.clear();
65
- }
66
+ },
66
67
  };
67
68
  }
68
69
 
@@ -70,12 +71,12 @@ function createMemoryCacheAdapter(store) {
70
71
  function createSqliteCacheAdapter(store) {
71
72
  const sqlStore = store;
72
73
  return {
73
- type: "sqlite",
74
+ type: 'sqlite',
74
75
  capabilities: {
75
76
  canList: true,
76
77
  canDelete: true,
77
78
  canClear: true,
78
- canGetStats: true
79
+ canGetStats: true,
79
80
  },
80
81
  async getStats() {
81
82
  return sqlStore.getStats();
@@ -86,7 +87,7 @@ function createSqliteCacheAdapter(store) {
86
87
  const entries = results.map((r) => ({
87
88
  hash: r.hash,
88
89
  expiresAt: r.expiresAt,
89
- createdAt: r.createdAt
90
+ createdAt: r.createdAt,
90
91
  }));
91
92
  return { entries };
92
93
  },
@@ -98,27 +99,27 @@ function createSqliteCacheAdapter(store) {
98
99
  },
99
100
  async clearAll() {
100
101
  await sqlStore.clear();
101
- }
102
+ },
102
103
  };
103
104
  }
104
105
 
105
106
  // src/adapters/dedup/generic.ts
106
107
  function createGenericDedupeAdapter(_store) {
107
108
  return {
108
- type: "generic",
109
+ type: 'generic',
109
110
  capabilities: {
110
111
  canList: false,
111
- canGetStats: false
112
+ canGetStats: false,
112
113
  },
113
114
  async getStats() {
114
- return { message: "Stats not available for this store type" };
115
+ return { message: 'Stats not available for this store type' };
115
116
  },
116
117
  async listJobs() {
117
118
  return { jobs: [] };
118
119
  },
119
120
  async getJob() {
120
121
  return void 0;
121
- }
122
+ },
122
123
  };
123
124
  }
124
125
 
@@ -126,10 +127,10 @@ function createGenericDedupeAdapter(_store) {
126
127
  function createMemoryDedupeAdapter(store) {
127
128
  const memStore = store;
128
129
  return {
129
- type: "memory",
130
+ type: 'memory',
130
131
  capabilities: {
131
132
  canList: true,
132
- canGetStats: true
133
+ canGetStats: true,
133
134
  },
134
135
  async getStats() {
135
136
  return memStore.getStats();
@@ -142,7 +143,7 @@ function createMemoryDedupeAdapter(store) {
142
143
  async getJob(hash) {
143
144
  const jobs = memStore.listJobs(0, 1e3);
144
145
  return jobs.find((j) => j.hash === hash);
145
- }
146
+ },
146
147
  };
147
148
  }
148
149
 
@@ -150,10 +151,10 @@ function createMemoryDedupeAdapter(store) {
150
151
  function createSqliteDedupeAdapter(store) {
151
152
  const sqlStore = store;
152
153
  return {
153
- type: "sqlite",
154
+ type: 'sqlite',
154
155
  capabilities: {
155
156
  canList: true,
156
- canGetStats: true
157
+ canGetStats: true,
157
158
  },
158
159
  async getStats() {
159
160
  return sqlStore.getStats();
@@ -166,22 +167,22 @@ function createSqliteDedupeAdapter(store) {
166
167
  async getJob(hash) {
167
168
  const results = await sqlStore.listJobs(0, 1e3);
168
169
  return results.find((j) => j.hash === hash);
169
- }
170
+ },
170
171
  };
171
172
  }
172
173
 
173
174
  // src/adapters/rate-limit/generic.ts
174
175
  function createGenericRateLimitAdapter(store) {
175
176
  return {
176
- type: "generic",
177
+ type: 'generic',
177
178
  capabilities: {
178
179
  canList: false,
179
180
  canGetStats: false,
180
181
  canUpdateConfig: false,
181
- canReset: true
182
+ canReset: true,
182
183
  },
183
184
  async getStats() {
184
- return { message: "Stats not available for this store type" };
185
+ return { message: 'Stats not available for this store type' };
185
186
  },
186
187
  async listResources() {
187
188
  return [];
@@ -190,11 +191,11 @@ function createGenericRateLimitAdapter(store) {
190
191
  return store.getStatus(name);
191
192
  },
192
193
  async updateResourceConfig() {
193
- throw new Error("Config updates not supported for this store type");
194
+ throw new Error('Config updates not supported for this store type');
194
195
  },
195
196
  async resetResource(name) {
196
197
  await store.reset(name);
197
- }
198
+ },
198
199
  };
199
200
  }
200
201
 
@@ -202,12 +203,12 @@ function createGenericRateLimitAdapter(store) {
202
203
  function createMemoryRateLimitAdapter(store) {
203
204
  const memStore = store;
204
205
  return {
205
- type: "memory",
206
+ type: 'memory',
206
207
  capabilities: {
207
208
  canList: true,
208
209
  canGetStats: true,
209
210
  canUpdateConfig: true,
210
- canReset: true
211
+ canReset: true,
211
212
  },
212
213
  async getStats() {
213
214
  return memStore.getStats();
@@ -223,7 +224,7 @@ function createMemoryRateLimitAdapter(store) {
223
224
  },
224
225
  async resetResource(name) {
225
226
  await memStore.reset(name);
226
- }
227
+ },
227
228
  };
228
229
  }
229
230
 
@@ -231,12 +232,12 @@ function createMemoryRateLimitAdapter(store) {
231
232
  function createSqliteRateLimitAdapter(store) {
232
233
  const sqlStore = store;
233
234
  return {
234
- type: "sqlite",
235
+ type: 'sqlite',
235
236
  capabilities: {
236
237
  canList: true,
237
238
  canGetStats: true,
238
239
  canUpdateConfig: true,
239
- canReset: true
240
+ canReset: true,
240
241
  },
241
242
  async getStats() {
242
243
  return sqlStore.getStats();
@@ -252,40 +253,72 @@ function createSqliteRateLimitAdapter(store) {
252
253
  },
253
254
  async resetResource(name) {
254
255
  await sqlStore.reset(name);
255
- }
256
+ },
256
257
  };
257
258
  }
258
259
 
259
260
  // src/adapters/detect.ts
260
261
  function isMemoryStore(store) {
261
- if (!store || typeof store !== "object") return false;
262
+ if (!store || typeof store !== 'object') return false;
262
263
  const s = store;
263
- return typeof s.destroy === "function" && typeof s.getStats === "function" && typeof s.cleanup === "function" && typeof s.getLRUItems === "function";
264
+ return (
265
+ typeof s.destroy === 'function' &&
266
+ typeof s.getStats === 'function' &&
267
+ typeof s.cleanup === 'function' &&
268
+ typeof s.getLRUItems === 'function'
269
+ );
264
270
  }
265
271
  function isSqliteStore(store) {
266
- if (!store || typeof store !== "object") return false;
272
+ if (!store || typeof store !== 'object') return false;
267
273
  const s = store;
268
- return typeof s.destroy === "function" && typeof s.getStats === "function" && typeof s.close === "function";
274
+ return (
275
+ typeof s.destroy === 'function' &&
276
+ typeof s.getStats === 'function' &&
277
+ typeof s.close === 'function'
278
+ );
269
279
  }
270
280
  function isMemoryDedupeStore(store) {
271
- if (!store || typeof store !== "object") return false;
281
+ if (!store || typeof store !== 'object') return false;
272
282
  const s = store;
273
- return typeof s.destroy === "function" && typeof s.getStats === "function" && typeof s.listJobs === "function" && typeof s.cleanup === "function" && !("close" in s && typeof s.close === "function");
283
+ return (
284
+ typeof s.destroy === 'function' &&
285
+ typeof s.getStats === 'function' &&
286
+ typeof s.listJobs === 'function' &&
287
+ typeof s.cleanup === 'function' &&
288
+ !('close' in s && typeof s.close === 'function')
289
+ );
274
290
  }
275
291
  function isSqliteDedupeStore(store) {
276
- if (!store || typeof store !== "object") return false;
292
+ if (!store || typeof store !== 'object') return false;
277
293
  const s = store;
278
- return typeof s.destroy === "function" && typeof s.getStats === "function" && typeof s.close === "function" && typeof s.listJobs === "function";
294
+ return (
295
+ typeof s.destroy === 'function' &&
296
+ typeof s.getStats === 'function' &&
297
+ typeof s.close === 'function' &&
298
+ typeof s.listJobs === 'function'
299
+ );
279
300
  }
280
301
  function isMemoryRateLimitStore(store) {
281
- if (!store || typeof store !== "object") return false;
302
+ if (!store || typeof store !== 'object') return false;
282
303
  const s = store;
283
- return typeof s.destroy === "function" && typeof s.getStats === "function" && typeof s.listResources === "function" && typeof s.setResourceConfig === "function" && !("close" in s && typeof s.close === "function");
304
+ return (
305
+ typeof s.destroy === 'function' &&
306
+ typeof s.getStats === 'function' &&
307
+ typeof s.listResources === 'function' &&
308
+ typeof s.setResourceConfig === 'function' &&
309
+ !('close' in s && typeof s.close === 'function')
310
+ );
284
311
  }
285
312
  function isSqliteRateLimitStore(store) {
286
- if (!store || typeof store !== "object") return false;
313
+ if (!store || typeof store !== 'object') return false;
287
314
  const s = store;
288
- return typeof s.destroy === "function" && typeof s.getStats === "function" && typeof s.close === "function" && typeof s.listResources === "function" && typeof s.setResourceConfig === "function";
315
+ return (
316
+ typeof s.destroy === 'function' &&
317
+ typeof s.getStats === 'function' &&
318
+ typeof s.close === 'function' &&
319
+ typeof s.listResources === 'function' &&
320
+ typeof s.setResourceConfig === 'function'
321
+ );
289
322
  }
290
323
  function detectCacheAdapter(store) {
291
324
  if (isMemoryStore(store)) {
@@ -315,35 +348,63 @@ function detectRateLimitAdapter(store) {
315
348
  return createGenericRateLimitAdapter(store);
316
349
  }
317
350
  var CLIENT_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
318
- var ClientConfigSchema = zod.z.object({
319
- name: zod.z.string().min(1, "Client name must not be empty").regex(
320
- CLIENT_NAME_REGEX,
321
- "Client name must be URL-safe (a-z, 0-9, -, _)"
322
- ),
323
- cacheStore: zod.z.custom().optional(),
324
- dedupeStore: zod.z.custom().optional(),
325
- rateLimitStore: zod.z.custom().optional()
326
- }).refine(
327
- (data) => data.cacheStore || data.dedupeStore || data.rateLimitStore,
328
- { message: "At least one store must be provided per client" }
329
- );
330
- var DashboardOptionsSchema = zod.z.object({
331
- clients: zod.z.array(ClientConfigSchema).min(1, "At least one client is required"),
332
- basePath: zod.z.string().default("/"),
333
- pollIntervalMs: zod.z.number().int().positive().default(5e3)
334
- }).refine(
335
- (data) => {
336
- const names = data.clients.map((c) => c.name);
337
- return new Set(names).size === names.length;
338
- },
339
- { message: "Client names must be unique" }
340
- );
351
+ var ClientConfigSchema = zod.z
352
+ .object({
353
+ client: zod.z.custom(
354
+ (val) =>
355
+ val != null &&
356
+ typeof val === 'object' &&
357
+ 'stores' in val &&
358
+ 'get' in val,
359
+ 'Must be an HttpClient instance',
360
+ ),
361
+ name: zod.z
362
+ .string()
363
+ .min(1, 'Client name must not be empty')
364
+ .regex(CLIENT_NAME_REGEX, 'Client name must be URL-safe (a-z, 0-9, -, _)')
365
+ .optional(),
366
+ })
367
+ .refine(
368
+ (data) => {
369
+ const { stores } = data.client;
370
+ return stores.cache || stores.dedupe || stores.rateLimit;
371
+ },
372
+ { message: 'HttpClient must have at least one store configured' },
373
+ );
374
+ function resolveClientName(c) {
375
+ return c.name ?? c.client.name;
376
+ }
377
+ var DashboardOptionsSchema = zod.z
378
+ .object({
379
+ clients: zod.z
380
+ .array(ClientConfigSchema)
381
+ .min(1, 'At least one client is required'),
382
+ basePath: zod.z.string().default('/'),
383
+ pollIntervalMs: zod.z.number().int().positive().default(5e3),
384
+ })
385
+ .refine(
386
+ (data) => {
387
+ const names = data.clients.map(resolveClientName);
388
+ return new Set(names).size === names.length;
389
+ },
390
+ { message: 'Client names must be unique' },
391
+ );
341
392
  var StandaloneDashboardOptionsSchema = DashboardOptionsSchema.and(
342
393
  zod.z.object({
343
394
  port: zod.z.number().int().nonnegative().default(4e3),
344
- host: zod.z.string().default("localhost")
345
- })
395
+ host: zod.z.string().default('localhost'),
396
+ }),
346
397
  );
398
+ function normalizeClient(config) {
399
+ const name = config.name ?? config.client.name;
400
+ const { stores } = config.client;
401
+ return {
402
+ name,
403
+ cacheStore: stores.cache,
404
+ dedupeStore: stores.dedupe,
405
+ rateLimitStore: stores.rateLimit,
406
+ };
407
+ }
347
408
  function validateDashboardOptions(options) {
348
409
  return DashboardOptionsSchema.parse(options);
349
410
  }
@@ -353,41 +414,41 @@ function validateStandaloneOptions(options) {
353
414
 
354
415
  // src/server/request-helpers.ts
355
416
  function parseUrl(req, basePath) {
356
- const raw = req.url ?? "/";
357
- const url = new URL(raw, "http://localhost");
417
+ const raw = req.url ?? '/';
418
+ const url = new URL(raw, 'http://localhost');
358
419
  let pathname = url.pathname;
359
- if (basePath !== "/" && pathname.startsWith(basePath)) {
360
- pathname = pathname.slice(basePath.length) || "/";
420
+ if (basePath !== '/' && pathname.startsWith(basePath)) {
421
+ pathname = pathname.slice(basePath.length) || '/';
361
422
  }
362
423
  return { pathname, query: url.searchParams };
363
424
  }
364
425
  function extractParam(pathname, pattern) {
365
- const patternParts = pattern.split("/");
366
- const pathParts = pathname.split("/");
426
+ const patternParts = pattern.split('/');
427
+ const pathParts = pathname.split('/');
367
428
  if (patternParts.length !== pathParts.length) return void 0;
368
429
  for (let i = 0; i < patternParts.length; i++) {
369
430
  const pp = patternParts[i];
370
- if (pp.startsWith(":")) continue;
431
+ if (pp.startsWith(':')) continue;
371
432
  if (pp !== pathParts[i]) return void 0;
372
433
  }
373
- const paramIndex = patternParts.findIndex((p) => p.startsWith(":"));
434
+ const paramIndex = patternParts.findIndex((p) => p.startsWith(':'));
374
435
  if (paramIndex === -1) return void 0;
375
436
  return pathParts[paramIndex];
376
437
  }
377
438
  async function readJsonBody(req) {
378
439
  return new Promise((resolve, reject) => {
379
- let body = "";
380
- req.on("data", (chunk) => {
440
+ let body = '';
441
+ req.on('data', (chunk) => {
381
442
  body += chunk.toString();
382
443
  });
383
- req.on("end", () => {
444
+ req.on('end', () => {
384
445
  try {
385
446
  resolve(JSON.parse(body));
386
447
  } catch {
387
- reject(new Error("Invalid JSON body"));
448
+ reject(new Error('Invalid JSON body'));
388
449
  }
389
450
  });
390
- req.on("error", reject);
451
+ req.on('error', reject);
391
452
  });
392
453
  }
393
454
 
@@ -395,9 +456,9 @@ async function readJsonBody(req) {
395
456
  function sendJson(res, data, status = 200) {
396
457
  const body = JSON.stringify(data);
397
458
  res.writeHead(status, {
398
- "Content-Type": "application/json",
399
- "Content-Length": Buffer.byteLength(body),
400
- "Cache-Control": "no-store"
459
+ 'Content-Type': 'application/json',
460
+ 'Content-Length': Buffer.byteLength(body),
461
+ 'Cache-Control': 'no-store',
401
462
  });
402
463
  res.end(body);
403
464
  }
@@ -405,10 +466,10 @@ function sendError(res, message, status = 500) {
405
466
  sendJson(res, { error: message }, status);
406
467
  }
407
468
  function sendNotFound(res) {
408
- sendError(res, "Not found", 404);
469
+ sendError(res, 'Not found', 404);
409
470
  }
410
471
  function sendMethodNotAllowed(res) {
411
- sendError(res, "Method not allowed", 405);
472
+ sendError(res, 'Method not allowed', 405);
412
473
  }
413
474
 
414
475
  // src/server/handlers/cache.ts
@@ -417,22 +478,22 @@ async function handleCacheStats(res, adapter) {
417
478
  const stats = await adapter.getStats();
418
479
  sendJson(res, { stats, capabilities: adapter.capabilities });
419
480
  } catch (err) {
420
- sendError(res, err instanceof Error ? err.message : "Unknown error");
481
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
421
482
  }
422
483
  }
423
484
  async function handleCacheEntries(_req, res, adapter, query) {
424
485
  try {
425
- const page = parseInt(query.get("page") ?? "0", 10);
426
- const limit = parseInt(query.get("limit") ?? "50", 10);
486
+ const page = parseInt(query.get('page') ?? '0', 10);
487
+ const limit = parseInt(query.get('limit') ?? '50', 10);
427
488
  const result = await adapter.listEntries(page, limit);
428
489
  sendJson(res, result);
429
490
  } catch (err) {
430
- sendError(res, err instanceof Error ? err.message : "Unknown error");
491
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
431
492
  }
432
493
  }
433
494
  async function handleCacheEntry(res, adapter, pathname) {
434
495
  try {
435
- const hash = extractParam(pathname, "/cache/entries/:hash");
496
+ const hash = extractParam(pathname, '/cache/entries/:hash');
436
497
  if (!hash) {
437
498
  sendNotFound(res);
438
499
  return;
@@ -444,12 +505,12 @@ async function handleCacheEntry(res, adapter, pathname) {
444
505
  }
445
506
  sendJson(res, { hash, value: entry });
446
507
  } catch (err) {
447
- sendError(res, err instanceof Error ? err.message : "Unknown error");
508
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
448
509
  }
449
510
  }
450
511
  async function handleDeleteCacheEntry(res, adapter, pathname) {
451
512
  try {
452
- const hash = extractParam(pathname, "/cache/entries/:hash");
513
+ const hash = extractParam(pathname, '/cache/entries/:hash');
453
514
  if (!hash) {
454
515
  sendNotFound(res);
455
516
  return;
@@ -457,7 +518,7 @@ async function handleDeleteCacheEntry(res, adapter, pathname) {
457
518
  await adapter.deleteEntry(hash);
458
519
  sendJson(res, { deleted: true });
459
520
  } catch (err) {
460
- sendError(res, err instanceof Error ? err.message : "Unknown error");
521
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
461
522
  }
462
523
  }
463
524
  async function handleClearCache(res, adapter) {
@@ -465,7 +526,7 @@ async function handleClearCache(res, adapter) {
465
526
  await adapter.clearAll();
466
527
  sendJson(res, { cleared: true });
467
528
  } catch (err) {
468
- sendError(res, err instanceof Error ? err.message : "Unknown error");
529
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
469
530
  }
470
531
  }
471
532
 
@@ -475,22 +536,22 @@ async function handleDedupeStats(res, adapter) {
475
536
  const stats = await adapter.getStats();
476
537
  sendJson(res, { stats, capabilities: adapter.capabilities });
477
538
  } catch (err) {
478
- sendError(res, err instanceof Error ? err.message : "Unknown error");
539
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
479
540
  }
480
541
  }
481
542
  async function handleDedupeJobs(_req, res, adapter, query) {
482
543
  try {
483
- const page = parseInt(query.get("page") ?? "0", 10);
484
- const limit = parseInt(query.get("limit") ?? "50", 10);
544
+ const page = parseInt(query.get('page') ?? '0', 10);
545
+ const limit = parseInt(query.get('limit') ?? '50', 10);
485
546
  const result = await adapter.listJobs(page, limit);
486
547
  sendJson(res, result);
487
548
  } catch (err) {
488
- sendError(res, err instanceof Error ? err.message : "Unknown error");
549
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
489
550
  }
490
551
  }
491
552
  async function handleDedupeJob(res, adapter, pathname) {
492
553
  try {
493
- const hash = extractParam(pathname, "/dedup/jobs/:hash");
554
+ const hash = extractParam(pathname, '/dedup/jobs/:hash');
494
555
  if (!hash) {
495
556
  sendNotFound(res);
496
557
  return;
@@ -502,19 +563,25 @@ async function handleDedupeJob(res, adapter, pathname) {
502
563
  }
503
564
  sendJson(res, job);
504
565
  } catch (err) {
505
- sendError(res, err instanceof Error ? err.message : "Unknown error");
566
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
506
567
  }
507
568
  }
508
569
 
509
570
  // src/server/handlers/health.ts
510
571
  function clientStoreInfo(client) {
511
572
  return {
512
- cache: client.cache ? { type: client.cache.type, capabilities: client.cache.capabilities } : null,
513
- dedup: client.dedup ? { type: client.dedup.type, capabilities: client.dedup.capabilities } : null,
514
- rateLimit: client.rateLimit ? {
515
- type: client.rateLimit.type,
516
- capabilities: client.rateLimit.capabilities
517
- } : null
573
+ cache: client.cache
574
+ ? { type: client.cache.type, capabilities: client.cache.capabilities }
575
+ : null,
576
+ dedup: client.dedup
577
+ ? { type: client.dedup.type, capabilities: client.dedup.capabilities }
578
+ : null,
579
+ rateLimit: client.rateLimit
580
+ ? {
581
+ type: client.rateLimit.type,
582
+ capabilities: client.rateLimit.capabilities,
583
+ }
584
+ : null,
518
585
  };
519
586
  }
520
587
  function handleHealth(res, ctx) {
@@ -523,9 +590,9 @@ function handleHealth(res, ctx) {
523
590
  clients[name] = clientStoreInfo(client);
524
591
  }
525
592
  sendJson(res, {
526
- status: "ok",
593
+ status: 'ok',
527
594
  clients,
528
- pollIntervalMs: ctx.pollIntervalMs
595
+ pollIntervalMs: ctx.pollIntervalMs,
529
596
  });
530
597
  }
531
598
 
@@ -535,7 +602,7 @@ async function handleRateLimitStats(res, adapter) {
535
602
  const stats = await adapter.getStats();
536
603
  sendJson(res, { stats, capabilities: adapter.capabilities });
537
604
  } catch (err) {
538
- sendError(res, err instanceof Error ? err.message : "Unknown error");
605
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
539
606
  }
540
607
  }
541
608
  async function handleRateLimitResources(res, adapter) {
@@ -543,12 +610,12 @@ async function handleRateLimitResources(res, adapter) {
543
610
  const resources = await adapter.listResources();
544
611
  sendJson(res, { resources });
545
612
  } catch (err) {
546
- sendError(res, err instanceof Error ? err.message : "Unknown error");
613
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
547
614
  }
548
615
  }
549
616
  async function handleRateLimitResource(res, adapter, pathname) {
550
617
  try {
551
- const name = extractParam(pathname, "/rate-limit/resources/:name");
618
+ const name = extractParam(pathname, '/rate-limit/resources/:name');
552
619
  if (!name) {
553
620
  sendNotFound(res);
554
621
  return;
@@ -556,12 +623,12 @@ async function handleRateLimitResource(res, adapter, pathname) {
556
623
  const status = await adapter.getResourceStatus(name);
557
624
  sendJson(res, { resource: name, ...status });
558
625
  } catch (err) {
559
- sendError(res, err instanceof Error ? err.message : "Unknown error");
626
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
560
627
  }
561
628
  }
562
629
  async function handleUpdateRateLimitConfig(req, res, adapter, pathname) {
563
630
  try {
564
- const name = extractParam(pathname, "/rate-limit/resources/:name/config");
631
+ const name = extractParam(pathname, '/rate-limit/resources/:name/config');
565
632
  if (!name) {
566
633
  sendNotFound(res);
567
634
  return;
@@ -570,12 +637,12 @@ async function handleUpdateRateLimitConfig(req, res, adapter, pathname) {
570
637
  await adapter.updateResourceConfig(name, body);
571
638
  sendJson(res, { updated: true });
572
639
  } catch (err) {
573
- sendError(res, err instanceof Error ? err.message : "Unknown error");
640
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
574
641
  }
575
642
  }
576
643
  async function handleResetRateLimitResource(res, adapter, pathname) {
577
644
  try {
578
- const name = extractParam(pathname, "/rate-limit/resources/:name/reset");
645
+ const name = extractParam(pathname, '/rate-limit/resources/:name/reset');
579
646
  if (!name) {
580
647
  sendNotFound(res);
581
648
  return;
@@ -583,7 +650,7 @@ async function handleResetRateLimitResource(res, adapter, pathname) {
583
650
  await adapter.resetResource(name);
584
651
  sendJson(res, { reset: true });
585
652
  } catch (err) {
586
- sendError(res, err instanceof Error ? err.message : "Unknown error");
653
+ sendError(res, err instanceof Error ? err.message : 'Unknown error');
587
654
  }
588
655
  }
589
656
 
@@ -594,13 +661,19 @@ function handleClients(res, ctx) {
594
661
  clients.push({
595
662
  name,
596
663
  stores: {
597
- cache: client.cache ? { type: client.cache.type, capabilities: client.cache.capabilities } : null,
598
- dedup: client.dedup ? { type: client.dedup.type, capabilities: client.dedup.capabilities } : null,
599
- rateLimit: client.rateLimit ? {
600
- type: client.rateLimit.type,
601
- capabilities: client.rateLimit.capabilities
602
- } : null
603
- }
664
+ cache: client.cache
665
+ ? { type: client.cache.type, capabilities: client.cache.capabilities }
666
+ : null,
667
+ dedup: client.dedup
668
+ ? { type: client.dedup.type, capabilities: client.dedup.capabilities }
669
+ : null,
670
+ rateLimit: client.rateLimit
671
+ ? {
672
+ type: client.rateLimit.type,
673
+ capabilities: client.rateLimit.capabilities,
674
+ }
675
+ : null,
676
+ },
604
677
  });
605
678
  }
606
679
  sendJson(res, { clients });
@@ -610,19 +683,19 @@ function handleClients(res, ctx) {
610
683
  var CLIENT_ROUTE_REGEX = /^\/api\/clients\/([a-zA-Z0-9_-]+)(\/.*)?$/;
611
684
  function createApiRouter(ctx) {
612
685
  return async (req, res, pathname, query) => {
613
- const method = req.method?.toUpperCase() ?? "GET";
614
- if (pathname === "/api/health" && method === "GET") {
686
+ const method = req.method?.toUpperCase() ?? 'GET';
687
+ if (pathname === '/api/health' && method === 'GET') {
615
688
  handleHealth(res, ctx);
616
689
  return true;
617
690
  }
618
- if (pathname === "/api/clients" && method === "GET") {
691
+ if (pathname === '/api/clients' && method === 'GET') {
619
692
  handleClients(res, ctx);
620
693
  return true;
621
694
  }
622
695
  const clientMatch = pathname.match(CLIENT_ROUTE_REGEX);
623
696
  if (clientMatch) {
624
697
  const clientName = clientMatch[1];
625
- const subPath = clientMatch[2] ?? "";
698
+ const subPath = clientMatch[2] ?? '';
626
699
  const client = ctx.clients.get(clientName);
627
700
  if (!client) {
628
701
  sendError(res, `Unknown client: ${clientName}`, 404);
@@ -630,7 +703,7 @@ function createApiRouter(ctx) {
630
703
  }
631
704
  return routeClientApi(req, res, client, subPath, method, query);
632
705
  }
633
- if (pathname.startsWith("/api/")) {
706
+ if (pathname.startsWith('/api/')) {
634
707
  sendNotFound(res);
635
708
  return true;
636
709
  }
@@ -638,29 +711,30 @@ function createApiRouter(ctx) {
638
711
  };
639
712
  }
640
713
  async function routeClientApi(req, res, client, subPath, method, query) {
641
- if (subPath.startsWith("/cache")) {
714
+ if (subPath.startsWith('/cache')) {
642
715
  if (!client.cache) {
643
- sendError(res, "Cache store not configured", 404);
716
+ sendError(res, 'Cache store not configured', 404);
644
717
  return true;
645
718
  }
646
- if (subPath === "/cache/stats" && method === "GET") {
719
+ if (subPath === '/cache/stats' && method === 'GET') {
647
720
  await handleCacheStats(res, client.cache);
648
721
  return true;
649
722
  }
650
- if (subPath === "/cache/entries" && method === "GET") {
723
+ if (subPath === '/cache/entries' && method === 'GET') {
651
724
  await handleCacheEntries(req, res, client.cache, query);
652
725
  return true;
653
726
  }
654
- if (subPath === "/cache/entries" && method === "DELETE") {
727
+ if (subPath === '/cache/entries' && method === 'DELETE') {
655
728
  await handleClearCache(res, client.cache);
656
729
  return true;
657
730
  }
658
- const isSingleEntry = subPath.startsWith("/cache/entries/") && subPath.split("/").length === 4;
659
- if (isSingleEntry && method === "GET") {
731
+ const isSingleEntry =
732
+ subPath.startsWith('/cache/entries/') && subPath.split('/').length === 4;
733
+ if (isSingleEntry && method === 'GET') {
660
734
  await handleCacheEntry(res, client.cache, subPath);
661
735
  return true;
662
736
  }
663
- if (isSingleEntry && method === "DELETE") {
737
+ if (isSingleEntry && method === 'DELETE') {
664
738
  await handleDeleteCacheEntry(res, client.cache, subPath);
665
739
  return true;
666
740
  }
@@ -669,21 +743,22 @@ async function routeClientApi(req, res, client, subPath, method, query) {
669
743
  return true;
670
744
  }
671
745
  }
672
- if (subPath.startsWith("/dedup")) {
746
+ if (subPath.startsWith('/dedup')) {
673
747
  if (!client.dedup) {
674
- sendError(res, "Dedup store not configured", 404);
748
+ sendError(res, 'Dedup store not configured', 404);
675
749
  return true;
676
750
  }
677
- if (subPath === "/dedup/stats" && method === "GET") {
751
+ if (subPath === '/dedup/stats' && method === 'GET') {
678
752
  await handleDedupeStats(res, client.dedup);
679
753
  return true;
680
754
  }
681
- if (subPath === "/dedup/jobs" && method === "GET") {
755
+ if (subPath === '/dedup/jobs' && method === 'GET') {
682
756
  await handleDedupeJobs(req, res, client.dedup, query);
683
757
  return true;
684
758
  }
685
- const isSingleJob = subPath.startsWith("/dedup/jobs/") && subPath.split("/").length === 4;
686
- if (isSingleJob && method === "GET") {
759
+ const isSingleJob =
760
+ subPath.startsWith('/dedup/jobs/') && subPath.split('/').length === 4;
761
+ if (isSingleJob && method === 'GET') {
687
762
  await handleDedupeJob(res, client.dedup, subPath);
688
763
  return true;
689
764
  }
@@ -692,29 +767,31 @@ async function routeClientApi(req, res, client, subPath, method, query) {
692
767
  return true;
693
768
  }
694
769
  }
695
- if (subPath.startsWith("/rate-limit")) {
770
+ if (subPath.startsWith('/rate-limit')) {
696
771
  if (!client.rateLimit) {
697
- sendError(res, "Rate limit store not configured", 404);
772
+ sendError(res, 'Rate limit store not configured', 404);
698
773
  return true;
699
774
  }
700
- if (subPath === "/rate-limit/stats" && method === "GET") {
775
+ if (subPath === '/rate-limit/stats' && method === 'GET') {
701
776
  await handleRateLimitStats(res, client.rateLimit);
702
777
  return true;
703
778
  }
704
- if (subPath === "/rate-limit/resources" && method === "GET") {
779
+ if (subPath === '/rate-limit/resources' && method === 'GET') {
705
780
  await handleRateLimitResources(res, client.rateLimit);
706
781
  return true;
707
782
  }
708
- if (subPath.endsWith("/config") && method === "PUT") {
783
+ if (subPath.endsWith('/config') && method === 'PUT') {
709
784
  await handleUpdateRateLimitConfig(req, res, client.rateLimit, subPath);
710
785
  return true;
711
786
  }
712
- if (subPath.endsWith("/reset") && method === "POST") {
787
+ if (subPath.endsWith('/reset') && method === 'POST') {
713
788
  await handleResetRateLimitResource(res, client.rateLimit, subPath);
714
789
  return true;
715
790
  }
716
- const isSingleResource = subPath.startsWith("/rate-limit/resources/") && subPath.split("/").length === 4;
717
- if (isSingleResource && method === "GET") {
791
+ const isSingleResource =
792
+ subPath.startsWith('/rate-limit/resources/') &&
793
+ subPath.split('/').length === 4;
794
+ if (isSingleResource && method === 'GET') {
718
795
  await handleRateLimitResource(res, client.rateLimit, subPath);
719
796
  return true;
720
797
  }
@@ -727,36 +804,45 @@ async function routeClientApi(req, res, client, subPath, method, query) {
727
804
  return true;
728
805
  }
729
806
  var MIME_TYPES = {
730
- ".html": "text/html",
731
- ".js": "application/javascript",
732
- ".css": "text/css",
733
- ".json": "application/json",
734
- ".png": "image/png",
735
- ".svg": "image/svg+xml",
736
- ".ico": "image/x-icon",
737
- ".woff": "font/woff",
738
- ".woff2": "font/woff2"
807
+ '.html': 'text/html',
808
+ '.js': 'application/javascript',
809
+ '.css': 'text/css',
810
+ '.json': 'application/json',
811
+ '.png': 'image/png',
812
+ '.svg': 'image/svg+xml',
813
+ '.ico': 'image/x-icon',
814
+ '.woff': 'font/woff',
815
+ '.woff2': 'font/woff2',
739
816
  };
740
817
  var cachedIndexHtml;
741
818
  var clientDir;
742
819
  function getCurrentDir() {
743
820
  try {
744
- return path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
821
+ return path.dirname(
822
+ url.fileURLToPath(
823
+ typeof document === 'undefined'
824
+ ? require('u' + 'rl').pathToFileURL(__filename).href
825
+ : (_documentCurrentScript &&
826
+ _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' &&
827
+ _documentCurrentScript.src) ||
828
+ new URL('index.cjs', document.baseURI).href,
829
+ ),
830
+ );
745
831
  } catch {
746
- return typeof __dirname !== "undefined" ? __dirname : process.cwd();
832
+ return typeof __dirname !== 'undefined' ? __dirname : process.cwd();
747
833
  }
748
834
  }
749
835
  function getClientDir() {
750
836
  if (clientDir) return clientDir;
751
837
  const currentDir = getCurrentDir();
752
- clientDir = path.join(currentDir, "..", "dist", "client");
838
+ clientDir = path.join(currentDir, '..', 'dist', 'client');
753
839
  return clientDir;
754
840
  }
755
841
  function getIndexHtml() {
756
842
  if (cachedIndexHtml) return cachedIndexHtml;
757
- const indexPath = path.join(getClientDir(), "index.html");
843
+ const indexPath = path.join(getClientDir(), 'index.html');
758
844
  if (fs.existsSync(indexPath)) {
759
- cachedIndexHtml = fs.readFileSync(indexPath, "utf-8");
845
+ cachedIndexHtml = fs.readFileSync(indexPath, 'utf-8');
760
846
  } else {
761
847
  cachedIndexHtml = `<!DOCTYPE html>
762
848
  <html lang="en">
@@ -774,29 +860,30 @@ function getIndexHtml() {
774
860
  }
775
861
  function serveStatic(res, pathname) {
776
862
  const dir = getClientDir();
777
- if (pathname !== "/" && pathname !== "/index.html") {
863
+ if (pathname !== '/' && pathname !== '/index.html') {
778
864
  const filePath = path.join(dir, pathname);
779
865
  if (fs.existsSync(filePath)) {
780
866
  try {
781
867
  const content = fs.readFileSync(filePath);
782
868
  const ext = path.extname(pathname);
783
- const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
869
+ const mimeType = MIME_TYPES[ext] ?? 'application/octet-stream';
784
870
  res.writeHead(200, {
785
- "Content-Type": mimeType,
786
- "Content-Length": content.length,
787
- "Cache-Control": pathname.includes("/assets/") ? "public, max-age=31536000, immutable" : "no-cache"
871
+ 'Content-Type': mimeType,
872
+ 'Content-Length': content.length,
873
+ 'Cache-Control': pathname.includes('/assets/')
874
+ ? 'public, max-age=31536000, immutable'
875
+ : 'no-cache',
788
876
  });
789
877
  res.end(content);
790
878
  return true;
791
- } catch {
792
- }
879
+ } catch {}
793
880
  }
794
881
  }
795
882
  const html = getIndexHtml();
796
883
  res.writeHead(200, {
797
- "Content-Type": "text/html",
798
- "Content-Length": Buffer.byteLength(html),
799
- "Cache-Control": "no-cache"
884
+ 'Content-Type': 'text/html',
885
+ 'Content-Length': Buffer.byteLength(html),
886
+ 'Cache-Control': 'no-cache',
800
887
  });
801
888
  res.end(html);
802
889
  return true;
@@ -807,33 +894,42 @@ function createDashboard(options) {
807
894
  const opts = validateDashboardOptions(options);
808
895
  const clients = /* @__PURE__ */ new Map();
809
896
  for (const clientConfig of opts.clients) {
810
- clients.set(clientConfig.name, {
811
- name: clientConfig.name,
812
- cache: clientConfig.cacheStore ? detectCacheAdapter(clientConfig.cacheStore) : void 0,
813
- dedup: clientConfig.dedupeStore ? detectDedupeAdapter(clientConfig.dedupeStore) : void 0,
814
- rateLimit: clientConfig.rateLimitStore ? detectRateLimitAdapter(clientConfig.rateLimitStore) : void 0
897
+ const normalized = normalizeClient(clientConfig);
898
+ clients.set(normalized.name, {
899
+ name: normalized.name,
900
+ cache: normalized.cacheStore
901
+ ? detectCacheAdapter(normalized.cacheStore)
902
+ : void 0,
903
+ dedup: normalized.dedupeStore
904
+ ? detectDedupeAdapter(normalized.dedupeStore)
905
+ : void 0,
906
+ rateLimit: normalized.rateLimitStore
907
+ ? detectRateLimitAdapter(normalized.rateLimitStore)
908
+ : void 0,
815
909
  });
816
910
  }
817
911
  const ctx = {
818
912
  clients,
819
- pollIntervalMs: opts.pollIntervalMs
913
+ pollIntervalMs: opts.pollIntervalMs,
820
914
  };
821
915
  const apiRouter = createApiRouter(ctx);
822
916
  return (req, res, _next) => {
823
917
  const { pathname, query } = parseUrl(req, opts.basePath);
824
- apiRouter(req, res, pathname, query).then((handled) => {
825
- if (!handled) {
826
- serveStatic(res, pathname);
827
- }
828
- }).catch(() => {
829
- res.writeHead(500, { "Content-Type": "application/json" });
830
- res.end(JSON.stringify({ error: "Internal server error" }));
831
- });
918
+ apiRouter(req, res, pathname, query)
919
+ .then((handled) => {
920
+ if (!handled) {
921
+ serveStatic(res, pathname);
922
+ }
923
+ })
924
+ .catch(() => {
925
+ res.writeHead(500, { 'Content-Type': 'application/json' });
926
+ res.end(JSON.stringify({ error: 'Internal server error' }));
927
+ });
832
928
  };
833
929
  }
834
930
  var JSON_HEADERS = {
835
- "Content-Type": "application/json",
836
- "Cache-Control": "no-store"
931
+ 'Content-Type': 'application/json',
932
+ 'Cache-Control': 'no-store',
837
933
  };
838
934
  function jsonResponse(data, status = 200) {
839
935
  return new Response(JSON.stringify(data), { status, headers: JSON_HEADERS });
@@ -842,36 +938,45 @@ function errorResponse(message, status = 500) {
842
938
  return jsonResponse({ error: message }, status);
843
939
  }
844
940
  var MIME_TYPES2 = {
845
- ".html": "text/html",
846
- ".js": "application/javascript",
847
- ".css": "text/css",
848
- ".json": "application/json",
849
- ".png": "image/png",
850
- ".svg": "image/svg+xml",
851
- ".ico": "image/x-icon",
852
- ".woff": "font/woff",
853
- ".woff2": "font/woff2"
941
+ '.html': 'text/html',
942
+ '.js': 'application/javascript',
943
+ '.css': 'text/css',
944
+ '.json': 'application/json',
945
+ '.png': 'image/png',
946
+ '.svg': 'image/svg+xml',
947
+ '.ico': 'image/x-icon',
948
+ '.woff': 'font/woff',
949
+ '.woff2': 'font/woff2',
854
950
  };
855
951
  var cachedIndexHtml2;
856
952
  var clientDir2;
857
953
  function getCurrentDir2() {
858
954
  try {
859
- return path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
955
+ return path.dirname(
956
+ url.fileURLToPath(
957
+ typeof document === 'undefined'
958
+ ? require('u' + 'rl').pathToFileURL(__filename).href
959
+ : (_documentCurrentScript &&
960
+ _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' &&
961
+ _documentCurrentScript.src) ||
962
+ new URL('index.cjs', document.baseURI).href,
963
+ ),
964
+ );
860
965
  } catch {
861
- return typeof __dirname !== "undefined" ? __dirname : process.cwd();
966
+ return typeof __dirname !== 'undefined' ? __dirname : process.cwd();
862
967
  }
863
968
  }
864
969
  function getClientDir2() {
865
970
  if (clientDir2) return clientDir2;
866
971
  const currentDir = getCurrentDir2();
867
- clientDir2 = path.join(currentDir, "..", "dist", "client");
972
+ clientDir2 = path.join(currentDir, '..', 'dist', 'client');
868
973
  return clientDir2;
869
974
  }
870
975
  function getIndexHtml2() {
871
976
  if (cachedIndexHtml2) return cachedIndexHtml2;
872
- const indexPath = path.join(getClientDir2(), "index.html");
977
+ const indexPath = path.join(getClientDir2(), 'index.html');
873
978
  if (fs.existsSync(indexPath)) {
874
- cachedIndexHtml2 = fs.readFileSync(indexPath, "utf-8");
979
+ cachedIndexHtml2 = fs.readFileSync(indexPath, 'utf-8');
875
980
  } else {
876
981
  cachedIndexHtml2 = `<!DOCTYPE html>
877
982
  <html lang="en">
@@ -889,59 +994,66 @@ function getIndexHtml2() {
889
994
  }
890
995
  function serveStaticWeb(pathname) {
891
996
  const dir = getClientDir2();
892
- if (pathname !== "/" && pathname !== "/index.html") {
997
+ if (pathname !== '/' && pathname !== '/index.html') {
893
998
  const filePath = path.join(dir, pathname);
894
999
  if (fs.existsSync(filePath)) {
895
1000
  try {
896
1001
  const content = fs.readFileSync(filePath);
897
1002
  const ext = path.extname(pathname);
898
- const mimeType = MIME_TYPES2[ext] ?? "application/octet-stream";
1003
+ const mimeType = MIME_TYPES2[ext] ?? 'application/octet-stream';
899
1004
  return new Response(content, {
900
1005
  status: 200,
901
1006
  headers: {
902
- "Content-Type": mimeType,
903
- "Content-Length": String(content.length),
904
- "Cache-Control": pathname.includes("/assets/") ? "public, max-age=31536000, immutable" : "no-cache"
905
- }
1007
+ 'Content-Type': mimeType,
1008
+ 'Content-Length': String(content.length),
1009
+ 'Cache-Control': pathname.includes('/assets/')
1010
+ ? 'public, max-age=31536000, immutable'
1011
+ : 'no-cache',
1012
+ },
906
1013
  });
907
- } catch {
908
- }
1014
+ } catch {}
909
1015
  }
910
1016
  }
911
1017
  const html = getIndexHtml2();
912
1018
  return new Response(html, {
913
1019
  status: 200,
914
1020
  headers: {
915
- "Content-Type": "text/html",
916
- "Cache-Control": "no-cache"
917
- }
1021
+ 'Content-Type': 'text/html',
1022
+ 'Cache-Control': 'no-cache',
1023
+ },
918
1024
  });
919
1025
  }
920
1026
  var CLIENT_ROUTE_REGEX2 = /^\/api\/clients\/([a-zA-Z0-9_-]+)(\/.*)?$/;
921
1027
  function clientStoreInfo2(client) {
922
1028
  return {
923
- cache: client.cache ? { type: client.cache.type, capabilities: client.cache.capabilities } : null,
924
- dedup: client.dedup ? { type: client.dedup.type, capabilities: client.dedup.capabilities } : null,
925
- rateLimit: client.rateLimit ? {
926
- type: client.rateLimit.type,
927
- capabilities: client.rateLimit.capabilities
928
- } : null
1029
+ cache: client.cache
1030
+ ? { type: client.cache.type, capabilities: client.cache.capabilities }
1031
+ : null,
1032
+ dedup: client.dedup
1033
+ ? { type: client.dedup.type, capabilities: client.dedup.capabilities }
1034
+ : null,
1035
+ rateLimit: client.rateLimit
1036
+ ? {
1037
+ type: client.rateLimit.type,
1038
+ capabilities: client.rateLimit.capabilities,
1039
+ }
1040
+ : null,
929
1041
  };
930
1042
  }
931
1043
  async function routeApi(request, pathname, query, ctx) {
932
1044
  const method = request.method.toUpperCase();
933
- if (pathname === "/api/health" && method === "GET") {
1045
+ if (pathname === '/api/health' && method === 'GET') {
934
1046
  const clients = {};
935
1047
  for (const [name, client] of ctx.clients) {
936
1048
  clients[name] = clientStoreInfo2(client);
937
1049
  }
938
1050
  return jsonResponse({
939
- status: "ok",
1051
+ status: 'ok',
940
1052
  clients,
941
- pollIntervalMs: ctx.pollIntervalMs
1053
+ pollIntervalMs: ctx.pollIntervalMs,
942
1054
  });
943
1055
  }
944
- if (pathname === "/api/clients" && method === "GET") {
1056
+ if (pathname === '/api/clients' && method === 'GET') {
945
1057
  const clientList = [];
946
1058
  for (const [name, client] of ctx.clients) {
947
1059
  clientList.push({ name, stores: clientStoreInfo2(client) });
@@ -951,168 +1063,180 @@ async function routeApi(request, pathname, query, ctx) {
951
1063
  const clientMatch = pathname.match(CLIENT_ROUTE_REGEX2);
952
1064
  if (clientMatch) {
953
1065
  const clientName = clientMatch[1];
954
- const subPath = clientMatch[2] ?? "";
1066
+ const subPath = clientMatch[2] ?? '';
955
1067
  const client = ctx.clients.get(clientName);
956
1068
  if (!client) {
957
1069
  return errorResponse(`Unknown client: ${clientName}`, 404);
958
1070
  }
959
1071
  return routeClientApi2(request, subPath, method, client, query);
960
1072
  }
961
- if (pathname.startsWith("/api/")) {
962
- return errorResponse("Not found", 404);
1073
+ if (pathname.startsWith('/api/')) {
1074
+ return errorResponse('Not found', 404);
963
1075
  }
964
1076
  return null;
965
1077
  }
966
1078
  async function routeClientApi2(request, subPath, method, client, query) {
967
- if (subPath.startsWith("/cache")) {
968
- if (!client.cache) return errorResponse("Cache store not configured", 404);
1079
+ if (subPath.startsWith('/cache')) {
1080
+ if (!client.cache) return errorResponse('Cache store not configured', 404);
969
1081
  return routeCache(subPath, method, client.cache, query);
970
1082
  }
971
- if (subPath.startsWith("/dedup")) {
972
- if (!client.dedup) return errorResponse("Dedup store not configured", 404);
1083
+ if (subPath.startsWith('/dedup')) {
1084
+ if (!client.dedup) return errorResponse('Dedup store not configured', 404);
973
1085
  return routeDedup(subPath, method, client.dedup, query);
974
1086
  }
975
- if (subPath.startsWith("/rate-limit")) {
1087
+ if (subPath.startsWith('/rate-limit')) {
976
1088
  if (!client.rateLimit)
977
- return errorResponse("Rate limit store not configured", 404);
1089
+ return errorResponse('Rate limit store not configured', 404);
978
1090
  return routeRateLimit(request, subPath, method, client.rateLimit);
979
1091
  }
980
- return errorResponse("Not found", 404);
1092
+ return errorResponse('Not found', 404);
981
1093
  }
982
1094
  async function routeCache(pathname, method, adapter, query) {
983
1095
  try {
984
- if (pathname === "/cache/stats" && method === "GET") {
1096
+ if (pathname === '/cache/stats' && method === 'GET') {
985
1097
  const stats = await adapter.getStats();
986
1098
  return jsonResponse({ stats, capabilities: adapter.capabilities });
987
1099
  }
988
- if (pathname === "/cache/entries" && method === "GET") {
989
- const page = parseInt(query.get("page") ?? "0", 10);
990
- const limit = parseInt(query.get("limit") ?? "50", 10);
1100
+ if (pathname === '/cache/entries' && method === 'GET') {
1101
+ const page = parseInt(query.get('page') ?? '0', 10);
1102
+ const limit = parseInt(query.get('limit') ?? '50', 10);
991
1103
  return jsonResponse(await adapter.listEntries(page, limit));
992
1104
  }
993
- if (pathname === "/cache/entries" && method === "DELETE") {
1105
+ if (pathname === '/cache/entries' && method === 'DELETE') {
994
1106
  await adapter.clearAll();
995
1107
  return jsonResponse({ cleared: true });
996
1108
  }
997
- const isSingleEntry = pathname.startsWith("/cache/entries/") && pathname.split("/").length === 4;
998
- if (isSingleEntry && method === "GET") {
999
- const hash = extractParam(pathname, "/cache/entries/:hash");
1000
- if (!hash) return errorResponse("Not found", 404);
1109
+ const isSingleEntry =
1110
+ pathname.startsWith('/cache/entries/') &&
1111
+ pathname.split('/').length === 4;
1112
+ if (isSingleEntry && method === 'GET') {
1113
+ const hash = extractParam(pathname, '/cache/entries/:hash');
1114
+ if (!hash) return errorResponse('Not found', 404);
1001
1115
  const entry = await adapter.getEntry(hash);
1002
- if (entry === void 0) return errorResponse("Not found", 404);
1116
+ if (entry === void 0) return errorResponse('Not found', 404);
1003
1117
  return jsonResponse({ hash, value: entry });
1004
1118
  }
1005
- if (isSingleEntry && method === "DELETE") {
1006
- const hash = extractParam(pathname, "/cache/entries/:hash");
1007
- if (!hash) return errorResponse("Not found", 404);
1119
+ if (isSingleEntry && method === 'DELETE') {
1120
+ const hash = extractParam(pathname, '/cache/entries/:hash');
1121
+ if (!hash) return errorResponse('Not found', 404);
1008
1122
  await adapter.deleteEntry(hash);
1009
1123
  return jsonResponse({ deleted: true });
1010
1124
  }
1011
1125
  if (isSingleEntry) {
1012
- return errorResponse("Method not allowed", 405);
1126
+ return errorResponse('Method not allowed', 405);
1013
1127
  }
1014
1128
  } catch (err) {
1015
- return errorResponse(err instanceof Error ? err.message : "Unknown error");
1129
+ return errorResponse(err instanceof Error ? err.message : 'Unknown error');
1016
1130
  }
1017
- return errorResponse("Not found", 404);
1131
+ return errorResponse('Not found', 404);
1018
1132
  }
1019
1133
  async function routeDedup(pathname, method, adapter, query) {
1020
1134
  try {
1021
- if (pathname === "/dedup/stats" && method === "GET") {
1135
+ if (pathname === '/dedup/stats' && method === 'GET') {
1022
1136
  const stats = await adapter.getStats();
1023
1137
  return jsonResponse({ stats, capabilities: adapter.capabilities });
1024
1138
  }
1025
- if (pathname === "/dedup/jobs" && method === "GET") {
1026
- const page = parseInt(query.get("page") ?? "0", 10);
1027
- const limit = parseInt(query.get("limit") ?? "50", 10);
1139
+ if (pathname === '/dedup/jobs' && method === 'GET') {
1140
+ const page = parseInt(query.get('page') ?? '0', 10);
1141
+ const limit = parseInt(query.get('limit') ?? '50', 10);
1028
1142
  return jsonResponse(await adapter.listJobs(page, limit));
1029
1143
  }
1030
- const isSingleJob = pathname.startsWith("/dedup/jobs/") && pathname.split("/").length === 4;
1031
- if (isSingleJob && method === "GET") {
1032
- const hash = extractParam(pathname, "/dedup/jobs/:hash");
1033
- if (!hash) return errorResponse("Not found", 404);
1144
+ const isSingleJob =
1145
+ pathname.startsWith('/dedup/jobs/') && pathname.split('/').length === 4;
1146
+ if (isSingleJob && method === 'GET') {
1147
+ const hash = extractParam(pathname, '/dedup/jobs/:hash');
1148
+ if (!hash) return errorResponse('Not found', 404);
1034
1149
  const job = await adapter.getJob(hash);
1035
- if (!job) return errorResponse("Not found", 404);
1150
+ if (!job) return errorResponse('Not found', 404);
1036
1151
  return jsonResponse(job);
1037
1152
  }
1038
1153
  if (isSingleJob) {
1039
- return errorResponse("Method not allowed", 405);
1154
+ return errorResponse('Method not allowed', 405);
1040
1155
  }
1041
1156
  } catch (err) {
1042
- return errorResponse(err instanceof Error ? err.message : "Unknown error");
1157
+ return errorResponse(err instanceof Error ? err.message : 'Unknown error');
1043
1158
  }
1044
- return errorResponse("Not found", 404);
1159
+ return errorResponse('Not found', 404);
1045
1160
  }
1046
1161
  async function routeRateLimit(request, pathname, method, adapter) {
1047
1162
  try {
1048
- if (pathname === "/rate-limit/stats" && method === "GET") {
1163
+ if (pathname === '/rate-limit/stats' && method === 'GET') {
1049
1164
  const stats = await adapter.getStats();
1050
1165
  return jsonResponse({ stats, capabilities: adapter.capabilities });
1051
1166
  }
1052
- if (pathname === "/rate-limit/resources" && method === "GET") {
1167
+ if (pathname === '/rate-limit/resources' && method === 'GET') {
1053
1168
  const resources = await adapter.listResources();
1054
1169
  return jsonResponse({ resources });
1055
1170
  }
1056
- if (pathname.endsWith("/config") && method === "PUT") {
1057
- const name = extractParam(pathname, "/rate-limit/resources/:name/config");
1058
- if (!name) return errorResponse("Not found", 404);
1171
+ if (pathname.endsWith('/config') && method === 'PUT') {
1172
+ const name = extractParam(pathname, '/rate-limit/resources/:name/config');
1173
+ if (!name) return errorResponse('Not found', 404);
1059
1174
  const body = await request.json();
1060
1175
  await adapter.updateResourceConfig(name, body);
1061
1176
  return jsonResponse({ updated: true });
1062
1177
  }
1063
- if (pathname.endsWith("/reset") && method === "POST") {
1064
- const name = extractParam(pathname, "/rate-limit/resources/:name/reset");
1065
- if (!name) return errorResponse("Not found", 404);
1178
+ if (pathname.endsWith('/reset') && method === 'POST') {
1179
+ const name = extractParam(pathname, '/rate-limit/resources/:name/reset');
1180
+ if (!name) return errorResponse('Not found', 404);
1066
1181
  await adapter.resetResource(name);
1067
1182
  return jsonResponse({ reset: true });
1068
1183
  }
1069
- const isSingleResource = pathname.startsWith("/rate-limit/resources/") && pathname.split("/").length === 4;
1070
- if (isSingleResource && method === "GET") {
1071
- const name = extractParam(pathname, "/rate-limit/resources/:name");
1072
- if (!name) return errorResponse("Not found", 404);
1184
+ const isSingleResource =
1185
+ pathname.startsWith('/rate-limit/resources/') &&
1186
+ pathname.split('/').length === 4;
1187
+ if (isSingleResource && method === 'GET') {
1188
+ const name = extractParam(pathname, '/rate-limit/resources/:name');
1189
+ if (!name) return errorResponse('Not found', 404);
1073
1190
  const status = await adapter.getResourceStatus(name);
1074
1191
  return jsonResponse({ resource: name, ...status });
1075
1192
  }
1076
1193
  if (isSingleResource) {
1077
- return errorResponse("Method not allowed", 405);
1194
+ return errorResponse('Method not allowed', 405);
1078
1195
  }
1079
1196
  } catch (err) {
1080
- return errorResponse(err instanceof Error ? err.message : "Unknown error");
1197
+ return errorResponse(err instanceof Error ? err.message : 'Unknown error');
1081
1198
  }
1082
- return errorResponse("Not found", 404);
1199
+ return errorResponse('Not found', 404);
1083
1200
  }
1084
1201
  function createDashboardHandler(options) {
1085
1202
  const opts = validateDashboardOptions(options);
1086
1203
  const clients = /* @__PURE__ */ new Map();
1087
1204
  for (const clientConfig of opts.clients) {
1088
- clients.set(clientConfig.name, {
1089
- name: clientConfig.name,
1090
- cache: clientConfig.cacheStore ? detectCacheAdapter(clientConfig.cacheStore) : void 0,
1091
- dedup: clientConfig.dedupeStore ? detectDedupeAdapter(clientConfig.dedupeStore) : void 0,
1092
- rateLimit: clientConfig.rateLimitStore ? detectRateLimitAdapter(clientConfig.rateLimitStore) : void 0
1205
+ const normalized = normalizeClient(clientConfig);
1206
+ clients.set(normalized.name, {
1207
+ name: normalized.name,
1208
+ cache: normalized.cacheStore
1209
+ ? detectCacheAdapter(normalized.cacheStore)
1210
+ : void 0,
1211
+ dedup: normalized.dedupeStore
1212
+ ? detectDedupeAdapter(normalized.dedupeStore)
1213
+ : void 0,
1214
+ rateLimit: normalized.rateLimitStore
1215
+ ? detectRateLimitAdapter(normalized.rateLimitStore)
1216
+ : void 0,
1093
1217
  });
1094
1218
  }
1095
1219
  const ctx = {
1096
1220
  clients,
1097
- pollIntervalMs: opts.pollIntervalMs
1221
+ pollIntervalMs: opts.pollIntervalMs,
1098
1222
  };
1099
1223
  return async (request) => {
1100
1224
  try {
1101
1225
  const url = new URL(request.url);
1102
1226
  let pathname = url.pathname;
1103
- if (opts.basePath !== "/" && pathname.startsWith(opts.basePath)) {
1104
- pathname = pathname.slice(opts.basePath.length) || "/";
1227
+ if (opts.basePath !== '/' && pathname.startsWith(opts.basePath)) {
1228
+ pathname = pathname.slice(opts.basePath.length) || '/';
1105
1229
  }
1106
1230
  const apiResponse = await routeApi(
1107
1231
  request,
1108
1232
  pathname,
1109
1233
  url.searchParams,
1110
- ctx
1234
+ ctx,
1111
1235
  );
1112
1236
  if (apiResponse) return apiResponse;
1113
1237
  return serveStaticWeb(pathname);
1114
1238
  } catch {
1115
- return errorResponse("Internal server error", 500);
1239
+ return errorResponse('Internal server error', 500);
1116
1240
  }
1117
1241
  };
1118
1242
  }
@@ -1123,10 +1247,11 @@ async function startDashboard(options) {
1123
1247
  middleware(req, res);
1124
1248
  });
1125
1249
  return new Promise((resolve, reject) => {
1126
- server.on("error", reject);
1250
+ server.on('error', reject);
1127
1251
  server.listen(opts.port, opts.host, () => {
1128
1252
  const addr = server.address();
1129
- const url = typeof addr === "string" ? addr : `http://${opts.host}:${opts.port}`;
1253
+ const url =
1254
+ typeof addr === 'string' ? addr : `http://${opts.host}:${opts.port}`;
1130
1255
  console.log(`Dashboard running at ${url}`);
1131
1256
  resolve({
1132
1257
  server,
@@ -1137,7 +1262,7 @@ async function startDashboard(options) {
1137
1262
  else res();
1138
1263
  });
1139
1264
  });
1140
- }
1265
+ },
1141
1266
  });
1142
1267
  });
1143
1268
  });
@@ -1147,4 +1272,4 @@ exports.createDashboard = createDashboard;
1147
1272
  exports.createDashboardHandler = createDashboardHandler;
1148
1273
  exports.startDashboard = startDashboard;
1149
1274
  //# sourceMappingURL=index.cjs.map
1150
- //# sourceMappingURL=index.cjs.map
1275
+ //# sourceMappingURL=index.cjs.map