Compare commits
13 Commits
bfb471b166
...
4950730d9e
| Author | SHA1 | Date | |
|---|---|---|---|
| 4950730d9e | |||
| dfee950913 | |||
| aadefd30f8 | |||
| 0b05168544 | |||
| 6d2218f9f5 | |||
| 72780b9803 | |||
| 26cc090284 | |||
| 7f4a0bd06b | |||
| b2e8b33dae | |||
| 4cc99aec91 | |||
| a79683d9d4 | |||
| f6de496e73 | |||
| 4503ff4576 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -1,4 +1 @@
|
||||
/.yarn/** linguist-vendored
|
||||
/.yarn/releases/* binary
|
||||
/.yarn/plugins/**/* binary
|
||||
* text=auto eol=lf
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -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
4
.hooks/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/hook.sh"
|
||||
|
||||
deno task check
|
||||
@ -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
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"*": ["yarn prettier -u --write"],
|
||||
"*.ts": ["yarn eslint --fix"]
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
dist
|
||||
coverage
|
||||
.yarn/**/*
|
||||
yarn.lock
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2
|
||||
}
|
||||
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
925
.yarn/releases/yarn-4.5.0.cjs
vendored
925
.yarn/releases/yarn-4.5.0.cjs
vendored
File diff suppressed because one or more lines are too long
@ -1,3 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.5.0.cjs
|
||||
37
deno.jsonc
Normal file
37
deno.jsonc
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"tasks": {
|
||||
"test": "deno test --allow-all --unstable-ffi",
|
||||
"test:dev": "deno test --allow-all --unstable-ffi --watch",
|
||||
"check": "deno fmt && deno lint --fix && deno check **/*.ts && deno task test",
|
||||
"hook": "deno run --allow-read --allow-run --allow-write https://deno.land/x/deno_hooks@0.1.1/mod.ts"
|
||||
},
|
||||
"workspace": [
|
||||
"packages/fabric/core",
|
||||
"packages/fabric/domain",
|
||||
"packages/fabric/sqlite-store",
|
||||
"packages/fabric/testing",
|
||||
"packages/templates/domain",
|
||||
"packages/templates/lib"
|
||||
],
|
||||
"imports": {
|
||||
"@db/sqlite": "jsr:@db/sqlite@^0.12.0",
|
||||
"@quentinadam/decimal": "jsr:@quentinadam/decimal@^0.1.6",
|
||||
"@std/expect": "jsr:@std/expect@^1.0.5",
|
||||
"@std/testing": "jsr:@std/testing@^1.0.3",
|
||||
"expect-type": "npm:expect-type@^1.1.0"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUncheckedIndexedAccess": true
|
||||
},
|
||||
"lint": {
|
||||
"include": ["src/"],
|
||||
"rules": {
|
||||
"tags": ["recommended"],
|
||||
"exclude": ["no-namespace"]
|
||||
}
|
||||
}
|
||||
}
|
||||
138
deno.lock
Normal file
138
deno.lock
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
},
|
||||
},
|
||||
);
|
||||
33
package.json
33
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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">();
|
||||
17
packages/fabric/core/array/array-element.ts
Normal file
17
packages/fabric/core/array/array-element.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Get the element type of an array.
|
||||
*/
|
||||
export type ArrayElement<T extends readonly unknown[]> = T extends
|
||||
readonly (infer U)[] ? U : never;
|
||||
|
||||
/**
|
||||
* Get the first element type of a tuple.
|
||||
*/
|
||||
export type TupleFirstElement<T extends readonly unknown[]> = T extends
|
||||
readonly [infer U, ...unknown[]] ? U : never;
|
||||
|
||||
/**
|
||||
* Get the LAST element type of a tuple.
|
||||
*/
|
||||
export type TupleLastElement<T extends readonly unknown[]> = T extends
|
||||
readonly [...unknown[], infer U] ? U : never;
|
||||
1
packages/fabric/core/array/index.ts
Normal file
1
packages/fabric/core/array/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./array-element.ts";
|
||||
6
packages/fabric/core/deno.jsonc
Normal file
6
packages/fabric/core/deno.jsonc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@fabric/core",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
}
|
||||
}
|
||||
3
packages/fabric/core/error/index.ts
Normal file
3
packages/fabric/core/error/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./is-error.ts";
|
||||
export * from "./tagged-error.ts";
|
||||
export * from "./unexpected-error.ts";
|
||||
16
packages/fabric/core/error/is-error.test.ts
Normal file
16
packages/fabric/core/error/is-error.test.ts
Normal 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>();
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
import { TaggedError } from "./tagged-error.js";
|
||||
import { TaggedError } from "./tagged-error.ts";
|
||||
|
||||
/**
|
||||
* Indicates if a value is an error.
|
||||
@ -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) {
|
||||
@ -1,4 +1,4 @@
|
||||
import { TaggedError } from "./tagged-error.js";
|
||||
import { TaggedError } from "./tagged-error.ts";
|
||||
|
||||
/**
|
||||
* `UnexpectedError` represents any type of unexpected error.
|
||||
9
packages/fabric/core/index.ts
Normal file
9
packages/fabric/core/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export * from "./array/index.ts";
|
||||
export * from "./error/index.ts";
|
||||
export * from "./record/index.ts";
|
||||
export * from "./result/index.ts";
|
||||
export * from "./run/index.ts";
|
||||
export * from "./time/index.ts";
|
||||
export * from "./types/index.ts";
|
||||
export * from "./utils/index.ts";
|
||||
export * from "./variant/index.ts";
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
2
packages/fabric/core/record/index.ts
Normal file
2
packages/fabric/core/record/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./is-record-empty.ts";
|
||||
export * from "./is-record.ts";
|
||||
19
packages/fabric/core/record/is-record-empty.test.ts
Normal file
19
packages/fabric/core/record/is-record-empty.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
2
packages/fabric/core/result/index.ts
Normal file
2
packages/fabric/core/result/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./async-result.ts";
|
||||
export * from "./result.ts";
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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
|
||||
1
packages/fabric/core/run/index.ts
Normal file
1
packages/fabric/core/run/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./run.ts";
|
||||
@ -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()),
|
||||
@ -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>,
|
||||
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Get the element type of an array.
|
||||
*/
|
||||
export type ArrayElement<T extends readonly unknown[]> =
|
||||
T extends readonly (infer U)[] ? U : never;
|
||||
|
||||
/**
|
||||
* Get the first element type of a tuple.
|
||||
*/
|
||||
export type TupleFirstElement<T extends readonly unknown[]> =
|
||||
T extends readonly [infer U, ...unknown[]] ? U : never;
|
||||
|
||||
/**
|
||||
* Get the LAST element type of a tuple.
|
||||
*/
|
||||
export type TupleLastElement<T extends readonly unknown[]> =
|
||||
T extends readonly [...unknown[], infer U] ? U : never;
|
||||
@ -1 +0,0 @@
|
||||
export * from "./array-element.js";
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./is-error.js";
|
||||
export * from "./tagged-error.js";
|
||||
export * from "./unexpected-error.js";
|
||||
@ -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">>();
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1,9 +0,0 @@
|
||||
export * from "./array/index.js";
|
||||
export * from "./error/index.js";
|
||||
export * from "./record/index.js";
|
||||
export * from "./result/index.js";
|
||||
export * from "./run/index.js";
|
||||
export * from "./time/index.js";
|
||||
export * from "./types/index.js";
|
||||
export * from "./utils/index.js";
|
||||
export * from "./variant/index.js";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./is-record-empty.js";
|
||||
export * from "./is-record.js";
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./async-result.js";
|
||||
export * from "./result.js";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./run.js";
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./posix-date.js";
|
||||
export * from "./time-constants.js";
|
||||
export * from "./timeout.js";
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
export * from "./enum.js";
|
||||
export * from "./fn.js";
|
||||
export * from "./keyof.js";
|
||||
export * from "./maybe-promise.js";
|
||||
export * from "./optional.js";
|
||||
export * from "./record.js";
|
||||
@ -1,8 +0,0 @@
|
||||
import { UnexpectedError } from "../error/unexpected-error.js";
|
||||
|
||||
export function ensureValue<T>(value?: T): T {
|
||||
if (!value) {
|
||||
throw new UnexpectedError("Value is undefined");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./ensure-value.js";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./match.js";
|
||||
export * from "./variant.js";
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
2
packages/fabric/core/time/index.ts
Normal file
2
packages/fabric/core/time/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./posix-date.ts";
|
||||
export * from "./time-constants.ts";
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
6
packages/fabric/core/types/index.ts
Normal file
6
packages/fabric/core/types/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from "./enum.ts";
|
||||
export * from "./fn.ts";
|
||||
export * from "./keyof.ts";
|
||||
export * from "./maybe-promise.ts";
|
||||
export * from "./optional.ts";
|
||||
export * from "./record.ts";
|
||||
8
packages/fabric/core/utils/ensure-value.ts
Normal file
8
packages/fabric/core/utils/ensure-value.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { UnexpectedError } from "../error/unexpected-error.ts";
|
||||
|
||||
export function ensureValue<T>(value?: T): T {
|
||||
if (!value) {
|
||||
throw new UnexpectedError("Value is nullish.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
1
packages/fabric/core/utils/index.ts
Normal file
1
packages/fabric/core/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./ensure-value.ts";
|
||||
2
packages/fabric/core/variant/index.ts
Normal file
2
packages/fabric/core/variant/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./match.ts";
|
||||
export * from "./variant.ts";
|
||||
35
packages/fabric/core/variant/match.test.ts
Normal file
35
packages/fabric/core/variant/match.test.ts
Normal 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");
|
||||
},
|
||||
);
|
||||
@ -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>;
|
||||
32
packages/fabric/core/variant/variant.test.ts
Normal file
32
packages/fabric/core/variant/variant.test.ts
Normal 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);
|
||||
});
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
7
packages/fabric/domain/deno.jsonc
Normal file
7
packages/fabric/domain/deno.jsonc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@fabric/domain",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./mocks": "./mocks.ts"
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
2
packages/fabric/domain/errors/index.ts
Normal file
2
packages/fabric/domain/errors/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./circular-dependency-error.ts";
|
||||
export * from "./query-error.ts";
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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> {
|
||||
/**
|
||||
@ -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.
|
||||
3
packages/fabric/domain/events/index.ts
Normal file
3
packages/fabric/domain/events/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./event-store.ts";
|
||||
export * from "./event.ts";
|
||||
export * from "./stored-event.ts";
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
10
packages/fabric/domain/files/index.ts
Normal file
10
packages/fabric/domain/files/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export * from "./base-file.ts";
|
||||
export * from "./bytes.ts";
|
||||
export * from "./image-file.ts";
|
||||
export * from "./in-memory-file.ts";
|
||||
export * from "./invalid-file-type-error.ts";
|
||||
export * from "./is-in-memory-file.ts";
|
||||
export * from "./is-mime-type.ts";
|
||||
export * from "./media-file.ts";
|
||||
export * from "./mime-type.ts";
|
||||
export * from "./stored-file.ts";
|
||||
@ -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 {
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
9
packages/fabric/domain/index.ts
Normal file
9
packages/fabric/domain/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export * from "./errors/index.ts";
|
||||
export * from "./events/index.ts";
|
||||
export * from "./files/index.ts";
|
||||
export * from "./models/index.ts";
|
||||
export * from "./security/index.ts";
|
||||
export * from "./services/index.ts";
|
||||
export * from "./types/index.ts";
|
||||
export * from "./use-case/index.ts";
|
||||
export * from "./utils/index.ts";
|
||||
1
packages/fabric/domain/mocks.ts
Normal file
1
packages/fabric/domain/mocks.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./services/mocks.ts";
|
||||
@ -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,
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user