@numueg/theme-sdk 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KWODVI7F.cjs → chunk-O4GLTNLE.cjs} +3 -3
- package/dist/{chunk-KWODVI7F.cjs.map → chunk-O4GLTNLE.cjs.map} +1 -1
- package/dist/{chunk-EB2ESBLD.mjs → chunk-QFFTHIFZ.mjs} +3 -3
- package/dist/{chunk-EB2ESBLD.mjs.map → chunk-QFFTHIFZ.mjs.map} +1 -1
- package/dist/{chunk-IBNAZRUT.cjs → chunk-V3JDQXD3.cjs} +2 -2
- package/dist/chunk-V3JDQXD3.cjs.map +1 -0
- package/dist/{chunk-4TMO5Y5W.mjs → chunk-ZYZZG4JR.mjs} +2 -2
- package/dist/chunk-ZYZZG4JR.mjs.map +1 -0
- package/dist/index.cjs +78 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +16 -8
- package/dist/index.mjs.map +1 -1
- package/dist/{mount-eAhGgGtZ.d.mts → mount-BDo4a42I.d.mts} +5 -0
- package/dist/{mount-HbnOAYZ-.d.ts → mount-DQZu8aBB.d.ts} +5 -0
- package/dist/v2-compat.cjs +6 -6
- package/dist/v2-compat.mjs +1 -1
- package/dist/validation.cjs +11 -11
- package/dist/validation.mjs +1 -1
- package/dist/verify.cjs +3 -3
- package/dist/verify.d.mts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-4TMO5Y5W.mjs.map +0 -1
- package/dist/chunk-IBNAZRUT.cjs.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/validation/index.ts
|
|
4
4
|
var THEME_CONTRACT_VERSION = 1;
|
|
5
|
-
var SDK_VERSION = "0.10.
|
|
5
|
+
var SDK_VERSION = "0.10.1" ;
|
|
6
6
|
var REQUIRED_TEMPLATES = [
|
|
7
7
|
"home",
|
|
8
8
|
"product",
|
|
@@ -404,5 +404,5 @@ exports.validateBuiltManifest = validateBuiltManifest;
|
|
|
404
404
|
exports.validateManifest = validateManifest;
|
|
405
405
|
exports.validateSectionSchema = validateSectionSchema;
|
|
406
406
|
exports.validateSettingsAgainstSchema = validateSettingsAgainstSchema;
|
|
407
|
-
//# sourceMappingURL=chunk-
|
|
408
|
-
//# sourceMappingURL=chunk-
|
|
407
|
+
//# sourceMappingURL=chunk-O4GLTNLE.cjs.map
|
|
408
|
+
//# sourceMappingURL=chunk-O4GLTNLE.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/validation/index.ts"],"names":[],"mappings":"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","file":"chunk-KWODVI7F.cjs","sourcesContent":["/**\n * Shared theme-contract validators — the single source of truth.\n *\n * This module is PURE: it imports only types (erased at compile time) and\n * has no React/DOM/fs dependencies, so the same logic runs in the CLI, the\n * Vite plugin, and (re-implemented) the backend. Callers parse JSON files\n * themselves and pass plain objects in; these functions never touch disk.\n *\n * The goal is that \"builds green\" implies \"matches the contract\": every\n * place a theme can drift from what the storefront expects is checked here\n * once, instead of being re-derived (and drifting) in each tool.\n */\n\nimport type { SectionSchema, SettingDefinition } from \"../types/theme\";\n\n/**\n * The theme CONTRACT version — bumped only on a breaking change to what a\n * built theme must look like (manifest shape, required emitted artifacts,\n * section/setting semantics). The plugin stamps this into the built\n * manifest/import-map; the host refuses bundles whose contract version it\n * doesn't support. Distinct from the npm SDK version (`SDK_VERSION`), which\n * moves on every release including non-breaking ones.\n */\nexport const THEME_CONTRACT_VERSION = 1;\n\n/** npm version of this SDK build (inlined by tsup `define`). Informational. */\ndeclare const __SDK_VERSION__: string | undefined;\nexport const SDK_VERSION: string =\n typeof __SDK_VERSION__ === \"string\" ? __SDK_VERSION__ : \"0.0.0\";\n\nexport interface ValidationIssue {\n level: \"error\" | \"warning\";\n /** Stable machine code, e.g. \"manifest.version.invalid\". */\n code: string;\n message: string;\n /** Dotted path or file hint, e.g. \"presets.templates.home\" or \"hero.json\". */\n path?: string;\n}\n\nexport interface ValidationResult {\n /** True when there are zero error-level issues. */\n valid: boolean;\n issues: ValidationIssue[];\n}\n\n/**\n * Page templates the storefront routes to. A theme that omits a REQUIRED\n * template still works (the host falls back to its built-in), so a miss is\n * a warning, not an error — but tooling can elevate it with --strict.\n */\nexport const REQUIRED_TEMPLATES = [\n \"home\",\n \"product\",\n \"collection\",\n \"cart\",\n \"page\",\n \"search\",\n \"404\",\n] as const;\n\nexport const KNOWN_TEMPLATES: ReadonlySet<string> = new Set([\n ...REQUIRED_TEMPLATES,\n \"blog\",\n \"article\",\n \"policies\",\n \"password\",\n \"account\",\n \"checkout\",\n]);\n\n/** Setting types the V3 customizer renders. Unknown types warn (forward-compat). */\nexport const KNOWN_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"text\", \"textarea\", \"richtext\", \"number\", \"range\", \"color\", \"checkbox\",\n \"select\", \"radio\", \"font\", \"image_picker\", \"url\", \"product\", \"product_list\",\n \"collection\", \"collection_list\", \"header\", \"paragraph\", \"html\", \"date\",\n \"time\", \"video_picker\", \"color_scheme\", \"page_picker\", \"blog_picker\",\n \"link_list_picker\", \"variant_picker\", \"file_upload\", \"icon_picker\", \"icon\",\n]);\n\n/** Setting types that are presentational (no value) — `id`/`label` optional. */\nconst PRESENTATIONAL_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"header\", \"paragraph\", \"html\",\n]);\n\n// ── Primitives ────────────────────────────────────────────────────\n\nconst SECTION_TYPE_RE = /^[a-z][a-z0-9_-]*$/;\nconst THEME_ID_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/;\n// Pragmatic semver (major.minor.patch + optional -prerelease / +build).\nconst SEMVER_RE =\n /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?$/;\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction isNonEmptyString(v: unknown): v is string {\n return typeof v === \"string\" && v.trim().length > 0;\n}\n\nclass IssueBag {\n readonly issues: ValidationIssue[] = [];\n err(code: string, message: string, path?: string) {\n this.issues.push({ level: \"error\", code, message, path });\n }\n warn(code: string, message: string, path?: string) {\n this.issues.push({ level: \"warning\", code, message, path });\n }\n result(): ValidationResult {\n return {\n valid: !this.issues.some((i) => i.level === \"error\"),\n issues: this.issues,\n };\n }\n}\n\n/** Pull the section types referenced by a manifest's presets. */\nfunction collectPresetSectionTypes(manifest: Record<string, unknown>): Set<string> {\n const types = new Set<string>();\n const presets = manifest.presets;\n if (!isObject(presets)) return types;\n const buckets = [presets.templates, presets.section_groups];\n for (const bucket of buckets) {\n if (!isObject(bucket)) continue;\n for (const entry of Object.values(bucket)) {\n if (!isObject(entry)) continue;\n const sections = entry.sections;\n // sections may be an array of instances OR a map id -> instance.\n const instances = Array.isArray(sections)\n ? sections\n : isObject(sections)\n ? Object.values(sections)\n : [];\n for (const inst of instances) {\n if (isObject(inst) && isNonEmptyString(inst.type)) types.add(inst.type);\n }\n }\n }\n return types;\n}\n\n// ── Setting / schema validation ───────────────────────────────────\n\nfunction validateSettingInto(\n bag: IssueBag,\n setting: unknown,\n pathPrefix: string,\n seenIds: Set<string>,\n) {\n if (!isObject(setting)) {\n bag.err(\"schema.setting.invalid\", \"Setting must be an object.\", pathPrefix);\n return;\n }\n const type = setting.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.setting.type.missing\", \"Setting is missing a `type`.\", pathPrefix);\n } else if (!KNOWN_SETTING_TYPES.has(type)) {\n bag.warn(\n \"schema.setting.type.unknown\",\n `Unknown setting type \"${type}\" — the customizer will fall back to a text input.`,\n pathPrefix,\n );\n }\n const presentational =\n isNonEmptyString(type) && PRESENTATIONAL_SETTING_TYPES.has(type);\n if (!presentational) {\n if (!isNonEmptyString(setting.id)) {\n bag.err(\"schema.setting.id.missing\", \"Setting is missing an `id`.\", pathPrefix);\n } else {\n if (seenIds.has(setting.id)) {\n bag.err(\n \"schema.setting.id.duplicate\",\n `Duplicate setting id \"${setting.id}\".`,\n pathPrefix,\n );\n }\n seenIds.add(setting.id);\n }\n if (!isNonEmptyString(setting.label)) {\n bag.warn(\"schema.setting.label.missing\", \"Setting has no `label`.\", pathPrefix);\n }\n }\n // select / radio need options.\n if (type === \"select\" || type === \"radio\") {\n const options = setting.options;\n if (!Array.isArray(options) || options.length === 0) {\n bag.err(\n \"schema.setting.options.missing\",\n `\"${type}\" setting \"${String(setting.id)}\" must declare a non-empty \\`options\\` array.`,\n pathPrefix,\n );\n }\n }\n // range needs bounds.\n if (type === \"range\") {\n if (typeof setting.min !== \"number\" || typeof setting.max !== \"number\") {\n bag.err(\n \"schema.setting.range.bounds\",\n `\"range\" setting \"${String(setting.id)}\" must declare numeric \\`min\\` and \\`max\\`.`,\n pathPrefix,\n );\n } else if (setting.min >= setting.max) {\n bag.err(\n \"schema.setting.range.order\",\n `\"range\" setting \"${String(setting.id)}\" has min >= max.`,\n pathPrefix,\n );\n }\n }\n}\n\n/**\n * Validate one section schema (the parsed schemas/sections/<type>.json).\n * When `filenameType` is given, enforces the component-filename = schema-type\n * convention by requiring `schema.type` to equal it.\n */\nexport function validateSectionSchema(\n schema: unknown,\n opts: { filenameType?: string } = {},\n): ValidationResult {\n const bag = new IssueBag();\n const where = opts.filenameType ? `${opts.filenameType}.json` : \"section schema\";\n if (!isObject(schema)) {\n bag.err(\"schema.invalid\", \"Section schema must be a JSON object.\", where);\n return bag.result();\n }\n const type = schema.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.type.missing\", \"Section schema is missing a `type`.\", where);\n } else {\n if (!SECTION_TYPE_RE.test(type)) {\n bag.err(\n \"schema.type.format\",\n `Section type \"${type}\" must match ${SECTION_TYPE_RE} (lowercase, start with a letter).`,\n where,\n );\n }\n if (opts.filenameType && type !== opts.filenameType) {\n bag.err(\n \"schema.type.filename_mismatch\",\n `Schema type \"${type}\" must equal its filename \"${opts.filenameType}\" (component-filename = schema-type convention).`,\n where,\n );\n }\n }\n if (!isNonEmptyString(schema.name)) {\n bag.err(\"schema.name.missing\", \"Section schema is missing a `name`.\", where);\n }\n if (schema.settings === undefined) {\n bag.warn(\"schema.settings.missing\", \"Section schema has no `settings`.\", where);\n } else if (!Array.isArray(schema.settings)) {\n bag.err(\"schema.settings.invalid\", \"`settings` must be an array.\", where);\n } else {\n const seen = new Set<string>();\n schema.settings.forEach((s, i) =>\n validateSettingInto(bag, s, `${where}.settings[${i}]`, seen),\n );\n }\n // Nested block schemas (shallow — type + settings shape).\n if (schema.blocks !== undefined) {\n if (!Array.isArray(schema.blocks)) {\n bag.err(\"schema.blocks.invalid\", \"`blocks` must be an array.\", where);\n } else {\n schema.blocks.forEach((b, i) => {\n if (!isObject(b) || !isNonEmptyString(b.type)) {\n bag.err(\"schema.block.type.missing\", `Block[${i}] is missing a \\`type\\`.`, where);\n return;\n }\n if (!SECTION_TYPE_RE.test(b.type)) {\n bag.err(\n \"schema.block.type.format\",\n `Block type \"${b.type}\" must match ${SECTION_TYPE_RE}.`,\n where,\n );\n }\n if (Array.isArray(b.settings)) {\n const seen = new Set<string>();\n b.settings.forEach((s, j) =>\n validateSettingInto(bag, s, `${where}.blocks[${i}].settings[${j}]`, seen),\n );\n }\n });\n }\n }\n return bag.result();\n}\n\n/**\n * Instance conformance: check a settings object (from a preset or a merchant\n * customization) against its section schema. Unknown keys warn (harmless at\n * runtime — ignored); invalid values (bad select option, out-of-range,\n * wrong primitive) error.\n */\nexport function validateSettingsAgainstSchema(\n settings: unknown,\n schema: Pick<SectionSchema, \"settings\" | \"type\">,\n pathPrefix = \"settings\",\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(settings)) {\n bag.err(\"instance.settings.invalid\", \"Section settings must be an object.\", pathPrefix);\n return bag.result();\n }\n const defs = Array.isArray(schema.settings) ? schema.settings : [];\n const byId = new Map<string, SettingDefinition>();\n for (const d of defs) if (isNonEmptyString(d?.id)) byId.set(d.id, d);\n\n for (const [key, value] of Object.entries(settings)) {\n const def = byId.get(key);\n if (!def) {\n bag.warn(\n \"instance.setting.unknown\",\n `Setting \"${key}\" is not declared in the \"${String(schema.type)}\" schema.`,\n `${pathPrefix}.${key}`,\n );\n continue;\n }\n if (value === null || value === undefined) continue;\n const p = `${pathPrefix}.${key}`;\n if ((def.type === \"select\" || def.type === \"radio\") && Array.isArray(def.options)) {\n const allowed = def.options.map((o) => o.value);\n if (typeof value === \"string\" && !allowed.includes(value)) {\n bag.err(\n \"instance.setting.option.invalid\",\n `\"${key}\" = \"${value}\" is not one of: ${allowed.join(\", \")}.`,\n p,\n );\n }\n }\n if (def.type === \"range\" && typeof value === \"number\") {\n if (typeof def.min === \"number\" && value < def.min) {\n bag.err(\"instance.setting.range.under\", `\"${key}\" = ${value} is below min ${def.min}.`, p);\n }\n if (typeof def.max === \"number\" && value > def.max) {\n bag.err(\"instance.setting.range.over\", `\"${key}\" = ${value} is above max ${def.max}.`, p);\n }\n }\n if ((def.type === \"checkbox\") && typeof value !== \"boolean\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a boolean.`, p);\n }\n if ((def.type === \"number\" || def.type === \"range\") && typeof value !== \"number\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a number.`, p);\n }\n }\n return bag.result();\n}\n\n// ── Manifest validation ───────────────────────────────────────────\n\n/** Core manifest field checks shared by source (theme.json) and built manifests. */\nfunction validateManifestCore(bag: IssueBag, m: Record<string, unknown>) {\n if (!isNonEmptyString(m.id)) {\n bag.err(\"manifest.id.missing\", \"theme.json is missing `id`.\", \"id\");\n } else if (!THEME_ID_RE.test(m.id)) {\n bag.err(\n \"manifest.id.format\",\n `Theme id \"${m.id}\" must be lowercase alphanumeric with dashes/underscores (no leading/trailing separator).`,\n \"id\",\n );\n }\n // name: string or bilingual object { en|default, ... }.\n const name = m.name;\n const nameOk =\n isNonEmptyString(name) ||\n (isObject(name) && Object.values(name).some((v) => isNonEmptyString(v)));\n if (!nameOk) {\n bag.err(\"manifest.name.missing\", \"theme.json is missing a non-empty `name`.\", \"name\");\n }\n if (!isNonEmptyString(m.version)) {\n bag.err(\"manifest.version.missing\", \"theme.json is missing `version`.\", \"version\");\n } else if (!SEMVER_RE.test(m.version)) {\n bag.err(\"manifest.version.invalid\", `Version \"${m.version}\" is not valid semver.`, \"version\");\n }\n if (!isNonEmptyString(m.author)) {\n bag.err(\"manifest.author.missing\", \"theme.json is missing `author`.\", \"author\");\n }\n if (m.min_sdk_version !== undefined && !SEMVER_RE.test(String(m.min_sdk_version))) {\n bag.warn(\"manifest.min_sdk_version.invalid\", \"`min_sdk_version` is not valid semver.\", \"min_sdk_version\");\n }\n}\n\n/**\n * Validate a source theme manifest (parsed theme.json).\n *\n * `ctx.sectionTypes` — the set of section types the theme actually ships\n * (from schemas/sections/*.json). When provided, every section type\n * referenced by a preset must be present, and missing required templates\n * are surfaced as warnings.\n */\nexport function validateManifest(\n manifest: unknown,\n ctx: { sectionTypes?: ReadonlySet<string> } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(manifest)) {\n bag.err(\"manifest.invalid\", \"theme.json must be a JSON object.\", \"theme.json\");\n return bag.result();\n }\n validateManifestCore(bag, manifest);\n\n const presets = manifest.presets;\n if (!isObject(presets) || Object.keys(presets).length === 0) {\n bag.warn(\"manifest.presets.empty\", \"theme.json has no presets — merchants start with an empty page.\", \"presets\");\n } else {\n // Every referenced section type must resolve to a shipped schema.\n if (ctx.sectionTypes) {\n for (const type of collectPresetSectionTypes(manifest)) {\n if (!ctx.sectionTypes.has(type)) {\n bag.err(\n \"manifest.preset.unknown_section\",\n `Preset references section type \"${type}\" with no schemas/sections/${type}.json.`,\n \"presets\",\n );\n }\n }\n }\n // Required-template coverage (warning).\n const templates = isObject(presets.templates) ? presets.templates : {};\n for (const t of REQUIRED_TEMPLATES) {\n if (!(t in templates)) {\n bag.warn(\n \"manifest.template.missing\",\n `No preset for required template \"${t}\" — the storefront will fall back to its built-in.`,\n `presets.templates.${t}`,\n );\n }\n }\n }\n return bag.result();\n}\n\n/**\n * Server-side gate: validate the artifacts the plugin emits — dist/manifest.json\n * (the embedded built manifest, including section_schemas) and dist/import-map.json\n * (the federation + contract-version descriptor). This is what the backend should\n * run on upload/submit, since it can't run the source build.\n *\n * `opts.hostContractVersion` — when given, refuses bundles whose\n * `contract_version` the host doesn't support.\n */\nexport function validateBuiltManifest(\n builtManifest: unknown,\n importMap?: unknown,\n opts: { hostContractVersion?: number } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(builtManifest)) {\n bag.err(\"built.invalid\", \"manifest.json must be a JSON object.\", \"manifest.json\");\n return bag.result();\n }\n validateManifestCore(bag, builtManifest);\n\n // section_schemas must cover every type the presets reference.\n const schemas = builtManifest.section_schemas;\n const shipped = new Set<string>(isObject(schemas) ? Object.keys(schemas) : []);\n if (!isObject(schemas)) {\n bag.warn(\"built.section_schemas.missing\", \"manifest.json has no `section_schemas`.\", \"section_schemas\");\n }\n for (const type of collectPresetSectionTypes(builtManifest)) {\n if (!shipped.has(type)) {\n bag.err(\n \"built.preset.unknown_section\",\n `Preset references section type \"${type}\" not present in section_schemas.`,\n \"presets\",\n );\n }\n }\n // Each shipped schema must itself be valid.\n if (isObject(schemas)) {\n for (const [type, schema] of Object.entries(schemas)) {\n const r = validateSectionSchema(schema, { filenameType: type });\n for (const issue of r.issues) bag.issues.push(issue);\n }\n }\n if (!isNonEmptyString(builtManifest.plugin_version)) {\n bag.warn(\"built.plugin_version.missing\", \"manifest.json has no `plugin_version`.\", \"plugin_version\");\n }\n\n // Contract-version compatibility from the import map.\n if (importMap !== undefined) {\n if (!isObject(importMap)) {\n bag.err(\"importmap.invalid\", \"import-map.json must be a JSON object.\", \"import-map.json\");\n } else {\n const cv = importMap.contract_version;\n if (typeof cv !== \"number\") {\n bag.warn(\n \"importmap.contract_version.missing\",\n \"import-map.json has no numeric `contract_version` — built by an older plugin.\",\n \"contract_version\",\n );\n } else if (\n typeof opts.hostContractVersion === \"number\" &&\n cv > opts.hostContractVersion\n ) {\n bag.err(\n \"importmap.contract_version.incompatible\",\n `Theme built for contract v${cv}; this platform supports up to v${opts.hostContractVersion}.`,\n \"contract_version\",\n );\n }\n }\n }\n return bag.result();\n}\n\n/** Convenience: merge several results into one. */\nexport function mergeResults(...results: ValidationResult[]): ValidationResult {\n const issues = results.flatMap((r) => r.issues);\n return { valid: !issues.some((i) => i.level === \"error\"), issues };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/validation/index.ts"],"names":[],"mappings":"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","file":"chunk-O4GLTNLE.cjs","sourcesContent":["/**\n * Shared theme-contract validators — the single source of truth.\n *\n * This module is PURE: it imports only types (erased at compile time) and\n * has no React/DOM/fs dependencies, so the same logic runs in the CLI, the\n * Vite plugin, and (re-implemented) the backend. Callers parse JSON files\n * themselves and pass plain objects in; these functions never touch disk.\n *\n * The goal is that \"builds green\" implies \"matches the contract\": every\n * place a theme can drift from what the storefront expects is checked here\n * once, instead of being re-derived (and drifting) in each tool.\n */\n\nimport type { SectionSchema, SettingDefinition } from \"../types/theme\";\n\n/**\n * The theme CONTRACT version — bumped only on a breaking change to what a\n * built theme must look like (manifest shape, required emitted artifacts,\n * section/setting semantics). The plugin stamps this into the built\n * manifest/import-map; the host refuses bundles whose contract version it\n * doesn't support. Distinct from the npm SDK version (`SDK_VERSION`), which\n * moves on every release including non-breaking ones.\n */\nexport const THEME_CONTRACT_VERSION = 1;\n\n/** npm version of this SDK build (inlined by tsup `define`). Informational. */\ndeclare const __SDK_VERSION__: string | undefined;\nexport const SDK_VERSION: string =\n typeof __SDK_VERSION__ === \"string\" ? __SDK_VERSION__ : \"0.0.0\";\n\nexport interface ValidationIssue {\n level: \"error\" | \"warning\";\n /** Stable machine code, e.g. \"manifest.version.invalid\". */\n code: string;\n message: string;\n /** Dotted path or file hint, e.g. \"presets.templates.home\" or \"hero.json\". */\n path?: string;\n}\n\nexport interface ValidationResult {\n /** True when there are zero error-level issues. */\n valid: boolean;\n issues: ValidationIssue[];\n}\n\n/**\n * Page templates the storefront routes to. A theme that omits a REQUIRED\n * template still works (the host falls back to its built-in), so a miss is\n * a warning, not an error — but tooling can elevate it with --strict.\n */\nexport const REQUIRED_TEMPLATES = [\n \"home\",\n \"product\",\n \"collection\",\n \"cart\",\n \"page\",\n \"search\",\n \"404\",\n] as const;\n\nexport const KNOWN_TEMPLATES: ReadonlySet<string> = new Set([\n ...REQUIRED_TEMPLATES,\n \"blog\",\n \"article\",\n \"policies\",\n \"password\",\n \"account\",\n \"checkout\",\n]);\n\n/** Setting types the V3 customizer renders. Unknown types warn (forward-compat). */\nexport const KNOWN_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"text\", \"textarea\", \"richtext\", \"number\", \"range\", \"color\", \"checkbox\",\n \"select\", \"radio\", \"font\", \"image_picker\", \"url\", \"product\", \"product_list\",\n \"collection\", \"collection_list\", \"header\", \"paragraph\", \"html\", \"date\",\n \"time\", \"video_picker\", \"color_scheme\", \"page_picker\", \"blog_picker\",\n \"link_list_picker\", \"variant_picker\", \"file_upload\", \"icon_picker\", \"icon\",\n]);\n\n/** Setting types that are presentational (no value) — `id`/`label` optional. */\nconst PRESENTATIONAL_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"header\", \"paragraph\", \"html\",\n]);\n\n// ── Primitives ────────────────────────────────────────────────────\n\nconst SECTION_TYPE_RE = /^[a-z][a-z0-9_-]*$/;\nconst THEME_ID_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/;\n// Pragmatic semver (major.minor.patch + optional -prerelease / +build).\nconst SEMVER_RE =\n /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?$/;\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction isNonEmptyString(v: unknown): v is string {\n return typeof v === \"string\" && v.trim().length > 0;\n}\n\nclass IssueBag {\n readonly issues: ValidationIssue[] = [];\n err(code: string, message: string, path?: string) {\n this.issues.push({ level: \"error\", code, message, path });\n }\n warn(code: string, message: string, path?: string) {\n this.issues.push({ level: \"warning\", code, message, path });\n }\n result(): ValidationResult {\n return {\n valid: !this.issues.some((i) => i.level === \"error\"),\n issues: this.issues,\n };\n }\n}\n\n/** Pull the section types referenced by a manifest's presets. */\nfunction collectPresetSectionTypes(manifest: Record<string, unknown>): Set<string> {\n const types = new Set<string>();\n const presets = manifest.presets;\n if (!isObject(presets)) return types;\n const buckets = [presets.templates, presets.section_groups];\n for (const bucket of buckets) {\n if (!isObject(bucket)) continue;\n for (const entry of Object.values(bucket)) {\n if (!isObject(entry)) continue;\n const sections = entry.sections;\n // sections may be an array of instances OR a map id -> instance.\n const instances = Array.isArray(sections)\n ? sections\n : isObject(sections)\n ? Object.values(sections)\n : [];\n for (const inst of instances) {\n if (isObject(inst) && isNonEmptyString(inst.type)) types.add(inst.type);\n }\n }\n }\n return types;\n}\n\n// ── Setting / schema validation ───────────────────────────────────\n\nfunction validateSettingInto(\n bag: IssueBag,\n setting: unknown,\n pathPrefix: string,\n seenIds: Set<string>,\n) {\n if (!isObject(setting)) {\n bag.err(\"schema.setting.invalid\", \"Setting must be an object.\", pathPrefix);\n return;\n }\n const type = setting.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.setting.type.missing\", \"Setting is missing a `type`.\", pathPrefix);\n } else if (!KNOWN_SETTING_TYPES.has(type)) {\n bag.warn(\n \"schema.setting.type.unknown\",\n `Unknown setting type \"${type}\" — the customizer will fall back to a text input.`,\n pathPrefix,\n );\n }\n const presentational =\n isNonEmptyString(type) && PRESENTATIONAL_SETTING_TYPES.has(type);\n if (!presentational) {\n if (!isNonEmptyString(setting.id)) {\n bag.err(\"schema.setting.id.missing\", \"Setting is missing an `id`.\", pathPrefix);\n } else {\n if (seenIds.has(setting.id)) {\n bag.err(\n \"schema.setting.id.duplicate\",\n `Duplicate setting id \"${setting.id}\".`,\n pathPrefix,\n );\n }\n seenIds.add(setting.id);\n }\n if (!isNonEmptyString(setting.label)) {\n bag.warn(\"schema.setting.label.missing\", \"Setting has no `label`.\", pathPrefix);\n }\n }\n // select / radio need options.\n if (type === \"select\" || type === \"radio\") {\n const options = setting.options;\n if (!Array.isArray(options) || options.length === 0) {\n bag.err(\n \"schema.setting.options.missing\",\n `\"${type}\" setting \"${String(setting.id)}\" must declare a non-empty \\`options\\` array.`,\n pathPrefix,\n );\n }\n }\n // range needs bounds.\n if (type === \"range\") {\n if (typeof setting.min !== \"number\" || typeof setting.max !== \"number\") {\n bag.err(\n \"schema.setting.range.bounds\",\n `\"range\" setting \"${String(setting.id)}\" must declare numeric \\`min\\` and \\`max\\`.`,\n pathPrefix,\n );\n } else if (setting.min >= setting.max) {\n bag.err(\n \"schema.setting.range.order\",\n `\"range\" setting \"${String(setting.id)}\" has min >= max.`,\n pathPrefix,\n );\n }\n }\n}\n\n/**\n * Validate one section schema (the parsed schemas/sections/<type>.json).\n * When `filenameType` is given, enforces the component-filename = schema-type\n * convention by requiring `schema.type` to equal it.\n */\nexport function validateSectionSchema(\n schema: unknown,\n opts: { filenameType?: string } = {},\n): ValidationResult {\n const bag = new IssueBag();\n const where = opts.filenameType ? `${opts.filenameType}.json` : \"section schema\";\n if (!isObject(schema)) {\n bag.err(\"schema.invalid\", \"Section schema must be a JSON object.\", where);\n return bag.result();\n }\n const type = schema.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.type.missing\", \"Section schema is missing a `type`.\", where);\n } else {\n if (!SECTION_TYPE_RE.test(type)) {\n bag.err(\n \"schema.type.format\",\n `Section type \"${type}\" must match ${SECTION_TYPE_RE} (lowercase, start with a letter).`,\n where,\n );\n }\n if (opts.filenameType && type !== opts.filenameType) {\n bag.err(\n \"schema.type.filename_mismatch\",\n `Schema type \"${type}\" must equal its filename \"${opts.filenameType}\" (component-filename = schema-type convention).`,\n where,\n );\n }\n }\n if (!isNonEmptyString(schema.name)) {\n bag.err(\"schema.name.missing\", \"Section schema is missing a `name`.\", where);\n }\n if (schema.settings === undefined) {\n bag.warn(\"schema.settings.missing\", \"Section schema has no `settings`.\", where);\n } else if (!Array.isArray(schema.settings)) {\n bag.err(\"schema.settings.invalid\", \"`settings` must be an array.\", where);\n } else {\n const seen = new Set<string>();\n schema.settings.forEach((s, i) =>\n validateSettingInto(bag, s, `${where}.settings[${i}]`, seen),\n );\n }\n // Nested block schemas (shallow — type + settings shape).\n if (schema.blocks !== undefined) {\n if (!Array.isArray(schema.blocks)) {\n bag.err(\"schema.blocks.invalid\", \"`blocks` must be an array.\", where);\n } else {\n schema.blocks.forEach((b, i) => {\n if (!isObject(b) || !isNonEmptyString(b.type)) {\n bag.err(\"schema.block.type.missing\", `Block[${i}] is missing a \\`type\\`.`, where);\n return;\n }\n if (!SECTION_TYPE_RE.test(b.type)) {\n bag.err(\n \"schema.block.type.format\",\n `Block type \"${b.type}\" must match ${SECTION_TYPE_RE}.`,\n where,\n );\n }\n if (Array.isArray(b.settings)) {\n const seen = new Set<string>();\n b.settings.forEach((s, j) =>\n validateSettingInto(bag, s, `${where}.blocks[${i}].settings[${j}]`, seen),\n );\n }\n });\n }\n }\n return bag.result();\n}\n\n/**\n * Instance conformance: check a settings object (from a preset or a merchant\n * customization) against its section schema. Unknown keys warn (harmless at\n * runtime — ignored); invalid values (bad select option, out-of-range,\n * wrong primitive) error.\n */\nexport function validateSettingsAgainstSchema(\n settings: unknown,\n schema: Pick<SectionSchema, \"settings\" | \"type\">,\n pathPrefix = \"settings\",\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(settings)) {\n bag.err(\"instance.settings.invalid\", \"Section settings must be an object.\", pathPrefix);\n return bag.result();\n }\n const defs = Array.isArray(schema.settings) ? schema.settings : [];\n const byId = new Map<string, SettingDefinition>();\n for (const d of defs) if (isNonEmptyString(d?.id)) byId.set(d.id, d);\n\n for (const [key, value] of Object.entries(settings)) {\n const def = byId.get(key);\n if (!def) {\n bag.warn(\n \"instance.setting.unknown\",\n `Setting \"${key}\" is not declared in the \"${String(schema.type)}\" schema.`,\n `${pathPrefix}.${key}`,\n );\n continue;\n }\n if (value === null || value === undefined) continue;\n const p = `${pathPrefix}.${key}`;\n if ((def.type === \"select\" || def.type === \"radio\") && Array.isArray(def.options)) {\n const allowed = def.options.map((o) => o.value);\n if (typeof value === \"string\" && !allowed.includes(value)) {\n bag.err(\n \"instance.setting.option.invalid\",\n `\"${key}\" = \"${value}\" is not one of: ${allowed.join(\", \")}.`,\n p,\n );\n }\n }\n if (def.type === \"range\" && typeof value === \"number\") {\n if (typeof def.min === \"number\" && value < def.min) {\n bag.err(\"instance.setting.range.under\", `\"${key}\" = ${value} is below min ${def.min}.`, p);\n }\n if (typeof def.max === \"number\" && value > def.max) {\n bag.err(\"instance.setting.range.over\", `\"${key}\" = ${value} is above max ${def.max}.`, p);\n }\n }\n if ((def.type === \"checkbox\") && typeof value !== \"boolean\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a boolean.`, p);\n }\n if ((def.type === \"number\" || def.type === \"range\") && typeof value !== \"number\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a number.`, p);\n }\n }\n return bag.result();\n}\n\n// ── Manifest validation ───────────────────────────────────────────\n\n/** Core manifest field checks shared by source (theme.json) and built manifests. */\nfunction validateManifestCore(bag: IssueBag, m: Record<string, unknown>) {\n if (!isNonEmptyString(m.id)) {\n bag.err(\"manifest.id.missing\", \"theme.json is missing `id`.\", \"id\");\n } else if (!THEME_ID_RE.test(m.id)) {\n bag.err(\n \"manifest.id.format\",\n `Theme id \"${m.id}\" must be lowercase alphanumeric with dashes/underscores (no leading/trailing separator).`,\n \"id\",\n );\n }\n // name: string or bilingual object { en|default, ... }.\n const name = m.name;\n const nameOk =\n isNonEmptyString(name) ||\n (isObject(name) && Object.values(name).some((v) => isNonEmptyString(v)));\n if (!nameOk) {\n bag.err(\"manifest.name.missing\", \"theme.json is missing a non-empty `name`.\", \"name\");\n }\n if (!isNonEmptyString(m.version)) {\n bag.err(\"manifest.version.missing\", \"theme.json is missing `version`.\", \"version\");\n } else if (!SEMVER_RE.test(m.version)) {\n bag.err(\"manifest.version.invalid\", `Version \"${m.version}\" is not valid semver.`, \"version\");\n }\n if (!isNonEmptyString(m.author)) {\n bag.err(\"manifest.author.missing\", \"theme.json is missing `author`.\", \"author\");\n }\n if (m.min_sdk_version !== undefined && !SEMVER_RE.test(String(m.min_sdk_version))) {\n bag.warn(\"manifest.min_sdk_version.invalid\", \"`min_sdk_version` is not valid semver.\", \"min_sdk_version\");\n }\n}\n\n/**\n * Validate a source theme manifest (parsed theme.json).\n *\n * `ctx.sectionTypes` — the set of section types the theme actually ships\n * (from schemas/sections/*.json). When provided, every section type\n * referenced by a preset must be present, and missing required templates\n * are surfaced as warnings.\n */\nexport function validateManifest(\n manifest: unknown,\n ctx: { sectionTypes?: ReadonlySet<string> } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(manifest)) {\n bag.err(\"manifest.invalid\", \"theme.json must be a JSON object.\", \"theme.json\");\n return bag.result();\n }\n validateManifestCore(bag, manifest);\n\n const presets = manifest.presets;\n if (!isObject(presets) || Object.keys(presets).length === 0) {\n bag.warn(\"manifest.presets.empty\", \"theme.json has no presets — merchants start with an empty page.\", \"presets\");\n } else {\n // Every referenced section type must resolve to a shipped schema.\n if (ctx.sectionTypes) {\n for (const type of collectPresetSectionTypes(manifest)) {\n if (!ctx.sectionTypes.has(type)) {\n bag.err(\n \"manifest.preset.unknown_section\",\n `Preset references section type \"${type}\" with no schemas/sections/${type}.json.`,\n \"presets\",\n );\n }\n }\n }\n // Required-template coverage (warning).\n const templates = isObject(presets.templates) ? presets.templates : {};\n for (const t of REQUIRED_TEMPLATES) {\n if (!(t in templates)) {\n bag.warn(\n \"manifest.template.missing\",\n `No preset for required template \"${t}\" — the storefront will fall back to its built-in.`,\n `presets.templates.${t}`,\n );\n }\n }\n }\n return bag.result();\n}\n\n/**\n * Server-side gate: validate the artifacts the plugin emits — dist/manifest.json\n * (the embedded built manifest, including section_schemas) and dist/import-map.json\n * (the federation + contract-version descriptor). This is what the backend should\n * run on upload/submit, since it can't run the source build.\n *\n * `opts.hostContractVersion` — when given, refuses bundles whose\n * `contract_version` the host doesn't support.\n */\nexport function validateBuiltManifest(\n builtManifest: unknown,\n importMap?: unknown,\n opts: { hostContractVersion?: number } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(builtManifest)) {\n bag.err(\"built.invalid\", \"manifest.json must be a JSON object.\", \"manifest.json\");\n return bag.result();\n }\n validateManifestCore(bag, builtManifest);\n\n // section_schemas must cover every type the presets reference.\n const schemas = builtManifest.section_schemas;\n const shipped = new Set<string>(isObject(schemas) ? Object.keys(schemas) : []);\n if (!isObject(schemas)) {\n bag.warn(\"built.section_schemas.missing\", \"manifest.json has no `section_schemas`.\", \"section_schemas\");\n }\n for (const type of collectPresetSectionTypes(builtManifest)) {\n if (!shipped.has(type)) {\n bag.err(\n \"built.preset.unknown_section\",\n `Preset references section type \"${type}\" not present in section_schemas.`,\n \"presets\",\n );\n }\n }\n // Each shipped schema must itself be valid.\n if (isObject(schemas)) {\n for (const [type, schema] of Object.entries(schemas)) {\n const r = validateSectionSchema(schema, { filenameType: type });\n for (const issue of r.issues) bag.issues.push(issue);\n }\n }\n if (!isNonEmptyString(builtManifest.plugin_version)) {\n bag.warn(\"built.plugin_version.missing\", \"manifest.json has no `plugin_version`.\", \"plugin_version\");\n }\n\n // Contract-version compatibility from the import map.\n if (importMap !== undefined) {\n if (!isObject(importMap)) {\n bag.err(\"importmap.invalid\", \"import-map.json must be a JSON object.\", \"import-map.json\");\n } else {\n const cv = importMap.contract_version;\n if (typeof cv !== \"number\") {\n bag.warn(\n \"importmap.contract_version.missing\",\n \"import-map.json has no numeric `contract_version` — built by an older plugin.\",\n \"contract_version\",\n );\n } else if (\n typeof opts.hostContractVersion === \"number\" &&\n cv > opts.hostContractVersion\n ) {\n bag.err(\n \"importmap.contract_version.incompatible\",\n `Theme built for contract v${cv}; this platform supports up to v${opts.hostContractVersion}.`,\n \"contract_version\",\n );\n }\n }\n }\n return bag.result();\n}\n\n/** Convenience: merge several results into one. */\nexport function mergeResults(...results: ValidationResult[]): ValidationResult {\n const issues = results.flatMap((r) => r.issues);\n return { valid: !issues.some((i) => i.level === \"error\"), issues };\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/validation/index.ts
|
|
2
2
|
var THEME_CONTRACT_VERSION = 1;
|
|
3
|
-
var SDK_VERSION = "0.10.
|
|
3
|
+
var SDK_VERSION = "0.10.1" ;
|
|
4
4
|
var REQUIRED_TEMPLATES = [
|
|
5
5
|
"home",
|
|
6
6
|
"product",
|
|
@@ -393,5 +393,5 @@ function mergeResults(...results) {
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
export { KNOWN_SETTING_TYPES, KNOWN_TEMPLATES, REQUIRED_TEMPLATES, SDK_VERSION, THEME_CONTRACT_VERSION, mergeResults, validateBuiltManifest, validateManifest, validateSectionSchema, validateSettingsAgainstSchema };
|
|
396
|
-
//# sourceMappingURL=chunk-
|
|
397
|
-
//# sourceMappingURL=chunk-
|
|
396
|
+
//# sourceMappingURL=chunk-QFFTHIFZ.mjs.map
|
|
397
|
+
//# sourceMappingURL=chunk-QFFTHIFZ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/validation/index.ts"],"names":[],"mappings":"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","file":"chunk-EB2ESBLD.mjs","sourcesContent":["/**\n * Shared theme-contract validators — the single source of truth.\n *\n * This module is PURE: it imports only types (erased at compile time) and\n * has no React/DOM/fs dependencies, so the same logic runs in the CLI, the\n * Vite plugin, and (re-implemented) the backend. Callers parse JSON files\n * themselves and pass plain objects in; these functions never touch disk.\n *\n * The goal is that \"builds green\" implies \"matches the contract\": every\n * place a theme can drift from what the storefront expects is checked here\n * once, instead of being re-derived (and drifting) in each tool.\n */\n\nimport type { SectionSchema, SettingDefinition } from \"../types/theme\";\n\n/**\n * The theme CONTRACT version — bumped only on a breaking change to what a\n * built theme must look like (manifest shape, required emitted artifacts,\n * section/setting semantics). The plugin stamps this into the built\n * manifest/import-map; the host refuses bundles whose contract version it\n * doesn't support. Distinct from the npm SDK version (`SDK_VERSION`), which\n * moves on every release including non-breaking ones.\n */\nexport const THEME_CONTRACT_VERSION = 1;\n\n/** npm version of this SDK build (inlined by tsup `define`). Informational. */\ndeclare const __SDK_VERSION__: string | undefined;\nexport const SDK_VERSION: string =\n typeof __SDK_VERSION__ === \"string\" ? __SDK_VERSION__ : \"0.0.0\";\n\nexport interface ValidationIssue {\n level: \"error\" | \"warning\";\n /** Stable machine code, e.g. \"manifest.version.invalid\". */\n code: string;\n message: string;\n /** Dotted path or file hint, e.g. \"presets.templates.home\" or \"hero.json\". */\n path?: string;\n}\n\nexport interface ValidationResult {\n /** True when there are zero error-level issues. */\n valid: boolean;\n issues: ValidationIssue[];\n}\n\n/**\n * Page templates the storefront routes to. A theme that omits a REQUIRED\n * template still works (the host falls back to its built-in), so a miss is\n * a warning, not an error — but tooling can elevate it with --strict.\n */\nexport const REQUIRED_TEMPLATES = [\n \"home\",\n \"product\",\n \"collection\",\n \"cart\",\n \"page\",\n \"search\",\n \"404\",\n] as const;\n\nexport const KNOWN_TEMPLATES: ReadonlySet<string> = new Set([\n ...REQUIRED_TEMPLATES,\n \"blog\",\n \"article\",\n \"policies\",\n \"password\",\n \"account\",\n \"checkout\",\n]);\n\n/** Setting types the V3 customizer renders. Unknown types warn (forward-compat). */\nexport const KNOWN_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"text\", \"textarea\", \"richtext\", \"number\", \"range\", \"color\", \"checkbox\",\n \"select\", \"radio\", \"font\", \"image_picker\", \"url\", \"product\", \"product_list\",\n \"collection\", \"collection_list\", \"header\", \"paragraph\", \"html\", \"date\",\n \"time\", \"video_picker\", \"color_scheme\", \"page_picker\", \"blog_picker\",\n \"link_list_picker\", \"variant_picker\", \"file_upload\", \"icon_picker\", \"icon\",\n]);\n\n/** Setting types that are presentational (no value) — `id`/`label` optional. */\nconst PRESENTATIONAL_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"header\", \"paragraph\", \"html\",\n]);\n\n// ── Primitives ────────────────────────────────────────────────────\n\nconst SECTION_TYPE_RE = /^[a-z][a-z0-9_-]*$/;\nconst THEME_ID_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/;\n// Pragmatic semver (major.minor.patch + optional -prerelease / +build).\nconst SEMVER_RE =\n /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?$/;\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction isNonEmptyString(v: unknown): v is string {\n return typeof v === \"string\" && v.trim().length > 0;\n}\n\nclass IssueBag {\n readonly issues: ValidationIssue[] = [];\n err(code: string, message: string, path?: string) {\n this.issues.push({ level: \"error\", code, message, path });\n }\n warn(code: string, message: string, path?: string) {\n this.issues.push({ level: \"warning\", code, message, path });\n }\n result(): ValidationResult {\n return {\n valid: !this.issues.some((i) => i.level === \"error\"),\n issues: this.issues,\n };\n }\n}\n\n/** Pull the section types referenced by a manifest's presets. */\nfunction collectPresetSectionTypes(manifest: Record<string, unknown>): Set<string> {\n const types = new Set<string>();\n const presets = manifest.presets;\n if (!isObject(presets)) return types;\n const buckets = [presets.templates, presets.section_groups];\n for (const bucket of buckets) {\n if (!isObject(bucket)) continue;\n for (const entry of Object.values(bucket)) {\n if (!isObject(entry)) continue;\n const sections = entry.sections;\n // sections may be an array of instances OR a map id -> instance.\n const instances = Array.isArray(sections)\n ? sections\n : isObject(sections)\n ? Object.values(sections)\n : [];\n for (const inst of instances) {\n if (isObject(inst) && isNonEmptyString(inst.type)) types.add(inst.type);\n }\n }\n }\n return types;\n}\n\n// ── Setting / schema validation ───────────────────────────────────\n\nfunction validateSettingInto(\n bag: IssueBag,\n setting: unknown,\n pathPrefix: string,\n seenIds: Set<string>,\n) {\n if (!isObject(setting)) {\n bag.err(\"schema.setting.invalid\", \"Setting must be an object.\", pathPrefix);\n return;\n }\n const type = setting.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.setting.type.missing\", \"Setting is missing a `type`.\", pathPrefix);\n } else if (!KNOWN_SETTING_TYPES.has(type)) {\n bag.warn(\n \"schema.setting.type.unknown\",\n `Unknown setting type \"${type}\" — the customizer will fall back to a text input.`,\n pathPrefix,\n );\n }\n const presentational =\n isNonEmptyString(type) && PRESENTATIONAL_SETTING_TYPES.has(type);\n if (!presentational) {\n if (!isNonEmptyString(setting.id)) {\n bag.err(\"schema.setting.id.missing\", \"Setting is missing an `id`.\", pathPrefix);\n } else {\n if (seenIds.has(setting.id)) {\n bag.err(\n \"schema.setting.id.duplicate\",\n `Duplicate setting id \"${setting.id}\".`,\n pathPrefix,\n );\n }\n seenIds.add(setting.id);\n }\n if (!isNonEmptyString(setting.label)) {\n bag.warn(\"schema.setting.label.missing\", \"Setting has no `label`.\", pathPrefix);\n }\n }\n // select / radio need options.\n if (type === \"select\" || type === \"radio\") {\n const options = setting.options;\n if (!Array.isArray(options) || options.length === 0) {\n bag.err(\n \"schema.setting.options.missing\",\n `\"${type}\" setting \"${String(setting.id)}\" must declare a non-empty \\`options\\` array.`,\n pathPrefix,\n );\n }\n }\n // range needs bounds.\n if (type === \"range\") {\n if (typeof setting.min !== \"number\" || typeof setting.max !== \"number\") {\n bag.err(\n \"schema.setting.range.bounds\",\n `\"range\" setting \"${String(setting.id)}\" must declare numeric \\`min\\` and \\`max\\`.`,\n pathPrefix,\n );\n } else if (setting.min >= setting.max) {\n bag.err(\n \"schema.setting.range.order\",\n `\"range\" setting \"${String(setting.id)}\" has min >= max.`,\n pathPrefix,\n );\n }\n }\n}\n\n/**\n * Validate one section schema (the parsed schemas/sections/<type>.json).\n * When `filenameType` is given, enforces the component-filename = schema-type\n * convention by requiring `schema.type` to equal it.\n */\nexport function validateSectionSchema(\n schema: unknown,\n opts: { filenameType?: string } = {},\n): ValidationResult {\n const bag = new IssueBag();\n const where = opts.filenameType ? `${opts.filenameType}.json` : \"section schema\";\n if (!isObject(schema)) {\n bag.err(\"schema.invalid\", \"Section schema must be a JSON object.\", where);\n return bag.result();\n }\n const type = schema.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.type.missing\", \"Section schema is missing a `type`.\", where);\n } else {\n if (!SECTION_TYPE_RE.test(type)) {\n bag.err(\n \"schema.type.format\",\n `Section type \"${type}\" must match ${SECTION_TYPE_RE} (lowercase, start with a letter).`,\n where,\n );\n }\n if (opts.filenameType && type !== opts.filenameType) {\n bag.err(\n \"schema.type.filename_mismatch\",\n `Schema type \"${type}\" must equal its filename \"${opts.filenameType}\" (component-filename = schema-type convention).`,\n where,\n );\n }\n }\n if (!isNonEmptyString(schema.name)) {\n bag.err(\"schema.name.missing\", \"Section schema is missing a `name`.\", where);\n }\n if (schema.settings === undefined) {\n bag.warn(\"schema.settings.missing\", \"Section schema has no `settings`.\", where);\n } else if (!Array.isArray(schema.settings)) {\n bag.err(\"schema.settings.invalid\", \"`settings` must be an array.\", where);\n } else {\n const seen = new Set<string>();\n schema.settings.forEach((s, i) =>\n validateSettingInto(bag, s, `${where}.settings[${i}]`, seen),\n );\n }\n // Nested block schemas (shallow — type + settings shape).\n if (schema.blocks !== undefined) {\n if (!Array.isArray(schema.blocks)) {\n bag.err(\"schema.blocks.invalid\", \"`blocks` must be an array.\", where);\n } else {\n schema.blocks.forEach((b, i) => {\n if (!isObject(b) || !isNonEmptyString(b.type)) {\n bag.err(\"schema.block.type.missing\", `Block[${i}] is missing a \\`type\\`.`, where);\n return;\n }\n if (!SECTION_TYPE_RE.test(b.type)) {\n bag.err(\n \"schema.block.type.format\",\n `Block type \"${b.type}\" must match ${SECTION_TYPE_RE}.`,\n where,\n );\n }\n if (Array.isArray(b.settings)) {\n const seen = new Set<string>();\n b.settings.forEach((s, j) =>\n validateSettingInto(bag, s, `${where}.blocks[${i}].settings[${j}]`, seen),\n );\n }\n });\n }\n }\n return bag.result();\n}\n\n/**\n * Instance conformance: check a settings object (from a preset or a merchant\n * customization) against its section schema. Unknown keys warn (harmless at\n * runtime — ignored); invalid values (bad select option, out-of-range,\n * wrong primitive) error.\n */\nexport function validateSettingsAgainstSchema(\n settings: unknown,\n schema: Pick<SectionSchema, \"settings\" | \"type\">,\n pathPrefix = \"settings\",\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(settings)) {\n bag.err(\"instance.settings.invalid\", \"Section settings must be an object.\", pathPrefix);\n return bag.result();\n }\n const defs = Array.isArray(schema.settings) ? schema.settings : [];\n const byId = new Map<string, SettingDefinition>();\n for (const d of defs) if (isNonEmptyString(d?.id)) byId.set(d.id, d);\n\n for (const [key, value] of Object.entries(settings)) {\n const def = byId.get(key);\n if (!def) {\n bag.warn(\n \"instance.setting.unknown\",\n `Setting \"${key}\" is not declared in the \"${String(schema.type)}\" schema.`,\n `${pathPrefix}.${key}`,\n );\n continue;\n }\n if (value === null || value === undefined) continue;\n const p = `${pathPrefix}.${key}`;\n if ((def.type === \"select\" || def.type === \"radio\") && Array.isArray(def.options)) {\n const allowed = def.options.map((o) => o.value);\n if (typeof value === \"string\" && !allowed.includes(value)) {\n bag.err(\n \"instance.setting.option.invalid\",\n `\"${key}\" = \"${value}\" is not one of: ${allowed.join(\", \")}.`,\n p,\n );\n }\n }\n if (def.type === \"range\" && typeof value === \"number\") {\n if (typeof def.min === \"number\" && value < def.min) {\n bag.err(\"instance.setting.range.under\", `\"${key}\" = ${value} is below min ${def.min}.`, p);\n }\n if (typeof def.max === \"number\" && value > def.max) {\n bag.err(\"instance.setting.range.over\", `\"${key}\" = ${value} is above max ${def.max}.`, p);\n }\n }\n if ((def.type === \"checkbox\") && typeof value !== \"boolean\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a boolean.`, p);\n }\n if ((def.type === \"number\" || def.type === \"range\") && typeof value !== \"number\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a number.`, p);\n }\n }\n return bag.result();\n}\n\n// ── Manifest validation ───────────────────────────────────────────\n\n/** Core manifest field checks shared by source (theme.json) and built manifests. */\nfunction validateManifestCore(bag: IssueBag, m: Record<string, unknown>) {\n if (!isNonEmptyString(m.id)) {\n bag.err(\"manifest.id.missing\", \"theme.json is missing `id`.\", \"id\");\n } else if (!THEME_ID_RE.test(m.id)) {\n bag.err(\n \"manifest.id.format\",\n `Theme id \"${m.id}\" must be lowercase alphanumeric with dashes/underscores (no leading/trailing separator).`,\n \"id\",\n );\n }\n // name: string or bilingual object { en|default, ... }.\n const name = m.name;\n const nameOk =\n isNonEmptyString(name) ||\n (isObject(name) && Object.values(name).some((v) => isNonEmptyString(v)));\n if (!nameOk) {\n bag.err(\"manifest.name.missing\", \"theme.json is missing a non-empty `name`.\", \"name\");\n }\n if (!isNonEmptyString(m.version)) {\n bag.err(\"manifest.version.missing\", \"theme.json is missing `version`.\", \"version\");\n } else if (!SEMVER_RE.test(m.version)) {\n bag.err(\"manifest.version.invalid\", `Version \"${m.version}\" is not valid semver.`, \"version\");\n }\n if (!isNonEmptyString(m.author)) {\n bag.err(\"manifest.author.missing\", \"theme.json is missing `author`.\", \"author\");\n }\n if (m.min_sdk_version !== undefined && !SEMVER_RE.test(String(m.min_sdk_version))) {\n bag.warn(\"manifest.min_sdk_version.invalid\", \"`min_sdk_version` is not valid semver.\", \"min_sdk_version\");\n }\n}\n\n/**\n * Validate a source theme manifest (parsed theme.json).\n *\n * `ctx.sectionTypes` — the set of section types the theme actually ships\n * (from schemas/sections/*.json). When provided, every section type\n * referenced by a preset must be present, and missing required templates\n * are surfaced as warnings.\n */\nexport function validateManifest(\n manifest: unknown,\n ctx: { sectionTypes?: ReadonlySet<string> } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(manifest)) {\n bag.err(\"manifest.invalid\", \"theme.json must be a JSON object.\", \"theme.json\");\n return bag.result();\n }\n validateManifestCore(bag, manifest);\n\n const presets = manifest.presets;\n if (!isObject(presets) || Object.keys(presets).length === 0) {\n bag.warn(\"manifest.presets.empty\", \"theme.json has no presets — merchants start with an empty page.\", \"presets\");\n } else {\n // Every referenced section type must resolve to a shipped schema.\n if (ctx.sectionTypes) {\n for (const type of collectPresetSectionTypes(manifest)) {\n if (!ctx.sectionTypes.has(type)) {\n bag.err(\n \"manifest.preset.unknown_section\",\n `Preset references section type \"${type}\" with no schemas/sections/${type}.json.`,\n \"presets\",\n );\n }\n }\n }\n // Required-template coverage (warning).\n const templates = isObject(presets.templates) ? presets.templates : {};\n for (const t of REQUIRED_TEMPLATES) {\n if (!(t in templates)) {\n bag.warn(\n \"manifest.template.missing\",\n `No preset for required template \"${t}\" — the storefront will fall back to its built-in.`,\n `presets.templates.${t}`,\n );\n }\n }\n }\n return bag.result();\n}\n\n/**\n * Server-side gate: validate the artifacts the plugin emits — dist/manifest.json\n * (the embedded built manifest, including section_schemas) and dist/import-map.json\n * (the federation + contract-version descriptor). This is what the backend should\n * run on upload/submit, since it can't run the source build.\n *\n * `opts.hostContractVersion` — when given, refuses bundles whose\n * `contract_version` the host doesn't support.\n */\nexport function validateBuiltManifest(\n builtManifest: unknown,\n importMap?: unknown,\n opts: { hostContractVersion?: number } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(builtManifest)) {\n bag.err(\"built.invalid\", \"manifest.json must be a JSON object.\", \"manifest.json\");\n return bag.result();\n }\n validateManifestCore(bag, builtManifest);\n\n // section_schemas must cover every type the presets reference.\n const schemas = builtManifest.section_schemas;\n const shipped = new Set<string>(isObject(schemas) ? Object.keys(schemas) : []);\n if (!isObject(schemas)) {\n bag.warn(\"built.section_schemas.missing\", \"manifest.json has no `section_schemas`.\", \"section_schemas\");\n }\n for (const type of collectPresetSectionTypes(builtManifest)) {\n if (!shipped.has(type)) {\n bag.err(\n \"built.preset.unknown_section\",\n `Preset references section type \"${type}\" not present in section_schemas.`,\n \"presets\",\n );\n }\n }\n // Each shipped schema must itself be valid.\n if (isObject(schemas)) {\n for (const [type, schema] of Object.entries(schemas)) {\n const r = validateSectionSchema(schema, { filenameType: type });\n for (const issue of r.issues) bag.issues.push(issue);\n }\n }\n if (!isNonEmptyString(builtManifest.plugin_version)) {\n bag.warn(\"built.plugin_version.missing\", \"manifest.json has no `plugin_version`.\", \"plugin_version\");\n }\n\n // Contract-version compatibility from the import map.\n if (importMap !== undefined) {\n if (!isObject(importMap)) {\n bag.err(\"importmap.invalid\", \"import-map.json must be a JSON object.\", \"import-map.json\");\n } else {\n const cv = importMap.contract_version;\n if (typeof cv !== \"number\") {\n bag.warn(\n \"importmap.contract_version.missing\",\n \"import-map.json has no numeric `contract_version` — built by an older plugin.\",\n \"contract_version\",\n );\n } else if (\n typeof opts.hostContractVersion === \"number\" &&\n cv > opts.hostContractVersion\n ) {\n bag.err(\n \"importmap.contract_version.incompatible\",\n `Theme built for contract v${cv}; this platform supports up to v${opts.hostContractVersion}.`,\n \"contract_version\",\n );\n }\n }\n }\n return bag.result();\n}\n\n/** Convenience: merge several results into one. */\nexport function mergeResults(...results: ValidationResult[]): ValidationResult {\n const issues = results.flatMap((r) => r.issues);\n return { valid: !issues.some((i) => i.level === \"error\"), issues };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/validation/index.ts"],"names":[],"mappings":"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","file":"chunk-QFFTHIFZ.mjs","sourcesContent":["/**\n * Shared theme-contract validators — the single source of truth.\n *\n * This module is PURE: it imports only types (erased at compile time) and\n * has no React/DOM/fs dependencies, so the same logic runs in the CLI, the\n * Vite plugin, and (re-implemented) the backend. Callers parse JSON files\n * themselves and pass plain objects in; these functions never touch disk.\n *\n * The goal is that \"builds green\" implies \"matches the contract\": every\n * place a theme can drift from what the storefront expects is checked here\n * once, instead of being re-derived (and drifting) in each tool.\n */\n\nimport type { SectionSchema, SettingDefinition } from \"../types/theme\";\n\n/**\n * The theme CONTRACT version — bumped only on a breaking change to what a\n * built theme must look like (manifest shape, required emitted artifacts,\n * section/setting semantics). The plugin stamps this into the built\n * manifest/import-map; the host refuses bundles whose contract version it\n * doesn't support. Distinct from the npm SDK version (`SDK_VERSION`), which\n * moves on every release including non-breaking ones.\n */\nexport const THEME_CONTRACT_VERSION = 1;\n\n/** npm version of this SDK build (inlined by tsup `define`). Informational. */\ndeclare const __SDK_VERSION__: string | undefined;\nexport const SDK_VERSION: string =\n typeof __SDK_VERSION__ === \"string\" ? __SDK_VERSION__ : \"0.0.0\";\n\nexport interface ValidationIssue {\n level: \"error\" | \"warning\";\n /** Stable machine code, e.g. \"manifest.version.invalid\". */\n code: string;\n message: string;\n /** Dotted path or file hint, e.g. \"presets.templates.home\" or \"hero.json\". */\n path?: string;\n}\n\nexport interface ValidationResult {\n /** True when there are zero error-level issues. */\n valid: boolean;\n issues: ValidationIssue[];\n}\n\n/**\n * Page templates the storefront routes to. A theme that omits a REQUIRED\n * template still works (the host falls back to its built-in), so a miss is\n * a warning, not an error — but tooling can elevate it with --strict.\n */\nexport const REQUIRED_TEMPLATES = [\n \"home\",\n \"product\",\n \"collection\",\n \"cart\",\n \"page\",\n \"search\",\n \"404\",\n] as const;\n\nexport const KNOWN_TEMPLATES: ReadonlySet<string> = new Set([\n ...REQUIRED_TEMPLATES,\n \"blog\",\n \"article\",\n \"policies\",\n \"password\",\n \"account\",\n \"checkout\",\n]);\n\n/** Setting types the V3 customizer renders. Unknown types warn (forward-compat). */\nexport const KNOWN_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"text\", \"textarea\", \"richtext\", \"number\", \"range\", \"color\", \"checkbox\",\n \"select\", \"radio\", \"font\", \"image_picker\", \"url\", \"product\", \"product_list\",\n \"collection\", \"collection_list\", \"header\", \"paragraph\", \"html\", \"date\",\n \"time\", \"video_picker\", \"color_scheme\", \"page_picker\", \"blog_picker\",\n \"link_list_picker\", \"variant_picker\", \"file_upload\", \"icon_picker\", \"icon\",\n]);\n\n/** Setting types that are presentational (no value) — `id`/`label` optional. */\nconst PRESENTATIONAL_SETTING_TYPES: ReadonlySet<string> = new Set([\n \"header\", \"paragraph\", \"html\",\n]);\n\n// ── Primitives ────────────────────────────────────────────────────\n\nconst SECTION_TYPE_RE = /^[a-z][a-z0-9_-]*$/;\nconst THEME_ID_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/;\n// Pragmatic semver (major.minor.patch + optional -prerelease / +build).\nconst SEMVER_RE =\n /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?$/;\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction isNonEmptyString(v: unknown): v is string {\n return typeof v === \"string\" && v.trim().length > 0;\n}\n\nclass IssueBag {\n readonly issues: ValidationIssue[] = [];\n err(code: string, message: string, path?: string) {\n this.issues.push({ level: \"error\", code, message, path });\n }\n warn(code: string, message: string, path?: string) {\n this.issues.push({ level: \"warning\", code, message, path });\n }\n result(): ValidationResult {\n return {\n valid: !this.issues.some((i) => i.level === \"error\"),\n issues: this.issues,\n };\n }\n}\n\n/** Pull the section types referenced by a manifest's presets. */\nfunction collectPresetSectionTypes(manifest: Record<string, unknown>): Set<string> {\n const types = new Set<string>();\n const presets = manifest.presets;\n if (!isObject(presets)) return types;\n const buckets = [presets.templates, presets.section_groups];\n for (const bucket of buckets) {\n if (!isObject(bucket)) continue;\n for (const entry of Object.values(bucket)) {\n if (!isObject(entry)) continue;\n const sections = entry.sections;\n // sections may be an array of instances OR a map id -> instance.\n const instances = Array.isArray(sections)\n ? sections\n : isObject(sections)\n ? Object.values(sections)\n : [];\n for (const inst of instances) {\n if (isObject(inst) && isNonEmptyString(inst.type)) types.add(inst.type);\n }\n }\n }\n return types;\n}\n\n// ── Setting / schema validation ───────────────────────────────────\n\nfunction validateSettingInto(\n bag: IssueBag,\n setting: unknown,\n pathPrefix: string,\n seenIds: Set<string>,\n) {\n if (!isObject(setting)) {\n bag.err(\"schema.setting.invalid\", \"Setting must be an object.\", pathPrefix);\n return;\n }\n const type = setting.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.setting.type.missing\", \"Setting is missing a `type`.\", pathPrefix);\n } else if (!KNOWN_SETTING_TYPES.has(type)) {\n bag.warn(\n \"schema.setting.type.unknown\",\n `Unknown setting type \"${type}\" — the customizer will fall back to a text input.`,\n pathPrefix,\n );\n }\n const presentational =\n isNonEmptyString(type) && PRESENTATIONAL_SETTING_TYPES.has(type);\n if (!presentational) {\n if (!isNonEmptyString(setting.id)) {\n bag.err(\"schema.setting.id.missing\", \"Setting is missing an `id`.\", pathPrefix);\n } else {\n if (seenIds.has(setting.id)) {\n bag.err(\n \"schema.setting.id.duplicate\",\n `Duplicate setting id \"${setting.id}\".`,\n pathPrefix,\n );\n }\n seenIds.add(setting.id);\n }\n if (!isNonEmptyString(setting.label)) {\n bag.warn(\"schema.setting.label.missing\", \"Setting has no `label`.\", pathPrefix);\n }\n }\n // select / radio need options.\n if (type === \"select\" || type === \"radio\") {\n const options = setting.options;\n if (!Array.isArray(options) || options.length === 0) {\n bag.err(\n \"schema.setting.options.missing\",\n `\"${type}\" setting \"${String(setting.id)}\" must declare a non-empty \\`options\\` array.`,\n pathPrefix,\n );\n }\n }\n // range needs bounds.\n if (type === \"range\") {\n if (typeof setting.min !== \"number\" || typeof setting.max !== \"number\") {\n bag.err(\n \"schema.setting.range.bounds\",\n `\"range\" setting \"${String(setting.id)}\" must declare numeric \\`min\\` and \\`max\\`.`,\n pathPrefix,\n );\n } else if (setting.min >= setting.max) {\n bag.err(\n \"schema.setting.range.order\",\n `\"range\" setting \"${String(setting.id)}\" has min >= max.`,\n pathPrefix,\n );\n }\n }\n}\n\n/**\n * Validate one section schema (the parsed schemas/sections/<type>.json).\n * When `filenameType` is given, enforces the component-filename = schema-type\n * convention by requiring `schema.type` to equal it.\n */\nexport function validateSectionSchema(\n schema: unknown,\n opts: { filenameType?: string } = {},\n): ValidationResult {\n const bag = new IssueBag();\n const where = opts.filenameType ? `${opts.filenameType}.json` : \"section schema\";\n if (!isObject(schema)) {\n bag.err(\"schema.invalid\", \"Section schema must be a JSON object.\", where);\n return bag.result();\n }\n const type = schema.type;\n if (!isNonEmptyString(type)) {\n bag.err(\"schema.type.missing\", \"Section schema is missing a `type`.\", where);\n } else {\n if (!SECTION_TYPE_RE.test(type)) {\n bag.err(\n \"schema.type.format\",\n `Section type \"${type}\" must match ${SECTION_TYPE_RE} (lowercase, start with a letter).`,\n where,\n );\n }\n if (opts.filenameType && type !== opts.filenameType) {\n bag.err(\n \"schema.type.filename_mismatch\",\n `Schema type \"${type}\" must equal its filename \"${opts.filenameType}\" (component-filename = schema-type convention).`,\n where,\n );\n }\n }\n if (!isNonEmptyString(schema.name)) {\n bag.err(\"schema.name.missing\", \"Section schema is missing a `name`.\", where);\n }\n if (schema.settings === undefined) {\n bag.warn(\"schema.settings.missing\", \"Section schema has no `settings`.\", where);\n } else if (!Array.isArray(schema.settings)) {\n bag.err(\"schema.settings.invalid\", \"`settings` must be an array.\", where);\n } else {\n const seen = new Set<string>();\n schema.settings.forEach((s, i) =>\n validateSettingInto(bag, s, `${where}.settings[${i}]`, seen),\n );\n }\n // Nested block schemas (shallow — type + settings shape).\n if (schema.blocks !== undefined) {\n if (!Array.isArray(schema.blocks)) {\n bag.err(\"schema.blocks.invalid\", \"`blocks` must be an array.\", where);\n } else {\n schema.blocks.forEach((b, i) => {\n if (!isObject(b) || !isNonEmptyString(b.type)) {\n bag.err(\"schema.block.type.missing\", `Block[${i}] is missing a \\`type\\`.`, where);\n return;\n }\n if (!SECTION_TYPE_RE.test(b.type)) {\n bag.err(\n \"schema.block.type.format\",\n `Block type \"${b.type}\" must match ${SECTION_TYPE_RE}.`,\n where,\n );\n }\n if (Array.isArray(b.settings)) {\n const seen = new Set<string>();\n b.settings.forEach((s, j) =>\n validateSettingInto(bag, s, `${where}.blocks[${i}].settings[${j}]`, seen),\n );\n }\n });\n }\n }\n return bag.result();\n}\n\n/**\n * Instance conformance: check a settings object (from a preset or a merchant\n * customization) against its section schema. Unknown keys warn (harmless at\n * runtime — ignored); invalid values (bad select option, out-of-range,\n * wrong primitive) error.\n */\nexport function validateSettingsAgainstSchema(\n settings: unknown,\n schema: Pick<SectionSchema, \"settings\" | \"type\">,\n pathPrefix = \"settings\",\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(settings)) {\n bag.err(\"instance.settings.invalid\", \"Section settings must be an object.\", pathPrefix);\n return bag.result();\n }\n const defs = Array.isArray(schema.settings) ? schema.settings : [];\n const byId = new Map<string, SettingDefinition>();\n for (const d of defs) if (isNonEmptyString(d?.id)) byId.set(d.id, d);\n\n for (const [key, value] of Object.entries(settings)) {\n const def = byId.get(key);\n if (!def) {\n bag.warn(\n \"instance.setting.unknown\",\n `Setting \"${key}\" is not declared in the \"${String(schema.type)}\" schema.`,\n `${pathPrefix}.${key}`,\n );\n continue;\n }\n if (value === null || value === undefined) continue;\n const p = `${pathPrefix}.${key}`;\n if ((def.type === \"select\" || def.type === \"radio\") && Array.isArray(def.options)) {\n const allowed = def.options.map((o) => o.value);\n if (typeof value === \"string\" && !allowed.includes(value)) {\n bag.err(\n \"instance.setting.option.invalid\",\n `\"${key}\" = \"${value}\" is not one of: ${allowed.join(\", \")}.`,\n p,\n );\n }\n }\n if (def.type === \"range\" && typeof value === \"number\") {\n if (typeof def.min === \"number\" && value < def.min) {\n bag.err(\"instance.setting.range.under\", `\"${key}\" = ${value} is below min ${def.min}.`, p);\n }\n if (typeof def.max === \"number\" && value > def.max) {\n bag.err(\"instance.setting.range.over\", `\"${key}\" = ${value} is above max ${def.max}.`, p);\n }\n }\n if ((def.type === \"checkbox\") && typeof value !== \"boolean\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a boolean.`, p);\n }\n if ((def.type === \"number\" || def.type === \"range\") && typeof value !== \"number\") {\n bag.warn(\"instance.setting.type.mismatch\", `\"${key}\" should be a number.`, p);\n }\n }\n return bag.result();\n}\n\n// ── Manifest validation ───────────────────────────────────────────\n\n/** Core manifest field checks shared by source (theme.json) and built manifests. */\nfunction validateManifestCore(bag: IssueBag, m: Record<string, unknown>) {\n if (!isNonEmptyString(m.id)) {\n bag.err(\"manifest.id.missing\", \"theme.json is missing `id`.\", \"id\");\n } else if (!THEME_ID_RE.test(m.id)) {\n bag.err(\n \"manifest.id.format\",\n `Theme id \"${m.id}\" must be lowercase alphanumeric with dashes/underscores (no leading/trailing separator).`,\n \"id\",\n );\n }\n // name: string or bilingual object { en|default, ... }.\n const name = m.name;\n const nameOk =\n isNonEmptyString(name) ||\n (isObject(name) && Object.values(name).some((v) => isNonEmptyString(v)));\n if (!nameOk) {\n bag.err(\"manifest.name.missing\", \"theme.json is missing a non-empty `name`.\", \"name\");\n }\n if (!isNonEmptyString(m.version)) {\n bag.err(\"manifest.version.missing\", \"theme.json is missing `version`.\", \"version\");\n } else if (!SEMVER_RE.test(m.version)) {\n bag.err(\"manifest.version.invalid\", `Version \"${m.version}\" is not valid semver.`, \"version\");\n }\n if (!isNonEmptyString(m.author)) {\n bag.err(\"manifest.author.missing\", \"theme.json is missing `author`.\", \"author\");\n }\n if (m.min_sdk_version !== undefined && !SEMVER_RE.test(String(m.min_sdk_version))) {\n bag.warn(\"manifest.min_sdk_version.invalid\", \"`min_sdk_version` is not valid semver.\", \"min_sdk_version\");\n }\n}\n\n/**\n * Validate a source theme manifest (parsed theme.json).\n *\n * `ctx.sectionTypes` — the set of section types the theme actually ships\n * (from schemas/sections/*.json). When provided, every section type\n * referenced by a preset must be present, and missing required templates\n * are surfaced as warnings.\n */\nexport function validateManifest(\n manifest: unknown,\n ctx: { sectionTypes?: ReadonlySet<string> } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(manifest)) {\n bag.err(\"manifest.invalid\", \"theme.json must be a JSON object.\", \"theme.json\");\n return bag.result();\n }\n validateManifestCore(bag, manifest);\n\n const presets = manifest.presets;\n if (!isObject(presets) || Object.keys(presets).length === 0) {\n bag.warn(\"manifest.presets.empty\", \"theme.json has no presets — merchants start with an empty page.\", \"presets\");\n } else {\n // Every referenced section type must resolve to a shipped schema.\n if (ctx.sectionTypes) {\n for (const type of collectPresetSectionTypes(manifest)) {\n if (!ctx.sectionTypes.has(type)) {\n bag.err(\n \"manifest.preset.unknown_section\",\n `Preset references section type \"${type}\" with no schemas/sections/${type}.json.`,\n \"presets\",\n );\n }\n }\n }\n // Required-template coverage (warning).\n const templates = isObject(presets.templates) ? presets.templates : {};\n for (const t of REQUIRED_TEMPLATES) {\n if (!(t in templates)) {\n bag.warn(\n \"manifest.template.missing\",\n `No preset for required template \"${t}\" — the storefront will fall back to its built-in.`,\n `presets.templates.${t}`,\n );\n }\n }\n }\n return bag.result();\n}\n\n/**\n * Server-side gate: validate the artifacts the plugin emits — dist/manifest.json\n * (the embedded built manifest, including section_schemas) and dist/import-map.json\n * (the federation + contract-version descriptor). This is what the backend should\n * run on upload/submit, since it can't run the source build.\n *\n * `opts.hostContractVersion` — when given, refuses bundles whose\n * `contract_version` the host doesn't support.\n */\nexport function validateBuiltManifest(\n builtManifest: unknown,\n importMap?: unknown,\n opts: { hostContractVersion?: number } = {},\n): ValidationResult {\n const bag = new IssueBag();\n if (!isObject(builtManifest)) {\n bag.err(\"built.invalid\", \"manifest.json must be a JSON object.\", \"manifest.json\");\n return bag.result();\n }\n validateManifestCore(bag, builtManifest);\n\n // section_schemas must cover every type the presets reference.\n const schemas = builtManifest.section_schemas;\n const shipped = new Set<string>(isObject(schemas) ? Object.keys(schemas) : []);\n if (!isObject(schemas)) {\n bag.warn(\"built.section_schemas.missing\", \"manifest.json has no `section_schemas`.\", \"section_schemas\");\n }\n for (const type of collectPresetSectionTypes(builtManifest)) {\n if (!shipped.has(type)) {\n bag.err(\n \"built.preset.unknown_section\",\n `Preset references section type \"${type}\" not present in section_schemas.`,\n \"presets\",\n );\n }\n }\n // Each shipped schema must itself be valid.\n if (isObject(schemas)) {\n for (const [type, schema] of Object.entries(schemas)) {\n const r = validateSectionSchema(schema, { filenameType: type });\n for (const issue of r.issues) bag.issues.push(issue);\n }\n }\n if (!isNonEmptyString(builtManifest.plugin_version)) {\n bag.warn(\"built.plugin_version.missing\", \"manifest.json has no `plugin_version`.\", \"plugin_version\");\n }\n\n // Contract-version compatibility from the import map.\n if (importMap !== undefined) {\n if (!isObject(importMap)) {\n bag.err(\"importmap.invalid\", \"import-map.json must be a JSON object.\", \"import-map.json\");\n } else {\n const cv = importMap.contract_version;\n if (typeof cv !== \"number\") {\n bag.warn(\n \"importmap.contract_version.missing\",\n \"import-map.json has no numeric `contract_version` — built by an older plugin.\",\n \"contract_version\",\n );\n } else if (\n typeof opts.hostContractVersion === \"number\" &&\n cv > opts.hostContractVersion\n ) {\n bag.err(\n \"importmap.contract_version.incompatible\",\n `Theme built for contract v${cv}; this platform supports up to v${opts.hostContractVersion}.`,\n \"contract_version\",\n );\n }\n }\n }\n return bag.result();\n}\n\n/** Convenience: merge several results into one. */\nexport function mergeResults(...results: ValidationResult[]): ValidationResult {\n const issues = results.flatMap((r) => r.issues);\n return { valid: !issues.some((i) => i.level === \"error\"), issues };\n}\n"]}
|
|
@@ -203,5 +203,5 @@ exports.useProducts = useProducts;
|
|
|
203
203
|
exports.useShop = useShop;
|
|
204
204
|
exports.useThemeSettings = useThemeSettings;
|
|
205
205
|
exports.useTranslation = useTranslation;
|
|
206
|
-
//# sourceMappingURL=chunk-
|
|
207
|
-
//# sourceMappingURL=chunk-
|
|
206
|
+
//# sourceMappingURL=chunk-V3JDQXD3.cjs.map
|
|
207
|
+
//# sourceMappingURL=chunk-V3JDQXD3.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contexts/index.ts","../src/hooks/useLocalization.ts","../src/hooks/useShop.ts","../src/hooks/useCustomer.ts","../src/hooks/useThemeSettings.ts","../src/hooks/usePage.ts","../src/hooks/useProducts.ts","../src/hooks/useCollections.ts"],"names":["createContext","useContext","useMemo","useState","useEffect"],"mappings":"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","file":"chunk-V3JDQXD3.cjs","sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\nimport type { Store, Product, Collection, Cart, Customer, Page } from \"../types/entities\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport interface LocalizationState {\n locale: string;\n direction: \"ltr\" | \"rtl\";\n translations: Record<string, string>;\n formatMoney: (amount: number, currency?: string) => string;\n formatDate: (date: string | Date) => string;\n /**\n * Phase 3.7 — locale-aware number formatter. Routes to either\n * Western (1234) or Arab-Indic (١٢٣٤) digits depending on\n * `store.settings.numerals`. Themes calling formatMoney get the\n * same digit choice automatically; this is for raw counts (\"12 items\").\n */\n formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;\n /**\n * Phase 3.6 — switch the active locale.\n *\n * Sets the `numu_locale` cookie and triggers a full page reload so\n * the server-rendered layout picks up the new locale (the storefront\n * resolves locale at SSR time from cookie/query). Returns once the\n * cookie is written; the page navigation cancels any pending React\n * work so callers don't need to await.\n */\n setLocale: (next: string) => void;\n /**\n * Phase 3.6 — list of locales the store advertises. Empty when the\n * store hasn't configured a multi-locale catalog. Themes use this\n * to decide whether to render the LocaleSwitcher at all.\n */\n availableLocales: string[];\n}\n\n/**\n * Result of a cart mutation (add / remove / update / discount / note).\n *\n * `ok` mirrors the HTTP result: `false` for any non-2xx response — an\n * out-of-stock or validation rejection, a 403 CSRF failure, etc. — so a\n * theme can surface the failure (toast, inline error) instead of assuming\n * the write landed. `status` is the HTTP status (0 for a network throw that\n * the caller catches); `message` carries the backend's error text when the\n * error body parses.\n *\n * Back-compat: the cart methods previously resolved `Promise<void>`; callers\n * that `await` them and ignore the return keep working unchanged — the\n * resolved value is purely additive.\n */\nexport interface CartMutationResult {\n ok: boolean;\n status: number;\n message?: string;\n /** The cart applied by a successful write (MAJOR currency units, same\n * normalization as `useCart().cart`). Lets callers read the written\n * state — e.g. addItem prices its AddToCart analytics event from the\n * added line — without racing React state. Absent on failure. */\n cart?: Cart;\n}\n\nexport interface CartContextValue {\n cart: Cart;\n addItem: (\n productId: string,\n variantId?: string,\n quantity?: number,\n /** Picker axes ({Color: \"Black\", Size: \"L\"}) — variant_name fallback for\n * products whose variant rows carry no option_values. */\n selectedOptions?: Record<string, string>,\n ) => Promise<CartMutationResult>;\n removeItem: (itemId: string) => Promise<CartMutationResult>;\n updateQuantity: (\n itemId: string,\n quantity: number,\n ) => Promise<CartMutationResult>;\n applyDiscount: (code: string) => Promise<CartMutationResult>;\n removeDiscount: () => Promise<CartMutationResult>;\n updateNote: (note: string) => Promise<CartMutationResult>;\n clearCart: () => Promise<void>;\n loading: boolean;\n}\n\nexport const ShopContext = createContext<Store | null>(null);\nexport const ProductContext = createContext<Product | null>(null);\nexport const CollectionContext = createContext<Collection | null>(null);\nexport const CartContext = createContext<CartContextValue | null>(null);\nexport const CustomerContext = createContext<Customer | null>(null);\nexport const ThemeSettingsContext = createContext<ThemeSettingsV3 | null>(null);\nexport const LocalizationContext = createContext<LocalizationState | null>(null);\nexport const PageContext = createContext<Page | null>(null);\n/**\n * Wave 5 — currently-active template identifier. Mirrors the key inside\n * `themeSettings.templates.<currentTemplate>` so themes can dispatch\n * which section list to render. Hosts set this via `NuMuProvider`'s\n * `currentTemplate` prop (which is passed in by the storefront page\n * component — `app/(store)/[subdomain]/product/[id]/page.tsx` passes\n * \"product\", `cart/page.tsx` passes \"cart\", etc.). Falls back to \"home\".\n */\nexport const CurrentTemplateContext = createContext<string>(\"home\");\n\n/**\n * A merchant-managed navigation menu item, exactly as the storefront\n * menus resolver returns it (`GET /storefront/store/{id}/menus`):\n * bilingual `label`, a pre-resolved `url`, and nested `children`.\n *\n * This is the RAW shape the host injects via `NuMuProvider`'s\n * `navigation` prop. `useNavigation(handle)` localizes it to the\n * display-ready `NavigationItem` (a single `title` string for the\n * active locale).\n */\nexport interface MenuItemData {\n id: string;\n label: Record<string, string>;\n url: string;\n type?: string | null;\n resource_id?: string | null;\n /**\n * §5 hide-page → hide-nav-link. `false` when the item targets a CMS page\n * (`/pages/<handle>`) that is currently unpublished or deleted. The backend\n * menus resolver annotates it; absent/`true` means visible (back-compat).\n */\n target_visible?: boolean;\n children?: MenuItemData[];\n}\n\n/**\n * Phase 2.4 — navigation menus keyed by handle (`main-menu`, `footer`,\n * plus custom), injected by the host from the storefront resolver so a\n * theme's `useNavigation(handle)` resolves without a client round-trip.\n *\n * Defaults to `{}` — an empty map signals \"host provided no menus\", at\n * which point `useNavigation` falls back to its own fetch / a theme's\n * `DEFAULT_NAV`. A present-but-handle-absent map means the menu simply\n * doesn't exist (render nothing / fallback), no fetch attempted.\n */\nexport const NavigationContext = createContext<Record<string, MenuItemData[]>>(\n {},\n);\n\n/**\n * Multi-currency presentment config, as returned by\n * `GET /api/storefront/currencies`. Display-only — the store's *capture*\n * currency never changes mid-session; this just lets visitors browse prices\n * in a currency they recognize.\n */\nexport interface CurrencyConfig {\n base: string;\n default_presentment: string;\n presentment: string[];\n rates: Record<string, string>; // Decimal-as-string\n auto_convert: boolean;\n}\n\n/**\n * The value `useCurrency()` returns and `CurrencyContext` carries.\n *\n * `convert(cents, target?)` returns the converted **cents** in `target`\n * (defaults to `selected`), using the API rates; returns the input unchanged\n * when no rate exists (theme renders base — better than a wrong number).\n */\nexport interface CurrencyState {\n base: string;\n selected: string;\n presentment: string[];\n rates: Record<string, number>;\n autoConvert: boolean;\n loading: boolean;\n setSelected: (currency: string) => void;\n convert: (cents: number, target?: string) => number;\n}\n\n/**\n * Phase 2 (correctness) — multi-currency lifted into a provider context.\n *\n * Previously `useCurrency()` fetched `/api/storefront/currencies` and held\n * the selected currency in per-instance `useState`, so each `<Money>`,\n * `useMoney()` and `<CurrencySwitcher>` owned an INDEPENDENT copy: switching\n * currency in the switcher never reached the price tags, and every consumer\n * re-fetched. `NuMuProvider` now fetches ONCE and publishes a single\n * `CurrencyState` here, so a `<CurrencySwitcher>` change re-renders every\n * `<Money>` on the page without a reload.\n *\n * Null when no provider is present (SSR before hydrate, or a theme mounted\n * outside `NuMuProvider`); `useCurrency()` then falls back to a base-only,\n * no-convert state derived from the store currency so `<Money>` still renders.\n */\nexport const CurrencyContext = createContext<CurrencyState | null>(null);\n","import { useContext } from \"react\";\nimport { LocalizationContext } from \"../contexts\";\nimport type { LocalizationState } from \"../contexts\";\n\nexport function useLocalization(): LocalizationState {\n const ctx = useContext(LocalizationContext);\n if (!ctx) throw new Error(\"useLocalization must be used within NuMuProvider\");\n return ctx;\n}\n\nexport function useDirection(): \"ltr\" | \"rtl\" {\n const { direction } = useLocalization();\n return direction;\n}\n\nexport function useLocale(): string {\n const { locale } = useLocalization();\n return locale;\n}\n\nexport function useTranslation() {\n const { translations, locale } = useLocalization();\n return {\n t: (key: string, fallback?: string) => translations[key] || fallback || key,\n locale,\n };\n}\n\n/**\n * Phase 3.6 — pull a translated field off a domain object.\n *\n * Convention: per-product translations live on `entity.attributes`\n * keyed as `<field>_<locale>` — e.g. `name_ar`, `description_ar`.\n * Backend writes these via the merchant hub when a merchant turns on\n * \"Translations\" for a product. Themes call:\n *\n * const productName = useFieldTranslation(product, \"name\");\n *\n * and get the Arabic name when the active locale is \"ar\", falling\n * back to the English `product.name` otherwise. Works for any object\n * that pairs a base field on the entity with an `attributes` JSONB\n * blob holding the translated variants.\n *\n * Why not put translations on the entity itself? Adding `name_ar`,\n * `description_ar`, `name_he`, etc. as first-class columns means a\n * schema migration every time a merchant enables a new locale. JSONB\n * lets the merchant flip locales on/off in settings without touching\n * the table.\n */\nexport function useFieldTranslation<\n T extends { attributes?: Record<string, unknown> },\n>(entity: T | null | undefined, field: string): string | undefined {\n const { locale } = useLocalization();\n if (!entity) return undefined;\n // English (or whichever default locale the merchant configured) is\n // stored as the base field; non-default locales live on attributes\n // with a `<field>_<locale>` key.\n const translated =\n entity.attributes && typeof entity.attributes === \"object\"\n ? (entity.attributes as Record<string, unknown>)[`${field}_${locale}`]\n : undefined;\n if (typeof translated === \"string\" && translated) return translated;\n // Base field — supports any value type that stringifies sensibly.\n const base = (entity as unknown as Record<string, unknown>)[field];\n if (typeof base === \"string\") return base;\n if (base == null) return undefined;\n return String(base);\n}\n\n/**\n * Phase 3.7 — formatted-number hook.\n *\n * Themes that need to render counts (\"12 items\", \"3 reviews\") should\n * route through this hook so Arab-Indic digits (٠١٢٣٤) vs Western\n * (01234) stay consistent with money + date elsewhere on the page.\n *\n * const fmtNum = useNumberFormat();\n * fmtNum(12) → \"12\" (or \"١٢\" for Arabic stores)\n * fmtNum(12.5, { minimumFractionDigits: 2 }) → \"12.50\" / \"١٢٫٥٠\"\n */\nexport function useNumberFormat() {\n const { formatNumber } = useLocalization();\n return formatNumber;\n}\n","\"use client\";\nimport { useContext, useMemo } from \"react\";\nimport { ShopContext } from \"../contexts\";\nimport { useLocale } from \"./useLocalization\";\nimport type { Store } from \"../types/entities\";\n\n/**\n * Augmented Store shape returned from useShop().\n *\n * Adds a couple of conveniences theme devs reach for constantly:\n * - `domain` — the active hostname (subdomain or custom). Resolved\n * from `store.domain ?? store.subdomain ?? store.slug`.\n * - `formatUrl(path)` — emits a fully-qualified URL using `domain`.\n * Theme code that needs to render absolute URLs (canonical tags,\n * Open Graph, JSON-LD) goes through this so dev/prod / subdomain\n * vs. custom-domain stays correct without each theme reinventing\n * the resolver.\n */\nexport interface ShopWithHelpers extends Store {\n /** Fully-qualified hostname this store currently serves on. */\n domain: string;\n /**\n * Format a path (relative or absolute) as a fully-qualified URL on\n * this store's domain. No-ops when given an already-absolute URL.\n *\n * Phase 6 — when the active locale is non-default and the store\n * has opted into locale URL prefixes, the path is prefixed with\n * `/{locale}/` (e.g. `/ar/products/foo`). Already-prefixed paths\n * are left alone so calling `formatUrl(formatUrl(...))` is safe.\n */\n formatUrl(path: string): string;\n}\n\nconst DEFAULT_PROTOCOL =\n typeof window !== \"undefined\"\n ? window.location.protocol.replace(\":\", \"\")\n : \"https\";\n\nfunction resolveDomain(store: Store): string {\n // Custom domain wins when set; otherwise fall back to the platform\n // subdomain. The slug is a last resort for stores that haven't been\n // assigned either yet (mostly demo stores in dev).\n if (store.domain) return store.domain;\n if (store.subdomain) {\n // The platform-domain default lives on the storefront; theme code\n // doesn't have access to env vars, so we read it off the runtime\n // window object when present and fall back to the well-known\n // production value. Themes can override by rendering with\n // `<meta name=\"numu:platform-domain\" content=\"...\">` if they're\n // hosted on a non-default platform.\n const platform =\n (typeof window !== \"undefined\" &&\n (window as unknown as { __NUMU_PLATFORM_DOMAIN?: string })\n .__NUMU_PLATFORM_DOMAIN) ||\n \"numueg.app\";\n return `${store.subdomain}.${platform}`;\n }\n return store.slug;\n}\n\nexport function useShop(): ShopWithHelpers {\n const ctx = useContext(ShopContext);\n if (!ctx) throw new Error(\"useShop must be used within NuMuProvider\");\n const locale = useLocale();\n\n return useMemo(() => {\n const domain = resolveDomain(ctx);\n const settings = (ctx as Store & { settings?: Record<string, unknown> })\n .settings;\n const localePrefixEnabled = Boolean(\n settings && (settings as { locale_url_prefix_enabled?: boolean }).locale_url_prefix_enabled,\n );\n const defaultLocale = ctx.default_language || \"en\";\n // List of locales that get a URL prefix. Defaults to \"everything\n // except the default\" — but a store can opt into `[\"ar\", \"en\"]`\n // (prefix even the default) if it wants a fully prefixed URL\n // scheme like Shopify does for markets.\n const prefixedLocales =\n (settings as { locale_url_prefix_locales?: string[] } | undefined)\n ?.locale_url_prefix_locales || null;\n const shouldPrefix = (l: string): boolean => {\n if (!localePrefixEnabled) return false;\n if (prefixedLocales) return prefixedLocales.includes(l);\n return l !== defaultLocale;\n };\n\n const formatUrl = (path: string): string => {\n if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;\n // Already absolute? Hand back as-is.\n if (/^https?:\\/\\//i.test(path)) return path;\n // Protocol-relative — assume same protocol as the host.\n if (path.startsWith(\"//\")) return `${DEFAULT_PROTOCOL}:${path}`;\n let normalized = path.startsWith(\"/\") ? path : `/${path}`;\n if (locale && shouldPrefix(locale)) {\n // Idempotent: don't double-prefix if the caller already\n // gave us a /{locale}/... path.\n const prefix = `/${locale}/`;\n const root = `/${locale}`;\n if (normalized !== root && !normalized.startsWith(prefix)) {\n normalized = `${root}${normalized}`;\n }\n }\n return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;\n };\n return { ...ctx, domain, formatUrl };\n }, [ctx, locale]);\n}\n","import { useContext } from \"react\";\nimport { CustomerContext } from \"../contexts\";\nimport type { Customer } from \"../types/entities\";\n\nexport function useCustomer(): Customer | null {\n return useContext(CustomerContext);\n}\n","import { useContext } from \"react\";\nimport { ThemeSettingsContext } from \"../contexts\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport function useThemeSettings(): ThemeSettingsV3 {\n const ctx = useContext(ThemeSettingsContext);\n if (!ctx) throw new Error(\"useThemeSettings must be used within NuMuProvider\");\n return ctx;\n}\n","import { useContext } from \"react\";\nimport { PageContext } from \"../contexts\";\nimport type { Page } from \"../types/entities\";\n\nexport function usePage(): Page | null {\n return useContext(PageContext);\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Product } from \"../types/entities\";\n\ninterface UseProductsOptions {\n /** Limit the slice returned. Defaults to whatever the host gave us. */\n limit?: number;\n /**\n * When true, fetch from `/api/products` if the host didn't provide a\n * list via PageContext. Useful for sections rendered on routes that\n * don't pre-fetch products (e.g. a custom CMS page that wants a\n * \"featured\" rail). Default: false — themes typically prefer SSR data.\n */\n fetchIfMissing?: boolean;\n}\n\ninterface UseProductsResult {\n products: Product[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useProducts — read the storefront-pre-fetched product list from the\n * page context, optionally falling back to a client-side fetch.\n *\n * The SSR path passes `page.data.products: Product[]` from\n * `numu-storefront/src/app/[domain]/page.tsx`; sections on the home\n * route get them for free. For other routes that don't pre-fetch, set\n * `fetchIfMissing: true` and we'll hit `/api/products`.\n */\nexport function useProducts(\n opts: UseProductsOptions = {},\n): UseProductsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.products as Product[] | undefined) ?? null;\n\n const [products, setProducts] = useState<Product[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n if (limit) params.set(\"limit\", String(limit));\n const res = await fetch(`/api/products?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/products → ${res.status}`);\n const data = (await res.json()) as { products?: Product[] };\n if (cancelled) return;\n setProducts(data.products ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, limit, shop?.id]);\n\n const sliced = limit != null ? products.slice(0, limit) : products;\n return { products: sliced, loading, error };\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Collection } from \"../types/entities\";\n\ninterface UseCollectionsOptions {\n limit?: number;\n fetchIfMissing?: boolean;\n}\n\ninterface UseCollectionsResult {\n collections: Collection[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useCollections — analog to useProducts. Reads page.data.collections\n * pre-fetched by the storefront SSR; falls back to /api/collections\n * when fetchIfMissing is true.\n */\nexport function useCollections(\n opts: UseCollectionsOptions = {},\n): UseCollectionsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.collections as Collection[] | undefined) ?? null;\n\n const [collections, setCollections] = useState<Collection[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n const res = await fetch(`/api/collections?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/collections → ${res.status}`);\n const data = (await res.json()) as { collections?: Collection[] };\n if (cancelled) return;\n setCollections(data.collections ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, shop?.id]);\n\n const sliced = limit != null ? collections.slice(0, limit) : collections;\n return { collections: sliced, loading, error };\n}\n"]}
|
|
@@ -179,5 +179,5 @@ function useCollections(opts = {}) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
export { CartContext, CollectionContext, CurrencyContext, CurrentTemplateContext, CustomerContext, LocalizationContext, NavigationContext, PageContext, ProductContext, ShopContext, ThemeSettingsContext, useCollections, useCustomer, useDirection, useFieldTranslation, useLocale, useLocalization, useNumberFormat, usePage, useProducts, useShop, useThemeSettings, useTranslation };
|
|
182
|
-
//# sourceMappingURL=chunk-
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
182
|
+
//# sourceMappingURL=chunk-ZYZZG4JR.mjs.map
|
|
183
|
+
//# sourceMappingURL=chunk-ZYZZG4JR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contexts/index.ts","../src/hooks/useLocalization.ts","../src/hooks/useShop.ts","../src/hooks/useCustomer.ts","../src/hooks/useThemeSettings.ts","../src/hooks/usePage.ts","../src/hooks/useProducts.ts","../src/hooks/useCollections.ts"],"names":["useContext","useState","useEffect"],"mappings":"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","file":"chunk-ZYZZG4JR.mjs","sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\nimport type { Store, Product, Collection, Cart, Customer, Page } from \"../types/entities\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport interface LocalizationState {\n locale: string;\n direction: \"ltr\" | \"rtl\";\n translations: Record<string, string>;\n formatMoney: (amount: number, currency?: string) => string;\n formatDate: (date: string | Date) => string;\n /**\n * Phase 3.7 — locale-aware number formatter. Routes to either\n * Western (1234) or Arab-Indic (١٢٣٤) digits depending on\n * `store.settings.numerals`. Themes calling formatMoney get the\n * same digit choice automatically; this is for raw counts (\"12 items\").\n */\n formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;\n /**\n * Phase 3.6 — switch the active locale.\n *\n * Sets the `numu_locale` cookie and triggers a full page reload so\n * the server-rendered layout picks up the new locale (the storefront\n * resolves locale at SSR time from cookie/query). Returns once the\n * cookie is written; the page navigation cancels any pending React\n * work so callers don't need to await.\n */\n setLocale: (next: string) => void;\n /**\n * Phase 3.6 — list of locales the store advertises. Empty when the\n * store hasn't configured a multi-locale catalog. Themes use this\n * to decide whether to render the LocaleSwitcher at all.\n */\n availableLocales: string[];\n}\n\n/**\n * Result of a cart mutation (add / remove / update / discount / note).\n *\n * `ok` mirrors the HTTP result: `false` for any non-2xx response — an\n * out-of-stock or validation rejection, a 403 CSRF failure, etc. — so a\n * theme can surface the failure (toast, inline error) instead of assuming\n * the write landed. `status` is the HTTP status (0 for a network throw that\n * the caller catches); `message` carries the backend's error text when the\n * error body parses.\n *\n * Back-compat: the cart methods previously resolved `Promise<void>`; callers\n * that `await` them and ignore the return keep working unchanged — the\n * resolved value is purely additive.\n */\nexport interface CartMutationResult {\n ok: boolean;\n status: number;\n message?: string;\n /** The cart applied by a successful write (MAJOR currency units, same\n * normalization as `useCart().cart`). Lets callers read the written\n * state — e.g. addItem prices its AddToCart analytics event from the\n * added line — without racing React state. Absent on failure. */\n cart?: Cart;\n}\n\nexport interface CartContextValue {\n cart: Cart;\n addItem: (\n productId: string,\n variantId?: string,\n quantity?: number,\n /** Picker axes ({Color: \"Black\", Size: \"L\"}) — variant_name fallback for\n * products whose variant rows carry no option_values. */\n selectedOptions?: Record<string, string>,\n ) => Promise<CartMutationResult>;\n removeItem: (itemId: string) => Promise<CartMutationResult>;\n updateQuantity: (\n itemId: string,\n quantity: number,\n ) => Promise<CartMutationResult>;\n applyDiscount: (code: string) => Promise<CartMutationResult>;\n removeDiscount: () => Promise<CartMutationResult>;\n updateNote: (note: string) => Promise<CartMutationResult>;\n clearCart: () => Promise<void>;\n loading: boolean;\n}\n\nexport const ShopContext = createContext<Store | null>(null);\nexport const ProductContext = createContext<Product | null>(null);\nexport const CollectionContext = createContext<Collection | null>(null);\nexport const CartContext = createContext<CartContextValue | null>(null);\nexport const CustomerContext = createContext<Customer | null>(null);\nexport const ThemeSettingsContext = createContext<ThemeSettingsV3 | null>(null);\nexport const LocalizationContext = createContext<LocalizationState | null>(null);\nexport const PageContext = createContext<Page | null>(null);\n/**\n * Wave 5 — currently-active template identifier. Mirrors the key inside\n * `themeSettings.templates.<currentTemplate>` so themes can dispatch\n * which section list to render. Hosts set this via `NuMuProvider`'s\n * `currentTemplate` prop (which is passed in by the storefront page\n * component — `app/(store)/[subdomain]/product/[id]/page.tsx` passes\n * \"product\", `cart/page.tsx` passes \"cart\", etc.). Falls back to \"home\".\n */\nexport const CurrentTemplateContext = createContext<string>(\"home\");\n\n/**\n * A merchant-managed navigation menu item, exactly as the storefront\n * menus resolver returns it (`GET /storefront/store/{id}/menus`):\n * bilingual `label`, a pre-resolved `url`, and nested `children`.\n *\n * This is the RAW shape the host injects via `NuMuProvider`'s\n * `navigation` prop. `useNavigation(handle)` localizes it to the\n * display-ready `NavigationItem` (a single `title` string for the\n * active locale).\n */\nexport interface MenuItemData {\n id: string;\n label: Record<string, string>;\n url: string;\n type?: string | null;\n resource_id?: string | null;\n /**\n * §5 hide-page → hide-nav-link. `false` when the item targets a CMS page\n * (`/pages/<handle>`) that is currently unpublished or deleted. The backend\n * menus resolver annotates it; absent/`true` means visible (back-compat).\n */\n target_visible?: boolean;\n children?: MenuItemData[];\n}\n\n/**\n * Phase 2.4 — navigation menus keyed by handle (`main-menu`, `footer`,\n * plus custom), injected by the host from the storefront resolver so a\n * theme's `useNavigation(handle)` resolves without a client round-trip.\n *\n * Defaults to `{}` — an empty map signals \"host provided no menus\", at\n * which point `useNavigation` falls back to its own fetch / a theme's\n * `DEFAULT_NAV`. A present-but-handle-absent map means the menu simply\n * doesn't exist (render nothing / fallback), no fetch attempted.\n */\nexport const NavigationContext = createContext<Record<string, MenuItemData[]>>(\n {},\n);\n\n/**\n * Multi-currency presentment config, as returned by\n * `GET /api/storefront/currencies`. Display-only — the store's *capture*\n * currency never changes mid-session; this just lets visitors browse prices\n * in a currency they recognize.\n */\nexport interface CurrencyConfig {\n base: string;\n default_presentment: string;\n presentment: string[];\n rates: Record<string, string>; // Decimal-as-string\n auto_convert: boolean;\n}\n\n/**\n * The value `useCurrency()` returns and `CurrencyContext` carries.\n *\n * `convert(cents, target?)` returns the converted **cents** in `target`\n * (defaults to `selected`), using the API rates; returns the input unchanged\n * when no rate exists (theme renders base — better than a wrong number).\n */\nexport interface CurrencyState {\n base: string;\n selected: string;\n presentment: string[];\n rates: Record<string, number>;\n autoConvert: boolean;\n loading: boolean;\n setSelected: (currency: string) => void;\n convert: (cents: number, target?: string) => number;\n}\n\n/**\n * Phase 2 (correctness) — multi-currency lifted into a provider context.\n *\n * Previously `useCurrency()` fetched `/api/storefront/currencies` and held\n * the selected currency in per-instance `useState`, so each `<Money>`,\n * `useMoney()` and `<CurrencySwitcher>` owned an INDEPENDENT copy: switching\n * currency in the switcher never reached the price tags, and every consumer\n * re-fetched. `NuMuProvider` now fetches ONCE and publishes a single\n * `CurrencyState` here, so a `<CurrencySwitcher>` change re-renders every\n * `<Money>` on the page without a reload.\n *\n * Null when no provider is present (SSR before hydrate, or a theme mounted\n * outside `NuMuProvider`); `useCurrency()` then falls back to a base-only,\n * no-convert state derived from the store currency so `<Money>` still renders.\n */\nexport const CurrencyContext = createContext<CurrencyState | null>(null);\n","import { useContext } from \"react\";\nimport { LocalizationContext } from \"../contexts\";\nimport type { LocalizationState } from \"../contexts\";\n\nexport function useLocalization(): LocalizationState {\n const ctx = useContext(LocalizationContext);\n if (!ctx) throw new Error(\"useLocalization must be used within NuMuProvider\");\n return ctx;\n}\n\nexport function useDirection(): \"ltr\" | \"rtl\" {\n const { direction } = useLocalization();\n return direction;\n}\n\nexport function useLocale(): string {\n const { locale } = useLocalization();\n return locale;\n}\n\nexport function useTranslation() {\n const { translations, locale } = useLocalization();\n return {\n t: (key: string, fallback?: string) => translations[key] || fallback || key,\n locale,\n };\n}\n\n/**\n * Phase 3.6 — pull a translated field off a domain object.\n *\n * Convention: per-product translations live on `entity.attributes`\n * keyed as `<field>_<locale>` — e.g. `name_ar`, `description_ar`.\n * Backend writes these via the merchant hub when a merchant turns on\n * \"Translations\" for a product. Themes call:\n *\n * const productName = useFieldTranslation(product, \"name\");\n *\n * and get the Arabic name when the active locale is \"ar\", falling\n * back to the English `product.name` otherwise. Works for any object\n * that pairs a base field on the entity with an `attributes` JSONB\n * blob holding the translated variants.\n *\n * Why not put translations on the entity itself? Adding `name_ar`,\n * `description_ar`, `name_he`, etc. as first-class columns means a\n * schema migration every time a merchant enables a new locale. JSONB\n * lets the merchant flip locales on/off in settings without touching\n * the table.\n */\nexport function useFieldTranslation<\n T extends { attributes?: Record<string, unknown> },\n>(entity: T | null | undefined, field: string): string | undefined {\n const { locale } = useLocalization();\n if (!entity) return undefined;\n // English (or whichever default locale the merchant configured) is\n // stored as the base field; non-default locales live on attributes\n // with a `<field>_<locale>` key.\n const translated =\n entity.attributes && typeof entity.attributes === \"object\"\n ? (entity.attributes as Record<string, unknown>)[`${field}_${locale}`]\n : undefined;\n if (typeof translated === \"string\" && translated) return translated;\n // Base field — supports any value type that stringifies sensibly.\n const base = (entity as unknown as Record<string, unknown>)[field];\n if (typeof base === \"string\") return base;\n if (base == null) return undefined;\n return String(base);\n}\n\n/**\n * Phase 3.7 — formatted-number hook.\n *\n * Themes that need to render counts (\"12 items\", \"3 reviews\") should\n * route through this hook so Arab-Indic digits (٠١٢٣٤) vs Western\n * (01234) stay consistent with money + date elsewhere on the page.\n *\n * const fmtNum = useNumberFormat();\n * fmtNum(12) → \"12\" (or \"١٢\" for Arabic stores)\n * fmtNum(12.5, { minimumFractionDigits: 2 }) → \"12.50\" / \"١٢٫٥٠\"\n */\nexport function useNumberFormat() {\n const { formatNumber } = useLocalization();\n return formatNumber;\n}\n","\"use client\";\nimport { useContext, useMemo } from \"react\";\nimport { ShopContext } from \"../contexts\";\nimport { useLocale } from \"./useLocalization\";\nimport type { Store } from \"../types/entities\";\n\n/**\n * Augmented Store shape returned from useShop().\n *\n * Adds a couple of conveniences theme devs reach for constantly:\n * - `domain` — the active hostname (subdomain or custom). Resolved\n * from `store.domain ?? store.subdomain ?? store.slug`.\n * - `formatUrl(path)` — emits a fully-qualified URL using `domain`.\n * Theme code that needs to render absolute URLs (canonical tags,\n * Open Graph, JSON-LD) goes through this so dev/prod / subdomain\n * vs. custom-domain stays correct without each theme reinventing\n * the resolver.\n */\nexport interface ShopWithHelpers extends Store {\n /** Fully-qualified hostname this store currently serves on. */\n domain: string;\n /**\n * Format a path (relative or absolute) as a fully-qualified URL on\n * this store's domain. No-ops when given an already-absolute URL.\n *\n * Phase 6 — when the active locale is non-default and the store\n * has opted into locale URL prefixes, the path is prefixed with\n * `/{locale}/` (e.g. `/ar/products/foo`). Already-prefixed paths\n * are left alone so calling `formatUrl(formatUrl(...))` is safe.\n */\n formatUrl(path: string): string;\n}\n\nconst DEFAULT_PROTOCOL =\n typeof window !== \"undefined\"\n ? window.location.protocol.replace(\":\", \"\")\n : \"https\";\n\nfunction resolveDomain(store: Store): string {\n // Custom domain wins when set; otherwise fall back to the platform\n // subdomain. The slug is a last resort for stores that haven't been\n // assigned either yet (mostly demo stores in dev).\n if (store.domain) return store.domain;\n if (store.subdomain) {\n // The platform-domain default lives on the storefront; theme code\n // doesn't have access to env vars, so we read it off the runtime\n // window object when present and fall back to the well-known\n // production value. Themes can override by rendering with\n // `<meta name=\"numu:platform-domain\" content=\"...\">` if they're\n // hosted on a non-default platform.\n const platform =\n (typeof window !== \"undefined\" &&\n (window as unknown as { __NUMU_PLATFORM_DOMAIN?: string })\n .__NUMU_PLATFORM_DOMAIN) ||\n \"numueg.app\";\n return `${store.subdomain}.${platform}`;\n }\n return store.slug;\n}\n\nexport function useShop(): ShopWithHelpers {\n const ctx = useContext(ShopContext);\n if (!ctx) throw new Error(\"useShop must be used within NuMuProvider\");\n const locale = useLocale();\n\n return useMemo(() => {\n const domain = resolveDomain(ctx);\n const settings = (ctx as Store & { settings?: Record<string, unknown> })\n .settings;\n const localePrefixEnabled = Boolean(\n settings && (settings as { locale_url_prefix_enabled?: boolean }).locale_url_prefix_enabled,\n );\n const defaultLocale = ctx.default_language || \"en\";\n // List of locales that get a URL prefix. Defaults to \"everything\n // except the default\" — but a store can opt into `[\"ar\", \"en\"]`\n // (prefix even the default) if it wants a fully prefixed URL\n // scheme like Shopify does for markets.\n const prefixedLocales =\n (settings as { locale_url_prefix_locales?: string[] } | undefined)\n ?.locale_url_prefix_locales || null;\n const shouldPrefix = (l: string): boolean => {\n if (!localePrefixEnabled) return false;\n if (prefixedLocales) return prefixedLocales.includes(l);\n return l !== defaultLocale;\n };\n\n const formatUrl = (path: string): string => {\n if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;\n // Already absolute? Hand back as-is.\n if (/^https?:\\/\\//i.test(path)) return path;\n // Protocol-relative — assume same protocol as the host.\n if (path.startsWith(\"//\")) return `${DEFAULT_PROTOCOL}:${path}`;\n let normalized = path.startsWith(\"/\") ? path : `/${path}`;\n if (locale && shouldPrefix(locale)) {\n // Idempotent: don't double-prefix if the caller already\n // gave us a /{locale}/... path.\n const prefix = `/${locale}/`;\n const root = `/${locale}`;\n if (normalized !== root && !normalized.startsWith(prefix)) {\n normalized = `${root}${normalized}`;\n }\n }\n return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;\n };\n return { ...ctx, domain, formatUrl };\n }, [ctx, locale]);\n}\n","import { useContext } from \"react\";\nimport { CustomerContext } from \"../contexts\";\nimport type { Customer } from \"../types/entities\";\n\nexport function useCustomer(): Customer | null {\n return useContext(CustomerContext);\n}\n","import { useContext } from \"react\";\nimport { ThemeSettingsContext } from \"../contexts\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport function useThemeSettings(): ThemeSettingsV3 {\n const ctx = useContext(ThemeSettingsContext);\n if (!ctx) throw new Error(\"useThemeSettings must be used within NuMuProvider\");\n return ctx;\n}\n","import { useContext } from \"react\";\nimport { PageContext } from \"../contexts\";\nimport type { Page } from \"../types/entities\";\n\nexport function usePage(): Page | null {\n return useContext(PageContext);\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Product } from \"../types/entities\";\n\ninterface UseProductsOptions {\n /** Limit the slice returned. Defaults to whatever the host gave us. */\n limit?: number;\n /**\n * When true, fetch from `/api/products` if the host didn't provide a\n * list via PageContext. Useful for sections rendered on routes that\n * don't pre-fetch products (e.g. a custom CMS page that wants a\n * \"featured\" rail). Default: false — themes typically prefer SSR data.\n */\n fetchIfMissing?: boolean;\n}\n\ninterface UseProductsResult {\n products: Product[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useProducts — read the storefront-pre-fetched product list from the\n * page context, optionally falling back to a client-side fetch.\n *\n * The SSR path passes `page.data.products: Product[]` from\n * `numu-storefront/src/app/[domain]/page.tsx`; sections on the home\n * route get them for free. For other routes that don't pre-fetch, set\n * `fetchIfMissing: true` and we'll hit `/api/products`.\n */\nexport function useProducts(\n opts: UseProductsOptions = {},\n): UseProductsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.products as Product[] | undefined) ?? null;\n\n const [products, setProducts] = useState<Product[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n if (limit) params.set(\"limit\", String(limit));\n const res = await fetch(`/api/products?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/products → ${res.status}`);\n const data = (await res.json()) as { products?: Product[] };\n if (cancelled) return;\n setProducts(data.products ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, limit, shop?.id]);\n\n const sliced = limit != null ? products.slice(0, limit) : products;\n return { products: sliced, loading, error };\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Collection } from \"../types/entities\";\n\ninterface UseCollectionsOptions {\n limit?: number;\n fetchIfMissing?: boolean;\n}\n\ninterface UseCollectionsResult {\n collections: Collection[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useCollections — analog to useProducts. Reads page.data.collections\n * pre-fetched by the storefront SSR; falls back to /api/collections\n * when fetchIfMissing is true.\n */\nexport function useCollections(\n opts: UseCollectionsOptions = {},\n): UseCollectionsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.collections as Collection[] | undefined) ?? null;\n\n const [collections, setCollections] = useState<Collection[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n const res = await fetch(`/api/collections?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/collections → ${res.status}`);\n const data = (await res.json()) as { collections?: Collection[] };\n if (cancelled) return;\n setCollections(data.collections ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, shop?.id]);\n\n const sliced = limit != null ? collections.slice(0, limit) : collections;\n return { collections: sliced, loading, error };\n}\n"]}
|