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

@tiledev/sdk-apptile-cart-hold

v0.2.1

Published

Reservation-backed cart holds for TilePacket apps — claims stock behind a cart line, stamps its expiry, and reports to the Cart Hold ledger. Plugs into @apptile/sdk-shopify as a CartLineGuard.

Readme

@tiledev/sdk-apptile-cart-hold

Reservation-backed cart holds for TilePacket apps. It reserves the stock behind a cart line for a merchant-set window, so the last unit a shopper adds is theirs to check out — not something that appears and is then taken away.

It plugs into @apptile/sdk-shopify as a CartLineGuard: one policy object the Shopify provider runs around every cart write, rather than logic re-threaded through each screen.

npm install @tiledev/sdk-apptile-cart-hold

@apptile/sdk-shopify is a peer — the two coordinate on the same cart. react is a peer of the main entry, which re-exports the hook; react-native is only ever a type.

The shape of the feature

add     → claim the units, then stamp the line with when its hold expires
raise   → claim the difference (safety net; the cart normally re-adds instead)
lower   → hand the units back
remove  → hand the whole line back

…plus a fire-and-forget report of each change to the Cart Hold manager ledger — what the merchant's "Customer Carts" / "Product in Carts" dashboard reads.

Two rules keep it safe:

  1. The stamp is the receipt. Only a line carrying _cart_hold_expiry_time was ever claimed, so only such a line is released. Pre-orders (selling plans) are never claimed and never released — no "is this a pre-order?" threaded through every call site.
  2. Failing open beats failing shut. A reservation service that is down must not stop people buying, so a transport error approves the add. Only an explicit refusal blocks it.

Usage

import AsyncStorage from "@react-native-async-storage/async-storage";
import { configureCartHold, DEFAULT_MESSAGES } from "@tiledev/sdk-apptile-cart-hold";

const cartHold = configureCartHold({
  config: {
    enabled: true,
    appId,               // the Apptile ENGINE app id (not the Tile app id)
    managerUrl,          // apptile-carthold-manager
    // reservationUrl defaults to https://cart-hold.apptile.io — pass it only to point elsewhere
  },
  shop: { shop: storeDomain, countryCode: "US", languageCode: "EN" },
  storage: AsyncStorage,                 // remembers the last hold duration
  resolveCustomer: () => currentCustomer(),  // labels ledger rows; anonymous if omitted
  onRefusal: (reason) => showToast(DEFAULT_MESSAGES[reason], "error"),
});

Then hand the guard to the Shopify provider so it wraps every cart write:

<ShopifyProvider config={shopifyConfig} cartGuard={cartHold.guard}>
  <App />
</ShopifyProvider>

reservationUrl is not under the manager. Claim/release live on their own deployment — https://cart-hold.apptile.io, which is the default, so most hosts leave the field out. Overriding it with the manager silently 404s every claim, and because the guard fails open, that mistake looks like Cart Hold doing nothing at all.

React helper

Everything ships from the package root, the hook included. There is no ./react subpath: one is reachable only through the exports map, which a resolver that ignores it (node10, some test runners and bundlers) cannot see at all.

import { useCartHold } from "@tiledev/sdk-apptile-cart-hold";

function Root() {
  const { guard, hasLapsedHold } = useCartHold(cartHold); // primes the duration on mount
  // …hand `guard` to ShopifyProvider; use `hasLapsedHold(cart.lines)` on the cart screen
}

Surface

@tiledev/sdk-apptile-cart-hold

| Export | Notes | | --- | --- | | configureCartHold(options) | Builds and remembers the session client. Returns a CartHoldClient. | | getCartHoldClient() | The configured client, or null. | | CartHoldClient | Class: .guard, .prime(), .holdSeconds(), .claim(), .release(), .report(). | | .guard | A CartLineGuard for ShopifyProviderbeforeAdd / beforeIncrease / onLanded / onReleased. | | holdExpiresAt · isHeld · isLapsed · hasLapsedHold · nextHoldExpiry · withStamp | Pure predicates over CartLines (no I/O). | | HOLD_ATTRIBUTE | "_cart_hold_expiry_time" — the platform-fixed line attribute. | | DEFAULT_RESERVATION_URL | "https://cart-hold.apptile.io" — the reservation base used when reservationUrl is omitted. | | DEFAULT_MESSAGES | Shopper copy for SOLD_OUT / UNAVAILABLE. | | type CartHoldOptions, CartHoldConfig, ClaimVerdict, HoldExpiry, KeyValueStorage, … | Config + wire types. |

The React hook — the same root entry

| Export | Notes | | --- | --- | | useCartHold(client) | Primes on mount; returns { guard, hasLapsedHold, nextHoldExpiry }. |

Config shape

interface CartHoldConfig {
  enabled: boolean;
  appId: string;          // Apptile engine app id — x-shopify-app-id header + /users/<id> key
  managerUrl: string;      // apptile-carthold-manager: hold duration + ledger
  reservationUrl?: string; // reservation service: POST /claim, POST /release
                           // default https://cart-hold.apptile.io
}

interface CartHoldOptions {
  config: CartHoldConfig;
  shop: { shop: string; countryCode: string; languageCode: string };
  storage?: KeyValueStorage;                 // fallback for the hold duration
  resolveCustomer?: () => { id?; email? } | null;
  onRefusal?: (reason: "SOLD_OUT" | "UNAVAILABLE") => void;
  onError?: (error: unknown, context?: Record<string, unknown>) => void;
  now?: () => number;                        // clock, for tests
  fetch?: typeof fetch;                      // injectable transport
  timeouts?: { claimMs?: number; reportMs?: number };
}

Wire protocol

| Call | Endpoint | Body / result | | --- | --- | --- | | hold duration | GET {managerUrl}/users/{appId} | → { expiresAt } seconds (also accepts { data: { expiresAt } }) | | claim | POST {reservationUrl}/claim | { variantId, quantity }{ ok, reason?, effectiveInventory? } | | release | POST {reservationUrl}/release | { variantId, quantity } (fire-and-forget) | | ledger | POST {managerUrl}/cart/update | { updateType, variantId, productId?, quantity, cartId, lineItemId?, userId, userMail, shop, countryCode, languageCode } |

{reservationUrl} defaults to https://cart-hold.apptile.io; {managerUrl} has no default, because the manager is a different service and differs per environment. A base given with a trailing slash is trimmed, so …apptile.io/ and …apptile.io behave the same.

All ids on the wire are the tail of the GID — a bare numeric id for variants/products; the cart id keeps its ?key= query, because Shopify will not resolve a cart without it.

Design notes

A guard, not screen code. The reservation logic is one CartLineGuard handed to ShopifyProvider, so beforeAdd claims and stamps, beforeIncrease catches raises, and onReleased gives units back — for every cart action at once, including the ones (reorder, bundles, *ById) that hand-wired code forgets.

One line per add. A decorated add is not merged into an existing line, so every add mints its own expiry. Units added ten minutes apart expire ten minutes apart; sharing one expiry would let later units be swept back while still in the cart.

Self-contained. @apptile/sdk-shopify is imported type-only, and every ambient dependency (config, storage, customer, toast, clock, fetch) is injected through CartHoldOptions — so tsc passes with nothing installed and the runtime never reaches for a global it was not given.

Pairs with @tiledev/sdk-apptile-cart-sync. Cart Hold works on lines (what is reserved); Cart Sync works on cart identity (which cart you are on). A line arriving by sync already carries whatever stamp its author gave it, and Cart Hold reads that stamp exactly the same way.