diff --git a/.changeset/spicy-houses-type.md b/.changeset/spicy-houses-type.md new file mode 100644 index 0000000000..fc16c77fcd --- /dev/null +++ b/.changeset/spicy-houses-type.md @@ -0,0 +1,20 @@ +--- +"@effect/schema": patch +--- + +Refactor the `declare` signature to ensure that the decoding and encoding functions do not utilize context. + +As a result, we can relax the signature of the following functions to accept `R !== never`: + +- `Parser.validateSync` +- `Parser.validateOption` +- `Parser.validateEither` +- `Parser.is` +- `Parser.asserts` +- `Schema.validateSync` +- `Schema.validateOption` +- `Schema.validateEither` +- `Schema.is` +- `Schema.asserts` + +Additionally, the `Class` API no longer requires the optional argument `disableValidation` to be `true` when `R !== never`. diff --git a/packages/schema/README.md b/packages/schema/README.md index 0042ec2f7b..ca52719474 100644 --- a/packages/schema/README.md +++ b/packages/schema/README.md @@ -3186,6 +3186,9 @@ Error: ReadonlySet */ ``` +> [!WARNING] +> The decoding and encoding functions cannot use context (the `R` type parameter) and cannot use async effects. + ## Adding Annotations When you define a new data type, some compilers like `Arbitrary` or `Pretty` may not know how to handle the newly defined data. For instance: diff --git a/packages/schema/dtslint/Context.ts b/packages/schema/dtslint/Context.ts index 733ed270dd..f3dfd01419 100644 --- a/packages/schema/dtslint/Context.ts +++ b/packages/schema/dtslint/Context.ts @@ -45,23 +45,23 @@ Schema.declare( } ) -// $ExpectType Schema Schema.declare( [aContext, bContext], + // @ts-expect-error (_a, _b) => () => Taga.pipe(Effect.flatMap(ParseResult.succeed)), (_a, _b) => () => ParseResult.succeed(1) ) -// $ExpectType Schema Schema.declare( [aContext, bContext], (_a, _b) => () => ParseResult.succeed("a"), + // @ts-expect-error (_a, _b) => () => Tagb.pipe(Effect.flatMap(ParseResult.succeed)) ) -// $ExpectType Schema Schema.declare( [aContext, bContext], + // @ts-expect-error (_a, _b) => () => Taga.pipe(Effect.flatMap(ParseResult.succeed)), (_a, _b) => () => Tagb.pipe(Effect.flatMap(ParseResult.succeed)) ) @@ -346,7 +346,7 @@ export type MyClassContext = Schema.Schema.Context // $ExpectType Schema<{ readonly a: string; }, { readonly a: string; }, "a"> MyClass.struct -// $ExpectType [props: { readonly a: string; }, disableValidation: true] +// $ExpectType [props: { readonly a: string; }, disableValidation?: boolean | undefined] export type MyClassParams = ConstructorParameters // --------------------------------------------- @@ -367,7 +367,7 @@ export type MyClassWithTransformContext = Schema.Schema.Context MyClassWithTransform.struct -// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation: true] +// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation?: boolean | undefined] export type MyClassWithTransformParams = ConstructorParameters // --------------------------------------------- @@ -388,7 +388,7 @@ export type MyClassWithTransformFromContext = Schema.Schema.Context MyClassWithTransformFrom.struct -// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation: true] +// $ExpectType [props: { readonly a: string; readonly b: number; }, disableValidation?: boolean | undefined] export type MyClassWithTransformFromParams = ConstructorParameters // --------------------------------------------- diff --git a/packages/schema/src/Parser.ts b/packages/schema/src/Parser.ts index ebe2d22ca4..80c4661c0a 100644 --- a/packages/schema/src/Parser.ts +++ b/packages/schema/src/Parser.ts @@ -206,8 +206,8 @@ export const decode: ( * @category validation * @since 1.0.0 */ -export const validateSync = ( - schema: Schema.Schema, +export const validateSync = ( + schema: Schema.Schema, options?: AST.ParseOptions ): (u: unknown, overrideOptions?: AST.ParseOptions) => A => getSync(AST.to(schema.ast), true, options) @@ -215,8 +215,8 @@ export const validateSync = ( * @category validation * @since 1.0.0 */ -export const validateOption = ( - schema: Schema.Schema, +export const validateOption = ( + schema: Schema.Schema, options?: AST.ParseOptions ): (u: unknown, overrideOptions?: AST.ParseOptions) => Option.Option => getOption(AST.to(schema.ast), true, options) @@ -224,8 +224,8 @@ export const validateOption = ( * @category validation * @since 1.0.0 */ -export const validateEither = ( - schema: Schema.Schema, +export const validateEither = ( + schema: Schema.Schema, options?: AST.ParseOptions ): (u: unknown, overrideOptions?: AST.ParseOptions) => Either.Either => getEither(AST.to(schema.ast), true, options) @@ -256,7 +256,7 @@ export const validate = ( * @category validation * @since 1.0.0 */ -export const is = (schema: Schema.Schema, options?: AST.ParseOptions) => { +export const is = (schema: Schema.Schema, options?: AST.ParseOptions) => { const parser = goMemo(AST.to(schema.ast), true) return (u: unknown, overrideOptions?: AST.ParseOptions): u is A => Either.isRight(parser(u, { ...mergeParseOptions(options, overrideOptions), isExact: true }) as any) @@ -266,7 +266,7 @@ export const is = (schema: Schema.Schema, options?: AST.Parse * @category validation * @since 1.0.0 */ -export const asserts = (schema: Schema.Schema, options?: AST.ParseOptions) => { +export const asserts = (schema: Schema.Schema, options?: AST.ParseOptions) => { const parser = goMemo(AST.to(schema.ast), true) return (u: unknown, overrideOptions?: AST.ParseOptions): asserts u is A => { const result: Either.Either = parser(u, { diff --git a/packages/schema/src/Schema.ts b/packages/schema/src/Schema.ts index f85f9038db..045c875f6a 100644 --- a/packages/schema/src/Schema.ts +++ b/packages/schema/src/Schema.ts @@ -340,8 +340,8 @@ export const validate = ( * @category validation * @since 1.0.0 */ -export const validateEither = ( - schema: Schema, +export const validateEither = ( + schema: Schema, options?: ParseOptions ) => { const validateEither = Parser.validateEither(schema, options) @@ -484,17 +484,16 @@ const getTemplateLiterals = ( const declareConstructor = < const P extends ReadonlyArray>, - R extends Schema.Context, I, A >( typeParameters: P, decodeUnknown: ( - ...typeParameters: P - ) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect, + ...typeParameters: { readonly [K in keyof P]: Schema, Schema.From, never> } + ) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect, encodeUnknown: ( - ...typeParameters: P - ) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect, + ...typeParameters: { readonly [K in keyof P]: Schema, Schema.From, never> } + ) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect, annotations?: DeclareAnnotations ): Schema> => make(AST.createDeclaration( @@ -539,14 +538,22 @@ export const declare: { is: (input: unknown) => input is A, annotations?: DeclareAnnotations ): Schema - >, R extends Schema.Context, I, A>( + >, I, A>( typeParameters: P, decodeUnknown: ( - ...typeParameters: P - ) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect, + ...typeParameters: { readonly [K in keyof P]: Schema, Schema.From, never> } + ) => ( + input: unknown, + options: ParseOptions, + ast: AST.Declaration + ) => Effect.Effect, encodeUnknown: ( - ...typeParameters: P - ) => (input: unknown, options: ParseOptions, ast: AST.Declaration) => Effect.Effect, + ...typeParameters: { readonly [K in keyof P]: Schema, Schema.From, never> } + ) => ( + input: unknown, + options: ParseOptions, + ast: AST.Declaration + ) => Effect.Effect, annotations?: DeclareAnnotations<{ readonly [K in keyof P]: Schema.To }, A> ): Schema> } = function() { @@ -4673,14 +4680,8 @@ type MissingSelfGeneric = */ export interface Class extends Schema { new( - ...args: [R] extends [never] ? [ - props: Equals extends true ? void | {} : C, - disableValidation?: boolean | undefined - ] : - [ - props: Equals extends true ? void | {} : C, - disableValidation: true - ] + props: Equals extends true ? void | {} : C, + disableValidation?: boolean | undefined ): A & Omit & Proto readonly struct: Schema @@ -4895,7 +4896,7 @@ const makeClass = ( Base: any, additionalProps?: any ): any => { - const validator = Parser.validateSync(selfSchema as any) + const validator = Parser.validateSync(selfSchema) return class extends Base { constructor(props?: any, disableValidation = false) {