@toa.io/core 1.0.0-alpha.292 → 1.0.0-alpha.299

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 (138) hide show
  1. package/package.json +10 -4
  2. package/transpiled/call.js +12 -0
  3. package/transpiled/call.js.map +1 -1
  4. package/transpiled/component.js +31 -4
  5. package/transpiled/component.js.map +1 -1
  6. package/transpiled/context.d.ts +5 -0
  7. package/transpiled/context.js +6 -0
  8. package/transpiled/context.js.map +1 -1
  9. package/transpiled/emission.d.ts +4 -2
  10. package/transpiled/emission.js +4 -2
  11. package/transpiled/emission.js.map +1 -1
  12. package/transpiled/entities/entity.js +34 -6
  13. package/transpiled/entities/entity.js.map +1 -1
  14. package/transpiled/entities/factory.d.ts +5 -0
  15. package/transpiled/entities/factory.js +6 -2
  16. package/transpiled/entities/factory.js.map +1 -1
  17. package/transpiled/event.d.ts +2 -2
  18. package/transpiled/event.js +6 -1
  19. package/transpiled/event.js.map +1 -1
  20. package/transpiled/exceptions.d.ts +19 -0
  21. package/transpiled/exceptions.js +74 -2
  22. package/transpiled/exceptions.js.map +1 -1
  23. package/transpiled/index.d.ts +2 -1
  24. package/transpiled/index.js +2 -1
  25. package/transpiled/index.js.map +1 -1
  26. package/transpiled/{outbox/outbox.d.ts → outbox.d.ts} +6 -7
  27. package/transpiled/outbox.js +424 -0
  28. package/transpiled/outbox.js.map +1 -0
  29. package/transpiled/query/options.js +1 -1
  30. package/transpiled/query/options.js.map +1 -1
  31. package/transpiled/receiver.js +14 -1
  32. package/transpiled/receiver.js.map +1 -1
  33. package/transpiled/remote.js +2 -3
  34. package/transpiled/remote.js.map +1 -1
  35. package/transpiled/state.d.ts +1 -1
  36. package/transpiled/trail.d.ts +49 -0
  37. package/transpiled/trail.js +98 -0
  38. package/transpiled/trail.js.map +1 -0
  39. package/transpiled/types/bindings.d.ts +22 -0
  40. package/transpiled/types/extensions.d.ts +13 -2
  41. package/transpiled/types/message.d.ts +5 -0
  42. package/transpiled/types/outbox.d.ts +29 -3
  43. package/transpiled/types/request.d.ts +5 -0
  44. package/transpiled/types/storages.d.ts +20 -0
  45. package/CHANGELOG.md +0 -43
  46. package/source/assignment.ts +0 -27
  47. package/source/call.ts +0 -63
  48. package/source/cascade.ts +0 -32
  49. package/source/component.ts +0 -83
  50. package/source/composition.ts +0 -27
  51. package/source/connector.ts +0 -217
  52. package/source/context.ts +0 -68
  53. package/source/contract/contract.ts +0 -23
  54. package/source/contract/index.ts +0 -2
  55. package/source/contract/reply.ts +0 -56
  56. package/source/contract/request.ts +0 -140
  57. package/source/contract/schemas.ts +0 -39
  58. package/source/discovery.ts +0 -53
  59. package/source/effect.ts +0 -14
  60. package/source/emission.ts +0 -21
  61. package/source/entities/changeset.ts +0 -37
  62. package/source/entities/entity.ts +0 -137
  63. package/source/entities/factory.ts +0 -52
  64. package/source/entities/index.ts +0 -4
  65. package/source/entities/newid.ts +0 -9
  66. package/source/entities/set.ts +0 -27
  67. package/source/event.ts +0 -57
  68. package/source/exceptions.ts +0 -133
  69. package/source/exposition.ts +0 -33
  70. package/source/guard.ts +0 -16
  71. package/source/index.ts +0 -36
  72. package/source/locator.ts +0 -37
  73. package/source/observation.ts +0 -16
  74. package/source/operation.ts +0 -139
  75. package/source/outbox/index.ts +0 -1
  76. package/source/outbox/outbox.ts +0 -341
  77. package/source/query/criteria.ts +0 -90
  78. package/source/query/options.ts +0 -35
  79. package/source/query.ts +0 -67
  80. package/source/receiver.ts +0 -120
  81. package/source/reflection.ts +0 -20
  82. package/source/remote.ts +0 -20
  83. package/source/state.ts +0 -179
  84. package/source/transition.ts +0 -79
  85. package/source/transmission.ts +0 -38
  86. package/source/types/atomicity.ts +0 -63
  87. package/source/types/bindings.ts +0 -59
  88. package/source/types/bridges.ts +0 -54
  89. package/source/types/extensions.ts +0 -89
  90. package/source/types/index.ts +0 -14
  91. package/source/types/message.ts +0 -5
  92. package/source/types/operations.ts +0 -31
  93. package/source/types/outbox.ts +0 -35
  94. package/source/types/receiver.ts +0 -10
  95. package/source/types/request.ts +0 -54
  96. package/source/types/state.ts +0 -9
  97. package/source/types/storages.ts +0 -134
  98. package/source/unmanaged.ts +0 -8
  99. package/test/call.fixtures.js +0 -25
  100. package/test/call.test.js +0 -79
  101. package/test/component.fixtures.js +0 -18
  102. package/test/component.test.js +0 -57
  103. package/test/connector.fixtures.js +0 -56
  104. package/test/connector.test.js +0 -266
  105. package/test/context.fixtures.js +0 -14
  106. package/test/context.test.js +0 -40
  107. package/test/contract/conditions.test.js +0 -32
  108. package/test/contract/contract.fixtures.js +0 -28
  109. package/test/contract/request.test.js +0 -196
  110. package/test/discovery.test.js +0 -71
  111. package/test/emission.fixtures.js +0 -13
  112. package/test/emission.test.js +0 -60
  113. package/test/entities/entity.fixtures.js +0 -24
  114. package/test/entities/entity.test.js +0 -137
  115. package/test/entities/factory.fixtures.js +0 -31
  116. package/test/entities/factory.test.js +0 -99
  117. package/test/entities/set.fixtures.js +0 -9
  118. package/test/entities/set.test.js +0 -13
  119. package/test/event.fixtures.js +0 -24
  120. package/test/event.test.js +0 -162
  121. package/test/locator.test.js +0 -120
  122. package/test/outbox.test.js +0 -202
  123. package/test/query.fixtures.js +0 -96
  124. package/test/query.test.js +0 -183
  125. package/test/receiver.fixtures.js +0 -20
  126. package/test/receiver.test.js +0 -144
  127. package/test/reflection.test.js +0 -31
  128. package/test/state.fixtures.js +0 -49
  129. package/test/state.test.js +0 -162
  130. package/test/transmission.fixtures.js +0 -13
  131. package/test/transmission.test.js +0 -77
  132. package/transpiled/outbox/index.d.ts +0 -1
  133. package/transpiled/outbox/index.js +0 -2
  134. package/transpiled/outbox/index.js.map +0 -1
  135. package/transpiled/outbox/outbox.js +0 -268
  136. package/transpiled/outbox/outbox.js.map +0 -1
  137. package/tsconfig.json +0 -10
  138. package/tsconfig.tsbuildinfo +0 -1
@@ -1,217 +0,0 @@
1
- import { console } from 'openspan'
2
- import { environment } from '@toa.io/generic'
3
- import type { Locator } from './locator.js'
4
-
5
- /** Abstract connections hierarchy */
6
- export class Connector {
7
- #dependencies: Connector[] = []
8
- #links: Connector[] = []
9
- #connecting: Promise<void> | undefined
10
- #disconnecting: Promise<void> | undefined
11
- #discarded: boolean = false
12
-
13
- public readonly id: string
14
- public connected: boolean = false
15
-
16
- public constructor() {
17
- this.id = this.constructor.name + '#' + Math.random().toString(36).substring(2, 8)
18
- }
19
-
20
- /**
21
- * Creates a dependency and backlink with another Connector or a set of Connectors
22
- *
23
- * See .connect() and .disconnect()
24
- *
25
- */
26
- public depends(connector: Connector | Connector[]): Connector {
27
- let next: Connector
28
-
29
- if (connector instanceof Array) {
30
- if (connector.length === 0) throw new Error('Connectors array must not be empty')
31
-
32
- if (connector.length > 1) {
33
- next = new Connector()
34
-
35
- for (const item of connector) {
36
- this.#dependencies.push(item)
37
- item.depends(next)
38
- }
39
- } else next = connector[0]
40
- } else next = connector
41
-
42
- this.#dependencies.push(next)
43
- next.link(this)
44
-
45
- // See .#discard()
46
- if (this.#disconnecting !== undefined) next.#discard()
47
-
48
- return next
49
- }
50
-
51
- /**
52
- * Disconnects, now and again if a connection is still to land.
53
- *
54
- * A dependency taken on after its dependant was disconnected — a lookup that returns
55
- * once the teardown walk has gone by — is one nothing is left to take down, as the
56
- * walk that would have reached it is over. One that stays connected holds open
57
- * everything it depends on with it: a dependency reads `connected` of every dependant
58
- * before it closes, and a dependant that is connected for good is a dependency that
59
- * never closes.
60
- */
61
- #discard(): void {
62
- this.#discarded = true
63
-
64
- // one that is up already, or on its way, is taken down here; one that is not is
65
- // taken down by the connection when it comes, and until then there is nothing to do
66
- if (this.#connecting !== undefined) void this.#drop()
67
- }
68
-
69
- /**
70
- * Creates a backlink to another Connector
71
- *
72
- * See .connect() and .disconnect()
73
- *
74
- */
75
- public link(connector: Connector): void {
76
- this.#links.push(connector)
77
- }
78
-
79
- /**
80
- * Connects dependants then self
81
- *
82
- * In case of exception disconnects with current connection interruption
83
- *
84
- * Method is idempotent
85
- *
86
- */
87
- public async connect(): Promise<void> {
88
- if (this.#connecting) return this.#connecting
89
-
90
- this.#disconnecting = undefined
91
-
92
- const work = async () => {
93
- await Promise.all(this.#dependencies.map((connector) => connector.connect()))
94
- await this.open()
95
- }
96
-
97
- // anonymous grouping nodes are not worth a span
98
- const connecting =
99
- TRACE_BOOT && this.constructor.name !== 'Connector'
100
- ? console.span(
101
- {
102
- name: `connect ${this.constructor.name}`,
103
- // a connector that has a locator is named by it; a grouping node has none
104
- attributes: { id: (this as { locator?: Locator }).locator?.id ?? this.id }
105
- },
106
- work
107
- )
108
- : work()
109
-
110
- this.#connecting = connecting
111
-
112
- try {
113
- await connecting
114
-
115
- /*
116
- * A disconnection that did not wait for this connection emptied `#connecting` to
117
- * say so. Reporting `connected` now would report it of a connector nothing takes
118
- * down again — its `#disconnecting` has settled, so a later call returns at once —
119
- * and what it depends on reads that of every dependant before it closes.
120
- */
121
- if (this.#connecting === connecting) this.connected = true
122
- } catch (e) {
123
- await this.disconnect(true)
124
- throw e
125
- }
126
-
127
- // a connection asked for before this one was discarded, landing after. See .#discard()
128
- if (this.#discarded) await this.#drop()
129
- }
130
-
131
- /** Disconnects a discarded connector, saying so rather than throwing at whoever is not waiting. */
132
- async #drop(): Promise<void> {
133
- this.#discarded = false
134
-
135
- try {
136
- await this.disconnect()
137
- } catch (error) {
138
- console.error('Discarded connector failed to disconnect', { id: this.id, error })
139
- }
140
- }
141
-
142
- /**
143
- * Disconnects self then dependants
144
- *
145
- * Does nothing if there are connected linked Connectors
146
- *
147
- * Method is idempotent
148
- *
149
- */
150
- public async disconnect(interrupt?: boolean): Promise<void> {
151
- // a connector that has not finished connecting has nothing to close, and
152
- // awaiting a connection that may never settle outlives any grace period
153
- const pending = interrupt === true || this.connected === false
154
-
155
- if (!pending) await this.#connecting
156
-
157
- if (this.#disconnecting) return this.#disconnecting
158
-
159
- const linked = this.#links.reduce((acc, parent) => acc || parent.connected, false)
160
-
161
- if (linked && interrupt !== true) return
162
-
163
- this.#disconnecting = (async () => {
164
- const start = +new Date()
165
-
166
- const interval = setInterval(() => {
167
- const delay = +new Date() - start
168
-
169
- if (delay > DELAY)
170
- console.warn(`Connector ${this.id} still disconnecting (${delay})`)
171
- }, DELAY)
172
-
173
- if (!pending) await this.close()
174
-
175
- // said once the closing is done rather than when it starts: a dependant that is
176
- // still closing is still using what it depends on, and the check above reads this
177
- // of every dependant. Saying it early lets the first to start tear down a shared
178
- // dependency under the others.
179
- this.connected = false
180
- this.#connecting = undefined
181
-
182
- clearInterval(interval)
183
-
184
- await Promise.all(this.#dependencies.map((connector) => connector.disconnect()))
185
-
186
- await this.dispose()
187
- })()
188
-
189
- await this.#disconnecting
190
- }
191
-
192
- public async reconnect(): Promise<void> {
193
- await this.disconnect()
194
- await this.connect()
195
- }
196
-
197
- public debug(node: Record<string, any> = {}): Record<string, any> {
198
- node[this.id] = { connected: this.connected }
199
-
200
- if (this.#dependencies.length > 0)
201
- for (const connector of this.#dependencies) connector.debug?.(node[this.id])
202
-
203
- return node
204
- }
205
-
206
- /** Called on connection */
207
- protected open(): Promise<void> | void {}
208
-
209
- /** Called on disconnection */
210
- protected close(): Promise<void> | void {}
211
-
212
- /** Called after self and dependants disconnection is complete */
213
- protected dispose(): Promise<void> | void {}
214
- }
215
-
216
- const DELAY = 5000
217
- const TRACE_BOOT = environment.get('TOA_BOOT_TRACE') === '1'
package/source/context.ts DELETED
@@ -1,68 +0,0 @@
1
- import { Connector } from './connector.js'
2
- import { environment } from '@toa.io/generic'
3
- import type { Locator } from './locator.js'
4
- import type { Component } from './component.js'
5
- import type { Remote } from './remote.js'
6
- import type { Aspect } from './types/extensions.js'
7
- import type { Request } from './types/request.js'
8
-
9
- type Discover = (namespace: string, name: string) => Promise<Remote>
10
-
11
- export class Context extends Connector {
12
- public readonly env: string | undefined
13
- public readonly name: string | undefined
14
- public readonly aspects: Aspect[]
15
- public readonly locator: Locator
16
-
17
- readonly #local: Component
18
- readonly #discover: Discover
19
- readonly #remotes: Record<string, Promise<Remote>> = {}
20
-
21
- public constructor(local: Component, discover: Discover, aspects: Aspect[] = []) {
22
- super()
23
-
24
- this.env = environment.get('TOA_ENV')
25
- this.name = environment.get('TOA_CONTEXT')
26
- this.aspects = aspects
27
- this.locator = local?.locator
28
-
29
- this.#local = local
30
- this.#discover = discover
31
-
32
- this.depends(local)
33
-
34
- if (aspects.length > 0) this.depends(aspects)
35
- }
36
-
37
- public async apply(endpoint: string, request: Request): Promise<any> {
38
- return this.#local.invoke(endpoint, request)
39
- }
40
-
41
- // eslint-disable-next-line max-params
42
- public async call(
43
- namespace: string,
44
- name: string,
45
- endpoint: string,
46
- request: Request
47
- ): Promise<any> {
48
- const remote = await this.#remote(namespace, name)
49
-
50
- return remote.invoke(endpoint, request)
51
- }
52
-
53
- async #remote(namespace: string, name: string): Promise<Remote> {
54
- const key = namespace + '.' + name
55
-
56
- this.#remotes[key] ??= this.#connect(namespace, name)
57
-
58
- return this.#remotes[key]
59
- }
60
-
61
- async #connect(namespace: string, name: string): Promise<Remote> {
62
- const remote = await this.#discover(namespace, name)
63
-
64
- this.depends(remote)
65
-
66
- return remote
67
- }
68
- }
@@ -1,23 +0,0 @@
1
- import { SystemException } from '../exceptions.js'
2
- import type { Schema, SchemaError } from '@toa.io/schemas'
3
- import type { Exception } from '../exceptions.js'
4
-
5
- /** What a contract throws with, stated by the subclass. */
6
- export type Refusal = new (error: SchemaError, cause?: unknown) => Exception
7
-
8
- export class Contract {
9
- public readonly schema: Schema
10
-
11
- public static Exception: Refusal = SystemException as unknown as Refusal
12
-
13
- public constructor(schema: Schema) {
14
- this.schema = schema
15
- }
16
-
17
- public fit(value: unknown): void {
18
- const error = this.schema.fit(value)
19
-
20
- if (error !== null)
21
- throw new (this.constructor as typeof Contract).Exception(error, value)
22
- }
23
- }
@@ -1,2 +0,0 @@
1
- export { Request } from './request.js'
2
- export { Reply } from './reply.js'
@@ -1,56 +0,0 @@
1
- import { Contract } from './contract.js'
2
- import { ResponseContractException } from '../exceptions.js'
3
- import type { Refusal } from './contract.js'
4
- import type { JSONSchema } from './schemas.js'
5
-
6
- export class Reply extends Contract {
7
- public static override Exception: Refusal =
8
- ResponseContractException as unknown as Refusal
9
-
10
- public static schema(output?: JSONSchema, errors?: Array<string | number>): JSONSchema {
11
- const schema: JSONSchema = {
12
- type: 'object',
13
- properties: {},
14
- additionalProperties: false
15
- }
16
-
17
- if (output !== undefined) {
18
- /*
19
- * A reply carries more than the operation declares — `VERSION` and the rest of the
20
- * record's own fields — so what it is validated against is the declaration relaxed.
21
- * On a copy: the declaration itself is what a component publishes when asked to
22
- * explain, and relaxing that would publish a contract nobody wrote.
23
- */
24
- output = structuredClone(output)
25
-
26
- if (output.type === 'object') output.additionalProperties = true
27
- else if (output.type === 'array' && output.items?.type === 'object')
28
- output.items.additionalProperties = true
29
-
30
- schema.properties.output = output
31
- }
32
-
33
- /*
34
- * An error a caller is meant to handle is one the operation states. Where none are
35
- * stated, an error is not a reply this operation makes — it is a mistake, and the
36
- * contract says so rather than passing an undeclared code on to whoever called.
37
- */
38
- schema.properties.error =
39
- errors === undefined
40
- ? false
41
- : {
42
- type: 'object',
43
- properties: {
44
- code: {
45
- enum: errors
46
- },
47
- message: {
48
- type: 'string'
49
- }
50
- },
51
- required: ['code']
52
- }
53
-
54
- return schema
55
- }
56
- }
@@ -1,140 +0,0 @@
1
- import * as schemas from './schemas.js'
2
- import { RequestContractException } from '../exceptions.js'
3
- import { Contract } from './contract.js'
4
- import type { Schema } from '@toa.io/schemas'
5
- import type { Refusal } from './contract.js'
6
- import type { JSONSchema } from './schemas.js'
7
-
8
- /** What an operation states about itself, and answers when asked to explain. */
9
- export interface Explanation {
10
- description?: string
11
- input?: JSONSchema | null
12
- output?: JSONSchema | null
13
- errors?: Array<string | number>
14
- }
15
-
16
- export interface Definition extends Explanation {
17
- type?: string
18
- scope?: string
19
- query?: boolean
20
- }
21
-
22
- /** What the operation works on, as the manifest declares it. */
23
- export interface Entity {
24
- properties: Record<string, JSONSchema>
25
- required?: string[]
26
- }
27
-
28
- export class Request extends Contract {
29
- public readonly discovery: Explanation = {}
30
-
31
- public static override Exception: Refusal =
32
- RequestContractException as unknown as Refusal
33
-
34
- public constructor(schema: Schema, definition: Definition, entity?: Entity) {
35
- super(schema)
36
-
37
- for (const key of ['description', 'input', 'output', 'errors'] as const)
38
- if (definition[key] !== undefined)
39
- (this.discovery as Record<string, unknown>)[key] = definition[key]
40
-
41
- /*
42
- * An operation that states no output states an empty schema, which every reply fits —
43
- * and which says nothing to whoever reads it. What its type answers does, so that is
44
- * said here instead. Here and not in the definition, because the definition is what a
45
- * reply is held to: a projection is a lawful answer, and describing one is not the same
46
- * as requiring it.
47
- */
48
- if (empty(this.discovery.output)) {
49
- const answered = answers(definition, entity)
50
-
51
- if (answered !== undefined) this.discovery.output = answered
52
- }
53
- }
54
-
55
- public static schema(
56
- definition: Definition,
57
- entity?: { properties: Record<string, JSONSchema> }
58
- ): JSONSchema {
59
- // `source` is not among these: the framework stamps it and whoever reads it takes the keys
60
- // it knows, so holding it to a schema would only fail calls from a peer that stamps one more
61
- const schema: JSONSchema = {
62
- type: 'object',
63
- properties: {
64
- authentic: { type: 'boolean' },
65
- task: { type: 'boolean' }
66
- },
67
- additionalProperties: true
68
- }
69
-
70
- const required: string[] = []
71
-
72
- if (definition.input !== undefined) {
73
- schema.properties.input = definition.input
74
- required.push('input')
75
- } else schema.properties.input = { type: 'null' }
76
-
77
- if (entity === undefined) definition.query = false
78
-
79
- if (definition.query === true) required.push('query')
80
-
81
- if (definition.query === false) schema.properties.query = { type: 'null' }
82
-
83
- if (definition.query !== false) {
84
- const query = structuredClone(schemas.query)
85
-
86
- query.properties.id = entity?.properties.id
87
-
88
- if (definition.type === 'observation') delete query.properties.version
89
- else delete query.properties.projection
90
-
91
- if (definition.type !== 'observation' || definition.scope !== 'objects') {
92
- delete query.properties.omit
93
- delete query.properties.limit
94
- } else if (query.required === undefined) query.required = ['limit']
95
- else query.required.push('limit')
96
-
97
- schema.properties.query = query
98
- }
99
-
100
- if (required.length > 0) schema.required = required
101
-
102
- return schema
103
- }
104
- }
105
-
106
- /**
107
- * What an operation of this type answers, where it did not say.
108
- *
109
- * A transition, an observation and an assignment work on the Entity Object, so that is what
110
- * they answer — a set of them where the scope is a set. A computation neither uses the scope
111
- * nor produces one, and an effect answers whatever it computed, so neither is guessed at.
112
- * A stream is not a value with a schema, and is not one either.
113
- *
114
- * What the entity requires is said as well. It is a description and nothing is held to it —
115
- * an operation answering a projection says so by declaring its own output — and a shape
116
- * whose every property reads as optional says less than it knows.
117
- */
118
- function answers(definition: Definition, entity?: Entity): JSONSchema | undefined {
119
- if (entity === undefined || !ENTITY.has(definition.type as string)) return undefined
120
-
121
- const object: JSONSchema = {
122
- type: 'object',
123
- properties: entity.properties,
124
- ...(entity.required === undefined ? {} : { required: entity.required })
125
- }
126
-
127
- if (definition.scope === 'objects') return { type: 'array', items: object } as JSONSchema
128
-
129
- return definition.scope === 'object' || definition.scope === 'changeset'
130
- ? object
131
- : undefined
132
- }
133
-
134
- /** The types whose subject is the Entity Object; see `documentation/design.md`. */
135
- const ENTITY = new Set(['transition', 'observation', 'assignment'])
136
-
137
- /** A schema that states nothing, which is what an operation saying nothing normalizes to. */
138
- function empty(schema: JSONSchema | null | undefined): boolean {
139
- return schema === undefined || schema === null || Object.keys(schema).length === 0
140
- }
@@ -1,39 +0,0 @@
1
- /** A JSON Schema, loose because it is handed to a validator and edited by the contracts. */
2
- export type JSONSchema = Record<string, any>
3
-
4
- export const query: JSONSchema = {
5
- type: 'object',
6
- properties: {
7
- id: { type: 'string' },
8
- ids: { type: 'array', uniqueItems: true, minItems: 1, items: { type: 'string' } },
9
- version: { type: 'integer', minimum: 0 },
10
- criteria: { type: 'string' },
11
- search: { type: 'string' },
12
- sample: { type: 'number' },
13
- omit: { type: 'integer', minimum: 0 },
14
- limit: { type: 'integer', minimum: 0 },
15
- sort: {
16
- type: 'array',
17
- uniqueItems: true,
18
- minItems: 1,
19
- items: { type: 'string', pattern: '^\\w{1,32}(?::(?:asc|desc))?$' }
20
- },
21
- projection: {
22
- type: 'array',
23
- uniqueItems: true,
24
- minItems: 1,
25
- items: { type: 'string', not: { const: 'id' } }
26
- },
27
- deleted: { type: 'boolean' }
28
- },
29
- additionalProperties: false
30
- }
31
-
32
- export const error: JSONSchema = {
33
- type: 'object',
34
- properties: {
35
- code: { anyOf: [{ type: 'integer' }, { type: 'string' }] },
36
- message: { type: 'string' }
37
- },
38
- required: ['code']
39
- }
@@ -1,53 +0,0 @@
1
- import { console } from 'openspan'
2
- import { Connector } from './connector.js'
3
- import type { Locator } from './locator.js'
4
-
5
- interface Lookup extends Connector {
6
- invoke: () => Promise<any>
7
- }
8
-
9
- export class Discovery extends Connector {
10
- readonly #lookup: (locator: Locator) => Promise<Lookup>
11
- #lookups: Record<string, Promise<Lookup>> = {}
12
-
13
- public constructor(lookup: (locator: Locator) => Promise<Lookup>) {
14
- super()
15
-
16
- this.#lookup = lookup
17
- }
18
-
19
- protected override async open(): Promise<void> {
20
- this.#lookups = {}
21
- }
22
-
23
- public async lookup(locator: Locator): Promise<any> {
24
- const id = locator.id
25
-
26
- if (this.#lookups[id] === undefined) {
27
- const lookup = await this.#lookup(locator)
28
-
29
- this.#lookups[id] = Promise.resolve(lookup)
30
- this.depends(lookup)
31
- }
32
-
33
- const since = Date.now()
34
-
35
- // the wait is unbounded by design, as a dependency may still be starting,
36
- // so repeating is the only way a component stuck on one stays visible
37
- const warning = setInterval(() => {
38
- const waiting = Math.round((Date.now() - since) / 1000)
39
-
40
- console.warn('Waiting for lookup response', { component: id, waiting })
41
- }, INTERVAL)
42
-
43
- warning.unref()
44
-
45
- const output = await (await this.#lookups[id]).invoke()
46
-
47
- clearInterval(warning)
48
-
49
- return output
50
- }
51
- }
52
-
53
- const INTERVAL = 5000
package/source/effect.ts DELETED
@@ -1,14 +0,0 @@
1
- import { Observation } from './observation.js'
2
- import type { Store } from './operation.js'
3
- import type { Entity } from './entities/entity.js'
4
-
5
- export class Effect extends Observation {
6
- protected override async acquire(store: Store): Promise<void> {
7
- const { query, entity, input } = store.request
8
-
9
- if (entity === undefined) return super.acquire(store)
10
-
11
- store.scope = await this.scope.ensure(query as any, entity, input)
12
- store.state = (store.scope as Entity).get()
13
- }
14
- }
@@ -1,21 +0,0 @@
1
- import { Connector } from './connector.js'
2
- import type { Event } from './event.js'
3
- import type { Event as StateEvent } from './types/state.js'
4
-
5
- export class Emission extends Connector {
6
- readonly #events: Event[]
7
-
8
- public constructor(events: Event[]) {
9
- super()
10
-
11
- this.#events = events
12
-
13
- this.depends(events)
14
- }
15
-
16
- public async emit(event: StateEvent): Promise<void> {
17
- const emission = this.#events.map((e) => e.emit(event))
18
-
19
- await Promise.all(emission)
20
- }
21
- }
@@ -1,37 +0,0 @@
1
- import { EntityContractException } from '../exceptions.js'
2
- import type { Schema } from '@toa.io/schemas'
3
- import type { Query, Record } from '../types/storages.js'
4
-
5
- export class Changeset {
6
- public readonly query: Query
7
-
8
- /** the changeset schema: the entity's properties, none of them required */
9
- readonly #schema: Schema
10
- #state: Record | object
11
-
12
- public constructor(schema: Schema, query: Query) {
13
- this.query = query
14
-
15
- this.#schema = schema
16
- this.#state = {}
17
- }
18
-
19
- public get(): object {
20
- return this.#state
21
- }
22
-
23
- public set(value: Record): void {
24
- const error = this.#schema.fit(value)
25
-
26
- if (error !== null) throw new EntityContractException(error, value)
27
-
28
- delete (value as Partial<Record>).VERSION
29
- value.UPDATED = Date.now()
30
-
31
- this.#state = value
32
- }
33
-
34
- public export(): object {
35
- return this.#state
36
- }
37
- }