@usethrottle/cart 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 ADDED
@@ -0,0 +1,118 @@
1
+ # @usethrottle/cart
2
+
3
+ Typed Node.js REST client for the Throttle Cart API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @usethrottle/cart
9
+ ```
10
+
11
+ ## Quickstart
12
+
13
+ End-to-end: create a cart, build it out, and convert it to an order.
14
+
15
+ ```ts
16
+ import { CartClient } from '@usethrottle/cart';
17
+
18
+ const client = new CartClient({ apiKey: process.env.THROTTLE_API_KEY! });
19
+
20
+ // 1. Create a cart
21
+ const cart = await client.carts.create({ storeId: 'store_abc', currency: 'USD' });
22
+
23
+ // 2. Add a line item
24
+ await client.items.add(cart.id, {
25
+ type: 'product',
26
+ name: 'Premium Widget',
27
+ unitPrice: 2999,
28
+ quantity: 2,
29
+ });
30
+
31
+ // 3. Select a shipping method
32
+ await client.shipping.select(cart.id, {
33
+ methodId: 'fedex_ground',
34
+ displayName: 'FedEx Ground',
35
+ rateAmount: 599,
36
+ });
37
+
38
+ // 4. Apply a discount code
39
+ await client.discounts.apply(cart.id, 'SAVE10');
40
+
41
+ // 5. Set tax lines
42
+ await client.taxLines.set(cart.id, [
43
+ { lineItemId: '...', jurisdictionCode: 'US-NY', taxType: 'sales', rate: 0.04, amount: 400 },
44
+ ]);
45
+
46
+ // 6. Checkout → Order
47
+ const order = await client.carts.checkout(cart.id, { paymentMethod: 'card' });
48
+ console.log(order.id, order.status);
49
+ ```
50
+
51
+ All monetary values are integers in the smallest currency unit (cents for USD).
52
+
53
+ ## Subscriptions
54
+
55
+ Pass `type: 'subscription'` and a `recurring` block on the line item:
56
+
57
+ ```ts
58
+ await client.items.add(cart.id, {
59
+ type: 'subscription',
60
+ name: 'Pro Plan',
61
+ unitPrice: 1900,
62
+ quantity: 1,
63
+ recurring: { interval: 'month', count: null }, // count: null = until cancelled
64
+ });
65
+ ```
66
+
67
+ Then call `client.carts.checkout` as normal. Set `count` to a number to cap
68
+ the billing cycles.
69
+
70
+ ## Events
71
+
72
+ Every mutation appends an immutable event to the cart's event log. Fetch it with:
73
+
74
+ ```ts
75
+ const events = await client.events.list(cart.id);
76
+ // [{ eventType: 'cart.created', sequence: 1, ... }, ...]
77
+
78
+ // Poll only new events since the last sequence you saw
79
+ const newEvents = await client.events.list(cart.id, { sinceSequence: 5, limit: 50 });
80
+ ```
81
+
82
+ For real-time delivery, subscribe to Throttle's outbound webhooks from your
83
+ merchant dashboard. Webhooks are signed with HMAC-SHA256 and fire from
84
+ Throttle's servers to your backend over HTTPS.
85
+
86
+ ## Errors
87
+
88
+ All non-2xx responses throw `ThrottleApiError`. Inspect `code` for the
89
+ machine-readable reason and `statusCode` for the HTTP status:
90
+
91
+ ```ts
92
+ import { ThrottleApiError } from '@usethrottle/cart';
93
+
94
+ try {
95
+ await client.discounts.apply(cart.id, 'EXPIRED');
96
+ } catch (e) {
97
+ if (e instanceof ThrottleApiError && e.code === 'discount_invalid') {
98
+ // surface a user-friendly message
99
+ }
100
+ if (e instanceof ThrottleApiError) {
101
+ console.error(e.statusCode, e.code, e.message);
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Client options
107
+
108
+ | Option | Default | Description |
109
+ |---|---|---|
110
+ | `apiKey` | — | **Required.** Your Throttle secret key (`sk_…`). |
111
+ | `baseUrl` | `https://throttle-api-gff1.onrender.com` | Override for local dev or staging. |
112
+ | `timeoutMs` | `30000` | Per-request abort timeout in ms. |
113
+ | `fetch` | `globalThis.fetch` | Custom fetch implementation (e.g. `node-fetch`). |
114
+
115
+ ## See also
116
+
117
+ - [API client (auto-generated): `@usethrottle/api-client`](https://www.npmjs.com/package/@usethrottle/api-client)
118
+ - [React embed components: `@usethrottle/checkout-react`](https://www.npmjs.com/package/@usethrottle/checkout-react)
package/dist/index.cjs ADDED
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CartClient: () => CartClient,
24
+ ThrottleApiError: () => ThrottleApiError
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/errors.ts
29
+ var ThrottleApiError = class extends Error {
30
+ code;
31
+ statusCode;
32
+ details;
33
+ constructor(args) {
34
+ super(args.message);
35
+ this.name = "ThrottleApiError";
36
+ this.code = args.code;
37
+ this.statusCode = args.statusCode;
38
+ this.details = args.details;
39
+ }
40
+ };
41
+
42
+ // src/client.ts
43
+ var CartClient = class {
44
+ apiKey;
45
+ baseUrl;
46
+ fetchImpl;
47
+ timeoutMs;
48
+ constructor(opts) {
49
+ if (!opts.apiKey) throw new Error("CartClient: apiKey is required");
50
+ this.apiKey = opts.apiKey;
51
+ this.baseUrl = (opts.baseUrl ?? "https://throttle-api-gff1.onrender.com").replace(/\/+$/, "");
52
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
53
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
54
+ }
55
+ async request(method, path, body) {
56
+ const url = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`;
57
+ const ctrl = new AbortController();
58
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
59
+ try {
60
+ const res = await this.fetchImpl(url, {
61
+ method,
62
+ headers: { "x-api-key": this.apiKey, "content-type": "application/json" },
63
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
64
+ signal: ctrl.signal
65
+ });
66
+ const json = await res.json().catch(() => ({}));
67
+ if (!res.ok) {
68
+ throw new ThrottleApiError({
69
+ code: json?.error?.code ?? "unknown_error",
70
+ message: json?.error?.message ?? `HTTP ${res.status}`,
71
+ statusCode: res.status,
72
+ details: json?.error?.details
73
+ });
74
+ }
75
+ return json.data;
76
+ } finally {
77
+ clearTimeout(timer);
78
+ }
79
+ }
80
+ get carts() {
81
+ return {
82
+ create: (input) => this.request("POST", "/api/v1/carts", input),
83
+ get: (id) => this.request("GET", `/api/v1/carts/${id}`),
84
+ update: (id, input) => this.request("PATCH", `/api/v1/carts/${id}`, input),
85
+ checkout: (id, input) => this.request("POST", `/api/v1/carts/${id}/checkout`, input ?? {}),
86
+ merge: (id, customerId) => this.request("POST", `/api/v1/carts/${id}/merge`, { customerId })
87
+ };
88
+ }
89
+ get items() {
90
+ return {
91
+ add: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/items`, input),
92
+ update: (cartId, itemId, input) => this.request("PATCH", `/api/v1/carts/${cartId}/items/${itemId}`, input),
93
+ remove: (cartId, itemId) => this.request("DELETE", `/api/v1/carts/${cartId}/items/${itemId}`)
94
+ };
95
+ }
96
+ get shipping() {
97
+ return {
98
+ select: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/shipping`, input),
99
+ clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/shipping`)
100
+ };
101
+ }
102
+ get discounts() {
103
+ return {
104
+ apply: (cartId, code) => this.request("POST", `/api/v1/carts/${cartId}/apply-discount`, { code }),
105
+ remove: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/discount`)
106
+ };
107
+ }
108
+ get taxLines() {
109
+ return {
110
+ set: (cartId, lines) => this.request("PUT", `/api/v1/carts/${cartId}/tax-lines`, { lines }),
111
+ clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/tax-lines`)
112
+ };
113
+ }
114
+ get events() {
115
+ return {
116
+ list: (cartId, opts) => {
117
+ const params = new URLSearchParams();
118
+ if (opts?.sinceSequence !== void 0) params.set("sinceSequence", String(opts.sinceSequence));
119
+ if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
120
+ const qs = params.toString();
121
+ return this.request("GET", `/api/v1/carts/${cartId}/events${qs ? "?" + qs : ""}`);
122
+ }
123
+ };
124
+ }
125
+ };
126
+ // Annotate the CommonJS export names for ESM import in node:
127
+ 0 && (module.exports = {
128
+ CartClient,
129
+ ThrottleApiError
130
+ });
@@ -0,0 +1,214 @@
1
+ interface Cart {
2
+ id: string;
3
+ merchantId: string;
4
+ storeId: string | null;
5
+ customerId: string | null;
6
+ status: 'open' | 'checkout' | 'converted' | 'abandoned';
7
+ currency: string;
8
+ subtotal: number;
9
+ taxTotal: number;
10
+ discountTotal: number;
11
+ shippingTotal: number;
12
+ total: number;
13
+ billingAddress: Record<string, unknown> | null;
14
+ shippingAddress: Record<string, unknown> | null;
15
+ notes: string | null;
16
+ expiresAt: string | null;
17
+ externalRef?: {
18
+ connectionId: string;
19
+ externalCartId: string;
20
+ externalMetadata: Record<string, unknown>;
21
+ };
22
+ selectedShipping?: SelectedShipping | null;
23
+ appliedDiscount?: AppliedDiscount | null;
24
+ taxLines?: TaxLine[];
25
+ lineItems?: LineItem[];
26
+ createdAt: string;
27
+ updatedAt: string;
28
+ }
29
+ interface LineItem {
30
+ id: string;
31
+ cartId: string;
32
+ type: 'product' | 'subscription' | 'service' | 'ticket' | 'donation' | 'custom';
33
+ referenceId: string | null;
34
+ name: string;
35
+ description: string | null;
36
+ unitPrice: number;
37
+ quantity: number;
38
+ subtotal: number;
39
+ taxAmount: number;
40
+ discountAmount: number;
41
+ total: number;
42
+ recurring: {
43
+ interval: string;
44
+ count: number | null;
45
+ } | null;
46
+ imageUrl: string | null;
47
+ position: number;
48
+ metadata: Record<string, unknown>;
49
+ }
50
+ interface SelectedShipping {
51
+ methodId: string;
52
+ carrier: string | null;
53
+ serviceCode: string | null;
54
+ displayName: string;
55
+ rateAmount: number;
56
+ currency: string;
57
+ estimatedDeliveryDays: number | null;
58
+ }
59
+ interface AppliedDiscount {
60
+ code: string;
61
+ type: 'percentage' | 'fixed_amount' | 'free_shipping';
62
+ amount: number;
63
+ currency: string;
64
+ freeShipping: boolean;
65
+ validUntil: string | null;
66
+ snapshotHash: string;
67
+ }
68
+ interface TaxLine {
69
+ id: string;
70
+ lineItemId: string;
71
+ jurisdictionCode: string;
72
+ jurisdictionName: string | null;
73
+ taxType: 'sales' | 'vat' | 'gst' | 'pst' | 'hst' | 'service' | 'excise' | 'other';
74
+ rate: number;
75
+ amount: number;
76
+ currency: string;
77
+ }
78
+ interface CartEvent {
79
+ id: string;
80
+ cartId: string;
81
+ eventType: string;
82
+ sequence: number;
83
+ payload: Record<string, unknown>;
84
+ actorType: 'system' | 'merchant' | 'buyer' | 'connector';
85
+ actorId: string | null;
86
+ createdAt: string;
87
+ }
88
+ interface CreateCartInput {
89
+ storeId: string;
90
+ customerId?: string;
91
+ currency?: string;
92
+ metadata?: Record<string, unknown>;
93
+ }
94
+ interface UpdateCartInput {
95
+ customerId?: string;
96
+ billingAddress?: Record<string, unknown>;
97
+ shippingAddress?: Record<string, unknown>;
98
+ notes?: string;
99
+ metadata?: Record<string, unknown>;
100
+ }
101
+ interface AddLineItemInput {
102
+ type?: LineItem['type'];
103
+ referenceId?: string;
104
+ name: string;
105
+ description?: string;
106
+ unitPrice: number;
107
+ quantity?: number;
108
+ taxAmount?: number;
109
+ discountAmount?: number;
110
+ recurring?: {
111
+ interval: string;
112
+ count: number | null;
113
+ } | null;
114
+ imageUrl?: string;
115
+ metadata?: Record<string, unknown>;
116
+ }
117
+ interface UpdateLineItemInput {
118
+ quantity?: number;
119
+ unitPrice?: number;
120
+ taxAmount?: number;
121
+ discountAmount?: number;
122
+ metadata?: Record<string, unknown>;
123
+ }
124
+ interface SelectShippingInput {
125
+ methodId: string;
126
+ displayName: string;
127
+ rateAmount: number;
128
+ currency?: string;
129
+ carrier?: string;
130
+ serviceCode?: string;
131
+ estimatedDeliveryDays?: number;
132
+ }
133
+ interface TaxLineInput {
134
+ lineItemId: string;
135
+ jurisdictionCode: string;
136
+ jurisdictionName?: string;
137
+ taxType: TaxLine['taxType'];
138
+ rate: number;
139
+ amount: number;
140
+ currency?: string;
141
+ }
142
+ interface CheckoutInput {
143
+ paymentMethod?: string;
144
+ netDays?: number;
145
+ termsAccepted?: boolean;
146
+ termsSnapshot?: string;
147
+ invoiceNumber?: string;
148
+ metadata?: Record<string, unknown>;
149
+ }
150
+ interface Order {
151
+ id: string;
152
+ status: string;
153
+ total: number;
154
+ currency: string;
155
+ }
156
+
157
+ interface CartClientOptions {
158
+ apiKey: string;
159
+ baseUrl?: string;
160
+ fetch?: typeof globalThis.fetch;
161
+ timeoutMs?: number;
162
+ }
163
+ declare class CartClient {
164
+ private readonly apiKey;
165
+ private readonly baseUrl;
166
+ private readonly fetchImpl;
167
+ private readonly timeoutMs;
168
+ constructor(opts: CartClientOptions);
169
+ request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
170
+ get carts(): {
171
+ create: (input: CreateCartInput) => Promise<Cart>;
172
+ get: (id: string) => Promise<Cart>;
173
+ update: (id: string, input: UpdateCartInput) => Promise<Cart>;
174
+ checkout: (id: string, input?: CheckoutInput) => Promise<Order>;
175
+ merge: (id: string, customerId: string) => Promise<Cart>;
176
+ };
177
+ get items(): {
178
+ add: (cartId: string, input: AddLineItemInput) => Promise<LineItem>;
179
+ update: (cartId: string, itemId: string, input: UpdateLineItemInput) => Promise<LineItem>;
180
+ remove: (cartId: string, itemId: string) => Promise<void>;
181
+ };
182
+ get shipping(): {
183
+ select: (cartId: string, input: SelectShippingInput) => Promise<Cart>;
184
+ clear: (cartId: string) => Promise<Cart>;
185
+ };
186
+ get discounts(): {
187
+ apply: (cartId: string, code: string) => Promise<Cart>;
188
+ remove: (cartId: string) => Promise<Cart>;
189
+ };
190
+ get taxLines(): {
191
+ set: (cartId: string, lines: TaxLineInput[]) => Promise<Cart>;
192
+ clear: (cartId: string) => Promise<Cart>;
193
+ };
194
+ get events(): {
195
+ list: (cartId: string, opts?: {
196
+ sinceSequence?: number;
197
+ limit?: number;
198
+ }) => Promise<CartEvent[]>;
199
+ };
200
+ }
201
+
202
+ declare class ThrottleApiError extends Error {
203
+ readonly code: string;
204
+ readonly statusCode: number;
205
+ readonly details?: Record<string, unknown>;
206
+ constructor(args: {
207
+ code: string;
208
+ message: string;
209
+ statusCode: number;
210
+ details?: Record<string, unknown>;
211
+ });
212
+ }
213
+
214
+ export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CheckoutInput, type CreateCartInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
@@ -0,0 +1,214 @@
1
+ interface Cart {
2
+ id: string;
3
+ merchantId: string;
4
+ storeId: string | null;
5
+ customerId: string | null;
6
+ status: 'open' | 'checkout' | 'converted' | 'abandoned';
7
+ currency: string;
8
+ subtotal: number;
9
+ taxTotal: number;
10
+ discountTotal: number;
11
+ shippingTotal: number;
12
+ total: number;
13
+ billingAddress: Record<string, unknown> | null;
14
+ shippingAddress: Record<string, unknown> | null;
15
+ notes: string | null;
16
+ expiresAt: string | null;
17
+ externalRef?: {
18
+ connectionId: string;
19
+ externalCartId: string;
20
+ externalMetadata: Record<string, unknown>;
21
+ };
22
+ selectedShipping?: SelectedShipping | null;
23
+ appliedDiscount?: AppliedDiscount | null;
24
+ taxLines?: TaxLine[];
25
+ lineItems?: LineItem[];
26
+ createdAt: string;
27
+ updatedAt: string;
28
+ }
29
+ interface LineItem {
30
+ id: string;
31
+ cartId: string;
32
+ type: 'product' | 'subscription' | 'service' | 'ticket' | 'donation' | 'custom';
33
+ referenceId: string | null;
34
+ name: string;
35
+ description: string | null;
36
+ unitPrice: number;
37
+ quantity: number;
38
+ subtotal: number;
39
+ taxAmount: number;
40
+ discountAmount: number;
41
+ total: number;
42
+ recurring: {
43
+ interval: string;
44
+ count: number | null;
45
+ } | null;
46
+ imageUrl: string | null;
47
+ position: number;
48
+ metadata: Record<string, unknown>;
49
+ }
50
+ interface SelectedShipping {
51
+ methodId: string;
52
+ carrier: string | null;
53
+ serviceCode: string | null;
54
+ displayName: string;
55
+ rateAmount: number;
56
+ currency: string;
57
+ estimatedDeliveryDays: number | null;
58
+ }
59
+ interface AppliedDiscount {
60
+ code: string;
61
+ type: 'percentage' | 'fixed_amount' | 'free_shipping';
62
+ amount: number;
63
+ currency: string;
64
+ freeShipping: boolean;
65
+ validUntil: string | null;
66
+ snapshotHash: string;
67
+ }
68
+ interface TaxLine {
69
+ id: string;
70
+ lineItemId: string;
71
+ jurisdictionCode: string;
72
+ jurisdictionName: string | null;
73
+ taxType: 'sales' | 'vat' | 'gst' | 'pst' | 'hst' | 'service' | 'excise' | 'other';
74
+ rate: number;
75
+ amount: number;
76
+ currency: string;
77
+ }
78
+ interface CartEvent {
79
+ id: string;
80
+ cartId: string;
81
+ eventType: string;
82
+ sequence: number;
83
+ payload: Record<string, unknown>;
84
+ actorType: 'system' | 'merchant' | 'buyer' | 'connector';
85
+ actorId: string | null;
86
+ createdAt: string;
87
+ }
88
+ interface CreateCartInput {
89
+ storeId: string;
90
+ customerId?: string;
91
+ currency?: string;
92
+ metadata?: Record<string, unknown>;
93
+ }
94
+ interface UpdateCartInput {
95
+ customerId?: string;
96
+ billingAddress?: Record<string, unknown>;
97
+ shippingAddress?: Record<string, unknown>;
98
+ notes?: string;
99
+ metadata?: Record<string, unknown>;
100
+ }
101
+ interface AddLineItemInput {
102
+ type?: LineItem['type'];
103
+ referenceId?: string;
104
+ name: string;
105
+ description?: string;
106
+ unitPrice: number;
107
+ quantity?: number;
108
+ taxAmount?: number;
109
+ discountAmount?: number;
110
+ recurring?: {
111
+ interval: string;
112
+ count: number | null;
113
+ } | null;
114
+ imageUrl?: string;
115
+ metadata?: Record<string, unknown>;
116
+ }
117
+ interface UpdateLineItemInput {
118
+ quantity?: number;
119
+ unitPrice?: number;
120
+ taxAmount?: number;
121
+ discountAmount?: number;
122
+ metadata?: Record<string, unknown>;
123
+ }
124
+ interface SelectShippingInput {
125
+ methodId: string;
126
+ displayName: string;
127
+ rateAmount: number;
128
+ currency?: string;
129
+ carrier?: string;
130
+ serviceCode?: string;
131
+ estimatedDeliveryDays?: number;
132
+ }
133
+ interface TaxLineInput {
134
+ lineItemId: string;
135
+ jurisdictionCode: string;
136
+ jurisdictionName?: string;
137
+ taxType: TaxLine['taxType'];
138
+ rate: number;
139
+ amount: number;
140
+ currency?: string;
141
+ }
142
+ interface CheckoutInput {
143
+ paymentMethod?: string;
144
+ netDays?: number;
145
+ termsAccepted?: boolean;
146
+ termsSnapshot?: string;
147
+ invoiceNumber?: string;
148
+ metadata?: Record<string, unknown>;
149
+ }
150
+ interface Order {
151
+ id: string;
152
+ status: string;
153
+ total: number;
154
+ currency: string;
155
+ }
156
+
157
+ interface CartClientOptions {
158
+ apiKey: string;
159
+ baseUrl?: string;
160
+ fetch?: typeof globalThis.fetch;
161
+ timeoutMs?: number;
162
+ }
163
+ declare class CartClient {
164
+ private readonly apiKey;
165
+ private readonly baseUrl;
166
+ private readonly fetchImpl;
167
+ private readonly timeoutMs;
168
+ constructor(opts: CartClientOptions);
169
+ request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
170
+ get carts(): {
171
+ create: (input: CreateCartInput) => Promise<Cart>;
172
+ get: (id: string) => Promise<Cart>;
173
+ update: (id: string, input: UpdateCartInput) => Promise<Cart>;
174
+ checkout: (id: string, input?: CheckoutInput) => Promise<Order>;
175
+ merge: (id: string, customerId: string) => Promise<Cart>;
176
+ };
177
+ get items(): {
178
+ add: (cartId: string, input: AddLineItemInput) => Promise<LineItem>;
179
+ update: (cartId: string, itemId: string, input: UpdateLineItemInput) => Promise<LineItem>;
180
+ remove: (cartId: string, itemId: string) => Promise<void>;
181
+ };
182
+ get shipping(): {
183
+ select: (cartId: string, input: SelectShippingInput) => Promise<Cart>;
184
+ clear: (cartId: string) => Promise<Cart>;
185
+ };
186
+ get discounts(): {
187
+ apply: (cartId: string, code: string) => Promise<Cart>;
188
+ remove: (cartId: string) => Promise<Cart>;
189
+ };
190
+ get taxLines(): {
191
+ set: (cartId: string, lines: TaxLineInput[]) => Promise<Cart>;
192
+ clear: (cartId: string) => Promise<Cart>;
193
+ };
194
+ get events(): {
195
+ list: (cartId: string, opts?: {
196
+ sinceSequence?: number;
197
+ limit?: number;
198
+ }) => Promise<CartEvent[]>;
199
+ };
200
+ }
201
+
202
+ declare class ThrottleApiError extends Error {
203
+ readonly code: string;
204
+ readonly statusCode: number;
205
+ readonly details?: Record<string, unknown>;
206
+ constructor(args: {
207
+ code: string;
208
+ message: string;
209
+ statusCode: number;
210
+ details?: Record<string, unknown>;
211
+ });
212
+ }
213
+
214
+ export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CheckoutInput, type CreateCartInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
package/dist/index.js ADDED
@@ -0,0 +1,102 @@
1
+ // src/errors.ts
2
+ var ThrottleApiError = class extends Error {
3
+ code;
4
+ statusCode;
5
+ details;
6
+ constructor(args) {
7
+ super(args.message);
8
+ this.name = "ThrottleApiError";
9
+ this.code = args.code;
10
+ this.statusCode = args.statusCode;
11
+ this.details = args.details;
12
+ }
13
+ };
14
+
15
+ // src/client.ts
16
+ var CartClient = class {
17
+ apiKey;
18
+ baseUrl;
19
+ fetchImpl;
20
+ timeoutMs;
21
+ constructor(opts) {
22
+ if (!opts.apiKey) throw new Error("CartClient: apiKey is required");
23
+ this.apiKey = opts.apiKey;
24
+ this.baseUrl = (opts.baseUrl ?? "https://throttle-api-gff1.onrender.com").replace(/\/+$/, "");
25
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
26
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
27
+ }
28
+ async request(method, path, body) {
29
+ const url = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`;
30
+ const ctrl = new AbortController();
31
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
32
+ try {
33
+ const res = await this.fetchImpl(url, {
34
+ method,
35
+ headers: { "x-api-key": this.apiKey, "content-type": "application/json" },
36
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
37
+ signal: ctrl.signal
38
+ });
39
+ const json = await res.json().catch(() => ({}));
40
+ if (!res.ok) {
41
+ throw new ThrottleApiError({
42
+ code: json?.error?.code ?? "unknown_error",
43
+ message: json?.error?.message ?? `HTTP ${res.status}`,
44
+ statusCode: res.status,
45
+ details: json?.error?.details
46
+ });
47
+ }
48
+ return json.data;
49
+ } finally {
50
+ clearTimeout(timer);
51
+ }
52
+ }
53
+ get carts() {
54
+ return {
55
+ create: (input) => this.request("POST", "/api/v1/carts", input),
56
+ get: (id) => this.request("GET", `/api/v1/carts/${id}`),
57
+ update: (id, input) => this.request("PATCH", `/api/v1/carts/${id}`, input),
58
+ checkout: (id, input) => this.request("POST", `/api/v1/carts/${id}/checkout`, input ?? {}),
59
+ merge: (id, customerId) => this.request("POST", `/api/v1/carts/${id}/merge`, { customerId })
60
+ };
61
+ }
62
+ get items() {
63
+ return {
64
+ add: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/items`, input),
65
+ update: (cartId, itemId, input) => this.request("PATCH", `/api/v1/carts/${cartId}/items/${itemId}`, input),
66
+ remove: (cartId, itemId) => this.request("DELETE", `/api/v1/carts/${cartId}/items/${itemId}`)
67
+ };
68
+ }
69
+ get shipping() {
70
+ return {
71
+ select: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/shipping`, input),
72
+ clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/shipping`)
73
+ };
74
+ }
75
+ get discounts() {
76
+ return {
77
+ apply: (cartId, code) => this.request("POST", `/api/v1/carts/${cartId}/apply-discount`, { code }),
78
+ remove: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/discount`)
79
+ };
80
+ }
81
+ get taxLines() {
82
+ return {
83
+ set: (cartId, lines) => this.request("PUT", `/api/v1/carts/${cartId}/tax-lines`, { lines }),
84
+ clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/tax-lines`)
85
+ };
86
+ }
87
+ get events() {
88
+ return {
89
+ list: (cartId, opts) => {
90
+ const params = new URLSearchParams();
91
+ if (opts?.sinceSequence !== void 0) params.set("sinceSequence", String(opts.sinceSequence));
92
+ if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
93
+ const qs = params.toString();
94
+ return this.request("GET", `/api/v1/carts/${cartId}/events${qs ? "?" + qs : ""}`);
95
+ }
96
+ };
97
+ }
98
+ };
99
+ export {
100
+ CartClient,
101
+ ThrottleApiError
102
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@usethrottle/cart",
3
+ "version": "0.1.0",
4
+ "description": "Typed REST client for the Throttle Cart API.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": ["dist", "README.md"],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "test": "vitest run",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^8.0.0",
24
+ "vitest": "^2.1.9",
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }