@productcraft/core 0.3.0 → 0.4.1
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 +55 -15
- package/dist/index.cjs +832 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +397 -13
- package/dist/index.d.ts +397 -13
- package/dist/index.js +797 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,25 +1,65 @@
|
|
|
1
1
|
# @productcraft/core
|
|
2
2
|
|
|
3
|
-
|
|
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**.
|
|
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
|
-
|
|
7
|
+
Requires **Node 20.3 or later** (also Bun, Deno and edge runtimes with `fetch` and `crypto.subtle`).
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
// A Fetch-style route handler (Next.js, Hono, Bun, Deno). With Express, use express.raw() and req.body.
|
|
49
|
+
export async function POST(req: Request): Promise<Response> {
|
|
50
|
+
const event = await verifyWebhook({ payload: await req.text(), headers: req.headers, secret: process.env.WEBHOOK_SECRET! });
|
|
51
|
+
console.log(event);
|
|
52
|
+
return new Response(null, { status: 204 });
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`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.
|
|
57
|
+
|
|
58
|
+
## For SDK authors
|
|
59
|
+
|
|
60
|
+
`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).
|
|
61
|
+
|
|
62
|
+
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
63
|
|
|
24
64
|
## License
|
|
25
65
|
|