{"_id":"ts-brand","_rev":"2978705","name":"ts-brand","description":"Reusable type branding in TypeScript","dist-tags":{"latest":"0.0.2"},"maintainers":[{"name":"kourge","email":"kourge@gmail.com"}],"time":{"modified":"2023-06-21T16:43:52.000Z","created":"2017-10-02T22:30:08.968Z","0.0.2":"2017-10-28T23:32:57.976Z","0.0.1":"2017-10-02T22:30:08.968Z"},"users":{},"author":{"name":"Wil Lee","email":"kourge@gmail.com"},"repository":{"type":"git","url":"git+https://github.com/kourge/ts-brand.git"},"versions":{"0.0.2":{"name":"ts-brand","version":"0.0.2","description":"Reusable type branding in TypeScript","main":"lib/index.js","typings":"lib/index.d.ts","scripts":{"test":"jest","build":"tsc"},"repository":{"type":"git","url":"git+https://github.com/kourge/ts-brand.git"},"keywords":["typescript","opaque","branding","type"],"author":{"name":"Wil Lee","email":"kourge@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/kourge/ts-brand/issues"},"homepage":"https://github.com/kourge/ts-brand#readme","devDependencies":{"@types/jest":"^21.1.1","jest":"^21.2.1","ts-jest":"^21.0.1","typescript":"^2.5.3"},"jest":{"transform":{"^.+\\.tsx?$":"<rootDir>/node_modules/ts-jest/preprocessor.js"},"testRegex":"(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$","moduleFileExtensions":["ts","tsx","js","jsx","json"]},"gitHead":"0184f81fa0d6e0ed07204060a38a3dd6f9a8efc7","_id":"ts-brand@0.0.2","_npmVersion":"5.5.1","_nodeVersion":"8.3.0","_npmUser":{"name":"kourge","email":"kourge@gmail.com"},"dist":{"shasum":"b6cbca6ac94df1050a05844e23944eaeda1738a0","size":4504,"noattachment":false,"key":"/ts-brand/-/ts-brand-0.0.2.tgz","tarball":"http://registry.cnpm.dingdandao.com/ts-brand/download/ts-brand-0.0.2.tgz"},"maintainers":[{"name":"kourge","email":"kourge@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/ts-brand-0.0.2.tgz_1509233577895_0.33010188373737037"},"directories":{},"publish_time":1509233577976,"_hasShrinkwrap":false,"_cnpm_publish_time":1509233577976,"_cnpmcore_publish_time":"2021-12-17T03:44:04.653Z"},"0.0.1":{"name":"ts-brand","version":"0.0.1","description":"Reusable type branding in TypeScript","main":"lib/index.js","scripts":{"test":"jest","build":"tsc"},"repository":{"type":"git","url":"git+https://github.com/kourge/ts-brand.git"},"keywords":["typescript","opaque","branding","type"],"author":{"name":"Wil Lee","email":"kourge@gmail.com"},"license":"MIT","bugs":{"url":"https://github.com/kourge/ts-brand/issues"},"homepage":"https://github.com/kourge/ts-brand#readme","devDependencies":{"@types/jest":"^21.1.1","jest":"^21.2.1","ts-jest":"^21.0.1","typescript":"^2.5.3"},"jest":{"transform":{"^.+\\.tsx?$":"<rootDir>/node_modules/ts-jest/preprocessor.js"},"testRegex":"(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$","moduleFileExtensions":["ts","tsx","js","jsx","json"]},"gitHead":"af731301b160df75b78865fdd7e3d7526209b4dc","_id":"ts-brand@0.0.1","_npmVersion":"5.4.2","_nodeVersion":"8.3.0","_npmUser":{"name":"kourge","email":"kourge@gmail.com"},"dist":{"shasum":"c373a8b68e5aab1109f4dc84399a6877fbfa0cee","size":4293,"noattachment":false,"key":"/ts-brand/-/ts-brand-0.0.1.tgz","tarball":"http://registry.cnpm.dingdandao.com/ts-brand/download/ts-brand-0.0.1.tgz"},"maintainers":[{"name":"kourge","email":"kourge@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/ts-brand-0.0.1.tgz_1506983408828_0.6684220773167908"},"directories":{},"publish_time":1506983408968,"_hasShrinkwrap":false,"_cnpm_publish_time":1506983408968,"_cnpmcore_publish_time":"2021-12-17T03:44:04.910Z"}},"readme":"# ts-brand\n\nWith `ts-brand`, you can achieve\n[nominal typing](\nhttps://basarat.gitbooks.io/typescript/docs/tips/nominalTyping.html\n)\nby leveraging a technique that is called \"type branding\" in the TypeScript\ncommunity. Type branding works by intersecting a base type with a object type\nwith a non-existent property. It is closely related in principal and usage to\nFlow's\n[opaque type aliases](\nhttps://flow.org/en/docs/types/opaque-types/\n).\n\n## Installation\n\n```\nnpm install --save ts-brand\n```\n\n## Motivation and Example\n\nLet's say we have the following API:\n\n```ts\ndeclare function getPost(postId: number): Promise<Post>;\ndeclare function getUser(userId: number): Promise<User>;\n\ninterface User {\n  id: number;\n  name: string;\n}\n\ninterface Post {\n  id: number;\n  authorId: number;\n  title: string;\n  body: string;\n}\n```\n\nWe want to leverage this API to write a function that, given a post's ID, can\nretrieve the user who wrote said post:\n\n```ts\nfunction authorOfPost(postId: number): Promise<User> {\n  return getPost(postId).then(post => getUser(post.id));\n}\n```\n\nDo you spot the bug? We're passing `post.id` to `getUser`, but we should have\npassed `post.authorId`:\n\n```ts\nfunction authorOfPost(postId: number): Promise<User> {\n  return getPost(postId).then(post => getUser(post.authorId));\n}\n```\n\nNominal typing gives us a way to avoid conflating a user ID with a post ID,\neven though they are both numbers:\n\n```ts\nimport {Brand} from 'ts-brand';\n\ndeclare function getPost(postId: Post['id']): Promise<Post>;\ndeclare function getUser(userId: User['id']): Promise<User>;\n\ninterface User {\n  id: Brand<number, 'user'>;\n  name: string;\n}\n\ninterface Post {\n  id: Brand<number, 'post'>;\n  authorId: User['id'];\n  title: string;\n  body: string;\n}\n```\n\nWe have:\n- Defined the ID types in terms of branded types with different branding types\n- Substituted ad-hoc `number` types with a\n[lookup type](\nhttps://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#keyof-and-lookup-types\n), thus designating the interface as the centerpiece\n- Retained the same runtime semantics as the original code\n- Made our original buggy example fail to compile\n\nThere is one more risk left. If someone else were to define a different kind of\n`Post`, and also wrote `Brand<number, 'post'>`, it would still be possible to\nconflate the two accidentally. To solve this, we can take advantage of the fact\nthat TypeScript interfaces can be recursive:\n\n```ts\ninterface User {\n  id: Brand<number, User>;\n  name: string;\n}\n\ninterface Post {\n  id: Brand<number, Post>;\n  authorId: User['id'];\n  title: string;\n  body: string;\n}\n```\n\nBy defining the ID type in terms of the interface surrounding it, we have made\nit such that the only way an ID can be treated like a `Post['id']` is if its\nbranding type matches the structure of `Post` exactly.\n\n## API\n\nThis module exports four type members and two function members.\n\n### `type Brand<Base, Branding, [ReservedName]>`\n\nA `Brand` is a type that takes at minimum two type parameters. Given a base\ntype `Base` and some unique and arbitrary branding type `Branding`, it\nproduces a type based on but distinct from `Base`. The resulting branded type\nis not directly assignable from the base type, and not mutually assignable\nwith another branded type derived from the same base type.\n\nTake care that the branding type is unique. Two branded types that share the\nsame base type and branding type are considered the same type! There are two\nways to avoid this.\n\nThe first way is to supply a third type parameter, `ReservedName`, with a\nstring literal type that is not `__type__`, which is the default.\n\nThe second way is to define a branded type in terms of its surrounding\ninterface, thereby forming a recursive type. This is possible because there\nare no constraints on what the branding type must be. It does not have to be\na string literal type, even though it often is.\n\nExamples:\n```ts\ntype Path = Brand<string, 'path'>;\ntype UserId = Brand<number, 'user'>;\ntype DifferentUserId = Brand<number, 'user', '__kind__'>;\ninterface Post { id: Brand<number, Post> }\n```\n\n### `type AnyBrand`\n\nAn `AnyBrand` is a branded type based on any base type branded with any\nbranding type. By itself it is not useful, but it can act as type constraint\nwhen manipulating branded types in general.\n\n### `type BaseOf<B extends AnyBrand>`\n\n`BaseOf` is a type that takes any branded type `B` and yields its base type.\n\n### `type Brander<B extends AnyBrand>`\n\nA `Brander` is a function that takes a value of some base type and casts that\nvalue to a branded type derived from said base type. It can be thought of as\nthe type of a \"constructor\", in the functional programming sense of the word.\n\nExample:\n```ts\ntype UserId = Brand<number, 'user'>;\n// A Brander<UserId> would take a number and return a UserId\n```\n\n### `function identity<B extends AnyBrand>(underlying: BaseOf<B>): B`\n\nA generic function that, when given some branded type, can take a value with\nthe base type of the branded type, and cast that value to the branded type.\nIt fulfills the contract of a `Brander`.\n\nAt runtime, this function simply returns the value as-is.\n\nExample:\n```ts\ntype UserId = Brand<number, 'user'>;\nconst UserId: Brander<UserId> = identity;\n```\n\n### `function make<B extends AnyBrand>(): Brander<B>`\n\nProduces a `Brander<B>`, given a brand type `B`. This simply returns\n`identity` but relies on type inference to give the return type the correct\ntype.\n\nExample:\n```ts\ntype UserId = Brand<number, 'user'>;\nconst UserId = make<UserId>();\nconst myUserId = UserId(42);\n```\n\n## Complete Example\n\nWe can form a cohesive, intuitive API definition by leveraging several features\nat once, such as namespace merging, type inference, and lookup types:\n\n```ts\nimport {Brand, make} from 'ts-brand';\n\nexport interface User {\n  id: Brand<number, User>;\n  name: string;\n}\n\nexport namespace User {\n  export type Id = User['id'];\n  export const Id = make<Id>();\n}\n\nexport interface Post {\n  id: Brand<number, Post>;\n  authorId: User.Id;\n  title: string;\n  body: string;\n}\n\nexport namespace Post {\n  export type Id = Post['id'];\n  export const Id = make<Id>();\n}\n\ndeclare function getPost(id: Post.Id): Promise<Post>;\ndeclare function getUser(id: User.Id): Promise<User>;\n```\n","_attachments":{},"homepage":"https://github.com/kourge/ts-brand#readme","bugs":{"url":"https://github.com/kourge/ts-brand/issues"},"license":"MIT"}