Compare commits

..

No commits in common. "4574b9871b04ba278d6d50fa0c57017b09937565" and "4950730d9ed1757cce9b0919e5cd927bd3606b7f" have entirely different histories.

19 changed files with 164 additions and 326 deletions

17
.vscode/settings.json vendored
View File

@ -1,17 +1,24 @@
{ {
"cSpell.enabled": true, "cSpell.enabled": true,
"cSpell.language": "en,es", "cSpell.language": "en,es",
"editor.defaultFormatter": "denoland.vscode-deno", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"files.autoSave": "off", "files.autoSave": "off",
"files.eol": "\n", "files.eol": "\n",
"javascript.preferences.importModuleSpecifierEnding": "js",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": "always", "source.fixAll": "always",
"source.organizeImports": "always" "source.organizeImports": "always"
}, },
"search.exclude": {
"**/.yarn": true,
"**/node_modules": true,
"packages/frontend/{android,ios}": true
},
"typescript.preferences.importModuleSpecifierEnding": "js",
"cSpell.words": ["autodocs", "Syntropy"], "cSpell.words": ["autodocs", "Syntropy"],
"deno.enable": true, "typescript.preferences.autoImportFileExcludePatterns": ["**/chai/**"],
"deno.lint": true, "[typescript]": {
"deno.unstable": [], "editor.defaultFormatter": "denoland.vscode-deno"
"deno.suggest.imports.autoDiscover": true }
} }

View File

@ -1,6 +1,6 @@
// deno-lint-ignore-file no-namespace no-explicit-any no-async-promise-executor // deno-lint-ignore-file no-namespace no-explicit-any
import { UnexpectedError } from "@fabric/core";
import type { TaggedError } from "../error/tagged-error.ts"; import type { TaggedError } from "../error/tagged-error.ts";
import { UnexpectedError } from "../error/unexpected-error.ts";
import type { MaybePromise } from "../types/maybe-promise.ts"; import type { MaybePromise } from "../types/maybe-promise.ts";
import { Result } from "./result.ts"; import { Result } from "./result.ts";
@ -8,123 +8,24 @@ import { Result } from "./result.ts";
* An AsyncResult represents the result of an asynchronous operation that can * An AsyncResult represents the result of an asynchronous operation that can
* resolve to a value of type `TValue` or an error of type `TError`. * resolve to a value of type `TValue` or an error of type `TError`.
*/ */
export class AsyncResult< export type AsyncResult<
TValue = any, TValue = any,
TError extends TaggedError = never, TError extends TaggedError = never,
> { > = Promise<Result<TValue, TError>>;
static tryFrom<T, TError extends TaggedError>(
export namespace AsyncResult {
export async function tryFrom<T, TError extends TaggedError>(
fn: () => MaybePromise<T>, fn: () => MaybePromise<T>,
errorMapper: (error: any) => TError, errorMapper: (error: any) => TError,
): AsyncResult<T, TError> { ): AsyncResult<T, TError> {
return new AsyncResult( try {
new Promise<Result<T, TError>>(async (resolve) => { return Result.succeedWith(await fn());
try { } catch (error) {
const value = await fn(); return Result.failWith(errorMapper(error));
resolve(Result.ok(value)); }
} catch (error) {
resolve(Result.failWith(errorMapper(error)));
}
}),
);
} }
static from<T>(fn: () => MaybePromise<T>): AsyncResult<T, never> { export function from<T>(fn: () => MaybePromise<T>): AsyncResult<T, never> {
return AsyncResult.tryFrom( return tryFrom(fn, (error) => new UnexpectedError(error) as never);
fn,
(error) => new UnexpectedError(error) as never,
);
}
static ok<T>(value: T): AsyncResult<T, never> {
return new AsyncResult(Promise.resolve(Result.ok(value)));
}
static succeedWith = AsyncResult.ok;
static failWith<TError extends TaggedError>(
error: TError,
): AsyncResult<never, TError> {
return new AsyncResult(Promise.resolve(Result.failWith(error)));
}
private constructor(private r: Promise<Result<TValue, TError>>) {
}
promise(): Promise<Result<TValue, TError>> {
return this.r;
}
async unwrapOrThrow(): Promise<TValue> {
return (await this.r).unwrapOrThrow();
}
async orThrow(): Promise<void> {
return (await this.r).orThrow();
}
async unwrapErrorOrThrow(): Promise<TError> {
return (await this.r).unwrapErrorOrThrow();
}
/**
* Map a function over the value of the result.
*/
map<TMappedValue>(
fn: (value: TValue) => TMappedValue,
): AsyncResult<TMappedValue, TError> {
return new AsyncResult(
this.r.then((result) => result.map(fn)),
);
}
/**
* Maps a function over the value of the result and flattens the result.
*/
flatMap<TMappedValue, TMappedError extends TaggedError>(
fn: (value: TValue) => AsyncResult<TMappedValue, TMappedError>,
): AsyncResult<TMappedValue, TError | TMappedError> {
return new AsyncResult(
this.r.then((result) => {
if (result.isError()) {
return result as any;
}
return (fn(result.unwrapOrThrow())).promise();
}),
);
}
/**
* Try to map a function over the value of the result.
* If the function throws an error, the result will be a failure.
*/
tryMap<TMappedValue>(
fn: (value: TValue) => TMappedValue,
errMapper: (error: any) => TError,
): AsyncResult<TMappedValue, TError> {
return new AsyncResult(
this.r.then((result) => result.tryMap(fn, errMapper)),
);
}
/**
* Map a function over the error of the result.
*/
mapError<TMappedError extends TaggedError>(
fn: (error: TError) => TMappedError,
): AsyncResult<TValue, TMappedError> {
return new AsyncResult(
this.r.then((result) => result.mapError(fn)),
);
}
/**
* Taps a function if the result is a success.
* This is useful for side effects that do not modify the result.
*/
tap(fn: (value: TValue) => void): AsyncResult<TValue, TError> {
return new AsyncResult(
this.r.then((result) => result.tap(fn)),
);
} }
} }

View File

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

View File

@ -4,7 +4,7 @@ import type { AsyncResult } from "../result/async-result.ts";
export namespace Run { export namespace Run {
// prettier-ignore // prettier-ignore
export function seq< export async function seq<
T1, T1,
TE1 extends TaggedError, TE1 extends TaggedError,
T2, T2,
@ -14,7 +14,7 @@ export namespace Run {
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,
): AsyncResult<T2, TE1 | TE2>; ): AsyncResult<T2, TE1 | TE2>;
// prettier-ignore // prettier-ignore
export function seq< export async function seq<
T1, T1,
TE1 extends TaggedError, TE1 extends TaggedError,
T2, T2,
@ -27,7 +27,7 @@ export namespace Run {
fn3: (value: T2) => AsyncResult<T3, TE3>, fn3: (value: T2) => AsyncResult<T3, TE3>,
): AsyncResult<T3, TE1 | TE2 | TE3>; ): AsyncResult<T3, TE1 | TE2 | TE3>;
// prettier-ignore // prettier-ignore
export function seq< export async function seq<
T1, T1,
TE1 extends TaggedError, TE1 extends TaggedError,
T2, T2,
@ -42,20 +42,24 @@ export namespace Run {
fn3: (value: T2) => AsyncResult<T3, TE3>, fn3: (value: T2) => AsyncResult<T3, TE3>,
fn4: (value: T3) => AsyncResult<T4, TE4>, fn4: (value: T3) => AsyncResult<T4, TE4>,
): AsyncResult<T4, TE1 | TE2 | TE3 | TE4>; ): AsyncResult<T4, TE1 | TE2 | TE3 | TE4>;
export function seq( export async function seq(
...fns: ((...args: any[]) => AsyncResult<any, any>)[] ...fns: ((...args: any[]) => AsyncResult<any, any>)[]
): AsyncResult<any, any> { ): AsyncResult<any, any> {
let result = fns[0]!(); let result = await fns[0]!();
for (let i = 1; i < fns.length; i++) { for (let i = 1; i < fns.length; i++) {
result = result.flatMap((value) => fns[i]!(value)); if (result.isError()) {
return result;
}
result = await fns[i]!(result.unwrapOrThrow());
} }
return result; return result;
} }
// prettier-ignore // prettier-ignore
export function seqOrThrow< export async function seqUNSAFE<
T1, T1,
TE1 extends TaggedError, TE1 extends TaggedError,
T2, T2,
@ -65,7 +69,7 @@ export namespace Run {
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,
): Promise<T2>; ): Promise<T2>;
// prettier-ignore // prettier-ignore
export function seqOrThrow< export async function seqUNSAFE<
T1, T1,
TE1 extends TaggedError, TE1 extends TaggedError,
T2, T2,
@ -77,11 +81,21 @@ export namespace Run {
fn2: (value: T1) => AsyncResult<T2, TE2>, fn2: (value: T1) => AsyncResult<T2, TE2>,
fn3: (value: T2) => AsyncResult<T3, TE3>, fn3: (value: T2) => AsyncResult<T3, TE3>,
): Promise<T2>; ): Promise<T2>;
export function seqOrThrow( export async function seqUNSAFE(
...fns: ((...args: any[]) => AsyncResult<any, any>)[] ...fns: ((...args: any[]) => AsyncResult<any, any>)[]
): Promise<any> { ): Promise<any> {
const result = (seq as any)(...fns); const result = await (seq as any)(...fns);
if (result.isError()) {
throw result.unwrapOrThrow();
}
return result.unwrapOrThrow(); return result.unwrapOrThrow();
} }
export async function UNSAFE<T, TError extends TaggedError>(
fn: () => AsyncResult<T, TError>,
): Promise<T> {
return (await fn()).unwrapOrThrow();
}
} }

View File

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

View File

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

View File

@ -1,10 +1,5 @@
// deno-lint-ignore-file no-explicit-any // deno-lint-ignore-file no-explicit-any
import { import type { AsyncResult, Keyof, Optional } from "@fabric/core";
type AsyncResult,
type Keyof,
type Optional,
TaggedError,
} from "@fabric/core";
import type { StoreQueryError } from "../../errors/query-error.ts"; import type { StoreQueryError } from "../../errors/query-error.ts";
import type { FilterOptions } from "./filter-options.ts"; import type { FilterOptions } from "./filter-options.ts";
import type { OrderByOptions } from "./order-by-options.ts"; import type { OrderByOptions } from "./order-by-options.ts";
@ -23,11 +18,6 @@ export interface StoreQuery<T> {
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
selectOneOrFail(): AsyncResult<T, StoreQueryError | NotFoundError>;
selectOneOrFail<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError | NotFoundError>;
} }
export interface StoreSortableQuery<T> { export interface StoreSortableQuery<T> {
@ -43,11 +33,6 @@ export interface StoreSortableQuery<T> {
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
selectOneOrFail(): AsyncResult<T, StoreQueryError | NotFoundError>;
selectOneOrFail<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError | NotFoundError>;
} }
export interface StoreLimitableQuery<T> { export interface StoreLimitableQuery<T> {
@ -62,11 +47,6 @@ export interface StoreLimitableQuery<T> {
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
selectOneOrFail(): AsyncResult<T, StoreQueryError | NotFoundError>;
selectOneOrFail<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError | NotFoundError>;
} }
export interface SelectableQuery<T> { export interface SelectableQuery<T> {
@ -79,14 +59,9 @@ export interface SelectableQuery<T> {
selectOne<K extends Keyof<T>>( selectOne<K extends Keyof<T>>(
keys: K[], keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>; ): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
selectOneOrFail(): AsyncResult<T, StoreQueryError | NotFoundError>;
selectOneOrFail<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError | NotFoundError>;
} }
export interface StoreQueryDefinition<K extends string = string> { export interface QueryDefinition<K extends string = string> {
from: K; from: K;
where?: FilterOptions<any>; where?: FilterOptions<any>;
orderBy?: OrderByOptions<any>; orderBy?: OrderByOptions<any>;
@ -94,9 +69,3 @@ export interface StoreQueryDefinition<K extends string = string> {
offset?: number; offset?: number;
keys?: string[]; keys?: string[];
} }
export class NotFoundError extends TaggedError<"NotFoundError"> {
constructor() {
super("NotFoundError");
}
}

View File

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

View File

@ -1,37 +0,0 @@
// deno-lint-ignore-file no-explicit-any
import type { TaggedError } from "@fabric/core";
import type { UseCase } from "./use-case.ts";
export type Command<
TDependencies = any,
TPayload = any,
TEvent extends Event = any,
TErrors extends TaggedError<string> = any,
> = BasicCommandDefinition<TDependencies, TPayload, TEvent, TErrors>;
interface BasicCommandDefinition<
TDependencies,
TPayload,
TEvent extends Event,
TErrors extends TaggedError<string>,
> {
/**
* The use case name.
*/
name: string;
/**
* Whether the use case requires authentication or not.
*/
isAuthRequired: boolean;
/**
* Permissions required to execute the use case.
*/
permissions?: string[];
/**
* The use case function.
*/
useCase: UseCase<TDependencies, TPayload, TEvent, TErrors>;
}

View File

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

View File

@ -2,14 +2,14 @@
import type { TaggedError } from "@fabric/core"; import type { TaggedError } from "@fabric/core";
import type { UseCase } from "./use-case.ts"; import type { UseCase } from "./use-case.ts";
export type Query< export type UseCaseDefinition<
TDependencies = any, TDependencies = any,
TPayload = any, TPayload = any,
TOutput = any, TOutput = any,
TErrors extends TaggedError<string> = any, TErrors extends TaggedError<string> = any,
> = BasicQueryDefinition<TDependencies, TPayload, TOutput, TErrors>; > = BasicUseCaseDefinition<TDependencies, TPayload, TOutput, TErrors>;
interface BasicQueryDefinition< interface BasicUseCaseDefinition<
TDependencies, TDependencies,
TPayload, TPayload,
TOutput, TOutput,
@ -25,11 +25,6 @@ interface BasicQueryDefinition<
*/ */
isAuthRequired: boolean; isAuthRequired: boolean;
/**
* Permissions required to execute the use case.
*/
permissions?: string[];
/** /**
* The use case function. * The use case function.
*/ */

View File

@ -1,4 +1,4 @@
import { PosixDate } from "@fabric/core"; import { PosixDate, Run } from "@fabric/core";
import { Event } from "@fabric/domain"; import { Event } from "@fabric/domain";
import { UUIDGeneratorMock } from "@fabric/domain/mocks"; import { UUIDGeneratorMock } from "@fabric/domain/mocks";
import { import {
@ -22,11 +22,11 @@ describe("Event Store", () => {
beforeEach(async () => { beforeEach(async () => {
store = new SQLiteEventStore(":memory:"); store = new SQLiteEventStore(":memory:");
await store.migrate().orThrow(); await Run.UNSAFE(() => store.migrate());
}); });
afterEach(async () => { afterEach(async () => {
await store.close().orThrow(); await Run.UNSAFE(() => store.close());
}); });
test("Should append an event", async () => { test("Should append an event", async () => {
@ -39,9 +39,9 @@ describe("Event Store", () => {
payload: { name: "test" }, payload: { name: "test" },
}; };
await store.append(userCreated).orThrow(); await Run.UNSAFE(() => store.append(userCreated));
const events = await store.getEventsFromStream(newUUID).unwrapOrThrow(); const events = await Run.UNSAFE(() => store.getEventsFromStream(newUUID));
expect(events).toHaveLength(1); expect(events).toHaveLength(1);
@ -69,7 +69,7 @@ describe("Event Store", () => {
store.subscribe(["UserCreated"], subscriber); store.subscribe(["UserCreated"], subscriber);
await store.append(userCreated).orThrow(); await Run.UNSAFE(() => store.append(userCreated));
expect(subscriber).toHaveBeenCalledTimes(1); expect(subscriber).toHaveBeenCalledTimes(1);
expect(subscriber).toHaveBeenCalledWith({ expect(subscriber).toHaveBeenCalledWith({

View File

@ -89,7 +89,10 @@ export class SQLiteEventStore<TEvents extends Event>
}), }),
(version) => this.storeEvent(event.streamId, version + 1n, event), (version) => this.storeEvent(event.streamId, version + 1n, event),
(storedEvent) => (storedEvent) =>
this.notifySubscribers(storedEvent).map(() => storedEvent), AsyncResult.from(async () => {
await this.notifySubscribers(storedEvent);
return storedEvent;
}),
); );
} }

View File

@ -5,14 +5,13 @@ import {
FilterOptions, FilterOptions,
ModelSchema, ModelSchema,
OrderByOptions, OrderByOptions,
QueryDefinition,
SelectableQuery, SelectableQuery,
StoreLimitableQuery, StoreLimitableQuery,
StoreQuery, StoreQuery,
StoreQueryDefinition,
StoreQueryError, StoreQueryError,
StoreSortableQuery, StoreSortableQuery,
} from "@fabric/domain"; } from "@fabric/domain";
import { NotFoundError } from "../../domain/models/store-query/store-query.ts";
import { filterToParams, filterToSQL } from "../sqlite/filter-to-sql.ts"; import { filterToParams, filterToSQL } from "../sqlite/filter-to-sql.ts";
import { transformRow } from "../sqlite/sql-to-value.ts"; import { transformRow } from "../sqlite/sql-to-value.ts";
import { SQLiteDatabase } from "../sqlite/sqlite-database.ts"; import { SQLiteDatabase } from "../sqlite/sqlite-database.ts";
@ -21,7 +20,7 @@ export class QueryBuilder<T> implements StoreQuery<T> {
constructor( constructor(
private db: SQLiteDatabase, private db: SQLiteDatabase,
private schema: ModelSchema, private schema: ModelSchema,
private query: StoreQueryDefinition, private query: QueryDefinition,
) {} ) {}
where(where: FilterOptions<T>): StoreSortableQuery<T> { where(where: FilterOptions<T>): StoreSortableQuery<T> {
@ -94,42 +93,11 @@ export class QueryBuilder<T> implements StoreQuery<T> {
(err) => new StoreQueryError(err.message), (err) => new StoreQueryError(err.message),
); );
} }
selectOneOrFail(): AsyncResult<T, StoreQueryError | NotFoundError>;
selectOneOrFail<K extends Extract<keyof T, string>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError | NotFoundError>;
selectOneOrFail<K extends Extract<keyof T, string>>(
keys?: K[],
): AsyncResult<any, StoreQueryError | NotFoundError> {
return AsyncResult.tryFrom(
async () => {
const [stmt, params] = getSelectStatement(
this.schema[this.query.from]!,
{
...this.query,
keys: keys!,
limit: 1,
},
);
const result = await this.db.onePrepared(
stmt,
params,
transformRow(this.schema[this.query.from]!),
);
if (!result) {
throw new NotFoundError();
}
return result;
},
(err) => new StoreQueryError(err.message),
);
}
} }
export function getSelectStatement( export function getSelectStatement(
collection: Collection, collection: Collection,
query: StoreQueryDefinition, query: QueryDefinition,
): [string, Record<string, any>] { ): [string, Record<string, any>] {
const selectFields = query.keys ? query.keys.join(", ") : "*"; const selectFields = query.keys ? query.keys.join(", ") : "*";

View File

@ -26,37 +26,41 @@ describe("State Store", () => {
beforeEach(async () => { beforeEach(async () => {
store = new SQLiteStateStore(":memory:", models); store = new SQLiteStateStore(":memory:", models);
await store.migrate().orThrow(); await Run.UNSAFE(() => store.migrate());
}); });
afterEach(async () => { afterEach(async () => {
await store.close().orThrow(); await Run.UNSAFE(() => store.close());
}); });
test("should insert a record", async () => { test("should insert a record", async () => {
const newUUID = UUIDGeneratorMock.generate(); const newUUID = UUIDGeneratorMock.generate();
await store.insertInto("users", { await Run.UNSAFE(() =>
id: newUUID, store.insertInto("users", {
name: "test", id: newUUID,
streamId: newUUID, name: "test",
streamVersion: 1n, streamId: newUUID,
deletedAt: null, streamVersion: 1n,
}).orThrow(); deletedAt: null,
})
);
}); });
test("should select all records", async () => { test("should select all records", async () => {
const newUUID = UUIDGeneratorMock.generate(); const newUUID = UUIDGeneratorMock.generate();
await store.insertInto("users", { await Run.UNSAFE(() =>
name: "test", store.insertInto("users", {
id: newUUID, name: "test",
streamId: newUUID, id: newUUID,
streamVersion: 1n, streamId: newUUID,
deletedAt: null, streamVersion: 1n,
}).orThrow(); deletedAt: null,
})
);
const result = await store.from("users").select().unwrapOrThrow(); const result = await Run.UNSAFE(() => store.from("users").select());
expectTypeOf(result).toEqualTypeOf< expectTypeOf(result).toEqualTypeOf<
{ {
@ -82,7 +86,7 @@ describe("State Store", () => {
test("should select records with a filter", async () => { test("should select records with a filter", async () => {
const newUUID = UUIDGeneratorMock.generate(); const newUUID = UUIDGeneratorMock.generate();
await Run.seqOrThrow( await Run.seqUNSAFE(
() => () =>
store.insertInto("users", { store.insertInto("users", {
name: "test", name: "test",
@ -109,12 +113,14 @@ describe("State Store", () => {
}), }),
); );
const result = await store const result = await Run.UNSAFE(() =>
.from("users") store
.where({ .from("users")
name: isLike("te%"), .where({
}) name: isLike("te%"),
.select().unwrapOrThrow(); })
.select()
);
expectTypeOf(result).toEqualTypeOf< expectTypeOf(result).toEqualTypeOf<
{ {
@ -140,20 +146,25 @@ describe("State Store", () => {
test("should update a record", async () => { test("should update a record", async () => {
const newUUID = UUIDGeneratorMock.generate(); const newUUID = UUIDGeneratorMock.generate();
await store.insertInto("users", { await Run.UNSAFE(() =>
name: "test", store.insertInto("users", {
id: newUUID, name: "test",
streamId: newUUID, id: newUUID,
streamVersion: 1n, streamId: newUUID,
deletedAt: null, streamVersion: 1n,
}).orThrow(); deletedAt: null,
})
);
await store.update("users", newUUID, { await Run.UNSAFE(() =>
name: "updated", store.update("users", newUUID, {
}).orThrow(); name: "updated",
})
);
const result = await store.from("users").where({ id: newUUID }).selectOne() const result = await Run.UNSAFE(() =>
.unwrapOrThrow(); store.from("users").where({ id: newUUID }).selectOne()
);
expect(result).toEqual({ expect(result).toEqual({
id: newUUID, id: newUUID,
@ -167,18 +178,21 @@ describe("State Store", () => {
test("should delete a record", async () => { test("should delete a record", async () => {
const newUUID = UUIDGeneratorMock.generate(); const newUUID = UUIDGeneratorMock.generate();
await store.insertInto("users", { await Run.UNSAFE(() =>
name: "test", store.insertInto("users", {
id: newUUID, name: "test",
streamId: newUUID, id: newUUID,
streamVersion: 1n, streamId: newUUID,
deletedAt: null, streamVersion: 1n,
}).orThrow(); deletedAt: null,
})
);
await store.delete("users", newUUID).orThrow(); await Run.UNSAFE(() => store.delete("users", newUUID));
const result = await store.from("users").where({ id: newUUID }).selectOne() const result = await Run.UNSAFE(() =>
.unwrapOrThrow(); store.from("users").where({ id: newUUID }).selectOne()
);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
@ -189,21 +203,25 @@ describe("State Store", () => {
const newUUID = UUIDGeneratorMock.generate(); const newUUID = UUIDGeneratorMock.generate();
const ownerUUID = UUIDGeneratorMock.generate(); const ownerUUID = UUIDGeneratorMock.generate();
await store.insertInto("users", { await Run.UNSAFE(() =>
id: ownerUUID, store.insertInto("users", {
name: "test", id: ownerUUID,
streamId: ownerUUID, name: "test",
streamVersion: 1n, streamId: ownerUUID,
deletedAt: null, streamVersion: 1n,
}).orThrow(); deletedAt: null,
})
);
await store.insertInto("demo", { await Run.UNSAFE(() =>
id: newUUID, store.insertInto("demo", {
value: 1.0, id: newUUID,
owner: ownerUUID, value: 1.0,
streamId: newUUID, owner: ownerUUID,
streamVersion: 1n, streamId: newUUID,
deletedAt: null, streamVersion: 1n,
}).orThrow(); deletedAt: null,
})
);
}); });
}); });

View File

@ -1,5 +1,5 @@
import { Query } from "@fabric/domain"; import { UseCaseDefinition } from "@fabric/domain";
export const UseCases = [] as const satisfies Query[]; export const UseCases = [] as const satisfies UseCaseDefinition[];
export type UseCases = typeof UseCases; export type UseCases = typeof UseCases;