From 85eaeebffa1bcda4d5ecd0ad8479e85673418ada Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Mon, 2 Nov 2026 13:00:56 +0100 Subject: [PATCH] feat(database): type the own properties of a schema codec as unknown values --- src/staged-query/schema.ts | 11 +++++++++-- src/tests/field_type.test.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/tests/field_type.test.ts diff --git a/src/staged-query/schema.ts b/src/staged-query/schema.ts index 6e06d7a..d9abc26 100644 --- a/src/staged-query/schema.ts +++ b/src/staged-query/schema.ts @@ -49,7 +49,14 @@ interface IoTsValidation { readonly _tag: "Left" | "Right"; } -interface IoTsCodec { +/** + * The shape of an io-ts codec that a schema field accepts + * + * Declared as a type alias so TypeScript gives it an implicit string index + * signature: `Object.entries` on a codec lists its own enumerable properties + * as `[string, unknown]`, where an interface would make them `any`. + */ +type IoTsCodec = { readonly _A: A; readonly _O: O; readonly _I: I; @@ -57,7 +64,7 @@ interface IoTsCodec { is(u: A | I): boolean; encode(a: A): O; decode(i: I): IoTsValidation; -} +}; export type FieldType = StringFieldType | IoTsCodec; diff --git a/src/tests/field_type.test.ts b/src/tests/field_type.test.ts new file mode 100644 index 0000000..b0aa796 --- /dev/null +++ b/src/tests/field_type.test.ts @@ -0,0 +1,34 @@ +import { expect } from "chai"; +import type { FieldType, StringFieldType } from "../schema"; +import type { Same } from "./helpers/same-type"; + +type IsAny = 0 extends 1 & T ? true : false; + +type Codec = Exclude; + +function propertiesOf(codec: Codec) { + return Object.entries(codec); +} + +type PropertyValue = ReturnType[number][1]; + +const valuesAreNotAny: Same, false> = true; +const valuesAreUnknown: Same = true; + +describe("Field types", () => { + it("Types the own properties of a codec as unknown values", () => { + const codec: Codec = { + _A: 0, + _O: 0, + _I: 0, + name: "number", + is: (u): boolean => u === 0, + encode: (a) => a, + decode: () => ({ _tag: "Right" }), + }; + const names = propertiesOf(codec).map(([name]) => name); + expect(names).to.include.members(["name", "is", "encode", "decode"]); + expect(valuesAreNotAny).to.equal(true); + expect(valuesAreUnknown).to.equal(true); + }); +});