@skuio/storefront 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api/serialize.ts","../src/api/validate.ts","../src/api/index.ts"],"sourcesContent":["import type { CategoryRecord, ProductRecord } from '../store/catalog'\nimport type { Address, Fulfillment, Money, Order } from '../types'\n\nexport interface ApiMoney {\n amount: number\n currency: string\n}\n\nexport interface ApiAddress {\n name: string | null\n line1: string | null\n line2: string | null\n city: string | null\n state: string | null\n postal_code: string | null\n country: string | null\n}\n\nexport interface ApiFulfillment {\n id: string\n external_id: string\n carrier: string | null\n service: string | null\n tracking_number: string | null\n tracking_url: string | null\n shipped_at: string | null\n lines: { sku: string; quantity: number }[]\n created_at: string\n}\n\nexport interface ApiOrder {\n id: string\n number: string\n status: Order['status']\n payment: { status: Order['payment']['status']; provider: string | null; reference: string | null; method_label: string | null; paid_at: string | null }\n currency: string\n subtotal: ApiMoney\n shipping: ApiMoney\n tax: ApiMoney\n discount: ApiMoney\n /** The store discount code the buyer used, upper case. Partner and commission stay in the store. */\n discount_code: string | null\n total: ApiMoney\n customer: { id: string; email: string; name: string | null; phone: string | null }\n shipping_address: ApiAddress\n billing_address: ApiAddress | null\n shipping_method: { code: string; name: string } | null\n lines: { id: string; sku: string; name: string; quantity: number; unit_amount: ApiMoney; total_amount: ApiMoney; fulfilled_quantity: number }[]\n fulfillments: ApiFulfillment[]\n external_reference: string | null\n notes: string | null\n metadata: Record<string, string>\n created_at: string\n updated_at: string\n paid_at: string | null\n cancelled_at: string | null\n}\n\nexport interface ApiProduct {\n sku: string\n name: string\n slug: string\n description_html: string | null\n short_description_html: string | null\n price: ApiMoney\n compare_at: ApiMoney | null\n attributes: Record<string, string>\n active: boolean\n position: number\n legacy_path: string | null\n categories: { id: string; slug: string | null; name: string | null }[]\n images: { url: string; alt: string | null; position: number }[]\n inventory: { available: number; allow_backorder: boolean; updated_at: string } | null\n created_at: string\n updated_at: string\n}\n\nexport interface ApiCategory {\n id: string\n parent_id: string | null\n slug: string\n name: string\n description_html: string | null\n position: number\n product_count: number\n created_at: string\n updated_at: string\n}\n\nconst money = (m: Money): ApiMoney => ({ amount: m.amount, currency: m.currency.toUpperCase() })\n\nconst address = (a: Address | null): ApiAddress | null =>\n a\n ? {\n name: a.name ?? null,\n line1: a.line1 ?? null,\n line2: a.line2 ?? null,\n city: a.city ?? null,\n state: a.state ?? null,\n postal_code: a.postalCode ?? null,\n country: a.country ?? null,\n }\n : null\n\nexport function serializeFulfillment(f: Fulfillment): ApiFulfillment {\n return {\n id: f.id,\n external_id: f.externalId,\n carrier: f.carrier,\n service: f.service,\n tracking_number: f.trackingNumber,\n tracking_url: f.trackingUrl,\n shipped_at: f.shippedAt,\n lines: f.lines.map((l) => ({ sku: l.sku, quantity: l.quantity })),\n created_at: f.createdAt,\n }\n}\n\n/**\n * The Store API's view of an order. `access_key` never leaves the store. `number` is the human number the buyer\n * sees, with the store's prefix (VLD4200), so the consumer shows the same number.\n */\nexport function serializeOrder(o: Order, numberPrefix = ''): ApiOrder {\n return {\n id: o.id,\n number: `${numberPrefix}${o.number}`,\n status: o.status,\n payment: { status: o.payment.status, provider: o.payment.provider, reference: o.payment.reference, method_label: o.payment.methodLabel, paid_at: o.payment.paidAt },\n currency: o.currency.toUpperCase(),\n subtotal: money(o.subtotal),\n shipping: money(o.shipping),\n tax: money(o.tax),\n discount: money(o.discount),\n discount_code: o.discountCode,\n total: money(o.total),\n customer: { id: o.customer.id, email: o.customer.email, name: o.customer.name, phone: o.customer.phone },\n shipping_address: address(o.shippingAddress) ?? { name: null, line1: null, line2: null, city: null, state: null, postal_code: null, country: null },\n billing_address: address(o.billingAddress),\n shipping_method: o.shippingMethod,\n lines: o.lines.map((l) => ({\n id: l.id,\n sku: l.sku,\n name: l.name,\n quantity: l.quantity,\n unit_amount: money(l.unitAmount),\n total_amount: money(l.totalAmount),\n fulfilled_quantity: l.fulfilledQuantity,\n })),\n fulfillments: o.fulfillments.map(serializeFulfillment),\n external_reference: o.externalReference,\n notes: o.notes,\n metadata: o.metadata,\n created_at: o.createdAt,\n updated_at: o.updatedAt,\n paid_at: o.paidAt,\n cancelled_at: o.cancelledAt,\n }\n}\n\n/** The Store API's view of a catalogue product — the stored row, not the shopper projection. */\nexport function serializeProduct(p: ProductRecord): ApiProduct {\n return {\n sku: p.sku,\n name: p.name,\n slug: p.slug,\n description_html: p.descriptionHtml,\n short_description_html: p.shortDescriptionHtml,\n price: money(p.price),\n compare_at: p.compareAtPrice ? money(p.compareAtPrice) : null,\n attributes: p.attributes,\n active: p.active,\n position: p.position,\n legacy_path: p.legacyPath,\n categories: p.categories.map((c) => ({ id: c.id, slug: c.slug, name: c.name })),\n images: p.images.map((i) => ({ url: i.url, alt: i.alt, position: i.position })),\n inventory: p.inventory ? { available: p.inventory.available, allow_backorder: p.inventory.allowBackorder, updated_at: p.inventory.updatedAt } : null,\n created_at: p.createdAt,\n updated_at: p.updatedAt,\n }\n}\n\nexport function serializeCategory(c: CategoryRecord): ApiCategory {\n return {\n id: c.id,\n parent_id: c.parentId,\n slug: c.slug,\n name: c.name,\n description_html: c.descriptionHtml,\n position: c.position,\n product_count: c.productCount,\n created_at: c.createdAt,\n updated_at: c.updatedAt,\n }\n}\n","import type { CategoryInput, ProductInput } from '../store/catalog'\nimport type { InventoryItemInput } from '../store/inventory'\nimport type { FulfillmentInput } from '../store/orders'\nimport type { Money } from '../types'\n\nexport class ValidationError extends Error {\n readonly details: Record<string, string>\n constructor(message: string, details: Record<string, string>) {\n super(message)\n this.name = 'ValidationError'\n this.details = details\n }\n}\n\ntype Details = Record<string, string>\n\nconst isObject = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v)\nconst isString = (v: unknown): v is string => typeof v === 'string'\nconst isInt = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v)\n\nfunction str(details: Details, obj: Record<string, unknown>, key: string, path: string, opts: { required?: boolean; max?: number; nullable?: boolean } = {}): string | null | undefined {\n const v = obj[key]\n if (v === undefined) {\n if (opts.required) details[path] = 'is required'\n return undefined\n }\n if (v === null) {\n if (opts.required && !opts.nullable) details[path] = 'is required'\n return opts.nullable || !opts.required ? null : undefined\n }\n if (!isString(v)) {\n details[path] = 'must be a string'\n return undefined\n }\n if (opts.required && !v.trim()) details[path] = 'is required'\n if (opts.max && v.length > opts.max) details[path] = `must be at most ${opts.max} characters`\n return v\n}\n\nfunction moneyField(details: Details, v: unknown, path: string, opts: { required?: boolean; nullable?: boolean } = {}): Money | null | undefined {\n if (v === undefined) {\n if (opts.required) details[path] = 'is required'\n return undefined\n }\n if (v === null) {\n if (!opts.nullable) details[path] = 'is required'\n return null\n }\n if (!isObject(v) || !isInt(v.amount) || !isString(v.currency) || v.currency.length !== 3) {\n details[path] = 'must be { amount: integer minor units, currency: ISO 4217 }'\n return undefined\n }\n if (v.amount < 0) details[path] = 'must not be negative'\n return { amount: v.amount, currency: v.currency }\n}\n\nfunction isoDate(details: Details, v: unknown, path: string): string | null | undefined {\n if (v === undefined) return undefined\n if (v === null) return null\n if (!isString(v) || Number.isNaN(Date.parse(v))) {\n details[path] = 'must be an ISO-8601 timestamp'\n return undefined\n }\n return new Date(v).toISOString()\n}\n\nfunction finish(details: Details, message: string): void {\n if (Object.keys(details).length) throw new ValidationError(message, details)\n}\n\nexport interface ListQuery {\n updatedSince: string | null\n cursor: string | null\n limit: number\n active: boolean | null\n}\n\n/**\n * Query string for the catalogue list endpoints. Out-of-range values are a 422\n * rather than a silent clamp, so a caller paging with a bad limit is told.\n */\nexport function validateListQuery(\n params: URLSearchParams,\n opts: { defaultLimit: number; maxLimit: number; allowUpdatedSince?: boolean; allowActive?: boolean }\n): ListQuery {\n const details: Details = {}\n const limitRaw = params.get('limit')\n const limit = limitRaw ? Number(limitRaw) : opts.defaultLimit\n if (limitRaw && (!Number.isInteger(limit) || limit < 1 || limit > opts.maxLimit)) {\n details.limit = `must be an integer from 1 to ${opts.maxLimit}`\n }\n\n const updatedSinceRaw = opts.allowUpdatedSince ? params.get('updated_since') : null\n if (updatedSinceRaw && Number.isNaN(Date.parse(updatedSinceRaw))) details.updated_since = 'must be an ISO-8601 timestamp'\n\n const activeRaw = opts.allowActive ? params.get('active') : null\n let active: boolean | null = null\n if (activeRaw !== null) {\n if (activeRaw === 'true') active = true\n else if (activeRaw === 'false') active = false\n else details.active = 'must be true or false'\n }\n\n finish(details, 'Invalid query')\n return {\n updatedSince: updatedSinceRaw ? new Date(updatedSinceRaw).toISOString() : null,\n cursor: params.get('cursor'),\n limit: Number.isInteger(limit) ? limit : opts.defaultLimit,\n active,\n }\n}\n\nexport function validateProducts(body: Record<string, unknown>): ProductInput[] {\n const details: Details = {}\n const list = body.products\n if (!Array.isArray(list)) {\n details.products = 'must be an array'\n finish(details, 'Invalid products payload')\n }\n const items = list as unknown[]\n if (items.length > 200) details.products = 'must have at most 200 items'\n const out: ProductInput[] = []\n const seen = new Set<string>()\n items.forEach((raw, i) => {\n const p = `products.${i}`\n if (!isObject(raw)) {\n details[p] = 'must be an object'\n return\n }\n const sku = str(details, raw, 'sku', `${p}.sku`, { required: true, max: 64 })\n const name = str(details, raw, 'name', `${p}.name`, { required: true, max: 255 })\n const slug = str(details, raw, 'slug', `${p}.slug`, { max: 255, nullable: true })\n const description = str(details, raw, 'description_html', `${p}.description_html`, { nullable: true })\n const short = str(details, raw, 'short_description_html', `${p}.short_description_html`, { nullable: true })\n const price = moneyField(details, raw.price, `${p}.price`, { required: true })\n const compare = moneyField(details, raw.compare_at_price, `${p}.compare_at_price`, { nullable: true })\n if (sku && seen.has(sku)) details[`${p}.sku`] = 'appears more than once'\n if (sku) seen.add(sku)\n if (slug && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) details[`${p}.slug`] = 'must be lower-case letters, digits and hyphens'\n\n const images: { url: string; alt?: string | null }[] = []\n if (raw.images !== undefined) {\n if (!Array.isArray(raw.images)) details[`${p}.images`] = 'must be an array'\n else\n raw.images.forEach((img, j) => {\n if (!isObject(img) || !isString(img.url) || !img.url.trim()) details[`${p}.images.${j}.url`] = 'is required'\n else images.push({ url: img.url, alt: isString(img.alt) ? img.alt : null })\n })\n }\n const categoryIds: string[] = []\n if (raw.category_ids !== undefined) {\n if (!Array.isArray(raw.category_ids) || !raw.category_ids.every(isString)) details[`${p}.category_ids`] = 'must be an array of strings'\n else categoryIds.push(...(raw.category_ids as string[]))\n }\n let attributes: Record<string, string> = {}\n if (raw.attributes !== undefined) {\n if (!isObject(raw.attributes) || !Object.values(raw.attributes).every(isString)) details[`${p}.attributes`] = 'must be an object of strings'\n else attributes = raw.attributes as Record<string, string>\n }\n if (raw.active !== undefined && typeof raw.active !== 'boolean') details[`${p}.active`] = 'must be a boolean'\n if (raw.position !== undefined && !isInt(raw.position)) details[`${p}.position`] = 'must be an integer'\n\n if (sku && name && price) {\n out.push({\n sku: sku.trim(),\n name: name.trim(),\n slug: slug ?? null,\n description_html: description ?? null,\n short_description_html: short ?? null,\n price,\n compare_at_price: compare ?? null,\n images,\n category_ids: categoryIds,\n attributes,\n active: raw.active === undefined ? true : (raw.active as boolean),\n ...(isInt(raw.position) ? { position: raw.position } : {}),\n })\n }\n })\n finish(details, 'Invalid products payload')\n return out\n}\n\nexport function validateCategories(body: Record<string, unknown>): CategoryInput[] {\n const details: Details = {}\n const list = body.categories\n if (!Array.isArray(list)) {\n details.categories = 'must be an array'\n finish(details, 'Invalid categories payload')\n }\n const items = list as unknown[]\n if (items.length > 500) details.categories = 'must have at most 500 items'\n const out: CategoryInput[] = []\n const seen = new Set<string>()\n items.forEach((raw, i) => {\n const p = `categories.${i}`\n if (!isObject(raw)) {\n details[p] = 'must be an object'\n return\n }\n const id = str(details, raw, 'id', `${p}.id`, { required: true, max: 64 })\n const name = str(details, raw, 'name', `${p}.name`, { required: true, max: 255 })\n const slug = str(details, raw, 'slug', `${p}.slug`, { max: 255, nullable: true })\n const parent = str(details, raw, 'parent_id', `${p}.parent_id`, { max: 64, nullable: true })\n const description = str(details, raw, 'description_html', `${p}.description_html`, { nullable: true })\n if (id && seen.has(id)) details[`${p}.id`] = 'appears more than once'\n if (id) seen.add(id)\n if (id && parent && parent === id) details[`${p}.parent_id`] = 'cannot be the category itself'\n if (slug && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) details[`${p}.slug`] = 'must be lower-case letters, digits and hyphens'\n let image: { url: string; alt?: string | null } | null = null\n if (raw.image !== undefined && raw.image !== null) {\n if (!isObject(raw.image) || !isString(raw.image.url)) details[`${p}.image`] = 'must be { url, alt? } or null'\n else image = { url: raw.image.url, alt: isString(raw.image.alt) ? raw.image.alt : null }\n }\n if (raw.position !== undefined && !isInt(raw.position)) details[`${p}.position`] = 'must be an integer'\n if (id && name) {\n out.push({ id: id.trim(), name: name.trim(), slug: slug ?? null, parent_id: parent ?? null, description_html: description ?? null, image, ...(isInt(raw.position) ? { position: raw.position } : {}) })\n }\n })\n finish(details, 'Invalid categories payload')\n return out\n}\n\nexport function validateInventory(body: Record<string, unknown>): InventoryItemInput[] {\n const details: Details = {}\n const list = body.items\n if (!Array.isArray(list)) {\n details.items = 'must be an array'\n finish(details, 'Invalid inventory payload')\n }\n const items = list as unknown[]\n if (items.length > 500) details.items = 'must have at most 500 items'\n const out: InventoryItemInput[] = []\n items.forEach((raw, i) => {\n const p = `items.${i}`\n if (!isObject(raw)) {\n details[p] = 'must be an object'\n return\n }\n const sku = str(details, raw, 'sku', `${p}.sku`, { required: true, max: 64 })\n if (!isInt(raw.available)) details[`${p}.available`] = 'must be an integer'\n else if (raw.available < 0) details[`${p}.available`] = 'must be a non-negative integer'\n if (raw.allow_backorder !== undefined && typeof raw.allow_backorder !== 'boolean') details[`${p}.allow_backorder`] = 'must be a boolean'\n if (sku && isInt(raw.available) && raw.available >= 0) out.push({ sku: sku.trim(), available: raw.available, allow_backorder: raw.allow_backorder === true })\n })\n finish(details, 'Invalid inventory payload')\n return out\n}\n\nexport function validateFulfillment(body: Record<string, unknown>): FulfillmentInput {\n const details: Details = {}\n const externalId = str(details, body, 'external_id', 'external_id', { required: true, max: 64 })\n const carrier = str(details, body, 'carrier', 'carrier', { nullable: true, max: 100 })\n const service = str(details, body, 'service', 'service', { nullable: true, max: 100 })\n const trackingNumber = str(details, body, 'tracking_number', 'tracking_number', { nullable: true, max: 100 })\n const trackingUrl = str(details, body, 'tracking_url', 'tracking_url', { nullable: true, max: 2000 })\n if (trackingUrl && !/^https?:\\/\\//.test(trackingUrl)) details.tracking_url = 'must be an absolute http(s) URL'\n const shippedAt = isoDate(details, body.shipped_at, 'shipped_at')\n if (body.notify_customer !== undefined && typeof body.notify_customer !== 'boolean') details.notify_customer = 'must be a boolean'\n const lines: { sku: string; quantity: number }[] = []\n if (!Array.isArray(body.lines) || body.lines.length === 0) details.lines = 'must be a non-empty array'\n else\n body.lines.forEach((raw, i) => {\n if (!isObject(raw) || !isString(raw.sku) || !raw.sku.trim()) details[`lines.${i}.sku`] = 'is required'\n if (!isObject(raw) || !isInt(raw.quantity) || raw.quantity < 1) details[`lines.${i}.quantity`] = 'must be an integer of at least 1'\n if (isObject(raw) && isString(raw.sku) && isInt(raw.quantity) && raw.quantity >= 1) lines.push({ sku: raw.sku.trim(), quantity: raw.quantity })\n })\n finish(details, 'Invalid fulfillment payload')\n return {\n external_id: externalId!.trim(),\n carrier: carrier ?? null,\n service: service ?? null,\n tracking_number: trackingNumber ?? null,\n tracking_url: trackingUrl ?? null,\n shipped_at: shippedAt ?? null,\n lines,\n notify_customer: body.notify_customer === undefined ? true : (body.notify_customer as boolean),\n }\n}\n\nexport interface OrderPatch {\n external_reference?: string | null\n status?: 'cancelled'\n payment?: { status: 'paid' | 'refunded' | 'failed'; provider?: string | null; reference?: string | null; paid_at?: string | null }\n}\n\nexport function validateOrderPatch(body: Record<string, unknown>): OrderPatch {\n const details: Details = {}\n const out: OrderPatch = {}\n if ('external_reference' in body) {\n const ref = str(details, body, 'external_reference', 'external_reference', { nullable: true, max: 64 })\n if (ref !== undefined) out.external_reference = ref\n }\n if (body.status !== undefined) {\n if (body.status !== 'cancelled') details.status = 'can only be set to \"cancelled\"'\n else out.status = 'cancelled'\n }\n if (body.payment !== undefined) {\n if (!isObject(body.payment)) details.payment = 'must be an object'\n else {\n const p = body.payment\n if (p.status !== 'paid' && p.status !== 'refunded' && p.status !== 'failed') details['payment.status'] = 'must be paid, refunded or failed'\n const provider = str(details, p, 'provider', 'payment.provider', { nullable: true, max: 64 })\n const reference = str(details, p, 'reference', 'payment.reference', { nullable: true, max: 255 })\n const paidAt = isoDate(details, p.paid_at, 'payment.paid_at')\n if (p.status === 'paid' || p.status === 'refunded' || p.status === 'failed') {\n out.payment = { status: p.status, provider: provider ?? null, reference: reference ?? null, paid_at: paidAt ?? null }\n }\n }\n }\n if (!('external_reference' in body) && body.status === undefined && body.payment === undefined) details.body = 'must include external_reference, status or payment'\n finish(details, 'Invalid order patch')\n return out\n}\n","import type { Connection, Store } from '../store'\nimport { API_KEY_PREFIX, type ConnectScope } from '../store/connections'\nimport { secretsEqual } from '../store/crypto'\nimport { StoreError } from '../store/errors'\nimport { serializeCategory, serializeFulfillment, serializeOrder, serializeProduct } from './serialize'\nimport {\n validateCategories, validateFulfillment, validateInventory, validateListQuery, validateOrderPatch, validateProducts, ValidationError,\n} from './validate'\n\nexport interface StoreApiOptions {\n store: Store\n /**\n * The STORE_API_KEY secret — the break-glass key that works without an owner\n * or a connection. Optional: a store whose consumers all connect through the\n * owner-approved flow can leave it unset.\n */\n apiKey: string | undefined\n /** Where the API is mounted in the request URL. */\n basePath?: string\n}\n\n/** Who a request authenticated as: the environment key, or an approved connection. */\nexport type ApiCaller = { kind: 'env' } | { kind: 'connection'; connection: Connection }\n\n/**\n * Where the Store API is served. `/api/store/v1` is the neutral path for any\n * operations system; `/api/skuio/v1` is the original path SKU.io's Storefront\n * integration calls, kept for good. Same handler, same keys, same responses.\n */\nexport const STORE_API_BASES = ['/api/store/v1', '/api/skuio/v1'] as const\n/** @deprecated The SKU.io path; use STORE_API_BASES. */\nexport const STORE_API_BASE = '/api/skuio/v1'\n\nconst json = (body: unknown, status = 200, headers: Record<string, string> = {}) =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store', ...headers } })\n\nconst error = (status: number, code: string, message: string, details: Record<string, string> | null = null) =>\n json({ error: { code, message, details } }, status)\n\n/**\n * The Store API (`docs/store-api.md`) as one fetch-style handler. Mount it on\n * a catch-all route under `/api/store/v1` (and `/api/skuio/v1`, the path SKU.io\n * calls) and hand it every method.\n */\nexport async function handleStoreApi(request: Request, options: StoreApiOptions): Promise<Response> {\n const url = new URL(request.url)\n const bases = options.basePath ? [options.basePath] : [...STORE_API_BASES]\n const base = bases.map((b) => b.replace(/\\/+$/, '')).find((b) => url.pathname.includes(b))\n if (!base) return error(404, 'not_found', 'No such endpoint')\n const at = url.pathname.indexOf(base)\n const path = url.pathname.slice(at + base.length).replace(/\\/+$/, '') || '/'\n const method = request.method.toUpperCase()\n\n try {\n // The token exchange is the one unauthenticated endpoint: the code is the credential.\n if (path === '/connect/token' && method === 'POST') {\n const issued = await options.store.connections.exchange(await readJson(request), clientAddress(request))\n return json({ data: { api_key: issued.apiKey, connection: serializeConnection(issued.connection) } }, 201)\n }\n\n const caller = await authorized(request, options.apiKey, options.store)\n if (!caller) return error(401, 'unauthorized', 'A valid bearer key is required: a connection key issued through /admin/connect, or the STORE_API_KEY secret')\n\n // A connection may use only what the owner approved; the STORE_API_KEY secret may use everything.\n const scope = scopeFor(path)\n if (scope && caller.kind === 'connection' && !caller.connection.scopes.includes(scope)) {\n return error(403, 'forbidden', `This connection was not approved for \"${scope}\"; reconnect and approve it at /admin/connect`)\n }\n\n return await route(method, path, url, request, options.store, caller)\n } catch (e) {\n if (e instanceof ValidationError) return error(422, 'validation_failed', e.message, e.details)\n if (e instanceof StoreError) return error(e.status, e.code, e.message, e.details)\n console.error('store api: unhandled error', e)\n return error(500, 'internal_error', 'The store could not complete the request')\n }\n}\n\nasync function authorized(request: Request, apiKey: string | undefined, store: Store): Promise<ApiCaller | null> {\n const header = request.headers.get('authorization') ?? ''\n const match = /^Bearer\\s+(.+)$/i.exec(header.trim())\n if (!match) return null\n const bearer = match[1]!.trim()\n if (bearer.startsWith(API_KEY_PREFIX)) {\n const connection = await store.connections.resolveKey(bearer)\n return connection ? { kind: 'connection', connection } : null\n }\n if (apiKey && (await secretsEqual(bearer, apiKey))) return { kind: 'env' }\n return null\n}\n\n/** The caller's address for failure counting; Cloudflare sets the first header, proxies the second. */\nfunction clientAddress(request: Request): string {\n return request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'\n}\n\nexport function serializeConnection(c: Connection) {\n return {\n id: c.id,\n name: c.name,\n tenant: c.tenant,\n scopes: c.scopes,\n key_prefix: c.keyPrefix,\n created_at: c.createdAt,\n issued_at: c.issuedAt,\n last_used_at: c.lastUsedAt,\n revoked_at: c.revokedAt,\n }\n}\n\nasync function readJson(request: Request): Promise<Record<string, unknown>> {\n const text = await request.text()\n if (!text.trim()) throw new ValidationError('Request body must be JSON', { body: 'is empty' })\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch {\n throw new ValidationError('Request body must be JSON', { body: 'is not valid JSON' })\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new ValidationError('Request body must be a JSON object', { body: 'is not an object' })\n return parsed as Record<string, unknown>\n}\n\n/**\n * The scope an endpoint needs. There is deliberately no endpoint that creates\n * an order: orders come only from the store's own checkout, priced from its\n * database, so a leaked key cannot invent sales.\n */\nfunction scopeFor(path: string): ConnectScope | null {\n const first = path.split('/').filter(Boolean)[0]\n if (first === 'orders') return /^\\/orders\\/[^/]+\\/fulfillments$/.test(path) ? 'fulfillments' : 'orders'\n if (first === 'inventory') return 'inventory'\n if (first === 'products' || first === 'categories') return 'catalog'\n return null // /health, /connect\n}\n\nasync function route(method: string, path: string, url: URL, request: Request, store: Store, caller: ApiCaller): Promise<Response> {\n const segments = path.split('/').filter(Boolean)\n\n if (path === '/health' && method === 'GET') {\n const health = await store.health()\n const connection = caller.kind === 'connection' ? { id: caller.connection.id, name: caller.connection.name, tenant: caller.connection.tenant } : null\n return json({ data: { ...health, connection } })\n }\n\n if (path === '/connect') {\n if (method === 'GET') return json({ data: caller.kind === 'connection' ? serializeConnection(caller.connection) : null })\n if (method === 'DELETE') {\n if (caller.kind !== 'connection') return error(409, 'conflict', 'The STORE_API_KEY secret is not a connection; rotate it on the Worker instead')\n await store.connections.revoke(caller.connection.id)\n return new Response(null, { status: 204 })\n }\n }\n\n if (segments[0] === 'orders') {\n if (segments.length === 1 && method === 'GET') {\n const updatedSince = url.searchParams.get('updated_since')\n const cursor = url.searchParams.get('cursor')\n const limitRaw = url.searchParams.get('limit')\n const limit = limitRaw ? Number(limitRaw) : 50\n if (limitRaw && (!Number.isInteger(limit) || limit < 1 || limit > 200)) throw new ValidationError('Invalid query', { limit: 'must be an integer from 1 to 200' })\n if (updatedSince && Number.isNaN(Date.parse(updatedSince))) throw new ValidationError('Invalid query', { updated_since: 'must be an ISO-8601 timestamp' })\n // Lazy housekeeping: abandoned processor checkouts become cancelled before the consumer sees the list.\n await store.orders.expireAbandoned()\n const page = await store.orders.changedSince(updatedSince ? new Date(updatedSince).toISOString() : null, cursor, limit)\n return json({ data: page.orders.map((o) => serializeOrder(o, store.config.orders.numberPrefix)), next_cursor: page.nextCursor })\n }\n const id = segments[1] ? decodeURIComponent(segments[1]) : ''\n if (segments.length === 2 && id) {\n if (method === 'GET') return json({ data: serializeOrder(await store.orders.require(id), store.config.orders.numberPrefix) })\n if (method === 'PATCH') {\n const patch = validateOrderPatch(await readJson(request))\n // Recording a payment is its own scope: a key that may read and stamp orders cannot\n // turn an unpaid order into a paid one unless the owner approved that too.\n if (patch.payment && caller.kind === 'connection' && !caller.connection.scopes.includes('payments')) {\n return error(403, 'forbidden', 'This connection was not approved for \"payments\"; reconnect and approve it at /admin/connect')\n }\n let order = await store.orders.require(id)\n if (patch.external_reference !== undefined) order = await store.orders.setExternalReference(id, patch.external_reference)\n if (patch.payment) order = await store.orders.setPaymentStatus(id, patch.payment)\n if (patch.status === 'cancelled') order = await store.orders.cancel(id, 'Cancelled by the operations system')\n return json({ data: serializeOrder(order, store.config.orders.numberPrefix) })\n }\n }\n if (segments.length === 3 && id && segments[2] === 'fulfillments' && method === 'POST') {\n const input = validateFulfillment(await readJson(request))\n const result = await store.orders.addFulfillment(id, input)\n return json({ data: serializeFulfillment(result.fulfillment) }, result.created ? 201 : 200)\n }\n }\n\n if (path === '/inventory' && method === 'PUT') {\n const items = validateInventory(await readJson(request))\n return json({ data: await store.inventory.set(items) })\n }\n\n if (path === '/products' && method === 'PUT') {\n const products = validateProducts(await readJson(request))\n return json({ data: { upserted: await store.catalog.upsertProducts(products) } })\n }\n\n if (path === '/products' && method === 'GET') {\n const query = validateListQuery(url.searchParams, { defaultLimit: 50, maxLimit: 200, allowUpdatedSince: true, allowActive: true })\n const page = await store.catalog.listProducts(query)\n return json({ data: page.products.map(serializeProduct), next_cursor: page.nextCursor })\n }\n\n if (segments[0] === 'products' && segments.length === 2) {\n const sku = decodeURIComponent(segments[1]!)\n if (method === 'GET') return json({ data: serializeProduct(await store.catalog.requireProduct(sku)) })\n if (method === 'DELETE') {\n const done = await store.catalog.deactivateProduct(sku)\n if (!done) return error(404, 'not_found', `Product ${sku} not found`)\n return new Response(null, { status: 204 })\n }\n }\n\n if (path === '/categories' && method === 'PUT') {\n const categories = validateCategories(await readJson(request))\n return json({ data: { upserted: await store.catalog.upsertCategories(categories) } })\n }\n\n if (path === '/categories' && method === 'GET') {\n const query = validateListQuery(url.searchParams, { defaultLimit: 500, maxLimit: 1000 })\n const page = await store.catalog.listCategories(query)\n return json({ data: page.categories.map(serializeCategory), next_cursor: page.nextCursor })\n }\n\n return error(404, 'not_found', `No such endpoint: ${method} ${path}`)\n}\n\nexport {\n serializeOrder, serializeFulfillment, serializeProduct, serializeCategory,\n type ApiOrder, type ApiFulfillment, type ApiProduct, type ApiCategory,\n} from './serialize'\nexport { ValidationError } from './validate'\n"],"mappings":";;;;;;;AAyFA,IAAM,QAAQ,CAAC,OAAwB,EAAE,QAAQ,EAAE,QAAQ,UAAU,EAAE,SAAS,YAAY,EAAE;AAE9F,IAAM,UAAU,CAAC,MACf,IACI;AAAA,EACE,MAAM,EAAE,QAAQ;AAAA,EAChB,OAAO,EAAE,SAAS;AAAA,EAClB,OAAO,EAAE,SAAS;AAAA,EAClB,MAAM,EAAE,QAAQ;AAAA,EAChB,OAAO,EAAE,SAAS;AAAA,EAClB,aAAa,EAAE,cAAc;AAAA,EAC7B,SAAS,EAAE,WAAW;AACxB,IACA;AAEC,SAAS,qBAAqB,GAAgC;AACnE,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,aAAa,EAAE;AAAA,IACf,SAAS,EAAE;AAAA,IACX,SAAS,EAAE;AAAA,IACX,iBAAiB,EAAE;AAAA,IACnB,cAAc,EAAE;AAAA,IAChB,YAAY,EAAE;AAAA,IACd,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,SAAS,EAAE;AAAA,IAChE,YAAY,EAAE;AAAA,EAChB;AACF;AAMO,SAAS,eAAe,GAAU,eAAe,IAAc;AACpE,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,QAAQ,GAAG,YAAY,GAAG,EAAE,MAAM;AAAA,IAClC,QAAQ,EAAE;AAAA,IACV,SAAS,EAAE,QAAQ,EAAE,QAAQ,QAAQ,UAAU,EAAE,QAAQ,UAAU,WAAW,EAAE,QAAQ,WAAW,cAAc,EAAE,QAAQ,aAAa,SAAS,EAAE,QAAQ,OAAO;AAAA,IAClK,UAAU,EAAE,SAAS,YAAY;AAAA,IACjC,UAAU,MAAM,EAAE,QAAQ;AAAA,IAC1B,UAAU,MAAM,EAAE,QAAQ;AAAA,IAC1B,KAAK,MAAM,EAAE,GAAG;AAAA,IAChB,UAAU,MAAM,EAAE,QAAQ;AAAA,IAC1B,eAAe,EAAE;AAAA,IACjB,OAAO,MAAM,EAAE,KAAK;AAAA,IACpB,UAAU,EAAE,IAAI,EAAE,SAAS,IAAI,OAAO,EAAE,SAAS,OAAO,MAAM,EAAE,SAAS,MAAM,OAAO,EAAE,SAAS,MAAM;AAAA,IACvG,kBAAkB,QAAQ,EAAE,eAAe,KAAK,EAAE,MAAM,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO,MAAM,aAAa,MAAM,SAAS,KAAK;AAAA,IAClJ,iBAAiB,QAAQ,EAAE,cAAc;AAAA,IACzC,iBAAiB,EAAE;AAAA,IACnB,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO;AAAA,MACzB,IAAI,EAAE;AAAA,MACN,KAAK,EAAE;AAAA,MACP,MAAM,EAAE;AAAA,MACR,UAAU,EAAE;AAAA,MACZ,aAAa,MAAM,EAAE,UAAU;AAAA,MAC/B,cAAc,MAAM,EAAE,WAAW;AAAA,MACjC,oBAAoB,EAAE;AAAA,IACxB,EAAE;AAAA,IACF,cAAc,EAAE,aAAa,IAAI,oBAAoB;AAAA,IACrD,oBAAoB,EAAE;AAAA,IACtB,OAAO,EAAE;AAAA,IACT,UAAU,EAAE;AAAA,IACZ,YAAY,EAAE;AAAA,IACd,YAAY,EAAE;AAAA,IACd,SAAS,EAAE;AAAA,IACX,cAAc,EAAE;AAAA,EAClB;AACF;AAGO,SAAS,iBAAiB,GAA8B;AAC7D,SAAO;AAAA,IACL,KAAK,EAAE;AAAA,IACP,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,kBAAkB,EAAE;AAAA,IACpB,wBAAwB,EAAE;AAAA,IAC1B,OAAO,MAAM,EAAE,KAAK;AAAA,IACpB,YAAY,EAAE,iBAAiB,MAAM,EAAE,cAAc,IAAI;AAAA,IACzD,YAAY,EAAE;AAAA,IACd,QAAQ,EAAE;AAAA,IACV,UAAU,EAAE;AAAA,IACZ,aAAa,EAAE;AAAA,IACf,YAAY,EAAE,WAAW,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AAAA,IAC9E,QAAQ,EAAE,OAAO,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,SAAS,EAAE;AAAA,IAC9E,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,WAAW,iBAAiB,EAAE,UAAU,gBAAgB,YAAY,EAAE,UAAU,UAAU,IAAI;AAAA,IAChJ,YAAY,EAAE;AAAA,IACd,YAAY,EAAE;AAAA,EAChB;AACF;AAEO,SAAS,kBAAkB,GAAgC;AAChE,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,WAAW,EAAE;AAAA,IACb,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,kBAAkB,EAAE;AAAA,IACpB,UAAU,EAAE;AAAA,IACZ,eAAe,EAAE;AAAA,IACjB,YAAY,EAAE;AAAA,IACd,YAAY,EAAE;AAAA,EAChB;AACF;;;AC5LO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAChC;AAAA,EACT,YAAY,SAAiB,SAAiC;AAC5D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;AAIA,IAAM,WAAW,CAAC,MAA6C,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAC/G,IAAM,WAAW,CAAC,MAA4B,OAAO,MAAM;AAC3D,IAAM,QAAQ,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC;AAEtF,SAAS,IAAI,SAAkB,KAA8B,KAAa,MAAc,OAAiE,CAAC,GAA8B;AACtL,QAAM,IAAI,IAAI,GAAG;AACjB,MAAI,MAAM,QAAW;AACnB,QAAI,KAAK,SAAU,SAAQ,IAAI,IAAI;AACnC,WAAO;AAAA,EACT;AACA,MAAI,MAAM,MAAM;AACd,QAAI,KAAK,YAAY,CAAC,KAAK,SAAU,SAAQ,IAAI,IAAI;AACrD,WAAO,KAAK,YAAY,CAAC,KAAK,WAAW,OAAO;AAAA,EAClD;AACA,MAAI,CAAC,SAAS,CAAC,GAAG;AAChB,YAAQ,IAAI,IAAI;AAChB,WAAO;AAAA,EACT;AACA,MAAI,KAAK,YAAY,CAAC,EAAE,KAAK,EAAG,SAAQ,IAAI,IAAI;AAChD,MAAI,KAAK,OAAO,EAAE,SAAS,KAAK,IAAK,SAAQ,IAAI,IAAI,mBAAmB,KAAK,GAAG;AAChF,SAAO;AACT;AAEA,SAAS,WAAW,SAAkB,GAAY,MAAc,OAAmD,CAAC,GAA6B;AAC/I,MAAI,MAAM,QAAW;AACnB,QAAI,KAAK,SAAU,SAAQ,IAAI,IAAI;AACnC,WAAO;AAAA,EACT;AACA,MAAI,MAAM,MAAM;AACd,QAAI,CAAC,KAAK,SAAU,SAAQ,IAAI,IAAI;AACpC,WAAO;AAAA,EACT;AACA,MAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,SAAS,EAAE,QAAQ,KAAK,EAAE,SAAS,WAAW,GAAG;AACxF,YAAQ,IAAI,IAAI;AAChB,WAAO;AAAA,EACT;AACA,MAAI,EAAE,SAAS,EAAG,SAAQ,IAAI,IAAI;AAClC,SAAO,EAAE,QAAQ,EAAE,QAAQ,UAAU,EAAE,SAAS;AAClD;AAEA,SAAS,QAAQ,SAAkB,GAAY,MAAyC;AACtF,MAAI,MAAM,OAAW,QAAO;AAC5B,MAAI,MAAM,KAAM,QAAO;AACvB,MAAI,CAAC,SAAS,CAAC,KAAK,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG;AAC/C,YAAQ,IAAI,IAAI;AAChB,WAAO;AAAA,EACT;AACA,SAAO,IAAI,KAAK,CAAC,EAAE,YAAY;AACjC;AAEA,SAAS,OAAO,SAAkB,SAAuB;AACvD,MAAI,OAAO,KAAK,OAAO,EAAE,OAAQ,OAAM,IAAI,gBAAgB,SAAS,OAAO;AAC7E;AAaO,SAAS,kBACd,QACA,MACW;AACX,QAAM,UAAmB,CAAC;AAC1B,QAAM,WAAW,OAAO,IAAI,OAAO;AACnC,QAAM,QAAQ,WAAW,OAAO,QAAQ,IAAI,KAAK;AACjD,MAAI,aAAa,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,KAAK,WAAW;AAChF,YAAQ,QAAQ,gCAAgC,KAAK,QAAQ;AAAA,EAC/D;AAEA,QAAM,kBAAkB,KAAK,oBAAoB,OAAO,IAAI,eAAe,IAAI;AAC/E,MAAI,mBAAmB,OAAO,MAAM,KAAK,MAAM,eAAe,CAAC,EAAG,SAAQ,gBAAgB;AAE1F,QAAM,YAAY,KAAK,cAAc,OAAO,IAAI,QAAQ,IAAI;AAC5D,MAAI,SAAyB;AAC7B,MAAI,cAAc,MAAM;AACtB,QAAI,cAAc,OAAQ,UAAS;AAAA,aAC1B,cAAc,QAAS,UAAS;AAAA,QACpC,SAAQ,SAAS;AAAA,EACxB;AAEA,SAAO,SAAS,eAAe;AAC/B,SAAO;AAAA,IACL,cAAc,kBAAkB,IAAI,KAAK,eAAe,EAAE,YAAY,IAAI;AAAA,IAC1E,QAAQ,OAAO,IAAI,QAAQ;AAAA,IAC3B,OAAO,OAAO,UAAU,KAAK,IAAI,QAAQ,KAAK;AAAA,IAC9C;AAAA,EACF;AACF;AAEO,SAAS,iBAAiB,MAA+C;AAC9E,QAAM,UAAmB,CAAC;AAC1B,QAAM,OAAO,KAAK;AAClB,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,YAAQ,WAAW;AACnB,WAAO,SAAS,0BAA0B;AAAA,EAC5C;AACA,QAAM,QAAQ;AACd,MAAI,MAAM,SAAS,IAAK,SAAQ,WAAW;AAC3C,QAAM,MAAsB,CAAC;AAC7B,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,CAAC,KAAK,MAAM;AACxB,UAAM,IAAI,YAAY,CAAC;AACvB,QAAI,CAAC,SAAS,GAAG,GAAG;AAClB,cAAQ,CAAC,IAAI;AACb;AAAA,IACF;AACA,UAAM,MAAM,IAAI,SAAS,KAAK,OAAO,GAAG,CAAC,QAAQ,EAAE,UAAU,MAAM,KAAK,GAAG,CAAC;AAC5E,UAAM,OAAO,IAAI,SAAS,KAAK,QAAQ,GAAG,CAAC,SAAS,EAAE,UAAU,MAAM,KAAK,IAAI,CAAC;AAChF,UAAM,OAAO,IAAI,SAAS,KAAK,QAAQ,GAAG,CAAC,SAAS,EAAE,KAAK,KAAK,UAAU,KAAK,CAAC;AAChF,UAAM,cAAc,IAAI,SAAS,KAAK,oBAAoB,GAAG,CAAC,qBAAqB,EAAE,UAAU,KAAK,CAAC;AACrG,UAAM,QAAQ,IAAI,SAAS,KAAK,0BAA0B,GAAG,CAAC,2BAA2B,EAAE,UAAU,KAAK,CAAC;AAC3G,UAAM,QAAQ,WAAW,SAAS,IAAI,OAAO,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,CAAC;AAC7E,UAAM,UAAU,WAAW,SAAS,IAAI,kBAAkB,GAAG,CAAC,qBAAqB,EAAE,UAAU,KAAK,CAAC;AACrG,QAAI,OAAO,KAAK,IAAI,GAAG,EAAG,SAAQ,GAAG,CAAC,MAAM,IAAI;AAChD,QAAI,IAAK,MAAK,IAAI,GAAG;AACrB,QAAI,QAAQ,CAAC,6BAA6B,KAAK,IAAI,EAAG,SAAQ,GAAG,CAAC,OAAO,IAAI;AAE7E,UAAM,SAAiD,CAAC;AACxD,QAAI,IAAI,WAAW,QAAW;AAC5B,UAAI,CAAC,MAAM,QAAQ,IAAI,MAAM,EAAG,SAAQ,GAAG,CAAC,SAAS,IAAI;AAAA;AAEvD,YAAI,OAAO,QAAQ,CAAC,KAAK,MAAM;AAC7B,cAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,EAAG,SAAQ,GAAG,CAAC,WAAW,CAAC,MAAM,IAAI;AAAA,cAC1F,QAAO,KAAK,EAAE,KAAK,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,IAAI,IAAI,MAAM,KAAK,CAAC;AAAA,QAC5E,CAAC;AAAA,IACL;AACA,UAAM,cAAwB,CAAC;AAC/B,QAAI,IAAI,iBAAiB,QAAW;AAClC,UAAI,CAAC,MAAM,QAAQ,IAAI,YAAY,KAAK,CAAC,IAAI,aAAa,MAAM,QAAQ,EAAG,SAAQ,GAAG,CAAC,eAAe,IAAI;AAAA,UACrG,aAAY,KAAK,GAAI,IAAI,YAAyB;AAAA,IACzD;AACA,QAAI,aAAqC,CAAC;AAC1C,QAAI,IAAI,eAAe,QAAW;AAChC,UAAI,CAAC,SAAS,IAAI,UAAU,KAAK,CAAC,OAAO,OAAO,IAAI,UAAU,EAAE,MAAM,QAAQ,EAAG,SAAQ,GAAG,CAAC,aAAa,IAAI;AAAA,UACzG,cAAa,IAAI;AAAA,IACxB;AACA,QAAI,IAAI,WAAW,UAAa,OAAO,IAAI,WAAW,UAAW,SAAQ,GAAG,CAAC,SAAS,IAAI;AAC1F,QAAI,IAAI,aAAa,UAAa,CAAC,MAAM,IAAI,QAAQ,EAAG,SAAQ,GAAG,CAAC,WAAW,IAAI;AAEnF,QAAI,OAAO,QAAQ,OAAO;AACxB,UAAI,KAAK;AAAA,QACP,KAAK,IAAI,KAAK;AAAA,QACd,MAAM,KAAK,KAAK;AAAA,QAChB,MAAM,QAAQ;AAAA,QACd,kBAAkB,eAAe;AAAA,QACjC,wBAAwB,SAAS;AAAA,QACjC;AAAA,QACA,kBAAkB,WAAW;AAAA,QAC7B;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA,QAAQ,IAAI,WAAW,SAAY,OAAQ,IAAI;AAAA,QAC/C,GAAI,MAAM,IAAI,QAAQ,IAAI,EAAE,UAAU,IAAI,SAAS,IAAI,CAAC;AAAA,MAC1D,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACD,SAAO,SAAS,0BAA0B;AAC1C,SAAO;AACT;AAEO,SAAS,mBAAmB,MAAgD;AACjF,QAAM,UAAmB,CAAC;AAC1B,QAAM,OAAO,KAAK;AAClB,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,YAAQ,aAAa;AACrB,WAAO,SAAS,4BAA4B;AAAA,EAC9C;AACA,QAAM,QAAQ;AACd,MAAI,MAAM,SAAS,IAAK,SAAQ,aAAa;AAC7C,QAAM,MAAuB,CAAC;AAC9B,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,CAAC,KAAK,MAAM;AACxB,UAAM,IAAI,cAAc,CAAC;AACzB,QAAI,CAAC,SAAS,GAAG,GAAG;AAClB,cAAQ,CAAC,IAAI;AACb;AAAA,IACF;AACA,UAAM,KAAK,IAAI,SAAS,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,UAAU,MAAM,KAAK,GAAG,CAAC;AACzE,UAAM,OAAO,IAAI,SAAS,KAAK,QAAQ,GAAG,CAAC,SAAS,EAAE,UAAU,MAAM,KAAK,IAAI,CAAC;AAChF,UAAM,OAAO,IAAI,SAAS,KAAK,QAAQ,GAAG,CAAC,SAAS,EAAE,KAAK,KAAK,UAAU,KAAK,CAAC;AAChF,UAAM,SAAS,IAAI,SAAS,KAAK,aAAa,GAAG,CAAC,cAAc,EAAE,KAAK,IAAI,UAAU,KAAK,CAAC;AAC3F,UAAM,cAAc,IAAI,SAAS,KAAK,oBAAoB,GAAG,CAAC,qBAAqB,EAAE,UAAU,KAAK,CAAC;AACrG,QAAI,MAAM,KAAK,IAAI,EAAE,EAAG,SAAQ,GAAG,CAAC,KAAK,IAAI;AAC7C,QAAI,GAAI,MAAK,IAAI,EAAE;AACnB,QAAI,MAAM,UAAU,WAAW,GAAI,SAAQ,GAAG,CAAC,YAAY,IAAI;AAC/D,QAAI,QAAQ,CAAC,6BAA6B,KAAK,IAAI,EAAG,SAAQ,GAAG,CAAC,OAAO,IAAI;AAC7E,QAAI,QAAqD;AACzD,QAAI,IAAI,UAAU,UAAa,IAAI,UAAU,MAAM;AACjD,UAAI,CAAC,SAAS,IAAI,KAAK,KAAK,CAAC,SAAS,IAAI,MAAM,GAAG,EAAG,SAAQ,GAAG,CAAC,QAAQ,IAAI;AAAA,UACzE,SAAQ,EAAE,KAAK,IAAI,MAAM,KAAK,KAAK,SAAS,IAAI,MAAM,GAAG,IAAI,IAAI,MAAM,MAAM,KAAK;AAAA,IACzF;AACA,QAAI,IAAI,aAAa,UAAa,CAAC,MAAM,IAAI,QAAQ,EAAG,SAAQ,GAAG,CAAC,WAAW,IAAI;AACnF,QAAI,MAAM,MAAM;AACd,UAAI,KAAK,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,KAAK,KAAK,GAAG,MAAM,QAAQ,MAAM,WAAW,UAAU,MAAM,kBAAkB,eAAe,MAAM,OAAO,GAAI,MAAM,IAAI,QAAQ,IAAI,EAAE,UAAU,IAAI,SAAS,IAAI,CAAC,EAAG,CAAC;AAAA,IACxM;AAAA,EACF,CAAC;AACD,SAAO,SAAS,4BAA4B;AAC5C,SAAO;AACT;AAEO,SAAS,kBAAkB,MAAqD;AACrF,QAAM,UAAmB,CAAC;AAC1B,QAAM,OAAO,KAAK;AAClB,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,YAAQ,QAAQ;AAChB,WAAO,SAAS,2BAA2B;AAAA,EAC7C;AACA,QAAM,QAAQ;AACd,MAAI,MAAM,SAAS,IAAK,SAAQ,QAAQ;AACxC,QAAM,MAA4B,CAAC;AACnC,QAAM,QAAQ,CAAC,KAAK,MAAM;AACxB,UAAM,IAAI,SAAS,CAAC;AACpB,QAAI,CAAC,SAAS,GAAG,GAAG;AAClB,cAAQ,CAAC,IAAI;AACb;AAAA,IACF;AACA,UAAM,MAAM,IAAI,SAAS,KAAK,OAAO,GAAG,CAAC,QAAQ,EAAE,UAAU,MAAM,KAAK,GAAG,CAAC;AAC5E,QAAI,CAAC,MAAM,IAAI,SAAS,EAAG,SAAQ,GAAG,CAAC,YAAY,IAAI;AAAA,aAC9C,IAAI,YAAY,EAAG,SAAQ,GAAG,CAAC,YAAY,IAAI;AACxD,QAAI,IAAI,oBAAoB,UAAa,OAAO,IAAI,oBAAoB,UAAW,SAAQ,GAAG,CAAC,kBAAkB,IAAI;AACrH,QAAI,OAAO,MAAM,IAAI,SAAS,KAAK,IAAI,aAAa,EAAG,KAAI,KAAK,EAAE,KAAK,IAAI,KAAK,GAAG,WAAW,IAAI,WAAW,iBAAiB,IAAI,oBAAoB,KAAK,CAAC;AAAA,EAC9J,CAAC;AACD,SAAO,SAAS,2BAA2B;AAC3C,SAAO;AACT;AAEO,SAAS,oBAAoB,MAAiD;AACnF,QAAM,UAAmB,CAAC;AAC1B,QAAM,aAAa,IAAI,SAAS,MAAM,eAAe,eAAe,EAAE,UAAU,MAAM,KAAK,GAAG,CAAC;AAC/F,QAAM,UAAU,IAAI,SAAS,MAAM,WAAW,WAAW,EAAE,UAAU,MAAM,KAAK,IAAI,CAAC;AACrF,QAAM,UAAU,IAAI,SAAS,MAAM,WAAW,WAAW,EAAE,UAAU,MAAM,KAAK,IAAI,CAAC;AACrF,QAAM,iBAAiB,IAAI,SAAS,MAAM,mBAAmB,mBAAmB,EAAE,UAAU,MAAM,KAAK,IAAI,CAAC;AAC5G,QAAM,cAAc,IAAI,SAAS,MAAM,gBAAgB,gBAAgB,EAAE,UAAU,MAAM,KAAK,IAAK,CAAC;AACpG,MAAI,eAAe,CAAC,eAAe,KAAK,WAAW,EAAG,SAAQ,eAAe;AAC7E,QAAM,YAAY,QAAQ,SAAS,KAAK,YAAY,YAAY;AAChE,MAAI,KAAK,oBAAoB,UAAa,OAAO,KAAK,oBAAoB,UAAW,SAAQ,kBAAkB;AAC/G,QAAM,QAA6C,CAAC;AACpD,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,WAAW,EAAG,SAAQ,QAAQ;AAAA;AAEzE,SAAK,MAAM,QAAQ,CAAC,KAAK,MAAM;AAC7B,UAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,EAAG,SAAQ,SAAS,CAAC,MAAM,IAAI;AACzF,UAAI,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,IAAI,QAAQ,KAAK,IAAI,WAAW,EAAG,SAAQ,SAAS,CAAC,WAAW,IAAI;AACjG,UAAI,SAAS,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM,IAAI,QAAQ,KAAK,IAAI,YAAY,EAAG,OAAM,KAAK,EAAE,KAAK,IAAI,IAAI,KAAK,GAAG,UAAU,IAAI,SAAS,CAAC;AAAA,IAChJ,CAAC;AACH,SAAO,SAAS,6BAA6B;AAC7C,SAAO;AAAA,IACL,aAAa,WAAY,KAAK;AAAA,IAC9B,SAAS,WAAW;AAAA,IACpB,SAAS,WAAW;AAAA,IACpB,iBAAiB,kBAAkB;AAAA,IACnC,cAAc,eAAe;AAAA,IAC7B,YAAY,aAAa;AAAA,IACzB;AAAA,IACA,iBAAiB,KAAK,oBAAoB,SAAY,OAAQ,KAAK;AAAA,EACrE;AACF;AAQO,SAAS,mBAAmB,MAA2C;AAC5E,QAAM,UAAmB,CAAC;AAC1B,QAAM,MAAkB,CAAC;AACzB,MAAI,wBAAwB,MAAM;AAChC,UAAM,MAAM,IAAI,SAAS,MAAM,sBAAsB,sBAAsB,EAAE,UAAU,MAAM,KAAK,GAAG,CAAC;AACtG,QAAI,QAAQ,OAAW,KAAI,qBAAqB;AAAA,EAClD;AACA,MAAI,KAAK,WAAW,QAAW;AAC7B,QAAI,KAAK,WAAW,YAAa,SAAQ,SAAS;AAAA,QAC7C,KAAI,SAAS;AAAA,EACpB;AACA,MAAI,KAAK,YAAY,QAAW;AAC9B,QAAI,CAAC,SAAS,KAAK,OAAO,EAAG,SAAQ,UAAU;AAAA,SAC1C;AACH,YAAM,IAAI,KAAK;AACf,UAAI,EAAE,WAAW,UAAU,EAAE,WAAW,cAAc,EAAE,WAAW,SAAU,SAAQ,gBAAgB,IAAI;AACzG,YAAM,WAAW,IAAI,SAAS,GAAG,YAAY,oBAAoB,EAAE,UAAU,MAAM,KAAK,GAAG,CAAC;AAC5F,YAAM,YAAY,IAAI,SAAS,GAAG,aAAa,qBAAqB,EAAE,UAAU,MAAM,KAAK,IAAI,CAAC;AAChG,YAAM,SAAS,QAAQ,SAAS,EAAE,SAAS,iBAAiB;AAC5D,UAAI,EAAE,WAAW,UAAU,EAAE,WAAW,cAAc,EAAE,WAAW,UAAU;AAC3E,YAAI,UAAU,EAAE,QAAQ,EAAE,QAAQ,UAAU,YAAY,MAAM,WAAW,aAAa,MAAM,SAAS,UAAU,KAAK;AAAA,MACtH;AAAA,IACF;AAAA,EACF;AACA,MAAI,EAAE,wBAAwB,SAAS,KAAK,WAAW,UAAa,KAAK,YAAY,OAAW,SAAQ,OAAO;AAC/G,SAAO,SAAS,qBAAqB;AACrC,SAAO;AACT;;;AC5RO,IAAM,kBAAkB,CAAC,iBAAiB,eAAe;AAEzD,IAAM,iBAAiB;AAE9B,IAAM,OAAO,CAAC,MAAe,SAAS,KAAK,UAAkC,CAAC,MAC5E,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG,EAAE,QAAQ,SAAS,EAAE,gBAAgB,mCAAmC,iBAAiB,YAAY,GAAG,QAAQ,EAAE,CAAC;AAExJ,IAAM,QAAQ,CAAC,QAAgB,MAAc,SAAiB,UAAyC,SACrG,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,QAAQ,EAAE,GAAG,MAAM;AAOpD,eAAsB,eAAe,SAAkB,SAA6C;AAClG,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,QAAQ,QAAQ,WAAW,CAAC,QAAQ,QAAQ,IAAI,CAAC,GAAG,eAAe;AACzE,QAAM,OAAO,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,QAAQ,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,IAAI,SAAS,SAAS,CAAC,CAAC;AACzF,MAAI,CAAC,KAAM,QAAO,MAAM,KAAK,aAAa,kBAAkB;AAC5D,QAAM,KAAK,IAAI,SAAS,QAAQ,IAAI;AACpC,QAAM,OAAO,IAAI,SAAS,MAAM,KAAK,KAAK,MAAM,EAAE,QAAQ,QAAQ,EAAE,KAAK;AACzE,QAAM,SAAS,QAAQ,OAAO,YAAY;AAE1C,MAAI;AAEF,QAAI,SAAS,oBAAoB,WAAW,QAAQ;AAClD,YAAM,SAAS,MAAM,QAAQ,MAAM,YAAY,SAAS,MAAM,SAAS,OAAO,GAAG,cAAc,OAAO,CAAC;AACvG,aAAO,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,QAAQ,YAAY,oBAAoB,OAAO,UAAU,EAAE,EAAE,GAAG,GAAG;AAAA,IAC3G;AAEA,UAAM,SAAS,MAAM,WAAW,SAAS,QAAQ,QAAQ,QAAQ,KAAK;AACtE,QAAI,CAAC,OAAQ,QAAO,MAAM,KAAK,gBAAgB,6GAA6G;AAG5J,UAAM,QAAQ,SAAS,IAAI;AAC3B,QAAI,SAAS,OAAO,SAAS,gBAAgB,CAAC,OAAO,WAAW,OAAO,SAAS,KAAK,GAAG;AACtF,aAAO,MAAM,KAAK,aAAa,yCAAyC,KAAK,+CAA+C;AAAA,IAC9H;AAEA,WAAO,MAAM,MAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,OAAO,MAAM;AAAA,EACtE,SAAS,GAAG;AACV,QAAI,aAAa,gBAAiB,QAAO,MAAM,KAAK,qBAAqB,EAAE,SAAS,EAAE,OAAO;AAC7F,QAAI,aAAa,WAAY,QAAO,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO;AAChF,YAAQ,MAAM,8BAA8B,CAAC;AAC7C,WAAO,MAAM,KAAK,kBAAkB,0CAA0C;AAAA,EAChF;AACF;AAEA,eAAe,WAAW,SAAkB,QAA4B,OAAyC;AAC/G,QAAM,SAAS,QAAQ,QAAQ,IAAI,eAAe,KAAK;AACvD,QAAM,QAAQ,mBAAmB,KAAK,OAAO,KAAK,CAAC;AACnD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,MAAM,CAAC,EAAG,KAAK;AAC9B,MAAI,OAAO,WAAW,cAAc,GAAG;AACrC,UAAM,aAAa,MAAM,MAAM,YAAY,WAAW,MAAM;AAC5D,WAAO,aAAa,EAAE,MAAM,cAAc,WAAW,IAAI;AAAA,EAC3D;AACA,MAAI,UAAW,MAAM,aAAa,QAAQ,MAAM,EAAI,QAAO,EAAE,MAAM,MAAM;AACzE,SAAO;AACT;AAGA,SAAS,cAAc,SAA0B;AAC/C,SAAO,QAAQ,QAAQ,IAAI,kBAAkB,KAAK,QAAQ,QAAQ,IAAI,iBAAiB,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAAK;AACrH;AAEO,SAAS,oBAAoB,GAAe;AACjD,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE;AAAA,IACV,QAAQ,EAAE;AAAA,IACV,YAAY,EAAE;AAAA,IACd,YAAY,EAAE;AAAA,IACd,WAAW,EAAE;AAAA,IACb,cAAc,EAAE;AAAA,IAChB,YAAY,EAAE;AAAA,EAChB;AACF;AAEA,eAAe,SAAS,SAAoD;AAC1E,QAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,MAAI,CAAC,KAAK,KAAK,EAAG,OAAM,IAAI,gBAAgB,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC7F,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,UAAM,IAAI,gBAAgB,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAAA,EACtF;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,EAAG,OAAM,IAAI,gBAAgB,sCAAsC,EAAE,MAAM,mBAAmB,CAAC;AAChK,SAAO;AACT;AAOA,SAAS,SAAS,MAAmC;AACnD,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,CAAC;AAC/C,MAAI,UAAU,SAAU,QAAO,kCAAkC,KAAK,IAAI,IAAI,iBAAiB;AAC/F,MAAI,UAAU,YAAa,QAAO;AAClC,MAAI,UAAU,cAAc,UAAU,aAAc,QAAO;AAC3D,SAAO;AACT;AAEA,eAAe,MAAM,QAAgB,MAAc,KAAU,SAAkB,OAAc,QAAsC;AACjI,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAE/C,MAAI,SAAS,aAAa,WAAW,OAAO;AAC1C,UAAM,SAAS,MAAM,MAAM,OAAO;AAClC,UAAM,aAAa,OAAO,SAAS,eAAe,EAAE,IAAI,OAAO,WAAW,IAAI,MAAM,OAAO,WAAW,MAAM,QAAQ,OAAO,WAAW,OAAO,IAAI;AACjJ,WAAO,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,WAAW,EAAE,CAAC;AAAA,EACjD;AAEA,MAAI,SAAS,YAAY;AACvB,QAAI,WAAW,MAAO,QAAO,KAAK,EAAE,MAAM,OAAO,SAAS,eAAe,oBAAoB,OAAO,UAAU,IAAI,KAAK,CAAC;AACxH,QAAI,WAAW,UAAU;AACvB,UAAI,OAAO,SAAS,aAAc,QAAO,MAAM,KAAK,YAAY,+EAA+E;AAC/I,YAAM,MAAM,YAAY,OAAO,OAAO,WAAW,EAAE;AACnD,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI,SAAS,CAAC,MAAM,UAAU;AAC5B,QAAI,SAAS,WAAW,KAAK,WAAW,OAAO;AAC7C,YAAM,eAAe,IAAI,aAAa,IAAI,eAAe;AACzD,YAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;AAC5C,YAAM,WAAW,IAAI,aAAa,IAAI,OAAO;AAC7C,YAAM,QAAQ,WAAW,OAAO,QAAQ,IAAI;AAC5C,UAAI,aAAa,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,KAAM,OAAM,IAAI,gBAAgB,iBAAiB,EAAE,OAAO,mCAAmC,CAAC;AAChK,UAAI,gBAAgB,OAAO,MAAM,KAAK,MAAM,YAAY,CAAC,EAAG,OAAM,IAAI,gBAAgB,iBAAiB,EAAE,eAAe,gCAAgC,CAAC;AAEzJ,YAAM,MAAM,OAAO,gBAAgB;AACnC,YAAM,OAAO,MAAM,MAAM,OAAO,aAAa,eAAe,IAAI,KAAK,YAAY,EAAE,YAAY,IAAI,MAAM,QAAQ,KAAK;AACtH,aAAO,KAAK,EAAE,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,eAAe,GAAG,MAAM,OAAO,OAAO,YAAY,CAAC,GAAG,aAAa,KAAK,WAAW,CAAC;AAAA,IACjI;AACA,UAAM,KAAK,SAAS,CAAC,IAAI,mBAAmB,SAAS,CAAC,CAAC,IAAI;AAC3D,QAAI,SAAS,WAAW,KAAK,IAAI;AAC/B,UAAI,WAAW,MAAO,QAAO,KAAK,EAAE,MAAM,eAAe,MAAM,MAAM,OAAO,QAAQ,EAAE,GAAG,MAAM,OAAO,OAAO,YAAY,EAAE,CAAC;AAC5H,UAAI,WAAW,SAAS;AACtB,cAAM,QAAQ,mBAAmB,MAAM,SAAS,OAAO,CAAC;AAGxD,YAAI,MAAM,WAAW,OAAO,SAAS,gBAAgB,CAAC,OAAO,WAAW,OAAO,SAAS,UAAU,GAAG;AACnG,iBAAO,MAAM,KAAK,aAAa,6FAA6F;AAAA,QAC9H;AACA,YAAI,QAAQ,MAAM,MAAM,OAAO,QAAQ,EAAE;AACzC,YAAI,MAAM,uBAAuB,OAAW,SAAQ,MAAM,MAAM,OAAO,qBAAqB,IAAI,MAAM,kBAAkB;AACxH,YAAI,MAAM,QAAS,SAAQ,MAAM,MAAM,OAAO,iBAAiB,IAAI,MAAM,OAAO;AAChF,YAAI,MAAM,WAAW,YAAa,SAAQ,MAAM,MAAM,OAAO,OAAO,IAAI,oCAAoC;AAC5G,eAAO,KAAK,EAAE,MAAM,eAAe,OAAO,MAAM,OAAO,OAAO,YAAY,EAAE,CAAC;AAAA,MAC/E;AAAA,IACF;AACA,QAAI,SAAS,WAAW,KAAK,MAAM,SAAS,CAAC,MAAM,kBAAkB,WAAW,QAAQ;AACtF,YAAM,QAAQ,oBAAoB,MAAM,SAAS,OAAO,CAAC;AACzD,YAAM,SAAS,MAAM,MAAM,OAAO,eAAe,IAAI,KAAK;AAC1D,aAAO,KAAK,EAAE,MAAM,qBAAqB,OAAO,WAAW,EAAE,GAAG,OAAO,UAAU,MAAM,GAAG;AAAA,IAC5F;AAAA,EACF;AAEA,MAAI,SAAS,gBAAgB,WAAW,OAAO;AAC7C,UAAM,QAAQ,kBAAkB,MAAM,SAAS,OAAO,CAAC;AACvD,WAAO,KAAK,EAAE,MAAM,MAAM,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;AAAA,EACxD;AAEA,MAAI,SAAS,eAAe,WAAW,OAAO;AAC5C,UAAM,WAAW,iBAAiB,MAAM,SAAS,OAAO,CAAC;AACzD,WAAO,KAAK,EAAE,MAAM,EAAE,UAAU,MAAM,MAAM,QAAQ,eAAe,QAAQ,EAAE,EAAE,CAAC;AAAA,EAClF;AAEA,MAAI,SAAS,eAAe,WAAW,OAAO;AAC5C,UAAM,QAAQ,kBAAkB,IAAI,cAAc,EAAE,cAAc,IAAI,UAAU,KAAK,mBAAmB,MAAM,aAAa,KAAK,CAAC;AACjI,UAAM,OAAO,MAAM,MAAM,QAAQ,aAAa,KAAK;AACnD,WAAO,KAAK,EAAE,MAAM,KAAK,SAAS,IAAI,gBAAgB,GAAG,aAAa,KAAK,WAAW,CAAC;AAAA,EACzF;AAEA,MAAI,SAAS,CAAC,MAAM,cAAc,SAAS,WAAW,GAAG;AACvD,UAAM,MAAM,mBAAmB,SAAS,CAAC,CAAE;AAC3C,QAAI,WAAW,MAAO,QAAO,KAAK,EAAE,MAAM,iBAAiB,MAAM,MAAM,QAAQ,eAAe,GAAG,CAAC,EAAE,CAAC;AACrG,QAAI,WAAW,UAAU;AACvB,YAAM,OAAO,MAAM,MAAM,QAAQ,kBAAkB,GAAG;AACtD,UAAI,CAAC,KAAM,QAAO,MAAM,KAAK,aAAa,WAAW,GAAG,YAAY;AACpE,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI,SAAS,iBAAiB,WAAW,OAAO;AAC9C,UAAM,aAAa,mBAAmB,MAAM,SAAS,OAAO,CAAC;AAC7D,WAAO,KAAK,EAAE,MAAM,EAAE,UAAU,MAAM,MAAM,QAAQ,iBAAiB,UAAU,EAAE,EAAE,CAAC;AAAA,EACtF;AAEA,MAAI,SAAS,iBAAiB,WAAW,OAAO;AAC9C,UAAM,QAAQ,kBAAkB,IAAI,cAAc,EAAE,cAAc,KAAK,UAAU,IAAK,CAAC;AACvF,UAAM,OAAO,MAAM,MAAM,QAAQ,eAAe,KAAK;AACrD,WAAO,KAAK,EAAE,MAAM,KAAK,WAAW,IAAI,iBAAiB,GAAG,aAAa,KAAK,WAAW,CAAC;AAAA,EAC5F;AAEA,SAAO,MAAM,KAAK,aAAa,qBAAqB,MAAM,IAAI,IAAI,EAAE;AACtE;","names":[]}
1
+ {"version":3,"sources":["../src/api/serialize.ts","../src/api/validate.ts","../src/api/index.ts"],"sourcesContent":["import type { CategoryRecord, ProductRecord } from '../store/catalog'\nimport type { Address, Fulfillment, Money, Order, QuoteStatus, StoreQuote } from '../types'\n\nexport interface ApiMoney {\n amount: number\n currency: string\n}\n\nexport interface ApiAddress {\n name: string | null\n line1: string | null\n line2: string | null\n city: string | null\n state: string | null\n postal_code: string | null\n country: string | null\n}\n\nexport interface ApiFulfillment {\n id: string\n external_id: string\n carrier: string | null\n service: string | null\n tracking_number: string | null\n tracking_url: string | null\n shipped_at: string | null\n lines: { sku: string; quantity: number }[]\n created_at: string\n}\n\nexport interface ApiOrder {\n id: string\n number: string\n status: Order['status']\n payment: { status: Order['payment']['status']; provider: string | null; reference: string | null; method_label: string | null; paid_at: string | null }\n currency: string\n subtotal: ApiMoney\n shipping: ApiMoney\n tax: ApiMoney\n discount: ApiMoney\n /** The store discount code the buyer used, upper case. Partner and commission stay in the store. */\n discount_code: string | null\n total: ApiMoney\n customer: { id: string; email: string; name: string | null; phone: string | null }\n shipping_address: ApiAddress\n billing_address: ApiAddress | null\n shipping_method: { code: string; name: string } | null\n lines: { id: string; sku: string; name: string; quantity: number; unit_amount: ApiMoney; total_amount: ApiMoney; fulfilled_quantity: number }[]\n fulfillments: ApiFulfillment[]\n external_reference: string | null\n notes: string | null\n metadata: Record<string, string>\n created_at: string\n updated_at: string\n paid_at: string | null\n cancelled_at: string | null\n}\n\nexport interface ApiQuote {\n id: string\n /** With the store's quote prefix: \"Q1001\". */\n number: string\n status: QuoteStatus\n currency: string\n customer: { id: string; email: string; name: string | null; phone: string | null }\n company: string | null\n message: string | null\n shipping_address: ApiAddress | null\n lines: { id: string; sku: string; name: string; quantity: number; list_amount: ApiMoney | null; unit_amount: ApiMoney | null; total_amount: ApiMoney | null }[]\n subtotal: ApiMoney | null\n shipping: ApiMoney | null\n tax: ApiMoney | null\n total: ApiMoney | null\n note: string | null\n expires_at: string | null\n external_reference: string | null\n /** The order the buyer's acceptance placed; it also arrives through GET /orders with `metadata.quote_id`. */\n order_id: string | null\n decline_reason: string | null\n metadata: Record<string, string>\n created_at: string\n updated_at: string\n quoted_at: string | null\n accepted_at: string | null\n declined_at: string | null\n cancelled_at: string | null\n}\n\nexport interface ApiProduct {\n sku: string\n name: string\n slug: string\n description_html: string | null\n short_description_html: string | null\n price: ApiMoney\n compare_at: ApiMoney | null\n attributes: Record<string, string>\n active: boolean\n position: number\n legacy_path: string | null\n categories: { id: string; slug: string | null; name: string | null }[]\n images: { url: string; alt: string | null; position: number }[]\n inventory: { available: number; allow_backorder: boolean; updated_at: string } | null\n created_at: string\n updated_at: string\n}\n\nexport interface ApiCategory {\n id: string\n parent_id: string | null\n slug: string\n name: string\n description_html: string | null\n position: number\n product_count: number\n created_at: string\n updated_at: string\n}\n\nconst money = (m: Money): ApiMoney => ({ amount: m.amount, currency: m.currency.toUpperCase() })\n\nconst address = (a: Address | null): ApiAddress | null =>\n a\n ? {\n name: a.name ?? null,\n line1: a.line1 ?? null,\n line2: a.line2 ?? null,\n city: a.city ?? null,\n state: a.state ?? null,\n postal_code: a.postalCode ?? null,\n country: a.country ?? null,\n }\n : null\n\nexport function serializeFulfillment(f: Fulfillment): ApiFulfillment {\n return {\n id: f.id,\n external_id: f.externalId,\n carrier: f.carrier,\n service: f.service,\n tracking_number: f.trackingNumber,\n tracking_url: f.trackingUrl,\n shipped_at: f.shippedAt,\n lines: f.lines.map((l) => ({ sku: l.sku, quantity: l.quantity })),\n created_at: f.createdAt,\n }\n}\n\n/**\n * The Store API's view of an order. `access_key` never leaves the store. `number` is the human number the buyer\n * sees, with the store's prefix (VLD4200), so the consumer shows the same number.\n */\nexport function serializeOrder(o: Order, numberPrefix = ''): ApiOrder {\n return {\n id: o.id,\n number: `${numberPrefix}${o.number}`,\n status: o.status,\n payment: { status: o.payment.status, provider: o.payment.provider, reference: o.payment.reference, method_label: o.payment.methodLabel, paid_at: o.payment.paidAt },\n currency: o.currency.toUpperCase(),\n subtotal: money(o.subtotal),\n shipping: money(o.shipping),\n tax: money(o.tax),\n discount: money(o.discount),\n discount_code: o.discountCode,\n total: money(o.total),\n customer: { id: o.customer.id, email: o.customer.email, name: o.customer.name, phone: o.customer.phone },\n shipping_address: address(o.shippingAddress) ?? { name: null, line1: null, line2: null, city: null, state: null, postal_code: null, country: null },\n billing_address: address(o.billingAddress),\n shipping_method: o.shippingMethod,\n lines: o.lines.map((l) => ({\n id: l.id,\n sku: l.sku,\n name: l.name,\n quantity: l.quantity,\n unit_amount: money(l.unitAmount),\n total_amount: money(l.totalAmount),\n fulfilled_quantity: l.fulfilledQuantity,\n })),\n fulfillments: o.fulfillments.map(serializeFulfillment),\n external_reference: o.externalReference,\n notes: o.notes,\n metadata: o.metadata,\n created_at: o.createdAt,\n updated_at: o.updatedAt,\n paid_at: o.paidAt,\n cancelled_at: o.cancelledAt,\n }\n}\n\nconst maybeMoney = (m: Money | null): ApiMoney | null => (m ? money(m) : null)\n\n/** The Store API's view of a quote. `access_key` never leaves the store. */\nexport function serializeQuote(q: StoreQuote, numberPrefix = ''): ApiQuote {\n return {\n id: q.id,\n number: `${numberPrefix}${q.number}`,\n status: q.status,\n currency: q.currency.toUpperCase(),\n customer: { id: q.customer.id, email: q.customer.email, name: q.customer.name, phone: q.customer.phone },\n company: q.company,\n message: q.message,\n shipping_address: address(q.shippingAddress),\n lines: q.lines.map((l) => ({\n id: l.id,\n sku: l.sku,\n name: l.name,\n quantity: l.quantity,\n list_amount: maybeMoney(l.listAmount),\n unit_amount: maybeMoney(l.unitAmount),\n total_amount: maybeMoney(l.totalAmount),\n })),\n subtotal: maybeMoney(q.subtotal),\n shipping: maybeMoney(q.shipping),\n tax: maybeMoney(q.tax),\n total: maybeMoney(q.total),\n note: q.offerNote,\n expires_at: q.expiresAt,\n external_reference: q.externalReference,\n order_id: q.orderId,\n decline_reason: q.declineReason,\n metadata: q.metadata,\n created_at: q.createdAt,\n updated_at: q.updatedAt,\n quoted_at: q.quotedAt,\n accepted_at: q.acceptedAt,\n declined_at: q.declinedAt,\n cancelled_at: q.cancelledAt,\n }\n}\n\n/**\n * Maps a stored image src to the public URL a consumer can load. Return null\n * (or undefined) to fall back to the default: an absolute URL as-is, anything\n * else resolved against the request origin.\n */\nexport type ImageUrlResolver = (src: string) => string | null | undefined\n\nexport interface SerializeProductOptions {\n /** The store's own image resolver; see StoreApiOptions.imageUrl. */\n imageUrl?: ImageUrlResolver\n /** The origin relative srcs resolve against when the resolver has no answer (\"https://store.example\"). */\n origin?: string\n}\n\nconst ABSOLUTE = /^[a-z][a-z0-9+.-]*:/i\n\n/**\n * The absolute URL the Store API reports for an image. The store's resolver\n * wins; without one (or when it returns null) an absolute URL passes through\n * unchanged and a relative path or bare filename is resolved against the\n * request origin. Resolving a bare filename only produces a loadable URL when\n * the store serves the file at that path — stores that fingerprint their\n * assets must pass a resolver.\n */\nexport function absoluteImageUrl(src: string, options: SerializeProductOptions = {}): string {\n const url = options.imageUrl?.(src) || src\n if (ABSOLUTE.test(url) || !options.origin) return url\n try {\n return new URL(url, `${options.origin.replace(/\\/+$/, '')}/`).href\n } catch {\n return url\n }\n}\n\n/** The Store API's view of a catalog product — the stored row, not the shopper projection. */\nexport function serializeProduct(p: ProductRecord, options: SerializeProductOptions = {}): ApiProduct {\n return {\n sku: p.sku,\n name: p.name,\n slug: p.slug,\n description_html: p.descriptionHtml,\n short_description_html: p.shortDescriptionHtml,\n price: money(p.price),\n compare_at: p.compareAtPrice ? money(p.compareAtPrice) : null,\n attributes: p.attributes,\n active: p.active,\n position: p.position,\n legacy_path: p.legacyPath,\n categories: p.categories.map((c) => ({ id: c.id, slug: c.slug, name: c.name })),\n images: p.images.map((i) => ({ url: absoluteImageUrl(i.url, options), alt: i.alt, position: i.position })),\n inventory: p.inventory ? { available: p.inventory.available, allow_backorder: p.inventory.allowBackorder, updated_at: p.inventory.updatedAt } : null,\n created_at: p.createdAt,\n updated_at: p.updatedAt,\n }\n}\n\nexport function serializeCategory(c: CategoryRecord): ApiCategory {\n return {\n id: c.id,\n parent_id: c.parentId,\n slug: c.slug,\n name: c.name,\n description_html: c.descriptionHtml,\n position: c.position,\n product_count: c.productCount,\n created_at: c.createdAt,\n updated_at: c.updatedAt,\n }\n}\n","import type { CategoryInput, ProductInput } from '../store/catalog'\nimport type { InventoryItemInput } from '../store/inventory'\nimport type { FulfillmentInput } from '../store/orders'\nimport type { QuoteOfferInput } from '../store/quotes'\nimport type { Money } from '../types'\n\nexport class ValidationError extends Error {\n readonly details: Record<string, string>\n constructor(message: string, details: Record<string, string>) {\n super(message)\n this.name = 'ValidationError'\n this.details = details\n }\n}\n\ntype Details = Record<string, string>\n\nconst isObject = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v)\nconst isString = (v: unknown): v is string => typeof v === 'string'\nconst isInt = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v)\n\nfunction str(details: Details, obj: Record<string, unknown>, key: string, path: string, opts: { required?: boolean; max?: number; nullable?: boolean } = {}): string | null | undefined {\n const v = obj[key]\n if (v === undefined) {\n if (opts.required) details[path] = 'is required'\n return undefined\n }\n if (v === null) {\n if (opts.required && !opts.nullable) details[path] = 'is required'\n return opts.nullable || !opts.required ? null : undefined\n }\n if (!isString(v)) {\n details[path] = 'must be a string'\n return undefined\n }\n if (opts.required && !v.trim()) details[path] = 'is required'\n if (opts.max && v.length > opts.max) details[path] = `must be at most ${opts.max} characters`\n return v\n}\n\nfunction moneyField(details: Details, v: unknown, path: string, opts: { required?: boolean; nullable?: boolean } = {}): Money | null | undefined {\n if (v === undefined) {\n if (opts.required) details[path] = 'is required'\n return undefined\n }\n if (v === null) {\n if (!opts.nullable) details[path] = 'is required'\n return null\n }\n if (!isObject(v) || !isInt(v.amount) || !isString(v.currency) || v.currency.length !== 3) {\n details[path] = 'must be { amount: integer minor units, currency: ISO 4217 }'\n return undefined\n }\n if (v.amount < 0) details[path] = 'must not be negative'\n return { amount: v.amount, currency: v.currency }\n}\n\nfunction isoDate(details: Details, v: unknown, path: string): string | null | undefined {\n if (v === undefined) return undefined\n if (v === null) return null\n if (!isString(v) || Number.isNaN(Date.parse(v))) {\n details[path] = 'must be an ISO-8601 timestamp'\n return undefined\n }\n return new Date(v).toISOString()\n}\n\nfunction finish(details: Details, message: string): void {\n if (Object.keys(details).length) throw new ValidationError(message, details)\n}\n\nexport interface ListQuery {\n updatedSince: string | null\n cursor: string | null\n limit: number\n active: boolean | null\n}\n\n/**\n * Query string for the catalog list endpoints. Out-of-range values are a 422\n * rather than a silent clamp, so a caller paging with a bad limit is told.\n */\nexport function validateListQuery(\n params: URLSearchParams,\n opts: { defaultLimit: number; maxLimit: number; allowUpdatedSince?: boolean; allowActive?: boolean }\n): ListQuery {\n const details: Details = {}\n const limitRaw = params.get('limit')\n const limit = limitRaw ? Number(limitRaw) : opts.defaultLimit\n if (limitRaw && (!Number.isInteger(limit) || limit < 1 || limit > opts.maxLimit)) {\n details.limit = `must be an integer from 1 to ${opts.maxLimit}`\n }\n\n const updatedSinceRaw = opts.allowUpdatedSince ? params.get('updated_since') : null\n if (updatedSinceRaw && Number.isNaN(Date.parse(updatedSinceRaw))) details.updated_since = 'must be an ISO-8601 timestamp'\n\n const activeRaw = opts.allowActive ? params.get('active') : null\n let active: boolean | null = null\n if (activeRaw !== null) {\n if (activeRaw === 'true') active = true\n else if (activeRaw === 'false') active = false\n else details.active = 'must be true or false'\n }\n\n finish(details, 'Invalid query')\n return {\n updatedSince: updatedSinceRaw ? new Date(updatedSinceRaw).toISOString() : null,\n cursor: params.get('cursor'),\n limit: Number.isInteger(limit) ? limit : opts.defaultLimit,\n active,\n }\n}\n\nexport function validateProducts(body: Record<string, unknown>): ProductInput[] {\n const details: Details = {}\n const list = body.products\n if (!Array.isArray(list)) {\n details.products = 'must be an array'\n finish(details, 'Invalid products payload')\n }\n const items = list as unknown[]\n if (items.length > 200) details.products = 'must have at most 200 items'\n const out: ProductInput[] = []\n const seen = new Set<string>()\n items.forEach((raw, i) => {\n const p = `products.${i}`\n if (!isObject(raw)) {\n details[p] = 'must be an object'\n return\n }\n const sku = str(details, raw, 'sku', `${p}.sku`, { required: true, max: 64 })\n const name = str(details, raw, 'name', `${p}.name`, { required: true, max: 255 })\n const slug = str(details, raw, 'slug', `${p}.slug`, { max: 255, nullable: true })\n const description = str(details, raw, 'description_html', `${p}.description_html`, { nullable: true })\n const short = str(details, raw, 'short_description_html', `${p}.short_description_html`, { nullable: true })\n const price = moneyField(details, raw.price, `${p}.price`, { required: true })\n const compare = moneyField(details, raw.compare_at_price, `${p}.compare_at_price`, { nullable: true })\n if (sku && seen.has(sku)) details[`${p}.sku`] = 'appears more than once'\n if (sku) seen.add(sku)\n if (slug && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) details[`${p}.slug`] = 'must be lower-case letters, digits and hyphens'\n\n const images: { url: string; alt?: string | null }[] = []\n if (raw.images !== undefined) {\n if (!Array.isArray(raw.images)) details[`${p}.images`] = 'must be an array'\n else\n raw.images.forEach((img, j) => {\n if (!isObject(img) || !isString(img.url) || !img.url.trim()) details[`${p}.images.${j}.url`] = 'is required'\n else images.push({ url: img.url, alt: isString(img.alt) ? img.alt : null })\n })\n }\n const categoryIds: string[] = []\n if (raw.category_ids !== undefined) {\n if (!Array.isArray(raw.category_ids) || !raw.category_ids.every(isString)) details[`${p}.category_ids`] = 'must be an array of strings'\n else categoryIds.push(...(raw.category_ids as string[]))\n }\n let attributes: Record<string, string> = {}\n if (raw.attributes !== undefined) {\n if (!isObject(raw.attributes) || !Object.values(raw.attributes).every(isString)) details[`${p}.attributes`] = 'must be an object of strings'\n else attributes = raw.attributes as Record<string, string>\n }\n if (raw.active !== undefined && typeof raw.active !== 'boolean') details[`${p}.active`] = 'must be a boolean'\n if (raw.position !== undefined && !isInt(raw.position)) details[`${p}.position`] = 'must be an integer'\n\n if (sku && name && price) {\n out.push({\n sku: sku.trim(),\n name: name.trim(),\n slug: slug ?? null,\n description_html: description ?? null,\n short_description_html: short ?? null,\n price,\n compare_at_price: compare ?? null,\n images,\n category_ids: categoryIds,\n attributes,\n active: raw.active === undefined ? true : (raw.active as boolean),\n ...(isInt(raw.position) ? { position: raw.position } : {}),\n })\n }\n })\n finish(details, 'Invalid products payload')\n return out\n}\n\nexport function validateCategories(body: Record<string, unknown>): CategoryInput[] {\n const details: Details = {}\n const list = body.categories\n if (!Array.isArray(list)) {\n details.categories = 'must be an array'\n finish(details, 'Invalid categories payload')\n }\n const items = list as unknown[]\n if (items.length > 500) details.categories = 'must have at most 500 items'\n const out: CategoryInput[] = []\n const seen = new Set<string>()\n items.forEach((raw, i) => {\n const p = `categories.${i}`\n if (!isObject(raw)) {\n details[p] = 'must be an object'\n return\n }\n const id = str(details, raw, 'id', `${p}.id`, { required: true, max: 64 })\n const name = str(details, raw, 'name', `${p}.name`, { required: true, max: 255 })\n const slug = str(details, raw, 'slug', `${p}.slug`, { max: 255, nullable: true })\n const parent = str(details, raw, 'parent_id', `${p}.parent_id`, { max: 64, nullable: true })\n const description = str(details, raw, 'description_html', `${p}.description_html`, { nullable: true })\n if (id && seen.has(id)) details[`${p}.id`] = 'appears more than once'\n if (id) seen.add(id)\n if (id && parent && parent === id) details[`${p}.parent_id`] = 'cannot be the category itself'\n if (slug && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) details[`${p}.slug`] = 'must be lower-case letters, digits and hyphens'\n let image: { url: string; alt?: string | null } | null = null\n if (raw.image !== undefined && raw.image !== null) {\n if (!isObject(raw.image) || !isString(raw.image.url)) details[`${p}.image`] = 'must be { url, alt? } or null'\n else image = { url: raw.image.url, alt: isString(raw.image.alt) ? raw.image.alt : null }\n }\n if (raw.position !== undefined && !isInt(raw.position)) details[`${p}.position`] = 'must be an integer'\n if (id && name) {\n out.push({ id: id.trim(), name: name.trim(), slug: slug ?? null, parent_id: parent ?? null, description_html: description ?? null, image, ...(isInt(raw.position) ? { position: raw.position } : {}) })\n }\n })\n finish(details, 'Invalid categories payload')\n return out\n}\n\nexport function validateInventory(body: Record<string, unknown>): InventoryItemInput[] {\n const details: Details = {}\n const list = body.items\n if (!Array.isArray(list)) {\n details.items = 'must be an array'\n finish(details, 'Invalid inventory payload')\n }\n const items = list as unknown[]\n if (items.length > 500) details.items = 'must have at most 500 items'\n const out: InventoryItemInput[] = []\n items.forEach((raw, i) => {\n const p = `items.${i}`\n if (!isObject(raw)) {\n details[p] = 'must be an object'\n return\n }\n const sku = str(details, raw, 'sku', `${p}.sku`, { required: true, max: 64 })\n if (!isInt(raw.available)) details[`${p}.available`] = 'must be an integer'\n else if (raw.available < 0) details[`${p}.available`] = 'must be a non-negative integer'\n if (raw.allow_backorder !== undefined && typeof raw.allow_backorder !== 'boolean') details[`${p}.allow_backorder`] = 'must be a boolean'\n if (sku && isInt(raw.available) && raw.available >= 0) out.push({ sku: sku.trim(), available: raw.available, allow_backorder: raw.allow_backorder === true })\n })\n finish(details, 'Invalid inventory payload')\n return out\n}\n\nexport function validateFulfillment(body: Record<string, unknown>): FulfillmentInput {\n const details: Details = {}\n const externalId = str(details, body, 'external_id', 'external_id', { required: true, max: 64 })\n const carrier = str(details, body, 'carrier', 'carrier', { nullable: true, max: 100 })\n const service = str(details, body, 'service', 'service', { nullable: true, max: 100 })\n const trackingNumber = str(details, body, 'tracking_number', 'tracking_number', { nullable: true, max: 100 })\n const trackingUrl = str(details, body, 'tracking_url', 'tracking_url', { nullable: true, max: 2000 })\n if (trackingUrl && !/^https?:\\/\\//.test(trackingUrl)) details.tracking_url = 'must be an absolute http(s) URL'\n const shippedAt = isoDate(details, body.shipped_at, 'shipped_at')\n if (body.notify_customer !== undefined && typeof body.notify_customer !== 'boolean') details.notify_customer = 'must be a boolean'\n const lines: { sku: string; quantity: number }[] = []\n if (!Array.isArray(body.lines) || body.lines.length === 0) details.lines = 'must be a non-empty array'\n else\n body.lines.forEach((raw, i) => {\n if (!isObject(raw) || !isString(raw.sku) || !raw.sku.trim()) details[`lines.${i}.sku`] = 'is required'\n if (!isObject(raw) || !isInt(raw.quantity) || raw.quantity < 1) details[`lines.${i}.quantity`] = 'must be an integer of at least 1'\n if (isObject(raw) && isString(raw.sku) && isInt(raw.quantity) && raw.quantity >= 1) lines.push({ sku: raw.sku.trim(), quantity: raw.quantity })\n })\n finish(details, 'Invalid fulfillment payload')\n return {\n external_id: externalId!.trim(),\n carrier: carrier ?? null,\n service: service ?? null,\n tracking_number: trackingNumber ?? null,\n tracking_url: trackingUrl ?? null,\n shipped_at: shippedAt ?? null,\n lines,\n notify_customer: body.notify_customer === undefined ? true : (body.notify_customer as boolean),\n }\n}\n\nexport interface OrderPatch {\n external_reference?: string | null\n status?: 'cancelled'\n payment?: { status: 'paid' | 'refunded' | 'failed'; provider?: string | null; reference?: string | null; paid_at?: string | null }\n}\n\nexport function validateOrderPatch(body: Record<string, unknown>): OrderPatch {\n const details: Details = {}\n const out: OrderPatch = {}\n if ('external_reference' in body) {\n const ref = str(details, body, 'external_reference', 'external_reference', { nullable: true, max: 64 })\n if (ref !== undefined) out.external_reference = ref\n }\n if (body.status !== undefined) {\n if (body.status !== 'cancelled') details.status = 'can only be set to \"cancelled\"'\n else out.status = 'cancelled'\n }\n if (body.payment !== undefined) {\n if (!isObject(body.payment)) details.payment = 'must be an object'\n else {\n const p = body.payment\n if (p.status !== 'paid' && p.status !== 'refunded' && p.status !== 'failed') details['payment.status'] = 'must be paid, refunded or failed'\n const provider = str(details, p, 'provider', 'payment.provider', { nullable: true, max: 64 })\n const reference = str(details, p, 'reference', 'payment.reference', { nullable: true, max: 255 })\n const paidAt = isoDate(details, p.paid_at, 'payment.paid_at')\n if (p.status === 'paid' || p.status === 'refunded' || p.status === 'failed') {\n out.payment = { status: p.status, provider: provider ?? null, reference: reference ?? null, paid_at: paidAt ?? null }\n }\n }\n }\n if (!('external_reference' in body) && body.status === undefined && body.payment === undefined) details.body = 'must include external_reference, status or payment'\n finish(details, 'Invalid order patch')\n return out\n}\n\nexport interface QuotePatch {\n external_reference?: string | null\n status?: 'cancelled'\n offer?: QuoteOfferInput\n}\n\n/** `PATCH /quotes/{id}`: stamp a reference, withdraw the quote, or price it (`offer`). */\nexport function validateQuotePatch(body: Record<string, unknown>): QuotePatch {\n const details: Details = {}\n const out: QuotePatch = {}\n if ('external_reference' in body) {\n const ref = str(details, body, 'external_reference', 'external_reference', { nullable: true, max: 64 })\n if (ref !== undefined) out.external_reference = ref\n }\n if (body.status !== undefined) {\n if (body.status !== 'cancelled') details.status = 'can only be set to \"cancelled\"'\n else out.status = 'cancelled'\n }\n if (body.offer !== undefined) {\n if (!isObject(body.offer)) details.offer = 'must be an object'\n else {\n const o = body.offer\n const lines: QuoteOfferInput['lines'] = []\n if (!Array.isArray(o.lines) || o.lines.length === 0) details['offer.lines'] = 'must be a non-empty array'\n else\n o.lines.forEach((raw, i) => {\n if (!isObject(raw)) {\n details[`offer.lines.${i}`] = 'must be an object'\n return\n }\n const sku = str(details, raw, 'sku', `offer.lines.${i}.sku`, { required: true, max: 100 })\n const name = str(details, raw, 'name', `offer.lines.${i}.name`, { nullable: true, max: 300 })\n if (!isInt(raw.quantity) || raw.quantity < 1) details[`offer.lines.${i}.quantity`] = 'must be an integer of at least 1'\n const unit = moneyField(details, raw.unit_amount, `offer.lines.${i}.unit_amount`, { required: true })\n if (sku && isInt(raw.quantity) && raw.quantity >= 1 && unit) lines.push({ sku: sku.trim(), name: name ?? null, quantity: raw.quantity, unit_amount: unit })\n })\n const shipping = moneyField(details, o.shipping, 'offer.shipping', { nullable: true })\n const tax = moneyField(details, o.tax, 'offer.tax', { nullable: true })\n const expiresAt = isoDate(details, o.expires_at, 'offer.expires_at')\n const note = str(details, o, 'note', 'offer.note', { nullable: true, max: 4000 })\n if (o.notify_customer !== undefined && typeof o.notify_customer !== 'boolean') details['offer.notify_customer'] = 'must be a boolean'\n out.offer = {\n lines,\n shipping: shipping ?? null,\n tax: tax ?? null,\n expires_at: expiresAt ?? null,\n note: note ?? null,\n notify_customer: o.notify_customer === undefined ? true : (o.notify_customer as boolean),\n }\n }\n }\n if (out.status && out.offer) details.status = 'cannot be combined with an offer'\n if (!('external_reference' in body) && body.status === undefined && body.offer === undefined) details.body = 'must include external_reference, status or offer'\n finish(details, 'Invalid quote patch')\n return out\n}\n","import type { Connection, Store } from '../store'\nimport { API_KEY_PREFIX, type ConnectScope } from '../store/connections'\nimport { secretsEqual } from '../store/crypto'\nimport { StoreError } from '../store/errors'\nimport { serializeCategory, serializeFulfillment, serializeOrder, serializeProduct, serializeQuote, type ImageUrlResolver, type SerializeProductOptions } from './serialize'\nimport {\n validateCategories, validateFulfillment, validateInventory, validateListQuery, validateOrderPatch, validateProducts, validateQuotePatch, ValidationError,\n} from './validate'\n\nexport interface StoreApiOptions {\n store: Store\n /**\n * The STORE_API_KEY secret — the break-glass key that works without an owner\n * or a connection. Optional: a store whose consumers all connect through the\n * owner-approved flow can leave it unset.\n */\n apiKey: string | undefined\n /** Where the API is mounted in the request URL. */\n basePath?: string\n /**\n * Maps a stored product image src (a bare filename, a path, or an absolute\n * URL) to the absolute URL the store actually serves it at — e.g. the\n * fingerprinted `/_astro/<name>.<hash>.jpg` a template's `resolveImage()`\n * finds, made absolute against the site URL. Return null to fall back to the\n * default: an absolute src as-is, a relative one resolved against the\n * request origin. `images[].url` in every response is always absolute.\n */\n imageUrl?: ImageUrlResolver\n}\n\n/** Who a request authenticated as: the environment key, or an approved connection. */\nexport type ApiCaller = { kind: 'env' } | { kind: 'connection'; connection: Connection }\n\n/**\n * Where the Store API is served. `/api/store/v1` is the neutral path for any\n * operations system; `/api/skuio/v1` is the original path SKU.io's Storefront\n * integration calls, kept for good. Same handler, same keys, same responses.\n */\nexport const STORE_API_BASES = ['/api/store/v1', '/api/skuio/v1'] as const\n/** @deprecated The SKU.io path; use STORE_API_BASES. */\nexport const STORE_API_BASE = '/api/skuio/v1'\n\nconst json = (body: unknown, status = 200, headers: Record<string, string> = {}) =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store', ...headers } })\n\nconst error = (status: number, code: string, message: string, details: Record<string, string> | null = null) =>\n json({ error: { code, message, details } }, status)\n\n/**\n * The Store API (`docs/store-api.md`) as one fetch-style handler. Mount it on\n * a catch-all route under `/api/store/v1` (and `/api/skuio/v1`, the path SKU.io\n * calls) and hand it every method.\n */\nexport async function handleStoreApi(request: Request, options: StoreApiOptions): Promise<Response> {\n const url = new URL(request.url)\n const bases = options.basePath ? [options.basePath] : [...STORE_API_BASES]\n const base = bases.map((b) => b.replace(/\\/+$/, '')).find((b) => url.pathname.includes(b))\n if (!base) return error(404, 'not_found', 'No such endpoint')\n const at = url.pathname.indexOf(base)\n const path = url.pathname.slice(at + base.length).replace(/\\/+$/, '') || '/'\n const method = request.method.toUpperCase()\n\n try {\n // The token exchange is the one unauthenticated endpoint: the code is the credential.\n if (path === '/connect/token' && method === 'POST') {\n const issued = await options.store.connections.exchange(await readJson(request), clientAddress(request))\n return json({ data: { api_key: issued.apiKey, connection: serializeConnection(issued.connection) } }, 201)\n }\n\n const caller = await authorized(request, options.apiKey, options.store)\n if (!caller) return error(401, 'unauthorized', 'A valid bearer key is required: a connection key issued through /admin/connect, or the STORE_API_KEY secret')\n\n // A connection may use only what the owner approved; the STORE_API_KEY secret may use everything.\n const scope = scopeFor(path)\n if (scope && caller.kind === 'connection' && !caller.connection.scopes.includes(scope)) {\n return error(403, 'forbidden', `This connection was not approved for \"${scope}\"; reconnect and approve it at /admin/connect`)\n }\n\n const images: SerializeProductOptions = { imageUrl: options.imageUrl, origin: url.origin }\n return await route(method, path, url, request, options.store, caller, images)\n } catch (e) {\n if (e instanceof ValidationError) return error(422, 'validation_failed', e.message, e.details)\n if (e instanceof StoreError) return error(e.status, e.code, e.message, e.details)\n console.error('store api: unhandled error', e)\n return error(500, 'internal_error', 'The store could not complete the request')\n }\n}\n\nasync function authorized(request: Request, apiKey: string | undefined, store: Store): Promise<ApiCaller | null> {\n const header = request.headers.get('authorization') ?? ''\n const match = /^Bearer\\s+(.+)$/i.exec(header.trim())\n if (!match) return null\n const bearer = match[1]!.trim()\n if (bearer.startsWith(API_KEY_PREFIX)) {\n const connection = await store.connections.resolveKey(bearer)\n return connection ? { kind: 'connection', connection } : null\n }\n if (apiKey && (await secretsEqual(bearer, apiKey))) return { kind: 'env' }\n return null\n}\n\n/** The caller's address for failure counting; Cloudflare sets the first header, proxies the second. */\nfunction clientAddress(request: Request): string {\n return request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'\n}\n\nexport function serializeConnection(c: Connection) {\n return {\n id: c.id,\n name: c.name,\n tenant: c.tenant,\n scopes: c.scopes,\n key_prefix: c.keyPrefix,\n created_at: c.createdAt,\n issued_at: c.issuedAt,\n last_used_at: c.lastUsedAt,\n revoked_at: c.revokedAt,\n }\n}\n\nasync function readJson(request: Request): Promise<Record<string, unknown>> {\n const text = await request.text()\n if (!text.trim()) throw new ValidationError('Request body must be JSON', { body: 'is empty' })\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch {\n throw new ValidationError('Request body must be JSON', { body: 'is not valid JSON' })\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new ValidationError('Request body must be a JSON object', { body: 'is not an object' })\n return parsed as Record<string, unknown>\n}\n\n/**\n * The scope an endpoint needs. There is deliberately no endpoint that creates\n * an order: orders come only from the store's own checkout, priced from its\n * database, or from a buyer accepting a quote, so a leaked key cannot invent\n * sales. A key with `quotes` can price a quote; only the buyer turns it into\n * an order.\n */\nfunction scopeFor(path: string): ConnectScope | null {\n const first = path.split('/').filter(Boolean)[0]\n if (first === 'orders') return /^\\/orders\\/[^/]+\\/fulfillments$/.test(path) ? 'fulfillments' : 'orders'\n if (first === 'inventory') return 'inventory'\n if (first === 'products' || first === 'categories') return 'catalog'\n if (first === 'quotes') return 'quotes'\n return null // /health, /connect\n}\n\nasync function route(method: string, path: string, url: URL, request: Request, store: Store, caller: ApiCaller, images: SerializeProductOptions): Promise<Response> {\n const segments = path.split('/').filter(Boolean)\n\n if (path === '/health' && method === 'GET') {\n const health = await store.health()\n const connection = caller.kind === 'connection' ? { id: caller.connection.id, name: caller.connection.name, tenant: caller.connection.tenant } : null\n return json({ data: { ...health, connection } })\n }\n\n if (path === '/connect') {\n if (method === 'GET') return json({ data: caller.kind === 'connection' ? serializeConnection(caller.connection) : null })\n if (method === 'DELETE') {\n if (caller.kind !== 'connection') return error(409, 'conflict', 'The STORE_API_KEY secret is not a connection; rotate it on the Worker instead')\n await store.connections.revoke(caller.connection.id)\n return new Response(null, { status: 204 })\n }\n }\n\n if (segments[0] === 'orders') {\n if (segments.length === 1 && method === 'GET') {\n const updatedSince = url.searchParams.get('updated_since')\n const cursor = url.searchParams.get('cursor')\n const limitRaw = url.searchParams.get('limit')\n const limit = limitRaw ? Number(limitRaw) : 50\n if (limitRaw && (!Number.isInteger(limit) || limit < 1 || limit > 200)) throw new ValidationError('Invalid query', { limit: 'must be an integer from 1 to 200' })\n if (updatedSince && Number.isNaN(Date.parse(updatedSince))) throw new ValidationError('Invalid query', { updated_since: 'must be an ISO-8601 timestamp' })\n // Lazy housekeeping: abandoned processor checkouts become cancelled before the consumer sees the list.\n await store.orders.expireAbandoned()\n const page = await store.orders.changedSince(updatedSince ? new Date(updatedSince).toISOString() : null, cursor, limit)\n return json({ data: page.orders.map((o) => serializeOrder(o, store.config.orders.numberPrefix)), next_cursor: page.nextCursor })\n }\n const id = segments[1] ? decodeURIComponent(segments[1]) : ''\n if (segments.length === 2 && id) {\n if (method === 'GET') return json({ data: serializeOrder(await store.orders.require(id), store.config.orders.numberPrefix) })\n if (method === 'PATCH') {\n const patch = validateOrderPatch(await readJson(request))\n // Recording a payment is its own scope: a key that may read and stamp orders cannot\n // turn an unpaid order into a paid one unless the owner approved that too.\n if (patch.payment && caller.kind === 'connection' && !caller.connection.scopes.includes('payments')) {\n return error(403, 'forbidden', 'This connection was not approved for \"payments\"; reconnect and approve it at /admin/connect')\n }\n let order = await store.orders.require(id)\n if (patch.external_reference !== undefined) order = await store.orders.setExternalReference(id, patch.external_reference)\n if (patch.payment) order = await store.orders.setPaymentStatus(id, patch.payment)\n if (patch.status === 'cancelled') order = await store.orders.cancel(id, 'Cancelled by the operations system')\n return json({ data: serializeOrder(order, store.config.orders.numberPrefix) })\n }\n }\n if (segments.length === 3 && id && segments[2] === 'fulfillments' && method === 'POST') {\n const input = validateFulfillment(await readJson(request))\n const result = await store.orders.addFulfillment(id, input)\n return json({ data: serializeFulfillment(result.fulfillment) }, result.created ? 201 : 200)\n }\n }\n\n if (segments[0] === 'quotes') {\n const prefix = store.config.quotes.numberPrefix\n if (segments.length === 1 && method === 'GET') {\n const query = validateListQuery(url.searchParams, { defaultLimit: 50, maxLimit: 200, allowUpdatedSince: true })\n // Lazy housekeeping: offers past their expiry become `expired` before the consumer sees the list.\n await store.quotes.expireStale()\n const page = await store.quotes.changedSince(query.updatedSince, query.cursor, query.limit)\n return json({ data: page.quotes.map((q) => serializeQuote(q, prefix)), next_cursor: page.nextCursor })\n }\n const id = segments[1] ? decodeURIComponent(segments[1]) : ''\n if (segments.length === 2 && id) {\n if (method === 'GET') return json({ data: serializeQuote(await store.quotes.require(id), prefix) })\n if (method === 'PATCH') {\n const patch = validateQuotePatch(await readJson(request))\n let quote = await store.quotes.require(id)\n if (patch.external_reference !== undefined) quote = await store.quotes.setExternalReference(id, patch.external_reference)\n if (patch.offer) quote = await store.quotes.offer(id, patch.offer)\n if (patch.status === 'cancelled') quote = await store.quotes.cancel(id, 'Cancelled by the operations system')\n return json({ data: serializeQuote(quote, prefix) })\n }\n }\n }\n\n if (path === '/inventory' && method === 'PUT') {\n const items = validateInventory(await readJson(request))\n return json({ data: await store.inventory.set(items) })\n }\n\n if (path === '/products' && method === 'PUT') {\n const products = validateProducts(await readJson(request))\n return json({ data: { upserted: await store.catalog.upsertProducts(products) } })\n }\n\n if (path === '/products' && method === 'GET') {\n const query = validateListQuery(url.searchParams, { defaultLimit: 50, maxLimit: 200, allowUpdatedSince: true, allowActive: true })\n const page = await store.catalog.listProducts(query)\n return json({ data: page.products.map((p) => serializeProduct(p, images)), next_cursor: page.nextCursor })\n }\n\n if (segments[0] === 'products' && segments.length === 2) {\n const sku = decodeURIComponent(segments[1]!)\n if (method === 'GET') return json({ data: serializeProduct(await store.catalog.requireProduct(sku), images) })\n if (method === 'DELETE') {\n const done = await store.catalog.deactivateProduct(sku)\n if (!done) return error(404, 'not_found', `Product ${sku} not found`)\n return new Response(null, { status: 204 })\n }\n }\n\n if (path === '/categories' && method === 'PUT') {\n const categories = validateCategories(await readJson(request))\n return json({ data: { upserted: await store.catalog.upsertCategories(categories) } })\n }\n\n if (path === '/categories' && method === 'GET') {\n const query = validateListQuery(url.searchParams, { defaultLimit: 500, maxLimit: 1000 })\n const page = await store.catalog.listCategories(query)\n return json({ data: page.categories.map(serializeCategory), next_cursor: page.nextCursor })\n }\n\n return error(404, 'not_found', `No such endpoint: ${method} ${path}`)\n}\n\nexport {\n serializeOrder, serializeFulfillment, serializeProduct, serializeCategory, serializeQuote, absoluteImageUrl,\n type ImageUrlResolver, type SerializeProductOptions,\n type ApiOrder, type ApiFulfillment, type ApiProduct, type ApiCategory, type ApiQuote,\n} from './serialize'\nexport { ValidationError } from './validate'\n"],"mappings":"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","names":[]}
@@ -189,6 +189,84 @@ function merchantNewOrderMail(order, ctx) {
189
189
  );
190
190
  return { subject: `New order ${number} \u2014 ${formatMoney(order.total, config.locale)}`, html, text };
191
191
  }
192
+ function quoteUrl(quote, siteUrl) {
193
+ return `${siteUrl}/quotes/${encodeURIComponent(quote.id)}/?key=${encodeURIComponent(quote.accessKey)}`;
194
+ }
195
+ function quoteNumber(quote, config) {
196
+ return `${config.quotes.numberPrefix}${quote.number}`;
197
+ }
198
+ function quoteLinesTable(quote, locale, priced) {
199
+ const rows = quote.lines.map(
200
+ (l) => `<tr><td style="padding:8px 0;border-bottom:1px solid #eee">${escapeHtml(l.name)}<br><span style="color:#666;font-size:12px">SKU ${escapeHtml(l.sku)} \xD7 ${l.quantity}${priced && l.unitAmount ? ` at ${formatMoney(l.unitAmount, locale)}` : ""}</span></td><td align="right" style="padding:8px 0;border-bottom:1px solid #eee;white-space:nowrap">${priced && l.totalAmount ? formatMoney(l.totalAmount, locale) : ""}</td></tr>`
201
+ ).join("");
202
+ const totals = priced && quote.total ? [
203
+ ["Subtotal", quote.subtotal ? formatMoney(quote.subtotal, locale) : ""],
204
+ ["Shipping", quote.shipping?.amount ? formatMoney(quote.shipping, locale) : "Included"],
205
+ ...quote.tax?.amount ? [["Tax", formatMoney(quote.tax, locale)]] : [],
206
+ ["Total", formatMoney(quote.total, locale)]
207
+ ].map(([k, v], i, arr) => `<tr><td style="padding:6px 0;${i === arr.length - 1 ? "font-weight:700" : "color:#666"}">${k}</td><td align="right" style="padding:6px 0;${i === arr.length - 1 ? "font-weight:700" : ""}">${v}</td></tr>`).join("") : "";
208
+ return `<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="font-size:14px">${rows}${totals}</table>`;
209
+ }
210
+ function quoteLinesText(quote, locale, priced) {
211
+ return [
212
+ ...quote.lines.map((l) => `${l.quantity} \xD7 ${l.name} (${l.sku})${priced && l.totalAmount ? ` \u2014 ${formatMoney(l.totalAmount, locale)}` : ""}`),
213
+ ...priced && quote.total ? [
214
+ "",
215
+ ...quote.subtotal ? [`Subtotal: ${formatMoney(quote.subtotal, locale)}`] : [],
216
+ `Shipping: ${quote.shipping?.amount ? formatMoney(quote.shipping, locale) : "Included"}`,
217
+ ...quote.tax?.amount ? [`Tax: ${formatMoney(quote.tax, locale)}`] : [],
218
+ `Total: ${formatMoney(quote.total, locale)}`
219
+ ] : []
220
+ ];
221
+ }
222
+ var button = (url, label) => `<p style="margin:24px 0 0"><a href="${escapeHtml(url)}" style="display:inline-block;background:#0f6fff;color:#fff;text-decoration:none;padding:12px 22px;border-radius:6px;font-weight:700;font-size:14px">${escapeHtml(label)}</a></p>`;
223
+ function quoteReceivedMail(quote, ctx) {
224
+ const { config, siteUrl } = ctx;
225
+ const number = quoteNumber(quote, config);
226
+ const url = quoteUrl(quote, siteUrl);
227
+ const intro = `Thanks \u2014 we have your request for a quote and will email you when it is priced. Keep this email: the link below shows the quote's status at any time.`;
228
+ const html = layout(config, `Quote request ${number} received`, `<p style="margin:0 0 16px;font-size:14px">${intro}</p>${quoteLinesTable(quote, config.locale, false)}${button(url, "View your quote request")}`, contactFooter(config));
229
+ const text = [`${config.name} \u2014 quote request ${number} received`, "", intro, "", ...quoteLinesText(quote, config.locale, false), "", `View your quote request: ${url}`].join("\n");
230
+ return { subject: `Quote request ${number} received \u2014 ${config.name}`, html, text };
231
+ }
232
+ function quoteReadyMail(quote, ctx) {
233
+ const { config, siteUrl } = ctx;
234
+ const number = quoteNumber(quote, config);
235
+ const url = quoteUrl(quote, siteUrl);
236
+ const expiry = quote.expiresAt ? ` It is valid until ${new Date(quote.expiresAt).toLocaleDateString(config.locale, { year: "numeric", month: "long", day: "numeric", timeZone: "UTC" })}.` : "";
237
+ const intro = `Your quote is ready.${expiry} Accept it to place the order at these prices, or decline it \u2014 either takes one click.`;
238
+ const html = layout(
239
+ config,
240
+ `Quote ${number} is ready`,
241
+ `<p style="margin:0 0 16px;font-size:14px">${escapeHtml(intro)}</p>${quoteLinesTable(quote, config.locale, true)}${quote.offerNote ? `<p style="margin:16px 0 0;font-size:14px;color:#444;white-space:pre-wrap">${escapeHtml(quote.offerNote)}</p>` : ""}${button(url, "Review and accept")}`,
242
+ contactFooter(config)
243
+ );
244
+ const text = [`${config.name} \u2014 quote ${number} is ready`, "", intro, "", ...quoteLinesText(quote, config.locale, true), ...quote.offerNote ? ["", quote.offerNote] : [], "", `Review and accept: ${url}`].join("\n");
245
+ return { subject: `Quote ${number} is ready \u2014 ${config.name}`, html, text };
246
+ }
247
+ function merchantQuoteMail(quote, ctx, event, orderNumber) {
248
+ const { config } = ctx;
249
+ const number = quoteNumber(quote, config);
250
+ const headline = event === "requested" ? `New quote request ${number}` : event === "accepted" ? `Quote ${number} accepted${orderNumber ? ` \u2014 order ${config.orders.numberPrefix}${orderNumber}` : ""}` : `Quote ${number} declined`;
251
+ const ship = addressLines(quote.shippingAddress);
252
+ const text = [
253
+ headline,
254
+ "",
255
+ ...quoteLinesText(quote, config.locale, event !== "requested"),
256
+ "",
257
+ `Customer: ${quote.customer.name ?? "(not given)"}`,
258
+ ...quote.company ? [`Company: ${quote.company}`] : [],
259
+ `Email: ${quote.customer.email}`,
260
+ `Phone: ${quote.customer.phone ?? "(not given)"}`,
261
+ ...ship.length ? ["", "Ship to:", ...ship] : [],
262
+ ...quote.message ? ["", `Message: ${quote.message}`] : [],
263
+ ...event === "declined" && quote.declineReason ? ["", `Reason: ${quote.declineReason}`] : [],
264
+ "",
265
+ `Quote id: ${quote.id}`
266
+ ].join("\n");
267
+ const html = layout(config, headline, `<pre style="font-family:ui-monospace,Menlo,monospace;font-size:13px;white-space:pre-wrap;margin:0">${escapeHtml(text)}</pre>`, escapeHtml(config.name));
268
+ return { subject: `${headline} \u2014 ${config.name}`, html, text };
269
+ }
192
270
 
193
271
  export {
194
272
  escapeHtml,
@@ -197,6 +275,10 @@ export {
197
275
  partnerSignInMail,
198
276
  magicLinkMail,
199
277
  ownerPasswordResetMail,
200
- merchantNewOrderMail
278
+ merchantNewOrderMail,
279
+ quoteUrl,
280
+ quoteReceivedMail,
281
+ quoteReadyMail,
282
+ merchantQuoteMail
201
283
  };
202
- //# sourceMappingURL=chunk-UCREHLZH.js.map
284
+ //# sourceMappingURL=chunk-BD274TXU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mail/templates.ts"],"sourcesContent":["import type { StorefrontConfig } from '../config'\nimport { formatMoney } from '../money'\nimport type { Address, Fulfillment, Order, StoreQuote } from '../types'\n\nexport interface RenderedMail {\n subject: string\n html: string\n text: string\n}\n\nexport interface MailContext {\n config: StorefrontConfig\n /** Absolute origin for links, no trailing slash. */\n siteUrl: string\n}\n\nexport function escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[c]!)\n}\n\nfunction addressLines(a: Address | null | undefined): string[] {\n if (!a) return []\n return [a.name, a.line1, a.line2, [a.city, a.state, a.postalCode].filter(Boolean).join(', '), a.country].filter(\n (x): x is string => !!x\n )\n}\n\nfunction layout(config: StorefrontConfig, title: string, bodyHtml: string, footer: string): string {\n return `<!doctype html><html><body style=\"margin:0;padding:0;background:#f4f4f5;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#1a1a1a\">\n<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"background:#f4f4f5;padding:24px 0\"><tr><td align=\"center\">\n<table role=\"presentation\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" style=\"max-width:600px;width:100%;background:#ffffff;border-radius:8px;overflow:hidden\">\n<tr><td style=\"background:#1f2933;color:#ffffff;padding:20px 28px;font-size:18px;font-weight:700\">${escapeHtml(config.name)}</td></tr>\n<tr><td style=\"padding:28px\"><h1 style=\"margin:0 0 16px;font-size:22px\">${escapeHtml(title)}</h1>${bodyHtml}</td></tr>\n<tr><td style=\"padding:16px 28px;border-top:1px solid #e6e6e6;font-size:12px;color:#666\">${footer}</td></tr>\n</table></td></tr></table></body></html>`\n}\n\nfunction linesTable(order: Order, locale: string): string {\n const rows = order.lines\n .map(\n (l) =>\n `<tr><td style=\"padding:8px 0;border-bottom:1px solid #eee\">${escapeHtml(l.name)}<br><span style=\"color:#666;font-size:12px\">SKU ${escapeHtml(l.sku)} × ${l.quantity}</span></td><td align=\"right\" style=\"padding:8px 0;border-bottom:1px solid #eee;white-space:nowrap\">${formatMoney(l.totalAmount, locale)}</td></tr>`\n )\n .join('')\n const totals = [\n ['Subtotal', formatMoney(order.subtotal, locale)],\n ['Shipping', order.shipping.amount ? formatMoney(order.shipping, locale) : 'Free'],\n ...(order.discount.amount ? [['Discount', `−${formatMoney(order.discount, locale)}`]] : []),\n ...(order.tax.amount ? [['Tax', formatMoney(order.tax, locale)]] : []),\n ['Total', formatMoney(order.total, locale)],\n ]\n .map(([k, v], i, arr) => `<tr><td style=\"padding:6px 0;${i === arr.length - 1 ? 'font-weight:700' : 'color:#666'}\">${k}</td><td align=\"right\" style=\"padding:6px 0;${i === arr.length - 1 ? 'font-weight:700' : ''}\">${v}</td></tr>`)\n .join('')\n return `<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"font-size:14px\">${rows}${totals}</table>`\n}\n\nfunction linesText(order: Order, locale: string): string[] {\n return [\n ...order.lines.map((l) => `${l.quantity} × ${l.name} (${l.sku}) — ${formatMoney(l.totalAmount, locale)}`),\n '',\n `Subtotal: ${formatMoney(order.subtotal, locale)}`,\n `Shipping: ${order.shipping.amount ? formatMoney(order.shipping, locale) : 'Free'}`,\n ...(order.discount.amount ? [`Discount: -${formatMoney(order.discount, locale)}`] : []),\n ...(order.tax.amount ? [`Tax: ${formatMoney(order.tax, locale)}`] : []),\n `Total: ${formatMoney(order.total, locale)}`,\n ]\n}\n\nfunction contactFooter(config: StorefrontConfig): string {\n const bits = [config.contact?.phone ? `Call ${escapeHtml(config.contact.phone)}` : '', config.contact?.email ? `<a href=\"mailto:${escapeHtml(config.contact.email)}\">${escapeHtml(config.contact.email)}</a>` : '']\n .filter(Boolean)\n .join(' · ')\n return bits ? `Questions? ${bits}` : escapeHtml(config.name)\n}\n\nexport function orderConfirmationMail(order: Order, ctx: MailContext): RenderedMail {\n const { config, siteUrl } = ctx\n const number = `${config.orders.numberPrefix}${order.number}`\n const statusUrl = `${siteUrl}/checkout/success/?order=${encodeURIComponent(order.id)}&key=${encodeURIComponent(order.accessKey)}`\n const ship = addressLines(order.shippingAddress)\n const paid = order.payment.status === 'paid'\n const intro = paid\n ? `Thanks for your order. We have received your payment and will email you again when it ships.`\n : `Thanks for your order. It is reserved and will ship once payment is received.`\n const html = layout(\n config,\n `Order ${number} ${paid ? 'confirmed' : 'received'}`,\n `<p style=\"margin:0 0 16px;font-size:14px\">${intro}</p>\n${linesTable(order, config.locale)}\n${ship.length ? `<h2 style=\"font-size:14px;margin:24px 0 6px\">Shipping to</h2><p style=\"margin:0;font-size:14px;color:#444\">${ship.map(escapeHtml).join('<br>')}</p>` : ''}\n${order.shippingMethod ? `<p style=\"margin:12px 0 0;font-size:13px;color:#666\">${escapeHtml(order.shippingMethod.name)}</p>` : ''}\n<p style=\"margin:24px 0 0;font-size:14px\"><a href=\"${escapeHtml(statusUrl)}\" style=\"color:#0f6fff\">View your order</a></p>`,\n contactFooter(config)\n )\n const text = [\n `${config.name} — order ${number} ${paid ? 'confirmed' : 'received'}`,\n '',\n intro,\n '',\n ...linesText(order, config.locale),\n '',\n ...(ship.length ? ['Shipping to:', ...ship, ''] : []),\n ...(order.shippingMethod ? [order.shippingMethod.name, ''] : []),\n `View your order: ${statusUrl}`,\n ].join('\\n')\n return { subject: `Order ${number} ${paid ? 'confirmed' : 'received'} — ${config.name}`, html, text }\n}\n\nexport function shippingConfirmationMail(order: Order, fulfillment: Fulfillment, ctx: MailContext): RenderedMail {\n const { config, siteUrl } = ctx\n const number = `${config.orders.numberPrefix}${order.number}`\n const shipped = fulfillment.lines\n .map((fl) => {\n const line = order.lines.find((l) => l.sku === fl.sku)\n return { name: line?.name ?? fl.sku, sku: fl.sku, quantity: fl.quantity }\n })\n const tracking = fulfillment.trackingNumber\n ? fulfillment.trackingUrl\n ? `<a href=\"${escapeHtml(fulfillment.trackingUrl)}\" style=\"color:#0f6fff\">${escapeHtml(fulfillment.trackingNumber)}</a>`\n : `<strong>${escapeHtml(fulfillment.trackingNumber)}</strong>`\n : ''\n const carrier = [fulfillment.carrier, fulfillment.service].filter(Boolean).join(' ')\n const portalUrl = `${siteUrl}/account/`\n const html = layout(\n config,\n `Order ${number} is on its way`,\n `<p style=\"margin:0 0 16px;font-size:14px\">Good news — ${shipped.length === order.lines.length && shipped.every((s) => (order.lines.find((l) => l.sku === s.sku)?.quantity ?? 0) === s.quantity) ? 'your order' : 'part of your order'} has shipped${carrier ? ` with ${escapeHtml(carrier)}` : ''}.</p>\n${tracking ? `<p style=\"margin:0 0 16px;font-size:14px\">Tracking number: ${tracking}</p>` : ''}\n<ul style=\"padding-left:18px;font-size:14px;margin:0 0 16px\">${shipped.map((s) => `<li>${s.quantity} × ${escapeHtml(s.name)} <span style=\"color:#666\">(${escapeHtml(s.sku)})</span></li>`).join('')}</ul>\n<p style=\"margin:0;font-size:14px\"><a href=\"${escapeHtml(portalUrl)}\" style=\"color:#0f6fff\">Track it in your account</a></p>`,\n contactFooter(config)\n )\n const text = [\n `${config.name} — order ${number} has shipped${carrier ? ` with ${carrier}` : ''}`,\n '',\n ...(fulfillment.trackingNumber ? [`Tracking: ${fulfillment.trackingNumber}${fulfillment.trackingUrl ? ` (${fulfillment.trackingUrl})` : ''}`, ''] : []),\n ...shipped.map((s) => `${s.quantity} × ${s.name} (${s.sku})`),\n '',\n `Track it in your account: ${portalUrl}`,\n ].join('\\n')\n return { subject: `Order ${number} has shipped — ${config.name}`, html, text }\n}\n\nexport function partnerSignInMail(input: { url: string; name: string; expiresMinutes: number }, ctx: MailContext): RenderedMail {\n const { config } = ctx\n const html = layout(\n config,\n 'Your partner dashboard',\n `<p style=\"margin:0 0 16px;font-size:14px\">Hi ${escapeHtml(input.name)} — click the button to open your ${escapeHtml(config.name)} partner dashboard: visits through your links, orders with your code, and your commission. The link works once and expires in ${input.expiresMinutes} minutes.</p>\n<p style=\"margin:0 0 16px\"><a href=\"${escapeHtml(input.url)}\" style=\"display:inline-block;background:#0f6fff;color:#fff;text-decoration:none;padding:12px 22px;border-radius:6px;font-weight:700;font-size:14px\">Open my dashboard</a></p>\n<p style=\"margin:0;font-size:12px;color:#666\">If you did not request this, you can ignore this email.</p>`,\n escapeHtml(config.name)\n )\n const text = [`Your ${config.name} partner dashboard`, '', `Open this link (it works once and expires in ${input.expiresMinutes} minutes):`, input.url, '', 'If you did not request this, ignore this email.'].join('\\n')\n return { subject: `Your ${config.name} partner dashboard`, html, text }\n}\n\nexport function magicLinkMail(input: { url: string; expiresMinutes: number }, ctx: MailContext): RenderedMail {\n const { config } = ctx\n const html = layout(\n config,\n 'Your sign-in link',\n `<p style=\"margin:0 0 16px;font-size:14px\">Click the button to sign in to your ${escapeHtml(config.name)} account. The link works once and expires in ${input.expiresMinutes} minutes.</p>\n<p style=\"margin:0 0 16px\"><a href=\"${escapeHtml(input.url)}\" style=\"display:inline-block;background:#0f6fff;color:#fff;text-decoration:none;padding:12px 22px;border-radius:6px;font-weight:700;font-size:14px\">Sign in</a></p>\n<p style=\"margin:0;font-size:12px;color:#666\">If you did not request this, you can ignore this email.</p>`,\n escapeHtml(config.name)\n )\n const text = [\n `Sign in to ${config.name}`,\n '',\n `Open this link to sign in (it works once and expires in ${input.expiresMinutes} minutes):`,\n input.url,\n '',\n 'If you did not request this, ignore this email.',\n ].join('\\n')\n return { subject: `Sign in to ${config.name}`, html, text }\n}\n\nexport function ownerPasswordResetMail(input: { url: string; expiresMinutes: number }, ctx: MailContext): RenderedMail {\n const { config } = ctx\n const html = layout(\n config,\n 'Reset your password',\n `<p style=\"margin:0 0 16px;font-size:14px\">Someone asked to reset the owner password for ${escapeHtml(config.name)}. If that was you, click the button. The link works once and expires in ${input.expiresMinutes} minutes.</p>\n<p style=\"margin:0 0 16px\"><a href=\"${escapeHtml(input.url)}\" style=\"display:inline-block;background:#0f6fff;color:#fff;text-decoration:none;padding:12px 22px;border-radius:6px;font-weight:700;font-size:14px\">Choose a new password</a></p>\n<p style=\"margin:0;font-size:12px;color:#666\">If you did not ask for this, ignore this email — your password stays as it is.</p>`,\n escapeHtml(config.name)\n )\n const text = [\n `Reset the owner password for ${config.name}`,\n '',\n `Open this link to choose a new password (it works once and expires in ${input.expiresMinutes} minutes):`,\n input.url,\n '',\n 'If you did not ask for this, ignore this email.',\n ].join('\\n')\n return { subject: `Reset your ${config.name} owner password`, html, text }\n}\n\nexport function merchantNewOrderMail(order: Order, ctx: MailContext): RenderedMail {\n const { config } = ctx\n const number = `${config.orders.numberPrefix}${order.number}`\n const ship = addressLines(order.shippingAddress)\n const text = [\n `New order ${number} — ${formatMoney(order.total, config.locale)}`,\n '',\n ...linesText(order, config.locale),\n '',\n `Customer: ${order.customer.name ?? '(not given)'}`,\n `Email: ${order.customer.email}`,\n `Phone: ${order.customer.phone ?? '(not given)'}`,\n '',\n ...(ship.length ? ['Ship to:', ...ship, ''] : []),\n ...(order.shippingMethod ? [`Shipping: ${order.shippingMethod.name}`] : []),\n `Payment: ${order.payment.status}${order.payment.provider ? ` via ${order.payment.provider}` : ''}${order.payment.reference ? ` (${order.payment.reference})` : ''}`,\n ...(order.notes ? ['', `Notes: ${order.notes}`] : []),\n '',\n `Order id: ${order.id}`,\n ].join('\\n')\n const html = layout(\n config,\n `New order ${number}`,\n `<pre style=\"font-family:ui-monospace,Menlo,monospace;font-size:13px;white-space:pre-wrap;margin:0\">${escapeHtml(text)}</pre>`,\n escapeHtml(config.name)\n )\n return { subject: `New order ${number} — ${formatMoney(order.total, config.locale)}`, html, text }\n}\n\n/** Where a buyer opens a quote without signing in: the access key is the credential. */\nexport function quoteUrl(quote: StoreQuote, siteUrl: string): string {\n return `${siteUrl}/quotes/${encodeURIComponent(quote.id)}/?key=${encodeURIComponent(quote.accessKey)}`\n}\n\nfunction quoteNumber(quote: StoreQuote, config: StorefrontConfig): string {\n return `${config.quotes.numberPrefix}${quote.number}`\n}\n\nfunction quoteLinesTable(quote: StoreQuote, locale: string, priced: boolean): string {\n const rows = quote.lines\n .map(\n (l) =>\n `<tr><td style=\"padding:8px 0;border-bottom:1px solid #eee\">${escapeHtml(l.name)}<br><span style=\"color:#666;font-size:12px\">SKU ${escapeHtml(l.sku)} × ${l.quantity}${priced && l.unitAmount ? ` at ${formatMoney(l.unitAmount, locale)}` : ''}</span></td><td align=\"right\" style=\"padding:8px 0;border-bottom:1px solid #eee;white-space:nowrap\">${priced && l.totalAmount ? formatMoney(l.totalAmount, locale) : ''}</td></tr>`\n )\n .join('')\n const totals =\n priced && quote.total\n ? [\n ['Subtotal', quote.subtotal ? formatMoney(quote.subtotal, locale) : ''],\n ['Shipping', quote.shipping?.amount ? formatMoney(quote.shipping, locale) : 'Included'],\n ...(quote.tax?.amount ? [['Tax', formatMoney(quote.tax, locale)]] : []),\n ['Total', formatMoney(quote.total, locale)],\n ]\n .map(([k, v], i, arr) => `<tr><td style=\"padding:6px 0;${i === arr.length - 1 ? 'font-weight:700' : 'color:#666'}\">${k}</td><td align=\"right\" style=\"padding:6px 0;${i === arr.length - 1 ? 'font-weight:700' : ''}\">${v}</td></tr>`)\n .join('')\n : ''\n return `<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"font-size:14px\">${rows}${totals}</table>`\n}\n\nfunction quoteLinesText(quote: StoreQuote, locale: string, priced: boolean): string[] {\n return [\n ...quote.lines.map((l) => `${l.quantity} × ${l.name} (${l.sku})${priced && l.totalAmount ? ` — ${formatMoney(l.totalAmount, locale)}` : ''}`),\n ...(priced && quote.total\n ? [\n '',\n ...(quote.subtotal ? [`Subtotal: ${formatMoney(quote.subtotal, locale)}`] : []),\n `Shipping: ${quote.shipping?.amount ? formatMoney(quote.shipping, locale) : 'Included'}`,\n ...(quote.tax?.amount ? [`Tax: ${formatMoney(quote.tax, locale)}`] : []),\n `Total: ${formatMoney(quote.total, locale)}`,\n ]\n : []),\n ]\n}\n\nconst button = (url: string, label: string) =>\n `<p style=\"margin:24px 0 0\"><a href=\"${escapeHtml(url)}\" style=\"display:inline-block;background:#0f6fff;color:#fff;text-decoration:none;padding:12px 22px;border-radius:6px;font-weight:700;font-size:14px\">${escapeHtml(label)}</a></p>`\n\n/** To the buyer: we have your request. Carries the link that is their way back in — no account needed. */\nexport function quoteReceivedMail(quote: StoreQuote, ctx: MailContext): RenderedMail {\n const { config, siteUrl } = ctx\n const number = quoteNumber(quote, config)\n const url = quoteUrl(quote, siteUrl)\n const intro = `Thanks — we have your request for a quote and will email you when it is priced. Keep this email: the link below shows the quote's status at any time.`\n const html = layout(config, `Quote request ${number} received`, `<p style=\"margin:0 0 16px;font-size:14px\">${intro}</p>${quoteLinesTable(quote, config.locale, false)}${button(url, 'View your quote request')}`, contactFooter(config))\n const text = [`${config.name} — quote request ${number} received`, '', intro, '', ...quoteLinesText(quote, config.locale, false), '', `View your quote request: ${url}`].join('\\n')\n return { subject: `Quote request ${number} received — ${config.name}`, html, text }\n}\n\n/** To the buyer: the quote is priced and waiting for their answer. */\nexport function quoteReadyMail(quote: StoreQuote, ctx: MailContext): RenderedMail {\n const { config, siteUrl } = ctx\n const number = quoteNumber(quote, config)\n const url = quoteUrl(quote, siteUrl)\n const expiry = quote.expiresAt ? ` It is valid until ${new Date(quote.expiresAt).toLocaleDateString(config.locale, { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' })}.` : ''\n const intro = `Your quote is ready.${expiry} Accept it to place the order at these prices, or decline it — either takes one click.`\n const html = layout(\n config,\n `Quote ${number} is ready`,\n `<p style=\"margin:0 0 16px;font-size:14px\">${escapeHtml(intro)}</p>${quoteLinesTable(quote, config.locale, true)}${quote.offerNote ? `<p style=\"margin:16px 0 0;font-size:14px;color:#444;white-space:pre-wrap\">${escapeHtml(quote.offerNote)}</p>` : ''}${button(url, 'Review and accept')}`,\n contactFooter(config)\n )\n const text = [`${config.name} — quote ${number} is ready`, '', intro, '', ...quoteLinesText(quote, config.locale, true), ...(quote.offerNote ? ['', quote.offerNote] : []), '', `Review and accept: ${url}`].join('\\n')\n return { subject: `Quote ${number} is ready — ${config.name}`, html, text }\n}\n\n/** To the merchant: a new request, or the buyer's answer to an offer. */\nexport function merchantQuoteMail(quote: StoreQuote, ctx: MailContext, event: 'requested' | 'accepted' | 'declined', orderNumber?: string | null): RenderedMail {\n const { config } = ctx\n const number = quoteNumber(quote, config)\n const headline =\n event === 'requested'\n ? `New quote request ${number}`\n : event === 'accepted'\n ? `Quote ${number} accepted${orderNumber ? ` — order ${config.orders.numberPrefix}${orderNumber}` : ''}`\n : `Quote ${number} declined`\n const ship = addressLines(quote.shippingAddress)\n const text = [\n headline,\n '',\n ...quoteLinesText(quote, config.locale, event !== 'requested'),\n '',\n `Customer: ${quote.customer.name ?? '(not given)'}`,\n ...(quote.company ? [`Company: ${quote.company}`] : []),\n `Email: ${quote.customer.email}`,\n `Phone: ${quote.customer.phone ?? '(not given)'}`,\n ...(ship.length ? ['', 'Ship to:', ...ship] : []),\n ...(quote.message ? ['', `Message: ${quote.message}`] : []),\n ...(event === 'declined' && quote.declineReason ? ['', `Reason: ${quote.declineReason}`] : []),\n '',\n `Quote id: ${quote.id}`,\n ].join('\\n')\n const html = layout(config, headline, `<pre style=\"font-family:ui-monospace,Menlo,monospace;font-size:13px;white-space:pre-wrap;margin:0\">${escapeHtml(text)}</pre>`, escapeHtml(config.name))\n return { subject: `${headline} — ${config.name}`, html, text }\n}\n"],"mappings":"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","names":[]}
@@ -429,4 +429,4 @@ export {
429
429
  addressFormats,
430
430
  checkAddressFormat
431
431
  };
432
- //# sourceMappingURL=chunk-56ODALDY.js.map
432
+ //# sourceMappingURL=chunk-NM5YP764.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/catalog.ts","../src/shipping.ts","../src/discounts.ts","../src/slug.ts","../src/cart.ts","../src/address.ts"],"sourcesContent":["import type { Category, CategoryNode, Product } from './types'\n\n/**\n * An in-memory snapshot of the catalog with the tree and the lookups the\n * pages need. Built from plain data — the store service builds one from the\n * database per request (cached briefly per isolate); tests build one from\n * fixtures.\n */\nexport class Catalog {\n readonly products: Product[]\n readonly categories: Category[]\n private readonly bySku = new Map<string, Product>()\n private readonly bySlug = new Map<string, Product>()\n private readonly categoryById = new Map<string, Category>()\n private readonly nodeById = new Map<string, CategoryNode>()\n private readonly roots: CategoryNode[]\n\n private constructor(products: Product[], categories: Category[]) {\n this.products = products\n this.categories = categories\n for (const p of products) {\n if (this.bySku.has(p.sku)) throw new Error(`Duplicate product sku: ${p.sku}`)\n this.bySku.set(p.sku, p)\n this.bySlug.set(p.slug, p)\n }\n for (const c of categories) this.categoryById.set(c.id, c)\n this.roots = this.buildTree()\n }\n\n static fromData(products: Product[], categories: Category[]): Catalog {\n return new Catalog(products, categories)\n }\n\n product(sku: string): Product | undefined {\n return this.bySku.get(sku)\n }\n\n productBySlug(slug: string): Product | undefined {\n return this.bySlug.get(slug)\n }\n\n /** Products directly in a category, or in it and every descendant with `deep`. */\n productsIn(categoryId: string, opts: { deep?: boolean } = {}): Product[] {\n const ids = new Set<string>([categoryId])\n if (opts.deep) {\n const node = this.nodeById.get(categoryId)\n if (node) this.collectIds(node, ids)\n }\n return this.products.filter((p) => p.categoryIds.some((id) => ids.has(id)))\n }\n\n tree(): CategoryNode[] {\n return this.roots\n }\n\n category(id: string): CategoryNode | undefined {\n return this.nodeById.get(id)\n }\n\n /** Find a node by its slug path from the root, e.g. ['toyota', 'tacoma']. */\n categoryByPath(path: string[]): CategoryNode | undefined {\n let level = this.roots\n let found: CategoryNode | undefined\n for (const slug of path) {\n found = level.find((n) => n.slug === slug)\n if (!found) return undefined\n level = found.children\n }\n return found\n }\n\n allCategories(): CategoryNode[] {\n return [...this.nodeById.values()]\n }\n\n /** The chain of categories from the root to the product's deepest category. */\n breadcrumbs(product: Product): CategoryNode[] {\n let best: CategoryNode | undefined\n for (const id of product.categoryIds) {\n const node = this.nodeById.get(id)\n if (node && (!best || node.depth > best.depth)) best = node\n }\n if (!best) return []\n const chain: CategoryNode[] = []\n let cur: CategoryNode | undefined = best\n while (cur) {\n chain.unshift(cur)\n cur = cur.parentId ? this.nodeById.get(cur.parentId) : undefined\n }\n return chain\n }\n\n /** Every node with at least one product in it or below it. */\n nonEmptyCategories(): CategoryNode[] {\n return this.allCategories().filter((n) => n.productCount > 0)\n }\n\n private collectIds(node: CategoryNode, into: Set<string>): void {\n for (const child of node.children) {\n into.add(child.id)\n this.collectIds(child, into)\n }\n }\n\n private buildTree(): CategoryNode[] {\n const nodes = new Map<string, CategoryNode>()\n for (const c of this.categories) {\n nodes.set(c.id, { ...c, children: [], path: [], depth: 0, productCount: 0 })\n }\n const roots: CategoryNode[] = []\n for (const node of nodes.values()) {\n const parent = node.parentId ? nodes.get(node.parentId) : undefined\n if (parent) parent.children.push(node)\n else roots.push(node)\n }\n const byName = (a: CategoryNode, b: CategoryNode) =>\n (a.position ?? 0) - (b.position ?? 0) || a.name.localeCompare(b.name)\n const walk = (node: CategoryNode, path: string[], depth: number): Set<string> => {\n node.path = [...path, node.slug]\n node.depth = depth\n node.children.sort(byName)\n const skus = new Set<string>()\n for (const p of this.products) if (p.categoryIds.includes(node.id)) skus.add(p.sku)\n for (const child of node.children) for (const sku of walk(child, node.path, depth + 1)) skus.add(sku)\n node.productCount = skus.size\n this.nodeById.set(node.id, node)\n return skus\n }\n roots.sort(byName)\n for (const root of roots) walk(root, [], 0)\n return roots\n }\n}\n","import type { Money } from './types.js'\n\nexport interface ShippingOption {\n id: string\n label: string\n price: Money\n /** Business days, inclusive range. */\n estimate?: { min: number; max: number }\n /**\n * Destinations this rate applies to (ISO 3166-1 alpha-2). Omit for \"every\n * country not claimed by another option\". Naming countries on at least one\n * option turns on region enforcement: a checkout is opened for one region at\n * a time, with only that region's rates and addresses. That is how a store\n * stops an international customer choosing the free domestic rate — Stripe\n * Checkout cannot filter rates by country on its own.\n */\n countries?: string[]\n}\n\nexport interface ShippingOptionExtras {\n estimate?: ShippingOption['estimate']\n countries?: string[]\n}\n\nexport function freeShipping(id: string, label: string, extras: ShippingOptionExtras = {}): ShippingOption {\n return { id, label, price: { amount: 0, currency: 'usd' }, ...extras }\n}\n\nexport function flatRate(id: string, label: string, price: Money, extras: ShippingOptionExtras = {}): ShippingOption {\n return { id, label, price, ...extras }\n}\n\nexport type ShippingRegion = 'domestic' | 'international'\n\nexport interface ResolvedShipping {\n options: ShippingOption[]\n countries: string[]\n /** False when no option names countries: one region, every rate, every country. */\n enforced: boolean\n}\n\n/**\n * The rates and destinations a checkout may offer for a region.\n *\n * domestic = options that name countries, shipped to those countries\n * international = options that name none, shipped to every remaining ship-to country\n */\nexport function shippingForRegion(\n shipping: ShippingOption[],\n shipToCountries: string[],\n region: ShippingRegion\n): ResolvedShipping {\n const scoped = shipping.filter((o) => o.countries && o.countries.length > 0)\n if (scoped.length === 0) return { options: shipping, countries: shipToCountries, enforced: false }\n\n const domestic = [...new Set(scoped.flatMap((o) => o.countries!))].filter((c) => shipToCountries.includes(c))\n if (region === 'domestic') return { options: scoped, countries: domestic, enforced: true }\n\n const rest = shipping.filter((o) => !o.countries || o.countries.length === 0)\n const international = shipToCountries.filter((c) => !domestic.includes(c))\n return { options: rest, countries: international, enforced: true }\n}\n\n/** Whether a store has anything to offer outside its domestic region. */\nexport function hasInternationalRegion(shipping: ShippingOption[], shipToCountries: string[]): boolean {\n const r = shippingForRegion(shipping, shipToCountries, 'international')\n return r.enforced && r.options.length > 0 && r.countries.length > 0\n}\n\n/** Which region a destination country falls in; null when the store does not ship there. */\nexport function regionForCountry(\n shipping: ShippingOption[],\n shipToCountries: string[],\n country: string\n): ShippingRegion | null {\n const code = country.toUpperCase()\n if (!shipToCountries.includes(code)) return null\n const domestic = shippingForRegion(shipping, shipToCountries, 'domestic')\n if (!domestic.enforced) return 'domestic'\n return domestic.countries.includes(code) ? 'domestic' : 'international'\n}\n\n/** The options a destination country may choose from. Empty when the store does not ship there. */\nexport function shippingForCountry(shipping: ShippingOption[], shipToCountries: string[], country: string): ShippingOption[] {\n const region = regionForCountry(shipping, shipToCountries, country)\n return region ? shippingForRegion(shipping, shipToCountries, region).options : []\n}\n","import { formatMoney } from './money'\n\n/**\n * Discount codes the store owns.\n *\n * A code lives in the store's configuration, is priced into the order by\n * `/api/checkout` like every other amount, and is recorded on the order —\n * so it works the same under any payment adapter, and a partner's sales are\n * read from the store's own orders rather than from a processor dashboard.\n *\n * A code may name a `partner` (an affiliate, a podcast, a reseller). The\n * order then carries that partner and the commission owed at the rate in\n * force when it was placed, so changing a rate later never rewrites history.\n */\nexport interface DiscountCode {\n /** What the buyer types. Matched case-insensitively, ignoring spaces. */\n code: string\n /** Percent off the merchandise subtotal, 1–100. Exactly one of percentOff / amountOff. */\n percentOff?: number\n /** Fixed amount off the merchandise subtotal, in minor units of the store currency. */\n amountOff?: number\n /** Shown to the buyer next to the code. Defaults to \"15% off\" / \"$10 off\". */\n label?: string\n /** Who the code attributes sales to. Orders placed with it are reported under this key. */\n partner?: string\n /** Commission owed to the partner, percent of the discounted merchandise subtotal (shipping and tax excluded). */\n commissionPercent?: number\n /** ISO timestamps; the code is valid from startsAt (inclusive) to endsAt (exclusive). */\n startsAt?: string\n endsAt?: string\n /** False switches the code off without deleting its history. Default true. */\n active?: boolean\n}\n\n/**\n * Quantity pricing: the more units in the order, the bigger the percentage off\n * the merchandise subtotal. Tiers count units across every line.\n *\n * It does not stack with a discount code: the buyer gets whichever saves\n * more, and a partner's code still attributes the order (and earns its\n * commission on what was actually charged) even when the tier wins.\n */\nexport interface QuantityDiscount {\n minQuantity: number\n percentOff: number\n /** Shown to the buyer, e.g. \"Two-miner pack\". Defaults to \"Buy N+, save X%\". */\n label?: string\n}\n\nexport function validateQuantityDiscounts(tiers: QuantityDiscount[]): void {\n const seen = new Set<number>()\n for (const t of tiers) {\n if (!(Number.isInteger(t.minQuantity) && t.minQuantity >= 2)) throw new Error(`quantity discount minQuantity must be an integer >= 2, got ${t.minQuantity}`)\n if (!(t.percentOff > 0 && t.percentOff < 100)) throw new Error(`quantity discount for ${t.minQuantity}+ needs percentOff in (0, 100)`)\n if (seen.has(t.minQuantity)) throw new Error(`quantity discount for ${t.minQuantity}+ is defined twice`)\n seen.add(t.minQuantity)\n }\n}\n\n/** The best tier the unit count reaches, or null. */\nexport function quantityTier(tiers: QuantityDiscount[] | undefined, units: number): QuantityDiscount | null {\n return (tiers ?? []).filter((t) => units >= t.minQuantity).sort((a, b) => b.percentOff - a.percentOff)[0] ?? null\n}\n\nexport function quantityLabel(t: QuantityDiscount): string {\n return t.label ?? `Buy ${t.minQuantity}+, save ${t.percentOff}%`\n}\n\n/**\n * Who a partner is, for the people they send and for their own portal.\n * `id` is what discount codes name in `partner`.\n */\nexport interface PartnerProfile {\n id: string\n /** The public name visitors know — a show, channel or brand: \"Welcome, friend of <name>\". */\n name: string\n /** The person behind it: a store may greet referred visitors as their friend, and the dashboard and sign-in email greet them by first name. Defaults to `name`. */\n contact?: string\n /** Where their portal sign-in link is sent. Without it they can only use a key link. */\n email?: string\n /** A line in their voice for the welcome banner, e.g. \"Stack sats, solo mine, have fun.\" */\n welcome?: string\n /** Absolute URL or site path of a square photo or logo for the welcome banner. */\n avatar?: string\n}\n\n/** What a buyer is shown about a code. Never includes the partner or the commission. */\nexport interface PublicDiscount {\n code: string\n label: string\n percentOff: number | null\n amountOff: number | null\n}\n\n/** How a code is carried from a partner's link to the checkout page. */\nexport interface ReferralSettings {\n /** Query parameter on any page that applies a code: /?code=PODCAST. */\n param: string\n /** How long the code stays applied in the buyer's browser. */\n cookieDays: number\n}\n\nexport const DISCOUNT_COOKIE = 'sf_discount'\n\n/** Canonical form of a code: upper case, no whitespace. */\nexport function normalizeDiscountCode(input: string): string {\n return input.replace(/\\s+/g, '').toUpperCase()\n}\n\n/** Throw on a configuration mistake; called from defineStorefront. */\nexport function validateDiscounts(codes: DiscountCode[], currency: string): void {\n const seen = new Set<string>()\n for (const d of codes) {\n const code = normalizeDiscountCode(d.code ?? '')\n if (!/^[A-Z0-9_-]{2,40}$/.test(code)) throw new Error(`discount code \"${d.code}\" must be 2–40 letters, digits, - or _`)\n if (seen.has(code)) throw new Error(`discount code ${code} is defined twice`)\n seen.add(code)\n const hasPercent = d.percentOff !== undefined\n const hasAmount = d.amountOff !== undefined\n if (hasPercent === hasAmount) throw new Error(`discount code ${code} needs exactly one of percentOff or amountOff`)\n if (hasPercent && !(d.percentOff! > 0 && d.percentOff! <= 100)) throw new Error(`discount code ${code}: percentOff must be in (0, 100]`)\n if (hasAmount && !(Number.isInteger(d.amountOff) && d.amountOff! > 0)) throw new Error(`discount code ${code}: amountOff must be a positive integer of minor units (${currency})`)\n if (d.commissionPercent !== undefined && !(d.commissionPercent >= 0 && d.commissionPercent <= 100)) throw new Error(`discount code ${code}: commissionPercent must be in [0, 100]`)\n if (d.commissionPercent && !d.partner) throw new Error(`discount code ${code}: commissionPercent needs a partner to owe it to`)\n for (const k of ['startsAt', 'endsAt'] as const) if (d[k] && Number.isNaN(Date.parse(d[k]!))) throw new Error(`discount code ${code}: ${k} is not a date`)\n }\n}\n\n/** The code a buyer typed, if it is valid right now. */\nexport function findDiscount(codes: DiscountCode[] | undefined, input: string | null | undefined, now: Date = new Date()): DiscountCode | null {\n if (!codes?.length || !input) return null\n const wanted = normalizeDiscountCode(input)\n const d = codes.find((c) => normalizeDiscountCode(c.code) === wanted)\n if (!d || d.active === false) return null\n if (d.startsAt && now.getTime() < Date.parse(d.startsAt)) return null\n if (d.endsAt && now.getTime() >= Date.parse(d.endsAt)) return null\n return d\n}\n\n/** Minor units off a merchandise subtotal. Percentages round half up; never more than the subtotal. */\nexport function discountAmount(d: DiscountCode, subtotal: number): number {\n const raw = d.percentOff !== undefined ? Math.round((subtotal * d.percentOff) / 100) : d.amountOff ?? 0\n return Math.max(0, Math.min(raw, subtotal))\n}\n\n/** Commission owed on an order, in minor units, at the code's rate. */\nexport function commissionAmount(d: DiscountCode, subtotal: number, discount: number): number {\n if (!d.partner || !d.commissionPercent) return 0\n return Math.round(((subtotal - discount) * d.commissionPercent) / 100)\n}\n\nexport function publicDiscount(d: DiscountCode, currency: string, locale = 'en-US'): PublicDiscount {\n const label =\n d.label ??\n (d.percentOff !== undefined\n ? `${d.percentOff}% off`\n : `${formatMoney({ amount: d.amountOff ?? 0, currency }, locale)} off`)\n return { code: normalizeDiscountCode(d.code), label, percentOff: d.percentOff ?? null, amountOff: d.amountOff ?? null }\n}\n","/** URL-safe slug: lower case ASCII letters, digits and single hyphens. */\nexport function slugify(input: string): string {\n return input\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 200) || 'item'\n}\n","import type { Money } from './types.js'\nimport { addMoney, multiplyMoney } from './money.js'\n\n/**\n * A cart is client-side state — lines the customer has chosen, with a snapshot\n * of what they saw (name, price) so the cart page can render without the\n * catalog. It is never trusted for money: checkout re-prices every SKU on the\n * server. These are pure functions; the browser store wraps them.\n */\nexport interface CartLine {\n sku: string\n quantity: number\n name: string\n unitPrice: Money\n url?: string\n image?: string\n}\n\nexport interface Cart {\n lines: CartLine[]\n updatedAt: string\n}\n\nexport const MAX_CART_LINES = 50\n\nexport const emptyCart = (): Cart => ({ lines: [], updatedAt: new Date().toISOString() })\n\nfunction touch(lines: CartLine[]): Cart {\n return { lines, updatedAt: new Date().toISOString() }\n}\n\nexport function addLine(cart: Cart, line: Omit<CartLine, 'quantity'>, quantity: number, maxQuantity: number): Cart {\n const qty = clampQty(quantity, maxQuantity)\n const existing = cart.lines.find((l) => l.sku === line.sku)\n if (existing) {\n return touch(\n cart.lines.map((l) => (l.sku === line.sku ? { ...l, ...line, quantity: clampQty(l.quantity + qty, maxQuantity) } : l))\n )\n }\n if (cart.lines.length >= MAX_CART_LINES) throw new Error(`Cart is limited to ${MAX_CART_LINES} lines`)\n return touch([...cart.lines, { ...line, quantity: qty }])\n}\n\nexport function setQuantity(cart: Cart, sku: string, quantity: number, maxQuantity: number): Cart {\n if (quantity <= 0) return removeLine(cart, sku)\n return touch(cart.lines.map((l) => (l.sku === sku ? { ...l, quantity: clampQty(quantity, maxQuantity) } : l)))\n}\n\nexport function removeLine(cart: Cart, sku: string): Cart {\n return touch(cart.lines.filter((l) => l.sku !== sku))\n}\n\nexport function cartCount(cart: Cart): number {\n return cart.lines.reduce((n, l) => n + l.quantity, 0)\n}\n\n/** Snapshot subtotal for display only. The server computes the real one. */\nexport function cartSubtotal(cart: Cart, currency: string): Money {\n return cart.lines.reduce((sum, l) => addMoney(sum, multiplyMoney(l.unitPrice, l.quantity)), { amount: 0, currency })\n}\n\n/** What the checkout endpoint needs — nothing but SKUs and quantities. */\nexport function cartToCheckoutItems(cart: Cart): { sku: string; quantity: number }[] {\n return cart.lines.map((l) => ({ sku: l.sku, quantity: l.quantity }))\n}\n\n/**\n * Parse stored JSON defensively: anything malformed yields an empty cart\n * rather than a broken page. Money amounts must be integers.\n */\nexport function parseCart(raw: string | null | undefined): Cart {\n if (!raw) return emptyCart()\n try {\n const data = JSON.parse(raw) as Partial<Cart>\n if (!Array.isArray(data.lines)) return emptyCart()\n const lines: CartLine[] = []\n for (const l of data.lines as Partial<CartLine>[]) {\n if (\n typeof l?.sku !== 'string' || !l.sku ||\n typeof l.name !== 'string' ||\n !Number.isInteger(l.quantity) || (l.quantity as number) < 1 ||\n !l.unitPrice || !Number.isInteger(l.unitPrice.amount) || typeof l.unitPrice.currency !== 'string'\n ) continue\n lines.push({\n sku: l.sku, name: l.name, quantity: l.quantity as number,\n unitPrice: { amount: l.unitPrice.amount, currency: l.unitPrice.currency },\n ...(typeof l.url === 'string' ? { url: l.url } : {}),\n ...(typeof l.image === 'string' ? { image: l.image } : {}),\n })\n }\n return { lines: lines.slice(0, MAX_CART_LINES), updatedAt: typeof data.updatedAt === 'string' ? data.updatedAt : new Date().toISOString() }\n } catch {\n return emptyCart()\n }\n}\n\nexport function serializeCart(cart: Cart): string {\n return JSON.stringify(cart)\n}\n\nfunction clampQty(n: number, max: number): number {\n return Number.isFinite(n) ? Math.min(Math.max(Math.trunc(n), 1), max) : 1\n}\n","/**\n * How an address is asked for in a given country: what the region and postal\n * fields are called, whether they are required, and — where carriers expect a\n * code — the list of regions to choose from. Countries not listed get a\n * neutral, optional \"State / province / region\" and \"Postal code\".\n */\nexport interface AddressFormat {\n regionLabel: string\n /** Choose-from list as [code, name]; the code is what is stored and sent to carriers. */\n regions?: [string, string][]\n regionRequired: boolean\n postalLabel: string\n postalRequired: boolean\n /** Checked on the server and as the input's pattern. */\n postalPattern?: string\n postalExample?: string\n}\n\nconst US_STATES: [string, string][] = [\n ['AL', 'Alabama'], ['AK', 'Alaska'], ['AZ', 'Arizona'], ['AR', 'Arkansas'], ['CA', 'California'], ['CO', 'Colorado'],\n ['CT', 'Connecticut'], ['DE', 'Delaware'], ['DC', 'District of Columbia'], ['FL', 'Florida'], ['GA', 'Georgia'], ['HI', 'Hawaii'],\n ['ID', 'Idaho'], ['IL', 'Illinois'], ['IN', 'Indiana'], ['IA', 'Iowa'], ['KS', 'Kansas'], ['KY', 'Kentucky'], ['LA', 'Louisiana'],\n ['ME', 'Maine'], ['MD', 'Maryland'], ['MA', 'Massachusetts'], ['MI', 'Michigan'], ['MN', 'Minnesota'], ['MS', 'Mississippi'],\n ['MO', 'Missouri'], ['MT', 'Montana'], ['NE', 'Nebraska'], ['NV', 'Nevada'], ['NH', 'New Hampshire'], ['NJ', 'New Jersey'],\n ['NM', 'New Mexico'], ['NY', 'New York'], ['NC', 'North Carolina'], ['ND', 'North Dakota'], ['OH', 'Ohio'], ['OK', 'Oklahoma'],\n ['OR', 'Oregon'], ['PA', 'Pennsylvania'], ['RI', 'Rhode Island'], ['SC', 'South Carolina'], ['SD', 'South Dakota'],\n ['TN', 'Tennessee'], ['TX', 'Texas'], ['UT', 'Utah'], ['VT', 'Vermont'], ['VA', 'Virginia'], ['WA', 'Washington'],\n ['WV', 'West Virginia'], ['WI', 'Wisconsin'], ['WY', 'Wyoming'],\n ['PR', 'Puerto Rico'], ['GU', 'Guam'], ['VI', 'U.S. Virgin Islands'], ['AS', 'American Samoa'], ['MP', 'Northern Mariana Islands'],\n ['AA', 'Armed Forces Americas'], ['AE', 'Armed Forces Europe'], ['AP', 'Armed Forces Pacific'],\n]\nconst CA_PROVINCES: [string, string][] = [\n ['AB', 'Alberta'], ['BC', 'British Columbia'], ['MB', 'Manitoba'], ['NB', 'New Brunswick'], ['NL', 'Newfoundland and Labrador'],\n ['NS', 'Nova Scotia'], ['NT', 'Northwest Territories'], ['NU', 'Nunavut'], ['ON', 'Ontario'], ['PE', 'Prince Edward Island'],\n ['QC', 'Quebec'], ['SK', 'Saskatchewan'], ['YT', 'Yukon'],\n]\nconst AU_STATES: [string, string][] = [\n ['ACT', 'Australian Capital Territory'], ['NSW', 'New South Wales'], ['NT', 'Northern Territory'], ['QLD', 'Queensland'],\n ['SA', 'South Australia'], ['TAS', 'Tasmania'], ['VIC', 'Victoria'], ['WA', 'Western Australia'],\n]\n\nconst FORMATS: Record<string, AddressFormat> = {\n US: { regionLabel: 'State', regions: US_STATES, regionRequired: true, postalLabel: 'ZIP code', postalRequired: true, postalPattern: '^\\\\d{5}(-\\\\d{4})?$', postalExample: '89101' },\n CA: { regionLabel: 'Province', regions: CA_PROVINCES, regionRequired: true, postalLabel: 'Postal code', postalRequired: true, postalPattern: '^[A-Za-z]\\\\d[A-Za-z] ?\\\\d[A-Za-z]\\\\d$', postalExample: 'K1A 0B1' },\n AU: { regionLabel: 'State / territory', regions: AU_STATES, regionRequired: true, postalLabel: 'Postcode', postalRequired: true, postalPattern: '^\\\\d{4}$', postalExample: '2000' },\n GB: { regionLabel: 'County (optional)', regionRequired: false, postalLabel: 'Postcode', postalRequired: true },\n IE: { regionLabel: 'County', regionRequired: false, postalLabel: 'Eircode (optional)', postalRequired: false },\n NZ: { regionLabel: 'Region (optional)', regionRequired: false, postalLabel: 'Postcode', postalRequired: true, postalPattern: '^\\\\d{4}$' },\n MX: { regionLabel: 'State', regionRequired: true, postalLabel: 'Postal code', postalRequired: true, postalPattern: '^\\\\d{5}$' },\n BR: { regionLabel: 'State', regionRequired: true, postalLabel: 'CEP', postalRequired: true, postalPattern: '^\\\\d{5}-?\\\\d{3}$' },\n JP: { regionLabel: 'Prefecture', regionRequired: true, postalLabel: 'Postal code', postalRequired: true, postalPattern: '^\\\\d{3}-?\\\\d{4}$' },\n DE: { regionLabel: 'State (optional)', regionRequired: false, postalLabel: 'Postcode', postalRequired: true, postalPattern: '^\\\\d{5}$' },\n}\nconst DEFAULT_FORMAT: AddressFormat = { regionLabel: 'State / province / region', regionRequired: false, postalLabel: 'Postal code', postalRequired: false }\n\nexport function addressFormat(country: string | null | undefined): AddressFormat {\n return FORMATS[(country ?? '').toUpperCase()] ?? DEFAULT_FORMAT\n}\n\n/** Formats for a set of countries, for the checkout page to switch between without a round trip. */\nexport function addressFormats(countries: string[]): Record<string, AddressFormat> {\n return Object.fromEntries(countries.map((c) => [c.toUpperCase(), addressFormat(c)]))\n}\n\n/**\n * Field errors for an address against its country's format; also normalises\n * a region given by name (\"Nevada\") to its code (\"NV\"). Mutates `address`.\n */\nexport function checkAddressFormat(address: { country?: string; state?: string; postalCode?: string }): Record<string, string> {\n const f = addressFormat(address.country)\n const details: Record<string, string> = {}\n const state = address.state?.trim() ?? ''\n if (f.regions && state) {\n const hit = f.regions.find(([code, name]) => code.toLowerCase() === state.toLowerCase() || name.toLowerCase() === state.toLowerCase())\n if (hit) address.state = hit[0]\n else details['shipping_address.state'] = `is not a ${f.regionLabel.toLowerCase()}`\n } else if (f.regionRequired && !state) details['shipping_address.state'] = 'is required'\n const postal = address.postalCode?.trim() ?? ''\n if (!postal && f.postalRequired) details['shipping_address.postal_code'] = 'is required'\n else if (postal && f.postalPattern && !new RegExp(f.postalPattern).test(postal)) details['shipping_address.postal_code'] = `doesn't look like a ${f.postalLabel}${f.postalExample ? ` (e.g. ${f.postalExample})` : ''}`\n return details\n}\n"],"mappings":"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","names":[]}
@@ -134,13 +134,14 @@ var CLIENT_ID = /^[a-z0-9][a-z0-9._-]{1,62}$/;
134
134
  function connectClientName(clientId) {
135
135
  return KNOWN_CONNECT_CLIENTS[clientId] ?? clientId;
136
136
  }
137
- var CONNECT_SCOPES = ["orders", "payments", "fulfillments", "inventory", "catalog"];
137
+ var CONNECT_SCOPES = ["orders", "payments", "fulfillments", "inventory", "catalog", "quotes"];
138
138
  var SCOPE_DESCRIPTIONS = {
139
139
  orders: "Read orders, stamp its own order number on them and cancel them",
140
140
  payments: "Mark orders paid, refunded or failed (for payments taken outside this store)",
141
141
  fulfillments: "Push shipments and tracking to orders",
142
142
  inventory: "Update available stock",
143
- catalog: "Read and update products and categories"
143
+ catalog: "Read and update products and categories",
144
+ quotes: "Read quote requests and send buyers priced quotes they can accept as orders"
144
145
  };
145
146
  var toConnection = (r) => ({
146
147
  id: r.id,
@@ -313,4 +314,4 @@ export {
313
314
  isLocalHost,
314
315
  ConnectionsService
315
316
  };
316
- //# sourceMappingURL=chunk-X7VXO4QU.js.map
317
+ //# sourceMappingURL=chunk-RUUUZ2EL.js.map