Skip to content

SchemaExtension ​

A feature from another package that reads keys of its own on fields, and on the layout's steps and sections, like affects().

Definition ​

  • SchemaExtension
    • name string: names it in messages
    • fieldProps AnyObjectSchema: optional, the keys it adds to every field and every display element of the layout
    • layoutProps AnyObjectSchema: optional, the keys it adds to steps and sections
    • definitions Readonly<Record<string, AnySchema>>: optional, named schemas its props refer to, for toJsonSchema()
    • check (ctx: ExtensionContext) => ReadonlyMap<ExtensionTarget, TargetConditions>: checks its keys and gives the conditions of each target

Explanation ​

See extensions.

ExtensionContext ​

  • ExtensionContext
    • targets readonly ExtensionTarget[]: every field, and every step, section and display element of the layout, in document order
    • resolve (dotPath: string, from: ExtensionTarget) => ResolvedField | string | null: the field a path names, a message when there is none, or null when it has issues of its own
    • issue (code: DefinitionIssueCode, path: DefinitionPath, message: string) => void: adds an issue

ExtensionTarget ​

ts
type ExtensionTarget = FieldTarget | LayoutTarget
  • FieldTarget
    • kind 'field'
    • field WalkedField
    • json Readonly<Record<string, unknown>>: the field as written
    • definitionPath DefinitionPath
  • LayoutTarget
    • kind 'step' | 'section' | 'display'
    • json Readonly<Record<string, unknown>>: the node as written
    • definitionPath DefinitionPath
    • dataPath readonly (string | number)[]: where the values of the fields around it are

ResolvedField ​

  • ResolvedField
    • field WalkedField
    • kind ValueKind, and kindDef KindDef
    • schema AnySchema: the values it accepts, without any condition
    • item { kind: ValueKind; schema: AnySchema } | undefined: for a list whose items have a value

TargetConditions ​

  • TargetConditions
    • reads readonly { field: ResolvedField; at: DefinitionPath }[]: the fields they read, and where
    • evaluate (read: (i: number) => unknown) => { hidden: boolean; clear: boolean }: whether they hide and clear the target, where read(i) is the input of reads[i]

Example ​

ts
const locked: SchemaExtension = {
  name: 'locked',
  // The key it adds to every field. defineForm() and toJsonSchema() read its type from here.
  fieldProps: v.object({ lockedBy: v.optional(v.string()) }),
  check(ctx) {
    const found = new Map<ExtensionTarget, TargetConditions>()
    for (const target of ctx.targets) {
      const path = target.json['lockedBy']
      if (typeof path !== 'string') continue
      const at = [...target.definitionPath, 'lockedBy']
      // The field it reads, found the way form rules find theirs.
      const field = ctx.resolve(path, target)
      if (typeof field === 'string') ctx.issue('unknown_field', at, field)
      if (typeof field === 'string' || field === null) continue
      found.set(target, {
        reads: [{ field, at }],
        // read(0) is that field's input, or undefined while it is hidden.
        evaluate: (read) => ({ hidden: read(0) === true, clear: false }),
      })
    }
    return found
  },
}

const builder = createSchemaBuilder({ extensions: [locked] })

Packages ​