Comparing version 3.0.2 to 3.1.0
@@ -1,226 +0,3 @@ | ||
import { Dict, Intersect, MaybeArray, Awaitable, Extract } from 'cosmokit'; | ||
import { Extract, Dict, Awaitable, MaybeArray, Intersect } from 'cosmokit'; | ||
import { Context, Logger, Service, Spread } from 'cordis'; | ||
export function isEvalExpr(value: any): value is Eval.Expr; | ||
export type Uneval<U, A extends boolean> = U extends number ? Eval.Term<number, A> : U extends string ? Eval.Term<string, A> : U extends boolean ? Eval.Term<boolean, A> : U extends Date ? Eval.Term<Date, A> : U extends RegExp ? Eval.Term<RegExp, A> : any; | ||
export type Eval<U> = U extends Comparable ? U : U extends Eval.Expr<infer T> ? T : never; | ||
declare const kExpr: unique symbol; | ||
declare const kType: unique symbol; | ||
declare const kAggr: unique symbol; | ||
export namespace Eval { | ||
interface Expr<T = any, A extends boolean = boolean> { | ||
[kExpr]: true; | ||
[kType]?: T; | ||
[kAggr]?: A; | ||
} | ||
type Any<A extends boolean = boolean> = Comparable | Expr<any, A>; | ||
type Term<T, A extends boolean = boolean> = T | Expr<T, A>; | ||
type Array<T, A extends boolean = boolean> = Term<T, A>[] | Expr<T[], A>; | ||
type Unary<S, R> = <T extends S, A extends boolean>(x: Term<T, A>) => Expr<R, A>; | ||
type Binary<S, R> = <T extends S, A extends boolean>(x: Term<T, A>, y: Term<T, A>) => Expr<R, A>; | ||
type Multi<S, R> = <T extends S, A extends boolean>(...args: Term<T, A>[]) => Expr<R, A>; | ||
interface Aggr<S, R> { | ||
<T extends S>(value: Term<T, false>): Expr<R, true>; | ||
<T extends S, A extends boolean>(value: Array<T, A>): Expr<R, A>; | ||
} | ||
interface Branch<T, A extends boolean> { | ||
case: Term<boolean, A>; | ||
then: Term<T, A>; | ||
} | ||
interface Static { | ||
<A extends boolean>(key: string, value: any): Eval.Expr<any, A>; | ||
if<T extends Comparable, A extends boolean>(cond: Any<A>, vThen: Term<T, A>, vElse: Term<T, A>): Expr<T, A>; | ||
ifNull<T extends Comparable, A extends boolean>(...args: Term<T, A>[]): Expr<T, A>; | ||
switch<T, A extends boolean>(branches: Branch<T, A>[], vDefault: Term<T, A>): Expr<T, A>; | ||
add: Multi<number, number>; | ||
mul: Multi<number, number>; | ||
multiply: Multi<number, number>; | ||
sub: Binary<number, number>; | ||
subtract: Binary<number, number>; | ||
div: Binary<number, number>; | ||
divide: Binary<number, number>; | ||
mod: Binary<number, number>; | ||
modulo: Binary<number, number>; | ||
abs: Unary<number, number>; | ||
floor: Unary<number, number>; | ||
ceil: Unary<number, number>; | ||
round: Unary<number, number>; | ||
exp: Unary<number, number>; | ||
log<A extends boolean>(x: Term<number, A>, base?: Term<number, A>): Expr<number, A>; | ||
pow: Binary<number, number>; | ||
power: Binary<number, number>; | ||
random(): Expr<number, false>; | ||
eq: Multi<Comparable, boolean>; | ||
ne: Binary<Comparable, boolean>; | ||
gt: Binary<Comparable, boolean>; | ||
ge: Binary<Comparable, boolean>; | ||
gte: Binary<Comparable, boolean>; | ||
lt: Binary<Comparable, boolean>; | ||
le: Binary<Comparable, boolean>; | ||
lte: Binary<Comparable, boolean>; | ||
in<T extends Comparable, A extends boolean>(x: Term<T, A>, array: Array<T, A>): Expr<boolean, A>; | ||
nin<T extends Comparable, A extends boolean>(x: Term<T, A>, array: Array<T, A>): Expr<boolean, A>; | ||
concat: Multi<string, string>; | ||
regex<A extends boolean>(x: Term<string, A>, y: Term<string, A> | Term<RegExp, A>): Expr<boolean, A>; | ||
and: Multi<boolean, boolean>; | ||
or: Multi<boolean, boolean>; | ||
not: Unary<boolean, boolean>; | ||
number: Unary<any, number>; | ||
sum: Aggr<number, number>; | ||
avg: Aggr<number, number>; | ||
max: Aggr<number, number> & Aggr<Date, Date>; | ||
min: Aggr<number, number> & Aggr<Date, Date>; | ||
count(value: Any<false>): Expr<number, true>; | ||
length(value: Any<false>): Expr<number, true>; | ||
size<A extends boolean>(value: (Any | Expr<Any, A>)[] | Expr<Any[], A>): Expr<number, A>; | ||
length<A extends boolean>(value: any[] | Expr<any[], A>): Expr<number, A>; | ||
object<T extends Dict<Expr>>(fields: T): Expr<T, false>; | ||
object<T extends any>(row: Row.Cell<T>): Expr<T, false>; | ||
array<T>(value: Expr<T, false>): Expr<T[], true>; | ||
} | ||
} | ||
export const Eval: Eval.Static; | ||
export { Eval as $ }; | ||
type MapUneval<S> = { | ||
[K in keyof S]?: Uneval<S[K], false>; | ||
}; | ||
export type Update<T = any> = MapUneval<Flatten<T>>; | ||
export function executeEval(data: any, expr: any): any; | ||
export function executeUpdate(data: any, update: any, ref: string): any; | ||
export type Values<S> = S[keyof S]; | ||
export type Keys<O, T = any> = Values<{ | ||
[K in keyof O]: O[K] extends T | undefined ? K : never; | ||
}> & string; | ||
export type Atomic = number | string | boolean | bigint | symbol | Date; | ||
export type Indexable = string | number; | ||
export type Comparable = string | number | boolean | Date; | ||
type FlatWrap<S, T, P extends string> = { | ||
[K in P]?: S; | ||
} | (S extends Atomic | T ? never : S extends any[] ? never : string extends keyof S ? never : FlatMap<S, T, `${P}.`>); | ||
type FlatMap<S, T = never, P extends string = ''> = Values<{ | ||
[K in keyof S & string as `${P}${K}`]: FlatWrap<S[K], S | T, `${P}${K}`>; | ||
}>; | ||
export type Flatten<S> = Intersect<FlatMap<S>>; | ||
export type Row<S> = { | ||
[K in keyof S]-?: Row.Cell<NonNullable<S[K]>>; | ||
}; | ||
export namespace Row { | ||
type Cell<T> = Eval.Expr<T, false> & (T extends Comparable ? {} : Row<T>); | ||
type Computed<S, T> = T | ((row: Row<S>) => T); | ||
} | ||
export function isComparable(value: any): value is Comparable; | ||
export function randomId(): string; | ||
export function makeRegExp(source: string | RegExp): RegExp; | ||
export const Primary: unique symbol; | ||
export type Primary = (string | number) & { | ||
[Primary]: true; | ||
}; | ||
export interface Field<T = any> { | ||
type: Field.Type<T>; | ||
length?: number; | ||
nullable?: boolean; | ||
initial?: T; | ||
precision?: number; | ||
scale?: number; | ||
expr?: Eval.Expr; | ||
legacy?: string[]; | ||
deprecated?: boolean; | ||
} | ||
export namespace Field { | ||
export const number: Type[]; | ||
export const string: Type[]; | ||
export const boolean: Type[]; | ||
export const date: Type[]; | ||
export const object: Type[]; | ||
export type Type<T = any> = T extends Primary ? 'primary' : T extends number ? 'integer' | 'unsigned' | 'float' | 'double' | 'decimal' : T extends string ? 'char' | 'string' | 'text' : T extends boolean ? 'boolean' : T extends Date ? 'timestamp' | 'date' | 'time' : T extends unknown[] ? 'list' | 'json' : T extends object ? 'json' : 'expr'; | ||
type Shorthand<S extends string> = S | `${S}(${any})`; | ||
type MapField<O = any> = { | ||
[K in keyof O]?: Field<O[K]> | Shorthand<Type<O[K]>> | Selection.Callback<O, O[K]>; | ||
}; | ||
export type Extension<O = any> = MapField<Flatten<O>>; | ||
export type Config<O = any> = { | ||
[K in keyof O]?: Field<O[K]>; | ||
}; | ||
export function parse(source: string | Field): Field; | ||
} | ||
export namespace Model { | ||
type Migration = (database: Database) => Promise<void>; | ||
interface Config<O = {}> { | ||
callback?: Migration; | ||
autoInc: boolean; | ||
primary: MaybeArray<Keys<O>>; | ||
unique: MaybeArray<Keys<O>>[]; | ||
foreign: { | ||
[K in keyof O]?: [string, string]; | ||
}; | ||
} | ||
} | ||
export interface Model<S> extends Model.Config<S> { | ||
} | ||
export class Model<S = any> { | ||
name: string; | ||
fields: Field.Config<S>; | ||
migrations: Map<Model.Migration, string[]>; | ||
constructor(name: string); | ||
extend(fields: Field.Extension<S>, config?: Partial<Model.Config<S>>): void; | ||
private checkIndex; | ||
resolveValue(key: string, value: any): any; | ||
format(source: object, strict?: boolean, prefix?: string, result?: S): S; | ||
parse(source: object, strict?: boolean, prefix?: string, result?: S): S; | ||
create(data?: {}): S; | ||
} | ||
export namespace Driver { | ||
interface Stats { | ||
size: number; | ||
tables: Dict<TableStats>; | ||
} | ||
interface TableStats { | ||
count: number; | ||
size: number; | ||
} | ||
type Cursor<K extends string = never> = K[] | CursorOptions<K>; | ||
interface CursorOptions<K> { | ||
limit?: number; | ||
offset?: number; | ||
fields?: K[]; | ||
sort?: Dict<Direction>; | ||
} | ||
interface WriteResult { | ||
inserted?: number; | ||
matched?: number; | ||
modified?: number; | ||
removed?: number; | ||
} | ||
} | ||
export namespace Driver { | ||
type Constructor<T> = new (ctx: Context, config: T) => Driver<T>; | ||
} | ||
export abstract class Driver<T = any> { | ||
ctx: Context; | ||
config: T; | ||
static inject: string[]; | ||
abstract start(): Promise<void>; | ||
abstract stop(): Promise<void>; | ||
abstract drop(table: string): Promise<void>; | ||
abstract dropAll(): Promise<void>; | ||
abstract stats(): Promise<Partial<Driver.Stats>>; | ||
abstract prepare(name: string): Promise<void>; | ||
abstract get(sel: Selection.Immutable, modifier: Modifier): Promise<any>; | ||
abstract eval(sel: Selection.Immutable, expr: Eval.Expr): Promise<any>; | ||
abstract set(sel: Selection.Mutable, data: Update): Promise<Driver.WriteResult>; | ||
abstract remove(sel: Selection.Mutable): Promise<Driver.WriteResult>; | ||
abstract create(sel: Selection.Mutable, data: any): Promise<any>; | ||
abstract upsert(sel: Selection.Mutable, data: any[], keys: string[]): Promise<Driver.WriteResult>; | ||
abstract withTransaction(callback: (driver: this) => Promise<void>): Promise<void>; | ||
database: Database; | ||
logger: Logger; | ||
constructor(ctx: Context, config: T); | ||
model<S = any>(table: string | Selection.Immutable | Dict<string | Selection.Immutable>): Model<S>; | ||
migrate(name: string, hooks: MigrationHooks): Promise<void>; | ||
} | ||
export interface MigrationHooks { | ||
before: (keys: string[]) => boolean; | ||
after: (keys: string[]) => void; | ||
finalize: () => Awaitable<void>; | ||
error: (reason: any) => void; | ||
} | ||
export type Query<T = any> = Query.Expr<Flatten<T>> | Query.Shorthand<Indexable> | Selection.Callback<T, boolean>; | ||
@@ -298,5 +75,3 @@ export namespace Query { | ||
protected resolveField(field: FieldLike<S>): Eval.Expr; | ||
protected resolveFields(fields: string | string[] | Dict<FieldLike<S>>): { | ||
[k: string]: any; | ||
}; | ||
protected resolveFields(fields: string | string[] | Dict<FieldLike<S>>): any; | ||
execute(): Promise<T>; | ||
@@ -345,2 +120,304 @@ } | ||
export function executeSort(data: any[], modifier: Modifier, name: string): any[]; | ||
export namespace Driver { | ||
interface Stats { | ||
size: number; | ||
tables: Dict<TableStats>; | ||
} | ||
interface TableStats { | ||
count: number; | ||
size: number; | ||
} | ||
type Cursor<K extends string = never> = K[] | CursorOptions<K>; | ||
interface CursorOptions<K> { | ||
limit?: number; | ||
offset?: number; | ||
fields?: K[]; | ||
sort?: Dict<Direction>; | ||
} | ||
interface WriteResult { | ||
inserted?: number; | ||
matched?: number; | ||
modified?: number; | ||
removed?: number; | ||
} | ||
interface Transformer<S = any, T = any> { | ||
types: Field.Type<S>[]; | ||
dump: (value: S | null) => T | null | void; | ||
load: (value: T | null) => S | null | void; | ||
} | ||
} | ||
export namespace Driver { | ||
type Constructor<T> = new (ctx: Context, config: T) => Driver<T>; | ||
} | ||
export abstract class Driver<T = any> { | ||
ctx: Context; | ||
config: T; | ||
static inject: string[]; | ||
abstract start(): Promise<void>; | ||
abstract stop(): Promise<void>; | ||
abstract drop(table: string): Promise<void>; | ||
abstract dropAll(): Promise<void>; | ||
abstract stats(): Promise<Partial<Driver.Stats>>; | ||
abstract prepare(name: string): Promise<void>; | ||
abstract get(sel: Selection.Immutable, modifier: Modifier): Promise<any>; | ||
abstract eval(sel: Selection.Immutable, expr: Eval.Expr): Promise<any>; | ||
abstract set(sel: Selection.Mutable, data: Update): Promise<Driver.WriteResult>; | ||
abstract remove(sel: Selection.Mutable): Promise<Driver.WriteResult>; | ||
abstract create(sel: Selection.Mutable, data: any): Promise<any>; | ||
abstract upsert(sel: Selection.Mutable, data: any[], keys: string[]): Promise<Driver.WriteResult>; | ||
abstract withTransaction(callback: (driver: this) => Promise<void>): Promise<void>; | ||
database: Database; | ||
logger: Logger; | ||
types: Dict<Driver.Transformer>; | ||
constructor(ctx: Context, config: T); | ||
model<S = any>(table: string | Selection.Immutable | Dict<string | Selection.Immutable>): Model<S>; | ||
migrate(name: string, hooks: MigrationHooks): Promise<void>; | ||
define<S, T>(converter: Driver.Transformer<S, T>): void; | ||
} | ||
export interface MigrationHooks { | ||
before: (keys: string[]) => boolean; | ||
after: (keys: string[]) => void; | ||
finalize: () => Awaitable<void>; | ||
error: (reason: any) => void; | ||
} | ||
export const Primary: unique symbol; | ||
export type Primary = (string | number) & { | ||
[Primary]: true; | ||
}; | ||
export interface Field<T = any> { | ||
type: Type<T>; | ||
deftype?: Field.Type<T>; | ||
length?: number; | ||
nullable?: boolean; | ||
initial?: T; | ||
precision?: number; | ||
scale?: number; | ||
expr?: Eval.Expr; | ||
legacy?: string[]; | ||
deprecated?: boolean; | ||
transformers?: Driver.Transformer[]; | ||
} | ||
export namespace Field { | ||
export const number: Type[]; | ||
export const string: Type[]; | ||
export const boolean: Type[]; | ||
export const date: Type[]; | ||
export const object: Type[]; | ||
export type Type<T = any> = T extends Primary ? 'primary' : T extends number ? 'integer' | 'unsigned' | 'float' | 'double' | 'decimal' : T extends string ? 'char' | 'string' | 'text' : T extends boolean ? 'boolean' : T extends Date ? 'timestamp' | 'date' | 'time' : T extends ArrayBuffer ? 'binary' : T extends unknown[] ? 'list' | 'json' : T extends object ? 'json' : 'expr'; | ||
type Shorthand<S extends string> = S | `${S}(${any})`; | ||
export type Object<T = any, N = any> = { | ||
type: 'object'; | ||
inner?: Extension<T, N>; | ||
} & Omit<Field<T>, 'type'>; | ||
export type Array<T = any, N = any> = { | ||
type: 'array'; | ||
inner?: Literal<T, N> | Definition<T, N> | Transform<T, any, N>; | ||
} & Omit<Field<T[]>, 'type'>; | ||
export type Transform<S = any, T = S, N = any> = { | ||
type: Type<T> | Keys<N, T> | NewType<T> | 'object' | 'array'; | ||
dump: (value: S | null) => T | null | void; | ||
load: (value: T | null) => S | null | void; | ||
initial?: S; | ||
} & Omit<Definition<T, N>, 'type' | 'initial'>; | ||
export type Definition<T, N> = (Omit<Field<T>, 'type'> & { | ||
type: Type<T> | Keys<N, T> | NewType<T>; | ||
}) | (T extends object ? Object<T, N> : never) | (T extends (infer I)[] ? Array<I, N> : never); | ||
export type Literal<T, N> = Shorthand<Type<T>> | Keys<N, T> | NewType<T> | (T extends object ? 'object' : never) | (T extends unknown[] ? 'array' : never); | ||
export type Parsable<T = any> = { | ||
type: Type<T> | Field<T>['type']; | ||
} & Omit<Field<T>, 'type'>; | ||
type MapField<O = any, N = any> = { | ||
[K in keyof O]?: Literal<O[K], N> | Definition<O[K], N> | Transform<O[K], any, N>; | ||
}; | ||
export type Extension<O = any, N = any> = MapField<Flatten<O>, N>; | ||
const NewType: unique symbol; | ||
export type NewType<T> = string & { | ||
[NewType]: T; | ||
}; | ||
export type Config<O = any> = { | ||
[K in keyof O]?: Field<O[K]>; | ||
}; | ||
export function parse(source: string | Parsable): Field; | ||
export function getInitial(type: Field.Type, initial?: any): any; | ||
} | ||
export namespace Model { | ||
type Migration = (database: Database) => Promise<void>; | ||
interface Config<O = {}> { | ||
callback?: Migration; | ||
autoInc: boolean; | ||
primary: MaybeArray<Keys<O>>; | ||
unique: MaybeArray<Keys<O>>[]; | ||
foreign: { | ||
[K in keyof O]?: [string, string]; | ||
}; | ||
} | ||
} | ||
export interface Model<S> extends Model.Config<S> { | ||
} | ||
export class Model<S = any> { | ||
name: string; | ||
fields: Field.Config<S>; | ||
migrations: Map<Model.Migration, string[]>; | ||
private type; | ||
constructor(name: string); | ||
extend(fields: Field.Extension<S>, config?: Partial<Model.Config<S>>): void; | ||
private checkIndex; | ||
resolveValue(field: string | Field | Type, value: any): any; | ||
resolveModel(obj: any, model?: Type): any; | ||
format(source: object, strict?: boolean, prefix?: string, result?: S): any; | ||
parse(source: object, strict?: boolean, prefix?: string, result?: S): any; | ||
create(data?: {}): any; | ||
getType(): Type<S>; | ||
getType(key: string): Type | undefined; | ||
} | ||
export interface Type<T = any, N = any> { | ||
[Type.kType]?: true; | ||
type: Field.Type<T> | Keys<N, T> | Field.NewType<T>; | ||
inner?: T extends (infer I)[] ? Type<I, N> : Field.Type<T> extends 'json' ? { | ||
[key in keyof T]: Type<T[key], N>; | ||
} : never; | ||
array?: boolean; | ||
} | ||
export namespace Type { | ||
export const kType: unique symbol; | ||
export const Boolean: Type<boolean>; | ||
export const Number: Type<number>; | ||
export const String: Type<string>; | ||
type Extract<T> = T extends Type<infer I> ? I : T extends Field<infer I> ? I : T extends Field.Type<infer I> ? I : T extends Eval.Term<infer I> ? I : never; | ||
export type Object<T = any> = Type<T>; | ||
export const Object: <T extends unknown>(obj?: T) => Object<{ [K in keyof T]: Extract<T>; }>; | ||
export type Array<T = any> = Type<T[]>; | ||
export const Array: <T>(type?: Type<T>) => Type.Array<T>; | ||
export function fromPrimitive<T>(value: T): Type<T>; | ||
export function fromField<T, N>(field: Type | Field<T> | Field.Type<T> | Keys<N, T> | Field.NewType<T>): Type<T, N>; | ||
export function fromTerm<T>(value: Eval.Term<T>): Type<T>; | ||
export function isType(value: any): value is Type; | ||
export function isArray(type: Type): boolean | undefined; | ||
export function getInner(type?: Type, key?: string): Type | undefined; | ||
} | ||
export function isEvalExpr(value: any): value is Eval.Expr; | ||
export type Uneval<U, A extends boolean> = U extends number ? Eval.Term<number, A> : U extends string ? Eval.Term<string, A> : U extends boolean ? Eval.Term<boolean, A> : U extends Date ? Eval.Term<Date, A> : U extends RegExp ? Eval.Term<RegExp, A> : any; | ||
export type Eval<U> = U extends Comparable ? U : U extends Eval.Expr<infer T> ? T : never; | ||
declare const kExpr: unique symbol; | ||
declare const kType: unique symbol; | ||
declare const kAggr: unique symbol; | ||
export namespace Eval { | ||
interface Expr<T = any, A extends boolean = boolean> { | ||
[kExpr]: true; | ||
[kType]?: T; | ||
[kAggr]?: A; | ||
[Type.kType]?: Type<T>; | ||
} | ||
type Any<A extends boolean = boolean> = Comparable | Expr<any, A>; | ||
type Term<T, A extends boolean = boolean> = T | Expr<T, A>; | ||
type Array<T, A extends boolean = boolean> = Term<T, A>[] | Expr<T[], A>; | ||
type Unary<S, R> = <T extends S, A extends boolean>(x: Term<T, A>) => Expr<R, A>; | ||
type Binary<S, R> = <T extends S, A extends boolean>(x: Term<T, A>, y: Term<T, A>) => Expr<R, A>; | ||
type Multi<S, R> = <T extends S, A extends boolean>(...args: Term<T, A>[]) => Expr<R, A>; | ||
interface Aggr<S, R> { | ||
<T extends S>(value: Term<T, false>): Expr<R, true>; | ||
<T extends S, A extends boolean>(value: Array<T, A>): Expr<R, A>; | ||
} | ||
interface Branch<T, A extends boolean> { | ||
case: Term<boolean, A>; | ||
then: Term<T, A>; | ||
} | ||
interface Static { | ||
<A extends boolean>(key: string, value: any, type: Type): Eval.Expr<any, A>; | ||
if<T extends Comparable, A extends boolean>(cond: Any<A>, vThen: Term<T, A>, vElse: Term<T, A>): Expr<T, A>; | ||
ifNull<T extends Comparable, A extends boolean>(...args: Term<T, A>[]): Expr<T, A>; | ||
switch<T, A extends boolean>(branches: Branch<T, A>[], vDefault: Term<T, A>): Expr<T, A>; | ||
add: Multi<number, number>; | ||
mul: Multi<number, number>; | ||
multiply: Multi<number, number>; | ||
sub: Binary<number, number>; | ||
subtract: Binary<number, number>; | ||
div: Binary<number, number>; | ||
divide: Binary<number, number>; | ||
mod: Binary<number, number>; | ||
modulo: Binary<number, number>; | ||
abs: Unary<number, number>; | ||
floor: Unary<number, number>; | ||
ceil: Unary<number, number>; | ||
round: Unary<number, number>; | ||
exp: Unary<number, number>; | ||
log<A extends boolean>(x: Term<number, A>, base?: Term<number, A>): Expr<number, A>; | ||
pow: Binary<number, number>; | ||
power: Binary<number, number>; | ||
random(): Expr<number, false>; | ||
eq: Multi<Comparable, boolean>; | ||
ne: Binary<Comparable, boolean>; | ||
gt: Binary<Comparable, boolean>; | ||
ge: Binary<Comparable, boolean>; | ||
gte: Binary<Comparable, boolean>; | ||
lt: Binary<Comparable, boolean>; | ||
le: Binary<Comparable, boolean>; | ||
lte: Binary<Comparable, boolean>; | ||
in<T extends Comparable, A extends boolean>(x: Term<T, A>, array: Array<T, A>): Expr<boolean, A>; | ||
nin<T extends Comparable, A extends boolean>(x: Term<T, A>, array: Array<T, A>): Expr<boolean, A>; | ||
concat: Multi<string, string>; | ||
regex<A extends boolean>(x: Term<string, A>, y: Term<string, A> | Term<RegExp, A>): Expr<boolean, A>; | ||
and: Multi<boolean, boolean>; | ||
or: Multi<boolean, boolean>; | ||
not: Unary<boolean, boolean>; | ||
literal<T>(value: T, type?: Field.Type<T> | Field.NewType<T> | string): Expr<T, false>; | ||
number: Unary<any, number>; | ||
sum: Aggr<number, number>; | ||
avg: Aggr<number, number>; | ||
max: Aggr<number, number> & Aggr<Date, Date>; | ||
min: Aggr<number, number> & Aggr<Date, Date>; | ||
count(value: Any<false>): Expr<number, true>; | ||
length(value: Any<false>): Expr<number, true>; | ||
size<A extends boolean>(value: (Any | Expr<Any, A>)[] | Expr<Any[], A>): Expr<number, A>; | ||
length<A extends boolean>(value: any[] | Expr<any[], A>): Expr<number, A>; | ||
object<T extends any>(row: Row.Cell<T>): Expr<T, false>; | ||
object<T extends any>(row: Row<T>): Expr<T, false>; | ||
array<T>(value: Expr<T, false>): Expr<T[], true>; | ||
} | ||
} | ||
export const Eval: Eval.Static; | ||
export { Eval as $ }; | ||
type MapUneval<S> = { | ||
[K in keyof S]?: Uneval<S[K], false>; | ||
}; | ||
export type Update<T = any> = MapUneval<Flatten<T>>; | ||
export function executeEval(data: any, expr: any): any; | ||
export function executeUpdate(data: any, update: any, ref: string): any; | ||
export type Values<S> = S[keyof S]; | ||
export type Keys<O, T = any> = Values<{ | ||
[K in keyof O]: O[K] extends T | undefined ? K : never; | ||
}> & string; | ||
export interface AtomicTypes { | ||
Number: number; | ||
String: string; | ||
Boolean: boolean; | ||
BigInt: bigint; | ||
Symbol: symbol; | ||
Date: Date; | ||
RegExp: RegExp; | ||
Function: Function; | ||
ArrayBuffer: ArrayBuffer; | ||
SharedArrayBuffer: SharedArrayBuffer; | ||
} | ||
export type Indexable = string | number; | ||
export type Comparable = string | number | boolean | Date; | ||
type FlatWrap<S, A extends 0[], P extends string> = { | ||
[K in P]?: S; | ||
} | (S extends Values<AtomicTypes> ? never : S extends any[] ? never : string extends keyof S ? never : A extends [0, ...infer R extends 0[]] ? FlatMap<S, R, `${P}.`> : never); | ||
type FlatMap<S, T extends 0[], P extends string = ''> = Values<{ | ||
[K in keyof S & string as `${P}${K}`]: FlatWrap<S[K], T, `${P}${K}`>; | ||
}>; | ||
type Sequence<N extends number, A extends 0[] = []> = A['length'] extends N ? A : Sequence<N, [0, ...A]>; | ||
export type Flatten<S, D extends number = 5> = Intersect<FlatMap<S, Sequence<D>>>; | ||
export type Row<S> = { | ||
[K in keyof S]-?: Row.Cell<NonNullable<S[K]>>; | ||
}; | ||
export namespace Row { | ||
type Cell<T> = Eval.Expr<T, false> & (T extends Comparable ? {} : Row<T>); | ||
type Computed<S, T> = T | ((row: Row<S>) => T); | ||
} | ||
export function isComparable(value: any): value is Comparable; | ||
export function randomId(): string; | ||
export function makeRegExp(source: string | RegExp): RegExp; | ||
export function unravel(source: object, init?: (value: any) => any): {}; | ||
type TableLike<S> = Keys<S> | Selection; | ||
@@ -366,3 +443,3 @@ type TableType<S, T extends TableLike<S>> = T extends Keys<S> ? S[T] : T extends Selection<infer U> ? U : never; | ||
} | ||
export class Database<S = any, C extends Context = Context> extends Service<undefined, C> { | ||
export class Database<S = any, N = any, C extends Context = Context> extends Service<undefined, C> { | ||
static [Service.provide]: string; | ||
@@ -374,2 +451,3 @@ static [Service.immediate]: boolean; | ||
drivers: Record<keyof any, Driver>; | ||
types: Dict<Field.Transform>; | ||
migrating: boolean; | ||
@@ -384,4 +462,8 @@ private prepareTasks; | ||
private prepare; | ||
extend<K extends Keys<S>>(name: K, fields: Field.Extension<S[K]>, config?: Partial<Model.Config<S[K]>>): void; | ||
migrate<K extends Keys<S>>(name: K, fields: Field.Extension<S[K]>, callback: Model.Migration): void; | ||
extend<K extends Keys<S>>(name: K, fields: Field.Extension<S[K], N>, config?: Partial<Model.Config<S[K]>>): void; | ||
private _parseField; | ||
private parseField; | ||
define<K extends Exclude<Keys<N>, Field.Type | 'object' | 'array'>>(name: K, field: Field.Definition<N[K], N> | Field.Transform<N[K], any, N>): K; | ||
define<S>(field: Field.Definition<S, N> | Field.Transform<S, any, N>): Field.NewType<S>; | ||
migrate<K extends Keys<S>>(name: K, fields: Field.Extension<S[K], N>, callback: Model.Migration): void; | ||
select<T>(table: Selection<T>, query?: Query<T>): Selection<T>; | ||
@@ -397,4 +479,4 @@ select<T extends Keys<S>>(table: T, query?: Query<S[T]>): Selection<S[T]>; | ||
upsert<T extends Keys<S>>(table: T, upsert: Row.Computed<S[T], Update<S[T]>[]>, keys?: MaybeArray<Keys<Flatten<S[T]>, Indexable>>): Promise<Driver.WriteResult>; | ||
withTransaction(callback: (database: Database<S>) => Promise<void>): Promise<void>; | ||
withTransaction<T extends Keys<S>>(table: T, callback: (database: Database<S>) => Promise<void>): Promise<void>; | ||
withTransaction(callback: (database: this) => Promise<void>): Promise<void>; | ||
withTransaction<T extends Keys<S>>(table: T, callback: (database: this) => Promise<void>): Promise<void>; | ||
stopAll(): Promise<void>; | ||
@@ -423,3 +505,7 @@ drop<T extends Keys<S>>(table: T): Promise<void>; | ||
} | ||
export interface Tables { | ||
} | ||
export interface Types { | ||
} | ||
export { Logger, Schema, Schema as z } from 'cordis'; | ||
export default Database; |
{ | ||
"name": "minato", | ||
"version": "3.0.2", | ||
"version": "3.1.0", | ||
"description": "Type Driven Database Framework", | ||
@@ -46,4 +46,4 @@ "type": "module", | ||
"cordis": "^3.13.4", | ||
"cosmokit": "^1.5.2" | ||
"cosmokit": "^1.6.2" | ||
} | ||
} |
@@ -1,4 +0,4 @@ | ||
import { Dict, Intersect, makeArray, MaybeArray, valueMap } from 'cosmokit' | ||
import { Dict, Intersect, makeArray, mapValues, MaybeArray, omit, valueMap } from 'cosmokit' | ||
import { Context, Service, Spread } from 'cordis' | ||
import { Flatten, Indexable, Keys, Row } from './utils.ts' | ||
import { Flatten, Indexable, Keys, randomId, Row, unravel } from './utils.ts' | ||
import { Selection } from './selection.ts' | ||
@@ -9,2 +9,3 @@ import { Field, Model } from './model.ts' | ||
import { Query } from './query.ts' | ||
import { Type } from './type.ts' | ||
@@ -51,3 +52,3 @@ type TableLike<S> = Keys<S> | Selection | ||
export class Database<S = any, C extends Context = Context> extends Service<undefined, C> { | ||
export class Database<S = any, N = any, C extends Context = Context> extends Service<undefined, C> { | ||
static [Service.provide] = 'model' | ||
@@ -58,2 +59,3 @@ static [Service.immediate] = true | ||
public drivers: Record<keyof any, Driver> = Object.create(null) | ||
public types: Dict<Field.Transform> = Object.create(null) | ||
public migrating = false | ||
@@ -96,6 +98,13 @@ private prepareTasks: Dict<Promise<void>> = Object.create(null) | ||
if (!this.stashed.delete(name)) return | ||
await this.getDriver(name)?.prepare(name) | ||
const driver = this.getDriver(name) | ||
if (!driver) return | ||
const { fields } = driver.model(name) | ||
Object.values(fields).forEach(field => field?.transformers?.forEach(x => driver.define(x))) | ||
await driver.prepare(name) | ||
} | ||
extend<K extends Keys<S>>(name: K, fields: Field.Extension<S[K]>, config: Partial<Model.Config<S[K]>> = {}) { | ||
extend<K extends Keys<S>>(name: K, fields: Field.Extension<S[K], N>, config: Partial<Model.Config<S[K]>> = {}) { | ||
let model = this.tables[name] | ||
@@ -106,2 +115,7 @@ if (!model) { | ||
} | ||
Object.entries(fields).forEach(([key, field]: [string, any]) => { | ||
const transformer = [] | ||
this.parseField(field, transformer, undefined, value => field = fields[key] = value) | ||
if (typeof field === 'object') field.transformers = transformer | ||
}) | ||
model.extend(fields, config) | ||
@@ -112,3 +126,97 @@ this.prepareTasks[name] = this.prepare(name) | ||
migrate<K extends Keys<S>>(name: K, fields: Field.Extension<S[K]>, callback: Model.Migration) { | ||
private _parseField(field: any, transformers: Driver.Transformer[] = [], setInitial?: (value) => void, setField?: (value) => void): Type { | ||
if (field === 'object') { | ||
setInitial?.({}) | ||
setField?.({ type: 'json', initial: {} }) | ||
return Type.Object() | ||
} else if (field === 'array') { | ||
setInitial?.([]) | ||
setField?.({ type: 'json', initial: [] }) | ||
return Type.Array() | ||
} else if (typeof field === 'string' && this.types[field]) { | ||
transformers.push({ | ||
types: [field as any], | ||
load: this.types[field].load, | ||
dump: this.types[field].dump, | ||
}, ...(this.types[field].transformers ?? [])) | ||
setInitial?.(this.types[field].initial) | ||
setField?.({ ...this.types[field], type: field }) | ||
return Type.fromField(field) | ||
} else if (typeof field === 'string') { | ||
setInitial?.(Field.getInitial((field as any).split('(')[0])) | ||
setField?.(field) | ||
return Type.fromField(field.split('(')[0]) | ||
} else if (typeof field === 'object' && field.type === 'object') { | ||
const inner = field.inner ? unravel(field.inner, value => (value.type = 'object', value.inner ??= {})) : Object.create(null) | ||
const initial = Object.create(null) | ||
const res = Type.Object(mapValues(inner, (x, k) => this.parseField(x, transformers, value => initial[k] = value))) | ||
setInitial?.(Field.getInitial('json', initial)) | ||
setField?.({ initial: Field.getInitial('json', initial), ...field, deftype: 'json', type: res }) | ||
return res | ||
} else if (typeof field === 'object' && field.type === 'array') { | ||
const res = field.inner ? Type.Array(this.parseField(field.inner, transformers)) : Type.Array() | ||
setInitial?.([]) | ||
setField?.({ initial: [], ...field, deftype: 'json', type: res }) | ||
return res | ||
} else if (typeof field === 'object' && this.types[field.type]) { | ||
transformers.push({ | ||
types: [field.type as any], | ||
load: this.types[field.type].load, | ||
dump: this.types[field.type].dump, | ||
}, ...(this.types[field.type].transformers ?? [])) | ||
setInitial?.(field.initial === undefined ? this.types[field.type].initial : field.initial) | ||
setField?.({ initial: this.types[field.type].initial, ...field }) | ||
return Type.fromField(field.type) | ||
} else { | ||
setInitial?.(Field.getInitial(field.type, field.initial)) | ||
setField?.(field) | ||
return Type.fromField(field.type) | ||
} | ||
} | ||
private parseField(field: any, transformers: Driver.Transformer[] = [], setInitial?: (value) => void, setField?: (value: Field.Parsable) => void): Type { | ||
let midfield | ||
let type = this._parseField(field, transformers, setInitial, (value) => (midfield = value, setField?.(value))) | ||
if (typeof field === 'object' && field.load && field.dump) { | ||
if (type.inner) type = Type.fromField(this.define({ ...omit(midfield, ['load', 'dump']), type } as any)) | ||
const name = this.define({ ...field, deftype: midfield.deftype, type: type.type }) | ||
transformers.push({ | ||
types: [name as any], | ||
load: field.load, | ||
dump: field.dump, | ||
}) | ||
// for transform type, intentionally assign a null initial on default | ||
setInitial?.(field.initial) | ||
setField?.({ ...field, deftype: midfield.deftype ?? this.types[type.type]?.deftype ?? type.type, initial: midfield.initial, type: name }) | ||
return Type.fromField(name as any) | ||
} | ||
if (typeof midfield === 'object') setField?.({ ...midfield, deftype: midfield.deftype ?? this.types[type.type]?.deftype ?? type?.type }) | ||
return type | ||
} | ||
define<K extends Exclude<Keys<N>, Field.Type | 'object' | 'array'>>(name: K, field: Field.Definition<N[K], N> | Field.Transform<N[K], any, N>): K | ||
define<S>(field: Field.Definition<S, N> | Field.Transform<S, any, N>): Field.NewType<S> | ||
define(name: any, field?: any) { | ||
if (typeof name === 'object') { | ||
field = name | ||
name = undefined | ||
} | ||
if (name && this.types[name]) throw new Error(`type "${name}" already defined`) | ||
if (!name) while (this.types[name = '_define_' + randomId()]); | ||
const transformers = [] | ||
const type = this._parseField(field, transformers, undefined, value => field = value) | ||
field.transformers = transformers | ||
this[Context.current].effect(() => { | ||
this.types[name] = { ...field } | ||
this.types[name].deftype ??= this.types[field.type]?.deftype ?? type.type as any | ||
return () => delete this.types[name] | ||
}) | ||
return name as any | ||
} | ||
migrate<K extends Keys<S>>(name: K, fields: Field.Extension<S[K], N>, callback: Model.Migration) { | ||
this.extend(name, fields, { callback }) | ||
@@ -192,4 +300,4 @@ } | ||
async withTransaction(callback: (database: Database<S>) => Promise<void>): Promise<void> | ||
async withTransaction<T extends Keys<S>>(table: T, callback: (database: Database<S>) => Promise<void>): Promise<void> | ||
async withTransaction(callback: (database: this) => Promise<void>): Promise<void> | ||
async withTransaction<T extends Keys<S>>(table: T, callback: (database: this) => Promise<void>): Promise<void> | ||
async withTransaction(arg: any, ...args: any[]) { | ||
@@ -196,0 +304,0 @@ if (this[kTransaction]) throw new Error('nested transactions are not supported') |
@@ -5,4 +5,5 @@ import { Awaitable, Dict, valueMap } from 'cosmokit' | ||
import { Direction, Modifier, Selection } from './selection.ts' | ||
import { Model } from './model.ts' | ||
import { Field, Model } from './model.ts' | ||
import { Database } from './database.ts' | ||
import { Type } from './type.ts' | ||
@@ -35,2 +36,8 @@ export namespace Driver { | ||
} | ||
export interface Transformer<S = any, T = any> { | ||
types: Field.Type<S>[] | ||
dump: (value: S | null) => T | null | void | ||
load: (value: T | null) => S | null | void | ||
} | ||
} | ||
@@ -61,2 +68,3 @@ | ||
public logger: Logger | ||
public types: Dict<Driver.Transformer> = Object.create(null) | ||
@@ -93,4 +101,4 @@ constructor(public ctx: Context, public config: T) { | ||
const model = new Model('temp') | ||
model.fields = valueMap(table.args[0].fields, (_, key) => ({ | ||
type: 'expr', | ||
model.fields = valueMap(table.args[0].fields, (expr, key) => ({ | ||
type: Type.fromTerm(expr), | ||
})) | ||
@@ -106,4 +114,4 @@ return model | ||
model.fields[`${key}.${field}`] = { | ||
type: 'expr', | ||
expr: { $: [key, field] } as any, | ||
expr: Eval('', [table[key].ref, field], Type.fromField(submodel.fields[field]!)), | ||
type: Type.fromField(submodel.fields[field]!), | ||
} | ||
@@ -132,2 +140,6 @@ } | ||
} | ||
define<S, T>(converter: Driver.Transformer<S, T>) { | ||
converter.types.forEach(type => this.types[type] = converter) | ||
} | ||
} | ||
@@ -134,0 +146,0 @@ |
117
src/eval.ts
@@ -1,3 +0,5 @@ | ||
import { defineProperty, Dict, isNullable, valueMap } from 'cosmokit' | ||
import { defineProperty, isNullable, valueMap } from 'cosmokit' | ||
import { Comparable, Flatten, isComparable, makeRegExp, Row } from './utils.ts' | ||
import { Type } from './type.ts' | ||
import { Field } from './model.ts' | ||
@@ -30,2 +32,3 @@ export function isEvalExpr(value: any): value is Eval.Expr { | ||
[kAggr]?: A | ||
[Type.kType]?: Type<T> | ||
} | ||
@@ -53,3 +56,3 @@ | ||
export interface Static { | ||
<A extends boolean>(key: string, value: any): Eval.Expr<any, A> | ||
<A extends boolean>(key: string, value: any, type: Type): Eval.Expr<any, A> | ||
@@ -107,2 +110,3 @@ // univeral | ||
// typecast | ||
literal<T>(value: T, type?: Field.Type<T> | Field.NewType<T> | string): Expr<T, false> | ||
number: Unary<any, number> | ||
@@ -120,4 +124,4 @@ | ||
object<T extends Dict<Expr>>(fields: T): Expr<T, false> | ||
object<T extends any>(row: Row.Cell<T>): Expr<T, false> | ||
object<T extends any>(row: Row<T>): Expr<T, false> | ||
array<T>(value: Expr<T, false>): Expr<T[], true> | ||
@@ -127,5 +131,5 @@ } | ||
export const Eval = ((key, value) => defineProperty({ ['$' + key]: value }, kExpr, true)) as Eval.Static | ||
export const Eval = ((key, value, type) => defineProperty(defineProperty({ ['$' + key]: value }, kExpr, true), Type.kType, type)) as Eval.Static | ||
const operators = {} as Record<`$${keyof Eval.Static}`, (args: any, data: any) => any> | ||
const operators = Object.create(null) as Record<`$${keyof Eval.Static}`, (args: any, data: any) => any> | ||
@@ -135,11 +139,14 @@ operators['$'] = getRecursive | ||
type UnaryCallback<T> = T extends (value: infer R) => Eval.Expr<infer S> ? (value: R, data: any[]) => S : never | ||
function unary<K extends keyof Eval.Static>(key: K, callback: UnaryCallback<Eval.Static[K]>): Eval.Static[K] { | ||
function unary<K extends keyof Eval.Static>(key: K, callback: UnaryCallback<Eval.Static[K]>, type: Type | ((...args: any[]) => Type)): Eval.Static[K] { | ||
operators[`$${key}`] = callback | ||
return ((value: any) => Eval(key, value)) as any | ||
return ((value: any) => Eval(key, value, typeof type === 'function' ? type(value) : type)) as any | ||
} | ||
type MultivariateCallback<T> = T extends (...args: infer R) => Eval.Expr<infer S> ? (args: R, data: any) => S : never | ||
function multary<K extends keyof Eval.Static>(key: K, callback: MultivariateCallback<Eval.Static[K]>): Eval.Static[K] { | ||
function multary<K extends keyof Eval.Static>( | ||
key: K, callback: MultivariateCallback<Eval.Static[K]>, | ||
type: Type | ((...args: any[]) => Type), | ||
): Eval.Static[K] { | ||
operators[`$${key}`] = callback | ||
return (...args: any) => Eval(key, args) as any | ||
return (...args: any) => Eval(key, args, typeof type === 'function' ? type(...args) : type) as any | ||
} | ||
@@ -155,6 +162,6 @@ | ||
} | ||
return (...args: any) => Eval(key, args) as any | ||
return (...args: any) => Eval(key, args, Type.Boolean) as any | ||
} | ||
Eval.switch = (branches, vDefault) => Eval('switch', { branches, default: vDefault }) | ||
Eval.switch = (branches, vDefault) => Eval('switch', { branches, default: vDefault }, Type.fromTerm(branches[0])) | ||
operators.$switch = (args, data) => { | ||
@@ -168,21 +175,22 @@ for (const branch of args.branches) { | ||
// univeral | ||
Eval.if = multary('if', ([cond, vThen, vElse], data) => executeEval(data, cond) ? executeEval(data, vThen) : executeEval(data, vElse)) | ||
Eval.ifNull = multary('ifNull', ([value, fallback], data) => executeEval(data, value) ?? executeEval(data, fallback)) | ||
Eval.if = multary('if', ([cond, vThen, vElse], data) => executeEval(data, cond) ? executeEval(data, vThen) | ||
: executeEval(data, vElse), (cond, vThen, vElse) => Type.fromTerm(vThen)) | ||
Eval.ifNull = multary('ifNull', ([value, fallback], data) => executeEval(data, value) ?? executeEval(data, fallback), (value) => Type.fromTerm(value)) | ||
// arithmetic | ||
Eval.add = multary('add', (args, data) => args.reduce<number>((prev, curr) => prev + executeEval(data, curr), 0)) | ||
Eval.mul = Eval.multiply = multary('multiply', (args, data) => args.reduce<number>((prev, curr) => prev * executeEval(data, curr), 1)) | ||
Eval.sub = Eval.subtract = multary('subtract', ([left, right], data) => executeEval(data, left) - executeEval(data, right)) | ||
Eval.div = Eval.divide = multary('divide', ([left, right], data) => executeEval(data, left) / executeEval(data, right)) | ||
Eval.mod = Eval.modulo = multary('modulo', ([left, right], data) => executeEval(data, left) % executeEval(data, right)) | ||
Eval.add = multary('add', (args, data) => args.reduce<number>((prev, curr) => prev + executeEval(data, curr), 0), Type.Number) | ||
Eval.mul = Eval.multiply = multary('multiply', (args, data) => args.reduce<number>((prev, curr) => prev * executeEval(data, curr), 1), Type.Number) | ||
Eval.sub = Eval.subtract = multary('subtract', ([left, right], data) => executeEval(data, left) - executeEval(data, right), Type.Number) | ||
Eval.div = Eval.divide = multary('divide', ([left, right], data) => executeEval(data, left) / executeEval(data, right), Type.Number) | ||
Eval.mod = Eval.modulo = multary('modulo', ([left, right], data) => executeEval(data, left) % executeEval(data, right), Type.Number) | ||
// mathematic | ||
Eval.abs = unary('abs', (arg, data) => Math.abs(executeEval(data, arg))) | ||
Eval.floor = unary('floor', (arg, data) => Math.floor(executeEval(data, arg))) | ||
Eval.ceil = unary('ceil', (arg, data) => Math.ceil(executeEval(data, arg))) | ||
Eval.round = unary('round', (arg, data) => Math.round(executeEval(data, arg))) | ||
Eval.exp = unary('exp', (arg, data) => Math.exp(executeEval(data, arg))) | ||
Eval.log = multary('log', ([left, right], data) => Math.log(executeEval(data, left)) / Math.log(executeEval(data, right ?? Math.E))) | ||
Eval.pow = Eval.power = multary('power', ([left, right], data) => Math.pow(executeEval(data, left), executeEval(data, right))) | ||
Eval.random = () => Eval('random', {}) | ||
Eval.abs = unary('abs', (arg, data) => Math.abs(executeEval(data, arg)), Type.Number) | ||
Eval.floor = unary('floor', (arg, data) => Math.floor(executeEval(data, arg)), Type.Number) | ||
Eval.ceil = unary('ceil', (arg, data) => Math.ceil(executeEval(data, arg)), Type.Number) | ||
Eval.round = unary('round', (arg, data) => Math.round(executeEval(data, arg)), Type.Number) | ||
Eval.exp = unary('exp', (arg, data) => Math.exp(executeEval(data, arg)), Type.Number) | ||
Eval.log = multary('log', ([left, right], data) => Math.log(executeEval(data, left)) / Math.log(executeEval(data, right ?? Math.E)), Type.Number) | ||
Eval.pow = Eval.power = multary('power', ([left, right], data) => Math.pow(executeEval(data, left), executeEval(data, right)), Type.Number) | ||
Eval.random = () => Eval('random', {}, Type.Number) | ||
operators.$random = () => Math.random() | ||
@@ -199,24 +207,33 @@ | ||
// element | ||
Eval.in = multary('in', ([value, array], data) => executeEval(data, array).includes(executeEval(data, value))) | ||
Eval.nin = multary('nin', ([value, array], data) => !executeEval(data, array).includes(executeEval(data, value))) | ||
Eval.in = multary('in', ([value, array], data) => executeEval(data, array).includes(executeEval(data, value)), Type.Boolean) | ||
Eval.nin = multary('nin', ([value, array], data) => !executeEval(data, array).includes(executeEval(data, value)), Type.Boolean) | ||
// string | ||
Eval.concat = multary('concat', (args, data) => args.map(arg => executeEval(data, arg)).join('')) | ||
Eval.regex = multary('regex', ([value, regex], data) => makeRegExp(executeEval(data, regex)).test(executeEval(data, value))) | ||
Eval.concat = multary('concat', (args, data) => args.map(arg => executeEval(data, arg)).join(''), Type.String) | ||
Eval.regex = multary('regex', ([value, regex], data) => makeRegExp(executeEval(data, regex)).test(executeEval(data, value)), Type.Boolean) | ||
// logical | ||
Eval.and = multary('and', (args, data) => args.every(arg => executeEval(data, arg))) | ||
Eval.or = multary('or', (args, data) => args.some(arg => executeEval(data, arg))) | ||
Eval.not = unary('not', (value, data) => !executeEval(data, value)) | ||
Eval.and = multary('and', (args, data) => args.every(arg => executeEval(data, arg)), Type.Boolean) | ||
Eval.or = multary('or', (args, data) => args.some(arg => executeEval(data, arg)), Type.Boolean) | ||
Eval.not = unary('not', (value, data) => !executeEval(data, value), Type.Boolean) | ||
// typecast | ||
Eval.literal = multary('literal', ([value, type]) => { | ||
if (type) throw new TypeError('literal cast is not supported') | ||
else return value | ||
}, (value, type) => type ? Type.fromField(type) : Type.fromTerm(value)) | ||
Eval.number = unary('number', (arg, data) => { | ||
const value = executeEval(data, arg) | ||
return value instanceof Date ? Math.floor(value.valueOf() / 1000) : Number(value) | ||
}) | ||
}, Type.Number) | ||
const unwrapAggr = (expr: any) => { | ||
const type = Type.fromTerm(expr) | ||
return Type.getInner(type) ?? type | ||
} | ||
// aggregation | ||
Eval.sum = unary('sum', (expr, table) => Array.isArray(table) | ||
? table.reduce<number>((prev, curr) => prev + executeAggr(expr, curr), 0) | ||
: Array.from<number>(executeEval(table, expr)).reduce((prev, curr) => prev + curr, 0)) | ||
: Array.from<number>(executeEval(table, expr)).reduce((prev, curr) => prev + curr, 0), Type.Number) | ||
Eval.avg = unary('avg', (expr, table) => { | ||
@@ -228,31 +245,33 @@ if (Array.isArray(table)) return table.reduce((prev, curr) => prev + executeAggr(expr, curr), 0) / table.length | ||
} | ||
}) | ||
}, Type.Number) | ||
Eval.max = unary('max', (expr, table) => Array.isArray(table) | ||
? table.map(data => executeAggr(expr, data)).reduce((x, y) => x > y ? x : y, -Infinity) | ||
: Array.from<number>(executeEval(table, expr)).reduce((x, y) => x > y ? x : y, -Infinity)) | ||
: Array.from<number>(executeEval(table, expr)).reduce((x, y) => x > y ? x : y, -Infinity), (expr) => unwrapAggr(expr)) | ||
Eval.min = unary('min', (expr, table) => Array.isArray(table) | ||
? table.map(data => executeAggr(expr, data)).reduce((x, y) => x < y ? x : y, Infinity) | ||
: Array.from<number>(executeEval(table, expr)).reduce((x, y) => x < y ? x : y, Infinity)) | ||
Eval.count = unary('count', (expr, table) => new Set(table.map(data => executeAggr(expr, data))).size) | ||
: Array.from<number>(executeEval(table, expr)).reduce((x, y) => x < y ? x : y, Infinity), (expr) => unwrapAggr(expr)) | ||
Eval.count = unary('count', (expr, table) => new Set(table.map(data => executeAggr(expr, data))).size, Type.Number) | ||
defineProperty(Eval, 'length', unary('length', (expr, table) => Array.isArray(table) | ||
? table.map(data => executeAggr(expr, data)).length | ||
: Array.from(executeEval(table, expr)).length)) | ||
: Array.from(executeEval(table, expr)).length, Type.Number)) | ||
operators.$object = (field, table) => valueMap(field, value => executeAggr(value, table)) | ||
Eval.object = (fields) => { | ||
Eval.object = (fields: any) => { | ||
if (fields.$model) { | ||
const modelFields = Object.keys(fields.$model.fields) | ||
const modelFields: [string, Field][] = Object.entries(fields.$model.fields) | ||
const prefix: string = fields.$prefix | ||
return Eval('object', Object.fromEntries(modelFields | ||
.filter(path => path.startsWith(prefix)) | ||
.map(k => [k.slice(prefix.length), fields[k.slice(prefix.length)]]), | ||
)) | ||
fields = Object.fromEntries(modelFields | ||
.filter(([, field]) => !field.deprecated) | ||
.filter(([path]) => path.startsWith(prefix)) | ||
.map(([k]) => [k.slice(prefix.length), fields[k.slice(prefix.length)]])) | ||
return Eval('object', fields, Type.Object(valueMap(fields, (value) => Type.fromTerm(value)))) | ||
} | ||
return Eval('object', fields) as any | ||
return Eval('object', fields, Type.Object(valueMap(fields, (value) => Type.fromTerm(value)))) as any | ||
} | ||
Eval.array = unary('array', (expr, table) => Array.isArray(table) | ||
? table.map(data => executeAggr(expr, data)) | ||
: Array.from(executeEval(table, expr))) | ||
: Array.from(executeEval(table, expr)), (expr) => Type.Array(Type.fromTerm(expr))) | ||
Eval.exec = unary('exec', (expr, data) => (expr.driver as any).executeSelection(expr, data)) | ||
Eval.exec = unary('exec', (expr, data) => (expr.driver as any).executeSelection(expr, data), (expr) => Type.fromTerm(expr.args[0])) | ||
@@ -259,0 +278,0 @@ export { Eval as $ } |
@@ -10,2 +10,3 @@ import { Database } from './database.ts' | ||
export * from './selection.ts' | ||
export * from './type.ts' | ||
export * from './utils.ts' | ||
@@ -24,4 +25,8 @@ | ||
export interface Tables {} | ||
export interface Types {} | ||
export { Logger, Schema, Schema as z } from 'cordis' | ||
export default Database |
149
src/model.ts
@@ -1,6 +0,7 @@ | ||
import { clone, isNullable, makeArray, MaybeArray } from 'cosmokit' | ||
import { Binary, clone, isNullable, makeArray, MaybeArray, valueMap } from 'cosmokit' | ||
import { Database } from './database.ts' | ||
import { Eval, isEvalExpr } from './eval.ts' | ||
import { Selection } from './selection.ts' | ||
import { Flatten, Keys } from './utils.ts' | ||
import { Flatten, Keys, unravel } from './utils.ts' | ||
import { Type } from './type.ts' | ||
import { Driver } from './driver.ts' | ||
@@ -11,3 +12,4 @@ export const Primary = Symbol('Primary') | ||
export interface Field<T = any> { | ||
type: Field.Type<T> | ||
type: Type<T> | ||
deftype?: Field.Type<T> | ||
length?: number | ||
@@ -21,2 +23,3 @@ nullable?: boolean | ||
deprecated?: boolean | ||
transformers?: Driver.Transformer[] | ||
} | ||
@@ -37,2 +40,3 @@ | ||
: T extends Date ? 'timestamp' | 'date' | 'time' | ||
: T extends ArrayBuffer ? 'binary' | ||
: T extends unknown[] ? 'list' | 'json' | ||
@@ -44,8 +48,44 @@ : T extends object ? 'json' | ||
type MapField<O = any> = { | ||
[K in keyof O]?: Field<O[K]> | Shorthand<Type<O[K]>> | Selection.Callback<O, O[K]> | ||
export type Object<T = any, N = any> = { | ||
type: 'object' | ||
inner?: Extension<T, N> | ||
} & Omit<Field<T>, 'type'> | ||
export type Array<T = any, N = any> = { | ||
type: 'array' | ||
inner?: Literal<T, N> | Definition<T, N> | Transform<T, any, N> | ||
} & Omit<Field<T[]>, 'type'> | ||
export type Transform<S = any, T = S, N = any> = { | ||
type: Type<T> | Keys<N, T> | NewType<T> | 'object' | 'array' | ||
dump: (value: S | null) => T | null | void | ||
load: (value: T | null) => S | null | void | ||
initial?: S | ||
} & Omit<Definition<T, N>, 'type' | 'initial'> | ||
export type Definition<T, N> = | ||
| (Omit<Field<T>, 'type'> & { type: Type<T> | Keys<N, T> | NewType<T> }) | ||
| (T extends object ? Object<T, N> : never) | ||
| (T extends (infer I)[] ? Array<I, N> : never) | ||
export type Literal<T, N> = | ||
| Shorthand<Type<T>> | ||
| Keys<N, T> | ||
| NewType<T> | ||
| (T extends object ? 'object' : never) | ||
| (T extends unknown[] ? 'array' : never) | ||
export type Parsable<T = any> = { | ||
type: Type<T> | Field<T>['type'] | ||
} & Omit<Field<T>, 'type'> | ||
type MapField<O = any, N = any> = { | ||
[K in keyof O]?: Literal<O[K], N> | Definition<O[K], N> | Transform<O[K], any, N> | ||
} | ||
export type Extension<O = any> = MapField<Flatten<O>> | ||
export type Extension<O = any, N = any> = MapField<Flatten<O>, N> | ||
const NewType = Symbol('newtype') | ||
export type NewType<T> = string & { [NewType]: T } | ||
export type Config<O = any> = { | ||
@@ -57,5 +97,12 @@ [K in keyof O]?: Field<O[K]> | ||
export function parse(source: string | Field): Field { | ||
if (typeof source === 'function') return { type: 'expr', expr: source } | ||
if (typeof source !== 'string') return { initial: null, ...source } | ||
export function parse(source: string | Parsable): Field { | ||
if (typeof source === 'function') throw new TypeError('view field is not supported') | ||
if (typeof source !== 'string') { | ||
return { | ||
initial: null, | ||
deftype: source.type as any, | ||
...source, | ||
type: Type.fromField(source.type), | ||
} | ||
} | ||
@@ -67,11 +114,6 @@ // parse string definition | ||
const args = (capture[2] || '').split(',') | ||
const field: Field = { type } | ||
const field: Field = { deftype: type, type: Type.fromField(type) } | ||
// set default initial value | ||
if (field.initial === undefined) { | ||
if (number.includes(field.type)) field.initial = 0 | ||
if (string.includes(field.type)) field.initial = '' | ||
if (field.type === 'list') field.initial = [] | ||
if (field.type === 'json') field.initial = {} | ||
} | ||
if (field.initial === undefined) field.initial = getInitial(type) | ||
@@ -88,2 +130,12 @@ // set length information | ||
} | ||
export function getInitial(type: Field.Type, initial?: any) { | ||
if (initial === undefined) { | ||
if (Field.number.includes(type)) return 0 | ||
if (Field.string.includes(type)) return '' | ||
if (type === 'list') return [] | ||
if (type === 'json') return {} | ||
} | ||
return initial | ||
} | ||
} | ||
@@ -112,2 +164,4 @@ | ||
private type: Type<S> | undefined | ||
constructor(public name: string) { | ||
@@ -136,3 +190,3 @@ this.autoInc = false | ||
if (typeof this.primary === 'string' && this.fields[this.primary]?.type === 'primary') { | ||
if (typeof this.primary === 'string' && this.fields[this.primary]?.deftype === 'primary') { | ||
this.autoInc = true | ||
@@ -154,9 +208,11 @@ } | ||
resolveValue(key: string, value: any) { | ||
resolveValue(field: string | Field | Type, value: any) { | ||
if (isNullable(value)) return value | ||
if (this.fields[key]?.type === 'time') { | ||
if (typeof field === 'string') field = this.fields[field] as Field | ||
if (field) field = Type.fromField(field) | ||
if (field?.type === 'time') { | ||
const date = new Date(0) | ||
date.setHours(value.getHours(), value.getMinutes(), value.getSeconds(), value.getMilliseconds()) | ||
return date | ||
} else if (this.fields[key]?.type === 'date') { | ||
} else if (field?.type === 'date') { | ||
const date = new Date(value) | ||
@@ -169,2 +225,27 @@ date.setHours(0, 0, 0, 0) | ||
resolveModel(obj: any, model?: Type) { | ||
if (!model) model = this.getType() | ||
if (isNullable(obj) || !model.inner) return obj | ||
if (Type.isArray(model) && Array.isArray(obj)) { | ||
return obj.map(x => this.resolveModel(x, Type.getInner(model)!)) | ||
} | ||
const result = {} | ||
for (const key in obj) { | ||
const type = Type.getInner(model, key) | ||
if (!type || isNullable(obj[key])) { | ||
result[key] = obj[key] | ||
} else if (type.type !== 'json') { | ||
result[key] = this.resolveValue(type, obj[key]) | ||
} else if (type.inner && Type.isArray(type) && Array.isArray(obj[key])) { | ||
result[key] = obj[key].map(x => this.resolveModel(x, Type.getInner(type))) | ||
} else if (type.inner) { | ||
result[key] = this.resolveModel(obj[key], type) | ||
} else { | ||
result[key] = obj[key] | ||
} | ||
} | ||
return result | ||
} | ||
format(source: object, strict = true, prefix = '', result = {} as S) { | ||
@@ -176,3 +257,3 @@ const fields = Object.keys(this.fields) | ||
if (fields.includes(key)) { | ||
result[key] = this.resolveValue(key, value) | ||
result[key] = value | ||
return | ||
@@ -191,3 +272,3 @@ } | ||
}) | ||
return result | ||
return prefix === '' ? this.resolveModel(result) : result | ||
} | ||
@@ -197,2 +278,9 @@ | ||
const fields = Object.keys(this.fields) | ||
if (strict && prefix === '') { | ||
// initialize object layout | ||
Object.assign(result as any, unravel(Object.fromEntries(fields | ||
.filter(key => key.includes('.')) | ||
.map(key => [key.slice(0, key.lastIndexOf('.')), {}])), | ||
)) | ||
} | ||
for (const key in source) { | ||
@@ -209,8 +297,8 @@ let node = result | ||
if (field) { | ||
node[segments[0]] = this.resolveValue(key, value) | ||
} else if (!value || typeof value !== 'object' || isEvalExpr(value) || Array.isArray(value) || Object.keys(value).length === 0) { | ||
node[segments[0]] = value | ||
} else if (!value || typeof value !== 'object' || isEvalExpr(value) || Array.isArray(value) || Binary.is(value) || Object.keys(value).length === 0) { | ||
if (strict) { | ||
throw new TypeError(`unknown field "${fullKey}" in model ${this.name}`) | ||
} else { | ||
node[segments[0]] = this.resolveValue(key, value) | ||
node[segments[0]] = value | ||
} | ||
@@ -222,3 +310,3 @@ } else { | ||
} | ||
return result | ||
return prefix === '' ? this.resolveModel(result) : result | ||
} | ||
@@ -238,2 +326,9 @@ | ||
} | ||
getType(): Type<S> | ||
getType(key: string): Type | undefined | ||
getType(key?: string): Type | undefined { | ||
this.type ??= Type.Object(valueMap(this.fields!, field => Type.fromField(field!))) as any | ||
return key ? Type.getInner(this.type, key) : this.type | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { defineProperty, Dict, valueMap } from 'cosmokit' | ||
import { defineProperty, Dict, filterKeys, valueMap } from 'cosmokit' | ||
import { Driver } from './driver.ts' | ||
@@ -7,2 +7,3 @@ import { Eval, executeEval } from './eval.ts' | ||
import { Keys, randomId, Row } from './utils.ts' | ||
import { Type } from './type.ts' | ||
@@ -46,3 +47,25 @@ declare module './eval.ts' { | ||
if (typeof key === 'symbol' || key in target || key.startsWith('$')) return Reflect.get(target, key) | ||
return createRow(ref, Eval('', [ref, `${prefix}${key}`]), `${prefix}${key}.`, model) | ||
let type: Type | ||
const field = model?.fields[prefix + key as string] | ||
if (Type.getInner(expr?.[Type.kType], key)) { | ||
// type may conatins object layout | ||
type = Type.getInner(expr?.[Type.kType], key)! | ||
} else if (field) { | ||
type = Type.fromField(field) | ||
} else if (Object.keys(model?.fields!).some(k => k.startsWith(`${prefix}${key}.`))) { | ||
type = Type.Object(Object.fromEntries(Object.entries(model?.fields!) | ||
.filter(([k]) => k.startsWith(`${prefix}${key}`)) | ||
.map(([k, field]) => [k.slice(prefix.length + key.length + 1), Type.fromField(field!)]))) | ||
} else { | ||
// unknown field inside json | ||
type = Type.fromField('expr') | ||
} | ||
const row = createRow(ref, Eval('', [ref, `${prefix}${key}`], type), `${prefix}${key}.`, model) | ||
if (Object.keys(model?.fields!).some(k => k.startsWith(`${prefix}${key}.`))) { | ||
return createRow(ref, Eval.object(row), `${prefix}${key}.`, model) | ||
} else { | ||
return row | ||
} | ||
}, | ||
@@ -92,7 +115,10 @@ }) | ||
const modelFields = Object.keys(this.model.fields) | ||
const keys = fields.flatMap((key) => { | ||
if (this.model.fields[key]) return key | ||
return modelFields.filter(path => path.startsWith(key + '.')) | ||
const entries = fields.flatMap((key) => { | ||
if (this.model.fields[key]) return [[key, this.row[key]]] | ||
else if (modelFields.some(path => path.startsWith(key + '.'))) { | ||
return modelFields.filter(path => path.startsWith(key + '.')).map(path => [path, this.row[path]]) | ||
} | ||
return [[key, key.split('.').reduce((row, k) => row[k], this.row)]] | ||
}) | ||
return Object.fromEntries(keys.map(key => [key, this.row[key]])) | ||
return Object.fromEntries(entries) | ||
} else { | ||
@@ -224,3 +250,5 @@ return valueMap(fields, field => this.resolveField(field)) | ||
if (!callback) callback = (row) => Eval.array(Eval.object(row)) | ||
return Eval('exec', selection._action('eval', this.resolveField(callback))) | ||
const expr = this.resolveField(callback) | ||
if (expr['$']) defineProperty(expr, Type.kType, Type.Array(Type.fromTerm(expr))) | ||
return Eval.exec(selection._action('eval', expr)) | ||
} | ||
@@ -248,2 +276,7 @@ | ||
} | ||
if (cursor.fields) { | ||
return super.execute().then( | ||
rows => rows.map(row => filterKeys(row as any, key => (cursor.fields as string[]).some(k => k === key || k.startsWith(`${key}.`)))), | ||
) | ||
} | ||
return super.execute() | ||
@@ -250,0 +283,0 @@ } |
@@ -10,21 +10,37 @@ import { Intersect } from 'cosmokit' | ||
export type Atomic = number | string | boolean | bigint | symbol | Date | ||
export interface AtomicTypes { | ||
Number: number | ||
String: string | ||
Boolean: boolean | ||
BigInt: bigint | ||
Symbol: symbol | ||
Date: Date | ||
RegExp: RegExp | ||
Function: Function | ||
ArrayBuffer: ArrayBuffer | ||
SharedArrayBuffer: SharedArrayBuffer | ||
} | ||
export type Indexable = string | number | ||
export type Comparable = string | number | boolean | Date | ||
type FlatWrap<S, T, P extends string> = { [K in P]?: S } | ||
// rule out atomic / recursive types | ||
| (S extends Atomic | T ? never | ||
type FlatWrap<S, A extends 0[], P extends string> = { [K in P]?: S } | ||
// rule out atomic types | ||
| (S extends Values<AtomicTypes> ? never | ||
// rule out array types | ||
: S extends any[] ? never | ||
// check recursion depth | ||
// rule out dict / infinite types | ||
: string extends keyof S ? never | ||
: FlatMap<S, T, `${P}.`>) | ||
: A extends [0, ...infer R extends 0[]] ? FlatMap<S, R, `${P}.`> | ||
: never) | ||
type FlatMap<S, T = never, P extends string = ''> = Values<{ | ||
[K in keyof S & string as `${P}${K}`]: FlatWrap<S[K], S | T, `${P}${K}`> | ||
type FlatMap<S, T extends 0[], P extends string = ''> = Values<{ | ||
[K in keyof S & string as `${P}${K}`]: FlatWrap<S[K], T, `${P}${K}`> | ||
}> | ||
export type Flatten<S> = Intersect<FlatMap<S>> | ||
type Sequence<N extends number, A extends 0[] = []> = A['length'] extends N ? A : Sequence<N, [0, ...A]> | ||
export type Flatten<S, D extends number = 5> = Intersect<FlatMap<S, Sequence<D>>> | ||
export type Row<S> = { | ||
@@ -55,1 +71,16 @@ [K in keyof S]-?: Row.Cell<NonNullable<S[K]>> | ||
} | ||
export function unravel(source: object, init?: (value) => any) { | ||
const result = {} | ||
for (const key in source) { | ||
let node = result | ||
const segments = key.split('.').reverse() | ||
for (let index = segments.length - 1; index > 0; index--) { | ||
const segment = segments[index] | ||
node = node[segment] ??= {} | ||
if (init) node = init(node) | ||
} | ||
node[segments[0]] = source[key] | ||
} | ||
return result | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
252010
20
4415
Updatedcosmokit@^1.6.2