@toa.io/core 1.0.0-alpha.28 → 1.0.0-alpha.283

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 (100) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/package.json +8 -10
  3. package/src/assignment.js +5 -8
  4. package/src/call.js +35 -9
  5. package/src/cascade.js +9 -11
  6. package/src/component.js +61 -21
  7. package/src/composition.js +3 -7
  8. package/src/connector.js +29 -22
  9. package/src/context.js +8 -6
  10. package/src/contract/contract.js +18 -0
  11. package/src/contract/index.js +2 -7
  12. package/src/contract/reply.js +39 -15
  13. package/src/contract/request.js +20 -10
  14. package/src/contract/schemas/index.js +9 -5
  15. package/src/contract/schemas/query.yaml +14 -1
  16. package/src/contract/schemas/source.yaml +23 -0
  17. package/src/discovery.js +15 -13
  18. package/src/effect.js +15 -0
  19. package/src/emission.js +2 -6
  20. package/src/entities/changeset.js +7 -14
  21. package/src/entities/entity.js +75 -29
  22. package/src/entities/factory.js +21 -14
  23. package/src/entities/index.js +1 -5
  24. package/src/entities/newid.js +9 -0
  25. package/src/entities/set.js +13 -4
  26. package/src/event.js +21 -7
  27. package/src/exceptions.js +53 -39
  28. package/src/exposition.js +5 -8
  29. package/src/guard.js +13 -0
  30. package/src/index.js +28 -47
  31. package/src/locator.js +2 -6
  32. package/src/observation.js +3 -15
  33. package/src/operation.js +40 -14
  34. package/src/outbox/index.js +1 -0
  35. package/src/outbox/outbox.js +296 -0
  36. package/src/query/criteria.js +3 -7
  37. package/src/query/options.js +5 -8
  38. package/src/query.js +36 -7
  39. package/src/receiver.js +76 -18
  40. package/src/reflection.js +2 -6
  41. package/src/remote.js +9 -12
  42. package/src/state.js +89 -50
  43. package/src/transition.js +13 -25
  44. package/src/transmission.js +15 -10
  45. package/src/unmanaged.js +7 -0
  46. package/test/call.fixtures.js +8 -12
  47. package/test/call.test.js +21 -9
  48. package/test/component.fixtures.js +5 -8
  49. package/test/component.test.js +22 -10
  50. package/test/connector.fixtures.js +10 -8
  51. package/test/connector.test.js +59 -38
  52. package/test/context.fixtures.js +8 -12
  53. package/test/context.test.js +18 -7
  54. package/test/contract/conditions.test.js +11 -9
  55. package/test/contract/contract.fixtures.js +12 -16
  56. package/test/contract/request.test.js +84 -34
  57. package/test/discovery.test.js +64 -0
  58. package/test/emission.fixtures.js +6 -10
  59. package/test/emission.test.js +29 -12
  60. package/test/entities/entity.fixtures.js +7 -11
  61. package/test/entities/entity.test.js +70 -49
  62. package/test/entities/factory.fixtures.js +22 -10
  63. package/test/entities/factory.test.js +29 -15
  64. package/test/entities/set.fixtures.js +6 -8
  65. package/test/entities/set.test.js +6 -5
  66. package/test/event.fixtures.js +9 -14
  67. package/test/event.test.js +57 -31
  68. package/test/locator.test.js +25 -24
  69. package/test/outbox.test.js +121 -0
  70. package/test/query.fixtures.js +1 -5
  71. package/test/query.test.js +42 -14
  72. package/test/receiver.fixtures.js +9 -12
  73. package/test/receiver.test.js +45 -22
  74. package/test/reflection.test.js +7 -6
  75. package/test/state.fixtures.js +25 -30
  76. package/test/state.test.js +72 -19
  77. package/test/transmission.fixtures.js +4 -6
  78. package/test/transmission.test.js +33 -15
  79. package/types/atomicity.d.ts +58 -0
  80. package/types/bindings.d.ts +8 -6
  81. package/types/bridges.ts +5 -2
  82. package/types/call.d.ts +13 -0
  83. package/types/component.d.ts +7 -4
  84. package/types/context.d.ts +6 -3
  85. package/types/entity.d.ts +4 -4
  86. package/types/event.d.ts +1 -1
  87. package/types/extensions.d.ts +54 -10
  88. package/types/guard.d.ts +5 -0
  89. package/types/index.ts +20 -13
  90. package/types/message.d.ts +1 -0
  91. package/types/operations.d.ts +6 -0
  92. package/types/outbox.d.ts +53 -0
  93. package/types/query.d.ts +2 -0
  94. package/types/receiver.d.ts +2 -2
  95. package/types/reflection.d.ts +1 -1
  96. package/types/remote.d.ts +18 -0
  97. package/types/request.d.ts +38 -6
  98. package/types/state.d.ts +14 -11
  99. package/types/storages.d.ts +36 -11
  100. package/src/contract/conditions.js +0 -21
@@ -1,28 +1,72 @@
1
- import * as _core from './index'
2
- import * as _component from './component'
3
- import * as _context from './context'
4
- import * as _storages from './storages'
5
- import * as _bindings from './bindings'
1
+ import * as _core from './index.js'
2
+ import * as _component from './component.js'
3
+ import * as _context from './context.js'
4
+ import * as _storages from './storages.js'
5
+ import * as _bindings from './bindings.js'
6
+ import { Manifest } from '@toa.io/norm'
6
7
 
7
- export interface Factory{
8
- tenant? (locator: _core.Locator, manifest: object): _core.Connector
8
+ /**
9
+ * What the process hosting an extension provides to it: the counterpart of a component's
10
+ * `Context`. What is returned is a connector the extension depends on.
11
+ */
12
+ export interface Host {
13
+ /** a component of the context, by locator */
14
+ remote (locator: _core.Locator, source?: _core.Source): Promise<_core.Remote>
15
+
16
+ /** a channel of the messaging binding */
17
+ broadcast<T = unknown> (channel: string, group?: string): Promise<_bindings.Broadcast<T>>
18
+
19
+ /** components to run inside the extension's own process */
20
+ composition (paths: string[]): Promise<_core.Connector>
21
+
22
+ /** a consumer of an event of the context, `namespace.component.event` */
23
+ receive (label: string, receiver: _core.Receiver): Promise<_core.Connector>
24
+
25
+ /** what the replicas of one group decide together */
26
+ atom (group: string): _core.atomicity.Atom
27
+ }
28
+
29
+ export interface Factory {
30
+ tenant? (locator: _core.Locator, manifest: object, component: Manifest):
31
+ _core.Connector | Promise<_core.Connector>
9
32
 
10
33
  aspect? (locator: _core.Locator, manifest: object | null): Aspect | Aspect[]
11
34
 
12
- service? (name?: string): _core.Connector | null
35
+ service? (name?: string): _core.Connector | null | Promise<_core.Connector | null>
13
36
 
14
37
  component? (component: _component.Component): _component.Component
15
38
 
16
39
  context? (context: _context.Context): _context.Context
17
40
 
41
+ manage? (composition: _core.Connector): _core.Connector
42
+
18
43
  storage? (storage: _storages.Storage): _storages.Storage
19
44
 
20
- emitter? (emitter: _bindings.Emitter, label: string): _bindings.Emitter
45
+ emitter? (emitter: _bindings.Emitter, label: string, locator: _core.Locator): _bindings.Emitter
21
46
 
22
47
  receiver? (receiver: _core.Receiver, locator: _core.Locator): _core.Receiver
23
48
  }
24
49
 
25
- export interface Aspect extends _core.Connector{
50
+ /**
51
+ * What an extension puts on a component's context, as the extension states it. How it is
52
+ * presented there is the bridge's — a bash bridge has no context to put anything on — and
53
+ * what is declared here is the key and what it holds.
54
+ */
55
+ export interface Contribution {
56
+ /** the key on the context */
57
+ name: string
58
+
59
+ /** what the key holds, as TypeScript */
60
+ type?: string
61
+
62
+ /** what `type` names, by the module it comes from */
63
+ imports?: Record<string, string[]>
64
+
65
+ /** a JSON Schema to read the type from instead, where a component states one */
66
+ schema?: object
67
+ }
68
+
69
+ export interface Aspect extends _core.Connector {
26
70
  name: string
27
71
 
28
72
  invoke (...args: any[]): any
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Runs on every change to an entity's state, before the contract is applied. `false` refuses
3
+ * the change as `EntityGuard`.
4
+ */
5
+ export type Guard<T, C = unknown> = (state: T, origin: T | null, context: C) => boolean
package/types/index.ts CHANGED
@@ -1,14 +1,21 @@
1
- export * as bindings from './bindings'
2
- export * as extensions from './extensions'
3
- export * as storages from './storages'
4
- export * as bridges from './bridges'
5
- export * as operations from './operations'
1
+ export * as bindings from './bindings.js'
2
+ export * as extensions from './extensions.js'
3
+ export * as storages from './storages.js'
4
+ export * as outbox from './outbox.js'
5
+ export * as atomicity from './atomicity.js'
6
+ export * as bridges from './bridges.js'
7
+ export * as operations from './operations.js'
6
8
 
7
- export type { Component } from './component'
8
- export { Connector } from './connector'
9
- export type { Context } from './context'
10
- export type { Exception } from './exception'
11
- export { Locator } from './locator'
12
- export type { Receiver } from './receiver'
13
- export type { Message } from './message'
14
- export type { Request, Query, Reply } from './request'
9
+ export type { Component } from './component.js'
10
+ export type { Remote } from './remote.js'
11
+ export { Connector } from './connector.js'
12
+ export type { Context } from './context.js'
13
+ export type { Exception } from './exception.js'
14
+ export { Locator } from './locator.js'
15
+ export type { Receiver } from './receiver.js'
16
+ export type { Message } from './message.js'
17
+ export type { Maybe, Request, Query, Reply, RemoteError, Source } from './request.js'
18
+ export type { Event } from './state.js'
19
+ export type { Guard } from './guard.js'
20
+ export type { Call, Observation, Transition } from './call.js'
21
+ export type { Contribution } from './extensions.js'
@@ -1,3 +1,4 @@
1
1
  export interface Message<T = any> {
2
2
  payload: T
3
+ telemetry?: string // W3C traceparent
3
4
  }
@@ -1,2 +1,8 @@
1
+ import { Request } from './request.js'
2
+
1
3
  export type type = 'transition' | 'observation' | 'assignment' | 'computation' | 'effect'
2
4
  export type scope = 'object' | 'objects' | 'changeset'
5
+
6
+ export class Operation {
7
+ invoke<T = any> (request: Request): Promise<T>
8
+ }
@@ -0,0 +1,53 @@
1
+ import * as _state from './state.js'
2
+ import * as _atomicity from './atomicity.js'
3
+
4
+ declare namespace toa.core {
5
+
6
+ namespace outbox {
7
+
8
+ /**
9
+ * The intent to publish, committed with the state change it belongs to. Everything about
10
+ * it is core's: the storage writes it and, where the images are the write's own, fills
11
+ * `event.origin` and `event.state` in.
12
+ */
13
+ interface Row {
14
+ id: string
15
+
16
+ /** which replica pumps this row; carries no other meaning, and no ordering */
17
+ lane: number
18
+
19
+ published: boolean
20
+
21
+ /** not before this */
22
+ pending: number
23
+
24
+ event: _state.Event
25
+ }
26
+
27
+ /** What a storage offers when it can commit a row atomically with the entity. */
28
+ interface Storage {
29
+ insert (row: Row, session?: unknown): Promise<void>
30
+
31
+ insertMany (rows: Row[], session?: unknown): Promise<void>
32
+
33
+ /**
34
+ * One page of what is due, still unpublished, and in one of the given lanes, in the
35
+ * order the rows were written. `after` continues from the last id of the page before.
36
+ */
37
+ pending (lanes: number[], now: number, limit: number, after?: string): Promise<Row[]>
38
+
39
+ settle (ids: string[]): Promise<void>
40
+ }
41
+
42
+ }
43
+
44
+ }
45
+
46
+ export type Row = toa.core.outbox.Row
47
+ export type Storage = toa.core.outbox.Storage
48
+
49
+ /**
50
+ * A lane is a slot of `atomicity`: which replica pumps a row, and nothing else. The outbox is
51
+ * the first thing to claim slots this way, not the last.
52
+ */
53
+ export type Atom = _atomicity.Atom
package/types/query.d.ts CHANGED
@@ -4,6 +4,8 @@ declare namespace toa.core {
4
4
  id?: string
5
5
  version?: number
6
6
  criteria?: Object
7
+ search?: string
8
+ sample?: number
7
9
  omit?: number
8
10
  limit?: number
9
11
  sort?: string[]
@@ -1,5 +1,5 @@
1
- import * as _message from './message'
2
- import * as _connector from './connector'
1
+ import * as _message from './message.js'
2
+ import * as _connector from './connector.js'
3
3
 
4
4
  declare namespace toa.core {
5
5
 
@@ -1,4 +1,4 @@
1
- import * as _core from './connector'
1
+ import * as _core from './connector.js'
2
2
 
3
3
  declare namespace toa.core {
4
4
 
@@ -0,0 +1,18 @@
1
+ import { Component } from './component.js'
2
+
3
+ export class Remote extends Component {
4
+ explain (endpoint: string): Promise<Explanation>
5
+ }
6
+
7
+ interface Explanation {
8
+ input: Schema | null
9
+ output: Schema | null
10
+ errors?: string[]
11
+ }
12
+
13
+ interface Schema {
14
+ type: string
15
+ properties: {
16
+ [key: string]: Schema
17
+ }
18
+ }
@@ -1,21 +1,53 @@
1
- import { Exception } from './exception'
1
+ import { Exception } from './exception.js'
2
2
 
3
- export interface Query {
3
+ /**
4
+ * What a call asks for. `Entity` is the record it is about, which narrows the projection;
5
+ * left out, any name is accepted.
6
+ */
7
+ export interface Query<Entity = any> {
4
8
  id?: string
9
+ ids?: Array<string>
5
10
  criteria?: string
11
+ search?: string
12
+ sample?: number
6
13
  omit?: number
7
14
  limit?: number
8
15
  sort?: Array<string>
9
- projection?: Array<string>
16
+ projection?: Array<string & keyof Entity>
10
17
  version?: number
18
+ deleted?: boolean
11
19
  }
12
20
 
13
- export interface Request {
14
- input?: any
15
- query?: Query
21
+ /**
22
+ * Origin of a call. Stamped by the caller, sanitized by the request contract.
23
+ */
24
+ export type Source =
25
+ | { namespace: string, component: string, operation: string }
26
+ | { namespace: string, component: string, event: string }
27
+ | { service: string }
28
+
29
+ export interface Request<Input = any, Entity = any> {
30
+ input?: Input
31
+ query?: Query<Entity>
32
+ /** What the operation acquires, where the caller supplies it rather than the storage. */
33
+ entity?: Entity
16
34
  authentic?: boolean
35
+ task?: boolean
36
+ telemetry?: string // W3C traceparent
37
+ source?: Source
38
+ }
39
+
40
+ /**
41
+ * An error an operation declares and returns. A call resolves to it rather than throwing:
42
+ * only an exception is thrown.
43
+ */
44
+ export interface RemoteError<Code extends string = string> extends Error {
45
+ code: Code
17
46
  }
18
47
 
48
+ /** What an operation returns where it may refuse: the value, or the error it refused with. */
49
+ export type Maybe<T> = T | Error
50
+
19
51
  export interface Reply {
20
52
  output?: any
21
53
  error?: object
package/types/state.d.ts CHANGED
@@ -1,14 +1,17 @@
1
- import * as _entity from './entity'
2
- import * as _storages from './storages'
1
+ import * as _entity from './entity.js'
2
+ import * as _storages from './storages.js'
3
3
 
4
4
  declare namespace toa.core {
5
5
 
6
6
  namespace transition {
7
7
 
8
- type Event = {
9
- origin: Object
10
- state: Object
11
- changeset: Object
8
+ type Event<State = Object, Trailers = Object> = {
9
+ /** the pre-image; null when the entity did not exist before */
10
+ origin: State | null
11
+ state: State
12
+ /** out-of-band values an algorithm wrote into `state._trailers`; must be serializable */
13
+ trailers?: Trailers
14
+ input?: Object
12
15
  }
13
16
 
14
17
  }
@@ -16,20 +19,20 @@ declare namespace toa.core {
16
19
  interface State {
17
20
  init(id: string): _entity.Entity
18
21
 
19
- object(query: _storages.Query): Promise<_entity.Entity>
22
+ object(query: _storages.Query, mutable?: boolean): Promise<_entity.Entity>
20
23
 
21
- objects(query: _storages.Query): Promise<_entity.Entity[]>
24
+ objects(query: _storages.Query, mutable?: boolean): Promise<_entity.Entity[]>
22
25
 
23
26
  changeset(query: _storages.Query): _entity.Changeset
24
27
 
25
28
  none(): null
26
29
 
27
- commit(entity: _entity.Entity): Promise<boolean>
30
+ commit(entity: _entity.Entity, input?: Object): Promise<boolean>
28
31
 
29
- apply(changeset: _entity.Changeset): Promise<void>
32
+ apply(changeset: _entity.Changeset, input?: Object): Promise<_storages.Record>
30
33
  }
31
34
 
32
35
  }
33
36
 
34
37
  export type State = toa.core.State
35
- export type Event = toa.core.transition.Event
38
+ export type Event<State = Object, Trailers = Object> = toa.core.transition.Event<State, Trailers>
@@ -1,7 +1,8 @@
1
1
  // noinspection ES6UnusedImports
2
2
 
3
- import { Locator } from './locator'
4
- import { Connector } from './connector'
3
+ import { Locator } from './locator.js'
4
+ import { Connector } from './connector.js'
5
+ import * as outbox from './outbox.js'
5
6
 
6
7
  declare namespace toa.core {
7
8
 
@@ -30,36 +31,60 @@ declare namespace toa.core {
30
31
  id?: string
31
32
  version?: number
32
33
  criteria?: ast.Node
34
+ search?: string
35
+ sample?: number
33
36
  options?: Object
34
37
  }
35
38
 
36
39
  interface Migration {
37
- disconnect(): Promise<void>
40
+ disconnect (): Promise<void>
38
41
 
39
- database(name: string): Promise<void>
42
+ database (name: string): Promise<void>
40
43
 
41
- table(database: string, locator: Locator, schema: Object, reset?: boolean): Promise<string>
44
+ table (database: string, locator: Locator, schema: Object, reset?: boolean): Promise<string>
45
+ }
46
+
47
+ interface Options {
48
+ /** whether this component publishes anything, and so needs an outbox */
49
+ outbox?: boolean
42
50
  }
43
51
 
44
52
  interface Factory {
45
- storage(locator: Locator, properties?: object): Storage
53
+ storage (locator: Locator, properties?: object, options?: Options): Storage
46
54
 
47
- migration?(driver?: string): Migration
55
+ migration? (driver?: string): Migration
48
56
  }
49
57
  }
50
58
 
51
59
  interface Storage extends Connector {
52
60
  // object observation
53
- get?(query: storages.Query): Promise<storages.Record | null>
61
+ get? (query: storages.Query): Promise<storages.Record | null>
54
62
 
55
63
  // objects observation
56
- find?(query: storages.Query): Promise<storages.Record[]>
64
+ find? (query: storages.Query): Promise<storages.Record[]>
57
65
 
58
66
  // commit
59
- store?(record: storages.Record): Promise<boolean>
67
+ store? (record: storages.Record, row?: outbox.Row): Promise<boolean>
68
+
69
+ // mass commit
70
+ massStore? (records: storages.Record[], rows?: outbox.Row[]): Promise<boolean>
60
71
 
61
72
  // assignment
62
- upsert?(query: storages.Query, changeset: Object, insert: storages.Record): Promise<storages.Record>
73
+ upsert? (query: storages.Query, changeset: Object, row?: outbox.Row): Promise<storages.Record>
74
+
75
+ // atomic get-or-create
76
+ ensure? (query: storages.Query, properties: Object, record: storages.Record, row?: outbox.Row): Promise<storages.Record>
77
+
78
+ /**
79
+ * Present only where a row can be committed atomically with the entity. Its absence is
80
+ * what makes the runtime fall back to publishing inline, so a storage that cannot do
81
+ * this must not offer it: a row written outside the transaction would be a second write
82
+ * with a crash window in front of it, which is the defect the outbox exists to close.
83
+ *
84
+ * The row schema is the connector's own — it also owns `pending` and `settle` — and
85
+ * never crosses this boundary.
86
+ */
87
+ outbox?: outbox.Storage
63
88
  }
64
89
 
65
90
  }
@@ -1,21 +0,0 @@
1
- 'use strict'
2
-
3
- const { SystemException } = require('../exceptions')
4
-
5
- class Conditions {
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) throw new this.constructor.Exception(error)
16
- }
17
-
18
- static Exception = SystemException
19
- }
20
-
21
- exports.Conditions = Conditions