@delightstack/stripe 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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/client/billing.client.svelte.d.ts +154 -0
  4. package/dist/client/billing.client.svelte.d.ts.map +1 -0
  5. package/dist/client/billing.client.svelte.js +279 -0
  6. package/dist/client/index.d.ts +2 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/index.js +1 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +1 -0
  12. package/dist/server/billing.config.d.ts +189 -0
  13. package/dist/server/billing.config.d.ts.map +1 -0
  14. package/dist/server/billing.config.js +37 -0
  15. package/dist/server/billing.handler.d.ts +46 -0
  16. package/dist/server/billing.handler.d.ts.map +1 -0
  17. package/dist/server/billing.handler.js +73 -0
  18. package/dist/server/billing.meter.d.ts +41 -0
  19. package/dist/server/billing.meter.d.ts.map +1 -0
  20. package/dist/server/billing.meter.js +55 -0
  21. package/dist/server/billing.products.d.ts +24 -0
  22. package/dist/server/billing.products.d.ts.map +1 -0
  23. package/dist/server/billing.products.js +124 -0
  24. package/dist/server/billing.routes.d.ts +8 -0
  25. package/dist/server/billing.routes.d.ts.map +1 -0
  26. package/dist/server/billing.routes.js +441 -0
  27. package/dist/server/billing.stripe.d.ts +34 -0
  28. package/dist/server/billing.stripe.d.ts.map +1 -0
  29. package/dist/server/billing.stripe.js +135 -0
  30. package/dist/server/billing.sync.d.ts +29 -0
  31. package/dist/server/billing.sync.d.ts.map +1 -0
  32. package/dist/server/billing.sync.js +122 -0
  33. package/dist/server/billing.webhook.d.ts +10 -0
  34. package/dist/server/billing.webhook.d.ts.map +1 -0
  35. package/dist/server/billing.webhook.js +222 -0
  36. package/dist/server/billing.webhook.register.d.ts +12 -0
  37. package/dist/server/billing.webhook.register.d.ts.map +1 -0
  38. package/dist/server/billing.webhook.register.js +57 -0
  39. package/dist/server/index.d.ts +9 -0
  40. package/dist/server/index.d.ts.map +1 -0
  41. package/dist/server/index.js +8 -0
  42. package/dist/sveltekit/guards.d.ts +29 -0
  43. package/dist/sveltekit/guards.d.ts.map +1 -0
  44. package/dist/sveltekit/guards.js +57 -0
  45. package/dist/sveltekit/index.d.ts +2 -0
  46. package/dist/sveltekit/index.d.ts.map +1 -0
  47. package/dist/sveltekit/index.js +1 -0
  48. package/dist/types/billing.type.d.ts +61 -0
  49. package/dist/types/billing.type.d.ts.map +1 -0
  50. package/dist/types/billing.type.js +1 -0
  51. package/dist/types/index.d.ts +3 -0
  52. package/dist/types/index.d.ts.map +1 -0
  53. package/dist/types/index.js +2 -0
  54. package/dist/types/webhook.type.d.ts +22 -0
  55. package/dist/types/webhook.type.d.ts.map +1 -0
  56. package/dist/types/webhook.type.js +2 -0
  57. package/package.json +91 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brian Schwabauer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @delightstack/stripe
2
+
3
+ Stripe billing for Cloudflare Workers and SvelteKit — products, metered usage, webhooks, and
4
+ subscription guards. Part of the [Delightstack](https://thedelight.co).
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pnpm add @delightstack/stripe stripe
10
+ ```
11
+
12
+ ## Entry points
13
+
14
+ | Import | Use |
15
+ | --- | --- |
16
+ | `@delightstack/stripe/server` | Worker-side billing: products, metered usage, webhook handling |
17
+ | `@delightstack/stripe/client` | Reactive Svelte 5 billing client |
18
+ | `@delightstack/stripe/sveltekit` | SvelteKit subscription guards |
19
+ | `@delightstack/stripe/types` | Shared types |
20
+
21
+ `svelte` and `@sveltejs/kit` are optional peer dependencies, required only when you use the
22
+ `client` / `sveltekit` entry points respectively.
23
+
24
+ ## Documentation
25
+
26
+ Full docs: <https://docs.thedelight.co>
27
+
28
+ ## License
29
+
30
+ MIT © Brian Schwabauer
@@ -0,0 +1,154 @@
1
+ import type { SubscriptionState, PlanInfo, PaymentMethodInfo, InvoiceInfo } from '../types';
2
+ /** Serialized data for SSR hydration */
3
+ export interface BillingClientData {
4
+ subscription: SubscriptionState | null;
5
+ plans: PlanInfo[];
6
+ publishable_key: string;
7
+ portal_enabled: boolean;
8
+ }
9
+ /** Error shape thrown by BillingClient API methods */
10
+ export interface BillingClientError {
11
+ code: string;
12
+ message: string;
13
+ status: number;
14
+ detail?: string;
15
+ }
16
+ /**
17
+ * Reactive billing client for Svelte 5.
18
+ * Manages subscription state, payment methods, and invoices.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // In +layout.ts:
23
+ * const billing = new BillingClient(data.billing);
24
+ * return { billing };
25
+ *
26
+ * // In components:
27
+ * billing.subscribed // reactive boolean
28
+ * billing.plan_ids // reactive string[]
29
+ * billing.hasEntitlement('premium')
30
+ * await billing.api.subscribe('pro-monthly')
31
+ * await billing.api.cancel()
32
+ * ```
33
+ */
34
+ export declare class BillingClient {
35
+ #private;
36
+ private base_path;
37
+ private fetchFn;
38
+ /** The current subscription state (null if no subscription) */
39
+ get subscription(): SubscriptionState | null;
40
+ /** Whether the user/org has an active or trialing subscription */
41
+ readonly subscribed: boolean;
42
+ /** Whether the subscription is in a trial period */
43
+ readonly trialing: boolean;
44
+ /** Current subscription status (null if no subscription) */
45
+ readonly status: import("stripe").Stripe.Subscription.Status | null;
46
+ /** The plan IDs currently subscribed to */
47
+ readonly plan_ids: string[];
48
+ /** The entitlement names granted by the current subscription */
49
+ readonly active_entitlements: string[];
50
+ /** Available plans for subscription */
51
+ get plans(): PlanInfo[];
52
+ /** Stripe publishable key (for Stripe.js initialization) */
53
+ get publishable_key(): string;
54
+ /** Whether the Stripe Billing Portal is enabled */
55
+ get portal_enabled(): boolean;
56
+ /** Trial end timestamp in ms (null if not trialing) */
57
+ readonly trial_end: number | null;
58
+ /** Current period end timestamp in ms */
59
+ readonly period_end: number | null;
60
+ /** Whether subscription is scheduled for cancellation */
61
+ readonly canceling: boolean;
62
+ constructor(data?: BillingClientData, options?: {
63
+ base_path?: string;
64
+ fetch?: typeof fetch;
65
+ });
66
+ /** Check if the current subscription grants a specific entitlement */
67
+ hasEntitlement(name: string): boolean;
68
+ /** Check if currently subscribed to a specific plan */
69
+ hasPlan(plan_id: string): boolean;
70
+ /** Serializes state for SSR hydration */
71
+ toJSON(): BillingClientData;
72
+ /**
73
+ * Apply a WebSocket billing event to update reactive state.
74
+ * Wire this into your WebSocket client:
75
+ *
76
+ * ```ts
77
+ * ws.on('billing:subscription:changed', (msg) => billing.handleEvent(msg));
78
+ * ```
79
+ */
80
+ handleEvent(message: {
81
+ event: string;
82
+ [key: string]: unknown;
83
+ }): void;
84
+ /**
85
+ * Returns hooks for wiring WebSocket events into the billing client.
86
+ *
87
+ * ```ts
88
+ * const ws = new WebsocketClient();
89
+ * const billing = new BillingClient(data.billing);
90
+ * const hooks = billing.websocketHooks();
91
+ *
92
+ * ws.on('billing:subscription:changed', hooks.onSubscriptionChanged);
93
+ * ws.on('billing:payment:succeeded', hooks.onPaymentSucceeded);
94
+ * ws.on('billing:payment:failed', hooks.onPaymentFailed);
95
+ * ```
96
+ */
97
+ websocketHooks(): {
98
+ onSubscriptionChanged: (msg: Record<string, unknown>) => void;
99
+ onPaymentSucceeded: (_msg: Record<string, unknown>) => void;
100
+ onPaymentFailed: (_msg: Record<string, unknown>) => void;
101
+ };
102
+ readonly api: {
103
+ /** Subscribe to a plan */
104
+ readonly subscribe: (plan_id: string, options?: {
105
+ payment_method_id?: string;
106
+ coupon?: string;
107
+ }) => Promise<SubscriptionState | null>;
108
+ /**
109
+ * Cancel the current subscription.
110
+ * Pass `{ at_period_end: true }` to keep access until the billing
111
+ * period ends instead of canceling immediately.
112
+ */
113
+ readonly cancel: (options?: {
114
+ at_period_end?: boolean;
115
+ }) => Promise<void>;
116
+ /** Refresh subscription state from the server */
117
+ readonly refreshSubscription: () => Promise<SubscriptionState | null>;
118
+ /** Force sync subscription state (fetches from Stripe and updates auth entitlements) */
119
+ readonly syncSubscription: () => Promise<SubscriptionState | null>;
120
+ /** List invoices */
121
+ readonly listInvoices: (options?: {
122
+ limit?: number;
123
+ }) => Promise<InvoiceInfo[]>;
124
+ /** List payment methods */
125
+ readonly listPaymentMethods: () => Promise<PaymentMethodInfo[]>;
126
+ /** Create a setup session for adding a payment method */
127
+ readonly createPaymentMethodSession: () => Promise<{
128
+ client_secret: string;
129
+ }>;
130
+ /** Remove a payment method */
131
+ readonly removePaymentMethod: (id: string) => Promise<void>;
132
+ /** Set a payment method as default */
133
+ readonly setDefaultPaymentMethod: (id: string) => Promise<void>;
134
+ /** Create a Billing Portal session */
135
+ readonly createPortalSession: (return_url?: string) => Promise<{
136
+ url: string;
137
+ }>;
138
+ /** Open the Billing Portal in a new tab */
139
+ readonly openPortal: (return_url?: string) => Promise<void>;
140
+ /** Create a Checkout session for a plan */
141
+ readonly createCheckoutSession: (plan_id: string, return_url?: string) => Promise<{
142
+ client_secret: string;
143
+ }>;
144
+ /** Fetch available plans */
145
+ readonly listPlans: () => Promise<PlanInfo[]>;
146
+ };
147
+ private post;
148
+ private get;
149
+ private del;
150
+ private patch;
151
+ private handleResponse;
152
+ private parseError;
153
+ }
154
+ //# sourceMappingURL=billing.client.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.client.svelte.d.ts","sourceRoot":"","sources":["../../src/client/billing.client.svelte.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Reactive billing client for Svelte 5.
3
+ * Manages subscription state, payment methods, and invoices.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // In +layout.ts:
8
+ * const billing = new BillingClient(data.billing);
9
+ * return { billing };
10
+ *
11
+ * // In components:
12
+ * billing.subscribed // reactive boolean
13
+ * billing.plan_ids // reactive string[]
14
+ * billing.hasEntitlement('premium')
15
+ * await billing.api.subscribe('pro-monthly')
16
+ * await billing.api.cancel()
17
+ * ```
18
+ */
19
+ export class BillingClient {
20
+ #subscription = $state(null);
21
+ #plans = $state([]);
22
+ #publishable_key;
23
+ #portal_enabled;
24
+ base_path;
25
+ fetchFn;
26
+ // ── Reactive state ─────────────────────────────────────────────
27
+ /** The current subscription state (null if no subscription) */
28
+ get subscription() {
29
+ return this.#subscription;
30
+ }
31
+ /** Whether the user/org has an active or trialing subscription */
32
+ subscribed = $derived(this.#subscription?.status === 'active' || this.#subscription?.status === 'trialing');
33
+ /** Whether the subscription is in a trial period */
34
+ trialing = $derived(this.#subscription?.status === 'trialing');
35
+ /** Current subscription status (null if no subscription) */
36
+ status = $derived(this.#subscription?.status ?? null);
37
+ /** The plan IDs currently subscribed to */
38
+ plan_ids = $derived(this.#subscription?.plan_ids ?? []);
39
+ /** The entitlement names granted by the current subscription */
40
+ active_entitlements = $derived(this.#subscription?.entitlements ?? []);
41
+ /** Available plans for subscription */
42
+ get plans() {
43
+ return this.#plans;
44
+ }
45
+ /** Stripe publishable key (for Stripe.js initialization) */
46
+ get publishable_key() {
47
+ return this.#publishable_key;
48
+ }
49
+ /** Whether the Stripe Billing Portal is enabled */
50
+ get portal_enabled() {
51
+ return this.#portal_enabled;
52
+ }
53
+ /** Trial end timestamp in ms (null if not trialing) */
54
+ trial_end = $derived(this.#subscription?.trial_end ?? null);
55
+ /** Current period end timestamp in ms */
56
+ period_end = $derived(this.#subscription?.current_period_end ?? null);
57
+ /** Whether subscription is scheduled for cancellation */
58
+ canceling = $derived(!!this.#subscription?.cancel_at);
59
+ constructor(data, options) {
60
+ this.#subscription = data?.subscription ?? null;
61
+ this.#plans = data?.plans ?? [];
62
+ this.#publishable_key = data?.publishable_key ?? '';
63
+ this.#portal_enabled = data?.portal_enabled ?? false;
64
+ this.base_path = options?.base_path ?? '/api/billing';
65
+ this.fetchFn = options?.fetch ?? fetch;
66
+ }
67
+ /** Check if the current subscription grants a specific entitlement */
68
+ hasEntitlement(name) {
69
+ return this.active_entitlements.includes(name);
70
+ }
71
+ /** Check if currently subscribed to a specific plan */
72
+ hasPlan(plan_id) {
73
+ return this.plan_ids.includes(plan_id);
74
+ }
75
+ /** Serializes state for SSR hydration */
76
+ toJSON() {
77
+ return {
78
+ subscription: this.#subscription,
79
+ plans: this.#plans,
80
+ publishable_key: this.#publishable_key,
81
+ portal_enabled: this.#portal_enabled,
82
+ };
83
+ }
84
+ /**
85
+ * Apply a WebSocket billing event to update reactive state.
86
+ * Wire this into your WebSocket client:
87
+ *
88
+ * ```ts
89
+ * ws.on('billing:subscription:changed', (msg) => billing.handleEvent(msg));
90
+ * ```
91
+ */
92
+ handleEvent(message) {
93
+ if (message.event === 'billing:subscription:changed') {
94
+ if (this.#subscription) {
95
+ this.#subscription = {
96
+ ...this.#subscription,
97
+ status: message.status,
98
+ plan_ids: message.plan_ids,
99
+ entitlements: message.entitlements,
100
+ };
101
+ }
102
+ else {
103
+ // New subscription event when we had none — trigger a full refresh
104
+ this.api.refreshSubscription().catch(() => { });
105
+ return;
106
+ }
107
+ // Also trigger a full refresh for accuracy
108
+ this.api.refreshSubscription().catch(() => { });
109
+ }
110
+ }
111
+ /**
112
+ * Returns hooks for wiring WebSocket events into the billing client.
113
+ *
114
+ * ```ts
115
+ * const ws = new WebsocketClient();
116
+ * const billing = new BillingClient(data.billing);
117
+ * const hooks = billing.websocketHooks();
118
+ *
119
+ * ws.on('billing:subscription:changed', hooks.onSubscriptionChanged);
120
+ * ws.on('billing:payment:succeeded', hooks.onPaymentSucceeded);
121
+ * ws.on('billing:payment:failed', hooks.onPaymentFailed);
122
+ * ```
123
+ */
124
+ websocketHooks() {
125
+ return {
126
+ onSubscriptionChanged: (msg) => {
127
+ this.handleEvent({
128
+ event: 'billing:subscription:changed',
129
+ ...msg,
130
+ });
131
+ },
132
+ onPaymentSucceeded: (_msg) => {
133
+ // Refresh subscription state after a successful payment
134
+ this.api.refreshSubscription().catch(() => { });
135
+ },
136
+ onPaymentFailed: (_msg) => {
137
+ // Refresh subscription state after a failed payment
138
+ this.api.refreshSubscription().catch(() => { });
139
+ },
140
+ };
141
+ }
142
+ // ── API methods ────────────────────────────────────────────────
143
+ api = {
144
+ /** Subscribe to a plan */
145
+ subscribe: async (plan_id, options) => {
146
+ const result = await this.post('/subscription', { plan_id, ...options });
147
+ this.#subscription = result.subscription;
148
+ return result.subscription;
149
+ },
150
+ /**
151
+ * Cancel the current subscription.
152
+ * Pass `{ at_period_end: true }` to keep access until the billing
153
+ * period ends instead of canceling immediately.
154
+ */
155
+ cancel: async (options) => {
156
+ await this.del('/subscription', options?.at_period_end ? { cancel_at_period_end: true } : undefined);
157
+ // Refresh from the server — with at_period_end the subscription is
158
+ // still active, so don't assume it's gone
159
+ await this.api.refreshSubscription().catch(() => {
160
+ this.#subscription = null;
161
+ });
162
+ },
163
+ /** Refresh subscription state from the server */
164
+ refreshSubscription: async () => {
165
+ const result = await this.get('/subscription');
166
+ this.#subscription = result.subscription;
167
+ return result.subscription;
168
+ },
169
+ /** Force sync subscription state (fetches from Stripe and updates auth entitlements) */
170
+ syncSubscription: async () => {
171
+ const result = await this.post('/sync', {});
172
+ this.#subscription = result.subscription;
173
+ return result.subscription;
174
+ },
175
+ /** List invoices */
176
+ listInvoices: async (options) => {
177
+ const params = new URLSearchParams();
178
+ if (options?.limit)
179
+ params.set('limit', String(options.limit));
180
+ const qs = params.toString();
181
+ const result = await this.get(`/invoice${qs ? `?${qs}` : ''}`);
182
+ return result.invoices;
183
+ },
184
+ /** List payment methods */
185
+ listPaymentMethods: async () => {
186
+ const result = await this.get('/payment-method');
187
+ return result.payment_methods;
188
+ },
189
+ /** Create a setup session for adding a payment method */
190
+ createPaymentMethodSession: async () => {
191
+ return this.post('/payment-method', {});
192
+ },
193
+ /** Remove a payment method */
194
+ removePaymentMethod: async (id) => {
195
+ await this.del(`/payment-method/${id}`);
196
+ },
197
+ /** Set a payment method as default */
198
+ setDefaultPaymentMethod: async (id) => {
199
+ await this.patch(`/payment-method/${id}`, {});
200
+ },
201
+ /** Create a Billing Portal session */
202
+ createPortalSession: async (return_url) => {
203
+ return this.post('/portal', { return_url });
204
+ },
205
+ /** Open the Billing Portal in a new tab */
206
+ openPortal: async (return_url) => {
207
+ const { url } = await this.api.createPortalSession(return_url);
208
+ window.open(url, '_blank');
209
+ },
210
+ /** Create a Checkout session for a plan */
211
+ createCheckoutSession: async (plan_id, return_url) => {
212
+ return this.post('/checkout', { plan_id, return_url });
213
+ },
214
+ /** Fetch available plans */
215
+ listPlans: async () => {
216
+ const result = await this.get('/plan');
217
+ this.#plans = result.plans;
218
+ return result.plans;
219
+ },
220
+ };
221
+ // ── Internal fetch helpers ─────────────────────────────────────
222
+ async post(path, body) {
223
+ const res = await this.fetchFn(`${this.base_path}${path}`, {
224
+ method: 'POST',
225
+ headers: { 'Content-Type': 'application/json' },
226
+ body: JSON.stringify(body),
227
+ });
228
+ return this.handleResponse(res);
229
+ }
230
+ async get(path) {
231
+ const res = await this.fetchFn(`${this.base_path}${path}`);
232
+ return this.handleResponse(res);
233
+ }
234
+ async del(path, body) {
235
+ const res = await this.fetchFn(`${this.base_path}${path}`, {
236
+ method: 'DELETE',
237
+ ...(body !== undefined
238
+ ? {
239
+ headers: { 'Content-Type': 'application/json' },
240
+ body: JSON.stringify(body),
241
+ }
242
+ : {}),
243
+ });
244
+ return this.handleResponse(res);
245
+ }
246
+ async patch(path, body) {
247
+ const res = await this.fetchFn(`${this.base_path}${path}`, {
248
+ method: 'PATCH',
249
+ headers: { 'Content-Type': 'application/json' },
250
+ body: JSON.stringify(body),
251
+ });
252
+ return this.handleResponse(res);
253
+ }
254
+ async handleResponse(res) {
255
+ if (res.status === 204)
256
+ return undefined;
257
+ if (!res.ok)
258
+ throw await this.parseError(res);
259
+ return (await res.json());
260
+ }
261
+ async parseError(res) {
262
+ try {
263
+ const body = (await res.json());
264
+ return {
265
+ code: body.code ?? 'unknown',
266
+ message: body.message ?? 'Unknown error',
267
+ status: body.status ?? res.status,
268
+ detail: body.detail,
269
+ };
270
+ }
271
+ catch {
272
+ return {
273
+ code: 'unknown',
274
+ message: res.statusText,
275
+ status: res.status,
276
+ };
277
+ }
278
+ }
279
+ }
@@ -0,0 +1,2 @@
1
+ export { BillingClient, type BillingClientData, type BillingClientError, } from './billing.client.svelte';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACvB,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1 @@
1
+ export { BillingClient, } from './billing.client.svelte';
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './types';