Live data from Hacker News

Adding type safety to object IDs in TypeScript

kravchyk.com

21–30 of 61 posts

Re: Adding type safety to object IDs in TypeScript

#21
post #16

Its bit sad that startsWith doesn't narrow the type, making this pattern slightly less convenient. The GH issue: https://github.com/microsoft/TypeScript/issues/46958

What is the problem with the workaround suggested in the last comment there?

You shouldn't need to write this kind of thing manually for every such type.

Re: Adding type safety to object IDs in TypeScript

#22
post #12
post #5

This is pretty close to type branding (newtype wrapping for the Haskell-inclined), though using template literal types is pretty novel. Normal brands look something like this: type Brand = BaseType & { readonly __brand__: Brand }; type FooId = Brand ; function fooBar(asdf: FooId | 'foobar'): void { } fooBar will only accept the literal string 'foobar' or a true FooId, but not any arbitrary string. FooId would then co…

The easiest way I know of is declare const isMyID: unique symbol; export type MyID = string & { [isMyID]: true };

[deleted]

Re: Adding type safety to object IDs in TypeScript

#23
Type-prefixed IDs are the way to go. For completeness it's worth noting that the first example using the `string | 'currentNode'` type can be slightly improved in cases where you _do_ want autocomplete for known-good values but are still OK with accepting arbitrary string values:

  type Target = 'currentNode' | (string & {});
  const targets: Target[] = [
    'currentNode', // you get autocomplete hints for this!
    'somethingElse', // no autocomplete here, but it typechecks
  ];

Re: Adding type safety to object IDs in TypeScript

#24

Type-prefixed IDs are the way to go. For completeness it's worth noting that the first example using the `string | 'currentNode'` type can be slightly improved in cases where you _do_ want autocomplete for known-good values but are still OK with accepting arbitrary string values: type Target = 'currentNode' | (string & {}); const targets: Target[] = [ 'currentNode', // you get autocomplete hints for this! 'somethingE…

Is (string & {}) semantically intuitive or is it kind of a hack? I don’t understand what it’s supposed to mean.

Re: Adding type safety to object IDs in TypeScript

#26

Type-prefixed IDs are the way to go. For completeness it's worth noting that the first example using the `string | 'currentNode'` type can be slightly improved in cases where you _do_ want autocomplete for known-good values but are still OK with accepting arbitrary string values: type Target = 'currentNode' | (string & {}); const targets: Target[] = [ 'currentNode', // you get autocomplete hints for this! 'somethingE…

Is (string & {}) semantically intuitive or is it kind of a hack? I don’t understand what it’s supposed to mean.

It's a useful hack. In JavaScript, there is no value that's both a string and an object. At runtime, it will just be a string. You can use it like a string and it will type-check, because it's a string plus some extra compile-time baggage, sort of like you subclassed the string type. ('&' is a subtype operation.)

When converting something to this type, it will fail unless you cast it, but it's a compile-time cast. At runtime, there's no conversion.

This is essentially "lying" to the type checker in order to extend it.

Re: Adding type safety to object IDs in TypeScript

#28
post #16

Its bit sad that startsWith doesn't narrow the type, making this pattern slightly less convenient. The GH issue: https://github.com/microsoft/TypeScript/issues/46958

I wish that TS had better type narrowing for the JS standard library, though there's a lot of constraints and design limitations that make it impractical. I ran into a similar issue with the some() method on Array not narrowing types a while back [1]; that issue links to the same sort of issue with filter(), as well as issues where the TS team has discussed what they can and can't do in control flow analysis.

[1] https://github.com/microsoft/TypeScript/issues/40844

Re: Adding type safety to object IDs in TypeScript

#29
post #16

Its bit sad that startsWith doesn't narrow the type, making this pattern slightly less convenient. The GH issue: https://github.com/microsoft/TypeScript/issues/46958

What is the problem with the workaround suggested in the last comment there?

Define a type guard using a "type predicate"[1].

For example:

    type UserId = `user_${string}`;
    type GroupId = `group_${string}`;

    const addUserId = (id: UserId) => {
      // do something
    }

    const processId = (id: string) => {
      if (id.startsWith('user_') {
        // type error here:
        addUserId(id);
      } else if (otherCondition)
        // do other things
      }
    }
Instead you define a function:

    const isUserId = (some: string): some is UserId => some.startsWith('user_');
Now you can use it as follows:

    const processId = (id: string) => {
      if (isUserId(id)) {
        // no more error:
        addUserId(id);
      } else if (otherCondition)
        // do other things
      }
    }
[1]: https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
Post reply on HN