@productcraft/core 0.3.0 → 0.4.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
@@ -1,25 +1,61 @@
1
1
  # @productcraft/core
2
2
 
3
- Shared transport + auth primitives for the ProductCraft Node.js SDKs.
3
+ The shared runtime behind the ProductCraft Node.js SDKs: requests with retries, timeouts, automatic idempotency keys, typed errors, pagination, polling and webhook verification.
4
4
 
5
- You **don't install this directly**. It's a runtime dependency of every product SDK (`@productcraft/auth`, `@productcraft/mail`, `@productcraft/waitlist`, `@productcraft/social`, `@productcraft/platform-auth`) plus the `productcraft` umbrella. Installing one of those pulls `@productcraft/core` along automatically.
5
+ You **don't usually install this directly**. Every product SDK (`@productcraft/auth`, `@productcraft/mail`, `@productcraft/waitlist`, `@productcraft/social`, `@productcraft/trawl`, `@productcraft/platform-auth`) and the `productcraft` umbrella depend on it. You import from it for two things: the error classes (also re-exported by the product packages as they move to the generated surface) and `verifyWebhook`.
6
6
 
7
- ## What it provides
7
+ Requires **Node 20.3 or later** (also Bun, Deno and edge runtimes with `fetch` and `crypto.subtle`).
8
8
 
9
- - `PCAuth` — discriminated union covering the three credential shapes the SDKs accept:
10
- ```ts
11
- | { type: "apiKey"; key: string } // pcft_live_*
12
- | { type: "bearer"; token: string } // pre-resolved JWT
13
- | { type: "cookie"; value: string } // captured auth_token
14
- ```
15
- - `PCClientConfig` — `{ auth?, baseUrl?, fetch? }`, the input shape every surface constructor takes. When `auth` is omitted, `resolveAuth` falls back to `PRODUCTCRAFT_API_KEY` from the environment (server-side only; never read without a `process` global).
16
- - `PC_API_KEY_ENV` / `PC_AUTH_APP_ENV` / `readEnv` / `resolveAuth` — the env-fallback helpers. Hosts are never read from the environment.
17
- - `PC_BASE_URL` — the per-surface prod base URLs, used as defaults.
18
- - `makeClient<paths>(baseUrl, config)` — internal helper that wires an `openapi-fetch` client with the auth-header middleware.
9
+ ## Errors
19
10
 
20
- ## When you'd touch it directly
11
+ Every failure is a class you can catch. `instanceof` works across the ESM and CJS builds and across duplicate installs (each class is branded with `Symbol.for("productcraft.error.<Name>")`).
21
12
 
22
- Only if you're writing a new ProductCraft SDK surface package. Authoring custom backend integrations against ProductCraft APIs is what the per-product packages are for — start with one of those.
13
+ ```ts
14
+ import { APIError, NotFoundError, BadRequestError, RateLimitError } from "@productcraft/core";
15
+
16
+ try {
17
+ // any SDK call
18
+ } catch (err) {
19
+ if (err instanceof NotFoundError) console.log(err.code, err.requestId, err.docUrl);
20
+ else if (err instanceof BadRequestError) err.errors.forEach((e) => console.log(e.param, e.message));
21
+ else if (err instanceof RateLimitError) console.log(err.retryAfter); // seconds
22
+ else throw err;
23
+ }
24
+ ```
25
+
26
+ | Class | When |
27
+ |---|---|
28
+ | `ProductCraftError` | Base of everything below |
29
+ | `APIError` | Any non-2xx: `status`, `code`, `message`, `errors[]`, `details`, `docUrl`, `requestId`, `headers`, `body` |
30
+ | `BadRequestError` 400, `AuthenticationError` 401, `PermissionDeniedError` 403, `NotFoundError` 404, `ConflictError` 409, `IdempotencyKeyReuseError` 409 `IDEMPOTENCY_KEY_REUSE`, `UnprocessableEntityError` 422, `RateLimitError` 429, `InternalServerError` 5xx | By status |
31
+ | `APIConnectionError`, `APITimeoutError` | No response, or an attempt (or a poll) ran out of time |
32
+ | `APIUserAbortError` | Your `AbortSignal` fired |
33
+ | `ConfigurationError` | The client cannot work as configured (no `workspaceId`, a key in a browser, no webhook secret) |
34
+ | `WebhookVerificationError` | A webhook failed signature or timestamp checks |
35
+
36
+ ## Retries, timeouts and idempotency
37
+
38
+ - Up to 2 retries (`maxRetries`) on connection errors, 408, 409 `IDEMPOTENCY_IN_PROGRESS`, 429 and 500/502/503/504, with exponential backoff and full jitter (`min(8 s, 0.5 s × 2ⁿ)`). A `Retry-After` (seconds or HTTP date) or `retry-after-ms` of 60 s or less replaces the backoff; anything longer fails fast.
39
+ - **A POST or PATCH without an idempotency key is never retried**, not even on a connection error, because the request may already have run. Operations that accept an `Idempotency-Key` get one automatically (`pc_<uuid>`), reused on every retry; pass `idempotencyKey` to choose your own.
40
+ - Each attempt times out after 60 s (`timeout`). With the defaults the worst case is about 3 attempts × 60 s plus backoff, roughly 3 minutes. For an overall deadline pass `signal: AbortSignal.timeout(ms)`.
41
+ - Idempotency is enforced by the API in Redis. If Redis is unavailable the API still runs the request (fail-open, for availability), so a retry during such an outage can run twice.
42
+
43
+ ## Webhooks
44
+
45
+ ```ts
46
+ import { verifyWebhook } from "@productcraft/core";
47
+
48
+ // Express: app.post("/webhooks", express.raw({ type: "application/json" }), handler)
49
+ const event = await verifyWebhook({ payload: req.body, headers: req.headers, secret: process.env.WEBHOOK_SECRET! });
50
+ ```
51
+
52
+ `payload` must be the raw body (string, `Buffer`, `Uint8Array` or `ArrayBuffer`). `headers` can be Node `IncomingHttpHeaders` or Fetch `Headers`. `secret` can be a list (rotation, or several endpoint secrets). Verifies Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature`, `whsec_` secrets) and the older `X-<Product>-Signature: t=…,v1=…` scheme, with a 300 s tolerance (`toleranceSeconds`). Returns the parsed JSON payload.
53
+
54
+ ## For SDK authors
55
+
56
+ `new APIClient(options, defaultBaseUrl, { name, version })` is what generated product clients call: `request(spec, options)` returns an `APIPromise` (`.withResponse()`, `.asResponse()`), `list()` a `PagePromise` (`for await`, `.pages()`, `.toArray({ maxItems })`), plus `poll()`, `createAndPoll()` and `unwrapWebhook()`. `createOpenAPIClient<paths>(apiClient)` builds the typed `openapi-fetch` escape hatch (`.client`) over the same credential. The contract is section 4 of the [SDK standard](https://github.com/clauderanelagh/productcraft-node/blob/main/docs/sdk-standard.md).
57
+
58
+ The 0.x primitives stay exported: `PCAuth`, `PCClientConfig`, `PC_BASE_URL`, `PC_API_KEY_ENV`, `PC_AUTH_APP_ENV`, `readEnv`, `resolveAuth`, `authMiddleware` and `makeClient` (deprecated in favour of `APIClient` + `createOpenAPIClient`).
23
59
 
24
60
  ## License
25
61