@asteby/storefront-react 0.25.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 +252 -7
  9. package/dist/account.cjs.map +1 -1
  10. package/dist/account.css +91 -0
  11. package/dist/account.d.cts +130 -6
  12. package/dist/account.d.ts +130 -6
  13. package/dist/account.js +247 -8
  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
@@ -0,0 +1,25 @@
1
+ import { resolveSubject } from '@asteby/storefront';
2
+
3
+ // src/subjects/fit-key.ts
4
+ var KEY_RE = /^[a-z0-9._+/-]{1,100}$/;
5
+ var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6
+ function subjectFromFitKey(kind, fitKey) {
7
+ if (!fitKey || !KEY_RE.test(fitKey) || !kind.fit_key_template) return null;
8
+ const names = [];
9
+ const pattern = kind.fit_key_template.split(/(\{[a-z][a-z0-9_]*\})/).map((part) => {
10
+ const m = /^\{([a-z][a-z0-9_]*)\}$/.exec(part);
11
+ if (!m) return escape(part);
12
+ names.push(m[1]);
13
+ return "([^/]+)";
14
+ }).join("");
15
+ const match = new RegExp(`^${pattern}$`).exec(fitKey);
16
+ if (!match) return null;
17
+ const values = {};
18
+ names.forEach((n, i) => values[n] = match[i + 1]);
19
+ const r = resolveSubject(kind, values);
20
+ return r.ok && r.subject.fit_key === fitKey ? r.subject : null;
21
+ }
22
+
23
+ export { subjectFromFitKey };
24
+ //# sourceMappingURL=chunk-Z4C4LKXF.js.map
25
+ //# sourceMappingURL=chunk-Z4C4LKXF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/subjects/fit-key.ts"],"names":[],"mappings":";;;AAKA,IAAM,MAAA,GAAS,wBAAA;AAEf,IAAM,SAAS,CAAC,CAAA,KAAc,CAAA,CAAE,OAAA,CAAQ,uBAAuB,MAAM,CAAA;AAO9D,SAAS,iBAAA,CAAkB,MAAmB,MAAA,EAA2D;AAC9G,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,IAAK,CAAC,IAAA,CAAK,gBAAA,EAAkB,OAAO,IAAA;AACtE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,OAAA,GAAU,KAAK,gBAAA,CAClB,KAAA,CAAM,uBAAuB,CAAA,CAC7B,GAAA,CAAI,CAAC,IAAA,KAAS;AACb,IAAA,MAAM,CAAA,GAAI,yBAAA,CAA0B,IAAA,CAAK,IAAI,CAAA;AAC7C,IAAA,IAAI,CAAC,CAAA,EAAG,OAAO,MAAA,CAAO,IAAI,CAAA;AAC1B,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,CAAE,CAAC,CAAE,CAAA;AAChB,IAAA,OAAO,SAAA;AAAA,EACT,CAAC,CAAA,CACA,IAAA,CAAK,EAAE,CAAA;AACV,EAAA,MAAM,KAAA,GAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,CAAG,CAAA,CAAE,KAAK,MAAM,CAAA;AACpD,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,SAAiC,EAAC;AACxC,EAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAO,MAAA,CAAO,CAAC,CAAA,GAAI,KAAA,CAAM,CAAA,GAAI,CAAC,CAAG,CAAA;AACnD,EAAA,MAAM,CAAA,GAAI,cAAA,CAAe,IAAA,EAAM,MAAM,CAAA;AAErC,EAAA,OAAO,EAAE,EAAA,IAAM,CAAA,CAAE,QAAQ,OAAA,KAAY,MAAA,GAAS,EAAE,OAAA,GAAU,IAAA;AAC5D","file":"chunk-Z4C4LKXF.js","sourcesContent":["// The compatibility key of a saved item, read backwards. The guest keeps only `fit_key` in a cookie (never a plate or\n// anything private); to show its label and to filter by it, it is resolved against the kind's catalog again.\nimport { resolveSubject, type ResolvedSubject, type SubjectKind } from '@asteby/storefront'\n\n/** Shape of a key: what `fit_key` can hold (catalog ids and slugs joined by the template's separators). */\nconst KEY_RE = /^[a-z0-9._+/-]{1,100}$/\n\nconst escape = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Reads a key back into the item it names: the values are taken from the kind's template and validated against the\n * catalog like a saved item. `null` for a key that is not well formed, does not fit the template or does not resolve\n * (stale catalog, tampered cookie). Free (non-catalog) fields come back as their slug, which is all the key kept.\n */\nexport function subjectFromFitKey(kind: SubjectKind, fitKey: string | null | undefined): ResolvedSubject | null {\n if (!fitKey || !KEY_RE.test(fitKey) || !kind.fit_key_template) return null\n const names: string[] = []\n const pattern = kind.fit_key_template\n .split(/(\\{[a-z][a-z0-9_]*\\})/)\n .map((part) => {\n const m = /^\\{([a-z][a-z0-9_]*)\\}$/.exec(part)\n if (!m) return escape(part)\n names.push(m[1]!)\n return '([^/]+)'\n })\n .join('')\n const match = new RegExp(`^${pattern}$`).exec(fitKey)\n if (!match) return null\n const values: Record<string, string> = {}\n names.forEach((n, i) => (values[n] = match[i + 1]!))\n const r = resolveSubject(kind, values)\n // The key must be exactly the one the server would compute for those values.\n return r.ok && r.subject.fit_key === fitKey ? r.subject : null\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import * as _asteby_storefront from '@asteby/storefront';
2
+ import { SubjectProblemCode, SubjectKind, ResolvedSubject } from '@asteby/storefront';
3
+
4
+ /** What the buyer is doing. It is the `intent` field of the form. */
5
+ /** `subject-refine` (without JavaScript) only echoes the values so the form shows the next level of the cascade. */
6
+ type SubjectIntent = 'subject-save' | 'subject-delete' | 'subject-default' | 'subject-refine';
7
+ type SubjectErrorCode =
8
+ /** A field was rejected (`error.field` says which, `fieldErrors` carries the code per field). */
9
+ 'validation'
10
+ /** The item is already saved. */
11
+ | 'duplicate'
12
+ /** The kind's limit was reached. */
13
+ | 'limit' | 'rate_limited'
14
+ /** No session. */
15
+ | 'unauthorized'
16
+ /** The item is not the buyer's (or no longer exists). */
17
+ | 'not_found'
18
+ /** The store declares no such kind. */
19
+ | 'unsupported'
20
+ /** The store's own credential lacks a permission. */
21
+ | 'store_misconfigured'
22
+ /** Anything else (network, server). */
23
+ | 'unavailable';
24
+ /** What the action returns. Texts are NOT here: the components write them (i18n). Never carries a private value. */
25
+ type SubjectActionData = {
26
+ ok: boolean;
27
+ intent?: SubjectIntent;
28
+ /** The item the form was about (`id` of an edit); absent for a new one. Lets the page reopen the right form after an error. */
29
+ id?: string;
30
+ /** The item after a save. */
31
+ subject?: _asteby_storefront.Subject;
32
+ /** Public values the buyer typed, to refill the form after an error or a refinement (never a private one). */
33
+ values?: Record<string, string>;
34
+ /** Per form field name (`attributes.make`) → why. */
35
+ fieldErrors?: Record<string, SubjectProblemCode | 'invalid'>;
36
+ error?: {
37
+ code: SubjectErrorCode;
38
+ field?: string;
39
+ };
40
+ };
41
+
42
+ /**
43
+ * Reads a key back into the item it names: the values are taken from the kind's template and validated against the
44
+ * catalog like a saved item. `null` for a key that is not well formed, does not fit the template or does not resolve
45
+ * (stale catalog, tampered cookie). Free (non-catalog) fields come back as their slug, which is all the key kept.
46
+ */
47
+ declare function subjectFromFitKey(kind: SubjectKind, fitKey: string | null | undefined): ResolvedSubject | null;
48
+
49
+ export { type SubjectActionData as S, type SubjectErrorCode as a, type SubjectIntent as b, subjectFromFitKey as s };
@@ -0,0 +1,49 @@
1
+ import * as _asteby_storefront from '@asteby/storefront';
2
+ import { SubjectProblemCode, SubjectKind, ResolvedSubject } from '@asteby/storefront';
3
+
4
+ /** What the buyer is doing. It is the `intent` field of the form. */
5
+ /** `subject-refine` (without JavaScript) only echoes the values so the form shows the next level of the cascade. */
6
+ type SubjectIntent = 'subject-save' | 'subject-delete' | 'subject-default' | 'subject-refine';
7
+ type SubjectErrorCode =
8
+ /** A field was rejected (`error.field` says which, `fieldErrors` carries the code per field). */
9
+ 'validation'
10
+ /** The item is already saved. */
11
+ | 'duplicate'
12
+ /** The kind's limit was reached. */
13
+ | 'limit' | 'rate_limited'
14
+ /** No session. */
15
+ | 'unauthorized'
16
+ /** The item is not the buyer's (or no longer exists). */
17
+ | 'not_found'
18
+ /** The store declares no such kind. */
19
+ | 'unsupported'
20
+ /** The store's own credential lacks a permission. */
21
+ | 'store_misconfigured'
22
+ /** Anything else (network, server). */
23
+ | 'unavailable';
24
+ /** What the action returns. Texts are NOT here: the components write them (i18n). Never carries a private value. */
25
+ type SubjectActionData = {
26
+ ok: boolean;
27
+ intent?: SubjectIntent;
28
+ /** The item the form was about (`id` of an edit); absent for a new one. Lets the page reopen the right form after an error. */
29
+ id?: string;
30
+ /** The item after a save. */
31
+ subject?: _asteby_storefront.Subject;
32
+ /** Public values the buyer typed, to refill the form after an error or a refinement (never a private one). */
33
+ values?: Record<string, string>;
34
+ /** Per form field name (`attributes.make`) → why. */
35
+ fieldErrors?: Record<string, SubjectProblemCode | 'invalid'>;
36
+ error?: {
37
+ code: SubjectErrorCode;
38
+ field?: string;
39
+ };
40
+ };
41
+
42
+ /**
43
+ * Reads a key back into the item it names: the values are taken from the kind's template and validated against the
44
+ * catalog like a saved item. `null` for a key that is not well formed, does not fit the template or does not resolve
45
+ * (stale catalog, tampered cookie). Free (non-catalog) fields come back as their slug, which is all the key kept.
46
+ */
47
+ declare function subjectFromFitKey(kind: SubjectKind, fitKey: string | null | undefined): ResolvedSubject | null;
48
+
49
+ export { type SubjectActionData as S, type SubjectErrorCode as a, type SubjectIntent as b, subjectFromFitKey as s };
package/dist/fitment.cjs CHANGED
@@ -35,6 +35,32 @@ function makeMeta(m, aliases) {
35
35
  function labelFromId(id) {
36
36
  return id.split("-").filter(Boolean).join(" ").toUpperCase();
37
37
  }
38
+ function parseEngine(e) {
39
+ if (typeof e === "string") return isText(e) ? { id: e, label: labelFromId(e) } : null;
40
+ if (Array.isArray(e)) {
41
+ const [id, ...years] = e;
42
+ if (!isText(id)) return null;
43
+ const ranges2 = [];
44
+ for (let i = 0; i + 1 < years.length; i += 2) {
45
+ const a = years[i];
46
+ const b = years[i + 1];
47
+ if (isNum(a) && isNum(b)) ranges2.push([a, b]);
48
+ }
49
+ const first = ranges2[0];
50
+ const last = ranges2[ranges2.length - 1];
51
+ return first && last ? { id, label: labelFromId(id), from: first[0], to: last[1], ranges: ranges2 } : { id, label: labelFromId(id) };
52
+ }
53
+ const o = e;
54
+ if (!o || !isText(o.id)) return null;
55
+ const ranges = (Array.isArray(o.ranges) ? o.ranges : []).filter((r) => Array.isArray(r) && isNum(r[0]) && isNum(r[1]));
56
+ return {
57
+ id: o.id,
58
+ label: isText(o.label) ? o.label : labelFromId(o.id),
59
+ ...isNum(o.from) ? { from: o.from } : {},
60
+ ...isNum(o.to) ? { to: o.to } : {},
61
+ ...ranges.length ? { ranges } : {}
62
+ };
63
+ }
38
64
  function parseCatalog(raw, opts = {}) {
39
65
  let makesRaw = raw?.makes;
40
66
  if (typeof makesRaw === "string") {
@@ -52,12 +78,7 @@ function parseCatalog(raw, opts = {}) {
52
78
  for (const entry of m.models) {
53
79
  const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry;
54
80
  if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue;
55
- const engines = mo.engines.map((e) => typeof e === "string" ? { id: e, label: labelFromId(e) } : e).filter((e) => e && isText(e.id)).map((e) => ({
56
- id: e.id,
57
- label: isText(e.label) ? e.label : labelFromId(e.id),
58
- ...isNum(e.from) ? { from: e.from } : {},
59
- ...isNum(e.to) ? { to: e.to } : {}
60
- }));
81
+ const engines = mo.engines.map(parseEngine).filter((e) => e !== null);
61
82
  if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines });
62
83
  }
63
84
  if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) });
@@ -70,8 +91,8 @@ function vehicleKey(v) {
70
91
  function fitTag(key, config = DEFAULT_FITMENT_CONFIG) {
71
92
  return `${config.tagPrefix}${key}`;
72
93
  }
73
- var engineCovers = (e, model, year) => year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
74
- var modelCovers = (m, year) => year == null || year >= m.from && year <= m.to;
94
+ var engineCovers = (e, model, year) => e.ranges ? e.ranges.some(([a, b]) => year >= a && year <= b) : year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
95
+ var modelCovers = (m, year) => year == null || m.engines.some((e) => engineCovers(e, m, year));
75
96
  function num(v) {
76
97
  const n = Number(v);
77
98
  return v !== "" && Number.isInteger(n) ? n : null;
@@ -112,7 +133,7 @@ function yearOptions(catalog, draft) {
112
133
  const from = Math.min(...models.map((m) => m.from));
113
134
  const to = Math.max(...models.map((m) => m.to));
114
135
  const years = [];
115
- for (let y = to; y >= from; y--) years.push({ value: String(y), label: String(y) });
136
+ for (let y = to; y >= from; y--) if (models.some((m) => modelCovers(m, y))) years.push({ value: String(y), label: String(y) });
116
137
  return years;
117
138
  }
118
139
  function modelOptions(catalog, draft) {
@@ -210,6 +231,29 @@ function withoutQueryParam(path, param) {
210
231
  url.searchParams.delete(param);
211
232
  return `${url.pathname}${url.search}`;
212
233
  }
234
+ function vehicleCatalogToTree(catalog) {
235
+ const tree = {};
236
+ for (const make of catalog.makes) {
237
+ if (make.models.length === 0) continue;
238
+ const models = {};
239
+ for (const model of make.models) {
240
+ const engines = {};
241
+ let from = Infinity;
242
+ let to = -Infinity;
243
+ for (const e of model.engines) {
244
+ const spans = e.ranges?.length ? e.ranges : [[e.from ?? model.from, e.to ?? model.to]];
245
+ for (const [a, b] of spans) {
246
+ from = Math.min(from, a);
247
+ to = Math.max(to, b);
248
+ }
249
+ 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 };
250
+ }
251
+ models[model.id] = { label: model.label, from: Number.isFinite(from) ? from : model.from, to: Number.isFinite(to) ? to : model.to, children: engines };
252
+ }
253
+ tree[make.id] = { label: make.label, children: models };
254
+ }
255
+ return tree;
256
+ }
213
257
 
214
258
  exports.DEFAULT_FITMENT_CONFIG = DEFAULT_FITMENT_CONFIG;
215
259
  exports.EMPTY_DRAFT = EMPTY_DRAFT;
@@ -231,6 +275,7 @@ exports.resolveVehicle = resolveVehicle;
231
275
  exports.rowCoversYear = rowCoversYear;
232
276
  exports.safeRedirect = safeRedirect;
233
277
  exports.splitModelValue = splitModelValue;
278
+ exports.vehicleCatalogToTree = vehicleCatalogToTree;
234
279
  exports.vehicleFromDraft = vehicleFromDraft;
235
280
  exports.vehicleKey = vehicleKey;
236
281
  exports.vehicleLongName = vehicleLongName;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fitment/fitment.ts"],"names":["model"],"mappings":"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","file":"fitment.cjs","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.cjs","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"]}
@@ -1,8 +1,15 @@
1
+ import { SubjectCatalog } from '@asteby/storefront';
2
+
3
+ /**
4
+ * `ranges` are the stretches of model years in which the engine has parts ([from, to] pairs; a gap is a real gap).
5
+ * Without them the engine covers `from`–`to`, or the whole range of its model.
6
+ */
1
7
  type VehicleEngine = {
2
8
  id: string;
3
9
  label: string;
4
10
  from?: number;
5
11
  to?: number;
12
+ ranges?: [number, number][];
6
13
  };
7
14
  type VehicleModel = {
8
15
  id: string;
@@ -146,7 +153,10 @@ type WithAttributes = {
146
153
  declare function fitStatus(product: WithTags, vehicle: {
147
154
  key: string;
148
155
  } | null, config?: FitmentConfig): Fit;
149
- /** Values for a `filter.tag` query that lists only what fits (the API takes them as "any of"). */
156
+ /**
157
+ * Values for `products.list({ tags_any })` that list only what fits (own tag plus the universal one; the API takes them
158
+ * as "any of"). It replaces searching the tag with `q`. Same as `fitTagsAny` of `@asteby/storefront` for a catalog-driven kind.
159
+ */
150
160
  declare function fitFilterTags(vehicle: {
151
161
  key: string;
152
162
  }, config?: FitmentConfig): string[];
@@ -175,5 +185,13 @@ declare function fitState(product: WithTags, config?: FitmentConfig): Fit;
175
185
  declare function safeRedirect(target: string | null | undefined, fallback?: string): string;
176
186
  /** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */
177
187
  declare function withoutQueryParam(path: string, param: string): string;
188
+ /**
189
+ * The same make → model → engine catalog as the tree the organization publishes for a kind (`subject_catalog` page,
190
+ * `SubjectKind.catalog`): a parts store that already keeps its catalog in this shape converts it once, with a script,
191
+ * and the server validates the buyer's saved vehicles against it (years by `from`/`to`/`ranges`). A model covers the
192
+ * hull of its engines' years, so the tree accepts exactly the years `resolveVehicle` accepts. Makes without models are
193
+ * left out (they do not show in the selector either).
194
+ */
195
+ declare function vehicleCatalogToTree(catalog: VehicleCatalog): SubjectCatalog['tree'];
178
196
 
179
- export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
197
+ export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleCatalogToTree, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
package/dist/fitment.d.ts CHANGED
@@ -1,8 +1,15 @@
1
+ import { SubjectCatalog } from '@asteby/storefront';
2
+
3
+ /**
4
+ * `ranges` are the stretches of model years in which the engine has parts ([from, to] pairs; a gap is a real gap).
5
+ * Without them the engine covers `from`–`to`, or the whole range of its model.
6
+ */
1
7
  type VehicleEngine = {
2
8
  id: string;
3
9
  label: string;
4
10
  from?: number;
5
11
  to?: number;
12
+ ranges?: [number, number][];
6
13
  };
7
14
  type VehicleModel = {
8
15
  id: string;
@@ -146,7 +153,10 @@ type WithAttributes = {
146
153
  declare function fitStatus(product: WithTags, vehicle: {
147
154
  key: string;
148
155
  } | null, config?: FitmentConfig): Fit;
149
- /** Values for a `filter.tag` query that lists only what fits (the API takes them as "any of"). */
156
+ /**
157
+ * Values for `products.list({ tags_any })` that list only what fits (own tag plus the universal one; the API takes them
158
+ * as "any of"). It replaces searching the tag with `q`. Same as `fitTagsAny` of `@asteby/storefront` for a catalog-driven kind.
159
+ */
150
160
  declare function fitFilterTags(vehicle: {
151
161
  key: string;
152
162
  }, config?: FitmentConfig): string[];
@@ -175,5 +185,13 @@ declare function fitState(product: WithTags, config?: FitmentConfig): Fit;
175
185
  declare function safeRedirect(target: string | null | undefined, fallback?: string): string;
176
186
  /** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */
177
187
  declare function withoutQueryParam(path: string, param: string): string;
188
+ /**
189
+ * The same make → model → engine catalog as the tree the organization publishes for a kind (`subject_catalog` page,
190
+ * `SubjectKind.catalog`): a parts store that already keeps its catalog in this shape converts it once, with a script,
191
+ * and the server validates the buyer's saved vehicles against it (years by `from`/`to`/`ranges`). A model covers the
192
+ * hull of its engines' years, so the tree accepts exactly the years `resolveVehicle` accepts. Makes without models are
193
+ * left out (they do not show in the selector either).
194
+ */
195
+ declare function vehicleCatalogToTree(catalog: VehicleCatalog): SubjectCatalog['tree'];
178
196
 
179
- export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
197
+ export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleCatalogToTree, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
package/dist/fitment.js CHANGED
@@ -33,6 +33,32 @@ function makeMeta(m, aliases) {
33
33
  function labelFromId(id) {
34
34
  return id.split("-").filter(Boolean).join(" ").toUpperCase();
35
35
  }
36
+ function parseEngine(e) {
37
+ if (typeof e === "string") return isText(e) ? { id: e, label: labelFromId(e) } : null;
38
+ if (Array.isArray(e)) {
39
+ const [id, ...years] = e;
40
+ if (!isText(id)) return null;
41
+ const ranges2 = [];
42
+ for (let i = 0; i + 1 < years.length; i += 2) {
43
+ const a = years[i];
44
+ const b = years[i + 1];
45
+ if (isNum(a) && isNum(b)) ranges2.push([a, b]);
46
+ }
47
+ const first = ranges2[0];
48
+ const last = ranges2[ranges2.length - 1];
49
+ return first && last ? { id, label: labelFromId(id), from: first[0], to: last[1], ranges: ranges2 } : { id, label: labelFromId(id) };
50
+ }
51
+ const o = e;
52
+ if (!o || !isText(o.id)) return null;
53
+ const ranges = (Array.isArray(o.ranges) ? o.ranges : []).filter((r) => Array.isArray(r) && isNum(r[0]) && isNum(r[1]));
54
+ return {
55
+ id: o.id,
56
+ label: isText(o.label) ? o.label : labelFromId(o.id),
57
+ ...isNum(o.from) ? { from: o.from } : {},
58
+ ...isNum(o.to) ? { to: o.to } : {},
59
+ ...ranges.length ? { ranges } : {}
60
+ };
61
+ }
36
62
  function parseCatalog(raw, opts = {}) {
37
63
  let makesRaw = raw?.makes;
38
64
  if (typeof makesRaw === "string") {
@@ -50,12 +76,7 @@ function parseCatalog(raw, opts = {}) {
50
76
  for (const entry of m.models) {
51
77
  const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry;
52
78
  if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue;
53
- const engines = mo.engines.map((e) => typeof e === "string" ? { id: e, label: labelFromId(e) } : e).filter((e) => e && isText(e.id)).map((e) => ({
54
- id: e.id,
55
- label: isText(e.label) ? e.label : labelFromId(e.id),
56
- ...isNum(e.from) ? { from: e.from } : {},
57
- ...isNum(e.to) ? { to: e.to } : {}
58
- }));
79
+ const engines = mo.engines.map(parseEngine).filter((e) => e !== null);
59
80
  if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines });
60
81
  }
61
82
  if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) });
@@ -68,8 +89,8 @@ function vehicleKey(v) {
68
89
  function fitTag(key, config = DEFAULT_FITMENT_CONFIG) {
69
90
  return `${config.tagPrefix}${key}`;
70
91
  }
71
- var engineCovers = (e, model, year) => year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
72
- var modelCovers = (m, year) => year == null || year >= m.from && year <= m.to;
92
+ var engineCovers = (e, model, year) => e.ranges ? e.ranges.some(([a, b]) => year >= a && year <= b) : year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
93
+ var modelCovers = (m, year) => year == null || m.engines.some((e) => engineCovers(e, m, year));
73
94
  function num(v) {
74
95
  const n = Number(v);
75
96
  return v !== "" && Number.isInteger(n) ? n : null;
@@ -110,7 +131,7 @@ function yearOptions(catalog, draft) {
110
131
  const from = Math.min(...models.map((m) => m.from));
111
132
  const to = Math.max(...models.map((m) => m.to));
112
133
  const years = [];
113
- for (let y = to; y >= from; y--) years.push({ value: String(y), label: String(y) });
134
+ for (let y = to; y >= from; y--) if (models.some((m) => modelCovers(m, y))) years.push({ value: String(y), label: String(y) });
114
135
  return years;
115
136
  }
116
137
  function modelOptions(catalog, draft) {
@@ -208,7 +229,30 @@ function withoutQueryParam(path, param) {
208
229
  url.searchParams.delete(param);
209
230
  return `${url.pathname}${url.search}`;
210
231
  }
232
+ function vehicleCatalogToTree(catalog) {
233
+ const tree = {};
234
+ for (const make of catalog.makes) {
235
+ if (make.models.length === 0) continue;
236
+ const models = {};
237
+ for (const model of make.models) {
238
+ const engines = {};
239
+ let from = Infinity;
240
+ let to = -Infinity;
241
+ for (const e of model.engines) {
242
+ const spans = e.ranges?.length ? e.ranges : [[e.from ?? model.from, e.to ?? model.to]];
243
+ for (const [a, b] of spans) {
244
+ from = Math.min(from, a);
245
+ to = Math.max(to, b);
246
+ }
247
+ 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 };
248
+ }
249
+ models[model.id] = { label: model.label, from: Number.isFinite(from) ? from : model.from, to: Number.isFinite(to) ? to : model.to, children: engines };
250
+ }
251
+ tree[make.id] = { label: make.label, children: models };
252
+ }
253
+ return tree;
254
+ }
211
255
 
212
- export { DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
256
+ export { DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleCatalogToTree, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
213
257
  //# sourceMappingURL=fitment.js.map
214
258
  //# sourceMappingURL=fitment.js.map