@teralabs/shipstack 1.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/docs/errors.md ADDED
@@ -0,0 +1,150 @@
1
+ # Error Handling in Shipstack
2
+
3
+ Shipstack provides a unified, predictable error-handling system across USPS, FedEx, and UPS.
4
+ All Shipstack-specific errors extend from `ShipstackError`.
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ Shipstack errors fall into two main categories:
11
+
12
+ 1. **ShipstackError** for normalized library failures
13
+ 2. **ThrottlingError** when a carrier enforces rate limits
14
+
15
+ Carrier-specific details are preserved on the error's `cause` property when available.
16
+
17
+ ---
18
+
19
+ ## Base Error: `ShipstackError`
20
+
21
+ ```ts
22
+ class ShipstackError extends Error {
23
+ carrier: "usps" | "fedex" | "ups";
24
+ cause?: unknown;
25
+ }
26
+ ```
27
+
28
+ ### Fields
29
+
30
+ | Field | Description |
31
+ |-------|-------------|
32
+ | `message` | Human-readable error message |
33
+ | `carrier` | The carrier associated with the failure |
34
+ | `cause` | Raw upstream error, response, or context when available |
35
+
36
+ ---
37
+
38
+ ## Example: Catching Errors
39
+
40
+ ```ts
41
+ try {
42
+ await client.getRates(request);
43
+ } catch (err) {
44
+ if (err instanceof ShipstackError) {
45
+ console.error("Carrier:", err.carrier);
46
+ console.error("Message:", err.message);
47
+ console.error("Cause:", err.cause);
48
+ }
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## `ThrottlingError`
55
+
56
+ UPS and FedEx may enforce rate limits. Shipstack throws a `ThrottlingError` when this happens.
57
+
58
+ ```ts
59
+ class ThrottlingError extends ShipstackError {
60
+ retryAfter?: number;
61
+ }
62
+ ```
63
+
64
+ ### When It Occurs
65
+
66
+ - Too many UPS tracking requests in a short period
67
+ - FedEx rate-limit enforcement
68
+ - Carrier-side retry-after signals
69
+
70
+ ### Example
71
+
72
+ ```ts
73
+ try {
74
+ await trackShipment(numbers, "ups", config);
75
+ } catch (err) {
76
+ if (err instanceof ThrottlingError) {
77
+ console.log("Carrier is rate limiting. Retry after:", err.retryAfter);
78
+ }
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Error Behavior by Workflow
85
+
86
+ ### Rates
87
+
88
+ - Missing or invalid ZIP codes
89
+ - Unsupported service combinations
90
+ - Carrier downtime
91
+
92
+ ### Tracking
93
+
94
+ - Invalid tracking numbers
95
+ - Carrier throttling
96
+ - Upstream authentication or network failures
97
+
98
+ ### Address Validation
99
+
100
+ - Missing required fields
101
+ - Invalid postal codes
102
+ - Ambiguous addresses
103
+
104
+ ### Shipments
105
+
106
+ #### Staged Shipments
107
+ Errors occur only if request data is incomplete or invalid.
108
+
109
+ #### Actual Shipments
110
+ Errors may occur due to authentication failures, invalid shipment payloads, carrier downtime, or billing issues.
111
+
112
+ ---
113
+
114
+ ## Best Practices
115
+
116
+ ### Always Catch `ShipstackError`
117
+
118
+ ```ts
119
+ try {
120
+ await createShipment(request, config);
121
+ } catch (err) {
122
+ if (err instanceof ShipstackError) {
123
+ // handle gracefully
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### Log `carrier` and `cause`
129
+
130
+ These are the most useful fields for debugging carrier-specific failures.
131
+
132
+ ### Do Not Expose Raw Upstream Errors to End Users
133
+ Prefer logging `cause` internally and returning a safe application-level message to the frontend.
134
+
135
+ Use `message` for user‑facing output.
136
+ Use `cause` for internal logging.
137
+
138
+ ---
139
+
140
+ ## Summary
141
+
142
+ Shipstack’s error system provides:
143
+
144
+ - A unified error interface
145
+ - Normalized carrier errors
146
+ - Specialized throttling detection
147
+ - Consistent behavior across all workflows
148
+ - Strong typing for safe error handling
149
+
150
+ ---
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Actual Shipment Example
3
+ *
4
+ * Demonstrates how to use Shipstack's advanced shipment workflow.
5
+ * This calls the carrier API and PURCHASES a real shipping label.
6
+ *
7
+ * IMPORTANT:
8
+ * - Only run this in a secure backend environment.
9
+ * - This will generate a real tracking number and label.
10
+ */
11
+
12
+ import { createShipment } from "@teralabs/shipstack";
13
+ import { config } from "../config.example";
14
+
15
+ async function exampleActualShipment() {
16
+ const shipment = await createShipment(
17
+ {
18
+ carrier: "ups",
19
+ serviceCode: "UPS_GROUND",
20
+
21
+ fromAddress: {
22
+ name: "Sender Name",
23
+ streetLines: ["123 Warehouse Rd"],
24
+ city: "Los Angeles",
25
+ stateOrProvinceCode: "CA",
26
+ postalCode: "90001",
27
+ countryCode: "US"
28
+ },
29
+
30
+ toAddress: {
31
+ name: "Customer Name",
32
+ streetLines: ["55 W 46th St"],
33
+ city: "New York",
34
+ stateOrProvinceCode: "NY",
35
+ postalCode: "10036",
36
+ countryCode: "US"
37
+ },
38
+
39
+ package: {
40
+ weightOz: 48,
41
+ lengthInches: 14,
42
+ widthInches: 10,
43
+ heightInches: 6
44
+ }
45
+ },
46
+ config
47
+ );
48
+
49
+ console.log("Carrier:", shipment.carrier);
50
+ console.log("Tracking Number:", shipment.trackingNumber);
51
+ console.log("Service Code:", shipment.serviceCode);
52
+ console.log("Charges:", shipment.charges);
53
+
54
+ // Label is returned as base64
55
+ console.log("Label Format:", shipment.label.format);
56
+ console.log("Label Base64 (first 100 chars):", shipment.label.base64.slice(0, 100));
57
+ }
58
+
59
+ exampleActualShipment();
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Basic Shipstack Examples
3
+ *
4
+ * Demonstrates:
5
+ * - Fetching rates
6
+ * - Ranking rates across carriers
7
+ * - Tracking shipments
8
+ * - Validating addresses
9
+ * - Using ShippingClient, ShippingManager, and the functional API
10
+ */
11
+
12
+ import {
13
+ ShippingClient,
14
+ ShippingManager,
15
+ getRates,
16
+ trackShipment,
17
+ validateAddress
18
+ } from "@teralabs/shipstack";
19
+
20
+ import { config } from "../config.example";
21
+
22
+ // ---------------------------------------------
23
+ // Stateful API (ShippingClient + ShippingManager)
24
+ // ---------------------------------------------
25
+
26
+ const client = new ShippingClient(config);
27
+ const manager = new ShippingManager(config);
28
+
29
+ async function exampleStateful() {
30
+ // 1. Get Rates
31
+ const rates = await client.getRates({
32
+ carrier: "usps",
33
+ originZip: "90210",
34
+ destZip: "10001",
35
+ weightOz: 16,
36
+ lengthInches: 10,
37
+ widthInches: 5,
38
+ heightInches: 5
39
+ });
40
+
41
+ console.log("USPS Rates:", rates);
42
+
43
+ // 2. Rank Rates Across Carriers
44
+ const rankedRates = await manager.getRankedRates(
45
+ {
46
+ originZip: "90210",
47
+ destZip: "10001",
48
+ weightOz: 16,
49
+ lengthInches: 10,
50
+ widthInches: 5,
51
+ heightInches: 5
52
+ },
53
+ ["usps", "fedex", "ups"]
54
+ );
55
+
56
+ console.log("Ranked Rates:", rankedRates);
57
+
58
+ // 3. Track a Shipment
59
+ const tracking = await client.track(
60
+ ["9400100000000000000000"],
61
+ "usps"
62
+ );
63
+
64
+ console.log("Tracking:", tracking);
65
+
66
+ // 4. Validate an Address
67
+ const address = await client.validateAddress({
68
+ carrier: "fedex",
69
+ address: {
70
+ streetLines: ["123 Main St"],
71
+ city: "New York",
72
+ stateOrProvinceCode: "NY",
73
+ postalCode: "10001",
74
+ countryCode: "US"
75
+ }
76
+ });
77
+
78
+ console.log("Address Validation:", address);
79
+ }
80
+
81
+ // ---------------------------------------------
82
+ // Functional API
83
+ // ---------------------------------------------
84
+
85
+ async function exampleFunctional() {
86
+ // 1. Get Rates
87
+ const rates = await getRates(
88
+ {
89
+ carrier: "ups",
90
+ originZip: "94103",
91
+ destZip: "10001",
92
+ weightOz: 32,
93
+ lengthInches: 12,
94
+ widthInches: 8,
95
+ heightInches: 6
96
+ },
97
+ config
98
+ );
99
+
100
+ console.log("UPS Rates:", rates);
101
+
102
+ // 2. Track a Shipment
103
+ const tracking = await trackShipment(
104
+ ["1Z9999999999999999"],
105
+ "ups",
106
+ config
107
+ );
108
+
109
+ console.log("Tracking:", tracking);
110
+
111
+ // 3. Validate an Address
112
+ const address = await validateAddress(
113
+ {
114
+ carrier: "usps",
115
+ address: {
116
+ streetLines: ["1600 Amphitheatre Pkwy"],
117
+ city: "Mountain View",
118
+ stateOrProvinceCode: "CA",
119
+ postalCode: "94043",
120
+ countryCode: "US"
121
+ }
122
+ },
123
+ config
124
+ );
125
+
126
+ console.log("Address Validation:", address);
127
+ }
128
+
129
+ // Run examples
130
+ exampleStateful();
131
+ exampleFunctional();
@@ -0,0 +1,22 @@
1
+ import { getRates, type RateRequest } from "@teralabs/shipstack";
2
+
3
+ import { config } from "../config.example";
4
+
5
+ export async function POST(request: Request) {
6
+ const body = (await request.json()) as Partial<RateRequest>;
7
+
8
+ const rateRequest: RateRequest = {
9
+ carrier: body.carrier ?? "usps",
10
+ originZip: body.originZip ?? "90210",
11
+ destZip: body.destZip ?? "10001",
12
+ weightOz: body.weightOz ?? 16,
13
+ lengthInches: body.lengthInches ?? 10,
14
+ widthInches: body.widthInches ?? 5,
15
+ heightInches: body.heightInches ?? 5,
16
+ destCountryCode: body.destCountryCode
17
+ };
18
+
19
+ const rates = await getRates(rateRequest, config);
20
+
21
+ return Response.json({ rates });
22
+ }
@@ -0,0 +1,46 @@
1
+ # Staged Shipment Example
2
+
3
+ This example demonstrates Shipstack's safe shipment builder. It generates a carrier-specific payload without purchasing a label.
4
+
5
+ ```ts
6
+ import { buildShipment } from "@teralabs/shipstack";
7
+ import { config } from "../config.example";
8
+
9
+ async function exampleStagedShipment() {
10
+ const staged = await buildShipment(
11
+ {
12
+ carrier: "fedex",
13
+ serviceCode: "FEDEX_GROUND",
14
+ fromAddress: {
15
+ name: "Sender Name",
16
+ streetLines: ["123 Warehouse Rd"],
17
+ city: "Los Angeles",
18
+ stateOrProvinceCode: "CA",
19
+ postalCode: "90001",
20
+ countryCode: "US"
21
+ },
22
+ toAddress: {
23
+ name: "Customer Name",
24
+ streetLines: ["55 W 46th St"],
25
+ city: "New York",
26
+ stateOrProvinceCode: "NY",
27
+ postalCode: "10036",
28
+ countryCode: "US"
29
+ },
30
+ package: {
31
+ weightOz: 32,
32
+ lengthInches: 12,
33
+ widthInches: 8,
34
+ heightInches: 4
35
+ }
36
+ },
37
+ config
38
+ );
39
+
40
+ console.log("Carrier:", staged.carrier);
41
+ console.log("Service Code:", staged.serviceCode);
42
+ console.log("Payload:", staged.payload);
43
+ }
44
+
45
+ exampleStagedShipment();
46
+ ```
package/docs/index.md ADDED
@@ -0,0 +1,286 @@
1
+ # Shipstack Documentation
2
+
3
+ Welcome to Shipstack. This documentation will help you get started, integrate, and extend Shipstack in any JavaScript or TypeScript environment.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+ - [What is Shipstack?](#what-is-shipstack)
9
+ - [Features](#features)
10
+ - [Installation](#installation)
11
+ - [Quick Start](#quick-start)
12
+ - [Configuration](#configuration)
13
+ - [Usage Examples](#usage-examples)
14
+ - [Advanced Workflows](#advanced-workflows)
15
+ - [Error Handling](#error-handling)
16
+ - [API Reference](#api-reference)
17
+ - [Testing](#testing)
18
+ - [Contributing](#contributing)
19
+ - [Changelog](#changelog)
20
+ - [License](#license)
21
+
22
+ ---
23
+
24
+ ## What is Shipstack?
25
+
26
+ Shipstack is a high‑performance, type‑safe, and framework‑agnostic shipping SDK for orchestrating logistics across USPS, FedEx, and UPS.
27
+ It provides a unified API for:
28
+
29
+ - Address validation
30
+ - Rate comparison
31
+ - Tracking
32
+ - Staged shipment generation
33
+ - Actual label creation (backend‑only)
34
+
35
+ Shipstack works in Node.js, Deno, Bun, Cloudflare Workers, and any modern JavaScript runtime.
36
+
37
+ ---
38
+
39
+ ## Features
40
+
41
+ - Unified orchestration for USPS, FedEx, and UPS
42
+ - Standardized data models for rates, tracking, and addresses
43
+ - Batch tracking with automatic carrier‑specific chunking
44
+ - Staged shipment system for safe, frontend‑compatible workflows
45
+ - Actual shipment creation for backend automation
46
+ - Auto‑generated OpenAPI clients for accuracy and compliance
47
+ - Fully framework‑agnostic and open source
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ npm install @teralabs/shipstack
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Quick Start
60
+
61
+ ```ts
62
+ import { ShippingClient, ShippingManager } from "@teralabs/shipstack";
63
+
64
+ const config = {
65
+ environment: "sandbox",
66
+ usps: {
67
+ enabled: true,
68
+ clientId: "YOUR_USPS_CLIENT_ID",
69
+ clientSecret: "YOUR_USPS_CLIENT_SECRET",
70
+ baseUrl: "https://sandbox.api.usps.com"
71
+ },
72
+ fedex: {
73
+ enabled: true,
74
+ clientId: "YOUR_FEDEX_CLIENT_ID",
75
+ clientSecret: "YOUR_FEDEX_CLIENT_SECRET",
76
+ accountNumber: "YOUR_FEDEX_ACCOUNT_NUMBER"
77
+ },
78
+ ups: {
79
+ enabled: true,
80
+ clientId: "YOUR_UPS_CLIENT_ID",
81
+ clientSecret: "YOUR_UPS_CLIENT_SECRET",
82
+ accountNumber: "YOUR_UPS_ACCOUNT_NUMBER"
83
+ }
84
+ };
85
+
86
+ const client = new ShippingClient(config);
87
+ const manager = new ShippingManager(config);
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Configuration
93
+
94
+ Shipstack does not rely on environment variables.
95
+ All configuration is passed as plain objects.
96
+
97
+ See `docs/config.example.ts` for a complete template.
98
+
99
+ ---
100
+
101
+ ## Usage Examples
102
+
103
+ ### Get Rates
104
+
105
+ ```ts
106
+ const rates = await client.getRates({
107
+ carrier: "usps",
108
+ originZip: "90210",
109
+ destZip: "10001",
110
+ weightOz: 16,
111
+ lengthInches: 10,
112
+ widthInches: 5,
113
+ heightInches: 5
114
+ });
115
+ ```
116
+
117
+ ### Rank Rates Across Carriers
118
+
119
+ ```ts
120
+ const rankedRates = await manager.getRankedRates(
121
+ {
122
+ originZip: "90210",
123
+ destZip: "10001",
124
+ weightOz: 16,
125
+ lengthInches: 10,
126
+ widthInches: 5,
127
+ heightInches: 5
128
+ },
129
+ ["usps", "fedex", "ups"]
130
+ );
131
+ ```
132
+
133
+ ### Track Shipments
134
+
135
+ ```ts
136
+ const tracking = await client.track(["9400100000000000000000"], "usps");
137
+ ```
138
+
139
+ ### Validate Address
140
+
141
+ ```ts
142
+ const result = await client.validateAddress({
143
+ carrier: "fedex",
144
+ address: {
145
+ streetLines: ["123 Main St"],
146
+ city: "New York",
147
+ stateOrProvinceCode: "NY",
148
+ postalCode: "10001",
149
+ countryCode: "US"
150
+ }
151
+ });
152
+ ```
153
+
154
+ ### Direct Carrier Access
155
+
156
+ ```ts
157
+ import { createUspsRatesClient } from "@teralabs/shipstack";
158
+
159
+ const usps = createUspsRatesClient(config.usps);
160
+ const raw = await usps.getRates({ ... });
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Advanced Workflows
166
+
167
+ ### Functional API
168
+
169
+ ```ts
170
+ import { getRates, validateAddress, trackShipment } from "@teralabs/shipstack";
171
+
172
+ const rates = await getRates(
173
+ {
174
+ carrier: "usps",
175
+ originZip: "90210",
176
+ destZip: "10001",
177
+ weightOz: 16,
178
+ lengthInches: 10,
179
+ widthInches: 5,
180
+ heightInches: 5
181
+ },
182
+ config
183
+ );
184
+ ```
185
+
186
+ ### ShippingManager
187
+
188
+ `ShippingManager` is the class-only helper for ranked multi-carrier checkout workflows.
189
+
190
+ ```ts
191
+ const ranked = await manager.getRankedRates(
192
+ {
193
+ originZip: "90210",
194
+ destZip: "10001",
195
+ weightOz: 16,
196
+ lengthInches: 10,
197
+ widthInches: 5,
198
+ heightInches: 5
199
+ },
200
+ ["usps", "fedex", "ups"]
201
+ );
202
+ ```
203
+
204
+ ### Staged Shipments (Safe Mode)
205
+
206
+ Generates a carrier‑specific payload without purchasing a label.
207
+
208
+ ```ts
209
+ import { buildShipment } from "@teralabs/shipstack";
210
+
211
+ const staged = await buildShipment({ /* ShipmentRequest */ }, config);
212
+ ```
213
+
214
+ ### Actual Shipments (Backend Only)
215
+
216
+ Purchases a real label.
217
+
218
+ ```ts
219
+ import { createShipment } from "@teralabs/shipstack";
220
+
221
+ const shipment = await createShipment({ /* ShipmentRequest */ }, config);
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Error Handling
227
+
228
+ All errors are standardized as `ShipstackError`.
229
+
230
+ ```ts
231
+ import { ShipstackError } from "@teralabs/shipstack";
232
+
233
+ try {
234
+ await client.getRates({
235
+ carrier: "usps",
236
+ originZip: "90210",
237
+ destZip: "10001",
238
+ weightOz: 16,
239
+ lengthInches: 10,
240
+ widthInches: 5,
241
+ heightInches: 5
242
+ });
243
+ } catch (error) {
244
+ if (error instanceof ShipstackError) {
245
+ // handle
246
+ }
247
+ }
248
+ ```
249
+
250
+ ---
251
+
252
+ ## API Reference
253
+
254
+ See `API.md` for detailed type and method documentation.
255
+
256
+ ---
257
+
258
+ ## Testing
259
+
260
+ Shipstack uses Vitest for unit and integration tests.
261
+
262
+ Run tests:
263
+
264
+ ```bash
265
+ npm run test
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Contributing
271
+
272
+ See `CONTRIBUTING.md` for guidelines.
273
+
274
+ ---
275
+
276
+ ## Changelog
277
+
278
+ See `CHANGELOG.md`.
279
+
280
+ ---
281
+
282
+ ## License
283
+
284
+ ISC License
285
+
286
+ ---