@wocart/theme 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +479 -2
- package/dist/astro/InstanceView.astro +167 -0
- package/dist/astro/index.d.ts +34 -0
- package/dist/astro/index.js +21 -0
- package/dist/definitions.d.ts +7 -0
- package/dist/definitions.js +422 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +18 -0
- package/dist/instances.d.ts +43 -0
- package/dist/instances.js +307 -0
- package/dist/locales.d.ts +12 -0
- package/dist/locales.js +14 -0
- package/dist/manifest.d.ts +178 -0
- package/dist/manifest.js +325 -0
- package/dist/messages.d.ts +60 -0
- package/dist/messages.js +100 -0
- package/dist/page-data.d.ts +9 -0
- package/dist/page-data.js +154 -0
- package/dist/schemas.d.ts +57 -0
- package/dist/schemas.js +108 -0
- package/dist/settings.d.ts +15 -0
- package/dist/settings.js +80 -0
- package/dist/types.d.ts +479 -0
- package/dist/types.js +1 -0
- package/package.json +80 -4
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `@wocart/theme` contract — every type in one place.
|
|
3
|
+
*
|
|
4
|
+
* Two sides, one seam:
|
|
5
|
+
* - code-owned: `ThemeDefinition` / `ComponentDefinition` (never serialized);
|
|
6
|
+
* - data-owned: `ComponentInstance` / `PageData` (always JSON).
|
|
7
|
+
*/
|
|
8
|
+
import type { StoreClient } from "@wocart/storefront/server";
|
|
9
|
+
import type { StoreConfig } from "@wocart/storefront";
|
|
10
|
+
/** Every value page data may hold. */
|
|
11
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
12
|
+
[key: string]: JsonValue;
|
|
13
|
+
};
|
|
14
|
+
/** A JSON object, as stored in `ComponentInstance.props`. */
|
|
15
|
+
export type JsonObject = {
|
|
16
|
+
[key: string]: JsonValue;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A BCP 47 language tag in canonical case: `ar`, `fr`, `en`, `ar-DZ`, `zgh`.
|
|
20
|
+
* A plain string type on purpose: the set of languages is platform data, never
|
|
21
|
+
* a closed enum baked into the contract.
|
|
22
|
+
*/
|
|
23
|
+
export type LocaleCode = string;
|
|
24
|
+
/**
|
|
25
|
+
* Text that is the same in every language (a plain string), or one value per
|
|
26
|
+
* language. Used for translatable field values, translatable text defaults, and
|
|
27
|
+
* editor-facing labels.
|
|
28
|
+
*/
|
|
29
|
+
export type LocalizedText = string | Readonly<Record<LocaleCode, string>>;
|
|
30
|
+
/**
|
|
31
|
+
* What resolution needs besides the instance: the theme's content pack for
|
|
32
|
+
* the language being rendered, and the language preference — the requested
|
|
33
|
+
* language first, then the store's fallback chain.
|
|
34
|
+
*/
|
|
35
|
+
export interface ResolutionScope {
|
|
36
|
+
content?: Record<string, string>;
|
|
37
|
+
locales?: readonly LocaleCode[];
|
|
38
|
+
}
|
|
39
|
+
/** The field kinds. `color` and `menu` were added after V1, additively. */
|
|
40
|
+
export type FieldKind = "text" | "textarea" | "number" | "boolean" | "select" | "url" | "color" | "menu";
|
|
41
|
+
interface FieldDefinitionBase {
|
|
42
|
+
/** The exact prop name the component expects. */
|
|
43
|
+
key: string;
|
|
44
|
+
/** Editor-facing name, the same in every language or translated. */
|
|
45
|
+
label: LocalizedText;
|
|
46
|
+
/** Editor-facing help text. */
|
|
47
|
+
description?: LocalizedText;
|
|
48
|
+
required?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `text` / `textarea`: a string that defaults from the content pack
|
|
52
|
+
* (`contentKey`) or from a literal (`default`) — mutually exclusive.
|
|
53
|
+
*/
|
|
54
|
+
export interface TextFieldDefinition extends FieldDefinitionBase {
|
|
55
|
+
kind: "text" | "textarea";
|
|
56
|
+
/**
|
|
57
|
+
* The key in the **theme's own** content pack that supplies this field's
|
|
58
|
+
* creation-time value. The SDK never models the pack: it reads
|
|
59
|
+
* `ThemeRenderContext.content` — an opaque flat string map — and falls
|
|
60
|
+
* through to `default` → omit when the key is absent. Pass the pack's key set
|
|
61
|
+
* to `validateThemeDefinition` to have typos reported.
|
|
62
|
+
*/
|
|
63
|
+
contentKey?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Literal creation-time default (mutually exclusive with `contentKey`), the
|
|
66
|
+
* same in every language or translated. Prefer `contentKey` for theme copy:
|
|
67
|
+
* the pack is already per language.
|
|
68
|
+
*/
|
|
69
|
+
default?: LocalizedText;
|
|
70
|
+
}
|
|
71
|
+
/** `number`: a finite number, optionally bounded. */
|
|
72
|
+
export interface NumberFieldDefinition extends FieldDefinitionBase {
|
|
73
|
+
kind: "number";
|
|
74
|
+
default?: number;
|
|
75
|
+
min?: number;
|
|
76
|
+
max?: number;
|
|
77
|
+
}
|
|
78
|
+
/** `boolean`. */
|
|
79
|
+
export interface BooleanFieldDefinition extends FieldDefinitionBase {
|
|
80
|
+
kind: "boolean";
|
|
81
|
+
default?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** `select`: one of a closed option list. */
|
|
84
|
+
export interface SelectFieldDefinition extends FieldDefinitionBase {
|
|
85
|
+
kind: "select";
|
|
86
|
+
options: readonly string[];
|
|
87
|
+
default?: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* `url`: a link target an editor may set, safe to render as an `href`. Only a
|
|
91
|
+
* site path (`/products`), a fragment (`#best-sellers`), a query (`?page=2`),
|
|
92
|
+
* `http(s)://`, `mailto:` or `tel:` is accepted. Anything else, including
|
|
93
|
+
* `javascript:`, `data:`, protocol-relative `//host` and backslash tricks, is
|
|
94
|
+
* rejected at assembly and reported as `invalid-prop` on read.
|
|
95
|
+
*/
|
|
96
|
+
export interface UrlFieldDefinition extends FieldDefinitionBase {
|
|
97
|
+
kind: "url";
|
|
98
|
+
default?: string;
|
|
99
|
+
}
|
|
100
|
+
/** `color`: a hex color, `#rgb` or `#rrggbb`. Not translatable. */
|
|
101
|
+
export interface ColorFieldDefinition extends FieldDefinitionBase {
|
|
102
|
+
kind: "color";
|
|
103
|
+
default?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `menu`: which of the store's menus to show here — Shopify's `link_list`.
|
|
107
|
+
* The value is a menu handle (`main-menu`); the theme reads the menu with
|
|
108
|
+
* the storefront client's `menus.get(handle)`. A theme never assumes a menu
|
|
109
|
+
* exists: an unknown handle reads as no menu.
|
|
110
|
+
*/
|
|
111
|
+
export interface MenuFieldDefinition extends FieldDefinitionBase {
|
|
112
|
+
kind: "menu";
|
|
113
|
+
default?: string;
|
|
114
|
+
}
|
|
115
|
+
export type FieldDefinition = TextFieldDefinition | NumberFieldDefinition | BooleanFieldDefinition | SelectFieldDefinition | UrlFieldDefinition | ColorFieldDefinition | MenuFieldDefinition;
|
|
116
|
+
/**
|
|
117
|
+
* One composition point a component declares (`"default"` = children).
|
|
118
|
+
*
|
|
119
|
+
* **Renderer ceiling (V1):** the reference renderer (`@wocart/theme/astro`)
|
|
120
|
+
* fills **at most one named slot** per instance. The Astro compiler lifts the
|
|
121
|
+
* first slot-attribute expression of a dynamic child expression into the
|
|
122
|
+
* slots-object key, evaluated at component scope, so per-item slot names inside
|
|
123
|
+
* a `.map()` are not expressible. Any further named slot **with content** is
|
|
124
|
+
* reported as `multiple-slots-unsupported` — never silently dropped.
|
|
125
|
+
*
|
|
126
|
+
* Declaring several named slots is legal; it is a capability declaration, not a
|
|
127
|
+
* promise that all of them can be filled at once. A component that needs more
|
|
128
|
+
* than one filled can transfer Astro's own slots inside itself
|
|
129
|
+
* (`<slot name="x" slot="x" />`) — a component-level decision, deliberately not
|
|
130
|
+
* part of this contract.
|
|
131
|
+
*/
|
|
132
|
+
export interface SlotDefinition {
|
|
133
|
+
/** `"default"` or a lowercase kebab-case name. */
|
|
134
|
+
name: string;
|
|
135
|
+
/** False = at most one child instance. Defaults to true. */
|
|
136
|
+
multiple?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* The component types this slot accepts (catalog keys). Absent = any type.
|
|
139
|
+
* Assembly rejects a type that is not in the catalog; the reader drops a
|
|
140
|
+
* child of any other type with a `disallowed-child` issue. Narrowing a list
|
|
141
|
+
* after documents are saved hides their children, so treat it like removing
|
|
142
|
+
* a slot.
|
|
143
|
+
*/
|
|
144
|
+
allow?: readonly string[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* What a component permits.
|
|
148
|
+
*
|
|
149
|
+
* V1 carries one flag, and it is **advisory**: `insertable` is metadata for an
|
|
150
|
+
* editor/CLI's insertion palette. Neither `parsePageData` nor `resolveInstance`
|
|
151
|
+
* enforces it — a saved document containing a non-insertable component still
|
|
152
|
+
* renders, and the renderer reports only what the document tells it. The render
|
|
153
|
+
* path never second-guesses a document that was already saved. Enforcement, when
|
|
154
|
+
* it exists, belongs at *insertion* time in the writer, not at render.
|
|
155
|
+
*/
|
|
156
|
+
export interface ComponentCapabilities {
|
|
157
|
+
insertable?: boolean;
|
|
158
|
+
}
|
|
159
|
+
/** The request facts available while rendering (formalises `Astro.locals.store` + URL). */
|
|
160
|
+
export interface ThemeRenderContext {
|
|
161
|
+
store: StoreConfig;
|
|
162
|
+
/**
|
|
163
|
+
* The theme's resolved content pack: an opaque, flat string map with store
|
|
164
|
+
* overrides already merged. The SDK reads it (through a field's `contentKey`)
|
|
165
|
+
* and never shapes it — the taxonomy belongs to the theme, and the theme owns
|
|
166
|
+
* how its components consume the pack.
|
|
167
|
+
*/
|
|
168
|
+
content: Record<string, string>;
|
|
169
|
+
/**
|
|
170
|
+
* The language being rendered (BCP 47). Translatable field values resolve
|
|
171
|
+
* to it first; `content` is the pack for it.
|
|
172
|
+
*/
|
|
173
|
+
locale: LocaleCode;
|
|
174
|
+
/**
|
|
175
|
+
* The store's fallback chain after `locale`, in order (e.g. the store's
|
|
176
|
+
* primary language, then the platform default). A translatable value with
|
|
177
|
+
* no entry for `locale` tries these before the pack and the default.
|
|
178
|
+
*/
|
|
179
|
+
fallbackLocales?: readonly LocaleCode[];
|
|
180
|
+
isRTL: boolean;
|
|
181
|
+
/** Request pathname. */
|
|
182
|
+
url: string;
|
|
183
|
+
/**
|
|
184
|
+
* The route's dynamic segments (`Astro.params`), e.g. `{ slug: "chair" }` on
|
|
185
|
+
* `/products/[slug]`. A section on a templated surface reads its route
|
|
186
|
+
* resource through these, with a `reads` key that includes them, instead of
|
|
187
|
+
* parsing `url`.
|
|
188
|
+
*/
|
|
189
|
+
params?: Readonly<Record<string, string | undefined>>;
|
|
190
|
+
}
|
|
191
|
+
/** Per-render read sharing; the renderer provides the implementation. */
|
|
192
|
+
export interface ReadCache {
|
|
193
|
+
get<T>(key: string, load: () => T | Promise<T>): Promise<T>;
|
|
194
|
+
}
|
|
195
|
+
/** What a `load` hook receives. Server-only; reads go through the storefront SDK. */
|
|
196
|
+
export interface LoadContext {
|
|
197
|
+
storefront: StoreClient;
|
|
198
|
+
context: ThemeRenderContext;
|
|
199
|
+
/**
|
|
200
|
+
* The resolved field values for this instance.
|
|
201
|
+
*
|
|
202
|
+
* The renderer merges `load`'s return value **over** these
|
|
203
|
+
* (`{ ...props, ...loaded }`), so a load-returned key wins over a field of
|
|
204
|
+
* the same name. A definition whose component reads a prop from `load` should
|
|
205
|
+
* keep that name out of `fields`.
|
|
206
|
+
*/
|
|
207
|
+
props: Record<string, unknown>;
|
|
208
|
+
reads: ReadCache;
|
|
209
|
+
}
|
|
210
|
+
/** Server-side data fetch for one component definition. */
|
|
211
|
+
export type LoadFn = (context: LoadContext) => Promise<Record<string, unknown>>;
|
|
212
|
+
/**
|
|
213
|
+
* Code-owned description of one reusable component. `component` is the real
|
|
214
|
+
* component reference — opaque here so the core stays framework-neutral; the
|
|
215
|
+
* `./astro` renderer narrows it.
|
|
216
|
+
*/
|
|
217
|
+
export interface ComponentDefinition {
|
|
218
|
+
/** Stable type identity: lowercase kebab-case, unique in the catalog. */
|
|
219
|
+
type: string;
|
|
220
|
+
component: unknown;
|
|
221
|
+
label: LocalizedText;
|
|
222
|
+
/** The editor palette heading this component is listed under. */
|
|
223
|
+
group?: LocalizedText;
|
|
224
|
+
fields?: readonly FieldDefinition[];
|
|
225
|
+
slots?: readonly SlotDefinition[];
|
|
226
|
+
load?: LoadFn;
|
|
227
|
+
capabilities?: ComponentCapabilities;
|
|
228
|
+
}
|
|
229
|
+
/** The value type one field resolves to. A select narrows to its option literals. */
|
|
230
|
+
export type FieldValue<F extends FieldDefinition> = F extends {
|
|
231
|
+
kind: "number";
|
|
232
|
+
} ? number : F extends {
|
|
233
|
+
kind: "boolean";
|
|
234
|
+
} ? boolean : F extends {
|
|
235
|
+
kind: "select";
|
|
236
|
+
options: readonly (infer Option)[];
|
|
237
|
+
} ? Option : string;
|
|
238
|
+
type FieldsOf<D> = D extends {
|
|
239
|
+
fields: readonly (infer F extends FieldDefinition)[];
|
|
240
|
+
} ? F : never;
|
|
241
|
+
type FieldProps<D> = {
|
|
242
|
+
[F in FieldsOf<D> as F extends {
|
|
243
|
+
default: unknown;
|
|
244
|
+
} ? F["key"] : never]: FieldValue<F>;
|
|
245
|
+
} & {
|
|
246
|
+
[F in FieldsOf<D> as F extends {
|
|
247
|
+
default: unknown;
|
|
248
|
+
} ? never : F["key"]]?: FieldValue<F>;
|
|
249
|
+
};
|
|
250
|
+
type LoadedProps<D> = D extends {
|
|
251
|
+
load: (context: LoadContext) => Promise<infer L>;
|
|
252
|
+
} ? L : {};
|
|
253
|
+
/**
|
|
254
|
+
* The props `InstanceView` passes to a definition's component, derived from
|
|
255
|
+
* the definition so the component and its fields cannot drift apart:
|
|
256
|
+
*
|
|
257
|
+
* ```ts
|
|
258
|
+
* type Props = InferProps<typeof heroDefinition>;
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* A field with a `default` is always present; any other field may be absent
|
|
262
|
+
* (no value, no content, or reported as invalid). Whatever `load` returns is
|
|
263
|
+
* merged over the fields, as the renderer does at runtime. Use `defineComponent`
|
|
264
|
+
* so field keys, kinds and select options keep their literal types.
|
|
265
|
+
*/
|
|
266
|
+
export type InferProps<D extends ComponentDefinition> = Omit<FieldProps<D>, keyof LoadedProps<D>> & LoadedProps<D>;
|
|
267
|
+
/** The catalog a theme ships — the unit the renderer and validator consume. */
|
|
268
|
+
export interface ThemeDefinition {
|
|
269
|
+
key: string;
|
|
270
|
+
version: string;
|
|
271
|
+
/**
|
|
272
|
+
* The surfaces this theme can render, declared as data. Optional and
|
|
273
|
+
* additive: a theme that declares none is treated as declaring none, and no
|
|
274
|
+
* existing page-document behavior changes — `parsePageData` /
|
|
275
|
+
* `validatePageData` never consult this list. Surface *enforcement* (rejecting
|
|
276
|
+
* a document whose surface the theme does not serve) is a later, separately
|
|
277
|
+
* tested concern; the manifest carries the declaration now so an editor can
|
|
278
|
+
* offer only valid surfaces.
|
|
279
|
+
*/
|
|
280
|
+
surfaces?: readonly SurfaceSpec[];
|
|
281
|
+
/**
|
|
282
|
+
* The languages this theme's content packs cover (BCP 47), e.g.
|
|
283
|
+
* `["ar", "fr", "en"]`. Declared, the validator requires every translated
|
|
284
|
+
* label to cover all of them, and the manifest tells an editor which
|
|
285
|
+
* languages the theme can serve. Absent means "not declared".
|
|
286
|
+
*/
|
|
287
|
+
locales?: readonly LocaleCode[];
|
|
288
|
+
/**
|
|
289
|
+
* The theme's global settings: what a merchant may change about the whole
|
|
290
|
+
* theme (its font, its colors, which menu the header shows), declared with
|
|
291
|
+
* the same fields sections use, in named groups. Optional and additive. A
|
|
292
|
+
* store's values are read with `resolveThemeSettings`; a writer checks them
|
|
293
|
+
* with `checkThemeSettings`. Setting keys are unique across all groups.
|
|
294
|
+
*/
|
|
295
|
+
settings?: readonly SettingsGroup[];
|
|
296
|
+
components: Record<string, ComponentDefinition>;
|
|
297
|
+
}
|
|
298
|
+
/** One group of theme settings, as an editor lists them ("Typography", "Header"). */
|
|
299
|
+
export interface SettingsGroup {
|
|
300
|
+
/** Lowercase kebab-case, unique in the theme. */
|
|
301
|
+
key: string;
|
|
302
|
+
label: LocalizedText;
|
|
303
|
+
fields: readonly FieldDefinition[];
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* One surface a theme declares it can render.
|
|
307
|
+
*
|
|
308
|
+
* `key` uses the same surface-key pattern as the page-document envelope
|
|
309
|
+
* (`SURFACE_KEY_PATTERN` in `schemas.ts`); `label` is the editor-facing name.
|
|
310
|
+
*/
|
|
311
|
+
export interface SurfaceSpec {
|
|
312
|
+
key: string;
|
|
313
|
+
label: LocalizedText;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* One component as data: a `ComponentDefinition` minus its runtime-only
|
|
317
|
+
* implementation. `component` (the live component reference) and `load` (the
|
|
318
|
+
* server-only data hook) are intentionally absent — neither is
|
|
319
|
+
* JSON-serializable and neither belongs in a tooling contract.
|
|
320
|
+
*
|
|
321
|
+
* `fields` / `slots` are always present (normalized from absent to `[]` by the
|
|
322
|
+
* generator) so consumers never branch on "missing vs empty".
|
|
323
|
+
*/
|
|
324
|
+
export interface ComponentSpec {
|
|
325
|
+
/** Stable type identity: lowercase kebab-case, equal to the catalog key. */
|
|
326
|
+
type: string;
|
|
327
|
+
/** Editor-facing name. */
|
|
328
|
+
label: LocalizedText;
|
|
329
|
+
/** The editor palette heading this component is listed under. */
|
|
330
|
+
group?: LocalizedText;
|
|
331
|
+
fields: readonly FieldDefinition[];
|
|
332
|
+
slots: readonly SlotDefinition[];
|
|
333
|
+
capabilities?: ComponentCapabilities;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* The serializable theme contract.
|
|
337
|
+
*
|
|
338
|
+
* - `contractVersion` identifies this manifest's schema and interpretation
|
|
339
|
+
* contract (currently `1`). It versions the *format*, not the theme.
|
|
340
|
+
* - `key` / `version` identify the *theme* (same values as `ThemeDefinition`).
|
|
341
|
+
* - `surfaces` declares what the theme can render (possibly empty).
|
|
342
|
+
* - `components` is the data-only catalogue, keyed by component type.
|
|
343
|
+
*
|
|
344
|
+
* Three versioned contracts coexist and must not be confused:
|
|
345
|
+
* `contractVersion` (this manifest), `ThemeDefinition.version` (the theme's
|
|
346
|
+
* semver), and `PageData.version` (the page-document envelope, currently `1`).
|
|
347
|
+
*/
|
|
348
|
+
export interface ThemeManifest {
|
|
349
|
+
contractVersion: 1;
|
|
350
|
+
key: string;
|
|
351
|
+
version: string;
|
|
352
|
+
surfaces: readonly SurfaceSpec[];
|
|
353
|
+
/** The languages the theme's packs cover (possibly none declared). */
|
|
354
|
+
locales: readonly LocaleCode[];
|
|
355
|
+
/** The theme's global settings (possibly none). */
|
|
356
|
+
settings: readonly SettingsGroup[];
|
|
357
|
+
components: Record<string, ComponentSpec>;
|
|
358
|
+
}
|
|
359
|
+
export type ThemeManifestIssueCode = "invalid-manifest" | "unsupported-contract" | "invalid-identity" | "invalid-version" | "invalid-surface" | "invalid-settings" | "invalid-component" | "type-mismatch" | "non-serializable";
|
|
360
|
+
/** One reported manifest problem; `path` points into the manifest (e.g. `components.hero.fields[0].kind`). */
|
|
361
|
+
export interface ThemeManifestIssue {
|
|
362
|
+
path: string;
|
|
363
|
+
code: ThemeManifestIssueCode;
|
|
364
|
+
message: string;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Result of the manifest boundary. `manifest` is non-null only when the input
|
|
368
|
+
* is structurally sound: structural failures (wrong shape, unsupported
|
|
369
|
+
* contract, malformed components, non-serializable values) yield `null` plus
|
|
370
|
+
* issues. The one advisory check — a non-semver `version` — returns the
|
|
371
|
+
* manifest *with* an `invalid-version` issue, so a version-format concern is
|
|
372
|
+
* never confused with a fatal structural failure.
|
|
373
|
+
*/
|
|
374
|
+
export interface ThemeManifestResult {
|
|
375
|
+
manifest: ThemeManifest | null;
|
|
376
|
+
issues: ThemeManifestIssue[];
|
|
377
|
+
}
|
|
378
|
+
/** One occurrence of a component on one page (JSON only). */
|
|
379
|
+
export interface ComponentInstance {
|
|
380
|
+
id: string;
|
|
381
|
+
type: string;
|
|
382
|
+
props?: JsonObject;
|
|
383
|
+
children?: ComponentInstance[];
|
|
384
|
+
slots?: Record<string, ComponentInstance[]>;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* One composed surface's persisted document.
|
|
388
|
+
*
|
|
389
|
+
* At runtime the envelope is **forward-compatible**: `parsePageData` and
|
|
390
|
+
* `PageDataSchema` carry unknown *top-level* keys through verbatim, so a later
|
|
391
|
+
* writer can record metadata (catalog provenance is the intended first use)
|
|
392
|
+
* without a `PageData.version` bump. Those keys are never read by the renderer,
|
|
393
|
+
* which only walks `sections`, and they are deliberately **not declared here**:
|
|
394
|
+
* an index signature would turn a `page.sectons` typo into `unknown` instead of
|
|
395
|
+
* a compile error. The field is added when a real reader exists.
|
|
396
|
+
*
|
|
397
|
+
* Unknown keys *inside* an instance are still stripped — the envelope is
|
|
398
|
+
* extensible, the instance is not.
|
|
399
|
+
*/
|
|
400
|
+
export interface PageData {
|
|
401
|
+
version: 1;
|
|
402
|
+
surface: string;
|
|
403
|
+
sections: ComponentInstance[];
|
|
404
|
+
}
|
|
405
|
+
export type PageDataIssueCode = "envelope-invalid" | "instance-dropped" | "duplicate-id" | "unknown-type" | "unknown-prop" | "invalid-prop" | "missing-required-prop" | "undeclared-children" | "unknown-slot" | "slot-not-multiple" | "disallowed-child";
|
|
406
|
+
/** One reported degradation; `path` points into the document (e.g. `sections[0].props.limit`). */
|
|
407
|
+
export interface PageDataIssue {
|
|
408
|
+
path: string;
|
|
409
|
+
code: PageDataIssueCode;
|
|
410
|
+
message: string;
|
|
411
|
+
}
|
|
412
|
+
/** Result of a read boundary: `page` is null only when the envelope itself failed. */
|
|
413
|
+
export interface PageDataResult {
|
|
414
|
+
page: PageData | null;
|
|
415
|
+
issues: PageDataIssue[];
|
|
416
|
+
}
|
|
417
|
+
/** What the renderer passes to the real component, after gating. */
|
|
418
|
+
export interface ResolvedInstance {
|
|
419
|
+
props: Record<string, unknown>;
|
|
420
|
+
children: ComponentInstance[];
|
|
421
|
+
slots: Record<string, ComponentInstance[]>;
|
|
422
|
+
issues: PageDataIssue[];
|
|
423
|
+
}
|
|
424
|
+
export type ThemeDefinitionIssueCode = "invalid-theme" | "invalid-settings" | "invalid-definition" | "invalid-type" | "type-mismatch" | "invalid-field" | "duplicate-field-key" | "unknown-content-key" | "invalid-slot" | "duplicate-slot" | "invalid-capability" | "invalid-load";
|
|
425
|
+
export interface ThemeDefinitionIssue {
|
|
426
|
+
path: string;
|
|
427
|
+
code: ThemeDefinitionIssueCode;
|
|
428
|
+
message: string;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Optional inputs to `validateThemeDefinition`. Every field is additive: with
|
|
432
|
+
* no options, the validator behaves exactly as it always has.
|
|
433
|
+
*
|
|
434
|
+
* `contentKeys` is the one cross-boundary check the Theme SDK can make and a
|
|
435
|
+
* theme cannot — it defines `contentKey` *and* reads the resolved content
|
|
436
|
+
* record, so a typo is invisible everywhere else. The SDK deliberately does not
|
|
437
|
+
* model content packs; it only compares a field's `contentKey` against the key
|
|
438
|
+
* set the theme supplies. Passing an **empty** array is meaningful: it enables
|
|
439
|
+
* the check and reports every declared key.
|
|
440
|
+
*/
|
|
441
|
+
export interface ThemeValidationOptions {
|
|
442
|
+
contentKeys?: readonly string[];
|
|
443
|
+
}
|
|
444
|
+
export type ThemeSettingsIssueCode = "invalid-settings" | "unknown-setting" | "invalid-setting" | "missing-required-setting";
|
|
445
|
+
/** One reported problem with a store's theme settings; `path` is `settings.<key>`. */
|
|
446
|
+
export interface ThemeSettingsIssue {
|
|
447
|
+
path: string;
|
|
448
|
+
code: ThemeSettingsIssueCode;
|
|
449
|
+
message: string;
|
|
450
|
+
}
|
|
451
|
+
/** The effective settings: every value checked, the rest from the theme's defaults. */
|
|
452
|
+
export interface ThemeSettingsResult {
|
|
453
|
+
values: Record<string, unknown>;
|
|
454
|
+
issues: ThemeSettingsIssue[];
|
|
455
|
+
}
|
|
456
|
+
type SettingsFieldsOf<T> = T extends {
|
|
457
|
+
settings: readonly (infer G)[];
|
|
458
|
+
} ? G extends {
|
|
459
|
+
fields: readonly (infer F extends FieldDefinition)[];
|
|
460
|
+
} ? F : never : never;
|
|
461
|
+
/**
|
|
462
|
+
* The effective settings of a theme, typed from its declaration: a setting
|
|
463
|
+
* with a `default` is always present, any other may be absent.
|
|
464
|
+
*
|
|
465
|
+
* ```ts
|
|
466
|
+
* const { values } = resolveThemeSettings(theme, stored);
|
|
467
|
+
* const settings = values as InferSettings<typeof theme>;
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
export type InferSettings<T extends ThemeDefinition> = {
|
|
471
|
+
[F in SettingsFieldsOf<T> as F extends {
|
|
472
|
+
default: unknown;
|
|
473
|
+
} ? F["key"] : never]: FieldValue<F>;
|
|
474
|
+
} & {
|
|
475
|
+
[F in SettingsFieldsOf<T> as F extends {
|
|
476
|
+
default: unknown;
|
|
477
|
+
} ? never : F["key"]]?: FieldValue<F>;
|
|
478
|
+
};
|
|
479
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wocart/theme",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "The Wocart theme contract: code-owned component definitions, JSON page documents, validation and resolution, and a reference Astro renderer.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wocart",
|
|
7
|
+
"theme",
|
|
8
|
+
"storefront",
|
|
9
|
+
"ecommerce",
|
|
10
|
+
"page-builder",
|
|
11
|
+
"astro",
|
|
12
|
+
"astro-component",
|
|
13
|
+
"withastro",
|
|
14
|
+
"typescript"
|
|
15
|
+
],
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/bighadj22/codflow.git",
|
|
20
|
+
"directory": "wocart-sdk/theme"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/bighadj22/codflow/tree/main/wocart-sdk/theme#readme",
|
|
23
|
+
"bugs": "https://github.com/bighadj22/codflow/issues",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./astro": {
|
|
37
|
+
"types": "./dist/astro/index.d.ts",
|
|
38
|
+
"default": "./dist/astro/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./astro/InstanceView.astro": "./dist/astro/InstanceView.astro",
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"CHANGELOG.md",
|
|
47
|
+
"LICENSE",
|
|
48
|
+
"NOTICE"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "node --eval \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc && node scripts/copy-astro.mjs",
|
|
52
|
+
"typecheck": "tsc --noEmit && tsc --project tsconfig.test.json && astro check",
|
|
53
|
+
"test": "npm run build && vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"verify:package": "npm run build && node scripts/check-package.mjs && node scripts/verify-package.mjs",
|
|
56
|
+
"prepack": "npm run build",
|
|
57
|
+
"prepublishOnly": "node scripts/check-package.mjs"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22.12.0"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"zod": "^4.4.3"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@wocart/storefront": "^1.0.0",
|
|
67
|
+
"astro": "^7.0.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"astro": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@wocart/storefront": "^1.0.0",
|
|
76
|
+
"@types/node": "^26.2.0",
|
|
77
|
+
"astro": "^7.3.2",
|
|
78
|
+
"fast-check": "^4.6.0",
|
|
79
|
+
"typescript": "^7.0.2",
|
|
80
|
+
"vitest": "^5.0.0"
|
|
81
|
+
}
|
|
82
|
+
}
|