/** * `CartSyncClient` — reports, adopts and reconciles the shared cart (the platform's "Cart Assist"). * * Modelled on the app-local `cartAssist` service, with every ambient dependency (config, storage, * customer-account transport, access token, fetch) inverted into `CartSyncOptions`. The per-launch * coalescing state the service kept as module globals lives on the instance here. * * ─── How this stays clear of Cart Hold ─────────────────────────────────────────────────────────── * * Cart Sync works on cart *identity* (which cart we are on); Cart Hold works on *lines* (what is * reserved). Keeping to that split is what makes them safe together: * * - Adoption is a read plus a state swap, and the merge is performed server-side. The one line * write is the fallback when the merge service can't take the cart or the merge failed: its lines * are re-added into the shared cart before adopting, attributes verbatim, outside Cart Hold's * guard — the same as a merged line. * - A line arriving by merge or adoption already carries whatever `_cart_hold_expiry_time` its * author gave it, which Cart Hold reads as the receipt. One without a stamp is simply not held. * - Claims made for a cart we then leave are NOT released here — those units either moved into the * adopted cart (the merge) or are still in the manager's ledger, whose sweep releases them. When * the shared cart is empty or gone, this device keeps its own cart, so its claims stay on the cart * they were made for. * * ─── Every failure keeps the local cart ────────────────────────────────────────────────────────── * * The device switches to the shared cart only once this cart's lines are known to be in it (or there * were none). A failed merge with nothing carried, or a cart that can't be read, leaves the device on * its own cart and the pointer where it was, and says why through `onError`. */ import { DEFAULT_MESSAGES, type CartSyncOptions, type SyncCartArgs } from "./types"; export declare class CartSyncClient { private readonly options; private readonly fetchImpl; private readonly timeoutMs; /** * The run in progress, plus the one queued behind it. Every caller while it lasts gets this same * promise, so it resolves only after the queued run too. */ private inFlight; /** The newest args from a `sync()` called mid-run: one more run, with these, when this one ends. */ private queuedArgs; /** Suppresses the repeat report when adopting a cart re-triggers the callers. */ private lastReported; /** Suppresses a repeat announcement for a cart the shopper has already been told about. */ private lastAnnounced; /** The local→shared pair last carried, so an adopt that fails after the re-add doesn't double it. */ private lastCarried; constructor(options: CartSyncOptions); private get config(); private reportError; private log; private request; private headers; /** * Tells Apptile which cart this shopper is on. The backend writes the `smart_cart_id` customer * metafield itself, with Admin credentials — the app deliberately does not, which is why no * metafield write scope is needed. Fire-and-forget: an add-to-cart must never fail because a * side-channel is down. */ reportCart(cartId: string, customerId: string | null): Promise; /** * Marks the customer's shared cart as converted once an order is placed. The backend appends it to * `smart_cart_checkedout_ids` (last five) rather than deleting the pointer, so another device still * holding that cart id can tell it is spent instead of resurrecting a paid-for cart. * * `shop` is required even though the app id identifies the store: this endpoint resolves the * customer from the query and — being a GET — is sent down the same domain-keyed branch the web * store uses. */ private markCheckedOutRemotely; /** * Folds the cart built on this device into the one Apptile has on file, before switching over. * Without it, a shopper who added items as a guest and then signed in would watch them vanish. * Server-side, so merged lines never pass through Cart Hold's guard (safe — see the class note). * * The service merges both ways (each cart ends up with both carts' lines; an item in both keeps the * larger quantity, on the shared cart's line) and answers `200 {"merged":true,…}`. Anything else — * another status, `merged` not `true`, no answer — is a failed merge: reported, and resolved false * so the caller does not switch carts on the strength of it. */ private mergeCarts; /** * Adds the given local lines (the ones the shared cart lacks) into the shared cart, for when the * merge service can't take the cart or the merge failed. One unsellable line fails a whole * `cartLinesAdd`, so a rejected batch is retried line by line. Resolves whether at least one line is * in the shared cart — including from an earlier run whose adopt then failed, which is not carried * twice. */ private carryLines; /** Adds the batch, or line by line when the batch is rejected. Resolves how many lines landed. */ private addAll; /** * Moves the carried lines' attribution counts with them. Runs before the adopt, because the Shopify * SDK takes the adopted cart's attribute as its base; written after, its next line write would * overwrite the merge. Best-effort: the shared cart is adopted either way. */ private carryAttribution; private fetchSharedCart; private readSpent; /** * Records a checkout locally — first, and even for an unknown customer: this device must not * re-adopt a cart it has just checked out even if the backend call never lands. */ recordCheckedOut(cartId: string | null): Promise; /** Retires the shared cart after an order: locally first, then on the backend. */ markCheckedOut(cartId: string | null, customerId: string | null): Promise; private announceOnce; private reportOnce; /** Reports even a cart reported before — for when the pointer has since moved off it. */ private reportNow; /** Says why the device stayed on its own cart. Returns the outcome, so a decision can end with it. */ private keepLocalCart; /** * Reconciles this device's cart with the shared pointer. No-op when the feature is off. * * A call while a run is in progress is not dropped: it queues one more run, with its args, that * starts when the current run ends. Later calls during the same run replace those args, so however * many triggers overlap, one run follows, with the newest. A trigger mid-run is often the one that * carries the news — the cart the app has just restored — and dropping it is how a sign-in once * left a shopper on an empty cart. The promise resolves after the queued run too. */ sync(args: SyncCartArgs): Promise; /** Runs, then runs again with the queued args for as long as a call queued some mid-run. */ private runThenQueued; /** * Decides, by what each cart holds, whether to switch to the shared cart — folding this cart's * lines into it first. Used when the host gives `readLines`. The README's decision table, in order: * * 1. this cart missing or empty → adopt the shared cart * 2. shared cart missing or empty → keep this cart, and make it the shared one * 3. both have lines, merge succeeded → adopt the shared cart * 4. both have lines, every line already there → adopt the shared cart (nothing to carry) * 5. both have lines, at least one carried → adopt the shared cart * 6. both have lines, nothing got across → keep this cart, pointer unchanged * * A `readLines` that throws — any of the three reads — ends the run in its catch: nothing changes. */ private decideByLines; /** * The 0.5 decision, for hosts that don't give `readLines`: merge when the merge service can take * the cart, then adopt. A cart it can't take is left behind, as in 0.5; a merge that fails now * keeps the local cart instead of adopting over it. */ private decideWithoutLines; private run; } /** Build (and remember) the client for this session. Safe to call again — replaces the instance. * Rebuild on a config change (e.g. a new shop). Mirrors `configureTileCredit`. */ export declare function configureCartSync(options: CartSyncOptions): CartSyncClient; /** The configured client, or null before `configureCartSync`. */ export declare function getCartSyncClient(): CartSyncClient | null; export { DEFAULT_MESSAGES }; //# sourceMappingURL=client.d.ts.map