ng-form-foundry-transformers 0.2.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.
- package/LICENSE +201 -0
- package/README.md +186 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +19 -0
- package/dist/core/infer.d.ts +15 -0
- package/dist/core/infer.js +67 -0
- package/dist/core/json-schema.d.ts +26 -0
- package/dist/core/json-schema.js +87 -0
- package/dist/core/registry.d.ts +18 -0
- package/dist/core/registry.js +40 -0
- package/dist/core/schema.d.ts +69 -0
- package/dist/core/schema.js +15 -0
- package/dist/core/transformer.d.ts +48 -0
- package/dist/core/transformer.js +2 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +40 -0
- package/dist/transformers/json/index.d.ts +8 -0
- package/dist/transformers/json/index.js +11 -0
- package/dist/transformers/json/json-transformer.d.ts +38 -0
- package/dist/transformers/json/json-transformer.js +41 -0
- package/dist/transformers/yaml/index.d.ts +11 -0
- package/dist/transformers/yaml/index.js +15 -0
- package/dist/transformers/yaml/revert.d.ts +13 -0
- package/dist/transformers/yaml/revert.js +74 -0
- package/dist/transformers/yaml/yaml-transformer.d.ts +32 -0
- package/dist/transformers/yaml/yaml-transformer.js +37 -0
- package/dist/transformers/yang/adapter.d.ts +40 -0
- package/dist/transformers/yang/adapter.js +68 -0
- package/dist/transformers/yang/cache.d.ts +18 -0
- package/dist/transformers/yang/cache.js +19 -0
- package/dist/transformers/yang/engine.d.ts +41 -0
- package/dist/transformers/yang/engine.js +2 -0
- package/dist/transformers/yang/engines/fake-engine.d.ts +15 -0
- package/dist/transformers/yang/engines/fake-engine.js +26 -0
- package/dist/transformers/yang/engines/subprocess-engine.d.ts +30 -0
- package/dist/transformers/yang/engines/subprocess-engine.js +64 -0
- package/dist/transformers/yang/index.d.ts +20 -0
- package/dist/transformers/yang/index.js +27 -0
- package/dist/transformers/yang/mapper.d.ts +14 -0
- package/dist/transformers/yang/mapper.js +96 -0
- package/dist/transformers/yang/model.d.ts +111 -0
- package/dist/transformers/yang/model.js +13 -0
- package/dist/transformers/yang/revert.d.ts +6 -0
- package/dist/transformers/yang/revert.js +173 -0
- package/dist/transformers/yang/rfc7951.d.ts +38 -0
- package/dist/transformers/yang/rfc7951.js +80 -0
- package/dist/transformers/yang/yang-transformer.d.ts +16 -0
- package/dist/transformers/yang/yang-transformer.js +29 -0
- package/package.json +55 -0
- package/python/emit_effective_tree.py +227 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransformerRegistry = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A lookup of {@link Transformer}s by their `id`. Lets a consumer pick a format
|
|
6
|
+
* at runtime (`registry.get('yaml')`) instead of importing each transformer
|
|
7
|
+
* directly. Direct imports remain available and tree-shakeable for consumers
|
|
8
|
+
* that only need one format.
|
|
9
|
+
*/
|
|
10
|
+
class TransformerRegistry {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.byId = new Map();
|
|
13
|
+
}
|
|
14
|
+
/** Register a transformer under its `id`. Throws if the `id` is already taken. */
|
|
15
|
+
register(transformer) {
|
|
16
|
+
if (this.byId.has(transformer.id)) {
|
|
17
|
+
throw new Error(`transformer '${transformer.id}' is already registered`);
|
|
18
|
+
}
|
|
19
|
+
this.byId.set(transformer.id, transformer);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
/** Get a transformer by `id`, or `undefined` if none is registered. */
|
|
23
|
+
get(id) {
|
|
24
|
+
return this.byId.get(id);
|
|
25
|
+
}
|
|
26
|
+
/** Get a transformer by `id`, throwing a listing of known ids if absent. */
|
|
27
|
+
require(id) {
|
|
28
|
+
const t = this.byId.get(id);
|
|
29
|
+
if (!t) {
|
|
30
|
+
const known = [...this.byId.keys()].join(', ') || '(none)';
|
|
31
|
+
throw new Error(`no transformer registered for '${id}'. Registered: ${known}`);
|
|
32
|
+
}
|
|
33
|
+
return t;
|
|
34
|
+
}
|
|
35
|
+
/** The ids of all registered transformers. */
|
|
36
|
+
ids() {
|
|
37
|
+
return [...this.byId.keys()];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.TransformerRegistry = TransformerRegistry;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ng-form-foundry schema DTO — the JSON contract the Angular app renders.
|
|
3
|
+
*
|
|
4
|
+
* These are a framework-free structural mirror of ng-form-foundry's `NodeGroup`
|
|
5
|
+
* model: the same JSON shape, without the `@angular/forms` imports that the
|
|
6
|
+
* frontend package couples to its type-level `FormControl` inference. The
|
|
7
|
+
* adapter only produces schema *data*, so it needs the plain shapes; the
|
|
8
|
+
* frontend re-derives its typed `FormGroup` from the same JSON via
|
|
9
|
+
* `buildFormFromSchema`.
|
|
10
|
+
*/
|
|
11
|
+
export type LeafType = 'string' | 'number' | 'boolean' | 'enum';
|
|
12
|
+
export interface Leaf {
|
|
13
|
+
kind: 'leaf';
|
|
14
|
+
name: string;
|
|
15
|
+
type: LeafType;
|
|
16
|
+
required?: true;
|
|
17
|
+
label?: string;
|
|
18
|
+
default?: string | number | boolean;
|
|
19
|
+
/** Present when `type` is `'enum'`. */
|
|
20
|
+
enum?: (string | number)[];
|
|
21
|
+
}
|
|
22
|
+
export interface LeafList {
|
|
23
|
+
kind: 'leafList';
|
|
24
|
+
name: string;
|
|
25
|
+
type: LeafType;
|
|
26
|
+
label?: string;
|
|
27
|
+
minItems?: number;
|
|
28
|
+
maxItems?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface NodeGroup {
|
|
31
|
+
kind: 'nodeGroup';
|
|
32
|
+
name: string;
|
|
33
|
+
label?: string;
|
|
34
|
+
root?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* When true, the group is optional and rendered with an on/off toggle: absent
|
|
37
|
+
* unless the user enables it. Maps a YANG presence container — present-but-empty
|
|
38
|
+
* serializes as `{}`, absent is omitted.
|
|
39
|
+
*/
|
|
40
|
+
presence?: boolean;
|
|
41
|
+
children: Record<string, NodeType>;
|
|
42
|
+
}
|
|
43
|
+
export interface NodeGroupList {
|
|
44
|
+
kind: 'nodeGroupList';
|
|
45
|
+
name: string;
|
|
46
|
+
label?: string;
|
|
47
|
+
type: NodeGroup;
|
|
48
|
+
minItems?: number;
|
|
49
|
+
maxItems?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A discriminated selection: the user picks one `case`, and only that case's
|
|
53
|
+
* fields are present. In the form value a choice is an object
|
|
54
|
+
* `{ __case: <caseName>, ...that case's fields }`; the adapter flattens it to the
|
|
55
|
+
* inline YANG encoding on write-back.
|
|
56
|
+
*/
|
|
57
|
+
export interface Choice {
|
|
58
|
+
kind: 'choice';
|
|
59
|
+
name: string;
|
|
60
|
+
label?: string;
|
|
61
|
+
cases: Record<string, Record<string, NodeType>>;
|
|
62
|
+
default?: string;
|
|
63
|
+
mandatory?: boolean;
|
|
64
|
+
}
|
|
65
|
+
export type NodeType = Leaf | LeafList | NodeGroup | NodeGroupList | Choice;
|
|
66
|
+
/** The form-value key that records which case of a {@link Choice} is active. */
|
|
67
|
+
export declare const CASE_KEY = "__case";
|
|
68
|
+
/** A plain, nested value object produced/consumed by a rendered form. */
|
|
69
|
+
export type FormValue = Record<string, unknown>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The ng-form-foundry schema DTO — the JSON contract the Angular app renders.
|
|
4
|
+
*
|
|
5
|
+
* These are a framework-free structural mirror of ng-form-foundry's `NodeGroup`
|
|
6
|
+
* model: the same JSON shape, without the `@angular/forms` imports that the
|
|
7
|
+
* frontend package couples to its type-level `FormControl` inference. The
|
|
8
|
+
* adapter only produces schema *data*, so it needs the plain shapes; the
|
|
9
|
+
* frontend re-derives its typed `FormGroup` from the same JSON via
|
|
10
|
+
* `buildFormFromSchema`.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CASE_KEY = void 0;
|
|
14
|
+
/** The form-value key that records which case of a {@link Choice} is active. */
|
|
15
|
+
exports.CASE_KEY = '__case';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { NodeGroup, FormValue } from './schema';
|
|
2
|
+
/**
|
|
3
|
+
* A transformer's revert context: whatever a transformer needs to turn an edited
|
|
4
|
+
* {@link FormValue} back into its source format. Opaque to the catalog — each
|
|
5
|
+
* transformer defines and consumes its own concrete shape (the YANG transformer
|
|
6
|
+
* keeps the effective model, the YAML transformer keeps the parsed document and
|
|
7
|
+
* inferred type map). Kept alongside the schema, not sent to the form UI.
|
|
8
|
+
*/
|
|
9
|
+
export type BindingMap = unknown;
|
|
10
|
+
/** The output of {@link Transformer.toSchema}: the form schema plus its revert context. */
|
|
11
|
+
export interface TransformResult<TBinding = BindingMap> {
|
|
12
|
+
/** The ng-form-foundry schema to build a form from. */
|
|
13
|
+
schema: NodeGroup;
|
|
14
|
+
/** Revert context, passed back to {@link Transformer.toSource}. */
|
|
15
|
+
binding: TBinding;
|
|
16
|
+
/** Initial form value extracted from the source, if the source carried data. */
|
|
17
|
+
initialValue?: FormValue;
|
|
18
|
+
}
|
|
19
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Maps one source format to and from the ng-form-foundry schema. YANG, JSON
|
|
22
|
+
* Schema, and plain YAML/JSON config each implement this so a single catalog can
|
|
23
|
+
* turn any of them into an editable form and write the edited value back.
|
|
24
|
+
*
|
|
25
|
+
* `toSchema`/`toSource` may be sync or async — YANG compiles through an external
|
|
26
|
+
* engine (async), while YAML/JSON parse in-process (sync). Register instances in
|
|
27
|
+
* the {@link import('./registry').TransformerRegistry} to look them up by `id`.
|
|
28
|
+
*
|
|
29
|
+
* For **config formats** (YAML, JSON) the thing you load and the thing you save
|
|
30
|
+
* are the same document type, so `TData` defaults to `TSource`. For **schema
|
|
31
|
+
* formats** they differ: YANG's `toSchema` consumes a model source, but its
|
|
32
|
+
* `toSource` reverts to RFC 7951 instance *data* — a different type — so YANG
|
|
33
|
+
* sets `TData` explicitly.
|
|
34
|
+
*
|
|
35
|
+
* @typeParam TSource what `toSchema` consumes (YAML text, a YANG source set…)
|
|
36
|
+
* @typeParam TData what `toSource` produces (defaults to `TSource`)
|
|
37
|
+
* @typeParam TBinding the revert context shape this transformer round-trips
|
|
38
|
+
* @typeParam TOptions per-call options (e.g. an optional JSON Schema for YAML)
|
|
39
|
+
*/
|
|
40
|
+
export interface Transformer<TSource = unknown, TData = TSource, TBinding = BindingMap, TOptions = void> {
|
|
41
|
+
/** Stable catalog key, e.g. `'yang'`, `'yaml'`, `'json-schema'`. */
|
|
42
|
+
readonly id: string;
|
|
43
|
+
/** Turn a source document into a form schema (+ revert context and any initial value). */
|
|
44
|
+
toSchema(source: TSource, options?: TOptions): MaybePromise<TransformResult<TBinding>>;
|
|
45
|
+
/** Turn an edited form value back into the source data, using the binding from `toSchema`. */
|
|
46
|
+
toSource(value: FormValue, binding: TBinding): MaybePromise<TData>;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ng-form-foundry-transformers — a catalog of source-format transformers that
|
|
3
|
+
* turn a model or config into an ng-form-foundry schema and revert the edited
|
|
4
|
+
* form value back to the source format.
|
|
5
|
+
*
|
|
6
|
+
* The {@link Transformer} contract is the common seam; each format lives under
|
|
7
|
+
* its own entry point and can be imported on its own (tree-shakeable):
|
|
8
|
+
*
|
|
9
|
+
* - `yang` — YANG model → form → RFC 7951 data ({@link createYangTransformer},
|
|
10
|
+
* {@link YangFormAdapter}).
|
|
11
|
+
* - `yaml` — YAML config → form → YAML ({@link yamlTransformer}); JSON-Schema
|
|
12
|
+
* driven or inferred from the data, comment-preserving on revert.
|
|
13
|
+
* - `json` — JSON config → form → JSON ({@link jsonTransformer}); same builders
|
|
14
|
+
* as `yaml`, indent preserved.
|
|
15
|
+
*
|
|
16
|
+
* Look transformers up at runtime through {@link TransformerRegistry}, or import
|
|
17
|
+
* the one you need directly.
|
|
18
|
+
*/
|
|
19
|
+
export * from './core';
|
|
20
|
+
export * from './transformers/yang';
|
|
21
|
+
export * from './transformers/yaml';
|
|
22
|
+
export * from './transformers/json';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ng-form-foundry-transformers — a catalog of source-format transformers that
|
|
4
|
+
* turn a model or config into an ng-form-foundry schema and revert the edited
|
|
5
|
+
* form value back to the source format.
|
|
6
|
+
*
|
|
7
|
+
* The {@link Transformer} contract is the common seam; each format lives under
|
|
8
|
+
* its own entry point and can be imported on its own (tree-shakeable):
|
|
9
|
+
*
|
|
10
|
+
* - `yang` — YANG model → form → RFC 7951 data ({@link createYangTransformer},
|
|
11
|
+
* {@link YangFormAdapter}).
|
|
12
|
+
* - `yaml` — YAML config → form → YAML ({@link yamlTransformer}); JSON-Schema
|
|
13
|
+
* driven or inferred from the data, comment-preserving on revert.
|
|
14
|
+
* - `json` — JSON config → form → JSON ({@link jsonTransformer}); same builders
|
|
15
|
+
* as `yaml`, indent preserved.
|
|
16
|
+
*
|
|
17
|
+
* Look transformers up at runtime through {@link TransformerRegistry}, or import
|
|
18
|
+
* the one you need directly.
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
32
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
// Core: the Transformer contract, the registry, shared schema types and builders.
|
|
36
|
+
__exportStar(require("./core"), exports);
|
|
37
|
+
// Transformers.
|
|
38
|
+
__exportStar(require("./transformers/yang"), exports);
|
|
39
|
+
__exportStar(require("./transformers/yaml"), exports);
|
|
40
|
+
__exportStar(require("./transformers/json"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON transformer — turn a JSON config document into an ng-form-foundry schema
|
|
3
|
+
* and write the edited value back to JSON. Drive the form from a JSON Schema when
|
|
4
|
+
* one is supplied, otherwise infer it from the data. Reuses the format-agnostic
|
|
5
|
+
* schema builders in `core`.
|
|
6
|
+
*/
|
|
7
|
+
export { jsonTransformer } from './json-transformer';
|
|
8
|
+
export type { JsonOptions, JsonFormat } from './json-transformer';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JSON transformer — turn a JSON config document into an ng-form-foundry schema
|
|
4
|
+
* and write the edited value back to JSON. Drive the form from a JSON Schema when
|
|
5
|
+
* one is supplied, otherwise infer it from the data. Reuses the format-agnostic
|
|
6
|
+
* schema builders in `core`.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.jsonTransformer = void 0;
|
|
10
|
+
var json_transformer_1 = require("./json-transformer");
|
|
11
|
+
Object.defineProperty(exports, "jsonTransformer", { enumerable: true, get: function () { return json_transformer_1.jsonTransformer; } });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FormValue } from '../../core/schema';
|
|
2
|
+
import type { TransformResult } from '../../core/transformer';
|
|
3
|
+
import { type JsonSchema } from '../../core/json-schema';
|
|
4
|
+
/** Options for {@link jsonTransformer}'s `toSchema`. */
|
|
5
|
+
export interface JsonOptions {
|
|
6
|
+
/**
|
|
7
|
+
* A JSON Schema describing the config. When given, the form is built from it;
|
|
8
|
+
* when omitted, the form is inferred from the JSON data itself.
|
|
9
|
+
*/
|
|
10
|
+
schema?: JsonSchema;
|
|
11
|
+
/** Name for the root node group. Defaults to `__root__`. */
|
|
12
|
+
rootName?: string;
|
|
13
|
+
}
|
|
14
|
+
/** How the source was formatted, so `toSource` re-emits it the same way. */
|
|
15
|
+
export interface JsonFormat {
|
|
16
|
+
/** Indent width in spaces (detected from the source; defaults to 2). */
|
|
17
|
+
indent: number;
|
|
18
|
+
/** Whether the source ended with a trailing newline. */
|
|
19
|
+
trailingNewline: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A JSON {@link Transformer}: turn a JSON config document into a form and write
|
|
23
|
+
* the edited value back to JSON. The form is built by the same format-agnostic
|
|
24
|
+
* `core` builders the YAML transformer uses — only parsing (`JSON.parse`) and
|
|
25
|
+
* serialization (`JSON.stringify`) are JSON-specific.
|
|
26
|
+
*
|
|
27
|
+
* Standard JSON has no comments, so revert re-serializes the edited value,
|
|
28
|
+
* preserving the source's indent width and trailing newline. Key order follows
|
|
29
|
+
* the form value (which follows the schema, derived from the original), so it is
|
|
30
|
+
* preserved for untouched keys. For JSON **with comments** (JSONC), parse and
|
|
31
|
+
* edit it as YAML with {@link import('../yaml/yaml-transformer').yamlTransformer}
|
|
32
|
+
* instead — `JSON.parse` rejects comments.
|
|
33
|
+
*/
|
|
34
|
+
export declare const jsonTransformer: {
|
|
35
|
+
id: string;
|
|
36
|
+
toSchema(source: string, options?: JsonOptions): TransformResult<JsonFormat>;
|
|
37
|
+
toSource(value: FormValue, binding: JsonFormat): string;
|
|
38
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jsonTransformer = void 0;
|
|
4
|
+
const infer_1 = require("../../core/infer");
|
|
5
|
+
const json_schema_1 = require("../../core/json-schema");
|
|
6
|
+
/**
|
|
7
|
+
* A JSON {@link Transformer}: turn a JSON config document into a form and write
|
|
8
|
+
* the edited value back to JSON. The form is built by the same format-agnostic
|
|
9
|
+
* `core` builders the YAML transformer uses — only parsing (`JSON.parse`) and
|
|
10
|
+
* serialization (`JSON.stringify`) are JSON-specific.
|
|
11
|
+
*
|
|
12
|
+
* Standard JSON has no comments, so revert re-serializes the edited value,
|
|
13
|
+
* preserving the source's indent width and trailing newline. Key order follows
|
|
14
|
+
* the form value (which follows the schema, derived from the original), so it is
|
|
15
|
+
* preserved for untouched keys. For JSON **with comments** (JSONC), parse and
|
|
16
|
+
* edit it as YAML with {@link import('../yaml/yaml-transformer').yamlTransformer}
|
|
17
|
+
* instead — `JSON.parse` rejects comments.
|
|
18
|
+
*/
|
|
19
|
+
exports.jsonTransformer = {
|
|
20
|
+
id: 'json',
|
|
21
|
+
toSchema(source, options) {
|
|
22
|
+
const data = (JSON.parse(source) ?? {});
|
|
23
|
+
const schema = options?.schema
|
|
24
|
+
? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName)
|
|
25
|
+
: (0, infer_1.inferNodeGroup)(data, options?.rootName);
|
|
26
|
+
const binding = {
|
|
27
|
+
indent: detectIndent(source),
|
|
28
|
+
trailingNewline: source.endsWith('\n'),
|
|
29
|
+
};
|
|
30
|
+
return { schema, binding, initialValue: data };
|
|
31
|
+
},
|
|
32
|
+
toSource(value, binding) {
|
|
33
|
+
const text = JSON.stringify(value, null, binding.indent);
|
|
34
|
+
return binding.trailingNewline ? text + '\n' : text;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/** Indent width (in spaces) of the first indented line, or 2 if none/tabs. */
|
|
38
|
+
function detectIndent(source) {
|
|
39
|
+
const match = source.match(/\n( +)\S/);
|
|
40
|
+
return match && match[1] ? match[1].length : 2;
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML transformer — turn a YAML config document into an ng-form-foundry schema
|
|
3
|
+
* and write the edited value back to YAML with comments preserved. Drive the form
|
|
4
|
+
* from a JSON Schema when one is supplied, otherwise infer it from the data.
|
|
5
|
+
*
|
|
6
|
+
* The schema builders it uses (`inferNodeGroup`, `jsonSchemaToNodeGroup`) live in
|
|
7
|
+
* `core` — they are format-agnostic and shared with the JSON transformer.
|
|
8
|
+
*/
|
|
9
|
+
export { yamlTransformer } from './yaml-transformer';
|
|
10
|
+
export type { YamlOptions } from './yaml-transformer';
|
|
11
|
+
export { applyValueToDocument } from './revert';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* YAML transformer — turn a YAML config document into an ng-form-foundry schema
|
|
4
|
+
* and write the edited value back to YAML with comments preserved. Drive the form
|
|
5
|
+
* from a JSON Schema when one is supplied, otherwise infer it from the data.
|
|
6
|
+
*
|
|
7
|
+
* The schema builders it uses (`inferNodeGroup`, `jsonSchemaToNodeGroup`) live in
|
|
8
|
+
* `core` — they are format-agnostic and shared with the JSON transformer.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.applyValueToDocument = exports.yamlTransformer = void 0;
|
|
12
|
+
var yaml_transformer_1 = require("./yaml-transformer");
|
|
13
|
+
Object.defineProperty(exports, "yamlTransformer", { enumerable: true, get: function () { return yaml_transformer_1.yamlTransformer; } });
|
|
14
|
+
var revert_1 = require("./revert");
|
|
15
|
+
Object.defineProperty(exports, "applyValueToDocument", { enumerable: true, get: function () { return revert_1.applyValueToDocument; } });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Document } from 'yaml';
|
|
2
|
+
/**
|
|
3
|
+
* Apply an edited form value onto a parsed YAML {@link Document} in place,
|
|
4
|
+
* preserving comments and formatting on every node that survives the edit.
|
|
5
|
+
*
|
|
6
|
+
* Existing scalars are mutated (`node.value = …`), which keeps their inline and
|
|
7
|
+
* leading comments; only genuinely new keys/items create fresh (comment-less)
|
|
8
|
+
* nodes, and keys/items dropped from the value are deleted along with their
|
|
9
|
+
* comments. Counterpart of the schema/inference in {@link import('./infer')} and
|
|
10
|
+
* {@link import('./json-schema')}. Callers should clone the document first if the
|
|
11
|
+
* original must be preserved.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyValueToDocument(doc: Document, value: unknown): void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyValueToDocument = applyValueToDocument;
|
|
4
|
+
const yaml_1 = require("yaml");
|
|
5
|
+
/**
|
|
6
|
+
* Apply an edited form value onto a parsed YAML {@link Document} in place,
|
|
7
|
+
* preserving comments and formatting on every node that survives the edit.
|
|
8
|
+
*
|
|
9
|
+
* Existing scalars are mutated (`node.value = …`), which keeps their inline and
|
|
10
|
+
* leading comments; only genuinely new keys/items create fresh (comment-less)
|
|
11
|
+
* nodes, and keys/items dropped from the value are deleted along with their
|
|
12
|
+
* comments. Counterpart of the schema/inference in {@link import('./infer')} and
|
|
13
|
+
* {@link import('./json-schema')}. Callers should clone the document first if the
|
|
14
|
+
* original must be preserved.
|
|
15
|
+
*/
|
|
16
|
+
function applyValueToDocument(doc, value) {
|
|
17
|
+
if (doc.contents == null) {
|
|
18
|
+
doc.contents = doc.createNode(value);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
applyAt(doc, [], value);
|
|
22
|
+
}
|
|
23
|
+
function applyAt(doc, path, value) {
|
|
24
|
+
const node = nodeAt(doc, path);
|
|
25
|
+
if (isPlainObject(value)) {
|
|
26
|
+
if (!(0, yaml_1.isMap)(node))
|
|
27
|
+
return replaceAt(doc, path, value);
|
|
28
|
+
const keys = new Set(Object.keys(value));
|
|
29
|
+
for (const key of mapKeyStrings(node)) {
|
|
30
|
+
if (!keys.has(key))
|
|
31
|
+
node.delete(key);
|
|
32
|
+
}
|
|
33
|
+
for (const key of keys)
|
|
34
|
+
applyAt(doc, [...path, key], value[key]);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
if (!(0, yaml_1.isSeq)(node))
|
|
39
|
+
return replaceAt(doc, path, value);
|
|
40
|
+
while (node.items.length > value.length)
|
|
41
|
+
node.items.pop();
|
|
42
|
+
for (let i = 0; i < value.length; i++) {
|
|
43
|
+
if (i < node.items.length)
|
|
44
|
+
applyAt(doc, [...path, i], value[i]);
|
|
45
|
+
else
|
|
46
|
+
node.items.push(doc.createNode(value[i]));
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// scalar (string / number / boolean / null)
|
|
51
|
+
if ((0, yaml_1.isScalar)(node))
|
|
52
|
+
node.value = value;
|
|
53
|
+
else
|
|
54
|
+
replaceAt(doc, path, value);
|
|
55
|
+
}
|
|
56
|
+
/** Set the whole subtree at `path` to a fresh node (used when the shape changed). */
|
|
57
|
+
function replaceAt(doc, path, value) {
|
|
58
|
+
if (path.length === 0) {
|
|
59
|
+
doc.contents = doc.createNode(value);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
doc.setIn(path, doc.createNode(value));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function nodeAt(doc, path) {
|
|
66
|
+
return path.length === 0 ? doc.contents : doc.getIn(path, true);
|
|
67
|
+
}
|
|
68
|
+
/** The string keys currently present in a YAML map node. */
|
|
69
|
+
function mapKeyStrings(node) {
|
|
70
|
+
return node.items.map((pair) => ((0, yaml_1.isScalar)(pair.key) ? String(pair.key.value) : String(pair.key)));
|
|
71
|
+
}
|
|
72
|
+
function isPlainObject(v) {
|
|
73
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
74
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Document } from 'yaml';
|
|
2
|
+
import type { FormValue } from '../../core/schema';
|
|
3
|
+
import type { TransformResult } from '../../core/transformer';
|
|
4
|
+
import { type JsonSchema } from '../../core/json-schema';
|
|
5
|
+
/** Options for {@link yamlTransformer}'s `toSchema`. */
|
|
6
|
+
export interface YamlOptions {
|
|
7
|
+
/**
|
|
8
|
+
* A JSON Schema describing the config. When given, the form is built from it
|
|
9
|
+
* (types, required, enums, nested shape); when omitted, the form is inferred
|
|
10
|
+
* from the YAML data itself (structure + value types).
|
|
11
|
+
*/
|
|
12
|
+
schema?: JsonSchema;
|
|
13
|
+
/** Name for the root node group. Defaults to `__root__`. */
|
|
14
|
+
rootName?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A YAML {@link Transformer}: turn a YAML config document into a form and write
|
|
18
|
+
* the edited value back to YAML. Built for **editing config files** — the revert
|
|
19
|
+
* preserves comments, key order, and formatting by applying edits onto the parsed
|
|
20
|
+
* document (see {@link applyValueToDocument}).
|
|
21
|
+
*
|
|
22
|
+
* The `binding` it round-trips is the parsed {@link Document}; `toSource` clones
|
|
23
|
+
* it before applying, so a single `toSchema` result can serve many edits.
|
|
24
|
+
*
|
|
25
|
+
* With a JSON Schema in {@link YamlOptions}, the form is schema-driven; without
|
|
26
|
+
* one it is inferred from the data.
|
|
27
|
+
*/
|
|
28
|
+
export declare const yamlTransformer: {
|
|
29
|
+
id: string;
|
|
30
|
+
toSchema(source: string, options?: YamlOptions): TransformResult<Document>;
|
|
31
|
+
toSource(value: FormValue, binding: Document): string;
|
|
32
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.yamlTransformer = void 0;
|
|
4
|
+
const yaml_1 = require("yaml");
|
|
5
|
+
const infer_1 = require("../../core/infer");
|
|
6
|
+
const json_schema_1 = require("../../core/json-schema");
|
|
7
|
+
const revert_1 = require("./revert");
|
|
8
|
+
/**
|
|
9
|
+
* A YAML {@link Transformer}: turn a YAML config document into a form and write
|
|
10
|
+
* the edited value back to YAML. Built for **editing config files** — the revert
|
|
11
|
+
* preserves comments, key order, and formatting by applying edits onto the parsed
|
|
12
|
+
* document (see {@link applyValueToDocument}).
|
|
13
|
+
*
|
|
14
|
+
* The `binding` it round-trips is the parsed {@link Document}; `toSource` clones
|
|
15
|
+
* it before applying, so a single `toSchema` result can serve many edits.
|
|
16
|
+
*
|
|
17
|
+
* With a JSON Schema in {@link YamlOptions}, the form is schema-driven; without
|
|
18
|
+
* one it is inferred from the data.
|
|
19
|
+
*/
|
|
20
|
+
exports.yamlTransformer = {
|
|
21
|
+
id: 'yaml',
|
|
22
|
+
toSchema(source, options) {
|
|
23
|
+
const doc = (0, yaml_1.parseDocument)(source);
|
|
24
|
+
const data = (doc.toJS() ?? {});
|
|
25
|
+
const schema = options?.schema
|
|
26
|
+
? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName)
|
|
27
|
+
: (0, infer_1.inferNodeGroup)(data, options?.rootName);
|
|
28
|
+
return { schema, binding: doc, initialValue: data };
|
|
29
|
+
},
|
|
30
|
+
toSource(value, binding) {
|
|
31
|
+
const doc = binding.clone();
|
|
32
|
+
(0, revert_1.applyValueToDocument)(doc, value);
|
|
33
|
+
return String(doc);
|
|
34
|
+
},
|
|
35
|
+
// `satisfies` verifies conformance to the catalog contract while keeping the
|
|
36
|
+
// concrete sync return types, so direct callers need no `await`.
|
|
37
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CompileRequest, YangEngine } from './engine';
|
|
2
|
+
import { ArtifactCache } from './cache';
|
|
3
|
+
import { CompiledModel } from './model';
|
|
4
|
+
import { FormValue, NodeGroup } from '../../core/schema';
|
|
5
|
+
export interface CompileOptions extends CompileRequest {
|
|
6
|
+
/** Cache key for the compiled model — typically a hash of the yang-library. */
|
|
7
|
+
modelId: string;
|
|
8
|
+
}
|
|
9
|
+
/** Thrown by {@link YangFormAdapter.toYangData} when the engine rejects the reverted data. */
|
|
10
|
+
export declare class YangValidationError extends Error {
|
|
11
|
+
readonly errors: string[];
|
|
12
|
+
constructor(errors: string[]);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The framework-agnostic core.
|
|
16
|
+
*
|
|
17
|
+
* Resolves a YANG model into a compiled `{ schema, binding }` (caching by
|
|
18
|
+
* `modelId`), serves the frontend `NodeGroup` schema, and round-trips instance
|
|
19
|
+
* data through the plain form value. The `binding` (resolved YANG model) never
|
|
20
|
+
* leaves the adapter; only the `schema` is meant for the client.
|
|
21
|
+
*
|
|
22
|
+
* Construct it directly (`new YangFormAdapter(engine, cache)`) from any Node
|
|
23
|
+
* runtime — Express, a worker, a CLI — or wrap it in a framework provider. It
|
|
24
|
+
* imports nothing framework-specific; all environment access goes through the
|
|
25
|
+
* injected {@link YangEngine} and {@link ArtifactCache}.
|
|
26
|
+
*/
|
|
27
|
+
export declare class YangFormAdapter {
|
|
28
|
+
private readonly engine;
|
|
29
|
+
private readonly cache;
|
|
30
|
+
constructor(engine: YangEngine, cache?: ArtifactCache);
|
|
31
|
+
/** Resolve and map a model, or return the cached result for `modelId`. */
|
|
32
|
+
compile(opts: CompileOptions): Promise<CompiledModel>;
|
|
33
|
+
/** The `NodeGroup` schema DTO to hand the Angular app. Excludes the binding. */
|
|
34
|
+
getFormSchema(modelId: string): Promise<NodeGroup>;
|
|
35
|
+
/** RFC 7951 instance data (e.g. a device GET) → a plain form value to edit. */
|
|
36
|
+
toFormValue(rfc7951: unknown, modelId: string): Promise<FormValue>;
|
|
37
|
+
/** An edited form value → RFC 7951 data, validated by the engine, ready for write-back. */
|
|
38
|
+
toYangData(value: FormValue, modelId: string): Promise<Record<string, unknown>>;
|
|
39
|
+
private require;
|
|
40
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.YangFormAdapter = exports.YangValidationError = void 0;
|
|
4
|
+
const cache_1 = require("./cache");
|
|
5
|
+
const mapper_1 = require("./mapper");
|
|
6
|
+
const revert_1 = require("./revert");
|
|
7
|
+
/** Thrown by {@link YangFormAdapter.toYangData} when the engine rejects the reverted data. */
|
|
8
|
+
class YangValidationError extends Error {
|
|
9
|
+
constructor(errors) {
|
|
10
|
+
super(`RFC 7951 validation failed:\n ${errors.join('\n ')}`);
|
|
11
|
+
this.errors = errors;
|
|
12
|
+
this.name = 'YangValidationError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.YangValidationError = YangValidationError;
|
|
16
|
+
/**
|
|
17
|
+
* The framework-agnostic core.
|
|
18
|
+
*
|
|
19
|
+
* Resolves a YANG model into a compiled `{ schema, binding }` (caching by
|
|
20
|
+
* `modelId`), serves the frontend `NodeGroup` schema, and round-trips instance
|
|
21
|
+
* data through the plain form value. The `binding` (resolved YANG model) never
|
|
22
|
+
* leaves the adapter; only the `schema` is meant for the client.
|
|
23
|
+
*
|
|
24
|
+
* Construct it directly (`new YangFormAdapter(engine, cache)`) from any Node
|
|
25
|
+
* runtime — Express, a worker, a CLI — or wrap it in a framework provider. It
|
|
26
|
+
* imports nothing framework-specific; all environment access goes through the
|
|
27
|
+
* injected {@link YangEngine} and {@link ArtifactCache}.
|
|
28
|
+
*/
|
|
29
|
+
class YangFormAdapter {
|
|
30
|
+
constructor(engine, cache = new cache_1.InMemoryCache()) {
|
|
31
|
+
this.engine = engine;
|
|
32
|
+
this.cache = cache;
|
|
33
|
+
}
|
|
34
|
+
/** Resolve and map a model, or return the cached result for `modelId`. */
|
|
35
|
+
async compile(opts) {
|
|
36
|
+
const cached = await this.cache.get(opts.modelId);
|
|
37
|
+
if (cached)
|
|
38
|
+
return cached;
|
|
39
|
+
const binding = await this.engine.resolve(opts);
|
|
40
|
+
const model = { schema: (0, mapper_1.mapToSchema)(binding), binding };
|
|
41
|
+
await this.cache.set(opts.modelId, model);
|
|
42
|
+
return model;
|
|
43
|
+
}
|
|
44
|
+
/** The `NodeGroup` schema DTO to hand the Angular app. Excludes the binding. */
|
|
45
|
+
async getFormSchema(modelId) {
|
|
46
|
+
return (await this.require(modelId)).schema;
|
|
47
|
+
}
|
|
48
|
+
/** RFC 7951 instance data (e.g. a device GET) → a plain form value to edit. */
|
|
49
|
+
async toFormValue(rfc7951, modelId) {
|
|
50
|
+
return (0, revert_1.toFormValue)(rfc7951, (await this.require(modelId)).binding);
|
|
51
|
+
}
|
|
52
|
+
/** An edited form value → RFC 7951 data, validated by the engine, ready for write-back. */
|
|
53
|
+
async toYangData(value, modelId) {
|
|
54
|
+
const model = await this.require(modelId);
|
|
55
|
+
const data = (0, revert_1.toYangData)(value, model.binding);
|
|
56
|
+
const result = await this.engine.validate(data, model.binding);
|
|
57
|
+
if (!result.valid)
|
|
58
|
+
throw new YangValidationError(result.errors);
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
async require(modelId) {
|
|
62
|
+
const model = await this.cache.get(modelId);
|
|
63
|
+
if (!model)
|
|
64
|
+
throw new Error(`model '${modelId}' is not compiled — call compile() first`);
|
|
65
|
+
return model;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.YangFormAdapter = YangFormAdapter;
|