@gravito/echo 3.1.0 → 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 (245) 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 -301
  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 -48
  24. package/dist/atlas/src/connection/Connection.d.ts +0 -108
  25. package/dist/atlas/src/connection/ConnectionManager.d.ts +0 -111
  26. package/dist/atlas/src/drivers/BunSQLDriver.d.ts +0 -32
  27. package/dist/atlas/src/drivers/BunSQLPreparedStatement.d.ts +0 -118
  28. package/dist/atlas/src/drivers/MongoDBDriver.d.ts +0 -36
  29. package/dist/atlas/src/drivers/MySQLDriver.d.ts +0 -66
  30. package/dist/atlas/src/drivers/PostgresDriver.d.ts +0 -83
  31. package/dist/atlas/src/drivers/RedisDriver.d.ts +0 -43
  32. package/dist/atlas/src/drivers/SQLiteDriver.d.ts +0 -45
  33. package/dist/atlas/src/drivers/types.d.ts +0 -260
  34. package/dist/atlas/src/errors/index.d.ts +0 -45
  35. package/dist/atlas/src/grammar/Grammar.d.ts +0 -342
  36. package/dist/atlas/src/grammar/MongoGrammar.d.ts +0 -47
  37. package/dist/atlas/src/grammar/MySQLGrammar.d.ts +0 -54
  38. package/dist/atlas/src/grammar/NullGrammar.d.ts +0 -35
  39. package/dist/atlas/src/grammar/PostgresGrammar.d.ts +0 -62
  40. package/dist/atlas/src/grammar/SQLiteGrammar.d.ts +0 -32
  41. package/dist/atlas/src/index.d.ts +0 -67
  42. package/dist/atlas/src/migration/Migration.d.ts +0 -64
  43. package/dist/atlas/src/migration/MigrationRepository.d.ts +0 -65
  44. package/dist/atlas/src/migration/Migrator.d.ts +0 -110
  45. package/dist/atlas/src/migration/index.d.ts +0 -6
  46. package/dist/atlas/src/observability/AtlasMetrics.d.ts +0 -11
  47. package/dist/atlas/src/observability/AtlasObservability.d.ts +0 -15
  48. package/dist/atlas/src/observability/AtlasTracer.d.ts +0 -12
  49. package/dist/atlas/src/observability/index.d.ts +0 -9
  50. package/dist/atlas/src/orm/index.d.ts +0 -5
  51. package/dist/atlas/src/orm/model/DirtyTracker.d.ts +0 -121
  52. package/dist/atlas/src/orm/model/Model.d.ts +0 -449
  53. package/dist/atlas/src/orm/model/ModelRegistry.d.ts +0 -20
  54. package/dist/atlas/src/orm/model/concerns/HasAttributes.d.ts +0 -136
  55. package/dist/atlas/src/orm/model/concerns/HasEvents.d.ts +0 -36
  56. package/dist/atlas/src/orm/model/concerns/HasPersistence.d.ts +0 -87
  57. package/dist/atlas/src/orm/model/concerns/HasRelationships.d.ts +0 -117
  58. package/dist/atlas/src/orm/model/concerns/HasSerialization.d.ts +0 -64
  59. package/dist/atlas/src/orm/model/concerns/applyMixins.d.ts +0 -15
  60. package/dist/atlas/src/orm/model/concerns/index.d.ts +0 -12
  61. package/dist/atlas/src/orm/model/decorators.d.ts +0 -109
  62. package/dist/atlas/src/orm/model/errors.d.ts +0 -52
  63. package/dist/atlas/src/orm/model/index.d.ts +0 -10
  64. package/dist/atlas/src/orm/model/relationships.d.ts +0 -207
  65. package/dist/atlas/src/orm/model/types.d.ts +0 -12
  66. package/dist/atlas/src/orm/schema/SchemaRegistry.d.ts +0 -123
  67. package/dist/atlas/src/orm/schema/SchemaSniffer.d.ts +0 -54
  68. package/dist/atlas/src/orm/schema/index.d.ts +0 -6
  69. package/dist/atlas/src/orm/schema/types.d.ts +0 -85
  70. package/dist/atlas/src/query/Expression.d.ts +0 -60
  71. package/dist/atlas/src/query/NPlusOneDetector.d.ts +0 -10
  72. package/dist/atlas/src/query/QueryBuilder.d.ts +0 -573
  73. package/dist/atlas/src/query/clauses/GroupByClause.d.ts +0 -51
  74. package/dist/atlas/src/query/clauses/HavingClause.d.ts +0 -70
  75. package/dist/atlas/src/query/clauses/JoinClause.d.ts +0 -87
  76. package/dist/atlas/src/query/clauses/LimitClause.d.ts +0 -82
  77. package/dist/atlas/src/query/clauses/OrderByClause.d.ts +0 -69
  78. package/dist/atlas/src/query/clauses/SelectClause.d.ts +0 -71
  79. package/dist/atlas/src/query/clauses/WhereClause.d.ts +0 -167
  80. package/dist/atlas/src/query/clauses/index.d.ts +0 -11
  81. package/dist/atlas/src/schema/Blueprint.d.ts +0 -276
  82. package/dist/atlas/src/schema/ColumnDefinition.d.ts +0 -154
  83. package/dist/atlas/src/schema/ForeignKeyDefinition.d.ts +0 -37
  84. package/dist/atlas/src/schema/Schema.d.ts +0 -131
  85. package/dist/atlas/src/schema/grammars/MySQLSchemaGrammar.d.ts +0 -23
  86. package/dist/atlas/src/schema/grammars/PostgresSchemaGrammar.d.ts +0 -26
  87. package/dist/atlas/src/schema/grammars/SQLiteSchemaGrammar.d.ts +0 -28
  88. package/dist/atlas/src/schema/grammars/SchemaGrammar.d.ts +0 -97
  89. package/dist/atlas/src/schema/grammars/index.d.ts +0 -7
  90. package/dist/atlas/src/schema/index.d.ts +0 -8
  91. package/dist/atlas/src/seed/Factory.d.ts +0 -90
  92. package/dist/atlas/src/seed/Seeder.d.ts +0 -28
  93. package/dist/atlas/src/seed/SeederRunner.d.ts +0 -74
  94. package/dist/atlas/src/seed/index.d.ts +0 -6
  95. package/dist/atlas/src/types/index.d.ts +0 -1100
  96. package/dist/atlas/src/utils/levenshtein.d.ts +0 -9
  97. package/dist/core/src/Application.d.ts +0 -215
  98. package/dist/core/src/CommandKernel.d.ts +0 -33
  99. package/dist/core/src/ConfigManager.d.ts +0 -26
  100. package/dist/core/src/Container.d.ts +0 -108
  101. package/dist/core/src/ErrorHandler.d.ts +0 -63
  102. package/dist/core/src/Event.d.ts +0 -5
  103. package/dist/core/src/EventManager.d.ts +0 -123
  104. package/dist/core/src/GlobalErrorHandlers.d.ts +0 -47
  105. package/dist/core/src/GravitoServer.d.ts +0 -28
  106. package/dist/core/src/HookManager.d.ts +0 -496
  107. package/dist/core/src/Listener.d.ts +0 -4
  108. package/dist/core/src/Logger.d.ts +0 -20
  109. package/dist/core/src/PlanetCore.d.ts +0 -289
  110. package/dist/core/src/Route.d.ts +0 -36
  111. package/dist/core/src/Router.d.ts +0 -284
  112. package/dist/core/src/ServiceProvider.d.ts +0 -156
  113. package/dist/core/src/adapters/GravitoEngineAdapter.d.ts +0 -27
  114. package/dist/core/src/adapters/PhotonAdapter.d.ts +0 -171
  115. package/dist/core/src/adapters/bun/BunContext.d.ts +0 -45
  116. package/dist/core/src/adapters/bun/BunNativeAdapter.d.ts +0 -31
  117. package/dist/core/src/adapters/bun/BunRequest.d.ts +0 -31
  118. package/dist/core/src/adapters/bun/RadixNode.d.ts +0 -19
  119. package/dist/core/src/adapters/bun/RadixRouter.d.ts +0 -31
  120. package/dist/core/src/adapters/bun/types.d.ts +0 -20
  121. package/dist/core/src/adapters/photon-types.d.ts +0 -73
  122. package/dist/core/src/adapters/types.d.ts +0 -235
  123. package/dist/core/src/engine/AOTRouter.d.ts +0 -124
  124. package/dist/core/src/engine/FastContext.d.ts +0 -100
  125. package/dist/core/src/engine/Gravito.d.ts +0 -137
  126. package/dist/core/src/engine/MinimalContext.d.ts +0 -79
  127. package/dist/core/src/engine/analyzer.d.ts +0 -27
  128. package/dist/core/src/engine/constants.d.ts +0 -23
  129. package/dist/core/src/engine/index.d.ts +0 -26
  130. package/dist/core/src/engine/path.d.ts +0 -26
  131. package/dist/core/src/engine/pool.d.ts +0 -83
  132. package/dist/core/src/engine/types.d.ts +0 -143
  133. package/dist/core/src/events/CircuitBreaker.d.ts +0 -229
  134. package/dist/core/src/events/DeadLetterQueue.d.ts +0 -145
  135. package/dist/core/src/events/EventBackend.d.ts +0 -11
  136. package/dist/core/src/events/EventOptions.d.ts +0 -109
  137. package/dist/core/src/events/EventPriorityQueue.d.ts +0 -202
  138. package/dist/core/src/events/IdempotencyCache.d.ts +0 -60
  139. package/dist/core/src/events/index.d.ts +0 -14
  140. package/dist/core/src/events/observability/EventMetrics.d.ts +0 -132
  141. package/dist/core/src/events/observability/EventTracer.d.ts +0 -68
  142. package/dist/core/src/events/observability/EventTracing.d.ts +0 -161
  143. package/dist/core/src/events/observability/OTelEventMetrics.d.ts +0 -240
  144. package/dist/core/src/events/observability/ObservableHookManager.d.ts +0 -108
  145. package/dist/core/src/events/observability/index.d.ts +0 -20
  146. package/dist/core/src/events/observability/metrics-types.d.ts +0 -16
  147. package/dist/core/src/events/types.d.ts +0 -75
  148. package/dist/core/src/exceptions/AuthenticationException.d.ts +0 -8
  149. package/dist/core/src/exceptions/AuthorizationException.d.ts +0 -8
  150. package/dist/core/src/exceptions/CircularDependencyException.d.ts +0 -9
  151. package/dist/core/src/exceptions/GravitoException.d.ts +0 -23
  152. package/dist/core/src/exceptions/HttpException.d.ts +0 -9
  153. package/dist/core/src/exceptions/ModelNotFoundException.d.ts +0 -10
  154. package/dist/core/src/exceptions/ValidationException.d.ts +0 -22
  155. package/dist/core/src/exceptions/index.d.ts +0 -7
  156. package/dist/core/src/helpers/Arr.d.ts +0 -19
  157. package/dist/core/src/helpers/Str.d.ts +0 -23
  158. package/dist/core/src/helpers/data.d.ts +0 -25
  159. package/dist/core/src/helpers/errors.d.ts +0 -34
  160. package/dist/core/src/helpers/response.d.ts +0 -41
  161. package/dist/core/src/helpers.d.ts +0 -338
  162. package/dist/core/src/http/CookieJar.d.ts +0 -51
  163. package/dist/core/src/http/cookie.d.ts +0 -29
  164. package/dist/core/src/http/middleware/BodySizeLimit.d.ts +0 -16
  165. package/dist/core/src/http/middleware/Cors.d.ts +0 -24
  166. package/dist/core/src/http/middleware/Csrf.d.ts +0 -23
  167. package/dist/core/src/http/middleware/HeaderTokenGate.d.ts +0 -28
  168. package/dist/core/src/http/middleware/SecurityHeaders.d.ts +0 -29
  169. package/dist/core/src/http/middleware/ThrottleRequests.d.ts +0 -18
  170. package/dist/core/src/http/types.d.ts +0 -355
  171. package/dist/core/src/index.d.ts +0 -76
  172. package/dist/core/src/instrumentation/index.d.ts +0 -35
  173. package/dist/core/src/instrumentation/opentelemetry.d.ts +0 -178
  174. package/dist/core/src/instrumentation/types.d.ts +0 -182
  175. package/dist/core/src/reliability/DeadLetterQueueManager.d.ts +0 -316
  176. package/dist/core/src/reliability/RetryPolicy.d.ts +0 -217
  177. package/dist/core/src/reliability/index.d.ts +0 -6
  178. package/dist/core/src/router/ControllerDispatcher.d.ts +0 -12
  179. package/dist/core/src/router/RequestValidator.d.ts +0 -20
  180. package/dist/core/src/runtime.d.ts +0 -119
  181. package/dist/core/src/security/Encrypter.d.ts +0 -33
  182. package/dist/core/src/security/Hasher.d.ts +0 -29
  183. package/dist/core/src/testing/HttpTester.d.ts +0 -39
  184. package/dist/core/src/testing/TestResponse.d.ts +0 -78
  185. package/dist/core/src/testing/index.d.ts +0 -2
  186. package/dist/core/src/types/events.d.ts +0 -94
  187. package/dist/echo/src/OrbitEcho.d.ts +0 -115
  188. package/dist/echo/src/index.d.ts +0 -64
  189. package/dist/echo/src/providers/GenericProvider.d.ts +0 -53
  190. package/dist/echo/src/providers/GitHubProvider.d.ts +0 -35
  191. package/dist/echo/src/providers/StripeProvider.d.ts +0 -38
  192. package/dist/echo/src/providers/index.d.ts +0 -14
  193. package/dist/echo/src/receive/SignatureValidator.d.ts +0 -67
  194. package/dist/echo/src/receive/WebhookReceiver.d.ts +0 -185
  195. package/dist/echo/src/receive/index.d.ts +0 -2
  196. package/dist/echo/src/send/WebhookDispatcher.d.ts +0 -198
  197. package/dist/echo/src/send/index.d.ts +0 -1
  198. package/dist/echo/src/types.d.ts +0 -756
  199. package/dist/index.d.ts.map +0 -1
  200. package/dist/photon/src/index.d.ts +0 -84
  201. package/dist/photon/src/middleware/binary.d.ts +0 -31
  202. package/dist/photon/src/middleware/htmx.d.ts +0 -39
  203. package/dist/photon/src/middleware/ratelimit.d.ts +0 -157
  204. package/dist/photon/src/openapi.d.ts +0 -19
  205. package/dist/providers/GenericProvider.d.ts.map +0 -1
  206. package/dist/providers/GitHubProvider.d.ts.map +0 -1
  207. package/dist/providers/StripeProvider.d.ts.map +0 -1
  208. package/dist/providers/index.d.ts.map +0 -1
  209. package/dist/receive/SignatureValidator.d.ts.map +0 -1
  210. package/dist/receive/WebhookReceiver.d.ts.map +0 -1
  211. package/dist/receive/index.d.ts.map +0 -1
  212. package/dist/send/WebhookDispatcher.d.ts.map +0 -1
  213. package/dist/send/index.d.ts.map +0 -1
  214. package/dist/types.d.ts.map +0 -1
  215. /package/dist/{echo/src/dlq → dlq}/DeadLetterQueue.d.ts +0 -0
  216. /package/dist/{echo/src/dlq → dlq}/MemoryDeadLetterQueue.d.ts +0 -0
  217. /package/dist/{echo/src/dlq → dlq}/index.d.ts +0 -0
  218. /package/dist/{echo/src/middleware → middleware}/index.d.ts +0 -0
  219. /package/dist/{echo/src/observability → observability}/index.d.ts +0 -0
  220. /package/dist/{echo/src/observability → observability}/logging/ConsoleEchoLogger.d.ts +0 -0
  221. /package/dist/{echo/src/observability → observability}/logging/EchoLogger.d.ts +0 -0
  222. /package/dist/{echo/src/observability → observability}/logging/index.d.ts +0 -0
  223. /package/dist/{echo/src/observability → observability}/metrics/MetricsProvider.d.ts +0 -0
  224. /package/dist/{echo/src/observability → observability}/metrics/NoopMetricsProvider.d.ts +0 -0
  225. /package/dist/{echo/src/observability → observability}/metrics/PrometheusMetricsProvider.d.ts +0 -0
  226. /package/dist/{echo/src/observability → observability}/metrics/index.d.ts +0 -0
  227. /package/dist/{echo/src/observability → observability}/tracing/NoopTracer.d.ts +0 -0
  228. /package/dist/{echo/src/observability → observability}/tracing/Tracer.d.ts +0 -0
  229. /package/dist/{echo/src/observability → observability}/tracing/index.d.ts +0 -0
  230. /package/dist/{echo/src/providers → providers}/LinearProvider.d.ts +0 -0
  231. /package/dist/{echo/src/providers → providers}/PaddleProvider.d.ts +0 -0
  232. /package/dist/{echo/src/providers → providers}/ShopifyProvider.d.ts +0 -0
  233. /package/dist/{echo/src/providers → providers}/SlackProvider.d.ts +0 -0
  234. /package/dist/{echo/src/providers → providers}/TwilioProvider.d.ts +0 -0
  235. /package/dist/{echo/src/providers → providers}/base/BaseProvider.d.ts +0 -0
  236. /package/dist/{echo/src/providers → providers}/base/HeaderUtils.d.ts +0 -0
  237. /package/dist/{echo/src/replay → replay}/WebhookReplayService.d.ts +0 -0
  238. /package/dist/{echo/src/replay → replay}/index.d.ts +0 -0
  239. /package/dist/{echo/src/resilience → resilience}/CircuitBreaker.d.ts +0 -0
  240. /package/dist/{echo/src/resilience → resilience}/index.d.ts +0 -0
  241. /package/dist/{echo/src/rotation → rotation}/KeyRotationManager.d.ts +0 -0
  242. /package/dist/{echo/src/rotation → rotation}/index.d.ts +0 -0
  243. /package/dist/{echo/src/storage → storage}/MemoryWebhookStore.d.ts +0 -0
  244. /package/dist/{echo/src/storage → storage}/WebhookStore.d.ts +0 -0
  245. /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