@toa.io/norm 1.0.0-alpha.4 → 1.0.0-alpha.41

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.4",
3
+ "version": "1.0.0-alpha.41",
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,11 @@
20
20
  "test": "echo \"Error: run tests from root\" && exit 1"
21
21
  },
22
22
  "dependencies": {
23
- "@toa.io/core": "1.0.0-alpha.4",
24
- "@toa.io/generic": "1.0.0-alpha.4",
25
- "@toa.io/schema": "1.0.0-alpha.4",
26
- "@toa.io/schemas": "1.0.0-alpha.4",
27
- "@toa.io/yaml": "1.0.0-alpha.4"
23
+ "@toa.io/core": "1.0.0-alpha.41",
24
+ "@toa.io/generic": "1.0.0-alpha.41",
25
+ "@toa.io/schema": "1.0.0-alpha.41",
26
+ "@toa.io/schemas": "1.0.0-alpha.41",
27
+ "@toa.io/yaml": "1.0.0-alpha.41"
28
28
  },
29
- "gitHead": "80a91c0d9c167484247a91e69a0c0a3c344f90d0"
29
+ "gitHead": "3b338a3f21ff9113cefa75d7789d9267917d41e3"
30
30
  }
@@ -1,19 +1,45 @@
1
1
  'use strict'
2
2
 
3
- function version (manifest) {
4
- if (manifest.version === undefined) {
5
- const bridge = require(manifest.bridge)
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')
6
8
 
7
- if ('version' in bridge)
8
- manifest.version = bridge.version(manifest)
9
- }
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
+ }
10
18
 
11
- if (manifest.version === undefined) {
12
- console.warn(`Component '${manifest.namespace ? manifest.namespace + '.' : ''}${manifest.name}' has no version`)
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)
13
25
 
14
- manifest.version = Math.random().toString(36).slice(2)
26
+ if (stat.isFile()) {
27
+ const stream = createReadStream(path)
28
+
29
+ stream.pipe(hash, { end: false })
30
+
31
+ return await once(stream, 'end')
15
32
  }
16
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
+ }
17
43
  }
18
44
 
19
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
@@ -4,8 +4,11 @@ const { hash } = require('@toa.io/generic')
4
4
  const assert = require('node:assert')
5
5
 
6
6
  // these defaults are required before validation
7
- const defaults = (manifest) => {
8
- if (manifest.name === undefined) manifest.name = protoName(manifest)
7
+ const defaults = (manifest, proto) => {
8
+ if (manifest.name === undefined)
9
+ if (proto) manifest.name = protoName(manifest)
10
+ else nameAfterDir(manifest)
11
+
9
12
  if (manifest.bindings === undefined) manifest.bindings = ['@toa.io/bindings.amqp']
10
13
  if (manifest.bridge === undefined) manifest.bridge = '@toa.io/bridges.node'
11
14
 
@@ -24,4 +27,13 @@ function protoName (manifest) {
24
27
  return 'proto' + hash(manifest.path)
25
28
  }
26
29
 
30
+ function nameAfterDir (manifest) {
31
+ const parts = manifest.path.split('/')
32
+ const dirname = parts[parts.length - 1]
33
+ const [name, namespace] = dirname.split('.').reverse()
34
+
35
+ manifest.name = name
36
+ manifest.namespace = namespace
37
+ }
38
+
27
39
  exports.defaults = defaults
@@ -11,7 +11,7 @@ const {
11
11
  version
12
12
  } = require('./.expand')
13
13
 
14
- const expand = (manifest) => {
14
+ async function expand (manifest) {
15
15
  entity(manifest)
16
16
  bridge(manifest)
17
17
  operations(manifest)
@@ -19,7 +19,8 @@ const expand = (manifest) => {
19
19
  receivers(manifest)
20
20
  properties(manifest)
21
21
  extensions(manifest)
22
- version(manifest)
22
+
23
+ await version(manifest)
23
24
  }
24
25
 
25
26
  exports.expand = expand
@@ -79,8 +79,24 @@ properties:
79
79
  propertyNames:
80
80
  oneOf:
81
81
  - $ref: '#/definitions/name'
82
- - enum: [_version]
83
- dependent:
82
+ - enum: [_version, _created, _updated, _deleted]
83
+ unique:
84
+ type: object
85
+ patternProperties:
86
+ '.*':
87
+ type: array
88
+ items:
89
+ type: string
90
+ index:
91
+ type: object
92
+ patternProperties:
93
+ '.*':
94
+ type: object
95
+ patternProperties:
96
+ '.*':
97
+ type: string
98
+ enum: [asc, desc, hash]
99
+ associated:
84
100
  type: boolean
85
101
  default: false
86
102
  custom:
@@ -109,7 +125,7 @@ properties:
109
125
  type:
110
126
  enum: [transition, observation, assignment, computation, effect]
111
127
  scope:
112
- enum: [object, objects, changeset, none]
128
+ enum: [object, objects, changeset, stream, none]
113
129
  concurrency:
114
130
  enum: [none, retry]
115
131
  forward:
@@ -158,7 +174,7 @@ properties:
158
174
  then:
159
175
  properties:
160
176
  scope:
161
- enum: [object, objects, none]
177
+ enum: [object, objects, stream, none]
162
178
  query:
163
179
  not:
164
180
  const: false
@@ -189,11 +205,8 @@ properties:
189
205
  then:
190
206
  properties:
191
207
  scope:
192
- const: none
193
208
  default: none
194
- query:
195
- const: false
196
- default: false
209
+ enum: [object, objects, stream, none]
197
210
  additionalProperties: false
198
211
  additionalProperties: false
199
212
 
@@ -244,6 +257,8 @@ properties:
244
257
  adaptive:
245
258
  type: boolean
246
259
  default: false
260
+ arguments:
261
+ type: array
247
262
  required: [operation]
248
263
  additionalProperties: false
249
264
 
@@ -11,6 +11,7 @@ properties:
11
11
  type: string
12
12
  packages:
13
13
  type: string
14
+ default: components/*
14
15
  build:
15
16
  type: object
16
17
  properties:
@@ -64,5 +65,5 @@ properties:
64
65
  required: [name, components]
65
66
  annotations:
66
67
  type: object
67
- required: [runtime, name, packages, registry]
68
+ required: [runtime, name, registry]
68
69
  additionalProperties: false
package/src/component.js CHANGED
@@ -20,7 +20,7 @@ const {
20
20
  const component = async (path) => {
21
21
  const manifest = await load(path)
22
22
 
23
- normalize(manifest)
23
+ normalize(manifest, path)
24
24
  validate(manifest)
25
25
  extensions(manifest)
26
26
 
@@ -29,7 +29,7 @@ const component = async (path) => {
29
29
  return manifest
30
30
  }
31
31
 
32
- const load = async (path, base) => {
32
+ const load = async (path, base, proto = false) => {
33
33
  if (base !== undefined) path = find(path, base, MANIFEST)
34
34
 
35
35
  const file = join(path, MANIFEST)
@@ -37,13 +37,13 @@ const load = async (path, base) => {
37
37
 
38
38
  manifest.path = path
39
39
 
40
- defaults(manifest)
41
- expand(manifest)
40
+ defaults(manifest, proto)
41
+ await expand(manifest)
42
42
 
43
43
  await merge(path, manifest)
44
44
 
45
45
  if (manifest.prototype !== null) {
46
- const prototype = await load(manifest.prototype, path)
46
+ const prototype = await load(manifest.prototype, path, true)
47
47
 
48
48
  collapse(manifest, prototype)
49
49
  }
@@ -112,15 +112,18 @@ describe('entity', () => {
112
112
  })
113
113
 
114
114
  it('should allow default id', () => {
115
- manifest.entity.schema.properties.id = { type: 'string', pattern: '^[a-fA-F0-9]+$' }
115
+ manifest.entity.schema.properties.id = {
116
+ type: 'string',
117
+ pattern: '^[a-fA-F0-9]+$'
118
+ }
116
119
  expect(() => validate(manifest)).not.toThrow()
117
120
  })
118
121
  })
119
122
 
120
- describe('dependent', () => {
123
+ describe('associated', () => {
121
124
  it('should provide default', () => {
122
125
  expect(() => validate(manifest)).not.toThrow()
123
- expect(manifest.entity.dependent).toBe(false)
126
+ expect(manifest.entity.associated).toBe(false)
124
127
  })
125
128
  })
126
129
  })
@@ -173,8 +176,7 @@ describe('operations', () => {
173
176
  })
174
177
 
175
178
  it.each([
176
- ['computation', 'compute'],
177
- ['effect', 'affect']
179
+ ['computation', 'compute']
178
180
  ])('should set query: false for %s', async (_, operation) => {
179
181
  validate(manifest)
180
182
 
@@ -72,9 +72,10 @@ it('should require name as label', () => {
72
72
  expect(() => validate(context)).not.toThrow(/pattern/)
73
73
  })
74
74
 
75
- it('should require packages location', () => {
75
+ it('should set default packages location', () => {
76
76
  delete context.packages
77
- expect(() => validate(context)).toThrow(/required/)
77
+ expect(() => validate(context)).not.toThrow()
78
+ expect(context.packages).toBe('components/*')
78
79
  })
79
80
 
80
81
  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 = {