npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dataships/sdk

v2.0.0

Published

Dataships client SDK — transport and consent-session lifecycle for the Dataships consent-session API. Platform-agnostic core with adapters per integration (Shopify checkout, Salesforce, custom platform, …).

Readme

@dataships/sdk

Transport and session-lifecycle client for the Dataships consent-session API (/v1/consent-session).

The SDK is platform-agnostic. It takes a ConsentSessionAdapter that knows how to read order/customer context from a host environment (Shopify checkout, Salesforce, a custom platform, …) and posts the consent-session envelope to the Dataships backend.

Install

The package is published publicly to npm under the @dataships scope:

npm install @dataships/sdk
# or
yarn add @dataships/sdk

Quickstart (Salesforce SFCC storefront)

import { DatashipsClient } from "@dataships/sdk";
import {
  SalesforceAdapter,
  getConfigFromScriptTag,
} from "@dataships/sdk/adapters/salesforce";

const config = getConfigFromScriptTag();

const client = new DatashipsClient({
  // Public key + the JWT the cartridge minted at render; both come from the
  // script tag. The SDK sends the token verbatim and never inspects it.
  auth: { publicKey: config.publicKey, token: config.token! },
  // Any env other than "production" also needs a baseUrl; see "Environments".
  env: config.env,
  app: "my-sfcc-cartridge",
  version: "1.0.0",
  adapter: new SalesforceAdapter({ config }),
});

const response = await client.upsertSession({ identifierType: "EMAIL" });
// Render one widget per entry; an empty array means render nothing.
for (const entry of response.consents) {
  render(entry.channel, entry.consentType, entry.checked, entry.widgetConfig);
}

const session = { consentSessionId: response.consentSessionId };
const stop = client.startHeartbeat(session, {
  onError: err => console.warn("heartbeat failed", err),
});

await client.saveConsent({
  channel: "EMAIL",
  consent: true,
  session,
});

The Salesforce adapter speaks the consent-session protocol (see below) and reads its config from the cartridge-rendered data-* attributes on the Dataships <script> tag. Rotate the token with client.setToken(next) before it expires; a missed rotation surfaces as a 401 DatashipsError and stops the heartbeat.

Environments

Only production has a built-in API host (https://api.dataships.io). The package ships no internal hostnames, so env: "staging" or env: "local" without a baseUrl throws at construction. Pass the host Dataships gives you for a non-production environment:

new DatashipsClient({ env: "staging", baseUrl: STAGING_API_URL, ... });

Script tag (CDN)

For pages without a bundler, dist/dataships.min.js is a minified IIFE that assigns window.Dataships with the browser client, every adapter (not the server client) and version. unpkg and jsdelivr serve it for a bare package URL.

<script src="https://sdk.dataships.io/sdk/v2/dataships.min.js"></script>
<script>
  const { DatashipsClient, CustomPlatformCheckoutAdapter } = window.Dataships;
  const client = new DatashipsClient({
    auth: { publicKey, token },
    app: "my-storefront",
    version: "1.0.0",
    adapter: new CustomPlatformCheckoutAdapter({ collectionStep: "CHECKOUT_INFORMATION" }),
  });
</script>

Versions and CDN URLs

  • https://sdk.dataships.io/sdk/v2/ follows the latest 2.x release and is the URL to use. A new major gets a new alias, so a page never moves majors on its own.
  • https://sdk.dataships.io/sdk/2.0.0/ is an exact release and never changes.
  • https://sdk.dataships.io/sdk/next/ follows the latest release candidate.

Every exact release publishes integrity.json beside its scripts, with the SHA-384 of each one. To use subresource integrity, pin the exact version (an alias moves with every release, so its hash would stop matching):

<script
  src="https://sdk.dataships.io/sdk/2.0.0/dataships.min.js"
  integrity="sha384-…"
  crossorigin="anonymous"></script>

Quickstart (custom platform / headless storefront)

import { DatashipsClient } from "@dataships/sdk";
import { CustomPlatformCheckoutAdapter } from "@dataships/sdk/adapters/custom-platform-checkout";

const adapter = new CustomPlatformCheckoutAdapter({
  collectionStep: "CHECKOUT_INFORMATION", // or "THANK_YOU" on the confirmation page
  locale: navigator.language,             // coerced to a SupportedLocale
  storageScope: publicKey,                // keys the datashipsSessionId in localStorage
});

const client = new DatashipsClient({
  // The merchant's server mints the short-lived ES256 JWT and injects it into the page.
  auth: { publicKey, token },
  env: "production",
  app: "my-storefront",
  version: "1.0.0",
  adapter,
});

// Anonymous first, then again once the shopper types an email.
let response = await client.upsertSession({ identifierType: "EMAIL" });
response = await client.upsertSession({ identifierType: "EMAIL", identifier: typedEmail });

const session = { consentSessionId: response.consentSessionId };
const stop = client.startHeartbeat(session);
await client.saveConsent({ channel: "EMAIL", consent: true, session });

The adapter posts integration: { type: "CUSTOM_PLATFORM_CHECKOUT", collectionStep, datashipsSessionId, isFastCheckout }. The signup adapter (CustomPlatformSignupAdapter, @dataships/sdk/adapters/custom-platform-signup) posts integration: { type: "CUSTOM_PLATFORM_SIGNUP", datashipsSessionId } for a merchant-built form: same beacon lifecycle under its own storage key, one id per form fill, resetDatashipsSessionId() after the form is submitted; identifiers come from a user literal or resolver. The datashipsSessionId beacon is minted on the checkout step, carried to the thank-you page through localStorage (scoped by storageScope), and rotated when a new checkout starts; pass your own datashipsSessionId to own that lifecycle instead. A thank-you upsert for a checkout that already showed the widget answers 204, which the SDK resolves to an empty consents.

Completing the flow — the placed order landing on every session opened under the beacon and processing being queued — is client.submitSession(), backed by POST /v1/consent-session/submit:

await client.submitSession({
  user: { firstName: "Jane", lastName: "Doe" }, // both required; contact details never travel here
  details: { id: "ORD-1001", total: 149.9, billingCountryCode: "IE", submittedAt: new Date() },
});

The beacon defaults to the adapter's own and is discarded once the submit is accepted; pass datashipsSessionId to complete a beacon you carried yourself. The identifier is what the widget collected on the upsert; the submit carries only the shopper's names, and the SDK refuses a blank one before sending. The backend is idempotent and answers 200 even when the beacon matches no session. details is the flow's payload merged under integration next to the adapter's type: for CUSTOM_PLATFORM_CHECKOUT that is the order above (total as a number in the integration's currency, billingCountryCode ISO 3166-1 alpha-2). The server client exposes the same call with datashipsSessionId required, for a merchant backend completing from its order-created hook.

Quickstart (Shopify checkout UI extension)

import { DatashipsClient } from "@dataships/sdk";
import { ShopifyCheckoutAdapter } from "@dataships/sdk/adapters/shopify-checkout";

const adapter = new ShopifyCheckoutAdapter({
  collectionStep: "CHECKOUT_INFORMATION", // or "THANK_YOU"
  datashipsOrderUuid,                     // the checkout's cart-attribute UUID, when it has one
  locale: shopify.localization.language.value.isoCode,
  checkoutToken: shopify.checkoutToken.value,
});

const client = new DatashipsClient({
  // Shopify session tokens live about a minute: fetch one per request.
  auth: { publicKey, getToken: () => shopify.sessionToken.get() },
  env: "production",
  app: "my-checkout-extension",
  version: "1.0.0",
  adapter,
});

const response = await client.upsertSession({ identifierType: "EMAIL", identifier: email });

getToken is called before every request, retries and heartbeats included, and is exclusive with token (setToken() throws under it). Update facts that change on the page with adapter.setCheckoutDetails({ orderId, wallet, … }) and adapter.setCollectionStep(...).

In the checkout editor, run the same flow with no credentials and no network call:

const client = new DatashipsClient({
  app: "my-checkout-extension",
  version: "1.0.0",
  adapter: new ShopifyCheckoutAdapter({
    collectionStep: "CHECKOUT_INFORMATION",
    previewMode: true,
  }),
});

Unsafe: the server key in the browser

A merchant with no server at all cannot mint tokens. As an explicit, accepted-risk escape hatch the browser client takes the server key under its own name:

const client = new DatashipsClient({
  auth: { unsafeServerKey: "ds_sk_live_…" }, // UNSAFE: exposed to every visitor
  env: "production",
  app: "my-storefront",
  version: "1.0.0",
  adapter,
});

The key is sent alone as X-Api-Key. Anyone who views the page source can open sessions, save consent and submit orders as that merchant until the key is revoked (ask Dataships support to revoke it); revocation is the only remedy. The SDK refuses a public key under this name, warns once in the console at construction, appends unsafe-server-key to X-Dataships-App so the backend can tell these calls from genuine server-to-server ones, and setToken() throws. Prefer auth: { publicKey, token } wherever a token can be minted.

Server-to-server (merchant backend)

The server key never leaves the merchant's servers. A separate entry keeps it out of storefront bundles: @dataships/sdk/server refuses to construct where window exists, and the browser client refuses server keys.

import { DatashipsServerClient } from "@dataships/sdk/server";

const client = new DatashipsServerClient({
  auth: { serverKey: process.env.DATASHIPS_SERVER_KEY! },
  app: "my-backend",
  version: "1.0.0",
  adapter, // any consent-session adapter supplying the integration context
});

// The connection IP is your server's, so the user's IP is mandatory.
const response = await client.upsertSession({
  identifierType: "EMAIL",
  identifier: "[email protected]",
  userIp: request.ip,
});

The server client exposes upsertSession, saveConsent, heartbeat (one beat; schedule your own cadence), submitSession (with an explicit datashipsSessionId), triggerVerificationCode and submitVerificationCode, with the same responses and DatashipsError as the browser client.

Adapters

| Adapter | Import | Use case | | --- | --- | --- | | Shopify checkout UI extensions | @dataships/sdk/adapters/shopify-checkout | Checkout UI extensions (SHOPIFY_CHECKOUT). No DOM: the extension passes the collection step, datashipsOrderUuid and checkout details from the Shopify checkout API. Authenticate with auth: { publicKey, getToken: () => shopify.sessionToken.get() } so every request carries a fresh session token. Used by the Dataships Shopify app. | | Salesforce (SFCC) | @dataships/sdk/adapters/salesforce | Reads data-* attributes off the SFCC cartridge <script> tag. Used by the Dataships SFCC cartridge. | | Custom platform checkout | @dataships/sdk/adapters/custom-platform-checkout | Bespoke / headless storefronts with no native integration (CUSTOM_PLATFORM). Every input comes from the integrator; the adapter owns the per-checkout datashipsSessionId. Used by @dataships/elements. | | Custom platform signup | @dataships/sdk/adapters/custom-platform-signup | Merchant-built signup forms (newsletter, account creation) on platforms with no native integration. Posts CUSTOM_PLATFORM_SIGNUP with the per-form-fill datashipsSessionId; the merchant supplies the user (email, phone, name) as a literal or a resolver. |

Consent-session protocol

Every adapter drives /v1/consent-session. The client authenticates with auth: { publicKey, token } or auth: { publicKey, getToken } (X-Api-Key + Authorization: Bearer). upsertSession({ identifierType, identifier?, userIp?, format? }) posts the { identifier, identifierType, locale, userIp?, format?, integration: { type, … } } envelope to POST /v1/consent-session and returns { consentSessionId, consents[], userVerificationRequired, lastHeartbeat, identifier }: one entry per channel that renders (channel, consentType, checked, and widgetConfig with the nullable copy fields), plus the identifier the session holds so a widget can refill a field it lost. format picks how the copy comes back: json (the API default) for plain strings you format yourself, html for text and consentDescription with links and emphasis applied. In json copy, {{MARKETING_PREFERENCES}} is the marketing modal link showing widgetConfig.placeholderValues.MARKETING_PREFERENCES, which the API resolves to the author's text or the served language's "Click here". Save-consent, heartbeat and the OTP calls live under /v1/consent-session/*. Pass { consentSessionId: response.consentSessionId } as the session for startHeartbeat / saveConsent. A 204 No Content on upsert resolves to an empty consents as well.

In preview mode (previewMode: true on the adapter) the client makes no network call and needs no auth. upsertSession returns a synthetic response for the requested identifier type: fakeEmailUpsertResponse (one EMAIL consent) for EMAIL, fakePhoneUpsertResponse (one SMS consent, userVerificationRequired: false) for PHONE. The other calls resolve without a request: triggerVerificationCode reports untriggered and submitVerificationCode resolves verified.

Errors surface as DatashipsError { status, code, reason, challenge, body }. 401 / 403 are never retried; a 422 COUNTRY_NOT_FOUND on upsert resolves to an empty consents instead of throwing. A 409 is retried when it is a held checkout lock (PESSIMISTIC_LOCK_EXCEPTION) but not when the session has already been processed (CONSENT_SESSION_ALREADY_PROCESSED, exposed as error.isSessionProcessed), which also stops the heartbeat.

Contributing

See CONTRIBUTING.md in the repository for local development, prereleases and the release process.

License

Licensed under the Apache License, Version 2.0.