@cloudwerk/trigger 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +966 -0
- package/dist/index.js +1130 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +193 -0
- package/dist/testing.js +181 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-w3ZeMXZ9.d.ts +510 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var TriggerError = class extends Error {
|
|
3
|
+
/** Error code for programmatic handling */
|
|
4
|
+
code;
|
|
5
|
+
constructor(code, message, options) {
|
|
6
|
+
super(message, options);
|
|
7
|
+
this.name = "TriggerError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var TriggerConfigError = class extends TriggerError {
|
|
12
|
+
/** The configuration field that is invalid */
|
|
13
|
+
field;
|
|
14
|
+
constructor(message, field) {
|
|
15
|
+
super("CONFIG_ERROR", message);
|
|
16
|
+
this.name = "TriggerConfigError";
|
|
17
|
+
this.field = field;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var TriggerNoHandlerError = class extends TriggerError {
|
|
21
|
+
constructor(triggerName) {
|
|
22
|
+
super(
|
|
23
|
+
"NO_HANDLER",
|
|
24
|
+
`Trigger '${triggerName}' must define a handle() function`
|
|
25
|
+
);
|
|
26
|
+
this.name = "TriggerNoHandlerError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var TriggerInvalidSourceError = class extends TriggerError {
|
|
30
|
+
/** The source type that is invalid */
|
|
31
|
+
sourceType;
|
|
32
|
+
constructor(message, sourceType) {
|
|
33
|
+
super("INVALID_SOURCE", message);
|
|
34
|
+
this.name = "TriggerInvalidSourceError";
|
|
35
|
+
this.sourceType = sourceType;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var TriggerInvalidCronError = class extends TriggerError {
|
|
39
|
+
/** The invalid cron expression */
|
|
40
|
+
cron;
|
|
41
|
+
constructor(cron, reason) {
|
|
42
|
+
super(
|
|
43
|
+
"INVALID_CRON",
|
|
44
|
+
`Invalid cron expression '${cron}'${reason ? `: ${reason}` : ""}`
|
|
45
|
+
);
|
|
46
|
+
this.name = "TriggerInvalidCronError";
|
|
47
|
+
this.cron = cron;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var TriggerInvalidWebhookPathError = class extends TriggerError {
|
|
51
|
+
/** The invalid path */
|
|
52
|
+
path;
|
|
53
|
+
constructor(path, reason) {
|
|
54
|
+
super(
|
|
55
|
+
"INVALID_WEBHOOK_PATH",
|
|
56
|
+
`Invalid webhook path '${path}'${reason ? `: ${reason}` : ""}`
|
|
57
|
+
);
|
|
58
|
+
this.name = "TriggerInvalidWebhookPathError";
|
|
59
|
+
this.path = path;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var TriggerContextError = class extends TriggerError {
|
|
63
|
+
constructor() {
|
|
64
|
+
super(
|
|
65
|
+
"CONTEXT_ERROR",
|
|
66
|
+
"Trigger context accessed outside of trigger execution. Context is only available during trigger handling."
|
|
67
|
+
);
|
|
68
|
+
this.name = "TriggerContextError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var TriggerNotFoundError = class extends TriggerError {
|
|
72
|
+
/** The trigger name that was not found */
|
|
73
|
+
triggerName;
|
|
74
|
+
/** Available trigger names */
|
|
75
|
+
availableTriggers;
|
|
76
|
+
constructor(triggerName, availableTriggers) {
|
|
77
|
+
const available = availableTriggers.length > 0 ? `Available triggers: ${availableTriggers.join(", ")}` : "No triggers are configured";
|
|
78
|
+
super(
|
|
79
|
+
"TRIGGER_NOT_FOUND",
|
|
80
|
+
`Trigger '${triggerName}' not found. ${available}`
|
|
81
|
+
);
|
|
82
|
+
this.name = "TriggerNotFoundError";
|
|
83
|
+
this.triggerName = triggerName;
|
|
84
|
+
this.availableTriggers = availableTriggers;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var TriggerProcessingError = class extends TriggerError {
|
|
88
|
+
/** The trigger name that failed */
|
|
89
|
+
triggerName;
|
|
90
|
+
/** Number of execution attempts */
|
|
91
|
+
attempts;
|
|
92
|
+
/** The event that was being processed (if available) */
|
|
93
|
+
event;
|
|
94
|
+
constructor(message, triggerName, attempts, options) {
|
|
95
|
+
super("PROCESSING_ERROR", message, options);
|
|
96
|
+
this.name = "TriggerProcessingError";
|
|
97
|
+
this.triggerName = triggerName;
|
|
98
|
+
this.attempts = attempts;
|
|
99
|
+
this.event = options?.event;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var TriggerTimeoutError = class extends TriggerError {
|
|
103
|
+
/** The trigger name that timed out */
|
|
104
|
+
triggerName;
|
|
105
|
+
/** Configured timeout in milliseconds */
|
|
106
|
+
timeoutMs;
|
|
107
|
+
constructor(triggerName, timeoutMs) {
|
|
108
|
+
super(
|
|
109
|
+
"TIMEOUT_ERROR",
|
|
110
|
+
`Trigger '${triggerName}' execution exceeded timeout of ${timeoutMs}ms`
|
|
111
|
+
);
|
|
112
|
+
this.name = "TriggerTimeoutError";
|
|
113
|
+
this.triggerName = triggerName;
|
|
114
|
+
this.timeoutMs = timeoutMs;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var TriggerMaxRetriesError = class extends TriggerError {
|
|
118
|
+
/** The trigger name that exceeded retries */
|
|
119
|
+
triggerName;
|
|
120
|
+
/** Maximum retries configured */
|
|
121
|
+
maxRetries;
|
|
122
|
+
/** The original error that caused the failure */
|
|
123
|
+
originalError;
|
|
124
|
+
constructor(triggerName, maxRetries, originalError) {
|
|
125
|
+
super(
|
|
126
|
+
"MAX_RETRIES_EXCEEDED",
|
|
127
|
+
`Trigger '${triggerName}' exceeded maximum retries (${maxRetries})`
|
|
128
|
+
);
|
|
129
|
+
this.name = "TriggerMaxRetriesError";
|
|
130
|
+
this.triggerName = triggerName;
|
|
131
|
+
this.maxRetries = maxRetries;
|
|
132
|
+
this.originalError = originalError;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var TriggerWebhookVerificationError = class extends TriggerError {
|
|
136
|
+
/** The trigger name */
|
|
137
|
+
triggerName;
|
|
138
|
+
/** The verification error message */
|
|
139
|
+
verificationError;
|
|
140
|
+
constructor(triggerName, verificationError) {
|
|
141
|
+
super(
|
|
142
|
+
"WEBHOOK_VERIFICATION_FAILED",
|
|
143
|
+
`Webhook signature verification failed for trigger '${triggerName}'${verificationError ? `: ${verificationError}` : ""}`
|
|
144
|
+
);
|
|
145
|
+
this.name = "TriggerWebhookVerificationError";
|
|
146
|
+
this.triggerName = triggerName;
|
|
147
|
+
this.verificationError = verificationError;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/define-trigger.ts
|
|
152
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
153
|
+
maxAttempts: 3,
|
|
154
|
+
delay: "1m",
|
|
155
|
+
backoff: "linear"
|
|
156
|
+
};
|
|
157
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
158
|
+
function parseDuration(duration) {
|
|
159
|
+
if (typeof duration === "number") {
|
|
160
|
+
return duration;
|
|
161
|
+
}
|
|
162
|
+
const match = duration.match(/^(\d+)(s|m|h)$/);
|
|
163
|
+
if (!match) {
|
|
164
|
+
throw new TriggerConfigError(
|
|
165
|
+
`Invalid duration format: '${duration}'. Expected format like '30s', '5m', or '1h'`,
|
|
166
|
+
"duration"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
const value = parseInt(match[1], 10);
|
|
170
|
+
const unit = match[2];
|
|
171
|
+
switch (unit) {
|
|
172
|
+
case "s":
|
|
173
|
+
return value;
|
|
174
|
+
case "m":
|
|
175
|
+
return value * 60;
|
|
176
|
+
case "h":
|
|
177
|
+
return value * 3600;
|
|
178
|
+
default:
|
|
179
|
+
throw new TriggerConfigError(`Unknown duration unit: ${unit}`, "duration");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function validateCron(cron) {
|
|
183
|
+
const parts = cron.trim().split(/\s+/);
|
|
184
|
+
if (parts.length < 5 || parts.length > 6) {
|
|
185
|
+
throw new TriggerInvalidCronError(
|
|
186
|
+
cron,
|
|
187
|
+
`Expected 5 or 6 fields, got ${parts.length}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const fieldNames = ["minute", "hour", "day of month", "month", "day of week"];
|
|
191
|
+
if (parts.length === 6) {
|
|
192
|
+
fieldNames.unshift("second");
|
|
193
|
+
}
|
|
194
|
+
const validChars = /^[\d,\-*/]+$/;
|
|
195
|
+
for (let i = 0; i < parts.length; i++) {
|
|
196
|
+
const part = parts[i];
|
|
197
|
+
if (part !== "*" && part !== "?" && !validChars.test(part)) {
|
|
198
|
+
throw new TriggerInvalidCronError(
|
|
199
|
+
cron,
|
|
200
|
+
`Invalid characters in ${fieldNames[i]} field: '${part}'`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function validateWebhookPath(path) {
|
|
206
|
+
if (!path) {
|
|
207
|
+
throw new TriggerInvalidWebhookPathError(path, "Path cannot be empty");
|
|
208
|
+
}
|
|
209
|
+
if (!path.startsWith("/")) {
|
|
210
|
+
throw new TriggerInvalidWebhookPathError(
|
|
211
|
+
path,
|
|
212
|
+
"Path must start with /"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const validPath = /^[a-zA-Z0-9\-_/:.]+$/;
|
|
216
|
+
if (!validPath.test(path)) {
|
|
217
|
+
throw new TriggerInvalidWebhookPathError(
|
|
218
|
+
path,
|
|
219
|
+
"Path contains invalid characters"
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (path.includes("//")) {
|
|
223
|
+
throw new TriggerInvalidWebhookPathError(
|
|
224
|
+
path,
|
|
225
|
+
"Path cannot contain double slashes"
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function validateSource(source) {
|
|
230
|
+
if (!source || typeof source !== "object") {
|
|
231
|
+
throw new TriggerInvalidSourceError("Source must be an object");
|
|
232
|
+
}
|
|
233
|
+
if (!("type" in source)) {
|
|
234
|
+
throw new TriggerInvalidSourceError("Source must have a type property");
|
|
235
|
+
}
|
|
236
|
+
switch (source.type) {
|
|
237
|
+
case "scheduled":
|
|
238
|
+
validateScheduledSource(source);
|
|
239
|
+
break;
|
|
240
|
+
case "queue":
|
|
241
|
+
if (!source.queue || typeof source.queue !== "string") {
|
|
242
|
+
throw new TriggerInvalidSourceError(
|
|
243
|
+
"Queue source must specify a queue name",
|
|
244
|
+
"queue"
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
case "r2":
|
|
249
|
+
if (!source.bucket || typeof source.bucket !== "string") {
|
|
250
|
+
throw new TriggerInvalidSourceError(
|
|
251
|
+
"R2 source must specify a bucket name",
|
|
252
|
+
"r2"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (!Array.isArray(source.events) || source.events.length === 0) {
|
|
256
|
+
throw new TriggerInvalidSourceError(
|
|
257
|
+
"R2 source must specify at least one event type",
|
|
258
|
+
"r2"
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
const validR2Events = ["object-create", "object-delete"];
|
|
262
|
+
for (const event of source.events) {
|
|
263
|
+
if (!validR2Events.includes(event)) {
|
|
264
|
+
throw new TriggerInvalidSourceError(
|
|
265
|
+
`Invalid R2 event type: '${event}'. Valid types: ${validR2Events.join(", ")}`,
|
|
266
|
+
"r2"
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
case "webhook":
|
|
272
|
+
validateWebhookSource(source);
|
|
273
|
+
break;
|
|
274
|
+
case "email":
|
|
275
|
+
if (!source.address || typeof source.address !== "string") {
|
|
276
|
+
throw new TriggerInvalidSourceError(
|
|
277
|
+
"Email source must specify an address pattern",
|
|
278
|
+
"email"
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
case "d1":
|
|
283
|
+
if (!source.database || typeof source.database !== "string") {
|
|
284
|
+
throw new TriggerInvalidSourceError(
|
|
285
|
+
"D1 source must specify a database name",
|
|
286
|
+
"d1"
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (!source.table || typeof source.table !== "string") {
|
|
290
|
+
throw new TriggerInvalidSourceError(
|
|
291
|
+
"D1 source must specify a table name",
|
|
292
|
+
"d1"
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (!Array.isArray(source.events) || source.events.length === 0) {
|
|
296
|
+
throw new TriggerInvalidSourceError(
|
|
297
|
+
"D1 source must specify at least one event type",
|
|
298
|
+
"d1"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
case "tail":
|
|
303
|
+
if (!Array.isArray(source.consumers) || source.consumers.length === 0) {
|
|
304
|
+
throw new TriggerInvalidSourceError(
|
|
305
|
+
"Tail source must specify at least one consumer",
|
|
306
|
+
"tail"
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
default:
|
|
311
|
+
throw new TriggerInvalidSourceError(
|
|
312
|
+
`Unknown source type: '${source.type}'`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function validateScheduledSource(source) {
|
|
317
|
+
if (!source.cron || typeof source.cron !== "string") {
|
|
318
|
+
throw new TriggerInvalidSourceError(
|
|
319
|
+
"Scheduled source must specify a cron expression",
|
|
320
|
+
"scheduled"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
validateCron(source.cron);
|
|
324
|
+
}
|
|
325
|
+
function validateWebhookSource(source) {
|
|
326
|
+
if (!source.path) {
|
|
327
|
+
throw new TriggerInvalidSourceError(
|
|
328
|
+
"Webhook source must specify a path",
|
|
329
|
+
"webhook"
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
validateWebhookPath(source.path);
|
|
333
|
+
if (source.methods) {
|
|
334
|
+
const validMethods = ["POST", "PUT", "PATCH"];
|
|
335
|
+
for (const method of source.methods) {
|
|
336
|
+
if (!validMethods.includes(method)) {
|
|
337
|
+
throw new TriggerInvalidSourceError(
|
|
338
|
+
`Invalid webhook method: '${method}'. Valid methods: ${validMethods.join(", ")}`,
|
|
339
|
+
"webhook"
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function validateConfig(config) {
|
|
346
|
+
if (!config.handle || typeof config.handle !== "function") {
|
|
347
|
+
throw new TriggerNoHandlerError(config.name || "unknown");
|
|
348
|
+
}
|
|
349
|
+
if (!config.source) {
|
|
350
|
+
throw new TriggerInvalidSourceError("Trigger must have a source");
|
|
351
|
+
}
|
|
352
|
+
validateSource(config.source);
|
|
353
|
+
if (config.retry) {
|
|
354
|
+
const { maxAttempts, delay, backoff } = config.retry;
|
|
355
|
+
if (maxAttempts !== void 0) {
|
|
356
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 0 || maxAttempts > 100) {
|
|
357
|
+
throw new TriggerConfigError(
|
|
358
|
+
"maxAttempts must be an integer between 0 and 100",
|
|
359
|
+
"maxAttempts"
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (delay !== void 0) {
|
|
364
|
+
parseDuration(delay);
|
|
365
|
+
}
|
|
366
|
+
if (backoff !== void 0 && backoff !== "linear" && backoff !== "exponential") {
|
|
367
|
+
throw new TriggerConfigError(
|
|
368
|
+
`Invalid backoff strategy: '${backoff}'. Valid strategies: 'linear', 'exponential'`,
|
|
369
|
+
"backoff"
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (config.timeout !== void 0) {
|
|
374
|
+
if (typeof config.timeout !== "number" || config.timeout <= 0) {
|
|
375
|
+
throw new TriggerConfigError(
|
|
376
|
+
"timeout must be a positive number (milliseconds)",
|
|
377
|
+
"timeout"
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
if (config.timeout > 6e5) {
|
|
381
|
+
throw new TriggerConfigError(
|
|
382
|
+
"timeout cannot exceed 600000ms (10 minutes)",
|
|
383
|
+
"timeout"
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (config.name !== void 0) {
|
|
388
|
+
if (typeof config.name !== "string" || config.name.length === 0) {
|
|
389
|
+
throw new TriggerConfigError("name must be a non-empty string", "name");
|
|
390
|
+
}
|
|
391
|
+
if (!/^[a-z][a-z0-9-]*$/.test(config.name)) {
|
|
392
|
+
throw new TriggerConfigError(
|
|
393
|
+
"name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens",
|
|
394
|
+
"name"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (config.onError !== void 0 && typeof config.onError !== "function") {
|
|
399
|
+
throw new TriggerConfigError("onError must be a function", "onError");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function defineTrigger(config) {
|
|
403
|
+
validateConfig(config);
|
|
404
|
+
const mergedRetryConfig = {
|
|
405
|
+
...DEFAULT_RETRY_CONFIG,
|
|
406
|
+
...config.retry
|
|
407
|
+
};
|
|
408
|
+
const definition = {
|
|
409
|
+
__brand: "cloudwerk-trigger",
|
|
410
|
+
name: config.name,
|
|
411
|
+
source: config.source,
|
|
412
|
+
retry: mergedRetryConfig,
|
|
413
|
+
timeout: config.timeout ?? DEFAULT_TIMEOUT,
|
|
414
|
+
handle: config.handle,
|
|
415
|
+
onError: config.onError
|
|
416
|
+
};
|
|
417
|
+
return definition;
|
|
418
|
+
}
|
|
419
|
+
function isTriggerDefinition(value) {
|
|
420
|
+
return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "cloudwerk-trigger";
|
|
421
|
+
}
|
|
422
|
+
function getTriggerSourceType(definition) {
|
|
423
|
+
return definition.source.type;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/verifiers/utils.ts
|
|
427
|
+
async function computeHmac(algorithm, secret, data) {
|
|
428
|
+
const encoder = new TextEncoder();
|
|
429
|
+
const keyData = encoder.encode(secret);
|
|
430
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
431
|
+
"raw",
|
|
432
|
+
keyData,
|
|
433
|
+
{ name: "HMAC", hash: algorithm },
|
|
434
|
+
false,
|
|
435
|
+
["sign"]
|
|
436
|
+
);
|
|
437
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, data);
|
|
438
|
+
return arrayBufferToHex(signature);
|
|
439
|
+
}
|
|
440
|
+
function arrayBufferToHex(buffer) {
|
|
441
|
+
const bytes = new Uint8Array(buffer);
|
|
442
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
443
|
+
}
|
|
444
|
+
function timingSafeEqual(a, b) {
|
|
445
|
+
if (a.length !== b.length) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
const aBytes = new TextEncoder().encode(a);
|
|
449
|
+
const bBytes = new TextEncoder().encode(b);
|
|
450
|
+
let result = 0;
|
|
451
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
452
|
+
result |= aBytes[i] ^ bBytes[i];
|
|
453
|
+
}
|
|
454
|
+
return result === 0;
|
|
455
|
+
}
|
|
456
|
+
function parseSignatureHeader(header, separator = ",") {
|
|
457
|
+
const result = /* @__PURE__ */ new Map();
|
|
458
|
+
for (const part of header.split(separator)) {
|
|
459
|
+
const [key, ...valueParts] = part.trim().split("=");
|
|
460
|
+
if (key && valueParts.length > 0) {
|
|
461
|
+
result.set(key, valueParts.join("="));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/verifiers/stripe.ts
|
|
468
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
469
|
+
function stripeVerifier(secret, options = {}) {
|
|
470
|
+
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS;
|
|
471
|
+
return async (request, rawBody) => {
|
|
472
|
+
const signature = request.headers.get("stripe-signature");
|
|
473
|
+
if (!signature) {
|
|
474
|
+
return {
|
|
475
|
+
valid: false,
|
|
476
|
+
error: "Missing stripe-signature header"
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const parts = parseSignatureHeader(signature);
|
|
480
|
+
const timestamp = parts.get("t");
|
|
481
|
+
const expectedSignature = parts.get("v1");
|
|
482
|
+
if (!timestamp || !expectedSignature) {
|
|
483
|
+
return {
|
|
484
|
+
valid: false,
|
|
485
|
+
error: "Invalid stripe-signature header format"
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
const timestampNum = parseInt(timestamp, 10);
|
|
489
|
+
if (isNaN(timestampNum)) {
|
|
490
|
+
return {
|
|
491
|
+
valid: false,
|
|
492
|
+
error: "Invalid timestamp in signature"
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
496
|
+
if (Math.abs(now - timestampNum) > tolerance) {
|
|
497
|
+
return {
|
|
498
|
+
valid: false,
|
|
499
|
+
error: `Timestamp outside tolerance (${tolerance}s)`
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const payload = new TextDecoder().decode(rawBody);
|
|
503
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
504
|
+
const signedPayloadBytes = new TextEncoder().encode(signedPayload);
|
|
505
|
+
const computedSignature = await computeHmac(
|
|
506
|
+
"SHA-256",
|
|
507
|
+
secret,
|
|
508
|
+
signedPayloadBytes.buffer
|
|
509
|
+
);
|
|
510
|
+
if (!timingSafeEqual(computedSignature, expectedSignature)) {
|
|
511
|
+
return {
|
|
512
|
+
valid: false,
|
|
513
|
+
error: "Signature mismatch"
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
return { valid: true };
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
var stripe = stripeVerifier;
|
|
520
|
+
|
|
521
|
+
// src/verifiers/github.ts
|
|
522
|
+
function githubVerifier(secret) {
|
|
523
|
+
return async (request, rawBody) => {
|
|
524
|
+
const signatureHeader = request.headers.get("x-hub-signature-256") || request.headers.get("x-hub-signature");
|
|
525
|
+
if (!signatureHeader) {
|
|
526
|
+
return {
|
|
527
|
+
valid: false,
|
|
528
|
+
error: "Missing X-Hub-Signature-256 or X-Hub-Signature header"
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
const useSha256 = signatureHeader.startsWith("sha256=");
|
|
532
|
+
const algorithm = useSha256 ? "SHA-256" : "SHA-1";
|
|
533
|
+
const prefix = useSha256 ? "sha256=" : "sha1=";
|
|
534
|
+
if (!signatureHeader.startsWith(prefix)) {
|
|
535
|
+
return {
|
|
536
|
+
valid: false,
|
|
537
|
+
error: `Invalid signature format (expected ${prefix} prefix)`
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const expectedSignature = signatureHeader.slice(prefix.length);
|
|
541
|
+
const computedSignature = await computeHmac(algorithm, secret, rawBody);
|
|
542
|
+
if (!timingSafeEqual(computedSignature, expectedSignature)) {
|
|
543
|
+
return {
|
|
544
|
+
valid: false,
|
|
545
|
+
error: "Signature mismatch"
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
return { valid: true };
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
var github = githubVerifier;
|
|
552
|
+
|
|
553
|
+
// src/verifiers/slack.ts
|
|
554
|
+
var DEFAULT_TOLERANCE_SECONDS2 = 300;
|
|
555
|
+
function slackVerifier(signingSecret, options = {}) {
|
|
556
|
+
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS2;
|
|
557
|
+
return async (request, rawBody) => {
|
|
558
|
+
const timestamp = request.headers.get("x-slack-request-timestamp");
|
|
559
|
+
const signature = request.headers.get("x-slack-signature");
|
|
560
|
+
if (!timestamp) {
|
|
561
|
+
return {
|
|
562
|
+
valid: false,
|
|
563
|
+
error: "Missing X-Slack-Request-Timestamp header"
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (!signature) {
|
|
567
|
+
return {
|
|
568
|
+
valid: false,
|
|
569
|
+
error: "Missing X-Slack-Signature header"
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const timestampNum = parseInt(timestamp, 10);
|
|
573
|
+
if (isNaN(timestampNum)) {
|
|
574
|
+
return {
|
|
575
|
+
valid: false,
|
|
576
|
+
error: "Invalid timestamp"
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
580
|
+
if (Math.abs(now - timestampNum) > tolerance) {
|
|
581
|
+
return {
|
|
582
|
+
valid: false,
|
|
583
|
+
error: `Timestamp outside tolerance (${tolerance}s)`
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (!signature.startsWith("v0=")) {
|
|
587
|
+
return {
|
|
588
|
+
valid: false,
|
|
589
|
+
error: "Invalid signature format (expected v0= prefix)"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const expectedSignature = signature.slice(3);
|
|
593
|
+
const body = new TextDecoder().decode(rawBody);
|
|
594
|
+
const sigBasestring = `v0:${timestamp}:${body}`;
|
|
595
|
+
const sigBasestringBytes = new TextEncoder().encode(sigBasestring);
|
|
596
|
+
const computedSignature = await computeHmac(
|
|
597
|
+
"SHA-256",
|
|
598
|
+
signingSecret,
|
|
599
|
+
sigBasestringBytes.buffer
|
|
600
|
+
);
|
|
601
|
+
if (!timingSafeEqual(computedSignature, expectedSignature)) {
|
|
602
|
+
return {
|
|
603
|
+
valid: false,
|
|
604
|
+
error: "Signature mismatch"
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
return { valid: true };
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
var slack = slackVerifier;
|
|
611
|
+
|
|
612
|
+
// src/verifiers/twilio.ts
|
|
613
|
+
function twilioVerifier(authToken, options) {
|
|
614
|
+
const { url } = options;
|
|
615
|
+
return async (request, rawBody) => {
|
|
616
|
+
const signature = request.headers.get("x-twilio-signature");
|
|
617
|
+
if (!signature) {
|
|
618
|
+
return {
|
|
619
|
+
valid: false,
|
|
620
|
+
error: "Missing X-Twilio-Signature header"
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
const body = new TextDecoder().decode(rawBody);
|
|
624
|
+
const params = new URLSearchParams(body);
|
|
625
|
+
const sortedParams = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}${value}`).join("");
|
|
626
|
+
const signatureBase = url + sortedParams;
|
|
627
|
+
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
628
|
+
const computedSignatureHex = await computeHmac(
|
|
629
|
+
"SHA-1",
|
|
630
|
+
authToken,
|
|
631
|
+
signatureBaseBytes.buffer
|
|
632
|
+
);
|
|
633
|
+
const computedSignatureBytes = new Uint8Array(
|
|
634
|
+
computedSignatureHex.match(/.{2}/g).map((byte) => parseInt(byte, 16))
|
|
635
|
+
);
|
|
636
|
+
const computedSignature = btoa(
|
|
637
|
+
String.fromCharCode(...computedSignatureBytes)
|
|
638
|
+
);
|
|
639
|
+
if (!timingSafeEqual(computedSignature, signature)) {
|
|
640
|
+
return {
|
|
641
|
+
valid: false,
|
|
642
|
+
error: "Signature mismatch"
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return { valid: true };
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
var twilio = twilioVerifier;
|
|
649
|
+
|
|
650
|
+
// src/verifiers/shopify.ts
|
|
651
|
+
function shopifyVerifier(secret) {
|
|
652
|
+
return async (request, rawBody) => {
|
|
653
|
+
const signature = request.headers.get("x-shopify-hmac-sha256");
|
|
654
|
+
if (!signature) {
|
|
655
|
+
return {
|
|
656
|
+
valid: false,
|
|
657
|
+
error: "Missing X-Shopify-Hmac-Sha256 header"
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
const computedSignatureHex = await computeHmac("SHA-256", secret, rawBody);
|
|
661
|
+
const computedSignatureBytes = new Uint8Array(
|
|
662
|
+
computedSignatureHex.match(/.{2}/g).map((byte) => parseInt(byte, 16))
|
|
663
|
+
);
|
|
664
|
+
const computedSignature = btoa(
|
|
665
|
+
String.fromCharCode(...computedSignatureBytes)
|
|
666
|
+
);
|
|
667
|
+
if (!timingSafeEqual(computedSignature, signature)) {
|
|
668
|
+
return {
|
|
669
|
+
valid: false,
|
|
670
|
+
error: "Signature mismatch"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return { valid: true };
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
var shopify = shopifyVerifier;
|
|
677
|
+
|
|
678
|
+
// src/verifiers/linear.ts
|
|
679
|
+
function linearVerifier(secret) {
|
|
680
|
+
return async (request, rawBody) => {
|
|
681
|
+
const signature = request.headers.get("linear-signature");
|
|
682
|
+
if (!signature) {
|
|
683
|
+
return {
|
|
684
|
+
valid: false,
|
|
685
|
+
error: "Missing Linear-Signature header"
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
const computedSignature = await computeHmac("SHA-256", secret, rawBody);
|
|
689
|
+
if (!timingSafeEqual(computedSignature, signature)) {
|
|
690
|
+
return {
|
|
691
|
+
valid: false,
|
|
692
|
+
error: "Signature mismatch"
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
return { valid: true };
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
var linear = linearVerifier;
|
|
699
|
+
|
|
700
|
+
// src/verifiers/custom.ts
|
|
701
|
+
function customVerifier(secret, options) {
|
|
702
|
+
const {
|
|
703
|
+
header,
|
|
704
|
+
algorithm = "SHA-256",
|
|
705
|
+
encoding = "hex",
|
|
706
|
+
prefix,
|
|
707
|
+
timestampHeader,
|
|
708
|
+
timestampTolerance = 300,
|
|
709
|
+
buildSignatureBase
|
|
710
|
+
} = options;
|
|
711
|
+
return async (request, rawBody) => {
|
|
712
|
+
let signature = request.headers.get(header);
|
|
713
|
+
if (!signature) {
|
|
714
|
+
return {
|
|
715
|
+
valid: false,
|
|
716
|
+
error: `Missing ${header} header`
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (prefix) {
|
|
720
|
+
if (!signature.startsWith(prefix)) {
|
|
721
|
+
return {
|
|
722
|
+
valid: false,
|
|
723
|
+
error: `Invalid signature format (expected ${prefix} prefix)`
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
signature = signature.slice(prefix.length);
|
|
727
|
+
}
|
|
728
|
+
let timestamp;
|
|
729
|
+
if (timestampHeader) {
|
|
730
|
+
timestamp = request.headers.get(timestampHeader) ?? void 0;
|
|
731
|
+
if (!timestamp) {
|
|
732
|
+
return {
|
|
733
|
+
valid: false,
|
|
734
|
+
error: `Missing ${timestampHeader} header`
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
const timestampNum = parseInt(timestamp, 10);
|
|
738
|
+
if (isNaN(timestampNum)) {
|
|
739
|
+
return {
|
|
740
|
+
valid: false,
|
|
741
|
+
error: "Invalid timestamp"
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
745
|
+
if (Math.abs(now - timestampNum) > timestampTolerance) {
|
|
746
|
+
return {
|
|
747
|
+
valid: false,
|
|
748
|
+
error: `Timestamp outside tolerance (${timestampTolerance}s)`
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const body = new TextDecoder().decode(rawBody);
|
|
753
|
+
const signatureBase = buildSignatureBase ? buildSignatureBase(body, timestamp) : body;
|
|
754
|
+
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
755
|
+
const computedSignatureHex = await computeHmac(
|
|
756
|
+
algorithm,
|
|
757
|
+
secret,
|
|
758
|
+
signatureBaseBytes.buffer
|
|
759
|
+
);
|
|
760
|
+
let computedSignature;
|
|
761
|
+
if (encoding === "base64") {
|
|
762
|
+
const bytes = new Uint8Array(
|
|
763
|
+
computedSignatureHex.match(/.{2}/g).map((byte) => parseInt(byte, 16))
|
|
764
|
+
);
|
|
765
|
+
computedSignature = btoa(String.fromCharCode(...bytes));
|
|
766
|
+
} else {
|
|
767
|
+
computedSignature = computedSignatureHex;
|
|
768
|
+
}
|
|
769
|
+
if (!timingSafeEqual(computedSignature, signature)) {
|
|
770
|
+
return {
|
|
771
|
+
valid: false,
|
|
772
|
+
error: "Signature mismatch"
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
return { valid: true };
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
var custom = customVerifier;
|
|
779
|
+
|
|
780
|
+
// src/verifiers/index.ts
|
|
781
|
+
var verifiers = {
|
|
782
|
+
stripe,
|
|
783
|
+
github,
|
|
784
|
+
slack,
|
|
785
|
+
twilio,
|
|
786
|
+
shopify,
|
|
787
|
+
linear,
|
|
788
|
+
custom
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// src/emit.ts
|
|
792
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
793
|
+
var contextStore = new AsyncLocalStorage();
|
|
794
|
+
var emissionsStore = new AsyncLocalStorage();
|
|
795
|
+
function runWithTriggerContext(context, fn) {
|
|
796
|
+
return contextStore.run(context, () => {
|
|
797
|
+
return emissionsStore.run([], fn);
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
function getTriggerContext() {
|
|
801
|
+
return contextStore.getStore();
|
|
802
|
+
}
|
|
803
|
+
function getPendingEmissions() {
|
|
804
|
+
return emissionsStore.getStore() ?? [];
|
|
805
|
+
}
|
|
806
|
+
function generateTraceId(prefix = "tr") {
|
|
807
|
+
const random = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
808
|
+
return `${prefix}_${random}`;
|
|
809
|
+
}
|
|
810
|
+
function createChildTraceId(parentTraceId) {
|
|
811
|
+
const suffix = crypto.randomUUID().slice(0, 8);
|
|
812
|
+
return `${parentTraceId}.${suffix}`;
|
|
813
|
+
}
|
|
814
|
+
async function emit(trigger, payload, options = {}) {
|
|
815
|
+
const context = getTriggerContext();
|
|
816
|
+
const emissions = emissionsStore.getStore();
|
|
817
|
+
const traceId = options.traceId ?? context?.traceId ?? generateTraceId();
|
|
818
|
+
const childTraceId = context ? createChildTraceId(traceId) : traceId;
|
|
819
|
+
const emittedAt = /* @__PURE__ */ new Date();
|
|
820
|
+
const emission = {
|
|
821
|
+
trigger,
|
|
822
|
+
payload,
|
|
823
|
+
options,
|
|
824
|
+
traceId: childTraceId,
|
|
825
|
+
emittedAt
|
|
826
|
+
};
|
|
827
|
+
if (emissions) {
|
|
828
|
+
emissions.push(emission);
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
success: true,
|
|
832
|
+
traceId: childTraceId,
|
|
833
|
+
trigger,
|
|
834
|
+
emittedAt
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
async function emitMany(emissions, options = {}) {
|
|
838
|
+
return Promise.all(
|
|
839
|
+
emissions.map(([trigger, payload]) => emit(trigger, payload, options))
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
var defaultEmitter = {
|
|
843
|
+
emit,
|
|
844
|
+
getPendingEmissions,
|
|
845
|
+
clearPendingEmissions: () => {
|
|
846
|
+
const emissions = emissionsStore.getStore();
|
|
847
|
+
if (emissions) {
|
|
848
|
+
emissions.length = 0;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/observability.ts
|
|
854
|
+
function generateSpanId() {
|
|
855
|
+
return crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
856
|
+
}
|
|
857
|
+
function createTriggerSpan(triggerName, sourceType, traceId, parentSpanId) {
|
|
858
|
+
return {
|
|
859
|
+
spanId: generateSpanId(),
|
|
860
|
+
traceId,
|
|
861
|
+
parentSpanId,
|
|
862
|
+
triggerName,
|
|
863
|
+
sourceType,
|
|
864
|
+
operationName: `trigger.${triggerName}`,
|
|
865
|
+
startTime: Date.now(),
|
|
866
|
+
status: "unset",
|
|
867
|
+
attributes: {
|
|
868
|
+
"trigger.name": triggerName,
|
|
869
|
+
"trigger.source_type": sourceType
|
|
870
|
+
},
|
|
871
|
+
events: []
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function endTriggerSpan(span, success, error) {
|
|
875
|
+
span.endTime = Date.now();
|
|
876
|
+
span.status = success ? "ok" : "error";
|
|
877
|
+
if (error) {
|
|
878
|
+
span.attributes["error.type"] = error.name;
|
|
879
|
+
span.attributes["error.message"] = error.message;
|
|
880
|
+
span.events.push({
|
|
881
|
+
name: "exception",
|
|
882
|
+
timestamp: Date.now(),
|
|
883
|
+
attributes: {
|
|
884
|
+
"exception.type": error.name,
|
|
885
|
+
"exception.message": error.message
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
return span;
|
|
890
|
+
}
|
|
891
|
+
function addSpanEvent(span, name, attributes) {
|
|
892
|
+
span.events.push({
|
|
893
|
+
name,
|
|
894
|
+
timestamp: Date.now(),
|
|
895
|
+
attributes
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
function setSpanAttributes(span, attributes) {
|
|
899
|
+
Object.assign(span.attributes, attributes);
|
|
900
|
+
}
|
|
901
|
+
var ExecutionTimer = class {
|
|
902
|
+
startTime;
|
|
903
|
+
endTime;
|
|
904
|
+
marks = /* @__PURE__ */ new Map();
|
|
905
|
+
constructor() {
|
|
906
|
+
this.startTime = performance.now();
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Mark a point in time.
|
|
910
|
+
*/
|
|
911
|
+
mark(name) {
|
|
912
|
+
this.marks.set(name, performance.now());
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Get time since start to a mark.
|
|
916
|
+
*/
|
|
917
|
+
getMarkDuration(name) {
|
|
918
|
+
const markTime = this.marks.get(name);
|
|
919
|
+
if (markTime === void 0) return void 0;
|
|
920
|
+
return markTime - this.startTime;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Stop the timer.
|
|
924
|
+
*/
|
|
925
|
+
stop() {
|
|
926
|
+
this.endTime = performance.now();
|
|
927
|
+
return this.duration;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Get total duration in milliseconds.
|
|
931
|
+
*/
|
|
932
|
+
get duration() {
|
|
933
|
+
const end = this.endTime ?? performance.now();
|
|
934
|
+
return end - this.startTime;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Get all marks with their durations.
|
|
938
|
+
*/
|
|
939
|
+
getMarks() {
|
|
940
|
+
const result = {};
|
|
941
|
+
for (const [name, time] of this.marks) {
|
|
942
|
+
result[name] = time - this.startTime;
|
|
943
|
+
}
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
var ConsoleMetricsReporter = class {
|
|
948
|
+
prefix;
|
|
949
|
+
constructor(prefix = "[trigger]") {
|
|
950
|
+
this.prefix = prefix;
|
|
951
|
+
}
|
|
952
|
+
reportExecution(metrics) {
|
|
953
|
+
const status = metrics.success ? "SUCCESS" : "FAILED";
|
|
954
|
+
const duration = metrics.durationMs ? `${metrics.durationMs}ms` : "unknown";
|
|
955
|
+
console.log(
|
|
956
|
+
`${this.prefix} ${metrics.name} [${metrics.traceId}] ${status} (${duration})`
|
|
957
|
+
);
|
|
958
|
+
if (metrics.error) {
|
|
959
|
+
console.error(`${this.prefix} Error:`, metrics.error.message);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
reportSpan(span) {
|
|
963
|
+
const duration = span.endTime ? `${span.endTime - span.startTime}ms` : "ongoing";
|
|
964
|
+
console.log(
|
|
965
|
+
`${this.prefix} Span: ${span.operationName} [${span.traceId}] ${span.status} (${duration})`
|
|
966
|
+
);
|
|
967
|
+
for (const event of span.events) {
|
|
968
|
+
console.log(`${this.prefix} Event: ${event.name}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
async flush() {
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
var NoOpMetricsReporter = class {
|
|
975
|
+
reportExecution(_metrics) {
|
|
976
|
+
}
|
|
977
|
+
reportSpan(_span) {
|
|
978
|
+
}
|
|
979
|
+
async flush() {
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
var MetricsCollector = class {
|
|
983
|
+
executions = [];
|
|
984
|
+
spans = [];
|
|
985
|
+
reporter;
|
|
986
|
+
constructor(reporter = new NoOpMetricsReporter()) {
|
|
987
|
+
this.reporter = reporter;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Record a trigger execution.
|
|
991
|
+
*/
|
|
992
|
+
recordExecution(metrics) {
|
|
993
|
+
this.executions.push(metrics);
|
|
994
|
+
this.reporter.reportExecution(metrics);
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Record a span.
|
|
998
|
+
*/
|
|
999
|
+
recordSpan(span) {
|
|
1000
|
+
this.spans.push(span);
|
|
1001
|
+
this.reporter.reportSpan(span);
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Get execution count for a trigger.
|
|
1005
|
+
*/
|
|
1006
|
+
getExecutionCount(triggerName) {
|
|
1007
|
+
return this.executions.filter((m) => m.name === triggerName).length;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Get success rate for a trigger.
|
|
1011
|
+
*/
|
|
1012
|
+
getSuccessRate(triggerName) {
|
|
1013
|
+
const executions = this.executions.filter((m) => m.name === triggerName);
|
|
1014
|
+
if (executions.length === 0) return 0;
|
|
1015
|
+
const successful = executions.filter((m) => m.success).length;
|
|
1016
|
+
return successful / executions.length;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Get average duration for a trigger.
|
|
1020
|
+
*/
|
|
1021
|
+
getAverageDuration(triggerName) {
|
|
1022
|
+
const executions = this.executions.filter(
|
|
1023
|
+
(m) => m.name === triggerName && m.durationMs !== void 0
|
|
1024
|
+
);
|
|
1025
|
+
if (executions.length === 0) return void 0;
|
|
1026
|
+
const total = executions.reduce((sum, m) => sum + (m.durationMs ?? 0), 0);
|
|
1027
|
+
return total / executions.length;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Get percentile duration for a trigger.
|
|
1031
|
+
*/
|
|
1032
|
+
getPercentileDuration(triggerName, percentile) {
|
|
1033
|
+
const executions = this.executions.filter((m) => m.name === triggerName && m.durationMs !== void 0).map((m) => m.durationMs).sort((a, b) => a - b);
|
|
1034
|
+
if (executions.length === 0) return void 0;
|
|
1035
|
+
const index = Math.ceil(percentile / 100 * executions.length) - 1;
|
|
1036
|
+
return executions[Math.max(0, index)];
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Get summary statistics for a trigger.
|
|
1040
|
+
*/
|
|
1041
|
+
getSummary(triggerName) {
|
|
1042
|
+
return {
|
|
1043
|
+
count: this.getExecutionCount(triggerName),
|
|
1044
|
+
successRate: this.getSuccessRate(triggerName),
|
|
1045
|
+
avgDurationMs: this.getAverageDuration(triggerName),
|
|
1046
|
+
p50DurationMs: this.getPercentileDuration(triggerName, 50),
|
|
1047
|
+
p95DurationMs: this.getPercentileDuration(triggerName, 95),
|
|
1048
|
+
p99DurationMs: this.getPercentileDuration(triggerName, 99)
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Flush metrics to the reporter.
|
|
1053
|
+
*/
|
|
1054
|
+
async flush() {
|
|
1055
|
+
await this.reporter.flush();
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Clear collected metrics.
|
|
1059
|
+
*/
|
|
1060
|
+
clear() {
|
|
1061
|
+
this.executions = [];
|
|
1062
|
+
this.spans = [];
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
function createTriggerMetrics(name, sourceType, ctx, timer, success, error, retryCount = 0) {
|
|
1066
|
+
const durationMs = timer.stop();
|
|
1067
|
+
return {
|
|
1068
|
+
name,
|
|
1069
|
+
sourceType,
|
|
1070
|
+
startedAt: new Date(Date.now() - durationMs),
|
|
1071
|
+
endedAt: /* @__PURE__ */ new Date(),
|
|
1072
|
+
durationMs,
|
|
1073
|
+
success,
|
|
1074
|
+
error,
|
|
1075
|
+
retryCount,
|
|
1076
|
+
traceId: ctx.traceId
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
export {
|
|
1080
|
+
ConsoleMetricsReporter,
|
|
1081
|
+
ExecutionTimer,
|
|
1082
|
+
MetricsCollector,
|
|
1083
|
+
NoOpMetricsReporter,
|
|
1084
|
+
TriggerConfigError,
|
|
1085
|
+
TriggerContextError,
|
|
1086
|
+
TriggerError,
|
|
1087
|
+
TriggerInvalidCronError,
|
|
1088
|
+
TriggerInvalidSourceError,
|
|
1089
|
+
TriggerInvalidWebhookPathError,
|
|
1090
|
+
TriggerMaxRetriesError,
|
|
1091
|
+
TriggerNoHandlerError,
|
|
1092
|
+
TriggerNotFoundError,
|
|
1093
|
+
TriggerProcessingError,
|
|
1094
|
+
TriggerTimeoutError,
|
|
1095
|
+
TriggerWebhookVerificationError,
|
|
1096
|
+
addSpanEvent,
|
|
1097
|
+
createChildTraceId,
|
|
1098
|
+
createTriggerMetrics,
|
|
1099
|
+
createTriggerSpan,
|
|
1100
|
+
custom,
|
|
1101
|
+
customVerifier,
|
|
1102
|
+
defaultEmitter,
|
|
1103
|
+
defineTrigger,
|
|
1104
|
+
emit,
|
|
1105
|
+
emitMany,
|
|
1106
|
+
endTriggerSpan,
|
|
1107
|
+
generateSpanId,
|
|
1108
|
+
generateTraceId,
|
|
1109
|
+
getPendingEmissions,
|
|
1110
|
+
getTriggerContext,
|
|
1111
|
+
getTriggerSourceType,
|
|
1112
|
+
github,
|
|
1113
|
+
githubVerifier,
|
|
1114
|
+
isTriggerDefinition,
|
|
1115
|
+
linear,
|
|
1116
|
+
linearVerifier,
|
|
1117
|
+
parseDuration,
|
|
1118
|
+
runWithTriggerContext,
|
|
1119
|
+
setSpanAttributes,
|
|
1120
|
+
shopify,
|
|
1121
|
+
shopifyVerifier,
|
|
1122
|
+
slack,
|
|
1123
|
+
slackVerifier,
|
|
1124
|
+
stripe,
|
|
1125
|
+
stripeVerifier,
|
|
1126
|
+
twilio,
|
|
1127
|
+
twilioVerifier,
|
|
1128
|
+
verifiers
|
|
1129
|
+
};
|
|
1130
|
+
//# sourceMappingURL=index.js.map
|