chore: migrate monorepo to deno 2 #3

Merged
piarrot merged 12 commits from chore-migrate-to-deno2 into main 2024-10-16 13:13:56 -03:00
216 changed files with 969 additions and 6595 deletions

3
.gitattributes vendored
View File

@ -1,4 +1 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
* text=auto eol=lf * text=auto eol=lf

8
.gitignore vendored
View File

@ -1,11 +1,3 @@
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
node_modules
.env .env
dist dist
coverage coverage

4
.hooks/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/hook.sh"
deno task check

View File

@ -1,9 +0,0 @@
# .husky/pre-commit
yarn lint-staged
branch="$(git rev-parse --abbrev-ref HEAD)"
if [ "$branch" = "main" ]; then
echo "You can't commit directly to main branch"
exit 1
fi

View File

@ -1,4 +0,0 @@
{
"*": ["yarn prettier -u --write"],
"*.ts": ["yarn eslint --fix"]
}

View File

@ -1,4 +0,0 @@
dist
coverage
.yarn/**/*
yarn.lock

View File

@ -1,3 +0,0 @@
{
"tabWidth": 2
}

View File

@ -2,13 +2,11 @@
"recommendations": [ "recommendations": [
"bierner.github-markdown-preview", "bierner.github-markdown-preview",
"bradlc.vscode-tailwindcss", "bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"streetsidesoftware.code-spell-checker", "streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-spanish", "streetsidesoftware.code-spell-checker-spanish",
"usernamehw.errorlens", "usernamehw.errorlens",
"bourhaouta.tailwindshades", "bourhaouta.tailwindshades",
"austenc.tailwind-docs", "austenc.tailwind-docs",
"vitest.explorer" "denoland.vscode-deno"
] ]
} }

View File

@ -17,5 +17,8 @@
}, },
"typescript.preferences.importModuleSpecifierEnding": "js", "typescript.preferences.importModuleSpecifierEnding": "js",
"cSpell.words": ["autodocs", "Syntropy"], "cSpell.words": ["autodocs", "Syntropy"],
"typescript.preferences.autoImportFileExcludePatterns": ["**/chai/**"] "typescript.preferences.autoImportFileExcludePatterns": ["**/chai/**"],
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
} }

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.5.0.cjs

37
deno.jsonc Normal file
View File

@ -0,0 +1,37 @@
{
"tasks": {
"test": "deno test --allow-all --unstable-ffi",
"test:dev": "deno test --allow-all --unstable-ffi --watch",
"check": "deno fmt && deno lint --fix && deno check **/*.ts && deno task test",
"hook": "deno run --allow-read --allow-run --allow-write https://deno.land/x/deno_hooks@0.1.1/mod.ts"
},
"workspace": [
"packages/fabric/core",
"packages/fabric/domain",
"packages/fabric/sqlite-store",
"packages/fabric/testing",
"packages/templates/domain",
"packages/templates/lib"
],
"imports": {
"@db/sqlite": "jsr:@db/sqlite@^0.12.0",
"@quentinadam/decimal": "jsr:@quentinadam/decimal@^0.1.6",
"@std/expect": "jsr:@std/expect@^1.0.5",
"@std/testing": "jsr:@std/testing@^1.0.3",
"expect-type": "npm:expect-type@^1.1.0"
},
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUncheckedIndexedAccess": true
},
"lint": {
"include": ["src/"],
"rules": {
"tags": ["recommended"],
"exclude": ["no-namespace"]
}
}
}

138
deno.lock Normal file
View File

@ -0,0 +1,138 @@
{
"version": "4",
"specifiers": {
"jsr:@db/sqlite@*": "0.12.0",
"jsr:@db/sqlite@0.12": "0.12.0",
"jsr:@denosaurs/plug@1": "1.0.6",
"jsr:@quentinadam/assert@~0.1.7": "0.1.7",
"jsr:@quentinadam/decimal@*": "0.1.6",
"jsr:@quentinadam/decimal@~0.1.6": "0.1.6",
"jsr:@std/assert@0.217": "0.217.0",
"jsr:@std/assert@0.221": "0.221.0",
"jsr:@std/assert@^1.0.6": "1.0.6",
"jsr:@std/data-structures@^1.0.4": "1.0.4",
"jsr:@std/encoding@0.221": "0.221.0",
"jsr:@std/expect@*": "1.0.5",
"jsr:@std/expect@^1.0.5": "1.0.5",
"jsr:@std/fmt@0.221": "0.221.0",
"jsr:@std/fs@0.221": "0.221.0",
"jsr:@std/fs@^1.0.4": "1.0.4",
"jsr:@std/internal@^1.0.4": "1.0.4",
"jsr:@std/path@0.217": "0.217.0",
"jsr:@std/path@0.221": "0.221.0",
"jsr:@std/path@^1.0.6": "1.0.6",
"jsr:@std/testing@^1.0.3": "1.0.3",
"npm:expect-type@*": "1.1.0",
"npm:expect-type@^1.1.0": "1.1.0"
},
"jsr": {
"@db/sqlite@0.12.0": {
"integrity": "dd1ef7f621ad50fc1e073a1c3609c4470bd51edc0994139c5bf9851de7a6d85f",
"dependencies": [
"jsr:@denosaurs/plug",
"jsr:@std/path@0.217"
]
},
"@denosaurs/plug@1.0.6": {
"integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7",
"dependencies": [
"jsr:@std/encoding",
"jsr:@std/fmt",
"jsr:@std/fs@0.221",
"jsr:@std/path@0.221"
]
},
"@quentinadam/assert@0.1.7": {
"integrity": "0246fb7fd3aa7b286535db40feefdafc588272d3f287b4eb995c96263ab6dfca"
},
"@quentinadam/decimal@0.1.6": {
"integrity": "fd3e2684614355db8a6ef94a839cbe115f040e8dcc843a17a0c5afa23e3db408",
"dependencies": [
"jsr:@quentinadam/assert"
]
},
"@std/assert@0.217.0": {
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
},
"@std/assert@0.221.0": {
"integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a"
},
"@std/assert@1.0.6": {
"integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207",
"dependencies": [
"jsr:@std/internal"
]
},
"@std/data-structures@1.0.4": {
"integrity": "fa0e20c11eb9ba673417450915c750a0001405a784e2a4e0c3725031681684a0"
},
"@std/encoding@0.221.0": {
"integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45"
},
"@std/expect@1.0.5": {
"integrity": "8c7ac797e2ffe57becc6399c0f2fd06230cb9ef124d45229c6e592c563824af1",
"dependencies": [
"jsr:@std/assert@^1.0.6",
"jsr:@std/internal"
]
},
"@std/fmt@0.221.0": {
"integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a"
},
"@std/fs@0.221.0": {
"integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286",
"dependencies": [
"jsr:@std/assert@0.221",
"jsr:@std/path@0.221"
]
},
"@std/fs@1.0.4": {
"integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
"dependencies": [
"jsr:@std/path@^1.0.6"
]
},
"@std/internal@1.0.4": {
"integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422"
},
"@std/path@0.217.0": {
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
"dependencies": [
"jsr:@std/assert@0.217"
]
},
"@std/path@0.221.0": {
"integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095",
"dependencies": [
"jsr:@std/assert@0.221"
]
},
"@std/path@1.0.6": {
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
},
"@std/testing@1.0.3": {
"integrity": "f98c2bee53860a5916727d7e7d3abe920dd6f9edace022e2d059f00d05c2cf42",
"dependencies": [
"jsr:@std/assert@^1.0.6",
"jsr:@std/data-structures",
"jsr:@std/fs@^1.0.4",
"jsr:@std/internal",
"jsr:@std/path@^1.0.6"
]
}
},
"npm": {
"expect-type@1.1.0": {
"integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA=="
}
},
"workspace": {
"dependencies": [
"jsr:@db/sqlite@0.12",
"jsr:@quentinadam/decimal@~0.1.6",
"jsr:@std/expect@^1.0.5",
"jsr:@std/testing@^1.0.3",
"npm:expect-type@^1.1.0"
]
}
}

View File

@ -1,15 +0,0 @@
// @ts-check
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
{
rules: {
"@typescript-eslint/no-namespace": "off",
},
},
);

View File

@ -1,33 +0,0 @@
{
"name": "ulthar-framework",
"packageManager": "yarn@4.5.0",
"version": "1.0.0",
"private": true,
"type": "module",
"workspaces": [
"packages/**/*",
"apps/**/*"
],
"devDependencies": {
"@eslint/js": "^9.12.0",
"@types/eslint": "^9.6.1",
"@types/eslint__js": "^8.42.3",
"cross-env": "^7.0.3",
"eslint": "^9.12.0",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"tsx": "^4.19.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.8.1",
"zx": "^8.1.9"
},
"scripts": {
"lint": "eslint . --fix --report-unused-disable-directives",
"format": "prettier --write .",
"test": "yarn workspaces foreach -vvpA run test --run --clearScreen false",
"build": "yarn workspaces foreach -vvpA --topological run build",
"add-package": "tsx ./scripts/add-package.ts",
"postinstall": "husky"
}
}

View File

@ -1,10 +1,8 @@
import { describe, expectTypeOf, test } from "vitest"; import { describe, expectTypeOf, test } from "@fabric/testing";
import type { ArrayElement } from "./array-element.ts";
describe("ArrayElement utils", () => { describe("ArrayElement", () => {
test("Given an array, it should return the element type of the array", () => { test("Given an array, it should return the element type of the array", () => {
type ArrayElement<T extends readonly unknown[]> =
T extends readonly (infer U)[] ? U : never;
type result = ArrayElement<["a", "b", "c"]>; type result = ArrayElement<["a", "b", "c"]>;
expectTypeOf<result>().toEqualTypeOf<"a" | "b" | "c">(); expectTypeOf<result>().toEqualTypeOf<"a" | "b" | "c">();

View File

@ -0,0 +1,17 @@
/**
* Get the element type of an array.
*/
export type ArrayElement<T extends readonly unknown[]> = T extends
readonly (infer U)[] ? U : never;
/**
* Get the first element type of a tuple.
*/
export type TupleFirstElement<T extends readonly unknown[]> = T extends
readonly [infer U, ...unknown[]] ? U : never;
/**
* Get the LAST element type of a tuple.
*/
export type TupleLastElement<T extends readonly unknown[]> = T extends
readonly [...unknown[], infer U] ? U : never;

View File

@ -0,0 +1 @@
export * from "./array-element.ts";

View File

@ -0,0 +1,6 @@
{
"name": "@fabric/core",
"exports": {
".": "./index.ts"
}
}

View File

@ -0,0 +1,3 @@
export * from "./is-error.ts";
export * from "./tagged-error.ts";
export * from "./unexpected-error.ts";

View File

@ -0,0 +1,16 @@
import { describe, expect, expectTypeOf, test } from "@fabric/testing";
import { isError } from "./is-error.ts";
import { UnexpectedError } from "./unexpected-error.ts";
describe("is-error", () => {
test("Given a value that is an error, it should return true", () => {
const error = new UnexpectedError();
expect(isError(error)).toBe(true);
//After a check, typescript should be able to infer the type
if (isError(error)) {
expectTypeOf(error).toEqualTypeOf<UnexpectedError>();
}
});
});

View File

@ -1,4 +1,4 @@
import { TaggedError } from "./tagged-error.js"; import { TaggedError } from "./tagged-error.ts";
/** /**
* Indicates if a value is an error. * Indicates if a value is an error.

View File

@ -1,12 +1,10 @@
import { TaggedVariant, VariantTag } from "../variant/index.js"; import { type TaggedVariant, VariantTag } from "../variant/index.ts";
/** /**
* A TaggedError is a tagged variant with an error message. * A TaggedError is a tagged variant with an error message.
*/ */
export abstract class TaggedError<Tag extends string = string> export abstract class TaggedError<Tag extends string = string> extends Error
extends Error implements TaggedVariant<Tag> {
implements TaggedVariant<Tag>
{
readonly [VariantTag]: Tag; readonly [VariantTag]: Tag;
constructor(tag: Tag, message?: string) { constructor(tag: Tag, message?: string) {

View File

@ -1,4 +1,4 @@
import { TaggedError } from "./tagged-error.js"; import { TaggedError } from "./tagged-error.ts";
/** /**
* `UnexpectedError` represents any type of unexpected error. * `UnexpectedError` represents any type of unexpected error.

View File

@ -0,0 +1,9 @@
export * from "./array/index.ts";
export * from "./error/index.ts";
export * from "./record/index.ts";
export * from "./result/index.ts";
export * from "./run/index.ts";
export * from "./time/index.ts";
export * from "./types/index.ts";
export * from "./utils/index.ts";
export * from "./variant/index.ts";

View File

@ -1,25 +0,0 @@
{
"name": "@fabric/core",
"private": true,
"sideEffects": false,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.js"
},
"files": [
"dist"
],
"packageManager": "yarn@4.1.1",
"devDependencies": {
"@vitest/coverage-v8": "^2.1.2",
"typescript": "^5.6.3",
"vitest": "^2.1.2"
},
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage",
"build": "tsc -p tsconfig.build.json"
}
}

View File

@ -0,0 +1,2 @@
export * from "./is-record-empty.ts";
export * from "./is-record.ts";

View File

@ -0,0 +1,19 @@
import { describe, expect, test } from "@fabric/testing";
import { isRecordEmpty } from "./is-record-empty.ts";
describe("Record - Is Record Empty", () => {
test("Given an empty record, it should return true", () => {
const result = isRecordEmpty({});
expect(result).toBe(true);
});
test("Given a record with a single key, it should return false", () => {
const result = isRecordEmpty({ key: "value" });
expect(result).toBe(false);
});
test("Given a record with multiple keys, it should return false", () => {
const result = isRecordEmpty({ key1: "value1", key2: "value2" });
expect(result).toBe(false);
});
});

View File

@ -1,23 +1,23 @@
import { describe, expect, it } from "vitest"; import { describe, expect, test } from "@fabric/testing";
import { isRecord } from "./is-record.js"; import { isRecord } from "./is-record.ts";
describe("isRecord", () => { describe("isRecord", () => {
it("should return true for an object", () => { test("Given an empty object, it should return true", () => {
const obj = { name: "John", age: 30 }; const obj = { name: "John", age: 30 };
expect(isRecord(obj)).toBe(true); expect(isRecord(obj)).toBe(true);
}); });
it("should return false for an array", () => { test("Given an array, it should return false", () => {
const arr = [1, 2, 3]; const arr = [1, 2, 3];
expect(isRecord(arr)).toBe(false); expect(isRecord(arr)).toBe(false);
}); });
it("should return false for null", () => { test("Given a number, it should return false", () => {
const value = null; const value = null;
expect(isRecord(value)).toBe(false); expect(isRecord(value)).toBe(false);
}); });
it("should return false for a string", () => { test("Given a string, it should return false", () => {
const value = "Hello"; const value = "Hello";
expect(isRecord(value)).toBe(false); expect(isRecord(value)).toBe(false);
}); });

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-namespace no-explicit-any
import { TaggedError } from "../error/tagged-error.js"; import type { TaggedError } from "../error/tagged-error.ts";
import { UnexpectedError } from "../error/unexpected-error.js"; import { UnexpectedError } from "../error/unexpected-error.ts";
import { MaybePromise } from "../types/maybe-promise.js"; import type { MaybePromise } from "../types/maybe-promise.ts";
import { Result } from "./result.js"; import { Result } from "./result.ts";
/** /**
* An AsyncResult represents the result of an asynchronous operation that can * An AsyncResult represents the result of an asynchronous operation that can
@ -25,9 +25,7 @@ export namespace AsyncResult {
} }
} }
export async function from<T>( export function from<T>(fn: () => MaybePromise<T>): AsyncResult<T, never> {
fn: () => MaybePromise<T>,
): AsyncResult<T, never> {
return tryFrom(fn, (error) => new UnexpectedError(error) as never); return tryFrom(fn, (error) => new UnexpectedError(error) as never);
} }
} }

View File

@ -0,0 +1,2 @@
export * from "./async-result.ts";
export * from "./result.ts";

View File

@ -1,10 +1,10 @@
import { describe, expect, expectTypeOf, it, vitest } from "vitest"; import { describe, expect, expectTypeOf, fn, test } from "@fabric/testing";
import { UnexpectedError } from "../error/unexpected-error.js"; import { UnexpectedError } from "../error/unexpected-error.ts";
import { Result } from "./result.js"; import { Result } from "./result.ts";
describe("Result", () => { describe("Result", () => {
describe("isOk", () => { describe("isOk", () => {
it("should return true if the result is ok", () => { test("should return true if the result is ok", () => {
const result = Result.succeedWith(1) as Result<number, UnexpectedError>; const result = Result.succeedWith(1) as Result<number, UnexpectedError>;
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
@ -18,7 +18,7 @@ describe("Result", () => {
}); });
describe("isError", () => { describe("isError", () => {
it("should return true if the result is an error", () => { test("should return true if the result is an error", () => {
const result = Result.failWith(new UnexpectedError()) as Result< const result = Result.failWith(new UnexpectedError()) as Result<
number, number,
UnexpectedError UnexpectedError
@ -35,7 +35,7 @@ describe("Result", () => {
}); });
describe("Map", () => { describe("Map", () => {
it("should return the result of the last function", () => { test("should return the result of the last function", () => {
const x = 0; const x = 0;
const result = Result.succeedWith(x + 1).map((x) => x * 2); const result = Result.succeedWith(x + 1).map((x) => x * 2);
@ -45,13 +45,13 @@ describe("Result", () => {
expectTypeOf(result).toEqualTypeOf<Result<number, never>>(); expectTypeOf(result).toEqualTypeOf<Result<number, never>>();
}); });
it("should not execute the function if the result is an error", () => { test("should not execute the function if the result is an error", () => {
const fn = vitest.fn(); const mock = fn() as () => number;
const result = Result.failWith(new UnexpectedError()).map(fn); const result = Result.failWith(new UnexpectedError()).map(mock);
expect(result.isError()).toBe(true); expect(result.isError()).toBe(true);
expect(fn).not.toHaveBeenCalled(); expect(mock).not.toHaveBeenCalled();
}); });
}); });
}); });

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-explicit-any
import { isError } from "../error/is-error.js"; import { isError } from "../error/is-error.ts";
import { TaggedError } from "../error/tagged-error.js"; import type { TaggedError } from "../error/tagged-error.ts";
/** /**
* A Result represents the outcome of an operation * A Result represents the outcome of an operation

View File

@ -0,0 +1 @@
export * from "./run.ts";

View File

@ -1,11 +1,12 @@
import { describe, expect, it } from "vitest"; // deno-lint-ignore-file require-await
import { UnexpectedError } from "../error/unexpected-error.js"; import { describe, expect, test } from "@fabric/testing";
import { Result } from "../result/result.js"; import { UnexpectedError } from "../error/unexpected-error.ts";
import { Run } from "./run.js"; import { Result } from "../result/result.ts";
import { Run } from "./run.ts";
describe("Run", () => { describe("Run", () => {
describe("In Sequence", () => { describe("In Sequence", () => {
it("should pipe the results of multiple async functions", async () => { test("should pipe the results of multiple async functions", async () => {
const result = await Run.seq( const result = await Run.seq(
async () => Result.succeedWith(1), async () => Result.succeedWith(1),
async (x) => Result.succeedWith(x + 1), async (x) => Result.succeedWith(x + 1),
@ -15,7 +16,7 @@ describe("Run", () => {
expect(result.unwrapOrThrow()).toEqual(4); expect(result.unwrapOrThrow()).toEqual(4);
}); });
it("should return the first error if one of the functions fails", async () => { test("should return the first error if one of the functions fails", async () => {
const result = await Run.seq( const result = await Run.seq(
async () => Result.succeedWith(1), async () => Result.succeedWith(1),
async () => Result.failWith(new UnexpectedError()), async () => Result.failWith(new UnexpectedError()),

View File

@ -1,21 +1,26 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-namespace no-explicit-any
import { TaggedError } from "../error/tagged-error.js"; import type { TaggedError } from "../error/tagged-error.ts";
import { AsyncResult } from "../result/async-result.js"; import type { AsyncResult } from "../result/async-result.ts";
export namespace Run { export namespace Run {
// prettier-ignore // prettier-ignore
export async function seq< export async function seq<
T1, TE1 extends TaggedError, T1,
T2, TE2 extends TaggedError, TE1 extends TaggedError,
T2,
TE2 extends TaggedError,
>( >(
fn1: () => AsyncResult<T1, TE1>, fn1: () => AsyncResult<T1, TE1>,
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,
): AsyncResult<T2, TE1 | TE2>; ): AsyncResult<T2, TE1 | TE2>;
// prettier-ignore // prettier-ignore
export async function seq< export async function seq<
T1, TE1 extends TaggedError, T1,
T2, TE2 extends TaggedError, TE1 extends TaggedError,
T3, TE3 extends TaggedError, T2,
TE2 extends TaggedError,
T3,
TE3 extends TaggedError,
>( >(
fn1: () => AsyncResult<T1, TE1>, fn1: () => AsyncResult<T1, TE1>,
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,
@ -23,10 +28,14 @@ export namespace Run {
): AsyncResult<T3, TE1 | TE2 | TE3>; ): AsyncResult<T3, TE1 | TE2 | TE3>;
// prettier-ignore // prettier-ignore
export async function seq< export async function seq<
T1, TE1 extends TaggedError, T1,
T2, TE2 extends TaggedError, TE1 extends TaggedError,
T3, TE3 extends TaggedError, T2,
T4, TE4 extends TaggedError, TE2 extends TaggedError,
T3,
TE3 extends TaggedError,
T4,
TE4 extends TaggedError,
>( >(
fn1: () => AsyncResult<T1, TE1>, fn1: () => AsyncResult<T1, TE1>,
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,
@ -36,14 +45,14 @@ export namespace Run {
export async function seq( export async function seq(
...fns: ((...args: any[]) => AsyncResult<any, any>)[] ...fns: ((...args: any[]) => AsyncResult<any, any>)[]
): AsyncResult<any, any> { ): AsyncResult<any, any> {
let result = await fns[0](); let result = await fns[0]!();
for (let i = 1; i < fns.length; i++) { for (let i = 1; i < fns.length; i++) {
if (result.isError()) { if (result.isError()) {
return result; return result;
} }
result = await fns[i](result.unwrapOrThrow()); result = await fns[i]!(result.unwrapOrThrow());
} }
return result; return result;
@ -51,17 +60,22 @@ export namespace Run {
// prettier-ignore // prettier-ignore
export async function seqUNSAFE< export async function seqUNSAFE<
T1, TE1 extends TaggedError, T1,
T2, TE2 extends TaggedError, TE1 extends TaggedError,
T2,
TE2 extends TaggedError,
>( >(
fn1: () => AsyncResult<T1, TE1>, fn1: () => AsyncResult<T1, TE1>,
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,
): Promise<T2>; ): Promise<T2>;
// prettier-ignore // prettier-ignore
export async function seqUNSAFE< export async function seqUNSAFE<
T1,TE1 extends TaggedError, T1,
T2,TE2 extends TaggedError, TE1 extends TaggedError,
T3,TE3 extends TaggedError, T2,
TE2 extends TaggedError,
T3,
TE3 extends TaggedError,
>( >(
fn1: () => AsyncResult<T1, TE1>, fn1: () => AsyncResult<T1, TE1>,
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,

View File

@ -1,17 +0,0 @@
/**
* Get the element type of an array.
*/
export type ArrayElement<T extends readonly unknown[]> =
T extends readonly (infer U)[] ? U : never;
/**
* Get the first element type of a tuple.
*/
export type TupleFirstElement<T extends readonly unknown[]> =
T extends readonly [infer U, ...unknown[]] ? U : never;
/**
* Get the LAST element type of a tuple.
*/
export type TupleLastElement<T extends readonly unknown[]> =
T extends readonly [...unknown[], infer U] ? U : never;

View File

@ -1 +0,0 @@
export * from "./array-element.js";

View File

@ -1,3 +0,0 @@
export * from "./is-error.js";
export * from "./tagged-error.js";
export * from "./unexpected-error.js";

View File

@ -1,23 +0,0 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import { Result } from "../result/result.js";
import { isError } from "./is-error.js";
import { TaggedError } from "./tagged-error.js";
describe("is-error", () => {
it("should determine if a value is an error", () => {
type DemoResult = Result<number, TaggedError<"DemoError">>;
//Ok should not be an error
const ok: DemoResult = 42;
expect(isError(ok)).toBe(false);
//Error should be an error
const error: DemoResult = new TaggedError("DemoError");
expect(isError(error)).toBe(true);
//After a check, typescript should be able to infer the type
if (isError(error)) {
expectTypeOf(error).toEqualTypeOf<TaggedError<"DemoError">>();
}
});
});

View File

@ -1,9 +0,0 @@
export * from "./array/index.js";
export * from "./error/index.js";
export * from "./record/index.js";
export * from "./result/index.js";
export * from "./run/index.js";
export * from "./time/index.js";
export * from "./types/index.js";
export * from "./utils/index.js";
export * from "./variant/index.js";

View File

@ -1,2 +0,0 @@
export * from "./is-record-empty.js";
export * from "./is-record.js";

View File

@ -1,19 +0,0 @@
import { describe, expect, it } from "vitest";
import { isRecordEmpty } from "./is-record-empty.js";
describe("Record - Is Record Empty", () => {
it("should return true for an empty record", () => {
const result = isRecordEmpty({});
expect(result).toBe(true);
});
it("should return false for a non-empty record", () => {
const result = isRecordEmpty({ key: "value" });
expect(result).toBe(false);
});
it("should return false for a record with multiple keys", () => {
const result = isRecordEmpty({ key1: "value1", key2: "value2" });
expect(result).toBe(false);
});
});

View File

@ -1,2 +0,0 @@
export * from "./async-result.js";
export * from "./result.js";

View File

@ -1 +0,0 @@
export * from "./run.js";

View File

@ -1,3 +0,0 @@
export * from "./posix-date.js";
export * from "./time-constants.js";
export * from "./timeout.js";

View File

@ -1,44 +0,0 @@
import { describe, expect, test } from "vitest";
import { timeout } from "./timeout.js";
const HEAVY_TESTS =
process.env.HEAVY_TESTS === "true" || process.env.RUN_ALL_TESTS === "true";
describe("timeout", () => {
test.runIf(HEAVY_TESTS)(
"timeout never triggers *before* the input time",
async () => {
const count = 10000;
const maxTimeInMs = 1000;
const result = await Promise.all(
new Array(count).fill(0).map(async (e, i) => {
const start = Date.now();
const ms = i % maxTimeInMs;
await timeout(ms);
const end = Date.now();
return end - start;
}),
);
expect(
result
.map((t, i) => {
return [t, i % maxTimeInMs]; //Actual time and expected time
})
.filter((e) => {
return e[0] < e[1]; //Actual time is less than the expected time
}),
).toEqual([]);
},
);
test("using timeout we can define a timeout in milliseconds", async () => {
const start = Date.now();
await timeout(100);
const end = Date.now();
const time = end - start;
expect(time).toBeGreaterThanOrEqual(100);
});
});

View File

@ -1,14 +0,0 @@
export function timeout(ms: number) {
return new Promise<void>((resolve) => {
const start = Date.now();
setTimeout(() => {
const end = Date.now();
const remaining = ms - (end - start);
if (remaining > 0) {
timeout(remaining).then(resolve);
} else {
resolve();
}
}, ms);
});
}

View File

@ -1,6 +0,0 @@
export * from "./enum.js";
export * from "./fn.js";
export * from "./keyof.js";
export * from "./maybe-promise.js";
export * from "./optional.js";
export * from "./record.js";

View File

@ -1,8 +0,0 @@
import { UnexpectedError } from "../error/unexpected-error.js";
export function ensureValue<T>(value?: T): T {
if (!value) {
throw new UnexpectedError("Value is undefined");
}
return value;
}

View File

@ -1 +0,0 @@
export * from "./ensure-value.js";

View File

@ -1,2 +0,0 @@
export * from "./match.js";
export * from "./variant.js";

View File

@ -1,36 +0,0 @@
import { describe, expect, it } from "vitest";
import { match } from "./match.js";
import { TaggedVariant, VariantTag } from "./variant.js";
interface V1 extends TaggedVariant<"V1"> {
a: number;
}
interface V2 extends TaggedVariant<"V2"> {
b: string;
}
type Variant = V1 | V2;
describe("Pattern matching", () => {
it("Should match a pattern", () => {
const v = { [VariantTag]: "V1", a: 42 } as Variant;
const result = match(v).case({
V1: (v) => v.a,
V2: (v) => v.b,
});
expect(result).toBe(42);
});
it("Should alert that a pattern is not exhaustive", () => {
const v = { [VariantTag]: "V1", a: 42 } as Variant;
expect(() =>
// @ts-expect-error Testing non-exhaustive pattern matching
match(v).case({
V2: (v) => v.b,
}),
).toThrowError("Non-exhaustive pattern match");
});
});

View File

@ -1,41 +0,0 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import { TaggedVariant, Variant, VariantTag } from "./variant.js";
interface SuccessVariant extends TaggedVariant<"success"> {
[VariantTag]: "success";
data: string;
}
interface ErrorVariant extends TaggedVariant<"error"> {
[VariantTag]: "error";
message: string;
}
describe("Variant", () => {
describe("isVariant", () => {
const successVariant = {
[VariantTag]: "success",
data: "Operation successful",
} as SuccessVariant | ErrorVariant;
const errorVariant = {
[VariantTag]: "error",
message: "Operation failed",
} as SuccessVariant | ErrorVariant;
it("should return true for a matching tag and correctly infer it", () => {
if (Variant.is(successVariant, "success")) {
expectTypeOf(successVariant).toEqualTypeOf<SuccessVariant>();
}
if (Variant.is(errorVariant, "error")) {
expectTypeOf(errorVariant).toEqualTypeOf<ErrorVariant>();
}
});
it("should return false for a non-matching tag", () => {
expect(Variant.is(successVariant, "error")).toBe(false);
expect(Variant.is(errorVariant, "success")).toBe(false);
});
});
});

View File

@ -0,0 +1,2 @@
export * from "./posix-date.ts";
export * from "./time-constants.ts";

View File

@ -1,5 +1,5 @@
import { isRecord } from "../record/is-record.js"; import { isRecord } from "../record/is-record.ts";
import { TaggedVariant } from "../variant/variant.js"; import type { TaggedVariant } from "../variant/variant.ts";
export class PosixDate { export class PosixDate {
constructor(public readonly timestamp: number = Date.now()) {} constructor(public readonly timestamp: number = Date.now()) {}
@ -22,8 +22,9 @@ export class PosixDate {
"timestamp" in value && "timestamp" in value &&
value["type"] === "posix-date" && value["type"] === "posix-date" &&
typeof value["timestamp"] === "number" typeof value["timestamp"] === "number"
) ) {
return true; return true;
}
return false; return false;
} }
} }

View File

@ -1,15 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"allowImportingTsExtensions": false,
"outDir": "dist"
},
"exclude": [
"src/**/*.spec.ts",
"dist",
"node_modules",
"coverage",
"vitest.config.ts"
]
}

View File

@ -1,4 +0,0 @@
{
"extends": "../../../tsconfig.json",
"exclude": ["dist", "node_modules"]
}

View File

@ -1,7 +1,7 @@
// deno-lint-ignore-file no-explicit-any
/** /**
* A function that takes an argument of type `T` and returns a value of type `R`. * A function that takes an argument of type `T` and returns a value of type `R`.
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Fn<T = any, R = any> = (arg: T) => R; export type Fn<T = any, R = any> = (arg: T) => R;
/** /**

View File

@ -0,0 +1,6 @@
export * from "./enum.ts";
export * from "./fn.ts";
export * from "./keyof.ts";
export * from "./maybe-promise.ts";
export * from "./optional.ts";
export * from "./record.ts";

View File

@ -0,0 +1,8 @@
import { UnexpectedError } from "../error/unexpected-error.ts";
export function ensureValue<T>(value?: T): T {
if (!value) {
throw new UnexpectedError("Value is nullish.");
}
return value;
}

View File

@ -0,0 +1 @@
export * from "./ensure-value.ts";

View File

@ -0,0 +1,2 @@
export * from "./match.ts";
export * from "./variant.ts";

View File

@ -0,0 +1,35 @@
import { expect } from "jsr:@std/expect";
import { match } from "./match.ts";
import { type TaggedVariant, VariantTag } from "./variant.ts";
interface V1 extends TaggedVariant<"V1"> {
a: number;
}
interface V2 extends TaggedVariant<"V2"> {
b: string;
}
type Variant = V1 | V2;
const v = { [VariantTag]: "V1", a: 42 } as Variant;
Deno.test("match().case() calls the correct function", () => {
const result = match(v).case({
V1: (v) => v.a,
V2: (v) => v.b,
});
expect(result).toBe(42);
});
Deno.test(
"match().case() throws an error for non-exhaustive pattern matching",
() => {
expect(() =>
// @ts-expect-error Testing non-exhaustive pattern matching
match(v).case({
V2: (v) => v.b,
})
).toThrow("Non-exhaustive pattern match");
},
);

View File

@ -1,5 +1,9 @@
import { Fn } from "../types/fn.js"; import type { Fn } from "../types/fn.ts";
import { TaggedVariant, VariantFromTag, VariantTag } from "./variant.js"; import {
type TaggedVariant,
type VariantFromTag,
VariantTag,
} from "./variant.ts";
export type VariantMatcher<TVariant extends TaggedVariant<string>, T> = { export type VariantMatcher<TVariant extends TaggedVariant<string>, T> = {
[K in TVariant[VariantTag]]: Fn<VariantFromTag<TVariant, K>, T>; [K in TVariant[VariantTag]]: Fn<VariantFromTag<TVariant, K>, T>;

View File

@ -0,0 +1,32 @@
import { expect } from "jsr:@std/expect";
import { type TaggedVariant, Variant, VariantTag } from "./variant.ts";
interface SuccessVariant extends TaggedVariant<"success"> {
[VariantTag]: "success";
data: string;
}
interface ErrorVariant extends TaggedVariant<"error"> {
[VariantTag]: "error";
message: string;
}
const successVariant = {
[VariantTag]: "success",
data: "Operation successful",
} as SuccessVariant | ErrorVariant;
const errorVariant = {
[VariantTag]: "error",
message: "Operation failed",
} as SuccessVariant | ErrorVariant;
Deno.test("is() should return true for a matching tag", () => {
expect(Variant.is(successVariant, "success")).toBe(true);
expect(Variant.is(errorVariant, "error")).toBe(true);
});
Deno.test("is() should return false for a non-matching tag", () => {
expect(Variant.is(successVariant, "error")).toBe(false);
expect(Variant.is(errorVariant, "success")).toBe(false);
});

View File

@ -1,10 +0,0 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
exclude: ["dist/**", "vitest.config.ts", "**/index.ts", "**/*.spec.ts"],
},
passWithNoTests: true,
},
});

View File

@ -0,0 +1,7 @@
{
"name": "@fabric/domain",
"exports": {
".": "./index.ts",
"./mocks": "./mocks.ts"
}
}

View File

@ -1,6 +1,7 @@
import { TaggedError } from "@fabric/core"; import { TaggedError } from "@fabric/core";
export class CircularDependencyError extends TaggedError<"CircularDependencyError"> { export class CircularDependencyError
extends TaggedError<"CircularDependencyError"> {
context: { key: string; dep: string }; context: { key: string; dep: string };
constructor(key: string, dep: string) { constructor(key: string, dep: string) {
super("CircularDependencyError"); super("CircularDependencyError");

View File

@ -0,0 +1,2 @@
export * from "./circular-dependency-error.ts";
export * from "./query-error.ts";

View File

@ -1,7 +1,7 @@
import { TaggedError } from "@fabric/core"; import { TaggedError } from "@fabric/core";
export class StoreQueryError extends TaggedError<"StoreQueryError"> { export class StoreQueryError extends TaggedError<"StoreQueryError"> {
constructor(public message: string) { constructor(message: string) {
super("StoreQueryError", message); super("StoreQueryError", message);
} }
} }

View File

@ -1,14 +1,14 @@
import { import type {
AsyncResult, AsyncResult,
MaybePromise, MaybePromise,
PosixDate, PosixDate,
VariantFromTag, VariantFromTag,
VariantTag, VariantTag,
} from "@fabric/core"; } from "@fabric/core";
import { StoreQueryError } from "../errors/query-error.js"; import type { StoreQueryError } from "../errors/query-error.ts";
import { UUID } from "../types/uuid.js"; import type { UUID } from "../types/uuid.ts";
import { Event } from "./event.js"; import type { Event } from "./event.ts";
import { StoredEvent } from "./stored-event.js"; import type { StoredEvent } from "./stored-event.ts";
export interface EventStore<TEvents extends Event> { export interface EventStore<TEvents extends Event> {
/** /**

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-explicit-any
import { VariantTag } from "@fabric/core"; import type { VariantTag } from "@fabric/core";
import { UUID } from "../types/uuid.js"; import type { UUID } from "../types/uuid.ts";
/** /**
* An event is a tagged variant with a payload and a timestamp. * An event is a tagged variant with a payload and a timestamp.

View File

@ -0,0 +1,3 @@
export * from "./event-store.ts";
export * from "./event.ts";
export * from "./stored-event.ts";

View File

@ -1,5 +1,5 @@
import { PosixDate } from "@fabric/core"; import type { PosixDate } from "@fabric/core";
import { Event } from "./event.js"; import type { Event } from "./event.ts";
/** /**
* A stored event is an inmutable event, already stored, with it's version in the stream and timestamp. * A stored event is an inmutable event, already stored, with it's version in the stream and timestamp.

View File

@ -1,4 +1,4 @@
import { MimeType } from "./mime-type.js"; import type { MimeType } from "./mime-type.ts";
/** /**
* Represents a file. Its the base type for all files. * Represents a file. Its the base type for all files.

View File

@ -1,5 +1,5 @@
import { ImageMimeType } from "./mime-type.js"; import type { ImageMimeType } from "./mime-type.ts";
import { StoredFile } from "./stored-file.js"; import type { StoredFile } from "./stored-file.ts";
/** /**
* Represents an image file. * Represents an image file.

View File

@ -1,5 +1,5 @@
import { Base64String } from "../types/base-64.js"; import type { Base64String } from "../types/base-64.ts";
import { BaseFile } from "./base-file.js"; import type { BaseFile } from "./base-file.ts";
/** /**
* Represents a file with its contents in memory. * Represents a file with its contents in memory.

View File

@ -0,0 +1,10 @@
export * from "./base-file.ts";
export * from "./bytes.ts";
export * from "./image-file.ts";
export * from "./in-memory-file.ts";
export * from "./invalid-file-type-error.ts";
export * from "./is-in-memory-file.ts";
export * from "./is-mime-type.ts";
export * from "./media-file.ts";
export * from "./mime-type.ts";
export * from "./stored-file.ts";

View File

@ -1,5 +1,5 @@
import { isRecord } from "@fabric/core"; import { isRecord } from "@fabric/core";
import { InMemoryFile } from "./in-memory-file.js"; import type { InMemoryFile } from "./in-memory-file.ts";
export function isInMemoryFile(value: unknown): value is InMemoryFile { export function isInMemoryFile(value: unknown): value is InMemoryFile {
try { try {

View File

@ -1,8 +1,8 @@
import { describe, expect, expectTypeOf, it } from "vitest"; import { describe, expect, expectTypeOf, test } from "@fabric/testing";
import { isMimeType } from "./is-mime-type.js"; import { isMimeType } from "./is-mime-type.ts";
describe("isMimeType", () => { describe("isMimeType", () => {
it("should return true if the file type is the same as the mime type", () => { test("should return true if the file type is the same as the mime type", () => {
const fileType = "image/png" as string; const fileType = "image/png" as string;
const result = isMimeType("image/.*", fileType); const result = isMimeType("image/.*", fileType);
expect(result).toBe(true); expect(result).toBe(true);
@ -11,7 +11,7 @@ describe("isMimeType", () => {
} }
}); });
it("should return false if the file type is not the same as the mime type", () => { test("should return false if the file type is not the same as the mime type", () => {
const fileType = "image/png" as string; const fileType = "image/png" as string;
expect(isMimeType("image/jpeg", fileType)).toBe(false); expect(isMimeType("image/jpeg", fileType)).toBe(false);

View File

@ -1,4 +1,4 @@
import { MimeType } from "./mime-type.js"; import type { MimeType } from "./mime-type.ts";
/** /**
* Checks if the actual file type is the same as the expected mime type. * Checks if the actual file type is the same as the expected mime type.

View File

@ -1,4 +1,4 @@
import { StoredFile } from "./stored-file.js"; import type { StoredFile } from "./stored-file.ts";
/** /**
* Represents a media file, either an image, a video or an audio file. * Represents a media file, either an image, a video or an audio file.

View File

@ -1,5 +1,5 @@
import { Entity } from "../types/entity.js"; import type { Entity } from "../types/entity.ts";
import { BaseFile } from "./base-file.js"; import type { BaseFile } from "./base-file.ts";
/** /**
* Represents a file as managed by the domain. * Represents a file as managed by the domain.

View File

@ -0,0 +1,9 @@
export * from "./errors/index.ts";
export * from "./events/index.ts";
export * from "./files/index.ts";
export * from "./models/index.ts";
export * from "./security/index.ts";
export * from "./services/index.ts";
export * from "./types/index.ts";
export * from "./use-case/index.ts";
export * from "./utils/index.ts";

View File

@ -0,0 +1 @@
export * from "./services/mocks.ts";

View File

@ -1,5 +1,5 @@
import { TaggedVariant, VariantTag } from "@fabric/core"; import { type TaggedVariant, VariantTag } from "@fabric/core";
import { BaseField } from "./base-field.js"; import type { BaseField } from "./base-field.ts";
export interface DecimalFieldOptions extends BaseField { export interface DecimalFieldOptions extends BaseField {
isUnsigned?: boolean; isUnsigned?: boolean;
@ -8,8 +8,7 @@ export interface DecimalFieldOptions extends BaseField {
} }
export interface DecimalField export interface DecimalField
extends TaggedVariant<"DecimalField">, extends TaggedVariant<"DecimalField">, DecimalFieldOptions {}
DecimalFieldOptions {}
export function createDecimalField<T extends DecimalFieldOptions>( export function createDecimalField<T extends DecimalFieldOptions>(
opts: T = {} as T, opts: T = {} as T,

Some files were not shown because too many files have changed in this diff Show More