@highstate/contract 0.19.1 → 0.21.1
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 +990 -1004
- package/package.json +11 -11
- package/src/component.spec.ts +912 -2
- package/src/component.ts +531 -95
- package/src/cuidv2d.spec.ts +39 -0
- package/src/cuidv2d.ts +54 -0
- package/src/entity.ts +321 -81
- package/src/evaluation.ts +62 -13
- package/src/index.ts +18 -3
- package/src/instance-input.ts +219 -0
- package/src/instance.ts +302 -67
- package/src/json-schema.ts +115 -0
- package/src/pulumi.ts +45 -16
- package/src/shared.ts +15 -0
- package/src/unit.ts +26 -11
- package/LICENSE +0 -21
- package/dist/index.js.map +0 -1
- package/src/compaction.spec.ts +0 -215
- package/src/compaction.ts +0 -381
package/src/instance.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: for simplicity of the API, maybe fix later */
|
|
2
|
+
|
|
3
|
+
import type { Entity, EntityIncludeRef, EntityTypes } from "./entity"
|
|
3
4
|
import { z } from "zod"
|
|
4
|
-
import {
|
|
5
|
+
import { createInput, createNonProvidedInput } from "./instance-input"
|
|
5
6
|
import {
|
|
6
7
|
type GenericName,
|
|
7
8
|
genericNameSchema,
|
|
@@ -9,36 +10,235 @@ import {
|
|
|
9
10
|
type VersionedName,
|
|
10
11
|
versionedNameSchema,
|
|
11
12
|
} from "./meta"
|
|
13
|
+
import { boundaryInput, componentKindSchema } from "./shared"
|
|
12
14
|
|
|
13
15
|
declare const type: unique symbol
|
|
14
16
|
|
|
15
|
-
export type InstanceInput
|
|
17
|
+
export type InstanceInput = z.infer<typeof instanceInputSchema>
|
|
18
|
+
|
|
19
|
+
export type RuntimeInput<
|
|
20
|
+
TTypes extends EntityTypes = EntityTypes,
|
|
21
|
+
TPath extends string | undefined = string | undefined,
|
|
22
|
+
> = {
|
|
16
23
|
[type]?: TTypes
|
|
17
|
-
[boundaryInput]
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
[boundaryInput]: InstanceInput
|
|
25
|
+
} & (
|
|
26
|
+
| ({
|
|
27
|
+
provided: true
|
|
28
|
+
instanceId: InstanceId
|
|
29
|
+
output: string
|
|
30
|
+
} & (undefined extends TPath ? { path?: string } : { path: TPath }))
|
|
31
|
+
| {
|
|
32
|
+
provided: false
|
|
33
|
+
path?: string
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export type MultipleInput<
|
|
38
|
+
TInput extends RuntimeInput = RuntimeInput,
|
|
39
|
+
TSelectors extends Record<string, unknown> = Record<never, never>,
|
|
40
|
+
> = TInput[] &
|
|
41
|
+
TSelectors & {
|
|
42
|
+
[boundaryInput]: InstanceInput
|
|
43
|
+
path?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type RequiredInputChildValue<TValue> = TValue extends RuntimeInput
|
|
47
|
+
? RequiredInput<TValue>
|
|
48
|
+
: TValue extends MultipleInput<infer TItem, infer TSelectors>
|
|
49
|
+
? MultipleInput<RequiredInput<TItem>, RequiredInputChildSelectors<TSelectors>>
|
|
50
|
+
: TValue extends Array<infer TItem>
|
|
51
|
+
? Array<RequiredInputChildValue<TItem>>
|
|
52
|
+
: TValue
|
|
53
|
+
|
|
54
|
+
type RequiredInputChildSelectors<TSelectors extends Record<string, unknown>> = {
|
|
55
|
+
[K in keyof TSelectors]: RequiredInputChildValue<TSelectors[K]>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type RequiredInputChildren<TInput extends RuntimeInput> = {
|
|
59
|
+
[K in keyof TInput as K extends
|
|
60
|
+
| "instanceId"
|
|
61
|
+
| "output"
|
|
62
|
+
| "path"
|
|
63
|
+
| "provided"
|
|
64
|
+
| typeof boundaryInput
|
|
65
|
+
| typeof type
|
|
66
|
+
? never
|
|
67
|
+
: K]: RequiredInputChildValue<TInput[K]>
|
|
20
68
|
}
|
|
21
69
|
|
|
70
|
+
type RequiredInputProvidedBranch<TInput extends RuntimeInput> = Extract<TInput, { provided: true }>
|
|
71
|
+
|
|
72
|
+
type StrictRequiredInput<TInput extends RuntimeInput> = RequiredInputProvidedBranch<TInput> &
|
|
73
|
+
RequiredInputChildren<RequiredInputProvidedBranch<TInput>>
|
|
74
|
+
|
|
75
|
+
export type RequiredInput<TInput extends RuntimeInput = RuntimeInput> = StrictRequiredInput<TInput>
|
|
76
|
+
|
|
77
|
+
type RequiredMultipleInputSelectors<TInput extends RuntimeInput> = LiftMultipleSelectorFields<
|
|
78
|
+
RequiredInputChildren<RequiredInputProvidedBranch<TInput>>
|
|
79
|
+
>
|
|
80
|
+
|
|
81
|
+
export type RequiredMultipleInput<TInput extends RuntimeInput = RuntimeInput> = MultipleInput<
|
|
82
|
+
RequiredInput<TInput>,
|
|
83
|
+
RequiredMultipleInputSelectors<TInput>
|
|
84
|
+
>
|
|
85
|
+
|
|
86
|
+
export type RequiredMultipleInputs<TInput extends RuntimeInput = RuntimeInput> =
|
|
87
|
+
RequiredMultipleInput<TInput>
|
|
88
|
+
|
|
22
89
|
export type EntityInput<TEntity extends Entity> = TEntity extends Entity<
|
|
23
90
|
VersionedName,
|
|
24
|
-
infer TImplementedTypes
|
|
91
|
+
infer TImplementedTypes,
|
|
92
|
+
z.ZodTypeAny,
|
|
93
|
+
unknown,
|
|
94
|
+
unknown,
|
|
95
|
+
infer TIncludes
|
|
25
96
|
>
|
|
26
|
-
?
|
|
97
|
+
? RuntimeInput<TImplementedTypes, undefined> &
|
|
98
|
+
(TIncludes extends Record<string, EntityIncludeRef>
|
|
99
|
+
? EntityInputIncludes<TIncludes>
|
|
100
|
+
: Record<never, never>)
|
|
27
101
|
: never
|
|
28
102
|
|
|
29
|
-
export type
|
|
30
|
-
|
|
31
|
-
|
|
103
|
+
export type RequiredEntityInput<TEntity extends Entity> = RequiredInput<EntityInput<TEntity>>
|
|
104
|
+
|
|
105
|
+
type EntityInputDepth = [1, 1, 1, 1]
|
|
106
|
+
|
|
107
|
+
type DecDepth<T extends readonly unknown[]> = T extends readonly [unknown, ...infer Rest]
|
|
108
|
+
? Rest
|
|
109
|
+
: readonly []
|
|
110
|
+
|
|
111
|
+
type ResolveIncludeEntity<TIncludeRef> = TIncludeRef extends { entity: infer E }
|
|
112
|
+
? E extends () => infer ER
|
|
113
|
+
? ER
|
|
114
|
+
: E
|
|
115
|
+
: TIncludeRef
|
|
116
|
+
|
|
117
|
+
type IncludeMultiple<TIncludeRef> = TIncludeRef extends { multiple?: infer M }
|
|
118
|
+
? M extends true
|
|
119
|
+
? true
|
|
120
|
+
: false
|
|
121
|
+
: false
|
|
122
|
+
|
|
123
|
+
type IncludeRequired<TIncludeRef> = TIncludeRef extends { required?: infer R }
|
|
124
|
+
? R extends false
|
|
125
|
+
? false
|
|
126
|
+
: true
|
|
127
|
+
: true
|
|
128
|
+
|
|
129
|
+
type IncludeRefValue<
|
|
130
|
+
TIncludeRef,
|
|
131
|
+
TDepth extends readonly unknown[],
|
|
132
|
+
> = ResolveIncludeEntity<TIncludeRef> extends infer TEntity
|
|
133
|
+
? TEntity extends Entity
|
|
134
|
+
? IncludeMultiple<TIncludeRef> extends true
|
|
135
|
+
? MultipleInput<
|
|
136
|
+
EntityInputWithDepth<TEntity, TDepth>,
|
|
137
|
+
TDepth extends readonly []
|
|
138
|
+
? Record<never, never>
|
|
139
|
+
: TEntity extends Entity<
|
|
140
|
+
VersionedName,
|
|
141
|
+
EntityTypes,
|
|
142
|
+
z.ZodTypeAny,
|
|
143
|
+
unknown,
|
|
144
|
+
unknown,
|
|
145
|
+
infer TIncludes
|
|
146
|
+
>
|
|
147
|
+
? TIncludes extends Record<string, EntityIncludeRef>
|
|
148
|
+
? LiftMultipleSelectorFields<EntityInputIncludes<TIncludes, DecDepth<TDepth>>>
|
|
149
|
+
: Record<never, never>
|
|
150
|
+
: Record<never, never>
|
|
151
|
+
>
|
|
152
|
+
: IncludeRequired<TIncludeRef> extends false
|
|
153
|
+
? OptionalEntityInput<TEntity, TDepth>
|
|
154
|
+
: EntityInputWithDepth<TEntity, TDepth>
|
|
155
|
+
: never
|
|
156
|
+
: never
|
|
32
157
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
158
|
+
type OptionalEntityInput<TEntity extends Entity, TDepth extends readonly unknown[]> =
|
|
159
|
+
| ({ provided: true } & EntityInputWithDepth<TEntity, TDepth>)
|
|
160
|
+
| ({ provided: false; [boundaryInput]: InstanceInput } & OptionalizedInputFields<
|
|
161
|
+
EntityInputWithDepth<TEntity, TDepth>
|
|
162
|
+
>)
|
|
163
|
+
|
|
164
|
+
type OptionalizeInputValue<T> = [T] extends [InstanceInput]
|
|
165
|
+
? RuntimeInput
|
|
166
|
+
: T extends MultipleInput<infer TItem, infer TSelectors>
|
|
167
|
+
? MultipleInput<OptionalizeInputValue<TItem>, OptionalizedSelectorFields<TSelectors>>
|
|
168
|
+
: T extends Array<infer TItem>
|
|
169
|
+
? Array<OptionalizeInputValue<TItem>>
|
|
170
|
+
: T
|
|
171
|
+
|
|
172
|
+
type OptionalizedSelectorFields<TSelectors extends Record<string, unknown>> = {
|
|
173
|
+
[K in keyof TSelectors]: OptionalizeInputValue<TSelectors[K]>
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
type RuntimeInputSelectorFields<TInput extends RuntimeInput> = {
|
|
177
|
+
[K in keyof TInput as K extends
|
|
178
|
+
| "instanceId"
|
|
179
|
+
| "output"
|
|
180
|
+
| "path"
|
|
181
|
+
| "provided"
|
|
182
|
+
| typeof boundaryInput
|
|
183
|
+
| typeof type
|
|
184
|
+
? never
|
|
185
|
+
: K]: LiftMultipleSelectorValue<TInput[K]>
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
type LiftMultipleSelectorValue<T> = T extends MultipleInput<infer TItem, infer TSelectors>
|
|
189
|
+
? MultipleInput<TItem, LiftMultipleSelectorFields<TSelectors>>
|
|
190
|
+
: T extends RuntimeInput
|
|
191
|
+
? MultipleInput<T, RuntimeInputSelectorFields<T>>
|
|
192
|
+
: T
|
|
193
|
+
|
|
194
|
+
type LiftMultipleSelectorFields<TSelectors extends Record<string, unknown>> = {
|
|
195
|
+
[K in keyof TSelectors]: LiftMultipleSelectorValue<TSelectors[K]>
|
|
196
|
+
}
|
|
37
197
|
|
|
38
|
-
|
|
39
|
-
|
|
198
|
+
type OptionalizedInputFields<TInput extends RuntimeInput> = {
|
|
199
|
+
[K in keyof TInput as K extends "instanceId" | "output" | "provided" | typeof boundaryInput
|
|
200
|
+
? never
|
|
201
|
+
: K]: OptionalizeInputValue<TInput[K]>
|
|
40
202
|
}
|
|
41
203
|
|
|
204
|
+
type EntityInputWithDepth<
|
|
205
|
+
TEntity extends Entity,
|
|
206
|
+
TDepth extends readonly unknown[],
|
|
207
|
+
> = TEntity extends Entity<
|
|
208
|
+
VersionedName,
|
|
209
|
+
infer TImplementedTypes,
|
|
210
|
+
z.ZodTypeAny,
|
|
211
|
+
unknown,
|
|
212
|
+
unknown,
|
|
213
|
+
infer TIncludes
|
|
214
|
+
>
|
|
215
|
+
? RuntimeInput<TImplementedTypes, undefined> &
|
|
216
|
+
(TDepth extends readonly []
|
|
217
|
+
? Record<never, never>
|
|
218
|
+
: TIncludes extends Record<string, EntityIncludeRef>
|
|
219
|
+
? EntityInputIncludes<TIncludes, DecDepth<TDepth>>
|
|
220
|
+
: Record<never, never>)
|
|
221
|
+
: never
|
|
222
|
+
|
|
223
|
+
type EntityInputIncludes<
|
|
224
|
+
TIncludes extends Record<string, EntityIncludeRef>,
|
|
225
|
+
TDepth extends readonly unknown[] = EntityInputDepth,
|
|
226
|
+
> = TIncludes extends Record<string, never>
|
|
227
|
+
? Record<never, never>
|
|
228
|
+
: string extends keyof TIncludes
|
|
229
|
+
? Record<never, never>
|
|
230
|
+
: {
|
|
231
|
+
[K in keyof TIncludes]: IncludeRefValue<TIncludes[K], TDepth>
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export type InstanceInputGroup<TTypes extends EntityTypes = EntityTypes> = MultipleInput<
|
|
235
|
+
RuntimeInput<TTypes>
|
|
236
|
+
>
|
|
237
|
+
|
|
238
|
+
type SelectInputResult<TInput extends RuntimeInput> = TInput extends RequiredInput<infer TRuntime>
|
|
239
|
+
? TRuntime
|
|
240
|
+
: TInput
|
|
241
|
+
|
|
42
242
|
export const positionSchema = z.object({
|
|
43
243
|
x: z.number(),
|
|
44
244
|
y: z.number(),
|
|
@@ -53,6 +253,7 @@ export type InstanceId = z.infer<typeof instanceIdSchema>
|
|
|
53
253
|
export const instanceInputSchema = z.object({
|
|
54
254
|
instanceId: instanceIdSchema,
|
|
55
255
|
output: z.string(),
|
|
256
|
+
path: z.string().optional(),
|
|
56
257
|
})
|
|
57
258
|
|
|
58
259
|
export const hubInputSchema = z.object({
|
|
@@ -209,52 +410,52 @@ export function parseInstanceId(
|
|
|
209
410
|
return parts as [VersionedName, GenericName]
|
|
210
411
|
}
|
|
211
412
|
|
|
212
|
-
|
|
213
|
-
|
|
413
|
+
/**
|
|
414
|
+
* Selects a single input from a group by instance id or instance name.
|
|
415
|
+
*
|
|
416
|
+
* The function accepts a plain array but still preserves Highstate boundary semantics.
|
|
417
|
+
* It first tries to read boundary metadata from the array object itself and then falls back
|
|
418
|
+
* to the first item boundary when needed.
|
|
419
|
+
*
|
|
420
|
+
* @param inputs The input group to search in.
|
|
421
|
+
* @param name The instance id or instance name to select.
|
|
422
|
+
* @returns The selected input with inherited group boundary, or a provided-false input when missing.
|
|
423
|
+
*/
|
|
424
|
+
export function selectInput<TInput extends RuntimeInput>(
|
|
425
|
+
inputs: TInput[] & Partial<{ [boundaryInput]: InstanceInput }>,
|
|
214
426
|
name: string,
|
|
215
|
-
):
|
|
216
|
-
const
|
|
217
|
-
input => parseInstanceId(input.instanceId)[1] === name || input.instanceId === name,
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
if (matchedInputs.length === 0) {
|
|
221
|
-
return null
|
|
222
|
-
}
|
|
427
|
+
): SelectInputResult<TInput> {
|
|
428
|
+
const groupBoundary = inputs[boundaryInput] ?? inputs[0]?.[boundaryInput]
|
|
223
429
|
|
|
224
|
-
if (
|
|
430
|
+
if (inputs.length === 0 && !groupBoundary) {
|
|
225
431
|
throw new Error(
|
|
226
|
-
`
|
|
432
|
+
`Cannot select input "${name}": empty input group has no boundary metadata to build a missing input reference.`,
|
|
227
433
|
)
|
|
228
434
|
}
|
|
229
435
|
|
|
230
|
-
|
|
231
|
-
|
|
436
|
+
const input = inputs.find(
|
|
437
|
+
input =>
|
|
438
|
+
input.provided &&
|
|
439
|
+
(input.instanceId === name || parseInstanceId(input.instanceId)[1] === name),
|
|
440
|
+
)
|
|
232
441
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
|
|
442
|
+
if (!input || !input.provided) {
|
|
443
|
+
const fallbackBoundary =
|
|
444
|
+
groupBoundary ??
|
|
445
|
+
inputs.find(input => Boolean(input[boundaryInput]))?.[boundaryInput] ??
|
|
446
|
+
inputs[0]?.[boundaryInput]
|
|
238
447
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
448
|
+
if (!fallbackBoundary) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`Cannot select input "${name}": input group has no boundary metadata to build a missing input reference.`,
|
|
451
|
+
)
|
|
452
|
+
}
|
|
242
453
|
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export function findInputs<T extends EntityTypes>(
|
|
247
|
-
inputs: InstanceInput<T>[],
|
|
248
|
-
names: string[],
|
|
249
|
-
): InstanceInput<T>[] {
|
|
250
|
-
return names.map(name => findInput(inputs, name)).filter(Boolean) as InstanceInput<T>[]
|
|
251
|
-
}
|
|
454
|
+
return createNonProvidedInput(fallbackBoundary) as SelectInputResult<TInput>
|
|
455
|
+
}
|
|
252
456
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
names: string[],
|
|
256
|
-
): InstanceInput<T>[] {
|
|
257
|
-
return names.map(name => findRequiredInput(inputs, name))
|
|
457
|
+
const boundary = groupBoundary ?? input[boundaryInput]
|
|
458
|
+
return createInput(input, { boundary }) as SelectInputResult<TInput>
|
|
258
459
|
}
|
|
259
460
|
|
|
260
461
|
/**
|
|
@@ -265,8 +466,7 @@ export function findRequiredInputs<T extends EntityTypes>(
|
|
|
265
466
|
export enum HighstateSignature {
|
|
266
467
|
Artifact = "d55c63ac-3174-4756-808f-f778e99af0d1",
|
|
267
468
|
Yaml = "c857cac5-caa6-4421-b82c-e561fbce6367",
|
|
268
|
-
|
|
269
|
-
Ref = "6d7f9da0-9cb6-496d-b72e-cf85ee4d9cf8",
|
|
469
|
+
Secret = "240e5789-6ae4-4b22-b9d8-87169e8b4bab",
|
|
270
470
|
}
|
|
271
471
|
|
|
272
472
|
export const yamlValueSchema = z.object({
|
|
@@ -274,17 +474,6 @@ export const yamlValueSchema = z.object({
|
|
|
274
474
|
value: z.string(),
|
|
275
475
|
})
|
|
276
476
|
|
|
277
|
-
export const objectWithIdSchema = z.object({
|
|
278
|
-
[HighstateSignature.Id]: z.literal(true),
|
|
279
|
-
id: z.number(),
|
|
280
|
-
value: z.unknown(),
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
export const objectRefSchema = z.object({
|
|
284
|
-
[HighstateSignature.Ref]: z.literal(true),
|
|
285
|
-
id: z.number(),
|
|
286
|
-
})
|
|
287
|
-
|
|
288
477
|
export type YamlValue = z.infer<typeof yamlValueSchema>
|
|
289
478
|
|
|
290
479
|
export const fileMetaSchema = z.object({
|
|
@@ -351,3 +540,49 @@ export const instanceStatusFieldSchema = z.object({
|
|
|
351
540
|
|
|
352
541
|
export type InstanceStatusFieldValue = z.infer<typeof instanceStatusFieldValueSchema>
|
|
353
542
|
export type InstanceStatusField = z.infer<typeof instanceStatusFieldSchema>
|
|
543
|
+
|
|
544
|
+
export function secretSchema<TSchema extends z.ZodType>(
|
|
545
|
+
schema: TSchema,
|
|
546
|
+
): z.ZodCodec<
|
|
547
|
+
z.ZodUnion<
|
|
548
|
+
[
|
|
549
|
+
z.ZodObject<{
|
|
550
|
+
[HighstateSignature.Secret]: z.ZodLiteral<true>
|
|
551
|
+
value: TSchema
|
|
552
|
+
}>,
|
|
553
|
+
TSchema,
|
|
554
|
+
]
|
|
555
|
+
>,
|
|
556
|
+
z.ZodObject<{
|
|
557
|
+
[HighstateSignature.Secret]: z.ZodLiteral<true>
|
|
558
|
+
value: TSchema
|
|
559
|
+
}>
|
|
560
|
+
> {
|
|
561
|
+
const secretType = z.object({
|
|
562
|
+
[HighstateSignature.Secret]: z.literal(true),
|
|
563
|
+
value: schema,
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
return z.codec(z.union([secretType, schema]), secretType, {
|
|
567
|
+
decode: value =>
|
|
568
|
+
typeof value === "object" && value !== null && HighstateSignature.Secret in value
|
|
569
|
+
? (value as any)
|
|
570
|
+
: { [HighstateSignature.Secret]: true, value },
|
|
571
|
+
encode: value => value as any,
|
|
572
|
+
})
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export type Secret<T> = {
|
|
576
|
+
[HighstateSignature.Secret]: true
|
|
577
|
+
value: T
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Checks if the given value is a Secret.
|
|
582
|
+
*
|
|
583
|
+
* @param value The value to check.
|
|
584
|
+
* @returns True if the value is a Secret, false otherwise.
|
|
585
|
+
*/
|
|
586
|
+
export function isSecret<T>(value: unknown): value is Secret<T> {
|
|
587
|
+
return typeof value === "object" && value !== null && HighstateSignature.Secret in value
|
|
588
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes JSON schema produced by Zod exports.
|
|
7
|
+
*
|
|
8
|
+
* In particular, it merges intersections of object schemas represented as
|
|
9
|
+
* `allOf: [{ type: "object", ... }, { type: "object", ... }, ...]` into a single
|
|
10
|
+
* object schema so that validators treat `additionalProperties` consistently.
|
|
11
|
+
*/
|
|
12
|
+
export function fixJsonSchema(schema: unknown): unknown {
|
|
13
|
+
if (!isRecord(schema)) {
|
|
14
|
+
return schema
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const allOf = schema.allOf
|
|
18
|
+
if (Array.isArray(allOf) && allOf.length > 0) {
|
|
19
|
+
const objectSchemas: Record<string, unknown>[] = []
|
|
20
|
+
const otherSchemas: unknown[] = []
|
|
21
|
+
|
|
22
|
+
for (const item of allOf) {
|
|
23
|
+
if (isRecord(item) && item.type === "object") {
|
|
24
|
+
objectSchemas.push(item)
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
otherSchemas.push(item)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (objectSchemas.length > 1) {
|
|
32
|
+
const required = new Set<string>()
|
|
33
|
+
const properties: Record<string, unknown> = {}
|
|
34
|
+
let additionalProperties: unknown
|
|
35
|
+
|
|
36
|
+
for (const objectSchema of objectSchemas) {
|
|
37
|
+
const objectRequired = objectSchema.required
|
|
38
|
+
if (Array.isArray(objectRequired)) {
|
|
39
|
+
for (const key of objectRequired) {
|
|
40
|
+
if (typeof key === "string") {
|
|
41
|
+
required.add(key)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const objectProperties = objectSchema.properties
|
|
47
|
+
if (isRecord(objectProperties)) {
|
|
48
|
+
for (const [key, value] of Object.entries(objectProperties)) {
|
|
49
|
+
const existing = properties[key]
|
|
50
|
+
if (existing === undefined) {
|
|
51
|
+
properties[key] = value
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
properties[key] = { allOf: [existing, value] }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if ("additionalProperties" in objectSchema) {
|
|
60
|
+
if (additionalProperties === undefined) {
|
|
61
|
+
additionalProperties = objectSchema.additionalProperties
|
|
62
|
+
} else if (additionalProperties !== objectSchema.additionalProperties) {
|
|
63
|
+
additionalProperties = false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const mergedObjectSchema: Record<string, unknown> = {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties,
|
|
71
|
+
...(required.size > 0 ? { required: Array.from(required) } : {}),
|
|
72
|
+
...(additionalProperties !== undefined
|
|
73
|
+
? { additionalProperties }
|
|
74
|
+
: { additionalProperties: false }),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const merged =
|
|
78
|
+
otherSchemas.length === 0
|
|
79
|
+
? mergedObjectSchema
|
|
80
|
+
: {
|
|
81
|
+
...schema,
|
|
82
|
+
allOf: [mergedObjectSchema, ...otherSchemas],
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return fixJsonSchema(merged)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const next: Record<string, unknown> = { ...schema }
|
|
90
|
+
|
|
91
|
+
if (Array.isArray(next.allOf)) {
|
|
92
|
+
next.allOf = next.allOf.map(fixJsonSchema)
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(next.anyOf)) {
|
|
95
|
+
next.anyOf = next.anyOf.map(fixJsonSchema)
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(next.oneOf)) {
|
|
98
|
+
next.oneOf = next.oneOf.map(fixJsonSchema)
|
|
99
|
+
}
|
|
100
|
+
if (isRecord(next.properties)) {
|
|
101
|
+
next.properties = Object.fromEntries(
|
|
102
|
+
Object.entries(next.properties).map(([key, value]) => [key, fixJsonSchema(value)]),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
if (isRecord(next.items)) {
|
|
106
|
+
next.items = fixJsonSchema(next.items)
|
|
107
|
+
} else if (Array.isArray(next.items)) {
|
|
108
|
+
next.items = next.items.map(fixJsonSchema)
|
|
109
|
+
}
|
|
110
|
+
if (isRecord(next.additionalProperties)) {
|
|
111
|
+
next.additionalProperties = fixJsonSchema(next.additionalProperties)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return next
|
|
115
|
+
}
|
package/src/pulumi.ts
CHANGED
|
@@ -1,33 +1,49 @@
|
|
|
1
1
|
import { parse } from "yaml"
|
|
2
2
|
import { z } from "zod"
|
|
3
|
-
import { entityInclusionSchema } from "./entity"
|
|
4
3
|
import {
|
|
5
4
|
fileMetaSchema,
|
|
6
5
|
HighstateSignature,
|
|
7
|
-
instanceIdSchema,
|
|
8
6
|
instanceInputSchema,
|
|
7
|
+
secretSchema,
|
|
9
8
|
yamlValueSchema,
|
|
10
9
|
} from "./instance"
|
|
11
10
|
import { commonObjectMetaSchema } from "./meta"
|
|
12
11
|
import { triggerInvocationSchema } from "./trigger"
|
|
13
12
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
export const unitInputReference = z.object({
|
|
13
|
+
export const unitInputSourceSchema = z.object({
|
|
17
14
|
...instanceInputSchema.shape,
|
|
15
|
+
})
|
|
18
16
|
|
|
17
|
+
export type UnitInputSource = z.infer<typeof unitInputSourceSchema>
|
|
18
|
+
|
|
19
|
+
export const unitInputValueSchema = z.object({
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* Inline resolved value passed to the unit.
|
|
22
|
+
*
|
|
23
|
+
* The backend is responsible for resolving the correct entity snapshot value
|
|
24
|
+
* and applying any inclusion transformations.
|
|
21
25
|
*/
|
|
22
|
-
|
|
26
|
+
value: z.unknown(),
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Optional provenance of the value.
|
|
30
|
+
*/
|
|
31
|
+
source: unitInputSourceSchema.optional(),
|
|
23
32
|
})
|
|
24
33
|
|
|
34
|
+
export type UnitInputValue = z.infer<typeof unitInputValueSchema>
|
|
35
|
+
|
|
25
36
|
export const unitConfigSchema = z.object({
|
|
26
37
|
/**
|
|
27
38
|
* The ID of the instance.
|
|
28
39
|
*/
|
|
29
40
|
instanceId: z.string(),
|
|
30
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The state ID of the instance.
|
|
44
|
+
*/
|
|
45
|
+
stateId: z.string(),
|
|
46
|
+
|
|
31
47
|
/**
|
|
32
48
|
* The record of argument values for the unit.
|
|
33
49
|
*/
|
|
@@ -36,7 +52,7 @@ export const unitConfigSchema = z.object({
|
|
|
36
52
|
/**
|
|
37
53
|
* The record of input references for the unit.
|
|
38
54
|
*/
|
|
39
|
-
inputs: z.record(z.string(),
|
|
55
|
+
inputs: z.record(z.string(), unitInputValueSchema.array()),
|
|
40
56
|
|
|
41
57
|
/**
|
|
42
58
|
* The list of triggers that have been invoked for this unit.
|
|
@@ -44,14 +60,11 @@ export const unitConfigSchema = z.object({
|
|
|
44
60
|
invokedTriggers: triggerInvocationSchema.array(),
|
|
45
61
|
|
|
46
62
|
/**
|
|
47
|
-
* The
|
|
63
|
+
* The record of secret values provided to the unit.
|
|
64
|
+
*
|
|
65
|
+
* It is stored in Pulumi stack config as a secret.
|
|
48
66
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* The map of instance ID to state ID in order to resolve instance references.
|
|
53
|
-
*/
|
|
54
|
-
stateIdMap: z.record(instanceIdSchema, z.string()),
|
|
67
|
+
secretValues: z.record(z.string(), z.unknown()),
|
|
55
68
|
|
|
56
69
|
/**
|
|
57
70
|
* The base path for imports.
|
|
@@ -87,7 +100,6 @@ export function parseArgumentValue(value: unknown): unknown {
|
|
|
87
100
|
|
|
88
101
|
export enum HighstateConfigKey {
|
|
89
102
|
Config = "highstate",
|
|
90
|
-
Secrets = "highstate.secrets",
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
export const unitArtifactId = Symbol("unitArtifactId")
|
|
@@ -118,6 +130,23 @@ export const fileContentSchema = z.union([
|
|
|
118
130
|
*/
|
|
119
131
|
value: z.string(),
|
|
120
132
|
}),
|
|
133
|
+
z.object({
|
|
134
|
+
type: z.literal("embedded-secret"),
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Whether the content is binary or not.
|
|
138
|
+
*
|
|
139
|
+
* If true, the `value` will be a base64 encoded string.
|
|
140
|
+
*/
|
|
141
|
+
isBinary: z.boolean().optional(),
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The content of the file wrapped as a Highstate secret.
|
|
145
|
+
*
|
|
146
|
+
* If `isBinary` is true, this will be a base64 encoded string.
|
|
147
|
+
*/
|
|
148
|
+
value: secretSchema(z.string()),
|
|
149
|
+
}),
|
|
121
150
|
z.object({
|
|
122
151
|
type: z.literal("artifact"),
|
|
123
152
|
...unitArtifactSchema.shape,
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { InstanceInput } from "./instance"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
export const componentKindSchema = z.enum(["composite", "unit"])
|
|
5
|
+
export type ComponentKind = z.infer<typeof componentKindSchema>
|
|
6
|
+
|
|
7
|
+
export const runtimeSchema = Symbol("runtimeSchema")
|
|
8
|
+
export const kind = Symbol("kind")
|
|
9
|
+
export const boundaryInput = Symbol("boundaryInput")
|
|
10
|
+
|
|
11
|
+
export function inputKey(input: InstanceInput): string {
|
|
12
|
+
return input.path
|
|
13
|
+
? `${input.instanceId}:${input.output}:${input.path}`
|
|
14
|
+
: `${input.instanceId}:${input.output}`
|
|
15
|
+
}
|