@texuli/spec 0.3.0 → 0.5.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/README.md +82 -14
- package/dist/build.d.ts +5 -0
- package/dist/build.js +1 -0
- package/dist/canonical.js +20 -13
- package/dist/catalog.d.ts +9 -3
- package/dist/catalog.js +7 -1
- package/dist/codegen/index.d.ts +2 -2
- package/dist/codegen/index.js +2 -2
- package/dist/codegen/kotlin.d.ts +6 -0
- package/dist/codegen/kotlin.js +80 -8
- package/dist/conformance.d.ts +11 -2
- package/dist/conformance.js +93 -2
- package/dist/contract.d.ts +112 -0
- package/dist/contract.js +196 -0
- package/dist/dialect.d.ts +10 -1
- package/dist/dialect.js +6 -1
- package/dist/generated/typescript/blueprint.d.ts +103 -5
- package/dist/generated/typescript/blueprint.js +1 -1
- package/dist/generated/typescript/catalog-schema.d.ts +35 -0
- package/dist/generated/typescript/catalog-schema.js +1 -1
- package/dist/generated/typescript/diagnostics.d.ts +10 -1
- package/dist/generated/typescript/diagnostics.js +1 -1
- package/dist/generated/typescript/manifest.js +1 -1
- package/dist/generated/typescript/vocabulary.d.ts +194 -4
- package/dist/generated/typescript/vocabulary.js +31 -2
- package/dist/generated/typescript/workflow.d.ts +84 -0
- package/dist/generated/typescript/workflow.js +3 -0
- package/dist/index.d.ts +13 -5
- package/dist/index.js +9 -3
- package/dist/migrations/0.2.0-0.3.0.d.ts +7 -0
- package/dist/migrations/0.2.0-0.3.0.js +10 -0
- package/dist/migrations/0.3.0-0.4.0.d.ts +9 -0
- package/dist/migrations/0.3.0-0.4.0.js +12 -0
- package/dist/migrations/0.4.0-0.5.0.d.ts +8 -0
- package/dist/migrations/0.4.0-0.5.0.js +11 -0
- package/dist/migrations/index.d.ts +15 -0
- package/dist/migrations/index.js +55 -0
- package/dist/migrations/types.d.ts +14 -0
- package/dist/migrations/types.js +1 -0
- package/dist/surface.d.ts +60 -0
- package/dist/surface.js +162 -0
- package/dist/validate.d.ts +89 -14
- package/dist/validate.js +725 -64
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workflow.d.ts +15 -0
- package/dist/workflow.js +256 -0
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -14,9 +14,17 @@ by Texuli's Kotlin Multiplatform twin. The package is framework-free: no
|
|
|
14
14
|
DOM, no Svelte, no Node APIs and no runtime dependencies.
|
|
15
15
|
|
|
16
16
|
> **Status: 0.x release.** Texuli is in development. The packages are
|
|
17
|
-
> published to npm as `0.
|
|
18
|
-
> stable release). Under 0.x, a new minor (`0.
|
|
19
|
-
> changes, so pin the exact version or use `^0.
|
|
17
|
+
> published to npm as `0.5.0`, a pre-1.0 release (1.0.0 will be the first
|
|
18
|
+
> stable release). Under 0.x, a new minor (`0.6.0`) may contain breaking
|
|
19
|
+
> changes, so pin the exact version or use `^0.5.0`, which accepts only 0.5.x.
|
|
20
|
+
|
|
21
|
+
> **New in `0.4.0`.** `@texuli/spec@0.4.0` carries **spec 0.4.0**: spec
|
|
22
|
+
> 0.3.0 (state, bindings, closed actions, `reorder`, workflow graphs), spec
|
|
23
|
+
> 0.4.0 (deck model v2 on `layout-deck` and `deck-slide`, below) and the
|
|
24
|
+
> `workflow`, `migrate` and `surface` subpaths described below.
|
|
25
|
+
> `@texuli/spec@0.3.0` carried spec 0.2.0 and only the root and `codegen`
|
|
26
|
+
> subpaths
|
|
27
|
+
> ([RELEASE](https://github.com/DailybotHQ/texuli/blob/main/docs/RELEASE.md)).
|
|
20
28
|
|
|
21
29
|
## Install
|
|
22
30
|
|
|
@@ -48,7 +56,7 @@ import {
|
|
|
48
56
|
validateInstance,
|
|
49
57
|
} from '@texuli/spec';
|
|
50
58
|
|
|
51
|
-
SPEC_VERSION; // the spec version
|
|
59
|
+
SPEC_VERSION; // the spec version: '0.5.0' in @texuli/spec@0.5.0 ('0.4.0' in @texuli/spec@0.4.0)
|
|
52
60
|
SPEC_COMPONENT_IDS.includes('alert'); // true: the closed component vocabulary
|
|
53
61
|
|
|
54
62
|
// The dialect validator: a closed subset of JSON Schema 2020-12.
|
|
@@ -68,31 +76,51 @@ const digest = sha256(canonicalJson({ b: 1, a: [true, null] }));
|
|
|
68
76
|
|
|
69
77
|
To check a whole blueprint, pass the blueprint schema and catalog documents
|
|
70
78
|
from the repository's `packages/spec/schemas/blueprint.schema.json` and
|
|
71
|
-
`packages/spec/catalog/components.json
|
|
79
|
+
`packages/spec/catalog/components.json`. Workflow graphs and migrations
|
|
80
|
+
are opt-in subpaths, so the root stays small: a host that renders workflow
|
|
81
|
+
graphs passes `schemas/workflow.schema.json` and `validateWorkflowGraph`
|
|
82
|
+
from `@texuli/spec/workflow` (without both, a `workflow` state variable or
|
|
83
|
+
a `workflowGraph` prop fails closed), and a host that reads older
|
|
84
|
+
envelopes runs `migrateBlueprint` from `@texuli/spec/migrate` first:
|
|
72
85
|
|
|
73
86
|
```ts
|
|
74
87
|
import { type Schema, type SpecCatalog, validateBlueprint } from '@texuli/spec';
|
|
88
|
+
import { migrateBlueprint } from '@texuli/spec/migrate';
|
|
89
|
+
import { validateWorkflowGraph } from '@texuli/spec/workflow';
|
|
75
90
|
|
|
76
|
-
const
|
|
91
|
+
const migrated = migrateBlueprint(blueprint); // 0.2.0 → 0.3.0 → 0.4.0 → 0.5.0; never throws
|
|
92
|
+
const result = validateBlueprint(migrated.blueprint, {
|
|
77
93
|
blueprintSchema: blueprintSchemaJson as Schema,
|
|
78
94
|
catalog: componentsJson as SpecCatalog,
|
|
95
|
+
workflowSchema: workflowSchemaJson as Schema, // only for workflow graphs
|
|
96
|
+
validateWorkflowGraph, // only for workflow graphs
|
|
79
97
|
});
|
|
80
98
|
if (!result.ok) {
|
|
81
99
|
for (const d of result.diagnostics) console.warn(d.code, d.path, d.message);
|
|
82
100
|
}
|
|
101
|
+
for (const w of result.warnings ?? []) console.info(w.code, w.path); // `deprecated`
|
|
83
102
|
```
|
|
84
103
|
|
|
85
|
-
A node with an id outside the catalog fails with `unknown-id
|
|
86
|
-
|
|
104
|
+
A node with an id outside the catalog fails with `unknown-id`. The other
|
|
105
|
+
codes are `schema`, `unknown-prop`, `prop-type`, `enum-value`,
|
|
87
106
|
`missing-required`, `extension-prop`, `catalog-mismatch`,
|
|
88
|
-
`unsupported-profile` and `
|
|
107
|
+
`unsupported-profile`, `unknown-slot`, and, from spec 0.3.0, `state` (a
|
|
108
|
+
refused state or flow declaration), `unknown-state` (a binding or action
|
|
109
|
+
names no declared variable), `binding` (a malformed, misplaced, read-only or
|
|
110
|
+
ill-fitting binding), `action` (an action with the wrong fields or target),
|
|
111
|
+
`unknown-event` (an `on` key the component does not fire) and `workflow` (a
|
|
112
|
+
workflow graph rule). Deprecated components and props add a `deprecated`
|
|
113
|
+
warning without failing.
|
|
89
114
|
|
|
90
115
|
## What's inside
|
|
91
116
|
|
|
92
117
|
| Subpath | Purpose | Headline exports |
|
|
93
118
|
| --- | --- | --- |
|
|
94
|
-
| `@texuli/spec` | Runtime: types, vocabulary, validation, hashing | `BlueprintEnvelope`, `SpecCatalog`, `SpecValidation` and the other generated types; `SPEC_COMPONENT_IDS`, `SpecComponentProps`, `SPEC_CATALOG_HASH`, `SPEC_VERSION`; `validateBlueprint`, `validateTree`, `validateInstance`, `checkSchemaDocument`; `canonicalJson`, `sha256` |
|
|
95
|
-
| `@texuli/spec/
|
|
119
|
+
| `@texuli/spec` | Runtime: types, vocabulary, validation, hashing | `BlueprintEnvelope`, `SpecCatalog`, `SpecValidation`, `WorkflowGraph` and the other generated types; `SPEC_COMPONENT_IDS`, `SpecComponentProps`, `SPEC_CATALOG_HASH`, `SPEC_VERSION`; `validateBlueprint`, `validateTree`, `validateInstance`, `checkSchemaDocument`; the contract constants (`SPEC_ACTION_OPS`, `SPEC_EVENT_NAMES`, `SPEC_STATE_TYPES`, `SPEC_LIMITS`, `SPEC_PATTERNS`, `ACTION_FIELDS`); `canonicalJson`, `sha256` |
|
|
120
|
+
| `@texuli/spec/workflow` | Runtime, opt-in: workflow graph validation | `validateWorkflowGraph` (pass it in `SpecDocuments.validateWorkflowGraph`) |
|
|
121
|
+
| `@texuli/spec/migrate` | Runtime, opt-in: envelope migrations | `migrateBlueprint`, `SPEC_MIGRATIONS`, `UNDECLARED_SPEC_VERSION` |
|
|
122
|
+
| `@texuli/spec/surface` | Runtime: the state surface rules every validator shares (no imports) | `checkStateSurface`, `surfaceInputs`, `SPEC_CONTROL_STATE_TYPES`, `SURFACE_PLAYER_CODES`, `LIST_TEXT_PATTERN` |
|
|
123
|
+
| `@texuli/spec/codegen` | Build time: generation and conformance | `buildSpec`, `projectCatalog`, `checkConformance`, `checkCatalogContract`, `formatJson`, `nextSpecVersion`, `checkVersionHistory` |
|
|
96
124
|
|
|
97
125
|
## Key guarantees
|
|
98
126
|
|
|
@@ -107,9 +135,36 @@ include `schema`, `unknown-prop`, `prop-type`, `enum-value`,
|
|
|
107
135
|
refused when you pass `catalogHash`.
|
|
108
136
|
- **Deterministic.** Canonical JSON sorts keys at every depth, and every
|
|
109
137
|
generated file carries the spec version and digest with no timestamps.
|
|
138
|
+
- **Interaction as data, never code.** Spec 0.3.0 adds local state,
|
|
139
|
+
bindings (`{"$state": name}`, read-only `{"$data": name}`), a closed
|
|
140
|
+
action set (`setState`, `toggleState`, `moveItem`, `flowGo`, `emit`,
|
|
141
|
+
`submit`) on a closed event vocabulary, drag as data (`reorder`) and
|
|
142
|
+
workflow graphs (at most 500 nodes and 2,000 edges). Nothing in a
|
|
143
|
+
blueprint is evaluated.
|
|
144
|
+
- **Decks as data.** Spec 0.4.0 (ADR-009) gives
|
|
145
|
+
`layout-deck` and `deck-slide` optional deck model v2 props: the 9:16
|
|
146
|
+
aspect, the `slide`, `zoom` and `morph` transitions, motion, navigation,
|
|
147
|
+
outline, a slide `layout` with its `props`, `background`, step labels and
|
|
148
|
+
component slides. The change is additive: every 0.3.0 envelope is a valid
|
|
149
|
+
0.4.0 envelope, and the `0.3.0 → 0.4.0` migration only stamps `spec`. The
|
|
150
|
+
deck model itself is validated by `@texuli/deck`, not by this package.
|
|
110
151
|
- **Structure only.** This validator checks structure. URL policy, tree
|
|
111
152
|
limits and rendering live in the runtime validator of
|
|
112
|
-
[`@texuli/catalog`](https://www.npmjs.com/package/@texuli/catalog).
|
|
153
|
+
[`@texuli/catalog`](https://www.npmjs.com/package/@texuli/catalog). The
|
|
154
|
+
reducers that run the actions belong to the runtime
|
|
155
|
+
([`@texuli/player`](https://www.npmjs.com/package/@texuli/player)), which
|
|
156
|
+
runs every 0.3.0 action from `0.4.0`).
|
|
157
|
+
- **A blueprint that validates paints.** The state surface rules
|
|
158
|
+
(`@texuli/spec/surface`) refuse exactly what the player refuses at paint
|
|
159
|
+
time: form fields and submits outside an `action-form`, nested forms,
|
|
160
|
+
controls and fields that name no declared variable of their type, flow
|
|
161
|
+
nodes without a matching view or event, and writers of a host-bound
|
|
162
|
+
variable. The spec, `@texuli/catalog`'s `validateDraft` and the Kotlin
|
|
163
|
+
twin run the same rules.
|
|
164
|
+
- **List item text never hides or reorders itself.** String fields of list
|
|
165
|
+
state items refuse control characters, bidirectional overrides and
|
|
166
|
+
isolates, and invisible formatting characters (`SPEC_PATTERNS.listText`;
|
|
167
|
+
ZWNJ and ZWJ stay allowed for scripts and emoji).
|
|
113
168
|
|
|
114
169
|
## Versioning
|
|
115
170
|
|
|
@@ -131,8 +186,17 @@ reads. Native (Kotlin) builds compile the generated models from the same
|
|
|
131
186
|
spec, and their decoders are closed: a blueprint that uses a field from a
|
|
132
187
|
newer spec fails to decode on an older native build with a `schema`
|
|
133
188
|
diagnostic instead of passing silently. A blueprint binds to an exact
|
|
134
|
-
catalog hash
|
|
135
|
-
|
|
189
|
+
catalog hash, and from 0.3.0 may declare the exact spec version it was
|
|
190
|
+
written for (`spec`; omitted means 0.2.0). `migrateBlueprint`
|
|
191
|
+
(`@texuli/spec/migrate`) brings an
|
|
192
|
+
envelope up to the current spec with pure, per-version steps
|
|
193
|
+
(`src/migrations/`: `0.2.0 → 0.3.0`, `0.3.0 → 0.4.0` and `0.4.0 → 0.5.0`,
|
|
194
|
+
each of which only stamps `spec`); a compatible version range is planned,
|
|
195
|
+
not built. `@texuli/spec@0.5.0` carries spec `0.5.0`, which adds the
|
|
196
|
+
`data-cohort-table`, `data-notification-inbox`, `data-filter-builder` and
|
|
197
|
+
`time-field` catalog ids; `@texuli/spec@0.4.0` carried spec `0.4.0` (digest
|
|
198
|
+
`fac29f542b16ddb78ab401c508e5b8346f655991c56e25aa811f4931661a8cb3`). Every
|
|
199
|
+
pin is in `versions.json`.
|
|
136
200
|
|
|
137
201
|
The full rules (the digest, `versions.json` pins, regeneration and the
|
|
138
202
|
conformance harness) are in
|
|
@@ -166,3 +230,7 @@ bugs at [github.com/DailybotHQ/texuli/issues](https://github.com/DailybotHQ/texu
|
|
|
166
230
|
## License
|
|
167
231
|
|
|
168
232
|
MIT © Dailybot, Inc.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
Powered by [Dailybot](https://www.dailybot.com?utm_source=dailybotopensource&utm_medium=texuli-spec).
|
package/dist/build.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export declare const SCHEMA_UNITS: readonly [{
|
|
|
24
24
|
readonly prefix: "Spec";
|
|
25
25
|
readonly ts: "manifest.ts";
|
|
26
26
|
readonly kt: "Manifest.kt";
|
|
27
|
+
}, {
|
|
28
|
+
readonly name: "workflow";
|
|
29
|
+
readonly prefix: "";
|
|
30
|
+
readonly ts: "workflow.ts";
|
|
31
|
+
readonly kt: "Workflow.kt";
|
|
27
32
|
}];
|
|
28
33
|
export declare const TS_OUT = "generated/typescript/";
|
|
29
34
|
export declare const KOTLIN_OUT = "generated/kotlin/com/texuli/spec/";
|
package/dist/build.js
CHANGED
|
@@ -13,6 +13,7 @@ export const SCHEMA_UNITS = [
|
|
|
13
13
|
{ name: 'catalog', prefix: 'Spec', ts: 'catalog-schema.ts', kt: 'CatalogSchema.kt' },
|
|
14
14
|
{ name: 'diagnostics', prefix: 'Spec', ts: 'diagnostics.ts', kt: 'Diagnostics.kt' },
|
|
15
15
|
{ name: 'manifest', prefix: 'Spec', ts: 'manifest.ts', kt: 'Manifest.kt' },
|
|
16
|
+
{ name: 'workflow', prefix: '', ts: 'workflow.ts', kt: 'Workflow.kt' },
|
|
16
17
|
];
|
|
17
18
|
export const TS_OUT = 'generated/typescript/';
|
|
18
19
|
export const KOTLIN_OUT = 'generated/kotlin/com/texuli/spec/';
|
package/dist/canonical.js
CHANGED
|
@@ -46,16 +46,25 @@ function encode(value, stack) {
|
|
|
46
46
|
export function prettyJson(value) {
|
|
47
47
|
return `${JSON.stringify(JSON.parse(canonicalJson(value)), null, 2)}\n`;
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The fractional part of `p`'s square (`n` 2) or cube (`n` 3) root as a
|
|
51
|
+
* 32-bit word: the SHA-256 constants (FIPS 180-4 §4.2.2, §5.3.3). Newton
|
|
52
|
+
* steps on plain IEEE arithmetic, which every runtime computes bit for bit
|
|
53
|
+
* (`Math.cbrt` and `**` are only approximated); every word sits thousands
|
|
54
|
+
* of ulps from a truncation boundary, and the test vectors cover all 72.
|
|
55
|
+
*/
|
|
56
|
+
function rootWord(p, n) {
|
|
57
|
+
let x = 2;
|
|
58
|
+
for (let i = 0; i < 40; i += 1) {
|
|
59
|
+
x -= n === 2 ? (x * x - p) / (2 * x) : (x * x * x - p) / (3 * x * x);
|
|
60
|
+
}
|
|
61
|
+
return ((x - Math.floor(x)) * 0x100000000) >>> 0;
|
|
62
|
+
}
|
|
63
|
+
const PRIMES = [];
|
|
64
|
+
for (let n = 2; PRIMES.length < 64; n += 1)
|
|
65
|
+
if (PRIMES.every((p) => n % p))
|
|
66
|
+
PRIMES.push(n);
|
|
67
|
+
const K = new Uint32Array(PRIMES.map((p) => rootWord(p, 3)));
|
|
59
68
|
/** UTF-8 bytes of a string (no `TextEncoder` needed). */
|
|
60
69
|
function utf8(text) {
|
|
61
70
|
const bytes = [];
|
|
@@ -83,9 +92,7 @@ export function sha256(text) {
|
|
|
83
92
|
for (const word of [high, bitLength >>> 0]) {
|
|
84
93
|
bytes.push((word >>> 24) & 0xff, (word >>> 16) & 0xff, (word >>> 8) & 0xff, word & 0xff);
|
|
85
94
|
}
|
|
86
|
-
const hash = new Uint32Array(
|
|
87
|
-
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
88
|
-
]);
|
|
95
|
+
const hash = new Uint32Array(PRIMES.slice(0, 8).map((p) => rootWord(p, 2)));
|
|
89
96
|
const w = new Uint32Array(64);
|
|
90
97
|
const rotr = (x, n) => (x >>> n) | (x << (32 - n));
|
|
91
98
|
for (let offset = 0; offset < bytes.length; offset += 64) {
|
package/dist/catalog.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* projection from a built catalog. The projection is the bootstrap step of
|
|
4
4
|
* spec/Compose design §2.3; the parity check reruns it to catch drift.
|
|
5
5
|
*/
|
|
6
|
-
import type { SpecCatalog, SpecComponent, SpecProp } from './generated/typescript/catalog-schema.js';
|
|
7
|
-
export type { SpecCatalog, SpecComponent, SpecProp };
|
|
6
|
+
import type { SpecCatalog, SpecComponent, SpecDeprecation, SpecEventName, SpecProp, SpecPropSchema } from './generated/typescript/catalog-schema.js';
|
|
7
|
+
export type { SpecCatalog, SpecComponent, SpecDeprecation, SpecEventName, SpecProp, SpecPropSchema, };
|
|
8
8
|
export declare const CAPABILITIES: readonly ["portable-core", "web-only", "native-only", "unknown"];
|
|
9
9
|
export type Capability = SpecComponent['capability'];
|
|
10
10
|
export type SpecPropKind = SpecProp['kind'];
|
|
@@ -24,17 +24,23 @@ export interface BuiltCatalogLike {
|
|
|
24
24
|
group: string;
|
|
25
25
|
tier: 'app' | 'content';
|
|
26
26
|
emit: readonly string[];
|
|
27
|
+
events?: readonly SpecEventName[];
|
|
28
|
+
bindable?: readonly string[];
|
|
29
|
+
deprecated?: SpecDeprecation;
|
|
27
30
|
props: readonly {
|
|
28
31
|
name: string;
|
|
29
32
|
kind: SpecPropKind;
|
|
30
33
|
required?: boolean;
|
|
31
34
|
enumValues?: readonly string[];
|
|
35
|
+
schema?: SpecPropSchema;
|
|
36
|
+
deprecated?: SpecDeprecation;
|
|
32
37
|
}[];
|
|
33
38
|
}[];
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Project a built catalog into the spec vocabulary. Deterministic: components
|
|
37
|
-
* sorted by id, props by name, emit tiers
|
|
42
|
+
* sorted by id, props by name, emit tiers, events and bindable props sorted;
|
|
43
|
+
* events, bindable, property schemas and deprecations only when present. Capability is `unknown`
|
|
38
44
|
* unless `capabilities` (the reviewed `catalog/capabilities.json`) records an
|
|
39
45
|
* audited value (ADR-003: no id is portable until audited).
|
|
40
46
|
*/
|
package/dist/catalog.js
CHANGED
|
@@ -13,7 +13,8 @@ export const WEB_ONLY_KINDS = ['slot', 'event', 'unknown'];
|
|
|
13
13
|
export const WEB_ONLY_PROP_NAMES = ['class', 'className', 'style', 'renderer'];
|
|
14
14
|
/**
|
|
15
15
|
* Project a built catalog into the spec vocabulary. Deterministic: components
|
|
16
|
-
* sorted by id, props by name, emit tiers
|
|
16
|
+
* sorted by id, props by name, emit tiers, events and bindable props sorted;
|
|
17
|
+
* events, bindable, property schemas and deprecations only when present. Capability is `unknown`
|
|
17
18
|
* unless `capabilities` (the reviewed `catalog/capabilities.json`) records an
|
|
18
19
|
* audited value (ADR-003: no id is portable until audited).
|
|
19
20
|
*/
|
|
@@ -39,7 +40,12 @@ export function projectCatalog(catalog, catalogHash, capabilities = {}) {
|
|
|
39
40
|
...(WEB_ONLY_KINDS.includes(prop.kind) || WEB_ONLY_PROP_NAMES.includes(prop.name)
|
|
40
41
|
? { extension: 'web' }
|
|
41
42
|
: {}),
|
|
43
|
+
...(prop.schema ? { schema: prop.schema } : {}),
|
|
44
|
+
...(prop.deprecated ? { deprecated: { ...prop.deprecated } } : {}),
|
|
42
45
|
})),
|
|
46
|
+
...(entry.events?.length ? { events: [...entry.events].sort() } : {}),
|
|
47
|
+
...(entry.bindable?.length ? { bindable: [...entry.bindable].sort() } : {}),
|
|
48
|
+
...(entry.deprecated ? { deprecated: { ...entry.deprecated } } : {}),
|
|
43
49
|
}));
|
|
44
50
|
return { catalogHash, components };
|
|
45
51
|
}
|
package/dist/codegen/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
export { buildSpec, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, type SpecBuild, TS_OUT, } from '../build.js';
|
|
8
8
|
export { prettyJson } from '../canonical.js';
|
|
9
9
|
export { type BuiltCatalogLike, projectCatalog } from '../catalog.js';
|
|
10
|
-
export { type ConformanceIssue, checkConformance } from '../conformance.js';
|
|
10
|
+
export { type ConformanceIssue, checkCatalogContract, checkConformance, deprecationExpired, } from '../conformance.js';
|
|
11
11
|
export { formatJson, LINE_WIDTH } from '../format.js';
|
|
12
12
|
export { checkVersionHistory, nextSpecVersion, type SpecChangeClass, type SpecVersionHistory, type SpecVersionPin, type VersionIssue, } from '../versioning.js';
|
|
13
|
-
export { KOTLIN_PACKAGE, kotlinString } from './kotlin.js';
|
|
13
|
+
export { KOTLIN_PACKAGE, kotlinProperty, kotlinString } from './kotlin.js';
|
|
14
14
|
export type { SchemaUnit, Stamp } from './shared.js';
|
package/dist/codegen/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export { buildSpec, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, TS_OUT, } from "../build.js";
|
|
8
8
|
export { prettyJson } from "../canonical.js";
|
|
9
9
|
export { projectCatalog } from "../catalog.js";
|
|
10
|
-
export { checkConformance } from "../conformance.js";
|
|
10
|
+
export { checkCatalogContract, checkConformance, deprecationExpired, } from "../conformance.js";
|
|
11
11
|
export { formatJson, LINE_WIDTH } from "../format.js";
|
|
12
12
|
export { checkVersionHistory, nextSpecVersion, } from "../versioning.js";
|
|
13
|
-
export { KOTLIN_PACKAGE, kotlinString } from "./kotlin.js";
|
|
13
|
+
export { KOTLIN_PACKAGE, kotlinProperty, kotlinString } from "./kotlin.js";
|
package/dist/codegen/kotlin.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ import { type SchemaUnit, type Stamp } from './shared.js';
|
|
|
10
10
|
export declare const KOTLIN_PACKAGE = "com.texuli.spec";
|
|
11
11
|
/** A Kotlin string literal (escapes backslash, quote, dollar and controls). */
|
|
12
12
|
export declare function kotlinString(value: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* The Kotlin property for a wire name: a leading `$` is stripped (`$state` →
|
|
15
|
+
* `state`), because `$` starts a string template in Kotlin. The wire name
|
|
16
|
+
* itself is always emitted through {@link kotlinString}.
|
|
17
|
+
*/
|
|
18
|
+
export declare function kotlinProperty(name: string): string;
|
|
13
19
|
/** One schema document as a Kotlin file of data classes, enums and decoders. */
|
|
14
20
|
export declare function schemaToKotlin(unit: SchemaUnit, stamp: Stamp): string;
|
|
15
21
|
/** The JSON model and decode helpers every generated decoder uses. */
|
package/dist/codegen/kotlin.js
CHANGED
|
@@ -64,6 +64,18 @@ const KEYWORDS = new Set([
|
|
|
64
64
|
function field(name) {
|
|
65
65
|
return KEYWORDS.has(name) ? `\`${name}\`` : name;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The Kotlin property for a wire name: a leading `$` is stripped (`$state` →
|
|
69
|
+
* `state`), because `$` starts a string template in Kotlin. The wire name
|
|
70
|
+
* itself is always emitted through {@link kotlinString}.
|
|
71
|
+
*/
|
|
72
|
+
export function kotlinProperty(name) {
|
|
73
|
+
return name.startsWith('$') ? name.slice(1) : name;
|
|
74
|
+
}
|
|
75
|
+
/** `$` escaped for a Kotlin string template (`\$state`). */
|
|
76
|
+
function templateText(name) {
|
|
77
|
+
return name.replaceAll('$', '\\$');
|
|
78
|
+
}
|
|
67
79
|
function kotlinType(node, ctx, inlineName) {
|
|
68
80
|
if (node.$ref !== undefined)
|
|
69
81
|
return refType(ctx.unit, node.$ref);
|
|
@@ -160,22 +172,27 @@ function dataClass(name, node, ctx) {
|
|
|
160
172
|
const props = Object.entries(node.properties ?? {});
|
|
161
173
|
const params = [];
|
|
162
174
|
const args = [];
|
|
175
|
+
const seen = new Set();
|
|
163
176
|
for (const [prop, child] of props) {
|
|
164
|
-
const
|
|
177
|
+
const property = kotlinProperty(prop);
|
|
178
|
+
if (seen.has(property))
|
|
179
|
+
throw new Error(`${name}: Kotlin property "${property}" collides`);
|
|
180
|
+
seen.add(property);
|
|
181
|
+
const inlineName = `${name}${pascal(property)}`;
|
|
165
182
|
const type = kotlinType(child, ctx, inlineName);
|
|
166
|
-
const path = `"$path.${prop}"`;
|
|
183
|
+
const path = `"$path.${templateText(prop)}"`;
|
|
167
184
|
if (required.has(prop)) {
|
|
168
185
|
const defaultValue = typeof child.const === 'string'
|
|
169
186
|
? ` = ${kotlinString(child.const)}`
|
|
170
187
|
: child.const !== undefined
|
|
171
188
|
? ` = ${child.const}L`
|
|
172
189
|
: '';
|
|
173
|
-
params.push(`${INDENT}val ${field(
|
|
174
|
-
args.push(`${INDENT}${INDENT}${INDENT}${INDENT}${field(
|
|
190
|
+
params.push(`${INDENT}val ${field(property)}: ${type}${defaultValue},`);
|
|
191
|
+
args.push(`${INDENT}${INDENT}${INDENT}${INDENT}${field(property)} = ${decodeExpr(child, `fields.required(${kotlinString(prop)}, path)`, path, ctx, inlineName)},`);
|
|
175
192
|
}
|
|
176
193
|
else {
|
|
177
|
-
params.push(`${INDENT}val ${field(
|
|
178
|
-
args.push(`${INDENT}${INDENT}${INDENT}${INDENT}${field(
|
|
194
|
+
params.push(`${INDENT}val ${field(property)}: ${type}? = null,`);
|
|
195
|
+
args.push(`${INDENT}${INDENT}${INDENT}${INDENT}${field(property)} = fields[${kotlinString(prop)}]?.let { ${decodeExpr(child, 'it', path, ctx, inlineName)} },`);
|
|
179
196
|
}
|
|
180
197
|
}
|
|
181
198
|
const allowed = props.map(([prop]) => kotlinString(prop)).join(', ');
|
|
@@ -299,6 +316,45 @@ export function kotlinJsonRuntime(stamp) {
|
|
|
299
316
|
];
|
|
300
317
|
return lines.join('\n');
|
|
301
318
|
}
|
|
319
|
+
function deprecationExpr(value) {
|
|
320
|
+
const replacement = value.replacement === undefined ? '' : `, replacement = ${kotlinString(value.replacement)}`;
|
|
321
|
+
return `SpecDeprecation(since = ${kotlinString(value.since)}${replacement})`;
|
|
322
|
+
}
|
|
323
|
+
/** A `SpecPropSchema(...)` constructor call, recursively; only the keywords present. */
|
|
324
|
+
function propSchemaExpr(schema) {
|
|
325
|
+
const args = [];
|
|
326
|
+
const number = (value) => (Number.isInteger(value) ? `${value}.0` : String(value));
|
|
327
|
+
if (schema.type !== undefined)
|
|
328
|
+
args.push(`type = SpecPropSchemaType.${upperSnake(schema.type)}`);
|
|
329
|
+
if (schema.description !== undefined)
|
|
330
|
+
args.push(`description = ${kotlinString(schema.description)}`);
|
|
331
|
+
if (schema.properties !== undefined) {
|
|
332
|
+
const entries = Object.entries(schema.properties).map(([name, child]) => `${kotlinString(name)} to ${propSchemaExpr(child)}`);
|
|
333
|
+
args.push(`properties = mapOf(${entries.join(', ')})`);
|
|
334
|
+
}
|
|
335
|
+
if (schema.required !== undefined)
|
|
336
|
+
args.push(`required = listOf(${schema.required.map(kotlinString).join(', ')})`);
|
|
337
|
+
if (schema.additionalProperties !== undefined) {
|
|
338
|
+
args.push(`additionalProperties = ${propSchemaExpr(schema.additionalProperties)}`);
|
|
339
|
+
}
|
|
340
|
+
if (schema.items !== undefined)
|
|
341
|
+
args.push(`items = ${propSchemaExpr(schema.items)}`);
|
|
342
|
+
if (schema.enum !== undefined)
|
|
343
|
+
args.push(`enum = listOf(${schema.enum.map(kotlinString).join(', ')})`);
|
|
344
|
+
for (const key of ['minLength', 'maxLength', 'minItems', 'maxItems']) {
|
|
345
|
+
const value = schema[key];
|
|
346
|
+
if (value !== undefined)
|
|
347
|
+
args.push(`${key} = ${value}L`);
|
|
348
|
+
}
|
|
349
|
+
for (const key of ['minimum', 'maximum']) {
|
|
350
|
+
const value = schema[key];
|
|
351
|
+
if (value !== undefined)
|
|
352
|
+
args.push(`${key} = ${number(value)}`);
|
|
353
|
+
}
|
|
354
|
+
if (schema.$ref !== undefined)
|
|
355
|
+
args.push(`ref = ${kotlinString(schema.$ref)}`);
|
|
356
|
+
return `SpecPropSchema(${args.join(', ')})`;
|
|
357
|
+
}
|
|
302
358
|
/** The catalog vocabulary as a Kotlin registry of `SpecComponent` values. */
|
|
303
359
|
export function vocabularyToKotlin(catalog, stamp) {
|
|
304
360
|
const ids = catalog.components.map((c) => c.id);
|
|
@@ -319,9 +375,25 @@ export function vocabularyToKotlin(catalog, stamp) {
|
|
|
319
375
|
const enumValues = p.enumValues
|
|
320
376
|
? `listOf(${p.enumValues.map(kotlinString).join(', ')})`
|
|
321
377
|
: undefined;
|
|
322
|
-
lines.push(`${INDENT}${INDENT}${INDENT}${INDENT}SpecProp(`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}name = ${kotlinString(p.name)},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}kind = SpecPropKind.${upperSnake(p.kind)},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}required = ${optional(p.required === undefined ? undefined : String(p.required))},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}enumValues = ${optional(enumValues)},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}extension = ${optional(p.extension ? `SpecPropExtension.${upperSnake(p.extension)}` : undefined)}
|
|
378
|
+
lines.push(`${INDENT}${INDENT}${INDENT}${INDENT}SpecProp(`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}name = ${kotlinString(p.name)},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}kind = SpecPropKind.${upperSnake(p.kind)},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}required = ${optional(p.required === undefined ? undefined : String(p.required))},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}enumValues = ${optional(enumValues)},`, `${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}extension = ${optional(p.extension ? `SpecPropExtension.${upperSnake(p.extension)}` : undefined)},`);
|
|
379
|
+
// Emitted only when present, so a catalog without them stays byte-stable.
|
|
380
|
+
if (p.schema)
|
|
381
|
+
lines.push(`${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}schema = ${propSchemaExpr(p.schema)},`);
|
|
382
|
+
if (p.deprecated) {
|
|
383
|
+
lines.push(`${INDENT}${INDENT}${INDENT}${INDENT}${INDENT}deprecated = ${deprecationExpr(p.deprecated)},`);
|
|
384
|
+
}
|
|
385
|
+
lines.push(`${INDENT}${INDENT}${INDENT}${INDENT}),`);
|
|
386
|
+
}
|
|
387
|
+
lines.push(`${INDENT}${INDENT}${INDENT}),`);
|
|
388
|
+
if (c.events) {
|
|
389
|
+
lines.push(`${INDENT}${INDENT}${INDENT}events = listOf(${c.events.map((e) => `SpecEventName.${upperSnake(e)}`).join(', ')}),`);
|
|
390
|
+
}
|
|
391
|
+
if (c.bindable) {
|
|
392
|
+
lines.push(`${INDENT}${INDENT}${INDENT}bindable = listOf(${c.bindable.map(kotlinString).join(', ')}),`);
|
|
323
393
|
}
|
|
324
|
-
|
|
394
|
+
if (c.deprecated)
|
|
395
|
+
lines.push(`${INDENT}${INDENT}${INDENT}deprecated = ${deprecationExpr(c.deprecated)},`);
|
|
396
|
+
lines.push(`${INDENT}${INDENT}),`);
|
|
325
397
|
}
|
|
326
398
|
lines.push(`${INDENT})`, '', `${INDENT}fun component(id: String): SpecComponent? = components.firstOrNull { it.id == id }`, '}', '');
|
|
327
399
|
return lines.join('\n');
|
package/dist/conformance.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type SpecCatalog } from './catalog.js';
|
|
1
2
|
/** One conformance failure: drift is a failed gate, never a warning. */
|
|
2
3
|
export interface ConformanceIssue {
|
|
3
|
-
check: 'dialect' | 'catalog' | 'capabilities' | 'manifest' | 'generated' | 'golden' | 'invalid';
|
|
4
|
+
check: 'dialect' | 'catalog' | 'capabilities' | 'manifest' | 'generated' | 'golden' | 'invalid' | 'contract' | 'prop-schema' | 'deprecation' | 'workflow';
|
|
4
5
|
path: string;
|
|
5
6
|
message: string;
|
|
6
7
|
}
|
|
@@ -14,7 +15,15 @@ export interface ConformanceIssue {
|
|
|
14
15
|
* - regenerating gives byte-identical TypeScript and Kotlin, with no file
|
|
15
16
|
* missing or extra;
|
|
16
17
|
* - every golden blueprint validates, and every invalid fixture fails with
|
|
17
|
-
* exactly its expected diagnostic codes
|
|
18
|
+
* exactly its expected diagnostic codes (blueprints and workflow graphs);
|
|
19
|
+
* - the blueprint and workflow `$defs` stay disjoint, every catalog property
|
|
20
|
+
* schema is usable (array and object props only, contract `$ref`s only),
|
|
21
|
+
* bindable props are declared portable props, and no deprecation outlives
|
|
22
|
+
* its one-minor warning window.
|
|
18
23
|
* Runtime-neutral: the caller reads the files.
|
|
19
24
|
*/
|
|
20
25
|
export declare function checkConformance(normative: Readonly<Record<string, string>>, committed: Readonly<Record<string, string>>): ConformanceIssue[];
|
|
26
|
+
/** The minor-version window: a deprecation since `since` expires one minor later. */
|
|
27
|
+
export declare function deprecationExpired(since: string, specVersion?: string): boolean;
|
|
28
|
+
/** Catalog rules the schema cannot express: property schemas, bindable props, deprecations. */
|
|
29
|
+
export declare function checkCatalogContract(catalog: SpecCatalog, specVersion?: string): ConformanceIssue[];
|
package/dist/conformance.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { buildSpec } from "./build.js";
|
|
2
2
|
import { CAPABILITIES } from "./catalog.js";
|
|
3
|
+
import { checkPropSchema, defCollisions } from "./contract.js";
|
|
3
4
|
import { checkSchemaDocument, isRecord, validateInstance } from "./dialect.js";
|
|
4
5
|
import { validateBlueprint } from "./validate.js";
|
|
6
|
+
import { SPEC_VERSION } from "./version.js";
|
|
7
|
+
import { validateWorkflowGraph } from "./workflow.js";
|
|
5
8
|
/**
|
|
6
9
|
* Run the spec's conformance harness (spec/Compose design §7.1 and §7.3)
|
|
7
10
|
* over the normative files and the committed outputs (`manifest.json`,
|
|
@@ -12,7 +15,11 @@ import { validateBlueprint } from "./validate.js";
|
|
|
12
15
|
* - regenerating gives byte-identical TypeScript and Kotlin, with no file
|
|
13
16
|
* missing or extra;
|
|
14
17
|
* - every golden blueprint validates, and every invalid fixture fails with
|
|
15
|
-
* exactly its expected diagnostic codes
|
|
18
|
+
* exactly its expected diagnostic codes (blueprints and workflow graphs);
|
|
19
|
+
* - the blueprint and workflow `$defs` stay disjoint, every catalog property
|
|
20
|
+
* schema is usable (array and object props only, contract `$ref`s only),
|
|
21
|
+
* bindable props are declared portable props, and no deprecation outlives
|
|
22
|
+
* its one-minor warning window.
|
|
16
23
|
* Runtime-neutral: the caller reads the files.
|
|
17
24
|
*/
|
|
18
25
|
export function checkConformance(normative, committed) {
|
|
@@ -32,6 +39,16 @@ export function checkConformance(normative, committed) {
|
|
|
32
39
|
}
|
|
33
40
|
const catalogSchema = schemas['schemas/catalog.schema.json'];
|
|
34
41
|
const blueprintSchema = schemas['schemas/blueprint.schema.json'];
|
|
42
|
+
const workflowSchema = schemas['schemas/workflow.schema.json'];
|
|
43
|
+
if (blueprintSchema) {
|
|
44
|
+
for (const name of defCollisions(blueprintSchema, workflowSchema)) {
|
|
45
|
+
issues.push({
|
|
46
|
+
check: 'contract',
|
|
47
|
+
path: 'schemas/blueprint.schema.json',
|
|
48
|
+
message: `$defs.${name} is also a workflow definition`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
35
52
|
const catalog = json('catalog/components.json');
|
|
36
53
|
if (catalogSchema) {
|
|
37
54
|
for (const issue of validateInstance(catalogSchema, catalog)) {
|
|
@@ -62,6 +79,7 @@ export function checkConformance(normative, committed) {
|
|
|
62
79
|
}
|
|
63
80
|
}
|
|
64
81
|
}
|
|
82
|
+
issues.push(...checkCatalogContract(catalog));
|
|
65
83
|
let build;
|
|
66
84
|
try {
|
|
67
85
|
build = buildSpec(normative);
|
|
@@ -100,8 +118,23 @@ export function checkConformance(normative, committed) {
|
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
120
|
}
|
|
121
|
+
if (workflowSchema) {
|
|
122
|
+
for (const path of Object.keys(normative).filter((p) => p.startsWith('fixtures/workflow/'))) {
|
|
123
|
+
const fixture = json(path);
|
|
124
|
+
const golden = path.startsWith('fixtures/workflow/golden/');
|
|
125
|
+
const got = validateWorkflowGraph(golden ? fixture : fixture.graph, workflowSchema).diagnostics.map((d) => d.code);
|
|
126
|
+
const expected = golden ? [] : (fixture.expect ?? []);
|
|
127
|
+
if (got.join(',') !== expected.join(',')) {
|
|
128
|
+
issues.push({
|
|
129
|
+
check: 'workflow',
|
|
130
|
+
path,
|
|
131
|
+
message: `expected [${expected.join(', ')}], got [${got.join(', ')}]`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
103
136
|
if (blueprintSchema) {
|
|
104
|
-
const documents = { blueprintSchema, catalog };
|
|
137
|
+
const documents = { blueprintSchema, catalog, workflowSchema, validateWorkflowGraph };
|
|
105
138
|
for (const path of Object.keys(normative).filter((p) => p.startsWith('fixtures/golden/'))) {
|
|
106
139
|
const result = validateBlueprint(json(path), documents);
|
|
107
140
|
for (const d of result.diagnostics) {
|
|
@@ -122,3 +155,61 @@ export function checkConformance(normative, committed) {
|
|
|
122
155
|
}
|
|
123
156
|
return issues;
|
|
124
157
|
}
|
|
158
|
+
/** The minor-version window: a deprecation since `since` expires one minor later. */
|
|
159
|
+
export function deprecationExpired(since, specVersion = SPEC_VERSION) {
|
|
160
|
+
const parse = (v) => v.split('.').map(Number);
|
|
161
|
+
const [major, minor] = parse(specVersion);
|
|
162
|
+
const [sinceMajor, sinceMinor] = parse(since);
|
|
163
|
+
return major > sinceMajor || (major === sinceMajor && minor >= sinceMinor + 1);
|
|
164
|
+
}
|
|
165
|
+
/** Catalog rules the schema cannot express: property schemas, bindable props, deprecations. */
|
|
166
|
+
export function checkCatalogContract(catalog, specVersion = SPEC_VERSION) {
|
|
167
|
+
const issues = [];
|
|
168
|
+
const path = 'catalog/components.json';
|
|
169
|
+
const expired = (where, deprecated) => {
|
|
170
|
+
if (deprecated && deprecationExpired(deprecated.since, specVersion)) {
|
|
171
|
+
issues.push({
|
|
172
|
+
check: 'deprecation',
|
|
173
|
+
path,
|
|
174
|
+
message: `${where}: deprecated since ${deprecated.since}; remove it (one minor of warnings has passed)`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
for (const component of catalog?.components ?? []) {
|
|
179
|
+
expired(component.id, component.deprecated);
|
|
180
|
+
const portable = new Set(component.props.filter((p) => !p.extension).map((p) => p.name));
|
|
181
|
+
for (const name of component.bindable ?? []) {
|
|
182
|
+
if (!portable.has(name)) {
|
|
183
|
+
issues.push({
|
|
184
|
+
check: 'prop-schema',
|
|
185
|
+
path,
|
|
186
|
+
message: `${component.id}: bindable "${name}" is not a portable prop`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (new Set(component.events ?? []).size !== (component.events ?? []).length) {
|
|
191
|
+
issues.push({ check: 'catalog', path, message: `${component.id}: events repeat` });
|
|
192
|
+
}
|
|
193
|
+
for (const prop of component.props) {
|
|
194
|
+
expired(`${component.id}.${prop.name}`, prop.deprecated);
|
|
195
|
+
if (!prop.schema)
|
|
196
|
+
continue;
|
|
197
|
+
if (prop.kind !== 'array' && prop.kind !== 'object') {
|
|
198
|
+
issues.push({
|
|
199
|
+
check: 'prop-schema',
|
|
200
|
+
path,
|
|
201
|
+
message: `${component.id}.${prop.name}: only array and object props take a schema`,
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
for (const problem of checkPropSchema(prop.schema)) {
|
|
206
|
+
issues.push({
|
|
207
|
+
check: 'prop-schema',
|
|
208
|
+
path,
|
|
209
|
+
message: `${component.id}.${prop.name}: ${problem}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return issues;
|
|
215
|
+
}
|