type Definition = { vals: number[]; }; type ErrorBrand = Readonly<{ [k in Err]: void; }>; // Entry point for our builder. Unlike the value builder // pattern example, we don't need a definition value storing the set of // added numbers because we're going to stash them in a type and check // them at compile time rather than runtime. const makeAThing = () => { // Users can call addNumber as the next step in the builder. // Initialize the Nums typeset to empty set. return { addNumber: addNumber(), }; }; type AddNumberBuilder = { // When the user calls addNumber(x: T), check that T doesn't // already exist in Nums. If it doesnt, continue the builder // and add T to the Nums set. addNumber: ( x: T extends Nums ? ErrorBrand<"Number already declared"> : T ) => AddNumberBuilder; // Finalize the builder, returning a function that asserts that x // exists in our Nums set. done: () => ( x: T extends Nums ? T : ErrorBrand<"Not a legal number"> ) => void; }; // To implement the partial evaluation builder, the addNumber function // returns a function that returns an AddNumberBuilder. const addNumber = () => { return ( x: T extends Nums ? ErrorBrand<"Number already declared"> : T ): AddNumberBuilder => { return { addNumber: addNumber(), done: done(), }; }; }; // The done function returns a function that returns our validation // function. const done = () => { return () => { return ( x: T extends Nums ? T : ErrorBrand<"Not a legal number"> ) => {}; }; }; const myBuilder = makeAThing().addNumber(7).addNumber(5).done(); myBuilder(5); myBuilder(7); myBuilder(8); // Argument of type 'number' is not assignable to parameter of type // 'Readonly<{ "Not a legal number": void; }>'.(2345) const myOther = makeAThing() .addNumber(7) .addNumber(5) .addNumber(5) // Argument of type 'number' is not assignable to parameter of // type 'Readonly<{ "Number already declared": void; }>'.(2345) .done();