@highstate/contract 0.19.1 → 0.20.0
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/index.js +682 -426
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/component.spec.ts +912 -3
- package/src/component.ts +524 -89
- package/src/cuidv2d.spec.ts +39 -0
- package/src/cuidv2d.ts +54 -0
- package/src/entity.ts +321 -81
- package/src/evaluation.ts +62 -12
- package/src/index.ts +9 -1
- package/src/instance-input.ts +219 -0
- package/src/instance.ts +307 -65
- package/src/json-schema.ts +115 -0
- package/src/pulumi.ts +45 -16
- package/src/unit.ts +26 -10
- package/src/compaction.spec.ts +0 -215
- package/src/compaction.ts +0 -381
package/src/evaluation.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component } from "./component"
|
|
2
|
-
import type { InstanceInput, InstanceModel } from "./instance"
|
|
2
|
+
import type { InstanceInput, InstanceModel, RuntimeInput } from "./instance"
|
|
3
3
|
import { mapValues } from "remeda"
|
|
4
4
|
|
|
5
5
|
export type RuntimeInstance = {
|
|
@@ -8,7 +8,17 @@ export type RuntimeInstance = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const boundaryInput = Symbol("boundaryInput")
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
function isStableInstanceInput(value: unknown): value is InstanceInput {
|
|
13
|
+
return (
|
|
14
|
+
typeof value === "object" &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
"instanceId" in value &&
|
|
17
|
+
"output" in value &&
|
|
18
|
+
typeof (value as { instanceId: unknown }).instanceId === "string" &&
|
|
19
|
+
typeof (value as { output: unknown }).output === "string"
|
|
20
|
+
)
|
|
21
|
+
}
|
|
12
22
|
|
|
13
23
|
function formatInstancePath(instance: InstanceModel): string {
|
|
14
24
|
let result = instance.id
|
|
@@ -86,20 +96,60 @@ export function registerInstance<T>(component: Component, instance: InstanceMode
|
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
try {
|
|
89
|
-
const
|
|
99
|
+
const rawOutputs = fn() as Record<string, RuntimeInput | RuntimeInput[] | undefined>
|
|
100
|
+
const outputs = mapValues(rawOutputs ?? {}, outputGroup => {
|
|
101
|
+
return [outputGroup].flat(2).filter(Boolean) as RuntimeInput[]
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const toStableInputs = (
|
|
105
|
+
outputGroup: RuntimeInput[],
|
|
106
|
+
useBoundaryFallback: boolean,
|
|
107
|
+
): InstanceInput[] => {
|
|
108
|
+
return outputGroup
|
|
109
|
+
.map(output => (useBoundaryFallback ? output[boundaryInput] : output))
|
|
110
|
+
.filter(isStableInstanceInput)
|
|
111
|
+
.map(output => {
|
|
112
|
+
return output.path
|
|
113
|
+
? {
|
|
114
|
+
instanceId: output.instanceId,
|
|
115
|
+
output: output.output,
|
|
116
|
+
path: output.path,
|
|
117
|
+
}
|
|
118
|
+
: {
|
|
119
|
+
instanceId: output.instanceId,
|
|
120
|
+
output: output.output,
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
90
124
|
|
|
91
|
-
instance.resolvedOutputs = outputs
|
|
92
|
-
|
|
93
|
-
outputs.map(output => output[boundaryInput] ?? output),
|
|
125
|
+
instance.resolvedOutputs = mapValues(outputs ?? {}, outputGroup =>
|
|
126
|
+
toStableInputs(outputGroup, false),
|
|
94
127
|
)
|
|
128
|
+
instance.outputs = mapValues(outputs ?? {}, outputGroup => toStableInputs(outputGroup, true))
|
|
95
129
|
|
|
96
130
|
// mark all outputs with the boundary input of the instance
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
131
|
+
// keep object identity to preserve proxy-based deep accessors
|
|
132
|
+
return mapValues(rawOutputs, (rawOutputGroup, outputKey) => {
|
|
133
|
+
const outputRefs = (outputs[outputKey] ?? []).map(output => {
|
|
134
|
+
if (output.provided) {
|
|
135
|
+
output[boundaryInput] = { instanceId: instance.id, output: outputKey }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return output
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (component.model.outputs[outputKey]?.multiple) {
|
|
142
|
+
const multipleOutput = (
|
|
143
|
+
Array.isArray(rawOutputGroup) ? rawOutputGroup : outputRefs
|
|
144
|
+
) as RuntimeInput[] & Partial<{ [boundaryInput]: InstanceInput }>
|
|
145
|
+
|
|
146
|
+
multipleOutput[boundaryInput] ??= { instanceId: instance.id, output: outputKey }
|
|
147
|
+
|
|
148
|
+
return multipleOutput
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return rawOutputGroup ?? outputRefs[0]
|
|
152
|
+
}) as T
|
|
103
153
|
} finally {
|
|
104
154
|
if (previousParentInstance) {
|
|
105
155
|
currentInstance = previousParentInstance
|
package/src/index.ts
CHANGED
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
export {
|
|
4
4
|
type Entity,
|
|
5
5
|
type EntityModel,
|
|
6
|
+
type EntityMeta,
|
|
7
|
+
type EntityWithMeta,
|
|
8
|
+
type EntityValue,
|
|
9
|
+
type EntityValueInput,
|
|
6
10
|
isEntity,
|
|
7
11
|
isAssignableTo,
|
|
8
12
|
defineEntity,
|
|
9
13
|
entityModelSchema,
|
|
14
|
+
getEntityId,
|
|
10
15
|
} from "./entity"
|
|
11
16
|
|
|
12
17
|
export * from "./instance"
|
|
18
|
+
export { createInput, createNonProvidedInput } from "./instance-input"
|
|
13
19
|
export * from "./unit"
|
|
14
20
|
export * from "./i18n"
|
|
15
21
|
export * from "./meta"
|
|
@@ -17,7 +23,7 @@ export * from "./terminal"
|
|
|
17
23
|
export * from "./page"
|
|
18
24
|
export * from "./trigger"
|
|
19
25
|
export * from "./worker"
|
|
20
|
-
export * from "./
|
|
26
|
+
export * from "./cuidv2d"
|
|
21
27
|
|
|
22
28
|
export {
|
|
23
29
|
// common utilities
|
|
@@ -58,6 +64,8 @@ export {
|
|
|
58
64
|
type ComponentArgumentOptions,
|
|
59
65
|
type FullComponentArgumentOptions,
|
|
60
66
|
type ComponentInputOptions,
|
|
67
|
+
type ComponentOutputOptions,
|
|
68
|
+
type FromInputComponentOutputOptions,
|
|
61
69
|
type FullComponentInputOptions,
|
|
62
70
|
type ComponentArgumentOptionsToSchema,
|
|
63
71
|
// schemas
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { InstanceInput, MultipleInput, RequiredInput, RuntimeInput } from "./instance"
|
|
2
|
+
import { boundaryInput } from "./evaluation"
|
|
3
|
+
|
|
4
|
+
type CreateInputOptions = {
|
|
5
|
+
boundary?: InstanceInput
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function appendInputPath(currentPath: string | undefined, segment: string): string {
|
|
9
|
+
return currentPath ? `${currentPath}.${segment}` : segment
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createRuntimeInputAccessor(input: RuntimeInput, boundary: InstanceInput): RuntimeInput {
|
|
13
|
+
const accessorCache = input.provided ? undefined : new Map<string, RuntimeInput>()
|
|
14
|
+
let currentBoundary = boundary
|
|
15
|
+
|
|
16
|
+
return new Proxy(input as Record<string | symbol, unknown>, {
|
|
17
|
+
get(target, property, receiver): unknown {
|
|
18
|
+
const runtimeTarget = target as RuntimeInput
|
|
19
|
+
|
|
20
|
+
if (property === boundaryInput) {
|
|
21
|
+
return currentBoundary
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (typeof property !== "string") {
|
|
25
|
+
return Reflect.get(target, property, receiver)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (property in target) {
|
|
29
|
+
return Reflect.get(target, property, receiver)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (property === "instanceId" || property === "output" || property === "path") {
|
|
33
|
+
return Reflect.get(target, property, receiver)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (runtimeTarget.provided) {
|
|
37
|
+
return Reflect.get(runtimeTarget, property, receiver)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cached = accessorCache?.get(property)
|
|
41
|
+
if (cached) {
|
|
42
|
+
return cached
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const nested = createNonProvidedInput(boundary, appendInputPath(runtimeTarget.path, property))
|
|
46
|
+
accessorCache?.set(property, nested)
|
|
47
|
+
|
|
48
|
+
return nested
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
set(target, property, value, receiver): boolean {
|
|
52
|
+
if (property === boundaryInput) {
|
|
53
|
+
currentBoundary = value as InstanceInput
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Reflect.set(target, property, value, receiver)
|
|
58
|
+
},
|
|
59
|
+
}) as RuntimeInput
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a runtime input object with boundary and nested accessor behavior.
|
|
64
|
+
*
|
|
65
|
+
* If the source input is non-provided, this function returns a non-provided proxy
|
|
66
|
+
* that can still safely chain nested properties.
|
|
67
|
+
*
|
|
68
|
+
* @param input The source input reference.
|
|
69
|
+
* @param options Optional boundary override.
|
|
70
|
+
*/
|
|
71
|
+
export function createInput(
|
|
72
|
+
input: RuntimeInput | InstanceInput,
|
|
73
|
+
options: CreateInputOptions = {},
|
|
74
|
+
): RuntimeInput {
|
|
75
|
+
if (!("provided" in input)) {
|
|
76
|
+
const boundary = options.boundary ?? input
|
|
77
|
+
|
|
78
|
+
const normalizedInput: RequiredInput = {
|
|
79
|
+
provided: true,
|
|
80
|
+
instanceId: input.instanceId,
|
|
81
|
+
output: input.output,
|
|
82
|
+
...(input.path ? { path: input.path } : {}),
|
|
83
|
+
[boundaryInput]: boundary,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return createRuntimeInputAccessor(normalizedInput, boundary)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!input.provided) {
|
|
90
|
+
const boundary = options.boundary ?? input[boundaryInput]
|
|
91
|
+
|
|
92
|
+
if (!boundary) {
|
|
93
|
+
throw new Error("Cannot create non-provided input without boundary metadata")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return createNonProvidedInput(boundary, input.path)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const boundary = options.boundary ?? input[boundaryInput]
|
|
100
|
+
|
|
101
|
+
if (!boundary) {
|
|
102
|
+
throw new Error("Cannot create provided input without boundary metadata")
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return createRuntimeInputAccessor(input as RequiredInput, boundary)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Creates a non-provided runtime input with recursive chained access support.
|
|
110
|
+
*
|
|
111
|
+
* @param boundary The boundary reference propagated across chained properties.
|
|
112
|
+
* @param path Optional path to assign to the non-provided input.
|
|
113
|
+
*/
|
|
114
|
+
export function createNonProvidedInput(boundary: InstanceInput, path?: string): RuntimeInput {
|
|
115
|
+
const target: RuntimeInput = {
|
|
116
|
+
provided: false,
|
|
117
|
+
...(path ? { path } : {}),
|
|
118
|
+
[boundaryInput]: boundary,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return createRuntimeInputAccessor(target, boundary)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createMultipleInputAccessor(
|
|
125
|
+
inputs: RuntimeInput[],
|
|
126
|
+
boundary: InstanceInput,
|
|
127
|
+
): MultipleInput {
|
|
128
|
+
const arrayInputs = [...inputs] as MultipleInput
|
|
129
|
+
arrayInputs[boundaryInput] = boundary
|
|
130
|
+
|
|
131
|
+
return new Proxy(arrayInputs as RuntimeInput[], {
|
|
132
|
+
get(target, property, receiver): unknown {
|
|
133
|
+
if (typeof property !== "string") {
|
|
134
|
+
return Reflect.get(target, property, receiver)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
property === "length" ||
|
|
139
|
+
property === "map" ||
|
|
140
|
+
property === "filter" ||
|
|
141
|
+
property === "path" ||
|
|
142
|
+
property in target
|
|
143
|
+
) {
|
|
144
|
+
return Reflect.get(target, property, receiver)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const unfolded = target.flatMap(input => {
|
|
148
|
+
if (!input.provided) {
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const selected = (input as Record<string, unknown>)[property]
|
|
153
|
+
|
|
154
|
+
if (selected === undefined || selected === null) {
|
|
155
|
+
return []
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (Array.isArray(selected)) {
|
|
159
|
+
return selected as RuntimeInput[]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return [selected as RuntimeInput]
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return createMultipleInputAccessor(unfolded, boundary)
|
|
166
|
+
},
|
|
167
|
+
}) as MultipleInput
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function createDeepOutputAccessor(output: RuntimeInput): RuntimeInput {
|
|
171
|
+
const normalizedOutput = createInput(output)
|
|
172
|
+
|
|
173
|
+
if (!normalizedOutput.provided) {
|
|
174
|
+
return normalizedOutput
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const accessorCache = new Map<string, unknown>()
|
|
178
|
+
|
|
179
|
+
return new Proxy(normalizedOutput as Record<string | symbol, unknown>, {
|
|
180
|
+
get(target, property, receiver): unknown {
|
|
181
|
+
if (typeof property !== "string") {
|
|
182
|
+
return Reflect.get(target, property, receiver)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
property === "provided" ||
|
|
187
|
+
property === "instanceId" ||
|
|
188
|
+
property === "output" ||
|
|
189
|
+
property === "path"
|
|
190
|
+
) {
|
|
191
|
+
return Reflect.get(target, property, receiver)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (property in target) {
|
|
195
|
+
return Reflect.get(target, property, receiver)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const cached = accessorCache.get(property)
|
|
199
|
+
if (cached !== undefined) {
|
|
200
|
+
return cached
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const providedTarget = target as RequiredInput
|
|
204
|
+
|
|
205
|
+
const nextInput = createInput(
|
|
206
|
+
{
|
|
207
|
+
...providedTarget,
|
|
208
|
+
path: appendInputPath(providedTarget.path, property),
|
|
209
|
+
},
|
|
210
|
+
{ boundary: providedTarget[boundaryInput] },
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
const resolved = createDeepOutputAccessor(nextInput)
|
|
214
|
+
accessorCache.set(property, resolved)
|
|
215
|
+
|
|
216
|
+
return resolved
|
|
217
|
+
},
|
|
218
|
+
}) as RuntimeInput
|
|
219
|
+
}
|