Format files and configure workspace formatting in vscode

This commit is contained in:
Pablo Baleztena 2024-10-16 01:53:38 -03:00
parent a79683d9d4
commit 4cc99aec91
45 changed files with 230 additions and 204 deletions

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"
}
}

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

@ -3,10 +3,8 @@ 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

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

View File

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

View File

@ -10,7 +10,7 @@ describe("Run", () => {
const result = await Run.seq(
async () => Result.succeedWith(1),
async (x) => Result.succeedWith(x + 1),
async (x) => Result.succeedWith(x * 2)
async (x) => Result.succeedWith(x * 2),
);
expect(result.unwrapOrThrow()).toEqual(4);
@ -20,7 +20,7 @@ describe("Run", () => {
const result = await Run.seq(
async () => Result.succeedWith(1),
async () => Result.failWith(new UnexpectedError()),
async (x) => Result.succeedWith(x * 2)
async (x) => Result.succeedWith(x * 2),
);
expect(result.isError()).toBe(true);

View File

@ -5,17 +5,22 @@ 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>,
@ -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>,
@ -80,7 +94,7 @@ export namespace Run {
}
export async function UNSAFE<T, TError extends TaggedError>(
fn: () => AsyncResult<T, TError>
fn: () => AsyncResult<T, TError>,
): Promise<T> {
return (await fn()).unwrapOrThrow();
}

View File

@ -22,8 +22,9 @@ export class PosixDate {
"timestamp" in value &&
value["type"] === "posix-date" &&
typeof value["timestamp"] === "number"
)
) {
return true;
}
return false;
}
}

View File

@ -31,5 +31,5 @@ Deno.test(
V2: (v) => v.b,
})
).toThrow("Non-exhaustive pattern match");
}
},
);

View File

@ -1,12 +1,16 @@
import type { Fn } from "../types/fn.ts";
import { type TaggedVariant, type VariantFromTag, VariantTag } from "./variant.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>;
};
export function match<const TVariant extends TaggedVariant<string>>(
v: TVariant
v: TVariant,
) {
return {
case<
@ -14,14 +18,14 @@ export function match<const TVariant extends TaggedVariant<string>>(
const TMatcher extends VariantMatcher<
TVariant,
TReturnType
> = VariantMatcher<TVariant, TReturnType>
> = VariantMatcher<TVariant, TReturnType>,
>(cases: TMatcher): TReturnType {
if (!(v[VariantTag] in cases)) {
throw new Error("Non-exhaustive pattern match");
}
return cases[v[VariantTag] as TVariant[VariantTag]](
v as Extract<TVariant, { [VariantTag]: TVariant[VariantTag] }>
v as Extract<TVariant, { [VariantTag]: TVariant[VariantTag] }>,
);
},
};

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

@ -15,21 +15,21 @@ export interface EventStore<TEvents extends Event> {
* Store a new event in the event store.
*/
append<T extends TEvents>(
event: T
event: T,
): AsyncResult<StoredEvent<T>, StoreQueryError>;
getEventsFromStream(
streamId: UUID
streamId: UUID,
): AsyncResult<StoredEvent<TEvents>[], StoreQueryError>;
subscribe<TEventKey extends TEvents[VariantTag]>(
events: TEventKey[],
subscriber: EventSubscriber<VariantFromTag<TEvents, TEventKey>>
subscriber: EventSubscriber<VariantFromTag<TEvents, TEventKey>>,
): void;
}
export type EventSubscriber<TEvents extends Event = Event> = (
event: StoredEvent<TEvents>
event: StoredEvent<TEvents>,
) => MaybePromise<void>;
export interface EventFilterOptions {

View File

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

View File

@ -5,7 +5,7 @@ import type { MimeType } from "./mime-type.ts";
*/
export function isMimeType<T extends MimeType>(
expectedMimeType: T,
actualMimeType: string
actualMimeType: string,
): actualMimeType is T {
return actualMimeType.match("^" + expectedMimeType + "$") !== null;
}

View File

@ -8,11 +8,10 @@ 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
opts: T = {} as T,
): DecimalField & T {
return {
[VariantTag]: "DecimalField",

View File

@ -5,12 +5,11 @@ import type { BaseField } from "./base-field.ts";
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,
T extends EmbeddedFieldOptions<K> = EmbeddedFieldOptions<K>
T extends EmbeddedFieldOptions<K> = EmbeddedFieldOptions<K>,
>(opts: T = {} as T): EmbeddedField & T {
return {
[VariantTag]: "EmbeddedField",

View File

@ -14,20 +14,21 @@ 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

@ -4,11 +4,10 @@ import type { BaseField } from "./base-field.ts";
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
opts: T = {} as T,
): FloatField & T {
return {
[VariantTag]: "FloatField",

View File

@ -7,11 +7,10 @@ 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
opts: T = {} as T,
): IntegerField & T {
return {
[VariantTag]: "IntegerField",

View File

@ -25,7 +25,7 @@ describe("Validate Reference Field", () => {
schema,
Field.reference({
targetModel: "foo",
})
}),
).unwrapErrorOrThrow();
expect(result).toBeInstanceOf(InvalidReferenceFieldError);
@ -36,7 +36,7 @@ describe("Validate Reference Field", () => {
schema,
Field.reference({
targetModel: "User",
})
}),
).unwrapOrThrow();
});
@ -46,7 +46,7 @@ describe("Validate Reference Field", () => {
Field.reference({
targetModel: "User",
targetKey: "foo",
})
}),
).unwrapErrorOrThrow();
expect(result).toBeInstanceOf(InvalidReferenceFieldError);
@ -58,7 +58,7 @@ describe("Validate Reference Field", () => {
Field.reference({
targetModel: "User",
targetKey: "otherNotUnique",
})
}),
).unwrapErrorOrThrow();
expect(result).toBeInstanceOf(InvalidReferenceFieldError);
@ -70,7 +70,7 @@ describe("Validate Reference Field", () => {
Field.reference({
targetModel: "User",
targetKey: "otherUnique",
})
}),
);
if (isError(result)) {

View File

@ -13,11 +13,10 @@ 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
opts: T = {} as T,
): ReferenceField & T {
return {
[VariantTag]: "ReferenceField",
@ -31,21 +30,21 @@ export function getTargetKey(field: ReferenceField): string {
export function validateReferenceField(
schema: ModelSchema,
field: ReferenceField
field: ReferenceField,
): Result<void, InvalidReferenceFieldError> {
if (!schema[field.targetModel]) {
return Result.failWith(
new InvalidReferenceFieldError(
`The target model '${field.targetModel}' is not in the schema.`
)
`The target model '${field.targetModel}' is not in the schema.`,
),
);
}
if (field.targetKey && !schema[field.targetModel]!.fields[field.targetKey]) {
return Result.failWith(
new InvalidReferenceFieldError(
`The target key '${field.targetKey}' is not in the target model '${field.targetModel}'.`
)
`The target key '${field.targetKey}' is not in the target model '${field.targetModel}'.`,
),
);
}
@ -55,15 +54,16 @@ export function validateReferenceField(
) {
return Result.failWith(
new InvalidReferenceFieldError(
`The target key '${field.targetModel}'.'${field.targetKey}' is not unique.`
)
`The target key '${field.targetModel}'.'${field.targetKey}' is not unique.`,
),
);
}
return Result.ok();
}
export class InvalidReferenceFieldError extends TaggedError<"InvalidReferenceField"> {
export class InvalidReferenceFieldError
extends TaggedError<"InvalidReferenceField"> {
constructor(readonly reason: string) {
super("InvalidReferenceField");
}

View File

@ -7,11 +7,10 @@ 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
opts: T = {} as T,
): StringField & T {
return {
[VariantTag]: "StringField",

View File

@ -4,11 +4,10 @@ import type { BaseField } from "./base-field.ts";
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
opts: T = {} as T,
): TimestampField & T {
return {
[VariantTag]: "TimestampField",

View File

@ -6,11 +6,10 @@ export interface UUIDFieldOptions extends BaseField {
}
export interface UUIDField
extends TaggedVariant<"UUIDField">,
UUIDFieldOptions {}
extends TaggedVariant<"UUIDField">, UUIDFieldOptions {}
export function createUUIDField<T extends UUIDFieldOptions>(
opts: T = {} as T
opts: T = {} as T,
): UUIDField & T {
return {
[VariantTag]: "UUIDField",

View File

@ -6,7 +6,7 @@ export type CustomModelFields = Record<string, FieldDefinition>;
export interface Collection<
TName extends string = string,
TFields extends CustomModelFields = CustomModelFields
TFields extends CustomModelFields = CustomModelFields,
> {
name: TName;
fields: TFields;
@ -24,14 +24,14 @@ export const DefaultModelFields = {
export interface Model<
TName extends string = string,
TFields extends CustomModelFields = CustomModelFields
TFields extends CustomModelFields = CustomModelFields,
> extends Collection<TName, TFields> {
fields: typeof DefaultModelFields & TFields;
}
export function defineModel<
TName extends string,
TFields extends CustomModelFields
TFields extends CustomModelFields,
>(name: TName, fields: TFields): Model<TName, TFields> {
return {
name,
@ -41,7 +41,7 @@ export function defineModel<
export function defineCollection<
TName extends string,
TFields extends CustomModelFields
TFields extends CustomModelFields,
>(name: TName, fields: TFields): Collection<TName, TFields> {
return {
name,

View File

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

@ -11,12 +11,12 @@ export interface StoreQuery<T> {
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
}
@ -26,12 +26,12 @@ export interface StoreSortableQuery<T> {
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
}
@ -40,24 +40,24 @@ export interface StoreLimitableQuery<T> {
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
}
export interface SelectableQuery<T> {
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
}

View File

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

View File

@ -8,6 +8,6 @@ export interface Projection<TModel extends Model, TEvents extends Event> {
events: TEvents[VariantTag][];
projection: (
event: StoredEvent<TEvents>,
model?: ModelToType<TModel>
model?: ModelToType<TModel>,
) => ModelToType<TModel>;
}

View File

@ -6,14 +6,14 @@ export type UseCaseDefinition<
TDependencies = any,
TPayload = any,
TOutput = any,
TErrors extends TaggedError<string> = any
TErrors extends TaggedError<string> = any,
> = BasicUseCaseDefinition<TDependencies, TPayload, TOutput, TErrors>;
interface BasicUseCaseDefinition<
TDependencies,
TPayload,
TOutput,
TErrors extends TaggedError<string>
TErrors extends TaggedError<string>,
> {
/**
* The use case name.

View File

@ -35,7 +35,7 @@ describe("sortByDependencies", () => {
sortByDependencies(array, {
keyGetter: (element) => element.name,
depGetter: (element) => element.dependencies,
}).unwrapErrorOrThrow()
}).unwrapErrorOrThrow(),
).toBeInstanceOf(CircularDependencyError);
});

View File

@ -9,7 +9,7 @@ export function sortByDependencies<T>(
}: {
keyGetter: (element: T) => string;
depGetter: (element: T) => string[];
}
},
): Result<T[], CircularDependencyError> {
const graph = new Map<string, string[]>();
const visited = new Set<string>();
@ -40,9 +40,9 @@ export function sortByDependencies<T>(
visit(key, []);
});
return sorted.map(
(key) => array.find((element) => keyGetter(element) === key) as T
(key) => array.find((element) => keyGetter(element) === key) as T,
);
},
(e) => e as CircularDependencyError
(e) => e as CircularDependencyError,
);
}

View File

@ -18,8 +18,7 @@ import {
import { SQLiteDatabase } from "../sqlite/sqlite-database.ts";
export class SQLiteEventStore<TEvents extends Event>
implements EventStore<TEvents>
{
implements EventStore<TEvents> {
private db: SQLiteDatabase;
private streamVersions = new Map<UUID, bigint>();
@ -46,15 +45,15 @@ export class SQLiteEventStore<TEvents extends Event>
timestamp NUMERIC NOT NULL,
payload TEXT NOT NULL,
UNIQUE(streamId, version)
)`
)`,
);
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
getEventsFromStream(
streamId: UUID
streamId: UUID,
): AsyncResult<StoredEvent<TEvents>[], StoreQueryError> {
return AsyncResult.tryFrom(
() => {
@ -70,16 +69,16 @@ export class SQLiteEventStore<TEvents extends Event>
version: BigInt(e.version),
timestamp: new PosixDate(e.timestamp),
payload: JSONUtils.parse(e.payload),
})
}),
);
return events;
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
append<T extends TEvents>(
event: T
event: T,
): AsyncResult<StoredEvent<T>, StoreQueryError> {
return Run.seq(
() => this.getLastVersion(event.streamId),
@ -93,7 +92,7 @@ export class SQLiteEventStore<TEvents extends Event>
AsyncResult.from(async () => {
await this.notifySubscribers(storedEvent);
return storedEvent;
})
}),
);
}
@ -111,20 +110,20 @@ export class SQLiteEventStore<TEvents extends Event>
`SELECT max(version) as lastVersion FROM events WHERE streamId = $id`,
{
$id: streamId,
}
},
);
return !lastVersion ? 0n : BigInt(lastVersion);
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
subscribe<TEventKey extends TEvents[VariantTag]>(
eventNames: TEventKey[],
subscriber: (
event: StoredEvent<EventFromKey<TEvents, TEventKey>>
) => MaybePromise<void>
event: StoredEvent<EventFromKey<TEvents, TEventKey>>,
) => MaybePromise<void>,
): void {
eventNames.forEach((event) => {
const subscribers = this.eventSubscribers.get(event) || [];
@ -139,14 +138,14 @@ export class SQLiteEventStore<TEvents extends Event>
close(): AsyncResult<void, StoreQueryError> {
return AsyncResult.tryFrom(
() => this.db.close(),
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
private storeEvent<T extends Event>(
streamId: UUID,
version: bigint,
event: T
event: T,
): AsyncResult<StoredEvent<T>, StoreQueryError> {
return AsyncResult.tryFrom(
() => {
@ -165,11 +164,11 @@ export class SQLiteEventStore<TEvents extends Event>
$version: storedEvent.version.toString(),
$timestamp: storedEvent.timestamp.timestamp,
$payload: JSON.stringify(storedEvent.payload),
}
},
);
return storedEvent;
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
}

View File

@ -135,7 +135,7 @@ describe("SQL where clause from filter options", () => {
const result = filterToSQL(opts);
const params = filterToParams(col, opts);
expect(result).toEqual(
"WHERE (name IN ($where_name_0_0,$where_name_0_1) AND age > $where_age_0) OR (status <> $where_status_1 AND salary > $where_salary_1) OR (rating < $where_rating_2 AND quantity >= $where_quantity_2)"
"WHERE (name IN ($where_name_0_0,$where_name_0_1) AND age > $where_age_0) OR (status <> $where_status_1 AND salary > $where_salary_1) OR (rating < $where_rating_2 AND quantity >= $where_quantity_2)",
);
expect(params).toEqual({
$where_name_0_0: "John",

View File

@ -16,20 +16,22 @@ import { fieldValueToSQL } from "./value-to-sql.ts";
export function filterToSQL(filterOptions?: FilterOptions) {
if (!filterOptions) return "";
if (Array.isArray(filterOptions))
if (Array.isArray(filterOptions)) {
return `WHERE ${getWhereFromMultiOption(filterOptions)}`;
}
return `WHERE ${getWhereFromSingleOption(filterOptions)}`;
}
export function filterToParams(
collection: Collection,
filterOptions?: FilterOptions
filterOptions?: FilterOptions,
) {
if (!filterOptions) return {};
if (Array.isArray(filterOptions))
if (Array.isArray(filterOptions)) {
return getParamsFromMultiFilterOption(collection, filterOptions);
}
return getParamsFromSingleFilterOption(collection, filterOptions);
}
@ -38,14 +40,14 @@ function getWhereFromMultiOption(filterOptions: MultiFilterOption) {
return filterOptions
.map(
(option, i) =>
`(${getWhereFromSingleOption(option, { postfix: `_${i}` })})`
`(${getWhereFromSingleOption(option, { postfix: `_${i}` })})`,
)
.join(" OR ");
}
function getWhereFromSingleOption(
filterOptions: SingleFilterOption,
opts: { postfix?: string } = {}
opts: { postfix?: string } = {},
) {
return Object.entries(filterOptions)
.map(([key, value]) => getWhereFromKeyValue(key, value, opts))
@ -61,7 +63,7 @@ function getWhereParamKey(key: string, opts: { postfix?: string } = {}) {
function getWhereFromKeyValue(
key: string,
value: FilterValue,
opts: { postfix?: string } = {}
opts: { postfix?: string } = {},
) {
if (value == undefined) {
return `${key} IS NULL`;
@ -73,19 +75,25 @@ function getWhereFromKeyValue(
}
if (value[FILTER_OPTION_TYPE_SYMBOL] === "in") {
return `${key} IN (${value[FILTER_OPTION_VALUE_SYMBOL].map(
return `${key} IN (${
value[FILTER_OPTION_VALUE_SYMBOL].map(
(_v: any, i: number) =>
`${getWhereParamKey(key, {
`${
getWhereParamKey(key, {
postfix: opts.postfix ? `${opts.postfix}_${i}` : `_${i}`,
})}`
).join(",")})`;
})
}`,
).join(",")
})`;
}
if (value[FILTER_OPTION_TYPE_SYMBOL] === "comparison") {
return `${key} ${value[FILTER_OPTION_OPERATOR_SYMBOL]} ${getWhereParamKey(
return `${key} ${value[FILTER_OPTION_OPERATOR_SYMBOL]} ${
getWhereParamKey(
key,
opts
)}`;
opts,
)
}`;
}
}
return `${key} = ${getWhereParamKey(key, opts)}`;
@ -93,7 +101,7 @@ function getWhereFromKeyValue(
function getParamsFromMultiFilterOption(
collection: Collection,
filterOptions: MultiFilterOption
filterOptions: MultiFilterOption,
) {
return filterOptions.reduce(
(acc, filterOption, i) => ({
@ -102,14 +110,14 @@ function getParamsFromMultiFilterOption(
postfix: `_${i}`,
}),
}),
{}
{},
);
}
function getParamsFromSingleFilterOption(
collection: Collection,
filterOptions: SingleFilterOption,
opts: { postfix?: string } = {}
opts: { postfix?: string } = {},
) {
return Object.entries(filterOptions)
.filter(([, value]) => {
@ -122,10 +130,10 @@ function getParamsFromSingleFilterOption(
collection.fields[key]!,
key,
value,
opts
opts,
),
}),
{}
{},
);
}
@ -147,7 +155,7 @@ function getParamsForFilterKeyValue(
field: FieldDefinition,
key: string,
value: FilterValue,
opts: { postfix?: string } = {}
opts: { postfix?: string } = {},
) {
if (typeof value === "object") {
if (value[FILTER_OPTION_TYPE_SYMBOL] === "in") {
@ -155,12 +163,14 @@ function getParamsForFilterKeyValue(
(acc: Record<string, any>, _: any, i: number) => {
return {
...acc,
[getWhereParamKey(key, {
[
getWhereParamKey(key, {
postfix: opts.postfix ? `${opts.postfix}_${i}` : `_${i}`,
})]: value[FILTER_OPTION_VALUE_SYMBOL][i],
})
]: value[FILTER_OPTION_VALUE_SYMBOL][i],
};
},
{}
{},
);
}
}

View File

@ -16,7 +16,7 @@ describe("ModelToSQL", () => {
const result = modelToSql(model);
expect(result).toEqual(
`CREATE TABLE something (id TEXT PRIMARY KEY, name TEXT NOT NULL, age INTEGER NOT NULL, date NUMERIC NOT NULL, reference TEXT NOT NULL REFERENCES somethingElse(id))`
`CREATE TABLE something (id TEXT PRIMARY KEY, name TEXT NOT NULL, age INTEGER NOT NULL, date NUMERIC NOT NULL, reference TEXT NOT NULL REFERENCES somethingElse(id))`,
);
});
});

View File

@ -5,7 +5,7 @@ import { Collection, FieldDefinition, getTargetKey } from "@fabric/domain";
type FieldSQLDefinitionMap = {
[K in FieldDefinition[VariantTag]]: (
name: string,
field: Extract<FieldDefinition, { [VariantTag]: K }>
field: Extract<FieldDefinition, { [VariantTag]: K }>,
) => string;
};
@ -61,7 +61,7 @@ function modifiersFromOpts(field: FieldDefinition) {
}
export function modelToSql(
model: Collection<string, Record<string, FieldDefinition>>
model: Collection<string, Record<string, FieldDefinition>>,
) {
const fields = Object.entries(model.fields)
.map(([name, type]) => fieldDefinitionToSQL(name, type))

View File

@ -29,7 +29,7 @@ export function recordToSQLParams(model: Model, record: Record<string, any>) {
...acc,
[keyToParam(key)]: fieldValueToSQL(model.fields[key]!, record[key]),
}),
{}
{},
);
}

View File

@ -24,7 +24,7 @@ function valueFromSQL(field: FieldDefinition, value: any): any {
type FieldSQLInsertMap = {
[K in FieldDefinition[VariantTag]]: (
field: Extract<FieldDefinition, { [VariantTag]: K }>,
value: any
value: any,
) => FieldToType<Extract<FieldDefinition, { [VariantTag]: K }>>;
};
const FieldSQLInsertMap: FieldSQLInsertMap = {

View File

@ -44,7 +44,7 @@ export class SQLiteDatabase {
allPrepared(
sql: string,
params?: Record<string, any>,
transformer?: (row: any) => any
transformer?: (row: any) => any,
) {
const cachedStmt = this.getCachedStatement(sql);
@ -56,7 +56,7 @@ export class SQLiteDatabase {
onePrepared(
sql: string,
params?: Record<string, any>,
transformer?: (row: any) => any
transformer?: (row: any) => any,
) {
const cachedStmt = this.getCachedStatement(sql);

View File

@ -5,7 +5,7 @@ import { FieldDefinition, FieldToType } from "@fabric/domain";
type FieldSQLInsertMap = {
[K in FieldDefinition[VariantTag]]: (
field: Extract<FieldDefinition, { [VariantTag]: K }>,
value: FieldToType<Extract<FieldDefinition, { [VariantTag]: K }>>
value: FieldToType<Extract<FieldDefinition, { [VariantTag]: K }>>,
) => any;
};
const FieldSQLInsertMap: FieldSQLInsertMap = {

View File

@ -20,7 +20,7 @@ export class QueryBuilder<T> implements StoreQuery<T> {
constructor(
private db: SQLiteDatabase,
private schema: ModelSchema,
private query: QueryDefinition
private query: QueryDefinition,
) {}
where(where: FilterOptions<T>): StoreSortableQuery<T> {
@ -47,7 +47,7 @@ export class QueryBuilder<T> implements StoreQuery<T> {
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
select<K extends Keyof<T>>(keys?: K[]): AsyncResult<any, StoreQueryError> {
return AsyncResult.tryFrom(
@ -57,21 +57,21 @@ export class QueryBuilder<T> implements StoreQuery<T> {
{
...this.query,
keys: keys!,
}
},
);
return this.db.allPrepared(
sql,
params,
transformRow(this.schema[this.query.from]!)
transformRow(this.schema[this.query.from]!),
);
},
(err) => new StoreQueryError(err.message)
(err) => new StoreQueryError(err.message),
);
}
selectOne(): AsyncResult<Optional<T>, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[]
keys: K[],
): AsyncResult<Optional<Pick<T, K>>, StoreQueryError>;
selectOne<K extends Keyof<T>>(keys?: K[]): AsyncResult<any, StoreQueryError> {
return AsyncResult.tryFrom(
@ -82,22 +82,22 @@ export class QueryBuilder<T> implements StoreQuery<T> {
...this.query,
keys: keys!,
limit: 1,
}
},
);
return await this.db.onePrepared(
stmt,
params,
transformRow(this.schema[this.query.from]!)
transformRow(this.schema[this.query.from]!),
);
},
(err) => new StoreQueryError(err.message)
(err) => new StoreQueryError(err.message),
);
}
}
export function getSelectStatement(
collection: Collection,
query: QueryDefinition
query: QueryDefinition,
): [string, Record<string, any>] {
const selectFields = query.keys ? query.keys.join(", ") : "*";

View File

@ -110,7 +110,7 @@ describe("State Store", () => {
streamId: UUIDGeneratorMock.generate(),
streamVersion: 1n,
deletedAt: null,
})
}),
);
const result = await Run.UNSAFE(() =>

View File

@ -20,8 +20,7 @@ import { SQLiteDatabase } from "../sqlite/sqlite-database.ts";
import { QueryBuilder } from "./query-builder.ts";
export class SQLiteStateStore<TModel extends Model>
implements WritableStateStore<TModel>
{
implements WritableStateStore<TModel> {
private schema: ModelSchemaFromModels<TModel>;
private db: SQLiteDatabase;
@ -38,25 +37,27 @@ export class SQLiteStateStore<TModel extends Model>
insertInto<T extends keyof ModelSchemaFromModels<TModel>>(
collection: T,
record: ModelToType<ModelSchemaFromModels<TModel>[T]>
record: ModelToType<ModelSchemaFromModels<TModel>[T]>,
): AsyncResult<void, StoreQueryError> {
const model = this.schema[collection];
return AsyncResult.tryFrom(
() => {
this.db.runPrepared(
`INSERT INTO ${model.name} (${recordToSQLKeys(
record
)}) VALUES (${recordToSQLKeyParams(record)})`,
recordToSQLParams(model, record)
`INSERT INTO ${model.name} (${
recordToSQLKeys(
record,
)
}) VALUES (${recordToSQLKeyParams(record)})`,
recordToSQLParams(model, record),
);
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
from<T extends keyof ModelSchemaFromModels<TModel>>(
collection: T
collection: T,
): StoreQuery<ModelToType<ModelSchemaFromModels<TModel>[T]>> {
return new QueryBuilder(this.db, this.schema, {
from: collection,
@ -66,7 +67,7 @@ export class SQLiteStateStore<TModel extends Model>
update<T extends keyof ModelSchemaFromModels<TModel>>(
collection: T,
id: UUID,
record: Partial<ModelToType<ModelSchemaFromModels<TModel>[T]>>
record: Partial<ModelToType<ModelSchemaFromModels<TModel>[T]>>,
): AsyncResult<void, StoreQueryError> {
const model = this.schema[collection];
@ -77,19 +78,21 @@ export class SQLiteStateStore<TModel extends Model>
id,
});
this.db.runPrepared(
`UPDATE ${model.name} SET ${recordToSQLSet(
record
)} WHERE id = ${keyToParam("id")}`,
params
`UPDATE ${model.name} SET ${
recordToSQLSet(
record,
)
} WHERE id = ${keyToParam("id")}`,
params,
);
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
delete<T extends keyof ModelSchemaFromModels<TModel>>(
collection: T,
id: UUID
id: UUID,
): AsyncResult<void, StoreQueryError> {
const model = this.schema[collection];
@ -97,10 +100,10 @@ export class SQLiteStateStore<TModel extends Model>
() => {
this.db.runPrepared(
`DELETE FROM ${model.name} WHERE id = ${keyToParam("id")}`,
{ $id: id }
{ $id: id },
);
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}
@ -116,7 +119,7 @@ export class SQLiteStateStore<TModel extends Model>
}
});
},
(error) => new StoreQueryError(error.message)
(error) => new StoreQueryError(error.message),
);
}