Compare commits

...

5 Commits

152 changed files with 843 additions and 6467 deletions

3
.gitattributes vendored
View File

@ -1,4 +1 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
* 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
dist
coverage

4
.hooks/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/hook.sh"
deno run -A npm:lint-staged

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 +1,4 @@
{
"*": ["yarn prettier -u --write"],
"*.ts": ["yarn eslint --fix"]
"*": ["deno fmt --check"],
"*.ts": ["deno lint --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": [
"bierner.github-markdown-preview",
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-spanish",
"usernamehw.errorlens",
"bourhaouta.tailwindshades",
"austenc.tailwind-docs",
"vitest.explorer"
"denoland.vscode-deno",
]
}

View File

@ -17,5 +17,8 @@
},
"typescript.preferences.importModuleSpecifierEnding": "js",
"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

36
deno.jsonc Normal file
View File

@ -0,0 +1,36 @@
{
"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",
"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

@ -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", () => {
type ArrayElement<T extends readonly unknown[]> =
T extends readonly (infer U)[] ? U : never;
type result = ArrayElement<["a", "b", "c"]>;
expectTypeOf<result>().toEqualTypeOf<"a" | "b" | "c">();

View File

@ -1,17 +1,17 @@
/**
* Get the element type of an array.
*/
export type ArrayElement<T extends readonly unknown[]> =
T extends readonly (infer U)[] ? U : never;
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;
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;
export type TupleLastElement<T extends readonly unknown[]> = T extends
readonly [...unknown[], infer U] ? U : never;

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 "./tagged-error.js";
export * from "./unexpected-error.js";
export * from "./is-error.ts";
export * from "./tagged-error.ts";
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.

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.
*/
export abstract class TaggedError<Tag extends string = string>
extends Error
implements TaggedVariant<Tag>
{
export abstract class TaggedError<Tag extends string = string> extends Error
implements TaggedVariant<Tag> {
readonly [VariantTag]: Tag;
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.

View File

@ -1,9 +1,9 @@
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";
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,2 +1,2 @@
export * from "./is-record-empty.js";
export * from "./is-record.js";
export * from "./is-record-empty.ts";
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 { isRecord } from "./is-record.js";
import { describe, expect, test } from "@fabric/testing";
import { isRecord } from "./is-record.ts";
describe("isRecord", () => {
it("should return true for an object", () => {
test("Given an empty object, it should return true", () => {
const obj = { name: "John", age: 30 };
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];
expect(isRecord(arr)).toBe(false);
});
it("should return false for null", () => {
test("Given a number, it should return false", () => {
const value = null;
expect(isRecord(value)).toBe(false);
});
it("should return false for a string", () => {
test("Given a string, it should return false", () => {
const value = "Hello";
expect(isRecord(value)).toBe(false);
});

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,12 @@
import { describe, expect, it } from "vitest";
import { UnexpectedError } from "../error/unexpected-error.js";
import { Result } from "../result/result.js";
import { Run } from "./run.js";
// 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", () => {
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(
async () => Result.succeedWith(1),
async (x) => Result.succeedWith(x + 1),
@ -15,7 +16,7 @@ describe("Run", () => {
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(
async () => Result.succeedWith(1),
async () => Result.failWith(new UnexpectedError()),

View File

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

View File

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

View File

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

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`.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Fn<T = any, R = any> = (arg: T) => R;
/**

View File

@ -1,6 +1,6 @@
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";
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

@ -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 {
if (!value) {
throw new UnexpectedError("Value is undefined");
throw new UnexpectedError("Value is nullish.");
}
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 "./variant.js";
export * from "./match.ts";
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,5 +1,9 @@
import { Fn } from "../types/fn.js";
import { TaggedVariant, VariantFromTag, VariantTag } from "./variant.js";
import type { Fn } from "../types/fn.ts";
import {
type TaggedVariant,
type VariantFromTag,
VariantTag,
} from "./variant.ts";
export type VariantMatcher<TVariant extends TaggedVariant<string>, T> = {
[K in TVariant[VariantTag]]: Fn<VariantFromTag<TVariant, K>, T>;

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,3 @@
export * from "./event-store.js";
export * from "./event.js";
export * from "./stored-event.js";
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 { Event } from "./event.js";
import type { PosixDate } from "@fabric/core";
import type { Event } from "./event.ts";
/**
* 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.

View File

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

View File

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

View File

@ -1,10 +1,10 @@
export * from "./base-file.js";
export * from "./bytes.js";
export * from "./image-file.js";
export * from "./in-memory-file.js";
export * from "./invalid-file-type-error.js";
export * from "./is-in-memory-file.js";
export * from "./is-mime-type.js";
export * from "./media-file.js";
export * from "./mime-type.js";
export * from "./stored-file.js";
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 { InMemoryFile } from "./in-memory-file.js";
import type { InMemoryFile } from "./in-memory-file.ts";
export function isInMemoryFile(value: unknown): value is InMemoryFile {
try {

View File

@ -1,8 +1,8 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import { isMimeType } from "./is-mime-type.js";
import { describe, expect, expectTypeOf, test } from "@fabric/testing";
import { isMimeType } from "./is-mime-type.ts";
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 result = isMimeType("image/.*", fileType);
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;
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.

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.

View File

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

View File

@ -1,9 +1,9 @@
export * from "./errors/index.js";
export * from "./events/index.js";
export * from "./files/index.js";
export * from "./models/index.js";
export * from "./security/index.js";
export * from "./services/index.js";
export * from "./types/index.js";
export * from "./use-case/index.js";
export * from "./utils/index.js";
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

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

View File

@ -1,13 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { TaggedVariant, VariantTag } from "@fabric/core";
import { BaseField } from "./base-field.js";
// deno-lint-ignore-file no-explicit-any
import { type TaggedVariant, VariantTag } from "@fabric/core";
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 EmbeddedField<T = any>
extends TaggedVariant<"EmbeddedField">,
EmbeddedFieldOptions<T> {}
extends TaggedVariant<"EmbeddedField">, EmbeddedFieldOptions<T> {}
export function createEmbeddedField<
K = any,

View File

@ -1,33 +1,34 @@
import { PosixDate } from "@fabric/core";
import { Decimal } from "decimal.js";
import { UUID } from "../../types/uuid.js";
import { DecimalField } from "./decimal.js";
import { EmbeddedField } from "./embedded.js";
import { FloatField } from "./float.js";
import { IntegerField } from "./integer.js";
import { ReferenceField } from "./reference-field.js";
import { StringField } from "./string-field.js";
import { TimestampField } from "./timestamp.js";
import { UUIDField } from "./uuid-field.js";
import type { PosixDate } from "@fabric/core";
import type Decimal from "jsr:@quentinadam/decimal";
import type { UUID } from "../../types/uuid.ts";
import type { DecimalField } from "./decimal.ts";
import type { EmbeddedField } from "./embedded.ts";
import type { FloatField } from "./float.ts";
import type { IntegerField } from "./integer.ts";
import type { ReferenceField } from "./reference-field.ts";
import type { StringField } from "./string-field.ts";
import type { TimestampField } from "./timestamp.ts";
import type { UUIDField } from "./uuid-field.ts";
/**
* Converts a field definition to its corresponding TypeScript type.
*/
//prettier-ignore
export type FieldToType<TField> =
TField extends StringField ? MaybeOptional<TField, string>
export type FieldToType<TField> = TField extends StringField
? MaybeOptional<TField, string>
: TField extends UUIDField ? MaybeOptional<TField, UUID>
: TField extends IntegerField ? IntegerFieldToType<TField>
: TField extends ReferenceField ? MaybeOptional<TField, UUID>
: TField extends DecimalField ? MaybeOptional<TField, Decimal>
: TField extends FloatField ? MaybeOptional<TField, number>
: TField extends TimestampField ? MaybeOptional<TField, PosixDate>
: TField extends EmbeddedField<infer TSubModel> ? MaybeOptional<TField, TSubModel>
: TField extends EmbeddedField<infer TSubModel>
? MaybeOptional<TField, TSubModel>
: never;
//prettier-ignore
type IntegerFieldToType<TField extends IntegerField> = TField["hasArbitraryPrecision"] extends true
? MaybeOptional<TField, bigint>
type IntegerFieldToType<TField extends IntegerField> =
TField["hasArbitraryPrecision"] extends true ? MaybeOptional<TField, bigint>
: TField["hasArbitraryPrecision"] extends false
? MaybeOptional<TField, number>
: MaybeOptional<TField, number | bigint>;

View File

@ -1,12 +1,10 @@
import { TaggedVariant, VariantTag } from "@fabric/core";
import { BaseField } from "./base-field.js";
import { type TaggedVariant, VariantTag } from "@fabric/core";
import type { BaseField } from "./base-field.ts";
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface FloatFieldOptions extends BaseField {}
export interface FloatField
extends TaggedVariant<"FloatField">,
FloatFieldOptions {}
extends TaggedVariant<"FloatField">, FloatFieldOptions {}
export function createFloatField<T extends FloatFieldOptions>(
opts: T = {} as T,

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import { isError } from "@fabric/core";
import { describe, expect, it } from "vitest";
import { defineModel } from "../model.js";
import { Field } from "./index.js";
import { describe, expect, test } from "@fabric/testing";
import { defineModel } from "../model.ts";
import { Field } from "./index.ts";
import {
InvalidReferenceFieldError,
validateReferenceField,
} from "./reference-field.js";
} from "./reference-field.ts";
describe("Validate Reference Field", () => {
const schema = {
@ -20,7 +20,7 @@ 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(
schema,
Field.reference({
@ -31,7 +31,7 @@ describe("Validate Reference Field", () => {
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(
schema,
Field.reference({
@ -40,7 +40,7 @@ describe("Validate Reference Field", () => {
).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(
schema,
Field.reference({
@ -52,7 +52,7 @@ describe("Validate Reference Field", () => {
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(
schema,
Field.reference({
@ -64,7 +64,7 @@ describe("Validate Reference Field", () => {
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(
schema,
Field.reference({

View File

@ -1,6 +1,11 @@
import { Result, TaggedError, TaggedVariant, VariantTag } from "@fabric/core";
import { ModelSchema } from "../model-schema.js";
import { BaseField } from "./base-field.js";
import {
Result,
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 {
targetModel: string;
@ -8,8 +13,7 @@ export interface ReferenceFieldOptions extends BaseField {
}
export interface ReferenceField
extends TaggedVariant<"ReferenceField">,
ReferenceFieldOptions {}
extends TaggedVariant<"ReferenceField">, ReferenceFieldOptions {}
export function createReferenceField<T extends ReferenceFieldOptions>(
opts: T = {} as T,
@ -36,7 +40,7 @@ export function validateReferenceField(
);
}
if (field.targetKey && !schema[field.targetModel].fields[field.targetKey]) {
if (field.targetKey && !schema[field.targetModel]!.fields[field.targetKey]) {
return Result.failWith(
new InvalidReferenceFieldError(
`The target key '${field.targetKey}' is not in the target model '${field.targetModel}'.`,
@ -46,7 +50,7 @@ export function validateReferenceField(
if (
field.targetKey &&
!schema[field.targetModel].fields[field.targetKey].isUnique
!schema[field.targetModel]!.fields[field.targetKey]!.isUnique
) {
return Result.failWith(
new InvalidReferenceFieldError(
@ -58,7 +62,8 @@ export function validateReferenceField(
return Result.ok();
}
export class InvalidReferenceFieldError extends TaggedError<"InvalidReferenceField"> {
export class InvalidReferenceFieldError
extends TaggedError<"InvalidReferenceField"> {
constructor(readonly reason: string) {
super("InvalidReferenceField");
}

View File

@ -1,5 +1,5 @@
import { TaggedVariant, VariantTag } from "@fabric/core";
import { BaseField } from "./base-field.js";
import { type TaggedVariant, VariantTag } from "@fabric/core";
import type { BaseField } from "./base-field.ts";
export interface StringFieldOptions extends BaseField {
maxLength?: number;
@ -7,8 +7,7 @@ export interface StringFieldOptions extends BaseField {
}
export interface StringField
extends TaggedVariant<"StringField">,
StringFieldOptions {}
extends TaggedVariant<"StringField">, StringFieldOptions {}
export function createStringField<T extends StringFieldOptions>(
opts: T = {} as T,

View File

@ -1,12 +1,10 @@
import { TaggedVariant, VariantTag } from "@fabric/core";
import { BaseField } from "./base-field.js";
import { type TaggedVariant, VariantTag } from "@fabric/core";
import type { BaseField } from "./base-field.ts";
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface TimestampFieldOptions extends BaseField {}
export interface TimestampField
extends TaggedVariant<"TimestampField">,
TimestampFieldOptions {}
extends TaggedVariant<"TimestampField">, TimestampFieldOptions {}
export function createTimestampField<T extends TimestampFieldOptions>(
opts: T = {} as T,

View File

@ -1,13 +1,12 @@
import { TaggedVariant, VariantTag } from "@fabric/core";
import { BaseField } from "./base-field.js";
import { type TaggedVariant, VariantTag } from "@fabric/core";
import type { BaseField } from "./base-field.ts";
export interface UUIDFieldOptions extends BaseField {
isPrimaryKey?: boolean;
}
export interface UUIDField
extends TaggedVariant<"UUIDField">,
UUIDFieldOptions {}
extends TaggedVariant<"UUIDField">, UUIDFieldOptions {}
export function createUUIDField<T extends UUIDFieldOptions>(
opts: T = {} as T,

View File

@ -1,5 +1,5 @@
export * from "./fields/index.js";
export * from "./model-schema.js";
export * from "./model.js";
export * from "./query/index.js";
export * from "./state-store.js";
export * from "./fields/index.ts";
export * from "./model-schema.ts";
export * from "./model.ts";
export * from "./query/index.ts";
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>;

View File

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

View File

@ -1,6 +1,6 @@
import { Keyof } from "@fabric/core";
import { FieldToType } from "./fields/field-to-type.js";
import { Field, FieldDefinition } from "./fields/index.js";
import type { Keyof } from "@fabric/core";
import type { FieldToType } from "./fields/field-to-type.ts";
import { Field, type FieldDefinition } from "./fields/index.ts";
export type CustomModelFields = Record<string, FieldDefinition>;

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Keyof, TaggedVariant } from "@fabric/core";
// deno-lint-ignore-file no-explicit-any
import type { Keyof, TaggedVariant } from "@fabric/core";
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> =
| SingleFilterOption<T>
@ -20,8 +20,7 @@ export const FILTER_OPTION_TYPE_SYMBOL = "_filter_type";
export const FILTER_OPTION_VALUE_SYMBOL = "_filter_value";
export const FILTER_OPTION_OPERATOR_SYMBOL = "_filter_operator";
export type LikeFilterOption<T> = T extends string
? {
export type LikeFilterOption<T> = T extends string ? {
[FILTER_OPTION_TYPE_SYMBOL]: "like";
[FILTER_OPTION_VALUE_SYMBOL]: string;
}

View File

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

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AsyncResult, Keyof, Optional } from "@fabric/core";
import { StoreQueryError } from "../../errors/query-error.js";
import { FilterOptions } from "./filter-options.js";
import { OrderByOptions } from "./order-by-options.js";
// deno-lint-ignore-file no-explicit-any
import type { AsyncResult, Keyof, Optional } from "@fabric/core";
import type { StoreQueryError } from "../../errors/query-error.ts";
import type { FilterOptions } from "./filter-options.ts";
import type { OrderByOptions } from "./order-by-options.ts";
export interface StoreQuery<T> {
where(where: FilterOptions<T>): StoreSortableQuery<T>;

View File

@ -1,8 +1,8 @@
import { AsyncResult } from "@fabric/core";
import { StoreQueryError } from "../errors/query-error.js";
import { ModelSchemaFromModels } from "./model-schema.js";
import { Model, ModelToType } from "./model.js";
import { StoreQuery } from "./query/query.js";
import type { AsyncResult } from "@fabric/core";
import type { StoreQueryError } from "../errors/query-error.ts";
import type { ModelSchemaFromModels } from "./model-schema.ts";
import type { Model, ModelToType } from "./model.ts";
import type { StoreQuery } from "./query/query.ts";
export interface ReadonlyStateStore<TModel extends Model> {
from<T extends keyof ModelSchemaFromModels<TModel>>(

View File

@ -1,7 +1,7 @@
import { VariantTag } from "@fabric/core";
import { Event } from "../events/event.js";
import { StoredEvent } from "../events/stored-event.js";
import { Model, ModelToType } from "../models/model.js";
import type { VariantTag } from "@fabric/core";
import type { Event } from "../events/event.ts";
import type { StoredEvent } from "../events/stored-event.ts";
import type { Model, ModelToType } from "../models/model.ts";
export interface Projection<TModel extends Model, TEvents extends Event> {
model: 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";

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