TypeScript Newtypes
newtype-ts provides a robust and performant implementation of newtypes in TypeScript, allowing developers to define distinct types that share the same underlying runtime representation. This helps enforce type safety at compile time, preventing logical errors such as accidentally assigning a `USD` value to a `EUR` variable. The library is currently at version 0.3.5 and is actively maintained, with recent releases focusing on polish and bug fixes. Its key differentiators include a strong reliance on `fp-ts` and `monocle-ts` for functional programming patterns and optics, ensuring no runtime overhead, and offering built-in refinements for common data types like `Integer` or `NonEmptyString`. It supports TypeScript 3.5.1+ and is designed for performance, with newtype operations showing negligible overhead compared to raw type operations.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'wrap') OR Cannot access 'iso' before initialization
cause The `iso` or `prism` function was imported incorrectly or `newtype-ts` was not properly initialized.fixEnsure you are using `import { iso } from 'newtype-ts'` and not a CommonJS `require` call, especially in modern TypeScript projects. Verify that your TypeScript configuration correctly handles ESM imports. -
Argument of type 'number' is not assignable to parameter of type 'Integer'
cause Attempting to pass a primitive type (e.g., `number`, `string`) directly to a function or variable expecting a `Newtype`.fixYou must explicitly wrap the primitive type into the `Newtype` using its associated `iso.wrap()` or `prism.getOption().map(...)` (for refined newtypes). For example, `f(prismInteger.getOption(2).getOrElse(() => throw new Error('not an integer')))`. -
Module not found: Error: Can't resolve 'fp-ts'
cause `fp-ts` or `monocle-ts` peer dependency is missing.fixInstall the required peer dependencies: `npm install fp-ts@^2.0.0 monocle-ts@^2.0.0`.
Warnings
- breaking Version 0.3.0 introduced significant breaking changes by upgrading to `fp-ts@2.x` and `monocle-ts@2.x`. This includes removal of deprecated APIs like `Carrier` type, `over` function, and `unsafeCoerce` function.
- gotcha Since version 0.3.0, `fp-ts` and `monocle-ts` are listed as `peerDependencies` and must be installed manually. Failing to do so will result in runtime errors about missing modules.
- gotcha The library heavily relies on TypeScript's structural typing and `unique symbol` for newtype enforcement. Incorrectly defining the `Newtype` interface or directly coercing types can bypass type safety.
Install
-
npm install newtype-ts -
yarn add newtype-ts -
pnpm add newtype-ts
Imports
- Newtype
const { Newtype } = require('newtype-ts')import { Newtype, iso } from 'newtype-ts' - iso
import * as NewtypeTs from 'newtype-ts'; const iso = NewtypeTs.iso;
import { iso } from 'newtype-ts' - prism
import { prism } from 'newtype-ts' - NonZero
import { NonZero } from 'newtype-ts'import { NonZero, prismNonZero } from 'newtype-ts/lib/NonZero'
Quickstart
import { Newtype, iso } from 'newtype-ts';
interface Email extends Newtype<{ readonly Email: unique symbol }, string> {}
// Create an Iso for Email, allowing wrapping and unwrapping
const isoEmail = iso<Email>();
// Example: a function that strictly requires an Email newtype
declare function sendEmail(to: Email, subject: string, body: string): Promise<boolean>;
// Wrap a string into an Email newtype
const userEmail: Email = isoEmail.wrap('test@example.com');
// Use the newtype in a type-safe context
sendEmail(userEmail, 'Hello', 'This is a test email.').then(success => {
if (success) {
console.log('Email sent successfully!');
} else {
console.error('Failed to send email.');
}
});
// This would cause a static type error:
// sendEmail('wrong@example.com', 'Subject', 'Body');
// Unwrap the email for operations requiring the base type
const emailString: string = isoEmail.unwrap(userEmail);
console.log(`Unwrapped email: ${emailString}`);