@highstate/contract 0.16.0 → 0.18.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 +451 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/compaction.spec.ts +215 -0
- package/src/compaction.ts +381 -0
- package/src/component.spec.ts +78 -2
- package/src/component.ts +29 -16
- package/src/entity.ts +286 -8
- package/src/index.ts +10 -1
- package/src/instance.ts +26 -11
- package/src/meta.ts +5 -2
- package/src/pulumi.ts +13 -1
- package/src/unit.ts +11 -6
- package/src/utils.ts +1 -1
package/src/component.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** biome-ignore-all lint/suspicious/noExplicitAny: maybe fix later */
|
|
2
2
|
|
|
3
3
|
import type { Simplify } from "type-fest"
|
|
4
|
-
import type { Entity } from "./entity"
|
|
4
|
+
import type { Entity, implementedTypes } from "./entity"
|
|
5
5
|
import type { OptionalEmptyRecords, OptionalUndefinedFields, PartialKeys } from "./utils"
|
|
6
6
|
import { isNonNullish, mapValues, pickBy, uniqueBy } from "remeda"
|
|
7
7
|
import { z } from "zod"
|
|
@@ -108,15 +108,15 @@ export type FullComponentInputOptions = {
|
|
|
108
108
|
export type ComponentInputOptions = Entity | FullComponentInputOptions
|
|
109
109
|
|
|
110
110
|
type ComponentInputOptionsToOutputRef<T extends ComponentInputOptions> = T extends Entity
|
|
111
|
-
? InstanceInput<T[
|
|
111
|
+
? InstanceInput<T[typeof implementedTypes]>
|
|
112
112
|
: T extends FullComponentInputOptions
|
|
113
113
|
? T["required"] extends false
|
|
114
114
|
? T["multiple"] extends true
|
|
115
|
-
? InstanceInput<T["entity"][
|
|
116
|
-
: InstanceInput<T["entity"][
|
|
115
|
+
? InstanceInput<T["entity"][typeof implementedTypes]>[] | undefined
|
|
116
|
+
: InstanceInput<T["entity"][typeof implementedTypes]> | undefined
|
|
117
117
|
: T["multiple"] extends true
|
|
118
|
-
? InstanceInput<T["entity"][
|
|
119
|
-
: InstanceInput<T["entity"][
|
|
118
|
+
? InstanceInput<T["entity"][typeof implementedTypes]>[]
|
|
119
|
+
: InstanceInput<T["entity"][typeof implementedTypes]>
|
|
120
120
|
: never
|
|
121
121
|
|
|
122
122
|
/**
|
|
@@ -171,6 +171,7 @@ export type InputComponentParams<
|
|
|
171
171
|
}>
|
|
172
172
|
|
|
173
173
|
export type ComponentOptions<
|
|
174
|
+
TType extends VersionedName,
|
|
174
175
|
TArgs extends Record<string, ComponentArgumentOptions>,
|
|
175
176
|
TInputs extends Record<string, ComponentInputOptions>,
|
|
176
177
|
TOutputs extends Record<string, ComponentInputOptions>,
|
|
@@ -181,7 +182,7 @@ export type ComponentOptions<
|
|
|
181
182
|
*
|
|
182
183
|
* Examples: `proxmox.virtual-machine.v1`, `common.server.v1`.
|
|
183
184
|
*/
|
|
184
|
-
type:
|
|
185
|
+
type: TType
|
|
185
186
|
|
|
186
187
|
/**
|
|
187
188
|
* The extra metadata of the component.
|
|
@@ -289,15 +290,15 @@ export type ComponentModel = z.infer<typeof componentModelSchema>
|
|
|
289
290
|
|
|
290
291
|
type InputSpecToInputRef<T extends ComponentInputSpec> = T[1] extends true
|
|
291
292
|
? T[2] extends true
|
|
292
|
-
? InstanceInput<T[0][
|
|
293
|
-
: InstanceInput<T[0][
|
|
293
|
+
? InstanceInput<T[0][typeof implementedTypes]>[]
|
|
294
|
+
: InstanceInput<T[0][typeof implementedTypes]>
|
|
294
295
|
: T[2] extends true
|
|
295
|
-
? InstanceInput<T[0][
|
|
296
|
-
: InstanceInput<T[0][
|
|
296
|
+
? InstanceInput<T[0][typeof implementedTypes]>[] | undefined
|
|
297
|
+
: InstanceInput<T[0][typeof implementedTypes]> | undefined
|
|
297
298
|
|
|
298
299
|
type InputSpecToOutputRef<T extends ComponentInputSpec> = T[2] extends true
|
|
299
|
-
? InstanceInput<T[0][
|
|
300
|
-
: InstanceInput<T[0][
|
|
300
|
+
? InstanceInput<T[0][typeof implementedTypes]>[]
|
|
301
|
+
: InstanceInput<T[0][typeof implementedTypes]>
|
|
301
302
|
|
|
302
303
|
export type InputSpecMapToInputRefMap<TInputs extends Record<string, ComponentInputSpec>> =
|
|
303
304
|
TInputs extends Record<string, [string, never, never]>
|
|
@@ -313,10 +314,16 @@ export const originalCreate = Symbol("originalCreate")
|
|
|
313
314
|
export const kind = Symbol("kind")
|
|
314
315
|
|
|
315
316
|
export type Component<
|
|
317
|
+
TType extends VersionedName = VersionedName,
|
|
316
318
|
TArgs extends Record<string, z.ZodType> = Record<string, never>,
|
|
317
319
|
TInputs extends Record<string, ComponentInputSpec> = Record<string, never>,
|
|
318
320
|
TOutputs extends Record<string, ComponentInputSpec> = Record<string, never>,
|
|
319
321
|
> = {
|
|
322
|
+
/**
|
|
323
|
+
* The type of the component.
|
|
324
|
+
*/
|
|
325
|
+
type: TType
|
|
326
|
+
|
|
320
327
|
/**
|
|
321
328
|
* The non-generic model of the component.
|
|
322
329
|
*/
|
|
@@ -343,12 +350,14 @@ export type Component<
|
|
|
343
350
|
}
|
|
344
351
|
|
|
345
352
|
export function defineComponent<
|
|
353
|
+
TType extends VersionedName = VersionedName,
|
|
346
354
|
TArgs extends Record<string, ComponentArgumentOptions> = Record<string, never>,
|
|
347
355
|
TInputs extends Record<string, ComponentInputOptions> = Record<string, never>,
|
|
348
356
|
TOutputs extends Record<string, ComponentInputOptions> = Record<string, never>,
|
|
349
357
|
>(
|
|
350
|
-
options: ComponentOptions<TArgs, TInputs, TOutputs>,
|
|
358
|
+
options: ComponentOptions<TType, TArgs, TInputs, TOutputs>,
|
|
351
359
|
): Component<
|
|
360
|
+
TType,
|
|
352
361
|
{ [K in keyof TArgs]: ComponentArgumentOptionsToSchema<TArgs[K]> },
|
|
353
362
|
{ [K in keyof TInputs]: ComponentInputOptionsToSpec<TInputs[K]> },
|
|
354
363
|
{ [K in keyof TOutputs]: ComponentInputOptionsToSpec<TOutputs[K]> }
|
|
@@ -536,7 +545,11 @@ function isSchemaOptional(schema: z.ZodType): boolean {
|
|
|
536
545
|
export function mapArgument(value: ComponentArgumentOptions, key: string): ComponentArgument {
|
|
537
546
|
if ("schema" in value) {
|
|
538
547
|
return {
|
|
539
|
-
schema: z.toJSONSchema(value.schema, {
|
|
548
|
+
schema: z.toJSONSchema(value.schema, {
|
|
549
|
+
target: "draft-7",
|
|
550
|
+
io: "input",
|
|
551
|
+
unrepresentable: "any",
|
|
552
|
+
}),
|
|
540
553
|
[runtimeSchema]: value.schema,
|
|
541
554
|
required: !isSchemaOptional(value.schema),
|
|
542
555
|
meta: {
|
|
@@ -547,7 +560,7 @@ export function mapArgument(value: ComponentArgumentOptions, key: string): Compo
|
|
|
547
560
|
}
|
|
548
561
|
|
|
549
562
|
return {
|
|
550
|
-
schema: z.toJSONSchema(value, { target: "draft-7", io: "input" }),
|
|
563
|
+
schema: z.toJSONSchema(value, { target: "draft-7", io: "input", unrepresentable: "any" }),
|
|
551
564
|
[runtimeSchema]: value,
|
|
552
565
|
required: !isSchemaOptional(value),
|
|
553
566
|
meta: {
|
package/src/entity.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IsEmptyObject, Simplify } from "type-fest"
|
|
1
2
|
import type { PartialKeys } from "./utils"
|
|
2
3
|
import { z } from "zod"
|
|
3
4
|
import { camelCaseToHumanReadable } from "./i18n"
|
|
@@ -8,6 +9,29 @@ import {
|
|
|
8
9
|
versionedNameSchema,
|
|
9
10
|
} from "./meta"
|
|
10
11
|
|
|
12
|
+
export const entityInclusionSchema = z.object({
|
|
13
|
+
/**
|
|
14
|
+
* The static type of the included entity.
|
|
15
|
+
*/
|
|
16
|
+
type: versionedNameSchema,
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether the included entity is required.
|
|
20
|
+
* If false, the entity may be omitted.
|
|
21
|
+
*/
|
|
22
|
+
required: z.boolean(),
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether the included entity is multiple.
|
|
26
|
+
*/
|
|
27
|
+
multiple: z.boolean(),
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The name of the field where the included entity is embedded.
|
|
31
|
+
*/
|
|
32
|
+
field: z.string(),
|
|
33
|
+
})
|
|
34
|
+
|
|
11
35
|
/**
|
|
12
36
|
* The entity is some abstract object which can be passed from one component to another through their inputs and outputs.
|
|
13
37
|
* Every entity must have a type.
|
|
@@ -19,6 +43,26 @@ export const entityModelSchema = z.object({
|
|
|
19
43
|
*/
|
|
20
44
|
type: versionedNameSchema,
|
|
21
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The list of all extended entity types (direct and indirect).
|
|
48
|
+
*/
|
|
49
|
+
extensions: z.string().array().optional(),
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The list of directly extended entity types.
|
|
53
|
+
*/
|
|
54
|
+
directExtensions: z.string().array().optional(),
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The list of all included entities (directly or inherited from extensions).
|
|
58
|
+
*/
|
|
59
|
+
inclusions: entityInclusionSchema.array().optional(),
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The list of directly included entities.
|
|
63
|
+
*/
|
|
64
|
+
directInclusions: entityInclusionSchema.array().optional(),
|
|
65
|
+
|
|
22
66
|
/**
|
|
23
67
|
* The JSON schema of the entity value.
|
|
24
68
|
*/
|
|
@@ -41,17 +85,29 @@ export const entityModelSchema = z.object({
|
|
|
41
85
|
definitionHash: z.number(),
|
|
42
86
|
})
|
|
43
87
|
|
|
88
|
+
export type EntityInclusion = z.infer<typeof entityInclusionSchema>
|
|
44
89
|
export type EntityModel = z.infer<typeof entityModelSchema>
|
|
90
|
+
export type EntityTypes = Record<VersionedName, true>
|
|
91
|
+
|
|
92
|
+
export declare const implementedTypes: unique symbol
|
|
45
93
|
|
|
46
94
|
export type Entity<
|
|
47
95
|
TType extends VersionedName = VersionedName,
|
|
48
|
-
|
|
96
|
+
TImplementedTypes extends EntityTypes = EntityTypes,
|
|
97
|
+
TSchema extends z.ZodTypeAny = z.ZodTypeAny,
|
|
49
98
|
> = {
|
|
50
99
|
/**
|
|
51
100
|
* The static type of the entity.
|
|
52
101
|
*/
|
|
53
102
|
type: TType
|
|
54
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The all types implemented by this entity including its own type.
|
|
106
|
+
*
|
|
107
|
+
* Does not exist at runtime.
|
|
108
|
+
*/
|
|
109
|
+
[implementedTypes]: TImplementedTypes
|
|
110
|
+
|
|
55
111
|
/**
|
|
56
112
|
* The zod schema of the entity value.
|
|
57
113
|
*/
|
|
@@ -63,7 +119,12 @@ export type Entity<
|
|
|
63
119
|
model: EntityModel
|
|
64
120
|
}
|
|
65
121
|
|
|
66
|
-
type EntityOptions<
|
|
122
|
+
type EntityOptions<
|
|
123
|
+
TType extends VersionedName,
|
|
124
|
+
TSchema extends z.ZodType,
|
|
125
|
+
TExtends extends Record<string, Entity>,
|
|
126
|
+
TIncludes extends Record<string, EntityIncludeRef>,
|
|
127
|
+
> = {
|
|
67
128
|
/**
|
|
68
129
|
* The static type of the entity.
|
|
69
130
|
*/
|
|
@@ -78,11 +139,122 @@ type EntityOptions<TType extends VersionedName, TSchema extends z.ZodType> = {
|
|
|
78
139
|
* The extra metadata of the entity.
|
|
79
140
|
*/
|
|
80
141
|
meta?: PartialKeys<z.infer<typeof objectMetaSchema>, "title">
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The list of entities to extend.
|
|
145
|
+
*
|
|
146
|
+
* The intersection of them will be used as the base schema and then intersected again with the provided schema.
|
|
147
|
+
*
|
|
148
|
+
* Note: Intersection does not mean inheritance or blind property merging.
|
|
149
|
+
* If two base types have field with the same name but different types,
|
|
150
|
+
* the resulting type of the field will be the intersection of the two types (and most likely `never`).
|
|
151
|
+
*/
|
|
152
|
+
extends?: TExtends
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The list of entities to include.
|
|
156
|
+
*
|
|
157
|
+
* Inclusion is more powerful mechanism that allows to embed other entities into this entity
|
|
158
|
+
* and then substitute them instead of this entity when connected to component inputs of included types.
|
|
159
|
+
*/
|
|
160
|
+
includes?: TIncludes
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type EntityIncludeRef = Entity | { entity: Entity; multiple?: boolean; required?: boolean }
|
|
164
|
+
|
|
165
|
+
function isEntityIncludeRef(
|
|
166
|
+
value: EntityIncludeRef,
|
|
167
|
+
): value is { entity: Entity; multiple?: boolean; required?: boolean } {
|
|
168
|
+
return typeof value === "object" && value !== null && "entity" in value
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type InclusionShape<TImplements extends Record<string, EntityIncludeRef>> = {
|
|
172
|
+
[K in keyof TImplements]: TImplements[K] extends {
|
|
173
|
+
entity: infer E
|
|
174
|
+
multiple?: infer M
|
|
175
|
+
required?: infer R
|
|
176
|
+
}
|
|
177
|
+
? E extends Entity
|
|
178
|
+
? M extends true
|
|
179
|
+
? R extends false
|
|
180
|
+
? z.ZodOptional<z.ZodArray<E["schema"]>>
|
|
181
|
+
: z.ZodArray<E["schema"]>
|
|
182
|
+
: R extends false
|
|
183
|
+
? z.ZodOptional<E["schema"]>
|
|
184
|
+
: E["schema"]
|
|
185
|
+
: never
|
|
186
|
+
: TImplements[K] extends Entity
|
|
187
|
+
? TImplements[K]["schema"]
|
|
188
|
+
: never
|
|
81
189
|
}
|
|
82
190
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
191
|
+
type AddSchemaExtensions<
|
|
192
|
+
TSchema extends z.ZodTypeAny,
|
|
193
|
+
TExtends extends Record<string, Entity>,
|
|
194
|
+
> = TExtends extends Record<string, never>
|
|
195
|
+
? TSchema
|
|
196
|
+
: IsEmptyObject<TExtends> extends true
|
|
197
|
+
? TSchema
|
|
198
|
+
: {
|
|
199
|
+
[K in keyof TExtends]: AddSchemaExtensions<
|
|
200
|
+
z.ZodIntersection<TSchema, TExtends[K]["schema"]>,
|
|
201
|
+
Omit<TExtends, K>
|
|
202
|
+
>
|
|
203
|
+
}[keyof TExtends]
|
|
204
|
+
|
|
205
|
+
type AddTypeExtensions<
|
|
206
|
+
TImplementedTypes extends EntityTypes,
|
|
207
|
+
TExtends extends Record<string, Entity>,
|
|
208
|
+
> = TExtends extends Record<string, never>
|
|
209
|
+
? TImplementedTypes
|
|
210
|
+
: IsEmptyObject<TExtends> extends true
|
|
211
|
+
? TImplementedTypes
|
|
212
|
+
: {
|
|
213
|
+
[K in keyof TExtends]: AddTypeExtensions<
|
|
214
|
+
TImplementedTypes & TExtends[K][typeof implementedTypes],
|
|
215
|
+
Omit<TExtends, K>
|
|
216
|
+
>
|
|
217
|
+
}[keyof TExtends]
|
|
218
|
+
|
|
219
|
+
type AddSchemaInclusions<
|
|
220
|
+
TSchema extends z.ZodTypeAny,
|
|
221
|
+
TImplements extends Record<string, EntityIncludeRef>,
|
|
222
|
+
> = TImplements extends Record<string, never>
|
|
223
|
+
? TSchema
|
|
224
|
+
: z.ZodIntersection<TSchema, z.ZodObject<InclusionShape<TImplements>>>
|
|
225
|
+
|
|
226
|
+
type AddTypeInclusions<
|
|
227
|
+
TImplementedTypes extends EntityTypes,
|
|
228
|
+
TImplements extends Record<string, EntityIncludeRef>,
|
|
229
|
+
> = TImplements extends Record<string, never>
|
|
230
|
+
? TImplementedTypes
|
|
231
|
+
: {
|
|
232
|
+
[K in keyof TImplements]: TImplements[K] extends {
|
|
233
|
+
entity: infer E
|
|
234
|
+
}
|
|
235
|
+
? E extends Entity
|
|
236
|
+
? AddTypeInclusions<TImplementedTypes & E[typeof implementedTypes], Omit<TImplements, K>>
|
|
237
|
+
: TImplementedTypes
|
|
238
|
+
: TImplements[K] extends Entity
|
|
239
|
+
? AddTypeInclusions<
|
|
240
|
+
TImplementedTypes & TImplements[K][typeof implementedTypes],
|
|
241
|
+
Omit<TImplements, K>
|
|
242
|
+
>
|
|
243
|
+
: TImplementedTypes
|
|
244
|
+
}[keyof TImplements]
|
|
245
|
+
|
|
246
|
+
export function defineEntity<
|
|
247
|
+
TType extends VersionedName,
|
|
248
|
+
TSchema extends z.ZodType,
|
|
249
|
+
TExtends extends Record<string, Entity> = Record<string, never>,
|
|
250
|
+
TImplements extends Record<string, EntityIncludeRef> = Record<string, never>,
|
|
251
|
+
>(
|
|
252
|
+
options: EntityOptions<TType, TSchema, TExtends, TImplements>,
|
|
253
|
+
): Entity<
|
|
254
|
+
TType,
|
|
255
|
+
Simplify<AddTypeInclusions<AddTypeExtensions<{ [K in TType]: true }, TExtends>, TImplements>>,
|
|
256
|
+
AddSchemaExtensions<AddSchemaInclusions<TSchema, TImplements>, TExtends>
|
|
257
|
+
> {
|
|
86
258
|
try {
|
|
87
259
|
entityModelSchema.shape.type.parse(options.type)
|
|
88
260
|
} catch (error) {
|
|
@@ -93,13 +265,94 @@ export function defineEntity<TType extends VersionedName, TSchema extends z.ZodT
|
|
|
93
265
|
throw new Error("Entity schema is required")
|
|
94
266
|
}
|
|
95
267
|
|
|
268
|
+
const includedEntities = Object.entries(options.includes ?? {}).map(([field, entityRef]) => {
|
|
269
|
+
if (isEntityIncludeRef(entityRef)) {
|
|
270
|
+
return {
|
|
271
|
+
entity: entityRef.entity,
|
|
272
|
+
inclusion: {
|
|
273
|
+
type: entityRef.entity.type,
|
|
274
|
+
required: entityRef.required ?? true,
|
|
275
|
+
multiple: entityRef.multiple ?? false,
|
|
276
|
+
field,
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
entity: entityRef,
|
|
283
|
+
inclusion: {
|
|
284
|
+
type: entityRef.type,
|
|
285
|
+
required: true,
|
|
286
|
+
multiple: false,
|
|
287
|
+
field,
|
|
288
|
+
},
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const inclusionShape = includedEntities.reduce(
|
|
293
|
+
(shape, { entity, inclusion }) => {
|
|
294
|
+
if (inclusion.multiple) {
|
|
295
|
+
shape[inclusion.field] = inclusion.required
|
|
296
|
+
? entity.schema.array()
|
|
297
|
+
: entity.schema.array().optional()
|
|
298
|
+
} else {
|
|
299
|
+
shape[inclusion.field] = inclusion.required ? entity.schema : entity.schema.optional()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return shape
|
|
303
|
+
},
|
|
304
|
+
{} as Record<string, z.ZodTypeAny>,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
let finalSchema = Object.values(options.extends ?? {}).reduce(
|
|
308
|
+
(schema, entity) => z.intersection(schema, entity.schema),
|
|
309
|
+
options.schema as z.ZodType,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if (includedEntities.length > 0) {
|
|
313
|
+
finalSchema = z.intersection(finalSchema, z.object(inclusionShape))
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const directInclusions = includedEntities.map(({ inclusion }) => inclusion)
|
|
317
|
+
const directExtensions = Object.values(options.extends ?? {}).map(entity => entity.type)
|
|
318
|
+
|
|
319
|
+
const inclusions = Object.values(options.extends ?? {}).reduce(
|
|
320
|
+
(incs, entity) => {
|
|
321
|
+
if (entity.model.inclusions) {
|
|
322
|
+
incs.push(...entity.model.inclusions)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return incs
|
|
326
|
+
},
|
|
327
|
+
[...directInclusions],
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
const extensions = Object.values(options.extends ?? {}).reduce((exts, entity) => {
|
|
331
|
+
exts.push(...(entity.model.extensions ?? []), entity.type)
|
|
332
|
+
|
|
333
|
+
return exts
|
|
334
|
+
}, [] as string[])
|
|
335
|
+
|
|
96
336
|
try {
|
|
337
|
+
let _schema: z.core.JSONSchema.BaseSchema
|
|
338
|
+
|
|
97
339
|
return {
|
|
98
340
|
type: options.type,
|
|
99
|
-
schema:
|
|
341
|
+
schema: finalSchema,
|
|
100
342
|
model: {
|
|
101
343
|
type: options.type,
|
|
102
|
-
|
|
344
|
+
extensions: extensions.length > 0 ? extensions : undefined,
|
|
345
|
+
directExtensions: directExtensions.length > 0 ? directExtensions : undefined,
|
|
346
|
+
inclusions: inclusions.length > 0 ? inclusions : undefined,
|
|
347
|
+
directInclusions: directInclusions.length > 0 ? directInclusions : undefined,
|
|
348
|
+
get schema() {
|
|
349
|
+
if (!_schema) {
|
|
350
|
+
// TODO: forbid unrepresentable types (and find way to filter out "undefined" literals)
|
|
351
|
+
_schema = z.toJSONSchema(finalSchema, { target: "draft-7", unrepresentable: "any" })
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return _schema
|
|
355
|
+
},
|
|
103
356
|
meta: {
|
|
104
357
|
...options.meta,
|
|
105
358
|
title:
|
|
@@ -108,7 +361,8 @@ export function defineEntity<TType extends VersionedName, TSchema extends z.ZodT
|
|
|
108
361
|
// will be calculated by the library loader
|
|
109
362
|
definitionHash: null!,
|
|
110
363
|
},
|
|
111
|
-
|
|
364
|
+
// biome-ignore lint/suspicious/noExplicitAny: we already typed return type
|
|
365
|
+
} as any
|
|
112
366
|
} catch (error) {
|
|
113
367
|
throw new Error(`Failed to define entity "${options.type}"`, { cause: error })
|
|
114
368
|
}
|
|
@@ -117,3 +371,27 @@ export function defineEntity<TType extends VersionedName, TSchema extends z.ZodT
|
|
|
117
371
|
export function isEntity(value: unknown): value is Entity {
|
|
118
372
|
return typeof value === "object" && value !== null && "model" in value
|
|
119
373
|
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Checks whether the given entity can be assigned to the target type.
|
|
377
|
+
*
|
|
378
|
+
* An entity is considered assignable to a target type if:
|
|
379
|
+
* - The entity's type is exactly the same as the target type.
|
|
380
|
+
* - The entity extends the target type (either directly or indirectly).
|
|
381
|
+
* - The entity includes the target type (either directly or indirectly).
|
|
382
|
+
*
|
|
383
|
+
* @param entity The entity to check.
|
|
384
|
+
* @param target The target versioned name to check against.
|
|
385
|
+
* @returns True if the entity is assignable to the target type, false otherwise.
|
|
386
|
+
*/
|
|
387
|
+
export function isAssignableTo(entity: EntityModel, target: VersionedName): boolean {
|
|
388
|
+
if (entity.type === target) {
|
|
389
|
+
return true
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (entity.extensions?.includes(target)) {
|
|
393
|
+
return true
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return entity.inclusions?.some(implementation => implementation.type === target) ?? false
|
|
397
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/** biome-ignore-all assist/source/organizeImports: will break groups */
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export {
|
|
4
|
+
type Entity,
|
|
5
|
+
type EntityModel,
|
|
6
|
+
isEntity,
|
|
7
|
+
isAssignableTo,
|
|
8
|
+
defineEntity,
|
|
9
|
+
entityModelSchema,
|
|
10
|
+
} from "./entity"
|
|
11
|
+
|
|
4
12
|
export * from "./instance"
|
|
5
13
|
export * from "./unit"
|
|
6
14
|
export * from "./i18n"
|
|
@@ -9,6 +17,7 @@ export * from "./terminal"
|
|
|
9
17
|
export * from "./page"
|
|
10
18
|
export * from "./trigger"
|
|
11
19
|
export * from "./worker"
|
|
20
|
+
export * from "./compaction"
|
|
12
21
|
|
|
13
22
|
export {
|
|
14
23
|
// common utilities
|
package/src/instance.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EntityTypes } from "./entity"
|
|
1
2
|
import type { boundaryInput, boundaryInputs } from "./evaluation"
|
|
2
3
|
import { z } from "zod"
|
|
3
4
|
import { componentKindSchema } from "./component"
|
|
@@ -11,20 +12,21 @@ import {
|
|
|
11
12
|
|
|
12
13
|
declare const type: unique symbol
|
|
13
14
|
|
|
14
|
-
export type InstanceInput<
|
|
15
|
-
[type]?:
|
|
15
|
+
export type InstanceInput<TTypes extends EntityTypes = EntityTypes> = {
|
|
16
|
+
[type]?: TTypes
|
|
16
17
|
[boundaryInput]?: InstanceInput
|
|
17
18
|
instanceId: InstanceId
|
|
18
19
|
output: string
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export type OptionalInstanceInput<
|
|
22
|
-
| ({ provided: true } & InstanceInput<
|
|
22
|
+
export type OptionalInstanceInput<TTypes extends EntityTypes = EntityTypes> =
|
|
23
|
+
| ({ provided: true } & InstanceInput<TTypes>)
|
|
23
24
|
| { provided: false; [boundaryInput]?: InstanceInput }
|
|
24
25
|
|
|
25
|
-
export type InstanceInputGroup<
|
|
26
|
-
[
|
|
27
|
-
|
|
26
|
+
export type InstanceInputGroup<TTypes extends EntityTypes = EntityTypes> =
|
|
27
|
+
InstanceInput<TTypes>[] & {
|
|
28
|
+
[boundaryInputs]?: InstanceInputGroup<TTypes>
|
|
29
|
+
}
|
|
28
30
|
|
|
29
31
|
export function inputKey(input: InstanceInput): string {
|
|
30
32
|
return `${input.instanceId}:${input.output}`
|
|
@@ -200,7 +202,7 @@ export function parseInstanceId(
|
|
|
200
202
|
return parts as [VersionedName, GenericName]
|
|
201
203
|
}
|
|
202
204
|
|
|
203
|
-
export function findInput<T extends
|
|
205
|
+
export function findInput<T extends EntityTypes>(
|
|
204
206
|
inputs: InstanceInput<T>[],
|
|
205
207
|
name: string,
|
|
206
208
|
): InstanceInput<T> | null {
|
|
@@ -221,7 +223,7 @@ export function findInput<T extends string>(
|
|
|
221
223
|
return matchedInputs[0]
|
|
222
224
|
}
|
|
223
225
|
|
|
224
|
-
export function findRequiredInput<T extends
|
|
226
|
+
export function findRequiredInput<T extends EntityTypes>(
|
|
225
227
|
inputs: InstanceInput<T>[],
|
|
226
228
|
name: string,
|
|
227
229
|
): InstanceInput<T> {
|
|
@@ -234,14 +236,14 @@ export function findRequiredInput<T extends string>(
|
|
|
234
236
|
return input
|
|
235
237
|
}
|
|
236
238
|
|
|
237
|
-
export function findInputs<T extends
|
|
239
|
+
export function findInputs<T extends EntityTypes>(
|
|
238
240
|
inputs: InstanceInput<T>[],
|
|
239
241
|
names: string[],
|
|
240
242
|
): InstanceInput<T>[] {
|
|
241
243
|
return names.map(name => findInput(inputs, name)).filter(Boolean) as InstanceInput<T>[]
|
|
242
244
|
}
|
|
243
245
|
|
|
244
|
-
export function findRequiredInputs<T extends
|
|
246
|
+
export function findRequiredInputs<T extends EntityTypes>(
|
|
245
247
|
inputs: InstanceInput<T>[],
|
|
246
248
|
names: string[],
|
|
247
249
|
): InstanceInput<T>[] {
|
|
@@ -256,6 +258,8 @@ export function findRequiredInputs<T extends string>(
|
|
|
256
258
|
export enum HighstateSignature {
|
|
257
259
|
Artifact = "d55c63ac-3174-4756-808f-f778e99af0d1",
|
|
258
260
|
Yaml = "c857cac5-caa6-4421-b82c-e561fbce6367",
|
|
261
|
+
Id = "348d020e-0d9e-4ae7-9415-b91af99f5339",
|
|
262
|
+
Ref = "6d7f9da0-9cb6-496d-b72e-cf85ee4d9cf8",
|
|
259
263
|
}
|
|
260
264
|
|
|
261
265
|
export const yamlValueSchema = z.object({
|
|
@@ -263,6 +267,17 @@ export const yamlValueSchema = z.object({
|
|
|
263
267
|
value: z.string(),
|
|
264
268
|
})
|
|
265
269
|
|
|
270
|
+
export const objectWithIdSchema = z.object({
|
|
271
|
+
[HighstateSignature.Id]: z.literal(true),
|
|
272
|
+
id: z.number(),
|
|
273
|
+
value: z.unknown(),
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
export const objectRefSchema = z.object({
|
|
277
|
+
[HighstateSignature.Ref]: z.literal(true),
|
|
278
|
+
id: z.number(),
|
|
279
|
+
})
|
|
280
|
+
|
|
266
281
|
export type YamlValue = z.infer<typeof yamlValueSchema>
|
|
267
282
|
|
|
268
283
|
export const fileMetaSchema = z.object({
|
package/src/meta.ts
CHANGED
|
@@ -125,7 +125,10 @@ export const timestampsSchema = z.object({
|
|
|
125
125
|
*/
|
|
126
126
|
export const genericNameSchema = z
|
|
127
127
|
.string()
|
|
128
|
-
.regex(
|
|
128
|
+
.regex(
|
|
129
|
+
/^[a-z][a-z0-9-.]+$/,
|
|
130
|
+
"Name must start with a letter and can only contain lowercase letters, numbers, dashes (-) and dots (.)",
|
|
131
|
+
)
|
|
129
132
|
.min(2)
|
|
130
133
|
.max(64)
|
|
131
134
|
|
|
@@ -190,7 +193,7 @@ export function parseVersionedName(name: string): [name: string, version: number
|
|
|
190
193
|
*/
|
|
191
194
|
export const fieldNameSchema = z
|
|
192
195
|
.string()
|
|
193
|
-
.regex(/^[a-z][a-zA-Z0-9]
|
|
196
|
+
.regex(/^[a-z][a-zA-Z0-9]+$/, "Field name must start with a letter and be in camelCase format")
|
|
194
197
|
.min(2)
|
|
195
198
|
.max(64)
|
|
196
199
|
|
package/src/pulumi.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parse } from "yaml"
|
|
2
2
|
import { z } from "zod"
|
|
3
|
+
import { entityInclusionSchema } from "./entity"
|
|
3
4
|
import {
|
|
4
5
|
fileMetaSchema,
|
|
5
6
|
HighstateSignature,
|
|
@@ -10,6 +11,17 @@ import {
|
|
|
10
11
|
import { commonObjectMetaSchema } from "./meta"
|
|
11
12
|
import { triggerInvocationSchema } from "./trigger"
|
|
12
13
|
|
|
14
|
+
export type UnitInputReference = z.infer<typeof unitInputReference>
|
|
15
|
+
|
|
16
|
+
export const unitInputReference = z.object({
|
|
17
|
+
...instanceInputSchema.shape,
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The resolved inclusion needed to extract the input value.
|
|
21
|
+
*/
|
|
22
|
+
inclusion: entityInclusionSchema.optional(),
|
|
23
|
+
})
|
|
24
|
+
|
|
13
25
|
export const unitConfigSchema = z.object({
|
|
14
26
|
/**
|
|
15
27
|
* The ID of the instance.
|
|
@@ -24,7 +36,7 @@ export const unitConfigSchema = z.object({
|
|
|
24
36
|
/**
|
|
25
37
|
* The record of input references for the unit.
|
|
26
38
|
*/
|
|
27
|
-
inputs: z.record(z.string(),
|
|
39
|
+
inputs: z.record(z.string(), unitInputReference.array()),
|
|
28
40
|
|
|
29
41
|
/**
|
|
30
42
|
* The list of triggers that have been invoked for this unit.
|