@minipim/sdk 0.1.0 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ This project is licensed under the GNU Affero General Public License v3.0.
9
+ The full text of the license is available at:
10
+ https://www.gnu.org/licenses/agpl-3.0.txt
11
+
12
+ Copyright (c) 2026 Epic Design Labs
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
package/README.md CHANGED
@@ -8,7 +8,9 @@ Typed TypeScript client for the [MiniPim](https://github.com/Epic-Design-Labs/mi
8
8
  pnpm add @minipim/sdk
9
9
  ```
10
10
 
11
- Requires Node 18+ (for native `fetch`) or a fetch polyfill.
11
+ Requires Node 18+ (for native `fetch`) or a fetch polyfill. Ships **both ESM and CommonJS** builds (v0.3.0+) — `import` and `require` both work, including in CJS test tooling. The root entry imports no `node:` modules, so `createMinipimClient` and the attribute/pagination helpers are safe in Edge runtimes.
12
+
13
+ `baseUrl` is the **origin** (`https://api.minipim.com`); every endpoint path already includes `/v1/...`, so you pass `pim.GET('/v1/products')`, not a pre-joined URL.
12
14
 
13
15
  ## Quick start
14
16
 
@@ -74,7 +76,8 @@ import { verifyMinipimWebhook } from '@minipim/sdk/webhook';
74
76
  export async function POST(req: Request) {
75
77
  const raw = await req.text();
76
78
  const sig = req.headers.get('x-minipim-signature-256') ?? '';
77
- const ok = await verifyMinipimWebhook({
79
+ // Node verifier is synchronous — no await.
80
+ const ok = verifyMinipimWebhook({
78
81
  rawBody: raw,
79
82
  signature: sig,
80
83
  secret: process.env.MINIPIM_WEBHOOK_SECRET!,
@@ -92,36 +95,76 @@ export async function POST(req: Request) {
92
95
  ### Edge / Cloudflare Workers / Vercel Edge
93
96
 
94
97
  ```ts
95
- import { verifyMinipimWebhookEdge } from '@minipim/sdk/webhook';
98
+ import { verifyMinipimWebhookEdge } from '@minipim/sdk/webhook-edge';
96
99
 
97
100
  const ok = await verifyMinipimWebhookEdge({ rawBody, signature, secret });
101
+ if (!ok) return new Response('invalid signature', { status: 401 });
98
102
  ```
99
103
 
100
104
  Both functions:
101
105
  - Constant-time comparison (safe against timing attacks).
102
106
  - Return `false` on malformed signatures (don't throw).
103
- - Same interface; the only difference is which crypto API they use under the hood.
107
+
108
+ **Node verifier is synchronous; Edge verifier is async.** The Node one (`@minipim/sdk/webhook`) returns `boolean` — use it directly in `if (!verifyMinipimWebhook(...))`. The Edge one (`@minipim/sdk/webhook-edge`) returns `Promise<boolean>` — you must `await` it. They live on separate entry points so the Node `node:crypto` import never lands in an Edge bundle.
104
109
 
105
110
  ## Pagination
106
111
 
107
- Every list endpoint returns `{ data, limit, offset, hasMore, total? }`. Iterate via `hasMore`:
112
+ Don't hand-roll the `hasMore` loop — the SDK ships `paginate`, a generic async iterator over any list endpoint:
108
113
 
109
114
  ```ts
110
- async function* allProducts() {
111
- let offset = 0;
112
- for (;;) {
113
- const { data, error } = await pim.GET('/v1/products', {
114
- params: { query: { limit: 200, offset } },
115
- });
116
- if (error) throw new Error(error.error.message);
117
- for (const p of data.data) yield p;
118
- if (!data.hasMore) return;
119
- offset += 200;
120
- }
115
+ import { paginate, collectAll } from '@minipim/sdk';
116
+ import type { components } from '@minipim/sdk';
117
+
118
+ type Product = components['schemas']['ProductSelect'];
119
+
120
+ // stream
121
+ for await (const p of paginate<Product>(pim, '/v1/products', { query: { status: 'active' } })) {
122
+ await ingest(p);
121
123
  }
124
+
125
+ // or collect everything
126
+ const all = await collectAll<Product>(pim, '/v1/products');
127
+ ```
128
+
129
+ `paginate` manages `limit`/`offset`, honors the server's `hasMore`, and defaults to the 200-row max page size (override with `pageSize`). Pass `withTotal: true` in `query` if you need `total` — it adds one `COUNT(*)`, so leave it off on hot reads.
130
+
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
+
133
+ ## Attribute helpers
134
+
135
+ 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:
136
+
137
+ ```ts
138
+ import {
139
+ flattenAttributes, getAttribute, asMoney, asMeasurement, formatMoney,
140
+ } from '@minipim/sdk';
141
+
142
+ // resolve one code for a (locale, channel), with server-matching fallback
143
+ const desc = getAttribute(product.attributes, 'description', { locale: 'en_US', channel: 'headless-main' });
144
+
145
+ // flatten the whole bag (client-side equivalent of resolvedAttributes, for lists)
146
+ const flat = flattenAttributes(product.attributes, { locale: 'en_US', channel: 'headless-main' });
147
+
148
+ // coerce money / measurement shapes
149
+ const price = asMoney(getAttribute(product.attributes, 'price')); // { amount_cents, currency } | null
150
+ if (price) console.log(formatMoney(price)); // "$18.99"
151
+ const weight = asMeasurement(getAttribute(product.attributes, 'weight')); // { amount, unit } | null
122
152
  ```
123
153
 
124
- Pass `withTotal: true` if you need `data.total` — adds one `COUNT(*)` query, so leave it off on hot reads.
154
+ Also available at the `@minipim/sdk/attributes` subpath.
155
+
156
+ ## Error handling
157
+
158
+ `res.error` is a per-path union that's awkward to narrow. Use the shared guard:
159
+
160
+ ```ts
161
+ import { isMinipimError, getErrorMessage } from '@minipim/sdk';
162
+
163
+ const { data, error } = await pim.GET('/v1/products/{id}', { params: { path: { id } } });
164
+ if (error) {
165
+ throw new Error(getErrorMessage(error, 'fetch failed')); // pulls error.error.message
166
+ }
167
+ ```
125
168
 
126
169
  ## Reconciliation after downtime
127
170
 
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/attributes.ts
21
+ var attributes_exports = {};
22
+ __export(attributes_exports, {
23
+ asMeasurement: () => asMeasurement,
24
+ asMoney: () => asMoney,
25
+ flattenAttributes: () => flattenAttributes,
26
+ formatMoney: () => formatMoney,
27
+ getAttribute: () => getAttribute
28
+ });
29
+ module.exports = __toCommonJS(attributes_exports);
30
+ function getAttribute(attributes, code, opts = {}) {
31
+ const records = attributes[code];
32
+ if (!records || records.length === 0) return void 0;
33
+ const locale = opts.locale ?? null;
34
+ const channel = opts.channel ?? null;
35
+ const layers = [
36
+ [locale, channel],
37
+ [locale, null],
38
+ [null, channel],
39
+ [null, null]
40
+ ];
41
+ for (const [l, c] of layers) {
42
+ const hit = records.find((r) => r.locale === l && r.channel === c);
43
+ if (hit) return hit.value;
44
+ }
45
+ return void 0;
46
+ }
47
+ function flattenAttributes(attributes, opts = {}) {
48
+ const out = {};
49
+ for (const code of Object.keys(attributes)) {
50
+ const v = getAttribute(attributes, code, opts);
51
+ if (v !== void 0) out[code] = v;
52
+ }
53
+ return out;
54
+ }
55
+ function asMoney(value) {
56
+ if (value && typeof value === "object" && typeof value.amount_cents === "number" && typeof value.currency === "string") {
57
+ return value;
58
+ }
59
+ return null;
60
+ }
61
+ function asMeasurement(value) {
62
+ if (value && typeof value === "object" && typeof value.amount === "number" && typeof value.unit === "string") {
63
+ return value;
64
+ }
65
+ return null;
66
+ }
67
+ function formatMoney(money, locale = "en-US") {
68
+ return new Intl.NumberFormat(locale, {
69
+ style: "currency",
70
+ currency: money.currency
71
+ }).format(money.amount_cents / 100);
72
+ }
73
+ // Annotate the CommonJS export names for ESM import in node:
74
+ 0 && (module.exports = {
75
+ asMeasurement,
76
+ asMoney,
77
+ flattenAttributes,
78
+ formatMoney,
79
+ getAttribute
80
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Attribute helpers. MiniPim stores attribute values keyed by
3
+ * `(locale, channel)` — list responses always return the raw
4
+ * `{ code: [{ locale, channel, value }] }` shape (only product *detail*
5
+ * with `?locale=&channel=` returns a flat `resolvedAttributes`). So every
6
+ * consumer ends up writing the same flatten + money/measurement coercions.
7
+ * These are those helpers, typed.
8
+ */
9
+ interface AttributeValueRecord {
10
+ locale: string | null;
11
+ channel: string | null;
12
+ value: unknown;
13
+ }
14
+ type AttributesPayload = Record<string, AttributeValueRecord[]>;
15
+ interface Money {
16
+ amount_cents: number;
17
+ currency: string;
18
+ }
19
+ interface Measurement {
20
+ amount: number;
21
+ unit: string;
22
+ }
23
+ interface ResolveOptions {
24
+ locale?: string | null;
25
+ channel?: string | null;
26
+ }
27
+ /**
28
+ * Resolve a single attribute code to its value for a (locale, channel).
29
+ * Fallback order mirrors the server: exact `(locale, channel)` →
30
+ * `(locale, null)` → `(null, channel)` → `(null, null)`. Returns
31
+ * `undefined` when nothing matches.
32
+ */
33
+ declare function getAttribute(attributes: AttributesPayload, code: string, opts?: ResolveOptions): unknown;
34
+ /**
35
+ * Flatten an entire attribute payload to a `{ code: value }` map for a
36
+ * (locale, channel) — the client-side equivalent of the server's
37
+ * `resolvedAttributes`, for use on list responses where the server doesn't
38
+ * flatten. Codes with no matching value are omitted.
39
+ */
40
+ declare function flattenAttributes(attributes: AttributesPayload, opts?: ResolveOptions): Record<string, unknown>;
41
+ /** Narrow an attribute value to Money, or null if it isn't money-shaped. */
42
+ declare function asMoney(value: unknown): Money | null;
43
+ /** Narrow an attribute value to Measurement, or null if it isn't measurement-shaped. */
44
+ declare function asMeasurement(value: unknown): Measurement | null;
45
+ /** Format integer-cents Money as a localized currency string. */
46
+ declare function formatMoney(money: Money, locale?: string): string;
47
+
48
+ export { type AttributeValueRecord, type AttributesPayload, type Measurement, type Money, type ResolveOptions, asMeasurement, asMoney, flattenAttributes, formatMoney, getAttribute };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Attribute helpers. MiniPim stores attribute values keyed by
3
+ * `(locale, channel)` — list responses always return the raw
4
+ * `{ code: [{ locale, channel, value }] }` shape (only product *detail*
5
+ * with `?locale=&channel=` returns a flat `resolvedAttributes`). So every
6
+ * consumer ends up writing the same flatten + money/measurement coercions.
7
+ * These are those helpers, typed.
8
+ */
9
+ interface AttributeValueRecord {
10
+ locale: string | null;
11
+ channel: string | null;
12
+ value: unknown;
13
+ }
14
+ type AttributesPayload = Record<string, AttributeValueRecord[]>;
15
+ interface Money {
16
+ amount_cents: number;
17
+ currency: string;
18
+ }
19
+ interface Measurement {
20
+ amount: number;
21
+ unit: string;
22
+ }
23
+ interface ResolveOptions {
24
+ locale?: string | null;
25
+ channel?: string | null;
26
+ }
27
+ /**
28
+ * Resolve a single attribute code to its value for a (locale, channel).
29
+ * Fallback order mirrors the server: exact `(locale, channel)` →
30
+ * `(locale, null)` → `(null, channel)` → `(null, null)`. Returns
31
+ * `undefined` when nothing matches.
32
+ */
33
+ declare function getAttribute(attributes: AttributesPayload, code: string, opts?: ResolveOptions): unknown;
34
+ /**
35
+ * Flatten an entire attribute payload to a `{ code: value }` map for a
36
+ * (locale, channel) — the client-side equivalent of the server's
37
+ * `resolvedAttributes`, for use on list responses where the server doesn't
38
+ * flatten. Codes with no matching value are omitted.
39
+ */
40
+ declare function flattenAttributes(attributes: AttributesPayload, opts?: ResolveOptions): Record<string, unknown>;
41
+ /** Narrow an attribute value to Money, or null if it isn't money-shaped. */
42
+ declare function asMoney(value: unknown): Money | null;
43
+ /** Narrow an attribute value to Measurement, or null if it isn't measurement-shaped. */
44
+ declare function asMeasurement(value: unknown): Measurement | null;
45
+ /** Format integer-cents Money as a localized currency string. */
46
+ declare function formatMoney(money: Money, locale?: string): string;
47
+
48
+ export { type AttributeValueRecord, type AttributesPayload, type Measurement, type Money, type ResolveOptions, asMeasurement, asMoney, flattenAttributes, formatMoney, getAttribute };
@@ -0,0 +1,51 @@
1
+ // src/attributes.ts
2
+ function getAttribute(attributes, code, opts = {}) {
3
+ const records = attributes[code];
4
+ if (!records || records.length === 0) return void 0;
5
+ const locale = opts.locale ?? null;
6
+ const channel = opts.channel ?? null;
7
+ const layers = [
8
+ [locale, channel],
9
+ [locale, null],
10
+ [null, channel],
11
+ [null, null]
12
+ ];
13
+ for (const [l, c] of layers) {
14
+ const hit = records.find((r) => r.locale === l && r.channel === c);
15
+ if (hit) return hit.value;
16
+ }
17
+ return void 0;
18
+ }
19
+ function flattenAttributes(attributes, opts = {}) {
20
+ const out = {};
21
+ for (const code of Object.keys(attributes)) {
22
+ const v = getAttribute(attributes, code, opts);
23
+ if (v !== void 0) out[code] = v;
24
+ }
25
+ return out;
26
+ }
27
+ function asMoney(value) {
28
+ if (value && typeof value === "object" && typeof value.amount_cents === "number" && typeof value.currency === "string") {
29
+ return value;
30
+ }
31
+ return null;
32
+ }
33
+ function asMeasurement(value) {
34
+ if (value && typeof value === "object" && typeof value.amount === "number" && typeof value.unit === "string") {
35
+ return value;
36
+ }
37
+ return null;
38
+ }
39
+ function formatMoney(money, locale = "en-US") {
40
+ return new Intl.NumberFormat(locale, {
41
+ style: "currency",
42
+ currency: money.currency
43
+ }).format(money.amount_cents / 100);
44
+ }
45
+ export {
46
+ asMeasurement,
47
+ asMoney,
48
+ flattenAttributes,
49
+ formatMoney,
50
+ getAttribute
51
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ asMeasurement: () => asMeasurement,
34
+ asMoney: () => asMoney,
35
+ collectAll: () => collectAll,
36
+ createMinipimClient: () => createMinipimClient,
37
+ flattenAttributes: () => flattenAttributes,
38
+ formatMoney: () => formatMoney,
39
+ getAttribute: () => getAttribute,
40
+ getErrorMessage: () => getErrorMessage,
41
+ isMinipimError: () => isMinipimError,
42
+ paginate: () => paginate
43
+ });
44
+ module.exports = __toCommonJS(src_exports);
45
+
46
+ // src/client.ts
47
+ var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
48
+ function createMinipimClient(opts) {
49
+ const headers = {
50
+ "x-organization-id": opts.organizationId,
51
+ ...opts.headers ?? {}
52
+ };
53
+ if (opts.apiKey) headers["Authorization"] = `Bearer ${opts.apiKey}`;
54
+ if (opts.userId) headers["x-pim-user-id"] = opts.userId;
55
+ return (0, import_openapi_fetch.default)({
56
+ baseUrl: opts.baseUrl.replace(/\/$/, ""),
57
+ fetch: opts.fetch,
58
+ headers
59
+ });
60
+ }
61
+
62
+ // src/paginate.ts
63
+ async function* paginate(client, path, opts = {}) {
64
+ const pageSize = opts.pageSize ?? 200;
65
+ let offset = 0;
66
+ for (; ; ) {
67
+ const get = client.GET;
68
+ const { data, error, response } = await get(path, {
69
+ params: { query: { ...opts.query ?? {}, limit: pageSize, offset } }
70
+ });
71
+ if (error || !data) {
72
+ throw new Error(
73
+ `paginate ${path} failed at offset ${offset} (HTTP ${response?.status})`
74
+ );
75
+ }
76
+ if (Array.isArray(data)) {
77
+ for (const item of data) yield item;
78
+ return;
79
+ }
80
+ if (!Array.isArray(data.data)) {
81
+ throw new Error(
82
+ `paginate ${path}: response is neither a list envelope nor an array \u2014 this endpoint may not be paginatable`
83
+ );
84
+ }
85
+ for (const item of data.data) yield item;
86
+ const more = data.hasMore ?? data.data.length === pageSize;
87
+ if (!more || data.data.length === 0) return;
88
+ offset += pageSize;
89
+ }
90
+ }
91
+ async function collectAll(client, path, opts = {}) {
92
+ const out = [];
93
+ for await (const item of paginate(client, path, opts)) out.push(item);
94
+ return out;
95
+ }
96
+
97
+ // src/errors.ts
98
+ function isMinipimError(value) {
99
+ if (!value || typeof value !== "object") return false;
100
+ const e = value.error;
101
+ return !!e && typeof e === "object" && typeof e.code === "string" && typeof e.message === "string";
102
+ }
103
+ function getErrorMessage(error, fallback = "request failed") {
104
+ return isMinipimError(error) ? error.error.message : fallback;
105
+ }
106
+
107
+ // src/attributes.ts
108
+ function getAttribute(attributes, code, opts = {}) {
109
+ const records = attributes[code];
110
+ if (!records || records.length === 0) return void 0;
111
+ const locale = opts.locale ?? null;
112
+ const channel = opts.channel ?? null;
113
+ const layers = [
114
+ [locale, channel],
115
+ [locale, null],
116
+ [null, channel],
117
+ [null, null]
118
+ ];
119
+ for (const [l, c] of layers) {
120
+ const hit = records.find((r) => r.locale === l && r.channel === c);
121
+ if (hit) return hit.value;
122
+ }
123
+ return void 0;
124
+ }
125
+ function flattenAttributes(attributes, opts = {}) {
126
+ const out = {};
127
+ for (const code of Object.keys(attributes)) {
128
+ const v = getAttribute(attributes, code, opts);
129
+ if (v !== void 0) out[code] = v;
130
+ }
131
+ return out;
132
+ }
133
+ function asMoney(value) {
134
+ if (value && typeof value === "object" && typeof value.amount_cents === "number" && typeof value.currency === "string") {
135
+ return value;
136
+ }
137
+ return null;
138
+ }
139
+ function asMeasurement(value) {
140
+ if (value && typeof value === "object" && typeof value.amount === "number" && typeof value.unit === "string") {
141
+ return value;
142
+ }
143
+ return null;
144
+ }
145
+ function formatMoney(money, locale = "en-US") {
146
+ return new Intl.NumberFormat(locale, {
147
+ style: "currency",
148
+ currency: money.currency
149
+ }).format(money.amount_cents / 100);
150
+ }
151
+ // Annotate the CommonJS export names for ESM import in node:
152
+ 0 && (module.exports = {
153
+ asMeasurement,
154
+ asMoney,
155
+ collectAll,
156
+ createMinipimClient,
157
+ flattenAttributes,
158
+ formatMoney,
159
+ getAttribute,
160
+ getErrorMessage,
161
+ isMinipimError,
162
+ paginate
163
+ });
@@ -0,0 +1,132 @@
1
+ import { Client } from 'openapi-fetch';
2
+ import { paths } from './openapi.cjs';
3
+ export { components, operations } from './openapi.cjs';
4
+ export { AttributeValueRecord, AttributesPayload, Measurement, Money, ResolveOptions, asMeasurement, asMoney, flattenAttributes, formatMoney, getAttribute } from './attributes.cjs';
5
+
6
+ /**
7
+ * Thin wrapper around `openapi-fetch` that pre-binds the tenancy header and
8
+ * (optionally) an API key. Returns the standard openapi-fetch client, so all
9
+ * its features (request interceptors, raw response access, etc.) work as
10
+ * documented at https://openapi-ts.dev/openapi-fetch/.
11
+ */
12
+
13
+ interface CreateMinipimClientOptions {
14
+ /**
15
+ * Base URL of the MiniPim API, e.g. `https://api.minipim.com`. Required.
16
+ * Trailing slashes are normalized.
17
+ */
18
+ baseUrl: string;
19
+ /**
20
+ * Tenant the client speaks for. Required — every authenticated endpoint
21
+ * needs `x-organization-id`. API keys identify the principal, not the
22
+ * tenant.
23
+ */
24
+ organizationId: string;
25
+ /**
26
+ * Bearer API key (e.g. `pim_abc123…`). Issue one in the admin under
27
+ * /api-keys. If you're running in dev with `PIM_AUTH=header`, omit this
28
+ * and set `userId` instead.
29
+ */
30
+ apiKey?: string;
31
+ /**
32
+ * Dev-only header (`x-pim-user-id`). Only set when the deployment is in
33
+ * `header` auth mode. Ignored in `jwt` / `clerk` deployments.
34
+ */
35
+ userId?: string;
36
+ /**
37
+ * Extra headers merged onto every request — useful for tracing IDs,
38
+ * feature flags, etc. Don't put auth here; use `apiKey`/`userId` so the
39
+ * names stay consistent.
40
+ */
41
+ headers?: Record<string, string>;
42
+ /**
43
+ * Override `fetch` — pass a polyfill in Node <18 or a wrapped fetch that
44
+ * adds timeouts, retries, telemetry. Defaults to the global `fetch`.
45
+ */
46
+ fetch?: typeof fetch;
47
+ }
48
+ type MinipimClient = Client<paths>;
49
+ /**
50
+ * Create a typed MiniPim client.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * import { createMinipimClient } from '@minipim/sdk';
55
+ *
56
+ * const pim = createMinipimClient({
57
+ * baseUrl: 'https://api.minipim.com',
58
+ * organizationId: '00000000-0000-0000-0000-000000000001',
59
+ * apiKey: process.env.MINIPIM_API_KEY!,
60
+ * });
61
+ *
62
+ * const { data, error } = await pim.GET('/v1/products', {
63
+ * params: { query: { limit: 50, categoryId: '…' } },
64
+ * });
65
+ * if (error) throw new Error(error.error.message);
66
+ * console.log(data.hasMore, data.data.length);
67
+ * ```
68
+ */
69
+ declare function createMinipimClient(opts: CreateMinipimClientOptions): MinipimClient;
70
+
71
+ /**
72
+ * Generic pagination helper. Every MiniPim list endpoint returns
73
+ * `{ data, limit, offset, hasMore }`; this walks the pages so consumers
74
+ * don't hand-roll the `hasMore` loop. Yields one item at a time.
75
+ *
76
+ * Typed loosely on purpose — openapi-fetch's per-path generics don't
77
+ * compose into a single reusable signature without a lot of conditional-
78
+ * type machinery, and the envelope shape is uniform across list endpoints.
79
+ * The item type is yours to specify via the generic.
80
+ */
81
+
82
+ interface PaginateOptions {
83
+ /** Query params (filters, sort, withTotal, etc). `limit`/`offset` are managed for you. */
84
+ query?: Record<string, unknown>;
85
+ /** Page size. Default 200 (the API max). */
86
+ pageSize?: number;
87
+ }
88
+ /**
89
+ * Async-iterate every item across all pages of a MiniPim list endpoint.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import { paginate } from '@minipim/sdk';
94
+ * for await (const product of paginate<Product>(pim, '/v1/products', {
95
+ * query: { status: 'active' },
96
+ * })) {
97
+ * await ingest(product);
98
+ * }
99
+ * ```
100
+ */
101
+ declare function paginate<T = unknown>(client: MinipimClient, path: string, opts?: PaginateOptions): AsyncGenerator<T, void, unknown>;
102
+ /** Collect every page into an array. Convenience around {@link paginate}. */
103
+ declare function collectAll<T = unknown>(client: MinipimClient, path: string, opts?: PaginateOptions): Promise<T[]>;
104
+
105
+ /**
106
+ * Error envelope + type guard. Every MiniPim 4xx/5xx returns
107
+ * `{ error: { code, message, details? } }`. openapi-fetch types `res.error`
108
+ * as a union across a path's declared error responses, which is awkward to
109
+ * narrow to `.error.message`. This guard collapses that to a single shape.
110
+ */
111
+ interface MinipimError {
112
+ error: {
113
+ code: string;
114
+ message: string;
115
+ details?: unknown;
116
+ };
117
+ }
118
+ /** True when `value` is a MiniPim `{ error: { code, message } }` envelope. */
119
+ declare function isMinipimError(value: unknown): value is MinipimError;
120
+ /**
121
+ * Pull a human-readable message out of an openapi-fetch `error` value,
122
+ * whatever its declared union shape. Falls back to `fallback`.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const { data, error } = await pim.GET('/v1/products');
127
+ * if (error) throw new Error(getErrorMessage(error, 'request failed'));
128
+ * ```
129
+ */
130
+ declare function getErrorMessage(error: unknown, fallback?: string): string;
131
+
132
+ export { type CreateMinipimClientOptions, type MinipimClient, type MinipimError, type PaginateOptions, collectAll, createMinipimClient, getErrorMessage, isMinipimError, paginate, paths };