Search code examples
typescriptextension-methodszod

Add fluent extension method to 3rd party class


I'm using zod which allows to define some validation rules using fluent function.

Especially, one of the functionis optional which allows to declare a field as optional. Unfortunately, this function does not accept a flag to enable/disable the optional behavior. In my case, the form is built dynamically and the optional flag must be defined at run time.

I can create a small utility method to add this logic :

import { z, ZodType} from 'zod';

const fieldIsRequired = true;

// Method 1 : wrapper / Working
const makeOptional = (input : ZodType, required : boolean): ZodType => required ? input : input.optional();

const entry1 = makeOptional(
    z
    .string()
    .min(10)
    .max(100)
    , !fieldIsRequired
);

This is working well, but I lost the fluent code. Having one rule is acceptable, but adding more rules will leads to a hamburger of function call and parameters

How can I add a new fluent function to the zod type, which come from a 3rd party lib ?

I've tried to implement extension methods, but I failed finding the correct syntax.

Here's what I tried :

import { z, ZodType} from 'zod';

const fieldIsRequired = true;

// Method 2 : extension method / Not Working

declare namespace zod {
    export abstract class ZodType {
        makeOptional: (required: boolean)=> ZodType;
    }
}
// Add syntaxic sugar to the Zod schema
ZodType.prototype.makeOptional = function (required: boolean): ZodType {
    return required ? this : this.optional();
};

const entry2 =   
  z
    .string()
    .min(10)
    .max(100)
    .makeOptional(!fieldIsRequired);

How to fix this ?

Repro : TS playground


Solution

  • You were very close. I think you needed declare module not declare namespace. This works for me:

    import { z, ZodType } from 'zod';
    
    const fieldIsRequired = true;
    
    declare module 'zod' {
        export interface ZodType {
            makeOptional: (required: boolean)=> ZodType;
        }
    }
    // Add syntaxic sugar to the Zod schema
    z.ZodType.prototype.makeOptional = function (required: boolean): ZodType {
        return required ? this : this.optional();
    };
    
    const entry2 =   
      z
        .string()
        .min(10)
        .max(100)
        .makeOptional(!fieldIsRequired);
    

    Playground

    Notes

    I figured this out by looking at the Module Augmentation section of the typescript docs. I almost made a post saying it wasn't possible since the next section says something about merging classes, but then I noticed they were using declare module.