{"_id":"@apeyron/buypass-sdk","_rev":"3-890801a1b796921c591e7e852b793ef1","name":"@apeyron/buypass-sdk","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@apeyron/buypass-sdk","version":"0.1.0","keywords":["buypass","menu","storefront","qr-menu","sdk"],"author":{"name":"Apeyron","email":"apeyronsrl@gmail.com"},"license":"MIT","_id":"@apeyron/buypass-sdk@0.1.0","maintainers":[{"name":"michaelsaccone","email":"michael.saccone.it@gmail.com"},{"name":"apeyron.ai","email":"apeyronsrl@gmail.com"}],"dist":{"shasum":"5a3bd97594820ada68d8a860dc16300dd8062e04","tarball":"https://registry.npmjs.org/@apeyron/buypass-sdk/-/buypass-sdk-0.1.0.tgz","fileCount":9,"integrity":"sha512-Jh1oWUZxDnGWXTrgh91pPDI8yxa8CetyuOFfCC4kD+ZzuT4DJHw4CbKcxLkvYMth+N7RL7vy/yz0wM0F0uMtuA==","signatures":[{"sig":"MEUCICCeVaHfDp+pB94MMTSp58AbFCdQJjz5ouBv2a0CJYfGAiEAyIB0FzG09qfj+YiFifGPcZkPyt7BHq0lQ/DfOWHQFFo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":67185},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"84834aac48c7c39be2c01bbe3bdf4c2f317dc750","scripts":{"dev":"tsup --watch","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit"},"_npmUser":{"name":"michaelsaccone","email":"michael.saccone.it@gmail.com"},"_npmVersion":"10.9.2","description":"Official TypeScript SDK for the Buypass public API. Powers whitelabel storefronts that consume a merchant's menu, products, and events.","directories":{},"sideEffects":false,"_nodeVersion":"22.17.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","typescript":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/buypass-sdk_0.1.0_1778841944069_0.5657326064396366","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@apeyron/buypass-sdk","version":"0.2.0","keywords":["buypass","menu","storefront","qr-menu","sdk"],"author":{"name":"Apeyron","email":"apeyronsrl@gmail.com"},"license":"MIT","_id":"@apeyron/buypass-sdk@0.2.0","maintainers":[{"name":"michaelsaccone","email":"michael.saccone.it@gmail.com"},{"name":"apeyron.ai","email":"apeyronsrl@gmail.com"}],"dist":{"shasum":"e05f86c591878ff416f6adaf30c55074a76844b9","tarball":"https://registry.npmjs.org/@apeyron/buypass-sdk/-/buypass-sdk-0.2.0.tgz","fileCount":9,"integrity":"sha512-cCLuHmihydWObwC+OWRR80F4L66foQf1rtZ4HNwTMXKe8LGz0KoRvDBYRduvCPbCXGjvfE5XMgnn/TlbejGMbg==","signatures":[{"sig":"MEUCIQCYMrIof4W+JGZrKpLCFrURs1o6ChEMconiYet67QtOewIgUU9aWZCrv6ymnK73OT2HxDQzD9+ff4cwSvMrX00pwo4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":114685},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"2c7afeabf386366f946686ef414ab0b346260245","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","clean":"rm -rf dist","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"michaelsaccone","email":"michael.saccone.it@gmail.com"},"_npmVersion":"10.9.2","description":"Official TypeScript SDK for the Buypass public API. Powers whitelabel storefronts that consume a merchant's menu, products, and events.","directories":{},"sideEffects":false,"_nodeVersion":"22.17.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","vitest":"^2.1.0","typescript":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/buypass-sdk_0.2.0_1778860094689_0.6799581210152521","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@apeyron/buypass-sdk","version":"0.3.0","description":"Official TypeScript SDK for the Buypass public API. Powers whitelabel storefronts that consume a merchant's menu, products, and events.","license":"MIT","author":{"name":"Apeyron","email":"apeyronsrl@gmail.com"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"publishConfig":{"access":"public"},"sideEffects":false,"engines":{"node":">=18"},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","clean":"rm -rf dist"},"devDependencies":{"tsup":"^8.3.0","typescript":"^5.5.0","vitest":"^2.1.0"},"keywords":["buypass","menu","storefront","qr-menu","sdk"],"gitHead":"453f4cca26ee0d8f2574b1e697788c635f2fec02","_id":"@apeyron/buypass-sdk@0.3.0","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-b2HBygxedTIJyLtExx+3VtnQoV9VJXYkcglaIQaoko7PUoFKN9y+29uigmkOamW//P61pptaUuSWewI1KEEMNw==","shasum":"038e3d715d90c495106b607692a6501cf984c87c","tarball":"https://registry.npmjs.org/@apeyron/buypass-sdk/-/buypass-sdk-0.3.0.tgz","fileCount":9,"unpackedSize":114645,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIF3f2dYnqaC7mLYgqh7t5ugvsXMvBLYIrZKzyv71XE8xAiEA1pOjP6O9NomiWilaRHlndtzz0AUMIH3stybzdWeCA1w="}]},"_npmUser":{"name":"michaelsaccone","email":"michael.saccone.it@gmail.com"},"directories":{},"maintainers":[{"name":"michaelsaccone","email":"michael.saccone.it@gmail.com"},{"name":"apeyron.ai","email":"apeyronsrl@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/buypass-sdk_0.3.0_1778878904587_0.8594583734507331"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-15T10:45:43.991Z","modified":"2026-05-15T21:01:44.896Z","0.1.0":"2026-05-15T10:45:44.219Z","0.2.0":"2026-05-15T15:48:14.824Z","0.3.0":"2026-05-15T21:01:44.746Z"},"author":{"name":"Apeyron","email":"apeyronsrl@gmail.com"},"license":"MIT","keywords":["buypass","menu","storefront","qr-menu","sdk"],"description":"Official TypeScript SDK for the Buypass public API. Powers whitelabel storefronts that consume a merchant's menu, products, and events.","maintainers":[{"name":"michaelsaccone","email":"michael.saccone.it@gmail.com"},{"name":"apeyron.ai","email":"apeyronsrl@gmail.com"}],"readme":"# @apeyron/buypass-sdk\n\nOfficial TypeScript SDK for the Buypass public API. Use it from any whitelabel storefront — Next.js, Vite, Bun, Cloudflare Workers — to consume a merchant's published menu, branding, and translations.\n\nThe SDK is read-only: merchants manage their content from the Buypass dashboard; your storefront renders it.\n\n## Install\n\n```bash\nnpm install @apeyron/buypass-sdk\n# or\nbun add @apeyron/buypass-sdk\n```\n\nRequires Node 18+ (or any runtime with native `fetch`).\n\n## Quickstart\n\n```ts\nimport { createBuypassClient } from \"@apeyron/buypass-sdk\";\n\nconst buypass = createBuypassClient({\n  merchantSlug: \"bilocale-con-pizza\",\n  defaultLocale: \"it\",\n});\n\n// Fetch the merchant's primary menu in one call\nconst menu = await buypass.menus.getDefault();\n\nconsole.log(menu.name);                     // \"Menu bilocale\"\nconsole.log(menu.primaryColor);             // \"#0F172A\"\nconsole.log(menu.categories[0]?.products);  // MenuProduct[]\n```\n\n## Configuration\n\n```ts\ncreateBuypassClient({\n  merchantSlug: \"bilocale-con-pizza\",   // required, stable merchant identifier\n  baseUrl: \"https://buypass.it\",         // defaults to production\n  defaultLocale: \"it\",                   // applied when get() / getDefault() omit locale\n  fetch: customFetch,                    // optional: tests, Workers, retry wrappers\n  defaultFetchOptions: {                 // merged into every request\n    next: { revalidate: 60 },            //   e.g. Next.js ISR\n  },\n});\n```\n\n`merchantSlug` is validated client-side (lowercase letters, digits, hyphens). Invalid slugs throw at construction time, not on first request.\n\n## API\n\n### Discovery\n\n#### `client.menus.list(): Promise<MenuSummary[]>`\n\nLists the merchant's publicly visible menus. Drafts and expired menus are filtered out server-side.\n\n```ts\nconst summaries = await buypass.menus.list();\n// [{ qrSlug: \"menu-bilocale\", name: \"Menu bilocale\", primaryColor, accentColor, defaultLocale, enabledLocales, logoUrl }]\n```\n\n### Full menu\n\n#### `client.menus.get(qrSlug, options?): Promise<Menu>`\n\nFull menu tree for a specific qrSlug, optionally translated. Variants are nested under their parent product (e.g. a wine returns one product with `variants: [{ name: \"Bottiglia\", price: 1000 }, { name: \"Bicchiere\", price: 500 }]`).\n\n```ts\nconst menu = await buypass.menus.get(\"menu-bilocale\", { locale: \"en\" });\n```\n\nWhen the requested locale isn't in the menu's `enabledLocales`, the response falls back to `defaultLocale` field-by-field (no partial silent fallbacks).\n\n#### `client.menus.getDefault(options?): Promise<Menu>`\n\nConvenience: `list()` + `get(first)` in one round trip. Common case for restaurants with a single menu. Throws `BuypassApiError({ code: \"MENU_NOT_FOUND\" })` if the merchant has no published menus.\n\n### Categories landing page\n\n#### `client.menus.listCategories(qrSlug, options?): Promise<MenuCategorySummary[]>`\n\nLightweight per-category metadata (id, slug, name, image, productCount). No products inline. Use this for the menu's landing page where you want to render a category grid without paying for the full tree.\n\n```ts\nconst categories = await buypass.menus.listCategories(\"menu-bilocale\", { locale: \"it\" });\n// [{ id, slug: \"antipasti\", name: \"Antipasti\", imageUrl, productCount: 12 }, ...]\n```\n\n### Paginated + filtered products (category page)\n\n#### `client.menus.listProducts(qrSlug, params?): Promise<MenuProductsPage>`\n\nServer-side filter + pagination. Use this for the category landing page so you don't ship the entire menu over the wire when a customer is only viewing one section.\n\n```ts\nconst page = await buypass.menus.listProducts(\"menu-bilocale\", {\n  categorySlug: \"antipasti\",          // omit or \"all\" → every category\n  search: \"pizza margherita\",         // trimmed + collapsed; empty = no filter\n  excludeAllergens: [\"GLUTINE\", \"LATTE\"],\n  page: 1,\n  pageSize: 20,                       // server enforces 1..100\n});\n// → { products: MenuProduct[], total, page, pageSize, hasMore }\n```\n\n**Validation:** `page < 1` or `pageSize` outside `[1, 100]` rejects with `BuypassApiError({ code: \"BAD_REQUEST\" })` *before* hitting the network.\n\n**Stable order:** `(category.position, product.position, productId)`. Pagination is consistent across requests as long as the underlying menu data doesn't shift.\n\n#### Progressive \"Load more\" pattern\n\n```ts\n\"use client\";\nimport { useState } from \"react\";\nimport { createBuypassClient, type MenuProduct } from \"@apeyron/buypass-sdk\";\n\nconst buypass = createBuypassClient({ merchantSlug: \"bilocale-con-pizza\" });\n\nexport function CategoryProducts({ qrSlug, categorySlug, initialPage }: {\n  qrSlug: string;\n  categorySlug: string;\n  initialPage: { products: MenuProduct[]; hasMore: boolean };\n}) {\n  const [products, setProducts] = useState(initialPage.products);\n  const [page, setPage] = useState(1);\n  const [hasMore, setHasMore] = useState(initialPage.hasMore);\n\n  async function loadMore() {\n    const next = await buypass.menus.listProducts(qrSlug, {\n      categorySlug,\n      page: page + 1,\n      pageSize: 20,\n    });\n    setProducts((prev) => [...prev, ...next.products]);\n    setPage(page + 1);\n    setHasMore(next.hasMore);\n  }\n\n  return (\n    <>\n      {products.map((p) => <ProductCard key={p.productId} product={p} />)}\n      {hasMore && <button onClick={loadMore}>Load more</button>}\n    </>\n  );\n}\n```\n\n### Single product detail\n\n#### `client.menus.getProduct(qrSlug, productSlug, options?): Promise<MenuProduct>`\n\n```ts\nconst product = await buypass.menus.getProduct(\"menu-bilocale\", \"spritz-aperol\", {\n  locale: \"en\",\n});\n// One MenuProduct with all its variants nested under `variants`.\n```\n\nThrows `BuypassApiError({ code: \"PRODUCT_NOT_FOUND\" })` when the slug doesn't resolve under the requested menu.\n\n## Allergen codes\n\nAllergen values are the **Italian** lemmas because Italian is the platform's canonical language. Type-narrow against these:\n\n```\nGLUTINE | LATTE | UOVA | CROSTACEI | PESCE |\nARACHIDI | SOIA | FRUTTA_GUSCIO | SEDANO | SENAPE |\nSESAMO | ANIDRIDE_SOLFOROSA | LUPINI | MOLLUSCHI\n```\n\n(Mirrors EU Regulation 1169/2011 Annex II.)\n\n## Error handling\n\nAll errors are instances of `BuypassApiError`. Switch on the semantic `code` rather than HTTP status — codes are part of the SDK contract.\n\n```ts\nimport { BuypassApiError } from \"@apeyron/buypass-sdk\";\n\ntry {\n  const product = await buypass.menus.getProduct(\"menu-bilocale\", \"ghost\");\n} catch (err) {\n  if (err instanceof BuypassApiError) {\n    switch (err.code) {\n      case \"MENU_NOT_FOUND\":     return notFound();\n      case \"PRODUCT_NOT_FOUND\":  return notFound();              // distinct from menu-level 404\n      case \"MENU_UNAVAILABLE\":   return showFallback();          // merchant's DIGITAL_MENU module lapsed\n      case \"NETWORK\":            return showOfflineBanner();\n      case \"INTERNAL\":           return retryLater();\n      case \"BAD_REQUEST\":        throw err;                      // programming error — surface\n    }\n  }\n  throw err;\n}\n```\n\n## Framework recipes\n\n### Next.js (App Router, RSC)\n\n```ts\n// app/page.tsx\nimport { createBuypassClient } from \"@apeyron/buypass-sdk\";\n\nconst buypass = createBuypassClient({\n  merchantSlug: process.env.BUYPASS_MERCHANT_SLUG!,\n  defaultFetchOptions: { next: { revalidate: 60 } },  // ISR: 60-second cache\n});\n\nexport default async function Page() {\n  const menu = await buypass.menus.getDefault();\n  return <MenuView menu={menu} />;\n}\n```\n\n**Cache strategies:**\n\n```ts\n// Always-fresh (real-time, e.g. for OOS-critical pages)\ndefaultFetchOptions: { next: { revalidate: 0 } }\n\n// Cache for 1 minute, then revalidate in the background (most pages)\ndefaultFetchOptions: { next: { revalidate: 60 } }\n\n// Mix per-call: cache the menu tree, but always revalidate the product detail\nconst menu = await buypass.menus.getDefault();                 // uses defaultFetchOptions (60s)\nconst dish = await buypass.menus.getProduct(qrSlug, slug, {\n  fetchOptions: { next: { revalidate: 0 } },                   // override per call\n});\n```\n\n### Vite / Browser SPA\n\n```ts\nconst buypass = createBuypassClient({\n  merchantSlug: import.meta.env.VITE_BUYPASS_MERCHANT_SLUG,\n});\nconst menu = await buypass.menus.getDefault({ locale: navigator.language.slice(0, 2) });\n```\n\n### Cloudflare Workers\n\n```ts\nimport { createBuypassClient } from \"@apeyron/buypass-sdk\";\n\nexport default {\n  async fetch(req: Request, env: Env) {\n    const buypass = createBuypassClient({\n      merchantSlug: env.BUYPASS_MERCHANT_SLUG,\n      defaultFetchOptions: { cf: { cacheTtl: 60, cacheEverything: true } as never },\n    });\n    const menu = await buypass.menus.getDefault();\n    return Response.json(menu);\n  },\n};\n```\n\n## Types\n\nThe SDK exports plain TypeScript interfaces — no class-validator decorators, no runtime overhead beyond what JSON parsing already costs. Re-export them in your app as needed:\n\n```ts\nimport type { Menu, MenuProduct, MenuCategory, Allergen } from \"@apeyron/buypass-sdk\";\n```\n\nPrices are in cents (EUR), matching the engine's contract.\n\n## Development\n\n```bash\n# from repo root\nbun install                # installs workspace deps\nbun --filter @apeyron/buypass-sdk run typecheck\nbun --filter @apeyron/buypass-sdk run build\n```\n\nBuild output lands in `packages/sdk/dist/` (ESM + CJS + d.ts).\n\n## Versioning\n\nSemver. The current line is `0.x` — minor bumps may introduce breaking changes while the public-API surface is still settling. A `1.0` will lock the surface.\n\nThe SDK's types track the engine's storefront DTOs at `src/engine/COMMERCE_MODULE/dtos/menu/`. When that contract changes, this package bumps in lockstep.\n","readmeFilename":"README.md"}