{"_id":"@artossoftware/stoq-sdk","_rev":"7-dbe43e019cc2f86c946fef70bb03ca7b","name":"@artossoftware/stoq-sdk","dist-tags":{"latest":"0.6.0"},"versions":{"0.1.0":{"name":"@artossoftware/stoq-sdk","version":"0.1.0","keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"license":"MIT","_id":"@artossoftware/stoq-sdk@0.1.0","maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"dist":{"shasum":"8e9dc8ec922d955a11370d4727dce85fce74fb5b","tarball":"https://registry.npmjs.org/@artossoftware/stoq-sdk/-/stoq-sdk-0.1.0.tgz","fileCount":10,"integrity":"sha512-3FuF4zainhwtmmcra0Htxkr9CGe0dR66OQ9mYSr9wKseMbDthYIw+fBLw5rjIimHR6lLjZ6TKSjUQFSbSjlvJA==","signatures":[{"sig":"MEUCIQDo6EoMHsew34V8Twi3Sk3y/UZHWatBgJxi7gQUZCMY5AIgadKDtBf0gddEm4ujv5FuiLuQHHvRT1MOHBatjPDRozM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":801576},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","unpkg":"./dist/stoq.min.js","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"gitHead":"6dcca65c4215cc654fa5c54bf418a9bf33990b6b","scripts":{"test":"vitest run","build":"rm -rf dist && tsup","smoke":"node scripts/smoke.mjs","typecheck":"tsc --noEmit","playground":"python3 -m http.server 4477","test:watch":"vitest","publish:npm":"scripts/credentials decrypt && yarn build && NPM_CONFIG_USERCONFIG=credentials/.npmrc npm publish --access public","credentials:decrypt":"scripts/credentials decrypt","credentials:encrypt":"scripts/credentials encrypt"},"_npmUser":{"name":"kartik-artos","email":"kartik@artossoftware.com"},"jsdelivr":"./dist/stoq.min.js","_npmVersion":"10.5.0","description":"Headless storefront SDK for STOQ preorders and back-in-stock signups","directories":{},"sideEffects":["./dist/stoq.min.js"],"_nodeVersion":"20.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","react":"^18.3.1","vitest":"^3.0.0","happy-dom":"^15.11.7","react-dom":"^18.3.1","typescript":"^5.6.0","@types/react":"^18.3.12","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stoq-sdk_0.1.0_1781259296488_0.1782343377795843","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@artossoftware/stoq-sdk","version":"0.1.1","keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"license":"MIT","_id":"@artossoftware/stoq-sdk@0.1.1","maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"dist":{"shasum":"2069a0456a998986206879e6e64466273ab93ec4","tarball":"https://registry.npmjs.org/@artossoftware/stoq-sdk/-/stoq-sdk-0.1.1.tgz","fileCount":10,"integrity":"sha512-Ayt/+6J0LC5ob7vu2WkM325dPCBDILBE/KtgkKjec9BUOafxV0tjLo4Zt7n0Jt97s5QbDdRB4tKdJ6M9kjqidw==","signatures":[{"sig":"MEUCIAgnxJhHCtDDMS9mpA06EtvqPN5mkOfO0ojjO9t3AK9CAiEA5SaOcnHfmSqT9Dgh7U7EDIyZ6imZvWiutAusRBVj/W8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":801715},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","unpkg":"./dist/stoq.min.js","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"gitHead":"b73c6c979c7ac535979c8a05118f24fbc827e000","scripts":{"test":"vitest run","build":"rm -rf dist && tsup","smoke":"node scripts/smoke.mjs","typecheck":"tsc --noEmit","playground":"python3 -m http.server 4477","test:watch":"vitest","publish:npm":"scripts/credentials decrypt && yarn build && NPM_CONFIG_USERCONFIG=credentials/.npmrc npm publish --access public","credentials:decrypt":"scripts/credentials decrypt","credentials:encrypt":"scripts/credentials encrypt"},"_npmUser":{"name":"kartik-artos","email":"kartik@artossoftware.com"},"jsdelivr":"./dist/stoq.min.js","_npmVersion":"10.5.0","description":"STOQ preorders & back-in-stock for headless Shopify storefronts — typed client, React hooks for Hydrogen, drop-in script tag via jsDelivr, notify-me + preorder modals","directories":{},"sideEffects":["./dist/stoq.min.js"],"_nodeVersion":"20.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","react":"^18.3.1","vitest":"^3.0.0","happy-dom":"^15.11.7","react-dom":"^18.3.1","typescript":"^5.6.0","@types/react":"^18.3.12","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stoq-sdk_0.1.1_1781259986891_0.4487908158217817","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@artossoftware/stoq-sdk","version":"0.2.0","keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"license":"MIT","_id":"@artossoftware/stoq-sdk@0.2.0","maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"dist":{"shasum":"7d14224e73c2b7f04f1b8503d26a6fa514694312","tarball":"https://registry.npmjs.org/@artossoftware/stoq-sdk/-/stoq-sdk-0.2.0.tgz","fileCount":10,"integrity":"sha512-ufSD8+lDB8315IJ5jaDLIoSPvfCbJmwK49VxAom5oIuVWA4D9drhE1WgnXmvbdXQPy4RLBYgXfi0yvjFdwKFqA==","signatures":[{"sig":"MEUCIQDRaYXSsa1Ne1c/junS2nHQGjkgN/m5i5YldqtZQtkKOQIgTh1ZqMzfje+r4lEznLm6I5D7uB+ql/sTKJHRSTWjkc0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":975393},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","unpkg":"./dist/stoq.min.js","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"gitHead":"faff6cc0afbdc7cc81a7cc0559524aac9f5fce23","scripts":{"test":"vitest run","build":"rm -rf dist && tsup","smoke":"node scripts/smoke.mjs","typecheck":"tsc --noEmit","playground":"python3 -m http.server 4477","test:watch":"vitest","publish:npm":"scripts/credentials decrypt && yarn build && NPM_CONFIG_USERCONFIG=credentials/.npmrc npm publish --access public","credentials:decrypt":"scripts/credentials decrypt","credentials:encrypt":"scripts/credentials encrypt"},"_npmUser":{"name":"kartik-artos","email":"kartik@artossoftware.com"},"jsdelivr":"./dist/stoq.min.js","_npmVersion":"10.5.0","description":"STOQ preorders & back-in-stock for headless Shopify storefronts — typed client, React hooks for Hydrogen, drop-in script tag via jsDelivr, notify-me + preorder modals","directories":{},"sideEffects":["./dist/stoq.min.js"],"_nodeVersion":"20.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","react":"^18.3.1","vitest":"^3.0.0","happy-dom":"^15.11.7","react-dom":"^18.3.1","typescript":"^5.6.0","@types/react":"^18.3.12","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stoq-sdk_0.2.0_1781268320421_0.7327463602572397","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@artossoftware/stoq-sdk","version":"0.3.0","keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"license":"MIT","_id":"@artossoftware/stoq-sdk@0.3.0","maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"dist":{"shasum":"bfa11c5eca7c9ec4ebc710e498dc3ac83dd16d25","tarball":"https://registry.npmjs.org/@artossoftware/stoq-sdk/-/stoq-sdk-0.3.0.tgz","fileCount":10,"integrity":"sha512-XD5xRr7Wbb/qvYSXrQJ44p9m6PXdxZc1rhSNyG/1WffVllagclYiUh58smPk2ga+AteNVXUVVKs2iYclnOic9Q==","signatures":[{"sig":"MEUCIHlr3aWjpEBC3Qz/fKFeHXvdRatln2uwkpt3qmDwzyG5AiEAgF8kLcrH1PNU8KOTiDwYHxOrQYXV28Ze9vTK2/wXTGo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":977089},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","unpkg":"./dist/stoq.min.js","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"gitHead":"40398b74073361ea2bd97b8792ddcbe46dfd19aa","scripts":{"test":"vitest run","build":"rm -rf dist && tsup","smoke":"node scripts/smoke.mjs","typecheck":"tsc --noEmit","playground":"python3 -m http.server 4477","test:watch":"vitest","publish:npm":"scripts/credentials decrypt && yarn build && NPM_CONFIG_USERCONFIG=credentials/.npmrc npm publish --access public","credentials:decrypt":"scripts/credentials decrypt","credentials:encrypt":"scripts/credentials encrypt"},"_npmUser":{"name":"kartik-artos","email":"kartik@artossoftware.com"},"jsdelivr":"./dist/stoq.min.js","_npmVersion":"10.5.0","description":"STOQ preorders & back-in-stock for headless Shopify storefronts — typed client, React hooks for Hydrogen, drop-in script tag via jsDelivr, notify-me + preorder modals","directories":{},"sideEffects":["./dist/stoq.min.js"],"_nodeVersion":"20.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","react":"^18.3.1","vitest":"^3.0.0","happy-dom":"^15.11.7","react-dom":"^18.3.1","typescript":"^5.6.0","@types/react":"^18.3.12","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stoq-sdk_0.3.0_1781275254709_0.9607899924419145","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@artossoftware/stoq-sdk","version":"0.3.1","keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"license":"MIT","_id":"@artossoftware/stoq-sdk@0.3.1","maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"dist":{"shasum":"e1b84ffd0fd0507d4da071d2c5fb8e0e5d12aa9e","tarball":"https://registry.npmjs.org/@artossoftware/stoq-sdk/-/stoq-sdk-0.3.1.tgz","fileCount":10,"integrity":"sha512-W8atyPYgy9bpB7gCDdBpBZNk5tqW3I2gE6KOIeP8kUoHCq8i15euPRRQjcb6LNXtTRMItZXb1ot/fR/w/pRW4w==","signatures":[{"sig":"MEQCIH8m6U805dOq8WwgH1pwOYIqv0wycrCfedmHqpPYsadoAiBfnU8jLH6JlPoMu0EU6hhaPs+ae6ZuceH21qmT1KXeNA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":976688},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","unpkg":"./dist/stoq.min.js","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"gitHead":"73911c7e604934e7d337da12b5df8fc1b59923e1","scripts":{"test":"vitest run","build":"rm -rf dist && tsup","smoke":"node scripts/smoke.mjs","typecheck":"tsc --noEmit","playground":"python3 -m http.server 4477","test:watch":"vitest","publish:npm":"scripts/credentials decrypt && yarn build && NPM_CONFIG_USERCONFIG=credentials/.npmrc npm publish --access public","credentials:decrypt":"scripts/credentials decrypt","credentials:encrypt":"scripts/credentials encrypt"},"_npmUser":{"name":"kartik-artos","email":"kartik@artossoftware.com"},"jsdelivr":"./dist/stoq.min.js","_npmVersion":"10.5.0","description":"STOQ preorders & back-in-stock for headless Shopify storefronts — typed client, React hooks for Hydrogen, drop-in script tag via jsDelivr, notify-me + preorder modals","directories":{},"sideEffects":["./dist/stoq.min.js"],"_nodeVersion":"20.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","react":"^18.3.1","vitest":"^3.0.0","happy-dom":"^15.11.7","react-dom":"^18.3.1","typescript":"^5.6.0","@types/react":"^18.3.12","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stoq-sdk_0.3.1_1781279164609_0.2824067692257848","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@artossoftware/stoq-sdk","version":"0.5.0","keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"license":"MIT","_id":"@artossoftware/stoq-sdk@0.5.0","maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"dist":{"shasum":"2304f3588713a1a46e630b57778e78566aaf8718","tarball":"https://registry.npmjs.org/@artossoftware/stoq-sdk/-/stoq-sdk-0.5.0.tgz","fileCount":10,"integrity":"sha512-v13tXFAbQx0Ko4LwNwT/+wonQDKWjhdGd6VCoS0Ywqh2LtRjOHH0ZbQYspgFYoETlYsibOF/z2Rwn+a6aTf4Kg==","signatures":[{"sig":"MEQCIC7hp9iz0mxFg4wDo9EoU+/Fe+4J7uEPgZeWijH7jX9KAiAv8MFXIWDjd8faO1izBJ0ONhAibcTrsZmKkhra/c3VOA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1141300},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","unpkg":"./dist/stoq.min.js","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"gitHead":"841fb5e987e76915277e30dc1f4545c99f5471f9","scripts":{"test":"vitest run","build":"rm -rf dist && tsup","smoke":"node scripts/smoke.mjs","typecheck":"tsc --noEmit","playground":"python3 -m http.server 4477","test:watch":"vitest","publish:npm":"scripts/credentials decrypt && yarn build && NPM_CONFIG_USERCONFIG=credentials/.npmrc npm publish --access public","credentials:decrypt":"scripts/credentials decrypt","credentials:encrypt":"scripts/credentials encrypt"},"_npmUser":{"name":"kartik-artos","email":"kartik@artossoftware.com"},"jsdelivr":"./dist/stoq.min.js","_npmVersion":"10.5.0","description":"STOQ preorders & back-in-stock for headless Shopify storefronts — typed client, React hooks for Hydrogen, drop-in script tag via jsDelivr, notify-me + preorder modals","directories":{},"sideEffects":["./dist/stoq.min.js"],"_nodeVersion":"20.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","react":"^18.3.1","vitest":"^3.0.0","happy-dom":"^15.11.7","react-dom":"^18.3.1","typescript":"^5.6.0","@types/react":"^18.3.12","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stoq-sdk_0.5.0_1782128583222_0.84400134041332","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@artossoftware/stoq-sdk","version":"0.6.0","description":"STOQ preorders & back-in-stock for headless Shopify storefronts — typed client, React hooks for Hydrogen, drop-in script tag via jsDelivr, notify-me + preorder modals","license":"MIT","homepage":"https://docs.stoqapp.com/sdk/docs/headless/","repository":{"type":"git","url":"git+https://github.com/fluke/restock.git","directory":"packages/storefront-sdk"},"bugs":{"email":"support@stoqapp.com"},"type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"unpkg":"./dist/stoq.min.js","jsdelivr":"./dist/stoq.min.js","sideEffects":["./dist/stoq.min.js"],"scripts":{"build":"rm -rf dist && tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","playground":"python3 -m http.server 4477","smoke":"node scripts/smoke.mjs","credentials:decrypt":"scripts/credentials decrypt","credentials:encrypt":"scripts/credentials encrypt","publish:npm":"scripts/credentials decrypt && yarn build && NPM_CONFIG_USERCONFIG=credentials/.npmrc npm publish --access public"},"keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0","@types/react":"^18.3.12","happy-dom":"^15.11.7","react":"^18.3.1","react-dom":"^18.3.1","tsup":"^8.3.0","typescript":"^5.6.0","vitest":"^3.0.0"},"_id":"@artossoftware/stoq-sdk@0.6.0","gitHead":"47a3b042d628388a308dd4fdaa8e32d1e6488622","_nodeVersion":"20.12.0","_npmVersion":"10.5.0","dist":{"integrity":"sha512-ljHupih1B2qrj7HoymtpTdVwV7lMvE/wdwmeRIh92o1Hq1caLdgo2zksLSMfPZEsc3tUKrU74hngYM5fPtqxYw==","shasum":"d2dcc3ae15e240b25a4e15c4f8d88c43d4bd8a57","tarball":"https://registry.npmjs.org/@artossoftware/stoq-sdk/-/stoq-sdk-0.6.0.tgz","fileCount":13,"unpackedSize":1193919,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD+riNP0nQN4N6F99+B4HGr7B4mPZLnUZs3fesIewe7MgIhAOVP3Pthv9eLyOrxOFoow9JVB+g4b20SBg6vHv2clJwr"}]},"_npmUser":{"name":"kartik-artos","email":"kartik@artossoftware.com"},"directories":{},"maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/stoq-sdk_0.6.0_1787420558316_0.09858330862629816"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-12T10:14:56.398Z","modified":"2026-08-22T17:42:38.669Z","0.1.0":"2026-06-12T10:14:56.657Z","0.1.1":"2026-06-12T10:26:27.142Z","0.2.0":"2026-06-12T12:45:20.636Z","0.3.0":"2026-06-12T14:40:54.860Z","0.3.1":"2026-06-12T15:46:04.787Z","0.5.0":"2026-06-22T11:43:03.395Z","0.6.0":"2026-08-22T17:42:38.491Z"},"license":"MIT","keywords":["stoq","shopify","preorder","back-in-stock","storefront"],"description":"STOQ preorders & back-in-stock for headless Shopify storefronts — typed client, React hooks for Hydrogen, drop-in script tag via jsDelivr, notify-me + preorder modals","maintainers":[{"name":"kartik-artos","email":"kartik@artossoftware.com"}],"readme":"# @artossoftware/stoq-sdk\n\nHeadless storefront SDK for [STOQ](https://stoqapp.com) preorders and back-in-stock signups. Framework-free, fully typed, SSR-safe (nothing touches `window`/`document` at module load). Zero runtime dependencies.\n\nUse it to build custom storefronts (Hydrogen, headless, heavily customized themes) on the same data the STOQ theme app embed uses.\n\nSetup is two fields: your shop domain and its **public Storefront API access token** — the same token your headless store already uses. The SDK handles the data loading from there: the merchant's STOQ configuration in one query at `init`, and per-variant availability on demand (batched and cached) when you ask about a variant — all served from your shop's own Storefront API endpoint on Shopify's edge, so reads are fast, close to your shoppers, and as fresh as Shopify itself. Signups (`createSignup`) POST to the STOQ API and only fire on actual signups.\n\n## Install\n\n```bash\nnpm install @artossoftware/stoq-sdk\n# or\nyarn add @artossoftware/stoq-sdk\n```\n\n### Script tag (CDN head-embed)\n\n`dist/stoq.min.js` is a minified IIFE that exposes a global `Stoq` and auto-initializes from its own `data-` attributes on DOMContentLoaded:\n\n```html\n<script\n  src=\"https://cdn.jsdelivr.net/npm/@artossoftware/stoq-sdk@0/dist/stoq.min.js\"\n  data-shop=\"my-store.myshopify.com\"\n  data-storefront-token=\"your-public-storefront-api-token\"\n  defer></script>\n\n<script>\n  window.addEventListener('stoq:loaded', async () => {\n    const state = await Stoq.client.getVariantState(123456789)\n    // ...\n  })\n</script>\n```\n\n`data-shop` and `data-storefront-token` are both required for auto-init. Optional attributes: `data-market`, `data-locale`, `data-storefront-api-version`, `data-namespace`. Without `data-shop` the script only defines the global — call `Stoq.init({...})` yourself.\n\nOnce loaded, the client is available as `Stoq.client` (all methods from the [API reference](#api) below), and the UI helpers can be called directly: `Stoq.openModal(...)`, `Stoq.openInlineForm(...)`, `Stoq.removeInlineForm()`, `Stoq.openPreorderModal(...)`, `Stoq.preorderButtonFor(variant)`.\n\n### npm / ESM\n\n```ts\nimport { init } from '@artossoftware/stoq-sdk'\n\nconst stoq = await init({\n  shop: 'my-store.myshopify.com',\n  storefrontToken: '...',     // public Storefront API token (required)\n  market: 123,                // optional: numeric id or gid://shopify/Market/123\n  locale: 'fr-CA',            // optional\n})\n\n// Ask about any variant by id (number, numeric string, or GID) — the SDK\n// fetches its availability itself. Already have the variant object from\n// your own product query? Pass it instead and the lookup is skipped:\n//   stoq.getVariantState({ id, availableForSale, currentlyNotInStock })\nconst state = await stoq.getVariantState(variantId)\n\nif (state.isPreorder) {\n  // Add to cart via the Storefront Cart API:\n  const line = await stoq.cartLineFor(variantId, 2)\n  // { merchandiseId: 'gid://shopify/ProductVariant/...',\n  //   sellingPlanId: 'gid://shopify/SellingPlan/...', quantity: 2 }\n} else if (stoq.signupsEnabled /* && your data says it's sold out */) {\n  const result = await stoq.createSignup({ variantId, productId, email: 'shopper@example.com' })\n  if (result.ok) {\n    // subscribed — `stoq:restock-modal:submitted` was fired on window\n  }\n}\n```\n\n**Use the PUBLIC token.** Shopify issues both public and private Storefront API tokens — `storefrontToken` must be the **public** one (designed to be exposed in browser code; in Hydrogen it ships as the `PUBLIC_STOREFRONT_API_TOKEN` env var). Never put a private Storefront API token (or any Admin API credential) in client-side code.\n\n## API\n\n### `init(config): Promise<StoqClient>`\n\nLoads the shop's settings and selling plans and resolves to a ready client. Fires the documented `stoq:loaded` CustomEvent on `window` (`detail.pageType === 'sdk'`). Rejects when `shop`/`storefrontToken` are missing, when the settings metafield cannot be loaded, or when the Storefront API query fails as a whole.\n\n| `config` field | Type | Notes |\n|---|---|---|\n| `shop` | `string` | Required. myshopify domain. Sent as `X-Shopify-Shop-Domain` on signups. |\n| `storefrontToken` | `string` | Required. The shop's **public** Storefront API access token. |\n| `market` | `string \\| number?` | Shopify Market for market-scoped plans/limits. Accepts numeric id or GID. |\n| `country` | `string?` | ISO country code for `@inContext` on the SDK's own availability lookups (market-correct bare-id calls). |\n| `locale` | `string?` | Used for translated settings and forwarded on signups. |\n| `storefrontApiVersion` | `string?` | Storefront API version for metafield reads. Default `2025-07`. |\n| `namespace` | `string?` | Metafield namespace. Default `restockrocket_production`. |\n| `host` | `string?` | STOQ API host override (signups only). Default `https://app.stoqapp.com`. |\n\n### Variant inputs\n\nEvery variant-taking method accepts either a bare id (number, numeric string, or GID) or a **variant object** — your own variant with availability fields:\n\n```ts\ntype VariantInput = number | string | {\n  id: number | string\n  availableForSale?: boolean      // Storefront API field\n  currentlyNotInStock?: boolean   // Storefront API field\n  quantityAvailable?: number      // Storefront API field (needs the inventory read scope)\n  inventoryQuantity?: number      // raw alternative\n  inventoryPolicy?: 'continue' | 'deny'\n}\n```\n\nA bare id always works — the SDK fetches the availability itself. Passing the object (you already have it on any product page) skips that lookup and uses your data verbatim.\n\n### `StoqClient`\n\n| Member | Returns | Description |\n|---|---|---|\n| `getVariantState(variant)` | `Promise<VariantState>` | Derived state for one variant (see below). Resolves without network when the input carries availability fields. |\n| `cartLineFor(variant, quantity?)` | `Promise<CartLine>` | Storefront Cart API line (`CartLineInput` shape). `merchandiseId` is always set; when the variant should be purchased as a preorder the line carries the offer's marker — `sellingPlanId` (`gid://shopify/SellingPlan/<id>`) for Shopify-selling-plan offers, or `attributes` (`_stoq_shopify_selling_plan_id` + `Purchase type` line-item properties) for property-based offers. Pass the whole line through. `quantity` defaults to 1. |\n| `createSignup(input)` | `Promise<SignupResult>` | Creates a back-in-stock signup (`POST /api/v1/intents.json`). Requires `email` or `phone`. Fires `stoq:restock-modal:submitted` on success. |\n| `openModal(options)` | `ModalHandle` | Opens the notify-me (back-in-stock) signup modal — see below. |\n| `openInlineForm(options)` | `ModalHandle \\| null` | Renders the signup form inline in a container you supply. |\n| `removeInlineForm(container?)` | `void` | Removes inline forms (all of them without an argument). |\n| `openPreorderModal(options)` | `Promise<PreorderConfirmation \\| null>` | Payment-option picker modal (see below). Stock-gated like `cartLineFor`: resolves `null` (with a console warning) when the variant isn't currently preorderable. |\n| `preorderButtonFor(variant)` | `PreorderButtonContent \\| null` | Merchant-configured CTA copy: `{ label, disclaimer, shippingText }`. `null` when no plan applies. Not stock-gated — it's copy; gate rendering on `getVariantState(...).isPreorder`. |\n| `preorderPaymentOptionsFor(variant)` | `PreorderPaymentOption[]` | Payment options (full vs partial), translated, default first. |\n| `preorderLineFor(variant, quantity?, sellingPlanId?)` | `CartLine \\| null` | Cart line for an explicitly chosen payment option. Synchronous, so it stock-gates only when you pass a variant object with availability fields; with a bare id it returns the line regardless — prefer `cartLineFor` when you hold only an id. |\n| `sellingPlanFor(variant)` | `SellingPlan \\| null` | The plan that applies to the variant in the configured market (market-scoped plans win over global ones). |\n| `refresh()` | `Promise<void>` | Busts the caches (configuration + availability) and refetches. |\n| `preorderEnabled` | `boolean` | Whether the merchant has preorders enabled (shop-level). |\n| `signupsEnabled` | `boolean` | Whether back-in-stock signups (notify-me) are enabled (shop-level). |\n| `settings` | `Readonly<StoqSettings>` | Parsed shop settings (frozen) — prefer the named getters above for the common flags. |\n| `sellingPlans` | `ReadonlyArray<SellingPlan>` | The shop's selling plans (frozen). |\n| `shop` | `string` | The bound shop domain. |\n\n### Notify-me modal & inline form\n\n`openModal` renders STOQ's back-in-stock signup modal — form, validation, duplicate handling, success state — with texts and colors defaulting to the merchant's STOQ design settings (override per call via `texts`/`styles`):\n\n```ts\nstoq.openModal({\n  variantId,\n  productId,            // required — the backend rejects signups without it\n  productTitle: 'Trail Runner',\n  variantTitle: '43 / Blue',\n  imageUrl: 'https://cdn.shopify.com/…/shoe.jpg',\n  prefill: { email: customerEmail },   // optional\n})\n```\n\n`openInlineForm({ container: '#notify-me', ...sameOptions })` renders the same form inside your own element instead of a modal; `removeInlineForm()` tears it down. Fires `stoq:restock-modal:opened`/`closed`/`submitted`. Browser-only — throws during SSR. See [docs/modal.md](docs/modal.md) for the full option reference.\n\n### Preorder button + modal\n\n`preorderButtonFor` gives you the merchant-configured copy so you can render your own preorder button; `openPreorderModal` handles payment-option selection (full vs partial payment), quantity limits and the acknowledgement checkbox, then resolves with a ready-to-use cart line:\n\n```ts\nconst variant = { id, availableForSale, currentlyNotInStock } // from your product query\n\nconst button = stoq.preorderButtonFor(variant)\n// { label: 'Preorder now', disclaimer: '<p>Ships later…</p>', shippingText: 'Ships by May 2026' }\n\nbuttonEl.textContent = button.label\nbuttonEl.addEventListener('click', async () => {\n  const confirmation = await stoq.openPreorderModal({\n    variantId: variant,\n    productTitle: 'Trail Runner',\n    variantTitle: '43 / Blue',\n    imageUrl: 'https://cdn.shopify.com/…/shoe.jpg',\n  })\n  if (confirmation) {\n    // confirmation.cartLine carries the CHOSEN option's selling plan id:\n    // { merchandiseId, quantity, sellingPlanId: 'gid://shopify/SellingPlan/…' }\n    await addToCart(confirmation.cartLine)\n  }\n})\n```\n\nWhen the plan has a single payment option, no acknowledgement requirement and the quantity is within the preorder limits, `openPreorderModal` resolves **immediately without rendering anything** (mirroring the theme embed, which only opens its modal when a choice or correction is needed). See [docs/preorder-modal.md](docs/preorder-modal.md) for the full option/behavior reference.\n\n### Rendered widgets (drop-in)\n\nThe methods above are the building blocks. If you don't want to wire them up yourself, the `render*` methods create STOQ's own element, insert it into a container you point at, wire the click → modal → cart flow, and return a handle to re-render or tear down. They're the imperative twins of the [React components](#components) — usable from a head-embed or any vanilla-JS storefront (no JSX needed):\n\n```ts\nconst handle = await stoq.renderPreorderWidget(variant, {\n  container: '#stoq-slot',        // element or CSS selector\n  onAddToCart: (line) => addToCart(line),\n  badge: true,                    // also render a \"Preorder\" badge\n  notifyMeFallback: true,         // show notify-me when not preorderable\n  productId, productTitle, variantTitle, imageUrl,\n})\n\n// Re-render when the shopper switches variant (idempotent — replaces in place):\nawait handle.update(nextVariant)\nhandle.destroy()                  // remove it\n```\n\n| Method | Renders | Twin of |\n|---|---|---|\n| `renderPreorderWidget(variant, opts)` | Badge + CTA button (with optional notify-me fallback) — the one-call entry point | — |\n| `renderPreorderButton(variant, opts)` | The preorder CTA `<button>`, shown only when preorderable | `<StoqPreorderButton>` |\n| `renderPreorderBadge(variant, opts)` | A `Preorder` `<span>` badge | `<StoqPreorderBadge>` |\n| `renderNotifyMe(variant, opts)` | A button that expands into the back-in-stock signup form | `<StoqNotifyMeButton>` |\n\nEach returns a `Promise<WidgetHandle>` — `{ element, update(variant?), destroy() }`. Re-running a `render*` call against the same container replaces the previous widget. Styling uses the same `stoq-*` classnames and `--stoq-*` custom properties as the React components ([see `STOQ_COMPONENT_STYLES`](#components)).\n\nUnlike the theme app embed's `renderPreorderButton`, which hijacks the theme's existing add-to-cart button, these render **their own** element into a container you own — the right model for headless storefronts. In the head-embed they're also on the global: `Stoq.renderPreorderWidget(...)` / `window._RestockRocket.renderPreorderWidget(...)`.\n\n### Custom elements (zero-JS drop-in)\n\nFor the simplest possible integration — no JS wiring at all — use the custom elements. The CDN head-embed registers them automatically on auto-init; just drop the tag in your markup:\n\n```html\n<stoq-preorder-widget\n  variant-id=\"40020223...\"\n  product-id=\"73912...\"\n  product-title=\"Trail Runner\"\n  badge\n  notify-me-fallback>\n</stoq-preorder-widget>\n\n<script>\n  document.addEventListener('stoq:add-to-cart', (e) => addToCart(e.detail.line))\n</script>\n```\n\n**Attributes in, events out.** Inputs are attributes — change `variant-id` (directly, or via a framework binding like `variant-id={selected.id}`) and the element **re-renders itself** (no `update()` call). Because HTML attributes can't carry a callback, add-to-cart surfaces as a `stoq:add-to-cart` CustomEvent (`detail: { line, confirmation }`, bubbles + composed). Pass availability via `available-for-sale` / `currently-not-in-stock` attributes to resolve with no lookup. For a no-framework storefront, `watch=\"<selector>\"` (e.g. `watch=\"select[name=id]\"`) copies the source's value into `variant-id` on `change`.\n\nElements: `<stoq-preorder-widget>` (badge + CTA, with `badge` / `notify-me-fallback` / `label` / `quantity`), `<stoq-preorder-badge>` (`text`), `<stoq-notify-me>` (`product-id` required, `expanded`, `label`).\n\nFor npm/ESM consumers (no auto-init), register them against your `init()`-ed client:\n\n```ts\nimport { init, defineStoqElements } from '@artossoftware/stoq-sdk'\nconst stoq = await init({ shop, storefrontToken })\ndefineStoqElements(stoq)   // now <stoq-*> tags work anywhere in the page\n```\n\n### `VariantState`\n\n| Field | Type | Meaning |\n|---|---|---|\n| `isPreorder` | `boolean` | An enabled plan applies in the current market AND the variant is currently sellable as a preorder (decided by its availability — supplied by you or looked up by the SDK). |\n| `sellingPlanId` | `number \\| null` | Numeric Shopify selling plan id to purchase with. |\n| `sellingPlanGid` | `string \\| null` | Same as `gid://shopify/SellingPlan/<id>`. |\n| `shippingText` | `string \\| null` | Estimated-shipping text (market-scoped). |\n| `maxCount` | `number \\| null` | Configured preorder unit limit (`null` = unlimited/not set). |\n| `remainingCount` | `number \\| null` | `maxCount - sold`, clamped at 0 (`null` when unlimited). |\n\nThere is deliberately no out-of-stock flag: your storefront already knows availability. Gate notify-me UI on your own availability data plus `client.signupsEnabled`.\n\n### `SignupInput` / `SignupResult`\n\n```ts\ncreateSignup({\n  variantId,            // required — id (or variant object; only the id is used)\n  productId,            // required — the backend rejects signups without it\n  email?, phone?,       // at least one required; phone in E.164\n  channel?,             // 'email' | 'sms'\n  quantity?,            // default 1\n  name?, locale?,\n  acceptsMarketing?,    // marketing consent → customer.accepts_marketing\n  country?,             // → customer.country + intent.country\n  countryCode?,         // → customer.country_code\n  shopifyCustomerId?,   // number | GID — logged-in shopper → customer.shopify_customer_id\n  product?: {           // → product params (snake_cased)\n    title?, variantTitle?, vendor?, sku?, variantCount?,\n  },\n}) => { ok, status, intents, errors }\n```\n\n`ok` is `true` when the backend created the signup (HTTP 201). Validation failures (e.g. duplicate signup) come back as `ok: false` with `errors` populated. The configured `market` is forwarded automatically on both the intent and the customer.\n\n**Headless tip:** pass `product` (at least `title`, `variantTitle`, `variantCount`) with every signup. STOQ uses it to render notifications even when the product hasn't been synced into STOQ's database yet — common on headless storefronts where the shop's catalog webhooks may lag a brand-new product.\n\n## Events\n\nDispatched on `window`; silently skipped during SSR. Names and payloads match the [documented STOQ custom events](https://docs.stoqapp.com).\n\n| Event | When | `detail` |\n|---|---|---|\n| `stoq:loaded` | after `init` resolves | `{ pageType: 'sdk', enabled, settings, preorderEnabled }` |\n| `stoq:restock-modal:submitted` | after a successful `createSignup` | `{ action: 'submitted', product: { id }, variant: { id }, customer: { email, phone } }` |\n| `stoq:preorder-modal:opened` | preorder modal rendered | `{ productData, variantId, sellingPlan }` |\n| `stoq:preorder-modal:closed` | preorder modal confirmed/dismissed | `{ action: 'accepted' \\| 'rejected', productData, variantId, selectedSellingPlanId, sellingPlan, quantity?, acknowledgedPreorder? }` |\n| `stoq:preorder-payment-option:selected` | shopper picks a payment option in the modal | `{ paymentWidget, variantId, selectedSellingPlanId }` (dispatched on the widget element, bubbles to `window`) |\n\n## How data loads\n\nTwo kinds of data, both from your shop's own Storefront API endpoint (`https://{shop}/api/{version}/graphql.json`, Shopify's edge):\n\n1. **Merchant configuration** (settings, preorder offers, shipping texts, limits) — STOQ mirrors it onto shop metafields; `init` reads them with one GraphQL query, session-cached for 5 minutes.\n2. **Per-variant availability** — fetched on demand when you ask about a variant by bare id: every lookup in the same tick is batched into one `nodes(ids: [...])` query, and results are cached in memory for 60 seconds. Pass your own variant object (you already have it from your product query) and the lookup is skipped entirely.\n\nAvailability is what decides whether an attached offer is currently sellable:\n\n| The variant says | Meaning | Sellable under |\n|---|---|---|\n| `currentlyNotInStock: true` | out of stock but still purchasable (`inventory_policy: continue`) | STOQ-inventory offers |\n| `availableForSale: true` and `currentlyNotInStock: false` | in stock | Shopify-inventory offers (sell in-stock units as preorders) |\n\nWhen you pass a variant object, `quantityAvailable` or raw `inventoryQuantity` + `inventoryPolicy` work too.\n\n**Markets:** when `market` is configured, the metafields query selects STOQ's `_for_market_<id>` metafields and prefers them — same precedence as the theme embed. The SDK's own availability lookups answer for the shop's default country unless you set `country` (applied as `@inContext`); if you localize inventory, passing your own `@inContext`-queried variant objects is the most precise option.\n\nIf the metafields query fails as a whole (bad token, wrong API version) `init` rejects with a `StoqStorefrontApiError`; a missing/invalid individual metafield — or a failed availability lookup — degrades gracefully (defaults + console warning).\n\n## Caching, freshness & rate limits\n\nThe metafield response is cached in `sessionStorage` (TTL 5 minutes) so repeat page views within a session don't re-fetch; keys include the shop, market, namespace and API version. When `sessionStorage` is unavailable (SSR, private browsing, quota) an in-memory cache is used. Availability lookups are cached in memory only, for 60 seconds — availability is volatile by nature. `refresh()` busts both and refetches.\n\nTwo things to design for:\n\n- **Merchant configuration can be briefly stale.** Between Shopify's edge caching, the metafield sync cadence and the 5-minute session cache, offer configuration may lag merchant changes by a few minutes. Availability is much fresher (60-second cache, or exactly your own data when you pass the variant object) — and Shopify enforces the real sellable state at checkout regardless.\n- **Writes are rate limited.** The signup endpoint (`POST /api/v1/intents.json`) is subject to rate limiting. Normal shopper-driven signups never get near the limits; if you batch or replay signups programmatically, expect `429`s and back off.\n\n## Behavior notes & caveats\n\n- **Pass the variant object when you have it.** It skips the SDK's availability lookup and uses your (possibly `@inContext`-localized) data verbatim; bare ids cost one batched Storefront API call per minute per variant.\n- **Gate notify-me on your own availability data** (`!variant.availableForSale`) plus `client.signupsEnabled`.\n- **Property-based plans** (`use_shopify_selling_plan: false`) never get a `sellingPlanId` — STOQ tracks those preorders through line-item properties, not a Shopify selling plan. The SDK puts that marker on the line for you as `attributes: [{ key: '_stoq_shopify_selling_plan_id', value }, { key: 'Purchase type', value: 'Preorder' }]` (the same two the theme embed writes). Pass the line to `cartLinesAdd` untouched; drop the attributes and the order is a plain full-price sale as far as STOQ can tell.\n- **Market-scoped offers need `market`.** An offer restricted to specific Shopify Markets only applies when you configure `market` (the theme embed reads it from `localization.market.id`; the SDK has no way to infer it). Without it, `isPreorder` is `false` for every variant on such an offer — a shop whose offers are *all* market-scoped looks like it has no preorders. Global (non-market) offers work without it.\n- **Stock gating.** `getVariantState`, `cartLineFor`, `openPreorderModal`, the `render*` widgets and the React components all refuse to treat a variant as a preorder unless its availability says it's currently sellable under the offer. The synchronous `preorderLineFor` can only do that when you pass a variant object with availability fields.\n- **Selling plan id preference**: `default_shopify_selling_plan_id` (multi-payment-option plans) wins over `shopify_selling_plan_id`, matching the theme embed.\n\n## React / Hydrogen\n\n`@artossoftware/stoq-sdk/react` is a hooks layer over the same client, built for Hydrogen and other React 18+ storefronts. React is an **optional peer dependency** — vanilla consumers of the core entry never need it installed.\n\n```bash\nnpm install @artossoftware/stoq-sdk react\n```\n\nSSR-safe: importing the module never touches `window`/`document`, and the client initializes inside an effect — server renders see `status: 'loading'` (the bundled components render nothing until the client is ready).\n\n### Provider\n\nWrap your app (or just the product route) once. In Hydrogen, pass the public Storefront API token your app already has — the `PUBLIC_STOREFRONT_API_TOKEN` env var every Hydrogen project ships with:\n\n```tsx\nimport { StoqProvider } from '@artossoftware/stoq-sdk/react'\n\nexport default function App() {\n  return (\n    <StoqProvider\n      config={{\n        shop: 'my-store.myshopify.com',\n        storefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,\n      }}\n    >\n      <Outlet />\n    </StoqProvider>\n  )\n}\n```\n\n`config` takes the same fields as `init`. The client re-initializes if any field changes; inline `config={{ ... }}` literals are fine.\n\n### Hooks\n\nEvery hook takes a [`VariantInput`](#variant-inputs) — a bare id works (the SDK fetches availability itself), and passing your variant object skips the lookup. Inline literals are fine; memoization keys on the field values, not object identity:\n\n| Hook | Returns | Notes |\n|---|---|---|\n| `useStoq()` | `{ client, status, error, revision, refresh }` | `status` is `'loading' \\| 'ready' \\| 'error'`. Throws outside `<StoqProvider>`. |\n| `useStoqVariant(variant)` | `VariantState \\| null` | `null` while loading. Re-derives on ready, on variant change, and after `refresh()`. |\n| `useStoqCartLine(variant, quantity?)` | `CartLine \\| null` | Wraps `client.cartLineFor` — includes `sellingPlanId` only when the variant should be sold as a preorder. |\n| `useStoqPreorder(variant)` | `{ state, button, options, openModal, resolveLine }` | Preorder UI state: merchant CTA copy, payment options (default first), `openModal()` (the preorder modal), and `resolveLine(quantity?, sellingPlanId?)` for the final cart line. |\n| `useStoqSignup()` | `{ submit, submitting, result, error }` | `submit(input)` wraps `client.createSignup`; validation failures land in `result.ok === false`, thrown errors in `error`. |\n\n`StoqClient` has no subscription mechanism — derived state only changes when data is refetched. Call `useStoq().refresh()` to bust the cache and refetch; it bumps an internal revision counter so every hook consumer re-renders with freshly derived state.\n\n### Adding a preorder to the cart with Hydrogen's `CartForm`\n\nThe cart line from `useStoqCartLine` (or `client.cartLineFor`) is already in `CartLineInput` shape — `merchandiseId`, `quantity`, and `sellingPlanId` when the variant should be purchased as a preorder — so it can be passed straight to `cartLinesAdd`. Include `availableForSale` and `currentlyNotInStock` in your variant fragment and pass the variant object through:\n\n```tsx\nimport { CartForm } from '@shopify/hydrogen'\nimport { useStoqVariant, useStoqCartLine } from '@artossoftware/stoq-sdk/react'\n\nfunction AddToCartButton({ variant }: {\n  variant: { id: string; availableForSale: boolean; currentlyNotInStock: boolean }\n}) {\n  const state = useStoqVariant(variant)\n  const line = useStoqCartLine(variant, 1)\n\n  // The SDK initializes in an effect (SSR-safe), so the server render and\n  // the first client paint don't have its answer yet — and neither does a\n  // misconfigured Provider. Don't return null here: fall back to your plain\n  // add-to-cart so the buy box is never empty.\n  const lines = [line ?? { merchandiseId: variant.id, quantity: 1 }]\n\n  return (\n    <CartForm\n      route=\"/cart\"\n      action={CartForm.ACTIONS.LinesAdd}\n      inputs={{ lines }}\n    >\n      <button type=\"submit\">\n        {state?.isPreorder ? 'Preorder now' : 'Add to cart'}\n      </button>\n      {state?.isPreorder && state.shippingText ? <p>{state.shippingText}</p> : null}\n    </CartForm>\n  )\n}\n```\n\nWhen `state.isPreorder` is true the line carries the offer's marker: `sellingPlanId` (`gid://shopify/SellingPlan/<id>`, Shopify attaches the deferred purchase option) for Shopify-selling-plan offers, or `attributes` (`_stoq_shopify_selling_plan_id` + `Purchase type` line-item properties) for property-based offers (`use_shopify_selling_plan: false`). Either way, pass the whole line through.\n\nAdding from a **callback** instead — `<StoqPreorderButton onAddToCart>`, or after `openPreorderModal` resolves — means posting to the `/cart` action yourself. `toCartFormInput(line)` builds the `{ cartFormInput }` payload Hydrogen's `CartForm` expects so you don't hand-write the wire format:\n\n```tsx\nimport { useFetcher } from 'react-router' // `@remix-run/react` on Hydrogen < 2025.5\nimport { StoqPreorderButton, toCartFormInput } from '@artossoftware/stoq-sdk/react'\n\nconst fetcher = useFetcher()\n<StoqPreorderButton\n  variantId={variant}\n  onAddToCart={(line) => fetcher.submit(toCartFormInput(line), { method: 'post', action: '/cart' })}\n/>\n```\n\nSee [`examples/HydrogenBuyBox.tsx`](examples/HydrogenBuyBox.tsx) for the complete buy box (preorder / notify-me / plain add-to-cart, with the loading fallback).\n\n### Components\n\nPolaris-free, with minimal **overrideable** default styling: the components emit `stoq-*` classnames and inject one small stylesheet whose selectors are all wrapped in `:where()` — zero specificity, so any rule in your own CSS wins. Quick theming via CSS custom properties:\n\n```css\n:root {\n  --stoq-accent: #1a56db;          /* buttons */\n  --stoq-accent-contrast: #fff;\n  --stoq-radius: 8px;\n  --stoq-badge-background: #eef1f4;\n  --stoq-badge-color: #303030;\n  --stoq-border: #c9cccf;          /* inputs */\n  --stoq-error: #b3261e;\n  --stoq-success: #1a7f37;\n}\n```\n\nTo opt out of the defaults entirely, ship your own `<style data-stoq-sdk-components>` (or `<link data-stoq-sdk-components>`) element — when one exists the SDK injects nothing. The full default stylesheet is exported as `STOQ_COMPONENT_STYLES`. All components render nothing during SSR/loading.\n\n```tsx\nimport { StoqNotifyMeButton, StoqPreorderBadge, StoqPreorderButton } from '@artossoftware/stoq-sdk/react'\n\n<StoqPreorderBadge variantId={variant} />          {/* default text \"Preorder\" */}\n<StoqPreorderBadge variantId={variant}>Ships in March</StoqPreorderBadge>\n\n<StoqPreorderButton\n  variantId={variant}\n  productTitle={product.title}\n  imageUrl={product.featuredImage?.url}\n  onAddToCart={(line) => addLinesToCart([line])}   {/* CartLineInput with the chosen option's sellingPlanId */}\n/>\n\n<StoqNotifyMeButton\n  variantId={variant.id}\n  productId={product.id}\n  onSuccess={(result) => console.log('subscribed', result)}\n/>\n```\n\n- `StoqPreorderBadge` renders its children (default `\"Preorder\"`) in `<span class=\"stoq-preorder-badge\">` only when the variant is currently preorderable — pass the variant object so that check is exact.\n- `StoqPreorderButton` renders `<button class=\"stoq-preorder-button\">` with the merchant-configured CTA label (`preorder_button_text`; override via `children`) only when the variant is currently preorderable. Clicking opens the preorder modal when the plan has multiple payment options or a required acknowledgement — otherwise it confirms immediately without rendering a modal — and calls `onAddToCart(line, confirmation)` with the final `CartLineInput` honoring the chosen option's selling plan id. Props: `variantId` (a `VariantInput`), `onAddToCart` (required), `quantity?`, `productId?`, `productTitle?`, `variantTitle?`, `imageUrl?`, `modal?` (per-call `texts`/`styles`). For full control build your own button on `useStoqPreorder()`. See [docs/preorder-modal.md](docs/preorder-modal.md).\n- `StoqNotifyMeButton` renders a button that expands into an inline email form and creates a back-in-stock signup. It hides itself when the shop's signup widget is disabled, but is intentionally **not** gated on stock state — gate rendering on your own availability data, e.g. `!variant.availableForSale`. Classnames: `stoq-notify-me`, `stoq-notify-me__button`, `stoq-notify-me__form`, `stoq-notify-me__input`, `stoq-notify-me__submit`, `stoq-notify-me__error`, `stoq-notify-me__success`.\n\nFor full control, build your own UI on `useStoqSignup()` / `useStoqVariant()` — the components are thin wrappers over those hooks.\n\n## Testing against a real store\n\nTwo harnesses live in the repo (`packages/storefront-sdk/` — `examples/` and `scripts/` are not part of the npm tarball):\n\n- **Playground** — `yarn build && yarn playground`, then open\n  `http://localhost:4477/examples/playground.html`. Enter a shop domain and\n  its public Storefront API token (persisted in localStorage), probe\n  variants under different availability states, open both modals, and run\n  the docs' verification checklist as one-click checks.\n- **CLI smoke** — `yarn smoke` (env: `SHOP`, `STOREFRONT_TOKEN`, `VARIANT`,\n  `PRODUCT`, `EMAIL`) runs init → state → button → cart line → signup against\n  a live store and exits non-zero on failure; suitable as a release gate.\n\n## Development\n\n```bash\nyarn install\nyarn build       # tsup → dist/index.js (+ .d.ts, ESM) and dist/stoq.min.js (IIFE, global `Stoq`)\nyarn test        # vitest (fetch fully mocked, no network)\nyarn typecheck\n```\n\nThis package is intentionally self-contained: it has its own lockfile and does not participate in the Rails app's webpack build. CI (`.github/workflows/sdk-ci.yml`) runs typecheck + test + build on every change under `packages/storefront-sdk/`.\n\n### Releasing\n\n1. Bump `version` in `package.json` and add a `## [x.y.z]` section to [`CHANGELOG.md`](CHANGELOG.md) — CI fails without one.\n2. Merge to `main`.\n3. `git tag sdk-v<x.y.z> <merge-sha> && git push origin sdk-v<x.y.z>` — `.github/workflows/sdk-publish.yml` checks the tag matches `package.json`, runs typecheck/test/build, and publishes to npm with provenance. jsDelivr picks the new version up from npm automatically; there is no separate CDN step.\n\n`yarn publish:npm` (decrypts the in-package npm token) remains as a manual fallback.\n","readmeFilename":"README.md","homepage":"https://docs.stoqapp.com/sdk/docs/headless/","repository":{"type":"git","url":"git+https://github.com/fluke/restock.git","directory":"packages/storefront-sdk"},"bugs":{"email":"support@stoqapp.com"}}