@asteby/storefront-react 0.24.0 → 0.26.0

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.
Files changed (48) hide show
  1. package/README.md +35 -2
  2. package/dist/account-server.cjs +128 -19
  3. package/dist/account-server.cjs.map +1 -1
  4. package/dist/account-server.d.cts +67 -4
  5. package/dist/account-server.d.ts +67 -4
  6. package/dist/account-server.js +125 -20
  7. package/dist/account-server.js.map +1 -1
  8. package/dist/account.cjs +261 -8
  9. package/dist/account.cjs.map +1 -1
  10. package/dist/account.css +99 -0
  11. package/dist/account.d.cts +132 -6
  12. package/dist/account.d.ts +132 -6
  13. package/dist/account.js +255 -9
  14. package/dist/account.js.map +1 -1
  15. package/dist/checkout.cjs +162 -8
  16. package/dist/checkout.cjs.map +1 -1
  17. package/dist/checkout.d.cts +104 -4
  18. package/dist/checkout.d.ts +104 -4
  19. package/dist/checkout.js +158 -10
  20. package/dist/checkout.js.map +1 -1
  21. package/dist/chunk-Z4C4LKXF.js +25 -0
  22. package/dist/chunk-Z4C4LKXF.js.map +1 -0
  23. package/dist/fit-key-B9sYQVwG.d.cts +49 -0
  24. package/dist/fit-key-B9sYQVwG.d.ts +49 -0
  25. package/dist/fitment.cjs +54 -9
  26. package/dist/fitment.cjs.map +1 -1
  27. package/dist/fitment.d.cts +20 -2
  28. package/dist/fitment.d.ts +20 -2
  29. package/dist/fitment.js +54 -10
  30. package/dist/fitment.js.map +1 -1
  31. package/dist/settings-types-jxFQFFqP.d.cts +77 -0
  32. package/dist/settings-types-jxFQFFqP.d.ts +77 -0
  33. package/dist/subjects-server.cjs +248 -0
  34. package/dist/subjects-server.cjs.map +1 -0
  35. package/dist/subjects-server.d.cts +130 -0
  36. package/dist/subjects-server.d.ts +130 -0
  37. package/dist/subjects-server.js +217 -0
  38. package/dist/subjects-server.js.map +1 -0
  39. package/dist/subjects.cjs +501 -0
  40. package/dist/subjects.cjs.map +1 -0
  41. package/dist/subjects.css +166 -0
  42. package/dist/subjects.d.cts +178 -0
  43. package/dist/subjects.d.ts +178 -0
  44. package/dist/subjects.js +465 -0
  45. package/dist/subjects.js.map +1 -0
  46. package/package.json +23 -2
  47. package/dist/types-D7pVK0GK.d.cts +0 -39
  48. package/dist/types-D7pVK0GK.d.ts +0 -39
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fitment/fitment.ts"],"names":["model"],"mappings":"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","file":"fitment.js","sourcesContent":["// Vehicle fitment (\"Mi vehículo\"): a make → model → year → engine catalog, vehicle keys, the\n// cascading selector options and the fit state of a product. Pure functions with no UI, no\n// storage and no copy: every tag prefix, attribute name and legacy spelling is injectable through\n// `FitmentConfig`, so any store that sells parts can use it. Nothing here decides on its own\n// whether a part fits a vehicle: it only compares what the backend (OPS) published.\n\nexport type VehicleEngine = { id: string; label: string; from?: number; to?: number }\nexport type VehicleModel = { id: string; label: string; from: number; to: number; engines: VehicleEngine[] }\nexport type MakePriority = 'high' | 'medium' | 'low'\n/**\n * A make of the catalog. `models` may be empty while the catalog is being imported: the make is\n * declared but it does not show in the selector until it has models.\n */\nexport type VehicleMake = {\n id: string\n label: string\n models: VehicleModel[]\n priority?: MakePriority\n /** Free-form segment of the make (e.g. light / heavy duty). Passed through as published. */\n kind?: string\n refs?: number\n observed_from?: number\n observed_to?: number\n typical_from?: number\n typical_to?: number\n}\nexport type VehicleCatalog = { makes: VehicleMake[] }\n\n/** Partial selection of the selector (all text, as it arrives from a form). */\nexport type VehicleDraft = { make: string; year: string; model: string; engine: string }\n\n/** A complete vehicle resolved against the catalog (with labels to display). */\nexport type Vehicle = {\n key: string\n make: { id: string; label: string }\n model: { id: string; label: string }\n year: number\n engine: { id: string; label: string }\n}\n\nexport type Option = { value: string; label: string }\n\n/** Fit state of a product for a vehicle. */\nexport type Fit = 'fits' | 'universal' | 'no' | 'unknown'\n\nexport type FitmentConfig = {\n /** Prefix of the tags that publish compatibility: `<prefix><vehicle key>`. Default `fit:`. */\n tagPrefix: string\n /** Tag that means \"fits everything\". Default `<tagPrefix>universal`. */\n universalTag: string\n /** Prefix of the single resolved-state tag written by `withFit`. Default `fit-state:`. */\n stateTagPrefix: string\n /** Product attribute holding the compatibility table. Default `compatibility`. */\n compatAttribute: string\n /** Separators of the compact compatibility table. */\n rowSeparator: string\n cellSeparator: string\n}\n\nexport const DEFAULT_FITMENT_CONFIG: FitmentConfig = {\n tagPrefix: 'fit:',\n universalTag: 'fit:universal',\n stateTagPrefix: 'fit-state:',\n compatAttribute: 'compatibility',\n rowSeparator: ';',\n cellSeparator: '|',\n}\n\n/** Merges overrides; an unset `universalTag` follows a custom `tagPrefix`. */\nexport function fitmentConfig(overrides: Partial<FitmentConfig> = {}): FitmentConfig {\n const tagPrefix = overrides.tagPrefix ?? DEFAULT_FITMENT_CONFIG.tagPrefix\n return {\n ...DEFAULT_FITMENT_CONFIG,\n ...overrides,\n tagPrefix,\n universalTag: overrides.universalTag ?? `${tagPrefix}universal`,\n }\n}\n\nexport const EMPTY_DRAFT: VehicleDraft = { make: '', year: '', model: '', engine: '' }\n\nconst isNum = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v)\nconst isText = (v: unknown): v is string => typeof v === 'string' && v.length > 0\n\nexport type ParseCatalogOptions = {\n /** Maps the backend's spelling of a priority to the canonical one (e.g. `{ alta: 'high' }`). English values always work. */\n priorityAliases?: Record<string, MakePriority>\n}\n\nfunction makeMeta(m: Record<string, unknown>, aliases: Record<string, MakePriority>): Partial<VehicleMake> {\n const raw = typeof m.priority === 'string' ? m.priority.toLowerCase() : ''\n const priority = aliases[raw] ?? raw\n const out: Partial<VehicleMake> = {}\n if (priority === 'high' || priority === 'medium' || priority === 'low') out.priority = priority\n if (isText(m.kind)) out.kind = m.kind\n for (const k of ['refs', 'observed_from', 'observed_to', 'typical_from', 'typical_to'] as const) {\n if (isNum(m[k])) out[k] = m[k] as number\n }\n return out\n}\n\n/** \"np300-frontier\" → \"NP300 FRONTIER\"; \"1.9-tdi\" → \"1.9 TDI\". */\nfunction labelFromId(id: string): string {\n return id.split('-').filter(Boolean).join(' ').toUpperCase()\n}\n\n/**\n * Reads the vehicle catalog from the props of a content section; drops whatever has the wrong shape.\n * Accepts the compact format (a real catalog weighs tens of KB and OPS caps each write): a model may\n * be a tuple `[id, from, to, engines]`, omit `label` (derived from the id) and list engines as ids.\n * Props stored as text (`makes` as a JSON string) are parsed.\n */\nexport function parseCatalog(raw: unknown, opts: ParseCatalogOptions = {}): VehicleCatalog {\n let makesRaw = (raw as { makes?: unknown } | null | undefined)?.makes\n if (typeof makesRaw === 'string') {\n try {\n makesRaw = JSON.parse(makesRaw)\n } catch {\n makesRaw = []\n }\n }\n const aliases = opts.priorityAliases ?? {}\n const makes: VehicleMake[] = []\n for (const m of Array.isArray(makesRaw) ? makesRaw : []) {\n if (!m || !isText(m.id) || !isText(m.label) || !Array.isArray(m.models)) continue\n const models: VehicleModel[] = []\n for (const entry of m.models) {\n const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry\n if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue\n const engines: VehicleEngine[] = mo.engines\n .map((e: unknown) => (typeof e === 'string' ? { id: e, label: labelFromId(e) } : e))\n .filter((e: VehicleEngine) => e && isText(e.id))\n .map((e: VehicleEngine) => ({\n id: e.id,\n label: isText(e.label) ? e.label : labelFromId(e.id),\n ...(isNum(e.from) ? { from: e.from } : {}),\n ...(isNum(e.to) ? { to: e.to } : {}),\n }))\n if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines })\n }\n // A make declared without models (waiting for the import) is kept; one with only invalid models is dropped.\n if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) })\n }\n return { makes }\n}\n\nexport function vehicleKey(v: { make: string; model: string; year: number | string; engine: string }): string {\n return `${v.make}/${v.model}/${v.year}/${v.engine}`\n}\n\n/** Tag that marks a part as fitting the vehicle with this key. */\nexport function fitTag(key: string, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string {\n return `${config.tagPrefix}${key}`\n}\n\nconst engineCovers = (e: VehicleEngine, model: VehicleModel, year: number) =>\n year >= (e.from ?? model.from) && year <= (e.to ?? model.to)\n\nconst modelCovers = (m: VehicleModel, year: number | null) => year == null || (year >= m.from && year <= m.to)\n\nfunction num(v: string): number | null {\n const n = Number(v)\n return v !== '' && Number.isInteger(n) ? n : null\n}\n\n/** Resolves a `make/model/year/engine` key against the catalog; null when it does not exist. */\nexport function resolveVehicle(catalog: VehicleCatalog, key: string | null | undefined): Vehicle | null {\n if (!key) return null\n const [makeId, modelId, yearText, engineId] = key.split('/')\n const year = num(yearText ?? '')\n const make = catalog.makes.find((m) => m.id === makeId)\n const model = make?.models.find((m) => m.id === modelId)\n if (!make || !model || year == null || !modelCovers(model, year)) return null\n const engine = model.engines.find((e) => e.id === engineId && engineCovers(e, model, year))\n if (!engine) return null\n return {\n key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }),\n make: { id: make.id, label: make.label },\n model: { id: model.id, label: model.label },\n year,\n engine: { id: engine.id, label: engine.label },\n }\n}\n\n/** Short name for chips and badges: \"Make Model 2018\". */\nexport function vehicleName(v: Vehicle): string {\n return `${v.make.label} ${v.model.label} ${v.year}`\n}\n\n/** With engine: \"Make Model 2018 · 1.6 L\". */\nexport function vehicleLongName(v: Vehicle): string {\n return `${vehicleName(v)} · ${v.engine.label}`\n}\n\n// --- Cascade make → year → model → engine ------------------------------------------------------\n// Every list filters by what is already chosen, in any order. With nothing chosen they show\n// everything (so the form also works without JavaScript).\n\nfunction modelsOf(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): { make: VehicleMake; model: VehicleModel }[] {\n const year = num(draft.year)\n return catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((make) => make.models.filter((m) => modelCovers(m, year)).map((model) => ({ make, model })))\n}\n\nexport function makeOptions(catalog: VehicleCatalog): Option[] {\n return catalog.makes.filter((m) => m.models.length > 0).map((m) => ({ value: m.id, label: m.label }))\n}\n\nexport function yearOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'model'>): Option[] {\n const models = catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((m) => m.models)\n .filter((m) => !draft.model || m.id === draft.model)\n if (models.length === 0) return []\n const from = Math.min(...models.map((m) => m.from))\n const to = Math.max(...models.map((m) => m.to))\n const years: Option[] = []\n for (let y = to; y >= from; y--) years.push({ value: String(y), label: String(y) })\n return years\n}\n\n/**\n * Model options. Model ids repeat across makes (`pick-up` exists in several): with no make chosen\n * the value is `make/model` (unique) and `splitModelValue` sets the make when it is picked; with a\n * make chosen the value is the model id.\n */\nexport function modelOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): Option[] {\n return modelsOf(catalog, draft).map(({ make, model }) => ({\n value: draft.make ? model.id : `${make.id}/${model.id}`,\n label: draft.make ? model.label : `${make.label} ${model.label}`,\n }))\n}\n\n/** `make/model` (value of the make-less list) → { make, model }; a bare id returns only the model. */\nexport function splitModelValue(value: string): { make?: string; model: string } {\n const i = value.indexOf('/')\n return i === -1 ? { model: value } : { make: value.slice(0, i), model: value.slice(i + 1) }\n}\n\nexport function engineOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year' | 'model'>): Option[] {\n const year = num(draft.year)\n const seen = new Map<string, string>()\n for (const { model } of modelsOf(catalog, draft).filter(({ model }) => !draft.model || model.id === draft.model)) {\n for (const e of model.engines) {\n if (year == null || engineCovers(e, model, year)) seen.set(e.id, e.label)\n }\n }\n return [...seen].map(([value, label]) => ({ value, label }))\n}\n\n/** Drops what was chosen and is no longer valid after changing another field. */\nexport function reconcileDraft(catalog: VehicleCatalog, draft: VehicleDraft): VehicleDraft {\n let next = { ...draft }\n if (next.year && !yearOptions(catalog, { make: next.make, model: '' }).some((o) => o.value === next.year)) next = { ...next, year: '' }\n // A bare model id sets the make only when it belongs to a single one; repeated ids are ambiguous and dropped.\n if (next.model && !next.make) {\n const owners = catalog.makes.filter((m) => m.models.some((mo) => mo.id === next.model))\n const [owner] = owners\n if (owners.length === 1 && owner) next = { ...next, make: owner.id }\n }\n if (next.model && !modelOptions(catalog, next).some((o) => o.value === next.model)) next = { ...next, model: '' }\n if (next.engine && !engineOptions(catalog, next).some((o) => o.value === next.engine)) next = { ...next, engine: '' }\n return next\n}\n\nexport function draftFromVehicle(v: Vehicle | null): VehicleDraft {\n return v ? { make: v.make.id, year: String(v.year), model: v.model.id, engine: v.engine.id } : EMPTY_DRAFT\n}\n\n/** Complete vehicle from the form, validated against the catalog. */\nexport function vehicleFromDraft(catalog: VehicleCatalog, draft: VehicleDraft): Vehicle | null {\n return resolveVehicle(catalog, vehicleKey({ make: draft.make, model: draft.model, year: draft.year, engine: draft.engine }))\n}\n\n// --- Popular vehicles --------------------------------------------------------------------------\n// They come from the data: makes with `high` priority in the catalog, most references first.\n\nexport function popularMakes(catalog: VehicleCatalog): VehicleMake[] {\n return catalog.makes.filter((m) => m.priority === 'high' && m.models.length > 0).sort((a, b) => (b.refs ?? 0) - (a.refs ?? 0))\n}\n\n/**\n * One shortcut per popular make: its first model in a typical year (the middle of the make's typical\n * range, clamped to the model's range) with the first engine that covers that year.\n */\nexport function popularVehicles(catalog: VehicleCatalog, limit = 6): { key: string; label: string }[] {\n const out: { key: string; label: string }[] = []\n for (const make of popularMakes(catalog)) {\n if (out.length >= limit) break\n const model = make.models[0]\n if (!model) continue\n const mid = make.typical_from != null && make.typical_to != null ? Math.round((make.typical_from + make.typical_to) / 2) : model.to\n const year = Math.min(model.to, Math.max(model.from, mid))\n const engine = model.engines.find((e) => engineCovers(e, model, year))\n if (!engine) continue\n out.push({ key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }), label: `${make.label} ${model.label} ${year}` })\n }\n return out\n}\n\n// --- Fitment -----------------------------------------------------------------------------------\n\ntype WithTags = { tags: readonly string[] }\ntype WithAttributes = { attributes: Record<string, unknown> }\n\n/**\n * Does the part fit the vehicle? Only compares what the backend published: a `<prefix><key>` tag\n * (fits), the universal tag (fits everything) or, when the part has other fit tags, no fit. With no\n * fitment data nothing is asserted (\"unknown\").\n */\nexport function fitStatus(product: WithTags, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const fits = product.tags.filter((t) => t.startsWith(config.tagPrefix))\n if (fits.length === 0) return 'unknown'\n if (fits.includes(config.universalTag)) return 'universal'\n if (!vehicle) return 'unknown'\n return fits.includes(fitTag(vehicle.key, config)) ? 'fits' : 'no'\n}\n\n/** Values for a `filter.tag` query that lists only what fits (the API takes them as \"any of\"). */\nexport function fitFilterTags(vehicle: { key: string }, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string[] {\n return [fitTag(vehicle.key, config), config.universalTag]\n}\n\nexport type CompatRow = { make: string; model: string; years: string; engine: string; note: string | null }\n\n/** Compatibility table: the attribute holds \"Make|Model|Years|Engine|Note;Make|…\". */\nexport function compatRows(product: WithAttributes, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): CompatRow[] {\n const raw = product.attributes[config.compatAttribute]\n if (typeof raw !== 'string' || !raw) return []\n return raw\n .split(config.rowSeparator)\n .map((row) => row.split(config.cellSeparator).map((c) => c.trim()))\n .flatMap((c): CompatRow[] => {\n const [make, model, years, engine, note] = c\n return c.length >= 4 && make && model ? [{ make, model, years: years ?? '', engine: engine ?? '', note: note || null }] : []\n })\n}\n\n/** \"2012-2019\" contains the year (a bare \"2016\" too). */\nexport function rowCoversYear(years: string, year: number): boolean {\n const [from, to] = years.split(/[–-]/).map((s) => Number(s.trim()))\n if (from === undefined || !Number.isFinite(from)) return false\n return year >= from && year <= (to !== undefined && Number.isFinite(to) ? to : from)\n}\n\n/**\n * The backend publishes one tag per compatible year and engine (hundreds per part). Loaders replace\n * them with ONE `<stateTagPrefix><state>` tag, resolved for the chosen vehicle, so they are not\n * serialized into the page.\n */\nexport function withFit<T extends WithTags>(product: T, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): T {\n const state = fitStatus(product, vehicle, config)\n return { ...product, tags: [...product.tags.filter((t) => !t.startsWith(config.tagPrefix)), `${config.stateTagPrefix}${state}`] }\n}\n\n/** Fit state already resolved by `withFit` (\"unknown\" if the product did not go through it). */\nexport function fitState(product: WithTags, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const tag = product.tags.find((t) => t.startsWith(config.stateTagPrefix))\n const value = tag?.slice(config.stateTagPrefix.length)\n return value === 'fits' || value === 'universal' || value === 'no' ? value : 'unknown'\n}\n\n/** Validates a redirect target: only relative paths of the own site. */\nexport function safeRedirect(target: string | null | undefined, fallback = '/'): string {\n return typeof target === 'string' && target.startsWith('/') && !target.startsWith('//') && !target.includes('\\\\') ? target : fallback\n}\n\n/** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */\nexport function withoutQueryParam(path: string, param: string): string {\n const url = new URL(path, 'http://local.invalid')\n url.searchParams.delete(param)\n return `${url.pathname}${url.search}`\n}\n"]}
1
+ {"version":3,"sources":["../src/fitment/fitment.ts"],"names":["ranges","model"],"mappings":"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","file":"fitment.js","sourcesContent":["// Vehicle fitment (\"Mi vehículo\"): a make → model → year → engine catalog, vehicle keys, the\n// cascading selector options and the fit state of a product. Pure functions with no UI, no\n// storage and no copy: every tag prefix, attribute name and legacy spelling is injectable through\n// `FitmentConfig`, so any store that sells parts can use it. Nothing here decides on its own\n// whether a part fits a vehicle: it only compares what the backend (OPS) published.\n\nimport type { SubjectCatalog, SubjectCatalogNode } from '@asteby/storefront'\n\n/**\n * `ranges` are the stretches of model years in which the engine has parts ([from, to] pairs; a gap is a real gap).\n * Without them the engine covers `from`–`to`, or the whole range of its model.\n */\nexport type VehicleEngine = { id: string; label: string; from?: number; to?: number; ranges?: [number, number][] }\nexport type VehicleModel = { id: string; label: string; from: number; to: number; engines: VehicleEngine[] }\nexport type MakePriority = 'high' | 'medium' | 'low'\n/**\n * A make of the catalog. `models` may be empty while the catalog is being imported: the make is\n * declared but it does not show in the selector until it has models.\n */\nexport type VehicleMake = {\n id: string\n label: string\n models: VehicleModel[]\n priority?: MakePriority\n /** Free-form segment of the make (e.g. light / heavy duty). Passed through as published. */\n kind?: string\n refs?: number\n observed_from?: number\n observed_to?: number\n typical_from?: number\n typical_to?: number\n}\nexport type VehicleCatalog = { makes: VehicleMake[] }\n\n/** Partial selection of the selector (all text, as it arrives from a form). */\nexport type VehicleDraft = { make: string; year: string; model: string; engine: string }\n\n/** A complete vehicle resolved against the catalog (with labels to display). */\nexport type Vehicle = {\n key: string\n make: { id: string; label: string }\n model: { id: string; label: string }\n year: number\n engine: { id: string; label: string }\n}\n\nexport type Option = { value: string; label: string }\n\n/** Fit state of a product for a vehicle. */\nexport type Fit = 'fits' | 'universal' | 'no' | 'unknown'\n\nexport type FitmentConfig = {\n /** Prefix of the tags that publish compatibility: `<prefix><vehicle key>`. Default `fit:`. */\n tagPrefix: string\n /** Tag that means \"fits everything\". Default `<tagPrefix>universal`. */\n universalTag: string\n /** Prefix of the single resolved-state tag written by `withFit`. Default `fit-state:`. */\n stateTagPrefix: string\n /** Product attribute holding the compatibility table. Default `compatibility`. */\n compatAttribute: string\n /** Separators of the compact compatibility table. */\n rowSeparator: string\n cellSeparator: string\n}\n\nexport const DEFAULT_FITMENT_CONFIG: FitmentConfig = {\n tagPrefix: 'fit:',\n universalTag: 'fit:universal',\n stateTagPrefix: 'fit-state:',\n compatAttribute: 'compatibility',\n rowSeparator: ';',\n cellSeparator: '|',\n}\n\n/** Merges overrides; an unset `universalTag` follows a custom `tagPrefix`. */\nexport function fitmentConfig(overrides: Partial<FitmentConfig> = {}): FitmentConfig {\n const tagPrefix = overrides.tagPrefix ?? DEFAULT_FITMENT_CONFIG.tagPrefix\n return {\n ...DEFAULT_FITMENT_CONFIG,\n ...overrides,\n tagPrefix,\n universalTag: overrides.universalTag ?? `${tagPrefix}universal`,\n }\n}\n\nexport const EMPTY_DRAFT: VehicleDraft = { make: '', year: '', model: '', engine: '' }\n\nconst isNum = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v)\nconst isText = (v: unknown): v is string => typeof v === 'string' && v.length > 0\n\nexport type ParseCatalogOptions = {\n /** Maps the backend's spelling of a priority to the canonical one (e.g. `{ alta: 'high' }`). English values always work. */\n priorityAliases?: Record<string, MakePriority>\n}\n\nfunction makeMeta(m: Record<string, unknown>, aliases: Record<string, MakePriority>): Partial<VehicleMake> {\n const raw = typeof m.priority === 'string' ? m.priority.toLowerCase() : ''\n const priority = aliases[raw] ?? raw\n const out: Partial<VehicleMake> = {}\n if (priority === 'high' || priority === 'medium' || priority === 'low') out.priority = priority\n if (isText(m.kind)) out.kind = m.kind\n for (const k of ['refs', 'observed_from', 'observed_to', 'typical_from', 'typical_to'] as const) {\n if (isNum(m[k])) out[k] = m[k] as number\n }\n return out\n}\n\n/** \"np300-frontier\" → \"NP300 FRONTIER\"; \"1.9-tdi\" → \"1.9 TDI\". */\nfunction labelFromId(id: string): string {\n return id.split('-').filter(Boolean).join(' ').toUpperCase()\n}\n\n/**\n * One engine of the catalog: `\"2.4\"` (the whole range of its model), `[\"2.4\", 2007, 2017, 2020, 2022]` (an id and pairs of\n * years: stretches with parts) or `{ id, label?, from?, to?, ranges? }`.\n */\nfunction parseEngine(e: unknown): VehicleEngine | null {\n if (typeof e === 'string') return isText(e) ? { id: e, label: labelFromId(e) } : null\n if (Array.isArray(e)) {\n const [id, ...years] = e as unknown[]\n if (!isText(id)) return null\n const ranges: [number, number][] = []\n for (let i = 0; i + 1 < years.length; i += 2) {\n const a = years[i]\n const b = years[i + 1]\n if (isNum(a) && isNum(b)) ranges.push([a, b])\n }\n const first = ranges[0]\n const last = ranges[ranges.length - 1]\n return first && last ? { id, label: labelFromId(id), from: first[0], to: last[1], ranges } : { id, label: labelFromId(id) }\n }\n const o = e as Partial<VehicleEngine> | null\n if (!o || !isText(o.id)) return null\n const ranges = (Array.isArray(o.ranges) ? o.ranges : []).filter((r): r is [number, number] => Array.isArray(r) && isNum(r[0]) && isNum(r[1]))\n return {\n id: o.id,\n label: isText(o.label) ? o.label : labelFromId(o.id),\n ...(isNum(o.from) ? { from: o.from } : {}),\n ...(isNum(o.to) ? { to: o.to } : {}),\n ...(ranges.length ? { ranges } : {}),\n }\n}\n\n/**\n * Reads the vehicle catalog from the props of a content section; drops whatever has the wrong shape.\n * Accepts the compact format (a real catalog weighs tens of KB and OPS caps each write): a model may\n * be a tuple `[id, from, to, engines]`, omit `label` (derived from the id) and list engines as ids.\n * Props stored as text (`makes` as a JSON string) are parsed.\n */\nexport function parseCatalog(raw: unknown, opts: ParseCatalogOptions = {}): VehicleCatalog {\n let makesRaw = (raw as { makes?: unknown } | null | undefined)?.makes\n if (typeof makesRaw === 'string') {\n try {\n makesRaw = JSON.parse(makesRaw)\n } catch {\n makesRaw = []\n }\n }\n const aliases = opts.priorityAliases ?? {}\n const makes: VehicleMake[] = []\n for (const m of Array.isArray(makesRaw) ? makesRaw : []) {\n if (!m || !isText(m.id) || !isText(m.label) || !Array.isArray(m.models)) continue\n const models: VehicleModel[] = []\n for (const entry of m.models) {\n const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry\n if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue\n const engines: VehicleEngine[] = mo.engines.map(parseEngine).filter((e: VehicleEngine | null): e is VehicleEngine => e !== null)\n if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines })\n }\n // A make declared without models (waiting for the import) is kept; one with only invalid models is dropped.\n if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) })\n }\n return { makes }\n}\n\nexport function vehicleKey(v: { make: string; model: string; year: number | string; engine: string }): string {\n return `${v.make}/${v.model}/${v.year}/${v.engine}`\n}\n\n/** Tag that marks a part as fitting the vehicle with this key. */\nexport function fitTag(key: string, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string {\n return `${config.tagPrefix}${key}`\n}\n\nconst engineCovers = (e: VehicleEngine, model: VehicleModel, year: number) =>\n e.ranges ? e.ranges.some(([a, b]) => year >= a && year <= b) : year >= (e.from ?? model.from) && year <= (e.to ?? model.to)\n\n// A model covers a year only when one of its engines does: no phantom years inside `from`–`to` that no engine has parts for.\nconst modelCovers = (m: VehicleModel, year: number | null) => year == null || m.engines.some((e) => engineCovers(e, m, year))\n\nfunction num(v: string): number | null {\n const n = Number(v)\n return v !== '' && Number.isInteger(n) ? n : null\n}\n\n/** Resolves a `make/model/year/engine` key against the catalog; null when it does not exist. */\nexport function resolveVehicle(catalog: VehicleCatalog, key: string | null | undefined): Vehicle | null {\n if (!key) return null\n const [makeId, modelId, yearText, engineId] = key.split('/')\n const year = num(yearText ?? '')\n const make = catalog.makes.find((m) => m.id === makeId)\n const model = make?.models.find((m) => m.id === modelId)\n if (!make || !model || year == null || !modelCovers(model, year)) return null\n const engine = model.engines.find((e) => e.id === engineId && engineCovers(e, model, year))\n if (!engine) return null\n return {\n key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }),\n make: { id: make.id, label: make.label },\n model: { id: model.id, label: model.label },\n year,\n engine: { id: engine.id, label: engine.label },\n }\n}\n\n/** Short name for chips and badges: \"Make Model 2018\". */\nexport function vehicleName(v: Vehicle): string {\n return `${v.make.label} ${v.model.label} ${v.year}`\n}\n\n/** With engine: \"Make Model 2018 · 1.6 L\". */\nexport function vehicleLongName(v: Vehicle): string {\n return `${vehicleName(v)} · ${v.engine.label}`\n}\n\n// --- Cascade make → year → model → engine ------------------------------------------------------\n// Every list filters by what is already chosen, in any order. With nothing chosen they show\n// everything (so the form also works without JavaScript).\n\nfunction modelsOf(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): { make: VehicleMake; model: VehicleModel }[] {\n const year = num(draft.year)\n return catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((make) => make.models.filter((m) => modelCovers(m, year)).map((model) => ({ make, model })))\n}\n\nexport function makeOptions(catalog: VehicleCatalog): Option[] {\n return catalog.makes.filter((m) => m.models.length > 0).map((m) => ({ value: m.id, label: m.label }))\n}\n\nexport function yearOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'model'>): Option[] {\n const models = catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((m) => m.models)\n .filter((m) => !draft.model || m.id === draft.model)\n if (models.length === 0) return []\n const from = Math.min(...models.map((m) => m.from))\n const to = Math.max(...models.map((m) => m.to))\n const years: Option[] = []\n // Only years in which some chosen model has an engine with parts.\n for (let y = to; y >= from; y--) if (models.some((m) => modelCovers(m, y))) years.push({ value: String(y), label: String(y) })\n return years\n}\n\n/**\n * Model options. Model ids repeat across makes (`pick-up` exists in several): with no make chosen\n * the value is `make/model` (unique) and `splitModelValue` sets the make when it is picked; with a\n * make chosen the value is the model id.\n */\nexport function modelOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): Option[] {\n return modelsOf(catalog, draft).map(({ make, model }) => ({\n value: draft.make ? model.id : `${make.id}/${model.id}`,\n label: draft.make ? model.label : `${make.label} ${model.label}`,\n }))\n}\n\n/** `make/model` (value of the make-less list) → { make, model }; a bare id returns only the model. */\nexport function splitModelValue(value: string): { make?: string; model: string } {\n const i = value.indexOf('/')\n return i === -1 ? { model: value } : { make: value.slice(0, i), model: value.slice(i + 1) }\n}\n\nexport function engineOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year' | 'model'>): Option[] {\n const year = num(draft.year)\n const seen = new Map<string, string>()\n for (const { model } of modelsOf(catalog, draft).filter(({ model }) => !draft.model || model.id === draft.model)) {\n for (const e of model.engines) {\n if (year == null || engineCovers(e, model, year)) seen.set(e.id, e.label)\n }\n }\n return [...seen].map(([value, label]) => ({ value, label }))\n}\n\n/** Drops what was chosen and is no longer valid after changing another field. */\nexport function reconcileDraft(catalog: VehicleCatalog, draft: VehicleDraft): VehicleDraft {\n let next = { ...draft }\n if (next.year && !yearOptions(catalog, { make: next.make, model: '' }).some((o) => o.value === next.year)) next = { ...next, year: '' }\n // A bare model id sets the make only when it belongs to a single one; repeated ids are ambiguous and dropped.\n if (next.model && !next.make) {\n const owners = catalog.makes.filter((m) => m.models.some((mo) => mo.id === next.model))\n const [owner] = owners\n if (owners.length === 1 && owner) next = { ...next, make: owner.id }\n }\n if (next.model && !modelOptions(catalog, next).some((o) => o.value === next.model)) next = { ...next, model: '' }\n if (next.engine && !engineOptions(catalog, next).some((o) => o.value === next.engine)) next = { ...next, engine: '' }\n return next\n}\n\nexport function draftFromVehicle(v: Vehicle | null): VehicleDraft {\n return v ? { make: v.make.id, year: String(v.year), model: v.model.id, engine: v.engine.id } : EMPTY_DRAFT\n}\n\n/** Complete vehicle from the form, validated against the catalog. */\nexport function vehicleFromDraft(catalog: VehicleCatalog, draft: VehicleDraft): Vehicle | null {\n return resolveVehicle(catalog, vehicleKey({ make: draft.make, model: draft.model, year: draft.year, engine: draft.engine }))\n}\n\n// --- Popular vehicles --------------------------------------------------------------------------\n// They come from the data: makes with `high` priority in the catalog, most references first.\n\nexport function popularMakes(catalog: VehicleCatalog): VehicleMake[] {\n return catalog.makes.filter((m) => m.priority === 'high' && m.models.length > 0).sort((a, b) => (b.refs ?? 0) - (a.refs ?? 0))\n}\n\n/**\n * One shortcut per popular make: its first model in a typical year (the middle of the make's typical\n * range, clamped to the model's range) with the first engine that covers that year.\n */\nexport function popularVehicles(catalog: VehicleCatalog, limit = 6): { key: string; label: string }[] {\n const out: { key: string; label: string }[] = []\n for (const make of popularMakes(catalog)) {\n if (out.length >= limit) break\n const model = make.models[0]\n if (!model) continue\n const mid = make.typical_from != null && make.typical_to != null ? Math.round((make.typical_from + make.typical_to) / 2) : model.to\n const year = Math.min(model.to, Math.max(model.from, mid))\n const engine = model.engines.find((e) => engineCovers(e, model, year))\n if (!engine) continue\n out.push({ key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }), label: `${make.label} ${model.label} ${year}` })\n }\n return out\n}\n\n// --- Fitment -----------------------------------------------------------------------------------\n\ntype WithTags = { tags: readonly string[] }\ntype WithAttributes = { attributes: Record<string, unknown> }\n\n/**\n * Does the part fit the vehicle? Only compares what the backend published: a `<prefix><key>` tag\n * (fits), the universal tag (fits everything) or, when the part has other fit tags, no fit. With no\n * fitment data nothing is asserted (\"unknown\").\n */\nexport function fitStatus(product: WithTags, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const fits = product.tags.filter((t) => t.startsWith(config.tagPrefix))\n if (fits.length === 0) return 'unknown'\n if (fits.includes(config.universalTag)) return 'universal'\n if (!vehicle) return 'unknown'\n return fits.includes(fitTag(vehicle.key, config)) ? 'fits' : 'no'\n}\n\n/**\n * Values for `products.list({ tags_any })` that list only what fits (own tag plus the universal one; the API takes them\n * as \"any of\"). It replaces searching the tag with `q`. Same as `fitTagsAny` of `@asteby/storefront` for a catalog-driven kind.\n */\nexport function fitFilterTags(vehicle: { key: string }, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string[] {\n return [fitTag(vehicle.key, config), config.universalTag]\n}\n\nexport type CompatRow = { make: string; model: string; years: string; engine: string; note: string | null }\n\n/** Compatibility table: the attribute holds \"Make|Model|Years|Engine|Note;Make|…\". */\nexport function compatRows(product: WithAttributes, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): CompatRow[] {\n const raw = product.attributes[config.compatAttribute]\n if (typeof raw !== 'string' || !raw) return []\n return raw\n .split(config.rowSeparator)\n .map((row) => row.split(config.cellSeparator).map((c) => c.trim()))\n .flatMap((c): CompatRow[] => {\n const [make, model, years, engine, note] = c\n return c.length >= 4 && make && model ? [{ make, model, years: years ?? '', engine: engine ?? '', note: note || null }] : []\n })\n}\n\n/** \"2012-2019\" contains the year (a bare \"2016\" too). */\nexport function rowCoversYear(years: string, year: number): boolean {\n const [from, to] = years.split(/[–-]/).map((s) => Number(s.trim()))\n if (from === undefined || !Number.isFinite(from)) return false\n return year >= from && year <= (to !== undefined && Number.isFinite(to) ? to : from)\n}\n\n/**\n * The backend publishes one tag per compatible year and engine (hundreds per part). Loaders replace\n * them with ONE `<stateTagPrefix><state>` tag, resolved for the chosen vehicle, so they are not\n * serialized into the page.\n */\nexport function withFit<T extends WithTags>(product: T, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): T {\n const state = fitStatus(product, vehicle, config)\n return { ...product, tags: [...product.tags.filter((t) => !t.startsWith(config.tagPrefix)), `${config.stateTagPrefix}${state}`] }\n}\n\n/** Fit state already resolved by `withFit` (\"unknown\" if the product did not go through it). */\nexport function fitState(product: WithTags, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const tag = product.tags.find((t) => t.startsWith(config.stateTagPrefix))\n const value = tag?.slice(config.stateTagPrefix.length)\n return value === 'fits' || value === 'universal' || value === 'no' ? value : 'unknown'\n}\n\n/** Validates a redirect target: only relative paths of the own site. */\nexport function safeRedirect(target: string | null | undefined, fallback = '/'): string {\n return typeof target === 'string' && target.startsWith('/') && !target.startsWith('//') && !target.includes('\\\\') ? target : fallback\n}\n\n/** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */\nexport function withoutQueryParam(path: string, param: string): string {\n const url = new URL(path, 'http://local.invalid')\n url.searchParams.delete(param)\n return `${url.pathname}${url.search}`\n}\n\n// --- Bridge to the buyer's «My items» (contract 1.9) -------------------------------------------------------------\n\n/**\n * The same make → model → engine catalog as the tree the organization publishes for a kind (`subject_catalog` page,\n * `SubjectKind.catalog`): a parts store that already keeps its catalog in this shape converts it once, with a script,\n * and the server validates the buyer's saved vehicles against it (years by `from`/`to`/`ranges`). A model covers the\n * hull of its engines' years, so the tree accepts exactly the years `resolveVehicle` accepts. Makes without models are\n * left out (they do not show in the selector either).\n */\nexport function vehicleCatalogToTree(catalog: VehicleCatalog): SubjectCatalog['tree'] {\n const tree: SubjectCatalog['tree'] = {}\n for (const make of catalog.makes) {\n if (make.models.length === 0) continue\n const models: Record<string, SubjectCatalogNode> = {}\n for (const model of make.models) {\n const engines: Record<string, SubjectCatalogNode> = {}\n let from = Infinity\n let to = -Infinity\n for (const e of model.engines) {\n const spans = e.ranges?.length ? e.ranges : ([[e.from ?? model.from, e.to ?? model.to]] as [number, number][])\n for (const [a, b] of spans) {\n from = Math.min(from, a)\n to = Math.max(to, b)\n }\n engines[e.id] = e.ranges?.length ? { label: e.label, ranges: e.ranges } : { label: e.label, from: e.from ?? model.from, to: e.to ?? model.to }\n }\n models[model.id] = { label: model.label, from: Number.isFinite(from) ? from : model.from, to: Number.isFinite(to) ? to : model.to, children: engines }\n }\n tree[make.id] = { label: make.label, children: models }\n }\n return tree\n}\n"]}
@@ -0,0 +1,77 @@
1
+ /** What the account form and the server action share. Plain types: no React, no server code. */
2
+ /** What the buyer is doing. It is the `intent` field of the account form. */
3
+ type AuthIntent = 'login' | 'register' | 'verify' | 'otp-request' | 'otp-login' | 'forgot' | 'reset';
4
+ /** Screen the form shows next, set by the action (`verify`: code of the registration; `otp`: code to sign in; `reset`: code + new password). */
5
+ type AuthStep = 'verify' | 'otp' | 'reset';
6
+ type AuthErrorCode =
7
+ /** Wrong email or password. */
8
+ 'invalid_credentials'
9
+ /** Wrong or expired code. */
10
+ | 'invalid_code'
11
+ /** The email already has an account. */
12
+ | 'email_taken'
13
+ /** Too many attempts: wait and try again. */
14
+ | 'rate_limited'
15
+ /** A field was rejected (`field` says which). */
16
+ | 'validation'
17
+ /** The store does not offer this way of signing in. */
18
+ | 'unsupported'
19
+ /**
20
+ * The store's own credential lacks a permission (`403 insufficient_scope`): the shop is misconfigured, not the
21
+ * buyer's data. The buyer can still check out as a guest; the server already logged how to repair it.
22
+ */
23
+ | 'store_misconfigured'
24
+ /** Anything else (network, server). */
25
+ | 'unavailable';
26
+ /** What the action returns when it does not redirect. Texts are NOT here: `<AccountAuth>` writes them (i18n). */
27
+ type AuthActionData = {
28
+ ok: boolean;
29
+ /** Next screen of the form. */
30
+ step?: AuthStep;
31
+ /** Email the flow is about, to carry it to the next screen. */
32
+ email?: string;
33
+ error?: {
34
+ code: AuthErrorCode;
35
+ field?: string;
36
+ };
37
+ };
38
+
39
+ /** What the account settings forms and their server action share. Plain types: no React, no server code. */
40
+ /** What the buyer is doing. It is the `intent` field of the settings forms. */
41
+ type SettingsIntent = 'session-revoke' | 'session-revoke-others' | 'locale' | 'notifications' | 'reauth-code-request' | 'export' | 'delete';
42
+ type SettingsErrorCode =
43
+ /** The buyer's session is gone (expired or closed elsewhere): ask them to sign in again. */
44
+ 'session_expired'
45
+ /** The password or the code given to confirm identity is wrong. The same answer for any reason. */
46
+ | 'invalid_credential'
47
+ /** The action needs the buyer to confirm their password or a code first. */
48
+ | 'reauth_required'
49
+ /** Too many tries: wait (`retryAfter` seconds) and try again. */
50
+ | 'rate_limited'
51
+ /** A field was rejected (`field` says which). */
52
+ | 'validation'
53
+ /** Deleting the account: an order is still on its way. */
54
+ | 'blocked_orders'
55
+ /** Deleting the account: a payment is still pending. */
56
+ | 'blocked_pending_payments'
57
+ /** The store does not offer this (an older server, or the section is off). */
58
+ | 'unsupported'
59
+ /** Anything else (network, server). */
60
+ | 'unavailable';
61
+ /** What the action returns when it does not redirect or send a file. Texts are NOT here: the components write them (i18n). */
62
+ type AccountSettingsActionData = {
63
+ ok: boolean;
64
+ /** Which form this answers, so each section shows only its own result. */
65
+ intent?: SettingsIntent;
66
+ /** A value was saved (language, notifications). */
67
+ saved?: boolean;
68
+ /** A sign-in code was sent to confirm identity (`reauth-code-request`). */
69
+ codeSent?: boolean;
70
+ error?: {
71
+ code: SettingsErrorCode;
72
+ field?: string;
73
+ retryAfter?: number;
74
+ };
75
+ };
76
+
77
+ export type { AccountSettingsActionData as A, SettingsErrorCode as S, AuthActionData as a, AuthErrorCode as b, AuthIntent as c, AuthStep as d, SettingsIntent as e };
@@ -0,0 +1,77 @@
1
+ /** What the account form and the server action share. Plain types: no React, no server code. */
2
+ /** What the buyer is doing. It is the `intent` field of the account form. */
3
+ type AuthIntent = 'login' | 'register' | 'verify' | 'otp-request' | 'otp-login' | 'forgot' | 'reset';
4
+ /** Screen the form shows next, set by the action (`verify`: code of the registration; `otp`: code to sign in; `reset`: code + new password). */
5
+ type AuthStep = 'verify' | 'otp' | 'reset';
6
+ type AuthErrorCode =
7
+ /** Wrong email or password. */
8
+ 'invalid_credentials'
9
+ /** Wrong or expired code. */
10
+ | 'invalid_code'
11
+ /** The email already has an account. */
12
+ | 'email_taken'
13
+ /** Too many attempts: wait and try again. */
14
+ | 'rate_limited'
15
+ /** A field was rejected (`field` says which). */
16
+ | 'validation'
17
+ /** The store does not offer this way of signing in. */
18
+ | 'unsupported'
19
+ /**
20
+ * The store's own credential lacks a permission (`403 insufficient_scope`): the shop is misconfigured, not the
21
+ * buyer's data. The buyer can still check out as a guest; the server already logged how to repair it.
22
+ */
23
+ | 'store_misconfigured'
24
+ /** Anything else (network, server). */
25
+ | 'unavailable';
26
+ /** What the action returns when it does not redirect. Texts are NOT here: `<AccountAuth>` writes them (i18n). */
27
+ type AuthActionData = {
28
+ ok: boolean;
29
+ /** Next screen of the form. */
30
+ step?: AuthStep;
31
+ /** Email the flow is about, to carry it to the next screen. */
32
+ email?: string;
33
+ error?: {
34
+ code: AuthErrorCode;
35
+ field?: string;
36
+ };
37
+ };
38
+
39
+ /** What the account settings forms and their server action share. Plain types: no React, no server code. */
40
+ /** What the buyer is doing. It is the `intent` field of the settings forms. */
41
+ type SettingsIntent = 'session-revoke' | 'session-revoke-others' | 'locale' | 'notifications' | 'reauth-code-request' | 'export' | 'delete';
42
+ type SettingsErrorCode =
43
+ /** The buyer's session is gone (expired or closed elsewhere): ask them to sign in again. */
44
+ 'session_expired'
45
+ /** The password or the code given to confirm identity is wrong. The same answer for any reason. */
46
+ | 'invalid_credential'
47
+ /** The action needs the buyer to confirm their password or a code first. */
48
+ | 'reauth_required'
49
+ /** Too many tries: wait (`retryAfter` seconds) and try again. */
50
+ | 'rate_limited'
51
+ /** A field was rejected (`field` says which). */
52
+ | 'validation'
53
+ /** Deleting the account: an order is still on its way. */
54
+ | 'blocked_orders'
55
+ /** Deleting the account: a payment is still pending. */
56
+ | 'blocked_pending_payments'
57
+ /** The store does not offer this (an older server, or the section is off). */
58
+ | 'unsupported'
59
+ /** Anything else (network, server). */
60
+ | 'unavailable';
61
+ /** What the action returns when it does not redirect or send a file. Texts are NOT here: the components write them (i18n). */
62
+ type AccountSettingsActionData = {
63
+ ok: boolean;
64
+ /** Which form this answers, so each section shows only its own result. */
65
+ intent?: SettingsIntent;
66
+ /** A value was saved (language, notifications). */
67
+ saved?: boolean;
68
+ /** A sign-in code was sent to confirm identity (`reauth-code-request`). */
69
+ codeSent?: boolean;
70
+ error?: {
71
+ code: SettingsErrorCode;
72
+ field?: string;
73
+ retryAfter?: number;
74
+ };
75
+ };
76
+
77
+ export type { AccountSettingsActionData as A, SettingsErrorCode as S, AuthActionData as a, AuthErrorCode as b, AuthIntent as c, AuthStep as d, SettingsIntent as e };
@@ -0,0 +1,248 @@
1
+ 'use strict';
2
+
3
+ var storefront = require('@asteby/storefront');
4
+
5
+ // src/subjects/server.ts
6
+ var KEY_RE = /^[a-z0-9._+/-]{1,100}$/;
7
+ var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
+ function subjectFromFitKey(kind, fitKey) {
9
+ if (!fitKey || !KEY_RE.test(fitKey) || !kind.fit_key_template) return null;
10
+ const names = [];
11
+ const pattern = kind.fit_key_template.split(/(\{[a-z][a-z0-9_]*\})/).map((part) => {
12
+ const m = /^\{([a-z][a-z0-9_]*)\}$/.exec(part);
13
+ if (!m) return escape(part);
14
+ names.push(m[1]);
15
+ return "([^/]+)";
16
+ }).join("");
17
+ const match = new RegExp(`^${pattern}$`).exec(fitKey);
18
+ if (!match) return null;
19
+ const values = {};
20
+ names.forEach((n, i) => values[n] = match[i + 1]);
21
+ const r = storefront.resolveSubject(kind, values);
22
+ return r.ok && r.subject.fit_key === fitKey ? r.subject : null;
23
+ }
24
+
25
+ // src/subjects/server.ts
26
+ var SUBJECT_COOKIE_PREFIX = "sf_subject_";
27
+ var KIND_KEY_RE = /^[a-z][a-z0-9_]{0,31}$/;
28
+ function subjectCookieName(kind) {
29
+ const key = typeof kind === "string" ? kind : kind.key;
30
+ if (!KIND_KEY_RE.test(key)) throw new TypeError("subjectCookieName: not a kind key");
31
+ return SUBJECT_COOKIE_PREFIX + key;
32
+ }
33
+ function readSubjectFitKey(cookieHeader, kind) {
34
+ if (!cookieHeader) return null;
35
+ const name = subjectCookieName(kind);
36
+ for (const part of cookieHeader.split(";")) {
37
+ const eq = part.indexOf("=");
38
+ if (eq < 0 || part.slice(0, eq).trim() !== name) continue;
39
+ let raw = part.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1");
40
+ try {
41
+ raw = decodeURIComponent(raw);
42
+ } catch {
43
+ return null;
44
+ }
45
+ return subjectFromFitKey(kind, raw) ? raw : null;
46
+ }
47
+ return null;
48
+ }
49
+ function serializeSubjectCookie(kind, fitKey, options = {}) {
50
+ if (fitKey !== null && !subjectFromFitKey(kind, fitKey)) throw new TypeError("serializeSubjectCookie: not a compatibility key of this kind");
51
+ const parts = [`${subjectCookieName(kind)}=${fitKey ? encodeURIComponent(fitKey) : ""}`, `Path=${options.path ?? "/"}`];
52
+ parts.push(`Max-Age=${fitKey ? Math.round((options.maxAgeDays ?? 365) * 86400) : 0}`);
53
+ if (options.domain) parts.push(`Domain=${options.domain}`);
54
+ const sameSite2 = options.sameSite ?? "Lax";
55
+ parts.push(`SameSite=${sameSite2}`, "HttpOnly");
56
+ if (options.secure ?? sameSite2 === "None") parts.push("Secure");
57
+ return parts.join("; ");
58
+ }
59
+ function hasActiveSubject(cookieHeader) {
60
+ return !!cookieHeader && cookieHeader.split(";").some((p) => p.trim().startsWith(SUBJECT_COOKIE_PREFIX) && p.indexOf("=") > 0 && !/=\s*$/.test(p));
61
+ }
62
+ function subjectCacheHeaders(cookieHeader, publicCacheControl, opts = {}) {
63
+ if (opts.signedIn || hasActiveSubject(cookieHeader)) return { "Cache-Control": "private, no-store", Vary: "Cookie" };
64
+ return { "Cache-Control": publicCacheControl, Vary: "Cookie" };
65
+ }
66
+ var NO_SUBJECTS = Object.freeze({ kinds: [], subjects: [], active: {}, degraded: { kinds: true, subjects: false } });
67
+ async function loadSubjectsContext(sf, options = {}) {
68
+ const opt = options.signal ? { signal: options.signal } : void 0;
69
+ let kinds;
70
+ try {
71
+ kinds = await sf.content.subjectKinds(opt);
72
+ } catch {
73
+ return NO_SUBJECTS;
74
+ }
75
+ if (kinds.length === 0) return { kinds: [], subjects: [], active: {}, degraded: { kinds: false, subjects: false } };
76
+ let subjects = [];
77
+ let degraded = false;
78
+ if (options.customer) {
79
+ try {
80
+ subjects = await options.customer.customer.subjects.list(void 0, opt);
81
+ } catch {
82
+ degraded = true;
83
+ }
84
+ }
85
+ const active = {};
86
+ for (const kind of kinds) {
87
+ const mine = subjects.filter((s) => s.kind === kind.key);
88
+ const chosen = mine.find((s) => s.is_default) ?? mine[0];
89
+ if (chosen) {
90
+ active[kind.key] = { kind: kind.key, label: chosen.label, fit_key: chosen.fit_key, source: "account", subject: chosen };
91
+ continue;
92
+ }
93
+ const key = readSubjectFitKey(options.cookieHeader, kind);
94
+ const guest = key ? subjectFromFitKey(kind, key) : null;
95
+ if (key && guest) active[kind.key] = { kind: kind.key, label: guest.label, fit_key: key, source: "cookie" };
96
+ }
97
+ return { kinds, subjects, active, degraded: { kinds: false, subjects: degraded } };
98
+ }
99
+ var text = (v) => typeof v === "string" ? v.trim() : "";
100
+ function parseSubjectForm(form, privateKeys = [], prefix = "subject") {
101
+ const attributes = {};
102
+ const echo = {};
103
+ const head = `${prefix}.`;
104
+ for (const [name, value] of form.entries()) {
105
+ if (!name.startsWith(head) || name.endsWith(".clear")) continue;
106
+ const key = name.slice(head.length);
107
+ const v = text(value);
108
+ if (v) {
109
+ attributes[key] = v;
110
+ if (!privateKeys.includes(key)) echo[key] = v;
111
+ }
112
+ }
113
+ for (const key of privateKeys) if (form.get(`${head}${key}.clear`)) attributes[key] = null;
114
+ const intents = form.getAll("intent").map(text);
115
+ const intent = intents.includes("subject-refine") ? "subject-refine" : intents[0] ?? "";
116
+ const nick = form.has("nickname") ? text(form.get("nickname")) : void 0;
117
+ return {
118
+ intent: intent === "subject-save" || intent === "subject-delete" || intent === "subject-default" || intent === "subject-refine" ? intent : null,
119
+ id: text(form.get("id")),
120
+ kind: text(form.get("kind")),
121
+ attributes,
122
+ echo,
123
+ nickname: nick === void 0 ? void 0 : nick || null,
124
+ isDefault: !!form.get("is_default")
125
+ };
126
+ }
127
+ var fail = (intent, code, extra = {}) => ({
128
+ ok: false,
129
+ ...intent ? { intent } : {},
130
+ ...extra,
131
+ error: { code, ...extra.error?.field ? { field: extra.error.field } : {} }
132
+ });
133
+ var fieldOf = (field) => field ? field.replace(/^attributes\./, "") : void 0;
134
+ function problemOf(message) {
135
+ const m = (message ?? "").toLowerCase();
136
+ const coded = /:\s*(required|unknown|invalid|not_in_catalog|year_range|catalog_unavailable)\b/.exec(m)?.[1];
137
+ if (coded) return coded;
138
+ if (/:\s*required\b/.test(m)) return "required";
139
+ if (m.includes("unknown field")) return "unknown";
140
+ if (m.includes("not in the catalog")) return "not_in_catalog";
141
+ if (m.includes("outside the years")) return "year_range";
142
+ if (m.includes("catalog is not available")) return "catalog_unavailable";
143
+ return "invalid";
144
+ }
145
+ function toFailure(err, intent, values, id) {
146
+ const carry = { values, ...id ? { id } : {} };
147
+ if (!storefront.isStorefrontError(err)) return fail(intent, "unavailable", carry);
148
+ if (err.code === "insufficient_scope") return fail(intent, "store_misconfigured", carry);
149
+ switch (err.status) {
150
+ case 401:
151
+ return fail(intent, "unauthorized", carry);
152
+ case 404:
153
+ return fail(intent, "not_found", carry);
154
+ case 409:
155
+ return fail(intent, err.field === "kind" ? "limit" : "duplicate", carry);
156
+ case 429:
157
+ return fail(intent, "rate_limited", carry);
158
+ case 403:
159
+ return fail(intent, "unsupported", carry);
160
+ case 400:
161
+ case 422: {
162
+ const field = fieldOf(err.field);
163
+ const why = problemOf(err.message);
164
+ return fail(intent, err.field === "kind" ? "unsupported" : "validation", {
165
+ ...carry,
166
+ ...field ? { fieldErrors: { [field]: why }, error: { code: "validation", field } } : {}
167
+ });
168
+ }
169
+ default:
170
+ return fail(intent, "unavailable", carry);
171
+ }
172
+ }
173
+ function sameSite(request) {
174
+ const site = request.headers.get("sec-fetch-site");
175
+ if (site && site !== "same-origin" && site !== "same-site" && site !== "none") return false;
176
+ const origin = request.headers.get("origin");
177
+ if (!origin) return true;
178
+ try {
179
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? new URL(request.url).host;
180
+ return new URL(origin).host === host.split(",")[0].trim();
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+ function createSubjectAction(options) {
186
+ const { storefront: sf } = options;
187
+ const prefix = options.namePrefix ?? "subject";
188
+ async function token(request) {
189
+ if (options.readCustomerToken) return await options.readCustomerToken(request) || null;
190
+ if (!options.customerCookie) return null;
191
+ const v = await options.customerCookie.parse(request.headers.get("cookie"));
192
+ return typeof v === "string" && v ? v : null;
193
+ }
194
+ return async function subjectAction({ request }) {
195
+ if (options.sameSiteOnly !== false && !sameSite(request)) return fail(void 0, "unsupported");
196
+ const form = await request.formData().catch(() => null);
197
+ if (!form) return fail(void 0, "validation");
198
+ const tok = await token(request);
199
+ const ip = options.clientIp?.(request);
200
+ const w = { ...ip ? { clientIp: ip } : {}, ...tok ? { customerToken: tok } : {} };
201
+ const rawKind = text(form.get("kind"));
202
+ let privateKeys = [];
203
+ try {
204
+ const kinds = await sf.content.subjectKinds();
205
+ privateKeys = kinds.find((k) => k.key === rawKind)?.fields.filter((f) => f.private).map((f) => f.key) ?? [];
206
+ } catch {
207
+ }
208
+ const p = parseSubjectForm(form, privateKeys, prefix);
209
+ const intent = p.intent;
210
+ if (!intent) return fail(void 0, "validation");
211
+ if (intent === "subject-refine") return { ok: false, intent, values: p.echo, ...p.id ? { id: p.id } : {} };
212
+ if (!tok) return fail(intent, "unauthorized", { values: p.echo, ...p.id ? { id: p.id } : {} });
213
+ try {
214
+ switch (intent) {
215
+ case "subject-save": {
216
+ if (!p.kind && !p.id) return fail(intent, "validation", { values: p.echo });
217
+ const saved = p.id ? await sf.customer.subjects.update(p.id, { attributes: p.attributes, ...p.nickname !== void 0 ? { nickname: p.nickname } : {}, ...p.isDefault ? { is_default: true } : {} }, w) : await sf.customer.subjects.create({ kind: p.kind, attributes: p.attributes, ...p.nickname !== void 0 ? { nickname: p.nickname } : {}, ...p.isDefault ? { is_default: true } : {} }, w);
218
+ return { ok: true, intent, subject: saved };
219
+ }
220
+ case "subject-delete": {
221
+ if (!p.id) return fail(intent, "validation");
222
+ await sf.customer.subjects.delete(p.id, w);
223
+ return { ok: true, intent };
224
+ }
225
+ case "subject-default": {
226
+ if (!p.id) return fail(intent, "validation");
227
+ return { ok: true, intent, subject: await sf.customer.subjects.update(p.id, { is_default: true }, w) };
228
+ }
229
+ }
230
+ } catch (err) {
231
+ return toFailure(err, intent, p.echo, p.id);
232
+ }
233
+ };
234
+ }
235
+
236
+ exports.NO_SUBJECTS = NO_SUBJECTS;
237
+ exports.SUBJECT_COOKIE_PREFIX = SUBJECT_COOKIE_PREFIX;
238
+ exports.createSubjectAction = createSubjectAction;
239
+ exports.hasActiveSubject = hasActiveSubject;
240
+ exports.loadSubjectsContext = loadSubjectsContext;
241
+ exports.parseSubjectForm = parseSubjectForm;
242
+ exports.readSubjectFitKey = readSubjectFitKey;
243
+ exports.serializeSubjectCookie = serializeSubjectCookie;
244
+ exports.subjectCacheHeaders = subjectCacheHeaders;
245
+ exports.subjectCookieName = subjectCookieName;
246
+ exports.subjectFromFitKey = subjectFromFitKey;
247
+ //# sourceMappingURL=subjects-server.cjs.map
248
+ //# sourceMappingURL=subjects-server.cjs.map