@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/component.ts
CHANGED
|
@@ -2,12 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Simplify } from "type-fest"
|
|
4
4
|
import type { Entity, implementedTypes } from "./entity"
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
EntityInput,
|
|
7
|
+
InstanceId,
|
|
8
|
+
InstanceInput,
|
|
9
|
+
MultipleInput,
|
|
10
|
+
RequiredInput,
|
|
11
|
+
RequiredMultipleInput,
|
|
12
|
+
RuntimeInput,
|
|
13
|
+
} from "./instance"
|
|
14
|
+
import type { OptionalEmptyRecords, PartialKeys } from "./utils"
|
|
6
15
|
import { isNonNullish, mapValues, pickBy, uniqueBy } from "remeda"
|
|
7
16
|
import { z } from "zod"
|
|
8
|
-
import {
|
|
17
|
+
import { registerInstance } from "./evaluation"
|
|
9
18
|
import { camelCaseToHumanReadable } from "./i18n"
|
|
10
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
createDeepOutputAccessor,
|
|
21
|
+
createInput,
|
|
22
|
+
createMultipleInputAccessor,
|
|
23
|
+
createNonProvidedInput,
|
|
24
|
+
} from "./instance-input"
|
|
11
25
|
import {
|
|
12
26
|
fieldNameSchema,
|
|
13
27
|
genericNameSchema,
|
|
@@ -16,8 +30,14 @@ import {
|
|
|
16
30
|
type VersionedName,
|
|
17
31
|
versionedNameSchema,
|
|
18
32
|
} from "./meta"
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
import {
|
|
34
|
+
boundaryInput,
|
|
35
|
+
type ComponentKind,
|
|
36
|
+
componentKindSchema,
|
|
37
|
+
inputKey,
|
|
38
|
+
kind,
|
|
39
|
+
runtimeSchema,
|
|
40
|
+
} from "./shared"
|
|
21
41
|
|
|
22
42
|
let validationEnabled = true
|
|
23
43
|
|
|
@@ -25,9 +45,6 @@ export function setValidationEnabled(enabled: boolean): void {
|
|
|
25
45
|
validationEnabled = enabled
|
|
26
46
|
}
|
|
27
47
|
|
|
28
|
-
export const componentKindSchema = z.enum(["composite", "unit"])
|
|
29
|
-
export type ComponentKind = z.infer<typeof componentKindSchema>
|
|
30
|
-
|
|
31
48
|
export const componentArgumentSchema = z.object({
|
|
32
49
|
/**
|
|
33
50
|
* The JSON schema of the argument value.
|
|
@@ -77,6 +94,13 @@ export const componentInputSchema = z.object({
|
|
|
77
94
|
*/
|
|
78
95
|
type: versionedNameSchema,
|
|
79
96
|
|
|
97
|
+
/**
|
|
98
|
+
* The input name this output type is derived from.
|
|
99
|
+
*
|
|
100
|
+
* If set, the output uses the referenced input as its fallback type source.
|
|
101
|
+
*/
|
|
102
|
+
fromInput: fieldNameSchema.optional(),
|
|
103
|
+
|
|
80
104
|
/**
|
|
81
105
|
* Whether the input is required.
|
|
82
106
|
*/
|
|
@@ -107,33 +131,75 @@ export type FullComponentInputOptions = {
|
|
|
107
131
|
|
|
108
132
|
export type ComponentInputOptions = Entity | FullComponentInputOptions
|
|
109
133
|
|
|
134
|
+
export type FromInputComponentOutputOptions<TInputName extends string = string> = {
|
|
135
|
+
fromInput: TInputName
|
|
136
|
+
meta?: PartialKeys<ComponentInput["meta"], "title">
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type ComponentOutputOptions<
|
|
140
|
+
TInputs extends Record<string, ComponentInputOptions> = Record<string, ComponentInputOptions>,
|
|
141
|
+
> = ComponentInputOptions | FromInputComponentOutputOptions<Extract<keyof TInputs, string>>
|
|
142
|
+
|
|
110
143
|
type ComponentInputOptionsToOutputRef<T extends ComponentInputOptions> = T extends Entity
|
|
111
|
-
?
|
|
144
|
+
? EntityInput<T>
|
|
112
145
|
: T extends FullComponentInputOptions
|
|
113
|
-
? T["
|
|
114
|
-
? T["
|
|
115
|
-
|
|
116
|
-
: InstanceInput<T["entity"][typeof implementedTypes]> | undefined
|
|
117
|
-
: T["multiple"] extends true
|
|
118
|
-
? InstanceInput<T["entity"][typeof implementedTypes]>[]
|
|
119
|
-
: InstanceInput<T["entity"][typeof implementedTypes]>
|
|
146
|
+
? T["multiple"] extends true
|
|
147
|
+
? RequiredMultipleInput<EntityInput<T["entity"]>>
|
|
148
|
+
: EntityInput<T["entity"]>
|
|
120
149
|
: never
|
|
121
150
|
|
|
151
|
+
type MultipleInputConstructorValue<TEntity extends Entity> =
|
|
152
|
+
| MultipleInput<EntityInput<TEntity>>
|
|
153
|
+
| EntityInput<TEntity>[]
|
|
154
|
+
| InstanceInput[]
|
|
155
|
+
| Array<MultipleInput<EntityInput<TEntity>> | EntityInput<TEntity>[] | InstanceInput[]>
|
|
156
|
+
|
|
122
157
|
/**
|
|
123
158
|
* The type-level specification of a component input hold by the component model.
|
|
124
159
|
*/
|
|
125
|
-
export type ComponentInputSpec
|
|
160
|
+
export type ComponentInputSpec<TEntity extends Entity = Entity> = [
|
|
161
|
+
entity: TEntity,
|
|
162
|
+
required: boolean,
|
|
163
|
+
multiple: boolean,
|
|
164
|
+
]
|
|
126
165
|
|
|
127
166
|
export type ComponentInputOptionsToSpec<T extends ComponentInputOptions> = T extends Entity
|
|
128
|
-
? [T, true, false]
|
|
167
|
+
? [T, true, false]
|
|
129
168
|
: T extends FullComponentInputOptions
|
|
130
169
|
? T["required"] extends false
|
|
131
170
|
? T["multiple"] extends true
|
|
132
|
-
?
|
|
133
|
-
|
|
171
|
+
? ComponentInputSpec<T["entity"]> extends infer Spec
|
|
172
|
+
? Spec extends ComponentInputSpec
|
|
173
|
+
? [Spec[0], false, true]
|
|
174
|
+
: never
|
|
175
|
+
: never
|
|
176
|
+
: ComponentInputSpec<T["entity"]> extends infer Spec
|
|
177
|
+
? Spec extends ComponentInputSpec
|
|
178
|
+
? [Spec[0], false, false]
|
|
179
|
+
: never
|
|
180
|
+
: never
|
|
134
181
|
: T["multiple"] extends true
|
|
135
|
-
?
|
|
136
|
-
|
|
182
|
+
? ComponentInputSpec<T["entity"]> extends infer Spec
|
|
183
|
+
? Spec extends ComponentInputSpec
|
|
184
|
+
? [Spec[0], true, true]
|
|
185
|
+
: never
|
|
186
|
+
: never
|
|
187
|
+
: ComponentInputSpec<T["entity"]> extends infer Spec
|
|
188
|
+
? Spec extends ComponentInputSpec
|
|
189
|
+
? [Spec[0], true, false]
|
|
190
|
+
: never
|
|
191
|
+
: never
|
|
192
|
+
: never
|
|
193
|
+
|
|
194
|
+
export type ComponentOutputOptionsToSpec<
|
|
195
|
+
TOutput extends ComponentOutputOptions<TInputs>,
|
|
196
|
+
TInputs extends Record<string, ComponentInputOptions>,
|
|
197
|
+
> = TOutput extends FromInputComponentOutputOptions<infer TInputName>
|
|
198
|
+
? TInputName extends keyof TInputs
|
|
199
|
+
? ComponentInputOptionsToSpec<TInputs[TInputName]>
|
|
200
|
+
: never
|
|
201
|
+
: TOutput extends ComponentInputOptions
|
|
202
|
+
? ComponentInputOptionsToSpec<TOutput>
|
|
137
203
|
: never
|
|
138
204
|
|
|
139
205
|
export type ComponentInputOptionsMapToSpecMap<T extends Record<string, ComponentInputOptions>> =
|
|
@@ -141,14 +207,23 @@ export type ComponentInputOptionsMapToSpecMap<T extends Record<string, Component
|
|
|
141
207
|
? Record<string, never>
|
|
142
208
|
: { [K in keyof T]: ComponentInputOptionsToSpec<T[K]> }
|
|
143
209
|
|
|
144
|
-
type ComponentInputMapToValue<T extends Record<string, ComponentInputOptions>> =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
210
|
+
type ComponentInputMapToValue<T extends Record<string, ComponentInputOptions>> = {
|
|
211
|
+
[K in keyof T]: ComponentInputOptionsToOutputRef<T[K]>
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
type ComponentOutputMapToCreateOutputValue<
|
|
215
|
+
TOutputs extends Record<string, ComponentOutputOptions<TInputs>>,
|
|
216
|
+
TInputs extends Record<string, ComponentInputOptions>,
|
|
217
|
+
> = {
|
|
218
|
+
[K in keyof TOutputs]: InputSpecToInputRef<ComponentOutputOptionsToSpec<TOutputs[K], TInputs>>
|
|
219
|
+
}
|
|
148
220
|
|
|
149
|
-
type
|
|
150
|
-
|
|
151
|
-
|
|
221
|
+
type ComponentOutputMapToReturnType<
|
|
222
|
+
TOutputs extends Record<string, ComponentOutputOptions<TInputs>>,
|
|
223
|
+
TInputs extends Record<string, ComponentInputOptions>,
|
|
224
|
+
> = TOutputs extends Record<string, never> // biome-ignore lint/suspicious/noConfusingVoidType: this is return type
|
|
225
|
+
? void
|
|
226
|
+
: Partial<ComponentOutputMapToCreateOutputValue<TOutputs, TInputs>>
|
|
152
227
|
|
|
153
228
|
export type ComponentParams<
|
|
154
229
|
TArgs extends Record<string, ComponentArgumentOptions>,
|
|
@@ -174,7 +249,7 @@ export type ComponentOptions<
|
|
|
174
249
|
TType extends VersionedName,
|
|
175
250
|
TArgs extends Record<string, ComponentArgumentOptions>,
|
|
176
251
|
TInputs extends Record<string, ComponentInputOptions>,
|
|
177
|
-
TOutputs extends Record<string,
|
|
252
|
+
TOutputs extends Record<string, ComponentOutputOptions<TInputs>>,
|
|
178
253
|
> = {
|
|
179
254
|
/**
|
|
180
255
|
* The type of the component.
|
|
@@ -215,7 +290,9 @@ export type ComponentOptions<
|
|
|
215
290
|
* Their names are not automatically prefixed with the component name,
|
|
216
291
|
* so you must prefix them manually if needed or guarantee uniqueness in another way.
|
|
217
292
|
*/
|
|
218
|
-
create: (
|
|
293
|
+
create: (
|
|
294
|
+
params: ComponentParams<TArgs, TInputs>,
|
|
295
|
+
) => ComponentOutputMapToReturnType<TOutputs, TInputs>
|
|
219
296
|
|
|
220
297
|
/**
|
|
221
298
|
* For internal use only.
|
|
@@ -290,34 +367,131 @@ export type ComponentModel = z.infer<typeof componentModelSchema>
|
|
|
290
367
|
|
|
291
368
|
type InputSpecToInputRef<T extends ComponentInputSpec> = T[1] extends true
|
|
292
369
|
? T[2] extends true
|
|
293
|
-
?
|
|
294
|
-
:
|
|
370
|
+
? MultipleInputConstructorValue<T[0]>
|
|
371
|
+
: EntityInput<T[0]> | InstanceInput
|
|
295
372
|
: T[2] extends true
|
|
296
|
-
?
|
|
297
|
-
:
|
|
373
|
+
? MultipleInputConstructorValue<T[0]>
|
|
374
|
+
: EntityInput<T[0]> | InstanceInput
|
|
375
|
+
|
|
376
|
+
type InputSpecToUnitOutputRef<T extends ComponentInputSpec> = T[2] extends true
|
|
377
|
+
? RequiredMultipleInput<EntityInput<T[0]>>
|
|
378
|
+
: RequiredInput<EntityInput<T[0]>>
|
|
298
379
|
|
|
299
|
-
type
|
|
300
|
-
?
|
|
301
|
-
:
|
|
380
|
+
type InputSpecToCompositeOutputRef<T extends ComponentInputSpec> = T[2] extends true
|
|
381
|
+
? RequiredMultipleInput<EntityInput<T[0]>>
|
|
382
|
+
: EntityInput<T[0]>
|
|
302
383
|
|
|
303
384
|
export type InputSpecMapToInputRefMap<TInputs extends Record<string, ComponentInputSpec>> =
|
|
304
385
|
TInputs extends Record<string, [string, never, never]>
|
|
305
386
|
? Record<string, never>
|
|
306
|
-
: {
|
|
387
|
+
: {
|
|
388
|
+
[K in keyof TInputs as TInputs[K][1] extends true ? K : never]-?: InputSpecToInputRef<
|
|
389
|
+
TInputs[K]
|
|
390
|
+
>
|
|
391
|
+
} & {
|
|
392
|
+
[K in keyof TInputs as TInputs[K][1] extends true ? never : K]?: InputSpecToInputRef<
|
|
393
|
+
TInputs[K]
|
|
394
|
+
>
|
|
395
|
+
}
|
|
307
396
|
|
|
308
|
-
export type OutputRefMap<
|
|
309
|
-
TInputs extends Record<string,
|
|
310
|
-
|
|
311
|
-
|
|
397
|
+
export type OutputRefMap<
|
|
398
|
+
TInputs extends Record<string, ComponentInputSpec>,
|
|
399
|
+
TKind extends ComponentKind = "composite",
|
|
400
|
+
> = TInputs extends Record<string, [string, never, never]>
|
|
401
|
+
? Record<string, never>
|
|
402
|
+
: {
|
|
403
|
+
[K in keyof TInputs]: TKind extends "unit"
|
|
404
|
+
? InputSpecToUnitOutputRef<TInputs[K]>
|
|
405
|
+
: InputSpecToCompositeOutputRef<TInputs[K]>
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
type StaticOutputRefByKind<
|
|
409
|
+
TOutputSpec extends ComponentInputSpec,
|
|
410
|
+
TKind extends ComponentKind,
|
|
411
|
+
> = TKind extends "unit"
|
|
412
|
+
? InputSpecToUnitOutputRef<TOutputSpec>
|
|
413
|
+
: InputSpecToCompositeOutputRef<TOutputSpec>
|
|
414
|
+
|
|
415
|
+
type ResolveForwardedOutputRef<
|
|
416
|
+
TForwardedInputName extends string,
|
|
417
|
+
TCallInputs extends Record<string, unknown>,
|
|
418
|
+
TFallbackSpec extends ComponentInputSpec,
|
|
419
|
+
TKind extends ComponentKind,
|
|
420
|
+
> = TForwardedInputName extends keyof TCallInputs
|
|
421
|
+
? NonNullable<TCallInputs[TForwardedInputName]>
|
|
422
|
+
: StaticOutputRefByKind<TFallbackSpec, TKind>
|
|
423
|
+
|
|
424
|
+
type ResolveOutputRefForCall<
|
|
425
|
+
TOutputOption,
|
|
426
|
+
TCallInputs extends Record<string, unknown>,
|
|
427
|
+
TFallbackSpec extends ComponentInputSpec,
|
|
428
|
+
TKind extends ComponentKind,
|
|
429
|
+
> = TOutputOption extends FromInputComponentOutputOptions<infer TInputName>
|
|
430
|
+
? ResolveForwardedOutputRef<TInputName, TCallInputs, TFallbackSpec, TKind>
|
|
431
|
+
: StaticOutputRefByKind<TFallbackSpec, TKind>
|
|
432
|
+
|
|
433
|
+
type ResolveOutputRefMapForCall<
|
|
434
|
+
TOutputSpecs extends Record<string, ComponentInputSpec>,
|
|
435
|
+
TOutputOptions extends Record<string, ComponentOutputOptions<TInputOptions>>,
|
|
436
|
+
TInputOptions extends Record<string, ComponentInputOptions>,
|
|
437
|
+
TCallInputs extends Record<string, unknown>,
|
|
438
|
+
TKind extends ComponentKind,
|
|
439
|
+
> = {
|
|
440
|
+
[K in keyof TOutputSpecs]: K extends keyof TOutputOptions
|
|
441
|
+
? [TOutputOptions[K]] extends [never]
|
|
442
|
+
? StaticOutputRefByKind<TOutputSpecs[K], TKind>
|
|
443
|
+
: ResolveOutputRefForCall<TOutputOptions[K], TCallInputs, TOutputSpecs[K], TKind>
|
|
444
|
+
: StaticOutputRefByKind<TOutputSpecs[K], TKind>
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
type IncludesAllExpectedTypes<
|
|
448
|
+
TProvided extends Record<string, unknown>,
|
|
449
|
+
TExpected extends Record<string, unknown>,
|
|
450
|
+
> = Exclude<keyof TExpected, keyof TProvided> extends never ? true : false
|
|
451
|
+
|
|
452
|
+
type ExtractProvidedInputTypes<TValue> = TValue extends RuntimeInput<infer TTypes, any>
|
|
453
|
+
? TTypes
|
|
454
|
+
: TValue extends Array<infer TItem>
|
|
455
|
+
? ExtractProvidedInputTypes<TItem>
|
|
456
|
+
: never
|
|
457
|
+
|
|
458
|
+
type ValidateProvidedInputValue<TValue, TExpectedSpec extends ComponentInputSpec> = [
|
|
459
|
+
ExtractProvidedInputTypes<TValue>,
|
|
460
|
+
] extends [never]
|
|
461
|
+
? TValue
|
|
462
|
+
: ExtractProvidedInputTypes<TValue> extends infer TProvidedTypes
|
|
463
|
+
? TProvidedTypes extends Record<string, unknown>
|
|
464
|
+
? IncludesAllExpectedTypes<
|
|
465
|
+
TProvidedTypes,
|
|
466
|
+
TExpectedSpec[0][typeof implementedTypes]
|
|
467
|
+
> extends true
|
|
468
|
+
? TValue
|
|
469
|
+
: never
|
|
470
|
+
: never
|
|
471
|
+
: never
|
|
472
|
+
|
|
473
|
+
type ValidateCallInputs<
|
|
474
|
+
TCallInputs extends Record<string, unknown>,
|
|
475
|
+
TInputSpecs extends Record<string, ComponentInputSpec>,
|
|
476
|
+
> = {
|
|
477
|
+
[K in keyof TCallInputs]: K extends keyof TInputSpecs
|
|
478
|
+
? ValidateProvidedInputValue<TCallInputs[K], TInputSpecs[K]>
|
|
479
|
+
: TCallInputs[K]
|
|
480
|
+
}
|
|
312
481
|
|
|
313
482
|
export const originalCreate = Symbol("originalCreate")
|
|
314
|
-
export const kind = Symbol("kind")
|
|
315
483
|
|
|
316
484
|
export type Component<
|
|
317
485
|
TType extends VersionedName = VersionedName,
|
|
318
486
|
TArgs extends Record<string, z.ZodType> = Record<string, never>,
|
|
319
487
|
TInputs extends Record<string, ComponentInputSpec> = Record<string, never>,
|
|
320
488
|
TOutputs extends Record<string, ComponentInputSpec> = Record<string, never>,
|
|
489
|
+
TKind extends ComponentKind = "composite",
|
|
490
|
+
TInputOptions extends Record<string, ComponentInputOptions> = Record<string, never>,
|
|
491
|
+
TOutputOptions extends Record<string, ComponentOutputOptions<TInputOptions>> = Record<
|
|
492
|
+
string,
|
|
493
|
+
never
|
|
494
|
+
>,
|
|
321
495
|
> = {
|
|
322
496
|
/**
|
|
323
497
|
* The type of the component.
|
|
@@ -334,12 +508,14 @@ export type Component<
|
|
|
334
508
|
*/
|
|
335
509
|
entities: Map<string, Entity>
|
|
336
510
|
|
|
337
|
-
(
|
|
511
|
+
<TCallInputs extends InputSpecMapToInputRefMap<TInputs> = InputSpecMapToInputRefMap<TInputs>>(
|
|
338
512
|
context: InputComponentParams<
|
|
339
513
|
{ [K in keyof TArgs]: z.input<TArgs[K]> },
|
|
340
514
|
InputSpecMapToInputRefMap<TInputs>
|
|
341
|
-
|
|
342
|
-
|
|
515
|
+
> & {
|
|
516
|
+
inputs?: ValidateCallInputs<TCallInputs, TInputs>
|
|
517
|
+
},
|
|
518
|
+
): ResolveOutputRefMapForCall<TOutputs, TOutputOptions, TInputOptions, TCallInputs, TKind>
|
|
343
519
|
|
|
344
520
|
/**
|
|
345
521
|
* The original create function.
|
|
@@ -353,14 +529,18 @@ export function defineComponent<
|
|
|
353
529
|
TType extends VersionedName = VersionedName,
|
|
354
530
|
TArgs extends Record<string, ComponentArgumentOptions> = Record<string, never>,
|
|
355
531
|
TInputs extends Record<string, ComponentInputOptions> = Record<string, never>,
|
|
356
|
-
TOutputs extends Record<string,
|
|
532
|
+
TOutputs extends Record<string, ComponentOutputOptions<TInputs>> = Record<string, never>,
|
|
533
|
+
TKind extends ComponentKind = "composite",
|
|
357
534
|
>(
|
|
358
|
-
options: ComponentOptions<TType, TArgs, TInputs, TOutputs
|
|
535
|
+
options: ComponentOptions<TType, TArgs, TInputs, TOutputs> & { [kind]?: TKind },
|
|
359
536
|
): Component<
|
|
360
537
|
TType,
|
|
361
538
|
{ [K in keyof TArgs]: ComponentArgumentOptionsToSchema<TArgs[K]> },
|
|
362
539
|
{ [K in keyof TInputs]: ComponentInputOptionsToSpec<TInputs[K]> },
|
|
363
|
-
{ [K in keyof TOutputs]:
|
|
540
|
+
{ [K in keyof TOutputs]: ComponentOutputOptionsToSpec<TOutputs[K], TInputs> },
|
|
541
|
+
TKind,
|
|
542
|
+
TInputs,
|
|
543
|
+
TOutputs
|
|
364
544
|
> {
|
|
365
545
|
try {
|
|
366
546
|
componentModelSchema.shape.type.parse(options.type)
|
|
@@ -374,13 +554,14 @@ export function defineComponent<
|
|
|
374
554
|
|
|
375
555
|
const entities = new Map<string, Entity>()
|
|
376
556
|
const mapInput = createInputMapper(entities)
|
|
557
|
+
const mapOutput = createOutputMapper(options.inputs, entities)
|
|
377
558
|
|
|
378
559
|
const model: ComponentModel = {
|
|
379
560
|
type: options.type,
|
|
380
561
|
kind: options[kind] ?? "composite",
|
|
381
562
|
args: mapValues(options.args ?? {}, mapArgument),
|
|
382
563
|
inputs: mapValues(options.inputs ?? {}, mapInput),
|
|
383
|
-
outputs: mapValues(options.outputs ?? {},
|
|
564
|
+
outputs: mapValues(options.outputs ?? {}, mapOutput),
|
|
384
565
|
meta: {
|
|
385
566
|
...options.meta,
|
|
386
567
|
title: options.meta?.title || camelCaseToHumanReadable(parseVersionedName(options.type)[0]),
|
|
@@ -392,43 +573,45 @@ export function defineComponent<
|
|
|
392
573
|
definitionHash: null!,
|
|
393
574
|
}
|
|
394
575
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
576
|
+
type RuntimeCreateInputGroup =
|
|
577
|
+
| RuntimeInput
|
|
578
|
+
| InstanceInput
|
|
579
|
+
| (Array<RuntimeCreateInputGroup> & Partial<{ [boundaryInput]: InstanceInput }>)
|
|
580
|
+
|
|
581
|
+
function create(params: InputComponentParams<any, Record<string, RuntimeCreateInputGroup>>): any {
|
|
398
582
|
const { name, args = {}, inputs } = params
|
|
399
583
|
const instanceId = getInstanceId(options.type, name)
|
|
400
584
|
|
|
401
|
-
const flatInputs: Record<string,
|
|
585
|
+
const flatInputs: Record<string, RuntimeInput[]> = {}
|
|
402
586
|
const tracedInputs: Record<string, InstanceInput[]> = {}
|
|
587
|
+
const runtimeInputKey = (input: RuntimeInput): string => {
|
|
588
|
+
if (input.provided) {
|
|
589
|
+
return inputKey(input)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return `missing:${input[boundaryInput].instanceId}:${input[boundaryInput].output}`
|
|
593
|
+
}
|
|
403
594
|
|
|
404
595
|
for (const [key, inputGroup] of Object.entries(inputs ?? {})) {
|
|
596
|
+
if (!(key in model.inputs)) {
|
|
597
|
+
continue
|
|
598
|
+
}
|
|
599
|
+
|
|
405
600
|
if (!inputGroup) {
|
|
406
601
|
continue
|
|
407
602
|
}
|
|
408
603
|
|
|
409
604
|
if (!Array.isArray(inputGroup)) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
flatInputs[key] = [inputGroup]
|
|
605
|
+
const normalizedInput = normalizeIncomingInput(inputGroup, key)
|
|
606
|
+
tracedInputs[key] = [normalizedInput[boundaryInput]]
|
|
607
|
+
flatInputs[key] = [normalizedInput]
|
|
415
608
|
continue
|
|
416
609
|
}
|
|
417
610
|
|
|
418
|
-
|
|
419
|
-
const group: InstanceInput[] = [...(inputGroup[boundaryInputs] ?? [])]
|
|
420
|
-
const inputs: InstanceInput[] = []
|
|
421
|
-
|
|
422
|
-
for (const item of inputGroup.flat(1)) {
|
|
423
|
-
if (item[boundaryInput]) {
|
|
424
|
-
group.push(item[boundaryInput])
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
inputs.push(item)
|
|
428
|
-
}
|
|
611
|
+
const { group, inputs } = normalizeIncomingInputGroup(inputGroup, key)
|
|
429
612
|
|
|
430
613
|
tracedInputs[key] = uniqueBy(group, inputKey)
|
|
431
|
-
flatInputs[key] = uniqueBy(inputs,
|
|
614
|
+
flatInputs[key] = uniqueBy(inputs, runtimeInputKey)
|
|
432
615
|
}
|
|
433
616
|
|
|
434
617
|
return registerInstance(
|
|
@@ -440,38 +623,43 @@ export function defineComponent<
|
|
|
440
623
|
name,
|
|
441
624
|
args,
|
|
442
625
|
inputs: tracedInputs,
|
|
443
|
-
resolvedInputs: mapValues(flatInputs, inputs =>
|
|
444
|
-
inputs
|
|
445
|
-
|
|
626
|
+
resolvedInputs: mapValues(flatInputs, inputs => {
|
|
627
|
+
return inputs
|
|
628
|
+
.filter(input => input.provided)
|
|
629
|
+
.map(input => ({
|
|
630
|
+
instanceId: input.instanceId,
|
|
631
|
+
output: input.output,
|
|
632
|
+
...(input.path ? { path: input.path } : {}),
|
|
633
|
+
}))
|
|
634
|
+
}),
|
|
446
635
|
},
|
|
447
636
|
() => {
|
|
448
637
|
const markedInputs = mapValues(model.inputs, (componentInput, key) => {
|
|
449
638
|
if (!componentInput.multiple) {
|
|
450
|
-
// for single component use first available input and attach boundaryInput
|
|
451
|
-
// technically, caller can pass multiple inputs or array of inputs ignoring the TypeScript restriction,
|
|
452
|
-
// but we don't care about that
|
|
453
639
|
const input = flatInputs[key]?.[0]
|
|
454
640
|
|
|
455
|
-
if (input) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
641
|
+
if (input?.provided) {
|
|
642
|
+
return createDeepOutputAccessor({
|
|
643
|
+
...input,
|
|
644
|
+
[boundaryInput]: { instanceId: instanceId, output: key },
|
|
645
|
+
})
|
|
459
646
|
}
|
|
460
647
|
|
|
461
|
-
|
|
462
|
-
return { provided: false, [boundaryInput]: { instanceId: instanceId, output: key } }
|
|
648
|
+
return createNonProvidedInput({ instanceId: instanceId, output: key })
|
|
463
649
|
}
|
|
464
650
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
651
|
+
const inputs = (flatInputs[key] ?? []).filter(isProvidedRuntimeInput).map(input =>
|
|
652
|
+
createDeepOutputAccessor({
|
|
653
|
+
...input,
|
|
654
|
+
[boundaryInput]: { instanceId: instanceId, output: key },
|
|
655
|
+
}),
|
|
656
|
+
)
|
|
470
657
|
|
|
471
|
-
|
|
658
|
+
const multipleBoundary = { instanceId, output: key }
|
|
659
|
+
return createMultipleInputAccessor(inputs, multipleBoundary)
|
|
472
660
|
})
|
|
473
661
|
|
|
474
|
-
const outputs: Record<string,
|
|
662
|
+
const outputs: Record<string, RuntimeInput | MultipleInput | null | undefined> =
|
|
475
663
|
options.create({
|
|
476
664
|
id: instanceId,
|
|
477
665
|
name,
|
|
@@ -479,9 +667,74 @@ export function defineComponent<
|
|
|
479
667
|
inputs: markedInputs as any,
|
|
480
668
|
}) ?? {}
|
|
481
669
|
|
|
482
|
-
|
|
670
|
+
const normalizedOutputs = normalizeCreateOutputs(outputs, model, instanceId)
|
|
671
|
+
|
|
672
|
+
return withDeepOutputAccessors(normalizedOutputs, model, instanceId)
|
|
483
673
|
},
|
|
484
674
|
)
|
|
675
|
+
|
|
676
|
+
function normalizeIncomingInput(
|
|
677
|
+
input: RuntimeInput | InstanceInput,
|
|
678
|
+
inputName: string,
|
|
679
|
+
): RuntimeInput {
|
|
680
|
+
if (isStableInstanceInput(input)) {
|
|
681
|
+
return createInput(input)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const runtimeInput = input as RuntimeInput
|
|
685
|
+
|
|
686
|
+
if (runtimeInput.provided) {
|
|
687
|
+
const inputBoundary = runtimeInput[boundaryInput] ?? {
|
|
688
|
+
instanceId: runtimeInput.instanceId,
|
|
689
|
+
output: runtimeInput.output,
|
|
690
|
+
...(runtimeInput.path ? { path: runtimeInput.path } : {}),
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return createInput(runtimeInput, { boundary: inputBoundary })
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return createNonProvidedInput(
|
|
697
|
+
runtimeInput[boundaryInput] ?? { instanceId, output: inputName },
|
|
698
|
+
)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function normalizeIncomingInputGroup(
|
|
702
|
+
inputGroup: RuntimeCreateInputGroup,
|
|
703
|
+
inputName: string,
|
|
704
|
+
): { group: InstanceInput[]; inputs: RuntimeInput[] } {
|
|
705
|
+
const group: InstanceInput[] = []
|
|
706
|
+
const inputs: RuntimeInput[] = []
|
|
707
|
+
|
|
708
|
+
const visit = (value: RuntimeCreateInputGroup): void => {
|
|
709
|
+
if (Array.isArray(value)) {
|
|
710
|
+
const arrayBoundary = (value as Partial<{ [boundaryInput]: InstanceInput }>)[
|
|
711
|
+
boundaryInput
|
|
712
|
+
]
|
|
713
|
+
|
|
714
|
+
if (arrayBoundary) {
|
|
715
|
+
group.push(arrayBoundary)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
for (const item of value) {
|
|
719
|
+
if (!item) {
|
|
720
|
+
continue
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
visit(item)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const normalizedInput = normalizeIncomingInput(value, inputName)
|
|
730
|
+
group.push(normalizedInput[boundaryInput])
|
|
731
|
+
inputs.push(normalizedInput)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
visit(inputGroup)
|
|
735
|
+
|
|
736
|
+
return { group, inputs }
|
|
737
|
+
}
|
|
485
738
|
}
|
|
486
739
|
|
|
487
740
|
try {
|
|
@@ -526,6 +779,131 @@ function processArgs(
|
|
|
526
779
|
return validatedArgs
|
|
527
780
|
}
|
|
528
781
|
|
|
782
|
+
function withDeepOutputAccessors(
|
|
783
|
+
outputs: Record<string, RuntimeInput[]>,
|
|
784
|
+
model: ComponentModel,
|
|
785
|
+
instanceId: InstanceId,
|
|
786
|
+
): Record<string, RuntimeInput | MultipleInput> {
|
|
787
|
+
return mapValues(outputs, (outputGroup, outputName) => {
|
|
788
|
+
const outputSpec = model.outputs[outputName]
|
|
789
|
+
if (!outputSpec) {
|
|
790
|
+
return outputGroup[0]!
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const normalizedOutputs = outputGroup.map(output => createDeepOutputAccessor(output))
|
|
794
|
+
|
|
795
|
+
if (outputSpec.multiple) {
|
|
796
|
+
return createMultipleInputAccessor(normalizedOutputs, {
|
|
797
|
+
instanceId,
|
|
798
|
+
output: outputName,
|
|
799
|
+
})
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return normalizedOutputs[0]!
|
|
803
|
+
})
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function isProvidedRuntimeInput(input: RuntimeInput): input is RequiredInput {
|
|
807
|
+
return input.provided
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function normalizeCreateOutputGroup(
|
|
811
|
+
outputGroup: RuntimeInput | InstanceInput | Array<RuntimeInput | InstanceInput>,
|
|
812
|
+
instanceId: InstanceId,
|
|
813
|
+
outputName: string,
|
|
814
|
+
): RuntimeInput[] {
|
|
815
|
+
return [outputGroup]
|
|
816
|
+
.flat(2)
|
|
817
|
+
.filter(isNonNullish)
|
|
818
|
+
.map(output => {
|
|
819
|
+
if (isStableInstanceInput(output)) {
|
|
820
|
+
return createInput(output)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const runtimeOutput = output as RuntimeInput
|
|
824
|
+
|
|
825
|
+
if (runtimeOutput.provided) {
|
|
826
|
+
const stableBoundary = runtimeOutput[boundaryInput] ?? {
|
|
827
|
+
instanceId: runtimeOutput.instanceId,
|
|
828
|
+
output: runtimeOutput.output,
|
|
829
|
+
...(runtimeOutput.path ? { path: runtimeOutput.path } : {}),
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return createInput(runtimeOutput, { boundary: stableBoundary })
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return createNonProvidedInput(
|
|
836
|
+
runtimeOutput[boundaryInput] ?? { instanceId, output: outputName },
|
|
837
|
+
)
|
|
838
|
+
})
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function normalizeCreateOutputs(
|
|
842
|
+
outputs: Record<string, RuntimeInput | MultipleInput | null | undefined>,
|
|
843
|
+
model: ComponentModel,
|
|
844
|
+
instanceId: InstanceId,
|
|
845
|
+
): Record<string, RuntimeInput[]> {
|
|
846
|
+
const normalizedOutputs: Record<string, RuntimeInput[]> = {}
|
|
847
|
+
|
|
848
|
+
for (const [outputName, outputSpec] of Object.entries(model.outputs)) {
|
|
849
|
+
const outputGroup = outputs[outputName]
|
|
850
|
+
|
|
851
|
+
if (!isNonNullish(outputGroup)) {
|
|
852
|
+
if (model.kind === "unit") {
|
|
853
|
+
throw new Error(`Unit output "${outputName}" in instance "${instanceId}" must be provided`)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
normalizedOutputs[outputName] = outputSpec.multiple
|
|
857
|
+
? []
|
|
858
|
+
: [createNonProvidedInput({ instanceId, output: outputName })]
|
|
859
|
+
continue
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const normalizedGroup = normalizeCreateOutputGroup(outputGroup, instanceId, outputName)
|
|
863
|
+
|
|
864
|
+
if (outputSpec.multiple && normalizedGroup.some(output => !output.provided)) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
`Multiple output "${outputName}" in instance "${instanceId}" cannot contain non-provided items`,
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (model.kind === "unit") {
|
|
871
|
+
if (normalizedGroup.length === 0 || normalizedGroup.some(output => !output.provided)) {
|
|
872
|
+
throw new Error(`Unit output "${outputName}" in instance "${instanceId}" must be provided`)
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
normalizedOutputs[outputName] =
|
|
877
|
+
outputSpec.multiple || normalizedGroup.length > 0
|
|
878
|
+
? normalizedGroup
|
|
879
|
+
: [createNonProvidedInput({ instanceId, output: outputName })]
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
for (const [outputName, outputGroup] of Object.entries(pickBy(outputs, isNonNullish))) {
|
|
883
|
+
if (!(outputName in model.outputs) || outputName in normalizedOutputs) {
|
|
884
|
+
continue
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
normalizedOutputs[outputName] = normalizeCreateOutputGroup(outputGroup, instanceId, outputName)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return normalizedOutputs
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function isStableInstanceInput(value: unknown): value is InstanceInput {
|
|
894
|
+
if (!value || typeof value !== "object") {
|
|
895
|
+
return false
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if ("provided" in value) {
|
|
899
|
+
return false
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const input = value as Partial<InstanceInput>
|
|
903
|
+
|
|
904
|
+
return typeof input.instanceId === "string" && typeof input.output === "string"
|
|
905
|
+
}
|
|
906
|
+
|
|
529
907
|
/**
|
|
530
908
|
* Checks if the value is a Highstate component.
|
|
531
909
|
*
|
|
@@ -602,6 +980,44 @@ export function createInputMapper(entities: Map<string, Entity>) {
|
|
|
602
980
|
}
|
|
603
981
|
}
|
|
604
982
|
|
|
983
|
+
function isFromInputOutputOptions(
|
|
984
|
+
value: ComponentOutputOptions,
|
|
985
|
+
): value is FromInputComponentOutputOptions {
|
|
986
|
+
return typeof value === "object" && value !== null && "fromInput" in value
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function createOutputMapper(
|
|
990
|
+
inputs: Record<string, ComponentInputOptions> | undefined,
|
|
991
|
+
entities: Map<string, Entity>,
|
|
992
|
+
) {
|
|
993
|
+
const mapInput = createInputMapper(entities)
|
|
994
|
+
|
|
995
|
+
return (value: ComponentOutputOptions, key: string): ComponentInput => {
|
|
996
|
+
if (!isFromInputOutputOptions(value)) {
|
|
997
|
+
return mapInput(value, key)
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const sourceInput = inputs?.[value.fromInput]
|
|
1001
|
+
if (!sourceInput) {
|
|
1002
|
+
throw new Error(
|
|
1003
|
+
`Output "${key}" references missing input "${value.fromInput}" via fromInput.`,
|
|
1004
|
+
)
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const mappedInput = mapInput(sourceInput, value.fromInput)
|
|
1008
|
+
|
|
1009
|
+
return {
|
|
1010
|
+
...mappedInput,
|
|
1011
|
+
fromInput: value.fromInput,
|
|
1012
|
+
meta: {
|
|
1013
|
+
...mappedInput.meta,
|
|
1014
|
+
...value.meta,
|
|
1015
|
+
title: value.meta?.title || camelCaseToHumanReadable(key),
|
|
1016
|
+
},
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
605
1021
|
/**
|
|
606
1022
|
* Formats the instance id from the instance type and instance name.
|
|
607
1023
|
*
|
|
@@ -635,6 +1051,26 @@ function toFullComponentInputOptions<T extends Record<string, ComponentInputOpti
|
|
|
635
1051
|
return mapValues(inputs, input => ("entity" in input ? input : { entity: input })) as any
|
|
636
1052
|
}
|
|
637
1053
|
|
|
1054
|
+
type ToFullComponentOutputOptions<T extends Record<string, ComponentOutputOptions>> = Simplify<{
|
|
1055
|
+
[K in keyof T]: T[K] extends Entity ? { entity: T[K] } : T[K]
|
|
1056
|
+
}>
|
|
1057
|
+
|
|
1058
|
+
function toFullComponentOutputOptions<T extends Record<string, ComponentOutputOptions>>(
|
|
1059
|
+
outputs: T,
|
|
1060
|
+
): ToFullComponentOutputOptions<T> {
|
|
1061
|
+
return mapValues(outputs, output => {
|
|
1062
|
+
if (typeof output !== "object" || output === null) {
|
|
1063
|
+
return { entity: output }
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if ("entity" in output || "fromInput" in output) {
|
|
1067
|
+
return output
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
return { entity: output }
|
|
1071
|
+
}) as any
|
|
1072
|
+
}
|
|
1073
|
+
|
|
638
1074
|
/**
|
|
639
1075
|
* The helper marker for component arguments.
|
|
640
1076
|
*
|
|
@@ -662,10 +1098,10 @@ export function $inputs<T extends Record<string, ComponentInputOptions>>(
|
|
|
662
1098
|
*
|
|
663
1099
|
* Helps validating outputs types and gives the compiler a hint for generating `meta` fields.
|
|
664
1100
|
*/
|
|
665
|
-
export function $outputs<T extends Record<string,
|
|
1101
|
+
export function $outputs<T extends Record<string, ComponentOutputOptions>>(
|
|
666
1102
|
outputs: T,
|
|
667
|
-
):
|
|
668
|
-
return
|
|
1103
|
+
): ToFullComponentOutputOptions<T> {
|
|
1104
|
+
return toFullComponentOutputOptions(outputs)
|
|
669
1105
|
}
|
|
670
1106
|
|
|
671
1107
|
/**
|