npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

morphic-ts

v0.8.0-rc.2

Published

Interpretable Generic Model Definition Encoding

Downloads

700

Readme

Morphic-ts

Business Models just got a lot easier

This library adress the pain of writting and maintaining code for business without any Magic in Typescript

The goal is to increase, in order of importance

  • Correctness
  • Productivity
  • Developper Experience

It is has two side blended into one; generic ADT manipulation AND Generic, customizable and extensible derivations

Two minutes intro

npm install 'morphic-ts'

Or

yarn add 'morphic-ts'

Then

import { summon } from 'morphic-ts/lib/batteries/summoner'

export const Person = summon(F =>
  F.interface(
    {
      name: F.string(),
      age: F.number()
    },
    'Person'
  )
)

// You now have acces to everything to develop around this Type
Person.build // basic build function (enforcing correct type)
Person.show // Show from fp-ts
Person.type // io-ts
Person.strictType // io-ts
Person.eq // Eq from fp-ts
Person.lenseFromPath // and other optics (optionnals, prism, ) from monocle-ts
Person.arb // fast-check
Person.jsonSchema // JsonSchema-ish representation

Discriminated, taggedUnion-like models

import { summon, tagged } from 'morphic-ts/lib/batteries/summoner-no-union'

export const Bicycle = summon(F =>
  F.interface(
    {
      type: F.stringLiteral('Bicycle'),
      color: F.string()
    },
    'Bicycle'
  )
)

export const Car = summon(F =>
  F.interface(
    {
      type: F.stringLiteral('Car'),
      kind: F.keysOf({ electric: null, fuel: null, gaz: null }),
      power: F.number()
    },
    'Car'
  )
)

const Vehicule = tagged('type')({ Car, Bicycle })

// Now you have access to previously depicted derivation + ADT support (ctors, predicates, optics, matchers,reducers, etc.. see `ADT Manipulation` below)

Want opaque nominal (instead of structural) infered types

You may use this pattern

const Car_ = summon(F =>
  F.interface(
    {
      type: F.stringLiteral('Car'),
      kind: F.keysOf({ electric: null, fuel: null, gaz: null }),
      power: F.number()
    },
    'Car'
  )
)
export interface Car extends AType<typeof Car_> {}
export interface CarRaw extends EType<typeof Car_> {}
export const Car = AsOpaque<CarRaw, Car>(Car_)

We're sorry for the boilerplate, this is a current Typescript limitation but in our experience, this is worth the effort.

Configurable

As nice as a General DSL solution to specify your Schema is, there's still some specifics you would like to use.

Morphic provides Interpreter to expose Config for a specific Algebra combinator.

For example, we may want to specify how fastcheck should generate some arrays. We can add an extra parameter to a definition (last position) and use Interpreter specific function (named 'InterpreterConfig', here fastCheckConfig) and it will expose the ability to specify the configuration for this Interpreter and combinator.

summon(F => F.array(F.string(), fastCheckConfig({ minLength: 2, maxLength: 4 })))

Note: this is type guided and type safe, it's not an any in disguise

You may provide several Configuration

summon(F => F.array(F.string(), { ...fastCheckConfig({ minLength: 2, maxLength: 4 }), ...showConfig(...)} ))

How it works

When you specify a Schema, you're using an API (eDSL implemented using final tagless). This API defines a Program (your schema) using an Algebra (the combinators exposed to do so).

This Algebra you're using is actually composed of several Algebras merged together, some defines how to encode a boolean, some others a strMap (string Map), etc..

Then for each possible derivation there's possibly an Ìnterpreter` implementing some Algebras. What Morphic does is orchestrating this machinery for you

This pattern has some interesting properties; it is extensible in both the Algebra and the Interpreter

Generic Derivation

Specify the structure of your Schema only once and automatically has access various supported implementations

Participate into expanding implementation and/or schema capabilities

Example of implementations:

  • Structural equality (via Eq from fp-ts)
  • Validators (io-ts)
  • Schema generation (JsonSchema flavor)
  • Pretty print of data structure (Show from fp-ts)
  • Generators (FastCheck)
  • ...
  • TypeOrm (WIP)

This is not an exhaustive list, because the design of Morphic enables to define more and more Interpreters for your Schemas (composed of Algebras).

ADT Manipulation

ADT stands for Algebraic Data Types, this may be strange, just think about it as the pattern to represent your casual Business objects

ADT manipulation support maybe be used without relying on full Morphic objects.

The feature can be used standalone via the makeADT function with support for:

  • Smart Ctors
  • Predicates
  • Optics (Arcane name for libraries helping manipulate immutable data structures in FP)
  • Matchers
  • Reducers
  • Creation of new ADTs via selection, exclusion, intersection or union of existing ADTs

Ad'hoc usage via makeADT (Morphic's summon already does that for you):

Let's define some Types

interface Bicycle {
  type: 'Bicycle'
  color: string
}

interface Motorbike {
  type: 'Motorbike'
  seats: number
}

interface Car {
  type: 'Car'
  kind: 'electric' | 'fuel' | 'gaz'
  power: number
  seats: number
}

Then build an ADT from them for PROFIT!

// ADT<Car | Motorbike | Bicycle, "type">
const Vehicle = makeADT('type')({
  Car: ofType<Car>(),
  Motorbike: ofType<Motorbike>(),
  Bicycle: ofType<Bicycle>()
})

Then you have..

Constuctors

Vehicle.of.Bicycle({ color: 'red' }) // type is Car | Motorbike | Bicycle

// `as` offer a narrowed type
Vehicle.as.Car({ kind: 'electric', power: 2, seats: 4 }) // type is Car

Predicates

// Predicate and Refinements
Vehicle.is.Bicycle // (a: Car | Motorbike | Bicycle) => a is Bicycle

// Exist also for several Types
const isTrafficJamProof = Vehicle.isAnyOf('Motorbike', 'Bicycle') // (a: Car | Motorbike | Bicycle) => a is Motorbike | Bicycle

Matchers

const nbSeats = Vehicle.match({
  Car: ({ seats }) => seats,
  Motorbike: ({ seats }) => seats,
  Bicycle: _ => 1
})

// Alternatively you may use `default`
Vehicle.match({
  Car: ({ seats }) => seats,
  Motorbike: ({ seats }) => seats,
  default: _ => 1
})

// Use matchWiden, then the resturn type will be unified from each results
// Here it would be number | 'none'
Vehicle.matchWiden({
  Car: ({ seats }) => seats,
  Motorbike: ({ seats }) => seats,
  default: _ => 'none' as const
})

Transformers

// You may tranform matching a subset
Vehicle.transform({
  Car: car => ({ ...car, seats: car.seats + 1 })
})

Reducers

// Creating a reducer is made as easy as specifying a type
Vehicle.createReducer({ totalSeats: 0 })({
  Car: ({ seats }) => ({ totalSeats }) => ({ totalSeats: totalSeats + seats }),
  Motorbike: ({ seats }) => ({ totalSeats }) => ({ totalSeats: totalSeats + seats }),
  default: _ => identity
})

Selection, Exclusion, Intersection and Union of ADTs

This will help getting unique advantage of Typescript ability to refine Unions

const Motorised = Vehicle.select('Car', 'Motorbike') // ADT<Car | Motorbike, "type">

const TrafficJamProof = Vehicle.exclude('Car') // ADT<Motorbike | Bicycle, "type">

const Faster = intersectADT(Motorised, TrafficJamProof) // ADT<Motorbike, "type">

const Faster = intersectADT(Motorised, TrafficJamProof) // ADT<Motorbike, "type">

const ManyChoice = unionADT(Motorised, TrafficJamProof) // ADT<Car  | Motorbike | Bicycle, "type">

Optics (via Monocle)

We support lenses, optionel, prism pretyped helpers

Lense example:

const seatLense = Motorised.lenseFromProp('seats') // Lens<Car | Motorbike, number>

const incSeat = seatLense.modify(increment) // (s: Car | Motorbike) => Car | Motorbike

Generic functions - Interepreters constraints [Advanced usage]

When one want to make a generic code, available to several interpreters, he can constraint the Interpreters URI by using some type level function


export function foo<
    E,
    A,
    ProgURI extends ProgramURI,
    InterpURI extends SelectInterpURI<E, A, { type: t.Type<A, E> }> // This will only accepts URIs of Interpreters defining a member `type` of type t.Type<A, E> (io-ts)
  >(
    S: MorphADT<E, A, Tag, ProgURI, InterpURI>
  ) {

    // Here S will completes all the available members of known Interpreters with the acceptable Interpreters URIs, so matching the constraint above
    ...
  }

Roadmap

  • Switch to Monorepo
  • Interpreter for persistency (TypeORM)
  • Implement Algebra for APIs

Disclaimer

THIS LIBRARY IS USED INTO TWO PROFESSIONAL PROJECTS IN DEVELPOMENT AT THE MOMENT

BUT BEWARE, THIS REPO IS A POC (WORK-IN-PROGRESS) THE API IS UNLIKELY TO CHANGE TRENDEMOUSLY BUT YOU MAY BE SAFER TO CONSIDER IT UNSTABLE AND USE AT YOUR OWN RISK