npm package: @mizchi/domain-types
Description: type utilities for domain modeling
Latest version: 0.0.11

# @mizchi/domain-types

typescript utilities for domain modeling

## Install

```bash
# node
$ npm add @mizchi/domain-types
# deno
$ deno add jsr:@mizchi/domain-types
```

## Effect Generator

Thin effect-system like generator nspired by https://effect.website/ and https://koka-lang.github.io/koka/doc/index.html

### simple usecase

```ts
import {
  performSync,
  performAsync,
  defineEffect,
  type EffectFor,
} from "@mizchi/domain-types";

// simple sync effect
const val = defineEffect<"val", [], number>("val");
function* program() {
  yield* val();
}
const g = performSync(program(), {
  [val.t]: () => 42,
});
console.log([...g]); // => [{t: "val", args: [], return: 42}]

// simple async effect
async function* asyncProgram() {
  yield* val();
}
const ag = performAsync(asyncProgram(), {
  [val.t]: () => 42,
});
console.log(await Array.fromAsync(ag)); // => [{t: "val", args: [], return: 42}]
```

### compose and return value

```ts
import { defineEffect, performAsync } from "@mizchi/domain-types";

const increment = defineEffect<"increment", [v: number], number>("increment");
const double = defineEffect<"double", [v: number], number>("double");

function* doubleAndAdd1(x: number) {
  const v = yield* double(x);
  return v + 1;
}
async function* program() {
  const v = yield* increment(0);
  console.assert(v === 1);
  const b = yield* doubleAndAdd1(v);
  console.assert(b === 3);
}
const g = performAsync(program(), {
  [increment.t]: (input) => input + 1,
  [double.t]: (input) => input * 2,
});
console.log(await Array.fromAsync(g));
// => [{t: "increment", args: [0], return: 1}, {t: "double", args: [1], return: 2}]
```

### real world example

```ts
import { defineEffect, performSync, performAsync } from "@mizchi/domain-types";

const get = defineEffect<"get", [path: string], string>("get");
const delay = defineEffect<"delay", [ms: number], void>("delay");

function* program() {
  const data = yield* get("/data");
  yield* delay(100);
  return data;
}

// test for sync handlers
const g1 = performSync(program(), {
  [get.t]: (path) => {
    return JSON.stringify({ path, data: "mocked data" });
  },
  [delay.t]: (ms) => {
    // nothing to do here, just simulating delay
  },
});
// run as generator
[...g1];

// production
const g2 = performAsync(program(), {
  [get.t]: async (path) => {
    return JSON.stringify({ path, data: "mocked data" });
  },
  [delay.t]: async (ms) => {
    await new Promise((resolve) => setTimeout(resolve, ms));
  },
});
for await (const step of g2) {
  console.log(step);
}
```

### type first approach

```ts
import {
  defineEffect,
  performAsync,
  performSync,
  type EffectFor,
  type AsyncHandlersFor,
  type SyncHandlersFor,
} from "@mizchi/domain-types";

// define effect keys
const VAL = "val" as const;
const X = "x" as const;
const Y = "y" as const;

// define effect builder
const val = defineEffect<typeof VAL, [], number>(VAL);
const x = defineEffect<typeof X, [arg1: number, arg2: number]>(X);
const y = defineEffect<typeof Y>(Y);
type ProgramEffect =
  | EffectFor<typeof val>
  | EffectFor<typeof x>
  | EffectFor<typeof y>;

// define program as AsyncGenerator
async function* program(): AsyncGenerator<ProgramEffect> {
  yield* val();
}

// define sync handlers
const syncHandlers: SyncHandlersFor<ProgramEffect> = {
  [val.t]: () => 42,
  [x.t]: (arg1, arg2) => {},
  [y.t]: () => {},
};

// includes promise
const handlers: AsyncHandlersFor<ProgramEffect> = {
  [val.t]: async () => 42,
  [x.t]: async (arg1, arg2) => {},
  [y.t]: () => {},
};

const g = performAsync(program(), syncHandlers);
await Array.fromAsync(g);
```

## Result

Simple result types with asserts inspired by https://github.com/supermacro/neverthrow

```ts
import {
  ok,
  err,
  assertErr,
  assertOk,
  type Result,
} from "@mizchi/domain-types";

function getValue(v: number): Result<string, string> {
  if (v > 0) {
    return ok("Value is positive");
  }
  return err("Value must be positive");
}

Deno.test("Result", () => {
  const result: Result<string, string> = getValue(5);
  if (result.ok) {
    const _: string = result.value;
  } else {
    const _: string = result.error;
  }
});

Deno.test("Result: ok case", () => {
  const result = getValue(10);
  // @ts-expect-error
  result.value;
  assertOk(result);
  console.log(result.value);
});

Deno.test("Result: err case", () => {
  const result = getValue(-5);
  // @ts-expect-error
  result.error;
  assertErr(result);
  console.log(result.error);
});
```

## Error

```ts
import { assertErrorInstance } from "@mizchi/domain-types";
Deno.test("assertError", () => {
  class MyError extends Error {}
  const e = new MyError("test error") as any;
  assertErrorInstance(e, MyError);
  const _: MyError = e; // should not throw
});
```

## LICENSE

MIT