@gravito/echo 3.1.1 → 3.1.2

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 (292) hide show
  1. package/README.md +24 -9
  2. package/dist/OrbitEcho.d.ts +76 -60
  3. package/dist/index.d.ts +31 -16
  4. package/dist/index.js +1 -1596
  5. package/dist/index.js.map +3 -28
  6. package/dist/{echo/src/middleware → middleware}/RequestBufferMiddleware.d.ts +3 -3
  7. package/dist/providers/GenericProvider.d.ts +37 -19
  8. package/dist/providers/GitHubProvider.d.ts +21 -13
  9. package/dist/providers/StripeProvider.d.ts +20 -13
  10. package/dist/providers/index.d.ts +14 -4
  11. package/dist/receive/SignatureValidator.d.ts +33 -1
  12. package/dist/receive/WebhookReceiver.d.ts +139 -22
  13. package/dist/receive/index.d.ts +0 -1
  14. package/dist/send/WebhookDispatcher.d.ts +159 -16
  15. package/dist/send/index.d.ts +0 -1
  16. package/dist/types.d.ts +669 -57
  17. package/package.json +5 -2
  18. package/dist/OrbitEcho.d.ts.map +0 -1
  19. package/dist/atlas/src/DB.d.ts +0 -348
  20. package/dist/atlas/src/OrbitAtlas.d.ts +0 -9
  21. package/dist/atlas/src/config/defineConfig.d.ts +0 -14
  22. package/dist/atlas/src/config/index.d.ts +0 -7
  23. package/dist/atlas/src/config/loadConfig.d.ts +0 -41
  24. package/dist/atlas/src/connection/Connection.d.ts +0 -112
  25. package/dist/atlas/src/connection/ConnectionManager.d.ts +0 -180
  26. package/dist/atlas/src/connection/ReplicaConnectionPool.d.ts +0 -54
  27. package/dist/atlas/src/drivers/BunSQLDriver.d.ts +0 -32
  28. package/dist/atlas/src/drivers/BunSQLPreparedStatement.d.ts +0 -118
  29. package/dist/atlas/src/drivers/MongoDBDriver.d.ts +0 -36
  30. package/dist/atlas/src/drivers/MySQLDriver.d.ts +0 -79
  31. package/dist/atlas/src/drivers/PostgresDriver.d.ts +0 -96
  32. package/dist/atlas/src/drivers/RedisDriver.d.ts +0 -43
  33. package/dist/atlas/src/drivers/SQLiteDriver.d.ts +0 -45
  34. package/dist/atlas/src/drivers/types.d.ts +0 -260
  35. package/dist/atlas/src/errors/index.d.ts +0 -45
  36. package/dist/atlas/src/grammar/Grammar.d.ts +0 -342
  37. package/dist/atlas/src/grammar/MongoGrammar.d.ts +0 -47
  38. package/dist/atlas/src/grammar/MySQLGrammar.d.ts +0 -54
  39. package/dist/atlas/src/grammar/NullGrammar.d.ts +0 -35
  40. package/dist/atlas/src/grammar/PostgresGrammar.d.ts +0 -62
  41. package/dist/atlas/src/grammar/SQLiteGrammar.d.ts +0 -32
  42. package/dist/atlas/src/index.d.ts +0 -79
  43. package/dist/atlas/src/migration/Migration.d.ts +0 -64
  44. package/dist/atlas/src/migration/MigrationRepository.d.ts +0 -65
  45. package/dist/atlas/src/migration/Migrator.d.ts +0 -110
  46. package/dist/atlas/src/migration/index.d.ts +0 -6
  47. package/dist/atlas/src/observability/AtlasMetrics.d.ts +0 -33
  48. package/dist/atlas/src/observability/AtlasObservability.d.ts +0 -15
  49. package/dist/atlas/src/observability/AtlasTracer.d.ts +0 -12
  50. package/dist/atlas/src/observability/index.d.ts +0 -9
  51. package/dist/atlas/src/orm/Repository.d.ts +0 -247
  52. package/dist/atlas/src/orm/index.d.ts +0 -6
  53. package/dist/atlas/src/orm/model/DirtyTracker.d.ts +0 -121
  54. package/dist/atlas/src/orm/model/Model.d.ts +0 -458
  55. package/dist/atlas/src/orm/model/ModelRegistry.d.ts +0 -20
  56. package/dist/atlas/src/orm/model/concerns/HasAttributes.d.ts +0 -150
  57. package/dist/atlas/src/orm/model/concerns/HasEvents.d.ts +0 -36
  58. package/dist/atlas/src/orm/model/concerns/HasPersistence.d.ts +0 -92
  59. package/dist/atlas/src/orm/model/concerns/HasRelationships.d.ts +0 -117
  60. package/dist/atlas/src/orm/model/concerns/HasSerialization.d.ts +0 -64
  61. package/dist/atlas/src/orm/model/concerns/applyMixins.d.ts +0 -15
  62. package/dist/atlas/src/orm/model/concerns/index.d.ts +0 -12
  63. package/dist/atlas/src/orm/model/decorators.d.ts +0 -138
  64. package/dist/atlas/src/orm/model/errors.d.ts +0 -52
  65. package/dist/atlas/src/orm/model/index.d.ts +0 -10
  66. package/dist/atlas/src/orm/model/relationships.d.ts +0 -207
  67. package/dist/atlas/src/orm/model/types.d.ts +0 -12
  68. package/dist/atlas/src/orm/schema/SchemaRegistry.d.ts +0 -124
  69. package/dist/atlas/src/orm/schema/SchemaSniffer.d.ts +0 -54
  70. package/dist/atlas/src/orm/schema/index.d.ts +0 -6
  71. package/dist/atlas/src/orm/schema/types.d.ts +0 -85
  72. package/dist/atlas/src/pool/AdaptivePoolManager.d.ts +0 -98
  73. package/dist/atlas/src/pool/PoolHealthChecker.d.ts +0 -91
  74. package/dist/atlas/src/pool/PoolStrategy.d.ts +0 -129
  75. package/dist/atlas/src/pool/PoolWarmer.d.ts +0 -92
  76. package/dist/atlas/src/query/Expression.d.ts +0 -60
  77. package/dist/atlas/src/query/NPlusOneDetector.d.ts +0 -10
  78. package/dist/atlas/src/query/QueryBuilder.d.ts +0 -643
  79. package/dist/atlas/src/query/RelationshipResolver.d.ts +0 -23
  80. package/dist/atlas/src/query/clauses/GroupByClause.d.ts +0 -51
  81. package/dist/atlas/src/query/clauses/HavingClause.d.ts +0 -70
  82. package/dist/atlas/src/query/clauses/JoinClause.d.ts +0 -87
  83. package/dist/atlas/src/query/clauses/LimitClause.d.ts +0 -82
  84. package/dist/atlas/src/query/clauses/OrderByClause.d.ts +0 -69
  85. package/dist/atlas/src/query/clauses/SelectClause.d.ts +0 -71
  86. package/dist/atlas/src/query/clauses/WhereClause.d.ts +0 -167
  87. package/dist/atlas/src/query/clauses/index.d.ts +0 -11
  88. package/dist/atlas/src/schema/Blueprint.d.ts +0 -276
  89. package/dist/atlas/src/schema/ColumnDefinition.d.ts +0 -154
  90. package/dist/atlas/src/schema/ForeignKeyDefinition.d.ts +0 -37
  91. package/dist/atlas/src/schema/MigrationGenerator.d.ts +0 -45
  92. package/dist/atlas/src/schema/Schema.d.ts +0 -131
  93. package/dist/atlas/src/schema/SchemaDiff.d.ts +0 -73
  94. package/dist/atlas/src/schema/TypeGenerator.d.ts +0 -57
  95. package/dist/atlas/src/schema/TypeWriter.d.ts +0 -42
  96. package/dist/atlas/src/schema/grammars/MySQLSchemaGrammar.d.ts +0 -23
  97. package/dist/atlas/src/schema/grammars/PostgresSchemaGrammar.d.ts +0 -26
  98. package/dist/atlas/src/schema/grammars/SQLiteSchemaGrammar.d.ts +0 -28
  99. package/dist/atlas/src/schema/grammars/SchemaGrammar.d.ts +0 -97
  100. package/dist/atlas/src/schema/grammars/index.d.ts +0 -7
  101. package/dist/atlas/src/schema/index.d.ts +0 -8
  102. package/dist/atlas/src/seed/Factory.d.ts +0 -90
  103. package/dist/atlas/src/seed/Seeder.d.ts +0 -28
  104. package/dist/atlas/src/seed/SeederRunner.d.ts +0 -74
  105. package/dist/atlas/src/seed/index.d.ts +0 -6
  106. package/dist/atlas/src/sharding/ShardingManager.d.ts +0 -59
  107. package/dist/atlas/src/types/index.d.ts +0 -1182
  108. package/dist/atlas/src/utils/CursorEncoding.d.ts +0 -63
  109. package/dist/atlas/src/utils/levenshtein.d.ts +0 -9
  110. package/dist/core/src/Application.d.ts +0 -215
  111. package/dist/core/src/CommandKernel.d.ts +0 -33
  112. package/dist/core/src/ConfigManager.d.ts +0 -65
  113. package/dist/core/src/Container/RequestScopeManager.d.ts +0 -62
  114. package/dist/core/src/Container/RequestScopeMetrics.d.ts +0 -144
  115. package/dist/core/src/Container.d.ts +0 -153
  116. package/dist/core/src/ErrorHandler.d.ts +0 -66
  117. package/dist/core/src/Event.d.ts +0 -5
  118. package/dist/core/src/EventManager.d.ts +0 -123
  119. package/dist/core/src/GlobalErrorHandlers.d.ts +0 -47
  120. package/dist/core/src/GravitoServer.d.ts +0 -28
  121. package/dist/core/src/HookManager.d.ts +0 -591
  122. package/dist/core/src/Listener.d.ts +0 -4
  123. package/dist/core/src/Logger.d.ts +0 -20
  124. package/dist/core/src/PlanetCore.d.ts +0 -378
  125. package/dist/core/src/RequestContext.d.ts +0 -97
  126. package/dist/core/src/Route.d.ts +0 -36
  127. package/dist/core/src/Router.d.ts +0 -284
  128. package/dist/core/src/ServiceProvider.d.ts +0 -178
  129. package/dist/core/src/adapters/GravitoEngineAdapter.d.ts +0 -27
  130. package/dist/core/src/adapters/PhotonAdapter.d.ts +0 -175
  131. package/dist/core/src/adapters/bun/BunContext.d.ts +0 -49
  132. package/dist/core/src/adapters/bun/BunNativeAdapter.d.ts +0 -31
  133. package/dist/core/src/adapters/bun/BunRequest.d.ts +0 -31
  134. package/dist/core/src/adapters/bun/RadixNode.d.ts +0 -19
  135. package/dist/core/src/adapters/bun/RadixRouter.d.ts +0 -31
  136. package/dist/core/src/adapters/bun/types.d.ts +0 -20
  137. package/dist/core/src/adapters/photon-types.d.ts +0 -73
  138. package/dist/core/src/adapters/types.d.ts +0 -235
  139. package/dist/core/src/cli/queue-commands.d.ts +0 -6
  140. package/dist/core/src/engine/AOTRouter.d.ts +0 -129
  141. package/dist/core/src/engine/FastContext.d.ts +0 -123
  142. package/dist/core/src/engine/Gravito.d.ts +0 -136
  143. package/dist/core/src/engine/MinimalContext.d.ts +0 -100
  144. package/dist/core/src/engine/analyzer.d.ts +0 -27
  145. package/dist/core/src/engine/constants.d.ts +0 -23
  146. package/dist/core/src/engine/index.d.ts +0 -26
  147. package/dist/core/src/engine/path.d.ts +0 -26
  148. package/dist/core/src/engine/pool.d.ts +0 -83
  149. package/dist/core/src/engine/types.d.ts +0 -146
  150. package/dist/core/src/error-handling/RequestScopeErrorContext.d.ts +0 -126
  151. package/dist/core/src/events/BackpressureManager.d.ts +0 -215
  152. package/dist/core/src/events/CircuitBreaker.d.ts +0 -229
  153. package/dist/core/src/events/DeadLetterQueue.d.ts +0 -219
  154. package/dist/core/src/events/EventBackend.d.ts +0 -12
  155. package/dist/core/src/events/EventOptions.d.ts +0 -204
  156. package/dist/core/src/events/EventPriorityQueue.d.ts +0 -301
  157. package/dist/core/src/events/FlowControlStrategy.d.ts +0 -109
  158. package/dist/core/src/events/IdempotencyCache.d.ts +0 -60
  159. package/dist/core/src/events/MessageQueueBridge.d.ts +0 -184
  160. package/dist/core/src/events/PriorityEscalationManager.d.ts +0 -82
  161. package/dist/core/src/events/RetryScheduler.d.ts +0 -104
  162. package/dist/core/src/events/WorkerPool.d.ts +0 -98
  163. package/dist/core/src/events/WorkerPoolConfig.d.ts +0 -153
  164. package/dist/core/src/events/WorkerPoolMetrics.d.ts +0 -65
  165. package/dist/core/src/events/aggregation/AggregationWindow.d.ts +0 -77
  166. package/dist/core/src/events/aggregation/DeduplicationManager.d.ts +0 -135
  167. package/dist/core/src/events/aggregation/EventAggregationManager.d.ts +0 -108
  168. package/dist/core/src/events/aggregation/EventBatcher.d.ts +0 -99
  169. package/dist/core/src/events/aggregation/types.d.ts +0 -117
  170. package/dist/core/src/events/index.d.ts +0 -25
  171. package/dist/core/src/events/observability/EventMetrics.d.ts +0 -132
  172. package/dist/core/src/events/observability/EventTracer.d.ts +0 -68
  173. package/dist/core/src/events/observability/EventTracing.d.ts +0 -161
  174. package/dist/core/src/events/observability/OTelEventMetrics.d.ts +0 -332
  175. package/dist/core/src/events/observability/ObservableHookManager.d.ts +0 -108
  176. package/dist/core/src/events/observability/StreamWorkerMetrics.d.ts +0 -76
  177. package/dist/core/src/events/observability/index.d.ts +0 -24
  178. package/dist/core/src/events/observability/metrics-types.d.ts +0 -16
  179. package/dist/core/src/events/types.d.ts +0 -134
  180. package/dist/core/src/exceptions/AuthenticationException.d.ts +0 -8
  181. package/dist/core/src/exceptions/AuthorizationException.d.ts +0 -8
  182. package/dist/core/src/exceptions/CircularDependencyException.d.ts +0 -9
  183. package/dist/core/src/exceptions/GravitoException.d.ts +0 -23
  184. package/dist/core/src/exceptions/HttpException.d.ts +0 -9
  185. package/dist/core/src/exceptions/ModelNotFoundException.d.ts +0 -10
  186. package/dist/core/src/exceptions/ValidationException.d.ts +0 -22
  187. package/dist/core/src/exceptions/index.d.ts +0 -7
  188. package/dist/core/src/health/HealthProvider.d.ts +0 -67
  189. package/dist/core/src/helpers/Arr.d.ts +0 -19
  190. package/dist/core/src/helpers/Str.d.ts +0 -23
  191. package/dist/core/src/helpers/data.d.ts +0 -25
  192. package/dist/core/src/helpers/errors.d.ts +0 -34
  193. package/dist/core/src/helpers/response.d.ts +0 -41
  194. package/dist/core/src/helpers.d.ts +0 -338
  195. package/dist/core/src/http/CookieJar.d.ts +0 -51
  196. package/dist/core/src/http/cookie.d.ts +0 -29
  197. package/dist/core/src/http/middleware/BodySizeLimit.d.ts +0 -16
  198. package/dist/core/src/http/middleware/Cors.d.ts +0 -24
  199. package/dist/core/src/http/middleware/Csrf.d.ts +0 -23
  200. package/dist/core/src/http/middleware/HeaderTokenGate.d.ts +0 -28
  201. package/dist/core/src/http/middleware/SecurityHeaders.d.ts +0 -29
  202. package/dist/core/src/http/middleware/ThrottleRequests.d.ts +0 -18
  203. package/dist/core/src/http/types.d.ts +0 -374
  204. package/dist/core/src/index.d.ts +0 -88
  205. package/dist/core/src/instrumentation/index.d.ts +0 -35
  206. package/dist/core/src/instrumentation/opentelemetry.d.ts +0 -178
  207. package/dist/core/src/instrumentation/types.d.ts +0 -182
  208. package/dist/core/src/observability/Metrics.d.ts +0 -244
  209. package/dist/core/src/observability/QueueDashboard.d.ts +0 -136
  210. package/dist/core/src/reliability/DeadLetterQueueManager.d.ts +0 -350
  211. package/dist/core/src/reliability/RetryPolicy.d.ts +0 -217
  212. package/dist/core/src/reliability/index.d.ts +0 -6
  213. package/dist/core/src/router/ControllerDispatcher.d.ts +0 -12
  214. package/dist/core/src/router/RequestValidator.d.ts +0 -20
  215. package/dist/core/src/runtime.d.ts +0 -119
  216. package/dist/core/src/security/Encrypter.d.ts +0 -33
  217. package/dist/core/src/security/Hasher.d.ts +0 -29
  218. package/dist/core/src/testing/HttpTester.d.ts +0 -39
  219. package/dist/core/src/testing/TestResponse.d.ts +0 -78
  220. package/dist/core/src/testing/index.d.ts +0 -2
  221. package/dist/core/src/types/events.d.ts +0 -94
  222. package/dist/echo/src/OrbitEcho.d.ts +0 -115
  223. package/dist/echo/src/index.d.ts +0 -64
  224. package/dist/echo/src/providers/GenericProvider.d.ts +0 -53
  225. package/dist/echo/src/providers/GitHubProvider.d.ts +0 -35
  226. package/dist/echo/src/providers/StripeProvider.d.ts +0 -38
  227. package/dist/echo/src/providers/index.d.ts +0 -14
  228. package/dist/echo/src/receive/SignatureValidator.d.ts +0 -67
  229. package/dist/echo/src/receive/WebhookReceiver.d.ts +0 -185
  230. package/dist/echo/src/receive/index.d.ts +0 -2
  231. package/dist/echo/src/send/WebhookDispatcher.d.ts +0 -198
  232. package/dist/echo/src/send/index.d.ts +0 -1
  233. package/dist/echo/src/types.d.ts +0 -756
  234. package/dist/index.d.ts.map +0 -1
  235. package/dist/monitor/src/MonitorOrbit.d.ts +0 -43
  236. package/dist/monitor/src/config.d.ts +0 -106
  237. package/dist/monitor/src/health/HealthController.d.ts +0 -28
  238. package/dist/monitor/src/health/HealthRegistry.d.ts +0 -80
  239. package/dist/monitor/src/health/index.d.ts +0 -36
  240. package/dist/monitor/src/index.d.ts +0 -13
  241. package/dist/monitor/src/metrics/MetricsController.d.ts +0 -22
  242. package/dist/monitor/src/metrics/MetricsRegistry.d.ts +0 -136
  243. package/dist/monitor/src/metrics/index.d.ts +0 -11
  244. package/dist/monitor/src/tracing/TracingManager.d.ts +0 -97
  245. package/dist/monitor/src/tracing/index.d.ts +0 -10
  246. package/dist/photon/src/index.d.ts +0 -89
  247. package/dist/photon/src/middleware/binary.d.ts +0 -31
  248. package/dist/photon/src/middleware/htmx.d.ts +0 -39
  249. package/dist/photon/src/middleware/ratelimit-redis.d.ts +0 -50
  250. package/dist/photon/src/middleware/ratelimit.d.ts +0 -161
  251. package/dist/photon/src/openapi.d.ts +0 -19
  252. package/dist/providers/GenericProvider.d.ts.map +0 -1
  253. package/dist/providers/GitHubProvider.d.ts.map +0 -1
  254. package/dist/providers/StripeProvider.d.ts.map +0 -1
  255. package/dist/providers/index.d.ts.map +0 -1
  256. package/dist/receive/SignatureValidator.d.ts.map +0 -1
  257. package/dist/receive/WebhookReceiver.d.ts.map +0 -1
  258. package/dist/receive/index.d.ts.map +0 -1
  259. package/dist/send/WebhookDispatcher.d.ts.map +0 -1
  260. package/dist/send/index.d.ts.map +0 -1
  261. package/dist/types.d.ts.map +0 -1
  262. /package/dist/{echo/src/dlq → dlq}/DeadLetterQueue.d.ts +0 -0
  263. /package/dist/{echo/src/dlq → dlq}/MemoryDeadLetterQueue.d.ts +0 -0
  264. /package/dist/{echo/src/dlq → dlq}/index.d.ts +0 -0
  265. /package/dist/{echo/src/middleware → middleware}/index.d.ts +0 -0
  266. /package/dist/{echo/src/observability → observability}/index.d.ts +0 -0
  267. /package/dist/{echo/src/observability → observability}/logging/ConsoleEchoLogger.d.ts +0 -0
  268. /package/dist/{echo/src/observability → observability}/logging/EchoLogger.d.ts +0 -0
  269. /package/dist/{echo/src/observability → observability}/logging/index.d.ts +0 -0
  270. /package/dist/{echo/src/observability → observability}/metrics/MetricsProvider.d.ts +0 -0
  271. /package/dist/{echo/src/observability → observability}/metrics/NoopMetricsProvider.d.ts +0 -0
  272. /package/dist/{echo/src/observability → observability}/metrics/PrometheusMetricsProvider.d.ts +0 -0
  273. /package/dist/{echo/src/observability → observability}/metrics/index.d.ts +0 -0
  274. /package/dist/{echo/src/observability → observability}/tracing/NoopTracer.d.ts +0 -0
  275. /package/dist/{echo/src/observability → observability}/tracing/Tracer.d.ts +0 -0
  276. /package/dist/{echo/src/observability → observability}/tracing/index.d.ts +0 -0
  277. /package/dist/{echo/src/providers → providers}/LinearProvider.d.ts +0 -0
  278. /package/dist/{echo/src/providers → providers}/PaddleProvider.d.ts +0 -0
  279. /package/dist/{echo/src/providers → providers}/ShopifyProvider.d.ts +0 -0
  280. /package/dist/{echo/src/providers → providers}/SlackProvider.d.ts +0 -0
  281. /package/dist/{echo/src/providers → providers}/TwilioProvider.d.ts +0 -0
  282. /package/dist/{echo/src/providers → providers}/base/BaseProvider.d.ts +0 -0
  283. /package/dist/{echo/src/providers → providers}/base/HeaderUtils.d.ts +0 -0
  284. /package/dist/{echo/src/replay → replay}/WebhookReplayService.d.ts +0 -0
  285. /package/dist/{echo/src/replay → replay}/index.d.ts +0 -0
  286. /package/dist/{echo/src/resilience → resilience}/CircuitBreaker.d.ts +0 -0
  287. /package/dist/{echo/src/resilience → resilience}/index.d.ts +0 -0
  288. /package/dist/{echo/src/rotation → rotation}/KeyRotationManager.d.ts +0 -0
  289. /package/dist/{echo/src/rotation → rotation}/index.d.ts +0 -0
  290. /package/dist/{echo/src/storage → storage}/MemoryWebhookStore.d.ts +0 -0
  291. /package/dist/{echo/src/storage → storage}/WebhookStore.d.ts +0 -0
  292. /package/dist/{echo/src/storage → storage}/index.d.ts +0 -0
package/dist/index.js CHANGED
@@ -1,1599 +1,4 @@
1
1
  // @bun
2
- // src/dlq/MemoryDeadLetterQueue.ts
3
- class MemoryDeadLetterQueue {
4
- queue = new Map;
5
- async enqueue(event) {
6
- const id = event.id ?? crypto.randomUUID();
7
- this.queue.set(id, { ...event, id });
8
- return id;
9
- }
10
- async peek(limit = 10) {
11
- return Array.from(this.queue.values()).sort((a, b) => a.failedAt.getTime() - b.failedAt.getTime()).slice(0, limit);
12
- }
13
- async dequeue(id) {
14
- this.queue.delete(id);
15
- }
16
- async size() {
17
- return this.queue.size;
18
- }
19
- async clear() {
20
- this.queue.clear();
21
- }
22
- }
23
- // src/middleware/RequestBufferMiddleware.ts
24
- var DEFAULT_CONFIG = {
25
- enabled: true,
26
- maxBodySize: 10 * 1024 * 1024,
27
- skipContentTypes: ["multipart/form-data", "application/octet-stream"]
28
- };
29
-
30
- class RequestBufferMiddleware {
31
- config;
32
- constructor(config = {}) {
33
- this.config = { ...DEFAULT_CONFIG, ...config };
34
- }
35
- handler() {
36
- return async (c, next) => {
37
- if (!this.config.enabled) {
38
- return await next();
39
- }
40
- const contentType = c.req.header("content-type") ?? "";
41
- if (this.config.skipContentTypes.some((type) => contentType.includes(type))) {
42
- return await next();
43
- }
44
- const rawBody = await this.readRawBody(c);
45
- const bodySize = typeof rawBody === "string" ? Buffer.byteLength(rawBody, "utf-8") : rawBody.length;
46
- if (bodySize > this.config.maxBodySize) {
47
- return c.json({ error: "Request body too large" }, 413);
48
- }
49
- const buffered = {
50
- rawBody,
51
- headers: c.req.header(),
52
- bufferedAt: new Date
53
- };
54
- c.set("bufferedRequest", buffered);
55
- return await next();
56
- };
57
- }
58
- async readRawBody(c) {
59
- const existing = c.get("bufferedRequest");
60
- if (existing) {
61
- return existing.rawBody;
62
- }
63
- const body = await c.req.text();
64
- return body;
65
- }
66
- }
67
- function createRequestBufferMiddleware(config) {
68
- return new RequestBufferMiddleware(config).handler();
69
- }
70
- // src/observability/logging/ConsoleEchoLogger.ts
71
- class ConsoleEchoLogger {
72
- formatContext(base, extra) {
73
- return {
74
- module: "echo",
75
- timestamp: new Date().toISOString(),
76
- ...base,
77
- ...extra
78
- };
79
- }
80
- debug(message, context) {
81
- console.debug(JSON.stringify({
82
- level: "debug",
83
- message,
84
- ...this.formatContext({}, context)
85
- }));
86
- }
87
- info(message, context) {
88
- console.info(JSON.stringify({
89
- level: "info",
90
- message,
91
- ...this.formatContext({}, context)
92
- }));
93
- }
94
- warn(message, context) {
95
- console.warn(JSON.stringify({
96
- level: "warn",
97
- message,
98
- ...this.formatContext({}, context)
99
- }));
100
- }
101
- error(message, context) {
102
- console.error(JSON.stringify({
103
- level: "error",
104
- message,
105
- ...this.formatContext({}, context)
106
- }));
107
- }
108
- }
109
- // src/observability/metrics/MetricsProvider.ts
110
- var EchoMetrics = {
111
- INCOMING_TOTAL: "echo_incoming_webhooks_total",
112
- INCOMING_DURATION: "echo_incoming_duration_seconds",
113
- INCOMING_VERIFICATION_FAILURES: "echo_incoming_verification_failures_total",
114
- OUTGOING_TOTAL: "echo_outgoing_webhooks_total",
115
- OUTGOING_DURATION: "echo_outgoing_duration_seconds",
116
- OUTGOING_RETRIES: "echo_outgoing_retries_total",
117
- OUTGOING_FAILURES: "echo_outgoing_failures_total",
118
- DLQ_SIZE: "echo_dlq_size",
119
- DLQ_ENQUEUED: "echo_dlq_enqueued_total",
120
- DLQ_PROCESSED: "echo_dlq_processed_total"
121
- };
122
- // src/observability/metrics/NoopMetricsProvider.ts
123
- class NoopMetricsProvider {
124
- increment() {}
125
- histogram() {}
126
- gauge() {}
127
- }
128
- // src/observability/metrics/PrometheusMetricsProvider.ts
129
- class PrometheusMetricsProvider {
130
- counters = new Map;
131
- histograms = new Map;
132
- gauges = new Map;
133
- increment(name, labels = {}) {
134
- const key = this.buildKey(name, labels);
135
- const counterMap = this.counters.get(name) ?? new Map;
136
- counterMap.set(key, (counterMap.get(key) ?? 0) + 1);
137
- this.counters.set(name, counterMap);
138
- }
139
- histogram(name, value, labels = {}) {
140
- const key = this.buildKey(name, labels);
141
- const values = this.histograms.get(key) ?? [];
142
- values.push(value);
143
- this.histograms.set(key, values);
144
- }
145
- gauge(name, value, labels = {}) {
146
- const key = this.buildKey(name, labels);
147
- this.gauges.set(key, value);
148
- }
149
- export() {
150
- const lines = [];
151
- for (const [name, counterMap] of this.counters) {
152
- lines.push(`# TYPE ${name} counter`);
153
- for (const [key, value] of counterMap) {
154
- lines.push(`${key} ${value}`);
155
- }
156
- }
157
- for (const [key, value] of this.gauges) {
158
- lines.push(`${key} ${value}`);
159
- }
160
- return lines.join(`
161
- `);
162
- }
163
- buildKey(name, labels) {
164
- if (Object.keys(labels).length === 0) {
165
- return name;
166
- }
167
- const labelStr = Object.entries(labels).map(([k, v]) => `${k}="${v}"`).join(",");
168
- return `${name}{${labelStr}}`;
169
- }
170
- }
171
- // src/observability/tracing/NoopTracer.ts
172
- class NoopSpan {
173
- setAttribute() {
174
- return this;
175
- }
176
- setAttributes() {
177
- return this;
178
- }
179
- addEvent() {
180
- return this;
181
- }
182
- setStatus() {
183
- return this;
184
- }
185
- end() {}
186
- }
187
-
188
- class NoopTracer {
189
- startSpan() {
190
- return new NoopSpan;
191
- }
192
- async withSpan(_name, fn) {
193
- return fn(new NoopSpan);
194
- }
195
- }
196
- // src/observability/tracing/Tracer.ts
197
- var SpanStatusCode;
198
- ((SpanStatusCode2) => {
199
- SpanStatusCode2[SpanStatusCode2["UNSET"] = 0] = "UNSET";
200
- SpanStatusCode2[SpanStatusCode2["OK"] = 1] = "OK";
201
- SpanStatusCode2[SpanStatusCode2["ERROR"] = 2] = "ERROR";
202
- })(SpanStatusCode ||= {});
203
- // src/receive/SignatureValidator.ts
204
- async function computeHmacSha256(payload, secret) {
205
- const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
206
- const payloadBuffer = typeof payload === "string" ? new TextEncoder().encode(payload) : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
207
- const signature = await crypto.subtle.sign("HMAC", key, payloadBuffer);
208
- return Buffer.from(signature).toString("hex");
209
- }
210
- async function computeHmacSha256Base64(payload, secret) {
211
- const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
212
- const payloadBuffer = typeof payload === "string" ? new TextEncoder().encode(payload) : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
213
- const signature = await crypto.subtle.sign("HMAC", key, payloadBuffer);
214
- return Buffer.from(signature).toString("base64");
215
- }
216
- async function computeHmacSha1(payload, secret) {
217
- const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-1" }, false, ["sign"]);
218
- const payloadBuffer = typeof payload === "string" ? new TextEncoder().encode(payload) : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
219
- const signature = await crypto.subtle.sign("HMAC", key, payloadBuffer);
220
- return Buffer.from(signature).toString("hex");
221
- }
222
- async function computeHmacSha1Base64(payload, secret) {
223
- const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-1" }, false, ["sign"]);
224
- const payloadBuffer = typeof payload === "string" ? new TextEncoder().encode(payload) : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
225
- const signature = await crypto.subtle.sign("HMAC", key, payloadBuffer);
226
- return Buffer.from(signature).toString("base64");
227
- }
228
- function timingSafeEqual(a, b) {
229
- if (a.length !== b.length) {
230
- return false;
231
- }
232
- const aBytes = new TextEncoder().encode(a);
233
- const bBytes = new TextEncoder().encode(b);
234
- let result = 0;
235
- for (let i = 0;i < aBytes.length; i++) {
236
- result |= (aBytes[i] ?? 0) ^ (bBytes[i] ?? 0);
237
- }
238
- return result === 0;
239
- }
240
- function validateTimestamp(timestamp, tolerance = 300) {
241
- const now = Math.floor(Date.now() / 1000);
242
- return Math.abs(now - timestamp) <= tolerance;
243
- }
244
- function parseStripeSignature(header) {
245
- const parts = header.split(",");
246
- let timestamp;
247
- const signatures = [];
248
- for (const part of parts) {
249
- const [key, value] = part.split("=");
250
- if (key === "t" && value !== undefined) {
251
- timestamp = parseInt(value, 10);
252
- } else if (key === "v1" && value !== undefined) {
253
- signatures.push(value);
254
- }
255
- }
256
- if (timestamp === undefined || signatures.length === 0) {
257
- return null;
258
- }
259
- return { timestamp, signatures };
260
- }
261
-
262
- // src/providers/base/HeaderUtils.ts
263
- function getHeader(headers, name) {
264
- const value = headers[name] ?? headers[name.toLowerCase()];
265
- return Array.isArray(value) ? value[0] : value;
266
- }
267
- function hasHeader(headers, name) {
268
- return getHeader(headers, name) !== undefined;
269
- }
270
-
271
- // src/providers/base/BaseProvider.ts
272
- class BaseProvider {
273
- tolerance;
274
- constructor(options = {}) {
275
- this.tolerance = options.tolerance ?? 300;
276
- }
277
- getHeader(headers, name) {
278
- return getHeader(headers, name);
279
- }
280
- hasHeader(headers, name) {
281
- return hasHeader(headers, name);
282
- }
283
- createFailure(error) {
284
- return { valid: false, error };
285
- }
286
- createSuccess(payload, options = {}) {
287
- return {
288
- valid: true,
289
- payload,
290
- eventType: options.eventType,
291
- webhookId: options.webhookId
292
- };
293
- }
294
- payloadToString(payload) {
295
- return typeof payload === "string" ? payload : payload.toString("utf-8");
296
- }
297
- safeParseJson(str) {
298
- try {
299
- return { success: true, data: JSON.parse(str) };
300
- } catch {
301
- return { success: false, error: "Failed to parse webhook payload" };
302
- }
303
- }
304
- }
305
-
306
- // src/providers/GenericProvider.ts
307
- class GenericProvider extends BaseProvider {
308
- name = "generic";
309
- signatureHeader;
310
- timestampHeader;
311
- constructor(options = {}) {
312
- super(options);
313
- this.signatureHeader = options.signatureHeader ?? "x-webhook-signature";
314
- this.timestampHeader = options.timestampHeader ?? "x-webhook-timestamp";
315
- }
316
- async verify(payload, headers, secret) {
317
- const signature = this.getHeader(headers, this.signatureHeader);
318
- if (!signature) {
319
- return this.createFailure(`Missing signature header: ${this.signatureHeader}`);
320
- }
321
- const timestampStr = this.getHeader(headers, this.timestampHeader);
322
- if (timestampStr) {
323
- const timestamp = parseInt(timestampStr, 10);
324
- if (Number.isNaN(timestamp) || !validateTimestamp(timestamp, this.tolerance)) {
325
- return this.createFailure("Timestamp validation failed");
326
- }
327
- }
328
- const payloadStr = this.payloadToString(payload);
329
- const expectedSignature = await computeHmacSha256(payloadStr, secret);
330
- if (!timingSafeEqual(signature.toLowerCase(), expectedSignature.toLowerCase())) {
331
- return this.createFailure("Signature verification failed");
332
- }
333
- const parseResult = this.safeParseJson(payloadStr);
334
- if (!parseResult.success) {
335
- return this.createSuccess(payloadStr);
336
- }
337
- const parsed = parseResult.data;
338
- return this.createSuccess(parsed, {
339
- eventType: parsed.type ?? parsed.event ?? parsed.eventType,
340
- webhookId: parsed.id ?? parsed.webhookId
341
- });
342
- }
343
- }
344
-
345
- // src/providers/GitHubProvider.ts
346
- class GitHubProvider extends BaseProvider {
347
- name = "github";
348
- async verify(payload, headers, secret) {
349
- const signature = this.getHeader(headers, "x-hub-signature-256");
350
- if (!signature) {
351
- return this.createFailure("Missing X-Hub-Signature-256 header");
352
- }
353
- if (!signature.startsWith("sha256=")) {
354
- return this.createFailure("Invalid signature format (expected sha256=...)");
355
- }
356
- const signatureValue = signature.slice(7);
357
- const payloadStr = this.payloadToString(payload);
358
- const expectedSignature = await computeHmacSha256(payloadStr, secret);
359
- if (!timingSafeEqual(signatureValue.toLowerCase(), expectedSignature.toLowerCase())) {
360
- return this.createFailure("Signature verification failed");
361
- }
362
- const parseResult = this.safeParseJson(payloadStr);
363
- if (!parseResult.success) {
364
- return this.createFailure("Failed to parse webhook payload");
365
- }
366
- const event = parseResult.data;
367
- const eventType = this.getHeader(headers, "x-github-event");
368
- const deliveryId = this.getHeader(headers, "x-github-delivery");
369
- return this.createSuccess(event, {
370
- eventType: eventType ?? undefined,
371
- webhookId: deliveryId ?? undefined
372
- });
373
- }
374
- parseEventType(payload) {
375
- if (typeof payload === "object" && payload !== null && "action" in payload) {
376
- return payload.action;
377
- }
378
- return;
379
- }
380
- }
381
-
382
- // src/providers/LinearProvider.ts
383
- class LinearProvider extends BaseProvider {
384
- name = "linear";
385
- async verify(payload, headers, secret) {
386
- const signature = this.getHeader(headers, "linear-signature");
387
- if (!signature) {
388
- return this.createFailure("Missing Linear-Signature header");
389
- }
390
- const payloadStr = this.payloadToString(payload);
391
- const expectedSignature = await computeHmacSha256(payloadStr, secret);
392
- if (!timingSafeEqual(signature, expectedSignature)) {
393
- return this.createFailure("Signature verification failed");
394
- }
395
- const parseResult = this.safeParseJson(payloadStr);
396
- if (!parseResult.success) {
397
- return this.createFailure(parseResult.error);
398
- }
399
- const data = parseResult.data;
400
- return this.createSuccess(data, {
401
- eventType: data.type ?? data.action,
402
- webhookId: this.getHeader(headers, "linear-delivery")
403
- });
404
- }
405
- }
406
-
407
- // src/providers/PaddleProvider.ts
408
- class PaddleProvider extends BaseProvider {
409
- name = "paddle";
410
- async verify(payload, headers, secret) {
411
- const signatureHeader = this.getHeader(headers, "paddle-signature");
412
- if (!signatureHeader) {
413
- return this.createFailure("Missing Paddle-Signature header");
414
- }
415
- const parsed = this.parsePaddleSignature(signatureHeader);
416
- if (!parsed) {
417
- return this.createFailure("Invalid Paddle-Signature format");
418
- }
419
- const { timestamp, signature } = parsed;
420
- if (!validateTimestamp(timestamp, this.tolerance)) {
421
- return this.createFailure(`Timestamp outside tolerance window (${this.tolerance}s)`);
422
- }
423
- const payloadStr = this.payloadToString(payload);
424
- const signedPayload = `${timestamp}:${payloadStr}`;
425
- const expectedSignature = await computeHmacSha256(signedPayload, secret);
426
- if (!timingSafeEqual(signature, expectedSignature)) {
427
- return this.createFailure("Signature verification failed");
428
- }
429
- const parseResult = this.safeParseJson(payloadStr);
430
- if (!parseResult.success) {
431
- return this.createFailure(parseResult.error);
432
- }
433
- const data = parseResult.data;
434
- return this.createSuccess(data, {
435
- eventType: data.event_type,
436
- webhookId: data.event_id
437
- });
438
- }
439
- parsePaddleSignature(header) {
440
- const parts = header.split(";");
441
- let timestamp;
442
- let signature;
443
- for (const part of parts) {
444
- const [key, value] = part.split("=");
445
- if (key === "ts" && value) {
446
- timestamp = parseInt(value, 10);
447
- } else if (key === "h1" && value) {
448
- signature = value;
449
- }
450
- }
451
- if (timestamp === undefined || !signature) {
452
- return null;
453
- }
454
- return { timestamp, signature };
455
- }
456
- }
457
-
458
- // src/providers/ShopifyProvider.ts
459
- class ShopifyProvider extends BaseProvider {
460
- name = "shopify";
461
- async verify(payload, headers, secret) {
462
- const signature = this.getHeader(headers, "x-shopify-hmac-sha256");
463
- if (!signature) {
464
- return this.createFailure("Missing X-Shopify-Hmac-Sha256 header");
465
- }
466
- const payloadStr = this.payloadToString(payload);
467
- const expectedSignature = await computeHmacSha256Base64(payloadStr, secret);
468
- if (!timingSafeEqual(signature, expectedSignature)) {
469
- return this.createFailure("Signature verification failed");
470
- }
471
- const parseResult = this.safeParseJson(payloadStr);
472
- if (!parseResult.success) {
473
- return this.createFailure(parseResult.error);
474
- }
475
- return this.createSuccess(parseResult.data, {
476
- eventType: this.getHeader(headers, "x-shopify-topic"),
477
- webhookId: this.getHeader(headers, "x-shopify-webhook-id")
478
- });
479
- }
480
- }
481
-
482
- // src/providers/SlackProvider.ts
483
- class SlackProvider extends BaseProvider {
484
- name = "slack";
485
- async verify(payload, headers, secret) {
486
- const signature = this.getHeader(headers, "x-slack-signature");
487
- const timestampStr = this.getHeader(headers, "x-slack-request-timestamp");
488
- if (!signature) {
489
- return this.createFailure("Missing X-Slack-Signature header");
490
- }
491
- if (!timestampStr) {
492
- return this.createFailure("Missing X-Slack-Request-Timestamp header");
493
- }
494
- if (!signature.startsWith("v0=")) {
495
- return this.createFailure("Invalid signature format (expected v0=...)");
496
- }
497
- const timestamp = parseInt(timestampStr, 10);
498
- if (!validateTimestamp(timestamp, this.tolerance)) {
499
- return this.createFailure(`Timestamp outside tolerance window (${this.tolerance}s)`);
500
- }
501
- const payloadStr = this.payloadToString(payload);
502
- const sigBasestring = `v0:${timestamp}:${payloadStr}`;
503
- const expectedSignature = await computeHmacSha256(sigBasestring, secret);
504
- if (!timingSafeEqual(signature.slice(3), expectedSignature)) {
505
- return this.createFailure("Signature verification failed");
506
- }
507
- const parseResult = this.safeParseJson(payloadStr);
508
- if (!parseResult.success) {
509
- return this.createFailure(parseResult.error);
510
- }
511
- const data = parseResult.data;
512
- return this.createSuccess(data, {
513
- eventType: data.type,
514
- webhookId: data.event_id
515
- });
516
- }
517
- }
518
-
519
- // src/providers/StripeProvider.ts
520
- class StripeProvider extends BaseProvider {
521
- name = "stripe";
522
- constructor(options = {}) {
523
- super(options);
524
- }
525
- async verify(payload, headers, secret) {
526
- const signatureHeader = this.getHeader(headers, "stripe-signature");
527
- if (!signatureHeader) {
528
- return this.createFailure("Missing Stripe-Signature header");
529
- }
530
- const parsed = parseStripeSignature(signatureHeader);
531
- if (!parsed) {
532
- return this.createFailure("Invalid Stripe-Signature header format");
533
- }
534
- const { timestamp, signatures } = parsed;
535
- if (!validateTimestamp(timestamp, this.tolerance)) {
536
- return this.createFailure(`Timestamp outside tolerance window (${this.tolerance}s)`);
537
- }
538
- const payloadStr = this.payloadToString(payload);
539
- const signedPayload = `${timestamp}.${payloadStr}`;
540
- const expectedSignature = await computeHmacSha256(signedPayload, secret);
541
- const signatureValid = signatures.some((sig) => timingSafeEqual(sig.toLowerCase(), expectedSignature.toLowerCase()));
542
- if (!signatureValid) {
543
- return this.createFailure("Signature verification failed");
544
- }
545
- const parseResult = this.safeParseJson(payloadStr);
546
- if (!parseResult.success) {
547
- return this.createFailure("Failed to parse webhook payload");
548
- }
549
- const event = parseResult.data;
550
- return this.createSuccess(event, {
551
- eventType: event.type,
552
- webhookId: event.id
553
- });
554
- }
555
- parseEventType(payload) {
556
- if (typeof payload === "object" && payload !== null && "type" in payload) {
557
- return payload.type;
558
- }
559
- return;
560
- }
561
- }
562
-
563
- // src/providers/TwilioProvider.ts
564
- class TwilioProvider extends BaseProvider {
565
- name = "twilio";
566
- baseUrl;
567
- constructor(options = {}) {
568
- super(options);
569
- this.baseUrl = options.baseUrl;
570
- }
571
- async verify(payload, headers, secret) {
572
- const signature = this.getHeader(headers, "x-twilio-signature");
573
- if (!signature) {
574
- return this.createFailure("Missing X-Twilio-Signature header");
575
- }
576
- const url = this.baseUrl ?? "";
577
- const payloadStr = this.payloadToString(payload);
578
- const params = new URLSearchParams(payloadStr);
579
- const sortedParams = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}${value}`).join("");
580
- const signaturePayload = url + sortedParams;
581
- const expectedSignature = await computeHmacSha1Base64(signaturePayload, secret);
582
- if (!timingSafeEqual(signature, expectedSignature)) {
583
- return this.createFailure("Signature verification failed");
584
- }
585
- return this.createSuccess(Object.fromEntries(params), {
586
- eventType: params.get("EventType") ?? undefined
587
- });
588
- }
589
- }
590
-
591
- // src/receive/WebhookReceiver.ts
592
- class WebhookReceiver {
593
- providers = new Map;
594
- handlers = new Map;
595
- globalHandlers = new Map;
596
- store;
597
- metrics = new NoopMetricsProvider;
598
- tracer = new NoopTracer;
599
- logger = new ConsoleEchoLogger;
600
- keyRotationManager;
601
- constructor() {
602
- this.registerProviderType("generic", GenericProvider);
603
- this.registerProviderType("stripe", StripeProvider);
604
- this.registerProviderType("github", GitHubProvider);
605
- this.registerProviderType("shopify", ShopifyProvider);
606
- this.registerProviderType("twilio", TwilioProvider);
607
- this.registerProviderType("slack", SlackProvider);
608
- this.registerProviderType("paddle", PaddleProvider);
609
- this.registerProviderType("linear", LinearProvider);
610
- }
611
- providerTypes = new Map;
612
- setStore(store) {
613
- this.store = store;
614
- return this;
615
- }
616
- setMetrics(metrics) {
617
- this.metrics = metrics;
618
- return this;
619
- }
620
- setTracer(tracer) {
621
- this.tracer = tracer;
622
- return this;
623
- }
624
- setLogger(logger) {
625
- this.logger = logger;
626
- return this;
627
- }
628
- setKeyRotationManager(manager) {
629
- this.keyRotationManager = manager;
630
- return this;
631
- }
632
- registerProviderType(name, ProviderCls) {
633
- this.providerTypes.set(name, ProviderCls);
634
- return this;
635
- }
636
- registerProvider(name, secret, options) {
637
- const type = options?.type ?? name;
638
- const ProviderClass = this.providerTypes.get(type);
639
- if (!ProviderClass) {
640
- throw new Error(`Unknown provider type: ${type}`);
641
- }
642
- const provider = new ProviderClass({ tolerance: options?.tolerance });
643
- this.providers.set(name, { provider, secret });
644
- return this;
645
- }
646
- registerProviderWithRotation(name, keys, options) {
647
- if (!this.keyRotationManager) {
648
- throw new Error("KeyRotationManager must be set before using key rotation");
649
- }
650
- const type = options?.type ?? name;
651
- const ProviderClass = this.providerTypes.get(type);
652
- if (!ProviderClass) {
653
- throw new Error(`Unknown provider type: ${type}`);
654
- }
655
- this.keyRotationManager.registerKeys(name, keys);
656
- const primaryKey = this.keyRotationManager.getPrimaryKey(name);
657
- if (!primaryKey) {
658
- throw new Error(`No primary key found for provider ${name}`);
659
- }
660
- const provider = new ProviderClass({ tolerance: options?.tolerance });
661
- this.providers.set(name, { provider, secret: primaryKey.key });
662
- return this;
663
- }
664
- on(providerName, eventType, handler) {
665
- if (!this.handlers.has(providerName)) {
666
- this.handlers.set(providerName, new Map);
667
- }
668
- const providerHandlers = this.handlers.get(providerName);
669
- if (!providerHandlers) {
670
- const newHandlers = new Map;
671
- this.handlers.set(providerName, newHandlers);
672
- return this.on(providerName, eventType, handler);
673
- }
674
- if (!providerHandlers.has(eventType)) {
675
- providerHandlers.set(eventType, []);
676
- }
677
- providerHandlers.get(eventType)?.push(handler);
678
- return this;
679
- }
680
- onAll(providerName, handler) {
681
- if (!this.globalHandlers.has(providerName)) {
682
- this.globalHandlers.set(providerName, []);
683
- }
684
- this.globalHandlers.get(providerName)?.push(handler);
685
- return this;
686
- }
687
- async handle(providerName, body, headers, context) {
688
- const buffered = context?.get("bufferedRequest");
689
- const actualBody = buffered?.rawBody ?? body;
690
- const actualHeaders = buffered?.headers ?? headers;
691
- return this.tracer.withSpan("echo.receive_webhook", async (span) => {
692
- const startTime = performance.now();
693
- const labels = { provider: providerName };
694
- span.setAttributes({
695
- "echo.provider": providerName,
696
- "echo.direction": "incoming",
697
- "echo.buffered": buffered !== undefined
698
- });
699
- this.logger.debug("Webhook received", {
700
- component: "receiver",
701
- provider: providerName,
702
- buffered: buffered !== undefined
703
- });
704
- try {
705
- const config = this.providers.get(providerName);
706
- if (!config) {
707
- const error = `Provider not registered: ${providerName}`;
708
- this.logger.warn("Webhook provider not registered", {
709
- component: "receiver",
710
- provider: providerName
711
- });
712
- span.setStatus({ code: 2 /* ERROR */, message: error });
713
- return {
714
- valid: false,
715
- error,
716
- handled: false
717
- };
718
- }
719
- const { provider, secret } = config;
720
- span.addEvent("verification_start");
721
- let result = {
722
- valid: false,
723
- error: "No verification performed"
724
- };
725
- if (this.keyRotationManager?.hasProvider(providerName)) {
726
- const activeKeys = this.keyRotationManager.getActiveKeys(providerName);
727
- let verified = false;
728
- for (const keyEntry of activeKeys) {
729
- const attemptResult = await provider.verify(actualBody, actualHeaders, keyEntry.key);
730
- if (attemptResult.valid) {
731
- result = attemptResult;
732
- verified = true;
733
- if (!keyEntry.isPrimary) {
734
- this.logger.info("Webhook verified with non-primary key", {
735
- component: "receiver",
736
- provider: providerName,
737
- keyVersion: keyEntry.version
738
- });
739
- }
740
- break;
741
- }
742
- }
743
- if (!verified) {
744
- result = {
745
- valid: false,
746
- error: "Signature verification failed with all active keys"
747
- };
748
- }
749
- } else {
750
- result = await provider.verify(actualBody, actualHeaders, secret);
751
- }
752
- if (!result.valid) {
753
- span.setStatus({ code: 2 /* ERROR */, message: result.error });
754
- span.setAttribute("echo.error", result.error ?? "unknown");
755
- this.metrics.increment(EchoMetrics.INCOMING_VERIFICATION_FAILURES, {
756
- provider: providerName,
757
- error_type: this.categorizeError(result.error)
758
- });
759
- this.logger.warn("Webhook verification failed", {
760
- component: "receiver",
761
- provider: providerName,
762
- error: result.error
763
- });
764
- return { ...result, handled: false };
765
- }
766
- span.addEvent("verification_success");
767
- span.setAttributes({
768
- "echo.event_type": result.eventType ?? "unknown",
769
- "echo.webhook_id": result.webhookId ?? ""
770
- });
771
- this.logger.info("Webhook verified successfully", {
772
- component: "receiver",
773
- provider: providerName,
774
- eventType: result.eventType,
775
- webhookId: result.webhookId
776
- });
777
- labels.event_type = result.eventType;
778
- labels.status = "success";
779
- let eventId;
780
- if (this.store) {
781
- eventId = await this.store.saveIncomingEvent({
782
- provider: providerName,
783
- eventType: result.eventType ?? "unknown",
784
- payload: result.payload,
785
- headers: Object.fromEntries(Object.entries(actualHeaders).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),
786
- rawBody: typeof actualBody === "string" ? actualBody : actualBody.toString("utf-8"),
787
- receivedAt: new Date,
788
- status: "pending"
789
- });
790
- }
791
- const event = {
792
- provider: providerName,
793
- type: result.eventType ?? "unknown",
794
- payload: result.payload,
795
- headers: actualHeaders,
796
- rawBody: typeof actualBody === "string" ? actualBody : actualBody.toString("utf-8"),
797
- receivedAt: new Date,
798
- id: result.webhookId
799
- };
800
- try {
801
- let handled = false;
802
- let handlerCount = 0;
803
- span.addEvent("handlers_start");
804
- const providerHandlers = this.handlers.get(providerName);
805
- if (providerHandlers) {
806
- const eventHandlers = providerHandlers.get(event.type);
807
- if (eventHandlers) {
808
- for (const handler of eventHandlers) {
809
- await handler(event);
810
- handled = true;
811
- handlerCount++;
812
- }
813
- }
814
- }
815
- const globalHandlers = this.globalHandlers.get(providerName);
816
- if (globalHandlers) {
817
- for (const handler of globalHandlers) {
818
- await handler(event);
819
- handled = true;
820
- handlerCount++;
821
- }
822
- }
823
- span.addEvent("handlers_complete", { handler_count: handlerCount });
824
- if (this.store && eventId) {
825
- await this.store.markProcessed(eventId);
826
- }
827
- this.logger.debug("Webhook processing complete", {
828
- component: "receiver",
829
- provider: providerName,
830
- eventType: result.eventType,
831
- handlersInvoked: handlerCount
832
- });
833
- span.setStatus({ code: 1 /* OK */ });
834
- return { ...result, handled, eventId };
835
- } catch (error) {
836
- if (this.store && eventId) {
837
- await this.store.markFailed(eventId, String(error));
838
- }
839
- throw error;
840
- }
841
- } catch (error) {
842
- labels.status = "failure";
843
- labels.error_type = error instanceof Error ? error.name : "unknown";
844
- span.setStatus({ code: 2 /* ERROR */, message: String(error) });
845
- throw error;
846
- } finally {
847
- const duration = (performance.now() - startTime) / 1000;
848
- this.metrics.increment(EchoMetrics.INCOMING_TOTAL, labels);
849
- this.metrics.histogram(EchoMetrics.INCOMING_DURATION, duration, labels);
850
- }
851
- });
852
- }
853
- categorizeError(error) {
854
- if (!error) {
855
- return "unknown";
856
- }
857
- if (error.includes("Missing")) {
858
- return "missing_header";
859
- }
860
- if (error.includes("Signature")) {
861
- return "signature_invalid";
862
- }
863
- if (error.includes("Timestamp")) {
864
- return "timestamp_invalid";
865
- }
866
- return "other";
867
- }
868
- async verify(providerName, body, headers) {
869
- const config = this.providers.get(providerName);
870
- if (!config) {
871
- return {
872
- valid: false,
873
- error: `Provider not registered: ${providerName}`
874
- };
875
- }
876
- return config.provider.verify(body, headers, config.secret);
877
- }
878
- }
879
-
880
- // src/rotation/KeyRotationManager.ts
881
- var DEFAULT_CONFIG2 = {
882
- enabled: false,
883
- autoCleanup: true,
884
- gracePeriod: 24 * 60 * 60 * 1000,
885
- keyProvider: async () => [],
886
- onRotate: () => {}
887
- };
888
-
889
- class KeyRotationManager {
890
- providerKeys = new Map;
891
- cleanupTimer;
892
- config;
893
- constructor(config = {}) {
894
- this.config = { ...DEFAULT_CONFIG2, ...config };
895
- if (this.config.enabled && this.config.autoCleanup) {
896
- this.startAutoCleanup();
897
- }
898
- }
899
- registerKeys(providerName, keys) {
900
- const primaryKeys = keys.filter((k) => k.isPrimary);
901
- if (primaryKeys.length !== 1) {
902
- throw new Error(`Provider ${providerName} must have exactly one primary key`);
903
- }
904
- const sorted = [...keys].sort((a, b) => b.activeFrom.getTime() - a.activeFrom.getTime());
905
- this.providerKeys.set(providerName, sorted);
906
- }
907
- getActiveKeys(providerName) {
908
- const keys = this.providerKeys.get(providerName) ?? [];
909
- const now = new Date;
910
- return keys.filter((key) => {
911
- const isActive = key.activeFrom <= now;
912
- const notExpired = !key.expiresAt || key.expiresAt > now;
913
- return isActive && notExpired;
914
- });
915
- }
916
- getPrimaryKey(providerName) {
917
- const keys = this.getActiveKeys(providerName);
918
- return keys.find((k) => k.isPrimary) ?? null;
919
- }
920
- async rotatePrimaryKey(providerName, newKey) {
921
- const existingKeys = this.providerKeys.get(providerName) ?? [];
922
- const updatedKeys = existingKeys.map((key) => ({
923
- ...key,
924
- isPrimary: false,
925
- expiresAt: key.isPrimary ? new Date(Date.now() + this.config.gracePeriod) : key.expiresAt
926
- }));
927
- const primaryKey = {
928
- ...newKey,
929
- isPrimary: true
930
- };
931
- updatedKeys.unshift(primaryKey);
932
- this.providerKeys.set(providerName, updatedKeys);
933
- this.config.onRotate(providerName, primaryKey);
934
- }
935
- cleanupExpiredKeys() {
936
- const now = new Date;
937
- let cleaned = 0;
938
- for (const [providerName, keys] of this.providerKeys.entries()) {
939
- const active = keys.filter((key) => !key.expiresAt || key.expiresAt > now);
940
- if (active.length !== keys.length) {
941
- cleaned += keys.length - active.length;
942
- this.providerKeys.set(providerName, active);
943
- }
944
- }
945
- return cleaned;
946
- }
947
- startAutoCleanup() {
948
- this.cleanupTimer = setInterval(() => {
949
- const cleaned = this.cleanupExpiredKeys();
950
- if (cleaned > 0) {
951
- console.log(`[KeyRotationManager] Cleaned up ${cleaned} expired keys`);
952
- }
953
- }, 60 * 60 * 1000);
954
- }
955
- destroy() {
956
- if (this.cleanupTimer) {
957
- clearInterval(this.cleanupTimer);
958
- }
959
- }
960
- getAllProviderKeys() {
961
- return new Map(this.providerKeys);
962
- }
963
- hasProvider(providerName) {
964
- return this.providerKeys.has(providerName);
965
- }
966
- }
967
-
968
- // src/resilience/CircuitBreaker.ts
969
- var DEFAULT_CONFIG3 = {
970
- enabled: true,
971
- failureThreshold: 5,
972
- successThreshold: 2,
973
- windowSize: 60000,
974
- openTimeout: 30000,
975
- onOpen: () => {},
976
- onHalfOpen: () => {},
977
- onClose: () => {}
978
- };
979
-
980
- class CircuitBreaker {
981
- name;
982
- state = "CLOSED";
983
- failures = 0;
984
- successes = 0;
985
- lastFailureAt;
986
- lastSuccessAt;
987
- openedAt;
988
- halfOpenAttempts = 0;
989
- config;
990
- constructor(name, config = {}) {
991
- this.name = name;
992
- this.config = { ...DEFAULT_CONFIG3, ...config };
993
- }
994
- async execute(fn) {
995
- if (!this.config.enabled) {
996
- return await fn();
997
- }
998
- this.checkStateTransition();
999
- if (this.state === "OPEN") {
1000
- throw new Error(`Circuit breaker is OPEN for ${this.name}`);
1001
- }
1002
- try {
1003
- const result = await fn();
1004
- this.onSuccess();
1005
- return result;
1006
- } catch (error) {
1007
- this.onFailure();
1008
- throw error;
1009
- }
1010
- }
1011
- onSuccess() {
1012
- this.lastSuccessAt = new Date;
1013
- this.successes++;
1014
- if (this.state === "HALF_OPEN") {
1015
- this.halfOpenAttempts++;
1016
- if (this.halfOpenAttempts >= this.config.successThreshold) {
1017
- this.transitionTo("CLOSED");
1018
- this.reset();
1019
- }
1020
- } else if (this.state === "CLOSED") {
1021
- this.failures = 0;
1022
- }
1023
- }
1024
- onFailure() {
1025
- this.lastFailureAt = new Date;
1026
- this.failures++;
1027
- if (this.state === "HALF_OPEN") {
1028
- this.transitionTo("OPEN");
1029
- this.openedAt = new Date;
1030
- } else if (this.state === "CLOSED") {
1031
- if (this.failures >= this.config.failureThreshold) {
1032
- this.transitionTo("OPEN");
1033
- this.openedAt = new Date;
1034
- }
1035
- }
1036
- }
1037
- checkStateTransition() {
1038
- if (this.state === "OPEN" && this.openedAt) {
1039
- const elapsed = Date.now() - this.openedAt.getTime();
1040
- if (elapsed >= this.config.openTimeout) {
1041
- this.transitionTo("HALF_OPEN");
1042
- this.halfOpenAttempts = 0;
1043
- }
1044
- }
1045
- if (this.lastFailureAt) {
1046
- const elapsed = Date.now() - this.lastFailureAt.getTime();
1047
- if (elapsed >= this.config.windowSize) {
1048
- this.failures = 0;
1049
- }
1050
- }
1051
- }
1052
- transitionTo(newState) {
1053
- this.state = newState;
1054
- switch (newState) {
1055
- case "OPEN":
1056
- this.config.onOpen(this.name);
1057
- break;
1058
- case "HALF_OPEN":
1059
- this.config.onHalfOpen(this.name);
1060
- break;
1061
- case "CLOSED":
1062
- this.config.onClose(this.name);
1063
- break;
1064
- }
1065
- }
1066
- reset() {
1067
- this.failures = 0;
1068
- this.successes = 0;
1069
- this.halfOpenAttempts = 0;
1070
- this.openedAt = undefined;
1071
- }
1072
- getMetrics() {
1073
- return {
1074
- state: this.state,
1075
- failures: this.failures,
1076
- successes: this.successes,
1077
- lastFailureAt: this.lastFailureAt,
1078
- lastSuccessAt: this.lastSuccessAt,
1079
- openedAt: this.openedAt
1080
- };
1081
- }
1082
- manualReset() {
1083
- this.transitionTo("CLOSED");
1084
- this.reset();
1085
- }
1086
- getState() {
1087
- return this.state;
1088
- }
1089
- getName() {
1090
- return this.name;
1091
- }
1092
- }
1093
-
1094
- // src/send/WebhookDispatcher.ts
1095
- var DEFAULT_RETRY_CONFIG = {
1096
- maxAttempts: 3,
1097
- initialDelay: 1000,
1098
- backoffMultiplier: 2,
1099
- maxDelay: 300000,
1100
- retryableStatuses: [408, 429, 500, 502, 503, 504]
1101
- };
1102
-
1103
- class WebhookDispatcher {
1104
- secret;
1105
- retryConfig;
1106
- timeout;
1107
- userAgent;
1108
- dlq;
1109
- metrics = new NoopMetricsProvider;
1110
- tracer = new NoopTracer;
1111
- logger;
1112
- circuitBreakers = new Map;
1113
- circuitBreakerConfig;
1114
- constructor(config) {
1115
- this.secret = config.secret;
1116
- this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry };
1117
- this.timeout = config.timeout ?? 30000;
1118
- this.userAgent = config.userAgent ?? "Gravito-Echo/1.0";
1119
- this.circuitBreakerConfig = config.circuitBreaker;
1120
- }
1121
- setDeadLetterQueue(dlq) {
1122
- this.dlq = dlq;
1123
- return this;
1124
- }
1125
- setMetrics(metrics) {
1126
- this.metrics = metrics;
1127
- return this;
1128
- }
1129
- setTracer(tracer) {
1130
- this.tracer = tracer;
1131
- return this;
1132
- }
1133
- setLogger(logger) {
1134
- this.logger = logger;
1135
- return this;
1136
- }
1137
- getCircuitBreaker(url) {
1138
- if (!this.circuitBreakerConfig?.enabled) {
1139
- return;
1140
- }
1141
- const host = new URL(url).host;
1142
- if (!this.circuitBreakers.has(host)) {
1143
- const breaker = new CircuitBreaker(host, {
1144
- ...this.circuitBreakerConfig,
1145
- onOpen: (name) => {
1146
- this.logger?.warn(`Circuit breaker OPEN for ${name}`, {
1147
- component: "dispatcher",
1148
- host: name
1149
- });
1150
- this.circuitBreakerConfig?.onOpen?.(name);
1151
- },
1152
- onHalfOpen: (name) => {
1153
- this.logger?.info(`Circuit breaker HALF_OPEN for ${name}`, {
1154
- component: "dispatcher",
1155
- host: name
1156
- });
1157
- this.circuitBreakerConfig?.onHalfOpen?.(name);
1158
- },
1159
- onClose: (name) => {
1160
- this.logger?.info(`Circuit breaker CLOSED for ${name}`, {
1161
- component: "dispatcher",
1162
- host: name
1163
- });
1164
- this.circuitBreakerConfig?.onClose?.(name);
1165
- }
1166
- });
1167
- this.circuitBreakers.set(host, breaker);
1168
- }
1169
- return this.circuitBreakers.get(host);
1170
- }
1171
- getCircuitBreakerMetrics(url) {
1172
- const host = new URL(url).host;
1173
- const breaker = this.circuitBreakers.get(host);
1174
- return breaker ? breaker.getMetrics() : null;
1175
- }
1176
- resetCircuitBreaker(url) {
1177
- const host = new URL(url).host;
1178
- const breaker = this.circuitBreakers.get(host);
1179
- breaker?.manualReset();
1180
- }
1181
- async dispatch(payload) {
1182
- return this.tracer.withSpan("echo.dispatch_webhook", async (span) => {
1183
- const startTime = performance.now();
1184
- const labels = { event_type: payload.event };
1185
- span.setAttributes({
1186
- "echo.direction": "outgoing",
1187
- "echo.event": payload.event,
1188
- "echo.url": payload.url,
1189
- "http.method": "POST",
1190
- "http.url": payload.url
1191
- });
1192
- const result = await this.dispatchInternal(payload);
1193
- const duration = (performance.now() - startTime) / 1000;
1194
- labels.status = result.success ? "success" : "failure";
1195
- labels.status_code = result.statusCode?.toString();
1196
- this.metrics.increment(EchoMetrics.OUTGOING_TOTAL, labels);
1197
- this.metrics.histogram(EchoMetrics.OUTGOING_DURATION, duration, labels);
1198
- if (result.attempt > 1) {
1199
- this.metrics.increment(EchoMetrics.OUTGOING_RETRIES, {
1200
- event_type: payload.event
1201
- });
1202
- }
1203
- span.setAttributes({
1204
- "echo.success": result.success,
1205
- "echo.attempt": result.attempt,
1206
- "echo.duration_ms": result.duration
1207
- });
1208
- if (result.statusCode) {
1209
- span.setAttribute("http.status_code", result.statusCode);
1210
- }
1211
- if (result.success) {
1212
- span.setStatus({ code: 1 /* OK */ });
1213
- } else {
1214
- span.setStatus({
1215
- code: 2 /* ERROR */,
1216
- message: result.error
1217
- });
1218
- this.metrics.increment(EchoMetrics.OUTGOING_FAILURES, {
1219
- event_type: payload.event,
1220
- error_type: this.categorizeError(result)
1221
- });
1222
- }
1223
- return result;
1224
- });
1225
- }
1226
- async dispatchInternal(payload) {
1227
- let lastResult = null;
1228
- for (let attempt = 1;attempt <= this.retryConfig.maxAttempts; attempt++) {
1229
- const result = await this.attemptDelivery(payload, attempt);
1230
- lastResult = result;
1231
- if (result.success) {
1232
- return result;
1233
- }
1234
- if (attempt < this.retryConfig.maxAttempts) {
1235
- const shouldRetry = this.shouldRetry(result);
1236
- if (shouldRetry) {
1237
- const delay = this.calculateDelay(attempt);
1238
- await this.sleep(delay);
1239
- continue;
1240
- }
1241
- }
1242
- break;
1243
- }
1244
- if (this.dlq && lastResult && !lastResult.success) {
1245
- await this.dlq.enqueue({
1246
- type: "outgoing",
1247
- originalEvent: {
1248
- url: payload.url,
1249
- event: payload.event,
1250
- payload: payload.data,
1251
- createdAt: new Date,
1252
- status: "failed",
1253
- attempts: []
1254
- },
1255
- failureReason: lastResult.error ?? "Unknown error",
1256
- failedAt: new Date,
1257
- retryCount: lastResult.attempt
1258
- });
1259
- }
1260
- if (!lastResult) {
1261
- throw new Error("No delivery attempts were made");
1262
- }
1263
- return lastResult;
1264
- }
1265
- async dispatchBatch(payloads, options = {}) {
1266
- const concurrency = options.concurrency ?? 5;
1267
- const stopOnFirstFailure = options.stopOnFirstFailure ?? false;
1268
- const results = [];
1269
- let succeeded = 0;
1270
- let failed = 0;
1271
- let stopped = false;
1272
- for (let i = 0;i < payloads.length && !stopped; i += concurrency) {
1273
- const chunk = payloads.slice(i, i + concurrency);
1274
- const chunkResults = await Promise.all(chunk.map(async (payload) => {
1275
- if (stopped) {
1276
- return {
1277
- payload,
1278
- result: {
1279
- success: false,
1280
- attempt: 0,
1281
- duration: 0,
1282
- deliveredAt: new Date,
1283
- error: "Batch dispatch stopped"
1284
- }
1285
- };
1286
- }
1287
- const result = await this.dispatch(payload);
1288
- if (result.success) {
1289
- succeeded++;
1290
- } else {
1291
- failed++;
1292
- if (stopOnFirstFailure) {
1293
- stopped = true;
1294
- }
1295
- }
1296
- return { payload, result };
1297
- }));
1298
- results.push(...chunkResults);
1299
- }
1300
- return {
1301
- total: payloads.length,
1302
- succeeded,
1303
- failed,
1304
- results
1305
- };
1306
- }
1307
- async retryFromDlq(id) {
1308
- if (!this.dlq) {
1309
- return null;
1310
- }
1311
- const events = await this.dlq.peek(100);
1312
- const event = events.find((e) => e.id === id);
1313
- if (!event || event.type !== "outgoing") {
1314
- return null;
1315
- }
1316
- const outgoing = event.originalEvent;
1317
- const result = await this.dispatch({
1318
- url: outgoing.url,
1319
- event: outgoing.event,
1320
- data: outgoing.payload
1321
- });
1322
- if (result.success) {
1323
- await this.dlq.dequeue(id);
1324
- } else {
1325
- event.retryCount++;
1326
- event.lastRetryAt = new Date;
1327
- }
1328
- return result;
1329
- }
1330
- async attemptDelivery(payload, attempt) {
1331
- const breaker = this.getCircuitBreaker(payload.url);
1332
- const deliveryFn = async () => {
1333
- const startTime = Date.now();
1334
- const timestamp = Math.floor(Date.now() / 1000);
1335
- const webhookId = payload.id ?? crypto.randomUUID();
1336
- try {
1337
- const body = JSON.stringify({
1338
- id: webhookId,
1339
- type: payload.event,
1340
- timestamp,
1341
- data: payload.data
1342
- });
1343
- const signedPayload = `${timestamp}.${body}`;
1344
- const signature = await computeHmacSha256(signedPayload, this.secret);
1345
- const controller = new AbortController;
1346
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1347
- try {
1348
- const response = await fetch(payload.url, {
1349
- method: "POST",
1350
- headers: {
1351
- "Content-Type": "application/json",
1352
- "User-Agent": this.userAgent,
1353
- "X-Webhook-ID": webhookId,
1354
- "X-Webhook-Timestamp": String(timestamp),
1355
- "X-Webhook-Signature": `t=${timestamp},v1=${signature}`
1356
- },
1357
- body,
1358
- signal: controller.signal
1359
- });
1360
- clearTimeout(timeoutId);
1361
- const duration = Date.now() - startTime;
1362
- const responseBody = await response.text();
1363
- return {
1364
- success: response.ok,
1365
- statusCode: response.status,
1366
- body: responseBody,
1367
- attempt,
1368
- duration,
1369
- deliveredAt: new Date,
1370
- error: response.ok ? undefined : `HTTP ${response.status}`
1371
- };
1372
- } finally {
1373
- clearTimeout(timeoutId);
1374
- }
1375
- } catch (error) {
1376
- const duration = Date.now() - startTime;
1377
- return {
1378
- success: false,
1379
- attempt,
1380
- duration,
1381
- deliveredAt: new Date,
1382
- error: error instanceof Error ? error.message : "Unknown error"
1383
- };
1384
- }
1385
- };
1386
- if (breaker) {
1387
- try {
1388
- return await breaker.execute(deliveryFn);
1389
- } catch (error) {
1390
- if (error instanceof Error && error.message.includes("Circuit breaker is OPEN")) {
1391
- return {
1392
- success: false,
1393
- attempt,
1394
- duration: 0,
1395
- deliveredAt: new Date,
1396
- error: error.message
1397
- };
1398
- }
1399
- throw error;
1400
- }
1401
- }
1402
- return await deliveryFn();
1403
- }
1404
- shouldRetry(result) {
1405
- if (!result.statusCode) {
1406
- return true;
1407
- }
1408
- return this.retryConfig.retryableStatuses.includes(result.statusCode);
1409
- }
1410
- calculateDelay(attempt) {
1411
- const delay = this.retryConfig.initialDelay * this.retryConfig.backoffMultiplier ** (attempt - 1);
1412
- return Math.min(delay, this.retryConfig.maxDelay);
1413
- }
1414
- categorizeError(result) {
1415
- if (!result.statusCode) {
1416
- return "network_error";
1417
- }
1418
- if (result.statusCode >= 500) {
1419
- return "server_error";
1420
- }
1421
- if (result.statusCode >= 400) {
1422
- return "client_error";
1423
- }
1424
- return "other";
1425
- }
1426
- sleep(ms) {
1427
- return new Promise((resolve) => setTimeout(resolve, ms));
1428
- }
1429
- }
1430
-
1431
- // src/OrbitEcho.ts
1432
- class OrbitEcho {
1433
- receiver;
1434
- dispatcher;
1435
- echoConfig;
1436
- keyRotationManager;
1437
- constructor(config = {}) {
1438
- this.echoConfig = config;
1439
- this.receiver = new WebhookReceiver;
1440
- if (config.keyRotation?.enabled) {
1441
- this.keyRotationManager = new KeyRotationManager(config.keyRotation);
1442
- this.receiver.setKeyRotationManager(this.keyRotationManager);
1443
- }
1444
- if (config.providers) {
1445
- for (const [name, providerConfig] of Object.entries(config.providers)) {
1446
- const rotationConfig = providerConfig;
1447
- if (rotationConfig.keys && rotationConfig.keys.length > 0 && this.keyRotationManager) {
1448
- this.receiver.registerProviderWithRotation(name, rotationConfig.keys, {
1449
- type: providerConfig.name,
1450
- tolerance: providerConfig.tolerance
1451
- });
1452
- } else {
1453
- this.receiver.registerProvider(name, providerConfig.secret, {
1454
- type: providerConfig.name,
1455
- tolerance: providerConfig.tolerance
1456
- });
1457
- }
1458
- }
1459
- }
1460
- if (config.dispatcher) {
1461
- this.dispatcher = new WebhookDispatcher(config.dispatcher);
1462
- if (config.deadLetterQueue) {
1463
- this.dispatcher.setDeadLetterQueue(config.deadLetterQueue);
1464
- }
1465
- }
1466
- if (config.store) {
1467
- this.receiver.setStore(config.store);
1468
- }
1469
- if (config.observability) {
1470
- const { metrics, tracer, logger } = config.observability;
1471
- if (metrics) {
1472
- this.receiver.setMetrics(metrics);
1473
- this.dispatcher?.setMetrics(metrics);
1474
- }
1475
- if (tracer) {
1476
- this.receiver.setTracer(tracer);
1477
- this.dispatcher?.setTracer(tracer);
1478
- }
1479
- if (logger) {
1480
- this.receiver.setLogger(logger);
1481
- }
1482
- }
1483
- }
1484
- install(core) {
1485
- if (this.echoConfig.requestBuffer?.enabled !== false) {
1486
- const bufferMiddleware = createRequestBufferMiddleware(this.echoConfig.requestBuffer);
1487
- core.adapter.use("*", bufferMiddleware);
1488
- core.logger.info("[OrbitEcho] Request buffer middleware installed");
1489
- }
1490
- core.container.instance("echo", this);
1491
- core.container.instance("echo.receiver", this.receiver);
1492
- if (this.dispatcher) {
1493
- core.container.instance("echo.dispatcher", this.dispatcher);
1494
- }
1495
- core.adapter.use("*", async (c, next) => {
1496
- c.set("echo", this);
1497
- return await next();
1498
- });
1499
- core.logger.info("[OrbitEcho] Webhook receiver and dispatcher registered");
1500
- }
1501
- getReceiver() {
1502
- return this.receiver;
1503
- }
1504
- getDispatcher() {
1505
- return this.dispatcher;
1506
- }
1507
- getConfig() {
1508
- return this.echoConfig;
1509
- }
1510
- getKeyRotationManager() {
1511
- return this.keyRotationManager;
1512
- }
1513
- async rotateProviderKey(providerName, newKey) {
1514
- if (!this.keyRotationManager) {
1515
- throw new Error("Key rotation is not enabled");
1516
- }
1517
- await this.keyRotationManager.rotatePrimaryKey(providerName, newKey);
1518
- }
1519
- }
1520
- // src/replay/WebhookReplayService.ts
1521
- class WebhookReplayService {
1522
- store;
1523
- dispatcher;
1524
- constructor(store, dispatcher) {
1525
- this.store = store;
1526
- this.dispatcher = dispatcher;
1527
- }
1528
- async replay(options) {
1529
- const events = await this.store.queryEvents({
1530
- direction: "outgoing",
1531
- provider: options.provider,
1532
- eventType: options.eventType,
1533
- from: options.timeRange?.from,
1534
- to: options.timeRange?.to
1535
- });
1536
- const targetEvents = options.eventIds ? events.filter((e) => options.eventIds?.includes(e.id ?? "")) : events;
1537
- const result = {
1538
- total: targetEvents.length,
1539
- replayed: 0,
1540
- skipped: 0,
1541
- failed: 0,
1542
- events: []
1543
- };
1544
- for (const event of targetEvents) {
1545
- if (event.direction !== "outgoing") {
1546
- result.skipped++;
1547
- result.events.push({
1548
- eventId: event.id ?? "unknown",
1549
- status: "skipped",
1550
- error: "Not an outgoing event"
1551
- });
1552
- continue;
1553
- }
1554
- const outgoing = event;
1555
- if (options.dryRun) {
1556
- result.replayed++;
1557
- result.events.push({
1558
- eventId: event.id ?? "unknown",
1559
- status: "replayed"
1560
- });
1561
- continue;
1562
- }
1563
- try {
1564
- const dispatchResult = await this.dispatcher.dispatch({
1565
- url: options.targetUrl ?? outgoing.url,
1566
- event: outgoing.event,
1567
- data: outgoing.payload
1568
- });
1569
- if (dispatchResult.success) {
1570
- result.replayed++;
1571
- result.events.push({
1572
- eventId: event.id ?? "unknown",
1573
- status: "replayed",
1574
- result: dispatchResult
1575
- });
1576
- } else {
1577
- result.failed++;
1578
- result.events.push({
1579
- eventId: event.id ?? "unknown",
1580
- status: "failed",
1581
- result: dispatchResult,
1582
- error: dispatchResult.error
1583
- });
1584
- }
1585
- } catch (error) {
1586
- result.failed++;
1587
- result.events.push({
1588
- eventId: event.id ?? "unknown",
1589
- status: "failed",
1590
- error: String(error)
1591
- });
1592
- }
1593
- }
1594
- return result;
1595
- }
1596
- }
1597
2
  export {
1598
3
  validateTimestamp,
1599
4
  timingSafeEqual,
@@ -1626,4 +31,4 @@ export {
1626
31
  BaseProvider
1627
32
  };
1628
33
 
1629
- //# debugId=58FA12C7769E147864756E2164756E21
34
+ //# debugId=AEF7E6966FAD4D5C64756E2164756E21