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
129 changed files with 834 additions and 1936 deletions
Showing only changes of commit f6de496e73 - Show all commits

View File

@ -1,4 +1,4 @@
{ {
"*": ["deno fmt"], "*": ["deno fmt --check"],
"*.ts": ["deno lint"] "*.ts": ["deno lint --fix"]
} }

View File

@ -2,8 +2,6 @@
"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",

View File

@ -1,18 +0,0 @@
{
"tasks": {
"dev": "deno run --watch main.ts",
"test": "deno test --watch",
"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"
],
"imports": {
"@std/assert": "jsr:@std/assert@1"
},
"compilerOptions": {
"strict": true
}
}

34
deno.jsonc Normal file
View File

@ -0,0 +1,34 @@
{
"tasks": {
"test": "deno test --allow-all --unstable-ffi",
"test:dev": "deno test --allow-all --unstable-ffi --watch",
"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"
],
"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"]
}
}
}

1072
deno.lock

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,6 @@
{
"name": "@fabric/core",
"exports": {
".": "./src/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

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

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

View File

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

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

@ -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,4 +1,4 @@
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.

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

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

View File

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

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

@ -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
@ -10,13 +10,13 @@ import { Result } from "./result.js";
*/ */
export type AsyncResult< export type AsyncResult<
TValue = any, TValue = any,
TError extends TaggedError = never, TError extends TaggedError = never
> = Promise<Result<TValue, TError>>; > = Promise<Result<TValue, TError>>;
export namespace AsyncResult { export namespace AsyncResult {
export async function tryFrom<T, TError extends TaggedError>( export async function tryFrom<T, TError extends TaggedError>(
fn: () => MaybePromise<T>, fn: () => MaybePromise<T>,
errorMapper: (error: any) => TError, errorMapper: (error: any) => TError
): AsyncResult<T, TError> { ): AsyncResult<T, TError> {
try { try {
return Result.succeedWith(await fn()); return Result.succeedWith(await fn());
@ -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

@ -1,2 +1,2 @@
export * from "./async-result.js"; export * from "./async-result.ts";
export * from "./result.js"; 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
@ -24,7 +24,7 @@ export class Result<TValue, TError extends TaggedError = never> {
static tryFrom<T, TError extends TaggedError>( static tryFrom<T, TError extends TaggedError>(
fn: () => T, fn: () => T,
errorMapper: (error: any) => TError, errorMapper: (error: any) => TError
): Result<T, TError> { ): Result<T, TError> {
try { try {
return Result.succeedWith(fn()); return Result.succeedWith(fn());
@ -83,7 +83,7 @@ export class Result<TValue, TError extends TaggedError = never> {
* Map a function over the value of the result. * Map a function over the value of the result.
*/ */
map<TMappedValue>( map<TMappedValue>(
fn: (value: TValue) => TMappedValue, fn: (value: TValue) => TMappedValue
): Result<TMappedValue, TError> { ): Result<TMappedValue, TError> {
if (!isError(this.value)) { if (!isError(this.value)) {
return Result.succeedWith(fn(this.value as TValue)); return Result.succeedWith(fn(this.value as TValue));
@ -96,7 +96,7 @@ export class Result<TValue, TError extends TaggedError = never> {
* Maps a function over the value of the result and flattens the result. * Maps a function over the value of the result and flattens the result.
*/ */
flatMap<TMappedValue, TMappedError extends TaggedError>( flatMap<TMappedValue, TMappedError extends TaggedError>(
fn: (value: TValue) => Result<TMappedValue, TMappedError>, fn: (value: TValue) => Result<TMappedValue, TMappedError>
): Result<TMappedValue, TError | TMappedError> { ): Result<TMappedValue, TError | TMappedError> {
if (!isError(this.value)) { if (!isError(this.value)) {
return fn(this.value as TValue) as any; return fn(this.value as TValue) as any;
@ -111,7 +111,7 @@ export class Result<TValue, TError extends TaggedError = never> {
*/ */
tryMap<TMappedValue>( tryMap<TMappedValue>(
fn: (value: TValue) => TMappedValue, fn: (value: TValue) => TMappedValue,
errMapper: (error: any) => TError, errMapper: (error: any) => TError
): Result<TMappedValue, TError> { ): Result<TMappedValue, TError> {
if (!isError(this.value)) { if (!isError(this.value)) {
try { try {
@ -128,7 +128,7 @@ export class Result<TValue, TError extends TaggedError = never> {
* Map a function over the error of the result. * Map a function over the error of the result.
*/ */
mapError<TMappedError extends TaggedError>( mapError<TMappedError extends TaggedError>(
fn: (error: TError) => TMappedError, fn: (error: TError) => TMappedError
): Result<TValue, TMappedError> { ): Result<TValue, TMappedError> {
if (isError(this.value)) { if (isError(this.value)) {
return Result.failWith(fn(this.value as TError)); return Result.failWith(fn(this.value as TError));

View File

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

View File

@ -1,28 +0,0 @@
import { describe, expect, it } from "vitest";
import { UnexpectedError } from "../error/unexpected-error.js";
import { Result } from "../result/result.js";
import { Run } from "./run.js";
describe("Run", () => {
describe("In Sequence", () => {
it("should pipe the results of multiple async functions", async () => {
const result = await Run.seq(
async () => Result.succeedWith(1),
async (x) => Result.succeedWith(x + 1),
async (x) => Result.succeedWith(x * 2),
);
expect(result.unwrapOrThrow()).toEqual(4);
});
it("should return the first error if one of the functions fails", async () => {
const result = await Run.seq(
async () => Result.succeedWith(1),
async () => Result.failWith(new UnexpectedError()),
async (x) => Result.succeedWith(x * 2),
);
expect(result.isError()).toBe(true);
});
});
});

View File

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

View File

@ -1,6 +1,6 @@
/* 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
@ -36,14 +36,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;
@ -80,7 +80,7 @@ export namespace Run {
} }
export async function UNSAFE<T, TError extends TaggedError>( export async function UNSAFE<T, TError extends TaggedError>(
fn: () => AsyncResult<T, TError>, fn: () => AsyncResult<T, TError>
): Promise<T> { ): Promise<T> {
return (await fn()).unwrapOrThrow(); return (await fn()).unwrapOrThrow();
} }

View File

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

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()) {}

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

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

View File

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

View File

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

View File

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

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

@ -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,12 +1,12 @@
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>;
}; };
export function match<const TVariant extends TaggedVariant<string>>( export function match<const TVariant extends TaggedVariant<string>>(
v: TVariant, v: TVariant
) { ) {
return { return {
case< case<
@ -14,14 +14,14 @@ export function match<const TVariant extends TaggedVariant<string>>(
const TMatcher extends VariantMatcher< const TMatcher extends VariantMatcher<
TVariant, TVariant,
TReturnType TReturnType
> = VariantMatcher<TVariant, TReturnType>, > = VariantMatcher<TVariant, TReturnType>
>(cases: TMatcher): TReturnType { >(cases: TMatcher): TReturnType {
if (!(v[VariantTag] in cases)) { if (!(v[VariantTag] in cases)) {
throw new Error("Non-exhaustive pattern match"); throw new Error("Non-exhaustive pattern match");
} }
return cases[v[VariantTag] as TVariant[VariantTag]]( return cases[v[VariantTag] as TVariant[VariantTag]](
v as Extract<TVariant, { [VariantTag]: TVariant[VariantTag] }>, v as Extract<TVariant, { [VariantTag]: TVariant[VariantTag] }>
); );
}, },
}; };

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,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,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,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": {
".": "./src/index.ts",
"./mocks": "./src/mocks.ts"
}
}

View File

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

View File

@ -1,2 +1,2 @@
export * from "./circular-dependency-error.js"; export * from "./circular-dependency-error.ts";
export * from "./query-error.js"; 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,35 +1,35 @@
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> {
/** /**
* Store a new event in the event store. * Store a new event in the event store.
*/ */
append<T extends TEvents>( append<T extends TEvents>(
event: T, event: T
): AsyncResult<StoredEvent<T>, StoreQueryError>; ): AsyncResult<StoredEvent<T>, StoreQueryError>;
getEventsFromStream( getEventsFromStream(
streamId: UUID, streamId: UUID
): AsyncResult<StoredEvent<TEvents>[], StoreQueryError>; ): AsyncResult<StoredEvent<TEvents>[], StoreQueryError>;
subscribe<TEventKey extends TEvents[VariantTag]>( subscribe<TEventKey extends TEvents[VariantTag]>(
events: TEventKey[], events: TEventKey[],
subscriber: EventSubscriber<VariantFromTag<TEvents, TEventKey>>, subscriber: EventSubscriber<VariantFromTag<TEvents, TEventKey>>
): void; ): void;
} }
export type EventSubscriber<TEvents extends Event = Event> = ( export type EventSubscriber<TEvents extends Event = Event> = (
event: StoredEvent<TEvents>, event: StoredEvent<TEvents>
) => MaybePromise<void>; ) => MaybePromise<void>;
export interface EventFilterOptions { export interface EventFilterOptions {

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.
@ -14,5 +14,5 @@ export interface Event<TTag extends string = string, TPayload = any> {
export type EventFromKey< export type EventFromKey<
TEvents extends Event, TEvents extends Event,
TKey extends TEvents[VariantTag], TKey extends TEvents[VariantTag]
> = Extract<TEvents, { [VariantTag]: TKey }>; > = Extract<TEvents, { [VariantTag]: TKey }>;

View File

@ -1,3 +1,3 @@
export * from "./event-store.js"; export * from "./event-store.ts";
export * from "./event.js"; export * from "./event.ts";
export * from "./stored-event.js"; 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

@ -1,10 +1,10 @@
export * from "./base-file.js"; export * from "./base-file.ts";
export * from "./bytes.js"; export * from "./bytes.ts";
export * from "./image-file.js"; export * from "./image-file.ts";
export * from "./in-memory-file.js"; export * from "./in-memory-file.ts";
export * from "./invalid-file-type-error.js"; export * from "./invalid-file-type-error.ts";
export * from "./is-in-memory-file.js"; export * from "./is-in-memory-file.ts";
export * from "./is-mime-type.js"; export * from "./is-mime-type.ts";
export * from "./media-file.js"; export * from "./media-file.ts";
export * from "./mime-type.js"; export * from "./mime-type.ts";
export * from "./stored-file.js"; 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,11 +1,11 @@
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.
*/ */
export function isMimeType<T extends MimeType>( export function isMimeType<T extends MimeType>(
expectedMimeType: T, expectedMimeType: T,
actualMimeType: string, actualMimeType: string
): actualMimeType is T { ): actualMimeType is T {
return actualMimeType.match("^" + expectedMimeType + "$") !== null; return actualMimeType.match("^" + expectedMimeType + "$") !== null;
} }

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

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

View File

@ -1 +1 @@
export * from "./services/mocks.js"; 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;
@ -12,7 +12,7 @@ export interface DecimalField
DecimalFieldOptions {} DecimalFieldOptions {}
export function createDecimalField<T extends DecimalFieldOptions>( export function createDecimalField<T extends DecimalFieldOptions>(
opts: T = {} as T, opts: T = {} as T
): DecimalField & T { ): DecimalField & T {
return { return {
[VariantTag]: "DecimalField", [VariantTag]: "DecimalField",

View File

@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-explicit-any
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";
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars
export interface EmbeddedFieldOptions<T = any> extends BaseField {} export interface EmbeddedFieldOptions<T = any> extends BaseField {}
export interface EmbeddedField<T = any> export interface EmbeddedField<T = any>
@ -11,7 +10,7 @@ export interface EmbeddedField<T = any>
export function createEmbeddedField< export function createEmbeddedField<
K = any, K = any,
T extends EmbeddedFieldOptions<K> = EmbeddedFieldOptions<K>, T extends EmbeddedFieldOptions<K> = EmbeddedFieldOptions<K>
>(opts: T = {} as T): EmbeddedField & T { >(opts: T = {} as T): EmbeddedField & T {
return { return {
[VariantTag]: "EmbeddedField", [VariantTag]: "EmbeddedField",

View File

@ -1,14 +1,14 @@
import { PosixDate } from "@fabric/core"; import type { PosixDate } from "@fabric/core";
import { Decimal } from "decimal.js"; import type Decimal from "jsr:@quentinadam/decimal";
import { UUID } from "../../types/uuid.js"; import type { UUID } from "../../types/uuid.ts";
import { DecimalField } from "./decimal.js"; import type { DecimalField } from "./decimal.ts";
import { EmbeddedField } from "./embedded.js"; import type { EmbeddedField } from "./embedded.ts";
import { FloatField } from "./float.js"; import type { FloatField } from "./float.ts";
import { IntegerField } from "./integer.js"; import type { IntegerField } from "./integer.ts";
import { ReferenceField } from "./reference-field.js"; import type { ReferenceField } from "./reference-field.ts";
import { StringField } from "./string-field.js"; import type { StringField } from "./string-field.ts";
import { TimestampField } from "./timestamp.js"; import type { TimestampField } from "./timestamp.ts";
import { UUIDField } from "./uuid-field.js"; import type { UUIDField } from "./uuid-field.ts";
/** /**
* Converts a field definition to its corresponding TypeScript type. * Converts a field definition to its corresponding TypeScript type.

View File

@ -1,7 +1,6 @@
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";
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface FloatFieldOptions extends BaseField {} export interface FloatFieldOptions extends BaseField {}
export interface FloatField export interface FloatField
@ -9,7 +8,7 @@ export interface FloatField
FloatFieldOptions {} FloatFieldOptions {}
export function createFloatField<T extends FloatFieldOptions>( export function createFloatField<T extends FloatFieldOptions>(
opts: T = {} as T, opts: T = {} as T
): FloatField & T { ): FloatField & T {
return { return {
[VariantTag]: "FloatField", [VariantTag]: "FloatField",

View File

@ -1,14 +1,17 @@
import { createDecimalField, DecimalField } from "./decimal.js"; import { createDecimalField, type DecimalField } from "./decimal.ts";
import { createEmbeddedField, EmbeddedField } from "./embedded.js"; import { createEmbeddedField, type EmbeddedField } from "./embedded.ts";
import { createFloatField, FloatField } from "./float.js"; import { createFloatField, type FloatField } from "./float.ts";
import { createIntegerField, IntegerField } from "./integer.js"; import { createIntegerField, type IntegerField } from "./integer.ts";
import { createReferenceField, ReferenceField } from "./reference-field.js"; import {
import { createStringField, StringField } from "./string-field.js"; createReferenceField,
import { createTimestampField, TimestampField } from "./timestamp.js"; type ReferenceField,
import { createUUIDField, UUIDField } from "./uuid-field.js"; } from "./reference-field.ts";
export * from "./base-field.js"; import { createStringField, type StringField } from "./string-field.ts";
export * from "./field-to-type.js"; import { createTimestampField, type TimestampField } from "./timestamp.ts";
export * from "./reference-field.js"; import { createUUIDField, type UUIDField } from "./uuid-field.ts";
export * from "./base-field.ts";
export * from "./field-to-type.ts";
export * from "./reference-field.ts";
export type FieldDefinition = export type FieldDefinition =
| StringField | StringField

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 IntegerFieldOptions extends BaseField { export interface IntegerFieldOptions extends BaseField {
isUnsigned?: boolean; isUnsigned?: boolean;
@ -11,7 +11,7 @@ export interface IntegerField
IntegerFieldOptions {} IntegerFieldOptions {}
export function createIntegerField<T extends IntegerFieldOptions>( export function createIntegerField<T extends IntegerFieldOptions>(
opts: T = {} as T, opts: T = {} as T
): IntegerField & T { ): IntegerField & T {
return { return {
[VariantTag]: "IntegerField", [VariantTag]: "IntegerField",

View File

@ -1,11 +1,11 @@
import { isError } from "@fabric/core"; import { isError } from "@fabric/core";
import { describe, expect, it } from "vitest"; import { describe, expect, test } from "@fabric/testing";
import { defineModel } from "../model.js"; import { defineModel } from "../model.ts";
import { Field } from "./index.js"; import { Field } from "./index.ts";
import { import {
InvalidReferenceFieldError, InvalidReferenceFieldError,
validateReferenceField, validateReferenceField,
} from "./reference-field.js"; } from "./reference-field.ts";
describe("Validate Reference Field", () => { describe("Validate Reference Field", () => {
const schema = { const schema = {
@ -20,57 +20,57 @@ describe("Validate Reference Field", () => {
}), }),
}; };
it("should return an error when the target model is not in the schema", () => { test("should return an error when the target model is not in the schema", () => {
const result = validateReferenceField( const result = validateReferenceField(
schema, schema,
Field.reference({ Field.reference({
targetModel: "foo", targetModel: "foo",
}), })
).unwrapErrorOrThrow(); ).unwrapErrorOrThrow();
expect(result).toBeInstanceOf(InvalidReferenceFieldError); expect(result).toBeInstanceOf(InvalidReferenceFieldError);
}); });
it("should not return an error if the target model is in the schema", () => { test("should not return an error if the target model is in the schema", () => {
validateReferenceField( validateReferenceField(
schema, schema,
Field.reference({ Field.reference({
targetModel: "User", targetModel: "User",
}), })
).unwrapOrThrow(); ).unwrapOrThrow();
}); });
it("should return an error if the target key is not in the target model", () => { test("should return an error if the target key is not in the target model", () => {
const result = validateReferenceField( const result = validateReferenceField(
schema, schema,
Field.reference({ Field.reference({
targetModel: "User", targetModel: "User",
targetKey: "foo", targetKey: "foo",
}), })
).unwrapErrorOrThrow(); ).unwrapErrorOrThrow();
expect(result).toBeInstanceOf(InvalidReferenceFieldError); expect(result).toBeInstanceOf(InvalidReferenceFieldError);
}); });
it("should return error if the target key is not unique", () => { test("should return error if the target key is not unique", () => {
const result = validateReferenceField( const result = validateReferenceField(
schema, schema,
Field.reference({ Field.reference({
targetModel: "User", targetModel: "User",
targetKey: "otherNotUnique", targetKey: "otherNotUnique",
}), })
).unwrapErrorOrThrow(); ).unwrapErrorOrThrow();
expect(result).toBeInstanceOf(InvalidReferenceFieldError); expect(result).toBeInstanceOf(InvalidReferenceFieldError);
}); });
it("should not return an error if the target key is in the target model and is unique", () => { test("should not return an error if the target key is in the target model and is unique", () => {
const result = validateReferenceField( const result = validateReferenceField(
schema, schema,
Field.reference({ Field.reference({
targetModel: "User", targetModel: "User",
targetKey: "otherUnique", targetKey: "otherUnique",
}), })
); );
if (isError(result)) { if (isError(result)) {

View File

@ -1,6 +1,11 @@
import { Result, TaggedError, TaggedVariant, VariantTag } from "@fabric/core"; import {
import { ModelSchema } from "../model-schema.js"; Result,
import { BaseField } from "./base-field.js"; TaggedError,
type TaggedVariant,
VariantTag,
} from "@fabric/core";
import type { ModelSchema } from "../model-schema.ts";
import type { BaseField } from "./base-field.ts";
export interface ReferenceFieldOptions extends BaseField { export interface ReferenceFieldOptions extends BaseField {
targetModel: string; targetModel: string;
@ -12,7 +17,7 @@ export interface ReferenceField
ReferenceFieldOptions {} ReferenceFieldOptions {}
export function createReferenceField<T extends ReferenceFieldOptions>( export function createReferenceField<T extends ReferenceFieldOptions>(
opts: T = {} as T, opts: T = {} as T
): ReferenceField & T { ): ReferenceField & T {
return { return {
[VariantTag]: "ReferenceField", [VariantTag]: "ReferenceField",
@ -26,32 +31,32 @@ export function getTargetKey(field: ReferenceField): string {
export function validateReferenceField( export function validateReferenceField(
schema: ModelSchema, schema: ModelSchema,
field: ReferenceField, field: ReferenceField
): Result<void, InvalidReferenceFieldError> { ): Result<void, InvalidReferenceFieldError> {
if (!schema[field.targetModel]) { if (!schema[field.targetModel]) {
return Result.failWith( return Result.failWith(
new InvalidReferenceFieldError( new InvalidReferenceFieldError(
`The target model '${field.targetModel}' is not in the schema.`, `The target model '${field.targetModel}' is not in the schema.`
), )
); );
} }
if (field.targetKey && !schema[field.targetModel].fields[field.targetKey]) { if (field.targetKey && !schema[field.targetModel]!.fields[field.targetKey]) {
return Result.failWith( return Result.failWith(
new InvalidReferenceFieldError( new InvalidReferenceFieldError(
`The target key '${field.targetKey}' is not in the target model '${field.targetModel}'.`, `The target key '${field.targetKey}' is not in the target model '${field.targetModel}'.`
), )
); );
} }
if ( if (
field.targetKey && field.targetKey &&
!schema[field.targetModel].fields[field.targetKey].isUnique !schema[field.targetModel]!.fields[field.targetKey]!.isUnique
) { ) {
return Result.failWith( return Result.failWith(
new InvalidReferenceFieldError( new InvalidReferenceFieldError(
`The target key '${field.targetModel}'.'${field.targetKey}' is not unique.`, `The target key '${field.targetModel}'.'${field.targetKey}' is not unique.`
), )
); );
} }

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 StringFieldOptions extends BaseField { export interface StringFieldOptions extends BaseField {
maxLength?: number; maxLength?: number;
@ -11,7 +11,7 @@ export interface StringField
StringFieldOptions {} StringFieldOptions {}
export function createStringField<T extends StringFieldOptions>( export function createStringField<T extends StringFieldOptions>(
opts: T = {} as T, opts: T = {} as T
): StringField & T { ): StringField & T {
return { return {
[VariantTag]: "StringField", [VariantTag]: "StringField",

View File

@ -1,7 +1,6 @@
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";
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface TimestampFieldOptions extends BaseField {} export interface TimestampFieldOptions extends BaseField {}
export interface TimestampField export interface TimestampField
@ -9,7 +8,7 @@ export interface TimestampField
TimestampFieldOptions {} TimestampFieldOptions {}
export function createTimestampField<T extends TimestampFieldOptions>( export function createTimestampField<T extends TimestampFieldOptions>(
opts: T = {} as T, opts: T = {} as T
): TimestampField & T { ): TimestampField & T {
return { return {
[VariantTag]: "TimestampField", [VariantTag]: "TimestampField",

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 UUIDFieldOptions extends BaseField { export interface UUIDFieldOptions extends BaseField {
isPrimaryKey?: boolean; isPrimaryKey?: boolean;
@ -10,7 +10,7 @@ export interface UUIDField
UUIDFieldOptions {} UUIDFieldOptions {}
export function createUUIDField<T extends UUIDFieldOptions>( export function createUUIDField<T extends UUIDFieldOptions>(
opts: T = {} as T, opts: T = {} as T
): UUIDField & T { ): UUIDField & T {
return { return {
[VariantTag]: "UUIDField", [VariantTag]: "UUIDField",

View File

@ -1,5 +1,5 @@
export * from "./fields/index.js"; export * from "./fields/index.ts";
export * from "./model-schema.js"; export * from "./model-schema.ts";
export * from "./model.js"; export * from "./model.ts";
export * from "./query/index.js"; export * from "./query/index.ts";
export * from "./state-store.js"; export * from "./state-store.ts";

View File

@ -1,4 +1,4 @@
import { Model } from "./model.js"; import type { Model } from "./model.ts";
export type ModelSchema = Record<string, Model>; export type ModelSchema = Record<string, Model>;

View File

@ -1,10 +1,11 @@
import { describe, expectTypeOf, it } from "vitest"; import type { PosixDate } from "@fabric/core";
import { UUID } from "../types/uuid.js"; import { describe, expectTypeOf, test } from "@fabric/testing";
import { Field } from "./fields/index.js"; import type { UUID } from "../types/uuid.ts";
import { defineModel, ModelToType } from "./model.js"; import { Field } from "./fields/index.ts";
import { defineModel, type ModelToType } from "./model.ts";
describe("CreateModel", () => { describe("CreateModel", () => {
it("should create a model and it's interface type", () => { test("should create a model and it's interface type", () => {
const User = defineModel("User", { const User = defineModel("User", {
name: Field.string(), name: Field.string(),
password: Field.string(), password: Field.string(),
@ -19,6 +20,7 @@ describe("CreateModel", () => {
name: string; name: string;
password: string; password: string;
phone: string | null; phone: string | null;
deletedAt: PosixDate | null;
}>(); }>();
}); });
}); });

View File

@ -1,12 +1,12 @@
import { Keyof } from "@fabric/core"; import type { Keyof } from "@fabric/core";
import { FieldToType } from "./fields/field-to-type.js"; import type { FieldToType } from "./fields/field-to-type.ts";
import { Field, FieldDefinition } from "./fields/index.js"; import { Field, type FieldDefinition } from "./fields/index.ts";
export type CustomModelFields = Record<string, FieldDefinition>; export type CustomModelFields = Record<string, FieldDefinition>;
export interface Collection< export interface Collection<
TName extends string = string, TName extends string = string,
TFields extends CustomModelFields = CustomModelFields, TFields extends CustomModelFields = CustomModelFields
> { > {
name: TName; name: TName;
fields: TFields; fields: TFields;
@ -24,14 +24,14 @@ export const DefaultModelFields = {
export interface Model< export interface Model<
TName extends string = string, TName extends string = string,
TFields extends CustomModelFields = CustomModelFields, TFields extends CustomModelFields = CustomModelFields
> extends Collection<TName, TFields> { > extends Collection<TName, TFields> {
fields: typeof DefaultModelFields & TFields; fields: typeof DefaultModelFields & TFields;
} }
export function defineModel< export function defineModel<
TName extends string, TName extends string,
TFields extends CustomModelFields, TFields extends CustomModelFields
>(name: TName, fields: TFields): Model<TName, TFields> { >(name: TName, fields: TFields): Model<TName, TFields> {
return { return {
name, name,
@ -41,7 +41,7 @@ export function defineModel<
export function defineCollection< export function defineCollection<
TName extends string, TName extends string,
TFields extends CustomModelFields, TFields extends CustomModelFields
>(name: TName, fields: TFields): Collection<TName, TFields> { >(name: TName, fields: TFields): Collection<TName, TFields> {
return { return {
name, name,

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-explicit-any
import type { Keyof, TaggedVariant } from "@fabric/core";
import { Keyof, TaggedVariant } from "@fabric/core";
export type AggregateOptions<T = any> = Record<string, AggregateFn<T>>; export type AggregateOptions<T = any> = Record<string, AggregateFn<T>>;

View File

@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-explicit-any
export type FilterOptions<T = any> = export type FilterOptions<T = any> =
| SingleFilterOption<T> | SingleFilterOption<T>

View File

@ -1,3 +1,3 @@
export * from "./filter-options.js"; export * from "./filter-options.ts";
export * from "./order-by-options.js"; export * from "./order-by-options.ts";
export * from "./query.js"; export * from "./query.ts";

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-explicit-any
import { AsyncResult, Keyof, Optional } from "@fabric/core"; import type { AsyncResult, Keyof, Optional } from "@fabric/core";
import { StoreQueryError } from "../../errors/query-error.js"; import type { StoreQueryError } from "../../errors/query-error.ts";
import { FilterOptions } from "./filter-options.js"; import type { FilterOptions } from "./filter-options.ts";
import { OrderByOptions } from "./order-by-options.js"; import type { OrderByOptions } from "./order-by-options.ts";
export interface StoreQuery<T> { export interface StoreQuery<T> {
where(where: FilterOptions<T>): StoreSortableQuery<T>; where(where: FilterOptions<T>): StoreSortableQuery<T>;
@ -11,12 +11,12 @@ export interface StoreQuery<T> {
select(): AsyncResult<T[], StoreQueryError>; select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>( select<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Pick<T, K>[], StoreQueryError>; ): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>; selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
} }
@ -26,12 +26,12 @@ export interface StoreSortableQuery<T> {
select(): AsyncResult<T[], StoreQueryError>; select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>( select<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Pick<T, K>[], StoreQueryError>; ): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>; selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
} }
@ -40,24 +40,24 @@ export interface StoreLimitableQuery<T> {
select(): AsyncResult<T[], StoreQueryError>; select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>( select<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Pick<T, K>[], StoreQueryError>; ): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>; selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
} }
export interface SelectableQuery<T> { export interface SelectableQuery<T> {
select(): AsyncResult<T[], StoreQueryError>; select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>( select<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Pick<T, K>[], StoreQueryError>; ): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>; selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[]
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
} }

View File

@ -1,12 +1,12 @@
import { AsyncResult } from "@fabric/core"; import type { AsyncResult } from "@fabric/core";
import { StoreQueryError } from "../errors/query-error.js"; import type { StoreQueryError } from "../errors/query-error.ts";
import { ModelSchemaFromModels } from "./model-schema.js"; import type { ModelSchemaFromModels } from "./model-schema.ts";
import { Model, ModelToType } from "./model.js"; import type { Model, ModelToType } from "./model.ts";
import { StoreQuery } from "./query/query.js"; import type { StoreQuery } from "./query/query.ts";
export interface ReadonlyStateStore<TModel extends Model> { export interface ReadonlyStateStore<TModel extends Model> {
from<T extends keyof ModelSchemaFromModels<TModel>>( from<T extends keyof ModelSchemaFromModels<TModel>>(
collection: T, collection: T
): StoreQuery<ModelToType<ModelSchemaFromModels<TModel>[T]>>; ): StoreQuery<ModelToType<ModelSchemaFromModels<TModel>[T]>>;
} }
@ -14,6 +14,6 @@ export interface WritableStateStore<TModel extends Model>
extends ReadonlyStateStore<TModel> { extends ReadonlyStateStore<TModel> {
insertInto<T extends keyof ModelSchemaFromModels<TModel>>( insertInto<T extends keyof ModelSchemaFromModels<TModel>>(
collection: T, collection: T,
record: ModelToType<ModelSchemaFromModels<TModel>[T]>, record: ModelToType<ModelSchemaFromModels<TModel>[T]>
): AsyncResult<void, StoreQueryError>; ): AsyncResult<void, StoreQueryError>;
} }

View File

@ -1,13 +1,13 @@
import { VariantTag } from "@fabric/core"; import type { VariantTag } from "@fabric/core";
import { Event } from "../events/event.js"; import type { Event } from "../events/event.ts";
import { StoredEvent } from "../events/stored-event.js"; import type { StoredEvent } from "../events/stored-event.ts";
import { Model, ModelToType } from "../models/model.js"; import type { Model, ModelToType } from "../models/model.ts";
export interface Projection<TModel extends Model, TEvents extends Event> { export interface Projection<TModel extends Model, TEvents extends Event> {
model: TModel; model: TModel;
events: TEvents[VariantTag][]; events: TEvents[VariantTag][];
projection: ( projection: (
event: StoredEvent<TEvents>, event: StoredEvent<TEvents>,
model?: ModelToType<TModel>, model?: ModelToType<TModel>
) => ModelToType<TModel>; ) => ModelToType<TModel>;
} }

View File

@ -1 +1 @@
export * from "./policy.js"; export * from "./policy.ts";

View File

@ -1 +1 @@
export * from "./uuid-generator.js"; export * from "./uuid-generator.ts";

View File

@ -1 +1 @@
export * from "./uuid-generator.mock.js"; export * from "./uuid-generator.mock.ts";

View File

@ -1,5 +1,5 @@
import { UUID } from "../types/uuid.js"; import type { UUID } from "../types/uuid.ts";
import { UUIDGenerator } from "./uuid-generator.js"; import type { UUIDGenerator } from "./uuid-generator.ts";
export const UUIDGeneratorMock: UUIDGenerator = { export const UUIDGeneratorMock: UUIDGenerator = {
generate(): UUID { generate(): UUID {

View File

@ -1,4 +1,4 @@
import { UUID } from "../types/uuid.js"; import type { UUID } from "../types/uuid.ts";
export interface UUIDGenerator { export interface UUIDGenerator {
generate(): UUID; generate(): UUID;

View File

@ -1 +0,0 @@
export { Decimal } from "decimal.js";

View File

@ -1,4 +1,4 @@
import { UUID } from "./uuid.js"; import type { UUID } from "./uuid.ts";
/** /**
* An entity is a domain object that is defined by its identity. * An entity is a domain object that is defined by its identity.

View File

@ -1,6 +1,5 @@
export * from "./base-64.js"; export * from "./base-64.ts";
export * from "./decimal.js"; export * from "./email.ts";
export * from "./email.js"; export * from "./entity.ts";
export * from "./entity.js"; export * from "./semver.ts";
export * from "./semver.js"; export * from "./uuid.ts";
export * from "./uuid.js";

View File

@ -1,2 +1,2 @@
export * from "./use-case-definition.js"; export * from "./use-case-definition.ts";
export * from "./use-case.js"; export * from "./use-case.ts";

View File

@ -1,19 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // deno-lint-ignore-file no-explicit-any
import { TaggedError } from "@fabric/core"; import type { TaggedError } from "@fabric/core";
import { UseCase } from "./use-case.js"; import type { UseCase } from "./use-case.ts";
export type UseCaseDefinition< export type UseCaseDefinition<
TDependencies = any, TDependencies = any,
TPayload = any, TPayload = any,
TOutput = any, TOutput = any,
TErrors extends TaggedError<string> = any, TErrors extends TaggedError<string> = any
> = BasicUseCaseDefinition<TDependencies, TPayload, TOutput, TErrors>; > = BasicUseCaseDefinition<TDependencies, TPayload, TOutput, TErrors>;
interface BasicUseCaseDefinition< interface BasicUseCaseDefinition<
TDependencies, TDependencies,
TPayload, TPayload,
TOutput, TOutput,
TErrors extends TaggedError<string>, TErrors extends TaggedError<string>
> { > {
/** /**
* The use case name. * The use case name.

View File

@ -1,4 +1,4 @@
import { AsyncResult, TaggedError } from "@fabric/core"; import type { AsyncResult, TaggedError } from "@fabric/core";
/** /**
* A use case is a piece of domain logic that can be executed. * A use case is a piece of domain logic that can be executed.

View File

@ -1,2 +1,2 @@
export * from "./json-utils.js"; export * from "./json-utils.ts";
export * from "./sort-by-dependencies.js"; export * from "./sort-by-dependencies.ts";

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import { expect } from "@std/expect";
import { describe, expect, it } from "vitest"; import { describe, it } from "@std/testing/bdd";
import { CircularDependencyError } from "../errors/circular-dependency-error.js"; import { CircularDependencyError } from "../errors/circular-dependency-error.ts";
import { sortByDependencies } from "./sort-by-dependencies.js"; import { sortByDependencies } from "./sort-by-dependencies.ts";
describe("sortByDependencies", () => { describe("sortByDependencies", () => {
it("should sort an array of objects by their dependencies", () => { it("should sort an array of objects by their dependencies", () => {
@ -35,11 +35,12 @@ describe("sortByDependencies", () => {
sortByDependencies(array, { sortByDependencies(array, {
keyGetter: (element) => element.name, keyGetter: (element) => element.name,
depGetter: (element) => element.dependencies, depGetter: (element) => element.dependencies,
}).unwrapErrorOrThrow(), }).unwrapErrorOrThrow()
).toBeInstanceOf(CircularDependencyError); ).toBeInstanceOf(CircularDependencyError);
}); });
it("should return an empty array when the input array is empty", () => { it("should return an empty array when the input array is empty", () => {
// deno-lint-ignore no-explicit-any
const array: any[] = []; const array: any[] = [];
const result = sortByDependencies(array, { const result = sortByDependencies(array, {

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