Skip to content

Commit

Permalink
fix(minato): fix typing issues
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 10, 2024
1 parent 037cd6a commit a90bbfe
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 28 deletions.
66 changes: 54 additions & 12 deletions packages/core/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,25 @@ export namespace Join2 {

const kTransaction = Symbol('transaction')

export class Database<S = any, N = any, C extends Context = Context> extends Service<undefined, C> {
export namespace Database {
export interface Tables {}

export interface Types {}
}

export class Database<
C extends Context = Context,
S extends C[typeof Database.Tables] = C[typeof Database.Tables],
N extends C[typeof Database.Types] = C[typeof Database.Types],
> extends Service<undefined, C> {
static [Service.provide] = 'model'
static [Service.immediate] = true
static readonly Tables = Symbol('minato.tables')
static readonly Types = Symbol('minato.types')

public tables: { [K in Keys<S>]: Model<S[K]> } = Object.create(null)
public drivers: Record<keyof any, Driver> = Object.create(null)
// { [K in Keys<S>]: Model<S[K]> }
public tables: any = Object.create(null)
public drivers: Record<keyof any, any> = Object.create(null)
public types: Dict<Field.Transform> = Object.create(null)
public migrating = false
private prepareTasks: Dict<Promise<void>> = Object.create(null)
Expand All @@ -79,7 +92,7 @@ export class Database<S = any, N = any, C extends Context = Context> extends Ser
}
}

private getDriver(table: any) {
private getDriver(table: any): Driver<any, C> {
// const model: Model = this.tables[name]
// if (model.driver) return this.drivers[model.driver]
const driver = Object.values(this.drivers)[0]
Expand Down Expand Up @@ -189,8 +202,13 @@ export class Database<S = any, N = any, C extends Context = Context> extends Ser
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>
// FIXME
// 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<T>(field: Field.Definition<T, N> | Field.Transform<T, any, N>): Field.NewType<T>
define(name: any, field?: any) {
if (typeof name === 'object') {
field = name
Expand All @@ -212,7 +230,11 @@ export class Database<S = any, N = any, C extends Context = Context> extends Ser
return name as any
}

migrate<K extends Keys<S>>(name: K, fields: Field.Extension<S[K], N>, callback: Model.Migration) {
migrate<K extends Keys<S>>(
name: K,
fields: Field.Extension<S[K], N>,
callback: Model.Migration<this>,
) {
this.extend(name, fields, { callback })
}

Expand All @@ -222,8 +244,18 @@ export class Database<S = any, N = any, C extends Context = Context> extends Ser
return new Selection(this.getDriver(table), table, query)
}

join<const U extends Join1.Input<S>>(tables: U, callback?: Join1.Predicate<S, U>, optional?: boolean[]): Selection<Join1.Output<S, U>>
join<const U extends Join2.Input<S>>(tables: U, callback?: Join2.Predicate<S, U>, optional?: Dict<boolean, Keys<U>>): Selection<Join2.Output<S, U>>
join<const U extends Join1.Input<S>>(
tables: U,
callback?: Join1.Predicate<S, U>,
optional?: boolean[],
): Selection<Join1.Output<S, U>>

join<const U extends Join2.Input<S>>(
tables: U,
callback?: Join2.Predicate<S, U>,
optional?: Dict<boolean, Keys<U>>,
): Selection<Join2.Output<S, U>>

join(tables: any, query?: any, optional?: any) {
if (Array.isArray(tables)) {
const sel = new Selection(this.getDriver(tables[0]), Object.fromEntries(tables.map((name) => [name, this.select(name)])))
Expand All @@ -233,7 +265,9 @@ export class Database<S = any, N = any, C extends Context = Context> extends Ser
sel.args[0].optional = Object.fromEntries(tables.map((name, index) => [name, optional?.[index]]))
return this.select(sel)
} else {
const sel = new Selection(this.getDriver(Object.values(tables)[0]), valueMap(tables, (t: TableLike<S>) => typeof t === 'string' ? this.select(t) : t))
const sel = new Selection(this.getDriver(Object.values(tables)[0]), valueMap(tables, (t: TableLike<S>) => {
return typeof t === 'string' ? this.select(t) : t
}))
if (typeof query === 'function') {
sel.args[0].having = Eval.and(query(sel.row))
}
Expand All @@ -242,15 +276,23 @@ export class Database<S = any, N = any, C extends Context = Context> extends Ser
}
}

async get<T extends Keys<S>, K extends Keys<S[T]>>(table: T, query: Query<S[T]>, cursor?: Driver.Cursor<K>): Promise<Pick<S[T], K>[]> {
async get<T extends Keys<S>, K extends Keys<S[T]>>(
table: T,
query: Query<S[T]>,
cursor?: Driver.Cursor<K>,
): Promise<Pick<S[T], K>[]> {
return this.select(table, query).execute(cursor)
}

async eval<T extends Keys<S>, U>(table: T, expr: Selection.Callback<S[T], U, true>, query?: Query<S[T]>): Promise<U> {
return this.select(table, query).execute(typeof expr === 'function' ? expr : () => expr)
}

async set<T extends Keys<S>>(table: T, query: Query<S[T]>, update: Row.Computed<S[T], Update<S[T]>>): Promise<Driver.WriteResult> {
async set<T extends Keys<S>>(
table: T,
query: Query<S[T]>,
update: Row.Computed<S[T], Update<S[T]>>,
): Promise<Driver.WriteResult> {
const sel = this.select(table, query)
if (typeof update === 'function') update = update(sel.row)
const primary = makeArray(sel.model.primary)
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export namespace Driver {
export type Constructor<T> = new (ctx: Context, config: T) => Driver<T>
}

export abstract class Driver<T = any> {
export abstract class Driver<T = any, C extends Context = Context> {
static inject = ['model']

abstract start(): Promise<void>
Expand All @@ -61,11 +61,11 @@ export abstract class Driver<T = any> {
abstract upsert(sel: Selection.Mutable, data: any[], keys: string[]): Promise<Driver.WriteResult>
abstract withTransaction(callback: (driver: this) => Promise<void>): Promise<void>

public database: Database
public database: Database<C>
public logger: Logger
public types: Dict<Driver.Transformer> = Object.create(null)

constructor(public ctx: Context, public config: T) {
constructor(public ctx: C, public config: T) {
this.database = ctx.model
this.logger = ctx.logger(this.constructor.name)

Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ declare module 'cordis' {
}

interface Context {
database: Database
model: Database
[Database.Tables]: Database.Tables
[Database.Types]: Database.Types
database: Database<this>
model: Database<this>
}
}

Expand Down
12 changes: 8 additions & 4 deletions packages/core/src/model.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Binary, clone, isNullable, makeArray, MaybeArray, valueMap } from 'cosmokit'
import { Database } from './database.ts'
import { Eval, isEvalExpr } from './eval.ts'
import { Flatten, Keys, unravel } from './utils.ts'
import { Type } from './type.ts'
import { Driver } from './driver.ts'
import { Selection } from './selection.ts'

export const Primary = Symbol('Primary')
export type Primary = (string | number) & { [Primary]: true }

export interface Field<T = any> {
type: Type<T>
// FIXME Type<T>
type: Field.Type<T> | Type<T>
deftype?: Field.Type<T>
length?: number
nullable?: boolean
Expand Down Expand Up @@ -75,8 +76,11 @@ export namespace Field {
type: Type<T> | Field<T>['type']
} & Omit<Field<T>, 'type'>

// FIXME
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type MapField<O = any, N = any> = {
[K in keyof O]?: Literal<O[K], N> | Definition<O[K], N> | Transform<O[K], any, N>
// [K in keyof O]?: Literal<O[K], N> | Definition<O[K], N> | Transform<O[K], any, N>
[K in keyof O]?: Field<O[K]> | Shorthand<Type<O[K]>> | Selection.Callback<O, O[K]>
}

export type Extension<O = any, N = any> = MapField<Flatten<O>, N>
Expand Down Expand Up @@ -134,7 +138,7 @@ export namespace Field {
}

export namespace Model {
export type Migration = (database: Database) => Promise<void>
export type Migration<D = any> = (database: D) => Promise<void>

export interface Config<O = {}> {
callback?: Migration
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export interface Selection extends Executable.Payload {
export class Selection<S = any> extends Executable<S, S[]> {
public tables: Dict<Model> = {}

constructor(driver: Driver, table: string | Selection | Dict<Selection.Immutable>, query?: Query) {
constructor(driver: Driver<any>, table: string | Selection | Dict<Selection.Immutable>, query?: Query) {
super(driver, {
type: 'get',
ref: randomId(),
Expand Down
10 changes: 6 additions & 4 deletions packages/core/src/type.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Binary, defineProperty, isNullable, mapValues } from 'cosmokit'
import { Field } from './model.ts'
import { Eval, isEvalExpr } from './eval.ts'
import { Keys } from './utils.ts'
// import { Keys } from './utils.ts'

export interface Type<T = any, N = any> {
[Type.kType]?: true
type: Field.Type<T> | Keys<N, T> | Field.NewType<T>
// FIXME
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
}
Expand Down Expand Up @@ -49,9 +50,10 @@ export namespace Type {
throw new TypeError(`invalid primitive: ${value}`)
}

export function fromField<T, N>(field: Type | Field<T> | Field.Type<T> | Keys<N, T> | Field.NewType<T>): Type<T, N> {
// FIXME: Type | Field<T> | Field.Type<T> | Keys<N, T> | Field.NewType<T>
export function fromField<T, N>(field: any): Type<T, N> {
if (isType(field)) return field
if (typeof field === 'string') return defineProperty({ type: field }, kType, true)
if (typeof field === 'string') return defineProperty({ type: field }, kType, true) as never
else if (field.type) return field.type
else if (field.expr?.[kType]) return field.expr[kType]
throw new TypeError(`invalid field: ${field}`)
Expand Down
4 changes: 2 additions & 2 deletions packages/sqlite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ export class SQLiteDriver extends Driver<SQLiteDriver.Config> {
return Object.keys(fields).find(field => field === key || key.startsWith(field + '.'))!
}))]
const primaryFields = makeArray(primary)
const data = await this.database.get(table, query)
const data = await this.database.get(table as never, query)
for (const row of data) {
this.#update(sel, primaryFields, updateFields, update, row)
}
Expand Down Expand Up @@ -362,7 +362,7 @@ export class SQLiteDriver extends Driver<SQLiteDriver.Config> {
const step = Math.floor(960 / keys.length)
for (let i = 0; i < data.length; i += step) {
const chunk = data.slice(i, i + step)
const results = await this.database.get(table, {
const results = await this.database.get(table as never, {
$or: chunk.map(item => Object.fromEntries(keys.map(key => [key, item[key]]))),
})
for (const item of chunk) {
Expand Down

0 comments on commit a90bbfe

Please sign in to comment.