request-scope-api 1.0.2 → 1.0.5

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 (57) hide show
  1. package/dist/cjs/adapters/mongo.adapter.js +6 -2
  2. package/dist/cjs/adapters/mongo.adapter.js.map +1 -1
  3. package/dist/cjs/adapters/pg.adapter.js +7 -2
  4. package/dist/cjs/adapters/pg.adapter.js.map +1 -1
  5. package/dist/cjs/capture.js +3 -3
  6. package/dist/cjs/capture.js.map +1 -1
  7. package/dist/cjs/dashboard/router.js +31 -69
  8. package/dist/cjs/dashboard/router.js.map +1 -1
  9. package/dist/cjs/index.js +9 -9
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/middleware.js +25 -25
  12. package/dist/cjs/middleware.js.map +1 -1
  13. package/dist/cjs/package.json +3 -0
  14. package/dist/esm/adapters/adapter.interface.js +0 -1
  15. package/dist/esm/adapters/mongo.adapter.js +3 -3
  16. package/dist/esm/adapters/mysql.adapter.js +0 -1
  17. package/dist/esm/adapters/pg.adapter.js +4 -3
  18. package/dist/esm/capture.js +1 -2
  19. package/dist/esm/config.js +0 -1
  20. package/dist/esm/dashboard/api.js +0 -1
  21. package/dist/esm/dashboard/router.js +33 -71
  22. package/dist/esm/index.js +3 -4
  23. package/dist/esm/masker.js +0 -1
  24. package/dist/esm/middleware.js +8 -9
  25. package/dist/esm/package.json +3 -0
  26. package/dist/esm/queue.js +0 -1
  27. package/dist/esm/retention.js +0 -1
  28. package/dist/esm/types.js +0 -1
  29. package/dist/types/adapters/adapter.interface.d.ts +1 -1
  30. package/dist/types/adapters/mongo.adapter.d.ts +1 -1
  31. package/dist/types/adapters/mysql.adapter.d.ts +2 -2
  32. package/dist/types/adapters/pg.adapter.d.ts +1 -1
  33. package/dist/types/capture.d.ts +1 -1
  34. package/dist/types/config.d.ts +1 -1
  35. package/dist/types/dashboard/router.d.ts +3 -25
  36. package/dist/types/index.d.ts +3 -3
  37. package/dist/types/middleware.d.ts +1 -1
  38. package/dist/types/queue.d.ts +1 -1
  39. package/dist/types/retention.d.ts +1 -1
  40. package/package.json +6 -5
  41. package/dist/dashboard/assets/index-C0TqFHk6.css +0 -1
  42. package/dist/dashboard/assets/index-MCuAZo4Q.js +0 -67
  43. package/dist/dashboard/index.html +0 -13
  44. package/dist/esm/adapters/adapter.interface.js.map +0 -1
  45. package/dist/esm/adapters/mongo.adapter.js.map +0 -1
  46. package/dist/esm/adapters/mysql.adapter.js.map +0 -1
  47. package/dist/esm/adapters/pg.adapter.js.map +0 -1
  48. package/dist/esm/capture.js.map +0 -1
  49. package/dist/esm/config.js.map +0 -1
  50. package/dist/esm/dashboard/api.js.map +0 -1
  51. package/dist/esm/dashboard/router.js.map +0 -1
  52. package/dist/esm/index.js.map +0 -1
  53. package/dist/esm/masker.js.map +0 -1
  54. package/dist/esm/middleware.js.map +0 -1
  55. package/dist/esm/queue.js.map +0 -1
  56. package/dist/esm/retention.js.map +0 -1
  57. package/dist/esm/types.js.map +0 -1
@@ -114,4 +114,3 @@ export function buildSensitiveFieldSet(config) {
114
114
  ];
115
115
  return new Set(names.map((n) => n.toLowerCase()));
116
116
  }
117
- //# sourceMappingURL=config.js.map
@@ -165,4 +165,3 @@ export async function deleteAllRecords(req, res) {
165
165
  res.status(500).json({ error: message });
166
166
  }
167
167
  }
168
- //# sourceMappingURL=api.js.map
@@ -1,104 +1,66 @@
1
1
  /**
2
- * RequestScope — Dashboard router.
3
- *
4
- * Provides an Express router with:
5
- * - Optional BasicAuth middleware
6
- * - API routes for querying records
7
- * - Static file serving for the React SPA
8
- * - SPA fallback for client-side routing
9
- *
10
- * Requirements: 8.1, 8.2, 8.3, 8.4
2
+ * RequestScope — Dashboard router (PRODUCTION FIXED ESM)
11
3
  */
12
- import { Router, static as expressStatic } from 'express';
4
+ import { Router, static as expressStatic } from "express";
13
5
  import path from 'path';
14
- import { getRecords, getRecordById, deleteAllRecords } from './api';
6
+ import { createRequire } from 'module';
7
+ import { getRecords, getRecordById, deleteAllRecords, } from "./api.js";
15
8
  // ---------------------------------------------------------------------------
16
- // Path resolution helper (works in both ESM and CJS)
9
+ // Dashboard path resolver (FIXED)
17
10
  // ---------------------------------------------------------------------------
18
- /**
19
- * Gets the dashboard directory path.
20
- * Works in both ESM and CJS by resolving from the compiled file location.
21
- */
22
11
  function getDashboardPath() {
23
- // Use __dirname for CJS (available natively)
24
- // For ESM, TypeScript with ESNext module will polyfill __dirname
25
- // @ts-ignore - __dirname handling differs between module systems
26
- return path.resolve(__dirname, '../../dashboard');
12
+ // @ts-ignore - TypeScript doesn't understand dual-module compilation
13
+ const packageJsonPath = createRequire(import.meta.url).resolve("request-scope-api/package.json");
14
+ const packageDir = path.dirname(packageJsonPath);
15
+ return path.join(packageDir, "dist", "dashboard");
27
16
  }
28
17
  // ---------------------------------------------------------------------------
29
18
  // BasicAuth middleware
30
19
  // ---------------------------------------------------------------------------
31
- /**
32
- * Creates a BasicAuth middleware if auth config is provided.
33
- *
34
- * Checks the Authorization header for Basic auth credentials.
35
- * If credentials are missing or don't match, responds with 401 and
36
- * WWW-Authenticate header.
37
- */
38
20
  function createBasicAuthMiddleware(auth) {
39
21
  if (!auth) {
40
22
  return (_req, _res, next) => next();
41
23
  }
42
- const { username, password } = auth;
43
- const expectedAuth = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
24
+ const expectedAuth = "Basic " +
25
+ Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
44
26
  return (req, res, next) => {
45
- const authHeader = req.headers.authorization;
46
- if (!authHeader || authHeader !== expectedAuth) {
47
- res.setHeader('WWW-Authenticate', 'Basic realm="RequestScope"');
48
- res.status(401).json({ error: 'Unauthorized' });
49
- return;
27
+ const header = req.headers.authorization;
28
+ if (!header || header !== expectedAuth) {
29
+ res.setHeader("WWW-Authenticate", 'Basic realm="RequestScope"');
30
+ return res.status(401).json({ error: "Unauthorized" });
50
31
  }
51
32
  next();
52
33
  };
53
34
  }
54
35
  // ---------------------------------------------------------------------------
55
- // Dashboard router factory
36
+ // Router factory
56
37
  // ---------------------------------------------------------------------------
57
- /**
58
- * Creates and returns an Express router for the RequestScope dashboard.
59
- *
60
- * The router includes:
61
- * - BasicAuth middleware (if auth config is provided)
62
- * - GET /api/records — List records with filtering and pagination
63
- * - GET /api/records/:id — Get a single record by ID
64
- * - Static file serving for the React SPA
65
- * - SPA fallback for client-side routing
66
- *
67
- * @param config - Optional dashboard configuration
68
- * @param adapter - Storage adapter instance (must be set on the app)
69
- * @returns Express Router
70
- */
71
38
  export function createDashboardRouter(config, adapter) {
72
39
  const router = Router();
73
- // Install BasicAuth middleware if auth config is present
74
- const authMiddleware = createBasicAuthMiddleware(config?.auth);
75
- router.use(authMiddleware);
76
- // Set the adapter on the app for API handlers to access
77
- router.use((req, res, next) => {
40
+ // auth middleware
41
+ router.use(createBasicAuthMiddleware(config?.auth));
42
+ // attach adapter
43
+ router.use((req, _res, next) => {
78
44
  if (adapter) {
79
- req.app.set('requestscopeAdapter', adapter);
45
+ req.app.set("requestscopeAdapter", adapter);
80
46
  }
81
47
  next();
82
48
  });
83
- // Mount API routes
84
- router.get('/api/records', getRecords);
85
- router.get('/api/records/:id', getRecordById);
86
- router.delete('/api/records', deleteAllRecords);
87
- // Serve static files from the dashboard bundle
88
- // The dashboard is built to dist/dashboard
89
- // Resolve path relative to this compiled file location
90
- // This works whether package is in node_modules or linked via npm link
49
+ // API routes
50
+ router.get("/api/records", getRecords);
51
+ router.get("/api/records/:id", getRecordById);
52
+ router.delete("/api/records", deleteAllRecords);
53
+ // dashboard path
91
54
  const dashboardPath = getDashboardPath();
92
- // Serve static files from the built dashboard
55
+ // static files
93
56
  router.use(expressStatic(dashboardPath));
94
- // Explicitly serve index.html for the root path
95
- router.get('/', (req, res) => {
96
- res.sendFile(path.join(dashboardPath, 'index.html'));
57
+ // root route
58
+ router.get("/", (_req, res) => {
59
+ res.sendFile(path.join(dashboardPath, "index.html"));
97
60
  });
98
- // SPA fallback for client-side routing (must be after static files)
99
- router.get('*', (req, res) => {
100
- res.sendFile(path.join(dashboardPath, 'index.html'));
61
+ // SPA fallback
62
+ router.get("*", (_req, res) => {
63
+ res.sendFile(path.join(dashboardPath, "index.html"));
101
64
  });
102
65
  return router;
103
66
  }
104
- //# sourceMappingURL=router.js.map
package/dist/esm/index.js CHANGED
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * Requirements: 1.1, 12.1, 12.2, 12.3, 12.4
10
10
  */
11
- import { requestscope as requestscopeMiddleware, errorHandler, setup } from './middleware';
12
- import { createDashboardRouter } from './dashboard/router';
11
+ import { requestscope as requestscopeMiddleware, errorHandler, setup } from './middleware.js';
12
+ import { createDashboardRouter } from './dashboard/router.js';
13
13
  // ---------------------------------------------------------------------------
14
14
  // Main middleware factory
15
15
  // ---------------------------------------------------------------------------
@@ -46,5 +46,4 @@ export { errorHandler };
46
46
  // ---------------------------------------------------------------------------
47
47
  // Named exports for convenience
48
48
  // ---------------------------------------------------------------------------
49
- export { requestscope as requestscopeMiddleware } from './middleware';
50
- //# sourceMappingURL=index.js.map
49
+ export { requestscope as requestscopeMiddleware } from './middleware.js';
@@ -67,4 +67,3 @@ export function maskObject(obj, sensitiveFields, depth = 0) {
67
67
  }
68
68
  return result;
69
69
  }
70
- //# sourceMappingURL=masker.js.map
@@ -10,14 +10,14 @@
10
10
  *
11
11
  * Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 2.2, 4.1, 4.2, 4.3, 5.1
12
12
  */
13
- import { validateConfig, applyDefaults, buildSensitiveFieldSet } from './config';
14
- import { MongoAdapter } from './adapters/mongo.adapter';
15
- import { MySQLAdapter } from './adapters/mysql.adapter';
16
- import { PgAdapter } from './adapters/pg.adapter';
17
- import { AsyncQueue } from './queue';
18
- import { RetentionScheduler } from './retention';
19
- import { bufferRequestBody, wrapResponse, ERROR_DATA_SYMBOL } from './capture';
20
- import { createDashboardRouter } from './dashboard/router';
13
+ import { validateConfig, applyDefaults, buildSensitiveFieldSet } from './config.js';
14
+ import { MongoAdapter } from './adapters/mongo.adapter.js';
15
+ import { MySQLAdapter } from './adapters/mysql.adapter.js';
16
+ import { PgAdapter } from './adapters/pg.adapter.js';
17
+ import { AsyncQueue } from './queue.js';
18
+ import { RetentionScheduler } from './retention.js';
19
+ import { bufferRequestBody, wrapResponse, ERROR_DATA_SYMBOL } from './capture.js';
20
+ import { createDashboardRouter } from './dashboard/router.js';
21
21
  // ---------------------------------------------------------------------------
22
22
  // Factory function
23
23
  // ---------------------------------------------------------------------------
@@ -190,4 +190,3 @@ export function errorHandler(err, req, res, next) {
190
190
  // Pass error to next error handler in the chain
191
191
  next(err);
192
192
  }
193
- //# sourceMappingURL=middleware.js.map
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/dist/esm/queue.js CHANGED
@@ -107,4 +107,3 @@ export class AsyncQueue {
107
107
  }
108
108
  }
109
109
  }
110
- //# sourceMappingURL=queue.js.map
@@ -57,4 +57,3 @@ export class RetentionScheduler {
57
57
  }
58
58
  }
59
59
  }
60
- //# sourceMappingURL=retention.js.map
package/dist/esm/types.js CHANGED
@@ -5,4 +5,3 @@
5
5
  * All interfaces are written to compile under `strict: true` with zero errors.
6
6
  */
7
7
  export {};
8
- //# sourceMappingURL=types.js.map
@@ -4,4 +4,4 @@
4
4
  * Adapters should import from this file so they stay decoupled from the
5
5
  * top-level types module while still satisfying the shared contract.
6
6
  */
7
- export type { StorageAdapter, RequestRecord, QueryFilters } from '../types';
7
+ export type { StorageAdapter, RequestRecord, QueryFilters } from '../types.js';
@@ -6,7 +6,7 @@
6
6
  * A descending index on `timestamp` is created at initialization for sort
7
7
  * performance and retention queries.
8
8
  */
9
- import type { StorageAdapter, RequestRecord, QueryFilters, StorageConfig } from '../types';
9
+ import type { StorageAdapter, RequestRecord, QueryFilters, StorageConfig } from '../types.js';
10
10
  export declare class MongoAdapter implements StorageAdapter {
11
11
  private readonly config;
12
12
  private client;
@@ -4,8 +4,8 @@
4
4
  * Compiles under `strict: true` with zero errors.
5
5
  * Requirements: 1.6, 1.7, 6.1, 6.3, 6.5, 7.4
6
6
  */
7
- import type { StorageAdapter, RequestRecord, QueryFilters } from '../types';
8
- import type { StorageConfig } from '../types';
7
+ import type { StorageAdapter, RequestRecord, QueryFilters } from '../types.js';
8
+ import type { StorageConfig } from '../types.js';
9
9
  export declare class MySQLAdapter implements StorageAdapter {
10
10
  private pool;
11
11
  private readonly config;
@@ -4,7 +4,7 @@
4
4
  * Compiles under `strict: true` with zero errors.
5
5
  * Requirements: 1.6, 1.7, 6.1, 6.4, 6.5, 7.4
6
6
  */
7
- import type { StorageAdapter, RequestRecord, QueryFilters, StorageConfig } from '../types';
7
+ import type { StorageAdapter, RequestRecord, QueryFilters, StorageConfig } from '../types.js';
8
8
  export declare class PgAdapter implements StorageAdapter {
9
9
  private pool;
10
10
  private readonly config;
@@ -14,7 +14,7 @@
14
14
  * Compiles under strict: true.
15
15
  */
16
16
  import { IncomingMessage, ServerResponse } from 'http';
17
- import type { RequestRecord } from './types';
17
+ import type { RequestRecord } from './types.js';
18
18
  /**
19
19
  * Extracts the client IP address from the request.
20
20
  *
@@ -4,7 +4,7 @@
4
4
  * All validation errors are thrown synchronously so that `requestscope()` fails
5
5
  * fast at startup before any middleware is registered.
6
6
  */
7
- import type { RequestScopeConfig } from './types';
7
+ import type { RequestScopeConfig } from './types.js';
8
8
  /**
9
9
  * Fills in missing optional fields with their documented default values.
10
10
  * Mutates the config object in place and returns it for chaining.
@@ -1,28 +1,6 @@
1
1
  /**
2
- * RequestScope — Dashboard router.
3
- *
4
- * Provides an Express router with:
5
- * - Optional BasicAuth middleware
6
- * - API routes for querying records
7
- * - Static file serving for the React SPA
8
- * - SPA fallback for client-side routing
9
- *
10
- * Requirements: 8.1, 8.2, 8.3, 8.4
11
- */
12
- import { Router } from 'express';
13
- import type { DashboardConfig, StorageAdapter } from '../types';
14
- /**
15
- * Creates and returns an Express router for the RequestScope dashboard.
16
- *
17
- * The router includes:
18
- * - BasicAuth middleware (if auth config is provided)
19
- * - GET /api/records — List records with filtering and pagination
20
- * - GET /api/records/:id — Get a single record by ID
21
- * - Static file serving for the React SPA
22
- * - SPA fallback for client-side routing
23
- *
24
- * @param config - Optional dashboard configuration
25
- * @param adapter - Storage adapter instance (must be set on the app)
26
- * @returns Express Router
2
+ * RequestScope — Dashboard router (PRODUCTION FIXED ESM)
27
3
  */
4
+ import { Router } from "express";
5
+ import type { DashboardConfig, StorageAdapter } from "../types.js";
28
6
  export declare function createDashboardRouter(config?: DashboardConfig, adapter?: StorageAdapter): Router;
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * Requirements: 1.1, 12.1, 12.2, 12.3, 12.4
10
10
  */
11
- import { requestscope as requestscopeMiddleware, errorHandler, setup } from './middleware';
12
- import type { RequestScopeConfig, StorageConfig, AuthConfig, RequestRecord, StorageAdapter, QueryFilters, DashboardConfig } from './types';
11
+ import { requestscope as requestscopeMiddleware, errorHandler, setup } from './middleware.js';
12
+ import type { RequestScopeConfig, StorageConfig, AuthConfig, RequestRecord, StorageAdapter, QueryFilters, DashboardConfig } from './types.js';
13
13
  /**
14
14
  * Creates and returns an Express middleware that captures HTTP requests.
15
15
  *
@@ -28,4 +28,4 @@ export declare function dashboard(config?: DashboardConfig, adapter?: StorageAda
28
28
  export { setup };
29
29
  export { errorHandler };
30
30
  export type { RequestScopeConfig, StorageConfig, AuthConfig, RequestRecord, StorageAdapter, QueryFilters, DashboardConfig, };
31
- export { requestscope as requestscopeMiddleware } from './middleware';
31
+ export { requestscope as requestscopeMiddleware } from './middleware.js';
@@ -11,7 +11,7 @@
11
11
  * Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 2.2, 4.1, 4.2, 4.3, 5.1
12
12
  */
13
13
  import type { Request, Response, NextFunction, RequestHandler } from 'express';
14
- import type { RequestScopeConfig, StorageAdapter } from './types';
14
+ import type { RequestScopeConfig, StorageAdapter } from './types.js';
15
15
  /**
16
16
  * Creates and returns an Express middleware that captures HTTP requests.
17
17
  *
@@ -6,7 +6,7 @@
6
6
  * total: adapter failures are caught, logged to stderr, and discarded so
7
7
  * they never propagate to the caller.
8
8
  */
9
- import type { RequestRecord, StorageAdapter } from './types';
9
+ import type { RequestRecord, StorageAdapter } from './types.js';
10
10
  export declare class AsyncQueue {
11
11
  private readonly adapter;
12
12
  private readonly batchSize;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Requirements: 7.1, 7.2, 7.4, 7.5
6
6
  */
7
- import type { StorageAdapter } from './adapters/adapter.interface';
7
+ import type { StorageAdapter } from './adapters/adapter.interface.js';
8
8
  export declare class RetentionScheduler {
9
9
  private readonly adapter;
10
10
  private readonly retentionDays;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "request-scope-api",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "description": "Zero-friction API request observability for Express.js applications",
5
5
  "keywords": [
6
6
  "express",
@@ -12,6 +12,7 @@
12
12
  ],
13
13
  "license": "MIT",
14
14
  "author": "jaydip",
15
+ "type": "module",
15
16
  "exports": {
16
17
  ".": {
17
18
  "import": "./dist/esm/index.js",
@@ -20,15 +21,16 @@
20
21
  }
21
22
  },
22
23
  "main": "dist/cjs/index.js",
24
+ "module": "dist/esm/index.js",
23
25
  "types": "dist/types/index.d.ts",
24
26
  "files": [
25
27
  "dist/",
26
28
  "README.md"
27
29
  ],
28
30
  "scripts": {
29
- "build": "npm run build:cjs && npm run build:esm",
30
31
  "build:cjs": "tsc --project tsconfig.json",
31
- "build:esm": "tsc --project tsconfig.esm.json",
32
+ "build:esm": "tsc --project tsconfig.esm.json && node scripts/post-build-esm.cjs",
33
+ "build": "npm run build:cjs && npm run build:esm",
32
34
  "prepublishOnly": "npm run build"
33
35
  },
34
36
  "dependencies": {
@@ -44,6 +46,5 @@
44
46
  "@types/pg": "8.10.9",
45
47
  "@types/uuid": "9.0.7",
46
48
  "typescript": "^5.9.3"
47
- },
48
- "module": "dist/esm/index.js"
49
+ }
49
50
  }
@@ -1 +0,0 @@
1
- *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.right-2{right:.5rem}.top-2{top:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mr-1{margin-right:.25rem}.mt-1{margin-top:.25rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.max-h-96{max-height:24rem}.min-h-screen{min-height:100vh}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.flex-shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(229 231 235 / var(--tw-divide-opacity, 1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.border-red-400{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity, 1))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-red-900{--tw-text-opacity: 1;color:rgb(127 29 29 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-800{--tw-text-opacity: 1;color:rgb(133 77 14 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-300:hover{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-600:hover{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.hover\:bg-red-200:hover{--tw-bg-opacity: 1;background-color:rgb(254 202 202 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:text-blue-800:hover{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-transparent:focus{border-color:transparent}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus\:ring-gray-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(55 65 81 / var(--tw-divide-opacity, 1))}.dark\:border-blue-400{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.dark\:border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.dark\:border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.dark\:border-red-800{--tw-border-opacity: 1;border-color:rgb(153 27 27 / var(--tw-border-opacity, 1))}.dark\:bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900\/20{background-color:#7f1d1d33}.dark\:bg-red-900\/30{background-color:#7f1d1d4d}.dark\:text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.dark\:text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.dark\:text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:placeholder-gray-400::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(156 163 175 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-gray-400::placeholder{--tw-placeholder-opacity: 1;color:rgb(156 163 175 / var(--tw-placeholder-opacity, 1))}.dark\:hover\:bg-gray-600:hover{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-red-900\/50:hover{background-color:#7f1d1d80}.dark\:hover\:text-blue-300:hover{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.dark\:hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}}