@openmixer/omx-contract 0.0.0 → 1.0.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/render/c.mjs ADDED
@@ -0,0 +1,104 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * The `c` target: `omxcontract/omx_contract_limits.h`, one `omxcontract/params/omx_<plugin>_params.h`
5
+ * per plugin item and the shape they fill, `omxcontract/params/omx_plugin_param.h`. Every define is
6
+ * spelled by openmixer's renderer, moved unchanged (c/contract-limits-render.mjs,
7
+ * c/params-generate.mjs); only the frame (banner, section comments) is this repository's.
8
+ */
9
+ import { readFileSync } from 'node:fs';
10
+ import {
11
+ genCoreLimitBlock, genDeclaredListsBlock, genDeclaredRates, genDeclaredScalarsBlock, genLimitBlock, renderContractLimits,
12
+ } from './c/contract-limits-render.mjs';
13
+ import { generate } from './c/params-generate.mjs';
14
+ import { doors } from './doors.mjs';
15
+
16
+ const PLUGIN_PARAM_H = readFileSync(new URL('./c/omx_plugin_param.h', import.meta.url), 'utf8');
17
+ const defined = (text) => new Set([...text.matchAll(/^#define (OMX_\w+)/gm)].map((m) => m[1]));
18
+
19
+ /** The rate tail of openmixer's header, outside the include guard, byte for byte. */
20
+ function ratesTail(rates) {
21
+ return `
22
+ /* The sample rates the console declares (core's STANDARD_SAMPLE_RATES, row-codecs.ts): the set a
23
+ * contract's rate-is-declared precondition accepts, through omx_rate_is_declared(), and the grid a
24
+ * native tool iterates. Guarded on its own so a tool built against a pinned copy of this header
25
+ * (lv2-inprocess-pin.sh) can include this tree's copy after it and gain the rates without mixing
26
+ * the pin's limits. */
27
+ #ifndef OMX_DECLARED_RATE_COUNT
28
+ ${genDeclaredRates(rates)}
29
+ /* The declared rates that are whole multiples of \`base\` (a tool's default grid: the 48 kHz family),
30
+ * in declaration order, at most \`cap\` of them; returns how many were written to \`out\`. */
31
+ static inline int omx_declared_rates_multiple_of(unsigned base, int *out, int cap) {
32
+ int n = 0;
33
+ for (unsigned k = 0; k < OMX_DECLARED_RATE_COUNT && n < cap; k++)
34
+ if ((unsigned)OMX_DECLARED_RATES[k] % base == 0) out[n++] = (int)OMX_DECLARED_RATES[k];
35
+ return n;
36
+ }
37
+ #endif
38
+ `;
39
+ }
40
+
41
+ /** The limits header of the resolved data. */
42
+ export function renderLimitsHeader(resolved) {
43
+ const d = doors(resolved);
44
+ const tables = d.NATIVE_LIMIT_TABLES.map(([prefix, table]) => genLimitBlock(prefix, table)).join('\n\n');
45
+ const coreLimits = new Map(d.ALIASES);
46
+ const aliases = [...coreLimits.keys()].map((fact) => genCoreLimitBlock(coreLimits, fact)).join('\n\n');
47
+ const above = defined([tables, aliases].join('\n'));
48
+ const scalars = d.DECLARED_SCALARS.length ? genDeclaredScalarsBlock(d.DECLARED_SCALARS, above) : '';
49
+ const lists = d.DECLARED_LISTS.length ? genDeclaredListsBlock(d.DECLARED_LISTS, defined([tables, aliases, scalars].join('\n'))) : '';
50
+ const sections = [
51
+ [tables, `/* Per-parameter numeric travel, one block per \`travels\` table named <PREFIX>_LIMITS:
52
+ * OMX_<PREFIX>_<FIELD>_MIN/_MAX/_DEFAULT, float literals. */`],
53
+ [aliases, `/* The second C spelling of a travel, its item's \`c.alias\` { fact, field }:
54
+ * OMX_<FACT>_<FIELD>_MIN/_MAX/_DEFAULT. Removed by a MAJOR release once no consumer reads it. */`],
55
+ [scalars, `/* Every scalar, sheet leaf and single travel's numbers, one block sorted by macro. An integer value
56
+ * is an integer literal, any other a float literal AND its OMX_<macro>_DOUBLE spelling. */`],
57
+ [lists, `/* Every list rendered to C, sorted by list: OMX_<LIST>_COUNT and OMX_<LIST>_INIT { a, b, … }. */`],
58
+ ].filter(([body]) => body);
59
+ return `// SPDX-License-Identifier: GPL-3.0-or-later
60
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
61
+ #ifndef OMX_CONTRACT_LIMITS_H
62
+ #define OMX_CONTRACT_LIMITS_H
63
+ /*
64
+ * GENERATED — DO NOT EDIT BY HAND.
65
+ * Produced by \`omx-contract render --target c\` from FreeMixer/omx-contract's data/, the declaration
66
+ * as data (each item's reason is its \`doc\`). Change the data, never this file: a consumer builds
67
+ * against the render of the omx-contract version it pins and commits none.
68
+ *
69
+ * A C contract (OMX_PRE/OMX_POST/OMX_INVARIANT) reads a limit from HERE, never a literal.
70
+ */
71
+
72
+ ${sections.map(([body, comment]) => `${comment}\n${body}`).join('\n\n')}
73
+
74
+ #endif /* OMX_CONTRACT_LIMITS_H */
75
+ ${d.STANDARD_SAMPLE_RATES ? ratesTail(d.STANDARD_SAMPLE_RATES) : ''}`;
76
+ }
77
+
78
+ /** The banner lines of a plugin's params header rendered from the data. */
79
+ const producedBy = (r) => ` * Produced by \`omx-contract render --target c\` from FreeMixer/omx-contract's ${r.rel}
80
+ * (${r.name}): ${r.source}.
81
+ * Change the data, never this file. Order is append-only (omx-contract spec §3.3).`;
82
+
83
+ /** Every file of the `c` target: `[{ path, text }]`, paths relative to the output directory. */
84
+ export function renderC(resolved) {
85
+ const files = [{ path: 'omxcontract/omx_contract_limits.h', text: renderLimitsHeader(resolved) }];
86
+ const plugins = doors(resolved).PLUGINS;
87
+ for (const r of plugins) {
88
+ files.push({ path: `omxcontract/params/${r.header}`, text: generate({ header: r.header, prefix: r.prefix, source: r.source, producedBy: producedBy(r) }, r.value) });
89
+ }
90
+ if (plugins.length) files.push({ path: 'omxcontract/params/omx_plugin_param.h', text: PLUGIN_PARAM_H });
91
+ return files;
92
+ }
93
+
94
+ /**
95
+ * The `c` target of a frozen openmixer sheet (test/fixtures/): `{ sheet, plugins: [{ header, prefix,
96
+ * source, params }] }` rendered by openmixer's own renderer, for the round-trip proof (spec §4.4 step 1).
97
+ */
98
+ export function renderSheetC(fixture) {
99
+ const s = fixture.sheet;
100
+ const sheet = { ...s, CORE_LIMITS: new Map(Object.entries(s.CORE_LIMITS)) };
101
+ const files = [{ path: 'omxcontract/omx_contract_limits.h', text: renderContractLimits(sheet) }];
102
+ for (const p of fixture.plugins) files.push({ path: `omxcontract/params/${p.header}`, text: generate(p, p.params) });
103
+ return files;
104
+ }
@@ -0,0 +1,64 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * The resolved data cut into the C renderer's doors, as openmixer's discovery cut its barrels
5
+ * (packages/core/src/native-limit-tables.ts, declared-scalars.ts, declared-lists.ts): a `travels`
6
+ * table named <PREFIX>_LIMITS is a native table; every scalar, sheet and single travel is scalars
7
+ * (its finite-number leaves, a travel's `unit`/`defaultFrom` labels skipped); a list is a declared
8
+ * list unless its item says `"c": { "render": false }`; a `c.alias` is the core-limit spelling.
9
+ */
10
+
11
+ const constantCase = (key) => key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
12
+ const isSheet = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.keys(v).length > 0;
13
+ const isTravel = (v) => typeof v.min === 'number' && typeof v.max === 'number';
14
+ const TRAVEL_LABELS = new Set(['unit', 'defaultFrom']);
15
+
16
+ /** Every leaf of `v` when ALL its leaves are finite numbers; `undefined` when any is not (declared-scalars.ts). */
17
+ function numberLeaves(v, path, macro) {
18
+ if (typeof v === 'number') return Number.isFinite(v) ? [{ path, macro, value: v }] : undefined;
19
+ if (!isSheet(v)) return undefined;
20
+ const out = [];
21
+ for (const [key, leaf] of Object.entries(v)) {
22
+ if (TRAVEL_LABELS.has(key) && typeof leaf === 'string' && !path.includes('.') && isTravel(v)) continue;
23
+ const leaves = numberLeaves(leaf, `${path}.${key}`, `${macro}_${constantCase(key)}`);
24
+ if (leaves === undefined) return undefined;
25
+ out.push(...leaves);
26
+ }
27
+ return out;
28
+ }
29
+
30
+ /** True for a `travels` table that is a native limit table (its name ends `_LIMITS`). */
31
+ export const isNativeTable = (r) => r.kind === 'travels' && r.shape === 'table' && r.name.endsWith('_LIMITS');
32
+
33
+ /** The doors of the resolved items (a Map name -> resolved item). */
34
+ export function doors(resolved) {
35
+ const items = [...resolved.values()];
36
+ const NATIVE_LIMIT_TABLES = items.filter(isNativeTable)
37
+ .map((r) => [r.name.slice(0, -'_LIMITS'.length), r.value])
38
+ .sort(([a], [b]) => a.localeCompare(b));
39
+ const byMacro = new Map();
40
+ for (const r of items) {
41
+ if (isNativeTable(r) || r.kind === 'plugin' || r.kind === 'list') continue;
42
+ const leaves = numberLeaves(r.value, r.name, r.name.replace(/_(KERNEL|LIMITS)$/, ''));
43
+ if (leaves === undefined) continue;
44
+ for (const s of leaves) {
45
+ const seen = byMacro.get(s.macro);
46
+ if (seen) throw new Error(`${seen.path} and ${s.path} both render as OMX_${s.macro} — rename one`);
47
+ byMacro.set(s.macro, s);
48
+ }
49
+ }
50
+ const DECLARED_SCALARS = [...byMacro.values()].sort((a, b) => (a.macro < b.macro ? -1 : a.macro > b.macro ? 1 : 0));
51
+ const DECLARED_LISTS = items.filter((r) => r.kind === 'list' && r.c?.render !== false)
52
+ .map((r) => ({ name: r.name, values: r.value }))
53
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
54
+ const ALIASES = new Map();
55
+ for (const r of items) {
56
+ if (!r.c?.alias) continue;
57
+ const { fact, field } = r.c.alias;
58
+ if (!ALIASES.has(fact)) ALIASES.set(fact, {});
59
+ ALIASES.get(fact)[field] = r.value;
60
+ }
61
+ const PLUGINS = items.filter((r) => r.kind === 'plugin');
62
+ const STANDARD_SAMPLE_RATES = resolved.get('STANDARD_SAMPLE_RATES')?.value;
63
+ return { NATIVE_LIMIT_TABLES, DECLARED_SCALARS, DECLARED_LISTS, ALIASES, PLUGINS, STANDARD_SAMPLE_RATES };
64
+ }
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * The `json` target: the resolved data (every reference and derivation evaluated) for readers in
5
+ * other languages, which need no resolver of their own.
6
+ */
7
+
8
+ /** Every file of the `json` target: `[{ path, text }]`. */
9
+ export function renderJson(resolved, version) {
10
+ const items = {};
11
+ for (const r of resolved.values()) {
12
+ const { name, ...rest } = r;
13
+ items[name] = rest;
14
+ }
15
+ return [{ path: 'omx-contract.json', text: `${JSON.stringify({ name: '@openmixer/omx-contract', version, items }, null, 2)}\n` }];
16
+ }
package/render/ts.mjs ADDED
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * The `ts` target: one ESM module and its type declarations, every item exported under its own
5
+ * name with its literal type (`as const`), so a re-export in openmixer keeps names, values and types.
6
+ */
7
+
8
+ /** The value a TypeScript reader sees for one resolved item. */
9
+ export function tsValue(r) {
10
+ if (r.kind !== 'plugin') return r.value;
11
+ return { plugin: r.plugin, header: r.header, prefix: r.prefix, params: r.value };
12
+ }
13
+
14
+ /** The literal type of a JSON value, every member readonly. */
15
+ export function tsType(v, indent = '') {
16
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v);
17
+ if (typeof v === 'string') return JSON.stringify(v);
18
+ const inner = `${indent} `;
19
+ if (Array.isArray(v)) return v.length ? `readonly [${v.map((x) => tsType(x, indent)).join(', ')}]` : 'readonly []';
20
+ const keys = Object.keys(v);
21
+ if (!keys.length) return '{}';
22
+ const key = (k) => (/^[A-Za-z_$][\w$]*$/.test(k) ? k : JSON.stringify(k));
23
+ return `{\n${keys.map((k) => `${inner}readonly ${key(k)}: ${tsType(v[k], inner)};`).join('\n')}\n${indent}}`;
24
+ }
25
+
26
+ const docComment = (r) => {
27
+ const text = [r.doc, r.why && `Why: ${r.why}`].filter(Boolean).join('\n\n').replace(/\*\//g, '*\\/');
28
+ return `/**\n${text.split('\n').map((l) => (l ? ` * ${l}` : ' *')).join('\n')}\n * @see ${r.rel}\n */`;
29
+ };
30
+
31
+ const BANNER = `// SPDX-License-Identifier: GPL-3.0-or-later
32
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
33
+ // GENERATED by \`omx-contract render --target ts\` from FreeMixer/omx-contract's data/ — do not edit.
34
+ `;
35
+
36
+ /** Every file of the `ts` target: `[{ path, text }]`. */
37
+ export function renderTs(resolved) {
38
+ const items = [...resolved.values()];
39
+ const js = BANNER + items.map((r) => `\n${docComment(r)}\nexport const ${r.name} = ${JSON.stringify(tsValue(r), null, 2)};\n`).join('');
40
+ const dts = BANNER + items.map((r) => `\n${docComment(r)}\nexport declare const ${r.name}: ${tsType(tsValue(r))};\n`).join('');
41
+ return [{ path: 'omx-contract.js', text: js }, { path: 'omx-contract.d.ts', text: dts }];
42
+ }
@@ -0,0 +1,194 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/FreeMixer/omx-contract/raw/main/schema/omx-contract.schema.json",
4
+ "title": "omx-contract data file",
5
+ "description": "A map from an export name to one item: a scalar, a travels table or travel, a sheet, a list or a plugin's parameter selection. GPL-3.0-or-later.",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": { "type": "string" }
9
+ },
10
+ "propertyNames": { "anyOf": [{ "const": "$schema" }, { "pattern": "^[A-Z][A-Z0-9_]*$" }] },
11
+ "additionalProperties": { "$ref": "#/$defs/item" },
12
+ "$defs": {
13
+ "unit": {
14
+ "description": "The closed list of units the items use; \"\" is a ratio or a fraction.",
15
+ "enum": ["dB", "dBFS", "ms", "s", "min", "Hz", "oct", "cents", "%", ""]
16
+ },
17
+ "text": { "type": "string", "minLength": 1 },
18
+ "name": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" },
19
+ "ref": {
20
+ "type": "object",
21
+ "properties": { "ref": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*(\\.[A-Za-z][A-Za-z0-9]*)*$" } },
22
+ "required": ["ref"],
23
+ "additionalProperties": false
24
+ },
25
+ "derive": {
26
+ "description": "The closed list of derivations; a new one is a renderer change with its own test.",
27
+ "oneOf": [
28
+ {
29
+ "type": "object",
30
+ "properties": { "derive": { "const": "count" }, "of": { "$ref": "#/$defs/name" } },
31
+ "required": ["derive", "of"],
32
+ "additionalProperties": false
33
+ },
34
+ {
35
+ "type": "object",
36
+ "properties": { "derive": { "const": "sqrt" }, "of": { "$ref": "#/$defs/number" } },
37
+ "required": ["derive", "of"],
38
+ "additionalProperties": false
39
+ },
40
+ {
41
+ "type": "object",
42
+ "properties": {
43
+ "derive": { "const": "lagrangeReadL1" },
44
+ "order": { "oneOf": [{ "type": "integer", "minimum": 1 }, { "$ref": "#/$defs/ref" }] }
45
+ },
46
+ "required": ["derive", "order"],
47
+ "additionalProperties": false
48
+ }
49
+ ]
50
+ },
51
+ "number": { "oneOf": [{ "type": "number" }, { "$ref": "#/$defs/ref" }, { "$ref": "#/$defs/derive" }] },
52
+ "travel": {
53
+ "type": "object",
54
+ "properties": {
55
+ "min": { "$ref": "#/$defs/number" },
56
+ "max": { "$ref": "#/$defs/number" },
57
+ "step": { "$ref": "#/$defs/number" },
58
+ "unit": { "$ref": "#/$defs/unit" },
59
+ "default": { "$ref": "#/$defs/number" },
60
+ "defaultFrom": { "enum": ["desk"] },
61
+ "byKind": {
62
+ "type": "object",
63
+ "propertyNames": { "pattern": "^[a-z][A-Za-z0-9]*$" },
64
+ "additionalProperties": { "$ref": "#/$defs/number" },
65
+ "minProperties": 1
66
+ },
67
+ "doc": { "$ref": "#/$defs/text" },
68
+ "why": { "$ref": "#/$defs/text" }
69
+ },
70
+ "required": ["min", "max"],
71
+ "additionalProperties": false
72
+ },
73
+ "sheetValue": {
74
+ "type": "object",
75
+ "propertyNames": { "pattern": "^[a-z][A-Za-z0-9]*$" },
76
+ "additionalProperties": { "oneOf": [{ "$ref": "#/$defs/number" }, { "$ref": "#/$defs/sheetValue" }] },
77
+ "minProperties": 1
78
+ },
79
+ "scalar": {
80
+ "type": "object",
81
+ "properties": {
82
+ "kind": { "const": "scalar" },
83
+ "doc": { "$ref": "#/$defs/text" },
84
+ "why": { "$ref": "#/$defs/text" },
85
+ "unit": { "$ref": "#/$defs/unit" },
86
+ "value": { "oneOf": [{ "$ref": "#/$defs/number" }, { "type": "boolean" }] }
87
+ },
88
+ "required": ["kind", "doc", "unit", "value"],
89
+ "additionalProperties": false
90
+ },
91
+ "travels": {
92
+ "type": "object",
93
+ "properties": {
94
+ "kind": { "const": "travels" },
95
+ "doc": { "$ref": "#/$defs/text" },
96
+ "why": { "$ref": "#/$defs/text" },
97
+ "fields": {
98
+ "type": "object",
99
+ "propertyNames": { "pattern": "^[a-z][A-Za-z0-9]*$" },
100
+ "additionalProperties": { "$ref": "#/$defs/travel" },
101
+ "minProperties": 1
102
+ },
103
+ "travel": { "$ref": "#/$defs/travel" },
104
+ "c": {
105
+ "type": "object",
106
+ "properties": {
107
+ "alias": {
108
+ "type": "object",
109
+ "properties": {
110
+ "fact": { "type": "string", "pattern": "^[a-z][A-Za-z0-9]*$" },
111
+ "field": { "type": "string", "pattern": "^[a-z][A-Za-z0-9]*$" }
112
+ },
113
+ "required": ["fact", "field"],
114
+ "additionalProperties": false
115
+ }
116
+ },
117
+ "additionalProperties": false
118
+ }
119
+ },
120
+ "required": ["kind", "doc"],
121
+ "oneOf": [{ "required": ["fields"] }, { "required": ["travel"] }],
122
+ "additionalProperties": false
123
+ },
124
+ "sheet": {
125
+ "type": "object",
126
+ "properties": {
127
+ "kind": { "const": "sheet" },
128
+ "doc": { "$ref": "#/$defs/text" },
129
+ "why": { "$ref": "#/$defs/text" },
130
+ "value": { "$ref": "#/$defs/sheetValue" }
131
+ },
132
+ "required": ["kind", "doc", "value"],
133
+ "additionalProperties": false
134
+ },
135
+ "list": {
136
+ "type": "object",
137
+ "properties": {
138
+ "kind": { "const": "list" },
139
+ "doc": { "$ref": "#/$defs/text" },
140
+ "why": { "$ref": "#/$defs/text" },
141
+ "unit": { "$ref": "#/$defs/unit" },
142
+ "values": { "type": "array", "items": { "type": "number" }, "minItems": 1 },
143
+ "c": {
144
+ "type": "object",
145
+ "properties": { "render": { "type": "boolean" } },
146
+ "additionalProperties": false
147
+ }
148
+ },
149
+ "required": ["kind", "doc", "unit", "values"],
150
+ "additionalProperties": false
151
+ },
152
+ "param": {
153
+ "type": "object",
154
+ "properties": {
155
+ "symbol": { "type": "string", "pattern": "^[a-z][A-Za-z0-9]*$" },
156
+ "from": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*(\\.[a-z][A-Za-z0-9]*)?$" },
157
+ "forKind": { "type": "string", "pattern": "^[a-z][A-Za-z0-9]*$" },
158
+ "flags": { "type": "array", "items": { "enum": ["toggle"] }, "uniqueItems": true },
159
+ "scale": { "enum": ["linear", "log"] },
160
+ "since": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
161
+ "doc": { "$ref": "#/$defs/text" }
162
+ },
163
+ "required": ["symbol", "from", "scale", "since"],
164
+ "additionalProperties": false
165
+ },
166
+ "plugin": {
167
+ "type": "object",
168
+ "properties": {
169
+ "kind": { "const": "plugin" },
170
+ "doc": { "$ref": "#/$defs/text" },
171
+ "why": { "$ref": "#/$defs/text" },
172
+ "plugin": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
173
+ "header": { "type": "string", "pattern": "^omx_[a-z0-9_]+_params\\.h$" },
174
+ "prefix": { "type": "string", "pattern": "^OMX_[A-Z0-9_]+$" },
175
+ "source": { "$ref": "#/$defs/text" },
176
+ "params": { "type": "array", "items": { "$ref": "#/$defs/param" }, "minItems": 1 }
177
+ },
178
+ "required": ["kind", "doc", "plugin", "header", "prefix", "source", "params"],
179
+ "additionalProperties": false
180
+ },
181
+ "item": {
182
+ "type": "object",
183
+ "required": ["kind"],
184
+ "properties": { "kind": { "enum": ["scalar", "travels", "sheet", "list", "plugin"] } },
185
+ "allOf": [
186
+ { "if": { "properties": { "kind": { "const": "scalar" } } }, "then": { "$ref": "#/$defs/scalar" } },
187
+ { "if": { "properties": { "kind": { "const": "travels" } } }, "then": { "$ref": "#/$defs/travels" } },
188
+ { "if": { "properties": { "kind": { "const": "sheet" } } }, "then": { "$ref": "#/$defs/sheet" } },
189
+ { "if": { "properties": { "kind": { "const": "list" } } }, "then": { "$ref": "#/$defs/list" } },
190
+ { "if": { "properties": { "kind": { "const": "plugin" } } }, "then": { "$ref": "#/$defs/plugin" } }
191
+ ]
192
+ }
193
+ }
194
+ }