ng-form-foundry-transformers 0.4.1 → 0.5.1

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.
@@ -5,6 +5,7 @@ const yaml_1 = require("yaml");
5
5
  const thesaurus_1 = require("../../core/thesaurus");
6
6
  const infer_1 = require("../../core/infer");
7
7
  const json_schema_1 = require("../../core/json-schema");
8
+ const merge_inferred_1 = require("../../core/merge-inferred");
8
9
  const bigint_1 = require("../../core/bigint");
9
10
  const revert_1 = require("./revert");
10
11
  /**
@@ -13,8 +14,9 @@ const revert_1 = require("./revert");
13
14
  * preserves comments, key order, and formatting by applying edits onto the parsed
14
15
  * document (see {@link applyValueToDocument}).
15
16
  *
16
- * The `binding` it round-trips is the parsed {@link Document}; `toSource` clones
17
- * it before applying, so a single `toSchema` result can serve many edits.
17
+ * The `binding` it round-trips is a {@link YamlBinding}; `toSource` clones the
18
+ * document inside it before applying, so a single `toSchema` result can serve
19
+ * many edits.
18
20
  *
19
21
  * With a JSON Schema in {@link YamlOptions}, the form is schema-driven; without
20
22
  * one it is inferred from the data.
@@ -27,20 +29,97 @@ exports.yamlTransformer = {
27
29
  // out-of-range integers become strings there (safe ones become plain numbers).
28
30
  const doc = (0, yaml_1.parseDocument)(source, { intAsBigInt: true });
29
31
  const data = (normalizeBigInts(doc.toJS()) ?? {});
30
- const schema = options?.schema
32
+ const unknownKeys = options?.unknownKeys ?? 'preserve';
33
+ const fromJsonSchema = options?.schema
31
34
  ? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
32
- : (0, infer_1.inferNodeGroup)(data, options?.rootName);
35
+ : undefined;
36
+ const schema = fromJsonSchema && unknownKeys === 'edit'
37
+ ? (0, merge_inferred_1.mergeInferred)(fromJsonSchema, (0, infer_1.inferNodeGroup)(data, options?.rootName))
38
+ : fromJsonSchema ?? (0, infer_1.inferNodeGroup)(data, options?.rootName);
39
+ // Inferred leaves (the whole schema, or the uncovered slice under 'edit')
40
+ // pick up the document's hex/octal presentation.
41
+ if ((!fromJsonSchema || unknownKeys === 'edit') && doc.contents)
42
+ annotateRadix(doc.contents, schema);
33
43
  const labeled = options?.thesaurus ? (0, thesaurus_1.applyThesaurus)(schema, options.thesaurus) : schema;
34
- return { schema: labeled, binding: doc, initialValue: data };
44
+ return {
45
+ schema: labeled,
46
+ // 'preserve' gates the revert on the JSON Schema's NodeGroup; 'edit' on
47
+ // the merged one (schema-born ≈ everything, but keys no form field can
48
+ // carry stay protected); 'drop' and inferred mode leave the value
49
+ // authoritative for the whole document.
50
+ binding: { doc, schema: fromJsonSchema && unknownKeys !== 'drop' ? schema : undefined },
51
+ initialValue: data,
52
+ };
35
53
  },
36
54
  toSource(value, binding) {
37
- const doc = binding.clone();
38
- (0, revert_1.applyValueToDocument)(doc, value);
55
+ const doc = binding.doc.clone();
56
+ (0, revert_1.applyValueToDocument)(doc, value, binding.schema);
39
57
  return String(doc);
40
58
  },
41
59
  // `satisfies` verifies conformance to the catalog contract while keeping the
42
60
  // concrete sync return types, so direct callers need no `await`.
43
61
  };
62
+ const RADIX_BY_FORMAT = { BIN: 2, OCT: 8, HEX: 16 };
63
+ /**
64
+ * Copy non-decimal integer presentation (`0x`/`0o`/`0b` literals) from the
65
+ * parsed document onto the inferred schema as leaf/leafList `radix` display
66
+ * hints — the plain-data inference cannot see them, because `toJS()` drops the
67
+ * scalar format. The revert needs nothing: scalars are mutated in place, so an
68
+ * edited value re-emits in its literal's own base. Across group-list items the
69
+ * first non-decimal occurrence wins — one shared item schema cannot vary the
70
+ * display per item. Schema-driven mode is untouched (JSON Schema has no radix
71
+ * vocabulary).
72
+ */
73
+ function annotateRadix(node, schema) {
74
+ switch (schema.kind) {
75
+ case 'leaf': {
76
+ const radix = scalarRadix(node);
77
+ if (radix && !schema.radix)
78
+ schema.radix = radix;
79
+ return;
80
+ }
81
+ case 'leafList': {
82
+ if (!(0, yaml_1.isSeq)(node) || schema.radix)
83
+ return;
84
+ const radixes = node.items.map(scalarRadix);
85
+ if (radixes[0] && radixes.every((r) => r === radixes[0]))
86
+ schema.radix = radixes[0];
87
+ return;
88
+ }
89
+ case 'nodeGroup': {
90
+ if (!(0, yaml_1.isMap)(node))
91
+ return;
92
+ for (const pair of node.items) {
93
+ const child = schema.children[pairKey(pair.key)];
94
+ if (child)
95
+ annotateRadix(pair.value, child);
96
+ }
97
+ return;
98
+ }
99
+ case 'nodeGroupList': {
100
+ if (!(0, yaml_1.isSeq)(node))
101
+ return;
102
+ for (const item of node.items)
103
+ annotateRadix(item, schema.type);
104
+ return;
105
+ }
106
+ default:
107
+ return; // choice/map never come out of plain-data inference
108
+ }
109
+ }
110
+ /** The display radix of a non-decimal integer scalar node, else undefined. */
111
+ function scalarRadix(node) {
112
+ if (!(0, yaml_1.isScalar)(node))
113
+ return undefined;
114
+ if (typeof node.value !== 'number' && typeof node.value !== 'bigint')
115
+ return undefined;
116
+ return RADIX_BY_FORMAT[node.format ?? ''];
117
+ }
118
+ /** The string a key node takes as a JS object key, matching `doc.toJS()`. */
119
+ function pairKey(key) {
120
+ const v = (0, yaml_1.isScalar)(key) ? key.value : key;
121
+ return v == null ? '' : String(v);
122
+ }
44
123
  /**
45
124
  * Replace every BigInt from an `intAsBigInt` parse with a form-value scalar: a
46
125
  * plain `number` when it fits the safe range, otherwise its decimal string (so
@@ -52,7 +131,8 @@ function normalizeBigInts(value) {
52
131
  if (Array.isArray(value))
53
132
  return value.map(normalizeBigInts);
54
133
  if (value && typeof value === 'object') {
55
- const out = {};
134
+ // Null prototype: a `__proto__` document key must stay a data key.
135
+ const out = Object.create(null);
56
136
  for (const key of Object.keys(value))
57
137
  out[key] = normalizeBigInts(value[key]);
58
138
  return out;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry-transformers",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
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",