@usethrottle/catalog 1.0.0 → 1.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/CHANGELOG.md +8 -0
- package/README.md +64 -7
- package/dist/admin.cjs +681 -0
- package/dist/admin.d.cts +660 -0
- package/dist/admin.d.ts +660 -0
- package/dist/admin.js +681 -0
- package/dist/{catalog-error-BWUoZCzY.d.ts → catalog-error-C_zLOtYA.d.ts} +3 -3
- package/dist/{catalog-error-DO6ANrGR.d.cts → catalog-error-s1BINNkt.d.cts} +3 -3
- package/dist/{chunk-ZFUELHUU.cjs → chunk-4ORXUW2U.cjs} +4 -2
- package/dist/{chunk-2PJXGAZF.js → chunk-JK2UY4CD.js} +3 -1
- package/dist/{client-BNtBTEyh.d.cts → client-BAoq6jmM.d.cts} +1 -1
- package/dist/{client-CHLvULRQ.d.ts → client-c2F-eCaL.d.ts} +1 -1
- package/dist/{types-DYfIvIaI.d.cts → contract-types-DhiThMEr.d.cts} +1 -272
- package/dist/{types-DYfIvIaI.d.ts → contract-types-DhiThMEr.d.ts} +1 -272
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/react.cjs +17 -17
- package/dist/react.d.cts +5 -4
- package/dist/react.d.ts +5 -4
- package/dist/react.js +1 -1
- package/dist/server.cjs +14 -13
- package/dist/server.d.cts +3 -2
- package/dist/server.d.ts +3 -2
- package/dist/server.js +3 -2
- package/dist/types-DsjoG8ce.d.cts +276 -0
- package/dist/types-xQLID-4j.d.ts +276 -0
- package/dist/url.d.cts +1 -0
- package/dist/url.d.ts +1 -0
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @usethrottle/catalog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1234c50: Add `@usethrottle/catalog/admin`, a server-only entry for the catalog push API: `createCatalogAdmin` (sync jobs, single batches, run and job polling, error and stock reports, archive, restore, rename, stock set and adjust) and `verifyCatalogWebhook`, typed from the generated push types. It is experimental: it is outside the 1.x freeze, so a breaking change to it is a minor release.
|
|
8
|
+
|
|
9
|
+
The category `depth` option now takes `1` to `10` (`CategoryTreeDepth`; it was `1 | 2 | 3`) on `categories.tree` and `useCategories`, and `listAllCategoryHandles` now lists every level of the tree. A call without `depth` still sends none.
|
|
10
|
+
|
|
3
11
|
## 1.0.0
|
|
4
12
|
|
|
5
13
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -36,16 +36,17 @@ Prices are integers in minor units (cents for USD). The client talks to `https:/
|
|
|
36
36
|
|
|
37
37
|
## Entries
|
|
38
38
|
|
|
39
|
-
| Import | What it holds
|
|
40
|
-
| ----------------------------- |
|
|
41
|
-
| `@usethrottle/catalog` | `createCatalogClient`, the error classes and `isCatalogError`, and the answer types. No DOM and no Node-only API.
|
|
42
|
-
| `@usethrottle/catalog/react` | `CatalogProvider`, the read hooks, `useCart` and `useSubmitReview` (marked `"use client"`).
|
|
43
|
-
| `@usethrottle/catalog/server` | `createServerCatalogClient`, `catalogTags`, `syncCatalogGeneration` and the paging helpers (`listAllProductHandles`, `staticProductParams`, `listAllCollectionHandles`, `listAllCategoryHandles`).
|
|
44
|
-
| `@usethrottle/catalog/url` | `parseShopQuery`, `serializeShopQuery`, `toProductsQuery`, `parseRange` and `formatRange`. Standalone: none of the client or React code (the ESM and CommonJS builds each load one tiny shared helper file).
|
|
39
|
+
| Import | What it holds |
|
|
40
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `@usethrottle/catalog` | `createCatalogClient`, the error classes and `isCatalogError`, and the answer types. No DOM and no Node-only API. |
|
|
42
|
+
| `@usethrottle/catalog/react` | `CatalogProvider`, the read hooks, `useCart` and `useSubmitReview` (marked `"use client"`). |
|
|
43
|
+
| `@usethrottle/catalog/server` | `createServerCatalogClient`, `catalogTags`, `syncCatalogGeneration` and the paging helpers (`listAllProductHandles`, `staticProductParams`, `listAllCollectionHandles`, `listAllCategoryHandles`: it asks for `CATEGORY_TREE_MAX_DEPTH` levels, 10, so a sitemap lists the whole tree). |
|
|
44
|
+
| `@usethrottle/catalog/url` | `parseShopQuery`, `serializeShopQuery`, `toProductsQuery`, `parseRange` and `formatRange`. Standalone: none of the client or React code (the ESM and CommonJS builds each load one tiny shared helper file). |
|
|
45
|
+
| `@usethrottle/catalog/admin` | **Experimental, server only.** `createCatalogAdmin` and `verifyCatalogWebhook` for the catalog push API (sync jobs, single batches, runs, products, stock), and the push request and answer types. See [Admin](#admin-server-only-experimental). |
|
|
45
46
|
|
|
46
47
|
Everything `/react` exports is a client reference in a server component, `SDK_VERSION` and `catalogQueryKey` included: in server code import `SDK_VERSION` from the root or `/server`, and call `catalogQueryKey` only in client code.
|
|
47
48
|
|
|
48
|
-
The client's namespaces: `products` (`list`, `get`, `related`), `filters.values`, `categories` (`tree`, `get`), `tags.list`, `collections` (`list`, `get`), `suggest`, `config`, `reviews` (`list`, `eligibility`, `submit`) and `carts` (`check`, `create`, `session`). `products.get` always asks for the full product; lists default to the lite projection (`fields: 'full'` or a list of keys changes it).
|
|
49
|
+
The client's namespaces: `products` (`list`, `get`, `related`), `filters.values`, `categories` (`tree`, `get`; `tree({ depth })` takes 1 to 10 levels, and a call without `depth` gets the storefront's default of 10), `tags.list`, `collections` (`list`, `get`), `suggest`, `config`, `reviews` (`list`, `eligibility`, `submit`) and `carts` (`check`, `create`, `session`). `products.get` always asks for the full product; lists default to the lite projection (`fields: 'full'` or a list of keys changes it).
|
|
49
50
|
|
|
50
51
|
## Keys
|
|
51
52
|
|
|
@@ -254,6 +255,62 @@ history.pushState(null, '', `?${next}`);
|
|
|
254
255
|
|
|
255
256
|
Ranges stay strings (`10..50`); `parseRange` and `formatRange` convert them.
|
|
256
257
|
|
|
258
|
+
## Admin (server only, experimental)
|
|
259
|
+
|
|
260
|
+
> **Experimental.** `@usethrottle/catalog/admin` is outside the 1.x freeze: a breaking change to it ships as a minor release, named in the changelog, until it is frozen. Every export carries a TSDoc `@experimental` tag.
|
|
261
|
+
|
|
262
|
+
`createCatalogAdmin` wraps the catalog push API (`/v1/catalog/jobs`, `/variants`, `/runs`, `/products`, `/inventory`). It holds a secret key (`pc_sk_`) or a vendor key (`pc_vk_`), so it throws `CatalogError` `SERVER_ONLY` when it is created in a browser environment, and `INVALID_ADMIN_KEY` for any other key (a publishable `pc_pk_` key gets its own message). The key is never part of an error message. Errors are the same classes as the rest of the package: `CatalogError` with the server's `code`, a `RateLimitedError` (with `retryAfterSeconds`) for every `429`, `TimeoutError` and `NetworkError`.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { createCatalogAdmin } from '@usethrottle/catalog/admin';
|
|
266
|
+
|
|
267
|
+
const admin = createCatalogAdmin({ key: process.env.CATALOG_ADMIN_KEY ?? '' });
|
|
268
|
+
|
|
269
|
+
// A whole feed as one all-or-nothing job: batches of up to 1,000 rows and 5 MB.
|
|
270
|
+
const job = await admin.jobs.run(rows, { mode: 'replace', fullSync: true });
|
|
271
|
+
const done = await job.wait(); // resolves when the job has finished; read done.failureCode
|
|
272
|
+
if (done.failureCode !== null) console.error(await job.errorsCsv());
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`jobs.run` takes any iterable or async iterable of rows, opens the job with the first batch, sends batches of at most `batchRows` (default and maximum 1,000) rows that also stay under 5 MB, closes the job and returns the handle. The rows of one product must be next to each other: they are never split across batches (so `replace` stays safe), and a product with more than 1,000 rows throws `INVALID_REQUEST`. A `429` (`PUSH_QUEUE_FULL`) waits `retryAfterSeconds`, then sends the same batch again with the same key, at most 3 times; after that the `RateLimitedError` is thrown. A job that was opened and then failed client-side is left open and expires after 24 hours, and its staged batches are discarded, never applied.
|
|
276
|
+
|
|
277
|
+
| Method | What it does |
|
|
278
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
279
|
+
| `jobs.open({ mode?, fullSync? })`, `jobs.get(id)`, `jobs.resume(id)` | A job handle: `send(rows, { idempotencyKey? })`, `close()`, `get({ includeBatches? })`, `wait({ intervalMs, timeoutMs, signal })`, `errorsCsv()`, `stockResults()`, `stockCsv()`. `resume` sends no request. |
|
|
280
|
+
| `jobs.run(rows, { mode?, fullSync?, batchRows? })` | Open, send, close, as above. |
|
|
281
|
+
| `batches.send(rows, { mode?, idempotencyKey? })` | One batch to `/v1/catalog/variants`, answered with a run. `mode` needs a vendor key; with a store key it throws `INVALID_REQUEST` (use `jobs.run`). |
|
|
282
|
+
| `runs.get(id)`, `runs.wait(id, options)`, `runs.errorsCsv(id)`, `runs.stockResults(id)`, `runs.stockCsv(id)` | Follow a run. `wait` resolves on `completed` or `failed`; a vendor run answer has `pushMode` beside `mode`. |
|
|
283
|
+
| `products.get(ref)`, `products.list({ cursor?, limit? })`, `products.archive(refs)`, `products.restore(refs)`, `products.rename(ref, handle)` | A product ref is exactly one of `{ externalId }`, `{ sku }` or `{ handle }`. |
|
|
284
|
+
| `inventory.set(rows)`, `inventory.adjust(rows, { idempotencyKey? })` | `PATCH /v1/catalog/inventory`. `adjust` sends every row with `quantityMode: 'adjust'`. |
|
|
285
|
+
|
|
286
|
+
**Stock results.** A push row that wrote a quantity records the result: `stockResults()` pages `…/stock` and yields `{ sequence, rowNumber, sku, externalId, quantity, sourceWins }` for each; `stockCsv()` returns the same as CSV.
|
|
287
|
+
|
|
288
|
+
**Idempotency.** A batch or an inventory request with an `adjust` row needs an `Idempotency-Key`, or the server answers `400 IDEMPOTENCY_KEY_REQUIRED`. The SDK generates a UUID for every job batch, vendor batch and `adjust` call that has none, and one retry after a network error reuses it, so a delta is never applied twice. A key you pass is 1 to 200 characters.
|
|
289
|
+
|
|
290
|
+
**Vendor batches.** A vendor key's `batches.send` takes canonical rows. A mapping profile cannot set `externalId`, so a vendor batch that needs `externalId` must not use one.
|
|
291
|
+
|
|
292
|
+
**Reports.** Job report row numbers are job-wide, not per batch. A job whose error or stock CSV is too large (above 900 parts) answers `409 REPORT_TOO_LARGE`; `stockResults()` pages, so read stock rows that way.
|
|
293
|
+
|
|
294
|
+
**Images.** Image copying stays on today's cron (about 20 per minute) and today's per-store resize budget (about 20k resizes a month, roughly 6,700 images). For a 120k-product feed most images are therefore served from the vendor's URL for months. The follow-up is a copy queue and a per-store resize cap (about $1.50 per 1,000 images, one-off).
|
|
295
|
+
|
|
296
|
+
**Webhooks.** `verifyCatalogWebhook` checks a catalog webhook with WebCrypto only. Pass the raw body, the `x-catalog-signature` header and the signing secret; it returns `true` or `false` and never throws. The header is `t=<seconds>,v1=<hex>` with one `t` and 1 to 8 `v1` digests (any match passes, so a secret can rotate), HMAC-SHA256 over `<t>.<rawBody>`, with a tolerance of 300 seconds.
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import {
|
|
300
|
+
verifyCatalogWebhook,
|
|
301
|
+
type PushWebhookEvent,
|
|
302
|
+
} from '@usethrottle/catalog/admin';
|
|
303
|
+
|
|
304
|
+
const ok = await verifyCatalogWebhook({
|
|
305
|
+
rawBody, // the body text exactly as received
|
|
306
|
+
signature: request.headers.get('x-catalog-signature') ?? '',
|
|
307
|
+
secret: process.env.CATALOG_WEBHOOK_SECRET ?? '',
|
|
308
|
+
});
|
|
309
|
+
if (ok) handle(JSON.parse(rawBody) as PushWebhookEvent);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Failed deliveries retry on a schedule whose entries are gaps after the previous attempt, about 44.6 hours in all.
|
|
313
|
+
|
|
257
314
|
## Module formats
|
|
258
315
|
|
|
259
316
|
The package ships ESM and CommonJS builds. Within one format, every entry shares one copy of the client and the error classes, so `instanceof` works across `/react`, `/server` and the root. An app that loads the package through both `import` and `require` gets two copies: `instanceof RateLimitedError` (or any other class) is then true only for errors from the same format. `isCatalogError` works across both, because it also checks a shared brand; use it, then branch on `error.code`.
|