ng-form-foundry-transformers 0.3.4 → 0.4.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 CHANGED
@@ -50,6 +50,69 @@ the library's `serializeForm(schema, form)`, which strips the `__case`
50
50
  discriminators from `getRawValue()`. The YANG adapter is the exception: its
51
51
  `toYangData` consumes the form value and flattens `__case` itself.
52
52
 
53
+ ## Thesaurus — display metadata for machine schemas
54
+
55
+ Machine schemas (O-RAN A1 policy types, inferred configs) ship without titles,
56
+ so forms fall back to raw attribute names (`guRanUeId`, `mcc`). Every
57
+ `toSchema` accepts a **thesaurus** — identifier → `{ label, description }` —
58
+ injected into the produced schema, JSON-Schema-driven or inferred alike:
59
+
60
+ ```ts
61
+ const thesaurus = {
62
+ ueId: { label: 'UE ID', description: 'UE identifier.' },
63
+ mcc: { label: 'MCC', description: 'Mobile Country Code (3 digits).' },
64
+ };
65
+ yamlTransformer.toSchema(text, { thesaurus }); // also json / libconfig
66
+ jsonSchemaToNodeGroup(schema, 'body', { thesaurus }); // or standalone
67
+ applyThesaurus(nodeGroup, thesaurus); // post-process anything
68
+ ```
69
+
70
+ Keys are **plain identifier names**, matched **case-insensitively** against
71
+ each node's property/setting name — one entry covers the identifier wherever
72
+ it appears. Keys are never paths: no separator exists, so a `.` in a key is a
73
+ literal character of the name. The thesaurus fills gaps only — schema-authored
74
+ `title`/`description` always win. Unlabeled choice cases are titled from their
75
+ discriminating required field; when sibling cases collide (identical required
76
+ sets), the library's case selectors disambiguate them by each case's
77
+ distinguishing fields.
78
+
79
+ When the same identifier means different things at different depths, a key
80
+ maps to a **list of variants scoped by `under`** — an ancestor-**name** suffix
81
+ (an array, so still no separators; each segment is a literal name). The
82
+ longest matching scope wins; an entry without `under` is the fallback:
83
+
84
+ ```ts
85
+ const thesaurus = {
86
+ id: [
87
+ { under: ['cell'], label: 'Cell ID' }, // any `id` directly inside a `cell`
88
+ { under: ['slice'], label: 'S-NSSAI' },
89
+ { label: 'ID' }, // everywhere else
90
+ ],
91
+ };
92
+ ```
93
+
94
+ Scoping follows the **data hierarchy of names**: list indices, map entry keys,
95
+ and — by default — choice case names are transparent. For a **choice**, case
96
+ fields match both with and without their case-name segment, so one scope can
97
+ cover the whole choice or target a single case:
98
+
99
+ ```ts
100
+ // scope (choice) → cases byUe / byCell, both with a field named `id`
101
+ const thesaurus = {
102
+ id: [
103
+ { under: ['scope', 'byUe'], label: 'UE ID' }, // only inside the byUe case
104
+ { under: ['scope', 'byCell'], label: 'Cell ID' }, // only inside byCell
105
+ { under: ['scope'], label: 'Scope ID' }, // any other case of scope
106
+ ],
107
+ };
108
+ // → byUe.id: "UE ID", byCell.id: "Cell ID"; the per-case labels also flow
109
+ // into caseLabels via each case's discriminating field.
110
+ ```
111
+
112
+ Scoping by an auto-generated case name (`under: ['case0']`) works but is
113
+ positional — it shifts when the source schema reorders its branches; prefer
114
+ case-name scopes only for hand-named cases.
115
+
53
116
  ## YAML transformer
54
117
 
55
118
  Edit a **YAML config file**: turn it into a form, then write the edited value
@@ -231,6 +294,17 @@ npm test # node:test on the compiled output (no Python needed)
231
294
 
232
295
  ## Status
233
296
 
297
+ `0.4.0` — version-alignment release with the workspace (the paired
298
+ `ng-form-foundry` 0.4.0 adds appearance-driven field layout); no transformer
299
+ changes.
300
+
301
+ `0.3.5` — the **thesaurus**: every `toSchema` and `jsonSchemaToNodeGroup`
302
+ accept identifier → `{ label, description }` display metadata, with variants
303
+ scoped by ancestor names (`under`) for identifiers that mean different things
304
+ at different depths; choice cases are titled from their labeled discriminating
305
+ field. Adversarially battle-tested (51-agent panel); the paired library
306
+ release guarantees unique case-selector labels.
307
+
234
308
  `0.3.4` — the **libconfig transformer debuts as a beta** (one-time console
235
309
  warning on first use; lossless span-splicing round-trip with scalar types
236
310
  preserved — see [`docs/libconfig.md`](docs/libconfig.md)), and
@@ -12,3 +12,5 @@ export { jsonSchemaToNodeGroup } from './json-schema';
12
12
  export type { JsonSchema, JsonSchemaOptions } from './json-schema';
13
13
  export type { NodeGroup, NodeType, Leaf, LeafList, NodeGroupList, Choice, ChoiceCase, NodeMap, LeafType, FormValue, } from './schema';
14
14
  export { CASE_KEY } from './schema';
15
+ export type { Thesaurus, ThesaurusEntry } from './schema';
16
+ export { applyThesaurus } from './thesaurus';
@@ -7,7 +7,7 @@
7
7
  * map a JSON Schema) that any data-oriented transformer reuses.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.CASE_KEY = exports.jsonSchemaToNodeGroup = exports.inferNodeGroup = exports.TransformerRegistry = void 0;
10
+ exports.applyThesaurus = exports.CASE_KEY = exports.jsonSchemaToNodeGroup = exports.inferNodeGroup = exports.TransformerRegistry = void 0;
11
11
  var registry_1 = require("./registry");
12
12
  Object.defineProperty(exports, "TransformerRegistry", { enumerable: true, get: function () { return registry_1.TransformerRegistry; } });
13
13
  // Shared schema builders — used by the YAML and JSON transformers alike.
@@ -17,3 +17,5 @@ var json_schema_1 = require("./json-schema");
17
17
  Object.defineProperty(exports, "jsonSchemaToNodeGroup", { enumerable: true, get: function () { return json_schema_1.jsonSchemaToNodeGroup; } });
18
18
  var schema_1 = require("./schema");
19
19
  Object.defineProperty(exports, "CASE_KEY", { enumerable: true, get: function () { return schema_1.CASE_KEY; } });
20
+ var thesaurus_1 = require("./thesaurus");
21
+ Object.defineProperty(exports, "applyThesaurus", { enumerable: true, get: function () { return thesaurus_1.applyThesaurus; } });
@@ -1,4 +1,4 @@
1
- import type { NodeGroup } from './schema';
1
+ import type { NodeGroup, Thesaurus } from './schema';
2
2
  /**
3
3
  * The subset of JSON Schema (draft 2020-12, back-compatible with draft-07) this
4
4
  * transformer maps to a form. Only the keywords that shape a form are read;
@@ -56,6 +56,12 @@ export interface JsonSchemaOptions {
56
56
  * unconditionally.
57
57
  */
58
58
  optionalPresence?: boolean;
59
+ /**
60
+ * Display metadata injected into the produced schema (`label`,
61
+ * `description`, choice `caseLabels`) — see {@link applyThesaurus}. Fills
62
+ * gaps only; schema-authored `title`/`description` always win.
63
+ */
64
+ thesaurus?: Thesaurus;
59
65
  }
60
66
  type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';
61
67
  /**
@@ -71,7 +77,9 @@ type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'bo
71
77
  * choice `mandatory`; a property *not* in `required` becomes a `presence` node
72
78
  * — absent from the form value until enabled — unless
73
79
  * {@link JsonSchemaOptions.optionalPresence} is `false`. `title` becomes the
74
- * label, `default`/`const` carry a scalar value.
80
+ * label, `default`/`const` carry a scalar value, and
81
+ * {@link JsonSchemaOptions.thesaurus} fills labels/descriptions the schema
82
+ * does not author.
75
83
  */
76
84
  export declare function jsonSchemaToNodeGroup(schema: JsonSchema, name?: string, options?: JsonSchemaOptions): NodeGroup;
77
85
  export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jsonSchemaToNodeGroup = jsonSchemaToNodeGroup;
4
+ const thesaurus_1 = require("./thesaurus");
4
5
  const ROOT = '__root__';
5
6
  /**
6
7
  * Map a JSON Schema whose top level is an object into a root {@link NodeGroup}.
@@ -15,7 +16,9 @@ const ROOT = '__root__';
15
16
  * choice `mandatory`; a property *not* in `required` becomes a `presence` node
16
17
  * — absent from the form value until enabled — unless
17
18
  * {@link JsonSchemaOptions.optionalPresence} is `false`. `title` becomes the
18
- * label, `default`/`const` carry a scalar value.
19
+ * label, `default`/`const` carry a scalar value, and
20
+ * {@link JsonSchemaOptions.thesaurus} fills labels/descriptions the schema
21
+ * does not author.
19
22
  */
20
23
  function jsonSchemaToNodeGroup(schema, name = ROOT, options) {
21
24
  const documents = [schema, ...(options?.refDocuments ?? [])];
@@ -24,7 +27,7 @@ function jsonSchemaToNodeGroup(schema, name = ROOT, options) {
24
27
  const { schema: root, scope } = resolver.resolve(schema);
25
28
  const group = objectToNodeGroup(root, name, scope, ctx, root.title);
26
29
  group.root = true;
27
- return group;
30
+ return options?.thesaurus ? (0, thesaurus_1.applyThesaurus)(group, options.thesaurus) : group;
28
31
  }
29
32
  /**
30
33
  * Apply {@link JsonSchemaOptions.optionalPresence} to one *property* node: a
@@ -66,6 +66,7 @@ export interface NodeGroup {
66
66
  */
67
67
  minPresent?: number;
68
68
  maxPresent?: number;
69
+ description?: string;
69
70
  children: Record<string, NodeType>;
70
71
  }
71
72
  export interface NodeGroupList {
@@ -122,3 +123,30 @@ export type NodeType = Leaf | LeafList | NodeGroup | NodeGroupList | Choice | No
122
123
  export declare const CASE_KEY = "__case";
123
124
  /** A plain, nested value object produced/consumed by a rendered form. */
124
125
  export type FormValue = Record<string, unknown>;
126
+ /** Display metadata for one schema identifier (see {@link Thesaurus}). */
127
+ export interface ThesaurusEntry {
128
+ label?: string;
129
+ description?: string;
130
+ /**
131
+ * Ancestor-name suffix this variant applies under: each segment is a
132
+ * **literal name** (no separators exist, so any character is literal),
133
+ * matched case-insensitively against the identifier's ancestor chain.
134
+ * `['scope', 'cell']` applies to a node whose parent is `cell` inside
135
+ * `scope`, at any depth. Longest matching `under` wins; an entry without
136
+ * `under` is the unscoped fallback. Case fields match both with and without
137
+ * their case-name segment, so `['scope']` covers every case of a choice and
138
+ * `['scope', 'byUe']` targets one case.
139
+ */
140
+ under?: string[];
141
+ }
142
+ /**
143
+ * Identifier → display metadata, injected into a generated {@link NodeGroup}
144
+ * by `applyThesaurus`. Keys are **plain identifier names** matched
145
+ * **case-insensitively** against node record keys — never paths: no separator
146
+ * exists, so a `.` (or any character) in a key is a literal part of the name.
147
+ *
148
+ * A key maps to one entry, or to a list of variants scoped by
149
+ * {@link ThesaurusEntry.under} when the same identifier carries different
150
+ * meanings at different depths.
151
+ */
152
+ export type Thesaurus = Record<string, ThesaurusEntry | ThesaurusEntry[]>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * A thesaurus: human-readable metadata for schema identifiers, injected into a
3
+ * generated {@link NodeGroup} as `label`/`description`/`caseLabels`.
4
+ *
5
+ * Machine schemas (O-RAN A1 policy types, inferred configs) ship without
6
+ * titles, so forms fall back to raw attribute names (`guRanUeId`, `mcc`).
7
+ * A thesaurus maps those identifiers to display metadata once, and every
8
+ * transformer entry point applies it to whatever schema it produced —
9
+ * JSON-Schema-driven or inferred alike.
10
+ *
11
+ * Keys are **plain identifier names**, matched **case-insensitively** against
12
+ * each node's record key (its property/setting name). They are never paths:
13
+ * no separator exists, so a name containing `.`/`/` or any other character is
14
+ * matched verbatim — one entry covers the identifier wherever it appears in
15
+ * the tree. When the same identifier means different things at different
16
+ * depths, a key maps to a list of variants scoped by
17
+ * {@link ThesaurusEntry.under} (an ancestor-name suffix); the longest
18
+ * matching scope wins, and an unscoped variant is the fallback.
19
+ */
20
+ import type { NodeGroup, Thesaurus } from './schema';
21
+ /**
22
+ * Return a copy of `schema` with thesaurus metadata filled in. Existing
23
+ * `label`/`description`/`caseLabels` values are respected, never overwritten —
24
+ * the thesaurus fills gaps, it does not restyle authored schemas.
25
+ *
26
+ * Coverage: every node reachable from the root (group children, list item
27
+ * fields, map value templates, choice case fields). Scoping follows the
28
+ * **data hierarchy of names**: fields inside list items scope under the list
29
+ * name (indices are transparent), map value fields under the map name
30
+ * (runtime keys are transparent), and choice case fields match both with and
31
+ * without their case-name segment — `under: ['scope']` covers every case of
32
+ * the `scope` choice, `under: ['scope', 'byUe']` targets one case (scoping by
33
+ * an auto-generated `case0`-style name works but is positional and brittle).
34
+ *
35
+ * A choice case with no label is titled from its discriminating field — the
36
+ * first `required` field (else the first field) whose thesaurus match carries
37
+ * a `label` (description-only entries do not title cases).
38
+ * Sibling cases may end up with the same label when their required sets
39
+ * coincide; the library's `caseDisplayLabels` disambiguates colliding labels
40
+ * in the selectors by each case's distinguishing fields, using the field
41
+ * labels injected here.
42
+ */
43
+ export declare function applyThesaurus<G extends NodeGroup>(schema: G, thesaurus: Thesaurus): G;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyThesaurus = applyThesaurus;
4
+ /**
5
+ * Return a copy of `schema` with thesaurus metadata filled in. Existing
6
+ * `label`/`description`/`caseLabels` values are respected, never overwritten —
7
+ * the thesaurus fills gaps, it does not restyle authored schemas.
8
+ *
9
+ * Coverage: every node reachable from the root (group children, list item
10
+ * fields, map value templates, choice case fields). Scoping follows the
11
+ * **data hierarchy of names**: fields inside list items scope under the list
12
+ * name (indices are transparent), map value fields under the map name
13
+ * (runtime keys are transparent), and choice case fields match both with and
14
+ * without their case-name segment — `under: ['scope']` covers every case of
15
+ * the `scope` choice, `under: ['scope', 'byUe']` targets one case (scoping by
16
+ * an auto-generated `case0`-style name works but is positional and brittle).
17
+ *
18
+ * A choice case with no label is titled from its discriminating field — the
19
+ * first `required` field (else the first field) whose thesaurus match carries
20
+ * a `label` (description-only entries do not title cases).
21
+ * Sibling cases may end up with the same label when their required sets
22
+ * coincide; the library's `caseDisplayLabels` disambiguates colliding labels
23
+ * in the selectors by each case's distinguishing fields, using the field
24
+ * labels injected here.
25
+ */
26
+ function applyThesaurus(schema, thesaurus) {
27
+ const lookup = new Map();
28
+ for (const [key, value] of Object.entries(thesaurus)) {
29
+ const variants = (Array.isArray(value) ? value : [value]).map((entry, order) => ({
30
+ under: (entry.under ?? []).map((segment) => segment.toLowerCase()),
31
+ entry,
32
+ order,
33
+ }));
34
+ lookup.set(key.toLowerCase(), variants);
35
+ }
36
+ const out = structuredClone(schema);
37
+ decorate(out, out.name, [[]], lookup);
38
+ return out;
39
+ }
40
+ /**
41
+ * The best-scoped entry for `name` given the node's applicable ancestor
42
+ * chains: the variant with the longest `under` that is a suffix of any chain
43
+ * (an entry without `under` always applies); equal lengths keep author order.
44
+ */
45
+ function resolve(name, chains, lookup) {
46
+ const variants = lookup.get(name.toLowerCase());
47
+ if (!variants)
48
+ return undefined;
49
+ let best;
50
+ for (const v of variants) {
51
+ if (!chains.some((chain) => isSuffix(v.under, chain)))
52
+ continue;
53
+ if (!best || v.under.length > best.under.length)
54
+ best = v;
55
+ }
56
+ return best?.entry;
57
+ }
58
+ function isSuffix(under, chain) {
59
+ if (under.length > chain.length)
60
+ return false;
61
+ const tail = chain.slice(chain.length - under.length);
62
+ return under.every((segment, i) => segment === tail[i]);
63
+ }
64
+ /**
65
+ * `transparentKey` marks a structural hop that contributes no ancestor
66
+ * segment: a map's `value` template is not a name in the data — entry fields
67
+ * scope under the map's own name.
68
+ */
69
+ function decorate(node, key, chains, lookup, transparentKey = false) {
70
+ const entry = resolve(key, chains, lookup) ?? (key === node.name ? undefined : resolve(node.name, chains, lookup));
71
+ if (entry) {
72
+ if (entry.label !== undefined && node.label === undefined)
73
+ node.label = entry.label;
74
+ if (entry.description !== undefined &&
75
+ (node.kind === 'leaf' || node.kind === 'nodeGroup' || node.kind === 'map') &&
76
+ node.description === undefined) {
77
+ node.description = entry.description;
78
+ }
79
+ }
80
+ // Children's ancestor chains gain this node's key. List items and map
81
+ // entries add no segment of their own — indices and runtime keys are not
82
+ // names — so their fields scope under the list/map name directly.
83
+ const childChains = transparentKey ? chains : chains.map((chain) => [...chain, key.toLowerCase()]);
84
+ switch (node.kind) {
85
+ case 'nodeGroup':
86
+ for (const [childKey, child] of Object.entries(node.children))
87
+ decorate(child, childKey, childChains, lookup);
88
+ return;
89
+ case 'nodeGroupList':
90
+ for (const [childKey, child] of Object.entries(node.type.children))
91
+ decorate(child, childKey, childChains, lookup);
92
+ return;
93
+ case 'map':
94
+ decorate(node.value, node.value.name, childChains, lookup, true);
95
+ return;
96
+ case 'choice': {
97
+ for (const [caseName, body] of Object.entries(node.cases)) {
98
+ // Case fields match with and without the case segment: the wire
99
+ // encoding is inline (case-transparent), while the extra chain lets a
100
+ // scope target one case of the choice.
101
+ const caseChains = [...childChains, ...childChains.map((c) => [...c, caseName.toLowerCase()])];
102
+ const fields = caseFieldRecord(body);
103
+ for (const [fieldKey, field] of Object.entries(fields))
104
+ decorate(field, fieldKey, caseChains, lookup);
105
+ if (node.caseLabels?.[caseName] !== undefined)
106
+ continue;
107
+ // The discriminant must carry a LABEL — a description-only entry on an
108
+ // earlier field must not block a labeled sibling from titling the case.
109
+ const keys = Object.keys(fields);
110
+ const labelOf = (k) => resolve(k, caseChains, lookup)?.label;
111
+ const discriminant = keys.find((k) => fields[k].required && labelOf(k) !== undefined) ??
112
+ keys.find((k) => labelOf(k) !== undefined);
113
+ const label = discriminant ? labelOf(discriminant) : undefined;
114
+ if (label !== undefined)
115
+ node.caseLabels = { ...(node.caseLabels ?? {}), [caseName]: label };
116
+ }
117
+ return;
118
+ }
119
+ default:
120
+ return; // leaf / leafList: nothing beneath
121
+ }
122
+ }
123
+ /** A case body as a field record (a leaf-bodied case is a one-field record keyed by its name). */
124
+ function caseFieldRecord(body) {
125
+ return typeof body.kind === 'string'
126
+ ? { [body.name]: body }
127
+ : body;
128
+ }
@@ -1,4 +1,4 @@
1
- import type { FormValue } from '../../core/schema';
1
+ import type { FormValue, Thesaurus } from '../../core/schema';
2
2
  import type { TransformResult } from '../../core/transformer';
3
3
  import { type JsonSchema, type JsonSchemaOptions } from '../../core/json-schema';
4
4
  /** Options for {@link jsonTransformer}'s `toSchema`. */
@@ -12,6 +12,13 @@ export interface JsonOptions {
12
12
  rootName?: string;
13
13
  /** Options forwarded to `jsonSchemaToNodeGroup` (`refDocuments`, `optionalPresence`). */
14
14
  schemaOptions?: JsonSchemaOptions;
15
+ /**
16
+ * Display metadata (`label`/`description`/choice `caseLabels`) injected into
17
+ * the produced schema, schema-driven or inferred alike — see
18
+ * `applyThesaurus`. Keys are plain identifier names, matched
19
+ * case-insensitively; never paths.
20
+ */
21
+ thesaurus?: Thesaurus;
15
22
  }
16
23
  /** How the source was formatted, so `toSource` re-emits it the same way. */
17
24
  export interface JsonFormat {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jsonTransformer = void 0;
4
+ const thesaurus_1 = require("../../core/thesaurus");
4
5
  const infer_1 = require("../../core/infer");
5
6
  const json_schema_1 = require("../../core/json-schema");
6
7
  const bigint_1 = require("../../core/bigint");
@@ -30,12 +31,13 @@ exports.jsonTransformer = {
30
31
  const schema = options?.schema
31
32
  ? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
32
33
  : (0, infer_1.inferNodeGroup)(data, options?.rootName);
34
+ const labeled = options?.thesaurus ? (0, thesaurus_1.applyThesaurus)(schema, options.thesaurus) : schema;
33
35
  const binding = {
34
36
  indent: detectIndent(source),
35
37
  trailingNewline: source.endsWith('\n'),
36
38
  bigInts,
37
39
  };
38
- return { schema, binding, initialValue: data };
40
+ return { schema: labeled, binding, initialValue: data };
39
41
  },
40
42
  toSource(value, binding) {
41
43
  const paths = new Set(binding.bigInts);
@@ -12,7 +12,7 @@
12
12
  * are then shown read-only. A JSON Schema in the options unlocks typed empty
13
13
  * collections, presence toggles for optional settings, enums, and ranges.
14
14
  */
15
- import type { FormValue } from '../../core/schema';
15
+ import type { FormValue, Thesaurus } from '../../core/schema';
16
16
  import type { TransformResult } from '../../core/transformer';
17
17
  import { type JsonSchema, type JsonSchemaOptions } from '../../core/json-schema';
18
18
  import { type CfgGroup } from './parser';
@@ -34,6 +34,13 @@ export interface LibconfigOptions {
34
34
  * line verbatim and edits only this file's own settings.
35
35
  */
36
36
  includes?: 'reject' | 'opaque';
37
+ /**
38
+ * Display metadata (`label`/`description`/choice `caseLabels`) injected into
39
+ * the produced schema, schema-driven or inferred alike — see
40
+ * `applyThesaurus`. Keys are plain identifier names, matched
41
+ * case-insensitively; never paths.
42
+ */
43
+ thesaurus?: Thesaurus;
37
44
  }
38
45
  /** The revert context: the original text and its positioned AST. */
39
46
  export interface LibconfigBinding {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.libconfigTransformer = void 0;
4
4
  exports.resetLibconfigBetaWarning = resetLibconfigBetaWarning;
5
+ const thesaurus_1 = require("../../core/thesaurus");
5
6
  const json_schema_1 = require("../../core/json-schema");
6
7
  const parser_1 = require("./parser");
7
8
  const schema_1 = require("./schema");
@@ -26,8 +27,9 @@ exports.libconfigTransformer = {
26
27
  const schema = options?.schema
27
28
  ? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
28
29
  : (0, schema_1.libconfigToNodeGroup)(root, source, options?.rootName ?? '__root__');
30
+ const labeled = options?.thesaurus ? (0, thesaurus_1.applyThesaurus)(schema, options.thesaurus) : schema;
29
31
  const initialValue = (0, schema_1.extractValue)(root, source, options?.schema != null);
30
- return { schema, binding: { source, root }, initialValue };
32
+ return { schema: labeled, binding: { source, root }, initialValue };
31
33
  },
32
34
  toSource(value, binding) {
33
35
  warnBeta();
@@ -1,5 +1,5 @@
1
1
  import { type Document } from 'yaml';
2
- import type { FormValue } from '../../core/schema';
2
+ import type { FormValue, Thesaurus } from '../../core/schema';
3
3
  import type { TransformResult } from '../../core/transformer';
4
4
  import { type JsonSchema, type JsonSchemaOptions } from '../../core/json-schema';
5
5
  /** Options for {@link yamlTransformer}'s `toSchema`. */
@@ -14,6 +14,13 @@ export interface YamlOptions {
14
14
  rootName?: string;
15
15
  /** Options forwarded to `jsonSchemaToNodeGroup` (`refDocuments`, `optionalPresence`). */
16
16
  schemaOptions?: JsonSchemaOptions;
17
+ /**
18
+ * Display metadata (`label`/`description`/choice `caseLabels`) injected into
19
+ * the produced schema, schema-driven or inferred alike — see
20
+ * `applyThesaurus`. Keys are plain identifier names, matched
21
+ * case-insensitively; never paths.
22
+ */
23
+ thesaurus?: Thesaurus;
17
24
  }
18
25
  /**
19
26
  * A YAML {@link Transformer}: turn a YAML config document into a form and write
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.yamlTransformer = void 0;
4
4
  const yaml_1 = require("yaml");
5
+ const thesaurus_1 = require("../../core/thesaurus");
5
6
  const infer_1 = require("../../core/infer");
6
7
  const json_schema_1 = require("../../core/json-schema");
7
8
  const bigint_1 = require("../../core/bigint");
@@ -29,7 +30,8 @@ exports.yamlTransformer = {
29
30
  const schema = options?.schema
30
31
  ? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
31
32
  : (0, infer_1.inferNodeGroup)(data, options?.rootName);
32
- return { schema, binding: doc, initialValue: data };
33
+ const labeled = options?.thesaurus ? (0, thesaurus_1.applyThesaurus)(schema, options.thesaurus) : schema;
34
+ return { schema: labeled, binding: doc, initialValue: data };
33
35
  },
34
36
  toSource(value, binding) {
35
37
  const doc = binding.clone();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry-transformers",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Framework-agnostic Node + TypeScript catalog of source-format transformers: turn a YANG model (and more) into an ng-form-foundry schema and revert the edited form value back to the source format.",
5
5
  "keywords": [
6
6
  "ng-form-foundry",