Nested morphs not being applied

import { type Out, Type, type } from 'arktype'
// import { Option } from 'ts-result-option'

class Option<T> {
constructor(public value: T) {}
isNone(): boolean {
return false
}
isSome(): boolean {
return true
}
static fromUndefinedOrNull<T>(value: T): Option<T> {
return new Option(value)
}
}

const asOption = <const def, T = type.infer<def>>(
t: type.validate<def>,
): Type<(In: T | undefined | null) => Out<Option<T>>> => {
const T = type(t).or('undefined | null') as unknown as Type<
T | undefined | null
>
return T.pipe((value, ctx) => Option.fromUndefinedOrNull(value))
}

const asOptionWithDefault = <const def, T = type.infer<def>>(
t: type.validate<def>,
): Type<(In: [T | undefined | null, '=', null]) => Out<Option<T>>> => {
return asOption(t).default(null)
}

const x = type({
a: asOptionWithDefault('number'),
})
const y = type({
a: asOptionWithDefault(x),
})
console.log(y.assert({ a: {} }))
// prints:
// {
// a: Option {
// value: {
// a: null,
// },
// isNone: [Function: isNone],
// isSome: [Function: isSome],
// },
// }
// expected:
// {
// a: Option {
// value: {
// a: Option { ... },
// },
// isNone: [Function: isNone],
// isSome: [Function: isSome],
// },
// }

export { asOption, asOptionWithDefault }
import { type Out, Type, type } from 'arktype'
// import { Option } from 'ts-result-option'

class Option<T> {
constructor(public value: T) {}
isNone(): boolean {
return false
}
isSome(): boolean {
return true
}
static fromUndefinedOrNull<T>(value: T): Option<T> {
return new Option(value)
}
}

const asOption = <const def, T = type.infer<def>>(
t: type.validate<def>,
): Type<(In: T | undefined | null) => Out<Option<T>>> => {
const T = type(t).or('undefined | null') as unknown as Type<
T | undefined | null
>
return T.pipe((value, ctx) => Option.fromUndefinedOrNull(value))
}

const asOptionWithDefault = <const def, T = type.infer<def>>(
t: type.validate<def>,
): Type<(In: [T | undefined | null, '=', null]) => Out<Option<T>>> => {
return asOption(t).default(null)
}

const x = type({
a: asOptionWithDefault('number'),
})
const y = type({
a: asOptionWithDefault(x),
})
console.log(y.assert({ a: {} }))
// prints:
// {
// a: Option {
// value: {
// a: null,
// },
// isNone: [Function: isNone],
// isSome: [Function: isSome],
// },
// }
// expected:
// {
// a: Option {
// value: {
// a: Option { ... },
// },
// isNone: [Function: isNone],
// isSome: [Function: isSome],
// },
// }

export { asOption, asOptionWithDefault }
As seen in the code comments, I would expect the nested morph to be applied to the value, maybe I am doing something wrong but can't figure out how to implement the desired behaviour. Thanks for any help ๐Ÿ™‚
9 Replies
ssalbdivad
ssalbdivadโ€ข4w ago
In terms of the types, a lot of this look good. One issue you will run into is that .default returns a definition that can be referenced in an object, not a type itself, so you'll need to update the return type there to reflect that (it should be something like [Type<T | null | undefined>, "=", null]). https://arktype.io/docs/objects#properties-defaultable In terms of the runtime behavior, I'd need a bit of time to delve into this and understand what is going on- it does look like there could be an issue with nested defaults and pipes. Would you be able to log a GitHub issue with this repro?
ArkType
ArkType Docs
TypeScript's 1:1 validator, optimized from editor to runtime
Raqueebuddin Aziz
Raqueebuddin AzizOPโ€ข4w ago
Thanks for the insight ark playground is unable to compile this code, are there any other preferences for a repro or a github repo would work?
ssalbdivad
ssalbdivadโ€ข4w ago
Ahh damn, yeah you can just post a code snippet in the issue honestly it seems pretty self-contained I was looking at the structure of the type which seems right so it seems related to the traversal logic where morphs are applied
Raqueebuddin Aziz
Raqueebuddin AzizOPโ€ข4w ago
Thanks again for looking into this: https://github.com/arktypeio/arktype/issues/1509
GitHub
[Bug] Nested morphs not being applied correctly ยท Issue #1509 ยท a...
Report a bug ๐Ÿ”Ž Search Terms nested morphs, default, pipe ๐Ÿงฉ Context ArkType version: 2.1.22 TypeScript version (5.1+): 5.9.2 Other context you think may be relevant (JS flavor, OS, etc.): System: OS...
Raqueebuddin Aziz
Raqueebuddin AzizOPโ€ข4w ago
in the meantime do you have any workarounds I might be able to use? this only happens with defaults I think
ssalbdivad
ssalbdivadโ€ข4w ago
I'm going to be travelling for a couple weeks but I will try to take a look sometime when I get back.
Raqueebuddin Aziz
Raqueebuddin AzizOPโ€ข4w ago
Thanks, have nice travels โค๏ธ
ssalbdivad
ssalbdivadโ€ข4w ago
The best workaround then would probably be to create a version of the utility that morphs the top-level object to add the values you need instead of relying on default directly if you've found that to be the issue. Maybe a little trickier to get a clean API there but seems like you're pretty good with generics so I have faith :Prayge: Definitely to want to revisit this case to ensure it works though as just being able to create those props directly does feel a lot cleaner
Raqueebuddin Aziz
Raqueebuddin AzizOPโ€ข4w ago
got it, yeah that was my original code, that one requires manually passing in the optional keys though, but in any case, thanks for the help and insights, love the library

Did you find this page helpful?