`ReadonlyType` removes `Codec` type narrowing
See https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbzjAnmApnAyjAhjYAYwBF1CIATdAGjgBU1MBfOAMyghDgCJUMAjCAA9uAbgBQ48gDsAzvACCcALz1GAOgDy-AFZkYACgTi4cSLABcajOoDClMgYY2AqtOARpBgNrP06nChgaQBzAwBKWj91AElpGHQQ9CgIgF1w8JNTOHVScioDAwA3XAAbAFd0cJUAPjgSiv8YCEDgsIys03UAUWl89EKGyurlOqGq6nEmcIlxPkwlVRx8IjyHAB55iFY4BRrZmXk4XCtFxDNoGCsAFgAmOCYDzyOAIRVrfwAldFwKT1KUH4DAoZpJ5nA3ks8AQSGQNlsdi99pJDvB+FZIedzFc4HcHhIgA
A is correct (there should be an error)
@SimenB Hi!
Hmmm, this might be a difficult one to solve for. The current instantiation logic doesn't take into account Codecs applied to types because instantiation may invalidate assumptions for which the Codec was written.
This said, updates would need to be applied to this logic to detect, unwrap and re-wrap the Codec.
https://github.com/sinclairzx81/typebox/blob/main/src/type/engine/instantiate.ts#L339-L368
... but there were some earlier discussions as to whether or not TypeBox should re-apply Codec on Instantiation at the following link (as it relates to Evaluated types)
https://github.com/sinclairzx81/typebox/issues/1333#issuecomment-3311622449
Evaluated Codecs
Codecs throw a bit of a spanner in the works for TB's type evaluation system because type evaluation may remap the underlying type and invalidate logic for which the Codec was written (for example, remapping a String into a Null, but where the Codec callback logic expects a String).
For example, naively remapping TCodec<TString, ...> into TCodec<TNull, ...> (through the instantiation.ts logic linked above) would introduce undefined / unexpected behaviors, which would be difficult to debug (let alone explain!!! :D ).
The following is the concern where the Codec C is remapped via mapped type, and where the mapping invalidates expectations of previously written logic (for Decode)
import Type from "typebox"
const C = Type.Codec(Type.String())
.Decode(value => parseFloat(value)) // <--- expect string here!
.Encode(value => value.toString())
const T = Type.Object({
x: C, // Type.TCodec<Type.TString, number>;
y: C,
z: C
})
// Remap all String properties to Null | (Codec is dropped)
const S = Type.Script({ T }, `{
[K in keyof T]:
T[K] extends string // Problem: TCodec<TNumber, number>
? null //
: never // The mapped type would invalidate a TCodec<TString, ...>, and
// where it isn't safe to re-apply as TCodec<TNull, ...> as this
// would break expectations of the Codec's Decode callback.
}`)
I think part of the problem here is that TB tries to enforce type level constraints for the Codec callbacks (for example, Codec C accepts a Decode parameter type of string which is derived from the Codec schema). If TB was to accept Evaluated Codecs, it would need to give up on static type assurances given by the schema and pass an unknown type Decode as type evaluation "could" swap the type out for anything.
The current "safe" bet is to just not map Codecs and defer figuring out what to do here till later versions.
At this stage, if using Codecs, it's recommended to avoid using them with any computed type (so Partial, Required, Exclude, Extract, Omit, Pick, Index, Mapped, Conditional, etc). TB draws a distinction between Computed and Non-Computed types, where any Non-Computed type should be safe to use with Codec.
Non-Computed (Codec-Safe) https://github.com/sinclairzx81/typebox/tree/main/src/type/types Computed (Codec-Unsafe-Dropped) https://github.com/sinclairzx81/typebox/tree/main/src/type/action
I'm not sure what to do with Evaluated Codecs at this stage. The current implementation is mostly brought over verbatim from 0.34.x. I think given the updates to support TS emulation, Codec's might need to be re-designed (probably more inline with undefined / explicit guarding of Codec values), but will need to defer any significant updates till 1.1 (which is being reviewed atm)
Will add a review tag to this one. Hope this brings some insight! Cheers S
Thanks for the detailed explanation!
Up until this works, would it be possible to get some sort of error that those modifiers don't work properly with codecs? The current errors (the types being wrong) is misleading when my eyes tell me the codec gives one types while the inferred types are different (and it does work runtime 😅). Migrating back to plain Object and Intersect is a perfectly fine workaround, but figuring out where to put those was a bit more of a faff than I expected 🙈
Anyways, thanks for all the work you put into this project!!