chore: migrate monorepo to deno 2 #3
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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -22,8 +22,9 @@ export class PosixDate {
|
||||
"timestamp" in value &&
|
||||
value["type"] === "posix-date" &&
|
||||
typeof value["timestamp"] === "number"
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,5 +31,5 @@ Deno.test(
|
||||
V2: (v) => v.b,
|
||||
})
|
||||
).toThrow("Non-exhaustive pattern match");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -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] }>,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 }>;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -14,23 +14,24 @@ 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>
|
||||
: TField["hasArbitraryPrecision"] extends false
|
||||
? MaybeOptional<TField, number>
|
||||
: MaybeOptional<TField, number | bigint>;
|
||||
type IntegerFieldToType<TField extends IntegerField> =
|
||||
TField["hasArbitraryPrecision"] extends true ? MaybeOptional<TField, bigint>
|
||||
: TField["hasArbitraryPrecision"] extends false
|
||||
? MaybeOptional<TField, number>
|
||||
: MaybeOptional<TField, number | bigint>;
|
||||
|
||||
type MaybeOptional<TField, TType> = TField extends { isOptional: true }
|
||||
? TType | null
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -20,11 +20,10 @@ 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
|
||||
? {
|
||||
[FILTER_OPTION_TYPE_SYMBOL]: "like";
|
||||
[FILTER_OPTION_VALUE_SYMBOL]: string;
|
||||
}
|
||||
export type LikeFilterOption<T> = T extends string ? {
|
||||
[FILTER_OPTION_TYPE_SYMBOL]: "like";
|
||||
[FILTER_OPTION_VALUE_SYMBOL]: string;
|
||||
}
|
||||
: never;
|
||||
|
||||
export interface InFilterOption<T> {
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -11,6 +11,6 @@ export type UseCase<
|
||||
> = TPayload extends undefined
|
||||
? (dependencies: TDependencies) => AsyncResult<TOutput, TErrors>
|
||||
: (
|
||||
dependencies: TDependencies,
|
||||
payload: TPayload,
|
||||
) => AsyncResult<TOutput, TErrors>;
|
||||
dependencies: TDependencies,
|
||||
payload: TPayload,
|
||||
) => AsyncResult<TOutput, TErrors>;
|
||||
|
||||
@ -35,7 +35,7 @@ describe("sortByDependencies", () => {
|
||||
sortByDependencies(array, {
|
||||
keyGetter: (element) => element.name,
|
||||
depGetter: (element) => element.dependencies,
|
||||
}).unwrapErrorOrThrow()
|
||||
}).unwrapErrorOrThrow(),
|
||||
).toBeInstanceOf(CircularDependencyError);
|
||||
});
|
||||
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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(
|
||||
(_v: any, i: number) =>
|
||||
`${getWhereParamKey(key, {
|
||||
postfix: opts.postfix ? `${opts.postfix}_${i}` : `_${i}`,
|
||||
})}`
|
||||
).join(",")})`;
|
||||
return `${key} IN (${
|
||||
value[FILTER_OPTION_VALUE_SYMBOL].map(
|
||||
(_v: any, i: number) =>
|
||||
`${
|
||||
getWhereParamKey(key, {
|
||||
postfix: opts.postfix ? `${opts.postfix}_${i}` : `_${i}`,
|
||||
})
|
||||
}`,
|
||||
).join(",")
|
||||
})`;
|
||||
}
|
||||
|
||||
if (value[FILTER_OPTION_TYPE_SYMBOL] === "comparison") {
|
||||
return `${key} ${value[FILTER_OPTION_OPERATOR_SYMBOL]} ${getWhereParamKey(
|
||||
key,
|
||||
opts
|
||||
)}`;
|
||||
return `${key} ${value[FILTER_OPTION_OPERATOR_SYMBOL]} ${
|
||||
getWhereParamKey(
|
||||
key,
|
||||
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, {
|
||||
postfix: opts.postfix ? `${opts.postfix}_${i}` : `_${i}`,
|
||||
})]: value[FILTER_OPTION_VALUE_SYMBOL][i],
|
||||
[
|
||||
getWhereParamKey(key, {
|
||||
postfix: opts.postfix ? `${opts.postfix}_${i}` : `_${i}`,
|
||||
})
|
||||
]: value[FILTER_OPTION_VALUE_SYMBOL][i],
|
||||
};
|
||||
},
|
||||
{}
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -29,7 +29,7 @@ export function recordToSQLParams(model: Model, record: Record<string, any>) {
|
||||
...acc,
|
||||
[keyToParam(key)]: fieldValueToSQL(model.fields[key]!, record[key]),
|
||||
}),
|
||||
{}
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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(", ") : "*";
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@ describe("State Store", () => {
|
||||
streamId: UUIDGeneratorMock.generate(),
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await Run.UNSAFE(() =>
|
||||
|
||||
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user