@onagentic/master-data 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/README.md CHANGED
@@ -1,81 +1,276 @@
1
1
  # @onagentic/master-data
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@onagentic/master-data.svg)](https://www.npmjs.com/package/@onagentic/master-data)
4
+ [![Build](https://img.shields.io/badge/build-passing-brightgreen.svg)](#)
5
+ [![Tests](https://img.shields.io/badge/tests-100%25-brightgreen.svg)](#)
6
+
3
7
  Lightweight TypeScript client for fetching master data from a Strapi-compatible REST API.
4
8
 
5
- Supports token auth, pagination, field selection, group filtering, and runtime config updates.
9
+ Supports Bearer/custom auth, pagination, field selection, group filtering, auto-pagination, and runtime config updates.
10
+
11
+ ---
6
12
 
7
13
  ## Install
8
14
 
9
15
  ```bash
16
+ # npm
10
17
  npm install @onagentic/master-data
18
+
19
+ # yarn
20
+ yarn add @onagentic/master-data
21
+
22
+ # pnpm
23
+ pnpm add @onagentic/master-data
11
24
  ```
12
25
 
26
+ > Requires Node.js `>=18.0.0` (uses native `fetch`).
27
+
28
+ ---
29
+
13
30
  ## Quick start
14
31
 
15
32
  ```ts
16
- import { createClient } from '@onagentic/master-data';
33
+ import { createClient } from "@onagentic/master-data";
17
34
 
18
35
  const client = createClient({
19
- baseUrl: 'https://api.example.com',
36
+ baseUrl: "https://api.example.com",
20
37
  token: process.env.API_TOKEN!,
21
38
  });
22
39
 
23
- // Single page (default: page 1, pageSize 100, active only)
24
- const items = await client.getItems('MASTER_ECO_SERVICES');
40
+ // Fetch one page returns MasterDataItem[]
41
+ const items = await client.getItems("UNIQUE_KEY");
25
42
 
26
- // All pages automatically
27
- const all = await client.fetchAll('MASTER_ECO_SERVICES');
43
+ // Fetch ALL pages automatically — returns MasterDataItem[]
44
+ const all = await client.fetchAll("UNIQUE_KEY");
28
45
 
29
46
  // Full response with pagination meta
30
- const response = await client.fetch('MASTER_ECO_SERVICES', {
47
+ const response = await client.fetch("UNIQUE_KEY", {
31
48
  pagination: { page: 2, pageSize: 50 },
32
- sort: { field: 'label', order: 'asc' },
33
- fields: ['documentId', 'key', 'label'],
49
+ sort: { field: "label", order: "asc" },
50
+ fields: ["documentId", "key", "label"],
34
51
  activeOnly: false,
35
52
  });
36
- console.log(response.meta.pagination); // { page, pageSize, pageCount, total }
53
+ console.log(response.meta.pagination);
54
+ // { page: 2, pageSize: 50, pageCount: 4, total: 180 }
55
+ ```
56
+
57
+ ### CJS (CommonJS)
58
+
59
+ ```js
60
+ const { createClient } = require("@onagentic/master-data");
61
+ ```
62
+
63
+ ---
64
+
65
+ ## API
66
+
67
+ ### `createClient(config)`
68
+
69
+ Factory function. Returns a `MasterDataClient` instance.
70
+
71
+ ```ts
72
+ const client = createClient({ baseUrl: "...", token: "..." });
73
+ ```
74
+
75
+ ---
76
+
77
+ ### `MasterDataClient`
78
+
79
+ You can also instantiate the class directly:
80
+
81
+ ```ts
82
+ import { MasterDataClient } from "@onagentic/master-data";
83
+
84
+ const client = new MasterDataClient({ baseUrl: "...", token: "..." });
85
+ ```
86
+
87
+ #### `client.getItems(groupCode, options?)`
88
+
89
+ Fetch one page and return `MasterDataItem[]`.
90
+
91
+ ```ts
92
+ const items = await client.getItems("UNIQUE_KEY");
93
+ ```
94
+
95
+ #### `client.fetchAll(groupCode, options?)`
96
+
97
+ Auto-paginate all pages and return a flat `MasterDataItem[]`. Use `maxPages` to cap the number of pages fetched (default: `100`).
98
+
99
+ ```ts
100
+ const items = await client.fetchAll("UNIQUE_KEY", { activeOnly: false });
101
+ const capped = await client.fetchAll("UNIQUE_KEY", { maxPages: 10 });
102
+ ```
103
+
104
+ #### `client.fetch(groupCode, options?)`
105
+
106
+ Fetch one page and return the full `MasterDataResponse` (including `meta.pagination`).
107
+
108
+ ```ts
109
+ const { data, meta } = await client.fetch("UNIQUE_KEY", {
110
+ pagination: { page: 1, pageSize: 25 },
111
+ });
112
+ console.log(meta.pagination.total);
113
+ ```
114
+
115
+ #### `client.updateConfig(partial)`
116
+
117
+ Update config at runtime without creating a new client (e.g. after token refresh).
118
+
119
+ ```ts
120
+ client.updateConfig({ token: "new-token" });
37
121
  ```
38
122
 
123
+ ---
124
+
39
125
  ## Config options
40
126
 
41
- | Option | Type | Default | Description |
42
- |---|---|---|---|
43
- | `baseUrl` | `string` | **required** | API base URL |
44
- | `token` | `string` | **required** | Bearer token |
45
- | `apiPath` | `string` | `/api/master-datas` | API endpoint path |
46
- | `defaultFields` | `string[]` | `['documentId','key','label','order','is_active']` | Fields to request |
47
- | `defaultPageSize` | `number` | `100` | Items per page |
48
- | `defaultSortField` | `string` | `'order'` | Field to sort by |
49
- | `defaultSortOrder` | `'asc'\|'desc'` | `'asc'` | Sort direction |
50
- | `timeout` | `number` | `10000` | Request timeout (ms) |
127
+ | Option | Type | Default | Description |
128
+ | ------------------ | --------------------------- | ---------------------------------------------------- | ------------------------------------ |
129
+ | `baseUrl` | `string` | **required** | API base URL |
130
+ | `token` | `string` | | Bearer token (`Authorization` header)|
131
+ | `headers` | `Record<string, string>` | | Custom/additional request headers |
132
+ | `apiPath` | `string` | `/api/master-datas` | API endpoint path |
133
+ | `defaultFields` | `string[]` | `['documentId','key','label','order','is_active']` | Fields to request |
134
+ | `defaultPageSize` | `number` | `100` | Items per page |
135
+ | `defaultSortField` | `string` | `'order'` | Default sort field |
136
+ | `defaultSortOrder` | `'asc' \| 'desc'` | `'asc'` | Default sort direction |
137
+ | `timeout` | `number` | `10000` | Request timeout in milliseconds |
138
+
139
+ > `token` and `headers` can be used together. `headers` entries override the default `Authorization` header if you need a custom auth scheme (e.g. `{ Authorization: 'Token my-token' }` or `{ apikey: 'key' }`).
140
+
141
+ ---
51
142
 
52
143
  ## Fetch options
53
144
 
54
- | Option | Type | Default | Description |
55
- |---|---|---|---|
56
- | `activeOnly` | `boolean` | `true` | Filter `is_active = true` |
57
- | `fetchAll` | `boolean` | `false` | Auto-paginate all pages (use with `getItems`) |
58
- | `pagination` | `{ page, pageSize }` | | Override pagination |
59
- | `sort` | `{ field, order }` | — | Override sort |
60
- | `fields` | `string[]` | — | Override fields |
145
+ **`FetchOptions`** used by `fetch()` and `getItems()`
146
+
147
+ | Option | Type | Default | Description |
148
+ | ------------ | ---------------------- | ------- | ------------------------------------ |
149
+ | `activeOnly` | `boolean` | `true` | Filter `is_active = true` |
150
+ | `pagination` | `{ page?, pageSize? }` | — | Override pagination for this request |
151
+ | `sort` | `{ field?, order? }` | — | Override sort for this request |
152
+ | `fields` | `string[]` | — | Override fields for this request |
153
+
154
+ **`FetchAllOptions`** — extends `FetchOptions`, used by `fetchAll()`
155
+
156
+ | Option | Type | Default | Description |
157
+ | ---------- | -------- | ------- | ------------------------------------------------------ |
158
+ | `maxPages` | `number` | `100` | Max pages to fetch — safeguard against infinite loops |
159
+
160
+ ---
161
+
162
+ ## TypeScript types
163
+
164
+ All types are exported and can be imported directly:
165
+
166
+ ```ts
167
+ import type {
168
+ MasterDataClientConfig,
169
+ MasterDataItem,
170
+ MasterDataResponse,
171
+ PaginationMeta,
172
+ PaginationOptions,
173
+ SortOptions,
174
+ FetchOptions,
175
+ FetchAllOptions,
176
+ } from "@onagentic/master-data";
177
+
178
+ import {
179
+ MasterDataClient,
180
+ createClient,
181
+ MasterDataError,
182
+ ValidationError,
183
+ TimeoutError,
184
+ ApiError,
185
+ ResponseShapeError,
186
+ } from "@onagentic/master-data";
187
+ ```
188
+
189
+ #### `MasterDataItem`
190
+
191
+ ```ts
192
+ interface MasterDataItem {
193
+ documentId: string;
194
+ key: string;
195
+ label: string;
196
+ order: number;
197
+ is_active: boolean;
198
+ [key: string]: unknown; // extra fields from API
199
+ }
200
+ ```
201
+
202
+ #### `MasterDataResponse`
61
203
 
62
- ## Runtime config update
204
+ ```ts
205
+ interface MasterDataResponse {
206
+ data: MasterDataItem[];
207
+ meta: {
208
+ pagination: PaginationMeta;
209
+ };
210
+ }
211
+ ```
212
+
213
+ #### `PaginationMeta`
63
214
 
64
215
  ```ts
65
- // e.g. after refreshing a token
66
- client.updateConfig({ token: 'new-token' });
216
+ interface PaginationMeta {
217
+ page: number;
218
+ pageSize: number;
219
+ pageCount: number;
220
+ total: number;
221
+ }
67
222
  ```
68
223
 
224
+ ---
225
+
226
+ ## Error handling
227
+
228
+ All errors extend `MasterDataError` — use `instanceof` to branch by type:
229
+
230
+ ```ts
231
+ import { ApiError, TimeoutError, ValidationError, ResponseShapeError } from "@onagentic/master-data";
232
+
233
+ try {
234
+ const items = await client.getItems("UNIQUE_KEY");
235
+ } catch (err) {
236
+ if (err instanceof ApiError) {
237
+ console.error(`HTTP ${err.status}:`, err.body);
238
+ } else if (err instanceof TimeoutError) {
239
+ console.error(`Timed out after ${err.timeoutMs}ms`);
240
+ } else if (err instanceof ValidationError) {
241
+ console.error("Bad config:", err.message);
242
+ } else if (err instanceof ResponseShapeError) {
243
+ console.error("Unexpected API response:", err.message);
244
+ } else {
245
+ throw err;
246
+ }
247
+ }
248
+ ```
249
+
250
+ | Class | When thrown | Extra properties |
251
+ | -------------------- | -------------------------------------------------- | ------------------------- |
252
+ | `ValidationError` | `baseUrl` or `groupCode` is missing | — |
253
+ | `TimeoutError` | Request exceeds `timeout` ms | `.timeoutMs: number` |
254
+ | `ApiError` | HTTP response is not `2xx` | `.status`, `.body` |
255
+ | `ResponseShapeError` | Response JSON doesn't match expected shape | — |
256
+
257
+ ---
258
+
69
259
  ## Release
70
260
 
71
261
  ```bash
72
- # bump version
73
- npm version patch # or minor / major
262
+ # 1. Bump version
263
+ npm version patch # or: minor / major
74
264
 
75
- # build + test + publish
76
- npm publish --access public
265
+ # 2. Build, test, then publish
266
+ npm publish
77
267
  ```
78
268
 
269
+ > `prepublishOnly` runs `build` and `test` automatically before publish.
270
+ > No need for `--access public` — already configured in `package.json`.
271
+
272
+ ---
273
+
79
274
  ## License
80
275
 
81
276
  MIT
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- 'use strict';var m=["documentId","key","label","order","is_active"];var C="order";function D(a){return a.map(([t,e])=>`${encodeURIComponent(t).replace(/%5B/gi,"[").replace(/%5D/gi,"]").replace(/%24/gi,"$")}=${encodeURIComponent(e)}`).join("&")}function h(a,t,e){var d,f,p,g;let r=[],n=((d=t.sort)==null?void 0:d.field)??e.defaultSortField,o=((f=t.sort)==null?void 0:f.order)??e.defaultSortOrder;r.push([`sort[${n}]`,o]);let l=((p=t.pagination)==null?void 0:p.page)??1,i=((g=t.pagination)==null?void 0:g.pageSize)??e.defaultPageSize;r.push(["pagination[page]",String(l)]),r.push(["pagination[pageSize]",String(i)]);let s=t.fields??e.defaultFields;return r.push(["fields",s.join(",")]),r.push(["filters[$and][0][groups][code][$containsi]",a]),t.activeOnly!==false&&r.push(["filters[$and][1][is_active][$eq]","true"]),D(r)}function c(a){return {baseUrl:a.baseUrl.replace(/\/$/,""),token:a.token,apiPath:a.apiPath??"/api/master-datas",defaultFields:a.defaultFields??m,defaultPageSize:a.defaultPageSize??100,defaultSortOrder:a.defaultSortOrder??"asc",defaultSortField:a.defaultSortField??C,timeout:a.timeout??1e4}}var u=class{config;constructor(t){if(!t.baseUrl)throw new Error("[master-data] baseUrl is required");if(!t.token)throw new Error("[master-data] token is required");this.config=c(t);}async fetch(t,e={}){if(!t)throw new Error("[master-data] groupCode is required");let r=h(t,e,this.config),n=`${this.config.baseUrl}${this.config.apiPath}?${r}`,o=new AbortController,l=setTimeout(()=>o.abort(),this.config.timeout),i;try{i=await fetch(n,{method:"GET",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"},signal:o.signal});}catch(s){throw s.name==="AbortError"?new Error(`[master-data] Request timed out after ${this.config.timeout}ms`):s}finally{clearTimeout(l);}if(!i.ok){let s=await i.text().catch(()=>"");throw new Error(`[master-data] API error ${i.status} ${i.statusText}: ${s}`)}return i.json()}async fetchAll(t,e={}){var l;let r=((l=e.pagination)==null?void 0:l.pageSize)??this.config.defaultPageSize,n=1,o=[];for(;;){let i=await this.fetch(t,{...e,pagination:{page:n,pageSize:r}});o.push(...i.data);let{pageCount:s}=i.meta.pagination;if(n>=s)break;n++;}return o}async getItems(t,e={}){return e.fetchAll?this.fetchAll(t,e):(await this.fetch(t,e)).data}updateConfig(t){let e=c({...this.config,...t});Object.assign(this.config,e);}};function S(a){return new u(a)}exports.MasterDataClient=u;exports.createClient=S;
1
+ 'use strict';var l=class extends Error{constructor(t){super(`[master-data] ${t}`),this.name="MasterDataError";}},c=class extends l{constructor(t){super(t),this.name="ValidationError";}},d=class extends l{timeoutMs;constructor(t){super(`Request timed out after ${t}ms`),this.name="TimeoutError",this.timeoutMs=t;}},f=class extends l{status;body;constructor(t,a,r){super(`API error ${t} ${a}: ${r}`),this.name="ApiError",this.status=t,this.body=r;}},g=class extends l{constructor(t){super(`Unexpected response shape: ${t}`),this.name="ResponseShapeError";}};var E=["documentId","key","label","order","is_active"];var b="order";function F(e){return e.map(([t,a])=>`${encodeURIComponent(t).replace(/%5B/gi,"[").replace(/%5D/gi,"]").replace(/%24/gi,"$")}=${encodeURIComponent(a)}`).join("&")}function S(e,t,a){var s,m,D,C;let r=[],p=((s=t.sort)==null?void 0:s.field)??a.defaultSortField,u=((m=t.sort)==null?void 0:m.order)??a.defaultSortOrder;r.push([`sort[${p}]`,u]);let i=((D=t.pagination)==null?void 0:D.page)??1,n=((C=t.pagination)==null?void 0:C.pageSize)??a.defaultPageSize;r.push(["pagination[page]",String(i)]),r.push(["pagination[pageSize]",String(n)]);let o=t.fields??a.defaultFields;return r.push(["fields",o.join(",")]),r.push(["filters[$and][0][groups][code][$containsi]",e]),t.activeOnly!==false&&r.push(["filters[$and][1][is_active][$eq]","true"]),F(r)}function y(e){return {baseUrl:e.baseUrl.replace(/\/$/,""),token:e.token??"",headers:e.headers??{},apiPath:e.apiPath??"/api/master-datas",defaultFields:e.defaultFields??E,defaultPageSize:e.defaultPageSize??100,defaultSortOrder:e.defaultSortOrder??"asc",defaultSortField:e.defaultSortField??b,timeout:e.timeout??1e4}}var M=100,h=class{config;constructor(t){if(!t.baseUrl)throw new c("baseUrl is required");this.config=y(t);}async fetch(t,a={}){if(!t)throw new c("groupCode is required");let r=S(t,a,this.config),p=`${this.config.baseUrl}${this.config.apiPath}?${r}`,u=new AbortController,i=setTimeout(()=>u.abort(),this.config.timeout),n;try{let s={"Content-Type":"application/json",...this.config.token?{Authorization:`Bearer ${this.config.token}`}:{}};n=await fetch(p,{method:"GET",headers:{...s,...this.config.headers},signal:u.signal});}catch(s){throw s.name==="AbortError"?new d(this.config.timeout):s}finally{clearTimeout(i);}if(!n.ok){let s=await n.text().catch(()=>"");throw new f(n.status,n.statusText,s)}let o=await n.json();if(!R(o))throw new g("expected { data: [...], meta: { pagination: {...} } }");return o}async fetchAll(t,a={}){var o;let{maxPages:r=M,...p}=a,u=((o=p.pagination)==null?void 0:o.pageSize)??this.config.defaultPageSize,i=1,n=[];for(;i<=r;){let s=await this.fetch(t,{...p,pagination:{page:i,pageSize:u}});n.push(...s.data);let{pageCount:m}=s.meta.pagination;if(i>=m)break;i++;}return n}async getItems(t,a={}){return (await this.fetch(t,a)).data}updateConfig(t){this.config=y({...this.config,...t});}};function R(e){if(typeof e!="object"||e===null)return false;let t=e;if(!Array.isArray(t.data))return false;let a=t.meta;if(typeof a!="object"||a===null)return false;let r=a.pagination;return typeof r!="object"||r===null?false:typeof r.page=="number"&&typeof r.pageSize=="number"&&typeof r.pageCount=="number"&&typeof r.total=="number"}function T(e){return new h(e)}exports.ApiError=f;exports.MasterDataClient=h;exports.MasterDataError=l;exports.ResponseShapeError=g;exports.TimeoutError=d;exports.ValidationError=c;exports.createClient=T;
package/dist/index.d.cts CHANGED
@@ -1,8 +1,17 @@
1
1
  interface MasterDataClientConfig {
2
2
  /** Base URL of the API endpoint (e.g. https://api.example.com) */
3
3
  baseUrl: string;
4
- /** Bearer token for authentication */
5
- token: string;
4
+ /**
5
+ * Bearer token — sent as `Authorization: Bearer <token>`.
6
+ * Leave empty if your API uses a different auth method via `headers`.
7
+ */
8
+ token?: string;
9
+ /**
10
+ * Custom request headers. Use this to override the default auth header or add extra headers.
11
+ * @example { apikey: 'my-api-key' } // API key style
12
+ * @example { Authorization: 'Token my-token' } // custom auth scheme
13
+ */
14
+ headers?: Record<string, string>;
6
15
  /** API path prefix (default: /api/master-datas) */
7
16
  apiPath?: string;
8
17
  /** Default fields to fetch (default: documentId,key,label,order,is_active) */
@@ -31,8 +40,13 @@ interface FetchOptions {
31
40
  sort?: SortOptions;
32
41
  /** Override default fields */
33
42
  fields?: string[];
34
- /** Fetch all pages automatically */
35
- fetchAll?: boolean;
43
+ }
44
+ interface FetchAllOptions extends FetchOptions {
45
+ /**
46
+ * Maximum number of pages to fetch (default: 100).
47
+ * Acts as a safeguard against infinite loops when the API returns unexpected pagination data.
48
+ */
49
+ maxPages?: number;
36
50
  }
37
51
  interface MasterDataItem {
38
52
  documentId: string;
@@ -56,7 +70,7 @@ interface MasterDataResponse {
56
70
  }
57
71
 
58
72
  declare class MasterDataClient {
59
- private readonly config;
73
+ private config;
60
74
  constructor(config: MasterDataClientConfig);
61
75
  /**
62
76
  * Fetch one page of master data items for the given group code.
@@ -64,11 +78,11 @@ declare class MasterDataClient {
64
78
  fetch(groupCode: string, options?: FetchOptions): Promise<MasterDataResponse>;
65
79
  /**
66
80
  * Fetch ALL pages for the given group code and return a flat item array.
67
- * Uses the `fetchAll` flag or call this method directly.
68
81
  */
69
- fetchAll(groupCode: string, options?: Omit<FetchOptions, 'fetchAll'>): Promise<MasterDataItem[]>;
82
+ fetchAll(groupCode: string, options?: FetchAllOptions): Promise<MasterDataItem[]>;
70
83
  /**
71
84
  * Convenience: fetch items and return the data array (single page).
85
+ * Use `fetchAll` for automatic multi-page fetching.
72
86
  */
73
87
  getItems(groupCode: string, options?: FetchOptions): Promise<MasterDataItem[]>;
74
88
  /**
@@ -77,6 +91,47 @@ declare class MasterDataClient {
77
91
  updateConfig(partial: Partial<MasterDataClientConfig>): void;
78
92
  }
79
93
 
94
+ declare class MasterDataError extends Error {
95
+ constructor(message: string);
96
+ }
97
+ declare class ValidationError extends MasterDataError {
98
+ constructor(message: string);
99
+ }
100
+ declare class TimeoutError extends MasterDataError {
101
+ readonly timeoutMs: number;
102
+ constructor(timeoutMs: number);
103
+ }
104
+ declare class ApiError extends MasterDataError {
105
+ readonly status: number;
106
+ readonly body: string;
107
+ constructor(status: number, statusText: string, body: string);
108
+ }
109
+ declare class ResponseShapeError extends MasterDataError {
110
+ constructor(detail: string);
111
+ }
112
+
113
+ /**
114
+ * Convenience factory — create a configured client in one call.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * import { createClient, ApiError, TimeoutError } from '@onagentic/master-data';
119
+ *
120
+ * const client = createClient({
121
+ * baseUrl: 'https://api.example.com',
122
+ * token: process.env.API_TOKEN!,
123
+ * });
124
+ *
125
+ * try {
126
+ * const items = await client.getItems('UNIQUE_KEY');
127
+ * const all = await client.fetchAll('UNIQUE_KEY', { maxPages: 10 });
128
+ * } catch (err) {
129
+ * if (err instanceof ApiError) console.error('HTTP', err.status, err.body);
130
+ * else if (err instanceof TimeoutError) console.error('Timed out after', err.timeoutMs, 'ms');
131
+ * else throw err;
132
+ * }
133
+ * ```
134
+ */
80
135
  declare function createClient(config: MasterDataClientConfig): MasterDataClient;
81
136
 
82
- export { type FetchOptions, MasterDataClient, type MasterDataClientConfig, type MasterDataItem, type MasterDataResponse, type PaginationMeta, type PaginationOptions, type SortOptions, createClient };
137
+ export { ApiError, type FetchAllOptions, type FetchOptions, MasterDataClient, type MasterDataClientConfig, MasterDataError, type MasterDataItem, type MasterDataResponse, type PaginationMeta, type PaginationOptions, ResponseShapeError, type SortOptions, TimeoutError, ValidationError, createClient };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,17 @@
1
1
  interface MasterDataClientConfig {
2
2
  /** Base URL of the API endpoint (e.g. https://api.example.com) */
3
3
  baseUrl: string;
4
- /** Bearer token for authentication */
5
- token: string;
4
+ /**
5
+ * Bearer token — sent as `Authorization: Bearer <token>`.
6
+ * Leave empty if your API uses a different auth method via `headers`.
7
+ */
8
+ token?: string;
9
+ /**
10
+ * Custom request headers. Use this to override the default auth header or add extra headers.
11
+ * @example { apikey: 'my-api-key' } // API key style
12
+ * @example { Authorization: 'Token my-token' } // custom auth scheme
13
+ */
14
+ headers?: Record<string, string>;
6
15
  /** API path prefix (default: /api/master-datas) */
7
16
  apiPath?: string;
8
17
  /** Default fields to fetch (default: documentId,key,label,order,is_active) */
@@ -31,8 +40,13 @@ interface FetchOptions {
31
40
  sort?: SortOptions;
32
41
  /** Override default fields */
33
42
  fields?: string[];
34
- /** Fetch all pages automatically */
35
- fetchAll?: boolean;
43
+ }
44
+ interface FetchAllOptions extends FetchOptions {
45
+ /**
46
+ * Maximum number of pages to fetch (default: 100).
47
+ * Acts as a safeguard against infinite loops when the API returns unexpected pagination data.
48
+ */
49
+ maxPages?: number;
36
50
  }
37
51
  interface MasterDataItem {
38
52
  documentId: string;
@@ -56,7 +70,7 @@ interface MasterDataResponse {
56
70
  }
57
71
 
58
72
  declare class MasterDataClient {
59
- private readonly config;
73
+ private config;
60
74
  constructor(config: MasterDataClientConfig);
61
75
  /**
62
76
  * Fetch one page of master data items for the given group code.
@@ -64,11 +78,11 @@ declare class MasterDataClient {
64
78
  fetch(groupCode: string, options?: FetchOptions): Promise<MasterDataResponse>;
65
79
  /**
66
80
  * Fetch ALL pages for the given group code and return a flat item array.
67
- * Uses the `fetchAll` flag or call this method directly.
68
81
  */
69
- fetchAll(groupCode: string, options?: Omit<FetchOptions, 'fetchAll'>): Promise<MasterDataItem[]>;
82
+ fetchAll(groupCode: string, options?: FetchAllOptions): Promise<MasterDataItem[]>;
70
83
  /**
71
84
  * Convenience: fetch items and return the data array (single page).
85
+ * Use `fetchAll` for automatic multi-page fetching.
72
86
  */
73
87
  getItems(groupCode: string, options?: FetchOptions): Promise<MasterDataItem[]>;
74
88
  /**
@@ -77,6 +91,47 @@ declare class MasterDataClient {
77
91
  updateConfig(partial: Partial<MasterDataClientConfig>): void;
78
92
  }
79
93
 
94
+ declare class MasterDataError extends Error {
95
+ constructor(message: string);
96
+ }
97
+ declare class ValidationError extends MasterDataError {
98
+ constructor(message: string);
99
+ }
100
+ declare class TimeoutError extends MasterDataError {
101
+ readonly timeoutMs: number;
102
+ constructor(timeoutMs: number);
103
+ }
104
+ declare class ApiError extends MasterDataError {
105
+ readonly status: number;
106
+ readonly body: string;
107
+ constructor(status: number, statusText: string, body: string);
108
+ }
109
+ declare class ResponseShapeError extends MasterDataError {
110
+ constructor(detail: string);
111
+ }
112
+
113
+ /**
114
+ * Convenience factory — create a configured client in one call.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * import { createClient, ApiError, TimeoutError } from '@onagentic/master-data';
119
+ *
120
+ * const client = createClient({
121
+ * baseUrl: 'https://api.example.com',
122
+ * token: process.env.API_TOKEN!,
123
+ * });
124
+ *
125
+ * try {
126
+ * const items = await client.getItems('UNIQUE_KEY');
127
+ * const all = await client.fetchAll('UNIQUE_KEY', { maxPages: 10 });
128
+ * } catch (err) {
129
+ * if (err instanceof ApiError) console.error('HTTP', err.status, err.body);
130
+ * else if (err instanceof TimeoutError) console.error('Timed out after', err.timeoutMs, 'ms');
131
+ * else throw err;
132
+ * }
133
+ * ```
134
+ */
80
135
  declare function createClient(config: MasterDataClientConfig): MasterDataClient;
81
136
 
82
- export { type FetchOptions, MasterDataClient, type MasterDataClientConfig, type MasterDataItem, type MasterDataResponse, type PaginationMeta, type PaginationOptions, type SortOptions, createClient };
137
+ export { ApiError, type FetchAllOptions, type FetchOptions, MasterDataClient, type MasterDataClientConfig, MasterDataError, type MasterDataItem, type MasterDataResponse, type PaginationMeta, type PaginationOptions, ResponseShapeError, type SortOptions, TimeoutError, ValidationError, createClient };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var m=["documentId","key","label","order","is_active"];var C="order";function D(a){return a.map(([t,e])=>`${encodeURIComponent(t).replace(/%5B/gi,"[").replace(/%5D/gi,"]").replace(/%24/gi,"$")}=${encodeURIComponent(e)}`).join("&")}function h(a,t,e){var d,f,p,g;let r=[],n=((d=t.sort)==null?void 0:d.field)??e.defaultSortField,o=((f=t.sort)==null?void 0:f.order)??e.defaultSortOrder;r.push([`sort[${n}]`,o]);let l=((p=t.pagination)==null?void 0:p.page)??1,i=((g=t.pagination)==null?void 0:g.pageSize)??e.defaultPageSize;r.push(["pagination[page]",String(l)]),r.push(["pagination[pageSize]",String(i)]);let s=t.fields??e.defaultFields;return r.push(["fields",s.join(",")]),r.push(["filters[$and][0][groups][code][$containsi]",a]),t.activeOnly!==false&&r.push(["filters[$and][1][is_active][$eq]","true"]),D(r)}function c(a){return {baseUrl:a.baseUrl.replace(/\/$/,""),token:a.token,apiPath:a.apiPath??"/api/master-datas",defaultFields:a.defaultFields??m,defaultPageSize:a.defaultPageSize??100,defaultSortOrder:a.defaultSortOrder??"asc",defaultSortField:a.defaultSortField??C,timeout:a.timeout??1e4}}var u=class{config;constructor(t){if(!t.baseUrl)throw new Error("[master-data] baseUrl is required");if(!t.token)throw new Error("[master-data] token is required");this.config=c(t);}async fetch(t,e={}){if(!t)throw new Error("[master-data] groupCode is required");let r=h(t,e,this.config),n=`${this.config.baseUrl}${this.config.apiPath}?${r}`,o=new AbortController,l=setTimeout(()=>o.abort(),this.config.timeout),i;try{i=await fetch(n,{method:"GET",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"},signal:o.signal});}catch(s){throw s.name==="AbortError"?new Error(`[master-data] Request timed out after ${this.config.timeout}ms`):s}finally{clearTimeout(l);}if(!i.ok){let s=await i.text().catch(()=>"");throw new Error(`[master-data] API error ${i.status} ${i.statusText}: ${s}`)}return i.json()}async fetchAll(t,e={}){var l;let r=((l=e.pagination)==null?void 0:l.pageSize)??this.config.defaultPageSize,n=1,o=[];for(;;){let i=await this.fetch(t,{...e,pagination:{page:n,pageSize:r}});o.push(...i.data);let{pageCount:s}=i.meta.pagination;if(n>=s)break;n++;}return o}async getItems(t,e={}){return e.fetchAll?this.fetchAll(t,e):(await this.fetch(t,e)).data}updateConfig(t){let e=c({...this.config,...t});Object.assign(this.config,e);}};function S(a){return new u(a)}export{u as MasterDataClient,S as createClient};
1
+ var l=class extends Error{constructor(t){super(`[master-data] ${t}`),this.name="MasterDataError";}},c=class extends l{constructor(t){super(t),this.name="ValidationError";}},d=class extends l{timeoutMs;constructor(t){super(`Request timed out after ${t}ms`),this.name="TimeoutError",this.timeoutMs=t;}},f=class extends l{status;body;constructor(t,a,r){super(`API error ${t} ${a}: ${r}`),this.name="ApiError",this.status=t,this.body=r;}},g=class extends l{constructor(t){super(`Unexpected response shape: ${t}`),this.name="ResponseShapeError";}};var E=["documentId","key","label","order","is_active"];var b="order";function F(e){return e.map(([t,a])=>`${encodeURIComponent(t).replace(/%5B/gi,"[").replace(/%5D/gi,"]").replace(/%24/gi,"$")}=${encodeURIComponent(a)}`).join("&")}function S(e,t,a){var s,m,D,C;let r=[],p=((s=t.sort)==null?void 0:s.field)??a.defaultSortField,u=((m=t.sort)==null?void 0:m.order)??a.defaultSortOrder;r.push([`sort[${p}]`,u]);let i=((D=t.pagination)==null?void 0:D.page)??1,n=((C=t.pagination)==null?void 0:C.pageSize)??a.defaultPageSize;r.push(["pagination[page]",String(i)]),r.push(["pagination[pageSize]",String(n)]);let o=t.fields??a.defaultFields;return r.push(["fields",o.join(",")]),r.push(["filters[$and][0][groups][code][$containsi]",e]),t.activeOnly!==false&&r.push(["filters[$and][1][is_active][$eq]","true"]),F(r)}function y(e){return {baseUrl:e.baseUrl.replace(/\/$/,""),token:e.token??"",headers:e.headers??{},apiPath:e.apiPath??"/api/master-datas",defaultFields:e.defaultFields??E,defaultPageSize:e.defaultPageSize??100,defaultSortOrder:e.defaultSortOrder??"asc",defaultSortField:e.defaultSortField??b,timeout:e.timeout??1e4}}var M=100,h=class{config;constructor(t){if(!t.baseUrl)throw new c("baseUrl is required");this.config=y(t);}async fetch(t,a={}){if(!t)throw new c("groupCode is required");let r=S(t,a,this.config),p=`${this.config.baseUrl}${this.config.apiPath}?${r}`,u=new AbortController,i=setTimeout(()=>u.abort(),this.config.timeout),n;try{let s={"Content-Type":"application/json",...this.config.token?{Authorization:`Bearer ${this.config.token}`}:{}};n=await fetch(p,{method:"GET",headers:{...s,...this.config.headers},signal:u.signal});}catch(s){throw s.name==="AbortError"?new d(this.config.timeout):s}finally{clearTimeout(i);}if(!n.ok){let s=await n.text().catch(()=>"");throw new f(n.status,n.statusText,s)}let o=await n.json();if(!R(o))throw new g("expected { data: [...], meta: { pagination: {...} } }");return o}async fetchAll(t,a={}){var o;let{maxPages:r=M,...p}=a,u=((o=p.pagination)==null?void 0:o.pageSize)??this.config.defaultPageSize,i=1,n=[];for(;i<=r;){let s=await this.fetch(t,{...p,pagination:{page:i,pageSize:u}});n.push(...s.data);let{pageCount:m}=s.meta.pagination;if(i>=m)break;i++;}return n}async getItems(t,a={}){return (await this.fetch(t,a)).data}updateConfig(t){this.config=y({...this.config,...t});}};function R(e){if(typeof e!="object"||e===null)return false;let t=e;if(!Array.isArray(t.data))return false;let a=t.meta;if(typeof a!="object"||a===null)return false;let r=a.pagination;return typeof r!="object"||r===null?false:typeof r.page=="number"&&typeof r.pageSize=="number"&&typeof r.pageCount=="number"&&typeof r.total=="number"}function T(e){return new h(e)}export{f as ApiError,h as MasterDataClient,l as MasterDataError,g as ResponseShapeError,d as TimeoutError,c as ValidationError,T as createClient};
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@onagentic/master-data",
3
- "version": "1.0.0",
4
- "description": "Lightweight client for fetching master data from a Strapi-compatible API endpoint",
3
+ "version": "1.1.0",
4
+ "description": "Lightweight client for fetching master data from a API endpoint",
5
5
  "keywords": [
6
+ "onagentic",
7
+ "typescript",
6
8
  "master-data",
7
- "strapi",
8
9
  "api-client"
9
10
  ],
10
11
  "license": "MIT",
@@ -13,7 +14,11 @@
13
14
  ".": {
14
15
  "types": "./dist/index.d.ts",
15
16
  "import": "./dist/index.js",
16
- "require": "./dist/index.cjs"
17
+ "require": {
18
+ "types": "./dist/index.d.cts",
19
+ "default": "./dist/index.cjs"
20
+ },
21
+ "default": "./dist/index.js"
17
22
  }
18
23
  },
19
24
  "main": "./dist/index.cjs",
@@ -27,6 +32,10 @@
27
32
  "test": "node --experimental-vm-modules node_modules/.bin/jest",
28
33
  "prepublishOnly": "npm run build && npm test"
29
34
  },
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "registry": "https://registry.npmjs.org/"
38
+ },
30
39
  "devDependencies": {
31
40
  "@jest/globals": "^29.7.0",
32
41
  "@types/node": "^22.0.0",
@@ -37,9 +46,5 @@
37
46
  },
38
47
  "engines": {
39
48
  "node": ">=18.0.0"
40
- },
41
- "repository": {
42
- "type": "git",
43
- "url": "https://github.com/onagentic/master-data.git"
44
49
  }
45
50
  }