@minipim/sdk 0.4.1 → 0.5.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
@@ -130,6 +130,43 @@ const all = await collectAll<Product>(pim, '/v1/products');
130
130
 
131
131
  Endpoints that return a **plain array** instead of the envelope (`/v1/categories` without `?limit=`, `/v1/attributes`, `/v1/products/{id}/variants`) are handled too (v0.3.0+): the array is treated as the one-and-only page, so `collectAll` works uniformly across every list endpoint.
132
132
 
133
+ ## Listing by category, and by price (v0.5.0+)
134
+
135
+ Two things storefronts almost always need, and previously had to do in memory.
136
+
137
+ **Category subtrees.** `categoryId` on its own is **self-only** — it matches products filed directly against that category. Catalogs commonly assign products to leaf categories, so a parent category can legitimately return an empty page. Add `includeDescendants` for the whole subtree:
138
+
139
+ ```ts
140
+ // Everything anywhere under "Backdrops", in ONE paginated query.
141
+ const { data } = await pim.GET('/v1/products', {
142
+ query: { categoryId, includeDescendants: true, limit: 50 },
143
+ });
144
+ ```
145
+
146
+ Don't fetch `/v1/categories`, resolve descendants yourself and issue one request per id — this is a single round trip and it paginates as one result set.
147
+
148
+ **Sorting and filtering by price.** There is no `sortBy: 'price'`, because a catalog's price attribute code is its own data (`price`, `msrp`, `list_price`, …). Name it:
149
+
150
+ ```ts
151
+ // Cheapest first
152
+ await pim.GET('/v1/products', {
153
+ query: { sortBy: 'attribute', sortAttribute: 'price', sortDir: 'asc' },
154
+ });
155
+
156
+ // $10.00–$50.00 — money bounds are INTEGER CENTS
157
+ await pim.GET('/v1/products', {
158
+ query: { filterAttribute: 'price', filterMin: 1000, filterMax: 5000 },
159
+ });
160
+ ```
161
+
162
+ - Works on `money`, `number` and `decimal` attributes. `measurement` is refused — its values carry a unit, so ordering raw amounts would rank 5 g above 2 kg.
163
+ - For `money`, bounds and ordering use `amount_cents`. **`filterMin: 1000` is $10.00.** No currency conversion — mixed-currency catalogs compare by number.
164
+ - The value read is the one at the **default scope** (no locale, no channel). Products with no value sort **last in both directions** and are excluded by either bound: no price, not a price of zero.
165
+ - An unknown code, or one of a non-orderable type, is a **422 naming the problem**. It never degrades to name ordering, so you can't get a plausible page that isn't sorted the way you asked.
166
+ - `sortAttribute` and `filterAttribute` are independent; set both for "cheapest first, within a budget".
167
+
168
+ Needs API **0.9.0+**. Check `GET /healthz`.
169
+
133
170
  ## Attribute helpers
134
171
 
135
172
  Attribute values are `unknown` and keyed by `(locale, channel)`. List responses return the raw `{ code: [{ locale, channel, value }] }` shape (only product *detail* with `?locale=&channel=` returns a flat `resolvedAttributes`). The SDK ships the flatten + coercion helpers so you don't reimplement them:
@@ -1468,6 +1468,51 @@ interface paths {
1468
1468
  * Iterate by reading `hasMore`. Pass `?withTotal=true` to add a `total`
1469
1469
  * field (costs one COUNT query). Default `limit` is 50, **max 200**.
1470
1470
  * Reconcile after downtime with `?updatedSince=<iso8601>`.
1471
+ *
1472
+ * ### Filtering by category
1473
+ *
1474
+ * `?categoryId=` is **self-only**: it matches products assigned directly to
1475
+ * that category and nothing filed beneath it. Many catalogs assign products
1476
+ * only to leaf categories, so a self-only filter on a parent legitimately
1477
+ * returns zero rows.
1478
+ *
1479
+ * Add `?includeDescendants=true` to match the category **and its entire
1480
+ * subtree**, resolved server-side in a single query:
1481
+ *
1482
+ * ```bash
1483
+ * # Every product anywhere under "Backdrops", paginated normally
1484
+ * curl 'https://api.minipim.com/v1/products?categoryId=8d2a1f3c-...&includeDescendants=true&limit=50'
1485
+ * ```
1486
+ *
1487
+ * Prefer this over fetching the tree and issuing one request per descendant —
1488
+ * it is one round trip, and it paginates as a single result set.
1489
+ *
1490
+ * ### Sorting and filtering by price (or any numeric attribute)
1491
+ *
1492
+ * Price is not a column — it is an attribute, and its **code is per-organization
1493
+ * data**, so there is no `sortBy=price`. Name the attribute instead:
1494
+ *
1495
+ * ```bash
1496
+ * # Cheapest first
1497
+ * curl '.../v1/products?sortBy=attribute&sortAttribute=price&sortDir=asc'
1498
+ *
1499
+ * # Between $10.00 and $50.00 — bounds are INTEGER CENTS for money attributes
1500
+ * curl '.../v1/products?filterAttribute=price&filterMin=1000&filterMax=5000'
1501
+ * ```
1502
+ *
1503
+ * - Works on `money`, `number` and `decimal` attributes. `measurement` is
1504
+ * refused: its values carry a unit, so ordering raw amounts would rank 5 g
1505
+ * above 2 kg.
1506
+ * - For `money`, bounds and ordering use `amount_cents`. **`filterMin=1000`
1507
+ * is $10.00.** No currency conversion happens — mixed-currency catalogs are
1508
+ * compared by number, so scope the query to one currency if that matters.
1509
+ * - The value read is the one at the **default scope** (no locale, no channel).
1510
+ * A product with no default-scope value sorts **last** in both directions and
1511
+ * is excluded by either bound — it has no price, rather than a price of zero.
1512
+ * - Unknown code, or a code of the wrong type, is a **422** naming the problem.
1513
+ * It never silently degrades to `name` ordering.
1514
+ * - `sortAttribute` and `filterAttribute` are independent; set both to the
1515
+ * same code for the usual "cheapest first, within a budget" query.
1471
1516
  */
1472
1517
  get: {
1473
1518
  parameters: {
@@ -1476,15 +1521,20 @@ interface paths {
1476
1521
  q?: string;
1477
1522
  familyId?: string;
1478
1523
  categoryId?: string;
1524
+ includeDescendants?: boolean | ("true" | "false" | "1" | "0");
1479
1525
  brand?: string;
1480
1526
  tag?: string | string[];
1481
1527
  connectorId?: string;
1482
1528
  updatedSince?: string;
1483
- sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance";
1529
+ sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance" | "attribute";
1530
+ sortAttribute?: string;
1531
+ filterAttribute?: string;
1532
+ filterMin?: number;
1533
+ filterMax?: number;
1484
1534
  sortDir?: "asc" | "desc";
1485
1535
  limit?: number;
1486
1536
  offset?: number;
1487
- withTotal?: boolean;
1537
+ withTotal?: boolean | ("true" | "false" | "1" | "0");
1488
1538
  locale?: string;
1489
1539
  channel?: string;
1490
1540
  };
@@ -1662,11 +1712,16 @@ interface paths {
1662
1712
  q?: string;
1663
1713
  familyId?: string;
1664
1714
  categoryId?: string;
1715
+ includeDescendants?: boolean | ("true" | "false" | "1" | "0");
1665
1716
  brand?: string;
1666
1717
  tag?: string | string[];
1667
1718
  connectorId?: string;
1668
1719
  updatedSince?: string;
1669
- sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance";
1720
+ sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance" | "attribute";
1721
+ sortAttribute?: string;
1722
+ filterAttribute?: string;
1723
+ filterMin?: number;
1724
+ filterMax?: number;
1670
1725
  sortDir?: "asc" | "desc";
1671
1726
  locale?: string;
1672
1727
  channel?: string;
@@ -3979,8 +4034,13 @@ interface paths {
3979
4034
  * ```
3980
4035
  *
3981
4036
  * To get a single category's full ancestor path, walk `parentId` upwards
3982
- * client-side, or hit `GET /v1/categories/{id}` which returns the same row
3983
- * shape with the resolved path included in `path`.
4037
+ * client-side this endpoint returns the whole tree in one call, so no extra
4038
+ * requests are needed. (`GET /v1/categories/{id}` returns the same row shape
4039
+ * for one category; it does **not** resolve an ancestor path.)
4040
+ *
4041
+ * To list the products under a category *including* its subtree, use
4042
+ * `GET /v1/products?categoryId=<id>&includeDescendants=true` rather than
4043
+ * resolving descendants here and issuing one request per id.
3984
4044
  *
3985
4045
  * Returns the full tree by default. `?limit=` / `?offset=` are supported
3986
4046
  * for large taxonomies (ordered by `position`); omit them to get everything.
package/dist/openapi.d.ts CHANGED
@@ -1468,6 +1468,51 @@ interface paths {
1468
1468
  * Iterate by reading `hasMore`. Pass `?withTotal=true` to add a `total`
1469
1469
  * field (costs one COUNT query). Default `limit` is 50, **max 200**.
1470
1470
  * Reconcile after downtime with `?updatedSince=<iso8601>`.
1471
+ *
1472
+ * ### Filtering by category
1473
+ *
1474
+ * `?categoryId=` is **self-only**: it matches products assigned directly to
1475
+ * that category and nothing filed beneath it. Many catalogs assign products
1476
+ * only to leaf categories, so a self-only filter on a parent legitimately
1477
+ * returns zero rows.
1478
+ *
1479
+ * Add `?includeDescendants=true` to match the category **and its entire
1480
+ * subtree**, resolved server-side in a single query:
1481
+ *
1482
+ * ```bash
1483
+ * # Every product anywhere under "Backdrops", paginated normally
1484
+ * curl 'https://api.minipim.com/v1/products?categoryId=8d2a1f3c-...&includeDescendants=true&limit=50'
1485
+ * ```
1486
+ *
1487
+ * Prefer this over fetching the tree and issuing one request per descendant —
1488
+ * it is one round trip, and it paginates as a single result set.
1489
+ *
1490
+ * ### Sorting and filtering by price (or any numeric attribute)
1491
+ *
1492
+ * Price is not a column — it is an attribute, and its **code is per-organization
1493
+ * data**, so there is no `sortBy=price`. Name the attribute instead:
1494
+ *
1495
+ * ```bash
1496
+ * # Cheapest first
1497
+ * curl '.../v1/products?sortBy=attribute&sortAttribute=price&sortDir=asc'
1498
+ *
1499
+ * # Between $10.00 and $50.00 — bounds are INTEGER CENTS for money attributes
1500
+ * curl '.../v1/products?filterAttribute=price&filterMin=1000&filterMax=5000'
1501
+ * ```
1502
+ *
1503
+ * - Works on `money`, `number` and `decimal` attributes. `measurement` is
1504
+ * refused: its values carry a unit, so ordering raw amounts would rank 5 g
1505
+ * above 2 kg.
1506
+ * - For `money`, bounds and ordering use `amount_cents`. **`filterMin=1000`
1507
+ * is $10.00.** No currency conversion happens — mixed-currency catalogs are
1508
+ * compared by number, so scope the query to one currency if that matters.
1509
+ * - The value read is the one at the **default scope** (no locale, no channel).
1510
+ * A product with no default-scope value sorts **last** in both directions and
1511
+ * is excluded by either bound — it has no price, rather than a price of zero.
1512
+ * - Unknown code, or a code of the wrong type, is a **422** naming the problem.
1513
+ * It never silently degrades to `name` ordering.
1514
+ * - `sortAttribute` and `filterAttribute` are independent; set both to the
1515
+ * same code for the usual "cheapest first, within a budget" query.
1471
1516
  */
1472
1517
  get: {
1473
1518
  parameters: {
@@ -1476,15 +1521,20 @@ interface paths {
1476
1521
  q?: string;
1477
1522
  familyId?: string;
1478
1523
  categoryId?: string;
1524
+ includeDescendants?: boolean | ("true" | "false" | "1" | "0");
1479
1525
  brand?: string;
1480
1526
  tag?: string | string[];
1481
1527
  connectorId?: string;
1482
1528
  updatedSince?: string;
1483
- sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance";
1529
+ sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance" | "attribute";
1530
+ sortAttribute?: string;
1531
+ filterAttribute?: string;
1532
+ filterMin?: number;
1533
+ filterMax?: number;
1484
1534
  sortDir?: "asc" | "desc";
1485
1535
  limit?: number;
1486
1536
  offset?: number;
1487
- withTotal?: boolean;
1537
+ withTotal?: boolean | ("true" | "false" | "1" | "0");
1488
1538
  locale?: string;
1489
1539
  channel?: string;
1490
1540
  };
@@ -1662,11 +1712,16 @@ interface paths {
1662
1712
  q?: string;
1663
1713
  familyId?: string;
1664
1714
  categoryId?: string;
1715
+ includeDescendants?: boolean | ("true" | "false" | "1" | "0");
1665
1716
  brand?: string;
1666
1717
  tag?: string | string[];
1667
1718
  connectorId?: string;
1668
1719
  updatedSince?: string;
1669
- sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance";
1720
+ sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance" | "attribute";
1721
+ sortAttribute?: string;
1722
+ filterAttribute?: string;
1723
+ filterMin?: number;
1724
+ filterMax?: number;
1670
1725
  sortDir?: "asc" | "desc";
1671
1726
  locale?: string;
1672
1727
  channel?: string;
@@ -3979,8 +4034,13 @@ interface paths {
3979
4034
  * ```
3980
4035
  *
3981
4036
  * To get a single category's full ancestor path, walk `parentId` upwards
3982
- * client-side, or hit `GET /v1/categories/{id}` which returns the same row
3983
- * shape with the resolved path included in `path`.
4037
+ * client-side this endpoint returns the whole tree in one call, so no extra
4038
+ * requests are needed. (`GET /v1/categories/{id}` returns the same row shape
4039
+ * for one category; it does **not** resolve an ancestor path.)
4040
+ *
4041
+ * To list the products under a category *including* its subtree, use
4042
+ * `GET /v1/products?categoryId=<id>&includeDescendants=true` rather than
4043
+ * resolving descendants here and issuing one request per id.
3984
4044
  *
3985
4045
  * Returns the full tree by default. `?limit=` / `?offset=` are supported
3986
4046
  * for large taxonomies (ordered by `position`); omit them to get everything.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minipim/sdk",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Typed TypeScript client for the MiniPim API.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/Epic-Design-Labs/minipim",