# Getting Started with yang-js This guide/tutorial will provide various examples around usage of `yang-js` library for working with YANG schemas and generated Models. It will get better soon... :-) ## Table of Contents - [Terminology](#terminology) - [Working with Yang Schema](#working-with-yang-schema) - [Composite Type](#composite-type) - [Schema Extension](#schema-extension) - [Schema Conversion](#schema-conversion) - [Schema Composition](#schema-composition) - [Schema Binding](#schema-binding) - [Working with Multiple Schemas](#working-with-multiple-schemas) - [Preload Dependency](#preload-dependency) - [Automatic Resolution](#automatic-resolution) - [External Package Bundle](#external-package-bundle) - [Internal Package Bunlde](#internal-package-bundle) - [Working with Models](#working-with-models) - [Model Events](#model-events) ## Terminology Here's a collection of commonly used terms and their definitions within this module and other related modules: - **Schema**: A descriptive resource that expresses the data hierarchy, constraints, and behavior of a data model - **Model**: An instance of Schema evaluated with data - **Component**: An implementation resource that associates control logic bindings to a Model ## Working with Yang Schema ### Composite Type A handy convention is to define/save the generated [Yang.eval](./src/yang.litcoffee#main-constructor) function as a **Composite Type** definition and re-use for creating multiple adaptive schema objects: ```coffeescript # coffeescript FooType = (Yang schema) foo1 = (FooType) { foo: a: 'apple' b: 10 } foo2 = (FooType) { foo: a: 'banana' b: 20 } ``` ### Schema Extension ```javascript // javascript var schema = Yang.parse('container foo { leaf a; }'); var model = schema.eval({ foo: { a: 'bar' } }); // try assigning a new arbitrary property model.foo.b = 'hello'; console.log(model.foo.b); // returns: 'hello' (without validation since not part of schema) ``` Here comes the magic: ```javascript // extend the previous container foo expression with an additional leaf schema.extends('leaf b { type uint8; }') model.foo.b = 'hello'; // throws error since not uint8 type ``` The [extends](./src/yang.litcoffee#extends-schema) mechanism provides interesting programmatic approach to *dynamically* modify a given `Yang` expression over time on a running system. ### Schema Conversion ```yang module foo { description "A Foo Example"; container bar { leaf a { type string; } leaf b { type uint8; } } } ``` Result of [parse](./src/yang.litcoffee#parse-schema) looks like: ```js { kind: 'module', tag: 'foo', description: { kind: 'description', tag: 'A Foo Example' }, container: [ { kind: 'container', tag: 'bar', leaf: [Object] } ] } ``` When the above `Yang` expression is converted [toJSON](./src/yang.litcoffee#tojson): ```js { module: { foo: { description: 'A Foo Example', container: { bar: { leaf: { a: [Object], b: [Object] } } } } } } ``` ### Schema Composition Below example demonstrates typical use: ```coffeescript # coffeescript Yang = require 'yang-js' schema = Yang.compose { bar: a: 'hello' b: 123 }, tag: 'foo' console.log schema.toString() ``` ```javascript // javascript const Yang = require('yang-js'); const schema = Yang.compose({ bar: { a: 'hello', b: 123, } }, { tag: 'foo' }) The output of [schema.toString()](./src/yang.litcoffee#tostring) looks as follows: ``` container foo { container bar { leaf a { type string; } leaf b { type number; } } } ``` Please note that [compose](../src/yang.litcoffee#compose-data) detected the top-level YANG construct to be a simple `container` instead of a `module`. It will only auto-detect as a `module` if any of the properties of the top-level object contains a `function` or the passed-in object itself is a `named function` with additional properties. Below example will auto-detect as `module` since a simple `container` cannot contain a *function* as one of its properties. ```coffeescript # coffeescript Yang = require 'yang-js' obj = bar: a: 'hello' b: 123 test: -> schema = Yang.compose obj, { tag: 'foo' } console.log schema.toString() ``` This is a very handy facility to dynamically discover YANG schema mapping for any arbitrary asset being used (even NPM modules) so that you can qualify/validate the target resource for schema compliance. You can also **override** the detected YANG construct as follows: ```coffeescript Yang = require 'yang-js' obj = bar: a: 'hello' b: 123 schema = Yang.compose obj, { tag: 'foo', kind: 'module' } console.log schema.toString() ``` When you *manually* alter the `Yang` expression instance, it will internally trigger a check for scope validation and reject if the the change will render the current schema invalid. Basically, you can't simply change a `container` that contains other elements into a `leaf` or any other arbitrary kind. ### Schema Binding ```coffeescript # coffeescript Yang = require 'yang-js' schema = """ module foo { feature hello; container bar { leaf readonly { config false; type boolean; } } rpc test; } """ schema = Yang.parse(schema).bind { 'feature(hello)': -> true # provide some capability '/foo:bar/readonly': -> true '/test': -> 'success' } ``` ```javascript // javascript const Yang = require('yang-js'); let schema = ` module foo { feature hello; container bar { leaf readonly { config false; type boolean; } } rpc test; } ` schema = Yang.parse(schema).bind({ 'feature(hello)': () => true, '/foo:bar/readonly': () => true, '/test': () => 'success' }); ``` In the above example, a `key/value` object was passed-in to the [bind](./src/yang.litcoffee#bind-obj) method where the `key` is a string that will be mapped to a Yang Expression contained within the expression being bound. It accepts XPATH-like expression which will be used to locate the target expression within the schema. The `value` of the binding must be a JS function, otherwise it will be *silently* ignored. You can also [bind](./src/yang.litcoffee#bind-obj) a function directly to a given Yang Expression instance as follows: ```coffeescript Yang = require 'yang-js' schema = Yang.parse('rpc test;').bind -> @output = "ok" ``` Please note that calling [bind](./src/yang.litcoffee#bind-obj) more than once on a given [Yang](./src/yang.litcoffee) expression will *replace* any prior binding. ## Working with Multiple Schemas ### Preload Dependency You can utilize [Yang.import](./src/yang.litcoffee#import-name-opts) to load the dependency module into the [Yang](./src/yang.litcoffee) compiler: ```js const Yang = require('yang-js'); Yang.import('/some/path/to/dependency.yang'); ``` You can also use built-in `require()` directly: ```js require('yang-js'); require('/some/path/to/dependency.yang'); ``` The pre-load approach is *iterative* in that you would need to ensure dependency YANG modules are loaded in the proper order of the nested dependency chain. ### Automatic Resolution When utilizing `Yang.import` or `register/require`, the [Yang](./src/yang.litcoffee) compiler internally utilizes [Yang.resolve](./src/yang.litcoffee#resolve-from-name) to attempt to locate dependency modules automatically. It first checks local `package.json` to resolve the dependency via [External](#external-package-bundle) or [Internal](#internal-package-bundle) definitions. If not found, it will try to locate the `some-dependency.yang` in the same directory that the *dependent* schema is being required. ### External Package Bundle To utilize YANG modules bundled in an external package inside your own app, you can add a section inside your local `package.json` as follows: ```json { "models": { "ietf-yang-types": "yang-js", "ietf-inet-types": "yang-js", "yang-store": "yang-js" } } ``` This will enable `Yang.resolve` and `Yang.import` to locate these YANG modules from the `yang-js` package. ### Interal Package Bundle To allow external packages to perform [Automatic Resolution](#automatic-resolution) of modules being provided by your app, as well as for your own app to resolve local dependencies, you can add a section inside your `package.json` as follows: ```json { "models": { "my-module": "./some/path/to/my-module.yang", "my-bound-module": "./lib/my-bound-module.js" } } ``` Please note that you can reference a YANG schema file directly as well as a **JavaScript file** which exports a [Yang](./src/yang.litcoffee) schema instance. The second approach is useful for exporting **bound** schemas (see [Schema Binding](#schema-binding)) which contains function bindings on the YANG schema. ## Working with Models ### Model Events ```coffeescript # coffeescript Yang = require 'yang-js' schema = """ module foo { list bar { container obj { leaf a { type string; } leaf b { type uint8; } } } } """ model = (Yang schema) { 'foo:bar': [ { obj: { a: 'apple', b: 10 } } { obj: { a: 'orange, b: 20 } } ] } model.on 'update', (prop, prev) -> # do something with 'prop' ``` The example above will register an event listener using [Model.on](./src/model.litcoffee#on-event) to trigger whenever the data state of the `model` is updated. You can also utilize XPATH expressions to only listen for specific events occurring inside the data tree: ```coffeescript model.on 'update', '/foo:bar/obj/a', (prop, prev) -> console.log "the property 'a' changed on one of the elements in the 'list bar'" model.in('/foo:bar[0]/obj/a').set 'pineapple' # trigger event model.in('/foo:bar[1]/obj/a').set 'grape' # trigger event ```