Skip to content

feat(js-api-client): modernize, harden, and test the API client#259

Merged
Plopix merged 34 commits intomainfrom
feat/update/wcld
Apr 3, 2026
Merged

feat(js-api-client): modernize, harden, and test the API client#259
Plopix merged 34 commits intomainfrom
feat/update/wcld

Conversation

@Plopix
Copy link
Copy Markdown
Member

@Plopix Plopix commented Apr 3, 2026

Summary

A comprehensive update to js-api-client covering bug fixes, new features, refactoring, tests, and documentation.

What changed

  • Bug fixes: typo in GraphQL query (chidlren), dead code removal, Promise antipattern, empty auth headers, Server-Timing parsing, correct package.json module field
  • Features: request timeout via AbortController, Symbol.dispose for automatic cleanup, http2IdleTimeout option, statusCode getter on errors, auth warning when no credentials configured
  • Refactoring: proper TypeScript types replacing any, descriptive generic names, extracted shared helpers in cart/subscription managers, cleaner mass-call client
  • Tests: comprehensive unit tests for API caller, grabber, mass-call client, and error handling
  • Docs: JSDoc on all main exports, updated README with new options
  • CI: GitHub Actions workflow for PR and main branch checks
  • Deprecation: mass-call client deprecated in favor of p-limit/p-queue

Architecture 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:#fff
Loading

Changes Breakdown

pie title Changes by Category
    "Bug Fixes" : 8
    "Features" : 5
    "Refactoring" : 5
    "Tests" : 4
    "Docs & CI" : 4
    "Deprecation" : 1
Loading

Request 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 sessions
Loading

Test plan

  • Unit tests added for create-api-caller, create-grabber, create-mass-call-client, and error handling
  • Verify existing consumers are not broken by type changes (extraHeaders narrowed to Record<string, string>)
  • Verify Symbol.dispose works with TypeScript using declarations
  • Test http2IdleTimeout with long-lived HTTP/2 connections
  • Confirm deprecation warning appears for mass-call client usage

@Plopix Plopix force-pushed the feat/update/wcld branch from 1ff5cb3 to 81c95c5 Compare April 3, 2026 20:59
Plopix added 29 commits April 3, 2026 14:01
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.
Add comprehensive JSDoc with @param, @returns, and @example tags to
12 exported factory functions for better IDE discoverability.
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.
Plopix added 4 commits April 3, 2026 14:01
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.
@Plopix Plopix force-pushed the feat/update/wcld branch 3 times, most recently from d418e88 to a45b6a5 Compare April 3, 2026 21:25
@Plopix Plopix force-pushed the feat/update/wcld branch from a45b6a5 to 13eece5 Compare April 3, 2026 21:28
@Plopix Plopix merged commit 259ce36 into main Apr 3, 2026
4 checks passed
@Plopix Plopix deleted the feat/update/wcld branch April 3, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant