Built In Types

WithComment

Use ft.WithComment to attach a title and an optional description to a Codec. These don't affect parsing, serializing or the static TypeScript type at all - they're purely metadata, picked up when you convert a Codec to a JSON Schema with ft.toJsonSchema or ft.toStandardJsonSchema.

import * as ft from "funtypes";

const UserCodec = ft.Object({
  id: ft.Number,
  name: ft.WithComment(
    {
      title: "Full name",
      description: "The user's full, legal name",
    },
    ft.String,
  ),
});
// => ft.Codec<{ id: number; name: string }>

// Parsing/serializing behave exactly as if
// WithComment wasn't there at all.
assert.deepEqual(
  UserCodec.parse({ id: 1, name: "Forbes Lindesay" }),
  { id: 1, name: "Forbes Lindesay" },
);

assert.deepEqual(
  ft.toJsonSchema(UserCodec),
  {
    type: "object",
    properties: {
      id: { type: "number" },
      name: {
        type: "string",
        title: "Full name",
        description: "The user's full, legal name",
      },
    },
    required: ["id", "name"],
  },
);

description is optional, if you only need a title:

import * as ft from "funtypes";

const ScoreCodec = ft.WithComment(
  { title: "Score" },
  ft.Number,
);

assert.deepEqual(ft.toJsonSchema(ScoreCodec), {
  type: "number",
  title: "Score",
});

Introspection

WithComment doesn't change ft.showType, but it is visible via Codec.introspection:

import * as ft from "funtypes";

const ScoreCodec = ft.WithComment(
  { title: "Score" },
  ft.Number,
);

assert.deepEqual(ft.showType(ScoreCodec), "number");

assert.deepEqual(ScoreCodec.introspection, {
  tag: "comment",
  title: "Score",
  description: undefined,
  underlying: ft.Number,
});

Commenting Optional Properties

ft.WithComment preserves optionality, so you can wrap a property created with ft.Optional without losing the ? in the static type:

import * as ft from "funtypes";

const UserCodec = ft.Object({
  nickname: ft.WithComment(
    { title: "Nickname" },
    ft.Optional(ft.String),
  ),
});
// => ft.Codec<{ nickname?: string }>

assert.deepEqual(
  ft.toJsonSchema(UserCodec),
  {
    type: "object",
    properties: {
      nickname: { type: "string", title: "Nickname" },
    },
  },
);

Commenting Objects

You can also comment a whole object, and still use ft.Pick, ft.Omit, ft.Partial, ft.Mutable or ft.Readonly on it afterwards - the comment carries over to the result:

import * as ft from "funtypes";

const UserCodec = ft.WithComment(
  { title: "User" },
  ft.Object({ id: ft.Number, name: ft.String }),
);

const PartialUserCodec = ft.Partial(UserCodec);
// => ft.Codec<{ id?: number; name?: string }>

const { introspection } = PartialUserCodec;
if (introspection.tag === "comment") {
  assert.deepEqual(introspection.title, "User");
}

Comments on named/branded/constrained types

If the underlying Codec is wrapped in ft.Named, ft.Constraint with a name, or is a Brand, ft.toJsonSchema will already give it its own named entry in $defs. Wrapping that in ft.WithComment adds the title/description alongside the $ref that points at it, rather than inside the shared $defs entry, so different usages of the same named type can have different comments.

Previous
Unknown