Skip to content

Latest commit

 

History

History
294 lines (203 loc) · 10.4 KB

File metadata and controls

294 lines (203 loc) · 10.4 KB

Upgrade Guide

From v6

New: bearerToken authentication and meApi caller

ClientConfiguration now accepts an optional bearerToken. When set, it is sent as Authorization: Bearer <token> on catalogueApi, discoveryApi, pimApi, nextPimApi, and the new meApi (backed by /@me). Priority is sessionId > bearerToken > staticAuthToken > accessTokenId/accessTokenSecret. The shop token bootstrap reuses the same priority, so providing a bearerToken is enough to auto-fetch a shop API token.

const api = createClient({
    tenantIdentifier: 'furniture',
    bearerToken: 'eyJhbGciOi…',
});
const me = await api.meApi(`query { me { id } }`);

createSignatureVerifier no longer takes jwtVerify / sha256 (breaking)

jose is now a direct dependency of the client, and createSignatureVerifier uses it internally together with the platform's crypto.subtle for SHA-256 hashing. You no longer need to (and cannot) pass your own jwtVerify or sha256 implementations.

Before (v6):

import jwt from 'jsonwebtoken';
import { createHmac } from 'crypto';
import { createSignatureVerifier } from '@crystallize/js-api-client';

const verify = createSignatureVerifier({
    secret,
    jwtVerify: async (token, s) => jwt.verify(token, s) as any,
    sha256: async (data) => createHmac('sha256', secret).update(data).digest('hex'),
});

After (v7):

import { createSignatureVerifier } from '@crystallize/js-api-client';

const verify = createSignatureVerifier({ secret });

The verifier expects an HS256-signed JWT envelope (Crystallize's signature format) and runs on any runtime that exposes crypto.subtle — Node 18+, browsers, workers, edge runtimes.

The CreateAsyncSignatureVerifierParams type has been renamed to CreateSignatureVerifierParams (single secret field). A new SignatureVerifier function type is exported for typing the returned verifier.

SimplifiedRequest.body is now string | null (breaking)

SimplifiedRequest.body was previously typed as any, but the verifier has always called JSON.parse(body) when truthy, so a raw JSON string was the only working shape. The type now reflects that. If you were passing an already-parsed object, stringify it first (body: JSON.stringify(obj)), or — better — pass the raw request body string you received from Crystallize.

New: createPluginPayloadDecrypter

Decrypts (and optionally verifies) Crystallize plugin JWE payloads. This is the single entry point for vendor-side integrations — the CLI now consumes it too. See the "Plugin payload decryption" section of the README. jose is bundled, so vendors only pass the private JWK and (optionally) a verify object; issuer defaults to https://api.crystallize.com and jwksUrl is derived from the issuer when not set.

jose added as a runtime dependency

jose@^5.10.0 is now a direct dependency of @crystallize/js-api-client. If your bundler was tree-shaking away a previously-peer JWT library, no action is needed; jose is small and universal.

From v5

extraHeaders type widened

The extraHeaders option on createClient now accepts Record<string, string> | Headers | [string, string][] instead of only Record<string, string>. This is not a breaking change — all existing code continues to work. If you were casting headers to Record<string, string>, you can now pass Headers instances or tuple arrays directly.

HTTP/2 stability

The HTTP/2 transport now guards against double-settlement of promises when abort signals fire after a request has already completed. No API changes — this is a reliability fix.

JSApiClientCallError.statusCode alias

A read-only statusCode getter was added as an alias for code, following the Node.js convention. Both properties return the same numeric HTTP status.

http2IdleTimeout option

You can now configure the HTTP/2 session idle timeout via createClient(config, { http2IdleTimeout: 60000 }). The default remains 300 000 ms (5 minutes).

timeout option

A request-level timeout can be set via createClient(config, { timeout: 10000 }). When set, requests that exceed the timeout are aborted with an AbortError.


Upgrade Guide to v5

This guide helps you migrate from v4 to v5 of @crystallize/js-api-client.

v5 focuses on:

  • A single client with clearly separated callers: catalogue, discovery, PIM, next PIM, and Shop Cart
  • High-level managers for Orders, Customers, Subscriptions, and Cart
  • Stronger types via @crystallize/schema
  • Removed deprecations

If you are starting fresh, see the README. If you are upgrading, follow the mapping below.

At a glance: changes

  • No more pre-exported singletons. Always createClient({ tenantIdentifier, … }) in your app.
  • searchApi was deprecated; use discoveryApi now.
  • orderApi and subscriptionApi → use nextPimApi or dedicated managers.
  • createAsyncSignatureVerifiercreateSignatureVerifier
  • Inputs accept ISO date strings (not Date objects)
  • Product Hydrater: removed useSyncApiForSKUs
  • Orders helpers consolidated into createOrderManager
  • handleImageUpload removed → use createBinaryFileManager
  • New createCartManager wraps cart operations
  • Public JS API Client types are removed in favor of @crystallize/schema

High-level helpers: before/after

Orders

Before (v4): separate pusher/payment/pipeline utilities

// register order
await CrystallizeOrderPusher({
    /* … */
});

// update payments
await CrystallizeCreateOrderPaymentUpdater('order-id', {
    payment: [
        /* … */
    ],
});

// move stage
await CrystallizeCreateOrderPipelineStageSetter('order-id', 'pipeline-id', 'stage-id');

After (v5): one manager

import { createOrderManager } from '@crystallize/js-api-client';
const om = createOrderManager(api);

await om.register({
    /* RegisterOrderInput (ISO dates) */
});
await om.setPayments('order-id', [{ provider: 'STRIPE' /* … */ }]);
await om.putInPipelineStage({ id: 'order-id', pipelineId: 'pipeline-id', stageId: 'stage-id' });
await om.update({ id: 'order-id' /* rest of UpdateOrderInput */ });

Types are validated using @crystallize/schema/pim.

Search → Discovery

Before (v4):

const res = await api.searchApi(`{ search { /* … */ } }`);

After (v5):

const res = await api.discoveryApi(`{ /* discovery query */ }`);

The Discovery schema differs from the old Search API. Update root fields accordingly.

nextPimApi error handling

To have @crystallize/js-api-client throw the new JSApiClientCallError on business errors coming from the Next PIM API, make sure your GraphQL operations explicitly select the BasicError fields on error-union types. Without these fields, the client cannot surface structured errors.

Add this inline fragment wherever the schema returns an error union:

... on BasicError {
    errorName
    message
}

Example (mutation shape simplified):

mutation CreateThing($input: CreateThingInput!) {
    createThing(input: $input) {
        ... on CreateThingResult {
            id
        }
        ... on BasicError {
            errorName
            message
        }
    }
}

With the BasicError fields present, the client detects the error payload and throws JSApiClientCallError containing errorName and message.

Subscriptions

Before (v4): direct subscriptionApi calls and ad-hoc helpers.

After (v5):

import { createSubscriptionContractManager } from '@crystallize/js-api-client';

const scm = createSubscriptionContractManager(api);
const template = await scm.createTemplateBasedOnVariantIdentity(
    '/path',
    'SKU',
    'plan',
    'periodId',
    'priceVariant',
    'en',
);
await scm.create({
    customerIdentifier: 'cust',
    tenantId: 'tenant',
    payment: {
        /* … */
    },
    ...template,
});

Product Hydrater

  • Removed option: useSyncApiForSKUs
  • Added price contexts: priceForEveryone, priceList, marketIdentifiers
createProductHydrater(api, {
    priceForEveryone: true,
    priceList: 'b2b',
    marketIdentifiers: ['eu'],
});

Cart operations

Before (v4): direct shopCartApi mutations sprinkled in code.

After (v5): use the manager, with automatic token handling.

import { createCartManager } from '@crystallize/js-api-client';
const cart = createCartManager(api);
const c = await cart.hydrate({ language: 'en', items: [{ sku: 'SKU', quantity: 1 }] });
await cart.addSkuItem(c.id, { sku: 'SKU-2', quantity: 1 });
await cart.place(c.id);

To fully control the token, pass shopApiToken in createClient and set options.shopApiToken.doNotFetch = true.

Image upload

Before (v4): handleImageUpload(path, client, tenantId)

After (v5): createBinaryFileManager(api)

import { createBinaryFileManager } from '@crystallize/js-api-client';
const files = createBinaryFileManager(api);
const mediaKey = await files.uploadImage('/path/to/picture.jpg');
const staticKey = await files.uploadFile('/path/to/static/file.pdf');
const bulkKey = await files.uploadMassOperationFile('/path/to/import.json');
// Use the returned keys in subsequent PIM mutations

uploadImage enforces image uploads and targets the MEDIA storage bucket. uploadFile routes other assets to the static file storage, while uploadMassOperationFile prepares files for ingestion by the mass operations pipeline. Use uploadToTenant if you need to provide your own buffer or specify the upload type manually.

Signature verification

Before (v4): createAsyncSignatureVerifier (and an older sync variant)

After (v5): createSignatureVerifier (async only)

const verify = createSignatureVerifier({ sha256: async () => '…', jwtVerify: async () => ({}) as any, secret });
await verify(signatureJwt, { url, method: 'POST', body: rawBody });

If you handle GET webhooks, also pass webhookUrl so the HMAC can be validated from query params.

Input and date handling

  • Replace any Date objects in inputs by ISO strings (e.g., new Date().toISOString()).
  • All inputs are validated using zod schemas re-exported by @crystallize/schema. An invalid object will throw a validation error before calling the API.

Type changes

  • Public types previously exported from @crystallize/js-api-client are removed in favor of @crystallize/schema.
  • Import from @crystallize/schema/catalogue, /pim, or /shop as appropriate.

Examples

See README for updated usage examples across all helpers.