@forumone/throughline-design-contract 0.0.1 → 0.3.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 ADDED
@@ -0,0 +1,21 @@
1
+ # @forumone/throughline-design-contract
2
+
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 24bd325: Add an optional `group` to the component contract, so an authoring UI can shelve components separately from what they are.
8
+
9
+ `category` was answering two questions at once — what a component _is_, and where an editor looks for it — and it is a bad answer to the second at any real size. A design system of sixty blocks files roughly half of them under `section`, so a picker grouped on `category` hands back the flat list the grouping was meant to avoid while `card` and `navigation` hold one entry each. Evening the shelves out within `category` would file components under the wrong kind for every consumer that reasons about kind, including `list_components`.
10
+
11
+ So `category` keeps its meaning and its enum, and `group` takes the second question with a vocabulary of shelf labels: `hero`, `narrative`, `proof`, `listing`, `media`, `form`, `cta`, `navigation`, `utility`. No `section`, which is the problem being solved; no `card` or `data`, which name a kind rather than a place to look.
12
+
13
+ New API: `groupOf(component)` resolves `group ?? category`, and `LoadedManifest` gains `listByGroup()` and `listGroups()` which match on the resolved value. Grouping consumers should call `groupOf` rather than reading either field.
14
+
15
+ Non-breaking. `group` is optional, and the fallback means a design system that sets none groups exactly as it did before.
16
+
17
+ ## 0.2.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [#9](https://github.com/forumone/throughline/pull/9) [`337f2ca`](https://github.com/forumone/throughline/commit/337f2ca779a30d2f135845259bbae8e961a625ed) Thanks [@briangraves](https://github.com/briangraves)! - Initial release. Defines `ComponentContractSchema`, `ManifestSchema`, `loadManifest`, `loadManifestFromUrl`, and `lintManifest`. Every design system that satisfies this contract is a valid input to the framework's Component Server.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forum One Communications Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,179 @@
1
1
  # @forumone/throughline-design-contract
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ The contract every AI-ready design system satisfies to be consumable by Throughline.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## What this package provides
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ - **`ComponentContractSchema`** the Zod schema for per-component metadata (intent, composition rules, content fields, tokens, accessibility, examples).
8
+ - **`ManifestSchema`** — the aggregated JSON format a design system publishes, versioned via `contractVersion`.
9
+ - **`loadManifest` / `loadManifestFromUrl`** — runtime loaders with strict validation and a readable error format.
10
+ - **`lintManifest`** (via the `/lint` subpath) — CI-runnable consistency checks for design system repos.
8
11
 
9
- ## Purpose
12
+ Zero runtime dependencies beyond Zod. Safe to import into client apps, design systems, and server packages alike.
10
13
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@forumone/throughline-design-contract`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
14
+ ## Installation
15
15
 
16
- ## What is OIDC Trusted Publishing?
16
+ ```bash
17
+ pnpm add @forumone/throughline-design-contract
18
+ ```
17
19
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
20
+ ## Authoring contracts in a design system
19
21
 
20
- ## Setup Instructions
22
+ Each component has a co-located contract file:
21
23
 
22
- To properly configure OIDC trusted publishing for this package:
24
+ ```
25
+ src/components/Hero/
26
+ ├── Hero.tsx
27
+ ├── Hero.stories.tsx
28
+ ├── Hero.contract.ts
29
+ └── index.ts
30
+ ```
23
31
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
32
+ `Hero.contract.ts` exports a single object that satisfies `ComponentContract`:
28
33
 
29
- ## DO NOT USE THIS PACKAGE
34
+ ```typescript
35
+ import type { ComponentContract } from '@forumone/throughline-design-contract'
30
36
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
37
+ export const contract: ComponentContract = {
38
+ name: 'Hero',
39
+ category: 'hero',
40
+ description: 'A page opener with a headline and optional call-to-action.',
41
+ intent:
42
+ 'Used to establish what a page is about within the first viewport. Appropriate for top-level pages that need editorial framing.',
43
+ composition: {
44
+ placement: ['page'],
45
+ maxPerPage: 1,
46
+ requiredSiblings: [],
47
+ forbiddenAdjacent: ['Hero'],
48
+ },
49
+ content: {
50
+ fields: [{ name: 'headline', type: 'text', required: true, maxLength: 80 }],
51
+ },
52
+ tokens: { consumes: ['color.brand.primary'] },
53
+ accessibility: {
54
+ keyboardSupport: [],
55
+ screenReaderBehavior: 'Headline is announced as h1 by default.',
56
+ contentWarnings: [],
57
+ },
58
+ examples: [{ label: 'Default', intent: 'Standard page opener', storyId: 'hero--default' }],
59
+ antiExamples: [
60
+ { label: 'Stacked heroes', why: 'Breaks the visual rhythm.', useInstead: 'Section' },
61
+ ],
62
+ }
63
+ ```
36
64
 
37
- ## More Information
65
+ Your design system's build tooling aggregates these into a `Manifest`. See the reference design system for a canonical example.
38
66
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
67
+ ## `category` and `group`
42
68
 
43
- ---
69
+ Two fields, two questions.
44
70
 
45
- **Maintained for OIDC setup purposes only**
71
+ `category` is what the component **is** `hero`, `section`, `card`, `media`, `cta`, `navigation`, `data`, `form`, `utility`. It is required, and consumers reason about it as a kind: `list_components` in the components MCP server filters on it.
72
+
73
+ `group` is where an **editor looks for it** — `hero`, `narrative`, `proof`, `listing`, `media`, `form`, `cta`, `navigation`, `utility`. It is optional, and exists because `category` is a bad answer to that second question at any real size. A design system of sixty blocks files roughly half of them under `section`, so an authoring UI grouped on `category` hands back the flat list the grouping was meant to avoid, while `card` and `navigation` hold one entry each. Evening the shelves out by moving components between categories would file them under the wrong *kind* for every other consumer, so the fix is a second field rather than a looser first one.
74
+
75
+ There is deliberately no `section` in the group vocabulary — a shelf holding half the library is the problem this field exists to solve — and no `card` or `data`, both of which name a kind rather than a place to look.
76
+
77
+ Read the resolved value with `groupOf`, never either field directly:
78
+
79
+ ```typescript
80
+ import { groupOf } from '@forumone/throughline-design-contract'
81
+
82
+ groupOf({ category: 'section', group: 'proof' }) // 'proof'
83
+ groupOf({ category: 'hero' }) // 'hero' — falls back
84
+ ```
85
+
86
+ The fallback is what makes this a non-breaking addition: a design system that sets no `group` anywhere groups exactly as it did before the field existed, and one part-way through adopting it stays consistent instead of grouping half one way and half the other.
87
+
88
+ ## Loading a manifest at runtime
89
+
90
+ ```typescript
91
+ import { loadManifest } from '@forumone/throughline-design-contract'
92
+ import manifest from '@my-company/design-system/manifest.json'
93
+
94
+ const loaded = loadManifest(manifest)
95
+
96
+ const hero = loaded.requireComponent('Hero')
97
+ console.log(hero.intent)
98
+
99
+ for (const name of loaded.listByCategory('card')) {
100
+ console.log(name)
101
+ }
102
+
103
+ // Grouped for an authoring UI. Matches on the resolved group, so components
104
+ // with no `group` are found by their category.
105
+ for (const shelf of loaded.listGroups()) {
106
+ console.log(shelf, loaded.listByGroup(shelf).length)
107
+ }
108
+ ```
109
+
110
+ Invalid manifests throw immediately with a path-qualified error message. The loader never partially loads invalid data.
111
+
112
+ ### Loading over HTTP
113
+
114
+ ```typescript
115
+ import { loadManifestFromUrl } from '@forumone/throughline-design-contract'
116
+
117
+ const loaded = await loadManifestFromUrl('https://ds.example.com/manifest.json')
118
+ ```
119
+
120
+ ## Linting in CI
121
+
122
+ Every design system should lint its own manifest before publishing. Import the lint helpers from the `/lint` subpath:
123
+
124
+ ```typescript
125
+ import { lintManifest, formatLintIssues } from '@forumone/throughline-design-contract/lint'
126
+ import manifest from './dist/manifest.json'
127
+
128
+ const issues = lintManifest(manifest, {
129
+ availableStoryIds: new Set(/* collected from storybook-static */),
130
+ })
131
+
132
+ if (issues.some((i) => i.severity === 'error')) {
133
+ console.error(formatLintIssues(issues))
134
+ process.exit(1)
135
+ }
136
+ ```
137
+
138
+ `lintManifest` checks:
139
+
140
+ - Every `requiredSiblings` and `forbiddenAdjacent` entry references a real component in the manifest.
141
+ - Every token in `tokens.consumes` exists in the manifest's token table (or in `availableTokens` if you pass one).
142
+ - Every example's `storyId` exists in `availableStoryIds` (skipped when the option is omitted).
143
+ - Warnings: components with no anti-examples; intent statements shorter than 50 characters.
144
+
145
+ `assertManifestClean(manifest, options)` is the CI-friendly form: throws on any error, silent on warnings-only.
146
+
147
+ ## Versioning
148
+
149
+ The current contract version is `1.0.0`, exported as `CONTRACT_VERSION`. Manifests declare it via the `contractVersion` field; the loader rejects mismatches so Claude never recommends a component from a schema it does not understand.
150
+
151
+ When the contract evolves, this package ships a new major version with migration guidance in the changelog.
152
+
153
+ ## Relationship to Storybook AI manifests
154
+
155
+ Storybook ships its own [AI manifest format](https://storybook.js.org/docs/ai/manifests) (`/manifests/components.json`). It is a static-analysis artifact that describes what *exists* in a Storybook — component ids, paths, props with types and JSDoc, story ids, import statements. Storybook marks it as preview / unstable.
156
+
157
+ This package's contract describes what's *appropriate* — intent, composition rules, accessibility expectations, anti-examples, token consumption. Hand-authored, stable, versioned.
158
+
159
+ They complement rather than overlap. Typical integration: during a design system's CI, parse Storybook's `components.json`, collect every `stories[].id`, and pass the set to `lintManifest` as `availableStoryIds`. That gives you the "does this `storyId` resolve to a real story?" check for free:
160
+
161
+ ```typescript
162
+ import storybookManifest from './storybook-static/manifests/components.json'
163
+ import contractManifest from './dist/manifest.json'
164
+ import { lintManifest } from '@forumone/throughline-design-contract/lint'
165
+
166
+ const availableStoryIds = new Set(
167
+ Object.values(storybookManifest.components).flatMap((c: { stories: Array<{ id: string }> }) =>
168
+ c.stories.map((s) => s.id),
169
+ ),
170
+ )
171
+
172
+ const issues = lintManifest(contractManifest, { availableStoryIds })
173
+ ```
174
+
175
+ ## Related packages
176
+
177
+ - `@forumone/throughline-reference-ds` — a reference design system that satisfies this contract (C3).
178
+ - `@forumone/throughline-components` — the Component Server MCP that consumes manifests (C5).
179
+ - `@forumone/throughline-plugin-contract` — the plugin contract for packages that extend Payload.
@@ -0,0 +1,5 @@
1
+ export * from './schema.js';
2
+ export * from './manifest.js';
3
+ export * from './loader.js';
4
+ export type { LintIssue, LintOptions } from './lint.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './schema.js';
2
+ export * from './manifest.js';
3
+ export * from './loader.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
package/dist/lint.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import type { Manifest } from './manifest.js';
2
+ export interface LintIssue {
3
+ severity: 'error' | 'warning';
4
+ component: string;
5
+ rule: string;
6
+ message: string;
7
+ }
8
+ export interface LintOptions {
9
+ /**
10
+ * Source of truth for Storybook story IDs. If provided, lint verifies
11
+ * every example's `storyId` exists in this set. Omit to skip the check
12
+ * (e.g. in environments that do not have Storybook output available).
13
+ */
14
+ availableStoryIds?: Set<string>;
15
+ /**
16
+ * Source of truth for token names. If provided, lint verifies every
17
+ * token referenced by a component's `tokens.consumes` exists in this set.
18
+ * Defaults to the manifest's own token table when omitted.
19
+ */
20
+ availableTokens?: Set<string>;
21
+ }
22
+ /**
23
+ * Lints a manifest against the contract's internal consistency rules.
24
+ * Returns an array of issues; an empty array means the manifest is clean.
25
+ *
26
+ * Errors describe unambiguous contract violations (unknown component
27
+ * references, unknown tokens). Warnings surface weaker signals that may
28
+ * still be worth addressing.
29
+ */
30
+ export declare function lintManifest(manifest: Manifest, options?: LintOptions): LintIssue[];
31
+ /** Formats lint issues as a multi-line string suitable for CI output. */
32
+ export declare function formatLintIssues(issues: LintIssue[]): string;
33
+ /**
34
+ * Throws when the manifest has any lint errors. Warnings do not throw.
35
+ * Use in CI to fail the build on contract violations.
36
+ */
37
+ export declare function assertManifestClean(manifest: Manifest, options?: LintOptions): void;
38
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,WAAgB,GAAG,SAAS,EAAE,CAyEvF;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAwB5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAMnF"}
package/dist/lint.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Lints a manifest against the contract's internal consistency rules.
3
+ * Returns an array of issues; an empty array means the manifest is clean.
4
+ *
5
+ * Errors describe unambiguous contract violations (unknown component
6
+ * references, unknown tokens). Warnings surface weaker signals that may
7
+ * still be worth addressing.
8
+ */
9
+ export function lintManifest(manifest, options = {}) {
10
+ const issues = [];
11
+ const componentNames = new Set(Object.keys(manifest.components));
12
+ const tokenNames = options.availableTokens ?? new Set(manifest.tokens.map((t) => t.name));
13
+ const storyIds = options.availableStoryIds;
14
+ for (const [name, component] of Object.entries(manifest.components)) {
15
+ for (const sibling of component.composition.requiredSiblings) {
16
+ if (!componentNames.has(sibling)) {
17
+ issues.push({
18
+ severity: 'error',
19
+ component: name,
20
+ rule: 'composition.requiredSiblings',
21
+ message: `References unknown component "${sibling}"`,
22
+ });
23
+ }
24
+ }
25
+ for (const adjacent of component.composition.forbiddenAdjacent) {
26
+ if (!componentNames.has(adjacent)) {
27
+ issues.push({
28
+ severity: 'error',
29
+ component: name,
30
+ rule: 'composition.forbiddenAdjacent',
31
+ message: `References unknown component "${adjacent}"`,
32
+ });
33
+ }
34
+ }
35
+ for (const token of component.tokens.consumes) {
36
+ if (!tokenNames.has(token)) {
37
+ issues.push({
38
+ severity: 'error',
39
+ component: name,
40
+ rule: 'tokens.consumes',
41
+ message: `References unknown token "${token}"`,
42
+ });
43
+ }
44
+ }
45
+ if (storyIds) {
46
+ for (const example of component.examples) {
47
+ if (!storyIds.has(example.storyId)) {
48
+ issues.push({
49
+ severity: 'error',
50
+ component: name,
51
+ rule: 'examples.storyId',
52
+ message: `Example "${example.label}" references unknown story "${example.storyId}"`,
53
+ });
54
+ }
55
+ }
56
+ }
57
+ if (component.antiExamples.length === 0) {
58
+ issues.push({
59
+ severity: 'warning',
60
+ component: name,
61
+ rule: 'antiExamples.empty',
62
+ message: 'Component has no anti-examples; consider adding at least one',
63
+ });
64
+ }
65
+ if (component.intent.length < 50) {
66
+ issues.push({
67
+ severity: 'warning',
68
+ component: name,
69
+ rule: 'intent.brevity',
70
+ message: 'Intent statement is quite short; consider a more specific description',
71
+ });
72
+ }
73
+ }
74
+ return issues;
75
+ }
76
+ /** Formats lint issues as a multi-line string suitable for CI output. */
77
+ export function formatLintIssues(issues) {
78
+ if (issues.length === 0)
79
+ return 'No issues found.';
80
+ const errors = issues.filter((i) => i.severity === 'error');
81
+ const warnings = issues.filter((i) => i.severity === 'warning');
82
+ const lines = [];
83
+ if (errors.length > 0) {
84
+ lines.push(`Errors (${errors.length}):`);
85
+ for (const issue of errors) {
86
+ lines.push(` [${issue.component}] ${issue.rule}: ${issue.message}`);
87
+ }
88
+ }
89
+ if (warnings.length > 0) {
90
+ if (lines.length > 0)
91
+ lines.push('');
92
+ lines.push(`Warnings (${warnings.length}):`);
93
+ for (const issue of warnings) {
94
+ lines.push(` [${issue.component}] ${issue.rule}: ${issue.message}`);
95
+ }
96
+ }
97
+ return lines.join('\n');
98
+ }
99
+ /**
100
+ * Throws when the manifest has any lint errors. Warnings do not throw.
101
+ * Use in CI to fail the build on contract violations.
102
+ */
103
+ export function assertManifestClean(manifest, options) {
104
+ const issues = lintManifest(manifest, options);
105
+ const errors = issues.filter((i) => i.severity === 'error');
106
+ if (errors.length > 0) {
107
+ throw new Error(`Manifest has errors:\n${formatLintIssues(errors)}`);
108
+ }
109
+ }
110
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.js","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { type Manifest } from './manifest.js';
2
+ import type { ComponentContract, ComponentCategory, ComponentGroup } from './schema.js';
3
+ /**
4
+ * A validated, queryable manifest. Returned by {@link loadManifest} and
5
+ * {@link loadManifestFromUrl}. Consumers use the helper methods rather than
6
+ * poking at {@link raw} directly, which lets the implementation evolve.
7
+ */
8
+ export declare class LoadedManifest {
9
+ readonly raw: Manifest;
10
+ constructor(raw: Manifest);
11
+ /** The design system's metadata. */
12
+ get designSystem(): Manifest['designSystem'];
13
+ /** The contract version this manifest satisfies. */
14
+ get contractVersion(): Manifest['contractVersion'];
15
+ /** Full contract for a component by name, or `undefined` if absent. */
16
+ getComponent(name: string): ComponentContract | undefined;
17
+ /** Like {@link getComponent} but throws when the component is missing. */
18
+ requireComponent(name: string): ComponentContract;
19
+ /** Every component name in declaration order. */
20
+ listComponents(): string[];
21
+ /** Every component whose category matches. */
22
+ listByCategory(category: ComponentCategory | string): ComponentContract[];
23
+ /** Every distinct category present, sorted alphabetically. */
24
+ listCategories(): string[];
25
+ /**
26
+ * Every component filed under a shelf. Matches on the resolved group, so a
27
+ * component with no `group` is found by its category.
28
+ */
29
+ listByGroup(group: ComponentGroup | string): ComponentContract[];
30
+ /** Every distinct resolved group present, sorted alphabetically. */
31
+ listGroups(): string[];
32
+ /** Token definition by name, or `undefined` if absent. */
33
+ getToken(name: string): {
34
+ value: string;
35
+ name: string;
36
+ category: string;
37
+ } | undefined;
38
+ }
39
+ /**
40
+ * Loads a manifest from a plain object. Validates against {@link ManifestSchema}
41
+ * and throws on any validation error with a readable, line-by-line message.
42
+ */
43
+ export declare function loadManifest(input: unknown): LoadedManifest;
44
+ /**
45
+ * Fetches a manifest from a URL and loads it. Useful when a design system
46
+ * serves its manifest via HTTP rather than bundling into the consumer.
47
+ */
48
+ export declare function loadManifestFromUrl(url: string, init?: RequestInit): Promise<LoadedManifest>;
49
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAkB,MAAM,eAAe,CAAA;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEvF;;;;GAIG;AACH,qBAAa,cAAc;aACG,GAAG,EAAE,QAAQ;gBAAb,GAAG,EAAE,QAAQ;IAEzC,oCAAoC;IACpC,IAAI,YAAY,IAAI,QAAQ,CAAC,cAAc,CAAC,CAE3C;IAED,oDAAoD;IACpD,IAAI,eAAe,IAAI,QAAQ,CAAC,iBAAiB,CAAC,CAEjD;IAED,uEAAuE;IACvE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIzD,0EAA0E;IAC1E,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB;IAQjD,iDAAiD;IACjD,cAAc,IAAI,MAAM,EAAE;IAI1B,8CAA8C;IAC9C,cAAc,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,GAAG,iBAAiB,EAAE;IAIzE,8DAA8D;IAC9D,cAAc,IAAI,MAAM,EAAE;IAQ1B;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,iBAAiB,EAAE;IAIhE,oEAAoE;IACpE,UAAU,IAAI,MAAM,EAAE;IAQtB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM;;;;;CAGtB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAS3D;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,cAAc,CAAC,CAOzB"}
package/dist/loader.js ADDED
@@ -0,0 +1,95 @@
1
+ import { ManifestSchema } from './manifest.js';
2
+ import { groupOf } from './schema.js';
3
+ /**
4
+ * A validated, queryable manifest. Returned by {@link loadManifest} and
5
+ * {@link loadManifestFromUrl}. Consumers use the helper methods rather than
6
+ * poking at {@link raw} directly, which lets the implementation evolve.
7
+ */
8
+ export class LoadedManifest {
9
+ raw;
10
+ constructor(raw) {
11
+ this.raw = raw;
12
+ }
13
+ /** The design system's metadata. */
14
+ get designSystem() {
15
+ return this.raw.designSystem;
16
+ }
17
+ /** The contract version this manifest satisfies. */
18
+ get contractVersion() {
19
+ return this.raw.contractVersion;
20
+ }
21
+ /** Full contract for a component by name, or `undefined` if absent. */
22
+ getComponent(name) {
23
+ return this.raw.components[name];
24
+ }
25
+ /** Like {@link getComponent} but throws when the component is missing. */
26
+ requireComponent(name) {
27
+ const component = this.getComponent(name);
28
+ if (!component) {
29
+ throw new Error(`Component "${name}" not found in manifest`);
30
+ }
31
+ return component;
32
+ }
33
+ /** Every component name in declaration order. */
34
+ listComponents() {
35
+ return Object.keys(this.raw.components);
36
+ }
37
+ /** Every component whose category matches. */
38
+ listByCategory(category) {
39
+ return Object.values(this.raw.components).filter((c) => c.category === category);
40
+ }
41
+ /** Every distinct category present, sorted alphabetically. */
42
+ listCategories() {
43
+ const categories = new Set();
44
+ for (const component of Object.values(this.raw.components)) {
45
+ categories.add(component.category);
46
+ }
47
+ return Array.from(categories).sort();
48
+ }
49
+ /**
50
+ * Every component filed under a shelf. Matches on the resolved group, so a
51
+ * component with no `group` is found by its category.
52
+ */
53
+ listByGroup(group) {
54
+ return Object.values(this.raw.components).filter((c) => groupOf(c) === group);
55
+ }
56
+ /** Every distinct resolved group present, sorted alphabetically. */
57
+ listGroups() {
58
+ const groups = new Set();
59
+ for (const component of Object.values(this.raw.components)) {
60
+ groups.add(groupOf(component));
61
+ }
62
+ return Array.from(groups).sort();
63
+ }
64
+ /** Token definition by name, or `undefined` if absent. */
65
+ getToken(name) {
66
+ return this.raw.tokens.find((t) => t.name === name);
67
+ }
68
+ }
69
+ /**
70
+ * Loads a manifest from a plain object. Validates against {@link ManifestSchema}
71
+ * and throws on any validation error with a readable, line-by-line message.
72
+ */
73
+ export function loadManifest(input) {
74
+ const result = ManifestSchema.safeParse(input);
75
+ if (!result.success) {
76
+ const issues = result.error.issues
77
+ .map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`)
78
+ .join('\n');
79
+ throw new Error(`Invalid manifest:\n${issues}`);
80
+ }
81
+ return new LoadedManifest(result.data);
82
+ }
83
+ /**
84
+ * Fetches a manifest from a URL and loads it. Useful when a design system
85
+ * serves its manifest via HTTP rather than bundling into the consumer.
86
+ */
87
+ export async function loadManifestFromUrl(url, init) {
88
+ const response = await fetch(url, init);
89
+ if (!response.ok) {
90
+ throw new Error(`Failed to fetch manifest from ${url}: HTTP ${response.status}`);
91
+ }
92
+ const json = (await response.json());
93
+ return loadManifest(json);
94
+ }
95
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}