@shoppexio/builder-contracts 0.1.0 → 0.1.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.
Files changed (71) hide show
  1. package/dist/builder-contracts.test.js +71 -1
  2. package/dist/builder-settings.d.ts +9 -666
  3. package/dist/builder-settings.d.ts.map +1 -1
  4. package/dist/canonical-settings.d.ts +18 -0
  5. package/dist/canonical-settings.d.ts.map +1 -0
  6. package/dist/canonical-settings.js +106 -0
  7. package/dist/events.d.ts +35 -240
  8. package/dist/events.d.ts.map +1 -1
  9. package/dist/events.js +7 -0
  10. package/dist/fields.d.ts +169 -8
  11. package/dist/fields.d.ts.map +1 -1
  12. package/dist/fields.js +27 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7 -0
  16. package/dist/legacy-manifest.d.ts +11 -0
  17. package/dist/legacy-manifest.d.ts.map +1 -1
  18. package/dist/legacy-manifest.js +106 -16
  19. package/dist/migrations.d.ts.map +1 -1
  20. package/dist/migrations.js +50 -4
  21. package/dist/persistence.d.ts +7 -0
  22. package/dist/persistence.d.ts.map +1 -0
  23. package/dist/persistence.js +58 -0
  24. package/dist/preview-boot.d.ts +68 -0
  25. package/dist/preview-boot.d.ts.map +1 -0
  26. package/dist/preview-boot.js +36 -0
  27. package/dist/preview-protocol.d.ts +227 -459
  28. package/dist/preview-protocol.d.ts.map +1 -1
  29. package/dist/preview-protocol.js +112 -0
  30. package/dist/preview-session-resolve.d.ts +115 -0
  31. package/dist/preview-session-resolve.d.ts.map +1 -0
  32. package/dist/preview-session-resolve.js +25 -0
  33. package/dist/preview-trusted-origins.d.ts +4 -0
  34. package/dist/preview-trusted-origins.d.ts.map +1 -0
  35. package/dist/preview-trusted-origins.js +26 -0
  36. package/dist/storefront-initial-data-html.d.ts +17 -0
  37. package/dist/storefront-initial-data-html.d.ts.map +1 -0
  38. package/dist/storefront-initial-data-html.js +83 -0
  39. package/dist/style-slots.d.ts +49 -151
  40. package/dist/style-slots.d.ts.map +1 -1
  41. package/dist/style-slots.js +75 -29
  42. package/dist/theme-manifest.d.ts +229 -454
  43. package/dist/theme-manifest.d.ts.map +1 -1
  44. package/dist/theme-manifest.js +92 -0
  45. package/dist/theme-schemes.d.ts +10 -0
  46. package/dist/theme-schemes.d.ts.map +1 -0
  47. package/dist/theme-schemes.js +24 -0
  48. package/dist/validation.d.ts +1 -1
  49. package/dist/validation.d.ts.map +1 -1
  50. package/dist/validation.js +18 -9
  51. package/package.json +43 -1
  52. package/src/builder-contracts.test.ts +398 -3
  53. package/src/canonical-settings.ts +156 -0
  54. package/src/events.ts +8 -0
  55. package/src/fields.ts +30 -0
  56. package/src/index.ts +7 -0
  57. package/src/legacy-manifest.ts +107 -16
  58. package/src/migrations.ts +65 -4
  59. package/src/persistence.ts +77 -0
  60. package/src/preview-boot.ts +47 -0
  61. package/src/preview-protocol.test.ts +132 -0
  62. package/src/preview-protocol.ts +122 -0
  63. package/src/preview-session-resolve.ts +34 -0
  64. package/src/preview-trusted-origins.test.ts +24 -0
  65. package/src/preview-trusted-origins.ts +35 -0
  66. package/src/storefront-initial-data-html.test.ts +63 -0
  67. package/src/storefront-initial-data-html.ts +112 -0
  68. package/src/style-slots.ts +96 -31
  69. package/src/theme-manifest.ts +118 -1
  70. package/src/theme-schemes.ts +33 -0
  71. package/src/validation.ts +27 -10
@@ -1,6 +1,7 @@
1
1
  import * as z from 'zod/v4';
2
2
  import { BuilderFieldSchema } from "./fields.js";
3
3
  import { ThemeManifestSchema } from "./theme-manifest.js";
4
+ import { StyleSlotDefaultsSchema } from "./style-slots.js";
4
5
  const LegacyFieldOptionSchema = z
5
6
  .object({
6
7
  label: z.string().min(1),
@@ -71,6 +72,17 @@ export const LegacyThemeManifestSchema = z
71
72
  id: z.string().min(1),
72
73
  name: z.string().min(1),
73
74
  version: z.string().min(1),
75
+ author: z.string().min(1).optional(),
76
+ description: z.string().min(1).optional(),
77
+ preview: z.string().min(1).optional(),
78
+ features: z.array(z.string().min(1)).optional(),
79
+ techStack: z.array(z.string().min(1)).optional(),
80
+ templates: z.array(z.string().min(1)).optional(),
81
+ createdAt: z.string().min(1).optional(),
82
+ demoUrl: z.string().min(1).optional(),
83
+ audience: z.string().min(1).optional(),
84
+ tags: z.array(z.string().min(1)).optional(),
85
+ hotfixPaths: z.array(z.string().min(1)).optional(),
74
86
  presets: z.array(LegacyThemePresetSchema).optional(),
75
87
  builder: z
76
88
  .object({
@@ -82,6 +94,10 @@ export const LegacyThemeManifestSchema = z
82
94
  })
83
95
  .passthrough();
84
96
  export function convertLegacyThemeManifest(input) {
97
+ const canonical = ThemeManifestSchema.safeParse(input);
98
+ if (canonical.success) {
99
+ return canonical.data;
100
+ }
85
101
  const legacy = LegacyThemeManifestSchema.parse(input);
86
102
  const blocks = Object.fromEntries(Object.entries(legacy.builder.blocks).map(([blockType, block]) => [
87
103
  blockType,
@@ -112,6 +128,17 @@ export function convertLegacyThemeManifest(input) {
112
128
  id: normalizeLegacyThemeId(legacy.id),
113
129
  name: legacy.name,
114
130
  version: legacy.version,
131
+ author: legacy.author,
132
+ description: legacy.description,
133
+ preview: legacy.preview,
134
+ features: legacy.features,
135
+ techStack: legacy.techStack,
136
+ templates: legacy.templates,
137
+ createdAt: legacy.createdAt,
138
+ demoUrl: legacy.demoUrl,
139
+ audience: legacy.audience,
140
+ tags: legacy.tags,
141
+ hotfixPaths: legacy.hotfixPaths,
115
142
  pages: Object.fromEntries(legacy.builder.pages.map((page) => {
116
143
  const syntheticBlockType = createSyntheticPageBlockType(page.id);
117
144
  const hasSyntheticBlock = blocks[syntheticBlockType] !== undefined;
@@ -137,6 +164,8 @@ export function convertLegacyThemeManifest(input) {
137
164
  blocks,
138
165
  styleSlots: {},
139
166
  presets: convertLegacyThemePresets(legacy.presets ?? []),
167
+ linkGroups: Array.isArray(legacy.builder.linkGroups) ? legacy.builder.linkGroups : [],
168
+ defaultLinkItems: isPlainObject(legacy.builder.defaultLinkItems) ? legacy.builder.defaultLinkItems : {},
140
169
  };
141
170
  return ThemeManifestSchema.parse(converted);
142
171
  }
@@ -152,16 +181,20 @@ function createSyntheticPageBlockType(pageId) {
152
181
  return `page-${normalizedPageId || 'custom'}`;
153
182
  }
154
183
  function convertLegacyThemePresets(presets) {
155
- return Object.fromEntries(presets.map((preset) => [
156
- preset.id,
157
- {
158
- label: preset.name ?? preset.label ?? preset.id,
159
- description: preset.description,
160
- content: flattenContentRecord(preset.overrides?.content ?? {}),
161
- layout: preset.overrides?.layout ?? {},
162
- style_slots: preset.overrides?.style_slots ?? preset.overrides?.styleSlots ?? {},
163
- },
164
- ]));
184
+ return Object.fromEntries(presets.map((preset) => {
185
+ const styleSlots = StyleSlotDefaultsSchema.parse(preset.overrides?.style_slots ?? preset.overrides?.styleSlots ?? {});
186
+ return [
187
+ preset.id,
188
+ {
189
+ label: preset.name ?? preset.label ?? preset.id,
190
+ description: preset.description,
191
+ preview: typeof preset.preview === 'string' ? preset.preview : undefined,
192
+ content: flattenContentRecord(preset.overrides?.content ?? {}),
193
+ layout: preset.overrides?.layout ?? {},
194
+ style_slots: styleSlots,
195
+ },
196
+ ];
197
+ }));
165
198
  }
166
199
  function flattenContentRecord(content, prefix = '') {
167
200
  const flattened = {};
@@ -180,21 +213,59 @@ function isPlainObject(value) {
180
213
  return typeof value === 'object' && value !== null && !Array.isArray(value);
181
214
  }
182
215
  function convertLegacyFields(fields, lists) {
183
- const convertedFields = Object.fromEntries(fields.map((field) => [field.path, convertLegacyField(field)]));
184
- const convertedLists = Object.fromEntries(lists.map((list) => [
185
- list.path,
186
- {
216
+ // Auto-promote inline `type: 'list'` fields to first-class list entries so
217
+ // the migrator can emit list-shaped data without separately maintaining a
218
+ // `lists: []` block-level entry. The promoted list inherits its kind from
219
+ // the field path (e.g. `header.left_links` → kind `navLinks`).
220
+ const inlineLists = [];
221
+ const remainingFields = [];
222
+ for (const field of fields) {
223
+ if (field.type === 'list') {
224
+ inlineLists.push({
225
+ path: field.path,
226
+ label: field.label,
227
+ kind: inferListKindFromPath(field.path),
228
+ description: field.description,
229
+ });
230
+ }
231
+ else {
232
+ remainingFields.push(field);
233
+ }
234
+ }
235
+ const convertedFields = Object.fromEntries(remainingFields.map((field) => [field.path, convertLegacyField(field)]));
236
+ const allLists = [...inlineLists, ...lists];
237
+ const convertedLists = Object.fromEntries(allLists.map((list) => {
238
+ const inlineField = fields.find((f) => f.type === 'list' && f.path === list.path);
239
+ const candidate = {
187
240
  type: 'list',
188
241
  label: list.label,
189
242
  description: list.description,
190
243
  itemShape: createListItemShape(list.kind),
191
- },
192
- ]));
244
+ ...(inlineField?.defaultValue !== undefined ? { defaultValue: inlineField.defaultValue } : {}),
245
+ };
246
+ return [list.path, candidate];
247
+ }));
193
248
  return {
194
249
  ...convertedFields,
195
250
  ...convertedLists,
196
251
  };
197
252
  }
253
+ function inferListKindFromPath(path) {
254
+ const suffix = path.split('.').at(-1) ?? '';
255
+ if (/items$/i.test(suffix))
256
+ return 'faqItems';
257
+ if (/links$/i.test(suffix))
258
+ return 'navLinks';
259
+ if (/images$/i.test(suffix) || /gallery$/i.test(suffix))
260
+ return 'galleryItems';
261
+ if (/features$/i.test(suffix))
262
+ return 'featureItems';
263
+ if (/reviews$/i.test(suffix) || /testimonials$/i.test(suffix))
264
+ return 'reviewItems';
265
+ if (/announcements$/i.test(suffix))
266
+ return 'announcementItems';
267
+ return 'genericItems';
268
+ }
198
269
  function convertLegacyField(field) {
199
270
  const type = normalizeLegacyFieldType(field.type);
200
271
  const base = {
@@ -265,6 +336,25 @@ function createListItemShape(kind) {
265
336
  author: { type: 'text', label: 'Author' },
266
337
  };
267
338
  }
339
+ if (kind === 'navLinks') {
340
+ return {
341
+ text: { type: 'text', label: 'Text' },
342
+ link: { type: 'link', label: 'Link' },
343
+ };
344
+ }
345
+ if (kind === 'featureItems') {
346
+ return {
347
+ title: { type: 'text', label: 'Title' },
348
+ description: { type: 'richtext', label: 'Description' },
349
+ icon: { type: 'text', label: 'Icon' },
350
+ };
351
+ }
352
+ if (kind === 'announcementItems') {
353
+ return {
354
+ text: { type: 'text', label: 'Text' },
355
+ link: { type: 'link', label: 'Link' },
356
+ };
357
+ }
268
358
  return {
269
359
  title: { type: 'text', label: 'Title' },
270
360
  body: { type: 'richtext', label: 'Body' },
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,eAAe,EAEpB,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACvC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,QAAQ,SAAI,GAAG,eAAe,CAYxE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,UAAU,CAAC;CAC9B,GAAG,aAAa,CAShB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,CAE9E;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,0BAA0B,EAAE,QAAQ,SAAI,GAAG,eAAe,CAkB7G;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,GAAG,eAAe,CAwB9G;AAED,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAa/F"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,eAAe,EAEpB,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACvC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,QAAQ,SAAI,GAAG,eAAe,CAYxE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,UAAU,CAAC;CAC9B,GAAG,aAAa,CAShB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,CAE9E;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,0BAA0B,EAAE,QAAQ,SAAI,GAAG,eAAe,CAkB7G;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,GAAG,eAAe,CAwB9G;AAED,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAuC/F"}
@@ -1,4 +1,5 @@
1
1
  import { BUILDER_SETTINGS_VERSION, BuilderSettingsSchema, } from "./builder-settings.js";
2
+ import { sanitizeBuilderSettingsState } from "./persistence.js";
2
3
  export function createEmptyBuilderSettings(revision = 0) {
3
4
  return {
4
5
  version: BUILDER_SETTINGS_VERSION,
@@ -31,7 +32,7 @@ export function migrateLegacyBuilderSettings(input, revision = 0) {
31
32
  ...mapLegacyTokenOverridesToStyleSlots(tokensOverride),
32
33
  ...(input.theme?.style_slots ?? {}),
33
34
  };
34
- return BuilderSettingsSchema.parse({
35
+ return sanitizeBuilderSettingsState(BuilderSettingsSchema.parse({
35
36
  version: BUILDER_SETTINGS_VERSION,
36
37
  revision,
37
38
  theme: {
@@ -41,7 +42,7 @@ export function migrateLegacyBuilderSettings(input, revision = 0) {
41
42
  pages: [],
42
43
  terms: {},
43
44
  },
44
- });
45
+ }));
45
46
  }
46
47
  export function applyManifestDefaultLayout(settings, manifest) {
47
48
  const next = cloneBuilderSettings(settings);
@@ -68,13 +69,33 @@ export function applyManifestDefaultLayout(settings, manifest) {
68
69
  export function mapLegacyTokenOverridesToStyleSlots(tokens) {
69
70
  const slots = {};
70
71
  assignResponsiveNumber(tokens, slots, 'buttons.borderRadius', 'button.radius');
72
+ assignColor(tokens, slots, 'buttons.background', 'button.background');
73
+ assignColor(tokens, slots, 'buttons.foreground', 'button.foreground');
74
+ assignColor(tokens, slots, 'buttons.border', 'button.border');
75
+ assignFontWeight(tokens, slots, 'buttons.fontWeight', 'button.font.weight');
71
76
  assignResponsiveNumber(tokens, slots, 'inputs.borderRadius', 'input.radius');
72
77
  assignResponsiveNumber(tokens, slots, 'inputs.height', 'input.height');
78
+ assignColor(tokens, slots, 'inputs.border', 'input.border');
79
+ assignColor(tokens, slots, 'inputs.background', 'input.background');
80
+ assignColor(tokens, slots, 'inputs.foreground', 'input.foreground');
81
+ assignResponsiveNumber(tokens, slots, 'cards.borderRadius', 'card.radius');
82
+ assignColor(tokens, slots, 'cards.background', 'card.background');
83
+ assignColor(tokens, slots, 'cards.border', 'card.border');
84
+ assignResponsiveNumber(tokens, slots, 'sections.paddingY', 'section.padding.y');
85
+ assignResponsiveNumber(tokens, slots, 'sections.paddingX', 'section.padding.x');
86
+ assignResponsiveNumber(tokens, slots, 'container.width', 'container.width');
73
87
  assignColor(tokens, slots, 'colors.primary', 'color.primary');
74
88
  assignColor(tokens, slots, 'colors.accent', 'color.accent');
75
89
  assignColor(tokens, slots, 'colors.background', 'color.background');
76
90
  assignColor(tokens, slots, 'colors.foreground', 'color.foreground');
77
91
  assignColor(tokens, slots, 'colors.muted', 'color.muted');
92
+ assignColor(tokens, slots, 'links.color', 'link.color');
93
+ assignFontWeight(tokens, slots, 'typography.headingWeight', 'typography.heading.weight');
94
+ assignResponsiveNumber(tokens, slots, 'typography.fontSize', 'typography.body.size');
95
+ assignResponsiveNumber(tokens, slots, 'typography.bodySize', 'typography.body.size');
96
+ assignString(tokens, slots, 'typography.fontFamily', 'theme.typography.font.family');
97
+ assignString(tokens, slots, 'typography.headingFont', 'theme.typography.heading.font');
98
+ assignResponsiveNumber(tokens, slots, 'header.height', 'theme.header.height');
78
99
  return slots;
79
100
  }
80
101
  function normalizeLegacyLayout(layout) {
@@ -128,7 +149,7 @@ function isRecord(value) {
128
149
  return typeof value === 'object' && value !== null && !Array.isArray(value);
129
150
  }
130
151
  function assignResponsiveNumber(tokens, slots, tokenPath, slotId) {
131
- const value = tokens[tokenPath];
152
+ const value = readLegacyTokenPath(tokens, tokenPath);
132
153
  if (typeof value === 'number' && Number.isFinite(value)) {
133
154
  slots[slotId] = { base: value };
134
155
  return;
@@ -138,11 +159,36 @@ function assignResponsiveNumber(tokens, slots, tokenPath, slotId) {
138
159
  }
139
160
  }
140
161
  function assignColor(tokens, slots, tokenPath, slotId) {
141
- const value = tokens[tokenPath];
162
+ const value = readLegacyTokenPath(tokens, tokenPath);
142
163
  if (typeof value === 'string') {
143
164
  slots[slotId] = value;
144
165
  }
145
166
  }
167
+ function assignString(tokens, slots, tokenPath, slotId) {
168
+ const value = readLegacyTokenPath(tokens, tokenPath);
169
+ if (typeof value === 'string' && value.trim().length > 0) {
170
+ slots[slotId] = value;
171
+ }
172
+ }
173
+ function assignFontWeight(tokens, slots, tokenPath, slotId) {
174
+ const value = readLegacyTokenPath(tokens, tokenPath);
175
+ if (typeof value === 'number' && Number.isFinite(value)) {
176
+ slots[slotId] = value;
177
+ }
178
+ }
179
+ function readLegacyTokenPath(tokens, tokenPath) {
180
+ if (Object.prototype.hasOwnProperty.call(tokens, tokenPath)) {
181
+ return tokens[tokenPath];
182
+ }
183
+ let current = tokens;
184
+ for (const segment of tokenPath.split('.')) {
185
+ if (!isRecord(current)) {
186
+ return undefined;
187
+ }
188
+ current = current[segment];
189
+ }
190
+ return current;
191
+ }
146
192
  function isResponsiveNumber(value) {
147
193
  if (!value || typeof value !== 'object') {
148
194
  return false;
@@ -0,0 +1,7 @@
1
+ import { type BuilderSettings } from './builder-settings.ts';
2
+ type JsonRecord = Record<string, unknown>;
3
+ export declare function extractPersistedBuilderSettings(input: unknown): BuilderSettings | null;
4
+ export declare function sanitizeBuilderSettingsState(settings: BuilderSettings): BuilderSettings;
5
+ export declare function mergeBuilderSettingsIntoThemeSettings(currentSettings: unknown, builderSettings: BuilderSettings): JsonRecord;
6
+ export {};
7
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmB1C,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAYtF;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CASvF;AAaD,wBAAgB,qCAAqC,CACnD,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,eAAe,GAC/B,UAAU,CAaZ"}
@@ -0,0 +1,58 @@
1
+ import { BuilderSettingsSchema, } from "./builder-settings.js";
2
+ const RESERVED_THEME_CONTENT_KEYS = new Set(['layout']);
3
+ function isRecord(value) {
4
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
5
+ }
6
+ function pickBuilderSettingsFields(value) {
7
+ if (!isRecord(value)) {
8
+ return null;
9
+ }
10
+ return {
11
+ version: value.version,
12
+ revision: value.revision,
13
+ theme: value.theme,
14
+ };
15
+ }
16
+ export function extractPersistedBuilderSettings(input) {
17
+ const parsedDirect = BuilderSettingsSchema.safeParse(pickBuilderSettingsFields(input));
18
+ if (parsedDirect.success) {
19
+ return sanitizeBuilderSettingsState(parsedDirect.data);
20
+ }
21
+ if (!isRecord(input)) {
22
+ return null;
23
+ }
24
+ const parsedNested = BuilderSettingsSchema.safeParse(pickBuilderSettingsFields(input.builder_settings));
25
+ return parsedNested.success ? sanitizeBuilderSettingsState(parsedNested.data) : null;
26
+ }
27
+ export function sanitizeBuilderSettingsState(settings) {
28
+ const content = sanitizeThemeContent(settings.theme.content);
29
+ return BuilderSettingsSchema.parse({
30
+ ...settings,
31
+ theme: {
32
+ ...settings.theme,
33
+ content,
34
+ },
35
+ });
36
+ }
37
+ function sanitizeThemeContent(content) {
38
+ const next = {};
39
+ for (const [key, value] of Object.entries(content)) {
40
+ if (RESERVED_THEME_CONTENT_KEYS.has(key)) {
41
+ continue;
42
+ }
43
+ next[key] = value;
44
+ }
45
+ return next;
46
+ }
47
+ export function mergeBuilderSettingsIntoThemeSettings(currentSettings, builderSettings) {
48
+ const root = isRecord(currentSettings) ? { ...currentSettings } : {};
49
+ const currentBuilderSettings = isRecord(root.builder_settings) ? root.builder_settings : {};
50
+ const sanitizedBuilderSettings = sanitizeBuilderSettingsState(builderSettings);
51
+ root.builder_settings = {
52
+ ...currentBuilderSettings,
53
+ version: sanitizedBuilderSettings.version,
54
+ revision: sanitizedBuilderSettings.revision,
55
+ theme: sanitizedBuilderSettings.theme,
56
+ };
57
+ return root;
58
+ }
@@ -0,0 +1,68 @@
1
+ import * as z from 'zod/v4';
2
+ export declare const PREVIEW_BOOT_SIDECAR_PATH = "__shoppex/preview-boot.json";
3
+ export declare const StorefrontSeedSchema: z.ZodObject<{
4
+ store: z.ZodRecord<z.ZodString, z.ZodUnknown>;
5
+ products: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
6
+ groups: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
7
+ pages: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
8
+ }, z.core.$strict>;
9
+ export type StorefrontSeed = z.infer<typeof StorefrontSeedSchema>;
10
+ export declare const PreviewBootPayloadSchema: z.ZodObject<{
11
+ shopId: z.ZodString;
12
+ shopSlug: z.ZodString;
13
+ builderSettings: z.ZodObject<{
14
+ version: z.ZodLiteral<2>;
15
+ revision: z.ZodNumber;
16
+ theme: z.ZodObject<{
17
+ content: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
18
+ layout: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
19
+ blocks: z.ZodDefault<z.ZodArray<z.ZodObject<{
20
+ id: z.ZodString;
21
+ type: z.ZodString;
22
+ variant: z.ZodOptional<z.ZodString>;
23
+ visible: z.ZodDefault<z.ZodBoolean>;
24
+ settings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
25
+ style_overrides: z.ZodOptional<z.ZodType<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown, z.core.$ZodTypeInternals<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown>>>;
26
+ }, z.core.$strict>>>;
27
+ }, z.core.$strict>>>;
28
+ style_slots: z.ZodDefault<z.ZodType<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown, z.core.$ZodTypeInternals<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown>>>;
29
+ pages: z.ZodDefault<z.ZodArray<z.ZodObject<{
30
+ id: z.ZodString;
31
+ title: z.ZodString;
32
+ slug: z.ZodString;
33
+ visible: z.ZodDefault<z.ZodBoolean>;
34
+ layout: z.ZodDefault<z.ZodObject<{
35
+ blocks: z.ZodDefault<z.ZodArray<z.ZodObject<{
36
+ id: z.ZodString;
37
+ type: z.ZodString;
38
+ variant: z.ZodOptional<z.ZodString>;
39
+ visible: z.ZodDefault<z.ZodBoolean>;
40
+ settings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
41
+ style_overrides: z.ZodOptional<z.ZodType<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown, z.core.$ZodTypeInternals<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown>>>;
42
+ }, z.core.$strict>>>;
43
+ }, z.core.$strict>>;
44
+ seo: z.ZodOptional<z.ZodObject<{
45
+ title: z.ZodOptional<z.ZodString>;
46
+ description: z.ZodOptional<z.ZodString>;
47
+ }, z.core.$strict>>;
48
+ }, z.core.$strict>>>;
49
+ terms: z.ZodDefault<z.ZodObject<{
50
+ termsOfService: z.ZodOptional<z.ZodString>;
51
+ privacyPolicy: z.ZodOptional<z.ZodString>;
52
+ refundPolicy: z.ZodOptional<z.ZodString>;
53
+ imprint: z.ZodOptional<z.ZodString>;
54
+ }, z.core.$strict>>;
55
+ }, z.core.$strict>;
56
+ }, z.core.$strict>;
57
+ storefrontSeed: z.ZodObject<{
58
+ store: z.ZodRecord<z.ZodString, z.ZodUnknown>;
59
+ products: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
60
+ groups: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
61
+ pages: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
62
+ }, z.core.$strict>;
63
+ artifactRevision: z.ZodOptional<z.ZodString>;
64
+ artifactStale: z.ZodOptional<z.ZodBoolean>;
65
+ }, z.core.$strict>;
66
+ export type PreviewBootPayload = z.infer<typeof PreviewBootPayloadSchema>;
67
+ export declare function mergePreviewBootIntoInitialData(payload: PreviewBootPayload): Record<string, unknown>;
68
+ //# sourceMappingURL=preview-boot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-boot.d.ts","sourceRoot":"","sources":["../src/preview-boot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AAEvE,eAAO,MAAM,oBAAoB;;;;;kBAOtB,CAAC;AAEZ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AAEZ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,kBAAkB,GAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB"}
@@ -0,0 +1,36 @@
1
+ import * as z from 'zod/v4';
2
+ import { BuilderSettingsSchema } from "./builder-settings.js";
3
+ export const PREVIEW_BOOT_SIDECAR_PATH = '__shoppex/preview-boot.json';
4
+ export const StorefrontSeedSchema = z
5
+ .object({
6
+ store: z.record(z.string(), z.unknown()),
7
+ products: z.array(z.unknown()).optional(),
8
+ groups: z.array(z.unknown()).optional(),
9
+ pages: z.array(z.unknown()).optional(),
10
+ })
11
+ .strict();
12
+ export const PreviewBootPayloadSchema = z
13
+ .object({
14
+ shopId: z.string().min(1),
15
+ shopSlug: z.string().min(1),
16
+ builderSettings: BuilderSettingsSchema,
17
+ storefrontSeed: StorefrontSeedSchema,
18
+ artifactRevision: z.string().optional(),
19
+ artifactStale: z.boolean().optional(),
20
+ })
21
+ .strict();
22
+ export function mergePreviewBootIntoInitialData(payload) {
23
+ const existingStore = isRecord(payload.storefrontSeed.store) ? payload.storefrontSeed.store : {};
24
+ return {
25
+ ...payload.storefrontSeed,
26
+ store: {
27
+ ...existingStore,
28
+ id: payload.shopId,
29
+ slug: payload.shopSlug,
30
+ builder_settings: payload.builderSettings,
31
+ },
32
+ };
33
+ }
34
+ function isRecord(value) {
35
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
36
+ }