A
arktype2mo ago
Graff

Recommended syntax for Higher Order Functions?

Hi all. Just found the project and started playing with it in a sandbox. I'm trying to create a module that will take an arktype as an input and return an updated arktype as the output while conserving the type safety. EG:
import { type as arkType } from "arktype";

export const uuid = arkType(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
).brand("UID");

const hasUuid = arkType({
uuid,
});

function generateUUID(): typeof uuid.infer {
const n = Bun.randomUUIDv7();
const out = uuid(n);
if (out instanceof arkType.errors) {
throw new Error(`Problem generating random uid: ${out.summary}`);
}
return out;
}

// what should I use here?
function appendUuidToSchema<const def>(schema: arkType.validate<def, object>) {
return hasUuid.and(schema);
}

// testing
const inputArkType = arkType({
environment: arkType.enumerated("dev", "qa", "local"),
});

const arktypeFromFunction = appendUuidToSchema(inputArkType);
type X = typeof arktypeFromFunction.infer;
/*
TS2322: Type
{ uuid: Brand<string, 'UID'>; environment: string; }
is not assignable to type
{ uuid: Brand<string, 'UID'>; } & ' anyOrNever' & Type<{ environment: 'dev' | 'qa' | 'local'; }, {}>
*/
const xx: X = {
uuid: generateUUID(),
environment: "dev",
};

const arktypeFromNativeMethods = hasUuid.and(inputArkType);
type Y = typeof arktypeFromNativeMethods.infer;
const yy: Y = {
uuid: generateUUID(),
environment: "dev",
};
import { type as arkType } from "arktype";

export const uuid = arkType(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
).brand("UID");

const hasUuid = arkType({
uuid,
});

function generateUUID(): typeof uuid.infer {
const n = Bun.randomUUIDv7();
const out = uuid(n);
if (out instanceof arkType.errors) {
throw new Error(`Problem generating random uid: ${out.summary}`);
}
return out;
}

// what should I use here?
function appendUuidToSchema<const def>(schema: arkType.validate<def, object>) {
return hasUuid.and(schema);
}

// testing
const inputArkType = arkType({
environment: arkType.enumerated("dev", "qa", "local"),
});

const arktypeFromFunction = appendUuidToSchema(inputArkType);
type X = typeof arktypeFromFunction.infer;
/*
TS2322: Type
{ uuid: Brand<string, 'UID'>; environment: string; }
is not assignable to type
{ uuid: Brand<string, 'UID'>; } & ' anyOrNever' & Type<{ environment: 'dev' | 'qa' | 'local'; }, {}>
*/
const xx: X = {
uuid: generateUUID(),
environment: "dev",
};

const arktypeFromNativeMethods = hasUuid.and(inputArkType);
type Y = typeof arktypeFromNativeMethods.infer;
const yy: Y = {
uuid: generateUUID(),
environment: "dev",
};
Any suggestions to make xx == yy ?
2 Replies
Andrew
Andrew3w ago
I've been running up against a similar issue where the correct return types are being erased when used in a type-factory style function. I'm not sure if there is a better approach to this, and maybe someone can do this without the ugly as any cast, but this can be achieved with some level of type safety using Modules and generics:
const uuid = type(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
).brand("UID")
type uuid = typeof uuid.infer

const uuidModule = type.module({
"#baseSchema": type.object,
"withUUID< base extends baseSchema>": {
"...": "base",
uuid: uuid
}
})

const appendUUIDToSchema = <T extends object>(
schema: type.Any<T>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): type.Any<T & { uuid: uuid }> => uuidModule.withUUID(schema as any)

const baseWithUUID = appendUUIDToSchema(type({ abc: "string" }))

//This also generated the correct type:
const withUUID = uuidModule.withUUID({ abd: "string" })
const uuid = type(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
).brand("UID")
type uuid = typeof uuid.infer

const uuidModule = type.module({
"#baseSchema": type.object,
"withUUID< base extends baseSchema>": {
"...": "base",
uuid: uuid
}
})

const appendUUIDToSchema = <T extends object>(
schema: type.Any<T>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): type.Any<T & { uuid: uuid }> => uuidModule.withUUID(schema as any)

const baseWithUUID = appendUUIDToSchema(type({ abc: "string" }))

//This also generated the correct type:
const withUUID = uuidModule.withUUID({ abd: "string" })
Graff
GraffOP3w ago
Thanks for taking a stab at it. I'll check this out later

Did you find this page helpful?