@pipeline-builder/api-core 4.1.3 → 4.1.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 (77) hide show
  1. package/README.md +3 -3
  2. package/lib/errors/app-errors.d.ts +0 -4
  3. package/lib/errors/app-errors.js +1 -8
  4. package/lib/helpers/restore-helpers.d.ts +3 -3
  5. package/lib/helpers/restore-helpers.js +4 -4
  6. package/lib/helpers/sse-helpers.d.ts +8 -1
  7. package/lib/helpers/sse-helpers.js +11 -3
  8. package/lib/middleware/auth.d.ts +174 -43
  9. package/lib/middleware/auth.js +384 -179
  10. package/lib/middleware/index.d.ts +1 -0
  11. package/lib/middleware/index.js +2 -1
  12. package/lib/middleware/route-table.d.ts +104 -0
  13. package/lib/middleware/route-table.js +230 -0
  14. package/lib/middleware/step-up.d.ts +48 -16
  15. package/lib/middleware/step-up.js +74 -36
  16. package/lib/services/api-key-exchange.d.ts +25 -0
  17. package/lib/services/api-key-exchange.js +219 -0
  18. package/lib/services/cache-service.d.ts +11 -26
  19. package/lib/services/cache-service.js +20 -74
  20. package/lib/services/compliance-event-subscriber.d.ts +5 -4
  21. package/lib/services/compliance-event-subscriber.js +8 -6
  22. package/lib/services/durable-event-bus.d.ts +22 -39
  23. package/lib/services/durable-event-bus.js +95 -43
  24. package/lib/services/env-redis.d.ts +27 -0
  25. package/lib/services/env-redis.js +63 -1
  26. package/lib/services/http-client.d.ts +10 -15
  27. package/lib/services/http-client.js +32 -27
  28. package/lib/services/index.d.ts +3 -0
  29. package/lib/services/index.js +4 -1
  30. package/lib/services/jwks-cache.d.ts +102 -0
  31. package/lib/services/jwks-cache.js +222 -0
  32. package/lib/services/quota.js +12 -4
  33. package/lib/services/remote-audit-client.d.ts +1 -1
  34. package/lib/services/remote-audit-client.js +32 -1
  35. package/lib/services/scheduler.d.ts +2 -3
  36. package/lib/services/scheduler.js +2 -2
  37. package/lib/services/service-boot.d.ts +4 -2
  38. package/lib/services/service-boot.js +7 -3
  39. package/lib/services/service-keys.d.ts +83 -0
  40. package/lib/services/service-keys.js +354 -0
  41. package/lib/services/sse-ticket-store.d.ts +55 -22
  42. package/lib/services/sse-ticket-store.js +122 -79
  43. package/lib/services/token-revocation.js +30 -18
  44. package/lib/testing/route-coverage.d.ts +64 -0
  45. package/lib/testing/route-coverage.js +183 -0
  46. package/lib/testing/service-tokens.d.ts +66 -0
  47. package/lib/testing/service-tokens.js +137 -0
  48. package/lib/testing/tier-mock.d.ts +2 -2
  49. package/lib/testing/tier-mock.js +3 -3
  50. package/lib/testing/user-tokens.d.ts +75 -0
  51. package/lib/testing/user-tokens.js +115 -0
  52. package/lib/types/catalog-metadata.d.ts +0 -2
  53. package/lib/types/catalog-metadata.js +1 -3
  54. package/lib/types/common.d.ts +103 -4
  55. package/lib/types/common.js +11 -1
  56. package/lib/types/error-codes.d.ts +15 -0
  57. package/lib/types/error-codes.js +22 -1
  58. package/lib/types/permissions.d.ts +35 -1
  59. package/lib/types/permissions.js +72 -2
  60. package/lib/utils/api-key.d.ts +34 -0
  61. package/lib/utils/api-key.js +75 -0
  62. package/lib/utils/env.d.ts +0 -2
  63. package/lib/utils/env.js +1 -6
  64. package/lib/utils/index.d.ts +4 -0
  65. package/lib/utils/index.js +5 -1
  66. package/lib/utils/jwk.d.ts +98 -0
  67. package/lib/utils/jwk.js +139 -0
  68. package/lib/utils/logger.d.ts +30 -1
  69. package/lib/utils/logger.js +74 -27
  70. package/lib/utils/secret-encryption.d.ts +7 -2
  71. package/lib/utils/secret-encryption.js +47 -4
  72. package/lib/utils/secret-rotation.d.ts +17 -0
  73. package/lib/utils/secret-rotation.js +54 -0
  74. package/lib/utils/sensitive-patterns.d.ts +49 -0
  75. package/lib/utils/sensitive-patterns.js +149 -0
  76. package/lib/validation/common-schemas.js +5 -4
  77. package/package.json +20 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ Core server-side utilities (auth middleware, response helpers, error codes, quot
8
8
 
9
9
  ## Responsibilities
10
10
 
11
- Provides the cross-cutting primitives every backend service depends on: JWT authentication and authorization middleware, inter-service token minting, standardized HTTP response and error helpers, request parameter/identity parsing, Zod validation schemas with an OpenAPI registry, a safe service-to-service HTTP client, quota enforcement types and client, structured Winston logging, an in-memory/Redis cache, domain event pub/sub, and the static AI provider catalog.
11
+ Provides the cross-cutting primitives every backend service depends on: JWT authentication and authorization middleware, inter-service token minting, standardized HTTP response and error helpers, request parameter/identity parsing, Zod validation schemas with an OpenAPI registry, a safe service-to-service HTTP client, quota enforcement types and client, structured Winston logging, an in-memory cache with cross-replica invalidation, domain event pub/sub, and the static AI provider catalog.
12
12
 
13
13
  ## Key exports
14
14
 
@@ -29,7 +29,7 @@ Provides the cross-cutting primitives every backend service depends on: JWT auth
29
29
  | `sendPaginated`, `sendPaginatedNested`, `parsePaginationParams` | Paginated response helpers |
30
30
  | `extractDbError`, `errorMessage` | Safe DB-error and error-to-string extraction |
31
31
  | `ErrorCode`, `getStatusForErrorCode` | Standard error code enum and HTTP status mapping |
32
- | `AppError`, `NotFoundError`, `ForbiddenError`, `ValidationError`, `ConflictError`, `UnauthorizedError` | Typed HTTP error classes |
32
+ | `AppError`, `NotFoundError`, `ForbiddenError`, `ValidationError`, `ConflictError` | Typed HTTP error classes |
33
33
 
34
34
  ### Request parsing (`./utils`)
35
35
  | Export | Purpose |
@@ -44,7 +44,7 @@ Provides the cross-cutting primitives every backend service depends on: JWT auth
44
44
  | `InternalHttpClient`, `createSafeClient` | Service-to-service HTTP client (`ServiceConfig`, `RequestOptions`) |
45
45
  | `createComplianceClient` / `ComplianceClient` | Typed compliance-service client built on the safe client |
46
46
  | `QuotaService`, `createQuotaService`, `QuotaType`, `QuotaCheckResult`, `QuotaTier`, `QUOTA_TIERS`, `getTierLimits` | Quota enforcement client and tier presets |
47
- | `CacheService`, `createCacheService` | In-memory TTL cache with optional Redis backend |
47
+ | `CacheService`, `createCacheService` | In-memory LRU TTL cache with cross-replica invalidation over Redis pub/sub |
48
48
  | `entityEvents` | Process-local domain event pub/sub for entity changes |
49
49
 
50
50
  ### Logging, validation & OpenAPI
@@ -24,7 +24,3 @@ export declare class ValidationError extends AppError {
24
24
  export declare class ConflictError extends AppError {
25
25
  constructor(message: string);
26
26
  }
27
- /** 401 — Unauthorized / authentication required. */
28
- export declare class UnauthorizedError extends AppError {
29
- constructor(message: string);
30
- }
@@ -43,11 +43,4 @@ export class ConflictError extends AppError {
43
43
  this.name = 'ConflictError';
44
44
  }
45
45
  }
46
- /** 401 — Unauthorized / authentication required. */
47
- export class UnauthorizedError extends AppError {
48
- constructor(message) {
49
- super(getStatusForErrorCode(ErrorCode.UNAUTHORIZED), ErrorCode.UNAUTHORIZED, message);
50
- this.name = 'UnauthorizedError';
51
- }
52
- }
53
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLWVycm9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9lcnJvcnMvYXBwLWVycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxTQUFTLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRTs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sUUFBUyxTQUFRLEtBQUs7SUFFZixVQUFVO0lBQ1YsSUFBSTtJQUZ0QixZQUNrQixVQUFrQixFQUNsQixJQUFlLEVBQy9CLE9BQWU7UUFFZixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7MEJBSkMsVUFBVTtvQkFDVixJQUFJO1FBSXBCLElBQUksQ0FBQyxJQUFJLEdBQUcsVUFBVSxDQUFDO0lBQ3pCLENBQUM7Q0FDRjtBQUVELGdDQUFnQztBQUNoQyxNQUFNLE9BQU8sYUFBYyxTQUFRLFFBQVE7SUFDekMsWUFBWSxPQUFlO1FBQ3pCLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLEVBQUUsU0FBUyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNoRixJQUFJLENBQUMsSUFBSSxHQUFHLGVBQWUsQ0FBQztJQUM5QixDQUFDO0NBQ0Y7QUFFRCxzQ0FBc0M7QUFDdEMsTUFBTSxPQUFPLGNBQWUsU0FBUSxRQUFRO0lBQzFDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLHdCQUF3QixDQUFDLEVBQUUsU0FBUyxDQUFDLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQzlHLElBQUksQ0FBQyxJQUFJLEdBQUcsZ0JBQWdCLENBQUM7SUFDL0IsQ0FBQztDQUNGO0FBRUQsc0NBQXNDO0FBQ3RDLE1BQU0sT0FBTyxlQUFnQixTQUFRLFFBQVE7SUFDM0MsWUFBWSxPQUFlO1FBQ3pCLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxTQUFTLENBQUMsZ0JBQWdCLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDOUYsSUFBSSxDQUFDLElBQUksR0FBRyxpQkFBaUIsQ0FBQztJQUNoQyxDQUFDO0NBQ0Y7QUFFRCxrQ0FBa0M7QUFDbEMsTUFBTSxPQUFPLGFBQWMsU0FBUSxRQUFRO0lBQ3pDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDOUUsSUFBSSxDQUFDLElBQUksR0FBRyxlQUFlLENBQUM7SUFDOUIsQ0FBQztDQUNGO0FBRUQsb0RBQW9EO0FBQ3BELE1BQU0sT0FBTyxpQkFBa0IsU0FBUSxRQUFRO0lBQzdDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLFlBQVksQ0FBQyxFQUFFLFNBQVMsQ0FBQyxZQUFZLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDdEYsSUFBSSxDQUFDLElBQUksR0FBRyxtQkFBbUIsQ0FBQztJQUNsQyxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgRXJyb3JDb2RlLCBnZXRTdGF0dXNGb3JFcnJvckNvZGUgfSBmcm9tICcuLi90eXBlcy9lcnJvci1jb2Rlcy5qcyc7XG5cbi8qKlxuICogQmFzZSBhcHBsaWNhdGlvbiBlcnJvciB3aXRoIEhUVFAgc3RhdHVzIGNvZGUgYW5kIGVycm9yIGNvZGUuXG4gKiBFeHRlbmQgdGhpcyBmb3IgZG9tYWluLXNwZWNpZmljIGVycm9yIHR5cGVzLlxuICovXG5leHBvcnQgY2xhc3MgQXBwRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKFxuICAgIHB1YmxpYyByZWFkb25seSBzdGF0dXNDb2RlOiBudW1iZXIsXG4gICAgcHVibGljIHJlYWRvbmx5IGNvZGU6IEVycm9yQ29kZSxcbiAgICBtZXNzYWdlOiBzdHJpbmcsXG4gICkge1xuICAgIHN1cGVyKG1lc3NhZ2UpO1xuICAgIHRoaXMubmFtZSA9ICdBcHBFcnJvcic7XG4gIH1cbn1cblxuLyoqIDQwNCDigJQgUmVzb3VyY2Ugbm90IGZvdW5kLiAqL1xuZXhwb3J0IGNsYXNzIE5vdEZvdW5kRXJyb3IgZXh0ZW5kcyBBcHBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKGdldFN0YXR1c0ZvckVycm9yQ29kZShFcnJvckNvZGUuTk9UX0ZPVU5EKSwgRXJyb3JDb2RlLk5PVF9GT1VORCwgbWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ05vdEZvdW5kRXJyb3InO1xuICB9XG59XG5cbi8qKiA0MDMg4oCUIEluc3VmZmljaWVudCBwZXJtaXNzaW9ucy4gKi9cbmV4cG9ydCBjbGFzcyBGb3JiaWRkZW5FcnJvciBleHRlbmRzIEFwcEVycm9yIHtcbiAgY29uc3RydWN0b3IobWVzc2FnZTogc3RyaW5nKSB7XG4gICAgc3VwZXIoZ2V0U3RhdHVzRm9yRXJyb3JDb2RlKEVycm9yQ29kZS5JTlNVRkZJQ0lFTlRfUEVSTUlTU0lPTlMpLCBFcnJvckNvZGUuSU5TVUZGSUNJRU5UX1BFUk1JU1NJT05TLCBtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnRm9yYmlkZGVuRXJyb3InO1xuICB9XG59XG5cbi8qKiA0MDAg4oCUIFZhbGlkYXRpb24gLyBiYWQgcmVxdWVzdC4gKi9cbmV4cG9ydCBjbGFzcyBWYWxpZGF0aW9uRXJyb3IgZXh0ZW5kcyBBcHBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKGdldFN0YXR1c0ZvckVycm9yQ29kZShFcnJvckNvZGUuVkFMSURBVElPTl9FUlJPUiksIEVycm9yQ29kZS5WQUxJREFUSU9OX0VSUk9SLCBtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnVmFsaWRhdGlvbkVycm9yJztcbiAgfVxufVxuXG4vKiogNDA5IOKAlCBDb25mbGljdCAvIGR1cGxpY2F0ZS4gKi9cbmV4cG9ydCBjbGFzcyBDb25mbGljdEVycm9yIGV4dGVuZHMgQXBwRXJyb3Ige1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBzdXBlcihnZXRTdGF0dXNGb3JFcnJvckNvZGUoRXJyb3JDb2RlLkNPTkZMSUNUKSwgRXJyb3JDb2RlLkNPTkZMSUNULCBtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnQ29uZmxpY3RFcnJvcic7XG4gIH1cbn1cblxuLyoqIDQwMSDigJQgVW5hdXRob3JpemVkIC8gYXV0aGVudGljYXRpb24gcmVxdWlyZWQuICovXG5leHBvcnQgY2xhc3MgVW5hdXRob3JpemVkRXJyb3IgZXh0ZW5kcyBBcHBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKGdldFN0YXR1c0ZvckVycm9yQ29kZShFcnJvckNvZGUuVU5BVVRIT1JJWkVEKSwgRXJyb3JDb2RlLlVOQVVUSE9SSVpFRCwgbWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ1VuYXV0aG9yaXplZEVycm9yJztcbiAgfVxufVxuIl19
46
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLWVycm9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9lcnJvcnMvYXBwLWVycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxTQUFTLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRTs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sUUFBUyxTQUFRLEtBQUs7SUFFZixVQUFVO0lBQ1YsSUFBSTtJQUZ0QixZQUNrQixVQUFrQixFQUNsQixJQUFlLEVBQy9CLE9BQWU7UUFFZixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7MEJBSkMsVUFBVTtvQkFDVixJQUFJO1FBSXBCLElBQUksQ0FBQyxJQUFJLEdBQUcsVUFBVSxDQUFDO0lBQ3pCLENBQUM7Q0FDRjtBQUVELGdDQUFnQztBQUNoQyxNQUFNLE9BQU8sYUFBYyxTQUFRLFFBQVE7SUFDekMsWUFBWSxPQUFlO1FBQ3pCLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLEVBQUUsU0FBUyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNoRixJQUFJLENBQUMsSUFBSSxHQUFHLGVBQWUsQ0FBQztJQUM5QixDQUFDO0NBQ0Y7QUFFRCxzQ0FBc0M7QUFDdEMsTUFBTSxPQUFPLGNBQWUsU0FBUSxRQUFRO0lBQzFDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLHdCQUF3QixDQUFDLEVBQUUsU0FBUyxDQUFDLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQzlHLElBQUksQ0FBQyxJQUFJLEdBQUcsZ0JBQWdCLENBQUM7SUFDL0IsQ0FBQztDQUNGO0FBRUQsc0NBQXNDO0FBQ3RDLE1BQU0sT0FBTyxlQUFnQixTQUFRLFFBQVE7SUFDM0MsWUFBWSxPQUFlO1FBQ3pCLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxTQUFTLENBQUMsZ0JBQWdCLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDOUYsSUFBSSxDQUFDLElBQUksR0FBRyxpQkFBaUIsQ0FBQztJQUNoQyxDQUFDO0NBQ0Y7QUFFRCxrQ0FBa0M7QUFDbEMsTUFBTSxPQUFPLGFBQWMsU0FBUSxRQUFRO0lBQ3pDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDOUUsSUFBSSxDQUFDLElBQUksR0FBRyxlQUFlLENBQUM7SUFDOUIsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IEVycm9yQ29kZSwgZ2V0U3RhdHVzRm9yRXJyb3JDb2RlIH0gZnJvbSAnLi4vdHlwZXMvZXJyb3ItY29kZXMuanMnO1xuXG4vKipcbiAqIEJhc2UgYXBwbGljYXRpb24gZXJyb3Igd2l0aCBIVFRQIHN0YXR1cyBjb2RlIGFuZCBlcnJvciBjb2RlLlxuICogRXh0ZW5kIHRoaXMgZm9yIGRvbWFpbi1zcGVjaWZpYyBlcnJvciB0eXBlcy5cbiAqL1xuZXhwb3J0IGNsYXNzIEFwcEVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICBjb25zdHJ1Y3RvcihcbiAgICBwdWJsaWMgcmVhZG9ubHkgc3RhdHVzQ29kZTogbnVtYmVyLFxuICAgIHB1YmxpYyByZWFkb25seSBjb2RlOiBFcnJvckNvZGUsXG4gICAgbWVzc2FnZTogc3RyaW5nLFxuICApIHtcbiAgICBzdXBlcihtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnQXBwRXJyb3InO1xuICB9XG59XG5cbi8qKiA0MDQg4oCUIFJlc291cmNlIG5vdCBmb3VuZC4gKi9cbmV4cG9ydCBjbGFzcyBOb3RGb3VuZEVycm9yIGV4dGVuZHMgQXBwRXJyb3Ige1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBzdXBlcihnZXRTdGF0dXNGb3JFcnJvckNvZGUoRXJyb3JDb2RlLk5PVF9GT1VORCksIEVycm9yQ29kZS5OT1RfRk9VTkQsIG1lc3NhZ2UpO1xuICAgIHRoaXMubmFtZSA9ICdOb3RGb3VuZEVycm9yJztcbiAgfVxufVxuXG4vKiogNDAzIOKAlCBJbnN1ZmZpY2llbnQgcGVybWlzc2lvbnMuICovXG5leHBvcnQgY2xhc3MgRm9yYmlkZGVuRXJyb3IgZXh0ZW5kcyBBcHBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKGdldFN0YXR1c0ZvckVycm9yQ29kZShFcnJvckNvZGUuSU5TVUZGSUNJRU5UX1BFUk1JU1NJT05TKSwgRXJyb3JDb2RlLklOU1VGRklDSUVOVF9QRVJNSVNTSU9OUywgbWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ0ZvcmJpZGRlbkVycm9yJztcbiAgfVxufVxuXG4vKiogNDAwIOKAlCBWYWxpZGF0aW9uIC8gYmFkIHJlcXVlc3QuICovXG5leHBvcnQgY2xhc3MgVmFsaWRhdGlvbkVycm9yIGV4dGVuZHMgQXBwRXJyb3Ige1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBzdXBlcihnZXRTdGF0dXNGb3JFcnJvckNvZGUoRXJyb3JDb2RlLlZBTElEQVRJT05fRVJST1IpLCBFcnJvckNvZGUuVkFMSURBVElPTl9FUlJPUiwgbWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ1ZhbGlkYXRpb25FcnJvcic7XG4gIH1cbn1cblxuLyoqIDQwOSDigJQgQ29uZmxpY3QgLyBkdXBsaWNhdGUuICovXG5leHBvcnQgY2xhc3MgQ29uZmxpY3RFcnJvciBleHRlbmRzIEFwcEVycm9yIHtcbiAgY29uc3RydWN0b3IobWVzc2FnZTogc3RyaW5nKSB7XG4gICAgc3VwZXIoZ2V0U3RhdHVzRm9yRXJyb3JDb2RlKEVycm9yQ29kZS5DT05GTElDVCksIEVycm9yQ29kZS5DT05GTElDVCwgbWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ0NvbmZsaWN0RXJyb3InO1xuICB9XG59XG4iXX0=
@@ -38,9 +38,9 @@ export interface PurgeableService<T extends RestorableEntity> {
38
38
  * genuine tombstone (a live/active row loads as `null` here and 404s, so purge
39
39
  * can never hard-delete a live row). Returns `{ existing, purgedId }` on success
40
40
  * (the caller emits the entity-specific audit off `existing`), or `null` when it
41
- * has ALREADY sent a response (400/404/403). Purge is intentionally NOT step-up
42
- * gated: it only finalizes what the retention sweep would remove anyway, and the
43
- * frontend gates it with an explicit confirm dialog. DRYs the near-identical
41
+ * has ALREADY sent a response (400/404/403). Step-up is the ROUTE's job: the
42
+ * pipeline, template and plugin services mount purge behind `requireStepUp`
43
+ * (sharing restore's chain so one token isn't consumed twice). DRYs the near-identical
44
44
  * pipeline / pipeline_template purge routes.
45
45
  */
46
46
  export declare function loadAndPurge<T extends RestorableEntity>(req: Request, res: Response, orgId: string, service: PurgeableService<T>, label: string, publishPermission: Permission, userId: string): Promise<{
@@ -45,9 +45,9 @@ export async function loadAndRestore(req, res, orgId, userId, service, label, pu
45
45
  * genuine tombstone (a live/active row loads as `null` here and 404s, so purge
46
46
  * can never hard-delete a live row). Returns `{ existing, purgedId }` on success
47
47
  * (the caller emits the entity-specific audit off `existing`), or `null` when it
48
- * has ALREADY sent a response (400/404/403). Purge is intentionally NOT step-up
49
- * gated: it only finalizes what the retention sweep would remove anyway, and the
50
- * frontend gates it with an explicit confirm dialog. DRYs the near-identical
48
+ * has ALREADY sent a response (400/404/403). Step-up is the ROUTE's job: the
49
+ * pipeline, template and plugin services mount purge behind `requireStepUp`
50
+ * (sharing restore's chain so one token isn't consumed twice). DRYs the near-identical
51
51
  * pipeline / pipeline_template purge routes.
52
52
  */
53
53
  export async function loadAndPurge(req, res, orgId, service, label, publishPermission, userId) {
@@ -75,4 +75,4 @@ export async function loadAndPurge(req, res, orgId, service, label, publishPermi
75
75
  }
76
76
  return { existing, purgedId };
77
77
  }
78
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzdG9yZS1oZWxwZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2hlbHBlcnMvcmVzdG9yZS1oZWxwZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFHdEMsT0FBTyxFQUFFLDRCQUE0QixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDbkUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDdkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRXBELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM5QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFpQnREOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxjQUFjLENBQ2xDLEdBQVksRUFDWixHQUFhLEVBQ2IsS0FBYSxFQUNiLE1BQWMsRUFDZCxPQUE2QixFQUM3QixLQUFhLEVBQ2IsaUJBQTZCO0lBRTdCLE1BQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3RDLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUNSLGNBQWMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxLQUFLLGtCQUFrQixFQUFFLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ2xGLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELGdGQUFnRjtJQUNoRixNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxlQUFlLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNkLGtCQUFrQixDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMvQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCwrRUFBK0U7SUFDL0UsMkVBQTJFO0lBQzNFLDZFQUE2RTtJQUM3RSxJQUFJLENBQUMsNEJBQTRCLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsTUFBTSxJQUFJLEVBQUUsRUFBRSxpQkFBaUIsQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXBHLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsS0FBSyxFQUFFLE1BQU0sSUFBSSxRQUFRLENBQUMsQ0FBQztJQUN0RSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDZCxrQkFBa0IsQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDL0IsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsT0FBTyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBUUQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVksQ0FDaEMsR0FBWSxFQUNaLEdBQWEsRUFDYixLQUFhLEVBQ2IsT0FBNEIsRUFDNUIsS0FBYSxFQUNiLGlCQUE2QixFQUM3QixNQUFjO0lBRWQsTUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdEMsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ1IsY0FBYyxDQUFDLEdBQUcsRUFBRSxHQUFHLEtBQUssa0JBQWtCLEVBQUUsU0FBUyxDQUFDLHNCQUFzQixDQUFDLENBQUM7UUFDbEYsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsZ0ZBQWdGO0lBQ2hGLHdFQUF3RTtJQUN4RSxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxlQUFlLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNkLGtCQUFrQixDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMvQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCxnRUFBZ0U7SUFDaEUsSUFBSSxDQUFDLDRCQUE0QixDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLE1BQU0sSUFBSSxFQUFFLEVBQUUsaUJBQWlCLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUVwRyw0RUFBNEU7SUFDNUUsNEVBQTRFO0lBQzVFLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDcEQsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2Qsa0JBQWtCLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQy9CLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELE9BQU8sRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLENBQUM7QUFDaEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyByZXF1aXJlVmlzaWJpbGl0eVdyaXRlQWNjZXNzIH0gZnJvbSAnLi9hY2Nlc3MtaGVscGVycy5qcyc7XG5pbXBvcnQgeyBzZW5kRW50aXR5Tm90Rm91bmQgfSBmcm9tICcuL2NydWQtaGVscGVycy5qcyc7XG5pbXBvcnQgeyBFcnJvckNvZGUgfSBmcm9tICcuLi90eXBlcy9lcnJvci1jb2Rlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IFBlcm1pc3Npb24gfSBmcm9tICcuLi90eXBlcy9wZXJtaXNzaW9ucy5qcyc7XG5pbXBvcnQgeyBnZXRQYXJhbSB9IGZyb20gJy4uL3V0aWxzL3BhcmFtcy5qcyc7XG5pbXBvcnQgeyBzZW5kQmFkUmVxdWVzdCB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcblxuLyoqIE1pbmltYWwgc2hhcGUgYSByZXN0b3JlIHJvdXRlIG5lZWRzIGZyb20gdGhlIGVudGl0eSBpdCByZXN0b3Jlcy4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzdG9yYWJsZUVudGl0eSB7XG4gIG9yZ0lkOiBzdHJpbmc7XG4gIC8qKiBSdW5nIHRoZSB0b21ic3RvbmUgc2F0IGF0IOKAlCBkcml2ZXMgdGhlIHJlc3RvcmUvcHVyZ2UgYXV0aG9yaXR5IGNoZWNrLiAqL1xuICB2aXNpYmlsaXR5Pzogc3RyaW5nO1xuICAvKiogQXV0aG9yIOKAlCB0aGUgb25seSBub24tYWRtaW4gd2hvIG1heSByZXN0b3JlL3B1cmdlIGEgYHByaXZhdGVgIHJvdy4gKi9cbiAgY3JlYXRlZEJ5Pzogc3RyaW5nO1xufVxuXG4vKiogVGhlIHNsaWNlIG9mIGEgc2VydmljZSBhIHJlc3RvcmUgcm91dGUgdXNlcyAoc2F0aXNmaWVkIGJ5IGFueSBDcnVkU2VydmljZSkuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc3RvcmFibGVTZXJ2aWNlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PiB7XG4gIGZpbmREZWxldGVkQnlJZChpZDogc3RyaW5nLCBvcmdJZD86IHN0cmluZyk6IFByb21pc2U8VCB8IG51bGw+O1xuICByZXN0b3JlKGlkOiBzdHJpbmcsIG9yZ0lkOiBzdHJpbmcsIHVzZXJJZDogc3RyaW5nKTogUHJvbWlzZTxUIHwgbnVsbD47XG59XG5cbi8qKlxuICogU2hhcmVkIHNrZWxldG9uIGZvciBhIHN0ZXAtdXAtZ2F0ZWQgZW50aXR5IHJlc3RvcmUgcm91dGU6IGV4dHJhY3QgYDppZGAsIGxvYWRcbiAqIHRoZSBvd24tb3JnIHRvbWJzdG9uZSwgZ2F0ZSBpdCBvbiB0aGUgdmlzaWJpbGl0eSBsYWRkZXIgKHBhcml0eSB3aXRoIGRlbGV0ZSksXG4gKiByZXN0b3JlLCBhbmQgNDA0IG9uIG1pc3MuIFJldHVybnMgYHsgZXhpc3RpbmcsIHJlc3RvcmVkIH1gIG9uIHN1Y2Nlc3MsXG4gKiBvciBgbnVsbGAgd2hlbiBpdCBoYXMgQUxSRUFEWSBzZW50IGEgcmVzcG9uc2UgKDQwMC80MDQvNDAzKSDigJQgdGhlIGNhbGxlciB0aGVuXG4gKiBkb2VzIHRoZSBlbnRpdHktc3BlY2lmaWMgYXVkaXQgKyByZXNwb25zZS4gRFJZcyB0aGUgbmVhci1pZGVudGljYWxcbiAqIHBpcGVsaW5lIC8gcGx1Z2luIC8gcGlwZWxpbmVfdGVtcGxhdGUgcmVzdG9yZSByb3V0ZXMgKHdoaWNoIGRpZmZlciBvbmx5IGluXG4gKiBzZXJ2aWNlLCBhdWRpdCBhY3Rpb24sIGFuZCByZXNwb25zZSBzaGFwaW5nKS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRBbmRSZXN0b3JlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PihcbiAgcmVxOiBSZXF1ZXN0LFxuICByZXM6IFJlc3BvbnNlLFxuICBvcmdJZDogc3RyaW5nLFxuICB1c2VySWQ6IHN0cmluZyxcbiAgc2VydmljZTogUmVzdG9yYWJsZVNlcnZpY2U8VD4sXG4gIGxhYmVsOiBzdHJpbmcsXG4gIHB1Ymxpc2hQZXJtaXNzaW9uOiBQZXJtaXNzaW9uLFxuKTogUHJvbWlzZTx7IGV4aXN0aW5nOiBUOyByZXN0b3JlZDogVCB9IHwgbnVsbD4ge1xuICBjb25zdCBpZCA9IGdldFBhcmFtKHJlcS5wYXJhbXMsICdpZCcpO1xuICBpZiAoIWlkKSB7XG4gICAgc2VuZEJhZFJlcXVlc3QocmVzLCBgJHtsYWJlbH0gSUQgaXMgcmVxdWlyZWQuYCwgRXJyb3JDb2RlLk1JU1NJTkdfUkVRVUlSRURfRklFTEQpO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gTG9hZCB0aGUgVE9NQlNUT05FIChvd24tb3JnKS4gQSBsaXZlIHJvdyAvIHVua25vd24gaWQgLyBhbHJlYWR5LXB1cmdlZCDihpIgNDA0LlxuICBjb25zdCBleGlzdGluZyA9IGF3YWl0IHNlcnZpY2UuZmluZERlbGV0ZWRCeUlkKGlkLCBvcmdJZCk7XG4gIGlmICghZXhpc3RpbmcpIHtcbiAgICBzZW5kRW50aXR5Tm90Rm91bmQocmVzLCBsYWJlbCk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvLyBTYW1lIGxhZGRlciBhdXRob3JpdHkgYXMgZGVsZXRlOiBgOnB1Ymxpc2hgIGZvciBhIHB1YmxpYyByb3csIGF1dGhvcnNoaXAgZm9yXG4gIC8vIGEgcHJpdmF0ZSBvbmUuIExvYWQtYmVhcmluZyBoZXJlIOKAlCB0aGUgdG9tYnN0b25lIGxvYWQgaXMgb3JnLXNjb3BlZCwgbm90XG4gIC8vIHZpc2liaWxpdHktc2NvcGVkLCBzbyB0aGlzIGlzIHdoYXQgc3RvcHMgYSBjb2xsZWFndWUgcmVzdXJyZWN0aW5nIGEgZHJhZnQuXG4gIGlmICghcmVxdWlyZVZpc2liaWxpdHlXcml0ZUFjY2VzcyhyZXEsIHJlcywgZXhpc3RpbmcsIHVzZXJJZCB8fCAnJywgcHVibGlzaFBlcm1pc3Npb24pKSByZXR1cm4gbnVsbDtcblxuICBjb25zdCByZXN0b3JlZCA9IGF3YWl0IHNlcnZpY2UucmVzdG9yZShpZCwgb3JnSWQsIHVzZXJJZCB8fCAnc3lzdGVtJyk7XG4gIGlmICghcmVzdG9yZWQpIHtcbiAgICBzZW5kRW50aXR5Tm90Rm91bmQocmVzLCBsYWJlbCk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICByZXR1cm4geyBleGlzdGluZywgcmVzdG9yZWQgfTtcbn1cblxuLyoqIFRoZSBzbGljZSBvZiBhIHNlcnZpY2UgYSBwdXJnZSByb3V0ZSB1c2VzIChzYXRpc2ZpZWQgYnkgYW55IENydWRTZXJ2aWNlKS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUHVyZ2VhYmxlU2VydmljZTxUIGV4dGVuZHMgUmVzdG9yYWJsZUVudGl0eT4ge1xuICBmaW5kRGVsZXRlZEJ5SWQoaWQ6IHN0cmluZywgb3JnSWQ/OiBzdHJpbmcpOiBQcm9taXNlPFQgfCBudWxsPjtcbiAgcHVyZ2VCeUlkKGlkOiBzdHJpbmcsIG9yZ0lkPzogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPjtcbn1cblxuLyoqXG4gKiBTaGFyZWQgc2tlbGV0b24gZm9yIGFuIGVudGl0eSBQVVJHRSByb3V0ZTogZXh0cmFjdCBgOmlkYCwgbG9hZCB0aGUgb3duLW9yZ1xuICogdG9tYnN0b25lLCBnYXRlIGl0IG9uIHRoZSB2aXNpYmlsaXR5IGxhZGRlciAocGFyaXR5IHdpdGhcbiAqIGRlbGV0ZS9yZXN0b3JlKSwgdGhlbiBwZXJtYW5lbnRseSBoYXJkLWRlbGV0ZSBpdCDigJQgYnV0IE9OTFkgd2hlbiBpdCBpcyBhXG4gKiBnZW51aW5lIHRvbWJzdG9uZSAoYSBsaXZlL2FjdGl2ZSByb3cgbG9hZHMgYXMgYG51bGxgIGhlcmUgYW5kIDQwNHMsIHNvIHB1cmdlXG4gKiBjYW4gbmV2ZXIgaGFyZC1kZWxldGUgYSBsaXZlIHJvdykuIFJldHVybnMgYHsgZXhpc3RpbmcsIHB1cmdlZElkIH1gIG9uIHN1Y2Nlc3NcbiAqICh0aGUgY2FsbGVyIGVtaXRzIHRoZSBlbnRpdHktc3BlY2lmaWMgYXVkaXQgb2ZmIGBleGlzdGluZ2ApLCBvciBgbnVsbGAgd2hlbiBpdFxuICogaGFzIEFMUkVBRFkgc2VudCBhIHJlc3BvbnNlICg0MDAvNDA0LzQwMykuIFB1cmdlIGlzIGludGVudGlvbmFsbHkgTk9UIHN0ZXAtdXBcbiAqIGdhdGVkOiBpdCBvbmx5IGZpbmFsaXplcyB3aGF0IHRoZSByZXRlbnRpb24gc3dlZXAgd291bGQgcmVtb3ZlIGFueXdheSwgYW5kIHRoZVxuICogZnJvbnRlbmQgZ2F0ZXMgaXQgd2l0aCBhbiBleHBsaWNpdCBjb25maXJtIGRpYWxvZy4gRFJZcyB0aGUgbmVhci1pZGVudGljYWxcbiAqIHBpcGVsaW5lIC8gcGlwZWxpbmVfdGVtcGxhdGUgcHVyZ2Ugcm91dGVzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZEFuZFB1cmdlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PihcbiAgcmVxOiBSZXF1ZXN0LFxuICByZXM6IFJlc3BvbnNlLFxuICBvcmdJZDogc3RyaW5nLFxuICBzZXJ2aWNlOiBQdXJnZWFibGVTZXJ2aWNlPFQ+LFxuICBsYWJlbDogc3RyaW5nLFxuICBwdWJsaXNoUGVybWlzc2lvbjogUGVybWlzc2lvbixcbiAgdXNlcklkOiBzdHJpbmcsXG4pOiBQcm9taXNlPHsgZXhpc3Rpbmc6IFQ7IHB1cmdlZElkOiBzdHJpbmcgfSB8IG51bGw+IHtcbiAgY29uc3QgaWQgPSBnZXRQYXJhbShyZXEucGFyYW1zLCAnaWQnKTtcbiAgaWYgKCFpZCkge1xuICAgIHNlbmRCYWRSZXF1ZXN0KHJlcywgYCR7bGFiZWx9IElEIGlzIHJlcXVpcmVkLmAsIEVycm9yQ29kZS5NSVNTSU5HX1JFUVVJUkVEX0ZJRUxEKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8vIExvYWQgdGhlIFRPTUJTVE9ORSAob3duLW9yZykuIEEgbGl2ZSByb3cgLyB1bmtub3duIGlkIC8gYWxyZWFkeS1wdXJnZWQg4oaSIDQwNCxcbiAgLy8gc28gYSBjdXJyZW50bHktYWN0aXZlIGVudGl0eSBpcyBuZXZlciBoYXJkLWRlbGV0ZWQgdGhyb3VnaCB0aGlzIHBhdGguXG4gIGNvbnN0IGV4aXN0aW5nID0gYXdhaXQgc2VydmljZS5maW5kRGVsZXRlZEJ5SWQoaWQsIG9yZ0lkKTtcbiAgaWYgKCFleGlzdGluZykge1xuICAgIHNlbmRFbnRpdHlOb3RGb3VuZChyZXMsIGxhYmVsKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8vIFNhbWUgbGFkZGVyIGF1dGhvcml0eSBhcyBkZWxldGUvcmVzdG9yZSAoc2VlIGxvYWRBbmRSZXN0b3JlKS5cbiAgaWYgKCFyZXF1aXJlVmlzaWJpbGl0eVdyaXRlQWNjZXNzKHJlcSwgcmVzLCBleGlzdGluZywgdXNlcklkIHx8ICcnLCBwdWJsaXNoUGVybWlzc2lvbikpIHJldHVybiBudWxsO1xuXG4gIC8vIFJldXNlIHRoZSByZXRlbnRpb24gc3dlZXAncyBzaW5nbGUtaWQgaGFyZC1kZWxldGUgKHNhbWUgdG9tYnN0b25lIG1hdGNoICtcbiAgLy8gZGVwZW5kZW50LXRlYXJkb3duIGhvb2tzKS4gUmV0dXJucyBudWxsIG9uIGEgcmFjZSAoYWxyZWFkeSBwdXJnZWQpIOKGkiA0MDQuXG4gIGNvbnN0IHB1cmdlZElkID0gYXdhaXQgc2VydmljZS5wdXJnZUJ5SWQoaWQsIG9yZ0lkKTtcbiAgaWYgKCFwdXJnZWRJZCkge1xuICAgIHNlbmRFbnRpdHlOb3RGb3VuZChyZXMsIGxhYmVsKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIHJldHVybiB7IGV4aXN0aW5nLCBwdXJnZWRJZCB9O1xufVxuIl19
78
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzdG9yZS1oZWxwZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2hlbHBlcnMvcmVzdG9yZS1oZWxwZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFHdEMsT0FBTyxFQUFFLDRCQUE0QixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDbkUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDdkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRXBELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM5QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFpQnREOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxjQUFjLENBQ2xDLEdBQVksRUFDWixHQUFhLEVBQ2IsS0FBYSxFQUNiLE1BQWMsRUFDZCxPQUE2QixFQUM3QixLQUFhLEVBQ2IsaUJBQTZCO0lBRTdCLE1BQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3RDLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUNSLGNBQWMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxLQUFLLGtCQUFrQixFQUFFLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ2xGLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELGdGQUFnRjtJQUNoRixNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxlQUFlLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNkLGtCQUFrQixDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMvQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCwrRUFBK0U7SUFDL0UsMkVBQTJFO0lBQzNFLDZFQUE2RTtJQUM3RSxJQUFJLENBQUMsNEJBQTRCLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsTUFBTSxJQUFJLEVBQUUsRUFBRSxpQkFBaUIsQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXBHLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsS0FBSyxFQUFFLE1BQU0sSUFBSSxRQUFRLENBQUMsQ0FBQztJQUN0RSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDZCxrQkFBa0IsQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDL0IsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsT0FBTyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBUUQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVksQ0FDaEMsR0FBWSxFQUNaLEdBQWEsRUFDYixLQUFhLEVBQ2IsT0FBNEIsRUFDNUIsS0FBYSxFQUNiLGlCQUE2QixFQUM3QixNQUFjO0lBRWQsTUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdEMsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ1IsY0FBYyxDQUFDLEdBQUcsRUFBRSxHQUFHLEtBQUssa0JBQWtCLEVBQUUsU0FBUyxDQUFDLHNCQUFzQixDQUFDLENBQUM7UUFDbEYsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsZ0ZBQWdGO0lBQ2hGLHdFQUF3RTtJQUN4RSxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxlQUFlLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNkLGtCQUFrQixDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMvQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCxnRUFBZ0U7SUFDaEUsSUFBSSxDQUFDLDRCQUE0QixDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLE1BQU0sSUFBSSxFQUFFLEVBQUUsaUJBQWlCLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUVwRyw0RUFBNEU7SUFDNUUsNEVBQTRFO0lBQzVFLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDcEQsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2Qsa0JBQWtCLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQy9CLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELE9BQU8sRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLENBQUM7QUFDaEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyByZXF1aXJlVmlzaWJpbGl0eVdyaXRlQWNjZXNzIH0gZnJvbSAnLi9hY2Nlc3MtaGVscGVycy5qcyc7XG5pbXBvcnQgeyBzZW5kRW50aXR5Tm90Rm91bmQgfSBmcm9tICcuL2NydWQtaGVscGVycy5qcyc7XG5pbXBvcnQgeyBFcnJvckNvZGUgfSBmcm9tICcuLi90eXBlcy9lcnJvci1jb2Rlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IFBlcm1pc3Npb24gfSBmcm9tICcuLi90eXBlcy9wZXJtaXNzaW9ucy5qcyc7XG5pbXBvcnQgeyBnZXRQYXJhbSB9IGZyb20gJy4uL3V0aWxzL3BhcmFtcy5qcyc7XG5pbXBvcnQgeyBzZW5kQmFkUmVxdWVzdCB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcblxuLyoqIE1pbmltYWwgc2hhcGUgYSByZXN0b3JlIHJvdXRlIG5lZWRzIGZyb20gdGhlIGVudGl0eSBpdCByZXN0b3Jlcy4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzdG9yYWJsZUVudGl0eSB7XG4gIG9yZ0lkOiBzdHJpbmc7XG4gIC8qKiBSdW5nIHRoZSB0b21ic3RvbmUgc2F0IGF0IOKAlCBkcml2ZXMgdGhlIHJlc3RvcmUvcHVyZ2UgYXV0aG9yaXR5IGNoZWNrLiAqL1xuICB2aXNpYmlsaXR5Pzogc3RyaW5nO1xuICAvKiogQXV0aG9yIOKAlCB0aGUgb25seSBub24tYWRtaW4gd2hvIG1heSByZXN0b3JlL3B1cmdlIGEgYHByaXZhdGVgIHJvdy4gKi9cbiAgY3JlYXRlZEJ5Pzogc3RyaW5nO1xufVxuXG4vKiogVGhlIHNsaWNlIG9mIGEgc2VydmljZSBhIHJlc3RvcmUgcm91dGUgdXNlcyAoc2F0aXNmaWVkIGJ5IGFueSBDcnVkU2VydmljZSkuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc3RvcmFibGVTZXJ2aWNlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PiB7XG4gIGZpbmREZWxldGVkQnlJZChpZDogc3RyaW5nLCBvcmdJZD86IHN0cmluZyk6IFByb21pc2U8VCB8IG51bGw+O1xuICByZXN0b3JlKGlkOiBzdHJpbmcsIG9yZ0lkOiBzdHJpbmcsIHVzZXJJZDogc3RyaW5nKTogUHJvbWlzZTxUIHwgbnVsbD47XG59XG5cbi8qKlxuICogU2hhcmVkIHNrZWxldG9uIGZvciBhIHN0ZXAtdXAtZ2F0ZWQgZW50aXR5IHJlc3RvcmUgcm91dGU6IGV4dHJhY3QgYDppZGAsIGxvYWRcbiAqIHRoZSBvd24tb3JnIHRvbWJzdG9uZSwgZ2F0ZSBpdCBvbiB0aGUgdmlzaWJpbGl0eSBsYWRkZXIgKHBhcml0eSB3aXRoIGRlbGV0ZSksXG4gKiByZXN0b3JlLCBhbmQgNDA0IG9uIG1pc3MuIFJldHVybnMgYHsgZXhpc3RpbmcsIHJlc3RvcmVkIH1gIG9uIHN1Y2Nlc3MsXG4gKiBvciBgbnVsbGAgd2hlbiBpdCBoYXMgQUxSRUFEWSBzZW50IGEgcmVzcG9uc2UgKDQwMC80MDQvNDAzKSDigJQgdGhlIGNhbGxlciB0aGVuXG4gKiBkb2VzIHRoZSBlbnRpdHktc3BlY2lmaWMgYXVkaXQgKyByZXNwb25zZS4gRFJZcyB0aGUgbmVhci1pZGVudGljYWxcbiAqIHBpcGVsaW5lIC8gcGx1Z2luIC8gcGlwZWxpbmVfdGVtcGxhdGUgcmVzdG9yZSByb3V0ZXMgKHdoaWNoIGRpZmZlciBvbmx5IGluXG4gKiBzZXJ2aWNlLCBhdWRpdCBhY3Rpb24sIGFuZCByZXNwb25zZSBzaGFwaW5nKS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRBbmRSZXN0b3JlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PihcbiAgcmVxOiBSZXF1ZXN0LFxuICByZXM6IFJlc3BvbnNlLFxuICBvcmdJZDogc3RyaW5nLFxuICB1c2VySWQ6IHN0cmluZyxcbiAgc2VydmljZTogUmVzdG9yYWJsZVNlcnZpY2U8VD4sXG4gIGxhYmVsOiBzdHJpbmcsXG4gIHB1Ymxpc2hQZXJtaXNzaW9uOiBQZXJtaXNzaW9uLFxuKTogUHJvbWlzZTx7IGV4aXN0aW5nOiBUOyByZXN0b3JlZDogVCB9IHwgbnVsbD4ge1xuICBjb25zdCBpZCA9IGdldFBhcmFtKHJlcS5wYXJhbXMsICdpZCcpO1xuICBpZiAoIWlkKSB7XG4gICAgc2VuZEJhZFJlcXVlc3QocmVzLCBgJHtsYWJlbH0gSUQgaXMgcmVxdWlyZWQuYCwgRXJyb3JDb2RlLk1JU1NJTkdfUkVRVUlSRURfRklFTEQpO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gTG9hZCB0aGUgVE9NQlNUT05FIChvd24tb3JnKS4gQSBsaXZlIHJvdyAvIHVua25vd24gaWQgLyBhbHJlYWR5LXB1cmdlZCDihpIgNDA0LlxuICBjb25zdCBleGlzdGluZyA9IGF3YWl0IHNlcnZpY2UuZmluZERlbGV0ZWRCeUlkKGlkLCBvcmdJZCk7XG4gIGlmICghZXhpc3RpbmcpIHtcbiAgICBzZW5kRW50aXR5Tm90Rm91bmQocmVzLCBsYWJlbCk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvLyBTYW1lIGxhZGRlciBhdXRob3JpdHkgYXMgZGVsZXRlOiBgOnB1Ymxpc2hgIGZvciBhIHB1YmxpYyByb3csIGF1dGhvcnNoaXAgZm9yXG4gIC8vIGEgcHJpdmF0ZSBvbmUuIExvYWQtYmVhcmluZyBoZXJlIOKAlCB0aGUgdG9tYnN0b25lIGxvYWQgaXMgb3JnLXNjb3BlZCwgbm90XG4gIC8vIHZpc2liaWxpdHktc2NvcGVkLCBzbyB0aGlzIGlzIHdoYXQgc3RvcHMgYSBjb2xsZWFndWUgcmVzdXJyZWN0aW5nIGEgZHJhZnQuXG4gIGlmICghcmVxdWlyZVZpc2liaWxpdHlXcml0ZUFjY2VzcyhyZXEsIHJlcywgZXhpc3RpbmcsIHVzZXJJZCB8fCAnJywgcHVibGlzaFBlcm1pc3Npb24pKSByZXR1cm4gbnVsbDtcblxuICBjb25zdCByZXN0b3JlZCA9IGF3YWl0IHNlcnZpY2UucmVzdG9yZShpZCwgb3JnSWQsIHVzZXJJZCB8fCAnc3lzdGVtJyk7XG4gIGlmICghcmVzdG9yZWQpIHtcbiAgICBzZW5kRW50aXR5Tm90Rm91bmQocmVzLCBsYWJlbCk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICByZXR1cm4geyBleGlzdGluZywgcmVzdG9yZWQgfTtcbn1cblxuLyoqIFRoZSBzbGljZSBvZiBhIHNlcnZpY2UgYSBwdXJnZSByb3V0ZSB1c2VzIChzYXRpc2ZpZWQgYnkgYW55IENydWRTZXJ2aWNlKS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUHVyZ2VhYmxlU2VydmljZTxUIGV4dGVuZHMgUmVzdG9yYWJsZUVudGl0eT4ge1xuICBmaW5kRGVsZXRlZEJ5SWQoaWQ6IHN0cmluZywgb3JnSWQ/OiBzdHJpbmcpOiBQcm9taXNlPFQgfCBudWxsPjtcbiAgcHVyZ2VCeUlkKGlkOiBzdHJpbmcsIG9yZ0lkPzogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPjtcbn1cblxuLyoqXG4gKiBTaGFyZWQgc2tlbGV0b24gZm9yIGFuIGVudGl0eSBQVVJHRSByb3V0ZTogZXh0cmFjdCBgOmlkYCwgbG9hZCB0aGUgb3duLW9yZ1xuICogdG9tYnN0b25lLCBnYXRlIGl0IG9uIHRoZSB2aXNpYmlsaXR5IGxhZGRlciAocGFyaXR5IHdpdGhcbiAqIGRlbGV0ZS9yZXN0b3JlKSwgdGhlbiBwZXJtYW5lbnRseSBoYXJkLWRlbGV0ZSBpdCDigJQgYnV0IE9OTFkgd2hlbiBpdCBpcyBhXG4gKiBnZW51aW5lIHRvbWJzdG9uZSAoYSBsaXZlL2FjdGl2ZSByb3cgbG9hZHMgYXMgYG51bGxgIGhlcmUgYW5kIDQwNHMsIHNvIHB1cmdlXG4gKiBjYW4gbmV2ZXIgaGFyZC1kZWxldGUgYSBsaXZlIHJvdykuIFJldHVybnMgYHsgZXhpc3RpbmcsIHB1cmdlZElkIH1gIG9uIHN1Y2Nlc3NcbiAqICh0aGUgY2FsbGVyIGVtaXRzIHRoZSBlbnRpdHktc3BlY2lmaWMgYXVkaXQgb2ZmIGBleGlzdGluZ2ApLCBvciBgbnVsbGAgd2hlbiBpdFxuICogaGFzIEFMUkVBRFkgc2VudCBhIHJlc3BvbnNlICg0MDAvNDA0LzQwMykuIFN0ZXAtdXAgaXMgdGhlIFJPVVRFJ3Mgam9iOiB0aGVcbiAqIHBpcGVsaW5lLCB0ZW1wbGF0ZSBhbmQgcGx1Z2luIHNlcnZpY2VzIG1vdW50IHB1cmdlIGJlaGluZCBgcmVxdWlyZVN0ZXBVcGBcbiAqIChzaGFyaW5nIHJlc3RvcmUncyBjaGFpbiBzbyBvbmUgdG9rZW4gaXNuJ3QgY29uc3VtZWQgdHdpY2UpLiBEUllzIHRoZSBuZWFyLWlkZW50aWNhbFxuICogcGlwZWxpbmUgLyBwaXBlbGluZV90ZW1wbGF0ZSBwdXJnZSByb3V0ZXMuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkQW5kUHVyZ2U8VCBleHRlbmRzIFJlc3RvcmFibGVFbnRpdHk+KFxuICByZXE6IFJlcXVlc3QsXG4gIHJlczogUmVzcG9uc2UsXG4gIG9yZ0lkOiBzdHJpbmcsXG4gIHNlcnZpY2U6IFB1cmdlYWJsZVNlcnZpY2U8VD4sXG4gIGxhYmVsOiBzdHJpbmcsXG4gIHB1Ymxpc2hQZXJtaXNzaW9uOiBQZXJtaXNzaW9uLFxuICB1c2VySWQ6IHN0cmluZyxcbik6IFByb21pc2U8eyBleGlzdGluZzogVDsgcHVyZ2VkSWQ6IHN0cmluZyB9IHwgbnVsbD4ge1xuICBjb25zdCBpZCA9IGdldFBhcmFtKHJlcS5wYXJhbXMsICdpZCcpO1xuICBpZiAoIWlkKSB7XG4gICAgc2VuZEJhZFJlcXVlc3QocmVzLCBgJHtsYWJlbH0gSUQgaXMgcmVxdWlyZWQuYCwgRXJyb3JDb2RlLk1JU1NJTkdfUkVRVUlSRURfRklFTEQpO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gTG9hZCB0aGUgVE9NQlNUT05FIChvd24tb3JnKS4gQSBsaXZlIHJvdyAvIHVua25vd24gaWQgLyBhbHJlYWR5LXB1cmdlZCDihpIgNDA0LFxuICAvLyBzbyBhIGN1cnJlbnRseS1hY3RpdmUgZW50aXR5IGlzIG5ldmVyIGhhcmQtZGVsZXRlZCB0aHJvdWdoIHRoaXMgcGF0aC5cbiAgY29uc3QgZXhpc3RpbmcgPSBhd2FpdCBzZXJ2aWNlLmZpbmREZWxldGVkQnlJZChpZCwgb3JnSWQpO1xuICBpZiAoIWV4aXN0aW5nKSB7XG4gICAgc2VuZEVudGl0eU5vdEZvdW5kKHJlcywgbGFiZWwpO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gU2FtZSBsYWRkZXIgYXV0aG9yaXR5IGFzIGRlbGV0ZS9yZXN0b3JlIChzZWUgbG9hZEFuZFJlc3RvcmUpLlxuICBpZiAoIXJlcXVpcmVWaXNpYmlsaXR5V3JpdGVBY2Nlc3MocmVxLCByZXMsIGV4aXN0aW5nLCB1c2VySWQgfHwgJycsIHB1Ymxpc2hQZXJtaXNzaW9uKSkgcmV0dXJuIG51bGw7XG5cbiAgLy8gUmV1c2UgdGhlIHJldGVudGlvbiBzd2VlcCdzIHNpbmdsZS1pZCBoYXJkLWRlbGV0ZSAoc2FtZSB0b21ic3RvbmUgbWF0Y2ggK1xuICAvLyBkZXBlbmRlbnQtdGVhcmRvd24gaG9va3MpLiBSZXR1cm5zIG51bGwgb24gYSByYWNlIChhbHJlYWR5IHB1cmdlZCkg4oaSIDQwNC5cbiAgY29uc3QgcHVyZ2VkSWQgPSBhd2FpdCBzZXJ2aWNlLnB1cmdlQnlJZChpZCwgb3JnSWQpO1xuICBpZiAoIXB1cmdlZElkKSB7XG4gICAgc2VuZEVudGl0eU5vdEZvdW5kKHJlcywgbGFiZWwpO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgcmV0dXJuIHsgZXhpc3RpbmcsIHB1cmdlZElkIH07XG59XG4iXX0=
@@ -9,8 +9,15 @@ export declare function writeSseHeaders(res: Response): void;
9
9
  /**
10
10
  * Set SSE response headers and flush.
11
11
  * Returns an `aborted()` function to check if the client disconnected.
12
+ *
13
+ * Disconnect is detected on the RESPONSE: `res` emits 'close' when the
14
+ * underlying connection goes away, and `writableFinished` tells an early close
15
+ * (client gone) from the normal close after `res.end()`. The request's own
16
+ * 'close' is not a disconnect signal — on current Node it fires once the request
17
+ * BODY has been consumed, which for a POST stream happens before this listener
18
+ * is attached, so a disconnect was never observed.
12
19
  */
13
- export declare function initSSEStream(req: Request, res: Response, timeoutMs: number): {
20
+ export declare function initSSEStream(_req: Request, res: Response, timeoutMs: number): {
14
21
  aborted: () => boolean;
15
22
  };
16
23
  /**
@@ -16,13 +16,21 @@ export function writeSseHeaders(res) {
16
16
  /**
17
17
  * Set SSE response headers and flush.
18
18
  * Returns an `aborted()` function to check if the client disconnected.
19
+ *
20
+ * Disconnect is detected on the RESPONSE: `res` emits 'close' when the
21
+ * underlying connection goes away, and `writableFinished` tells an early close
22
+ * (client gone) from the normal close after `res.end()`. The request's own
23
+ * 'close' is not a disconnect signal — on current Node it fires once the request
24
+ * BODY has been consumed, which for a POST stream happens before this listener
25
+ * is attached, so a disconnect was never observed.
19
26
  */
20
- export function initSSEStream(req, res, timeoutMs) {
27
+ export function initSSEStream(_req, res, timeoutMs) {
21
28
  writeSseHeaders(res);
22
29
  res.setTimeout(timeoutMs);
23
30
  res.flushHeaders();
24
31
  let _aborted = false;
25
- req.on('close', () => { _aborted = true; });
32
+ res.on('close', () => { if (!res.writableFinished)
33
+ _aborted = true; });
26
34
  return { aborted: () => _aborted };
27
35
  }
28
36
  /**
@@ -49,4 +57,4 @@ export function handleAIError(res, message, fallbackMessage) {
49
57
  res.write(`data: ${JSON.stringify({ type: 'error', message })}\n\n`);
50
58
  res.end();
51
59
  }
52
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaGVscGVycy9zc2UtaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBR3RDLE9BQU8sRUFBRSxjQUFjLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUV6RTs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQUMsR0FBYTtJQUMzQyxHQUFHLENBQUMsU0FBUyxDQUFDLGNBQWMsRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO0lBQ25ELEdBQUcsQ0FBQyxTQUFTLENBQUMsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQzNDLEdBQUcsQ0FBQyxTQUFTLENBQUMsWUFBWSxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBQzFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDM0MsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxhQUFhLENBQUMsR0FBWSxFQUFFLEdBQWEsRUFBRSxTQUFpQjtJQUMxRSxlQUFlLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDckIsR0FBRyxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMxQixHQUFHLENBQUMsWUFBWSxFQUFFLENBQUM7SUFDbkIsSUFBSSxRQUFRLEdBQUcsS0FBSyxDQUFDO0lBQ3JCLEdBQUcsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxHQUFHLFFBQVEsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM1QyxPQUFPLEVBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDO0FBQ3JDLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsYUFBYSxDQUFDLEdBQWEsRUFBRSxPQUFlLEVBQUUsZUFBdUI7SUFDbkYsSUFBSSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNyQixJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7WUFDdEUscUVBQXFFO1lBQ3JFLHdFQUF3RTtZQUN4RSx3RUFBd0U7WUFDeEUsaUVBQWlFO1lBQ2pFLDBFQUEwRTtZQUMxRSx5RUFBeUU7WUFDekUseUVBQXlFO1lBQ3pFLE9BQU8saUJBQWlCLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3pDLENBQUM7UUFDRCxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsNEJBQTRCLENBQUMsRUFBRSxDQUFDO1lBQ25ELE9BQU8sY0FBYyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUN0QyxDQUFDO1FBQ0QsT0FBTyxpQkFBaUIsQ0FBQyxHQUFHLEVBQUUsZUFBZSxFQUFFLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUNELEdBQUcsQ0FBQyxLQUFLLENBQUMsU0FBUyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNyRSxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUM7QUFDWixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB0eXBlIHsgUmVxdWVzdCwgUmVzcG9uc2UgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IHNlbmRCYWRSZXF1ZXN0LCBzZW5kSW50ZXJuYWxFcnJvciB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcblxuLyoqXG4gKiBTZXQgdGhlIGZvdXIgc3RhbmRhcmQgU1NFIHJlc3BvbnNlIGhlYWRlcnMgKGRvZXMgTk9UIGZsdXNoIOKAlCB0aGUgY2FsbGVyIGZsdXNoZXNcbiAqIGFmdGVyIGFueSBhZGRpdGlvbmFsIHBlci1lbmRwb2ludCBzZXR1cCBsaWtlIGByZXMuc2V0VGltZW91dGApLiBUaGUgc2luZ2xlXG4gKiBzb3VyY2Ugb2YgdHJ1dGggZm9yIHRoZSBgdGV4dC9ldmVudC1zdHJlYW1gICsgbm8tY2FjaGUgKyBrZWVwLWFsaXZlICtcbiAqIGBYLUFjY2VsLUJ1ZmZlcmluZzogbm9gIChkaXNhYmxlIG5naW54IGJ1ZmZlcmluZykgYmxvY2suXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3cml0ZVNzZUhlYWRlcnMocmVzOiBSZXNwb25zZSk6IHZvaWQge1xuICByZXMuc2V0SGVhZGVyKCdDb250ZW50LVR5cGUnLCAndGV4dC9ldmVudC1zdHJlYW0nKTtcbiAgcmVzLnNldEhlYWRlcignQ2FjaGUtQ29udHJvbCcsICduby1jYWNoZScpO1xuICByZXMuc2V0SGVhZGVyKCdDb25uZWN0aW9uJywgJ2tlZXAtYWxpdmUnKTtcbiAgcmVzLnNldEhlYWRlcignWC1BY2NlbC1CdWZmZXJpbmcnLCAnbm8nKTtcbn1cblxuLyoqXG4gKiBTZXQgU1NFIHJlc3BvbnNlIGhlYWRlcnMgYW5kIGZsdXNoLlxuICogUmV0dXJucyBhbiBgYWJvcnRlZCgpYCBmdW5jdGlvbiB0byBjaGVjayBpZiB0aGUgY2xpZW50IGRpc2Nvbm5lY3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGluaXRTU0VTdHJlYW0ocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlLCB0aW1lb3V0TXM6IG51bWJlcik6IHsgYWJvcnRlZDogKCkgPT4gYm9vbGVhbiB9IHtcbiAgd3JpdGVTc2VIZWFkZXJzKHJlcyk7XG4gIHJlcy5zZXRUaW1lb3V0KHRpbWVvdXRNcyk7XG4gIHJlcy5mbHVzaEhlYWRlcnMoKTtcbiAgbGV0IF9hYm9ydGVkID0gZmFsc2U7XG4gIHJlcS5vbignY2xvc2UnLCAoKSA9PiB7IF9hYm9ydGVkID0gdHJ1ZTsgfSk7XG4gIHJldHVybiB7IGFib3J0ZWQ6ICgpID0+IF9hYm9ydGVkIH07XG59XG5cbi8qKlxuICogQ2xhc3NpZnkgQUkgZ2VuZXJhdGlvbiBlcnJvcnMgYW5kIHNlbmQgdGhlIGFwcHJvcHJpYXRlIEhUVFAgcmVzcG9uc2Ugb3IgU1NFIGV2ZW50LlxuICogSWYgaGVhZGVycyBhcmUgYWxyZWFkeSBzZW50IChzdHJlYW1pbmcpLCB3cml0ZXMgYW4gU1NFIGVycm9yIGV2ZW50IGFuZCBlbmRzIHRoZSByZXNwb25zZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGhhbmRsZUFJRXJyb3IocmVzOiBSZXNwb25zZSwgbWVzc2FnZTogc3RyaW5nLCBmYWxsYmFja01lc3NhZ2U6IHN0cmluZyk6IHZvaWQge1xuICBpZiAoIXJlcy5oZWFkZXJzU2VudCkge1xuICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKCdub3QgY29uZmlndXJlZCcpIHx8IG1lc3NhZ2UuaW5jbHVkZXMoJ0FQSSBrZXknKSkge1xuICAgICAgLy8gU3VyZmFjZSB0aGUgT1JJR0lOQUwgbWVzc2FnZS4gSXQgbmFtZXMgdGhlIE1JU1NJTkcgQ09ORklHVVJBVElPTiDigJRcbiAgICAgIC8vIFwibm8gcHJvdmlkZXIgQVBJIGtleSBpcyBzZXQgYW5kIE9QRU5BSV9DT01QQVRJQkxFX0JBU0VfVVJMIGlzIHVuc2V0XCIsXG4gICAgICAvLyBvciB3aGljaCBlbnYgdmFyIGEgbmFtZWQgcHJvdmlkZXIgd2FudHMg4oCUIG5ldmVyIGEgc2VjcmV0IFZBTFVFLCBzbyBpdFxuICAgICAgLy8gaXMgc2FmZSB0byBzaG93IGFuZCBpdCBpcyB0aGUgb25seSB0aGluZyB0aGF0IG1ha2VzIHRoaXMgZXJyb3JcbiAgICAgIC8vIGFjdGlvbmFibGUuIFRoZSBnZW5lcmljIHJlcGxhY2VtZW50IGFsc28gbWlzLWRlc2NyaWJlZCB0aGUgY29tbW9uIGNhc2U6XG4gICAgICAvLyBpdCBzYWlkIFwiZm9yIHRoZSByZXF1ZXN0ZWQgcHJvdmlkZXJcIiB3aGVuIG5vIHByb3ZpZGVyIHdhcyByZXF1ZXN0ZWQgYXRcbiAgICAgIC8vIGFsbCwgcG9pbnRpbmcgb3BlcmF0b3JzIGF0IGEgcHJvdmlkZXIgYnVnIGluc3RlYWQgb2YgYXQgY29uZmlndXJhdGlvbi5cbiAgICAgIHJldHVybiBzZW5kSW50ZXJuYWxFcnJvcihyZXMsIG1lc3NhZ2UpO1xuICAgIH1cbiAgICBpZiAobWVzc2FnZS5pbmNsdWRlcygnbm90IGF2YWlsYWJsZSBmb3IgcHJvdmlkZXInKSkge1xuICAgICAgcmV0dXJuIHNlbmRCYWRSZXF1ZXN0KHJlcywgbWVzc2FnZSk7XG4gICAgfVxuICAgIHJldHVybiBzZW5kSW50ZXJuYWxFcnJvcihyZXMsIGZhbGxiYWNrTWVzc2FnZSwgeyBkZXRhaWxzOiBtZXNzYWdlIH0pO1xuICB9XG4gIHJlcy53cml0ZShgZGF0YTogJHtKU09OLnN0cmluZ2lmeSh7IHR5cGU6ICdlcnJvcicsIG1lc3NhZ2UgfSl9XFxuXFxuYCk7XG4gIHJlcy5lbmQoKTtcbn1cbiJdfQ==
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaGVscGVycy9zc2UtaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBR3RDLE9BQU8sRUFBRSxjQUFjLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUV6RTs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQUMsR0FBYTtJQUMzQyxHQUFHLENBQUMsU0FBUyxDQUFDLGNBQWMsRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO0lBQ25ELEdBQUcsQ0FBQyxTQUFTLENBQUMsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQzNDLEdBQUcsQ0FBQyxTQUFTLENBQUMsWUFBWSxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBQzFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDM0MsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxNQUFNLFVBQVUsYUFBYSxDQUFDLElBQWEsRUFBRSxHQUFhLEVBQUUsU0FBaUI7SUFDM0UsZUFBZSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3JCLEdBQUcsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDMUIsR0FBRyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQ25CLElBQUksUUFBUSxHQUFHLEtBQUssQ0FBQztJQUNyQixHQUFHLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLGdCQUFnQjtRQUFFLFFBQVEsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN2RSxPQUFPLEVBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDO0FBQ3JDLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsYUFBYSxDQUFDLEdBQWEsRUFBRSxPQUFlLEVBQUUsZUFBdUI7SUFDbkYsSUFBSSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNyQixJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7WUFDdEUscUVBQXFFO1lBQ3JFLHdFQUF3RTtZQUN4RSx3RUFBd0U7WUFDeEUsaUVBQWlFO1lBQ2pFLDBFQUEwRTtZQUMxRSx5RUFBeUU7WUFDekUseUVBQXlFO1lBQ3pFLE9BQU8saUJBQWlCLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3pDLENBQUM7UUFDRCxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsNEJBQTRCLENBQUMsRUFBRSxDQUFDO1lBQ25ELE9BQU8sY0FBYyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUN0QyxDQUFDO1FBQ0QsT0FBTyxpQkFBaUIsQ0FBQyxHQUFHLEVBQUUsZUFBZSxFQUFFLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUNELEdBQUcsQ0FBQyxLQUFLLENBQUMsU0FBUyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNyRSxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUM7QUFDWixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB0eXBlIHsgUmVxdWVzdCwgUmVzcG9uc2UgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IHNlbmRCYWRSZXF1ZXN0LCBzZW5kSW50ZXJuYWxFcnJvciB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcblxuLyoqXG4gKiBTZXQgdGhlIGZvdXIgc3RhbmRhcmQgU1NFIHJlc3BvbnNlIGhlYWRlcnMgKGRvZXMgTk9UIGZsdXNoIOKAlCB0aGUgY2FsbGVyIGZsdXNoZXNcbiAqIGFmdGVyIGFueSBhZGRpdGlvbmFsIHBlci1lbmRwb2ludCBzZXR1cCBsaWtlIGByZXMuc2V0VGltZW91dGApLiBUaGUgc2luZ2xlXG4gKiBzb3VyY2Ugb2YgdHJ1dGggZm9yIHRoZSBgdGV4dC9ldmVudC1zdHJlYW1gICsgbm8tY2FjaGUgKyBrZWVwLWFsaXZlICtcbiAqIGBYLUFjY2VsLUJ1ZmZlcmluZzogbm9gIChkaXNhYmxlIG5naW54IGJ1ZmZlcmluZykgYmxvY2suXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3cml0ZVNzZUhlYWRlcnMocmVzOiBSZXNwb25zZSk6IHZvaWQge1xuICByZXMuc2V0SGVhZGVyKCdDb250ZW50LVR5cGUnLCAndGV4dC9ldmVudC1zdHJlYW0nKTtcbiAgcmVzLnNldEhlYWRlcignQ2FjaGUtQ29udHJvbCcsICduby1jYWNoZScpO1xuICByZXMuc2V0SGVhZGVyKCdDb25uZWN0aW9uJywgJ2tlZXAtYWxpdmUnKTtcbiAgcmVzLnNldEhlYWRlcignWC1BY2NlbC1CdWZmZXJpbmcnLCAnbm8nKTtcbn1cblxuLyoqXG4gKiBTZXQgU1NFIHJlc3BvbnNlIGhlYWRlcnMgYW5kIGZsdXNoLlxuICogUmV0dXJucyBhbiBgYWJvcnRlZCgpYCBmdW5jdGlvbiB0byBjaGVjayBpZiB0aGUgY2xpZW50IGRpc2Nvbm5lY3RlZC5cbiAqXG4gKiBEaXNjb25uZWN0IGlzIGRldGVjdGVkIG9uIHRoZSBSRVNQT05TRTogYHJlc2AgZW1pdHMgJ2Nsb3NlJyB3aGVuIHRoZVxuICogdW5kZXJseWluZyBjb25uZWN0aW9uIGdvZXMgYXdheSwgYW5kIGB3cml0YWJsZUZpbmlzaGVkYCB0ZWxscyBhbiBlYXJseSBjbG9zZVxuICogKGNsaWVudCBnb25lKSBmcm9tIHRoZSBub3JtYWwgY2xvc2UgYWZ0ZXIgYHJlcy5lbmQoKWAuIFRoZSByZXF1ZXN0J3Mgb3duXG4gKiAnY2xvc2UnIGlzIG5vdCBhIGRpc2Nvbm5lY3Qgc2lnbmFsIOKAlCBvbiBjdXJyZW50IE5vZGUgaXQgZmlyZXMgb25jZSB0aGUgcmVxdWVzdFxuICogQk9EWSBoYXMgYmVlbiBjb25zdW1lZCwgd2hpY2ggZm9yIGEgUE9TVCBzdHJlYW0gaGFwcGVucyBiZWZvcmUgdGhpcyBsaXN0ZW5lclxuICogaXMgYXR0YWNoZWQsIHNvIGEgZGlzY29ubmVjdCB3YXMgbmV2ZXIgb2JzZXJ2ZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbml0U1NFU3RyZWFtKF9yZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UsIHRpbWVvdXRNczogbnVtYmVyKTogeyBhYm9ydGVkOiAoKSA9PiBib29sZWFuIH0ge1xuICB3cml0ZVNzZUhlYWRlcnMocmVzKTtcbiAgcmVzLnNldFRpbWVvdXQodGltZW91dE1zKTtcbiAgcmVzLmZsdXNoSGVhZGVycygpO1xuICBsZXQgX2Fib3J0ZWQgPSBmYWxzZTtcbiAgcmVzLm9uKCdjbG9zZScsICgpID0+IHsgaWYgKCFyZXMud3JpdGFibGVGaW5pc2hlZCkgX2Fib3J0ZWQgPSB0cnVlOyB9KTtcbiAgcmV0dXJuIHsgYWJvcnRlZDogKCkgPT4gX2Fib3J0ZWQgfTtcbn1cblxuLyoqXG4gKiBDbGFzc2lmeSBBSSBnZW5lcmF0aW9uIGVycm9ycyBhbmQgc2VuZCB0aGUgYXBwcm9wcmlhdGUgSFRUUCByZXNwb25zZSBvciBTU0UgZXZlbnQuXG4gKiBJZiBoZWFkZXJzIGFyZSBhbHJlYWR5IHNlbnQgKHN0cmVhbWluZyksIHdyaXRlcyBhbiBTU0UgZXJyb3IgZXZlbnQgYW5kIGVuZHMgdGhlIHJlc3BvbnNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaGFuZGxlQUlFcnJvcihyZXM6IFJlc3BvbnNlLCBtZXNzYWdlOiBzdHJpbmcsIGZhbGxiYWNrTWVzc2FnZTogc3RyaW5nKTogdm9pZCB7XG4gIGlmICghcmVzLmhlYWRlcnNTZW50KSB7XG4gICAgaWYgKG1lc3NhZ2UuaW5jbHVkZXMoJ25vdCBjb25maWd1cmVkJykgfHwgbWVzc2FnZS5pbmNsdWRlcygnQVBJIGtleScpKSB7XG4gICAgICAvLyBTdXJmYWNlIHRoZSBPUklHSU5BTCBtZXNzYWdlLiBJdCBuYW1lcyB0aGUgTUlTU0lORyBDT05GSUdVUkFUSU9OIOKAlFxuICAgICAgLy8gXCJubyBwcm92aWRlciBBUEkga2V5IGlzIHNldCBhbmQgT1BFTkFJX0NPTVBBVElCTEVfQkFTRV9VUkwgaXMgdW5zZXRcIixcbiAgICAgIC8vIG9yIHdoaWNoIGVudiB2YXIgYSBuYW1lZCBwcm92aWRlciB3YW50cyDigJQgbmV2ZXIgYSBzZWNyZXQgVkFMVUUsIHNvIGl0XG4gICAgICAvLyBpcyBzYWZlIHRvIHNob3cgYW5kIGl0IGlzIHRoZSBvbmx5IHRoaW5nIHRoYXQgbWFrZXMgdGhpcyBlcnJvclxuICAgICAgLy8gYWN0aW9uYWJsZS4gVGhlIGdlbmVyaWMgcmVwbGFjZW1lbnQgYWxzbyBtaXMtZGVzY3JpYmVkIHRoZSBjb21tb24gY2FzZTpcbiAgICAgIC8vIGl0IHNhaWQgXCJmb3IgdGhlIHJlcXVlc3RlZCBwcm92aWRlclwiIHdoZW4gbm8gcHJvdmlkZXIgd2FzIHJlcXVlc3RlZCBhdFxuICAgICAgLy8gYWxsLCBwb2ludGluZyBvcGVyYXRvcnMgYXQgYSBwcm92aWRlciBidWcgaW5zdGVhZCBvZiBhdCBjb25maWd1cmF0aW9uLlxuICAgICAgcmV0dXJuIHNlbmRJbnRlcm5hbEVycm9yKHJlcywgbWVzc2FnZSk7XG4gICAgfVxuICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKCdub3QgYXZhaWxhYmxlIGZvciBwcm92aWRlcicpKSB7XG4gICAgICByZXR1cm4gc2VuZEJhZFJlcXVlc3QocmVzLCBtZXNzYWdlKTtcbiAgICB9XG4gICAgcmV0dXJuIHNlbmRJbnRlcm5hbEVycm9yKHJlcywgZmFsbGJhY2tNZXNzYWdlLCB7IGRldGFpbHM6IG1lc3NhZ2UgfSk7XG4gIH1cbiAgcmVzLndyaXRlKGBkYXRhOiAke0pTT04uc3RyaW5naWZ5KHsgdHlwZTogJ2Vycm9yJywgbWVzc2FnZSB9KX1cXG5cXG5gKTtcbiAgcmVzLmVuZCgpO1xufVxuIl19
@@ -1,44 +1,41 @@
1
1
  import type { Request, Response, NextFunction } from 'express';
2
- import jwt from 'jsonwebtoken';
3
- import type { JwtPayload } from '../types/common.js';
2
+ import { type AssuranceLevel, type JwtPayload } from '../types/common.js';
4
3
  import { type Permission } from '../types/permissions.js';
5
4
  /**
6
- * Test-only: drop the cached JWT secrets so the next verify re-reads the env.
5
+ * Verify a token that speaks for a PERSON — access, step-up, and the token an
6
+ * opaque access key is exchanged for — against platform's published signing
7
+ * keys.
7
8
  *
8
- * Both secrets are cached for {@link JWT_SECRET_REFRESH_INTERVAL_MS}, so a test
9
- * that sets `JWT_SECRET`/`JWT_SECRET_PREVIOUS` mid-run would otherwise keep
10
- * verifying against the values read on first use. Don't call from production
11
- * code — rotation is picked up by the refresh interval.
12
- */
13
- export declare function _resetJwtSecretCacheForTests(): void;
14
- /**
15
- * The JWT verify options every token check in the fleet must share: pinned
16
- * algorithm (blocks alg-confusion / `alg:none`) plus issuer/audience when the
17
- * deployment configures them.
9
+ * ES256 and a `kid` are both mandatory: no `kid` means no way to pick the right
10
+ * key across a rotation, and any other algorithm is refused outright, which is
11
+ * what makes "no service accepts an HS256 user token" a property of the code
12
+ * rather than a convention. Key lookup goes through the shared JWKS cache
13
+ * (10-minute refresh, one refetch on an unknown `kid`, brief negative cache).
18
14
  *
19
- * Centralized because it was built inline at each call site and `step-up.ts`
20
- * re-implemented it WITHOUT the issuer/audience pinning — so in a deployment
21
- * that sets `JWT_ISSUER`/`JWT_AUDIENCE`, a step-up token minted by any other
22
- * system sharing `JWT_SECRET` was accepted.
15
+ * @throws {UnknownKidError} the key set is current and has no such key → 401.
16
+ * @throws {JwksUnavailableError} the key set could not be obtained → 503; an
17
+ * unverifiable token is never treated as valid.
23
18
  */
24
- export declare function buildJwtVerifyOptions(): jwt.VerifyOptions;
19
+ export declare function verifyUserJwt<T = JwtPayload>(token: string): Promise<T>;
25
20
  /**
26
- * Verify a JWT against the primary secret, falling back to the previous secret
27
- * (when `JWT_SECRET_PREVIOUS` is configured) ONLY on a signature/verification
28
- * failure. This makes token verification survive a secret rotation with zero
29
- * downtime: a token valid under EITHER secret passes.
21
+ * Verify ANY bearer token and return its claims.
22
+ *
23
+ * Since #14 there is no shared secret left: BOTH chains are ES256 with a `kid`,
24
+ * so the dispatch is by **who owns the `kid`** rather than by algorithm — and
25
+ * because a `kid` is an RFC 7638 thumbprint, ownership is a fact about the key,
26
+ * not a claim the token makes about itself:
30
27
  *
31
- * jsonwebtoken's `verify` takes a single secret, so this is try-primary-then-
32
- * previous — NOT a secret array. No existing check is weakened:
33
- * - `verifyOptions` (algorithm pinning + optional issuer/audience) is applied
34
- * identically on BOTH attempts, so alg-confusion / `alg:none` stay blocked.
35
- * - Expiry / not-before from the PRIMARY attempt are authoritative and are
36
- * re-thrown without a previous-secret retry, so a `TokenExpiredError` is
37
- * never masked into an "invalid signature".
38
- * - When no previous secret is set, behaviour is byte-for-byte the original:
39
- * the primary error propagates unchanged.
28
+ * - a `kid` published in the per-service key bundle → an INTERNAL SERVICE token
29
+ * ({@link signServiceToken}), verified with that service's key; its `sub` must
30
+ * name the same service, so one service can never speak for another.
31
+ * - anything else → a platform-signed USER token, verified against platform's
32
+ * published JWKS. A token claiming `principalType: 'service'` is refused
33
+ * here: an internal identity may not ride the user signing key.
34
+ *
35
+ * Anything that is not ES256 with a `kid` fails in both chains, which is what
36
+ * makes "no HS256 token is accepted anywhere" a property of the code.
40
37
  */
41
- export declare function verifyJwtWithRotation(token: string, verifyOptions: jwt.VerifyOptions): JwtPayload;
38
+ export declare function verifyBearerToken(token: string): Promise<JwtPayload>;
42
39
  export interface RequireAuthOptions {
43
40
  /**
44
41
  * Allow x-org-id/x-org-name headers to override the JWT's organization fields.
@@ -50,6 +47,35 @@ export interface RequireAuthOptions {
50
47
  * (e.g. a sysadmin managing a given org's billing). If unsure, leave it disabled.
51
48
  */
52
49
  allowOrgHeaderOverride?: boolean;
50
+ /**
51
+ * Demand that the whole SESSION be at least this assurance level (#8).
52
+ *
53
+ * `minAssurance: 2` means the person authenticated with a second factor — a
54
+ * passkey with user verification, a password plus an authenticator code, or
55
+ * SSO through an IdP the org marked as enforcing MFA. A weaker token gets
56
+ * **401 `MFA_REQUIRED`**, which the client answers by sending the person to
57
+ * enrolment or to a stronger sign-in; refreshing cannot help, because `aal`
58
+ * lives on the session slot and a refresh never raises it.
59
+ *
60
+ * This is the SESSION-level counterpart to {@link requireStepUp}, which stays
61
+ * a fresh, per-action confirmation. Use both on the most dangerous routes.
62
+ *
63
+ * MACHINES NEVER SATISFY IT. A service principal, an org service account and
64
+ * an exchanged access key all get **403 `HUMAN_SESSION_REQUIRED`** — there is
65
+ * no person behind them to have presented a factor, so "assurance" is not a
66
+ * question their credential can answer. That is deliberately not a silent
67
+ * pass-through for internal services: a route that demands MFA is a route a
68
+ * human is doing something dangerous on.
69
+ */
70
+ minAssurance?: AssuranceLevel;
71
+ /**
72
+ * With {@link minAssurance}, additionally bound how long ago that sign-in
73
+ * happened (seconds, compared against the token's `auth_time`). A session that
74
+ * is MFA-grade but older than this gets **401 `REAUTH_REQUIRED`**. Ignored
75
+ * without `minAssurance` — "how recent" is only meaningful once "how strong"
76
+ * is being asked.
77
+ */
78
+ maxAge?: number;
53
79
  }
54
80
  /** JWT auth middleware. Use directly or call with options. */
55
81
  export declare function requireAuth(req: Request, res: Response, next: NextFunction): void;
@@ -106,6 +132,42 @@ export declare function isAccessTokenRevoked(claims: {
106
132
  jti?: string;
107
133
  impersonatorId?: string;
108
134
  }): Promise<boolean>;
135
+ /**
136
+ * Whether a set of verified claims speaks for a PERSON's own session — the only
137
+ * kind of credential an assurance level can be asserted about.
138
+ *
139
+ * Excludes all three machine shapes: an internal service principal, an org
140
+ * service account, and any exchanged access key (`token_use: 'api_key'`, i.e. a
141
+ * PAT or a service-account key), each of which authenticates a program holding a
142
+ * secret rather than a person presenting factors.
143
+ */
144
+ export declare function isHumanPrincipal(claims: Pick<JwtPayload, 'principalType' | 'token_use'> | undefined): boolean;
145
+ /** What an assurance gate demands. */
146
+ export interface AssuranceOptions {
147
+ minAssurance: AssuranceLevel;
148
+ maxAge?: number;
149
+ }
150
+ /**
151
+ * Standalone assurance gate, for a service whose own `requireAuth` is not
152
+ * api-core's (platform's is its own, because it reads Mongo). Compose it AFTER
153
+ * authentication: `router.get('/x', requireAuth, requireAssurance({ minAssurance: 2 }), h)`.
154
+ *
155
+ * Identical rules and identical refusals to `requireAuth({ minAssurance })` —
156
+ * that option simply calls the same check inline, so there is one implementation
157
+ * of "is this session MFA-grade" in the codebase.
158
+ */
159
+ export declare function requireAssurance(options: AssuranceOptions): (req: Request, res: Response, next: NextFunction) => void;
160
+ /**
161
+ * Whether a verified token carries a well-formed identity: a known
162
+ * `principalType` and `token_use`, and the claims that kind of principal must
163
+ * have. Fails closed — a token minted before these claims existed, or one with a
164
+ * contradictory combination, is not an identity any gate should reason about.
165
+ *
166
+ * - `service`: `token_use: 'access'` and a `service:<name>` subject (the name
167
+ * feeds the denylist and audit attribution).
168
+ * - `user` / `service_account`: `amr` (known methods), `aal` and `auth_time`.
169
+ */
170
+ export declare function hasValidIdentityClaims(decoded: Partial<JwtPayload>): boolean;
109
171
  /**
110
172
  * Whether the request's user holds `permission`. Superadmins implicitly hold
111
173
  * every permission. Reads the resolved `permissions` claim (set at token issue,
@@ -134,6 +196,16 @@ export interface AuthzDenialInfo {
134
196
  * auditor. Idempotent; the last registration wins.
135
197
  */
136
198
  export declare function setAuthzDenialAuditor(fn: ((info: AuthzDenialInfo) => void) | undefined): void;
199
+ /**
200
+ * Emit a denial event, best-effort. Only fires for state-changing (non-GET,
201
+ * non-HEAD/OPTIONS) requests — a rejected GET is low-signal probing noise and
202
+ * would amplify audit volume under a scan. Never throws.
203
+ *
204
+ * The permission gates here call it themselves. Export it for data-driven checks
205
+ * a route makes inline (cross-org access, service-only endpoints), so those 403s
206
+ * reach the same `authz.denied` trail. `required` names what was missing.
207
+ */
208
+ export declare function recordAuthzDenial(req: Request, required: string): void;
137
209
  /**
138
210
  * Requires that the user hold AT LEAST ONE of the given permissions (or be a
139
211
  * superadmin). Use after requireAuth. Mirrors `requireRole`'s any-of semantics;
@@ -152,7 +224,7 @@ export declare function requirePermission(...permissions: Permission[]): (req: R
152
224
  export declare function requireAllPermissions(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
153
225
  /**
154
226
  * Like {@link requirePermission} (any-of) but ALSO admits an internal service
155
- * principal (a `service:*` machine token). For READ endpoints that BOTH
227
+ * principal (`principalType: 'service'`). For READ endpoints that BOTH
156
228
  * interactive users — who must hold one of the `:read` capabilities — AND
157
229
  * service-to-service callers legitimately hit; service tokens carry no
158
230
  * permission claims (they're least-privilege `role:member`), so a plain
@@ -163,13 +235,41 @@ export declare function requireAllPermissions(...permissions: Permission[]): (re
163
235
  */
164
236
  export declare function requirePermissionOrService(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
165
237
  /**
166
- * Gate an endpoint to internal service-to-service callers only (a `service:*`
167
- * machine principal minted via {@link signServiceToken} / `getServiceAuthHeader`).
238
+ * Gate an endpoint to internal service-to-service callers only (a
239
+ * `principalType: 'service'` principal minted via {@link signServiceToken} / `getServiceAuthHeader`).
168
240
  * Rejects any user token. For NON-user-facing endpoints — entity-event ingest,
169
241
  * auto-subscribe, and similar peer-service hooks — that a browser must never
170
242
  * reach. Compose after `requireAuth` so `req.user` is populated.
171
243
  */
172
244
  export declare function requireServicePrincipal(req: Request, res: Response, next: NextFunction): void;
245
+ /**
246
+ * THE gate for an INTERNAL route (#14) — the one every `/internal/*` path, the
247
+ * quota usage counters, the entity-event ingest and the audit ingest go through.
248
+ *
249
+ * Two rules, both fail-closed:
250
+ *
251
+ * 1. **No user token, ever.** Not a member's, not a superadmin's, not a
252
+ * service-account key's. An internal route is a peer-service API surface, so
253
+ * "a sufficiently privileged human" is not an acceptable caller — that
254
+ * equivalence is exactly how a browser-reachable path becomes a tenant
255
+ * boundary bug. `requireServicePrincipal` already refused users; this also
256
+ * closes the `|| isSystemAdmin(req)` escape hatches that had grown around
257
+ * the individual internal routes.
258
+ * 2. **Only the NAMED callers.** `callers` lists the services that legitimately
259
+ * call this route, and the name is cryptographically bound to the signing
260
+ * key (`services/service-keys.ts`), so this is an identity check rather than
261
+ * a claim check. It is the same allow-list the mesh policy names, at the
262
+ * layer that also holds in docker compose, where there is no mesh at all —
263
+ * the Istio `AuthorizationPolicy` is defence in depth on top, never the
264
+ * enforcement.
265
+ *
266
+ * Compose after `requireAuth` so `req.user` is populated. Refusals are counted
267
+ * (`internal_route_refused_total`) and audited through the shared `authz.denied`
268
+ * sink.
269
+ */
270
+ export declare function requireInternalService(options: {
271
+ callers: readonly string[];
272
+ }): (req: Request, res: Response, next: NextFunction) => void;
173
273
  /**
174
274
  * The system tenant's canonical org **id** — a fixed, well-known ObjectId (NOT
175
275
  * the string 'system'). This is the single knob every service resolves the
@@ -214,10 +314,19 @@ export declare function requireSystemAdmin(req: Request, res: Response, next: Ne
214
314
  * Sysadmins (isSuperAdmin) bypass — they always have every feature.
215
315
  */
216
316
  export declare function requireFeature(feature: string): (req: Request, res: Response, next: NextFunction) => void;
217
- /** Replace the service-token denylist (service names). Empty ⇒ kill-switch off. */
218
- export declare function setServiceTokenDenylist(names: Iterable<string>): void;
219
- /** True when `sub` is `service:<name>` and `<name>` is on the denylist. */
220
- export declare function isServiceTokenDenied(sub: string): boolean;
317
+ /**
318
+ * The calling service's name for a SERVICE principal (`principalType: 'service'`),
319
+ * else `undefined`. A user token never yields a name, whatever its `sub` says.
320
+ */
321
+ export declare function serviceNameOf(claims: {
322
+ principalType?: string;
323
+ sub?: string;
324
+ } | undefined): string | undefined;
325
+ /** True when the claims are a service principal whose service is on the denylist. */
326
+ export declare function isServiceTokenDenied(claims: {
327
+ principalType?: string;
328
+ sub?: string;
329
+ }): boolean;
221
330
  export interface ServiceTokenOptions {
222
331
  /** Calling service identifier (e.g. 'billing', 'platform'). Embedded as `sub: service:<name>`. */
223
332
  serviceName: string;
@@ -248,19 +357,41 @@ export interface ServiceTokenOptions {
248
357
  * Mint a JWT identifying the calling service. Used for inter-service HTTP calls.
249
358
  * The token satisfies `requireAuth` and (when orgId is present) `requireOrganization`.
250
359
  * Scope it with `opts.role` — least privilege keeps a leaked token low-value.
360
+ *
361
+ * `opts.serviceName` must be THIS process's own identity (`SERVICE_NAME`): a
362
+ * service holds only its own signing key, so minting a token that names another
363
+ * service throws rather than producing one every peer would reject. That is the
364
+ * one behaviour change #14 forces on callers — see `services/service-keys.ts`.
251
365
  */
252
366
  export declare function signServiceToken(opts: ServiceTokenOptions): string;
253
367
  /** Convenience: returns a `Bearer <token>` header value for fetch/axios calls. */
254
368
  export declare function getServiceAuthHeader(opts: ServiceTokenOptions): string;
255
- /** True when `req.user.sub` was issued by `signServiceToken` (i.e. starts with `service:`). */
369
+ /** True when `req.user` is a service principal (`principalType: 'service'`, minted by `signServiceToken`). */
256
370
  export declare function isServicePrincipal(req: Request): boolean;
371
+ /**
372
+ * True when `req.user` is an ORG SERVICE ACCOUNT (`principalType:
373
+ * 'service_account'`) — a non-human principal owned by one org, authenticating
374
+ * with a `pb_sa_…` key exchanged at platform.
375
+ *
376
+ * A service account is NOT an internal service principal: it holds org Roles and
377
+ * is subject to every permission gate a member is. What it can never do is
378
+ * satisfy a HUMAN-presence requirement — step-up (and any later assurance gate)
379
+ * refuses it outright, because there is no person behind it to re-verify.
380
+ */
381
+ export declare function isServiceAccountPrincipal(req: Request): boolean;
382
+ /** True when `req.user` is the named internal service (e.g. `'billing'`). */
383
+ export declare function isServicePrincipalNamed(req: Request, serviceName: string): boolean;
257
384
  /**
258
385
  * PRE-auth check: cryptographically verify the request carries a valid, signed
259
- * SERVICE token (`sub` starts with `service:`). Unlike {@link isServicePrincipal}
386
+ * SERVICE token (`principalType: 'service'`). Unlike {@link isServicePrincipal}
260
387
  * (which reads the already-populated `req.user`), this verifies the bearer token
261
388
  * itself, so it is safe to call BEFORE `requireAuth` runs — e.g. the global rate
262
389
  * limiter's `skip`, which must not trust the spoofable `x-internal-service`
263
- * header. Mirrors `requireAuth`'s verification (algorithm pinning + optional
264
- * issuer/audience) and returns `false` on any missing/invalid/non-service token.
390
+ * header. Mirrors `requireAuth`'s SERVICE chain exactly (the signer's own key,
391
+ * resolved by `kid`, with the `sub`/signer agreement check and the optional
392
+ * issuer/audience) and returns `false` on any missing/invalid/non-service token
393
+ * — including any platform-signed USER token, whose `kid` is not in the service
394
+ * bundle at all. Stays SYNCHRONOUS, which is why the per-service public keys are
395
+ * distributed as a mounted bundle rather than fetched over HTTP.
265
396
  */
266
397
  export declare function verifyServicePrincipal(req: Request): boolean;