Giter Club home page Giter Club logo

morphic-ts's Introduction

Publish CI

This library addresses the pain of writing and maintaining code for business objects in Typescript

The goal is to increase, in order of importance

  • Correctness
  • Productivity
  • Developer Experience

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

Two minutes intro

Add the batteries package to your repo

yarn add '@morphic-ts/batteries'

Then summon your first Morph

import { summonFor } from '@morphic-ts/batteries/lib/summoner-BASTJ'

const { summon } = summonFor<{}>({}) // Necessary to Specify the config environment (see Config Environment)

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

You now have access 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.lensFromPath // and other optics (optionals, prism, ) from monocle-ts
Person.arb // fast-check
Person.jsonSchema // JsonSchema-ish representation

Note that batteries exposes several summoners:

BASTJ, ESBAST, ESBASTJ, ESBST These naming are derived from the exposed derivations:

  • E(Eq)
  • B(Build)
  • A(Arbitraries from fast-check)
  • S(Show)
  • ST((Strict) ioTs codecs)
  • J(JsonSchema)

Typically, ESBST is best for runtime with lower memory footprint (no fastcheck) and maybe the right default

You can create yours in userland, it's quite easy if you check how it's done in the batteries package

Discriminated, taggedUnion-like models

import { summonFor } from '@morphic-ts/batteries/lib/summoner-ESBASTJ'

const { summon, tagged } = summonFor<{}>({})

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 Vehicle = tagged('type')({ Car, Bicycle })

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

Use tag if your input model does not have a discriminant

in case your existing model do not expose a discriminant, you can use tag to enrich at decode time your model; that enrichment won't be serialized on encoding.

For instance for Car, the solution would be:

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

Which would accept an input with shape

{
  "kind": "electric",
  "power": 90
}

Beware that using this with taggedUnion (or tagged construct) is very inefficient on decoding (trying to decode all Morphs one after the other until it finds a matching one)

Want opaque nominal (instead of structural) inferred types

You may use this pattern

import type { AType, EType } from '@morphic-ts/summoners'

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. A snippet is available to help with that in this repo .vscode folder; we recommend using it extensively.

Configurable

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

Morphic gives you the ability to change any derivation via an optional config.

For example, we may want to specify how fastcheck should generate some strings:

summon(F => F.array(F.string({ FastCheckURI: arb => arb.filter(s => s.length > 2) })))

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

You may provide several Configurations (by indexing by several URI)

Config Environment

Configs are used to override some specific interpreter instances and this is of great value.

But there's a pitfall.

If one wants to use the fastcheck version of a Morph, for testing reasons, but do not want to have fastcheck included in its app, he needs to reinterpret a Morph:

Using derive:

const Person = summonESBST(F =>
  F.interface(
    {
      name: F.string,
      birthDate: F.date
    },
    'Person'
  )
)
const PersonARB = Person.derive(modelFastCheckInterpreter)({})

He can also reinterpret using another summoner.

const AnotherPerson = summonESBASTJ(Person) // Reinterpreter using another summoner (thus generating different type classes)

However, it is often desirable to override fastcheck via a config, for instance, to generate realistic arbitration (here the name with alphabetic letters and a min/max length or a birth date in the past).

Doing so means that one needs to add a config for fastcheck when defining the Person members Morphs, thus, including the fastcheck lib.

But doing so, the lib is imported outside of tests, which is not desirable.

Config Environment solve this issue by offering the ability to give access to an environment for the config of each interpreter.

The motivation is providing ways to abstract over dependencies used in configs.

We can access an environnement to use in a config like so (here IoTsTypes):

summon(F =>
  F.interface({ a: F.string({ IoTsURI: (x, env: IoTsTypes) => env.WM.withMessage(x, () => 'not ok') }) }, 'a')
)

The environnement type has to be specified at definition site.

To prevent really importing the lib to get it's type (the definition is purely a type), we can rely on type imports from typescript.

import type * as fc from 'fast-check'

The Config also infers the correct Env type and only typechecks correctly if summon has been instantiated with correct Env constraints using the summonFor constructor.

Creating the summon requires providing (all) the environments a summoner will be able to support.

export const { summon } = summonFor<{ IoTsURI: IoTsTypes }>({ IoTsURI: { WM } })

I advise you to use a proxy interface to keep this opaque an lean.

export interface AppEnv {
  IoTsURI: IoTsTypes
}

export const { summon } = summonFor<AppEnv>({ IoTsURI: { WM } })

If the underlying Interpreter of summoner does not generate a type-class (e.g. io-ts), then there is no need to feed it at creation time:

export const { summon } = summonFor<{ IoTsURI: IoTsTypes }>({})

This will type-check accordingly.

However the type constraint of the Env will remain in the summoner signature, so that any (re)interpretation from another summoner will thread that constraint; there no compromise on type safety.

The consequence is that any interpreting summoner Env will need to cover all the Env from the source summoner.

This transitive aspect is the necessary condition for correct (re)interpretations.

Define

Summoners now also provide a define member in order to help creating Programs (not Morphs).

Those define are only constrained by the summoner Algebra (Program), not the summoner TypeClasses. And as such, these can freely be combined with any kind of summoner implementing this Algebra.

They also carry their Config Env constraints.

You can directly create a Define instance by using defineFor and specifying the algebra (via a program Uri).

defineFor(ProgramNoUnionURI)(F => F.string)

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

Note: ADT behaviour is available via Summoners; however it is also available without the derivation machinery, hence this paragraph, which also applies to summoned Morphs.

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..

Constructors

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
  },
  _ => 1
)

// match widens the returned type by contructing a union of all branches result types
// Here it is number | 'none'
Vehicle.match(
  {
    Car: ({ seats }) => seats,
    Motorbike: ({ seats }) => seats
  },
  _ => 'none' as const
)

// A stricter variant enforcing homogeneous return type in branches exists
Vehicle.matchStrict({
  Car: ({ seats }) => seats,
  Motorbike: ({ seats }) => seats,
  Bicycle: _ => 1
})

// Which would error in case of heterogeneous return types, like this:
Vehicle.matchStrict({
  Car: ({ seats }) => seats,
  Motorbike: ({ seats }) => seats,
  Bicycle: _ => 'none'
})

Transformers

// You may transform 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
})
// Partial reducers are also supported
Vehicle.createPartialReducer({ totalSeats: 0 })({
  Car: ({ seats }) => ({ totalSeats }) => ({ totalSeats: totalSeats + seats }),
  Motorbike: ({ seats }) => ({ totalSeats }) => ({ totalSeats: totalSeats + seats })
})

Selection, Exclusion, Intersection and Union of ADTs

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

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

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

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

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

Optics (via Monocle)

We support lenses, optional, prism pre-typed helpers

Lens example:

const motorizedSeatLens = Motorized.lensFromProp('seats') // Lens<Car | Motorbike, number>

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

const vehicleSeatOptional = Vehicle.matchOptional<number>({
  Motorbike: motorizedSeatLens.asOptional(),
  Car: motorizedSeatLens.asOptional()
  // undesired cases can be omitted
}) // Optional<Vehicle, number>

Get in touch

The best place to reach out is https://fpchat-invite.herokuapp.com (channel #morphic)

morphic-ts's People

Contributors

anthonyjoeseph avatar brettm12345 avatar dependabot[bot] avatar ericcrosson avatar greenkeeper[bot] avatar jessekelly881 avatar lucas-t5 avatar mbresson avatar mikearnaldi avatar sledorze avatar wmaurer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

morphic-ts's Issues

How to use custom io-ts codecs?

I see in the unit tests I can define the validate function, but how would I build a custom encoder?

For example, if I were implementing the Date type, its input is string, and its output (decode) would be a Date object. The encode would take Date and return a string.

Having additional string literals break ADT

Repro:

import { summon, tagged, AsOpaque } from "@morphic-ts/batteries/lib/summoner-no-union";
import { AType, EType } from "@morphic-ts/batteries/lib/usage/utils";

export const CommonType = summon(F =>
  F.keysOf({
    none: null,
    foo: null,
    bar: null
  })
);

const TypeA_ = summon(F =>
  F.interface(
    {
      event: F.stringLiteral("TypeA"),
      common: CommonType(F)
    },
    "TypeA"
  )
);
export interface TypeA extends AType<typeof TypeA_> {}
export interface TypeAR extends EType<typeof TypeA_> {}

export const TypeA = AsOpaque<TypeAR, TypeA>(TypeA_);

const TypeB_ = summon(F =>
  F.interface(
    {
      event: F.stringLiteral("TypeB"),
      common: CommonType(F)
    },
    "TypeB"
  )
);
export interface TypeB extends AType<typeof TypeB_> {}
export interface TypeBR extends EType<typeof TypeB_> {}

export const TypeB = AsOpaque<TypeBR, TypeB>(TypeB_);

const ADT = tagged("event")({
  TypeA,
  TypeB
});

Bug:
inference

Support for Opaque ADTs

I would like to be able to make my ADT types opaque

This technique works but it is clunky

type Vehicle = ADTType<typeof Vehicle> & { _: Vehicle }
const bike: Vehicle = Vehicle.of.Bicycle({ color: 'blue' }) as Vehicle

I would like an OpaqueADTType type alias and ofOpaque and asOpaque methods

Reduce Opacity boilerplate

Do you want to request a feature or report a bug?
Feature

What is the current behavior?
Preventing Opacity requiers several boilerplate lines

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.

const State_ = summon(F => ...) // being a Morph..

// we need to opacify it like this..
export interface State extends AType<typeof State_> {}
export interface StateRaw extends EType<typeof State_> {}
export const State = AsOpaque<StateRaw, State>(State_)

What is the expected behavior?
Less boilerplate or even no boilerplate

Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?

Compose Optics Across Different Cases ('matchOptional' feature request)

Optics for a shared prop is easy:

import * as Op from 'monocle-ts/lib/Optional'

const seatsOptional: Op.Optional<Vehicle, number> = pipe(
  Op.id<Vehicle>(),
  Op.filter(Vehicle.isAnyOf(['Motorbike', 'Car'])),
  Op.prop('seats'),
);

(Or with monocle 2.2 & earlier):

const seatsOptional: Op.Optional<Vehicle, number> = Vehicle
  .prismFromPredicate(
    Vehicle.isAnyOf(['Motorbike', 'Car'])
  ).composeLens(
    Vehicle
      .select(['Motorbike', 'Car'])
      .lensFromProp('seats')
  )

But what if seats is unevenly nested across cases?

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

Current best solution

Proposed new solution:

import * as L from 'monocle-ts/lib/Lens'

const seatsOptional: Op.Optional<Vehicle, number> = Vehicle.matchOptional<number>({
  Motorbike: pipe(
    L.id<Motorbike>(),
    L.prop('seats'),
    L.asOptional,
  ),
  Car: pipe(
    L.id<Car>(),
    L.prop('attributes'),
    L.prop('seats'),
    L.asOptional,
  ),
  // undesired cases can be omitted
})

Logo and banner

morphic-banner

Hello there !

Not a real big issue but morphic-ts is too amazing to lack a custom logo.

SVG file

What do you think of it ?

Raw SVG Code :

<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<path d="M7.9,53.4C6.5,53.2,5.5,44,8,35.2c1.1-3.8,3.5-11.9,7.7-13C23.2,20,27,43.8,36.4,45.3c13.1,2,19.9-41,33.3-40.5
	c11.6,0.4,20.9,31.1,23.1,52.8c1.8,17.4-0.3,36.9-4,37.6C82.8,96.4,78.4,48.1,64,45.9c-10.3-1.6-15.9,22.5-26.8,19.9
	C25.6,63,21.6,36,15.2,37.2C10.5,38.1,9.7,53.7,7.9,53.4z"/>
</svg>

Use of F.both makes the inner properties become any

Hey!

When I use F.both this way:

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

I expect Person receives the type of:

{
  name: string;
  age?: number;
}

But instead I get the type of:

{
  name: any;
  age: any;
}

I've created the repo to reproduce the issue:
https://github.com/sukazavr/test-both/blob/master/src/data.ts

From where you can see:
image
image

I can see this issue happens with TS 4.0.8 and 4.4.4 versions.
Maybe it's the WebStorm 2021.2.3 bug...

Add an API Algebra

Do you want to request a feature or report a bug?
Feature

What is the current behavior?
We only support describing Models

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.

What is the expected behavior?
We can define APIs, using Models for those and have Interpreters to generate Clients / Servers / JsonSchema / etc..

Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?

Questions..

Hey guys,

so far, I like this project, I think at least..
but so far, it is unclear to me how to constrain the primitives to something interesting, like a min or max length string, a string that looks like an email address, a password with particular requirements, an integer that must fall between two ranges..
I look at io-ts, and think, this type must be representable in morphic-ts - right - but, how?

am I overlooking something - Is there some sample around that satisfies these requirements, or am I just too early to the game?

I suppose a GitHub issue is not the place to ask, but then - where is?
Thank you for your time my fellow FP in TS journeymen.

Is There a Predicate for Intersecting ADTs?

Is there a better way to do this?

const Vehicle = makeADT('type')({
  Car: ofType<Car>(),
  Motorbike: ofType<Motorbike>(),
  Bicycle: ofType<Bicycle>(),
  RollerSkate: ofType(),
})
const NoEmissions = makeADT('type')([
  Walk: ofType(),
  Bicycle: ofType<Bicycle>(),
  RollerSkate: ofType(),
])
const isVehicle = NoEmissions.isAnyOf(['Bicycle', 'RollerSkate'])

I would rather do something like this:

const isVehicle = NoEmissions.isIntersectingADT(Vehicle)

Does something like this already exist? If not, is it even possible?

Support TypeORM

Do you want to request a feature or report a bug?
feature

What is the current behavior?
no ORM mapping

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.
NA

What is the expected behavior?
Being able to define an Entity with all specifics of TypeORM in its config part

Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?
NA

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on Greenkeeper branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet.
We recommend using:

If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please click the 'fix repo' button on account.greenkeeper.io.

Are generics possible? I.e. passing around F.x() as a function parameter.

Hi! I am trying to pass a morphic-type as an argument to another function. I.e. in the code below. However I am running into a type error "HKT<Env, string, string> is not assignable to parameter of type HKT<AppEnv, string, string>". What is the correct way to pass morphic-ts types?

import * as t from "io-ts";
import summon, { AppEnv } from 'lib/summonMorphic'
import { Eq } from 'fp-ts/Eq';
import * as A from 'fp-ts/ReadonlyArray';
import type { HKT } from '@morphic-ts/common/lib/HKT'
import * as s from 'fp-ts/string';

export const isUnique = <T extends unknown>(eq: Eq<T>) => (array: ReadonlyArray<T>) =>
    A.getEq(eq).equals(array, A.uniq(eq)(array));

interface UniqueArrayBrand {
    readonly UniqueArray: unique symbol
}

export const UniqueArray = <T extends unknown> (a: HKT<AppEnv, T, T>) => (eq: Eq<T>) => summon(F => {
    const isUniqueArray = (arr: ReadonlyArray<T>): arr is t.Branded<ReadonlyArray<T>, UniqueArrayBrand> =>
        isUnique(eq)(arr);

    return F.refined(F.array(a), isUniqueArray, "UniqueArrayBrand")
})

export const UniqueStrArray = summon(F => UniqueArray(F.string())(s.Eq)(F));

Use with newtype-ts

Do you want to request a feature or report a bug?
feature

What is the current behavior?
unsupported

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.

What is the expected behavior?
supported

Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?
any

@morphic-ts/adt undefined dependency on tslib

@morphic-ts/adt has a dependency on tslib but does not define it.

Compiling project with @morphic-ts/adt fails with:

Error: Cannot find module 'tslib'
Require stack:
- /<project>/node_modules/@morphic-ts/adt/lib/index.js

You maybe missed it because if you test with other packages installed, which have tslib, you will not see the error. Specifically, this mono-repo has tslib as devDep

@morphic-ts/adt - makeADT purely from type

I'm using ts-adt to create types, including sum-types. I see there are some no-op functions for typng (due to TS limitations) and I realized its a nice lib, but it seems not very convenient to use if you have already types created and only need the runtime part.

Is it possible to create a factory which works purelly on type:

const a = makeADT<SumType, '_type'>()

I understand that there is no information for runtime code there what keys exist, but is it possible to solve it via proxy maybe? The proxy would handle anything, the typing would restrict to domain boundaries. What do you think?

Using Morphic To Implement Domain Driven Backend Architecture

I was wondering if anyone tried to use Morphic to create a granular/domain-driven way of backend architecture.
Something a bit like GraphQL - where you add a resolver per entity (entity may be a field or a larger entity), then the they add to each other to create the final endpoints.
I find this more domain driven and DRY than regular way I usually see REST endpoints implemented.

For example, in my backend (tRPC/Zod, Prisma ORM) - each time I add a new field to the backend - I have to add it separately to:

  1. Zod's schema (Query and mutation)
  2. Prisma's "include" code (Query and mutation)
  3. Prisma's "update" code (mutation)
  4. Sometimes to more places in code that handle authorization and side-effects (emails and etc), and are related to the same schema from before.

In a more domain-driven approach, I would add all of those in one place that describe the field, then some framework (that may use Morphic) - would build the final queries/mutations (while not losing type-safety).

Even if no framework is used, I would like to just declare the schema once - then derive Zod & Prisma & Side-effects code.

Any comment is welcome!

createReducer w/ default type inference is broken

this is legal:

const Vehicle = makeADT('type')({
  Car: ofType<Car>(),
  Motorbike: ofType<Motorbike>(),
  Bicycle: ofType<Bicycle>(),
});
type Vehicle = ADTType<typeof Vehicle>;
Vehicle.createReducer(55)(
  {
    Car: (a: Car) => (b: number) => 43,
    anything: (a: Motorbike) => (b: number) => 43,
    i: (a: Bicycle) => (b: number) => 43,
    want: (a: boolean) => (b: string) => 43,
  },
  (a: Vehicle) => (b: number) => 4,
)

typescript version 4.0.3, @morphic-ts/adt version 2.0.0-alpha.8

Also, the createReducer docs in the README show the wrong default syntax.

Maybe this should be a separate issue, but I'd personally like createReducer to automatically have default behavior of not changing the state, and a separate createReducerStrict that enforces every case to be handled.

Since redux is often practically useless w/o an async middleware, a default behavior of not changing state is common (I think)

I can make PRs if these things sound reasonable

An in-range update of io-ts is breaking the build 🚨


☝️ Important announcement: Greenkeeper will be saying goodbye 👋 and passing the torch to Snyk on June 3rd, 2020! Find out how to migrate to Snyk and more at greenkeeper.io


The devDependency io-ts was updated from 2.1.3 to 2.2.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

io-ts is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • build: There are 1 failures, 0 warnings, and 0 notices.

Release Notes for 2.2.0
  • Experimental
    • add Codec, Decoder, Encoder, Guard, Schema, Schemable, Tree modules (@gcanti)
Commits

The new version differs by 5 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

[Question] How can I define an empty for each type of the ADT

Hi,
First thanks for the library. It is very cool.
I have a question. Is there a way to loop on each element of the ADT and define an empty element?
I started with:

Object.keys(adt.keys).map((t)=>....)

But then I need to pattern match on a string/key, not on an element of the ADT. Any help appreciated.

Union on MorphicADT

Do you want to request a feature or report a bug?
feature

What is the current behavior?
only select available

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.

What is the expected behavior?
should be able to define smaller adts and merge them after

Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?
not relevant

Wrong isAnyOf example in ADT

Hello and thanks for great library.

I get error when I try to implement ADT example example from readme

const isTrafficJamProof = Vehicle.isAnyOf('Motorbike', 'Bicycle') // TS2554: Expected 1 arguments, but got 2

All works normal when array passed.

const isTrafficJamProof = Vehicle.isAnyOf(['Motorbike', 'Bicycle']) // (a: Car | Motorbike | Bicycle) => a is Motorbike | Bicycle

where is the record type?

there is a strMap but I want to create a type from keysOf another type.

something like this

const Language = summon(F => F.keysOf({en: null, es: null, sv:null}));

// there is no record
const TranslationText = summon(F => F.record(Language(F), F.string()));

Feature Request: createReducer without initial state

Hi Stéphane,

thanks for creating such a fantastic library.

I have a small feature request for createReducer. Would you be willing to include a createReducer function without initial state.

Use cases here are Array.prototype.reduce or elmish update functions, which both need an intial state. But this initial state is independant of the reducer.

Regards,
Thomas

The whole fast-check library is included in a production bundle using webpack 4

Do you want to request a feature or report a bug?
feature
What is the current behavior?
Using morphic-ts with webpack 4 leads to a production bundle containing the whole fast-check library.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.

What is the expected behavior?
Only modules actually used by morphic-ts should be included in the bundle.
Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?
0.7.0
Not tested with earlier versions

Make typescript a peer dependency in package.json

Is there a reason that "typescript": "^3.9.3" is a devDependency and not a peerDependency? It causes a confusing error when adding @morphic-ts/x to projects that use an older version of typescript (e.g. the current version of create-react-app, which uses typescript 3.7.3 at the time of writing)

TypeScript error in /Users/anthonygabriele/Developer/repos/examples/my-app/node_modules/@morphic-ts/adt/lib/ctors.d.ts(1,13):
'=' expected.  TS1005

  > 1 | import type { Remove, ExtractUnion } from './utils';
      |             ^
    2 | import type { KeysDefinition, Tagged } from '.';
    3 | /**
    4 |  *  @since 0.0.1

I believe this could be avoided if typescript was a peerDependency instead of a devDependency

Add a Config to hide output in Show (string) useful for sensible information

Do you want to request a feature or report a bug?
Feat: Add optional show config to prevent displaying sensible information

What is the current behavior?
Sensible information would be displayed

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.

What is the expected behavior?
string tagged as sensible would not be displayed

Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?

An in-range update of ts-jest is breaking the build 🚨


☝️ Important announcement: Greenkeeper will be saying goodbye 👋 and passing the torch to Snyk on June 3rd, 2020! Find out how to migrate to Snyk and more at greenkeeper.io


The devDependency ts-jest was updated from 25.3.1 to 25.4.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

ts-jest is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • build: There are 1 failures, 0 warnings, and 0 notices.

Commits

The new version differs by 34 commits.

  • d6472a3 Merge branch 'master' of github.com:kulshekhar/ts-jest
  • 36e1b32 chore(release): 25.4.0
  • 346d362 chore: correct set debug log in Bug/Regression template (#1534)
  • 85f95e8 chore: update package-lock.json
  • 1b374d8 build(deps): bump yargs-parser from 18.1.2 to 18.1.3 (#1531)
  • 8ad8822 chore (BREAKING): require minimum typescript@3.4 (#1530)
  • aa6b74c fix(compiler): make projectReferences work with isolatedModules: true (#1527)
  • 6c0b75d build(deps): bump resolve from 1.15.1 to 1.16.0 (#1528)
  • b3c55d6 chore: upgrade dependencies, correct typings (#1524)
  • 19c32a2 docs: add github action to generate doc (#1522)
  • 232c458 chore(e2e): add nrwl-nx to e2e external test cases (#1519)
  • 406a4cf Merge pull request #1521 from kulshekhar/dependabot/npm_and_yarn/types/react-16.9.34
  • 422699a build(deps-dev): bump @types/react from 16.9.33 to 16.9.34
  • 3abbdca build(deps-dev): bump lint-staged from 10.1.2 to 10.1.3 (#1520)
  • fcac096 Merge pull request #1518 from kulshekhar/dependabot/npm_and_yarn/types/react-16.9.33

There are 34 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Clearer readme/docs

First of all: great job.

This project seems so useful and interesting and I would use it right now, but the readme/documentation it's a bit confusing to me: what is the meaning of BASTJ and ESBASTJ suffixes in the batteries package?

I think that the summoner and config parts should be a little clearer...

Maybe you can also use the gcanti/docs-ts package

Term does not scale well

I have a problem with term
The problem is that when doing

summon(F => F.xyz(...))

F represents all the possible interpreters for which xyz is implemented.
So if xyz is term, then term MUST define the values for all possible interpreters.
I’ve made a branch where I was defining term for Ord.
If I push that, even if you’re not using Ord, you will be forced to define it for each term instance.
As a Morph can always be interpreted by any interpreter implementing its Algebra; an Ord interpreter have to successfully interpret term, so there's no dodging, you must define it.

Make README language more inclusive

First of all, thanks so much for creating this great library. I'm still pretty early in my learning process but I'm already really excited about its potential. 🙏

While reading the README I've noticed you're defaulting to the "he" pronoun. I think it would be great if you could use more inclusive language (e.g. by defaulting to "they" / "them" instead of "he" / "his"). It's a small change but it makes a big difference in making the library more welcoming to more folks 🙌

Fast-check should be loaded only for tests

Do you want to request a feature or report a bug?
Feature
What is the current behavior?
Fast-check is always loaded when bundling an app.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://codesandbox.io/ or similar.

What is the expected behavior?
From my experience, fast-check is only desirable during tests and should not be loaded as part of an app bundle.
Which versions of morphic-ts, and which browser and OS are affected by this issue? Did this work in previous versions of morphic-ts?

Feature Request: createPartialReducer

Creates a reducer with a default behavior of keeping state the same.

Vehicle.createPartialReducer(55)({
  Car: (a: Car) => (b: number) => 43,
})

would be equivalent to:

Vehicle.createReducer(55)(
  {
    Car: (a: Car) => (b: number) => 43,
  },
  (a: Vehicle) => (b: number) => b,
)

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on Greenkeeper branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet.
We recommend using:

If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please click the 'fix repo' button on account.greenkeeper.io.

Adding factory-ts

Hi all! This isn't really an issue. More, I had an idea, and I'm wondering if anyone else is interested in this. I have been using morphic-ts for a while now and just ran across factory-ts. Using factory-ts with morphic-ts is admittedly very easy, but I think it could be easier if it was included in morphic-ts core.

Right now the code looks like this:

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

type Person = t.TypeOf

const personFactory = Factory.Sync.makeFactory({
name: Factory.each(i => name.fullName()),
age: Factory.each(i => 20 + (i % 10))
});

but having PersonM.factory would be more convenient.

https://github.com/willryan/factory.ts

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.