@highstate/backend 0.9.9 → 0.9.10
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/dist/{chunk-NMGIUI6X.js → chunk-DQDXRDUA.js} +228 -96
- package/dist/chunk-DQDXRDUA.js.map +1 -0
- package/dist/{chunk-EQ4LMS7B.js → chunk-WXDYCRTT.js} +1 -57
- package/dist/chunk-WXDYCRTT.js.map +1 -0
- package/dist/highstate.manifest.json +3 -3
- package/dist/index.js +100 -98
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +1 -1
- package/dist/shared/index.js +11 -13
- package/package.json +3 -3
- package/src/common/utils.ts +0 -74
- package/src/library/abstractions.ts +0 -5
- package/src/orchestrator/operation-workset.ts +47 -55
- package/src/orchestrator/operation.ts +1 -1
- package/src/project/manager.ts +66 -61
- package/src/shared/async-batcher.ts +73 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/resolvers/graph-resolver.ts +146 -79
- package/src/shared/resolvers/input-hash.ts +22 -17
- package/src/shared/resolvers/input.ts +29 -26
- package/src/shared/resolvers/registry.ts +19 -9
- package/src/shared/resolvers/validation.ts +12 -18
- package/src/state/abstractions.ts +2 -3
- package/src/state/local.ts +13 -6
- package/src/terminal/manager.ts +2 -2
- package/dist/chunk-EQ4LMS7B.js.map +0 -1
- package/dist/chunk-NMGIUI6X.js.map +0 -1
@@ -0,0 +1,73 @@
|
|
1
|
+
export type AsyncBatcherOptions = {
|
2
|
+
waitMs?: number
|
3
|
+
maxWaitTimeMs?: number
|
4
|
+
}
|
5
|
+
|
6
|
+
export function createAsyncBatcher<T>(
|
7
|
+
fn: (items: T[]) => Promise<void> | void,
|
8
|
+
{ waitMs = 100, maxWaitTimeMs = 1000 }: AsyncBatcherOptions = {},
|
9
|
+
) {
|
10
|
+
let batch: T[] = []
|
11
|
+
let activeTimeout: NodeJS.Timeout | null = null
|
12
|
+
let maxWaitTimeout: NodeJS.Timeout | null = null
|
13
|
+
let firstCallTimestamp: number | null = null
|
14
|
+
|
15
|
+
async function processBatch() {
|
16
|
+
if (batch.length === 0) return
|
17
|
+
|
18
|
+
const currentBatch = batch
|
19
|
+
batch = [] // Reset batch before async call
|
20
|
+
|
21
|
+
await fn(currentBatch)
|
22
|
+
|
23
|
+
// Clear max wait timer since batch has been processed
|
24
|
+
if (maxWaitTimeout) {
|
25
|
+
clearTimeout(maxWaitTimeout)
|
26
|
+
maxWaitTimeout = null
|
27
|
+
}
|
28
|
+
firstCallTimestamp = null
|
29
|
+
}
|
30
|
+
|
31
|
+
function schedule() {
|
32
|
+
if (activeTimeout) clearTimeout(activeTimeout)
|
33
|
+
activeTimeout = setTimeout(() => {
|
34
|
+
activeTimeout = null
|
35
|
+
void processBatch()
|
36
|
+
}, waitMs)
|
37
|
+
|
38
|
+
// Ensure batch is executed within maxWaitTimeMs
|
39
|
+
if (!firstCallTimestamp) {
|
40
|
+
firstCallTimestamp = Date.now()
|
41
|
+
maxWaitTimeout = setTimeout(() => {
|
42
|
+
if (activeTimeout) clearTimeout(activeTimeout)
|
43
|
+
activeTimeout = null
|
44
|
+
void processBatch()
|
45
|
+
}, maxWaitTimeMs)
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
return {
|
50
|
+
/**
|
51
|
+
* Add an item to the batch.
|
52
|
+
*/
|
53
|
+
call(item: T): void {
|
54
|
+
batch.push(item)
|
55
|
+
schedule()
|
56
|
+
},
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Immediately flush the pending batch (if any).
|
60
|
+
*/
|
61
|
+
async flush(): Promise<void> {
|
62
|
+
if (activeTimeout) {
|
63
|
+
clearTimeout(activeTimeout)
|
64
|
+
activeTimeout = null
|
65
|
+
}
|
66
|
+
if (maxWaitTimeout) {
|
67
|
+
clearTimeout(maxWaitTimeout)
|
68
|
+
maxWaitTimeout = null
|
69
|
+
}
|
70
|
+
await processBatch()
|
71
|
+
},
|
72
|
+
}
|
73
|
+
}
|
package/src/shared/index.ts
CHANGED
@@ -1,111 +1,178 @@
|
|
1
1
|
import type { Logger } from "pino"
|
2
2
|
import { unique } from "remeda"
|
3
3
|
|
4
|
-
export type
|
5
|
-
name: string
|
4
|
+
export type ResolverOutputHandler<TOutput> = (id: string, value: TOutput) => void
|
6
5
|
|
7
|
-
|
6
|
+
export abstract class GraphResolver<TNode, TOutput> {
|
7
|
+
private readonly workset: Set<string> = new Set()
|
8
|
+
private readonly dependentMap: Map<string, Set<string>> = new Map()
|
9
|
+
private readonly outputMap: Map<string, TOutput> = new Map()
|
8
10
|
|
9
|
-
|
11
|
+
constructor(
|
12
|
+
private readonly nodes: ReadonlyMap<string, TNode>,
|
13
|
+
protected readonly logger: Logger,
|
14
|
+
private readonly outputHandler?: ResolverOutputHandler<TOutput>,
|
15
|
+
) {}
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
logger: Logger,
|
15
|
-
): TOutput | Promise<TOutput>
|
16
|
-
}
|
17
|
-
|
18
|
-
export interface GraphResolverBackend<TOutput> {
|
19
|
-
promiseCache: Map<string, Promise<TOutput>>
|
20
|
-
|
21
|
-
setOutput?(id: string, value: TOutput): void
|
22
|
-
setDependencies?(id: string, dependencies: string[]): void
|
23
|
-
}
|
24
|
-
|
25
|
-
export type GraphResolverFactory<TNode, TOutput> = {
|
26
|
-
factoryName: string
|
27
|
-
} & ((
|
28
|
-
nodes: ReadonlyMap<string, TNode>,
|
29
|
-
logger: Logger,
|
30
|
-
backend?: GraphResolverBackend<TOutput>,
|
31
|
-
) => GraphResolver<TOutput>)
|
17
|
+
addToWorkset(nodeId: string): void {
|
18
|
+
this.workset.add(nodeId)
|
19
|
+
}
|
32
20
|
|
33
|
-
|
21
|
+
addAllNodesToWorkset(): void {
|
22
|
+
for (const nodeId of this.nodes.keys()) {
|
23
|
+
this.workset.add(nodeId)
|
24
|
+
}
|
25
|
+
}
|
34
26
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
27
|
+
/**
|
28
|
+
* The map of calculated outputs.
|
29
|
+
*/
|
30
|
+
get outputs(): ReadonlyMap<string, TOutput> {
|
31
|
+
return this.outputMap
|
39
32
|
}
|
40
|
-
}
|
41
33
|
|
42
|
-
|
43
|
-
|
34
|
+
requireOutput(nodeId: string): TOutput {
|
35
|
+
const output = this.outputMap.get(nodeId)
|
36
|
+
if (!output) {
|
37
|
+
throw new Error(`Output for node ${nodeId} is not available`)
|
38
|
+
}
|
44
39
|
|
45
|
-
|
46
|
-
promiseCache,
|
40
|
+
return output
|
47
41
|
}
|
48
|
-
}
|
49
|
-
|
50
|
-
export function defineGraphResolver<TInput, TOutput>(
|
51
|
-
options: GraphResolverOptions<TInput, TOutput>,
|
52
|
-
): GraphResolverFactory<TInput, TOutput> {
|
53
|
-
const factory: GraphResolverFactory<TInput, TOutput> = (nodes, logger, backend) => {
|
54
|
-
backend ??= createDefaultGraphResolverBackend<TOutput>()
|
55
|
-
logger = logger.child({ resolver: options.name })
|
56
42
|
|
57
|
-
|
43
|
+
/**
|
44
|
+
* Gets the list of the identifiers of the dependencies for the node.
|
45
|
+
*
|
46
|
+
* Used to produce the dependency graph.
|
47
|
+
*/
|
48
|
+
protected abstract getNodeDependencies(node: TNode): string[]
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Processes the node and returns the output.
|
52
|
+
*/
|
53
|
+
protected abstract processNode(node: TNode, logger: Logger): TOutput | Promise<TOutput>
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Invalidates the node and all nodes that depend on it.
|
57
|
+
*
|
58
|
+
* Also adds the node to the work set for processing.
|
59
|
+
*/
|
60
|
+
invalidate(nodeId: string): void {
|
61
|
+
const stack = [nodeId]
|
62
|
+
|
63
|
+
while (stack.length > 0) {
|
64
|
+
const nodeId = stack.pop()!
|
65
|
+
if (!this.nodes.has(nodeId)) {
|
66
|
+
// it is ok to invalidate deleted nodes
|
67
|
+
continue
|
68
|
+
}
|
58
69
|
|
59
|
-
|
60
|
-
|
70
|
+
// remove the node from the output map
|
71
|
+
this.outputMap.delete(nodeId)
|
72
|
+
this.workset.add(nodeId)
|
61
73
|
|
62
|
-
const
|
63
|
-
if (
|
64
|
-
|
74
|
+
const dependents = this.dependentMap.get(nodeId)
|
75
|
+
if (!dependents) {
|
76
|
+
continue
|
65
77
|
}
|
66
78
|
|
67
|
-
|
68
|
-
|
79
|
+
for (const dependentId of dependents) {
|
80
|
+
if (this.outputMap.has(dependentId)) {
|
81
|
+
// add the dependent to the stack for further processing
|
82
|
+
stack.push(dependentId)
|
83
|
+
}
|
69
84
|
}
|
70
85
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
86
|
+
// clear the dependent map for the node
|
87
|
+
this.dependentMap.delete(nodeId)
|
88
|
+
}
|
89
|
+
}
|
75
90
|
|
76
|
-
|
77
|
-
|
91
|
+
/**
|
92
|
+
* Resolves all not-resolved or invalidated nodes in the graph.
|
93
|
+
*
|
94
|
+
* The abort signal of the previous operation must be called before calling this method again.
|
95
|
+
*/
|
96
|
+
async process(signal?: AbortSignal): Promise<void> {
|
97
|
+
while (this.workset.size > 0) {
|
98
|
+
const rootNodeId = this.workset.values().next().value!
|
99
|
+
const stack = [{ nodeId: rootNodeId, resolved: false, dependencies: [] as string[] }]
|
100
|
+
|
101
|
+
while (stack.length > 0) {
|
102
|
+
const stackItem = stack[stack.length - 1]
|
103
|
+
const { nodeId, resolved } = stackItem
|
104
|
+
|
105
|
+
const node = this.nodes.get(nodeId)
|
106
|
+
if (!node) {
|
107
|
+
this.logger.warn({ nodeId }, "node not found in the graph, skipping")
|
108
|
+
stack.pop()
|
109
|
+
continue
|
110
|
+
}
|
78
111
|
|
79
|
-
|
80
|
-
|
112
|
+
if (this.outputMap.has(nodeId)) {
|
113
|
+
// already processed
|
114
|
+
stack.pop()
|
115
|
+
continue
|
116
|
+
}
|
81
117
|
|
82
|
-
|
118
|
+
if (!resolved) {
|
119
|
+
stackItem.dependencies = unique(this.getNodeDependencies(node))
|
120
|
+
|
121
|
+
let hasUnresolvedDependencies = false
|
122
|
+
|
123
|
+
for (const depId of stackItem.dependencies) {
|
124
|
+
if (!this.nodes.has(depId)) {
|
125
|
+
this.logger.warn({ depId, nodeId }, "dependency not found in the graph, skipping")
|
126
|
+
continue
|
127
|
+
}
|
128
|
+
|
129
|
+
// if (stack.some(item => item.nodeId === depId)) {
|
130
|
+
// this.logger.warn(
|
131
|
+
// { depId, nodeId, stack },
|
132
|
+
// "dependency is already in the stack, looks like a circular dependency, skipping",
|
133
|
+
// )
|
134
|
+
// continue
|
135
|
+
// }
|
136
|
+
|
137
|
+
if (!this.outputMap.has(depId)) {
|
138
|
+
stack.push({ nodeId: depId, resolved: false, dependencies: [] })
|
139
|
+
hasUnresolvedDependencies = true
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
if (hasUnresolvedDependencies) {
|
144
|
+
// wait for dependencies to be resolved
|
145
|
+
stackItem.resolved = true
|
146
|
+
continue
|
147
|
+
}
|
148
|
+
}
|
83
149
|
|
84
|
-
|
85
|
-
|
150
|
+
// all dependencies are resolved, process the node
|
151
|
+
const output = await this.processNode(node, this.logger)
|
152
|
+
|
153
|
+
if (signal?.aborted) {
|
154
|
+
this.logger.warn({ nodeId }, "processing aborted, skipping output")
|
155
|
+
return
|
86
156
|
}
|
87
157
|
|
88
|
-
|
89
|
-
|
158
|
+
// update the dependent map
|
159
|
+
for (const depId of stackItem.dependencies) {
|
160
|
+
let dependantSet = this.dependentMap.get(depId)
|
161
|
+
if (!dependantSet) {
|
162
|
+
dependantSet = new Set()
|
163
|
+
this.dependentMap.set(depId, dependantSet)
|
164
|
+
}
|
90
165
|
|
91
|
-
|
92
|
-
if (backend.promiseCache.get(itemId) === promise) {
|
93
|
-
// persist the result to the state if provided
|
94
|
-
// ignore if the promise has been replaced (which means this calculation was invalidated)
|
95
|
-
backend.setOutput?.(itemId, result)
|
96
|
-
outputs.set(itemId, result)
|
166
|
+
dependantSet.add(nodeId)
|
97
167
|
}
|
98
168
|
|
99
|
-
|
100
|
-
|
169
|
+
this.outputMap.set(nodeId, output)
|
170
|
+
this.outputHandler?.(nodeId, output)
|
101
171
|
|
102
|
-
|
103
|
-
|
104
|
-
}
|
172
|
+
stack.pop()
|
173
|
+
}
|
105
174
|
|
106
|
-
|
175
|
+
this.workset.delete(rootNodeId)
|
176
|
+
}
|
107
177
|
}
|
108
|
-
|
109
|
-
factory.factoryName = options.name
|
110
|
-
return factory
|
111
178
|
}
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import type { ComponentModel, InstanceModel } from "@highstate/contract"
|
2
1
|
import type { InstanceState } from "../state"
|
3
2
|
import type { ResolvedInstanceInput } from "./input"
|
3
|
+
import { isUnitModel, type ComponentModel, type InstanceModel } from "@highstate/contract"
|
4
4
|
import { sha256 } from "crypto-hash"
|
5
|
-
import {
|
5
|
+
import { GraphResolver } from "./graph-resolver"
|
6
6
|
|
7
|
-
export type
|
7
|
+
export type InputHashNode = {
|
8
8
|
instance: InstanceModel
|
9
9
|
component: ComponentModel
|
10
10
|
resolvedInputs: Record<string, ResolvedInstanceInput[]>
|
@@ -12,7 +12,7 @@ export type InputHashResolverInput = {
|
|
12
12
|
sourceHash: string | undefined
|
13
13
|
}
|
14
14
|
|
15
|
-
export type
|
15
|
+
export type InputHashOutput = {
|
16
16
|
inputHash: string
|
17
17
|
outputHash: string
|
18
18
|
}
|
@@ -20,14 +20,8 @@ export type InputHashResolverOutput = {
|
|
20
20
|
/**
|
21
21
|
* Resolves the hash of the instance based on its args, resolved input hashes, source hash, and the output hash.
|
22
22
|
*/
|
23
|
-
export
|
24
|
-
|
25
|
-
InputHashResolverOutput
|
26
|
-
>({
|
27
|
-
name: "input-hash-resolver",
|
28
|
-
getNodeId: node => node.instance.id,
|
29
|
-
|
30
|
-
getNodeDependencies({ resolvedInputs }) {
|
23
|
+
export class InputHashResolver extends GraphResolver<InputHashNode, InputHashOutput> {
|
24
|
+
getNodeDependencies({ resolvedInputs }: InputHashNode): string[] {
|
31
25
|
const dependencies: string[] = []
|
32
26
|
|
33
27
|
for (const inputs of Object.values(resolvedInputs ?? {})) {
|
@@ -37,13 +31,24 @@ export const createInputHashResolver = defineGraphResolver<
|
|
37
31
|
}
|
38
32
|
|
39
33
|
return dependencies
|
40
|
-
}
|
34
|
+
}
|
41
35
|
|
42
|
-
async
|
36
|
+
async processNode({
|
37
|
+
instance,
|
38
|
+
component,
|
39
|
+
resolvedInputs,
|
40
|
+
sourceHash,
|
41
|
+
state,
|
42
|
+
}: InputHashNode): Promise<InputHashOutput> {
|
43
43
|
let sink = component.definitionHash + JSON.stringify(instance.args ?? {})
|
44
44
|
|
45
45
|
if (sourceHash) {
|
46
46
|
sink += sourceHash
|
47
|
+
} else if (isUnitModel(component)) {
|
48
|
+
this.logger.warn(
|
49
|
+
{ instanceId: instance.id },
|
50
|
+
"missing source hash for unit model, this may lead to incorrect input hash",
|
51
|
+
)
|
47
52
|
}
|
48
53
|
|
49
54
|
const sortedInputs = Object.entries(resolvedInputs)
|
@@ -61,7 +66,7 @@ export const createInputHashResolver = defineGraphResolver<
|
|
61
66
|
instanceIds.sort()
|
62
67
|
|
63
68
|
for (const instanceId of instanceIds) {
|
64
|
-
const dependency =
|
69
|
+
const dependency = this.outputs.get(instanceId)
|
65
70
|
if (!dependency) continue
|
66
71
|
|
67
72
|
sink += dependency.inputHash
|
@@ -73,5 +78,5 @@ export const createInputHashResolver = defineGraphResolver<
|
|
73
78
|
inputHash: await sha256(sink),
|
74
79
|
outputHash: state?.outputHash ?? "",
|
75
80
|
}
|
76
|
-
}
|
77
|
-
}
|
81
|
+
}
|
82
|
+
}
|
@@ -7,9 +7,9 @@ import {
|
|
7
7
|
type InstanceModel,
|
8
8
|
} from "@highstate/contract"
|
9
9
|
import { fromEntries, mapValues } from "remeda"
|
10
|
-
import {
|
10
|
+
import { GraphResolver } from "./graph-resolver"
|
11
11
|
|
12
|
-
export type
|
12
|
+
export type InputResolverNode =
|
13
13
|
| {
|
14
14
|
kind: "instance"
|
15
15
|
instance: InstanceModel
|
@@ -28,6 +28,7 @@ export type ResolvedInstanceInput = {
|
|
28
28
|
export type InputResolverOutput =
|
29
29
|
| {
|
30
30
|
kind: "instance"
|
31
|
+
instance: InstanceModel
|
31
32
|
component: ComponentModel
|
32
33
|
resolvedInputs: Record<string, ResolvedInstanceInput[]>
|
33
34
|
resolvedOutputs: Record<string, InstanceInput[]> | undefined
|
@@ -42,17 +43,16 @@ export type InputResolverOutput =
|
|
42
43
|
/**
|
43
44
|
* Resolves the all recursive instance and hub inputs based on its direct inputs and injected inputs.
|
44
45
|
*/
|
45
|
-
export
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
getNodeDependencies(node) {
|
46
|
+
export class InputResolver extends GraphResolver<InputResolverNode, InputResolverOutput> {
|
47
|
+
// constructor(
|
48
|
+
// nodes: ReadonlyMap<string, InputResolverNode>,
|
49
|
+
// logger: Logger,
|
50
|
+
// outputHandler?: ResolverOutputHandler<InputResolverOutput>,
|
51
|
+
// ) {
|
52
|
+
// super(nodes, logger, outputHandler)
|
53
|
+
// }
|
54
|
+
|
55
|
+
getNodeDependencies(node: InputResolverNode): string[] {
|
56
56
|
const dependencies: string[] = []
|
57
57
|
|
58
58
|
if (node.kind === "hub") {
|
@@ -84,11 +84,11 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
84
84
|
}
|
85
85
|
|
86
86
|
return dependencies
|
87
|
-
}
|
87
|
+
}
|
88
88
|
|
89
|
-
|
89
|
+
processNode(node: InputResolverNode): InputResolverOutput {
|
90
90
|
const getHubOutput = (input: HubInput) => {
|
91
|
-
const output =
|
91
|
+
const output = this.outputs.get(`hub:${input.hubId}`)
|
92
92
|
if (!output) {
|
93
93
|
return { resolvedInputs: [] }
|
94
94
|
}
|
@@ -101,7 +101,7 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
101
101
|
}
|
102
102
|
|
103
103
|
const getInstanceOutput = (input: InstanceInput) => {
|
104
|
-
const output =
|
104
|
+
const output = this.outputs.get(`instance:${input.instanceId}`)
|
105
105
|
if (!output) {
|
106
106
|
return {
|
107
107
|
component: null,
|
@@ -122,7 +122,6 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
122
122
|
}
|
123
123
|
|
124
124
|
// resolve inputs for hub
|
125
|
-
|
126
125
|
if (node.kind === "hub") {
|
127
126
|
const hubResult: Map<string, ResolvedInstanceInput> = new Map()
|
128
127
|
|
@@ -135,7 +134,7 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
135
134
|
const componentInput = component?.outputs[input.output]
|
136
135
|
|
137
136
|
if (!componentInput) {
|
138
|
-
logger.warn({ msg: "output not found in the component", input, component })
|
137
|
+
this.logger.warn({ msg: "output not found in the component", input, component })
|
139
138
|
continue
|
140
139
|
}
|
141
140
|
|
@@ -160,11 +159,12 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
160
159
|
if (node.instance.resolvedInputs) {
|
161
160
|
return {
|
162
161
|
kind: "instance",
|
162
|
+
instance: node.instance,
|
163
163
|
component: node.component,
|
164
164
|
resolvedInputs: mapValues(node.instance.resolvedInputs, (inputs, inputName) => {
|
165
165
|
const componentInput = node.component.inputs[inputName]
|
166
166
|
if (!componentInput) {
|
167
|
-
logger.warn({
|
167
|
+
this.logger.warn({
|
168
168
|
msg: "input not found in the component",
|
169
169
|
inputName,
|
170
170
|
component: node.component,
|
@@ -181,7 +181,6 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
181
181
|
}
|
182
182
|
|
183
183
|
// resolve inputs for instance
|
184
|
-
|
185
184
|
const resolvedInputsMap: Map<string, Map<string, ResolvedInstanceInput>> = new Map()
|
186
185
|
|
187
186
|
const addInstanceResult = (inputName: string, input: ResolvedInstanceInput) => {
|
@@ -197,14 +196,18 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
197
196
|
const addInstanceInput = (inputName: string, input: InstanceInput) => {
|
198
197
|
const componentInput = node.component.inputs[inputName]
|
199
198
|
if (!componentInput) {
|
200
|
-
logger.warn({
|
199
|
+
this.logger.warn({
|
200
|
+
msg: "input not found in the component",
|
201
|
+
input,
|
202
|
+
component: node.component,
|
203
|
+
})
|
201
204
|
return
|
202
205
|
}
|
203
206
|
|
204
207
|
const { component, resolvedOutputs } = getInstanceOutput(input)
|
205
208
|
|
206
209
|
if (!component) {
|
207
|
-
logger.warn({ instanceId: node.instance.id, input }, "no output found for the input")
|
210
|
+
this.logger.warn({ instanceId: node.instance.id, input }, "no output found for the input")
|
208
211
|
return
|
209
212
|
}
|
210
213
|
|
@@ -219,7 +222,6 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
219
222
|
}
|
220
223
|
} else {
|
221
224
|
// if the instance is not evaluated, we a forced to use the input as is
|
222
|
-
|
223
225
|
addInstanceResult(inputName, { input, type: componentInput.type })
|
224
226
|
}
|
225
227
|
}
|
@@ -272,14 +274,15 @@ export const createInputResolver = defineGraphResolver<InputResolverInput, Input
|
|
272
274
|
|
273
275
|
return {
|
274
276
|
kind: "instance",
|
277
|
+
instance: node.instance,
|
275
278
|
component: node.component,
|
276
279
|
resolvedInputs,
|
277
280
|
resolvedOutputs: node.instance.resolvedOutputs,
|
278
281
|
resolvedInjectionInputs: Array.from(injectionInputs.values()),
|
279
282
|
matchedInjectionInputs: Array.from(matchedInjectionInputs.values()),
|
280
283
|
}
|
281
|
-
}
|
282
|
-
}
|
284
|
+
}
|
285
|
+
}
|
283
286
|
|
284
287
|
export function getResolvedHubInputs(output: InputResolverOutput): ResolvedInstanceInput[] {
|
285
288
|
if (output.kind !== "hub") {
|
@@ -1,10 +1,20 @@
|
|
1
|
-
import type {
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
import {
|
1
|
+
import type { Logger } from "pino"
|
2
|
+
import type { GraphResolver, ResolverOutputHandler } from "./graph-resolver"
|
3
|
+
import { InputResolver } from "./input"
|
4
|
+
import { InputHashResolver } from "./input-hash"
|
5
|
+
import { ValidationResolver } from "./validation"
|
5
6
|
|
6
|
-
export
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
export type GraphResolverType = "InputResolver" | "InputHashResolver" | "ValidationResolver"
|
8
|
+
|
9
|
+
export const resolverFactories = {
|
10
|
+
InputResolver,
|
11
|
+
InputHashResolver,
|
12
|
+
ValidationResolver,
|
13
|
+
} as Record<
|
14
|
+
GraphResolverType,
|
15
|
+
new (
|
16
|
+
nodes: ReadonlyMap<string, unknown>,
|
17
|
+
logger: Logger,
|
18
|
+
outputHandler?: ResolverOutputHandler<unknown>,
|
19
|
+
) => GraphResolver<unknown, unknown>
|
20
|
+
>
|
@@ -1,15 +1,15 @@
|
|
1
1
|
import type { ComponentModel, InstanceModel } from "@highstate/contract"
|
2
2
|
import type { ResolvedInstanceInput } from "./input"
|
3
3
|
import { Ajv } from "ajv"
|
4
|
-
import {
|
4
|
+
import { GraphResolver } from "./graph-resolver"
|
5
5
|
|
6
|
-
export type
|
6
|
+
export type ValidationNode = {
|
7
7
|
instance: InstanceModel
|
8
8
|
component: ComponentModel
|
9
9
|
resolvedInputs: Record<string, ResolvedInstanceInput[]>
|
10
10
|
}
|
11
11
|
|
12
|
-
export type
|
12
|
+
export type ValidationOutput =
|
13
13
|
| {
|
14
14
|
status: "ok"
|
15
15
|
}
|
@@ -25,14 +25,8 @@ export type ValidationResolverOutput =
|
|
25
25
|
/**
|
26
26
|
* Validates the instance based on its arguments and inputs.
|
27
27
|
*/
|
28
|
-
export
|
29
|
-
|
30
|
-
ValidationResolverOutput
|
31
|
-
>({
|
32
|
-
name: "validation-resolver",
|
33
|
-
getNodeId: node => node.instance.id,
|
34
|
-
|
35
|
-
getNodeDependencies({ resolvedInputs }) {
|
28
|
+
export class ValidationResolver extends GraphResolver<ValidationNode, ValidationOutput> {
|
29
|
+
getNodeDependencies({ resolvedInputs }: ValidationNode): string[] {
|
36
30
|
const dependencies: string[] = []
|
37
31
|
|
38
32
|
for (const inputs of Object.values(resolvedInputs)) {
|
@@ -42,12 +36,12 @@ export const createValidationResolver = defineGraphResolver<
|
|
42
36
|
}
|
43
37
|
|
44
38
|
return dependencies
|
45
|
-
}
|
39
|
+
}
|
46
40
|
|
47
|
-
|
41
|
+
processNode({ instance, component, resolvedInputs }: ValidationNode): ValidationOutput {
|
48
42
|
const ajv = new Ajv({ strict: false })
|
49
43
|
|
50
|
-
logger.debug({ instanceId: instance.id }, "validating instance")
|
44
|
+
this.logger.debug({ instanceId: instance.id }, "validating instance")
|
51
45
|
|
52
46
|
for (const [name, argument] of Object.entries(component.args)) {
|
53
47
|
if (!argument.required && !instance.args?.[name]) {
|
@@ -55,7 +49,7 @@ export const createValidationResolver = defineGraphResolver<
|
|
55
49
|
}
|
56
50
|
|
57
51
|
if (!ajv.validate(argument.schema, instance.args?.[name])) {
|
58
|
-
logger.debug({ instanceId: instance.id, argumentName: name }, "invalid argument")
|
52
|
+
this.logger.debug({ instanceId: instance.id, argumentName: name }, "invalid argument")
|
59
53
|
|
60
54
|
return {
|
61
55
|
status: "invalid-args",
|
@@ -66,7 +60,7 @@ export const createValidationResolver = defineGraphResolver<
|
|
66
60
|
|
67
61
|
for (const inputs of Object.values(resolvedInputs)) {
|
68
62
|
for (const input of inputs) {
|
69
|
-
const inputInstance =
|
63
|
+
const inputInstance = this.outputs.get(input.input.instanceId)
|
70
64
|
if (inputInstance?.status !== "ok") {
|
71
65
|
return {
|
72
66
|
status: "invalid-inputs",
|
@@ -90,5 +84,5 @@ export const createValidationResolver = defineGraphResolver<
|
|
90
84
|
}
|
91
85
|
|
92
86
|
return { status: "ok" }
|
93
|
-
}
|
94
|
-
}
|
87
|
+
}
|
88
|
+
}
|