feat(js-api-client): modernize, harden, and test the API client#259
Merged
feat(js-api-client): modernize, harden, and test the API client#259
Conversation
The onFolder helper in create-catalogue-fetcher.ts produced a field named 'chidlren' instead of 'children' in generated GraphQL queries, causing incorrect query results for folder children.
The outer try/catch in the `post` function was a no-op that added visual noise and misleadingly suggested error handling was happening.
The file contained 543 lines of entirely commented-out code, replaced by the modules in src/core/pim/subscriptions/. Git history preserves it if ever needed.
…drater
The query object construction used `{ ...{ ...productListQuery } }` which
is functionally identical to `{ ...productListQuery }`. Simplified to
remove the unnecessary nesting.
…alization
The `results` variable was typed as `{ [key: string]: any }` but initialized
as `[]`. While JS allows property assignment on arrays, this is misleading.
Changed to `{}` to match the declared type and actual usage.
The module field pointed to ./dist/index.mjs which doesn't exist. The build outputs ./dist/index.js for ESM. This mismatch could cause import failures in bundlers (Webpack, Rollup) that use the module field.
…gging Stack traces and error.name now correctly show 'JSApiClientCallError' instead of generic 'Error', making it easier to identify API client errors in logs and error handlers.
… API caller The `RequestInit | any | undefined` union types collapsed to `any`, defeating TypeScript's type checking for all library consumers. Introduced a focused GrabOptions type covering method, headers, and body, and updated all signatures.
Add .env to .gitignore and create .env.example with placeholder values to prevent accidental credential commits.
Adds a console.warn when the fallback auth path is reached with empty accessTokenId and accessTokenSecret, helping developers catch missing auth configuration early instead of getting cryptic 401/403 errors. Uses a WeakSet to ensure the warning fires only once per config object.
Replace fragile split-based parsing with a regex that extracts the dur= value per the Server-Timing spec, avoiding garbage results from non-standard header formats.
…es in fetchers/managers Replace abbreviations like OO, OOI, OC, OSC, OP with readable names (OrderExtra, OrderItemExtra, CustomerExtra, SubscriptionContractExtra, PaymentExtra) across order, customer, and subscription modules for better IDE tooltip readability.
Enables `using client = createClient({...})` syntax (TypeScript 5.2+)
so HTTP/2 connections are automatically closed when the scope exits.
Added `esnext.disposable` to tsconfig lib and implemented Symbol.dispose
on both ClientInterface and MassClientInterface.
Add optional `timeout` field (in milliseconds) to CreateClientOptions. When configured, requests that exceed the timeout are automatically aborted using AbortSignal.timeout(). Works for both fetch and HTTP/2 code paths. Default is no timeout (backward compatible).
- Replace Promise<any> with Promise<MassCallResults> (Record<string, unknown>)
- Replace any in afterRequest callback with Record<string, unknown>
- Replace any for exception in onFailure with unknown
- Type buildStandardPromise return as { key: string; result: unknown } | undefined
- Fix typo: situaion → situation in changeIncrementFor parameter
…d approach The five identical enqueue methods (catalogueApi, discoveryApi, pimApi, nextPimApi, shopCartApi) differed only in their key prefix and caller reference. Replaced with Object.fromEntries to eliminate ~20 lines of boilerplate while preserving the public API and types.
Add 38 unit tests covering create-api-caller, create-grabber, and create-mass-call-client without requiring API credentials or network access. Tests cover: authentication headers, successful responses, HTTP errors, GraphQL errors, Core Next wrapped errors, 204 handling, profiling, mass call batching, retry logic, and adaptive concurrency.
Cover HTTP error codes (400-503), GraphQL errors in 200 responses, Core Next wrapped errors, network failures, timeout scenarios, malformed JSON responses, 204 No Content, and JSApiClientCallError property validation.
Runs build and unit tests across Node.js 20, 22, and 24 on every pull request and push to main, so broken code is caught before merge.
The onBatchDone callback is typed as returning Promise<void> but was not awaited, causing batch callbacks to overlap with subsequent batch execution.
Replace `new Promise(async (resolve) => { ... })` with plain async/await
flow. The old pattern swallowed errors from beforeRequest/afterRequest
hooks, causing the promise to silently hang instead of rejecting.
The clients Map was untyped (new Map()), hiding potential null-access bugs. Added proper generic type parameters and a guard in resetIdleTimeout for when clientObj is undefined.
When no accessTokenId/accessTokenSecret are set, return an empty object instead of headers with empty string values. Crystallize APIs already handle missing headers correctly.
…act manager cancel, pause, resume, renew, create, and update all followed the same pattern: build a mutation object, call nextPimApi, unwrap the response. A private lifecycleAction helper now encapsulates this, eliminating ~80 lines of duplication while keeping the public API identical.
…tion Cart manager methods (fetch, place, abandon, fulfill, addSkuItem, removeItem, setMeta, setCustomer, hydrate) all followed the same pattern of building a GraphQL query/mutation, calling shopCartApi, and unwrapping the response. Extracted cartQuery and cartMutation helpers to remove ~80 lines of structural duplication. Public API unchanged.
…mit/p-queue The mass call client duplicates functionality that mature ecosystem packages provide better. Added @deprecated JSDoc tag, one-time console.warn on first use, and updated README with deprecation notice and p-limit usage example. The function remains fully functional.
Adds a `get statusCode()` getter that returns `this.code`, following Node.js conventions where `code` is typically a string error code (ECONNREFUSED, etc.) and `statusCode` is the numeric HTTP status. Non-breaking — existing `.code` usage is unaffected.
Expose http2IdleTimeout on CreateClientOptions so users can tune the idle timeout for serverless (short) and long-running server (long) use cases. Defaults to 300000ms (5 minutes) when not set.
…Code alias Document the new timeout and http2IdleTimeout client options, and add an error handling section showing the statusCode getter on JSApiClientCallError.
d418e88 to
a45b6a5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A comprehensive update to
js-api-clientcovering bug fixes, new features, refactoring, tests, and documentation.What changed
chidlren), dead code removal, Promise antipattern, empty auth headers, Server-Timing parsing, correct package.jsonmodulefieldAbortController,Symbol.disposefor automatic cleanup,http2IdleTimeoutoption,statusCodegetter on errors, auth warning when no credentials configuredany, descriptive generic names, extracted shared helpers in cart/subscription managers, cleaner mass-call clientp-limit/p-queueArchitecture Overview
graph TD A[createClient] --> B[createGrabber] A --> C[createApiCaller] B --> D{useHttp2?} D -->|yes| E[HTTP/2 Session Pool] D -->|no| F[Standard fetch] E -->|new: http2IdleTimeout| E C -->|new: timeout via AbortController| G[Request Execution] C --> H[Profiling / Server-Timing] A --> I[catalogueApi] A --> J[searchApi] A --> K[pimApi] A --> L[shopCartApi] I --> M[Catalogue Fetcher] I --> N[Navigation Fetcher] I --> O[Product Hydrater] K --> P[Order Manager] K --> Q[Customer Manager] K --> R[Subscription Contract Manager] L --> S[Cart Manager] style A fill:#4a9eff,color:#fff style B fill:#6c5ce7,color:#fff style C fill:#6c5ce7,color:#fff style G fill:#00b894,color:#fffChanges Breakdown
pie title Changes by Category "Bug Fixes" : 8 "Features" : 5 "Refactoring" : 5 "Tests" : 4 "Docs & CI" : 4 "Deprecation" : 1Request Lifecycle (updated)
sequenceDiagram participant App participant Client participant ApiCaller participant Grabber participant API as Crystallize API App->>Client: createClient(config, options) Client->>Grabber: createGrabber({ useHttp2, http2IdleTimeout }) Client->>ApiCaller: createApiCaller(uri, grabber, options) App->>ApiCaller: query(gql, variables) alt timeout configured ApiCaller->>ApiCaller: Create AbortController + setTimeout end alt no credentials configured ApiCaller-->>App: ⚠️ console.warn (new) end ApiCaller->>Grabber: grab(uri, body, headers, signal?) Grabber->>API: HTTP/1.1 or HTTP/2 request API-->>Grabber: Response Grabber-->>ApiCaller: parsed JSON alt error ApiCaller-->>App: JSApiClientCallError (now with .statusCode) end ApiCaller-->>App: data App->>Client: close() or Symbol.dispose (new) Client->>Grabber: close HTTP/2 sessionsTest plan
create-api-caller,create-grabber,create-mass-call-client, and error handlingextraHeadersnarrowed toRecord<string, string>)Symbol.disposeworks with TypeScriptusingdeclarationshttp2IdleTimeoutwith long-lived HTTP/2 connections