@paragraphcms/client 2.1.0 → 2.2.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
@@ -10,7 +10,9 @@ Replace this block with the README image.
10
10
  </p>
11
11
  -->
12
12
 
13
- `@paragraphcms/client` is a small, typed SDK built on top of the standard Fetch API. It runs in Node.js 18+ and other server-side runtimes that expose `fetch`.
13
+ `@paragraphcms/client` is a small, typed SDK for the Paragraph CMS API. It runs in Node.js 18+ and other server-side runtimes that expose `fetch`.
14
+
15
+ Internally, the client uses `ky` for HTTP transport and retries, plus `Bottleneck` for per-instance rate limiting.
14
16
 
15
17
  ## Install
16
18
 
package/dist/client.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import type { AiContentResult, AiMetaDescriptionResult, AiMetaNameResult, ApiInfo, ClientOptions, Collection, CollectionListQuery, CollectionMutationResult, CreateCollectionRequest, CreateDataModelRequest, CreateLabelRequest, CreateLocaleRequest, CreatePageRequest, CreatePageTranslationRequest, CreateStatusRequest, DataModel, DataModelListQuery, DataModelMutationResult, DeleteByCodeResult, DeleteResult, GenerateContentRequest, GenerateMetaRequest, GetPageQuery, Label, LabelMutationResult, LabelListQuery, ListResponse, Locale, LocaleMutationResult, Media, MediaDeleteResult, MediaDetail, MediaListQuery, MediaMutationResult, MediaUploadResult, Member, MemberListQuery, Page, PageListQuery, PageSummary, PageMutationResult, PageRestoreResult, PermanentDeleteResult, ReorderLabelsRequest, ReorderResult, ReorderStatusesRequest, RequestOptions, Status, StatusListQuery, StatusMutationResult, UpdateCollectionRequest, UpdateDataModelRequest, UpdateLabelRequest, UpdateMediaRequest, UpdatePageRequest, UpdateStatusRequest, UploadMediaRequest } from "./types.js";
2
2
  export declare class Client {
3
3
  private readonly apiKey;
4
- private readonly fetchImpl;
4
+ private readonly http;
5
5
  private readonly defaultHeaders;
6
6
  private readonly timeoutMs?;
7
7
  private readonly maxRateLimitRetries;
8
- private readonly limiter;
9
8
  readonly pages: {
10
9
  list: (query?: PageListQuery, options?: RequestOptions) => Promise<ListResponse<PageSummary>>;
11
10
  create: (body?: CreatePageRequest, options?: RequestOptions) => Promise<PageMutationResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -1,15 +1,33 @@
1
+ import ky, { HTTPError, TimeoutError, } from "ky";
1
2
  import { ParagraphApiError, ParagraphClientError, } from "./errors.js";
2
3
  import { RequestRateLimiter } from "./rate-limiter.js";
3
4
  const API_BASE_URL = "https://api.paragraphcms.com/v1";
4
5
  const DEFAULT_REQUESTS_PER_SECOND = 5;
5
- const DEFAULT_RATE_LIMIT_RETRIES = 2;
6
- const DEFAULT_RATE_LIMIT_RETRY_DELAY_MS = 1000;
6
+ const DEFAULT_RATE_LIMIT_RETRIES = 3;
7
7
  const LOOKUP_PAGE_SIZE = 100;
8
8
  const PRESERVED_TRANSFORM_KEYS = new Set([
9
9
  "content",
10
10
  "editorNode",
11
11
  "fields",
12
12
  ]);
13
+ const RETRYABLE_METHODS = [
14
+ "get",
15
+ "post",
16
+ "patch",
17
+ "delete",
18
+ ];
19
+ const SAFE_TRANSIENT_RETRY_METHODS = new Set([
20
+ "GET",
21
+ "DELETE",
22
+ ]);
23
+ const RETRYABLE_STATUS_CODES = [
24
+ 429,
25
+ 500,
26
+ 502,
27
+ 503,
28
+ 504,
29
+ ];
30
+ const RETRY_AFTER_STATUS_CODES = [429, 503];
13
31
  function resolveFetchImplementation(customFetch) {
14
32
  const fetchImpl = customFetch ?? globalThis.fetch;
15
33
  if (typeof fetchImpl !== "function") {
@@ -111,81 +129,20 @@ function createRequestDescriptor(method, url) {
111
129
  url: url.toString(),
112
130
  };
113
131
  }
114
- function createRequestSignal(signal, timeoutMs) {
115
- if (!signal && (!timeoutMs || timeoutMs <= 0)) {
116
- return {
117
- signal: undefined,
118
- cleanup: () => { },
119
- didTimeout: () => false,
120
- };
121
- }
122
- const controller = new AbortController();
123
- let timedOut = false;
124
- let timeoutId;
125
- const onAbort = () => {
126
- controller.abort(signal?.reason);
127
- };
128
- if (signal) {
129
- if (signal.aborted) {
130
- controller.abort(signal.reason);
131
- }
132
- else {
133
- signal.addEventListener("abort", onAbort, { once: true });
134
- }
135
- }
136
- if (timeoutMs && timeoutMs > 0) {
137
- timeoutId = setTimeout(() => {
138
- timedOut = true;
139
- controller.abort(new Error(`Request timed out after ${timeoutMs}ms.`));
140
- }, timeoutMs);
141
- }
142
- return {
143
- signal: controller.signal,
144
- cleanup: () => {
145
- if (timeoutId !== undefined) {
146
- clearTimeout(timeoutId);
147
- }
148
- if (signal) {
149
- signal.removeEventListener("abort", onAbort);
150
- }
151
- },
152
- didTimeout: () => timedOut,
153
- };
132
+ function toKyInput(url) {
133
+ const path = url.pathname
134
+ .replace(/^\/v1/, "")
135
+ .replace(/^\/+/, "");
136
+ return `${path}${url.search}`;
154
137
  }
155
- function waitForDelay(delayMs, signal) {
156
- if (delayMs <= 0) {
157
- return Promise.resolve();
158
- }
159
- return new Promise((resolve, reject) => {
160
- let timeoutId;
161
- const cleanup = () => {
162
- if (timeoutId !== undefined) {
163
- clearTimeout(timeoutId);
164
- }
165
- if (signal) {
166
- signal.removeEventListener("abort", onAbort);
167
- }
168
- };
169
- const onAbort = () => {
170
- cleanup();
171
- reject(signal?.reason ??
172
- new DOMException("The operation was aborted.", "AbortError"));
173
- };
174
- if (signal) {
175
- if (signal.aborted) {
176
- onAbort();
177
- return;
178
- }
179
- signal.addEventListener("abort", onAbort, { once: true });
180
- }
181
- timeoutId = setTimeout(() => {
182
- cleanup();
183
- resolve();
184
- }, delayMs);
185
- });
138
+ function canRetryTransientRequest(method) {
139
+ return (typeof method === "string" &&
140
+ SAFE_TRANSIENT_RETRY_METHODS.has(method.toUpperCase()));
186
141
  }
187
142
  function isAbortError(error) {
188
- return (error instanceof DOMException && error.name === "AbortError");
143
+ return ((error instanceof DOMException &&
144
+ error.name === "AbortError") ||
145
+ (error instanceof Error && error.name === "AbortError"));
189
146
  }
190
147
  function resolveMaxRateLimitRetries(value, fallback = DEFAULT_RATE_LIMIT_RETRIES) {
191
148
  const resolved = value ?? fallback;
@@ -195,23 +152,30 @@ function resolveMaxRateLimitRetries(value, fallback = DEFAULT_RATE_LIMIT_RETRIES
195
152
  }
196
153
  return resolved;
197
154
  }
198
- function parseRetryAfterMs(headerValue) {
199
- if (!headerValue) {
200
- return undefined;
201
- }
202
- const seconds = Number(headerValue);
203
- if (Number.isFinite(seconds) && seconds >= 0) {
204
- return Math.ceil(seconds * 1000);
205
- }
206
- const retryAt = Date.parse(headerValue);
207
- if (Number.isNaN(retryAt)) {
208
- return undefined;
209
- }
210
- return Math.max(0, retryAt - Date.now());
155
+ function resolveTotalTimeout(timeoutMs) {
156
+ return timeoutMs && timeoutMs > 0 ? timeoutMs : false;
211
157
  }
212
- function resolveRateLimitRetryDelayMs(headers, retryCount) {
213
- return (parseRetryAfterMs(headers.get("retry-after")) ??
214
- DEFAULT_RATE_LIMIT_RETRY_DELAY_MS * 2 ** retryCount);
158
+ function createRetryOptions(limit) {
159
+ return {
160
+ limit,
161
+ methods: [...RETRYABLE_METHODS],
162
+ statusCodes: [...RETRYABLE_STATUS_CODES],
163
+ afterStatusCodes: [...RETRY_AFTER_STATUS_CODES],
164
+ retryOnTimeout: false,
165
+ shouldRetry: ({ error }) => {
166
+ if (error instanceof HTTPError) {
167
+ if (error.response.status === 429) {
168
+ return true;
169
+ }
170
+ return canRetryTransientRequest(error.request.method)
171
+ ? undefined
172
+ : false;
173
+ }
174
+ return canRetryTransientRequest(error.request?.method)
175
+ ? undefined
176
+ : false;
177
+ },
178
+ };
215
179
  }
216
180
  function toBlobPart(file) {
217
181
  if (typeof File !== "undefined" && file instanceof File) {
@@ -300,11 +264,10 @@ function createUploadFormData(input) {
300
264
  }
301
265
  export class Client {
302
266
  apiKey;
303
- fetchImpl;
267
+ http;
304
268
  defaultHeaders;
305
269
  timeoutMs;
306
270
  maxRateLimitRetries;
307
- limiter;
308
271
  pages = {
309
272
  list: (query, options) => this.listPages(query, options),
310
273
  create: (body = {}, options) => this.requestData("POST", "/pages", {
@@ -524,11 +487,16 @@ export class Client {
524
487
  throw new ParagraphClientError("`apiKey` is required.");
525
488
  }
526
489
  this.apiKey = options.apiKey.trim();
527
- this.fetchImpl = resolveFetchImplementation(options.fetch);
528
490
  this.defaultHeaders = new Headers(options.headers);
529
491
  this.timeoutMs = options.timeoutMs;
530
492
  this.maxRateLimitRetries = resolveMaxRateLimitRetries(options.maxRateLimitRetries);
531
- this.limiter = new RequestRateLimiter(options.maxRequestsPerSecond ?? DEFAULT_REQUESTS_PER_SECOND);
493
+ const fetchImpl = resolveFetchImplementation(options.fetch);
494
+ const limiter = new RequestRateLimiter(options.maxRequestsPerSecond ?? DEFAULT_REQUESTS_PER_SECOND);
495
+ this.http = ky.create({
496
+ fetch: (input, init) => limiter.schedule(() => fetchImpl(input, init)),
497
+ prefix: API_BASE_URL,
498
+ timeout: false,
499
+ });
532
500
  }
533
501
  getInfo(options) {
534
502
  return this.requestData("GET", "", { options });
@@ -651,6 +619,7 @@ export class Client {
651
619
  }
652
620
  requestJson(method, path, config) {
653
621
  const url = buildUrl(path, config?.query);
622
+ const kyInput = toKyInput(url);
654
623
  const request = createRequestDescriptor(method, url);
655
624
  const headers = new Headers(this.defaultHeaders);
656
625
  const timeoutMs = config?.options?.timeoutMs ?? this.timeoutMs;
@@ -678,27 +647,28 @@ export class Client {
678
647
  if (config?.formData) {
679
648
  headers.delete("content-type");
680
649
  }
681
- const requestSignal = createRequestSignal(config?.options?.signal, timeoutMs);
682
650
  return (async () => {
683
- let retryCount = 0;
684
651
  try {
685
- const fetchImpl = this.fetchImpl;
686
- while (true) {
687
- const response = await this.limiter.schedule(() => fetchImpl(url, {
688
- method,
689
- headers,
690
- body,
691
- signal: requestSignal.signal,
692
- }));
693
- const payload = await parseResponse(response);
694
- if (response.ok) {
695
- return toSdkPayload(payload);
696
- }
697
- const responseHeaders = new Headers(response.headers);
698
- const apiError = isApiErrorPayload(payload)
652
+ const response = await this.http(kyInput, {
653
+ method,
654
+ headers,
655
+ body,
656
+ signal: config?.options?.signal,
657
+ timeout: false,
658
+ totalTimeout: resolveTotalTimeout(timeoutMs),
659
+ retry: createRetryOptions(maxRateLimitRetries),
660
+ });
661
+ const payload = await parseResponse(response);
662
+ return toSdkPayload(payload);
663
+ }
664
+ catch (error) {
665
+ if (error instanceof HTTPError) {
666
+ const responseHeaders = new Headers(error.response.headers);
667
+ const payload = error.data;
668
+ throw isApiErrorPayload(payload)
699
669
  ? new ParagraphApiError({
700
670
  body: toSdkPayload(payload),
701
- status: response.status,
671
+ status: error.response.status,
702
672
  code: payload.error.code,
703
673
  message: payload.error.message,
704
674
  details: toSdkPayload(payload.error.details),
@@ -706,32 +676,19 @@ export class Client {
706
676
  request,
707
677
  })
708
678
  : new ParagraphApiError({
709
- status: response.status,
710
- code: response.status === 401
679
+ status: error.response.status,
680
+ code: error.response.status === 401
711
681
  ? "unauthorized"
712
682
  : "requestFailed",
713
683
  message: typeof payload === "string" && payload.length > 0
714
684
  ? payload
715
- : response.statusText || "Request failed.",
685
+ : error.response.statusText ||
686
+ "Request failed.",
716
687
  headers: responseHeaders,
717
688
  request,
718
689
  });
719
- if (response.status === 429 &&
720
- retryCount < maxRateLimitRetries) {
721
- const retryDelayMs = resolveRateLimitRetryDelayMs(responseHeaders, retryCount);
722
- retryCount += 1;
723
- await waitForDelay(retryDelayMs, requestSignal.signal);
724
- continue;
725
- }
726
- throw apiError;
727
690
  }
728
- }
729
- catch (error) {
730
- if (error instanceof ParagraphApiError ||
731
- error instanceof ParagraphClientError) {
732
- throw error;
733
- }
734
- if (requestSignal.didTimeout()) {
691
+ if (error instanceof TimeoutError) {
735
692
  throw new ParagraphClientError(`Request timed out after ${timeoutMs}ms.`, {
736
693
  request,
737
694
  cause: error,
@@ -746,9 +703,6 @@ export class Client {
746
703
  cause: error,
747
704
  });
748
705
  }
749
- finally {
750
- requestSignal.cleanup();
751
- }
752
706
  })();
753
707
  }
754
708
  }
@@ -1,9 +1,6 @@
1
1
  export declare class RequestRateLimiter {
2
- private readonly minIntervalMs;
3
- private nextAvailableAt;
4
- private scheduling;
2
+ private readonly limiter;
5
3
  constructor(requestsPerSecond: number);
6
4
  schedule<T>(task: () => Promise<T>): Promise<T>;
7
- private reserveSlot;
8
5
  }
9
6
  //# sourceMappingURL=rate-limiter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAQA,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,UAAU,CAAoC;gBAE1C,iBAAiB,EAAE,MAAM;IAarC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;IAKlC,OAAO,CAAC,WAAW;CAiBpB"}
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAKA,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;gBAEzB,iBAAiB,EAAE,MAAM;IAgBrC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;CAGnC"}
@@ -1,35 +1,19 @@
1
+ import Bottleneck from "bottleneck";
1
2
  import { ParagraphClientError } from "./errors.js";
2
- function sleep(ms) {
3
- return new Promise((resolve) => {
4
- setTimeout(resolve, ms);
5
- });
6
- }
3
+ const DEFAULT_MAX_CONCURRENT_REQUESTS = 3;
7
4
  export class RequestRateLimiter {
8
- minIntervalMs;
9
- nextAvailableAt = 0;
10
- scheduling = Promise.resolve();
5
+ limiter;
11
6
  constructor(requestsPerSecond) {
12
7
  if (!Number.isFinite(requestsPerSecond) ||
13
8
  requestsPerSecond <= 0) {
14
9
  throw new ParagraphClientError("`maxRequestsPerSecond` must be a positive number.");
15
10
  }
16
- this.minIntervalMs = Math.ceil(1000 / requestsPerSecond);
11
+ this.limiter = new Bottleneck({
12
+ minTime: Math.ceil(1000 / requestsPerSecond),
13
+ maxConcurrent: DEFAULT_MAX_CONCURRENT_REQUESTS,
14
+ });
17
15
  }
18
16
  schedule(task) {
19
- const slot = this.reserveSlot();
20
- return slot.then(task);
21
- }
22
- reserveSlot() {
23
- const slot = this.scheduling.then(async () => {
24
- const now = Date.now();
25
- const scheduledAt = Math.max(now, this.nextAvailableAt);
26
- const delayMs = scheduledAt - now;
27
- this.nextAvailableAt = scheduledAt + this.minIntervalMs;
28
- if (delayMs > 0) {
29
- await sleep(delayMs);
30
- }
31
- });
32
- this.scheduling = slot.catch(() => { });
33
- return slot;
17
+ return this.limiter.schedule(task);
34
18
  }
35
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paragraphcms/client",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Official TypeScript client for the Paragraph CMS API.",
5
5
  "license": "MIT",
6
6
  "author": "Paragraph CMS",
@@ -41,5 +41,9 @@
41
41
  ],
42
42
  "devDependencies": {
43
43
  "typescript": "^5.9.3"
44
+ },
45
+ "dependencies": {
46
+ "bottleneck": "^2.19.5",
47
+ "ky": "^2.0.2"
44
48
  }
45
49
  }