Hyperlinkv0.8.0-beta.28

Schema

Schema.StructWithRestfunctioneffect/Schema.ts:3855
StructWithRest<S, Records>

Extends a struct schema with one or more record (index-signature) schemas, producing a schema whose decoded type intersects the struct and all records.

Gotchas

TypeScript index signatures also apply to fixed keys. StructWithRest does not reject incompatible fixed fields at the call site; use StructWithRest.ValidateRecords when you want an explicit type-level compatibility check.

Example (Defining structs with string-indexed extra keys)

import { Schema } from "effect"

const schema = Schema.StructWithRest(
  Schema.Struct({ id: Schema.Number }),
  [Schema.Record(Schema.String, Schema.Number)]
)

// { readonly id: number, readonly [x: string]: number }
type T = typeof schema.Type
constructors
Source effect/Schema.ts:3855226 lines
export declare namespace StructWithRest {
  /**
   * Constraint for object-like schemas that can be used as the fixed portion of a
   * `StructWithRest` schema.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Objects = Constraint & { readonly ast: SchemaAST.Objects }

  /**
   * Readonly list of record schemas that provide the additional index signatures
   * for a `StructWithRest` schema.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Records = ReadonlyArray<$Record<Record.Key, Constraint>>

  type MergeTuple<T extends ReadonlyArray<unknown>> = T extends readonly [infer Head, ...infer Tail] ?
    Head & MergeTuple<Tail>
    : {}

  type Intersect<
    S extends Objects,
    Records extends StructWithRest.Records,
    Side extends "Type" | "Iso" | "Encoded" | "~type.make"
  > =
    & S[Side]
    & MergeTuple<{ readonly [K in keyof Records]: Records[K][Side] }>

  /**
   * Computes the decoded type for `StructWithRest` by intersecting the base object
   * schema's decoded `Type` with the decoded types of all rest record schemas.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "Type">

  /**
   * Computes the iso type for `StructWithRest` by intersecting the base object
   * schema's `Iso` type with the `Iso` types of all rest record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Iso<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "Iso">

  /**
   * Computes the encoded type for `StructWithRest` by intersecting the base object
   * schema's encoded type with the encoded types of all rest record schemas.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "Encoded">

  /**
   * Computes the input type accepted when constructing a `StructWithRest` value by
   * intersecting the base object's make input with the make inputs of all rest
   * record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type MakeIn<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "~type.make">

  type Services<
    S extends Objects,
    Records extends StructWithRest.Records,
    Side extends "DecodingServices" | "EncodingServices"
  > =
    | S[Side]
    | { [K in keyof Records]: Records[K][Side] }[number]

  /**
   * Union of the decoding service requirements of the base object schema and all
   * rest record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type DecodingServices<S extends Objects, Records extends StructWithRest.Records> = Services<
    S,
    Records,
    "DecodingServices"
  >

  /**
   * Union of the encoding service requirements of the base object schema and all
   * rest record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type EncodingServices<S extends Objects, Records extends StructWithRest.Records> = Services<
    S,
    Records,
    "EncodingServices"
  >

  type IncompatibleKeys<A, B, OK extends (keyof A & keyof B) = Extract<keyof A, keyof B>> = {
    [K in OK]: Required<Pick<A, K>>[K] extends B[K] ? never : K
  }[OK]

  type IncompatibleSideKeys<
    S extends Objects,
    Records extends StructWithRest.Records,
    Side extends "Type" | "Encoded" | "Iso" | "~type.make"
  > = {
    [I in keyof Records]: Records[I][Side] extends object ? IncompatibleKeys<S[Side], Records[I][Side]> : never
  }[number]

  type IncompatibleRecords<S extends Objects, Records extends StructWithRest.Records> =
    | IncompatibleSideKeys<S, Records, "Type">
    | IncompatibleSideKeys<S, Records, "Encoded">
    | IncompatibleSideKeys<S, Records, "Iso">
    | IncompatibleSideKeys<S, Records, "~type.make">

  /**
   * Checks whether fixed fields are compatible with the rest record schemas.
   *
   * **Details**
   *
   * Returns `true` when all fixed fields can also satisfy the matching rest
   * index signatures. Returns a diagnostic object when TypeScript would make
   * the resulting intersection too narrow for one or more fixed keys.
   *
   * **Example** (Checking record compatibility)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const user = Schema.Struct({ id: Schema.String })
   * const stringExtras = [Schema.Record(Schema.String, Schema.String)] as const
   *
   * type UserCheck = Schema.StructWithRest.ValidateRecords<typeof user, typeof stringExtras>
   *
   * const userCheck: UserCheck = true
   * void userCheck
   *
   * const counter = Schema.Struct({ count: Schema.NumberFromString })
   *
   * type CounterCheck = Schema.StructWithRest.ValidateRecords<typeof counter, typeof stringExtras>
   * //    ^? { "incompatible index signatures": "count" }
   *
   * const counterCheck = null as unknown as CounterCheck
   * void counterCheck
   * ```
   *
   * @category utility types
   * @since 4.0.0
   */
  export type ValidateRecords<
    S extends Objects,
    Records extends StructWithRest.Records
  > = [IncompatibleRecords<S, Records>] extends [never] ? true
    : {
      "incompatible index signatures": IncompatibleRecords<S, Records>
    }
}

/**
 * Type-level representation returned by {@link StructWithRest}.
 *
 * @category models
 * @since 4.0.0
 */
export interface StructWithRest<
  S extends StructWithRest.Objects,
  Records extends StructWithRest.Records
> extends
  BottomLazy<
    SchemaAST.Objects,
    StructWithRest<S, Records>
  >
{
  readonly "Type": Simplify<StructWithRest.Type<S, Records>>
  readonly "Encoded": Simplify<StructWithRest.Encoded<S, Records>>
  readonly "DecodingServices": StructWithRest.DecodingServices<S, Records>
  readonly "EncodingServices": StructWithRest.EncodingServices<S, Records>
  readonly "~type.make.in": Simplify<StructWithRest.MakeIn<S, Records>>
  readonly "~type.make": Simplify<StructWithRest.MakeIn<S, Records>>
  readonly "Iso": Simplify<StructWithRest.Iso<S, Records>>
  readonly schema: S
  readonly records: Records
}

/**
 * Extends a struct schema with one or more record (index-signature) schemas,
 * producing a schema whose decoded type intersects the struct and all records.
 *
 * **Gotchas**
 *
 * TypeScript index signatures also apply to fixed keys. `StructWithRest` does
 * not reject incompatible fixed fields at the call site; use
 * `StructWithRest.ValidateRecords` when you want an explicit type-level
 * compatibility check.
 *
 * **Example** (Defining structs with string-indexed extra keys)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.StructWithRest(
 *   Schema.Struct({ id: Schema.Number }),
 *   [Schema.Record(Schema.String, Schema.Number)]
 * )
 *
 * // { readonly id: number, readonly [x: string]: number }
 * type T = typeof schema.Type
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function StructWithRest<
  const S extends StructWithRest.Objects,
  const Records extends StructWithRest.Records
>(
  schema: S,
  records: Records
): StructWithRest<S, Records> {
  return make(SchemaAST.structWithRest(schema.ast, records.map(SchemaAST.getAST)), { schema, records })
}
Referenced by 1 symbols