API reference@evolu/commonType › brand

Call Signature

function brand<Name, ParentType>(
  name: ValidateConcreteTypeName<Name>,
  parent: ValidateParent<ParentType>,
  validate?: (value: ParentType["Output"]) => Result<void, never>,
): BrandType<ParentType, Name, never>;

Defined in: packages/common/src/Type.ts:4914

Branded Type.

Branding is the recommended way to define domain-specific primitive Types in Evolu. A Brand distinguishes values that share the same runtime representation, preventing values with different meanings from being used interchangeably.

brand takes the name of the new Brand, the parent Type to brand, and an optional validation callback for an additional constraint. Its Output retains the parent Output and its brands, and adds the new Brand.

Without a validation callback, the brand adds no errors and inherits its parent's formatter. A validation callback returns ok() when the parent value satisfies the constraint or an Err describing the failure. brand always preserves the parent value; representation-changing work belongs in transform. A fallible validation callback must format only the error it introduces; inherited errors are formatted by the parent Type automatically. A fallible brand's error type must equal the Brand name.

Example

A signed 64-bit integer:

import {
  assertEqual,
  assertErr,
  assertOk,
  assertType,
  BigInt,
  brand,
  Data,
  err,
  ok,
  type Brand,
  type TypeError,
} from "@evolu/common";

const Int64 = brand(
  "Int64",
  BigInt,
  (value) =>
    globalThis.BigInt.asIntN(64, value) === value
      ? ok()
      : err<Int64Error>({ type: "Int64", value }),
  () => "Expected a signed 64-bit integer.",
);
type Int64 = typeof Int64.Output;

// Note the Brand.
assertType<bigint & Brand<"Int64">, Int64>();

interface Int64Error extends TypeError<"Int64"> {
  readonly value: bigint;
}

assertOk(Int64.fromUnknown(42n), 42n);
const invalid = Int64.fromUnknown(2n ** 63n);
assertErr(invalid);
assertType(Data, invalid.error);
assertEqual(invalid.error, {
  type: "Int64",
  value: 2n ** 63n,
});

To reuse and compose a Brand constraint with different parent Types, define a BrandFactory.

Call Signature

function brand<Name, ParentType, Error>(
  name: Name,
  parent: ValidateBrandParent<Name, ParentType>,
  validate: (value: ParentType["Output"]) => Result<void, Error>,
  formatError: TypeErrorFormatter<NoInfer>,
): BrandType<ParentType, Name, Error>;

Defined in: packages/common/src/Type.ts:4925

Creates a validated Brand Type with its own error formatter.