@toa.io/norm 0.2.1-dev.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.
Files changed (71) hide show
  1. package/features/component.operations.feature +54 -0
  2. package/features/steps/.test/manifest.test.js +110 -0
  3. package/features/steps/.types/context.d.ts +9 -0
  4. package/features/steps/hooks.js +18 -0
  5. package/features/steps/manifest.js +66 -0
  6. package/features/steps/parameters.js +9 -0
  7. package/package.json +31 -0
  8. package/src/.component/.expand/bridge.js +9 -0
  9. package/src/.component/.expand/entity.js +14 -0
  10. package/src/.component/.expand/events.js +14 -0
  11. package/src/.component/.expand/extensions.js +10 -0
  12. package/src/.component/.expand/index.js +15 -0
  13. package/src/.component/.expand/operations.js +17 -0
  14. package/src/.component/.expand/receivers.js +16 -0
  15. package/src/.component/.normalize/events.js +13 -0
  16. package/src/.component/.normalize/extensions.js +35 -0
  17. package/src/.component/.normalize/index.js +9 -0
  18. package/src/.component/.normalize/operations.js +12 -0
  19. package/src/.component/collapse.js +44 -0
  20. package/src/.component/defaults.js +25 -0
  21. package/src/.component/dereference.js +72 -0
  22. package/src/.component/expand.js +14 -0
  23. package/src/.component/index.js +17 -0
  24. package/src/.component/merge.js +55 -0
  25. package/src/.component/normalize.js +13 -0
  26. package/src/.component/schema.yaml +222 -0
  27. package/src/.component/validate.js +40 -0
  28. package/src/.context/.dependencies/connectors.js +44 -0
  29. package/src/.context/.dependencies/describe.js +10 -0
  30. package/src/.context/.dependencies/extensions.js +24 -0
  31. package/src/.context/.dependencies/index.js +9 -0
  32. package/src/.context/.dependencies/resolve.js +45 -0
  33. package/src/.context/complete.js +33 -0
  34. package/src/.context/dependencies.js +16 -0
  35. package/src/.context/dereference.js +31 -0
  36. package/src/.context/expand.js +14 -0
  37. package/src/.context/index.js +15 -0
  38. package/src/.context/normalize.js +19 -0
  39. package/src/.context/schema.yaml +60 -0
  40. package/src/.context/validate.js +19 -0
  41. package/src/component.js +59 -0
  42. package/src/context.js +48 -0
  43. package/src/index.js +9 -0
  44. package/src/shortcuts.js +52 -0
  45. package/test/component/collapse.fixtures.js +134 -0
  46. package/test/component/collapse.test.js +81 -0
  47. package/test/component/dereference.fixtures.js +165 -0
  48. package/test/component/dereference.test.js +31 -0
  49. package/test/component/dummies/extension/index.js +9 -0
  50. package/test/component/dummies/extension/package.json +5 -0
  51. package/test/component/expand.fixtures.js +137 -0
  52. package/test/component/expand.test.js +17 -0
  53. package/test/component/normalize.fixtures.js +22 -0
  54. package/test/component/normalize.test.js +45 -0
  55. package/test/component/validate.fixtures.js +58 -0
  56. package/test/component/validate.test.js +250 -0
  57. package/test/context/complete.fixtures.js +59 -0
  58. package/test/context/complete.test.js +31 -0
  59. package/test/context/dereference.fixtures.js +60 -0
  60. package/test/context/dereference.test.js +24 -0
  61. package/test/context/expand.fixtures.js +5 -0
  62. package/test/context/expand.test.js +41 -0
  63. package/test/context/normalize.fixtures.js +32 -0
  64. package/test/context/normalize.test.js +34 -0
  65. package/test/context/validate.fixtures.js +38 -0
  66. package/test/context/validate.test.js +91 -0
  67. package/test/shortcuts.fixtures.js +17 -0
  68. package/test/shortcuts.test.js +92 -0
  69. package/types/component.d.ts +78 -0
  70. package/types/context.d.ts +75 -0
  71. package/types/index.d.ts +5 -0
@@ -0,0 +1,54 @@
1
+ Feature: Operations declaration
2
+
3
+ Scenario Outline: Operation <channel> entity references
4
+
5
+ Operation IO schemas may reference entity's properties using syntax:
6
+ - `.` - entity's property with the same name
7
+ - '.foo` - entity's property with `foo` name
8
+
9
+ Given I have an entity schema:
10
+ """
11
+ foo: string
12
+ bar: 1
13
+ """
14
+ When I declare assignment with:
15
+ """
16
+ <channel>:
17
+ foo: .
18
+ baz: .bar
19
+ """
20
+ Then normalized assignment declaration must contain:
21
+ """
22
+ <channel>:
23
+ type: object
24
+ properties:
25
+ foo:
26
+ type: string
27
+ baz:
28
+ type: number
29
+ default: 1
30
+ """
31
+
32
+ Examples:
33
+ | channel |
34
+ | input |
35
+ | output |
36
+
37
+ Scenario: Prototype property reference
38
+
39
+ Operation IO schemas must be able to reference prototype's entity properties.
40
+
41
+ When I declare assignment with:
42
+ """
43
+ output:
44
+ id: .
45
+ """
46
+
47
+ Then normalized assignment declaration must contain:
48
+ """
49
+ output:
50
+ type: object
51
+ properties:
52
+ id:
53
+ $ref: https://schemas.toa.io/0.0.0/definitions#/definitions/id
54
+ """
@@ -0,0 +1,110 @@
1
+ 'use strict'
2
+
3
+ const { AssertionError } = require('assert')
4
+ const { dump } = require('@toa.io/yaml')
5
+ const { gherkin } = require('@toa.io/mock')
6
+
7
+ const mock = { gherkin }
8
+
9
+ jest.mock('@cucumber/cucumber', () => mock.gherkin)
10
+ require('../manifest')
11
+
12
+ it('should be', () => undefined)
13
+
14
+ /** @type {toa.norm.features.Context} */
15
+ let context
16
+
17
+ beforeEach(() => {
18
+ const manifest = {
19
+ name: 'test',
20
+ namespace: 'features',
21
+ version: '1.0.0',
22
+ entity: null
23
+ }
24
+
25
+ context = { manifest }
26
+ })
27
+
28
+ describe('Given I have an entity schema:', () => {
29
+ const step = gherkin.steps.Gi('I have an entity schema:')
30
+
31
+ it('should be', () => undefined)
32
+
33
+ it('should set entity schema', () => {
34
+ const schema = { foo: 'string' }
35
+ const yaml = dump(schema)
36
+
37
+ step.call(context, yaml)
38
+
39
+ expect(context.manifest.entity).toStrictEqual({ schema })
40
+ })
41
+ })
42
+
43
+ describe('When I declare {operation} with:', () => {
44
+ const step = gherkin.steps.Wh('I declare {operation} with:')
45
+
46
+ it('should be', () => undefined)
47
+
48
+ it('should declare operation', () => {
49
+ const type = 'assignment'
50
+ const scope = 'changeset'
51
+ const input = {}
52
+ const yaml = dump(input)
53
+
54
+ step.call(context, type, yaml)
55
+
56
+ expect(context.manifest.operations[type]).toStrictEqual({ type, scope })
57
+ })
58
+ })
59
+
60
+ describe('Then normalized {operation} declaration must contain:', () => {
61
+ const step = gherkin.steps.Th('normalized {operation} declaration must contain:')
62
+
63
+ it('should be', () => undefined)
64
+
65
+ it('should throw if does not contain', async () => {
66
+ const type = 'assignment'
67
+
68
+ context.manifest.operations = {
69
+ [type]: {
70
+ type,
71
+ input: { bar: 'number' },
72
+ scope: 'changeset'
73
+ }
74
+ }
75
+
76
+ const input = {
77
+ type: 'object',
78
+ properties: {
79
+ bar: { type: 'string' }
80
+ }
81
+ }
82
+
83
+ const yaml = dump({ input })
84
+
85
+ await expect(step.call(context, type, yaml)).rejects.toThrow(AssertionError)
86
+ })
87
+
88
+ it('should not throw if contain', async () => {
89
+ const type = 'assignment'
90
+
91
+ context.manifest.operations = {
92
+ [type]: {
93
+ type,
94
+ input: { bar: 'number' },
95
+ scope: 'changeset'
96
+ }
97
+ }
98
+
99
+ const input = {
100
+ type: 'object',
101
+ properties: {
102
+ bar: { type: 'number' }
103
+ }
104
+ }
105
+
106
+ const yaml = dump({ input })
107
+
108
+ await expect(step.call(context, type, yaml)).resolves.not.toThrow()
109
+ })
110
+ })
@@ -0,0 +1,9 @@
1
+ import { Declaration } from '../../../types/component'
2
+
3
+ declare namespace toa.norm.features {
4
+
5
+ type Context = {
6
+ manifest?: Declaration
7
+ }
8
+
9
+ }
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const { Before } = require('@cucumber/cucumber')
4
+
5
+ Before(
6
+ /**
7
+ * @this {toa.norm.features.Context}
8
+ */
9
+ function () {
10
+ this.manifest = {
11
+ name: 'test',
12
+ namespace: 'features',
13
+ version: '1.0.0',
14
+ entity: {
15
+ storage: null
16
+ }
17
+ }
18
+ })
@@ -0,0 +1,66 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { join } = require('node:path')
5
+ const { parse, save } = require('@toa.io/yaml')
6
+ const { directory } = require('@toa.io/filesystem')
7
+ const { match } = require('@toa.io/generic')
8
+
9
+ const { component: load } = require('../../src')
10
+
11
+ const { Given, When, Then } = require('@cucumber/cucumber')
12
+
13
+ Given('I have an entity schema:',
14
+ /**
15
+ * @param {string} yaml
16
+ * @this {toa.norm.features.Context}
17
+ */
18
+ function (yaml) {
19
+ const schema = parse(yaml)
20
+
21
+ this.manifest.entity = { schema }
22
+ })
23
+
24
+ When('I declare {operation} with:',
25
+ /**
26
+ * @param {toa.norm.component.operations.Type} type
27
+ * @param {string} yaml
28
+ * @this {toa.norm.features.Context}
29
+ */
30
+ function (type, yaml) {
31
+ /** @type {toa.norm.component.Operation} */
32
+ const declaration = parse(yaml)
33
+
34
+ declaration.type = type
35
+ declaration.scope = scope(type)
36
+
37
+ this.manifest.operations = { [type]: declaration }
38
+ })
39
+
40
+ Then('normalized {operation} declaration must contain:',
41
+ /**
42
+ * @param {toa.norm.component.operations.Type} type
43
+ * @param {string} yaml
44
+ * @this {toa.norm.features.Context}
45
+ */
46
+ async function (type, yaml) {
47
+ const temp = await directory.temp()
48
+ const path = join(temp, 'manifest.toa.yaml')
49
+
50
+ await save(this.manifest, path)
51
+
52
+ const manifest = await load(temp)
53
+ const operation = manifest.operations[type]
54
+ const query = parse(yaml)
55
+ const contains = match(operation, query)
56
+
57
+ assert.equal(contains, true)
58
+ })
59
+
60
+ /**
61
+ * @param {toa.norm.component.operations.Type} type
62
+ * @returns {toa.norm.component.operations.Scope}
63
+ */
64
+ const scope = (type) => {
65
+ return type === 'assignment' ? 'changeset' : 'object'
66
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { defineParameterType } = require('@cucumber/cucumber')
4
+
5
+ defineParameterType({
6
+ name: 'operation',
7
+ regexp: /transition|observation|assignment/,
8
+ transformer: (type) => type
9
+ })
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@toa.io/norm",
3
+ "version": "0.2.1-dev.1",
4
+ "description": "Toa declarations normalization and validation",
5
+ "author": "temich <tema.gurtovoy@gmail.com>",
6
+ "homepage": "https://github.com/toa-io/toa#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/toa-io/toa.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/toa-io/toa/issues"
13
+ },
14
+ "main": "src/index.js",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "test": "echo \"Error: run tests from root\" && exit 1"
20
+ },
21
+ "devDependencies": {
22
+ "@toa.io/mock": "*"
23
+ },
24
+ "dependencies": {
25
+ "@toa.io/core": "*",
26
+ "@toa.io/generic": "*",
27
+ "@toa.io/schema": "*",
28
+ "@toa.io/schemas": "*",
29
+ "@toa.io/yaml": "*"
30
+ }
31
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('../../shortcuts')
4
+
5
+ function bridge (manifest) {
6
+ manifest.bridge = resolve(manifest.bridge)
7
+ }
8
+
9
+ exports.bridge = bridge
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { expand } = require('@toa.io/schemas')
4
+
5
+ const { resolve } = require('../../shortcuts')
6
+
7
+ function entity (manifest) {
8
+ if (manifest.entity === undefined) return
9
+
10
+ manifest.entity.schema = expand(manifest.entity.schema)
11
+ manifest.entity.storage = resolve(manifest.entity.storage)
12
+ }
13
+
14
+ exports.entity = entity
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('../../shortcuts')
4
+
5
+ function events (manifest) {
6
+ if (manifest.events === undefined) return
7
+
8
+ for (const event of Object.values(manifest.events)) {
9
+ if (event.binding !== undefined) event.binding = resolve(event.binding)
10
+ if (event.bridge !== undefined) event.bridge = resolve(event.bridge)
11
+ }
12
+ }
13
+
14
+ exports.events = events
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ const { recognize } = require('../../shortcuts')
4
+
5
+ function extensions (manifest) {
6
+ recognize(manifest, 'extensions')
7
+ recognize(manifest.extensions)
8
+ }
9
+
10
+ exports.extensions = extensions
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const { bridge } = require('./bridge')
4
+ const { entity } = require('./entity')
5
+ const { events } = require('./events')
6
+ const { extensions } = require('./extensions')
7
+ const { operations } = require('./operations')
8
+ const { receivers } = require('./receivers')
9
+
10
+ exports.bridge = bridge
11
+ exports.entity = entity
12
+ exports.events = events
13
+ exports.extensions = extensions
14
+ exports.operations = operations
15
+ exports.receivers = receivers
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const { expand } = require('@toa.io/schemas')
4
+ const { resolve } = require('../../shortcuts')
5
+
6
+ function operations (manifest) {
7
+ if (manifest.operations === undefined) return
8
+
9
+ for (const operation of Object.values(manifest.operations)) {
10
+ if (operation.input !== undefined) operation.input = expand(operation.input)
11
+ if (operation.output !== undefined) operation.output = expand(operation.output)
12
+ if (operation.bridge !== undefined) operation.bridge = resolve(operation.bridge)
13
+ if (operation.bindings !== undefined) operation.bindings = operation.bindings.map(resolve)
14
+ }
15
+ }
16
+
17
+ exports.operations = operations
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('../../shortcuts')
4
+
5
+ function receivers (manifest) {
6
+ if (manifest.receivers === undefined) return
7
+
8
+ for (const [locator, receiver] of Object.entries(manifest.receivers)) {
9
+ if (typeof receiver === 'string') manifest.receivers[locator] = { transition: receiver }
10
+
11
+ if (receiver.binding !== undefined) receiver.binding = resolve(receiver.binding)
12
+ if (receiver.bridge !== undefined) receiver.bridge = resolve(receiver.bridge)
13
+ }
14
+ }
15
+
16
+ exports.receivers = receivers
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ const events = (component) => {
4
+ if (component.events === undefined) return
5
+
6
+ const binding = component.bindings.find((binding) => require(binding).properties.async === true)
7
+
8
+ for (const event of Object.values(component.events)) {
9
+ if (event.binding === undefined) event.binding = binding
10
+ }
11
+ }
12
+
13
+ exports.events = events
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ const { directory: { find } } = require('@toa.io/filesystem')
4
+ const { resolve } = require('../../shortcuts')
5
+
6
+ /**
7
+ * @param {toa.norm.Component} manifest
8
+ */
9
+ const extensions = (manifest) => {
10
+ if (manifest.extensions === undefined) return
11
+
12
+ const extensions = manifest.extensions
13
+
14
+ for (let [reference, declaration] of Object.entries(extensions)) {
15
+ let key = resolve(reference)
16
+
17
+ // relative path
18
+ if (key[0] === '.') key = find(key, manifest.path)
19
+
20
+ const extension = require(key)
21
+
22
+ if (extension.manifest !== undefined) {
23
+ declaration = extension.manifest(declaration, manifest)
24
+
25
+ if (declaration === undefined) throw new Error(`Extension '${reference}' didn't return manifest`)
26
+ }
27
+
28
+ extensions[key] = declaration
29
+
30
+ // shortcut was used
31
+ if (reference !== key) delete extensions[reference]
32
+ }
33
+ }
34
+
35
+ exports.extensions = extensions
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { events } = require('./events')
4
+ const { extensions } = require('./extensions')
5
+ const { operations } = require('./operations')
6
+
7
+ exports.events = events
8
+ exports.extensions = extensions
9
+ exports.operations = operations
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ const operations = (component) => {
4
+ if (component.operations === undefined) return
5
+
6
+ for (const [endpoint, operation] of Object.entries(component.operations)) {
7
+ if (operation.bindings === undefined) operation.bindings = component.bindings
8
+ if (operation.virtual === true) delete component.operations[endpoint]
9
+ }
10
+ }
11
+
12
+ exports.operations = operations
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const { merge } = require('@toa.io/generic')
4
+
5
+ const collapse = (manifest, prototype) => {
6
+ delete manifest.prototype
7
+
8
+ if (prototype.operations) {
9
+ manifest.prototype = { prototype: prototype.prototype, path: prototype.path }
10
+
11
+ const operations = Object.entries(prototype.operations)
12
+
13
+ if (operations.length > 0) {
14
+ if (manifest.operations === undefined) manifest.operations = {}
15
+
16
+ for (let [endpoint, operation] of operations) {
17
+ if (manifest.operations[endpoint] === undefined) manifest.operations[endpoint] = {}
18
+ else {
19
+ const { virtual, ...real } = operation
20
+
21
+ operation = real
22
+ }
23
+
24
+ const { bridge, binding, ...declaration } = operation
25
+
26
+ merge(manifest.operations[endpoint], declaration)
27
+
28
+ if (bridge !== undefined) {
29
+ if (manifest.prototype.operations === undefined) manifest.prototype.operations = {}
30
+
31
+ manifest.prototype.operations[endpoint] = { bridge }
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ delete prototype.entity?.storage
38
+
39
+ const { entity, remotes, events } = prototype
40
+
41
+ merge(manifest, { entity, remotes, events })
42
+ }
43
+
44
+ exports.collapse = collapse
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ // these defaults are required before validation
4
+ const defaults = (manifest) => {
5
+ if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
6
+
7
+ if (manifest.bindings === undefined) {
8
+ const local = process.env.TOA_ENV === 'local'
9
+
10
+ manifest.bindings = local
11
+ ? ['@toa.io/bindings.amqp']
12
+ : ['@toa.io/bindings.http', '@toa.io/bindings.amqp']
13
+ }
14
+
15
+ if (manifest.bridge === undefined) manifest.bridge = '@toa.io/bridges.node'
16
+
17
+ if (manifest.entity === null || manifest.entity === undefined) manifest.entity = { storage: null }
18
+ if (manifest.entity.storage === undefined) manifest.entity.storage = '@toa.io/storages.mongodb'
19
+ if (manifest.entity.storage === null) manifest.entity.storage = '@toa.io/storages.null'
20
+
21
+ // TODO: bridge.version()
22
+ if (manifest.version === undefined) manifest.version = '0.0.0'
23
+ }
24
+
25
+ exports.defaults = defaults
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ const { merge } = require('@toa.io/generic')
4
+
5
+ const dereference = (manifest) => {
6
+ // schemas
7
+ const property = resolve(manifest.entity.schema.properties)
8
+
9
+ schema(manifest.entity.schema, property)
10
+
11
+ for (const operation of Object.values(manifest.operations)) {
12
+ if (operation.input !== undefined) operation.input = schema(operation.input, property)
13
+ if (operation.output !== undefined) operation.output = schema(operation.output, property)
14
+ }
15
+
16
+ // forwarding
17
+ for (const operation of Object.values(manifest.operations)) {
18
+ if (operation.forward !== undefined) forward(operation, manifest.operations)
19
+ }
20
+
21
+ for (const operation of Object.values(manifest.operations)) {
22
+ delete operation.forwarded
23
+ }
24
+ }
25
+
26
+ const resolve = (schema) => (property) => {
27
+ if (schema[property] === undefined) {
28
+ throw new Error(`Referenced property '${property}' is not defined`)
29
+ }
30
+
31
+ return schema[property]
32
+ }
33
+
34
+ const schema = (object, resolve) => {
35
+ if (object === undefined || typeof object !== 'object') return
36
+ if (object.type === 'string' && object.default?.[0] === '.') return resolve(object.default.substring(1))
37
+
38
+ if (object.type === 'array') {
39
+ object.items = schema(object.items, resolve)
40
+ } else if (object.properties !== undefined) {
41
+ for (const [name, value] of Object.entries(object.properties)) {
42
+ if (value?.type === 'string' && value.default === '.') object.properties[name] = resolve(name)
43
+ else object.properties[name] = schema(value, resolve)
44
+ }
45
+ }
46
+
47
+ return object
48
+ }
49
+
50
+ const types = {
51
+ id: { $ref: 'https://schemas.toa.io/0.0.0/definitions#/definitions/id' }
52
+ }
53
+
54
+ const forward = (operation, operations) => {
55
+ const target = operations[operation.forward]
56
+
57
+ if (target === undefined) throw new Error(`Referenced operation '${operation.forward}' is not defined`)
58
+
59
+ if (target.forward !== undefined) {
60
+ if (target.forwarded !== true) forward(target, operations)
61
+
62
+ operation.forward = target.forward
63
+ }
64
+
65
+ operation.forwarded = true
66
+
67
+ const { virtual, ...real } = target
68
+
69
+ merge(operation, real)
70
+ }
71
+
72
+ exports.dereference = dereference
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { entity, bridge, operations, events, receivers, extensions } = require('./.expand')
4
+
5
+ const expand = (manifest) => {
6
+ entity(manifest)
7
+ bridge(manifest)
8
+ operations(manifest)
9
+ events(manifest)
10
+ receivers(manifest)
11
+ extensions(manifest)
12
+ }
13
+
14
+ exports.expand = expand
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const { collapse } = require('./collapse')
4
+ const { defaults } = require('./defaults')
5
+ const { dereference } = require('./dereference')
6
+ const { expand } = require('./expand')
7
+ const { merge } = require('./merge')
8
+ const { normalize } = require('./normalize')
9
+ const { validate } = require('./validate')
10
+
11
+ exports.collapse = collapse
12
+ exports.defaults = defaults
13
+ exports.dereference = dereference
14
+ exports.expand = expand
15
+ exports.merge = merge
16
+ exports.normalize = normalize
17
+ exports.validate = validate