Effect-TS / schema

Modeling the schema of data structures as first-class values

Home Page:https://effect.website

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

exactOptionalPropertyTypes: interop with other libs

florianbepunkt opened this issue · comments

I suggest to revisit the need for the exactOptionalPropertyTypes tsconfig flag.

Why is it needed, what is it needed for and what features would not work, if it is not enabled. Maybe it would be beneficial to document this. So users can decide on enabling / disabling it.

The reason is that enabling exactOptionalPropertyTypes brings up a lot of issues with the broader node ecosystem. I just checked a few midde to large-sized projects and I get a lot of compile errors that come from third-party libraries. It looks like they are all solvable with some type casting here and there, but it creates a lot of friction in my opinion.

For example: AWS CDK constructs do not compile, but other major libraries also do not adhere to the exactOptionalPropertyTypes flag. Since effect-ts/schema will likely be used in real projects with external dependencies, I'm worried about the fragmentation this brings: You can't use this lib, if you plan on using XYZ.

Maybe it would be beneficial to document this. So users can decide on enabling / disabling it.

Good idea.

Short anwser: there will be a mismatch between the types and the runtime behavior

Long anwser: I could add the following to the README

Understanding exactOptionalPropertyTypes

The @effect/schema library takes advantage of the exactOptionalPropertyTypes option of tsconfig.json. This option affects how optional properties are typed (to learn more about this option, you can refer to the official TypeScript documentation).

Let's delve into this with an example.

With exactOptionalPropertyTypes Enabled

import * as Schema from "@effect/schema/Schema";

/*
const schema: Schema.Schema<{
    readonly myfield?: string; // the type is strict
}, {
    readonly myfield?: string; // the type is strict
}>
*/
const schema = Schema.struct({
  myfield: Schema.optional(Schema.string.pipe(Schema.nonEmpty()))
});

Schema.decodeSync(schema)({ myfield: undefined }); // Error: Type 'undefined' is not assignable to type 'string'.ts(2379)

Here, notice that the type of myfield is strict (string), which means the type checker will catch any attempt to assign an invalid value (like undefined).

With exactOptionalPropertyTypes Disabled

If, for some reason, you can't enable the exactOptionalPropertyTypes option (perhaps due to conflicts with other third-party libraries), you can still use @effect/schema. However, there will be a mismatch between the types and the runtime behavior:

import * as Schema from "@effect/schema/Schema";

/*
const schema: Schema.Schema<{
    readonly myfield?: string | undefined; // the type is widened to string | undefined
}, {
    readonly myfield?: string | undefined; // the type is widened to string | undefined
}>
*/
const schema = Schema.struct({
  myfield: Schema.optional(Schema.string.pipe(Schema.nonEmpty()))
});

Schema.decodeSync(schema)({ myfield: undefined }); // No type error, but a decoding failure occurs
/*
Error: error(s) found
└─ ["a"]
   └─ Expected string, actual undefined
*/

In this case, the type of myfield is widened to string | undefined, which means the type checker won't catch the invalid value (undefined). However, during decoding, you'll encounter an error, indicating that undefined is not allowed.

@gcanti Thank you for the detailed info. I think it should be added to the docs, so developers can weight the additional type safety against any third-lib issues (if they arise), knowing that they would not sacrifice runtime safety when decoding schemas.