@toa.io/core 1.0.0-alpha.24 → 1.0.0-alpha.243
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 +7 -9
- package/src/call.js +6 -0
- package/src/cascade.js +7 -5
- package/src/component.js +15 -18
- package/src/composition.js +1 -1
- package/src/connector.js +10 -12
- package/src/context.js +4 -0
- package/src/contract/contract.js +22 -0
- package/src/contract/reply.js +26 -9
- package/src/contract/request.js +15 -5
- package/src/contract/schemas/query.yaml +14 -1
- package/src/discovery.js +13 -7
- package/src/effect.js +19 -0
- package/src/entities/changeset.js +5 -8
- package/src/entities/entity.js +48 -23
- package/src/entities/factory.js +15 -4
- package/src/entities/newid.js +11 -0
- package/src/entities/set.js +13 -0
- package/src/exceptions.js +26 -19
- package/src/exposition.js +3 -2
- package/src/guard.js +17 -0
- package/src/index.js +6 -0
- package/src/observation.js +1 -9
- package/src/operation.js +28 -7
- package/src/query/options.js +3 -2
- package/src/query.js +3 -1
- package/src/receiver.js +17 -10
- package/src/remote.js +5 -7
- package/src/state.js +69 -33
- package/src/transition.js +8 -19
- package/src/transmission.js +12 -3
- package/src/unmanaged.js +11 -0
- package/test/component.test.js +4 -3
- package/test/connector.fixtures.js +8 -0
- package/test/connector.test.js +20 -0
- package/test/contract/conditions.test.js +5 -5
- package/test/contract/request.test.js +7 -7
- package/test/discovery.test.js +56 -0
- package/test/entities/entity.test.js +0 -45
- package/test/entities/factory.test.js +3 -3
- package/test/state.test.js +0 -14
- package/types/bindings.d.ts +7 -5
- package/types/bridges.ts +3 -0
- package/types/component.d.ts +4 -1
- package/types/extensions.d.ts +6 -3
- package/types/index.ts +1 -0
- package/types/operations.d.ts +6 -0
- package/types/query.d.ts +2 -0
- package/types/remote.d.ts +18 -0
- package/types/request.d.ts +4 -0
- package/types/storages.d.ts +11 -9
- package/src/contract/conditions.js +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/core",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.243",
|
|
4
4
|
"description": "Toa Core",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -21,13 +21,11 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@rsql/parser": "1.2.4",
|
|
24
|
-
"@toa.io/
|
|
25
|
-
"@toa.io/
|
|
26
|
-
"
|
|
27
|
-
"
|
|
24
|
+
"@toa.io/generic": "1.0.0-alpha.225",
|
|
25
|
+
"@toa.io/yaml": "1.0.0-alpha.225",
|
|
26
|
+
"error-value": "0.3.0",
|
|
27
|
+
"openspan": "1.0.0-alpha.173",
|
|
28
|
+
"uuid": "11.1.1"
|
|
28
29
|
},
|
|
29
|
-
"
|
|
30
|
-
"clone-deep": "4.0.1"
|
|
31
|
-
},
|
|
32
|
-
"gitHead": "308058561dfc97337a8b9755f357644e7dba8859"
|
|
30
|
+
"gitHead": "743e8060d35ca750f70487c04aef9698591c5edd"
|
|
33
31
|
}
|
package/src/call.js
CHANGED
|
@@ -18,6 +18,8 @@ class Call extends Connector {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async invoke (request = {}) {
|
|
21
|
+
request.input ??= null
|
|
22
|
+
|
|
21
23
|
this.#contract.fit(request)
|
|
22
24
|
|
|
23
25
|
// avoid validation on the recipient's side
|
|
@@ -37,6 +39,10 @@ class Call extends Connector {
|
|
|
37
39
|
return reply.output
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
|
|
43
|
+
explain () {
|
|
44
|
+
return this.#contract.discovery
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
exports.Call = Call
|
package/src/cascade.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { merge } = require('@toa.io/generic')
|
|
4
3
|
const { Connector } = require('./connector')
|
|
5
4
|
|
|
6
5
|
class Cascade extends Connector {
|
|
7
|
-
#bridges
|
|
6
|
+
// #bridges
|
|
8
7
|
#last
|
|
9
8
|
|
|
10
|
-
constructor (bridges) {
|
|
9
|
+
constructor (bridges, rc) {
|
|
11
10
|
super()
|
|
12
11
|
|
|
13
|
-
this.#bridges = bridges
|
|
12
|
+
// this.#bridges = bridges
|
|
14
13
|
this.#last = bridges[bridges.length - 1]
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
if (rc === undefined)
|
|
16
|
+
this.depends(bridges)
|
|
17
|
+
else
|
|
18
|
+
this.depends(bridges).depends(rc)
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
async run (...args) {
|
package/src/component.js
CHANGED
|
@@ -1,40 +1,37 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const { console } = require('openspan')
|
|
4
5
|
const { Connector } = require('./connector')
|
|
5
|
-
const { NotImplementedException } = require('./exceptions')
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* @implements {toa.core.Component}
|
|
9
|
-
*/
|
|
10
7
|
class Component extends Connector {
|
|
11
8
|
locator
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
/** @protected */
|
|
11
|
+
operations
|
|
14
12
|
|
|
15
13
|
constructor (locator, operations) {
|
|
16
14
|
super()
|
|
17
15
|
|
|
18
16
|
this.locator = locator
|
|
19
|
-
this
|
|
17
|
+
this.operations = operations
|
|
20
18
|
|
|
21
19
|
Object.values(operations).forEach((operation) => this.depends(operation))
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
async
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
async invoke (endpoint, request) {
|
|
23
|
+
assert.ok(endpoint in this.operations,
|
|
24
|
+
`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
console.info(`Runtime '${this.locator.id}' disconnected`)
|
|
30
|
-
}
|
|
26
|
+
const reply = await this.operations[endpoint].invoke(request)
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
if (reply?.exception !== undefined)
|
|
29
|
+
console.error('Failed to execute operation', {
|
|
30
|
+
endpoint: `${this.locator.id}.${endpoint}`,
|
|
31
|
+
exception: reply.exception
|
|
32
|
+
})
|
|
36
33
|
|
|
37
|
-
return
|
|
34
|
+
return reply
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
|
package/src/composition.js
CHANGED
package/src/connector.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { console } = require('
|
|
4
|
-
const { newid } = require('@toa.io/generic')
|
|
3
|
+
const { console } = require('openspan')
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Abstract connections hierarchy
|
|
@@ -26,7 +25,7 @@ class Connector {
|
|
|
26
25
|
connected = false
|
|
27
26
|
|
|
28
27
|
constructor () {
|
|
29
|
-
this.id = this.constructor.name + '#' +
|
|
28
|
+
this.id = this.constructor.name + '#' + Math.random().toString(36).substring(2, 8)
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -86,8 +85,6 @@ class Connector {
|
|
|
86
85
|
|
|
87
86
|
this.#disconnecting = undefined
|
|
88
87
|
|
|
89
|
-
console.debug(`Connecting '${this.id}' with ${this.#dependencies.length} dependencies...`)
|
|
90
|
-
|
|
91
88
|
this.#connecting = (async () => {
|
|
92
89
|
await Promise.all(this.#dependencies.map((connector) => connector.connect()))
|
|
93
90
|
await this.open()
|
|
@@ -100,8 +97,6 @@ class Connector {
|
|
|
100
97
|
await this.disconnect(true)
|
|
101
98
|
throw e
|
|
102
99
|
}
|
|
103
|
-
|
|
104
|
-
console.debug(`Connector '${this.id}' connected`)
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
/**
|
|
@@ -115,7 +110,11 @@ class Connector {
|
|
|
115
110
|
* @returns {Promise<void>}
|
|
116
111
|
*/
|
|
117
112
|
async disconnect (interrupt) {
|
|
118
|
-
|
|
113
|
+
// a connector that has not finished connecting has nothing to close, and
|
|
114
|
+
// awaiting a connection that may never settle outlives any grace period
|
|
115
|
+
const pending = interrupt === true || this.connected === false
|
|
116
|
+
|
|
117
|
+
if (!pending) await this.#connecting
|
|
119
118
|
|
|
120
119
|
if (this.#disconnecting) return this.#disconnecting
|
|
121
120
|
|
|
@@ -132,10 +131,11 @@ class Connector {
|
|
|
132
131
|
const interval = setInterval(() => {
|
|
133
132
|
const delay = +new Date() - start
|
|
134
133
|
|
|
135
|
-
if (delay > DELAY)
|
|
134
|
+
if (delay > DELAY)
|
|
135
|
+
console.warn(`Connector ${this.id} still disconnecting (${delay})`)
|
|
136
136
|
}, DELAY)
|
|
137
137
|
|
|
138
|
-
if (
|
|
138
|
+
if (!pending) await this.close()
|
|
139
139
|
|
|
140
140
|
clearInterval(interval)
|
|
141
141
|
|
|
@@ -145,8 +145,6 @@ class Connector {
|
|
|
145
145
|
})()
|
|
146
146
|
|
|
147
147
|
await this.#disconnecting
|
|
148
|
-
|
|
149
|
-
console.debug(`Connector '${this.id}' disconnected`)
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
async reconnect () {
|
package/src/context.js
CHANGED
|
@@ -6,6 +6,8 @@ const { Connector } = require('./connector')
|
|
|
6
6
|
* @implements {toa.core.Context}
|
|
7
7
|
*/
|
|
8
8
|
class Context extends Connector {
|
|
9
|
+
env
|
|
10
|
+
name
|
|
9
11
|
aspects
|
|
10
12
|
|
|
11
13
|
#local
|
|
@@ -15,6 +17,8 @@ class Context extends Connector {
|
|
|
15
17
|
constructor (local, discover, aspects = []) {
|
|
16
18
|
super()
|
|
17
19
|
|
|
20
|
+
this.env = process.env.TOA_ENV
|
|
21
|
+
this.name = process.env.TOA_CONTEXT
|
|
18
22
|
this.aspects = aspects
|
|
19
23
|
|
|
20
24
|
this.#local = local
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { SystemException } = require('../exceptions')
|
|
4
|
+
|
|
5
|
+
class Contract {
|
|
6
|
+
schema
|
|
7
|
+
|
|
8
|
+
constructor (schema) {
|
|
9
|
+
this.schema = schema
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fit (value) {
|
|
13
|
+
const error = this.schema.fit(value)
|
|
14
|
+
|
|
15
|
+
if (error !== null)
|
|
16
|
+
throw new this.constructor.Exception(error, value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static Exception = SystemException
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.Contract = Contract
|
package/src/contract/reply.js
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const schemas = require('./schemas')
|
|
4
|
-
const {
|
|
4
|
+
const { Contract } = require('./contract')
|
|
5
5
|
const { ResponseContractException } = require('../exceptions')
|
|
6
6
|
|
|
7
|
-
class Reply extends
|
|
7
|
+
class Reply extends Contract {
|
|
8
8
|
static Exception = ResponseContractException
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
* @returns {toa.schema.JSON}
|
|
12
|
-
*/
|
|
13
|
-
static schema (output, error) {
|
|
10
|
+
static schema (output, errors) {
|
|
14
11
|
const schema = { type: 'object', properties: {}, additionalProperties: false }
|
|
15
12
|
|
|
16
|
-
if (output !== undefined)
|
|
13
|
+
if (output !== undefined) {
|
|
14
|
+
if (output.type === 'object')
|
|
15
|
+
output.additionalProperties = true
|
|
16
|
+
else if (output.type === 'array' && output.items?.type === 'object')
|
|
17
|
+
output.items.additionalProperties = true
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
schema.properties.output = output
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (errors !== undefined)
|
|
23
|
+
schema.properties.error = {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
code: {
|
|
27
|
+
enum: errors
|
|
28
|
+
},
|
|
29
|
+
message: {
|
|
30
|
+
type: 'string'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
required: ['code']
|
|
34
|
+
}
|
|
35
|
+
else
|
|
36
|
+
schema.properties.error = schemas.error
|
|
20
37
|
|
|
21
38
|
return schema
|
|
22
39
|
}
|
package/src/contract/request.js
CHANGED
|
@@ -2,9 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
const schemas = require('./schemas')
|
|
4
4
|
const { RequestContractException } = require('../exceptions')
|
|
5
|
-
const {
|
|
5
|
+
const { Contract } = require('./contract')
|
|
6
|
+
|
|
7
|
+
class Request extends Contract {
|
|
8
|
+
/** @readonly */
|
|
9
|
+
discovery = {}
|
|
10
|
+
|
|
11
|
+
constructor (schema, definition) {
|
|
12
|
+
super(schema)
|
|
13
|
+
|
|
14
|
+
for (const key of ['input', 'output', 'errors'])
|
|
15
|
+
if (definition[key] !== undefined)
|
|
16
|
+
this.discovery[key] = definition[key]
|
|
17
|
+
}
|
|
6
18
|
|
|
7
|
-
class Request extends Conditions {
|
|
8
19
|
static Exception = RequestContractException
|
|
9
20
|
|
|
10
21
|
/**
|
|
@@ -13,7 +24,7 @@ class Request extends Conditions {
|
|
|
13
24
|
static schema (definition, entity) {
|
|
14
25
|
const schema = {
|
|
15
26
|
type: 'object',
|
|
16
|
-
properties: { authentic: { type: 'boolean' } },
|
|
27
|
+
properties: { authentic: { type: 'boolean' }, task: { type: 'boolean' } },
|
|
17
28
|
additionalProperties: true
|
|
18
29
|
}
|
|
19
30
|
|
|
@@ -22,9 +33,8 @@ class Request extends Conditions {
|
|
|
22
33
|
if (definition.input !== undefined) {
|
|
23
34
|
schema.properties.input = definition.input
|
|
24
35
|
required.push('input')
|
|
25
|
-
} else
|
|
36
|
+
} else
|
|
26
37
|
schema.properties.input = { type: 'null' }
|
|
27
|
-
}
|
|
28
38
|
|
|
29
39
|
if (entity === undefined)
|
|
30
40
|
definition.query = false
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
type: object
|
|
2
2
|
properties:
|
|
3
|
-
id:
|
|
3
|
+
id:
|
|
4
|
+
type: string
|
|
5
|
+
ids:
|
|
6
|
+
type: array
|
|
7
|
+
uniqueItems: true
|
|
8
|
+
minItems: 1
|
|
9
|
+
items:
|
|
10
|
+
type: string
|
|
4
11
|
version:
|
|
5
12
|
type: integer
|
|
6
13
|
minimum: 0
|
|
7
14
|
criteria:
|
|
8
15
|
type: string
|
|
16
|
+
search:
|
|
17
|
+
type: string
|
|
18
|
+
sample:
|
|
19
|
+
type: number
|
|
9
20
|
omit:
|
|
10
21
|
type: integer
|
|
11
22
|
minimum: 0
|
|
@@ -27,4 +38,6 @@ properties:
|
|
|
27
38
|
type: string
|
|
28
39
|
not:
|
|
29
40
|
const: id
|
|
41
|
+
deleted:
|
|
42
|
+
type: boolean
|
|
30
43
|
additionalProperties: false
|
package/src/discovery.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { console } = require('
|
|
3
|
+
const { console } = require('openspan')
|
|
4
4
|
const { Connector } = require('./connector')
|
|
5
5
|
|
|
6
6
|
class Discovery extends Connector {
|
|
@@ -25,20 +25,26 @@ class Discovery extends Connector {
|
|
|
25
25
|
this.depends(this.#lookups[id])
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const since = Date.now()
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// the wait is unbounded by design, as a dependency may still be starting,
|
|
31
|
+
// so repeating is the only way a component stuck on one stays visible
|
|
32
|
+
const warning = setInterval(() => {
|
|
33
|
+
const waiting = Math.round((Date.now() - since) / 1000)
|
|
34
|
+
|
|
35
|
+
console.warn('Waiting for lookup response', { component: id, waiting })
|
|
36
|
+
}, INTERVAL)
|
|
37
|
+
|
|
38
|
+
warning.unref()
|
|
32
39
|
|
|
33
40
|
const output = await this.#lookups[id].invoke()
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
clearTimeout(timeout)
|
|
42
|
+
clearInterval(warning)
|
|
37
43
|
|
|
38
44
|
return output
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
const
|
|
48
|
+
const INTERVAL = 5000
|
|
43
49
|
|
|
44
50
|
exports.Discovery = Discovery
|
package/src/effect.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Observation } = require('./observation')
|
|
4
|
+
|
|
5
|
+
class Effect extends Observation {
|
|
6
|
+
|
|
7
|
+
async acquire (store) {
|
|
8
|
+
const { query, entity, input } = store.request
|
|
9
|
+
|
|
10
|
+
if (entity === undefined)
|
|
11
|
+
return super.acquire(store)
|
|
12
|
+
|
|
13
|
+
store.scope = await this.scope.ensure(query, entity, input)
|
|
14
|
+
store.state = store.scope.get()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.Effect = Effect
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
merge,
|
|
5
|
-
overwrite,
|
|
6
|
-
newid
|
|
7
|
-
} = require('@toa.io/generic')
|
|
8
3
|
const { EntityContractException } = require('../exceptions')
|
|
9
4
|
|
|
10
5
|
class Changeset {
|
|
@@ -18,7 +13,7 @@ class Changeset {
|
|
|
18
13
|
this.query = query
|
|
19
14
|
|
|
20
15
|
this.#schema = schema
|
|
21
|
-
this.#state =
|
|
16
|
+
this.#state = {}
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
get () {
|
|
@@ -26,10 +21,12 @@ class Changeset {
|
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
set (value) {
|
|
29
|
-
const error = this.#schema.
|
|
24
|
+
const error = this.#schema.match(value)
|
|
30
25
|
|
|
31
|
-
if (error !== null)
|
|
26
|
+
if (error !== null)
|
|
27
|
+
throw new EntityContractException(error, value)
|
|
32
28
|
|
|
29
|
+
delete value._version
|
|
33
30
|
value._updated = Date.now()
|
|
34
31
|
|
|
35
32
|
this.#state = value
|
package/src/entities/entity.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} = require('@toa.io/generic')
|
|
7
|
-
|
|
8
|
-
const { EntityContractException } = require('../exceptions')
|
|
3
|
+
const { difference } = require('@toa.io/generic')
|
|
4
|
+
const { EntityContractException, EntityGuardException } = require('../exceptions')
|
|
5
|
+
const { newid } = require('./newid')
|
|
9
6
|
|
|
10
7
|
class Entity {
|
|
8
|
+
deleted = false
|
|
11
9
|
#schema
|
|
10
|
+
#guards
|
|
12
11
|
#origin = null
|
|
13
12
|
#state
|
|
14
13
|
|
|
15
|
-
constructor (schema, argument) {
|
|
14
|
+
constructor (schema, argument, guards) {
|
|
16
15
|
this.#schema = schema
|
|
16
|
+
this.#guards = guards
|
|
17
17
|
|
|
18
18
|
if (typeof argument === 'object') {
|
|
19
19
|
const object = structuredClone(argument)
|
|
20
|
-
this
|
|
20
|
+
this.#set(object)
|
|
21
21
|
this.#origin = argument
|
|
22
22
|
} else {
|
|
23
23
|
const id = argument === undefined ? newid() : argument
|
|
@@ -29,37 +29,62 @@ class Entity {
|
|
|
29
29
|
return this.#state
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
set (value) {
|
|
33
|
-
|
|
32
|
+
set (value, optional = false) {
|
|
33
|
+
if (!optional)
|
|
34
|
+
this.#guard(value)
|
|
35
|
+
|
|
36
|
+
const error = optional ? this.#schema.fitOptional(value) : this.#schema.fit(value)
|
|
34
37
|
|
|
35
|
-
if (error !== null)
|
|
38
|
+
if (error !== null)
|
|
39
|
+
throw new EntityContractException(error, value)
|
|
36
40
|
|
|
37
41
|
this.#set(value)
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
event () {
|
|
44
|
+
event (input = undefined) {
|
|
41
45
|
return {
|
|
42
46
|
origin: this.#origin,
|
|
43
47
|
state: this.#state,
|
|
44
|
-
changeset: this.#origin === null ? this.#state : difference(this.#origin, this.#state)
|
|
48
|
+
changeset: this.#origin === null ? this.#state : difference(this.#origin, this.#state),
|
|
49
|
+
trailers: this.#state._trailers,
|
|
50
|
+
input
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
#init (id) {
|
|
49
|
-
const value = {
|
|
50
|
-
...this.#schema.defaults({ id }),
|
|
51
|
-
_version: 0,
|
|
52
|
-
_created: Date.now()
|
|
53
|
-
}
|
|
55
|
+
const value = { id, _version: 0 }
|
|
54
56
|
|
|
55
|
-
this
|
|
57
|
+
this.set(value, true)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#guard (value) {
|
|
61
|
+
if (this.#guards === undefined)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
for (const guard of this.#guards) {
|
|
65
|
+
const ok = guard.fit(value, this.#origin)
|
|
66
|
+
|
|
67
|
+
if (ok === false)
|
|
68
|
+
throw new EntityGuardException(guard.name, value)
|
|
69
|
+
}
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
#set (value) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
if (!('_trailers' in value))
|
|
74
|
+
Object.defineProperty(value, '_trailers', {
|
|
75
|
+
writable: false,
|
|
76
|
+
configurable: false,
|
|
77
|
+
enumerable: false,
|
|
78
|
+
value: {}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (!('_created' in value)) {
|
|
82
|
+
value._created = Date.now()
|
|
83
|
+
value._updated ??= value._created
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ('_deleted' in value && value._deleted !== null)
|
|
87
|
+
this.deleted = true
|
|
63
88
|
|
|
64
89
|
if (this.#state !== undefined) {
|
|
65
90
|
value._updated = Date.now()
|
package/src/entities/factory.js
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { newid } = require('./newid')
|
|
3
4
|
const { Entity } = require('./entity')
|
|
4
5
|
const { EntitySet } = require('./set')
|
|
5
6
|
const { Changeset } = require('./changeset')
|
|
6
7
|
|
|
7
8
|
class Factory {
|
|
8
9
|
#schema
|
|
10
|
+
#guards
|
|
9
11
|
|
|
10
|
-
constructor (schema) {
|
|
12
|
+
constructor (schema, guards) {
|
|
11
13
|
this.#schema = schema
|
|
14
|
+
this.#guards = guards
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fit (values) {
|
|
18
|
+
this.#schema.validate({ id: newid(), ...values }, 'Entity')
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
init (id) {
|
|
15
|
-
return new Entity(this.#schema, id)
|
|
22
|
+
return new Entity(this.#schema, id, this.#guards)
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
object (record) {
|
|
19
|
-
return new Entity(this.#schema, record)
|
|
26
|
+
return new Entity(this.#schema, record, this.#guards)
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
objects (recordset) {
|
|
29
|
+
objects (recordset, init) {
|
|
23
30
|
const set = recordset.map((record) => this.object(record))
|
|
24
31
|
|
|
32
|
+
if (init !== undefined)
|
|
33
|
+
for (const id of init)
|
|
34
|
+
set.unshift(this.init(id))
|
|
35
|
+
|
|
25
36
|
return new EntitySet(set)
|
|
26
37
|
}
|
|
27
38
|
|
package/src/entities/set.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { SystemException } = require("../exceptions")
|
|
4
|
+
|
|
3
5
|
class EntitySet {
|
|
4
6
|
#set
|
|
5
7
|
|
|
@@ -10,6 +12,17 @@ class EntitySet {
|
|
|
10
12
|
get () {
|
|
11
13
|
return this.#set.map((entity) => entity.get())
|
|
12
14
|
}
|
|
15
|
+
|
|
16
|
+
set (values) {
|
|
17
|
+
if (values.length !== this.#set.length)
|
|
18
|
+
throw new SystemException('Objects array must not be modified')
|
|
19
|
+
|
|
20
|
+
values.forEach((value, index) => this.#set[index].set(value))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
events (input = undefined) {
|
|
24
|
+
return this.#set.map((entity) => entity.event(input))
|
|
25
|
+
}
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
exports.EntitySet = EntitySet
|