@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/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["type"]>
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"]["type"]>[] | undefined
116
- : InstanceInput<T["entity"]["type"]> | undefined
115
+ ? InstanceInput<T["entity"][typeof implementedTypes]>[] | undefined
116
+ : InstanceInput<T["entity"][typeof implementedTypes]> | undefined
117
117
  : T["multiple"] extends true
118
- ? InstanceInput<T["entity"]["type"]>[]
119
- : InstanceInput<T["entity"]["type"]>
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: VersionedName
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]["type"]>[]
293
- : InstanceInput<T[0]["type"]>
293
+ ? InstanceInput<T[0][typeof implementedTypes]>[]
294
+ : InstanceInput<T[0][typeof implementedTypes]>
294
295
  : T[2] extends true
295
- ? InstanceInput<T[0]["type"]>[] | undefined
296
- : InstanceInput<T[0]["type"]> | undefined
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]["type"]>[]
300
- : InstanceInput<T[0]["type"]>
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, { target: "draft-7", io: "input" }),
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
- TSchema extends z.ZodType = z.ZodType,
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<TType extends VersionedName, TSchema extends z.ZodType> = {
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
- export function defineEntity<TType extends VersionedName, TSchema extends z.ZodType>(
84
- options: EntityOptions<TType, TSchema>,
85
- ): Entity<TType, TSchema> {
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: options.schema,
341
+ schema: finalSchema,
100
342
  model: {
101
343
  type: options.type,
102
- schema: z.toJSONSchema(options.schema, { target: "draft-7" }),
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 * from "./entity"
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<TType extends string = string> = {
15
- [type]?: TType
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<TType extends string = string> =
22
- | ({ provided: true } & InstanceInput<TType>)
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<TType extends string = string> = InstanceInput<TType>[] & {
26
- [boundaryInputs]?: InstanceInputGroup<TType>
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 string>(
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 string>(
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 string>(
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 string>(
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(/^[a-z][a-z0-9-_.]+$/)
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(), instanceInputSchema.array()),
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.