Skip to content

Instantly share code, notes, and snippets.

@VictorQueiroz
Last active September 21, 2025 00:09
Show Gist options
  • Select an option

  • Save VictorQueiroz/231ff0eaf2c1a06115d896f59a851cdd to your computer and use it in GitHub Desktop.

Select an option

Save VictorQueiroz/231ff0eaf2c1a06115d896f59a851cdd to your computer and use it in GitHub Desktop.
Helper that can be used to build strict Typebox types.
import {
ArrayOptions,
DateOptions,
IntegerOptions,
ObjectOptions,
SchemaOptions,
Static,
StringOptions,
TBoolean,
TInteger,
TNull,
TNumber,
TObject,
TSchema,
TString,
TUnion,
Type,
Union
} from "@sinclair/typebox";
import JSBI from "jsbi";
import assert from "node:assert";
function getIntegerRange(bitLength: number, unsigned: boolean) {
const max = BigInt(2) ** BigInt(bitLength) - BigInt(1);
const min = unsigned ? BigInt(0) : (-2n) ** BigInt(bitLength - 1);
return { min, max };
}
const integer = {
int32: (options: SchemaOptions = {}) =>
VariableBitsInteger(32, false, options),
uint32: (options: SchemaOptions = {}) =>
VariableBitsInteger(32, true, options),
int8: (options: SchemaOptions = {}) =>
VariableBitsInteger(8, false, options),
uint8: (options: SchemaOptions = {}) =>
VariableBitsInteger(8, true, options),
int16: (options: SchemaOptions = {}) =>
VariableBitsInteger(16, false, options),
uint16: (options: SchemaOptions = {}) =>
VariableBitsInteger(16, true, options)
};
const dataType = {
...integer,
float32: (options: SchemaOptions = {}): TNumber =>
Type.Number(
schemaOptions<SchemaOptions>({
description: `A 32-bit floating point number.`,
examples: [3.4028235e38],
default: 0.0,
minimum: -3.4028235e38,
maximum: 3.4028235e38,
multipleOf: 1e-7,
...options
})
),
str: (options: StringOptions = {}): TString =>
Type.String({
contentEncoding: "8bit",
contentMediaType: "text/plain",
examples: [],
...options
}),
date: (options: DateOptions = {}) =>
Type.Date(
schemaOptions<DateOptions>({
description: `A date type in ISO 8601 format.`,
examples: [
"2025-09-19T14:34:16.830Z",
"1970-01-01T00:00:00.000Z"
],
default: "1970-01-01T00:00:00.000Z",
...options
})
),
null: (options: SchemaOptions = {}) =>
Type.Null(
schemaOptions({ examples: [null], default: null, ...options })
),
boolean: (options: SchemaOptions = {}): TBoolean =>
Type.Boolean(
schemaOptions({ examples: [true, false], ...options })
)
};
function schemaId(id: string) {
assert.strict.ok(
id.length > 0,
"Schema must have a non-empty $id."
);
assert.match(
id,
/^[A-Za-z_]+[A-Za-z0-9_]+$/,
[
`${id} is not a valid schema $id.`,
`$id must start with a letter or underscore and contain only alphanumeric characters and underscores.`
].join(" ")
);
return id;
}
function schemaOptions<
T extends
| DateOptions
| SchemaOptions
| ObjectOptions
| IntegerOptions
| StringOptions
| ArrayOptions
>(options: T): T {
options = { examples: [], readOnly: true, ...options };
if (!Array.isArray(options.examples)) {
console.warn(
"Schema options `examples` property must be an array. Found: %o",
options.examples
);
}
const id = options["$id"] ?? null;
if (id !== null) {
options["$id"] = schemaId(id);
}
return options;
}
function union<T extends TSchema[]>(
elements: T,
options: SchemaOptions = {}
): Union<T> {
return Type.Union<T>(
elements,
schemaOptions<SchemaOptions>({
description: `An union of the following types: ${elements
.map(e => e.$id)
.join(", ")}.`,
examples: elements.map(e => e.examples).flat(),
...options
})
);
}
function nullable<T extends TSchema>(
schema: T,
options: SchemaOptions = {}
): TUnion<[T, TNull]> {
return union<[T, TNull]>(
[schema, dataType.null()],
schemaOptions<SchemaOptions>({
description: `An union describing a type that can also be null.`,
examples: [null],
...options
})
);
}
const composedTypes = {
nullable,
uuid: (options: StringOptions = {}) =>
types.str(
schemaOptions<StringOptions>({
pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`,
maxLength: 36,
minLength: 36,
description: `A string following the UUID format as per RFC 4122.`,
examples: ["3fa85f64-5717-4562-b3fc-2c963f66afa6"],
...options
})
)
};
interface IObjectOptions<T extends TSchema> extends ObjectOptions {
examples?: Static<T>[];
}
const types = {
...composedTypes,
...dataType,
union,
literal: Type.Literal,
unknown: Type.Unknown,
array: <T extends TSchema>(items: T, options: ArrayOptions = {}) =>
Type.Array<T>(
items,
schemaOptions<ArrayOptions>({
description: `An array of ${items.$id} items.`,
minItems: 0,
minContains: 0,
examples: items.examples,
...options
})
),
ref: <T extends TSchema>(
schema: T,
options: SchemaOptions = {}
) => {
assert.strict.ok(
schema.$id,
"Schema must have an $id to be referenced."
);
const schemaRef = Type.Ref(
schema.$id,
schemaOptions<SchemaOptions>(options)
);
return Type.Unsafe<Static<T>>(schemaRef);
},
optional: Type.Optional,
obj: <TProperties extends Record<string, TSchema>>(
properties: TProperties,
objectOptions: IObjectOptions<TObject<TProperties>> = {}
): TObject<TProperties> => {
const options = schemaOptions<ObjectOptions>({
minProperties: Object.keys(properties).length,
maxProperties: Object.keys(properties).length,
additionalProperties: false,
...objectOptions
});
return Type.Object(
Object.fromEntries(
Object.entries(properties).map(([key, value]) => [
key,
{
description:
value.description ??
`The ${key} property of the ${options.$id} object.`,
readOnly: options.readOnly,
...value
}
])
) as TProperties,
options
);
}
};
function VariableBitsInteger(
bitLength: number,
unsigned: boolean = false,
options: IntegerOptions = {}
): TInteger {
const { min, max } = getIntegerRange(bitLength, unsigned);
return Type.Integer(
schemaOptions<IntegerOptions>({
$id: `${unsigned ? "Unsigned" : "Signed"}${bitLength}BitInteger`,
description: `A ${bitLength}-bit ${unsigned ? "unsigned" : "signed"} integer.`,
examples: [max.toString()],
default: min.toString(),
minimum: JSBI.toNumber(JSBI.BigInt(min.toString())),
maximum: JSBI.toNumber(JSBI.BigInt(max.toString())),
...options
})
);
}
export default types;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment