Search code examples
typescript

Without type assertions, can I give an interface's function a private parameter?


Without type assertions, can I give an interface's function a private parameter? Here's what I mean by "private":

  1. This parameter can be passed within the current module.
  2. External to the current module, that parameter cannot be used.

This code demonstrates what I'd like to be able to do:

export interface PublicInterface {
  publicFunction1: (foo: string) => string;
  publicFunction2: () => string;
}

export const implementation: PublicInterface = {
  publicFunction1: function (foo: string | number): string {
    return "abc";
  },
  publicFunction2: function (): string {
    return implementation.publicFunction1(123); // I'd like this to compile without a type assertion
  },
};

// another-file.ts
implementation.publicFunction1("xyz");
implementation.publicFunction1(123); // I'd like this to continue to error

I tried to accomplish this with function overloads instead of an interface, but I got an error when I did not export every overload: "Overload signatures must all be exported or non-exported." This leads me to believe this isn't possible without a type assertion, but I thought I would ask anyway. If there is a way to do this I'd like to learn it.


Solution

  • If export const x: X = ⋯ works, you can split that assignment into two pieces:

    • define the internal thing with all the functionality you need internally, like const internalX = ⋯; and
    • assign the internal thing to the exported thing, whose type is annotated with the public-facing annotation, like export const x: X = internalX;

    Like this:

    export interface PublicInterface {
      publicFunction1: (foo: string) => string;
      publicFunction2: () => string;
    }
    
    // not exported, narrower type
    const internalImplementation = {
      publicFunction1: function (foo: string | number): string {
        return "abc";
      },
      publicFunction2: function (): string {
        return internalImplementation.publicFunction1(123); // okay
      },
    };
    
    // exported, wider type
    export const implementation: PublicInterface = internalImplementation;
    

    So the implementation of publicFunction2 works because it's using internalImplementation. And when you import, you'll only have access to the external view:

    import { implementation } from "./myModule";
    implementation.publicFunction1("xyz"); // okay
    implementation.publicFunction1(123); // error, number not assignable to string
    

    Code sandbox link to code