@cloudwerk/trigger 0.0.1 → 0.1.0
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/README.md +118 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @cloudwerk/trigger
|
|
2
|
+
|
|
3
|
+
Event-driven triggers for Cloudwerk applications with support for scheduled, queue, R2, webhook, email, D1, and tail events.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @cloudwerk/trigger
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// app/triggers/daily-cleanup.ts
|
|
15
|
+
import { defineTrigger } from '@cloudwerk/trigger'
|
|
16
|
+
|
|
17
|
+
export default defineTrigger({
|
|
18
|
+
source: {
|
|
19
|
+
type: 'scheduled',
|
|
20
|
+
cron: '0 0 * * *', // Daily at midnight
|
|
21
|
+
},
|
|
22
|
+
async handle(event, ctx) {
|
|
23
|
+
await ctx.env.DB.exec('DELETE FROM sessions WHERE expires_at < datetime("now")')
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Trigger Types
|
|
29
|
+
|
|
30
|
+
### Scheduled (Cron)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
defineTrigger({
|
|
34
|
+
source: { type: 'scheduled', cron: '0 9 * * 1-5', timezone: 'America/New_York' },
|
|
35
|
+
handle: async (event, ctx) => { /* ... */ },
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Webhook
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { stripeVerifier } from '@cloudwerk/trigger/verifiers'
|
|
43
|
+
|
|
44
|
+
defineTrigger({
|
|
45
|
+
source: {
|
|
46
|
+
type: 'webhook',
|
|
47
|
+
path: '/webhooks/stripe',
|
|
48
|
+
verifier: stripeVerifier({ secret: process.env.STRIPE_WEBHOOK_SECRET }),
|
|
49
|
+
},
|
|
50
|
+
handle: async (event, ctx) => { /* ... */ },
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Queue
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
defineTrigger({
|
|
58
|
+
source: { type: 'queue', name: 'notifications', batchSize: 25 },
|
|
59
|
+
handle: async (event, ctx) => { /* ... */ },
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### R2 (Object Storage)
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
defineTrigger({
|
|
67
|
+
source: { type: 'r2', bucket: 'uploads', events: ['object:create'], prefix: 'images/' },
|
|
68
|
+
handle: async (event, ctx) => { /* ... */ },
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Email
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
defineTrigger({
|
|
76
|
+
source: { type: 'email', domain: 'support.myapp.com' },
|
|
77
|
+
handle: async (event, ctx) => { /* ... */ },
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### D1 (Database Changes)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
defineTrigger({
|
|
85
|
+
source: { type: 'd1', database: 'DB', tables: ['users'], operations: ['INSERT', 'UPDATE'] },
|
|
86
|
+
handle: async (event, ctx) => { /* ... */ },
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Tail (Log Consumption)
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
defineTrigger({
|
|
94
|
+
source: { type: 'tail', workers: ['api-worker'], filters: [{ field: 'outcome', value: 'exception' }] },
|
|
95
|
+
handle: async (event, ctx) => { /* ... */ },
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Built-in Webhook Verifiers
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import {
|
|
103
|
+
stripeVerifier,
|
|
104
|
+
githubVerifier,
|
|
105
|
+
slackVerifier,
|
|
106
|
+
shopifyVerifier,
|
|
107
|
+
linearVerifier,
|
|
108
|
+
customHmacVerifier,
|
|
109
|
+
} from '@cloudwerk/trigger/verifiers'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
For complete documentation, visit the [Cloudwerk Triggers Guide](https://cloudwerk.dev/guides/triggers/).
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -245,7 +245,7 @@ function validateSource(source) {
|
|
|
245
245
|
);
|
|
246
246
|
}
|
|
247
247
|
break;
|
|
248
|
-
case "r2":
|
|
248
|
+
case "r2": {
|
|
249
249
|
if (!source.bucket || typeof source.bucket !== "string") {
|
|
250
250
|
throw new TriggerInvalidSourceError(
|
|
251
251
|
"R2 source must specify a bucket name",
|
|
@@ -268,6 +268,7 @@ function validateSource(source) {
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
break;
|
|
271
|
+
}
|
|
271
272
|
case "webhook":
|
|
272
273
|
validateWebhookSource(source);
|
|
273
274
|
break;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/define-trigger.ts","../src/verifiers/utils.ts","../src/verifiers/stripe.ts","../src/verifiers/github.ts","../src/verifiers/slack.ts","../src/verifiers/twilio.ts","../src/verifiers/shopify.ts","../src/verifiers/linear.ts","../src/verifiers/custom.ts","../src/verifiers/index.ts","../src/emit.ts","../src/observability.ts"],"sourcesContent":["/**\n * @cloudwerk/trigger - Error Classes\n *\n * Custom error classes for trigger processing.\n */\n\n// ============================================================================\n// Base Error\n// ============================================================================\n\n/**\n * Base error class for trigger-related errors.\n */\nexport class TriggerError extends Error {\n /** Error code for programmatic handling */\n readonly code: string\n\n constructor(code: string, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'TriggerError'\n this.code = code\n }\n}\n\n// ============================================================================\n// Configuration Errors\n// ============================================================================\n\n/**\n * Error thrown when trigger configuration is invalid.\n *\n * @example\n * ```typescript\n * throw new TriggerConfigError('timeout must be a positive number', 'timeout')\n * ```\n */\nexport class TriggerConfigError extends TriggerError {\n /** The configuration field that is invalid */\n readonly field?: string\n\n constructor(message: string, field?: string) {\n super('CONFIG_ERROR', message)\n this.name = 'TriggerConfigError'\n this.field = field\n }\n}\n\n/**\n * Error thrown when no handler function is defined.\n */\nexport class TriggerNoHandlerError extends TriggerError {\n constructor(triggerName: string) {\n super(\n 'NO_HANDLER',\n `Trigger '${triggerName}' must define a handle() function`\n )\n this.name = 'TriggerNoHandlerError'\n }\n}\n\n/**\n * Error thrown when the trigger source configuration is invalid.\n */\nexport class TriggerInvalidSourceError extends TriggerError {\n /** The source type that is invalid */\n readonly sourceType?: string\n\n constructor(message: string, sourceType?: string) {\n super('INVALID_SOURCE', message)\n this.name = 'TriggerInvalidSourceError'\n this.sourceType = sourceType\n }\n}\n\n/**\n * Error thrown when a cron expression is invalid.\n */\nexport class TriggerInvalidCronError extends TriggerError {\n /** The invalid cron expression */\n readonly cron: string\n\n constructor(cron: string, reason?: string) {\n super(\n 'INVALID_CRON',\n `Invalid cron expression '${cron}'${reason ? `: ${reason}` : ''}`\n )\n this.name = 'TriggerInvalidCronError'\n this.cron = cron\n }\n}\n\n/**\n * Error thrown when a webhook path is invalid.\n */\nexport class TriggerInvalidWebhookPathError extends TriggerError {\n /** The invalid path */\n readonly path: string\n\n constructor(path: string, reason?: string) {\n super(\n 'INVALID_WEBHOOK_PATH',\n `Invalid webhook path '${path}'${reason ? `: ${reason}` : ''}`\n )\n this.name = 'TriggerInvalidWebhookPathError'\n this.path = path\n }\n}\n\n// ============================================================================\n// Runtime Errors\n// ============================================================================\n\n/**\n * Error thrown when accessing a trigger outside of execution context.\n */\nexport class TriggerContextError extends TriggerError {\n constructor() {\n super(\n 'CONTEXT_ERROR',\n 'Trigger context accessed outside of trigger execution. Context is only available during trigger handling.'\n )\n this.name = 'TriggerContextError'\n }\n}\n\n/**\n * Error thrown when a trigger binding is not found.\n */\nexport class TriggerNotFoundError extends TriggerError {\n /** The trigger name that was not found */\n readonly triggerName: string\n\n /** Available trigger names */\n readonly availableTriggers: string[]\n\n constructor(triggerName: string, availableTriggers: string[]) {\n const available =\n availableTriggers.length > 0\n ? `Available triggers: ${availableTriggers.join(', ')}`\n : 'No triggers are configured'\n\n super(\n 'TRIGGER_NOT_FOUND',\n `Trigger '${triggerName}' not found. ${available}`\n )\n this.name = 'TriggerNotFoundError'\n this.triggerName = triggerName\n this.availableTriggers = availableTriggers\n }\n}\n\n/**\n * Error thrown when trigger processing fails.\n */\nexport class TriggerProcessingError extends TriggerError {\n /** The trigger name that failed */\n readonly triggerName: string\n\n /** Number of execution attempts */\n readonly attempts: number\n\n /** The event that was being processed (if available) */\n readonly event?: unknown\n\n constructor(\n message: string,\n triggerName: string,\n attempts: number,\n options?: ErrorOptions & { event?: unknown }\n ) {\n super('PROCESSING_ERROR', message, options)\n this.name = 'TriggerProcessingError'\n this.triggerName = triggerName\n this.attempts = attempts\n this.event = options?.event\n }\n}\n\n/**\n * Error thrown when a trigger execution times out.\n */\nexport class TriggerTimeoutError extends TriggerError {\n /** The trigger name that timed out */\n readonly triggerName: string\n\n /** Configured timeout in milliseconds */\n readonly timeoutMs: number\n\n constructor(triggerName: string, timeoutMs: number) {\n super(\n 'TIMEOUT_ERROR',\n `Trigger '${triggerName}' execution exceeded timeout of ${timeoutMs}ms`\n )\n this.name = 'TriggerTimeoutError'\n this.triggerName = triggerName\n this.timeoutMs = timeoutMs\n }\n}\n\n/**\n * Error thrown when max retries are exceeded.\n */\nexport class TriggerMaxRetriesError extends TriggerError {\n /** The trigger name that exceeded retries */\n readonly triggerName: string\n\n /** Maximum retries configured */\n readonly maxRetries: number\n\n /** The original error that caused the failure */\n readonly originalError?: Error\n\n constructor(triggerName: string, maxRetries: number, originalError?: Error) {\n super(\n 'MAX_RETRIES_EXCEEDED',\n `Trigger '${triggerName}' exceeded maximum retries (${maxRetries})`\n )\n this.name = 'TriggerMaxRetriesError'\n this.triggerName = triggerName\n this.maxRetries = maxRetries\n this.originalError = originalError\n }\n}\n\n// ============================================================================\n// Webhook Errors\n// ============================================================================\n\n/**\n * Error thrown when webhook signature verification fails.\n */\nexport class TriggerWebhookVerificationError extends TriggerError {\n /** The trigger name */\n readonly triggerName: string\n\n /** The verification error message */\n readonly verificationError?: string\n\n constructor(triggerName: string, verificationError?: string) {\n super(\n 'WEBHOOK_VERIFICATION_FAILED',\n `Webhook signature verification failed for trigger '${triggerName}'${\n verificationError ? `: ${verificationError}` : ''\n }`\n )\n this.name = 'TriggerWebhookVerificationError'\n this.triggerName = triggerName\n this.verificationError = verificationError\n }\n}\n","/**\n * @cloudwerk/trigger - defineTrigger()\n *\n * Factory function for creating trigger definitions.\n */\n\nimport type {\n TriggerConfig,\n TriggerDefinition,\n TriggerSource,\n RetryConfig,\n WebhookTriggerSource,\n ScheduledTriggerSource,\n} from './types.js'\nimport {\n TriggerConfigError,\n TriggerNoHandlerError,\n TriggerInvalidSourceError,\n TriggerInvalidCronError,\n TriggerInvalidWebhookPathError,\n} from './errors.js'\n\n// ============================================================================\n// Default Configuration\n// ============================================================================\n\nconst DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxAttempts: 3,\n delay: '1m',\n backoff: 'linear',\n}\n\nconst DEFAULT_TIMEOUT = 30000 // 30 seconds\n\n// ============================================================================\n// Duration Parsing\n// ============================================================================\n\n/**\n * Parse a duration string into seconds.\n *\n * Supports formats like:\n * - '30s' - 30 seconds\n * - '5m' - 5 minutes\n * - '1h' - 1 hour\n * - 60 - number of seconds\n *\n * @param duration - Duration string or number\n * @returns Duration in seconds\n */\nexport function parseDuration(duration: string | number): number {\n if (typeof duration === 'number') {\n return duration\n }\n\n const match = duration.match(/^(\\d+)(s|m|h)$/)\n if (!match) {\n throw new TriggerConfigError(\n `Invalid duration format: '${duration}'. Expected format like '30s', '5m', or '1h'`,\n 'duration'\n )\n }\n\n const value = parseInt(match[1], 10)\n const unit = match[2]\n\n switch (unit) {\n case 's':\n return value\n case 'm':\n return value * 60\n case 'h':\n return value * 3600\n default:\n throw new TriggerConfigError(`Unknown duration unit: ${unit}`, 'duration')\n }\n}\n\n// ============================================================================\n// Cron Validation\n// ============================================================================\n\n/**\n * Basic cron expression validation.\n *\n * Validates that the cron expression has the expected format.\n * Full validation is left to the runtime.\n *\n * @param cron - Cron expression to validate\n * @throws TriggerInvalidCronError if invalid\n */\nfunction validateCron(cron: string): void {\n const parts = cron.trim().split(/\\s+/)\n\n // Standard cron has 5 fields, extended has 6\n if (parts.length < 5 || parts.length > 6) {\n throw new TriggerInvalidCronError(\n cron,\n `Expected 5 or 6 fields, got ${parts.length}`\n )\n }\n\n // Basic validation of each field\n const fieldNames = ['minute', 'hour', 'day of month', 'month', 'day of week']\n if (parts.length === 6) {\n fieldNames.unshift('second')\n }\n\n const validChars = /^[\\d,\\-*/]+$/\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]\n // Allow wildcards and special characters\n if (part !== '*' && part !== '?' && !validChars.test(part)) {\n throw new TriggerInvalidCronError(\n cron,\n `Invalid characters in ${fieldNames[i]} field: '${part}'`\n )\n }\n }\n}\n\n// ============================================================================\n// Webhook Path Validation\n// ============================================================================\n\n/**\n * Validate a webhook path.\n *\n * @param path - Path to validate\n * @throws TriggerInvalidWebhookPathError if invalid\n */\nfunction validateWebhookPath(path: string): void {\n if (!path) {\n throw new TriggerInvalidWebhookPathError(path, 'Path cannot be empty')\n }\n\n if (!path.startsWith('/')) {\n throw new TriggerInvalidWebhookPathError(\n path,\n 'Path must start with /'\n )\n }\n\n // Check for invalid characters\n const validPath = /^[a-zA-Z0-9\\-_/:.]+$/\n if (!validPath.test(path)) {\n throw new TriggerInvalidWebhookPathError(\n path,\n 'Path contains invalid characters'\n )\n }\n\n // Check for double slashes\n if (path.includes('//')) {\n throw new TriggerInvalidWebhookPathError(\n path,\n 'Path cannot contain double slashes'\n )\n }\n}\n\n// ============================================================================\n// Source Validation\n// ============================================================================\n\n/**\n * Validate the trigger source configuration.\n *\n * @param source - Source configuration to validate\n * @throws TriggerInvalidSourceError if invalid\n */\nfunction validateSource(source: TriggerSource): void {\n if (!source || typeof source !== 'object') {\n throw new TriggerInvalidSourceError('Source must be an object')\n }\n\n if (!('type' in source)) {\n throw new TriggerInvalidSourceError('Source must have a type property')\n }\n\n switch (source.type) {\n case 'scheduled':\n validateScheduledSource(source as ScheduledTriggerSource)\n break\n case 'queue':\n if (!source.queue || typeof source.queue !== 'string') {\n throw new TriggerInvalidSourceError(\n 'Queue source must specify a queue name',\n 'queue'\n )\n }\n break\n case 'r2':\n if (!source.bucket || typeof source.bucket !== 'string') {\n throw new TriggerInvalidSourceError(\n 'R2 source must specify a bucket name',\n 'r2'\n )\n }\n if (!Array.isArray(source.events) || source.events.length === 0) {\n throw new TriggerInvalidSourceError(\n 'R2 source must specify at least one event type',\n 'r2'\n )\n }\n const validR2Events = ['object-create', 'object-delete']\n for (const event of source.events) {\n if (!validR2Events.includes(event)) {\n throw new TriggerInvalidSourceError(\n `Invalid R2 event type: '${event}'. Valid types: ${validR2Events.join(', ')}`,\n 'r2'\n )\n }\n }\n break\n case 'webhook':\n validateWebhookSource(source as WebhookTriggerSource)\n break\n case 'email':\n if (!source.address || typeof source.address !== 'string') {\n throw new TriggerInvalidSourceError(\n 'Email source must specify an address pattern',\n 'email'\n )\n }\n break\n case 'd1':\n if (!source.database || typeof source.database !== 'string') {\n throw new TriggerInvalidSourceError(\n 'D1 source must specify a database name',\n 'd1'\n )\n }\n if (!source.table || typeof source.table !== 'string') {\n throw new TriggerInvalidSourceError(\n 'D1 source must specify a table name',\n 'd1'\n )\n }\n if (!Array.isArray(source.events) || source.events.length === 0) {\n throw new TriggerInvalidSourceError(\n 'D1 source must specify at least one event type',\n 'd1'\n )\n }\n break\n case 'tail':\n if (!Array.isArray(source.consumers) || source.consumers.length === 0) {\n throw new TriggerInvalidSourceError(\n 'Tail source must specify at least one consumer',\n 'tail'\n )\n }\n break\n default:\n throw new TriggerInvalidSourceError(\n `Unknown source type: '${(source as TriggerSource).type}'`\n )\n }\n}\n\nfunction validateScheduledSource(source: ScheduledTriggerSource): void {\n if (!source.cron || typeof source.cron !== 'string') {\n throw new TriggerInvalidSourceError(\n 'Scheduled source must specify a cron expression',\n 'scheduled'\n )\n }\n validateCron(source.cron)\n}\n\nfunction validateWebhookSource(source: WebhookTriggerSource): void {\n if (!source.path) {\n throw new TriggerInvalidSourceError(\n 'Webhook source must specify a path',\n 'webhook'\n )\n }\n validateWebhookPath(source.path)\n\n if (source.methods) {\n const validMethods = ['POST', 'PUT', 'PATCH']\n for (const method of source.methods) {\n if (!validMethods.includes(method)) {\n throw new TriggerInvalidSourceError(\n `Invalid webhook method: '${method}'. Valid methods: ${validMethods.join(', ')}`,\n 'webhook'\n )\n }\n }\n }\n}\n\n// ============================================================================\n// Configuration Validation\n// ============================================================================\n\n/**\n * Validate trigger configuration.\n *\n * @param config - Trigger configuration to validate\n * @throws TriggerConfigError if configuration is invalid\n * @throws TriggerNoHandlerError if no handler is defined\n */\nfunction validateConfig<TSource extends TriggerSource>(\n config: TriggerConfig<TSource>\n): void {\n // Must have a handler\n if (!config.handle || typeof config.handle !== 'function') {\n throw new TriggerNoHandlerError(config.name || 'unknown')\n }\n\n // Must have a source\n if (!config.source) {\n throw new TriggerInvalidSourceError('Trigger must have a source')\n }\n\n // Validate source\n validateSource(config.source)\n\n // Validate retry config\n if (config.retry) {\n const { maxAttempts, delay, backoff } = config.retry\n\n if (maxAttempts !== undefined) {\n if (!Number.isInteger(maxAttempts) || maxAttempts < 0 || maxAttempts > 100) {\n throw new TriggerConfigError(\n 'maxAttempts must be an integer between 0 and 100',\n 'maxAttempts'\n )\n }\n }\n\n if (delay !== undefined) {\n // This will throw if invalid\n parseDuration(delay)\n }\n\n if (backoff !== undefined && backoff !== 'linear' && backoff !== 'exponential') {\n throw new TriggerConfigError(\n `Invalid backoff strategy: '${backoff}'. Valid strategies: 'linear', 'exponential'`,\n 'backoff'\n )\n }\n }\n\n // Validate timeout\n if (config.timeout !== undefined) {\n if (typeof config.timeout !== 'number' || config.timeout <= 0) {\n throw new TriggerConfigError(\n 'timeout must be a positive number (milliseconds)',\n 'timeout'\n )\n }\n\n // Warn about long timeouts (but don't error)\n // Cloudflare Workers have limits on execution time\n if (config.timeout > 600000) {\n throw new TriggerConfigError(\n 'timeout cannot exceed 600000ms (10 minutes)',\n 'timeout'\n )\n }\n }\n\n // Validate name if provided\n if (config.name !== undefined) {\n if (typeof config.name !== 'string' || config.name.length === 0) {\n throw new TriggerConfigError('name must be a non-empty string', 'name')\n }\n\n // Trigger names should be lowercase alphanumeric with hyphens\n if (!/^[a-z][a-z0-9-]*$/.test(config.name)) {\n throw new TriggerConfigError(\n 'name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens',\n 'name'\n )\n }\n }\n\n // Validate onError if provided\n if (config.onError !== undefined && typeof config.onError !== 'function') {\n throw new TriggerConfigError('onError must be a function', 'onError')\n }\n}\n\n// ============================================================================\n// defineTrigger()\n// ============================================================================\n\n/**\n * Define a trigger consumer.\n *\n * This function creates a trigger definition that will be automatically\n * discovered and registered by Cloudwerk during build.\n *\n * @typeParam TSource - The trigger source type\n * @param config - Trigger configuration\n * @returns Trigger definition\n *\n * @example\n * ```typescript\n * // app/triggers/daily-cleanup.ts\n * import { defineTrigger } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: { type: 'scheduled', cron: '0 0 * * *' },\n * async handle(event, ctx) {\n * console.log(`[${ctx.traceId}] Running cleanup at ${event.scheduledTime}`)\n * await cleanupOldRecords()\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // app/triggers/process-uploads.ts\n * import { defineTrigger } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'r2',\n * bucket: 'uploads',\n * events: ['object-create'],\n * prefix: 'images/',\n * },\n * retry: { maxAttempts: 5, delay: '30s' },\n * async handle(event, ctx) {\n * console.log(`New file: ${event.key}`)\n * await processImage(event.key)\n * },\n * async onError(error, event, ctx) {\n * await reportError(error, { key: event.key, traceId: ctx.traceId })\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // app/triggers/stripe-webhook.ts\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/stripe',\n * verify: verifiers.stripe(STRIPE_WEBHOOK_SECRET),\n * },\n * async handle(event, ctx) {\n * switch (event.payload.type) {\n * case 'checkout.session.completed':\n * await handleCheckoutComplete(event.payload)\n * break\n * }\n * }\n * })\n * ```\n */\nexport function defineTrigger<TSource extends TriggerSource>(\n config: TriggerConfig<TSource>\n): TriggerDefinition<TSource> {\n // Validate configuration\n validateConfig(config)\n\n // Merge retry config with defaults\n const mergedRetryConfig: Required<RetryConfig> = {\n ...DEFAULT_RETRY_CONFIG,\n ...config.retry,\n }\n\n // Create the definition object\n const definition: TriggerDefinition<TSource> = {\n __brand: 'cloudwerk-trigger',\n name: config.name,\n source: config.source,\n retry: mergedRetryConfig,\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n handle: config.handle,\n onError: config.onError,\n }\n\n return definition\n}\n\n/**\n * Check if a value is a trigger definition created by defineTrigger().\n *\n * @param value - Value to check\n * @returns true if value is a TriggerDefinition\n */\nexport function isTriggerDefinition(value: unknown): value is TriggerDefinition {\n return (\n typeof value === 'object' &&\n value !== null &&\n '__brand' in value &&\n (value as TriggerDefinition).__brand === 'cloudwerk-trigger'\n )\n}\n\n/**\n * Get the source type from a trigger definition.\n *\n * @param definition - Trigger definition\n * @returns The source type string\n */\nexport function getTriggerSourceType(\n definition: TriggerDefinition\n): TriggerSource['type'] {\n return definition.source.type\n}\n","/**\n * Utility functions for webhook signature verification.\n */\n\n/**\n * Compute HMAC signature using Web Crypto API.\n *\n * @param algorithm - Hash algorithm ('SHA-256', 'SHA-1', etc.)\n * @param secret - Secret key as string\n * @param data - Data to sign as ArrayBuffer\n * @returns Hex-encoded signature\n */\nexport async function computeHmac(\n algorithm: 'SHA-256' | 'SHA-1' | 'SHA-512',\n secret: string,\n data: ArrayBuffer\n): Promise<string> {\n const encoder = new TextEncoder()\n const keyData = encoder.encode(secret)\n\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: algorithm },\n false,\n ['sign']\n )\n\n const signature = await crypto.subtle.sign('HMAC', cryptoKey, data)\n return arrayBufferToHex(signature)\n}\n\n/**\n * Convert ArrayBuffer to hex string.\n */\nexport function arrayBufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer)\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Timing-safe string comparison to prevent timing attacks.\n *\n * @param a - First string\n * @param b - Second string\n * @returns true if strings are equal\n */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n const aBytes = new TextEncoder().encode(a)\n const bBytes = new TextEncoder().encode(b)\n\n let result = 0\n for (let i = 0; i < aBytes.length; i++) {\n result |= aBytes[i] ^ bBytes[i]\n }\n\n return result === 0\n}\n\n/**\n * Parse a signature header that contains key=value pairs.\n *\n * @param header - Header value (e.g., \"t=123,v1=abc\")\n * @param separator - Separator between pairs (default: ',')\n * @returns Map of key-value pairs\n */\nexport function parseSignatureHeader(\n header: string,\n separator: string = ','\n): Map<string, string> {\n const result = new Map<string, string>()\n\n for (const part of header.split(separator)) {\n const [key, ...valueParts] = part.trim().split('=')\n if (key && valueParts.length > 0) {\n result.set(key, valueParts.join('='))\n }\n }\n\n return result\n}\n","/**\n * Stripe webhook signature verifier.\n *\n * @see https://stripe.com/docs/webhooks/signatures\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual, parseSignatureHeader } from './utils.js'\n\n/**\n * Default tolerance for timestamp validation (5 minutes).\n */\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\nexport interface StripeVerifierOptions {\n /**\n * Tolerance for timestamp validation in seconds.\n * @default 300 (5 minutes)\n */\n tolerance?: number\n}\n\n/**\n * Create a Stripe webhook signature verifier.\n *\n * @param secret - Stripe webhook signing secret (whsec_...)\n * @param options - Verifier options\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/stripe',\n * verify: verifiers.stripe(process.env.STRIPE_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const stripeEvent = event.payload\n * switch (stripeEvent.type) {\n * case 'checkout.session.completed':\n * // Handle checkout completion\n * break\n * }\n * }\n * })\n * ```\n */\nexport function stripeVerifier(\n secret: string,\n options: StripeVerifierOptions = {}\n): WebhookVerifier {\n const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('stripe-signature')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing stripe-signature header',\n }\n }\n\n // Parse signature header (t=timestamp,v1=signature)\n const parts = parseSignatureHeader(signature)\n const timestamp = parts.get('t')\n const expectedSignature = parts.get('v1')\n\n if (!timestamp || !expectedSignature) {\n return {\n valid: false,\n error: 'Invalid stripe-signature header format',\n }\n }\n\n // Validate timestamp\n const timestampNum = parseInt(timestamp, 10)\n if (isNaN(timestampNum)) {\n return {\n valid: false,\n error: 'Invalid timestamp in signature',\n }\n }\n\n const now = Math.floor(Date.now() / 1000)\n if (Math.abs(now - timestampNum) > tolerance) {\n return {\n valid: false,\n error: `Timestamp outside tolerance (${tolerance}s)`,\n }\n }\n\n // Compute expected signature\n const payload = new TextDecoder().decode(rawBody)\n const signedPayload = `${timestamp}.${payload}`\n const signedPayloadBytes = new TextEncoder().encode(signedPayload)\n\n const computedSignature = await computeHmac(\n 'SHA-256',\n secret,\n signedPayloadBytes.buffer\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, expectedSignature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for stripeVerifier.\n */\nexport const stripe = stripeVerifier\n","/**\n * GitHub webhook signature verifier.\n *\n * @see https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Create a GitHub webhook signature verifier.\n *\n * @param secret - GitHub webhook secret\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/github',\n * verify: verifiers.github(process.env.GITHUB_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const githubEvent = event.headers.get('x-github-event')\n * switch (githubEvent) {\n * case 'push':\n * // Handle push event\n * break\n * case 'pull_request':\n * // Handle PR event\n * break\n * }\n * }\n * })\n * ```\n */\nexport function githubVerifier(secret: string): WebhookVerifier {\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n // GitHub uses X-Hub-Signature-256 (SHA-256) or X-Hub-Signature (SHA-1)\n const signatureHeader =\n request.headers.get('x-hub-signature-256') ||\n request.headers.get('x-hub-signature')\n\n if (!signatureHeader) {\n return {\n valid: false,\n error: 'Missing X-Hub-Signature-256 or X-Hub-Signature header',\n }\n }\n\n // Determine algorithm based on header\n const useSha256 = signatureHeader.startsWith('sha256=')\n const algorithm = useSha256 ? 'SHA-256' : 'SHA-1'\n const prefix = useSha256 ? 'sha256=' : 'sha1='\n\n if (!signatureHeader.startsWith(prefix)) {\n return {\n valid: false,\n error: `Invalid signature format (expected ${prefix} prefix)`,\n }\n }\n\n const expectedSignature = signatureHeader.slice(prefix.length)\n\n // Compute signature\n const computedSignature = await computeHmac(algorithm, secret, rawBody)\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, expectedSignature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for githubVerifier.\n */\nexport const github = githubVerifier\n","/**\n * Slack webhook signature verifier.\n *\n * @see https://api.slack.com/authentication/verifying-requests-from-slack\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Default tolerance for timestamp validation (5 minutes).\n */\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\nexport interface SlackVerifierOptions {\n /**\n * Tolerance for timestamp validation in seconds.\n * @default 300 (5 minutes)\n */\n tolerance?: number\n}\n\n/**\n * Create a Slack webhook signature verifier.\n *\n * @param signingSecret - Slack signing secret\n * @param options - Verifier options\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/slack',\n * verify: verifiers.slack(process.env.SLACK_SIGNING_SECRET),\n * },\n * async handle(event) {\n * const payload = event.payload\n * // Handle Slack event\n * }\n * })\n * ```\n */\nexport function slackVerifier(\n signingSecret: string,\n options: SlackVerifierOptions = {}\n): WebhookVerifier {\n const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const timestamp = request.headers.get('x-slack-request-timestamp')\n const signature = request.headers.get('x-slack-signature')\n\n if (!timestamp) {\n return {\n valid: false,\n error: 'Missing X-Slack-Request-Timestamp header',\n }\n }\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing X-Slack-Signature header',\n }\n }\n\n // Validate timestamp\n const timestampNum = parseInt(timestamp, 10)\n if (isNaN(timestampNum)) {\n return {\n valid: false,\n error: 'Invalid timestamp',\n }\n }\n\n const now = Math.floor(Date.now() / 1000)\n if (Math.abs(now - timestampNum) > tolerance) {\n return {\n valid: false,\n error: `Timestamp outside tolerance (${tolerance}s)`,\n }\n }\n\n // Verify signature format\n if (!signature.startsWith('v0=')) {\n return {\n valid: false,\n error: 'Invalid signature format (expected v0= prefix)',\n }\n }\n\n const expectedSignature = signature.slice(3)\n\n // Compute signature: v0:timestamp:body\n const body = new TextDecoder().decode(rawBody)\n const sigBasestring = `v0:${timestamp}:${body}`\n const sigBasestringBytes = new TextEncoder().encode(sigBasestring)\n\n const computedSignature = await computeHmac(\n 'SHA-256',\n signingSecret,\n sigBasestringBytes.buffer\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, expectedSignature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for slackVerifier.\n */\nexport const slack = slackVerifier\n","/**\n * Twilio webhook signature verifier.\n *\n * @see https://www.twilio.com/docs/usage/security#validating-requests\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\nexport interface TwilioVerifierOptions {\n /**\n * The full URL of your webhook endpoint.\n * Required for signature validation.\n */\n url: string\n}\n\n/**\n * Create a Twilio webhook signature verifier.\n *\n * @param authToken - Twilio Auth Token\n * @param options - Verifier options including the webhook URL\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/twilio',\n * verify: verifiers.twilio(process.env.TWILIO_AUTH_TOKEN, {\n * url: 'https://your-app.com/webhooks/twilio',\n * }),\n * },\n * async handle(event) {\n * const { From, Body } = event.payload\n * // Handle SMS or voice webhook\n * }\n * })\n * ```\n */\nexport function twilioVerifier(\n authToken: string,\n options: TwilioVerifierOptions\n): WebhookVerifier {\n const { url } = options\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('x-twilio-signature')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing X-Twilio-Signature header',\n }\n }\n\n // Parse form data from body\n const body = new TextDecoder().decode(rawBody)\n const params = new URLSearchParams(body)\n\n // Sort parameters alphabetically and concatenate\n const sortedParams = Array.from(params.entries())\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, value]) => `${key}${value}`)\n .join('')\n\n // Signature is computed over URL + sorted params\n const signatureBase = url + sortedParams\n const signatureBaseBytes = new TextEncoder().encode(signatureBase)\n\n // Twilio uses SHA-1 HMAC, base64 encoded\n const computedSignatureHex = await computeHmac(\n 'SHA-1',\n authToken,\n signatureBaseBytes.buffer\n )\n\n // Convert hex to base64\n const computedSignatureBytes = new Uint8Array(\n computedSignatureHex.match(/.{2}/g)!.map((byte) => parseInt(byte, 16))\n )\n const computedSignature = btoa(\n String.fromCharCode(...computedSignatureBytes)\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for twilioVerifier.\n */\nexport const twilio = twilioVerifier\n","/**\n * Shopify webhook signature verifier.\n *\n * @see https://shopify.dev/docs/apps/webhooks/configuration/https#step-5-verify-the-webhook\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Create a Shopify webhook signature verifier.\n *\n * @param secret - Shopify webhook signing secret (from Partner Dashboard)\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/shopify',\n * verify: verifiers.shopify(process.env.SHOPIFY_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const topic = event.headers.get('x-shopify-topic')\n * switch (topic) {\n * case 'orders/create':\n * // Handle new order\n * break\n * case 'products/update':\n * // Handle product update\n * break\n * }\n * }\n * })\n * ```\n */\nexport function shopifyVerifier(secret: string): WebhookVerifier {\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('x-shopify-hmac-sha256')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing X-Shopify-Hmac-Sha256 header',\n }\n }\n\n // Compute HMAC-SHA256\n const computedSignatureHex = await computeHmac('SHA-256', secret, rawBody)\n\n // Convert hex to base64 (Shopify uses base64)\n const computedSignatureBytes = new Uint8Array(\n computedSignatureHex.match(/.{2}/g)!.map((byte) => parseInt(byte, 16))\n )\n const computedSignature = btoa(\n String.fromCharCode(...computedSignatureBytes)\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for shopifyVerifier.\n */\nexport const shopify = shopifyVerifier\n","/**\n * Linear webhook signature verifier.\n *\n * @see https://developers.linear.app/docs/graphql/webhooks#webhook-security\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Create a Linear webhook signature verifier.\n *\n * @param secret - Linear webhook signing secret\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/linear',\n * verify: verifiers.linear(process.env.LINEAR_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const { action, type, data } = event.payload\n * switch (type) {\n * case 'Issue':\n * if (action === 'create') {\n * // Handle new issue\n * }\n * break\n * }\n * }\n * })\n * ```\n */\nexport function linearVerifier(secret: string): WebhookVerifier {\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('linear-signature')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing Linear-Signature header',\n }\n }\n\n // Compute HMAC-SHA256\n const computedSignature = await computeHmac('SHA-256', secret, rawBody)\n\n // Compare signatures (Linear uses hex)\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for linearVerifier.\n */\nexport const linear = linearVerifier\n","/**\n * Custom webhook signature verifier factory.\n *\n * Create verifiers for any webhook provider using common patterns.\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Signature encoding format.\n */\nexport type SignatureEncoding = 'hex' | 'base64'\n\n/**\n * Hash algorithm for HMAC computation.\n */\nexport type HashAlgorithm = 'SHA-256' | 'SHA-1' | 'SHA-512'\n\nexport interface CustomVerifierOptions {\n /**\n * Name of the header containing the signature.\n * @example 'x-webhook-signature'\n */\n header: string\n\n /**\n * Hash algorithm to use.\n * @default 'SHA-256'\n */\n algorithm?: HashAlgorithm\n\n /**\n * Encoding of the signature in the header.\n * @default 'hex'\n */\n encoding?: SignatureEncoding\n\n /**\n * Prefix before the signature (e.g., 'sha256=').\n * Will be stripped before comparison.\n */\n prefix?: string\n\n /**\n * Optional timestamp header for replay attack prevention.\n */\n timestampHeader?: string\n\n /**\n * Tolerance for timestamp validation in seconds.\n * Only used if timestampHeader is set.\n * @default 300\n */\n timestampTolerance?: number\n\n /**\n * Function to build the signature base string.\n * By default, just uses the raw body.\n *\n * @param body - Raw request body as string\n * @param timestamp - Timestamp value if timestampHeader is set\n * @returns String to compute signature over\n */\n buildSignatureBase?: (body: string, timestamp?: string) => string\n}\n\n/**\n * Create a custom webhook signature verifier.\n *\n * @param secret - Webhook signing secret\n * @param options - Verifier configuration\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * // Simple HMAC-SHA256 with hex encoding\n * verifiers.custom(secret, {\n * header: 'x-webhook-signature',\n * algorithm: 'SHA-256',\n * encoding: 'hex',\n * })\n *\n * // With timestamp validation (like Stripe)\n * verifiers.custom(secret, {\n * header: 'x-signature',\n * timestampHeader: 'x-timestamp',\n * timestampTolerance: 300,\n * buildSignatureBase: (body, timestamp) => `${timestamp}.${body}`,\n * })\n *\n * // With prefix (like GitHub)\n * verifiers.custom(secret, {\n * header: 'x-hub-signature-256',\n * prefix: 'sha256=',\n * })\n * ```\n */\nexport function customVerifier(\n secret: string,\n options: CustomVerifierOptions\n): WebhookVerifier {\n const {\n header,\n algorithm = 'SHA-256',\n encoding = 'hex',\n prefix,\n timestampHeader,\n timestampTolerance = 300,\n buildSignatureBase,\n } = options\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n // Get signature header\n let signature = request.headers.get(header)\n\n if (!signature) {\n return {\n valid: false,\n error: `Missing ${header} header`,\n }\n }\n\n // Strip prefix if present\n if (prefix) {\n if (!signature.startsWith(prefix)) {\n return {\n valid: false,\n error: `Invalid signature format (expected ${prefix} prefix)`,\n }\n }\n signature = signature.slice(prefix.length)\n }\n\n // Validate timestamp if configured\n let timestamp: string | undefined\n if (timestampHeader) {\n timestamp = request.headers.get(timestampHeader) ?? undefined\n\n if (!timestamp) {\n return {\n valid: false,\n error: `Missing ${timestampHeader} header`,\n }\n }\n\n const timestampNum = parseInt(timestamp, 10)\n if (isNaN(timestampNum)) {\n return {\n valid: false,\n error: 'Invalid timestamp',\n }\n }\n\n const now = Math.floor(Date.now() / 1000)\n if (Math.abs(now - timestampNum) > timestampTolerance) {\n return {\n valid: false,\n error: `Timestamp outside tolerance (${timestampTolerance}s)`,\n }\n }\n }\n\n // Build signature base\n const body = new TextDecoder().decode(rawBody)\n const signatureBase = buildSignatureBase\n ? buildSignatureBase(body, timestamp)\n : body\n const signatureBaseBytes = new TextEncoder().encode(signatureBase)\n\n // Compute HMAC\n const computedSignatureHex = await computeHmac(\n algorithm,\n secret,\n signatureBaseBytes.buffer\n )\n\n // Convert to expected encoding\n let computedSignature: string\n if (encoding === 'base64') {\n const bytes = new Uint8Array(\n computedSignatureHex.match(/.{2}/g)!.map((byte) => parseInt(byte, 16))\n )\n computedSignature = btoa(String.fromCharCode(...bytes))\n } else {\n computedSignature = computedSignatureHex\n }\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for customVerifier.\n */\nexport const custom = customVerifier\n","/**\n * @cloudwerk/trigger - Webhook Signature Verifiers\n *\n * Built-in verifiers for popular webhook providers.\n */\n\nexport { stripeVerifier, stripe } from './stripe.js'\nexport { githubVerifier, github } from './github.js'\nexport { slackVerifier, slack } from './slack.js'\nexport { twilioVerifier, twilio } from './twilio.js'\nexport { shopifyVerifier, shopify } from './shopify.js'\nexport { linearVerifier, linear } from './linear.js'\nexport { customVerifier, custom } from './custom.js'\n\n// Re-export all verifiers as a convenience object\nimport { stripe } from './stripe.js'\nimport { github } from './github.js'\nimport { slack } from './slack.js'\nimport { twilio } from './twilio.js'\nimport { shopify } from './shopify.js'\nimport { linear } from './linear.js'\nimport { custom } from './custom.js'\n\n/**\n * Collection of all built-in webhook verifiers.\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/stripe',\n * verify: verifiers.stripe(STRIPE_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * // Handle verified webhook\n * }\n * })\n * ```\n */\nexport const verifiers = {\n stripe,\n github,\n slack,\n twilio,\n shopify,\n linear,\n custom,\n} as const\n","/**\n * @cloudwerk/trigger - Trigger Chaining via emit()\n *\n * Allows triggers to invoke other triggers with automatic\n * trace ID propagation for observability.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport type { TriggerContext } from './types.js'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for emitting to a trigger.\n */\nexport interface EmitOptions {\n /**\n * Delay before the trigger executes (in milliseconds).\n * @default 0\n */\n delay?: number\n\n /**\n * Custom trace ID (defaults to current context's traceId).\n */\n traceId?: string\n}\n\n/**\n * Result of an emit operation.\n */\nexport interface EmitResult {\n /** Whether the emit was successful */\n success: boolean\n /** The trace ID used for this emission */\n traceId: string\n /** Target trigger name */\n trigger: string\n /** Timestamp when emit was called */\n emittedAt: Date\n}\n\n/**\n * Pending emission that was queued during execution.\n */\nexport interface PendingEmission {\n trigger: string\n payload: unknown\n options: EmitOptions\n traceId: string\n emittedAt: Date\n}\n\n/**\n * Emitter interface for different execution modes.\n */\nexport interface TriggerEmitter {\n /**\n * Emit to a trigger.\n */\n emit(trigger: string, payload: unknown, options?: EmitOptions): Promise<EmitResult>\n\n /**\n * Get all pending emissions (for testing/debugging).\n */\n getPendingEmissions(): PendingEmission[]\n\n /**\n * Clear pending emissions.\n */\n clearPendingEmissions(): void\n}\n\n// ============================================================================\n// Context Storage\n// ============================================================================\n\n/**\n * Store for current trigger context, used for trace ID propagation.\n */\nconst contextStore = new AsyncLocalStorage<TriggerContext>()\n\n/**\n * Store for pending emissions during trigger execution.\n */\nconst emissionsStore = new AsyncLocalStorage<PendingEmission[]>()\n\n/**\n * Run a function within a trigger context.\n *\n * @param context - Trigger context\n * @param fn - Function to execute\n * @returns Function result\n */\nexport function runWithTriggerContext<T>(\n context: TriggerContext,\n fn: () => T\n): T {\n return contextStore.run(context, () => {\n return emissionsStore.run([], fn)\n })\n}\n\n/**\n * Get the current trigger context.\n *\n * @returns Current context or undefined if not in a trigger\n */\nexport function getTriggerContext(): TriggerContext | undefined {\n return contextStore.getStore()\n}\n\n/**\n * Get pending emissions from the current context.\n */\nexport function getPendingEmissions(): PendingEmission[] {\n return emissionsStore.getStore() ?? []\n}\n\n// ============================================================================\n// Trace ID Generation\n// ============================================================================\n\n/**\n * Generate a new trace ID.\n *\n * @param prefix - Optional prefix (default: 'tr')\n * @returns Trace ID string\n */\nexport function generateTraceId(prefix: string = 'tr'): string {\n const random = crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n return `${prefix}_${random}`\n}\n\n/**\n * Create a child trace ID from a parent.\n *\n * @param parentTraceId - Parent trace ID\n * @returns Child trace ID\n */\nexport function createChildTraceId(parentTraceId: string): string {\n const suffix = crypto.randomUUID().slice(0, 8)\n return `${parentTraceId}.${suffix}`\n}\n\n// ============================================================================\n// emit() Function\n// ============================================================================\n\n/**\n * Emit an event to another trigger.\n *\n * This function queues an event to be processed by another trigger,\n * with automatic trace ID propagation for distributed tracing.\n *\n * @param trigger - Name of the trigger to invoke\n * @param payload - Data to pass to the trigger\n * @param options - Emit options\n * @returns EmitResult\n *\n * @example\n * ```typescript\n * import { defineTrigger, emit } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: { type: 'r2', bucket: 'uploads', events: ['object-create'] },\n * async handle(event, ctx) {\n * // Process the upload\n * const metadata = await processFile(event.key)\n *\n * // Chain to other triggers\n * await emit('index-for-search', { key: event.key, metadata })\n * await emit('generate-thumbnail', { key: event.key })\n *\n * // Trace ID is automatically propagated\n * console.log(ctx.traceId) // Same traceId in chained triggers\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With delay\n * await emit('send-reminder', { userId }, { delay: 60000 }) // 1 minute delay\n * ```\n */\nexport async function emit(\n trigger: string,\n payload: unknown,\n options: EmitOptions = {}\n): Promise<EmitResult> {\n const context = getTriggerContext()\n const emissions = emissionsStore.getStore()\n\n // Determine trace ID\n const traceId = options.traceId ?? context?.traceId ?? generateTraceId()\n const childTraceId = context ? createChildTraceId(traceId) : traceId\n\n const emittedAt = new Date()\n\n // Create pending emission record\n const emission: PendingEmission = {\n trigger,\n payload,\n options,\n traceId: childTraceId,\n emittedAt,\n }\n\n // Queue the emission\n if (emissions) {\n emissions.push(emission)\n }\n\n // In production, this would:\n // 1. For same-worker triggers: queue for immediate execution\n // 2. For cross-worker triggers: publish to internal queue\n //\n // For now, we just record the emission. The runtime dispatcher\n // will process these after the current handler completes.\n\n return {\n success: true,\n traceId: childTraceId,\n trigger,\n emittedAt,\n }\n}\n\n/**\n * Emit to multiple triggers in parallel.\n *\n * @param emissions - Array of [trigger, payload] pairs\n * @param options - Shared options for all emissions\n * @returns Array of EmitResults\n *\n * @example\n * ```typescript\n * const results = await emitMany([\n * ['process-image', { key: 'image.jpg' }],\n * ['update-index', { key: 'image.jpg' }],\n * ['notify-user', { userId, message: 'Upload complete' }],\n * ])\n * ```\n */\nexport async function emitMany(\n emissions: [string, unknown][],\n options: EmitOptions = {}\n): Promise<EmitResult[]> {\n return Promise.all(\n emissions.map(([trigger, payload]) => emit(trigger, payload, options))\n )\n}\n\n// ============================================================================\n// Default Emitter\n// ============================================================================\n\n/**\n * Default trigger emitter implementation.\n *\n * In production, this would be replaced with an implementation\n * that actually dispatches to other triggers via queues or\n * direct function calls.\n */\nexport const defaultEmitter: TriggerEmitter = {\n emit,\n getPendingEmissions,\n clearPendingEmissions: () => {\n const emissions = emissionsStore.getStore()\n if (emissions) {\n emissions.length = 0\n }\n },\n}\n","/**\n * @cloudwerk/trigger - Observability & Metrics\n *\n * Utilities for tracing, metrics collection, and monitoring trigger executions.\n */\n\nimport type { TriggerContext, TriggerSource } from './types.js'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Trigger execution metrics.\n */\nexport interface TriggerMetrics {\n /** Trigger name */\n name: string\n /** Trigger source type */\n sourceType: TriggerSource['type']\n /** Execution start timestamp */\n startedAt: Date\n /** Execution end timestamp */\n endedAt?: Date\n /** Duration in milliseconds */\n durationMs?: number\n /** Whether execution was successful */\n success: boolean\n /** Error if execution failed */\n error?: Error\n /** Number of retries */\n retryCount: number\n /** Trace ID for distributed tracing */\n traceId: string\n}\n\n/**\n * Trigger execution span for tracing.\n */\nexport interface TriggerSpan {\n /** Span ID */\n spanId: string\n /** Trace ID */\n traceId: string\n /** Parent span ID (for nested triggers) */\n parentSpanId?: string\n /** Trigger name */\n triggerName: string\n /** Source type */\n sourceType: TriggerSource['type']\n /** Operation name */\n operationName: string\n /** Start time (Unix timestamp ms) */\n startTime: number\n /** End time (Unix timestamp ms) */\n endTime?: number\n /** Span status */\n status: 'ok' | 'error' | 'unset'\n /** Attributes */\n attributes: Record<string, string | number | boolean>\n /** Events that occurred during the span */\n events: SpanEvent[]\n}\n\n/**\n * Event that occurred during a span.\n */\nexport interface SpanEvent {\n /** Event name */\n name: string\n /** Timestamp (Unix ms) */\n timestamp: number\n /** Event attributes */\n attributes?: Record<string, string | number | boolean>\n}\n\n/**\n * Metrics reporter interface for pluggable metrics backends.\n */\nexport interface MetricsReporter {\n /** Report execution metrics */\n reportExecution(metrics: TriggerMetrics): void\n /** Report a span */\n reportSpan(span: TriggerSpan): void\n /** Flush pending metrics */\n flush(): Promise<void>\n}\n\n// ============================================================================\n// Span Creation\n// ============================================================================\n\n/**\n * Generate a span ID.\n */\nexport function generateSpanId(): string {\n return crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n}\n\n/**\n * Create a new trigger span.\n */\nexport function createTriggerSpan(\n triggerName: string,\n sourceType: TriggerSource['type'],\n traceId: string,\n parentSpanId?: string\n): TriggerSpan {\n return {\n spanId: generateSpanId(),\n traceId,\n parentSpanId,\n triggerName,\n sourceType,\n operationName: `trigger.${triggerName}`,\n startTime: Date.now(),\n status: 'unset',\n attributes: {\n 'trigger.name': triggerName,\n 'trigger.source_type': sourceType,\n },\n events: [],\n }\n}\n\n/**\n * End a trigger span.\n */\nexport function endTriggerSpan(\n span: TriggerSpan,\n success: boolean,\n error?: Error\n): TriggerSpan {\n span.endTime = Date.now()\n span.status = success ? 'ok' : 'error'\n\n if (error) {\n span.attributes['error.type'] = error.name\n span.attributes['error.message'] = error.message\n span.events.push({\n name: 'exception',\n timestamp: Date.now(),\n attributes: {\n 'exception.type': error.name,\n 'exception.message': error.message,\n },\n })\n }\n\n return span\n}\n\n/**\n * Add an event to a span.\n */\nexport function addSpanEvent(\n span: TriggerSpan,\n name: string,\n attributes?: Record<string, string | number | boolean>\n): void {\n span.events.push({\n name,\n timestamp: Date.now(),\n attributes,\n })\n}\n\n/**\n * Add attributes to a span.\n */\nexport function setSpanAttributes(\n span: TriggerSpan,\n attributes: Record<string, string | number | boolean>\n): void {\n Object.assign(span.attributes, attributes)\n}\n\n// ============================================================================\n// Execution Timer\n// ============================================================================\n\n/**\n * Timer for measuring trigger execution duration.\n */\nexport class ExecutionTimer {\n private startTime: number\n private endTime?: number\n private marks: Map<string, number> = new Map()\n\n constructor() {\n this.startTime = performance.now()\n }\n\n /**\n * Mark a point in time.\n */\n mark(name: string): void {\n this.marks.set(name, performance.now())\n }\n\n /**\n * Get time since start to a mark.\n */\n getMarkDuration(name: string): number | undefined {\n const markTime = this.marks.get(name)\n if (markTime === undefined) return undefined\n return markTime - this.startTime\n }\n\n /**\n * Stop the timer.\n */\n stop(): number {\n this.endTime = performance.now()\n return this.duration\n }\n\n /**\n * Get total duration in milliseconds.\n */\n get duration(): number {\n const end = this.endTime ?? performance.now()\n return end - this.startTime\n }\n\n /**\n * Get all marks with their durations.\n */\n getMarks(): Record<string, number> {\n const result: Record<string, number> = {}\n for (const [name, time] of this.marks) {\n result[name] = time - this.startTime\n }\n return result\n }\n}\n\n// ============================================================================\n// Default Console Reporter\n// ============================================================================\n\n/**\n * Console-based metrics reporter for development.\n */\nexport class ConsoleMetricsReporter implements MetricsReporter {\n private prefix: string\n\n constructor(prefix: string = '[trigger]') {\n this.prefix = prefix\n }\n\n reportExecution(metrics: TriggerMetrics): void {\n const status = metrics.success ? 'SUCCESS' : 'FAILED'\n const duration = metrics.durationMs ? `${metrics.durationMs}ms` : 'unknown'\n\n console.log(\n `${this.prefix} ${metrics.name} [${metrics.traceId}] ${status} (${duration})`\n )\n\n if (metrics.error) {\n console.error(`${this.prefix} Error:`, metrics.error.message)\n }\n }\n\n reportSpan(span: TriggerSpan): void {\n const duration = span.endTime\n ? `${span.endTime - span.startTime}ms`\n : 'ongoing'\n\n console.log(\n `${this.prefix} Span: ${span.operationName} [${span.traceId}] ${span.status} (${duration})`\n )\n\n for (const event of span.events) {\n console.log(`${this.prefix} Event: ${event.name}`)\n }\n }\n\n async flush(): Promise<void> {\n // Console reporter doesn't need flushing\n }\n}\n\n// ============================================================================\n// No-Op Reporter\n// ============================================================================\n\n/**\n * No-op metrics reporter that discards all metrics.\n */\nexport class NoOpMetricsReporter implements MetricsReporter {\n reportExecution(_metrics: TriggerMetrics): void {\n // Intentionally empty\n }\n\n reportSpan(_span: TriggerSpan): void {\n // Intentionally empty\n }\n\n async flush(): Promise<void> {\n // Intentionally empty\n }\n}\n\n// ============================================================================\n// Metrics Collector\n// ============================================================================\n\n/**\n * Collect and aggregate trigger metrics.\n */\nexport class MetricsCollector {\n private executions: TriggerMetrics[] = []\n private spans: TriggerSpan[] = []\n private reporter: MetricsReporter\n\n constructor(reporter: MetricsReporter = new NoOpMetricsReporter()) {\n this.reporter = reporter\n }\n\n /**\n * Record a trigger execution.\n */\n recordExecution(metrics: TriggerMetrics): void {\n this.executions.push(metrics)\n this.reporter.reportExecution(metrics)\n }\n\n /**\n * Record a span.\n */\n recordSpan(span: TriggerSpan): void {\n this.spans.push(span)\n this.reporter.reportSpan(span)\n }\n\n /**\n * Get execution count for a trigger.\n */\n getExecutionCount(triggerName: string): number {\n return this.executions.filter((m) => m.name === triggerName).length\n }\n\n /**\n * Get success rate for a trigger.\n */\n getSuccessRate(triggerName: string): number {\n const executions = this.executions.filter((m) => m.name === triggerName)\n if (executions.length === 0) return 0\n const successful = executions.filter((m) => m.success).length\n return successful / executions.length\n }\n\n /**\n * Get average duration for a trigger.\n */\n getAverageDuration(triggerName: string): number | undefined {\n const executions = this.executions.filter(\n (m) => m.name === triggerName && m.durationMs !== undefined\n )\n if (executions.length === 0) return undefined\n const total = executions.reduce((sum, m) => sum + (m.durationMs ?? 0), 0)\n return total / executions.length\n }\n\n /**\n * Get percentile duration for a trigger.\n */\n getPercentileDuration(\n triggerName: string,\n percentile: number\n ): number | undefined {\n const executions = this.executions\n .filter((m) => m.name === triggerName && m.durationMs !== undefined)\n .map((m) => m.durationMs!)\n .sort((a, b) => a - b)\n\n if (executions.length === 0) return undefined\n\n const index = Math.ceil((percentile / 100) * executions.length) - 1\n return executions[Math.max(0, index)]\n }\n\n /**\n * Get summary statistics for a trigger.\n */\n getSummary(triggerName: string): {\n count: number\n successRate: number\n avgDurationMs?: number\n p50DurationMs?: number\n p95DurationMs?: number\n p99DurationMs?: number\n } {\n return {\n count: this.getExecutionCount(triggerName),\n successRate: this.getSuccessRate(triggerName),\n avgDurationMs: this.getAverageDuration(triggerName),\n p50DurationMs: this.getPercentileDuration(triggerName, 50),\n p95DurationMs: this.getPercentileDuration(triggerName, 95),\n p99DurationMs: this.getPercentileDuration(triggerName, 99),\n }\n }\n\n /**\n * Flush metrics to the reporter.\n */\n async flush(): Promise<void> {\n await this.reporter.flush()\n }\n\n /**\n * Clear collected metrics.\n */\n clear(): void {\n this.executions = []\n this.spans = []\n }\n}\n\n// ============================================================================\n// Helper to create metrics from execution\n// ============================================================================\n\n/**\n * Create trigger metrics from an execution.\n */\nexport function createTriggerMetrics(\n name: string,\n sourceType: TriggerSource['type'],\n ctx: TriggerContext,\n timer: ExecutionTimer,\n success: boolean,\n error?: Error,\n retryCount: number = 0\n): TriggerMetrics {\n const durationMs = timer.stop()\n\n return {\n name,\n sourceType,\n startedAt: new Date(Date.now() - durationMs),\n endedAt: new Date(),\n durationMs,\n success,\n error,\n retryCount,\n traceId: ctx.traceId,\n }\n}\n"],"mappings":"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","names":["DEFAULT_TOLERANCE_SECONDS"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/define-trigger.ts","../src/verifiers/utils.ts","../src/verifiers/stripe.ts","../src/verifiers/github.ts","../src/verifiers/slack.ts","../src/verifiers/twilio.ts","../src/verifiers/shopify.ts","../src/verifiers/linear.ts","../src/verifiers/custom.ts","../src/verifiers/index.ts","../src/emit.ts","../src/observability.ts"],"sourcesContent":["/**\n * @cloudwerk/trigger - Error Classes\n *\n * Custom error classes for trigger processing.\n */\n\n// ============================================================================\n// Base Error\n// ============================================================================\n\n/**\n * Base error class for trigger-related errors.\n */\nexport class TriggerError extends Error {\n /** Error code for programmatic handling */\n readonly code: string\n\n constructor(code: string, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'TriggerError'\n this.code = code\n }\n}\n\n// ============================================================================\n// Configuration Errors\n// ============================================================================\n\n/**\n * Error thrown when trigger configuration is invalid.\n *\n * @example\n * ```typescript\n * throw new TriggerConfigError('timeout must be a positive number', 'timeout')\n * ```\n */\nexport class TriggerConfigError extends TriggerError {\n /** The configuration field that is invalid */\n readonly field?: string\n\n constructor(message: string, field?: string) {\n super('CONFIG_ERROR', message)\n this.name = 'TriggerConfigError'\n this.field = field\n }\n}\n\n/**\n * Error thrown when no handler function is defined.\n */\nexport class TriggerNoHandlerError extends TriggerError {\n constructor(triggerName: string) {\n super(\n 'NO_HANDLER',\n `Trigger '${triggerName}' must define a handle() function`\n )\n this.name = 'TriggerNoHandlerError'\n }\n}\n\n/**\n * Error thrown when the trigger source configuration is invalid.\n */\nexport class TriggerInvalidSourceError extends TriggerError {\n /** The source type that is invalid */\n readonly sourceType?: string\n\n constructor(message: string, sourceType?: string) {\n super('INVALID_SOURCE', message)\n this.name = 'TriggerInvalidSourceError'\n this.sourceType = sourceType\n }\n}\n\n/**\n * Error thrown when a cron expression is invalid.\n */\nexport class TriggerInvalidCronError extends TriggerError {\n /** The invalid cron expression */\n readonly cron: string\n\n constructor(cron: string, reason?: string) {\n super(\n 'INVALID_CRON',\n `Invalid cron expression '${cron}'${reason ? `: ${reason}` : ''}`\n )\n this.name = 'TriggerInvalidCronError'\n this.cron = cron\n }\n}\n\n/**\n * Error thrown when a webhook path is invalid.\n */\nexport class TriggerInvalidWebhookPathError extends TriggerError {\n /** The invalid path */\n readonly path: string\n\n constructor(path: string, reason?: string) {\n super(\n 'INVALID_WEBHOOK_PATH',\n `Invalid webhook path '${path}'${reason ? `: ${reason}` : ''}`\n )\n this.name = 'TriggerInvalidWebhookPathError'\n this.path = path\n }\n}\n\n// ============================================================================\n// Runtime Errors\n// ============================================================================\n\n/**\n * Error thrown when accessing a trigger outside of execution context.\n */\nexport class TriggerContextError extends TriggerError {\n constructor() {\n super(\n 'CONTEXT_ERROR',\n 'Trigger context accessed outside of trigger execution. Context is only available during trigger handling.'\n )\n this.name = 'TriggerContextError'\n }\n}\n\n/**\n * Error thrown when a trigger binding is not found.\n */\nexport class TriggerNotFoundError extends TriggerError {\n /** The trigger name that was not found */\n readonly triggerName: string\n\n /** Available trigger names */\n readonly availableTriggers: string[]\n\n constructor(triggerName: string, availableTriggers: string[]) {\n const available =\n availableTriggers.length > 0\n ? `Available triggers: ${availableTriggers.join(', ')}`\n : 'No triggers are configured'\n\n super(\n 'TRIGGER_NOT_FOUND',\n `Trigger '${triggerName}' not found. ${available}`\n )\n this.name = 'TriggerNotFoundError'\n this.triggerName = triggerName\n this.availableTriggers = availableTriggers\n }\n}\n\n/**\n * Error thrown when trigger processing fails.\n */\nexport class TriggerProcessingError extends TriggerError {\n /** The trigger name that failed */\n readonly triggerName: string\n\n /** Number of execution attempts */\n readonly attempts: number\n\n /** The event that was being processed (if available) */\n readonly event?: unknown\n\n constructor(\n message: string,\n triggerName: string,\n attempts: number,\n options?: ErrorOptions & { event?: unknown }\n ) {\n super('PROCESSING_ERROR', message, options)\n this.name = 'TriggerProcessingError'\n this.triggerName = triggerName\n this.attempts = attempts\n this.event = options?.event\n }\n}\n\n/**\n * Error thrown when a trigger execution times out.\n */\nexport class TriggerTimeoutError extends TriggerError {\n /** The trigger name that timed out */\n readonly triggerName: string\n\n /** Configured timeout in milliseconds */\n readonly timeoutMs: number\n\n constructor(triggerName: string, timeoutMs: number) {\n super(\n 'TIMEOUT_ERROR',\n `Trigger '${triggerName}' execution exceeded timeout of ${timeoutMs}ms`\n )\n this.name = 'TriggerTimeoutError'\n this.triggerName = triggerName\n this.timeoutMs = timeoutMs\n }\n}\n\n/**\n * Error thrown when max retries are exceeded.\n */\nexport class TriggerMaxRetriesError extends TriggerError {\n /** The trigger name that exceeded retries */\n readonly triggerName: string\n\n /** Maximum retries configured */\n readonly maxRetries: number\n\n /** The original error that caused the failure */\n readonly originalError?: Error\n\n constructor(triggerName: string, maxRetries: number, originalError?: Error) {\n super(\n 'MAX_RETRIES_EXCEEDED',\n `Trigger '${triggerName}' exceeded maximum retries (${maxRetries})`\n )\n this.name = 'TriggerMaxRetriesError'\n this.triggerName = triggerName\n this.maxRetries = maxRetries\n this.originalError = originalError\n }\n}\n\n// ============================================================================\n// Webhook Errors\n// ============================================================================\n\n/**\n * Error thrown when webhook signature verification fails.\n */\nexport class TriggerWebhookVerificationError extends TriggerError {\n /** The trigger name */\n readonly triggerName: string\n\n /** The verification error message */\n readonly verificationError?: string\n\n constructor(triggerName: string, verificationError?: string) {\n super(\n 'WEBHOOK_VERIFICATION_FAILED',\n `Webhook signature verification failed for trigger '${triggerName}'${\n verificationError ? `: ${verificationError}` : ''\n }`\n )\n this.name = 'TriggerWebhookVerificationError'\n this.triggerName = triggerName\n this.verificationError = verificationError\n }\n}\n","/**\n * @cloudwerk/trigger - defineTrigger()\n *\n * Factory function for creating trigger definitions.\n */\n\nimport type {\n TriggerConfig,\n TriggerDefinition,\n TriggerSource,\n RetryConfig,\n WebhookTriggerSource,\n ScheduledTriggerSource,\n} from './types.js'\nimport {\n TriggerConfigError,\n TriggerNoHandlerError,\n TriggerInvalidSourceError,\n TriggerInvalidCronError,\n TriggerInvalidWebhookPathError,\n} from './errors.js'\n\n// ============================================================================\n// Default Configuration\n// ============================================================================\n\nconst DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxAttempts: 3,\n delay: '1m',\n backoff: 'linear',\n}\n\nconst DEFAULT_TIMEOUT = 30000 // 30 seconds\n\n// ============================================================================\n// Duration Parsing\n// ============================================================================\n\n/**\n * Parse a duration string into seconds.\n *\n * Supports formats like:\n * - '30s' - 30 seconds\n * - '5m' - 5 minutes\n * - '1h' - 1 hour\n * - 60 - number of seconds\n *\n * @param duration - Duration string or number\n * @returns Duration in seconds\n */\nexport function parseDuration(duration: string | number): number {\n if (typeof duration === 'number') {\n return duration\n }\n\n const match = duration.match(/^(\\d+)(s|m|h)$/)\n if (!match) {\n throw new TriggerConfigError(\n `Invalid duration format: '${duration}'. Expected format like '30s', '5m', or '1h'`,\n 'duration'\n )\n }\n\n const value = parseInt(match[1], 10)\n const unit = match[2]\n\n switch (unit) {\n case 's':\n return value\n case 'm':\n return value * 60\n case 'h':\n return value * 3600\n default:\n throw new TriggerConfigError(`Unknown duration unit: ${unit}`, 'duration')\n }\n}\n\n// ============================================================================\n// Cron Validation\n// ============================================================================\n\n/**\n * Basic cron expression validation.\n *\n * Validates that the cron expression has the expected format.\n * Full validation is left to the runtime.\n *\n * @param cron - Cron expression to validate\n * @throws TriggerInvalidCronError if invalid\n */\nfunction validateCron(cron: string): void {\n const parts = cron.trim().split(/\\s+/)\n\n // Standard cron has 5 fields, extended has 6\n if (parts.length < 5 || parts.length > 6) {\n throw new TriggerInvalidCronError(\n cron,\n `Expected 5 or 6 fields, got ${parts.length}`\n )\n }\n\n // Basic validation of each field\n const fieldNames = ['minute', 'hour', 'day of month', 'month', 'day of week']\n if (parts.length === 6) {\n fieldNames.unshift('second')\n }\n\n const validChars = /^[\\d,\\-*/]+$/\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]\n // Allow wildcards and special characters\n if (part !== '*' && part !== '?' && !validChars.test(part)) {\n throw new TriggerInvalidCronError(\n cron,\n `Invalid characters in ${fieldNames[i]} field: '${part}'`\n )\n }\n }\n}\n\n// ============================================================================\n// Webhook Path Validation\n// ============================================================================\n\n/**\n * Validate a webhook path.\n *\n * @param path - Path to validate\n * @throws TriggerInvalidWebhookPathError if invalid\n */\nfunction validateWebhookPath(path: string): void {\n if (!path) {\n throw new TriggerInvalidWebhookPathError(path, 'Path cannot be empty')\n }\n\n if (!path.startsWith('/')) {\n throw new TriggerInvalidWebhookPathError(\n path,\n 'Path must start with /'\n )\n }\n\n // Check for invalid characters\n const validPath = /^[a-zA-Z0-9\\-_/:.]+$/\n if (!validPath.test(path)) {\n throw new TriggerInvalidWebhookPathError(\n path,\n 'Path contains invalid characters'\n )\n }\n\n // Check for double slashes\n if (path.includes('//')) {\n throw new TriggerInvalidWebhookPathError(\n path,\n 'Path cannot contain double slashes'\n )\n }\n}\n\n// ============================================================================\n// Source Validation\n// ============================================================================\n\n/**\n * Validate the trigger source configuration.\n *\n * @param source - Source configuration to validate\n * @throws TriggerInvalidSourceError if invalid\n */\nfunction validateSource(source: TriggerSource): void {\n if (!source || typeof source !== 'object') {\n throw new TriggerInvalidSourceError('Source must be an object')\n }\n\n if (!('type' in source)) {\n throw new TriggerInvalidSourceError('Source must have a type property')\n }\n\n switch (source.type) {\n case 'scheduled':\n validateScheduledSource(source as ScheduledTriggerSource)\n break\n case 'queue':\n if (!source.queue || typeof source.queue !== 'string') {\n throw new TriggerInvalidSourceError(\n 'Queue source must specify a queue name',\n 'queue'\n )\n }\n break\n case 'r2': {\n if (!source.bucket || typeof source.bucket !== 'string') {\n throw new TriggerInvalidSourceError(\n 'R2 source must specify a bucket name',\n 'r2'\n )\n }\n if (!Array.isArray(source.events) || source.events.length === 0) {\n throw new TriggerInvalidSourceError(\n 'R2 source must specify at least one event type',\n 'r2'\n )\n }\n const validR2Events = ['object-create', 'object-delete']\n for (const event of source.events) {\n if (!validR2Events.includes(event)) {\n throw new TriggerInvalidSourceError(\n `Invalid R2 event type: '${event}'. Valid types: ${validR2Events.join(', ')}`,\n 'r2'\n )\n }\n }\n break\n }\n case 'webhook':\n validateWebhookSource(source as WebhookTriggerSource)\n break\n case 'email':\n if (!source.address || typeof source.address !== 'string') {\n throw new TriggerInvalidSourceError(\n 'Email source must specify an address pattern',\n 'email'\n )\n }\n break\n case 'd1':\n if (!source.database || typeof source.database !== 'string') {\n throw new TriggerInvalidSourceError(\n 'D1 source must specify a database name',\n 'd1'\n )\n }\n if (!source.table || typeof source.table !== 'string') {\n throw new TriggerInvalidSourceError(\n 'D1 source must specify a table name',\n 'd1'\n )\n }\n if (!Array.isArray(source.events) || source.events.length === 0) {\n throw new TriggerInvalidSourceError(\n 'D1 source must specify at least one event type',\n 'd1'\n )\n }\n break\n case 'tail':\n if (!Array.isArray(source.consumers) || source.consumers.length === 0) {\n throw new TriggerInvalidSourceError(\n 'Tail source must specify at least one consumer',\n 'tail'\n )\n }\n break\n default:\n throw new TriggerInvalidSourceError(\n `Unknown source type: '${(source as TriggerSource).type}'`\n )\n }\n}\n\nfunction validateScheduledSource(source: ScheduledTriggerSource): void {\n if (!source.cron || typeof source.cron !== 'string') {\n throw new TriggerInvalidSourceError(\n 'Scheduled source must specify a cron expression',\n 'scheduled'\n )\n }\n validateCron(source.cron)\n}\n\nfunction validateWebhookSource(source: WebhookTriggerSource): void {\n if (!source.path) {\n throw new TriggerInvalidSourceError(\n 'Webhook source must specify a path',\n 'webhook'\n )\n }\n validateWebhookPath(source.path)\n\n if (source.methods) {\n const validMethods = ['POST', 'PUT', 'PATCH']\n for (const method of source.methods) {\n if (!validMethods.includes(method)) {\n throw new TriggerInvalidSourceError(\n `Invalid webhook method: '${method}'. Valid methods: ${validMethods.join(', ')}`,\n 'webhook'\n )\n }\n }\n }\n}\n\n// ============================================================================\n// Configuration Validation\n// ============================================================================\n\n/**\n * Validate trigger configuration.\n *\n * @param config - Trigger configuration to validate\n * @throws TriggerConfigError if configuration is invalid\n * @throws TriggerNoHandlerError if no handler is defined\n */\nfunction validateConfig<TSource extends TriggerSource>(\n config: TriggerConfig<TSource>\n): void {\n // Must have a handler\n if (!config.handle || typeof config.handle !== 'function') {\n throw new TriggerNoHandlerError(config.name || 'unknown')\n }\n\n // Must have a source\n if (!config.source) {\n throw new TriggerInvalidSourceError('Trigger must have a source')\n }\n\n // Validate source\n validateSource(config.source)\n\n // Validate retry config\n if (config.retry) {\n const { maxAttempts, delay, backoff } = config.retry\n\n if (maxAttempts !== undefined) {\n if (!Number.isInteger(maxAttempts) || maxAttempts < 0 || maxAttempts > 100) {\n throw new TriggerConfigError(\n 'maxAttempts must be an integer between 0 and 100',\n 'maxAttempts'\n )\n }\n }\n\n if (delay !== undefined) {\n // This will throw if invalid\n parseDuration(delay)\n }\n\n if (backoff !== undefined && backoff !== 'linear' && backoff !== 'exponential') {\n throw new TriggerConfigError(\n `Invalid backoff strategy: '${backoff}'. Valid strategies: 'linear', 'exponential'`,\n 'backoff'\n )\n }\n }\n\n // Validate timeout\n if (config.timeout !== undefined) {\n if (typeof config.timeout !== 'number' || config.timeout <= 0) {\n throw new TriggerConfigError(\n 'timeout must be a positive number (milliseconds)',\n 'timeout'\n )\n }\n\n // Warn about long timeouts (but don't error)\n // Cloudflare Workers have limits on execution time\n if (config.timeout > 600000) {\n throw new TriggerConfigError(\n 'timeout cannot exceed 600000ms (10 minutes)',\n 'timeout'\n )\n }\n }\n\n // Validate name if provided\n if (config.name !== undefined) {\n if (typeof config.name !== 'string' || config.name.length === 0) {\n throw new TriggerConfigError('name must be a non-empty string', 'name')\n }\n\n // Trigger names should be lowercase alphanumeric with hyphens\n if (!/^[a-z][a-z0-9-]*$/.test(config.name)) {\n throw new TriggerConfigError(\n 'name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens',\n 'name'\n )\n }\n }\n\n // Validate onError if provided\n if (config.onError !== undefined && typeof config.onError !== 'function') {\n throw new TriggerConfigError('onError must be a function', 'onError')\n }\n}\n\n// ============================================================================\n// defineTrigger()\n// ============================================================================\n\n/**\n * Define a trigger consumer.\n *\n * This function creates a trigger definition that will be automatically\n * discovered and registered by Cloudwerk during build.\n *\n * @typeParam TSource - The trigger source type\n * @param config - Trigger configuration\n * @returns Trigger definition\n *\n * @example\n * ```typescript\n * // app/triggers/daily-cleanup.ts\n * import { defineTrigger } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: { type: 'scheduled', cron: '0 0 * * *' },\n * async handle(event, ctx) {\n * console.log(`[${ctx.traceId}] Running cleanup at ${event.scheduledTime}`)\n * await cleanupOldRecords()\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // app/triggers/process-uploads.ts\n * import { defineTrigger } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'r2',\n * bucket: 'uploads',\n * events: ['object-create'],\n * prefix: 'images/',\n * },\n * retry: { maxAttempts: 5, delay: '30s' },\n * async handle(event, ctx) {\n * console.log(`New file: ${event.key}`)\n * await processImage(event.key)\n * },\n * async onError(error, event, ctx) {\n * await reportError(error, { key: event.key, traceId: ctx.traceId })\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // app/triggers/stripe-webhook.ts\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/stripe',\n * verify: verifiers.stripe(STRIPE_WEBHOOK_SECRET),\n * },\n * async handle(event, ctx) {\n * switch (event.payload.type) {\n * case 'checkout.session.completed':\n * await handleCheckoutComplete(event.payload)\n * break\n * }\n * }\n * })\n * ```\n */\nexport function defineTrigger<TSource extends TriggerSource>(\n config: TriggerConfig<TSource>\n): TriggerDefinition<TSource> {\n // Validate configuration\n validateConfig(config)\n\n // Merge retry config with defaults\n const mergedRetryConfig: Required<RetryConfig> = {\n ...DEFAULT_RETRY_CONFIG,\n ...config.retry,\n }\n\n // Create the definition object\n const definition: TriggerDefinition<TSource> = {\n __brand: 'cloudwerk-trigger',\n name: config.name,\n source: config.source,\n retry: mergedRetryConfig,\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n handle: config.handle,\n onError: config.onError,\n }\n\n return definition\n}\n\n/**\n * Check if a value is a trigger definition created by defineTrigger().\n *\n * @param value - Value to check\n * @returns true if value is a TriggerDefinition\n */\nexport function isTriggerDefinition(value: unknown): value is TriggerDefinition {\n return (\n typeof value === 'object' &&\n value !== null &&\n '__brand' in value &&\n (value as TriggerDefinition).__brand === 'cloudwerk-trigger'\n )\n}\n\n/**\n * Get the source type from a trigger definition.\n *\n * @param definition - Trigger definition\n * @returns The source type string\n */\nexport function getTriggerSourceType(\n definition: TriggerDefinition\n): TriggerSource['type'] {\n return definition.source.type\n}\n","/**\n * Utility functions for webhook signature verification.\n */\n\n/**\n * Compute HMAC signature using Web Crypto API.\n *\n * @param algorithm - Hash algorithm ('SHA-256', 'SHA-1', etc.)\n * @param secret - Secret key as string\n * @param data - Data to sign as ArrayBuffer\n * @returns Hex-encoded signature\n */\nexport async function computeHmac(\n algorithm: 'SHA-256' | 'SHA-1' | 'SHA-512',\n secret: string,\n data: ArrayBuffer\n): Promise<string> {\n const encoder = new TextEncoder()\n const keyData = encoder.encode(secret)\n\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: algorithm },\n false,\n ['sign']\n )\n\n const signature = await crypto.subtle.sign('HMAC', cryptoKey, data)\n return arrayBufferToHex(signature)\n}\n\n/**\n * Convert ArrayBuffer to hex string.\n */\nexport function arrayBufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer)\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Timing-safe string comparison to prevent timing attacks.\n *\n * @param a - First string\n * @param b - Second string\n * @returns true if strings are equal\n */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n const aBytes = new TextEncoder().encode(a)\n const bBytes = new TextEncoder().encode(b)\n\n let result = 0\n for (let i = 0; i < aBytes.length; i++) {\n result |= aBytes[i] ^ bBytes[i]\n }\n\n return result === 0\n}\n\n/**\n * Parse a signature header that contains key=value pairs.\n *\n * @param header - Header value (e.g., \"t=123,v1=abc\")\n * @param separator - Separator between pairs (default: ',')\n * @returns Map of key-value pairs\n */\nexport function parseSignatureHeader(\n header: string,\n separator: string = ','\n): Map<string, string> {\n const result = new Map<string, string>()\n\n for (const part of header.split(separator)) {\n const [key, ...valueParts] = part.trim().split('=')\n if (key && valueParts.length > 0) {\n result.set(key, valueParts.join('='))\n }\n }\n\n return result\n}\n","/**\n * Stripe webhook signature verifier.\n *\n * @see https://stripe.com/docs/webhooks/signatures\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual, parseSignatureHeader } from './utils.js'\n\n/**\n * Default tolerance for timestamp validation (5 minutes).\n */\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\nexport interface StripeVerifierOptions {\n /**\n * Tolerance for timestamp validation in seconds.\n * @default 300 (5 minutes)\n */\n tolerance?: number\n}\n\n/**\n * Create a Stripe webhook signature verifier.\n *\n * @param secret - Stripe webhook signing secret (whsec_...)\n * @param options - Verifier options\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/stripe',\n * verify: verifiers.stripe(process.env.STRIPE_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const stripeEvent = event.payload\n * switch (stripeEvent.type) {\n * case 'checkout.session.completed':\n * // Handle checkout completion\n * break\n * }\n * }\n * })\n * ```\n */\nexport function stripeVerifier(\n secret: string,\n options: StripeVerifierOptions = {}\n): WebhookVerifier {\n const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('stripe-signature')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing stripe-signature header',\n }\n }\n\n // Parse signature header (t=timestamp,v1=signature)\n const parts = parseSignatureHeader(signature)\n const timestamp = parts.get('t')\n const expectedSignature = parts.get('v1')\n\n if (!timestamp || !expectedSignature) {\n return {\n valid: false,\n error: 'Invalid stripe-signature header format',\n }\n }\n\n // Validate timestamp\n const timestampNum = parseInt(timestamp, 10)\n if (isNaN(timestampNum)) {\n return {\n valid: false,\n error: 'Invalid timestamp in signature',\n }\n }\n\n const now = Math.floor(Date.now() / 1000)\n if (Math.abs(now - timestampNum) > tolerance) {\n return {\n valid: false,\n error: `Timestamp outside tolerance (${tolerance}s)`,\n }\n }\n\n // Compute expected signature\n const payload = new TextDecoder().decode(rawBody)\n const signedPayload = `${timestamp}.${payload}`\n const signedPayloadBytes = new TextEncoder().encode(signedPayload)\n\n const computedSignature = await computeHmac(\n 'SHA-256',\n secret,\n signedPayloadBytes.buffer\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, expectedSignature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for stripeVerifier.\n */\nexport const stripe = stripeVerifier\n","/**\n * GitHub webhook signature verifier.\n *\n * @see https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Create a GitHub webhook signature verifier.\n *\n * @param secret - GitHub webhook secret\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/github',\n * verify: verifiers.github(process.env.GITHUB_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const githubEvent = event.headers.get('x-github-event')\n * switch (githubEvent) {\n * case 'push':\n * // Handle push event\n * break\n * case 'pull_request':\n * // Handle PR event\n * break\n * }\n * }\n * })\n * ```\n */\nexport function githubVerifier(secret: string): WebhookVerifier {\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n // GitHub uses X-Hub-Signature-256 (SHA-256) or X-Hub-Signature (SHA-1)\n const signatureHeader =\n request.headers.get('x-hub-signature-256') ||\n request.headers.get('x-hub-signature')\n\n if (!signatureHeader) {\n return {\n valid: false,\n error: 'Missing X-Hub-Signature-256 or X-Hub-Signature header',\n }\n }\n\n // Determine algorithm based on header\n const useSha256 = signatureHeader.startsWith('sha256=')\n const algorithm = useSha256 ? 'SHA-256' : 'SHA-1'\n const prefix = useSha256 ? 'sha256=' : 'sha1='\n\n if (!signatureHeader.startsWith(prefix)) {\n return {\n valid: false,\n error: `Invalid signature format (expected ${prefix} prefix)`,\n }\n }\n\n const expectedSignature = signatureHeader.slice(prefix.length)\n\n // Compute signature\n const computedSignature = await computeHmac(algorithm, secret, rawBody)\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, expectedSignature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for githubVerifier.\n */\nexport const github = githubVerifier\n","/**\n * Slack webhook signature verifier.\n *\n * @see https://api.slack.com/authentication/verifying-requests-from-slack\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Default tolerance for timestamp validation (5 minutes).\n */\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\nexport interface SlackVerifierOptions {\n /**\n * Tolerance for timestamp validation in seconds.\n * @default 300 (5 minutes)\n */\n tolerance?: number\n}\n\n/**\n * Create a Slack webhook signature verifier.\n *\n * @param signingSecret - Slack signing secret\n * @param options - Verifier options\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/slack',\n * verify: verifiers.slack(process.env.SLACK_SIGNING_SECRET),\n * },\n * async handle(event) {\n * const payload = event.payload\n * // Handle Slack event\n * }\n * })\n * ```\n */\nexport function slackVerifier(\n signingSecret: string,\n options: SlackVerifierOptions = {}\n): WebhookVerifier {\n const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const timestamp = request.headers.get('x-slack-request-timestamp')\n const signature = request.headers.get('x-slack-signature')\n\n if (!timestamp) {\n return {\n valid: false,\n error: 'Missing X-Slack-Request-Timestamp header',\n }\n }\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing X-Slack-Signature header',\n }\n }\n\n // Validate timestamp\n const timestampNum = parseInt(timestamp, 10)\n if (isNaN(timestampNum)) {\n return {\n valid: false,\n error: 'Invalid timestamp',\n }\n }\n\n const now = Math.floor(Date.now() / 1000)\n if (Math.abs(now - timestampNum) > tolerance) {\n return {\n valid: false,\n error: `Timestamp outside tolerance (${tolerance}s)`,\n }\n }\n\n // Verify signature format\n if (!signature.startsWith('v0=')) {\n return {\n valid: false,\n error: 'Invalid signature format (expected v0= prefix)',\n }\n }\n\n const expectedSignature = signature.slice(3)\n\n // Compute signature: v0:timestamp:body\n const body = new TextDecoder().decode(rawBody)\n const sigBasestring = `v0:${timestamp}:${body}`\n const sigBasestringBytes = new TextEncoder().encode(sigBasestring)\n\n const computedSignature = await computeHmac(\n 'SHA-256',\n signingSecret,\n sigBasestringBytes.buffer\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, expectedSignature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for slackVerifier.\n */\nexport const slack = slackVerifier\n","/**\n * Twilio webhook signature verifier.\n *\n * @see https://www.twilio.com/docs/usage/security#validating-requests\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\nexport interface TwilioVerifierOptions {\n /**\n * The full URL of your webhook endpoint.\n * Required for signature validation.\n */\n url: string\n}\n\n/**\n * Create a Twilio webhook signature verifier.\n *\n * @param authToken - Twilio Auth Token\n * @param options - Verifier options including the webhook URL\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/twilio',\n * verify: verifiers.twilio(process.env.TWILIO_AUTH_TOKEN, {\n * url: 'https://your-app.com/webhooks/twilio',\n * }),\n * },\n * async handle(event) {\n * const { From, Body } = event.payload\n * // Handle SMS or voice webhook\n * }\n * })\n * ```\n */\nexport function twilioVerifier(\n authToken: string,\n options: TwilioVerifierOptions\n): WebhookVerifier {\n const { url } = options\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('x-twilio-signature')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing X-Twilio-Signature header',\n }\n }\n\n // Parse form data from body\n const body = new TextDecoder().decode(rawBody)\n const params = new URLSearchParams(body)\n\n // Sort parameters alphabetically and concatenate\n const sortedParams = Array.from(params.entries())\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, value]) => `${key}${value}`)\n .join('')\n\n // Signature is computed over URL + sorted params\n const signatureBase = url + sortedParams\n const signatureBaseBytes = new TextEncoder().encode(signatureBase)\n\n // Twilio uses SHA-1 HMAC, base64 encoded\n const computedSignatureHex = await computeHmac(\n 'SHA-1',\n authToken,\n signatureBaseBytes.buffer\n )\n\n // Convert hex to base64\n const computedSignatureBytes = new Uint8Array(\n computedSignatureHex.match(/.{2}/g)!.map((byte) => parseInt(byte, 16))\n )\n const computedSignature = btoa(\n String.fromCharCode(...computedSignatureBytes)\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for twilioVerifier.\n */\nexport const twilio = twilioVerifier\n","/**\n * Shopify webhook signature verifier.\n *\n * @see https://shopify.dev/docs/apps/webhooks/configuration/https#step-5-verify-the-webhook\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Create a Shopify webhook signature verifier.\n *\n * @param secret - Shopify webhook signing secret (from Partner Dashboard)\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/shopify',\n * verify: verifiers.shopify(process.env.SHOPIFY_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const topic = event.headers.get('x-shopify-topic')\n * switch (topic) {\n * case 'orders/create':\n * // Handle new order\n * break\n * case 'products/update':\n * // Handle product update\n * break\n * }\n * }\n * })\n * ```\n */\nexport function shopifyVerifier(secret: string): WebhookVerifier {\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('x-shopify-hmac-sha256')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing X-Shopify-Hmac-Sha256 header',\n }\n }\n\n // Compute HMAC-SHA256\n const computedSignatureHex = await computeHmac('SHA-256', secret, rawBody)\n\n // Convert hex to base64 (Shopify uses base64)\n const computedSignatureBytes = new Uint8Array(\n computedSignatureHex.match(/.{2}/g)!.map((byte) => parseInt(byte, 16))\n )\n const computedSignature = btoa(\n String.fromCharCode(...computedSignatureBytes)\n )\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for shopifyVerifier.\n */\nexport const shopify = shopifyVerifier\n","/**\n * Linear webhook signature verifier.\n *\n * @see https://developers.linear.app/docs/graphql/webhooks#webhook-security\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Create a Linear webhook signature verifier.\n *\n * @param secret - Linear webhook signing secret\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/linear',\n * verify: verifiers.linear(process.env.LINEAR_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * const { action, type, data } = event.payload\n * switch (type) {\n * case 'Issue':\n * if (action === 'create') {\n * // Handle new issue\n * }\n * break\n * }\n * }\n * })\n * ```\n */\nexport function linearVerifier(secret: string): WebhookVerifier {\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n const signature = request.headers.get('linear-signature')\n\n if (!signature) {\n return {\n valid: false,\n error: 'Missing Linear-Signature header',\n }\n }\n\n // Compute HMAC-SHA256\n const computedSignature = await computeHmac('SHA-256', secret, rawBody)\n\n // Compare signatures (Linear uses hex)\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for linearVerifier.\n */\nexport const linear = linearVerifier\n","/**\n * Custom webhook signature verifier factory.\n *\n * Create verifiers for any webhook provider using common patterns.\n */\n\nimport type { WebhookVerifier, WebhookVerificationResult } from '../types.js'\nimport { computeHmac, timingSafeEqual } from './utils.js'\n\n/**\n * Signature encoding format.\n */\nexport type SignatureEncoding = 'hex' | 'base64'\n\n/**\n * Hash algorithm for HMAC computation.\n */\nexport type HashAlgorithm = 'SHA-256' | 'SHA-1' | 'SHA-512'\n\nexport interface CustomVerifierOptions {\n /**\n * Name of the header containing the signature.\n * @example 'x-webhook-signature'\n */\n header: string\n\n /**\n * Hash algorithm to use.\n * @default 'SHA-256'\n */\n algorithm?: HashAlgorithm\n\n /**\n * Encoding of the signature in the header.\n * @default 'hex'\n */\n encoding?: SignatureEncoding\n\n /**\n * Prefix before the signature (e.g., 'sha256=').\n * Will be stripped before comparison.\n */\n prefix?: string\n\n /**\n * Optional timestamp header for replay attack prevention.\n */\n timestampHeader?: string\n\n /**\n * Tolerance for timestamp validation in seconds.\n * Only used if timestampHeader is set.\n * @default 300\n */\n timestampTolerance?: number\n\n /**\n * Function to build the signature base string.\n * By default, just uses the raw body.\n *\n * @param body - Raw request body as string\n * @param timestamp - Timestamp value if timestampHeader is set\n * @returns String to compute signature over\n */\n buildSignatureBase?: (body: string, timestamp?: string) => string\n}\n\n/**\n * Create a custom webhook signature verifier.\n *\n * @param secret - Webhook signing secret\n * @param options - Verifier configuration\n * @returns WebhookVerifier function\n *\n * @example\n * ```typescript\n * // Simple HMAC-SHA256 with hex encoding\n * verifiers.custom(secret, {\n * header: 'x-webhook-signature',\n * algorithm: 'SHA-256',\n * encoding: 'hex',\n * })\n *\n * // With timestamp validation (like Stripe)\n * verifiers.custom(secret, {\n * header: 'x-signature',\n * timestampHeader: 'x-timestamp',\n * timestampTolerance: 300,\n * buildSignatureBase: (body, timestamp) => `${timestamp}.${body}`,\n * })\n *\n * // With prefix (like GitHub)\n * verifiers.custom(secret, {\n * header: 'x-hub-signature-256',\n * prefix: 'sha256=',\n * })\n * ```\n */\nexport function customVerifier(\n secret: string,\n options: CustomVerifierOptions\n): WebhookVerifier {\n const {\n header,\n algorithm = 'SHA-256',\n encoding = 'hex',\n prefix,\n timestampHeader,\n timestampTolerance = 300,\n buildSignatureBase,\n } = options\n\n return async (\n request: Request,\n rawBody: ArrayBuffer\n ): Promise<WebhookVerificationResult> => {\n // Get signature header\n let signature = request.headers.get(header)\n\n if (!signature) {\n return {\n valid: false,\n error: `Missing ${header} header`,\n }\n }\n\n // Strip prefix if present\n if (prefix) {\n if (!signature.startsWith(prefix)) {\n return {\n valid: false,\n error: `Invalid signature format (expected ${prefix} prefix)`,\n }\n }\n signature = signature.slice(prefix.length)\n }\n\n // Validate timestamp if configured\n let timestamp: string | undefined\n if (timestampHeader) {\n timestamp = request.headers.get(timestampHeader) ?? undefined\n\n if (!timestamp) {\n return {\n valid: false,\n error: `Missing ${timestampHeader} header`,\n }\n }\n\n const timestampNum = parseInt(timestamp, 10)\n if (isNaN(timestampNum)) {\n return {\n valid: false,\n error: 'Invalid timestamp',\n }\n }\n\n const now = Math.floor(Date.now() / 1000)\n if (Math.abs(now - timestampNum) > timestampTolerance) {\n return {\n valid: false,\n error: `Timestamp outside tolerance (${timestampTolerance}s)`,\n }\n }\n }\n\n // Build signature base\n const body = new TextDecoder().decode(rawBody)\n const signatureBase = buildSignatureBase\n ? buildSignatureBase(body, timestamp)\n : body\n const signatureBaseBytes = new TextEncoder().encode(signatureBase)\n\n // Compute HMAC\n const computedSignatureHex = await computeHmac(\n algorithm,\n secret,\n signatureBaseBytes.buffer\n )\n\n // Convert to expected encoding\n let computedSignature: string\n if (encoding === 'base64') {\n const bytes = new Uint8Array(\n computedSignatureHex.match(/.{2}/g)!.map((byte) => parseInt(byte, 16))\n )\n computedSignature = btoa(String.fromCharCode(...bytes))\n } else {\n computedSignature = computedSignatureHex\n }\n\n // Compare signatures\n if (!timingSafeEqual(computedSignature, signature)) {\n return {\n valid: false,\n error: 'Signature mismatch',\n }\n }\n\n return { valid: true }\n }\n}\n\n/**\n * Alias for customVerifier.\n */\nexport const custom = customVerifier\n","/**\n * @cloudwerk/trigger - Webhook Signature Verifiers\n *\n * Built-in verifiers for popular webhook providers.\n */\n\nexport { stripeVerifier, stripe } from './stripe.js'\nexport { githubVerifier, github } from './github.js'\nexport { slackVerifier, slack } from './slack.js'\nexport { twilioVerifier, twilio } from './twilio.js'\nexport { shopifyVerifier, shopify } from './shopify.js'\nexport { linearVerifier, linear } from './linear.js'\nexport { customVerifier, custom } from './custom.js'\n\n// Re-export all verifiers as a convenience object\nimport { stripe } from './stripe.js'\nimport { github } from './github.js'\nimport { slack } from './slack.js'\nimport { twilio } from './twilio.js'\nimport { shopify } from './shopify.js'\nimport { linear } from './linear.js'\nimport { custom } from './custom.js'\n\n/**\n * Collection of all built-in webhook verifiers.\n *\n * @example\n * ```typescript\n * import { defineTrigger, verifiers } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: {\n * type: 'webhook',\n * path: '/webhooks/stripe',\n * verify: verifiers.stripe(STRIPE_WEBHOOK_SECRET),\n * },\n * async handle(event) {\n * // Handle verified webhook\n * }\n * })\n * ```\n */\nexport const verifiers = {\n stripe,\n github,\n slack,\n twilio,\n shopify,\n linear,\n custom,\n} as const\n","/**\n * @cloudwerk/trigger - Trigger Chaining via emit()\n *\n * Allows triggers to invoke other triggers with automatic\n * trace ID propagation for observability.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport type { TriggerContext } from './types.js'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for emitting to a trigger.\n */\nexport interface EmitOptions {\n /**\n * Delay before the trigger executes (in milliseconds).\n * @default 0\n */\n delay?: number\n\n /**\n * Custom trace ID (defaults to current context's traceId).\n */\n traceId?: string\n}\n\n/**\n * Result of an emit operation.\n */\nexport interface EmitResult {\n /** Whether the emit was successful */\n success: boolean\n /** The trace ID used for this emission */\n traceId: string\n /** Target trigger name */\n trigger: string\n /** Timestamp when emit was called */\n emittedAt: Date\n}\n\n/**\n * Pending emission that was queued during execution.\n */\nexport interface PendingEmission {\n trigger: string\n payload: unknown\n options: EmitOptions\n traceId: string\n emittedAt: Date\n}\n\n/**\n * Emitter interface for different execution modes.\n */\nexport interface TriggerEmitter {\n /**\n * Emit to a trigger.\n */\n emit(trigger: string, payload: unknown, options?: EmitOptions): Promise<EmitResult>\n\n /**\n * Get all pending emissions (for testing/debugging).\n */\n getPendingEmissions(): PendingEmission[]\n\n /**\n * Clear pending emissions.\n */\n clearPendingEmissions(): void\n}\n\n// ============================================================================\n// Context Storage\n// ============================================================================\n\n/**\n * Store for current trigger context, used for trace ID propagation.\n */\nconst contextStore = new AsyncLocalStorage<TriggerContext>()\n\n/**\n * Store for pending emissions during trigger execution.\n */\nconst emissionsStore = new AsyncLocalStorage<PendingEmission[]>()\n\n/**\n * Run a function within a trigger context.\n *\n * @param context - Trigger context\n * @param fn - Function to execute\n * @returns Function result\n */\nexport function runWithTriggerContext<T>(\n context: TriggerContext,\n fn: () => T\n): T {\n return contextStore.run(context, () => {\n return emissionsStore.run([], fn)\n })\n}\n\n/**\n * Get the current trigger context.\n *\n * @returns Current context or undefined if not in a trigger\n */\nexport function getTriggerContext(): TriggerContext | undefined {\n return contextStore.getStore()\n}\n\n/**\n * Get pending emissions from the current context.\n */\nexport function getPendingEmissions(): PendingEmission[] {\n return emissionsStore.getStore() ?? []\n}\n\n// ============================================================================\n// Trace ID Generation\n// ============================================================================\n\n/**\n * Generate a new trace ID.\n *\n * @param prefix - Optional prefix (default: 'tr')\n * @returns Trace ID string\n */\nexport function generateTraceId(prefix: string = 'tr'): string {\n const random = crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n return `${prefix}_${random}`\n}\n\n/**\n * Create a child trace ID from a parent.\n *\n * @param parentTraceId - Parent trace ID\n * @returns Child trace ID\n */\nexport function createChildTraceId(parentTraceId: string): string {\n const suffix = crypto.randomUUID().slice(0, 8)\n return `${parentTraceId}.${suffix}`\n}\n\n// ============================================================================\n// emit() Function\n// ============================================================================\n\n/**\n * Emit an event to another trigger.\n *\n * This function queues an event to be processed by another trigger,\n * with automatic trace ID propagation for distributed tracing.\n *\n * @param trigger - Name of the trigger to invoke\n * @param payload - Data to pass to the trigger\n * @param options - Emit options\n * @returns EmitResult\n *\n * @example\n * ```typescript\n * import { defineTrigger, emit } from '@cloudwerk/trigger'\n *\n * export default defineTrigger({\n * source: { type: 'r2', bucket: 'uploads', events: ['object-create'] },\n * async handle(event, ctx) {\n * // Process the upload\n * const metadata = await processFile(event.key)\n *\n * // Chain to other triggers\n * await emit('index-for-search', { key: event.key, metadata })\n * await emit('generate-thumbnail', { key: event.key })\n *\n * // Trace ID is automatically propagated\n * console.log(ctx.traceId) // Same traceId in chained triggers\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With delay\n * await emit('send-reminder', { userId }, { delay: 60000 }) // 1 minute delay\n * ```\n */\nexport async function emit(\n trigger: string,\n payload: unknown,\n options: EmitOptions = {}\n): Promise<EmitResult> {\n const context = getTriggerContext()\n const emissions = emissionsStore.getStore()\n\n // Determine trace ID\n const traceId = options.traceId ?? context?.traceId ?? generateTraceId()\n const childTraceId = context ? createChildTraceId(traceId) : traceId\n\n const emittedAt = new Date()\n\n // Create pending emission record\n const emission: PendingEmission = {\n trigger,\n payload,\n options,\n traceId: childTraceId,\n emittedAt,\n }\n\n // Queue the emission\n if (emissions) {\n emissions.push(emission)\n }\n\n // In production, this would:\n // 1. For same-worker triggers: queue for immediate execution\n // 2. For cross-worker triggers: publish to internal queue\n //\n // For now, we just record the emission. The runtime dispatcher\n // will process these after the current handler completes.\n\n return {\n success: true,\n traceId: childTraceId,\n trigger,\n emittedAt,\n }\n}\n\n/**\n * Emit to multiple triggers in parallel.\n *\n * @param emissions - Array of [trigger, payload] pairs\n * @param options - Shared options for all emissions\n * @returns Array of EmitResults\n *\n * @example\n * ```typescript\n * const results = await emitMany([\n * ['process-image', { key: 'image.jpg' }],\n * ['update-index', { key: 'image.jpg' }],\n * ['notify-user', { userId, message: 'Upload complete' }],\n * ])\n * ```\n */\nexport async function emitMany(\n emissions: [string, unknown][],\n options: EmitOptions = {}\n): Promise<EmitResult[]> {\n return Promise.all(\n emissions.map(([trigger, payload]) => emit(trigger, payload, options))\n )\n}\n\n// ============================================================================\n// Default Emitter\n// ============================================================================\n\n/**\n * Default trigger emitter implementation.\n *\n * In production, this would be replaced with an implementation\n * that actually dispatches to other triggers via queues or\n * direct function calls.\n */\nexport const defaultEmitter: TriggerEmitter = {\n emit,\n getPendingEmissions,\n clearPendingEmissions: () => {\n const emissions = emissionsStore.getStore()\n if (emissions) {\n emissions.length = 0\n }\n },\n}\n","/**\n * @cloudwerk/trigger - Observability & Metrics\n *\n * Utilities for tracing, metrics collection, and monitoring trigger executions.\n */\n\nimport type { TriggerContext, TriggerSource } from './types.js'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Trigger execution metrics.\n */\nexport interface TriggerMetrics {\n /** Trigger name */\n name: string\n /** Trigger source type */\n sourceType: TriggerSource['type']\n /** Execution start timestamp */\n startedAt: Date\n /** Execution end timestamp */\n endedAt?: Date\n /** Duration in milliseconds */\n durationMs?: number\n /** Whether execution was successful */\n success: boolean\n /** Error if execution failed */\n error?: Error\n /** Number of retries */\n retryCount: number\n /** Trace ID for distributed tracing */\n traceId: string\n}\n\n/**\n * Trigger execution span for tracing.\n */\nexport interface TriggerSpan {\n /** Span ID */\n spanId: string\n /** Trace ID */\n traceId: string\n /** Parent span ID (for nested triggers) */\n parentSpanId?: string\n /** Trigger name */\n triggerName: string\n /** Source type */\n sourceType: TriggerSource['type']\n /** Operation name */\n operationName: string\n /** Start time (Unix timestamp ms) */\n startTime: number\n /** End time (Unix timestamp ms) */\n endTime?: number\n /** Span status */\n status: 'ok' | 'error' | 'unset'\n /** Attributes */\n attributes: Record<string, string | number | boolean>\n /** Events that occurred during the span */\n events: SpanEvent[]\n}\n\n/**\n * Event that occurred during a span.\n */\nexport interface SpanEvent {\n /** Event name */\n name: string\n /** Timestamp (Unix ms) */\n timestamp: number\n /** Event attributes */\n attributes?: Record<string, string | number | boolean>\n}\n\n/**\n * Metrics reporter interface for pluggable metrics backends.\n */\nexport interface MetricsReporter {\n /** Report execution metrics */\n reportExecution(metrics: TriggerMetrics): void\n /** Report a span */\n reportSpan(span: TriggerSpan): void\n /** Flush pending metrics */\n flush(): Promise<void>\n}\n\n// ============================================================================\n// Span Creation\n// ============================================================================\n\n/**\n * Generate a span ID.\n */\nexport function generateSpanId(): string {\n return crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n}\n\n/**\n * Create a new trigger span.\n */\nexport function createTriggerSpan(\n triggerName: string,\n sourceType: TriggerSource['type'],\n traceId: string,\n parentSpanId?: string\n): TriggerSpan {\n return {\n spanId: generateSpanId(),\n traceId,\n parentSpanId,\n triggerName,\n sourceType,\n operationName: `trigger.${triggerName}`,\n startTime: Date.now(),\n status: 'unset',\n attributes: {\n 'trigger.name': triggerName,\n 'trigger.source_type': sourceType,\n },\n events: [],\n }\n}\n\n/**\n * End a trigger span.\n */\nexport function endTriggerSpan(\n span: TriggerSpan,\n success: boolean,\n error?: Error\n): TriggerSpan {\n span.endTime = Date.now()\n span.status = success ? 'ok' : 'error'\n\n if (error) {\n span.attributes['error.type'] = error.name\n span.attributes['error.message'] = error.message\n span.events.push({\n name: 'exception',\n timestamp: Date.now(),\n attributes: {\n 'exception.type': error.name,\n 'exception.message': error.message,\n },\n })\n }\n\n return span\n}\n\n/**\n * Add an event to a span.\n */\nexport function addSpanEvent(\n span: TriggerSpan,\n name: string,\n attributes?: Record<string, string | number | boolean>\n): void {\n span.events.push({\n name,\n timestamp: Date.now(),\n attributes,\n })\n}\n\n/**\n * Add attributes to a span.\n */\nexport function setSpanAttributes(\n span: TriggerSpan,\n attributes: Record<string, string | number | boolean>\n): void {\n Object.assign(span.attributes, attributes)\n}\n\n// ============================================================================\n// Execution Timer\n// ============================================================================\n\n/**\n * Timer for measuring trigger execution duration.\n */\nexport class ExecutionTimer {\n private startTime: number\n private endTime?: number\n private marks: Map<string, number> = new Map()\n\n constructor() {\n this.startTime = performance.now()\n }\n\n /**\n * Mark a point in time.\n */\n mark(name: string): void {\n this.marks.set(name, performance.now())\n }\n\n /**\n * Get time since start to a mark.\n */\n getMarkDuration(name: string): number | undefined {\n const markTime = this.marks.get(name)\n if (markTime === undefined) return undefined\n return markTime - this.startTime\n }\n\n /**\n * Stop the timer.\n */\n stop(): number {\n this.endTime = performance.now()\n return this.duration\n }\n\n /**\n * Get total duration in milliseconds.\n */\n get duration(): number {\n const end = this.endTime ?? performance.now()\n return end - this.startTime\n }\n\n /**\n * Get all marks with their durations.\n */\n getMarks(): Record<string, number> {\n const result: Record<string, number> = {}\n for (const [name, time] of this.marks) {\n result[name] = time - this.startTime\n }\n return result\n }\n}\n\n// ============================================================================\n// Default Console Reporter\n// ============================================================================\n\n/**\n * Console-based metrics reporter for development.\n */\nexport class ConsoleMetricsReporter implements MetricsReporter {\n private prefix: string\n\n constructor(prefix: string = '[trigger]') {\n this.prefix = prefix\n }\n\n reportExecution(metrics: TriggerMetrics): void {\n const status = metrics.success ? 'SUCCESS' : 'FAILED'\n const duration = metrics.durationMs ? `${metrics.durationMs}ms` : 'unknown'\n\n console.log(\n `${this.prefix} ${metrics.name} [${metrics.traceId}] ${status} (${duration})`\n )\n\n if (metrics.error) {\n console.error(`${this.prefix} Error:`, metrics.error.message)\n }\n }\n\n reportSpan(span: TriggerSpan): void {\n const duration = span.endTime\n ? `${span.endTime - span.startTime}ms`\n : 'ongoing'\n\n console.log(\n `${this.prefix} Span: ${span.operationName} [${span.traceId}] ${span.status} (${duration})`\n )\n\n for (const event of span.events) {\n console.log(`${this.prefix} Event: ${event.name}`)\n }\n }\n\n async flush(): Promise<void> {\n // Console reporter doesn't need flushing\n }\n}\n\n// ============================================================================\n// No-Op Reporter\n// ============================================================================\n\n/**\n * No-op metrics reporter that discards all metrics.\n */\nexport class NoOpMetricsReporter implements MetricsReporter {\n reportExecution(_metrics: TriggerMetrics): void {\n // Intentionally empty\n }\n\n reportSpan(_span: TriggerSpan): void {\n // Intentionally empty\n }\n\n async flush(): Promise<void> {\n // Intentionally empty\n }\n}\n\n// ============================================================================\n// Metrics Collector\n// ============================================================================\n\n/**\n * Collect and aggregate trigger metrics.\n */\nexport class MetricsCollector {\n private executions: TriggerMetrics[] = []\n private spans: TriggerSpan[] = []\n private reporter: MetricsReporter\n\n constructor(reporter: MetricsReporter = new NoOpMetricsReporter()) {\n this.reporter = reporter\n }\n\n /**\n * Record a trigger execution.\n */\n recordExecution(metrics: TriggerMetrics): void {\n this.executions.push(metrics)\n this.reporter.reportExecution(metrics)\n }\n\n /**\n * Record a span.\n */\n recordSpan(span: TriggerSpan): void {\n this.spans.push(span)\n this.reporter.reportSpan(span)\n }\n\n /**\n * Get execution count for a trigger.\n */\n getExecutionCount(triggerName: string): number {\n return this.executions.filter((m) => m.name === triggerName).length\n }\n\n /**\n * Get success rate for a trigger.\n */\n getSuccessRate(triggerName: string): number {\n const executions = this.executions.filter((m) => m.name === triggerName)\n if (executions.length === 0) return 0\n const successful = executions.filter((m) => m.success).length\n return successful / executions.length\n }\n\n /**\n * Get average duration for a trigger.\n */\n getAverageDuration(triggerName: string): number | undefined {\n const executions = this.executions.filter(\n (m) => m.name === triggerName && m.durationMs !== undefined\n )\n if (executions.length === 0) return undefined\n const total = executions.reduce((sum, m) => sum + (m.durationMs ?? 0), 0)\n return total / executions.length\n }\n\n /**\n * Get percentile duration for a trigger.\n */\n getPercentileDuration(\n triggerName: string,\n percentile: number\n ): number | undefined {\n const executions = this.executions\n .filter((m) => m.name === triggerName && m.durationMs !== undefined)\n .map((m) => m.durationMs!)\n .sort((a, b) => a - b)\n\n if (executions.length === 0) return undefined\n\n const index = Math.ceil((percentile / 100) * executions.length) - 1\n return executions[Math.max(0, index)]\n }\n\n /**\n * Get summary statistics for a trigger.\n */\n getSummary(triggerName: string): {\n count: number\n successRate: number\n avgDurationMs?: number\n p50DurationMs?: number\n p95DurationMs?: number\n p99DurationMs?: number\n } {\n return {\n count: this.getExecutionCount(triggerName),\n successRate: this.getSuccessRate(triggerName),\n avgDurationMs: this.getAverageDuration(triggerName),\n p50DurationMs: this.getPercentileDuration(triggerName, 50),\n p95DurationMs: this.getPercentileDuration(triggerName, 95),\n p99DurationMs: this.getPercentileDuration(triggerName, 99),\n }\n }\n\n /**\n * Flush metrics to the reporter.\n */\n async flush(): Promise<void> {\n await this.reporter.flush()\n }\n\n /**\n * Clear collected metrics.\n */\n clear(): void {\n this.executions = []\n this.spans = []\n }\n}\n\n// ============================================================================\n// Helper to create metrics from execution\n// ============================================================================\n\n/**\n * Create trigger metrics from an execution.\n */\nexport function createTriggerMetrics(\n name: string,\n sourceType: TriggerSource['type'],\n ctx: TriggerContext,\n timer: ExecutionTimer,\n success: boolean,\n error?: Error,\n retryCount: number = 0\n): TriggerMetrics {\n const durationMs = timer.stop()\n\n return {\n name,\n sourceType,\n startedAt: new Date(Date.now() - durationMs),\n endedAt: new Date(),\n durationMs,\n success,\n error,\n retryCount,\n traceId: ctx.traceId,\n }\n}\n"],"mappings":"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","names":["DEFAULT_TOLERANCE_SECONDS"]}
|
package/dist/testing.js
CHANGED
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing/index.ts"],"sourcesContent":["/**\n * @cloudwerk/trigger/testing\n *\n * Testing utilities for Cloudwerk triggers.\n *\n * @example\n * ```typescript\n * import { testTrigger, mockEvent } from '@cloudwerk/trigger/testing'\n * import processUploads from '../app/triggers/process-uploads'\n *\n * describe('process-uploads', () => {\n * it('processes uploads', async () => {\n * const result = await testTrigger(processUploads, {\n * event: mockEvent.r2({\n * type: 'object-create',\n * bucket: 'uploads',\n * key: 'test.pdf'\n * }),\n * })\n *\n * expect(result.success).toBe(true)\n * })\n * })\n * ```\n */\n\nimport type {\n TriggerDefinition,\n TriggerContext,\n ScheduledEvent,\n QueueBatchEvent,\n QueueMessage,\n R2Event,\n WebhookEvent,\n EmailEvent,\n} from '../types.js'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for testing a trigger.\n */\nexport interface TestTriggerOptions<TEvent> {\n /** The event to pass to the trigger */\n event: TEvent\n /** Mock bindings (env) */\n bindings?: Record<string, unknown>\n /** Custom trace ID */\n traceId?: string\n}\n\n/**\n * Result of testing a trigger.\n */\nexport interface TestTriggerResult {\n /** Whether the trigger executed successfully */\n success: boolean\n /** Error thrown by the trigger (if any) */\n error?: Error\n /** Whether onError handler was called */\n errorHandlerCalled: boolean\n /** Promises passed to waitUntil */\n waitUntilPromises: Promise<unknown>[]\n}\n\n// ============================================================================\n// Mock Event Factories\n// ============================================================================\n\n/**\n * Create a mock scheduled event.\n */\nfunction createScheduledEvent(\n options: Partial<ScheduledEvent> & { cron?: string }\n): ScheduledEvent {\n let noRetryFn = () => {}\n\n return {\n cron: options.cron ?? '* * * * *',\n scheduledTime: options.scheduledTime ?? Date.now(),\n noRetry: options.noRetry ?? (() => {\n noRetryFn()\n }),\n }\n}\n\n/**\n * Create a mock queue message.\n */\nfunction createQueueMessage<T>(\n body: T,\n options: Partial<Omit<QueueMessage<T>, 'body'>> = {}\n): QueueMessage<T> {\n return {\n id: options.id ?? crypto.randomUUID(),\n body,\n timestamp: options.timestamp ?? new Date(),\n attempts: options.attempts ?? 1,\n ack: options.ack ?? (() => {}),\n retry: options.retry ?? (() => {}),\n }\n}\n\n/**\n * Create a mock queue batch event.\n */\nfunction createQueueBatchEvent<T>(\n options: { messages?: T[]; queue?: string; ackAll?: () => void; retryAll?: () => void }\n): QueueBatchEvent<T> {\n const messages: QueueMessage<T>[] = (options.messages ?? []).map((body) =>\n createQueueMessage(body)\n )\n\n return {\n messages,\n queue: options.queue ?? 'test-queue',\n ackAll: options.ackAll ?? (() => {}),\n retryAll: options.retryAll ?? (() => {}),\n }\n}\n\n/**\n * Create a mock R2 event.\n */\nfunction createR2Event(options: Partial<R2Event>): R2Event {\n return {\n type: options.type ?? 'object-create',\n bucket: options.bucket ?? 'test-bucket',\n key: options.key ?? 'test-key',\n etag: options.etag,\n size: options.size,\n uploadedAt: options.uploadedAt,\n account: options.account ?? 'test-account',\n eventId: options.eventId ?? crypto.randomUUID(),\n }\n}\n\n/**\n * Create a mock webhook event.\n */\nfunction createWebhookEvent<T>(\n options: Partial<WebhookEvent<T>> & { payload?: T }\n): WebhookEvent<T> {\n return {\n payload: options.payload ?? ({} as T),\n headers: options.headers ?? new Headers(),\n signature: options.signature ?? null,\n rawBody: options.rawBody ?? new ArrayBuffer(0),\n verified: options.verified ?? true,\n method: options.method ?? 'POST',\n path: options.path ?? '/webhook',\n }\n}\n\n/**\n * Create a mock email event.\n */\nfunction createEmailEvent(options: Partial<EmailEvent>): EmailEvent {\n return {\n from: options.from ?? 'sender@example.com',\n to: options.to ?? 'recipient@example.com',\n subject: options.subject ?? 'Test Email',\n rawEmail:\n options.rawEmail ??\n new ReadableStream({\n start(controller) {\n controller.enqueue(new TextEncoder().encode(''))\n controller.close()\n },\n }),\n text: options.text ?? (async () => ''),\n html: options.html ?? (async () => null),\n }\n}\n\n/**\n * Factory functions for creating mock events.\n */\nexport const mockEvent = {\n scheduled: createScheduledEvent,\n queue: createQueueBatchEvent,\n queueMessage: createQueueMessage,\n r2: createR2Event,\n webhook: createWebhookEvent,\n email: createEmailEvent,\n}\n\n// ============================================================================\n// Test Trigger Function\n// ============================================================================\n\n/**\n * Create a mock trigger context.\n */\nfunction createMockContext(\n bindings?: Record<string, unknown>,\n traceId?: string\n): TriggerContext & { waitUntilPromises: Promise<unknown>[] } {\n const waitUntilPromises: Promise<unknown>[] = []\n\n return {\n traceId: traceId ?? `test_${crypto.randomUUID()}`,\n waitUntil: (promise: Promise<unknown>) => {\n waitUntilPromises.push(promise)\n },\n passThroughOnException: () => {},\n env: bindings ?? {},\n waitUntilPromises,\n }\n}\n\n/**\n * Test a trigger with a mock event.\n *\n * @param definition - The trigger definition to test\n * @param options - Test options including the mock event\n * @returns Test result\n *\n * @example\n * ```typescript\n * import { testTrigger, mockEvent } from '@cloudwerk/trigger/testing'\n * import dailyCleanup from '../app/triggers/daily-cleanup'\n *\n * describe('daily-cleanup', () => {\n * it('runs cleanup successfully', async () => {\n * const result = await testTrigger(dailyCleanup, {\n * event: mockEvent.scheduled({ cron: '0 0 * * *' }),\n * })\n *\n * expect(result.success).toBe(true)\n * })\n *\n * it('handles errors gracefully', async () => {\n * const result = await testTrigger(dailyCleanup, {\n * event: mockEvent.scheduled({ cron: '0 0 * * *' }),\n * bindings: {\n * // Provide mock that throws\n * DB: { prepare: () => { throw new Error('DB error') } }\n * }\n * })\n *\n * expect(result.success).toBe(false)\n * expect(result.error?.message).toBe('DB error')\n * })\n * })\n * ```\n */\nexport async function testTrigger<TEvent>(\n definition: TriggerDefinition,\n options: TestTriggerOptions<TEvent>\n): Promise<TestTriggerResult> {\n const ctx = createMockContext(options.bindings, options.traceId)\n let errorHandlerCalled = false\n\n try {\n // Cast the event to any to satisfy the type system\n // The actual type checking happens at definition time\n await definition.handle(options.event as never, ctx)\n\n return {\n success: true,\n errorHandlerCalled,\n waitUntilPromises: ctx.waitUntilPromises,\n }\n } catch (error) {\n if (definition.onError) {\n try {\n await definition.onError(error as Error, options.event as never, ctx)\n errorHandlerCalled = true\n } catch {\n // Error handler itself threw, ignore\n }\n }\n\n return {\n success: false,\n error: error as Error,\n errorHandlerCalled,\n waitUntilPromises: ctx.waitUntilPromises,\n }\n }\n}\n\n// ============================================================================\n// Test Harness\n// ============================================================================\n\n/**\n * Options for creating a test harness.\n */\nexport interface TriggerTestHarnessOptions {\n /** Mock bindings to use for all triggers */\n bindings?: Record<string, unknown>\n}\n\n/**\n * A test harness for running multiple triggers.\n *\n * @example\n * ```typescript\n * import { TriggerTestHarness } from '@cloudwerk/trigger/testing'\n *\n * const harness = new TriggerTestHarness({\n * bindings: { DB: mockD1() }\n * })\n *\n * harness.register('process-uploads', processUploads)\n * harness.register('send-notifications', sendNotifications)\n *\n * // Run a trigger\n * await harness.emit('process-uploads', mockEvent.r2({ key: 'test.pdf' }))\n *\n * // Check results\n * expect(harness.completed).toHaveLength(1)\n * ```\n */\nexport class TriggerTestHarness {\n private triggers = new Map<string, TriggerDefinition>()\n private results: TestTriggerResult[] = []\n private bindings: Record<string, unknown>\n\n constructor(options: TriggerTestHarnessOptions = {}) {\n this.bindings = options.bindings ?? {}\n }\n\n /**\n * Register a trigger with the harness.\n */\n register(name: string, definition: TriggerDefinition): void {\n this.triggers.set(name, definition)\n }\n\n /**\n * Emit an event to a trigger.\n */\n async emit<TEvent>(name: string, event: TEvent): Promise<TestTriggerResult> {\n const definition = this.triggers.get(name)\n if (!definition) {\n throw new Error(`Trigger '${name}' not registered`)\n }\n\n const result = await testTrigger(definition, {\n event,\n bindings: this.bindings,\n })\n\n this.results.push(result)\n return result\n }\n\n /**\n * Get all completed trigger results.\n */\n get completed(): TestTriggerResult[] {\n return this.results.filter((r) => r.success)\n }\n\n /**\n * Get all failed trigger results.\n */\n get failed(): TestTriggerResult[] {\n return this.results.filter((r) => !r.success)\n }\n\n /**\n * Get all results.\n */\n get all(): TestTriggerResult[] {\n return [...this.results]\n }\n\n /**\n * Reset the harness (clear results).\n */\n reset(): void {\n this.results = []\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing/index.ts"],"sourcesContent":["/**\n * @cloudwerk/trigger/testing\n *\n * Testing utilities for Cloudwerk triggers.\n *\n * @example\n * ```typescript\n * import { testTrigger, mockEvent } from '@cloudwerk/trigger/testing'\n * import processUploads from '../app/triggers/process-uploads'\n *\n * describe('process-uploads', () => {\n * it('processes uploads', async () => {\n * const result = await testTrigger(processUploads, {\n * event: mockEvent.r2({\n * type: 'object-create',\n * bucket: 'uploads',\n * key: 'test.pdf'\n * }),\n * })\n *\n * expect(result.success).toBe(true)\n * })\n * })\n * ```\n */\n\nimport type {\n TriggerDefinition,\n TriggerContext,\n ScheduledEvent,\n QueueBatchEvent,\n QueueMessage,\n R2Event,\n WebhookEvent,\n EmailEvent,\n} from '../types.js'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for testing a trigger.\n */\nexport interface TestTriggerOptions<TEvent> {\n /** The event to pass to the trigger */\n event: TEvent\n /** Mock bindings (env) */\n bindings?: Record<string, unknown>\n /** Custom trace ID */\n traceId?: string\n}\n\n/**\n * Result of testing a trigger.\n */\nexport interface TestTriggerResult {\n /** Whether the trigger executed successfully */\n success: boolean\n /** Error thrown by the trigger (if any) */\n error?: Error\n /** Whether onError handler was called */\n errorHandlerCalled: boolean\n /** Promises passed to waitUntil */\n waitUntilPromises: Promise<unknown>[]\n}\n\n// ============================================================================\n// Mock Event Factories\n// ============================================================================\n\n/**\n * Create a mock scheduled event.\n */\nfunction createScheduledEvent(\n options: Partial<ScheduledEvent> & { cron?: string }\n): ScheduledEvent {\n const noRetryFn = () => {}\n\n return {\n cron: options.cron ?? '* * * * *',\n scheduledTime: options.scheduledTime ?? Date.now(),\n noRetry: options.noRetry ?? (() => {\n noRetryFn()\n }),\n }\n}\n\n/**\n * Create a mock queue message.\n */\nfunction createQueueMessage<T>(\n body: T,\n options: Partial<Omit<QueueMessage<T>, 'body'>> = {}\n): QueueMessage<T> {\n return {\n id: options.id ?? crypto.randomUUID(),\n body,\n timestamp: options.timestamp ?? new Date(),\n attempts: options.attempts ?? 1,\n ack: options.ack ?? (() => {}),\n retry: options.retry ?? (() => {}),\n }\n}\n\n/**\n * Create a mock queue batch event.\n */\nfunction createQueueBatchEvent<T>(\n options: { messages?: T[]; queue?: string; ackAll?: () => void; retryAll?: () => void }\n): QueueBatchEvent<T> {\n const messages: QueueMessage<T>[] = (options.messages ?? []).map((body) =>\n createQueueMessage(body)\n )\n\n return {\n messages,\n queue: options.queue ?? 'test-queue',\n ackAll: options.ackAll ?? (() => {}),\n retryAll: options.retryAll ?? (() => {}),\n }\n}\n\n/**\n * Create a mock R2 event.\n */\nfunction createR2Event(options: Partial<R2Event>): R2Event {\n return {\n type: options.type ?? 'object-create',\n bucket: options.bucket ?? 'test-bucket',\n key: options.key ?? 'test-key',\n etag: options.etag,\n size: options.size,\n uploadedAt: options.uploadedAt,\n account: options.account ?? 'test-account',\n eventId: options.eventId ?? crypto.randomUUID(),\n }\n}\n\n/**\n * Create a mock webhook event.\n */\nfunction createWebhookEvent<T>(\n options: Partial<WebhookEvent<T>> & { payload?: T }\n): WebhookEvent<T> {\n return {\n payload: options.payload ?? ({} as T),\n headers: options.headers ?? new Headers(),\n signature: options.signature ?? null,\n rawBody: options.rawBody ?? new ArrayBuffer(0),\n verified: options.verified ?? true,\n method: options.method ?? 'POST',\n path: options.path ?? '/webhook',\n }\n}\n\n/**\n * Create a mock email event.\n */\nfunction createEmailEvent(options: Partial<EmailEvent>): EmailEvent {\n return {\n from: options.from ?? 'sender@example.com',\n to: options.to ?? 'recipient@example.com',\n subject: options.subject ?? 'Test Email',\n rawEmail:\n options.rawEmail ??\n new ReadableStream({\n start(controller) {\n controller.enqueue(new TextEncoder().encode(''))\n controller.close()\n },\n }),\n text: options.text ?? (async () => ''),\n html: options.html ?? (async () => null),\n }\n}\n\n/**\n * Factory functions for creating mock events.\n */\nexport const mockEvent = {\n scheduled: createScheduledEvent,\n queue: createQueueBatchEvent,\n queueMessage: createQueueMessage,\n r2: createR2Event,\n webhook: createWebhookEvent,\n email: createEmailEvent,\n}\n\n// ============================================================================\n// Test Trigger Function\n// ============================================================================\n\n/**\n * Create a mock trigger context.\n */\nfunction createMockContext(\n bindings?: Record<string, unknown>,\n traceId?: string\n): TriggerContext & { waitUntilPromises: Promise<unknown>[] } {\n const waitUntilPromises: Promise<unknown>[] = []\n\n return {\n traceId: traceId ?? `test_${crypto.randomUUID()}`,\n waitUntil: (promise: Promise<unknown>) => {\n waitUntilPromises.push(promise)\n },\n passThroughOnException: () => {},\n env: bindings ?? {},\n waitUntilPromises,\n }\n}\n\n/**\n * Test a trigger with a mock event.\n *\n * @param definition - The trigger definition to test\n * @param options - Test options including the mock event\n * @returns Test result\n *\n * @example\n * ```typescript\n * import { testTrigger, mockEvent } from '@cloudwerk/trigger/testing'\n * import dailyCleanup from '../app/triggers/daily-cleanup'\n *\n * describe('daily-cleanup', () => {\n * it('runs cleanup successfully', async () => {\n * const result = await testTrigger(dailyCleanup, {\n * event: mockEvent.scheduled({ cron: '0 0 * * *' }),\n * })\n *\n * expect(result.success).toBe(true)\n * })\n *\n * it('handles errors gracefully', async () => {\n * const result = await testTrigger(dailyCleanup, {\n * event: mockEvent.scheduled({ cron: '0 0 * * *' }),\n * bindings: {\n * // Provide mock that throws\n * DB: { prepare: () => { throw new Error('DB error') } }\n * }\n * })\n *\n * expect(result.success).toBe(false)\n * expect(result.error?.message).toBe('DB error')\n * })\n * })\n * ```\n */\nexport async function testTrigger<TEvent>(\n definition: TriggerDefinition,\n options: TestTriggerOptions<TEvent>\n): Promise<TestTriggerResult> {\n const ctx = createMockContext(options.bindings, options.traceId)\n let errorHandlerCalled = false\n\n try {\n // Cast the event to any to satisfy the type system\n // The actual type checking happens at definition time\n await definition.handle(options.event as never, ctx)\n\n return {\n success: true,\n errorHandlerCalled,\n waitUntilPromises: ctx.waitUntilPromises,\n }\n } catch (error) {\n if (definition.onError) {\n try {\n await definition.onError(error as Error, options.event as never, ctx)\n errorHandlerCalled = true\n } catch {\n // Error handler itself threw, ignore\n }\n }\n\n return {\n success: false,\n error: error as Error,\n errorHandlerCalled,\n waitUntilPromises: ctx.waitUntilPromises,\n }\n }\n}\n\n// ============================================================================\n// Test Harness\n// ============================================================================\n\n/**\n * Options for creating a test harness.\n */\nexport interface TriggerTestHarnessOptions {\n /** Mock bindings to use for all triggers */\n bindings?: Record<string, unknown>\n}\n\n/**\n * A test harness for running multiple triggers.\n *\n * @example\n * ```typescript\n * import { TriggerTestHarness } from '@cloudwerk/trigger/testing'\n *\n * const harness = new TriggerTestHarness({\n * bindings: { DB: mockD1() }\n * })\n *\n * harness.register('process-uploads', processUploads)\n * harness.register('send-notifications', sendNotifications)\n *\n * // Run a trigger\n * await harness.emit('process-uploads', mockEvent.r2({ key: 'test.pdf' }))\n *\n * // Check results\n * expect(harness.completed).toHaveLength(1)\n * ```\n */\nexport class TriggerTestHarness {\n private triggers = new Map<string, TriggerDefinition>()\n private results: TestTriggerResult[] = []\n private bindings: Record<string, unknown>\n\n constructor(options: TriggerTestHarnessOptions = {}) {\n this.bindings = options.bindings ?? {}\n }\n\n /**\n * Register a trigger with the harness.\n */\n register(name: string, definition: TriggerDefinition): void {\n this.triggers.set(name, definition)\n }\n\n /**\n * Emit an event to a trigger.\n */\n async emit<TEvent>(name: string, event: TEvent): Promise<TestTriggerResult> {\n const definition = this.triggers.get(name)\n if (!definition) {\n throw new Error(`Trigger '${name}' not registered`)\n }\n\n const result = await testTrigger(definition, {\n event,\n bindings: this.bindings,\n })\n\n this.results.push(result)\n return result\n }\n\n /**\n * Get all completed trigger results.\n */\n get completed(): TestTriggerResult[] {\n return this.results.filter((r) => r.success)\n }\n\n /**\n * Get all failed trigger results.\n */\n get failed(): TestTriggerResult[] {\n return this.results.filter((r) => !r.success)\n }\n\n /**\n * Get all results.\n */\n get all(): TestTriggerResult[] {\n return [...this.results]\n }\n\n /**\n * Reset the harness (clear results).\n */\n reset(): void {\n this.results = []\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudwerk/trigger",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Event-driven triggers for Cloudwerk (scheduled, queue, R2, webhooks)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@cloudwerk/core": "0.
|
|
25
|
+
"@cloudwerk/core": "0.13.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"typescript": "^5.4.0",
|