Easier default with morph

Below is an example of a morph where I try to parse a duration using luxon. Unfortunately, because of the defaultablility model, I think I have to do it this way. Is there any simpler way to achieve this outcome where I don't have to provide an ACCESS_TOKEN_TTL but if I do, I parse it as a Duration?
import { ArkErrors, type } from "arktype";
import { Duration } from "luxon";

const serverEnv = type({
SESSION_COOKIE_PASSWORD: "string",
AUTH_TYPE: "'local' | 'workos'",
}).and(
type({
AUTH_TYPE: "'local'",
ACCESS_TOKEN_TTL: "string | null = null",
REFRESH_TOKEN_TTL: "string | null = null",
})
.pipe.try((localAuth) => ({
AUTH_TYPE: localAuth.AUTH_TYPE,
ACCESS_TOKEN_TTL:
localAuth.ACCESS_TOKEN_TTL === null
? Duration.fromObject({ hours: 1 })
: Duration.fromISO(localAuth.ACCESS_TOKEN_TTL),
REFRESH_TOKEN_TTL:
localAuth.REFRESH_TOKEN_TTL === null
? Duration.fromObject({ days: 1 })
: Duration.fromISO(localAuth.REFRESH_TOKEN_TTL),
}))
.or({
AUTH_TYPE: "'workos'",
WORKOS_CLIENT_ID: "string",
WORKOS_API_KEY: "string",
WORKOS_REDIRECT_URI: "string.url.parse",
}),
);
import { ArkErrors, type } from "arktype";
import { Duration } from "luxon";

const serverEnv = type({
SESSION_COOKIE_PASSWORD: "string",
AUTH_TYPE: "'local' | 'workos'",
}).and(
type({
AUTH_TYPE: "'local'",
ACCESS_TOKEN_TTL: "string | null = null",
REFRESH_TOKEN_TTL: "string | null = null",
})
.pipe.try((localAuth) => ({
AUTH_TYPE: localAuth.AUTH_TYPE,
ACCESS_TOKEN_TTL:
localAuth.ACCESS_TOKEN_TTL === null
? Duration.fromObject({ hours: 1 })
: Duration.fromISO(localAuth.ACCESS_TOKEN_TTL),
REFRESH_TOKEN_TTL:
localAuth.REFRESH_TOKEN_TTL === null
? Duration.fromObject({ days: 1 })
: Duration.fromISO(localAuth.REFRESH_TOKEN_TTL),
}))
.or({
AUTH_TYPE: "'workos'",
WORKOS_CLIENT_ID: "string",
WORKOS_API_KEY: "string",
WORKOS_REDIRECT_URI: "string.url.parse",
}),
);
3 Replies
ssalbdivad
ssalbdivad5d ago
Hey assuming what you're looking for is for certain explicit values like null to be defaulted, there are a couple issues tracking it as a feature. Would love help with an implementation, otherwise will address as soon as I can: https://github.com/arktypeio/arktype/issues/1283 https://github.com/arktypeio/arktype/issues/1390
GitHub
Allow null to be treated as not-present for optional fields · Issu...
Request a feature (Related to #1191) I wish to be able to have a type with an optional non-null field that accepts null as being equivalent to not present: import { scope } from "arktype"...
GitHub
Config for values that fallback to a default · Issue #1390 · arkt...
undefined should be implied by exactOptionalPropertyTypes to avoid this unintuitive current behavior: const myObj = type({ key: "number = 5", }); // { key: 5 } const omittedResult = myObj...
Mezuzza
MezuzzaOP5d ago
Thanks for the info. I think what would be preferrable is something like:
const myType = type({
myDuration: type("string")
.pipe
.try(Duration.parse)
.orElse(() =>
Duration.fromObject({minutes: 3})
)
})
const myType = type({
myDuration: type("string")
.pipe
.try(Duration.parse)
.orElse(() =>
Duration.fromObject({minutes: 3})
)
})
The main point being that I'm not thinking of handling null, but rather just saying "Default myDuration to w/e is in orElse". Alternatively, what I would love is maybe something like:
declare module "arktype" {
interface UserDefinedKeywords {
// custom keywords here
luxonDuration: {
parse: Duration.parse,
values: {
// Mapping of custom values to their constructors
"1 hour": () => Duration.fromObject({hours: 1})
}
}
}
}

const myType = type({
myDuration: "luxonDuration = 1 hour"
});
declare module "arktype" {
interface UserDefinedKeywords {
// custom keywords here
luxonDuration: {
parse: Duration.parse,
values: {
// Mapping of custom values to their constructors
"1 hour": () => Duration.fromObject({hours: 1})
}
}
}
}

const myType = type({
myDuration: "luxonDuration = 1 hour"
});
Maybe that's too much magic and I'm not sure if there are limitations to the approach I mentioned, but it would be nice to be able to do custom keywords like that
ssalbdivad
ssalbdivad4d ago
well the logic for default values like this already exists, although there's no way to string-embed non literal values. you can use type("number").default(() => Math.random()) to do this sort of thing, or a default value tuple if you prefer the lack of a builtin concept of catch really seems to be the problem here, and I don't mind the idea of a fallback for a particular transformation. it gets messy when you expand the scope to encompass an arbitrary structural errors but if you want to create an issue to track implementation of a fallback for pipe I'd be interested in that. for now my best recommendation if you are using this pattern a lot would be to build your own external generic abstraction around it to make it easier

Did you find this page helpful?