gcanti / newtype-ts

Implementation of newtypes in TypeScript
https://gcanti.github.io/newtype-ts/
MIT License
582 stars 14 forks source link

How to extend / create a sub type of a Newtype? #32

Closed Shinigami92 closed 2 years ago

Shinigami92 commented 2 years ago

📖 Documentation

I have in example the following setup:

// common.ts
import type { Newtype } from 'newtype-ts';

export type ID = Newtype<{ readonly ID: unique symbol }, string>;

export interface Node<Id extends ID> {
  id: Id;
  comment?: string;
  createdAt: string;
}

export interface Edge<NodeId extends ID, NodeClass extends Node<NodeId>> {
  node: NodeClass;
  createdAt: string;
}

---

// cat.ts
import { iso } from 'newtype-ts';
import type { ID, Node } from './common';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CatId extends ID {}

const isoCatId = iso<CatId>();

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PersonId extends ID {}

const isoPersonId = iso<PersonId>();

const cid: CatId = isoCatId.wrap('a');
const lid: PersonId = isoPersonId.wrap('b');

const lid2: PersonId = cid;

export interface Cat extends Node<CatId> {
  name: string;
  ownerId: PersonId;
}

Sadly I don't get any error for const lid2: PersonId = cid;

How do I correctly create a sub type of ID?

DenisFrezzato commented 2 years ago

You could do something like this


interface ID<A>extends Newtype<{ readonly ID: unique symbol; readonly a: A }, string> {}

interface CatID extends ID<'Cat'> {}
interface DogID extends ID<'Dog'> {}

declare const c: CatID
export const d: DogID = c // Type 'CatID' is not assignable to type 'DogID'.

I don't suggest you do to so though, because you can provide a unique symbol to ID, so nothing is preventing to do this

interface HorseID extends ID<'Cat'> {}
Shinigami92 commented 2 years ago

You could do something like this

interface ID<A>extends Newtype<{ readonly ID: unique symbol; readonly a: A }, string> {}

interface CatID extends ID<'Cat'> {}
interface DogID extends ID<'Dog'> {}

declare const c: CatID
export const d: DogID = c // Type 'CatID' is not assignable to type 'DogID'.

I don't suggest you do to so though, because you can provide a unique symbol to ID, so nothing is preventing to do this

interface HorseID extends ID<'Cat'> {}

Thanks, it's working I will ignore your warning, cause these kind of extending newtypes and make it more compile time safe is better then wrong written code by human. It would be the same reason for me to use or not to use TypeScript at all :wink: