@toa.io/norm 1.0.0-alpha.2 → 1.0.0-alpha.200

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/norm",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.200",
4
4
  "description": "Toa declarations normalization and validation",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -20,11 +20,10 @@
20
20
  "test": "echo \"Error: run tests from root\" && exit 1"
21
21
  },
22
22
  "dependencies": {
23
- "@toa.io/core": "1.0.0-alpha.2",
24
- "@toa.io/generic": "1.0.0-alpha.2",
25
- "@toa.io/schema": "1.0.0-alpha.2",
26
- "@toa.io/schemas": "1.0.0-alpha.2",
27
- "@toa.io/yaml": "1.0.0-alpha.2"
23
+ "@toa.io/core": "1.0.0-alpha.200",
24
+ "@toa.io/generic": "1.0.0-alpha.173",
25
+ "@toa.io/schemas": "1.0.0-alpha.200",
26
+ "@toa.io/yaml": "1.0.0-alpha.182"
28
27
  },
29
- "gitHead": "7688e6e980a65c82ac2e459be4e355eebf406cd0"
28
+ "gitHead": "35b4adeedae2e9450ef44a74f42a53eff20ee203"
30
29
  }
@@ -14,7 +14,8 @@ const SHORTCUTS = {
14
14
  configuration: '@toa.io/extensions.configuration',
15
15
  state: '@toa.io/extensions.state',
16
16
  stash: '@toa.io/extensions.stash',
17
- storages: '@toa.io/extensions.storages'
17
+ storages: '@toa.io/extensions.storages',
18
+ mail: '@toa.io/extensions.mail'
18
19
  }
19
20
 
20
21
  exports.extensions = extensions
@@ -7,6 +7,7 @@ const { extensions } = require('./extensions')
7
7
  const { operations } = require('./operations')
8
8
  const { properties } = require('./properties')
9
9
  const { receivers } = require('./receivers')
10
+ const { version } = require('./version')
10
11
 
11
12
  exports.bridge = bridge
12
13
  exports.entity = entity
@@ -15,3 +16,4 @@ exports.extensions = extensions
15
16
  exports.operations = operations
16
17
  exports.properties = properties
17
18
  exports.receivers = receivers
19
+ exports.version = version
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+ const { createHash } = require('node:crypto')
5
+ const fs = require('node:fs/promises')
6
+ const { createReadStream } = require('node:fs')
7
+ const { once } = require('node:events')
8
+
9
+ async function version (manifest) {
10
+ manifest.version ??= await hash(manifest.path)
11
+ }
12
+
13
+ async function hash (path) {
14
+ const hash = await hashd(path)
15
+
16
+ return hash.digest('hex').slice(0, 8)
17
+ }
18
+
19
+ /**
20
+ * @param {string} path
21
+ * @param {import('node:crypto').Hash} hash
22
+ */
23
+ async function hashd (path, hash = createHash('sha256')) {
24
+ const stat = await fs.stat(path)
25
+
26
+ if (stat.isFile()) {
27
+ const stream = createReadStream(path)
28
+
29
+ stream.pipe(hash, { end: false })
30
+
31
+ return await once(stream, 'end')
32
+ }
33
+
34
+ if (stat.isDirectory()) {
35
+ const entries = await fs.opendir(path)
36
+
37
+ for await (const entry of entries) {
38
+ await hashd(join(path, entry.name), hash)
39
+ }
40
+
41
+ return hash
42
+ }
43
+ }
44
+
45
+ exports.version = version
@@ -4,7 +4,7 @@ const operations = (component) => {
4
4
  if (component.operations === undefined) return
5
5
 
6
6
  for (const [endpoint, operation] of Object.entries(component.operations)) {
7
- if (operation.type === 'computation' || operation.type === 'effect')
7
+ if (operation.scope === 'none')
8
8
  operation.query = false
9
9
 
10
10
  if (operation.bindings === undefined) operation.bindings = component.bindings
@@ -42,6 +42,11 @@ const collapse = (manifest, prototype) => {
42
42
  delete prototype.entity.schema.properties.id
43
43
  }
44
44
 
45
+ if (prototype.events !== undefined && manifest.events !== undefined)
46
+ for (const event of Object.keys(prototype.events))
47
+ if (event in manifest.events)
48
+ delete prototype.events[event]
49
+
45
50
  merge(manifest, { entity, events, extensions })
46
51
  }
47
52
 
@@ -3,8 +3,11 @@
3
3
  const { hash } = require('@toa.io/generic')
4
4
 
5
5
  // these defaults are required before validation
6
- const defaults = (manifest) => {
7
- if (manifest.name === undefined) manifest.name = protoName(manifest)
6
+ const defaults = (manifest, proto) => {
7
+ if (manifest.name === undefined)
8
+ if (proto) manifest.name = protoName(manifest)
9
+ else nameAfterDir(manifest)
10
+
8
11
  if (manifest.bindings === undefined) manifest.bindings = ['@toa.io/bindings.amqp']
9
12
  if (manifest.bridge === undefined) manifest.bridge = '@toa.io/bridges.node'
10
13
 
@@ -17,13 +20,19 @@ const defaults = (manifest) => {
17
20
  }
18
21
 
19
22
  if (manifest.prototype === undefined) manifest.prototype = '@toa.io/prototype'
20
-
21
- // TODO: bridge.version()
22
- if (manifest.version === undefined) manifest.version = '0.0.0'
23
23
  }
24
24
 
25
25
  function protoName (manifest) {
26
26
  return 'proto' + hash(manifest.path)
27
27
  }
28
28
 
29
+ function nameAfterDir (manifest) {
30
+ const parts = manifest.path.split('/')
31
+ const dirname = parts[parts.length - 1]
32
+ const [name, namespace] = dirname.split('.').reverse()
33
+
34
+ manifest.name = name
35
+ manifest.namespace = namespace
36
+ }
37
+
29
38
  exports.defaults = defaults
@@ -25,7 +25,12 @@ const createResolver = (properties) => (property) => {
25
25
  function operations (manifest, resolver) {
26
26
  for (const operation of Object.values(manifest.operations)) {
27
27
  if (operation.input !== undefined) operation.input = schema(operation.input, resolver)
28
- if (operation.output !== undefined) operation.output = schema(operation.output, resolver)
28
+
29
+ if (operation.output !== undefined)
30
+ if (Array.isArray(operation.output) && operation.output.length === 1)
31
+ operation.output = [schema(operation.output[0], resolver)]
32
+ else
33
+ operation.output = schema(operation.output, resolver)
29
34
  }
30
35
 
31
36
  // forwarding
@@ -7,10 +7,11 @@ const {
7
7
  events,
8
8
  receivers,
9
9
  extensions,
10
- properties
10
+ properties,
11
+ version
11
12
  } = require('./.expand')
12
13
 
13
- const expand = (manifest) => {
14
+ async function expand (manifest) {
14
15
  entity(manifest)
15
16
  bridge(manifest)
16
17
  operations(manifest)
@@ -18,6 +19,8 @@ const expand = (manifest) => {
18
19
  receivers(manifest)
19
20
  properties(manifest)
20
21
  extensions(manifest)
22
+
23
+ await version(manifest)
21
24
  }
22
25
 
23
26
  exports.expand = expand
@@ -3,8 +3,13 @@
3
3
  const { directory: { find } } = require('@toa.io/filesystem')
4
4
  const { resolve } = require('../shortcuts')
5
5
 
6
+ const cache = {}
7
+
6
8
  const extensions = (manifest) => {
7
- if (manifest.extensions === undefined) return
9
+ if (manifest.extensions === undefined)
10
+ manifest.extensions = PREDEFINED
11
+ else
12
+ manifest.extensions = Object.assign({}, PREDEFINED, manifest.extensions)
8
13
 
9
14
  const extensions = manifest.extensions
10
15
 
@@ -14,7 +19,8 @@ const extensions = (manifest) => {
14
19
  // relative path
15
20
  if (key[0] === '.') key = find(key, manifest.path)
16
21
 
17
- const extension = require(key)
22
+ cache[key] ??= require(key)
23
+ const extension = cache[key]
18
24
 
19
25
  if (extension.manifest !== undefined) {
20
26
  declaration = extension.manifest(declaration, manifest)
@@ -29,4 +35,8 @@ const extensions = (manifest) => {
29
35
  }
30
36
  }
31
37
 
38
+ const PREDEFINED = {
39
+ '@toa.io/extensions.telemetry': null
40
+ }
41
+
32
42
  exports.extensions = extensions
@@ -6,7 +6,8 @@ const bridge = async (root, manifest) => {
6
6
  await Promise.all([
7
7
  define(root, manifest, 'operations'),
8
8
  define(root, manifest, 'events'),
9
- define(root, manifest, 'receivers')
9
+ define(root, manifest, 'receivers'),
10
+ define(root, manifest, 'guards')
10
11
  ])
11
12
  }
12
13
 
@@ -19,7 +20,7 @@ const define = async (root, manifest, property) => {
19
20
  if (item.bridge === undefined || item.bridge === manifest.bridge) continue // default bridge later
20
21
 
21
22
  const bridge = item.bridge || manifest.bridge
22
- const { define } = require(bridge)
23
+ const define = req(bridge).define
23
24
  const definition = await define[singular](root, endpoint)
24
25
 
25
26
  merge(item, definition)
@@ -44,14 +45,27 @@ const define = async (root, manifest, property) => {
44
45
 
45
46
  const declared = manifest[property][endpoint]
46
47
 
47
- if (declared === undefined) manifest[property][endpoint] = item
48
- else merge(declared, item)
48
+ try {
49
+ if (declared === undefined) manifest[property][endpoint] = item
50
+ else merge(declared, item)
51
+ } catch (error) {
52
+ console.error(`Error merging ${singular} '${endpoint}' in ${root}`)
53
+ throw error
54
+ }
49
55
  }
50
56
  }
51
57
  }
52
58
 
59
+ const cache = {}
60
+
61
+ function req(mod) {
62
+ cache[mod] ??= require(mod)
63
+
64
+ return cache[mod]
65
+ }
66
+
53
67
  const scan = async (bridge, root, property) => {
54
- const { define } = require(bridge)
68
+ const define = req(bridge).define
55
69
 
56
70
  if (property in define)
57
71
  return define[property](root)
@@ -8,7 +8,7 @@ definitions:
8
8
  binding:
9
9
  type: string
10
10
  not:
11
- const: '@toa.io/bindings.loop' # loop is for system use only
11
+ const: "@toa.io/bindings.loop" # loop is for system use only
12
12
 
13
13
  type: object
14
14
  properties:
@@ -17,19 +17,19 @@ properties:
17
17
  nullable: true
18
18
  properties:
19
19
  prototype:
20
- $ref: '#/properties/prototype'
20
+ $ref: "#/properties/prototype"
21
21
  path:
22
22
  type: string
23
23
  operations:
24
24
  type: object
25
25
  propertyNames:
26
- $ref: '#/definitions/name'
26
+ $ref: "#/definitions/name"
27
27
  patternProperties:
28
- '.*':
28
+ ".*":
29
29
  type: object
30
30
  properties:
31
31
  bridge:
32
- $ref: '#/properties/bridge'
32
+ $ref: "#/properties/bridge"
33
33
 
34
34
  path:
35
35
  type: string
@@ -38,37 +38,43 @@ properties:
38
38
  type: object
39
39
  properties:
40
40
  id:
41
- $ref: 'definitions#/definitions/locator'
41
+ type: string
42
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(\.([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))$
42
43
  label:
43
- $ref: 'definitions#/definitions/label'
44
+ type: string
45
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(-([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))*$
44
46
 
45
47
  name:
46
- $ref: 'definitions#/definitions/token'
48
+ type: string
49
+ pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
47
50
 
48
51
  namespace:
49
- $ref: 'definitions#/definitions/token'
50
- default: 'default'
52
+ type: string
53
+ pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
54
+ default: "default"
51
55
  not:
52
56
  oneOf:
53
- - const: 'system'
57
+ - const: "system"
54
58
 
55
59
  version:
56
- $ref: 'definitions#/definitions/version'
60
+ type: string
57
61
 
58
62
  build:
59
63
  type: object
60
64
  properties:
61
65
  image:
62
66
  type: string
67
+ run:
68
+ type: string
63
69
 
64
70
  entity:
65
71
  type: object
66
72
  properties:
67
73
  storage:
68
74
  type: string
69
- default: '@toa.io/storages.mongodb'
75
+ default: "@toa.io/storages.mongodb"
70
76
  schema:
71
- $ref: 'definitions#/definitions/schema'
77
+ $ref: https://json-schema.org/draft/2019-09/schema
72
78
  type: object
73
79
  properties:
74
80
  type:
@@ -78,9 +84,25 @@ properties:
78
84
  type: object
79
85
  propertyNames:
80
86
  oneOf:
81
- - $ref: '#/definitions/name'
82
- - enum: [_version]
83
- dependent:
87
+ - $ref: "#/definitions/name"
88
+ - enum: [_version, _created, _updated, _deleted]
89
+ unique:
90
+ type: object
91
+ patternProperties:
92
+ ".*":
93
+ type: array
94
+ items:
95
+ type: string
96
+ index:
97
+ type: object
98
+ patternProperties:
99
+ ".*":
100
+ type: object
101
+ patternProperties:
102
+ ".*":
103
+ type: string
104
+ enum: [asc, desc, hash, text]
105
+ associated:
84
106
  type: boolean
85
107
  default: false
86
108
  custom:
@@ -93,7 +115,7 @@ properties:
93
115
  type: array
94
116
  uniqueItems: true
95
117
  items:
96
- $ref: '#/definitions/binding'
118
+ $ref: "#/definitions/binding"
97
119
 
98
120
  bridge:
99
121
  type: string
@@ -101,27 +123,40 @@ properties:
101
123
  operations:
102
124
  type: object
103
125
  propertyNames:
104
- $ref: '#/definitions/name'
126
+ $ref: "#/definitions/name"
105
127
  patternProperties:
106
- '.*':
128
+ ".*":
107
129
  type: object
108
130
  properties:
109
131
  type:
110
- enum: [transition, observation, assignment, computation, effect]
132
+ enum:
133
+ [
134
+ transition,
135
+ observation,
136
+ assignment,
137
+ computation,
138
+ effect,
139
+ unmanaged,
140
+ ]
111
141
  scope:
112
- enum: [object, objects, changeset, none]
142
+ enum: [object, objects, changeset, stream, none]
113
143
  concurrency:
114
144
  enum: [none, retry]
115
145
  forward:
116
- $ref: '#/definitions/name'
146
+ $ref: "#/definitions/name"
117
147
  bridge:
118
148
  type: string
119
149
  bindings:
120
- $ref: '#/properties/bindings'
150
+ $ref: "#/properties/bindings"
121
151
  input:
122
- $ref: 'definitions#/definitions/schema'
152
+ $ref: https://json-schema.org/draft/2019-09/schema
123
153
  output:
124
- $ref: 'definitions#/definitions/schema'
154
+ $ref: https://json-schema.org/draft/2019-09/schema
155
+ default: {}
156
+ errors:
157
+ type: array
158
+ items:
159
+ type: string
125
160
  query:
126
161
  type: boolean
127
162
  required: [type, scope, bindings]
@@ -133,8 +168,8 @@ properties:
133
168
  then:
134
169
  properties:
135
170
  scope:
136
- enum: [object]
137
- if: # transition query: false
171
+ enum: [object, objects]
172
+ if: # not query: false
138
173
  not:
139
174
  properties:
140
175
  query:
@@ -142,6 +177,17 @@ properties:
142
177
  required: [query]
143
178
  then:
144
179
  required: [concurrency]
180
+ - if: # transition and scope=objects
181
+ properties:
182
+ type:
183
+ const: transition
184
+ scope:
185
+ const: objects
186
+ then:
187
+ properties:
188
+ query:
189
+ not:
190
+ const: false
145
191
  - if: # not transition
146
192
  not:
147
193
  properties:
@@ -158,7 +204,7 @@ properties:
158
204
  then:
159
205
  properties:
160
206
  scope:
161
- enum: [object, objects, none]
207
+ enum: [object, objects, stream, none]
162
208
  query:
163
209
  not:
164
210
  const: false
@@ -173,7 +219,7 @@ properties:
173
219
  - if: # computation
174
220
  properties:
175
221
  type:
176
- const: computation
222
+ enum: [computation, unmanaged]
177
223
  then:
178
224
  properties:
179
225
  scope:
@@ -189,20 +235,17 @@ properties:
189
235
  then:
190
236
  properties:
191
237
  scope:
192
- const: none
193
238
  default: none
194
- query:
195
- const: false
196
- default: false
239
+ enum: [object, objects, stream, none]
197
240
  additionalProperties: false
198
241
  additionalProperties: false
199
242
 
200
243
  events:
201
244
  type: object
202
245
  propertyNames:
203
- $ref: 'definitions#/definitions/name'
246
+ $ref: "#/definitions/name"
204
247
  patternProperties:
205
- '.*':
248
+ ".*":
206
249
  type: object
207
250
  properties:
208
251
  bridge:
@@ -210,7 +253,7 @@ properties:
210
253
  path:
211
254
  type: string
212
255
  binding:
213
- $ref: '#/definitions/binding'
256
+ $ref: "#/definitions/binding"
214
257
  conditioned:
215
258
  type: boolean
216
259
  default: false
@@ -223,17 +266,17 @@ properties:
223
266
  receivers:
224
267
  type: object
225
268
  patternProperties:
226
- '.*':
269
+ ".*":
227
270
  type: object
228
271
  properties:
229
272
  operation:
230
- $ref: 'definitions#/definitions/name'
273
+ $ref: "#/definitions/name"
231
274
  bridge:
232
275
  type: string
233
276
  binding:
234
277
  type: string
235
278
  source:
236
- $ref: 'definitions#/definitions/name'
279
+ $ref: "#/definitions/name"
237
280
  not:
238
281
  const: context
239
282
  path:
@@ -244,9 +287,22 @@ properties:
244
287
  adaptive:
245
288
  type: boolean
246
289
  default: false
290
+ arguments:
291
+ type: array
247
292
  required: [operation]
248
293
  additionalProperties: false
249
294
 
295
+ guards:
296
+ type: object
297
+ patternProperties:
298
+ ".*":
299
+ type: object
300
+ properties:
301
+ bridge: { type: string }
302
+ path: { type: string }
303
+ required: [bridge, path]
304
+ additionalProperties: false
305
+
250
306
  extensions:
251
307
  type: object
252
308
 
@@ -3,15 +3,15 @@
3
3
  const path = require('node:path')
4
4
 
5
5
  const { load } = require('@toa.io/yaml')
6
- const { Schema } = require('@toa.io/schema')
6
+ const schemas = require('@toa.io/schemas')
7
7
 
8
8
  const object = load.sync(path.resolve(__dirname, 'schema.yaml'))
9
- const schema = new Schema(object)
9
+ const schema = schemas.schema(object)
10
10
 
11
11
  const validate = (manifest) => {
12
12
  const error = schema.fit(manifest)
13
13
 
14
- if (error) throw new Error(error.message)
14
+ if (error) throw error
15
15
 
16
16
  if (manifest.events !== undefined) events(manifest)
17
17
  if (manifest.receivers !== undefined) receivers(manifest)
@@ -32,11 +32,11 @@ const receivers = (manifest) => {
32
32
  }
33
33
 
34
34
  if (!TYPES.has(manifest.operations[receiver.operation].type)) {
35
- throw new Error(`Receiver '${locator}' must refer to an operation of one of the allowed types: ${Array.from(TYPES).join(', ')}`)
35
+ throw new Error(`Receiver '${locator}' must refer to an operation of the allowed types: ${Array.from(TYPES).join(', ')}`)
36
36
  }
37
37
  }
38
38
  }
39
39
 
40
- const TYPES = new Set(['transition', 'effect'])
40
+ const TYPES = new Set(['transition', 'assignment', 'effect', 'unmanaged'])
41
41
 
42
42
  exports.validate = validate
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { resolve } = require('node:path')
4
+
3
5
  const connectors = (context, extracted) => {
4
6
  const connectors = {}
5
7
 
@@ -10,11 +12,14 @@ const connectors = (context, extracted) => {
10
12
 
11
13
  for (const component of components) {
12
14
  if (component.entity !== undefined) {
13
- if (connectors[component.entity.storage] === undefined) {
14
- connectors[component.entity.storage] = []
15
+ let storage = component.entity.storage
16
+
17
+ if (storage[0] === '.') {
18
+ storage = resolve(component.path, storage)
15
19
  }
16
20
 
17
- connectors[component.entity.storage].push(component)
21
+ connectors[storage] ??= []
22
+ connectors[storage].push(component)
18
23
  }
19
24
 
20
25
  const bindings = new Set()
@@ -24,6 +24,15 @@ const resolve = (references, annotations) => {
24
24
  }
25
25
  }
26
26
 
27
+ for (const dependency of Object.keys(annotations)) {
28
+ const { module } = load(dependency)
29
+
30
+ if (!module.standalone || (dependency in dependencies))
31
+ continue
32
+
33
+ dependencies[dependency] = []
34
+ }
35
+
27
36
  return dependencies
28
37
  }
29
38
 
@@ -4,13 +4,15 @@ $id: https://schemas.toa.io/0.0.0/context
4
4
  type: object
5
5
  properties:
6
6
  version:
7
- $ref: 'definitions#/definitions/version'
7
+ type: string
8
8
  name:
9
- $ref: 'definitions#/definitions/label'
9
+ type: string
10
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(-([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))*$
10
11
  description:
11
12
  type: string
12
13
  packages:
13
14
  type: string
15
+ default: components/*
14
16
  build:
15
17
  type: object
16
18
  properties:
@@ -20,7 +22,7 @@ properties:
20
22
  type: object
21
23
  properties:
22
24
  version:
23
- $ref: 'definitions#/definitions/version'
25
+ type: string
24
26
  registry:
25
27
  type: string
26
28
  format: uri
@@ -45,7 +47,7 @@ properties:
45
47
  - linux/amd64
46
48
  - linux/arm/v7
47
49
  - linux/arm64
48
- required: [base, platforms]
50
+ # required: [base]
49
51
  compositions:
50
52
  type: array
51
53
  minItems: 1
@@ -53,16 +55,18 @@ properties:
53
55
  type: object
54
56
  properties:
55
57
  name:
56
- $ref: 'definitions#/definitions/token'
58
+ type: string
59
+ pattern: ^[a-zA-Z]([a-zA-Z0-9]{1,31})?$
57
60
  image:
58
61
  type: string
59
62
  components:
60
63
  type: array
61
64
  minItems: 1
62
65
  items:
63
- $ref: 'definitions#/definitions/locator'
66
+ type: string
67
+ pattern: ^([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?)(\.([a-zA-Z]+([_a-zA-Z0-9]*[a-zA-Z0-9]+)?))$
64
68
  required: [name, components]
65
69
  annotations:
66
70
  type: object
67
- required: [runtime, name, packages, registry]
71
+ required: [runtime, name, registry]
68
72
  additionalProperties: false
@@ -3,15 +3,12 @@
3
3
  const { resolve } = require('node:path')
4
4
 
5
5
  const { load } = require('@toa.io/yaml')
6
- const { Schema } = require('@toa.io/schema')
6
+ const schemas = require('@toa.io/schemas')
7
7
 
8
8
  const path = resolve(__dirname, 'schema.yaml')
9
9
  const object = load.sync(path)
10
- const schema = new Schema(object)
10
+ const schema = schemas.schema(object)
11
11
 
12
- /**
13
- * @param {toa.norm.context.Declaration} context
14
- */
15
12
  const validate = (context) => {
16
13
  schema.validate(context)
17
14
  }
package/src/component.js CHANGED
@@ -13,7 +13,6 @@ const {
13
13
  collapse,
14
14
  dereference,
15
15
  defaults,
16
- dependencies,
17
16
  normalize,
18
17
  extensions
19
18
  } = require('./.component')
@@ -21,7 +20,7 @@ const {
21
20
  const component = async (path) => {
22
21
  const manifest = await load(path)
23
22
 
24
- normalize(manifest)
23
+ normalize(manifest, path)
25
24
  validate(manifest)
26
25
  extensions(manifest)
27
26
 
@@ -30,21 +29,21 @@ const component = async (path) => {
30
29
  return manifest
31
30
  }
32
31
 
33
- const load = async (path, base) => {
32
+ const load = async (path, base, proto = false) => {
34
33
  if (base !== undefined) path = find(path, base, MANIFEST)
35
34
 
36
35
  const file = join(path, MANIFEST)
37
- const manifest = /** @type {toa.norm.Component} */ await yaml(file) ?? {}
36
+ const manifest = await yaml(file) ?? {}
38
37
 
39
38
  manifest.path = path
40
39
 
41
- defaults(manifest)
42
- expand(manifest)
40
+ defaults(manifest, proto)
41
+ await expand(manifest)
43
42
 
44
43
  await merge(path, manifest)
45
44
 
46
45
  if (manifest.prototype !== null) {
47
- const prototype = await load(manifest.prototype, path)
46
+ const prototype = await load(manifest.prototype, path, true)
48
47
 
49
48
  collapse(manifest, prototype)
50
49
  }
package/src/shortcuts.js CHANGED
@@ -50,7 +50,9 @@ const SHORTCUTS = {
50
50
  configuration: '@toa.io/extensions.configuration',
51
51
  origins: '@toa.io/extensions.origins',
52
52
  stash: '@toa.io/extensions.stash',
53
- storages: '@toa.io/extensions.storages'
53
+ storages: '@toa.io/extensions.storages',
54
+ telemetry: '@toa.io/extensions.telemetry',
55
+ mail: '@toa.io/extensions.mail'
54
56
  }
55
57
 
56
58
  exports.recognize = recognize
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const source = {
4
+ path: '',
4
5
  entity: {
5
6
  schema: {
6
7
  properties: {
@@ -1,5 +1,3 @@
1
- // noinspection JSUnresolvedVariable
2
-
3
1
  'use strict'
4
2
 
5
3
  const clone = require('clone-deep')
@@ -20,13 +18,13 @@ it('should be ok', () => {
20
18
  it('should provide error', () => {
21
19
  manifest.foo = 'bar'
22
20
 
23
- expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
21
+ expect(() => validate(manifest)).toThrow()
24
22
  })
25
23
 
26
24
  it('should not have additional properties', () => {
27
25
  manifest.foo = 'bar'
28
26
 
29
- expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
27
+ expect(() => validate(manifest)).toThrow()
30
28
  })
31
29
 
32
30
  describe('namespace', () => {
@@ -83,13 +81,13 @@ describe('entity', () => {
83
81
 
84
82
  it('should not have additional properties', () => {
85
83
  manifest.entity.foo = 'bar'
86
- expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
84
+ expect(() => validate(manifest)).toThrow()
87
85
  })
88
86
 
89
87
  describe('schema', () => {
90
88
  it('should be required', () => {
91
89
  delete manifest.entity.schema
92
- expect(() => validate(manifest)).toThrow(/must have required property/)
90
+ expect(() => validate(manifest)).toThrow()
93
91
  })
94
92
 
95
93
  it('should be JSON schema object', () => {
@@ -99,7 +97,7 @@ describe('entity', () => {
99
97
 
100
98
  it('should be JSON schema object of type object', () => {
101
99
  manifest.entity.schema = { type: 'integer' }
102
- expect(() => validate(manifest)).toThrow(/must be equal to constant 'object'/)
100
+ expect(() => validate(manifest)).toThrow(/must be equal to constant/)
103
101
 
104
102
  manifest.entity.schema = {}
105
103
  validate(manifest)
@@ -112,15 +110,18 @@ describe('entity', () => {
112
110
  })
113
111
 
114
112
  it('should allow default id', () => {
115
- manifest.entity.schema.properties.id = { type: 'string', pattern: '^[a-fA-F0-9]+$' }
113
+ manifest.entity.schema.properties.id = {
114
+ type: 'string',
115
+ pattern: '^[a-fA-F0-9]+$'
116
+ }
116
117
  expect(() => validate(manifest)).not.toThrow()
117
118
  })
118
119
  })
119
120
 
120
- describe('dependent', () => {
121
+ describe('associated', () => {
121
122
  it('should provide default', () => {
122
123
  expect(() => validate(manifest)).not.toThrow()
123
- expect(manifest.entity.dependent).toBe(false)
124
+ expect(manifest.entity.associated).toBe(false)
124
125
  })
125
126
  })
126
127
  })
@@ -151,7 +152,7 @@ describe('operations', () => {
151
152
 
152
153
  it('should not have additional properties', () => {
153
154
  manifest.operations.get.foo = 'bar'
154
- expect(() => validate(manifest)).toThrow(/additional property/)
155
+ expect(() => validate(manifest)).toThrow()
155
156
  })
156
157
 
157
158
  it('should have type (transition or observation)', () => {
@@ -173,8 +174,7 @@ describe('operations', () => {
173
174
  })
174
175
 
175
176
  it.each([
176
- ['computation', 'compute'],
177
- ['effect', 'affect']
177
+ ['computation', 'compute']
178
178
  ])('should set query: false for %s', async (_, operation) => {
179
179
  validate(manifest)
180
180
 
@@ -247,7 +247,7 @@ describe('receivers', () => {
247
247
  it('should throw if transition points to observation', () => {
248
248
  manifest.receivers['foo.bar.happened'].operation = 'get'
249
249
 
250
- expect(() => validate(manifest)).toThrow(/one of the allowed types/)
250
+ expect(() => validate(manifest)).toThrow(/of the allowed types/)
251
251
  })
252
252
 
253
253
  it('should throw if source has a name `context`', async () => {
@@ -21,14 +21,6 @@ describe('runtime', () => {
21
21
  expect(() => validate(context)).toThrow(/required/)
22
22
  })
23
23
 
24
- it('should require version as semver', () => {
25
- delete context.runtime.version
26
- expect(() => validate(context)).toThrow(/required/)
27
-
28
- context.runtime.version = '.'
29
- expect(() => validate(context)).toThrow(/pattern/)
30
- })
31
-
32
24
  it('should require registry to match uri format', () => {
33
25
  context.runtime.registry = 'not-a-uri'
34
26
  expect(() => validate(context)).toThrow(/must match format/)
@@ -53,12 +45,6 @@ describe('registry', () => {
53
45
  expect(() => validate(context)).toThrow(/required property 'registry'/)
54
46
  })
55
47
 
56
- it('should require base', () => {
57
- delete context.registry.base
58
-
59
- expect(() => validate(context)).toThrow(/required property 'base'/)
60
- })
61
-
62
48
  it('should set default platforms', () => {
63
49
  delete context.registry.platforms
64
50
 
@@ -80,9 +66,10 @@ it('should require name as label', () => {
80
66
  expect(() => validate(context)).not.toThrow(/pattern/)
81
67
  })
82
68
 
83
- it('should require packages location', () => {
69
+ it('should set default packages location', () => {
84
70
  delete context.packages
85
- expect(() => validate(context)).toThrow(/required/)
71
+ expect(() => validate(context)).not.toThrow()
72
+ expect(context.packages).toBe('components/*')
86
73
  })
87
74
 
88
75
  it('should require registry url', () => {
@@ -37,7 +37,7 @@ type Receiver = {
37
37
  type Entity = {
38
38
  schema: Object
39
39
  storage?: string
40
- dependent?: boolean
40
+ associated?: boolean
41
41
  }
42
42
 
43
43
  type Declaration = {