@trustrails/sdk 0.4.10 → 0.6.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 CHANGED
@@ -58,19 +58,20 @@ new TrustRails({ apiKey: string, baseUrl?: string })
58
58
 
59
59
  ##### `search(options?: SearchOptions): Promise<SearchResponse>`
60
60
 
61
- Search for products. Returns summary data (title, price, availability, category). For full technical specs, call `product(id)` with the product ID.
61
+ Search for products. With `lite: true` the products are trimmed (`SearchResponse<LiteProduct>`). Search results leave out `specs` and `delivery_time`, but carry every known `attributes` spec, so compare specs from the search results (a missing name is unknown, `conflicting` means retailers disagree). Call `product(id)` only for your final 1-3 picks, and only for what search lacks: the retailer's description (processor, GPU, ports, weight, battery; if it doesn't give a detail, it isn't listed), or every retailer's offer and buy link (search carries only the best offer's `purchase_url`, cheapest in stock). State the value of a `confirmed` or `inferred` attribute; only `conflicting` or a missing name needs a caveat. See [What each call returns](#what-each-call-returns).
62
62
 
63
63
  **Parameters:**
64
- - `options.query?: string` - Refinement terms after brand and category are extracted: model lines, series, variants, technology descriptors, or model numbers (e.g. `'neo'`, `'ultra'`, `'oled'`, `'WH-1000XM5'`). Omit entirely if brand + category fully describe what you want. Never put brand names, product family names, or prices here — use filters.
65
- - `options.brand?: string` - Filter by brand name (e.g., 'Sony', 'HP', 'Anker')
66
- - `options.category?: string` - Filter by product category (e.g., 'Laptops', 'Headphones')
64
+ - `options.query?: string` - Refinement terms only — model lines, series, variants, model numbers (e.g. `'neo'`, `'ultra'`, `'oled'`, `'WH-1000XM5'`). Never put category names here (`query='tablet'` → use `category='Tablets'`). Never put brand names or prices — use filters. Omit entirely when browsing a category or brand. Query words must appear in the title, except words naming the category (`'router'` in Networking) and bare numbers or specs (`'4070'`, `'16GB'`), which are ignored when finding products and only rank them: put a model number with its prefix (`'RTX 4070'`, not `'4070'`) and check each title for it.
65
+ - `options.brand?: string` - Filter by brand name (exact match, case-insensitive), e.g. 'Sony', 'HP', 'Anker'
66
+ - `options.category?: string` - Filter by product category. Use only these exact values: Laptops, Desktops, Tablets, Phones, TVs, Monitors, Headphones, Speakers, Cameras, Keyboards, Mice, Printers, Networking, Storage, Gaming, Wearables, Drones, Audio, Cables & Chargers. 'Smartphones' is not valid (use 'Phones'), nor is 'Televisions' (use 'TVs')
67
67
  - `options.minPrice?: number` - Minimum price filter in GBP
68
68
  - `options.maxPrice?: number` - Maximum price filter in GBP
69
- - `options.lite?: boolean` - Return trimmed product objects with only essential fields including `offer_count` (reduces payload by ~80%)
69
+ - `options.constraints?: Constraints` - Hard spec requirements, checked per product against its `attributes`, e.g. `{ memory_gb: { gte: 24 }, storage_gb: { gte: 1000 }, screen_in: { eq: 15 } }`. Operators: `eq`, `gte`, `lte`; a range is `{ gte, lte }`. Names and units: `memory_gb` (RAM, GB), `storage_gb` (GB, 1TB = 1000), `screen_in` (inches), `resolution_p` (pixels high: 4K = 2160, QHD = 1440, Full HD = 1080), `refresh_hz` (Hz), `power_w` (W), `wifi_gen` (Wi-Fi generation: 6, 6E = 6.5, 7). On every operator, storage matches within 3% (1TB = 1000–1024GB, so `gte 1024` accepts a 1TB drive but not 960GB) and screen size within 0.5 inch; a whole-number screen size N also covers up to N+1 on `eq` and `lte` (`eq 13` accepts 13.6", `lte 15` accepts 15.6"); other specs exactly. Each product then has `constraint_status` per name: `matched` (a retailer's title states a value that meets it), or `unverified` (not known to meet it, never treat as a match: in `attributes[name]`, `conflicting` means retailers disagree and missing means unknown). Products whose stated value fails are left out and counted in `excluded_by_constraints`. Wins over the same spec written in `query`, where only a spec that says what it is counts (`24GB RAM`, `1TB`; a bare `24GB` does not) and means at least, except screen size. A plain `16GB` or `65W` is `gte`; use `eq` only when the user says exactly, and for screen size. Always pass a `category` or `brand` with constraints. A bad request (unknown name or operator, negative value, a combination nothing can meet such as `gte` above `lte`, or constraints with no category, brand or search words) throws a `TrustRailsError` with `statusCode` 400; the message naming the valid values is in `error.response`, the raw JSON body.
70
+ - `options.lite?: boolean` - Return trimmed product objects (`LiteProduct`) with only essential fields (id, title, brand, price, currency, availability, image_url, purchase_url, offer_count), every known `attributes` spec as `status` and value without `sources`, and, with constraints, `constraint_status`
70
71
  - `options.limit?: number` - Maximum number of results (default: 50, max: 100)
71
- - `options.sort?: string` - Sort order: `'relevance'` (default), `'price_asc'` (cheapest first), `'price_desc'` (most expensive first)
72
+ - `options.sort?: string` - Sort order: `'relevance'` (default), `'price_asc'` (in stock first, then cheapest), `'price_desc'` (in stock first, then most expensive). With constraints, matched products still come first.
72
73
 
73
- **Returns:** Promise resolving to `SearchResponse` containing `products` array and `total` count.
74
+ **Returns:** Promise resolving to `SearchResponse` containing `products` array and `total` count, plus `constraints` (as applied), `excluded_by_constraints` and `unverified_total` whenever constraints applied, whether passed or written in `query`. With constraints, `total` counts the products that match every constraint and `unverified_total` the unverified products that passed the other filters (only some may be in `products`). `candidates_truncated: true` means the first 2,000 candidates in the chosen sort order were checked and more exist: add a brand or category, or a narrower query, and search again. If the search was already narrowed, the results may be incomplete.
74
75
 
75
76
  **Example — brand + category only (query omitted):**
76
77
  ```typescript
@@ -93,18 +94,29 @@ const results = await trustrails.search({
93
94
  });
94
95
  ```
95
96
 
97
+ **Example — spec constraints:**
98
+ ```typescript
99
+ // MacBooks whose titles state 24GB+ RAM and 1TB+ storage come back as 'matched'
100
+ const results = await trustrails.search({
101
+ brand: 'Apple',
102
+ category: 'Laptops',
103
+ maxPrice: 2000,
104
+ constraints: { memory_gb: { gte: 24 }, storage_gb: { gte: 1000 } },
105
+ });
106
+ ```
107
+
96
108
  **Lite mode (faster responses, smaller payloads):**
97
109
  ```typescript
98
110
  const results = await trustrails.search({
99
111
  brand: 'Anker',
100
112
  category: 'Cables & Chargers',
101
- lite: true // Returns only: id, title, brand, price, availability, image_url, purchase_url
113
+ lite: true // Returns id, title, brand, price, currency, availability, image_url, purchase_url, offer_count and attributes (without sources)
102
114
  });
103
115
  ```
104
116
 
105
117
  ##### `product(id: string): Promise<Product>`
106
118
 
107
- Get full details for a single product. Returns complete technical specifications including `specs.description` (full prose spec text with processor, RAM, storage, display, etc.), stock level, delivery time, and all retailer offers with per-retailer pricing. Accepts canonical product IDs or original retailer offer IDs. Use this after `search()` to get detailed specs for comparison or recommendations.
119
+ Get full details for a single product. Returns `attributes`: structured specs read from retailer titles, each with its `status` and the retailers behind it (see `Product`). Also returns `specs.description` (the retailer's own prose, for processor, ports, graphics and anything `attributes` does not cover), availability, delivery time, and all retailer offers with per-retailer pricing. `specs.description` can describe another configuration or a maximum ("up to 32GB"), so it never overrides or fills in an attribute: a spec missing from `attributes` is unknown. Accepts canonical product IDs or original retailer offer IDs. Use this after `search()` only for your final 1-3 picks, and only for what search lacks (the description's details, or every offer and buy link): `search()` already carries `attributes` to compare specs.
108
120
 
109
121
  **Parameters:**
110
122
  - `id: string` - The product ID
@@ -116,6 +128,16 @@ Get full details for a single product. Returns complete technical specifications
116
128
  const product = await trustrails.product('prod_123');
117
129
  ```
118
130
 
131
+ ### What each call returns
132
+
133
+ | Call | Returns |
134
+ |------|---------|
135
+ | `search({ lite: true })` (what agents should always set) | id, title, brand, price, currency, availability, image_url, purchase_url, offer_count, every known `attributes` spec as `{ status, value }` (or `{ status, values }` when conflicting) without sources, and `constraint_status` when constrained |
136
+ | `search()` | the lite fields plus ean, category, product_type, provenance and every attribute with its `sources` |
137
+ | `product(id)` | everything: `specs.description` and dimensions, `offers`, `delivery_time` |
138
+
139
+ A product with no `attributes` key has none of these seven specs known.
140
+
119
141
  ## Types
120
142
 
121
143
  ### `Product`
@@ -128,14 +150,13 @@ interface Product {
128
150
  brand?: string;
129
151
  price: number;
130
152
  currency: string;
131
- availability: "in_stock" | "low_stock" | "out_of_stock";
132
- stock: number;
133
- delivery_time: string;
153
+ availability: "in_stock" | "low_stock" | "out_of_stock" | "unknown";
154
+ delivery_time?: string; // product() only
134
155
  image_url?: string;
135
156
  category: string;
136
157
  product_type: "product" | "accessory";
137
- specs: {
138
- description?: string; // Full prose spec text — always check this for technical details
158
+ specs?: { // product() only
159
+ description?: string; // Retailer prose; never overrides attributes
139
160
  model_number?: string;
140
161
  dimensions?: string;
141
162
  };
@@ -144,8 +165,10 @@ interface Product {
144
165
  last_updated: string;
145
166
  };
146
167
  purchase_url: string;
147
- offer_count?: number; // number of retailer offers (when >1, call product() to compare prices)
148
- offers?: Offer[]; // per-retailer offers sorted by price (returned by product())
168
+ attributes?: Attributes; // structured specs (search results and product()), absent when none is known, see below
169
+ constraint_status?: Partial<Record<ConstraintName, "matched" | "unverified">>; // only when constraints applied
170
+ offer_count?: number; // number of retailer offers (when >1, call product() for your final picks to compare prices)
171
+ offers?: Offer[]; // per-retailer offers, in stock first then cheapest (returned by product())
149
172
  }
150
173
 
151
174
  interface Offer {
@@ -154,8 +177,8 @@ interface Offer {
154
177
  title: string;
155
178
  price: number;
156
179
  currency: string;
157
- availability: "in_stock" | "low_stock" | "out_of_stock";
158
- stock: number;
180
+ availability: "in_stock" | "low_stock" | "out_of_stock" | "unknown";
181
+ stock: number | null; // unit count only when the retailer supplies one
159
182
  delivery_time: string;
160
183
  purchase_url: string;
161
184
  image_url?: string;
@@ -163,6 +186,55 @@ interface Offer {
163
186
  }
164
187
  ```
165
188
 
189
+ ### `Attributes`
190
+
191
+ Structured specs read from retailer titles, by name. A name nobody states is absent (unknown).
192
+
193
+ - `confirmed`: two or more retailers state the same value.
194
+ - `inferred`: one retailer's title states it.
195
+ - `conflicting`: retailers state different values; none is picked, so any constraint on it is `unverified`. A retailer can appear under two values, so check `offers[].title`.
196
+
197
+ ```typescript
198
+ type Attributes = Partial<Record<ConstraintName, Attribute>>;
199
+
200
+ type Attribute =
201
+ | { status: "confirmed" | "inferred"; value: number; sources: { retailer: string; field: "title" }[] }
202
+ | { status: "conflicting"; values: { value: number; sources: { retailer: string; field: "title" }[] }[] };
203
+
204
+ // An attribute without its sources, as a lite search result carries it
205
+ type LiteAttribute =
206
+ | { status: "confirmed" | "inferred"; value: number }
207
+ | { status: "conflicting"; values: { value: number }[] };
208
+
209
+ type LiteAttributes = Partial<Record<ConstraintName, LiteAttribute>>;
210
+
211
+ // memory_gb (RAM, GB), storage_gb (GB, 1TB = 1000), screen_in (inches), resolution_p (pixels high: 4K = 2160),
212
+ // refresh_hz (Hz), power_w (W), wifi_gen (Wi-Fi generation: 6, 6E = 6.5, 7)
213
+ type ConstraintName = "memory_gb" | "storage_gb" | "screen_in" | "resolution_p" | "refresh_hz" | "power_w" | "wifi_gen";
214
+
215
+ type Constraints = Partial<Record<ConstraintName, { eq?: number; gte?: number; lte?: number }>>;
216
+ ```
217
+
218
+ ### `LiteProduct`
219
+
220
+ A product in a `lite: true` search.
221
+
222
+ ```typescript
223
+ interface LiteProduct {
224
+ id: string;
225
+ title: string;
226
+ brand?: string;
227
+ price: number;
228
+ currency: string;
229
+ availability: "in_stock" | "low_stock" | "out_of_stock" | "unknown";
230
+ image_url?: string;
231
+ purchase_url: string;
232
+ offer_count: number;
233
+ constraint_status?: Partial<Record<ConstraintName, "matched" | "unverified">>; // only when constraints applied
234
+ attributes?: LiteAttributes; // every known spec, without sources; absent when none is known
235
+ }
236
+ ```
237
+
166
238
  ### `SearchOptions`
167
239
 
168
240
  ```typescript
@@ -172,6 +244,7 @@ interface SearchOptions {
172
244
  category?: string;
173
245
  minPrice?: number;
174
246
  maxPrice?: number;
247
+ constraints?: Constraints;
175
248
  lite?: boolean;
176
249
  limit?: number;
177
250
  sort?: 'relevance' | 'price_asc' | 'price_desc';
@@ -181,9 +254,13 @@ interface SearchOptions {
181
254
  ### `SearchResponse`
182
255
 
183
256
  ```typescript
184
- interface SearchResponse {
185
- products: Product[];
186
- total: number;
257
+ interface SearchResponse<P = Product> { // SearchResponse<LiteProduct> when lite: true
258
+ products: P[];
259
+ total: number; // with constraints: the products that match every constraint
260
+ constraints?: Constraints; // as applied, when constraints applied
261
+ excluded_by_constraints?: number; // products left out because their stated value fails a constraint
262
+ unverified_total?: number; // with constraints: the unverified products that passed the other filters; only some may be in products
263
+ candidates_truncated?: boolean; // true when the first 2,000 candidates were checked and more exist
187
264
  }
188
265
  ```
189
266
 
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Product availability status
3
3
  */
4
- type Availability = "in_stock" | "low_stock" | "out_of_stock";
4
+ type Availability = "in_stock" | "low_stock" | "out_of_stock" | "unknown";
5
5
  /**
6
6
  * Per-retailer offer for a product
7
7
  */
@@ -12,12 +12,73 @@ interface Offer {
12
12
  price: number;
13
13
  currency: string;
14
14
  availability: Availability;
15
- stock: number;
15
+ stock: number | null;
16
16
  delivery_time: string;
17
17
  purchase_url: string;
18
18
  image_url?: string;
19
19
  last_updated: string;
20
20
  }
21
+ /**
22
+ * Where a stated value came from
23
+ */
24
+ interface AttributeSource {
25
+ retailer: string;
26
+ field: 'title';
27
+ }
28
+ /**
29
+ * One structured spec of a product, with the evidence for it
30
+ */
31
+ type Attribute = {
32
+ status: 'confirmed' | 'inferred';
33
+ value: number;
34
+ sources: AttributeSource[];
35
+ } | {
36
+ status: 'conflicting';
37
+ values: Array<{
38
+ value: number;
39
+ sources: AttributeSource[];
40
+ }>;
41
+ };
42
+ /**
43
+ * An attribute without its sources, as a lite search result carries it
44
+ */
45
+ type LiteAttribute = {
46
+ status: 'confirmed' | 'inferred';
47
+ value: number;
48
+ } | {
49
+ status: 'conflicting';
50
+ values: Array<{
51
+ value: number;
52
+ }>;
53
+ };
54
+ /**
55
+ * Structured specs of a product, by name
56
+ */
57
+ type Attributes = Partial<Record<ConstraintName, Attribute>>;
58
+ /**
59
+ * Structured specs of a lite search result, by name
60
+ */
61
+ type LiteAttributes = Partial<Record<ConstraintName, LiteAttribute>>;
62
+ /**
63
+ * Spec names for `constraints` and `attributes`
64
+ */
65
+ type ConstraintName = 'memory_gb' | 'storage_gb' | 'screen_in' | 'resolution_p' | 'refresh_hz' | 'power_w' | 'wifi_gen';
66
+ /**
67
+ * Operators for one spec
68
+ */
69
+ interface ConstraintOps {
70
+ eq?: number;
71
+ gte?: number;
72
+ lte?: number;
73
+ }
74
+ /**
75
+ * Hard spec requirements
76
+ */
77
+ type Constraints = Partial<Record<ConstraintName, ConstraintOps>>;
78
+ /**
79
+ * Whether a product meets one constraint
80
+ */
81
+ type ConstraintStatus = 'matched' | 'unverified';
21
82
  /**
22
83
  * Product information returned by TrustRails API
23
84
  */
@@ -29,12 +90,13 @@ interface Product {
29
90
  price: number;
30
91
  currency: string;
31
92
  availability: Availability;
32
- stock: number;
33
- delivery_time: string;
93
+ /** Product detail only: absent from search results */
94
+ delivery_time?: string;
34
95
  image_url?: string;
35
96
  category: string;
36
97
  product_type: 'product' | 'accessory';
37
- specs: {
98
+ /** Product detail only: absent from search results */
99
+ specs?: {
38
100
  description?: string;
39
101
  model_number?: string;
40
102
  dimensions?: string;
@@ -44,11 +106,33 @@ interface Product {
44
106
  last_updated: string;
45
107
  };
46
108
  purchase_url: string;
109
+ /** Structured specs read from retailer titles: absent when none is known */
110
+ attributes?: Attributes;
111
+ /** Per-constraint result (only in searches with constraints) */
112
+ constraint_status?: Partial<Record<ConstraintName, ConstraintStatus>>;
47
113
  /** Number of retailer offers available for this product */
48
114
  offer_count?: number;
49
- /** Per-retailer offers sorted by price (full mode / getProduct only) */
115
+ /** Per-retailer offers, in stock first then cheapest (product() only) */
50
116
  offers?: Offer[];
51
117
  }
118
+ /**
119
+ * Trimmed product returned by a lite search
120
+ */
121
+ interface LiteProduct {
122
+ id: string;
123
+ title: string;
124
+ brand?: string;
125
+ price: number;
126
+ currency: string;
127
+ availability: Availability;
128
+ image_url?: string;
129
+ purchase_url: string;
130
+ offer_count: number;
131
+ /** Per-constraint result (only in searches with constraints) */
132
+ constraint_status?: Partial<Record<ConstraintName, ConstraintStatus>>;
133
+ /** Every known spec, without sources: absent when none is known */
134
+ attributes?: LiteAttributes;
135
+ }
52
136
  /**
53
137
  * Options for searching products
54
138
  */
@@ -58,19 +142,30 @@ interface SearchOptions {
58
142
  maxPrice?: number;
59
143
  brand?: string;
60
144
  category?: string;
61
- /** Return trimmed product objects with only essential fields. Reduces payload size by ~80%. */
145
+ /** Hard spec requirements checked against each product's attributes */
146
+ constraints?: Constraints;
147
+ /** Return trimmed product objects (see LiteProduct) with only essential fields. */
62
148
  lite?: boolean;
63
149
  /** Maximum number of products to return (default 50, max 100) */
64
150
  limit?: number;
65
- /** Sort order: 'relevance' (default), 'price_asc' (cheapest first), 'price_desc' (most expensive first) */
151
+ /** Sort order: 'relevance' (default), 'price_asc' (in stock first, then cheapest), 'price_desc' (in stock first, then most expensive) */
66
152
  sort?: 'relevance' | 'price_asc' | 'price_desc';
67
153
  }
68
154
  /**
69
155
  * Response from product search endpoint
70
156
  */
71
- interface SearchResponse {
72
- products: Product[];
157
+ interface SearchResponse<P = Product> {
158
+ products: P[];
159
+ /** With constraints: the products with every constraint matched */
73
160
  total: number;
161
+ /** The constraints applied */
162
+ constraints?: Constraints;
163
+ /** Products left out for failing a constraint */
164
+ excluded_by_constraints?: number;
165
+ /** With constraints: the unverified products that passed the other filters; only some may be in products */
166
+ unverified_total?: number;
167
+ /** True when the first 2,000 candidates in the sort order were checked and more exist */
168
+ candidates_truncated?: boolean;
74
169
  }
75
170
  /**
76
171
  * Configuration options for TrustRails client
@@ -105,8 +200,8 @@ declare class TrustRails {
105
200
  */
106
201
  constructor(config: TrustRailsConfig);
107
202
  /**
108
- * Search for products. Returns summary data (title, price, availability, category).
109
- * For full technical specs, call product(id) with the product ID.
203
+ * Search for products. With `lite: true` the products are trimmed (see LiteProduct).
204
+ * Search results carry every known `attributes` spec (with sources unless lite); for the retailer's description and every offer, call product(id).
110
205
  *
111
206
  * @param options - Search parameters
112
207
  * @returns Promise resolving to search results
@@ -118,15 +213,22 @@ declare class TrustRails {
118
213
  * brand: 'Anker',
119
214
  * minPrice: 20,
120
215
  * maxPrice: 50,
121
- * category: 'Chargers'
216
+ * category: 'Cables & Chargers'
122
217
  * });
123
218
  * ```
124
219
  */
125
- search(options?: SearchOptions): Promise<SearchResponse>;
220
+ search(options: SearchOptions & {
221
+ lite: true;
222
+ }): Promise<SearchResponse<LiteProduct>>;
223
+ search(options?: SearchOptions & {
224
+ lite?: false;
225
+ }): Promise<SearchResponse>;
226
+ search(options: SearchOptions): Promise<SearchResponse<Product | LiteProduct>>;
126
227
  /**
127
228
  * Get full details for a single product. Returns complete specs, description,
128
- * stock level, delivery time, and retailer source. Use after search() for
129
- * detailed comparison or recommendations.
229
+ * availability, delivery time, and every retailer offer. Use after search() for
230
+ * your final 1-3 picks, and only for what search() lacks (the description's details, every offer):
231
+ * compare specs from the attributes search() already returns.
130
232
  *
131
233
  * @param id - The product ID
132
234
  * @returns Promise resolving to complete product details
@@ -148,4 +250,4 @@ declare class TrustRailsError extends Error {
148
250
  constructor(message: string, statusCode?: number | undefined, response?: any | undefined);
149
251
  }
150
252
 
151
- export { type Product, type SearchOptions, type SearchResponse, TrustRails, type TrustRailsConfig, TrustRailsError, TrustRails as default };
253
+ export { type Attribute, type AttributeSource, type Attributes, type ConstraintName, type ConstraintOps, type ConstraintStatus, type Constraints, type LiteAttribute, type LiteAttributes, type LiteProduct, type Product, type SearchOptions, type SearchResponse, TrustRails, type TrustRailsConfig, TrustRailsError, TrustRails as default };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Product availability status
3
3
  */
4
- type Availability = "in_stock" | "low_stock" | "out_of_stock";
4
+ type Availability = "in_stock" | "low_stock" | "out_of_stock" | "unknown";
5
5
  /**
6
6
  * Per-retailer offer for a product
7
7
  */
@@ -12,12 +12,73 @@ interface Offer {
12
12
  price: number;
13
13
  currency: string;
14
14
  availability: Availability;
15
- stock: number;
15
+ stock: number | null;
16
16
  delivery_time: string;
17
17
  purchase_url: string;
18
18
  image_url?: string;
19
19
  last_updated: string;
20
20
  }
21
+ /**
22
+ * Where a stated value came from
23
+ */
24
+ interface AttributeSource {
25
+ retailer: string;
26
+ field: 'title';
27
+ }
28
+ /**
29
+ * One structured spec of a product, with the evidence for it
30
+ */
31
+ type Attribute = {
32
+ status: 'confirmed' | 'inferred';
33
+ value: number;
34
+ sources: AttributeSource[];
35
+ } | {
36
+ status: 'conflicting';
37
+ values: Array<{
38
+ value: number;
39
+ sources: AttributeSource[];
40
+ }>;
41
+ };
42
+ /**
43
+ * An attribute without its sources, as a lite search result carries it
44
+ */
45
+ type LiteAttribute = {
46
+ status: 'confirmed' | 'inferred';
47
+ value: number;
48
+ } | {
49
+ status: 'conflicting';
50
+ values: Array<{
51
+ value: number;
52
+ }>;
53
+ };
54
+ /**
55
+ * Structured specs of a product, by name
56
+ */
57
+ type Attributes = Partial<Record<ConstraintName, Attribute>>;
58
+ /**
59
+ * Structured specs of a lite search result, by name
60
+ */
61
+ type LiteAttributes = Partial<Record<ConstraintName, LiteAttribute>>;
62
+ /**
63
+ * Spec names for `constraints` and `attributes`
64
+ */
65
+ type ConstraintName = 'memory_gb' | 'storage_gb' | 'screen_in' | 'resolution_p' | 'refresh_hz' | 'power_w' | 'wifi_gen';
66
+ /**
67
+ * Operators for one spec
68
+ */
69
+ interface ConstraintOps {
70
+ eq?: number;
71
+ gte?: number;
72
+ lte?: number;
73
+ }
74
+ /**
75
+ * Hard spec requirements
76
+ */
77
+ type Constraints = Partial<Record<ConstraintName, ConstraintOps>>;
78
+ /**
79
+ * Whether a product meets one constraint
80
+ */
81
+ type ConstraintStatus = 'matched' | 'unverified';
21
82
  /**
22
83
  * Product information returned by TrustRails API
23
84
  */
@@ -29,12 +90,13 @@ interface Product {
29
90
  price: number;
30
91
  currency: string;
31
92
  availability: Availability;
32
- stock: number;
33
- delivery_time: string;
93
+ /** Product detail only: absent from search results */
94
+ delivery_time?: string;
34
95
  image_url?: string;
35
96
  category: string;
36
97
  product_type: 'product' | 'accessory';
37
- specs: {
98
+ /** Product detail only: absent from search results */
99
+ specs?: {
38
100
  description?: string;
39
101
  model_number?: string;
40
102
  dimensions?: string;
@@ -44,11 +106,33 @@ interface Product {
44
106
  last_updated: string;
45
107
  };
46
108
  purchase_url: string;
109
+ /** Structured specs read from retailer titles: absent when none is known */
110
+ attributes?: Attributes;
111
+ /** Per-constraint result (only in searches with constraints) */
112
+ constraint_status?: Partial<Record<ConstraintName, ConstraintStatus>>;
47
113
  /** Number of retailer offers available for this product */
48
114
  offer_count?: number;
49
- /** Per-retailer offers sorted by price (full mode / getProduct only) */
115
+ /** Per-retailer offers, in stock first then cheapest (product() only) */
50
116
  offers?: Offer[];
51
117
  }
118
+ /**
119
+ * Trimmed product returned by a lite search
120
+ */
121
+ interface LiteProduct {
122
+ id: string;
123
+ title: string;
124
+ brand?: string;
125
+ price: number;
126
+ currency: string;
127
+ availability: Availability;
128
+ image_url?: string;
129
+ purchase_url: string;
130
+ offer_count: number;
131
+ /** Per-constraint result (only in searches with constraints) */
132
+ constraint_status?: Partial<Record<ConstraintName, ConstraintStatus>>;
133
+ /** Every known spec, without sources: absent when none is known */
134
+ attributes?: LiteAttributes;
135
+ }
52
136
  /**
53
137
  * Options for searching products
54
138
  */
@@ -58,19 +142,30 @@ interface SearchOptions {
58
142
  maxPrice?: number;
59
143
  brand?: string;
60
144
  category?: string;
61
- /** Return trimmed product objects with only essential fields. Reduces payload size by ~80%. */
145
+ /** Hard spec requirements checked against each product's attributes */
146
+ constraints?: Constraints;
147
+ /** Return trimmed product objects (see LiteProduct) with only essential fields. */
62
148
  lite?: boolean;
63
149
  /** Maximum number of products to return (default 50, max 100) */
64
150
  limit?: number;
65
- /** Sort order: 'relevance' (default), 'price_asc' (cheapest first), 'price_desc' (most expensive first) */
151
+ /** Sort order: 'relevance' (default), 'price_asc' (in stock first, then cheapest), 'price_desc' (in stock first, then most expensive) */
66
152
  sort?: 'relevance' | 'price_asc' | 'price_desc';
67
153
  }
68
154
  /**
69
155
  * Response from product search endpoint
70
156
  */
71
- interface SearchResponse {
72
- products: Product[];
157
+ interface SearchResponse<P = Product> {
158
+ products: P[];
159
+ /** With constraints: the products with every constraint matched */
73
160
  total: number;
161
+ /** The constraints applied */
162
+ constraints?: Constraints;
163
+ /** Products left out for failing a constraint */
164
+ excluded_by_constraints?: number;
165
+ /** With constraints: the unverified products that passed the other filters; only some may be in products */
166
+ unverified_total?: number;
167
+ /** True when the first 2,000 candidates in the sort order were checked and more exist */
168
+ candidates_truncated?: boolean;
74
169
  }
75
170
  /**
76
171
  * Configuration options for TrustRails client
@@ -105,8 +200,8 @@ declare class TrustRails {
105
200
  */
106
201
  constructor(config: TrustRailsConfig);
107
202
  /**
108
- * Search for products. Returns summary data (title, price, availability, category).
109
- * For full technical specs, call product(id) with the product ID.
203
+ * Search for products. With `lite: true` the products are trimmed (see LiteProduct).
204
+ * Search results carry every known `attributes` spec (with sources unless lite); for the retailer's description and every offer, call product(id).
110
205
  *
111
206
  * @param options - Search parameters
112
207
  * @returns Promise resolving to search results
@@ -118,15 +213,22 @@ declare class TrustRails {
118
213
  * brand: 'Anker',
119
214
  * minPrice: 20,
120
215
  * maxPrice: 50,
121
- * category: 'Chargers'
216
+ * category: 'Cables & Chargers'
122
217
  * });
123
218
  * ```
124
219
  */
125
- search(options?: SearchOptions): Promise<SearchResponse>;
220
+ search(options: SearchOptions & {
221
+ lite: true;
222
+ }): Promise<SearchResponse<LiteProduct>>;
223
+ search(options?: SearchOptions & {
224
+ lite?: false;
225
+ }): Promise<SearchResponse>;
226
+ search(options: SearchOptions): Promise<SearchResponse<Product | LiteProduct>>;
126
227
  /**
127
228
  * Get full details for a single product. Returns complete specs, description,
128
- * stock level, delivery time, and retailer source. Use after search() for
129
- * detailed comparison or recommendations.
229
+ * availability, delivery time, and every retailer offer. Use after search() for
230
+ * your final 1-3 picks, and only for what search() lacks (the description's details, every offer):
231
+ * compare specs from the attributes search() already returns.
130
232
  *
131
233
  * @param id - The product ID
132
234
  * @returns Promise resolving to complete product details
@@ -148,4 +250,4 @@ declare class TrustRailsError extends Error {
148
250
  constructor(message: string, statusCode?: number | undefined, response?: any | undefined);
149
251
  }
150
252
 
151
- export { type Product, type SearchOptions, type SearchResponse, TrustRails, type TrustRailsConfig, TrustRailsError, TrustRails as default };
253
+ export { type Attribute, type AttributeSource, type Attributes, type ConstraintName, type ConstraintOps, type ConstraintStatus, type Constraints, type LiteAttribute, type LiteAttributes, type LiteProduct, type Product, type SearchOptions, type SearchResponse, TrustRails, type TrustRailsConfig, TrustRailsError, TrustRails as default };
package/dist/index.js CHANGED
@@ -33,24 +33,6 @@ var TrustRails = class {
33
33
  throw new Error("apiKey is required");
34
34
  }
35
35
  }
36
- /**
37
- * Search for products. Returns summary data (title, price, availability, category).
38
- * For full technical specs, call product(id) with the product ID.
39
- *
40
- * @param options - Search parameters
41
- * @returns Promise resolving to search results
42
- *
43
- * @example
44
- * ```typescript
45
- * const results = await client.search({
46
- * query: 'USB-C charger',
47
- * brand: 'Anker',
48
- * minPrice: 20,
49
- * maxPrice: 50,
50
- * category: 'Chargers'
51
- * });
52
- * ```
53
- */
54
36
  async search(options = {}) {
55
37
  const url = new URL(`${this.baseUrl}/api/search`);
56
38
  if (options.query) {
@@ -68,6 +50,9 @@ var TrustRails = class {
68
50
  if (options.category) {
69
51
  url.searchParams.set("category", options.category);
70
52
  }
53
+ if (options.constraints) {
54
+ url.searchParams.set("constraints", JSON.stringify(options.constraints));
55
+ }
71
56
  if (options.lite) {
72
57
  url.searchParams.set("lite", "true");
73
58
  }
@@ -105,8 +90,9 @@ var TrustRails = class {
105
90
  }
106
91
  /**
107
92
  * Get full details for a single product. Returns complete specs, description,
108
- * stock level, delivery time, and retailer source. Use after search() for
109
- * detailed comparison or recommendations.
93
+ * availability, delivery time, and every retailer offer. Use after search() for
94
+ * your final 1-3 picks, and only for what search() lacks (the description's details, every offer):
95
+ * compare specs from the attributes search() already returns.
110
96
  *
111
97
  * @param id - The product ID
112
98
  * @returns Promise resolving to complete product details
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Custom error class for TrustRails API errors\n */\nexport class TrustRailsError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public response?: any\n ) {\n super(message);\n this.name = 'TrustRailsError';\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, TrustRailsError);\n }\n }\n}\n","import { TrustRailsError } from './errors';\nimport type { Product, SearchOptions, SearchResponse, TrustRailsConfig } from './types';\n\nconst DEFAULT_BASE_URL = 'https://trustrails.app';\n\n/**\n * TrustRails SDK\n *\n * @example\n * ```typescript\n * const trustrails = new TrustRails('your-api-key');\n * const results = await trustrails.search({ query: 'laptop', maxPrice: 1000 });\n * ```\n */\nexport class TrustRails {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n /**\n * Creates a new TrustRails SDK instance\n *\n * @param apiKey - Your TrustRails API key\n */\n constructor(apiKey: string);\n /**\n * Creates a new TrustRails SDK instance with custom configuration\n *\n * @param config - Configuration object with apiKey and optional baseUrl\n */\n constructor(config: TrustRailsConfig);\n constructor(apiKeyOrConfig: string | TrustRailsConfig) {\n if (typeof apiKeyOrConfig === 'string') {\n this.apiKey = apiKeyOrConfig;\n this.baseUrl = DEFAULT_BASE_URL;\n } else {\n this.apiKey = apiKeyOrConfig.apiKey;\n this.baseUrl = apiKeyOrConfig.baseUrl || DEFAULT_BASE_URL;\n }\n\n // Clean up baseUrl - remove trailing slash\n if (this.baseUrl.endsWith('/')) {\n this.baseUrl = this.baseUrl.slice(0, -1);\n }\n\n if (!this.apiKey) {\n throw new Error('apiKey is required');\n }\n }\n\n /**\n * Search for products. Returns summary data (title, price, availability, category).\n * For full technical specs, call product(id) with the product ID.\n *\n * @param options - Search parameters\n * @returns Promise resolving to search results\n *\n * @example\n * ```typescript\n * const results = await client.search({\n * query: 'USB-C charger',\n * brand: 'Anker',\n * minPrice: 20,\n * maxPrice: 50,\n * category: 'Chargers'\n * });\n * ```\n */\n async search(options: SearchOptions = {}): Promise<SearchResponse> {\n const url = new URL(`${this.baseUrl}/api/search`);\n\n if (options.query) {\n url.searchParams.set('query', options.query);\n }\n\n if (options.minPrice) {\n url.searchParams.set('min_price', options.minPrice.toString());\n }\n\n if (options.maxPrice) {\n url.searchParams.set('max_price', options.maxPrice.toString());\n }\n\n if (options.brand) {\n url.searchParams.set('brand', options.brand);\n }\n\n if (options.category) {\n url.searchParams.set('category', options.category);\n }\n\n if (options.lite) {\n url.searchParams.set('lite', 'true');\n }\n\n if (options.limit) {\n url.searchParams.set('limit', options.limit.toString());\n }\n\n if (options.sort) {\n url.searchParams.set('sort', options.sort);\n }\n\n try {\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new TrustRailsError(\n `Search failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const data = await response.json() as SearchResponse;\n return data;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n\n /**\n * Get full details for a single product. Returns complete specs, description,\n * stock level, delivery time, and retailer source. Use after search() for\n * detailed comparison or recommendations.\n *\n * @param id - The product ID\n * @returns Promise resolving to complete product details\n *\n * @example\n * ```typescript\n * const product = await client.product('prod_123');\n * ```\n */\n async product(id: string): Promise<Product> {\n if (!id) {\n throw new Error('Product ID is required');\n }\n\n const url = `${this.baseUrl}/api/product/${encodeURIComponent(id)}`;\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new TrustRailsError(`Product not found: ${id}`, 404);\n }\n throw new TrustRailsError(\n `Get product failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const product = await response.json() as Product;\n return product;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Custom error class for TrustRails API errors\n */\nexport class TrustRailsError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public response?: any\n ) {\n super(message);\n this.name = 'TrustRailsError';\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, TrustRailsError);\n }\n }\n}\n","import { TrustRailsError } from './errors';\nimport type { LiteProduct, Product, SearchOptions, SearchResponse, TrustRailsConfig } from './types';\n\nconst DEFAULT_BASE_URL = 'https://trustrails.app';\n\n/**\n * TrustRails SDK\n *\n * @example\n * ```typescript\n * const trustrails = new TrustRails('your-api-key');\n * const results = await trustrails.search({ query: 'laptop', maxPrice: 1000 });\n * ```\n */\nexport class TrustRails {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n /**\n * Creates a new TrustRails SDK instance\n *\n * @param apiKey - Your TrustRails API key\n */\n constructor(apiKey: string);\n /**\n * Creates a new TrustRails SDK instance with custom configuration\n *\n * @param config - Configuration object with apiKey and optional baseUrl\n */\n constructor(config: TrustRailsConfig);\n constructor(apiKeyOrConfig: string | TrustRailsConfig) {\n if (typeof apiKeyOrConfig === 'string') {\n this.apiKey = apiKeyOrConfig;\n this.baseUrl = DEFAULT_BASE_URL;\n } else {\n this.apiKey = apiKeyOrConfig.apiKey;\n this.baseUrl = apiKeyOrConfig.baseUrl || DEFAULT_BASE_URL;\n }\n\n // Clean up baseUrl - remove trailing slash\n if (this.baseUrl.endsWith('/')) {\n this.baseUrl = this.baseUrl.slice(0, -1);\n }\n\n if (!this.apiKey) {\n throw new Error('apiKey is required');\n }\n }\n\n /**\n * Search for products. With `lite: true` the products are trimmed (see LiteProduct).\n * Search results carry every known `attributes` spec (with sources unless lite); for the retailer's description and every offer, call product(id).\n *\n * @param options - Search parameters\n * @returns Promise resolving to search results\n *\n * @example\n * ```typescript\n * const results = await client.search({\n * query: 'USB-C charger',\n * brand: 'Anker',\n * minPrice: 20,\n * maxPrice: 50,\n * category: 'Cables & Chargers'\n * });\n * ```\n */\n search(options: SearchOptions & { lite: true }): Promise<SearchResponse<LiteProduct>>;\n search(options?: SearchOptions & { lite?: false }): Promise<SearchResponse>;\n search(options: SearchOptions): Promise<SearchResponse<Product | LiteProduct>>;\n async search(options: SearchOptions = {}): Promise<SearchResponse<Product | LiteProduct>> {\n const url = new URL(`${this.baseUrl}/api/search`);\n\n if (options.query) {\n url.searchParams.set('query', options.query);\n }\n\n if (options.minPrice) {\n url.searchParams.set('min_price', options.minPrice.toString());\n }\n\n if (options.maxPrice) {\n url.searchParams.set('max_price', options.maxPrice.toString());\n }\n\n if (options.brand) {\n url.searchParams.set('brand', options.brand);\n }\n\n if (options.category) {\n url.searchParams.set('category', options.category);\n }\n\n if (options.constraints) {\n url.searchParams.set('constraints', JSON.stringify(options.constraints));\n }\n\n if (options.lite) {\n url.searchParams.set('lite', 'true');\n }\n\n if (options.limit) {\n url.searchParams.set('limit', options.limit.toString());\n }\n\n if (options.sort) {\n url.searchParams.set('sort', options.sort);\n }\n\n try {\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new TrustRailsError(\n `Search failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const data = await response.json() as SearchResponse<Product | LiteProduct>;\n return data;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n\n /**\n * Get full details for a single product. Returns complete specs, description,\n * availability, delivery time, and every retailer offer. Use after search() for\n * your final 1-3 picks, and only for what search() lacks (the description's details, every offer):\n * compare specs from the attributes search() already returns.\n *\n * @param id - The product ID\n * @returns Promise resolving to complete product details\n *\n * @example\n * ```typescript\n * const product = await client.product('prod_123');\n * ```\n */\n async product(id: string): Promise<Product> {\n if (!id) {\n throw new Error('Product ID is required');\n }\n\n const url = `${this.baseUrl}/api/product/${encodeURIComponent(id)}`;\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new TrustRailsError(`Product not found: ${id}`, 404);\n }\n throw new TrustRailsError(\n `Get product failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const product = await response.json() as Product;\n return product;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n}\n"]}
package/dist/index.mjs CHANGED
@@ -29,24 +29,6 @@ var TrustRails = class {
29
29
  throw new Error("apiKey is required");
30
30
  }
31
31
  }
32
- /**
33
- * Search for products. Returns summary data (title, price, availability, category).
34
- * For full technical specs, call product(id) with the product ID.
35
- *
36
- * @param options - Search parameters
37
- * @returns Promise resolving to search results
38
- *
39
- * @example
40
- * ```typescript
41
- * const results = await client.search({
42
- * query: 'USB-C charger',
43
- * brand: 'Anker',
44
- * minPrice: 20,
45
- * maxPrice: 50,
46
- * category: 'Chargers'
47
- * });
48
- * ```
49
- */
50
32
  async search(options = {}) {
51
33
  const url = new URL(`${this.baseUrl}/api/search`);
52
34
  if (options.query) {
@@ -64,6 +46,9 @@ var TrustRails = class {
64
46
  if (options.category) {
65
47
  url.searchParams.set("category", options.category);
66
48
  }
49
+ if (options.constraints) {
50
+ url.searchParams.set("constraints", JSON.stringify(options.constraints));
51
+ }
67
52
  if (options.lite) {
68
53
  url.searchParams.set("lite", "true");
69
54
  }
@@ -101,8 +86,9 @@ var TrustRails = class {
101
86
  }
102
87
  /**
103
88
  * Get full details for a single product. Returns complete specs, description,
104
- * stock level, delivery time, and retailer source. Use after search() for
105
- * detailed comparison or recommendations.
89
+ * availability, delivery time, and every retailer offer. Use after search() for
90
+ * your final 1-3 picks, and only for what search() lacks (the description's details, every offer):
91
+ * compare specs from the attributes search() already returns.
106
92
  *
107
93
  * @param id - The product ID
108
94
  * @returns Promise resolving to complete product details
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Custom error class for TrustRails API errors\n */\nexport class TrustRailsError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public response?: any\n ) {\n super(message);\n this.name = 'TrustRailsError';\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, TrustRailsError);\n }\n }\n}\n","import { TrustRailsError } from './errors';\nimport type { Product, SearchOptions, SearchResponse, TrustRailsConfig } from './types';\n\nconst DEFAULT_BASE_URL = 'https://trustrails.app';\n\n/**\n * TrustRails SDK\n *\n * @example\n * ```typescript\n * const trustrails = new TrustRails('your-api-key');\n * const results = await trustrails.search({ query: 'laptop', maxPrice: 1000 });\n * ```\n */\nexport class TrustRails {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n /**\n * Creates a new TrustRails SDK instance\n *\n * @param apiKey - Your TrustRails API key\n */\n constructor(apiKey: string);\n /**\n * Creates a new TrustRails SDK instance with custom configuration\n *\n * @param config - Configuration object with apiKey and optional baseUrl\n */\n constructor(config: TrustRailsConfig);\n constructor(apiKeyOrConfig: string | TrustRailsConfig) {\n if (typeof apiKeyOrConfig === 'string') {\n this.apiKey = apiKeyOrConfig;\n this.baseUrl = DEFAULT_BASE_URL;\n } else {\n this.apiKey = apiKeyOrConfig.apiKey;\n this.baseUrl = apiKeyOrConfig.baseUrl || DEFAULT_BASE_URL;\n }\n\n // Clean up baseUrl - remove trailing slash\n if (this.baseUrl.endsWith('/')) {\n this.baseUrl = this.baseUrl.slice(0, -1);\n }\n\n if (!this.apiKey) {\n throw new Error('apiKey is required');\n }\n }\n\n /**\n * Search for products. Returns summary data (title, price, availability, category).\n * For full technical specs, call product(id) with the product ID.\n *\n * @param options - Search parameters\n * @returns Promise resolving to search results\n *\n * @example\n * ```typescript\n * const results = await client.search({\n * query: 'USB-C charger',\n * brand: 'Anker',\n * minPrice: 20,\n * maxPrice: 50,\n * category: 'Chargers'\n * });\n * ```\n */\n async search(options: SearchOptions = {}): Promise<SearchResponse> {\n const url = new URL(`${this.baseUrl}/api/search`);\n\n if (options.query) {\n url.searchParams.set('query', options.query);\n }\n\n if (options.minPrice) {\n url.searchParams.set('min_price', options.minPrice.toString());\n }\n\n if (options.maxPrice) {\n url.searchParams.set('max_price', options.maxPrice.toString());\n }\n\n if (options.brand) {\n url.searchParams.set('brand', options.brand);\n }\n\n if (options.category) {\n url.searchParams.set('category', options.category);\n }\n\n if (options.lite) {\n url.searchParams.set('lite', 'true');\n }\n\n if (options.limit) {\n url.searchParams.set('limit', options.limit.toString());\n }\n\n if (options.sort) {\n url.searchParams.set('sort', options.sort);\n }\n\n try {\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new TrustRailsError(\n `Search failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const data = await response.json() as SearchResponse;\n return data;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n\n /**\n * Get full details for a single product. Returns complete specs, description,\n * stock level, delivery time, and retailer source. Use after search() for\n * detailed comparison or recommendations.\n *\n * @param id - The product ID\n * @returns Promise resolving to complete product details\n *\n * @example\n * ```typescript\n * const product = await client.product('prod_123');\n * ```\n */\n async product(id: string): Promise<Product> {\n if (!id) {\n throw new Error('Product ID is required');\n }\n\n const url = `${this.baseUrl}/api/product/${encodeURIComponent(id)}`;\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new TrustRailsError(`Product not found: ${id}`, 404);\n }\n throw new TrustRailsError(\n `Get product failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const product = await response.json() as Product;\n return product;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Custom error class for TrustRails API errors\n */\nexport class TrustRailsError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public response?: any\n ) {\n super(message);\n this.name = 'TrustRailsError';\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, TrustRailsError);\n }\n }\n}\n","import { TrustRailsError } from './errors';\nimport type { LiteProduct, Product, SearchOptions, SearchResponse, TrustRailsConfig } from './types';\n\nconst DEFAULT_BASE_URL = 'https://trustrails.app';\n\n/**\n * TrustRails SDK\n *\n * @example\n * ```typescript\n * const trustrails = new TrustRails('your-api-key');\n * const results = await trustrails.search({ query: 'laptop', maxPrice: 1000 });\n * ```\n */\nexport class TrustRails {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n /**\n * Creates a new TrustRails SDK instance\n *\n * @param apiKey - Your TrustRails API key\n */\n constructor(apiKey: string);\n /**\n * Creates a new TrustRails SDK instance with custom configuration\n *\n * @param config - Configuration object with apiKey and optional baseUrl\n */\n constructor(config: TrustRailsConfig);\n constructor(apiKeyOrConfig: string | TrustRailsConfig) {\n if (typeof apiKeyOrConfig === 'string') {\n this.apiKey = apiKeyOrConfig;\n this.baseUrl = DEFAULT_BASE_URL;\n } else {\n this.apiKey = apiKeyOrConfig.apiKey;\n this.baseUrl = apiKeyOrConfig.baseUrl || DEFAULT_BASE_URL;\n }\n\n // Clean up baseUrl - remove trailing slash\n if (this.baseUrl.endsWith('/')) {\n this.baseUrl = this.baseUrl.slice(0, -1);\n }\n\n if (!this.apiKey) {\n throw new Error('apiKey is required');\n }\n }\n\n /**\n * Search for products. With `lite: true` the products are trimmed (see LiteProduct).\n * Search results carry every known `attributes` spec (with sources unless lite); for the retailer's description and every offer, call product(id).\n *\n * @param options - Search parameters\n * @returns Promise resolving to search results\n *\n * @example\n * ```typescript\n * const results = await client.search({\n * query: 'USB-C charger',\n * brand: 'Anker',\n * minPrice: 20,\n * maxPrice: 50,\n * category: 'Cables & Chargers'\n * });\n * ```\n */\n search(options: SearchOptions & { lite: true }): Promise<SearchResponse<LiteProduct>>;\n search(options?: SearchOptions & { lite?: false }): Promise<SearchResponse>;\n search(options: SearchOptions): Promise<SearchResponse<Product | LiteProduct>>;\n async search(options: SearchOptions = {}): Promise<SearchResponse<Product | LiteProduct>> {\n const url = new URL(`${this.baseUrl}/api/search`);\n\n if (options.query) {\n url.searchParams.set('query', options.query);\n }\n\n if (options.minPrice) {\n url.searchParams.set('min_price', options.minPrice.toString());\n }\n\n if (options.maxPrice) {\n url.searchParams.set('max_price', options.maxPrice.toString());\n }\n\n if (options.brand) {\n url.searchParams.set('brand', options.brand);\n }\n\n if (options.category) {\n url.searchParams.set('category', options.category);\n }\n\n if (options.constraints) {\n url.searchParams.set('constraints', JSON.stringify(options.constraints));\n }\n\n if (options.lite) {\n url.searchParams.set('lite', 'true');\n }\n\n if (options.limit) {\n url.searchParams.set('limit', options.limit.toString());\n }\n\n if (options.sort) {\n url.searchParams.set('sort', options.sort);\n }\n\n try {\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new TrustRailsError(\n `Search failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const data = await response.json() as SearchResponse<Product | LiteProduct>;\n return data;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n\n /**\n * Get full details for a single product. Returns complete specs, description,\n * availability, delivery time, and every retailer offer. Use after search() for\n * your final 1-3 picks, and only for what search() lacks (the description's details, every offer):\n * compare specs from the attributes search() already returns.\n *\n * @param id - The product ID\n * @returns Promise resolving to complete product details\n *\n * @example\n * ```typescript\n * const product = await client.product('prod_123');\n * ```\n */\n async product(id: string): Promise<Product> {\n if (!id) {\n throw new Error('Product ID is required');\n }\n\n const url = `${this.baseUrl}/api/product/${encodeURIComponent(id)}`;\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new TrustRailsError(`Product not found: ${id}`, 404);\n }\n throw new TrustRailsError(\n `Get product failed: ${response.statusText}`,\n response.status,\n await response.text()\n );\n }\n\n const product = await response.json() as Product;\n return product;\n } catch (error) {\n if (error instanceof TrustRailsError) {\n throw error;\n }\n throw new TrustRailsError(\n `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustrails/sdk",
3
- "version": "0.4.10",
3
+ "version": "0.6.0",
4
4
  "description": "Official TypeScript SDK for TrustRails API",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",