@usethrottle/webhook-types 0.2.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 +62 -0
- package/dist/index.d.ts +215 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @usethrottle/webhook-types
|
|
2
|
+
|
|
3
|
+
TypeScript discriminated unions for every Throttle outbound webhook event, plus a Stripe-compatible signature verification helper.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @usethrottle/webhook-types
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
type WebhookEnvelope,
|
|
14
|
+
verifyWebhookSignature,
|
|
15
|
+
} from '@usethrottle/webhook-types';
|
|
16
|
+
|
|
17
|
+
export async function POST(req: Request) {
|
|
18
|
+
const rawBody = await req.text();
|
|
19
|
+
const ok = verifyWebhookSignature({
|
|
20
|
+
header: req.headers.get('throttle-signature')!,
|
|
21
|
+
rawBody,
|
|
22
|
+
secret: process.env.THROTTLE_WEBHOOK_SECRET!,
|
|
23
|
+
});
|
|
24
|
+
if (!ok) return new Response('bad signature', { status: 400 });
|
|
25
|
+
|
|
26
|
+
const envelope = JSON.parse(rawBody) as WebhookEnvelope;
|
|
27
|
+
const { event } = envelope;
|
|
28
|
+
|
|
29
|
+
// event.data is narrowed by event.type — no casts.
|
|
30
|
+
switch (event.type) {
|
|
31
|
+
case 'cart.item_added':
|
|
32
|
+
// event.data: { cartId, sequence, itemId, name, quantity, unitPrice }
|
|
33
|
+
analytics.track('item_added', event.data);
|
|
34
|
+
break;
|
|
35
|
+
case 'order.created':
|
|
36
|
+
// event.data: { order, fromCart?, sessionId? }
|
|
37
|
+
await fulfill(event.data.order as { id: string });
|
|
38
|
+
break;
|
|
39
|
+
case 'payment.disputed':
|
|
40
|
+
// event.data: { paymentId, reason, openedAt }
|
|
41
|
+
await ops.alert(event.data.paymentId, event.data.reason);
|
|
42
|
+
break;
|
|
43
|
+
case 'workspace.payment_method.backup_used':
|
|
44
|
+
// event.data: { workspace, primaryPaymentMethod, backupPaymentMethod, charge }
|
|
45
|
+
await notifyOps('backup PM used', event.data);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new Response('ok');
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Coverage
|
|
54
|
+
|
|
55
|
+
- **WebhookEvent** — discriminated union covering all 65 outbound event types currently emitted by the Throttle outbound bus (cart, order, payment, fulfillment, subscription, discount, customer, invoice, shipping/tax, workspace lifecycle).
|
|
56
|
+
- **WebhookEventType** — `WebhookEvent['type']`; use it to constrain switch statements or event-name handlers.
|
|
57
|
+
- **Typed payloads** — primitive fields (ids, amounts, codes, timestamps) are fully typed. Embedded domain objects (`order`, `payment`, `subscription`, `workspace`, etc.) are typed as `Record<string, unknown>` because the canonical schemas live in the API server and shouldn't be duplicated here. Cast through the OpenAPI client (`@usethrottle/api-client`) for the embedded shapes.
|
|
58
|
+
- **verifyWebhookSignature** — Stripe-compatible `t=…,v1=…` header parsing with timing-safe HMAC-SHA256 comparison + 5-minute replay window by default.
|
|
59
|
+
|
|
60
|
+
## Versioning
|
|
61
|
+
|
|
62
|
+
The event list is the contract. Adding an event in the Throttle outbound bus is a minor bump; removing one is a major bump. Typed payload field changes are minor when additive; breaking when not. See [docs.usethrottle.dev/developers/webhooks](https://docs.usethrottle.dev/developers/webhooks) for the full event reference + signature format.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @usethrottle/webhook-types
|
|
3
|
+
*
|
|
4
|
+
* TypeScript discriminated unions for every Throttle outbound webhook event.
|
|
5
|
+
* Keyed by `event.type`, this lets your handler narrow `event.data` without
|
|
6
|
+
* casts or `Record<string, unknown>` ceremony.
|
|
7
|
+
*
|
|
8
|
+
* Coverage: all 69 outbound event types are enumerated in `WebhookEventType`.
|
|
9
|
+
* Payload shapes are typed for the 24 most-integrated events (cart, order,
|
|
10
|
+
* payment, fulfillment, subscription core lifecycle). The remaining events
|
|
11
|
+
* resolve to `data: Record<string, unknown>` until typed in a future release.
|
|
12
|
+
*
|
|
13
|
+
* Tracks the canonical event registry in `@platform/webhooks` so adding a new
|
|
14
|
+
* event type there is a compile-time error here until the union is updated.
|
|
15
|
+
*/
|
|
16
|
+
export interface WebhookEnvelope<T extends WebhookEvent = WebhookEvent> {
|
|
17
|
+
/** Stable event identifier (UUID v7). */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Workspace that owns the event source. */
|
|
20
|
+
workspaceId: string;
|
|
21
|
+
/** Application (store) the event was emitted from. */
|
|
22
|
+
applicationId: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the event originated from test-mode resources. Outbound endpoints
|
|
25
|
+
* registered with `isTest=true` only receive `isTest:true` events.
|
|
26
|
+
*/
|
|
27
|
+
isTest: boolean;
|
|
28
|
+
/** ISO 8601 timestamp the event was emitted at. */
|
|
29
|
+
createdAt: string;
|
|
30
|
+
/** Discriminated union — narrow by `event.type`. */
|
|
31
|
+
event: T;
|
|
32
|
+
}
|
|
33
|
+
export interface MoneyAmount {
|
|
34
|
+
amount: number;
|
|
35
|
+
currency: string;
|
|
36
|
+
}
|
|
37
|
+
export interface CartCreatedData {
|
|
38
|
+
cartId: string;
|
|
39
|
+
sequence: number;
|
|
40
|
+
applicationId: string;
|
|
41
|
+
customerId: string | null;
|
|
42
|
+
currency: string;
|
|
43
|
+
}
|
|
44
|
+
export interface CartUpdatedData {
|
|
45
|
+
cartId: string;
|
|
46
|
+
sequence: number;
|
|
47
|
+
changedFields: string[];
|
|
48
|
+
}
|
|
49
|
+
export interface CartItemAddedData {
|
|
50
|
+
cartId: string;
|
|
51
|
+
sequence: number;
|
|
52
|
+
itemId: string;
|
|
53
|
+
name: string;
|
|
54
|
+
quantity: number;
|
|
55
|
+
unitPrice: number;
|
|
56
|
+
}
|
|
57
|
+
export interface CartItemUpdatedData {
|
|
58
|
+
cartId: string;
|
|
59
|
+
sequence: number;
|
|
60
|
+
itemId: string;
|
|
61
|
+
quantity: number;
|
|
62
|
+
unitPrice: number;
|
|
63
|
+
}
|
|
64
|
+
export interface CartItemRemovedData {
|
|
65
|
+
cartId: string;
|
|
66
|
+
sequence: number;
|
|
67
|
+
itemId: string;
|
|
68
|
+
}
|
|
69
|
+
export interface CartShippingSelectedData {
|
|
70
|
+
cartId: string;
|
|
71
|
+
sequence: number;
|
|
72
|
+
methodId: string;
|
|
73
|
+
displayName: string;
|
|
74
|
+
rateAmount: number;
|
|
75
|
+
currency: string;
|
|
76
|
+
}
|
|
77
|
+
export interface CartDiscountAppliedData {
|
|
78
|
+
cartId: string;
|
|
79
|
+
sequence: number;
|
|
80
|
+
code: string;
|
|
81
|
+
amount: number;
|
|
82
|
+
type: 'percentage' | 'fixed_amount';
|
|
83
|
+
discountTotal: number;
|
|
84
|
+
}
|
|
85
|
+
export interface OrderCreatedData {
|
|
86
|
+
orderId: string;
|
|
87
|
+
orderNumber: string;
|
|
88
|
+
customerId: string | null;
|
|
89
|
+
currency: string;
|
|
90
|
+
total: number;
|
|
91
|
+
status: 'draft' | 'pending' | 'paid' | 'cancelled' | 'refunded';
|
|
92
|
+
}
|
|
93
|
+
export interface OrderUpdatedData {
|
|
94
|
+
orderId: string;
|
|
95
|
+
changedFields: string[];
|
|
96
|
+
}
|
|
97
|
+
export interface OrderCancelledData {
|
|
98
|
+
orderId: string;
|
|
99
|
+
reason: string | null;
|
|
100
|
+
}
|
|
101
|
+
export interface OrderFulfilledData {
|
|
102
|
+
orderId: string;
|
|
103
|
+
fulfillmentId: string;
|
|
104
|
+
}
|
|
105
|
+
export interface PaymentCreatedData {
|
|
106
|
+
paymentId: string;
|
|
107
|
+
orderId: string | null;
|
|
108
|
+
amount: number;
|
|
109
|
+
currency: string;
|
|
110
|
+
status: 'pending' | 'authorized' | 'captured' | 'failed' | 'refunded' | 'voided';
|
|
111
|
+
}
|
|
112
|
+
export interface PaymentCapturedData {
|
|
113
|
+
paymentId: string;
|
|
114
|
+
orderId: string | null;
|
|
115
|
+
amount: number;
|
|
116
|
+
currency: string;
|
|
117
|
+
}
|
|
118
|
+
export interface PaymentFailedData {
|
|
119
|
+
paymentId: string;
|
|
120
|
+
orderId: string | null;
|
|
121
|
+
code: string;
|
|
122
|
+
message: string;
|
|
123
|
+
}
|
|
124
|
+
export interface PaymentRefundedData {
|
|
125
|
+
paymentId: string;
|
|
126
|
+
refundId: string;
|
|
127
|
+
amount: number;
|
|
128
|
+
currency: string;
|
|
129
|
+
}
|
|
130
|
+
export interface FulfillmentCreatedData {
|
|
131
|
+
fulfillmentId: string;
|
|
132
|
+
orderId: string;
|
|
133
|
+
type: 'shipment' | 'digital' | 'access' | 'service' | 'in_person';
|
|
134
|
+
status: 'pending' | 'in_progress' | 'fulfilled' | 'cancelled';
|
|
135
|
+
}
|
|
136
|
+
export interface FulfillmentShippedData {
|
|
137
|
+
fulfillmentId: string;
|
|
138
|
+
orderId: string;
|
|
139
|
+
trackingNumber: string | null;
|
|
140
|
+
carrier: string | null;
|
|
141
|
+
}
|
|
142
|
+
export interface FulfillmentDeliveredData {
|
|
143
|
+
fulfillmentId: string;
|
|
144
|
+
orderId: string;
|
|
145
|
+
deliveredAt: string;
|
|
146
|
+
}
|
|
147
|
+
export interface SubscriptionCreatedData {
|
|
148
|
+
subscriptionId: string;
|
|
149
|
+
customerId: string;
|
|
150
|
+
planReference: string;
|
|
151
|
+
status: 'active' | 'trialing' | 'past_due' | 'cancelled' | 'paused';
|
|
152
|
+
currentPeriodStart: string;
|
|
153
|
+
currentPeriodEnd: string;
|
|
154
|
+
}
|
|
155
|
+
export interface SubscriptionUpdatedData {
|
|
156
|
+
subscriptionId: string;
|
|
157
|
+
changedFields: string[];
|
|
158
|
+
}
|
|
159
|
+
export interface SubscriptionCancelledData {
|
|
160
|
+
subscriptionId: string;
|
|
161
|
+
cancelledAt: string;
|
|
162
|
+
reason: string | null;
|
|
163
|
+
}
|
|
164
|
+
export interface SubscriptionRenewedData {
|
|
165
|
+
subscriptionId: string;
|
|
166
|
+
currentPeriodStart: string;
|
|
167
|
+
currentPeriodEnd: string;
|
|
168
|
+
}
|
|
169
|
+
export interface CustomerCreatedData {
|
|
170
|
+
customerId: string;
|
|
171
|
+
email: string | null;
|
|
172
|
+
externalId: string | null;
|
|
173
|
+
}
|
|
174
|
+
export interface CustomerUpdatedData {
|
|
175
|
+
customerId: string;
|
|
176
|
+
changedFields: string[];
|
|
177
|
+
}
|
|
178
|
+
type Event<T extends string, D> = {
|
|
179
|
+
type: T;
|
|
180
|
+
data: D;
|
|
181
|
+
};
|
|
182
|
+
export type WebhookEvent = Event<'cart.created', CartCreatedData> | Event<'cart.updated', CartUpdatedData> | Event<'cart.item_added', CartItemAddedData> | Event<'cart.item_updated', CartItemUpdatedData> | Event<'cart.item_removed', CartItemRemovedData> | Event<'cart.shipping_selected', CartShippingSelectedData> | Event<'cart.shipping_cleared', {
|
|
183
|
+
cartId: string;
|
|
184
|
+
sequence: number;
|
|
185
|
+
}> | Event<'cart.discount_applied', CartDiscountAppliedData> | Event<'cart.discount_removed', {
|
|
186
|
+
cartId: string;
|
|
187
|
+
sequence: number;
|
|
188
|
+
}> | Event<'cart.tax_recomputed', {
|
|
189
|
+
cartId: string;
|
|
190
|
+
sequence: number;
|
|
191
|
+
lineCount: number;
|
|
192
|
+
}> | Event<'order.created', OrderCreatedData> | Event<'order.updated', OrderUpdatedData> | Event<'order.cancelled', OrderCancelledData> | Event<'order.fulfilled', OrderFulfilledData> | Event<'payment.created', PaymentCreatedData> | Event<'payment.authorized', PaymentCreatedData> | Event<'payment.captured', PaymentCapturedData> | Event<'payment.failed', PaymentFailedData> | Event<'payment.refunded', PaymentRefundedData> | Event<'payment.voided', PaymentCreatedData> | Event<'fulfillment.created', FulfillmentCreatedData> | Event<'fulfillment.shipped', FulfillmentShippedData> | Event<'fulfillment.delivered', FulfillmentDeliveredData> | Event<'fulfillment.cancelled', {
|
|
193
|
+
fulfillmentId: string;
|
|
194
|
+
orderId: string;
|
|
195
|
+
}> | Event<'subscription.created', SubscriptionCreatedData> | Event<'subscription.updated', SubscriptionUpdatedData> | Event<'subscription.cancelled', SubscriptionCancelledData> | Event<'subscription.renewed', SubscriptionRenewedData> | Event<'customer.created', CustomerCreatedData> | Event<'customer.updated', CustomerUpdatedData> | Event<Exclude<WebhookEventType, TypedEventType>, Record<string, unknown>>;
|
|
196
|
+
type TypedEventType = 'cart.created' | 'cart.updated' | 'cart.item_added' | 'cart.item_updated' | 'cart.item_removed' | 'cart.shipping_selected' | 'cart.shipping_cleared' | 'cart.discount_applied' | 'cart.discount_removed' | 'cart.tax_recomputed' | 'order.created' | 'order.updated' | 'order.cancelled' | 'order.fulfilled' | 'payment.created' | 'payment.authorized' | 'payment.captured' | 'payment.failed' | 'payment.refunded' | 'payment.voided' | 'fulfillment.created' | 'fulfillment.shipped' | 'fulfillment.delivered' | 'fulfillment.cancelled' | 'subscription.created' | 'subscription.updated' | 'subscription.cancelled' | 'subscription.renewed' | 'customer.created' | 'customer.updated';
|
|
197
|
+
export type WebhookEventType = 'cart.created' | 'cart.updated' | 'cart.item_added' | 'cart.item_updated' | 'cart.item_removed' | 'cart.shipping_selected' | 'cart.shipping_cleared' | 'cart.discount_applied' | 'cart.discount_removed' | 'cart.tax_recomputed' | 'cart.checked_out' | 'order.created' | 'order.updated' | 'order.cancelled' | 'order.refunded' | 'order.fulfilled' | 'order.completed' | 'payment.created' | 'payment.authorized' | 'payment.captured' | 'payment.failed' | 'payment.refunded' | 'payment.voided' | 'payment.pending' | 'fulfillment.created' | 'fulfillment.shipped' | 'fulfillment.delivered' | 'fulfillment.cancelled' | 'fulfillment.access_granted' | 'fulfillment.access_revoked' | 'subscription.created' | 'subscription.updated' | 'subscription.cancelled' | 'subscription.renewed' | 'subscription.trial_ending' | 'subscription.trial_ended' | 'subscription.past_due' | 'subscription.recovered' | 'subscription.paused' | 'subscription.resumed' | 'discount.created' | 'discount.updated' | 'discount.archived' | 'discount.redeemed' | 'customer.created' | 'customer.updated' | 'customer.deleted' | 'invoice.created' | 'invoice.paid' | 'invoice.overdue' | 'invoice.disputed' | 'invoice.refunded' | 'invoice.aged' | 'shipping.rate_calculated' | 'tax.calculated' | 'tax.recalculated' | 'workspace.payment_method.added' | 'workspace.payment_method.removed' | 'workspace.payment_method.set_default' | 'workspace.payment_method.set_backup' | 'platform.invoice.created' | 'platform.invoice.paid' | 'platform.invoice.failed' | 'platform.charge.refunded' | 'workspace.created' | 'workspace.trial_started' | 'workspace.trial_ending' | 'workspace.trial_ended' | 'workspace.subscribed';
|
|
198
|
+
/**
|
|
199
|
+
* Verifies the `Throttle-Signature` header against the raw request body.
|
|
200
|
+
* Use this from your webhook handler BEFORE parsing the JSON.
|
|
201
|
+
*
|
|
202
|
+
* Header format: `t=<timestamp>,v1=<hex>`. The HMAC is computed over
|
|
203
|
+
* `${timestamp}.${rawBody}` with the endpoint's signing secret
|
|
204
|
+
* (returned as `signingSecret` when you created the endpoint via
|
|
205
|
+
* POST /api/v1/webhooks). Reject if the timestamp is more than
|
|
206
|
+
* `toleranceSeconds` old (default 5 minutes) — protects against replay.
|
|
207
|
+
*/
|
|
208
|
+
export declare function verifyWebhookSignature(args: {
|
|
209
|
+
header: string;
|
|
210
|
+
rawBody: string;
|
|
211
|
+
secret: string;
|
|
212
|
+
toleranceSeconds?: number;
|
|
213
|
+
}): boolean;
|
|
214
|
+
export {};
|
|
215
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @usethrottle/webhook-types
|
|
3
|
+
*
|
|
4
|
+
* TypeScript discriminated unions for every Throttle outbound webhook event.
|
|
5
|
+
* Keyed by `event.type`, this lets your handler narrow `event.data` without
|
|
6
|
+
* casts or `Record<string, unknown>` ceremony.
|
|
7
|
+
*
|
|
8
|
+
* Coverage: all 69 outbound event types are enumerated in `WebhookEventType`.
|
|
9
|
+
* Payload shapes are typed for the 24 most-integrated events (cart, order,
|
|
10
|
+
* payment, fulfillment, subscription core lifecycle). The remaining events
|
|
11
|
+
* resolve to `data: Record<string, unknown>` until typed in a future release.
|
|
12
|
+
*
|
|
13
|
+
* Tracks the canonical event registry in `@platform/webhooks` so adding a new
|
|
14
|
+
* event type there is a compile-time error here until the union is updated.
|
|
15
|
+
*/
|
|
16
|
+
// ----------------------------------------------------------------------------
|
|
17
|
+
// Signature verification (Stripe-compatible scheme)
|
|
18
|
+
// ----------------------------------------------------------------------------
|
|
19
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
20
|
+
/**
|
|
21
|
+
* Verifies the `Throttle-Signature` header against the raw request body.
|
|
22
|
+
* Use this from your webhook handler BEFORE parsing the JSON.
|
|
23
|
+
*
|
|
24
|
+
* Header format: `t=<timestamp>,v1=<hex>`. The HMAC is computed over
|
|
25
|
+
* `${timestamp}.${rawBody}` with the endpoint's signing secret
|
|
26
|
+
* (returned as `signingSecret` when you created the endpoint via
|
|
27
|
+
* POST /api/v1/webhooks). Reject if the timestamp is more than
|
|
28
|
+
* `toleranceSeconds` old (default 5 minutes) — protects against replay.
|
|
29
|
+
*/
|
|
30
|
+
export function verifyWebhookSignature(args) {
|
|
31
|
+
const { header, rawBody, secret, toleranceSeconds = 300 } = args;
|
|
32
|
+
const parts = Object.fromEntries(header.split(',').map((p) => {
|
|
33
|
+
const [k, v] = p.split('=');
|
|
34
|
+
return [k.trim(), v?.trim()];
|
|
35
|
+
}));
|
|
36
|
+
if (!parts.t || !parts.v1)
|
|
37
|
+
return false;
|
|
38
|
+
const timestamp = Number(parts.t);
|
|
39
|
+
if (!Number.isFinite(timestamp))
|
|
40
|
+
return false;
|
|
41
|
+
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds)
|
|
42
|
+
return false;
|
|
43
|
+
const expected = createHmac('sha256', secret)
|
|
44
|
+
.update(`${parts.t}.${rawBody}`)
|
|
45
|
+
.digest('hex');
|
|
46
|
+
const received = Buffer.from(parts.v1, 'hex');
|
|
47
|
+
if (received.length !== expected.length / 2)
|
|
48
|
+
return false;
|
|
49
|
+
return timingSafeEqual(received, Buffer.from(expected, 'hex'));
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkUH,+EAA+E;AAC/E,oDAAoD;AACpD,+EAA+E;AAE/E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAKtC;IACC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC;IAEjE,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC1B,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC,CACH,CAAC;IAEF,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IAExC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,gBAAgB;QAAE,OAAO,KAAK,CAAC;IAE7E,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;SAC1C,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;SAC/B,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,OAAO,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;AACjE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usethrottle/webhook-types",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TypeScript discriminated unions for every Throttle outbound webhook event.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"test": "tsc --noEmit && vitest run --passWithNoTests"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20.0.0",
|
|
20
|
+
"typescript": "^5.7.0",
|
|
21
|
+
"vitest": "^2.1.9"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
}
|
|
29
|
+
}
|