diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index 97b16123..ba8bf6d9 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -48,12 +48,25 @@ export namespace Join2 { const kTransaction = Symbol('transaction') -export class Database extends Service { +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 { 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]: Model } = Object.create(null) - public drivers: Record = Object.create(null) + // { [K in Keys]: Model } + public tables: any = Object.create(null) + public drivers: Record = Object.create(null) public types: Dict = Object.create(null) public migrating = false private prepareTasks: Dict> = Object.create(null) @@ -79,7 +92,7 @@ export class Database extends Ser } } - private getDriver(table: any) { + private getDriver(table: any): Driver { // const model: Model = this.tables[name] // if (model.driver) return this.drivers[model.driver] const driver = Object.values(this.drivers)[0] @@ -189,8 +202,13 @@ export class Database extends Ser return type } - define, Field.Type | 'object' | 'array'>>(name: K, field: Field.Definition | Field.Transform): K - define(field: Field.Definition | Field.Transform): Field.NewType + // FIXME + // define, Field.Type | 'object' | 'array'>>( + // name: K, + // field: Field.Definition | Field.Transform, + // ): K + + // define(field: Field.Definition | Field.Transform): Field.NewType define(name: any, field?: any) { if (typeof name === 'object') { field = name @@ -212,7 +230,11 @@ export class Database extends Ser return name as any } - migrate>(name: K, fields: Field.Extension, callback: Model.Migration) { + migrate>( + name: K, + fields: Field.Extension, + callback: Model.Migration, + ) { this.extend(name, fields, { callback }) } @@ -222,8 +244,18 @@ export class Database extends Ser return new Selection(this.getDriver(table), table, query) } - join>(tables: U, callback?: Join1.Predicate, optional?: boolean[]): Selection> - join>(tables: U, callback?: Join2.Predicate, optional?: Dict>): Selection> + join>( + tables: U, + callback?: Join1.Predicate, + optional?: boolean[], + ): Selection> + + join>( + tables: U, + callback?: Join2.Predicate, + optional?: Dict>, + ): Selection> + 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)]))) @@ -233,7 +265,9 @@ export class Database 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) => typeof t === 'string' ? this.select(t) : t)) + const sel = new Selection(this.getDriver(Object.values(tables)[0]), valueMap(tables, (t: TableLike) => { + return typeof t === 'string' ? this.select(t) : t + })) if (typeof query === 'function') { sel.args[0].having = Eval.and(query(sel.row)) } @@ -242,7 +276,11 @@ export class Database extends Ser } } - async get, K extends Keys>(table: T, query: Query, cursor?: Driver.Cursor): Promise[]> { + async get, K extends Keys>( + table: T, + query: Query, + cursor?: Driver.Cursor, + ): Promise[]> { return this.select(table, query).execute(cursor) } @@ -250,7 +288,11 @@ export class Database extends Ser return this.select(table, query).execute(typeof expr === 'function' ? expr : () => expr) } - async set>(table: T, query: Query, update: Row.Computed>): Promise { + async set>( + table: T, + query: Query, + update: Row.Computed>, + ): Promise { const sel = this.select(table, query) if (typeof update === 'function') update = update(sel.row) const primary = makeArray(sel.model.primary) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index b9f0d4db..508e0cdb 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -44,7 +44,7 @@ export namespace Driver { export type Constructor = new (ctx: Context, config: T) => Driver } -export abstract class Driver { +export abstract class Driver { static inject = ['model'] abstract start(): Promise @@ -61,11 +61,11 @@ export abstract class Driver { abstract upsert(sel: Selection.Mutable, data: any[], keys: string[]): Promise abstract withTransaction(callback: (driver: this) => Promise): Promise - public database: Database + public database: Database public logger: Logger public types: Dict = 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) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 35d87689..51804c06 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -16,8 +16,10 @@ declare module 'cordis' { } interface Context { - database: Database - model: Database + [Database.Tables]: Database.Tables + [Database.Types]: Database.Types + database: Database + model: Database } } diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index b3aab7c3..cb731d5d 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -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 { - type: Type + // FIXME Type + type: Field.Type | Type deftype?: Field.Type length?: number nullable?: boolean @@ -75,8 +76,11 @@ export namespace Field { type: Type | Field['type'] } & Omit, 'type'> + // FIXME + // eslint-disable-next-line @typescript-eslint/no-unused-vars type MapField = { - [K in keyof O]?: Literal | Definition | Transform + // [K in keyof O]?: Literal | Definition | Transform + [K in keyof O]?: Field | Shorthand> | Selection.Callback } export type Extension = MapField, N> @@ -134,7 +138,7 @@ export namespace Field { } export namespace Model { - export type Migration = (database: Database) => Promise + export type Migration = (database: D) => Promise export interface Config { callback?: Migration diff --git a/packages/core/src/selection.ts b/packages/core/src/selection.ts index cab05eeb..54c121f7 100644 --- a/packages/core/src/selection.ts +++ b/packages/core/src/selection.ts @@ -160,7 +160,7 @@ export interface Selection extends Executable.Payload { export class Selection extends Executable { public tables: Dict = {} - constructor(driver: Driver, table: string | Selection | Dict, query?: Query) { + constructor(driver: Driver, table: string | Selection | Dict, query?: Query) { super(driver, { type: 'get', ref: randomId(), diff --git a/packages/core/src/type.ts b/packages/core/src/type.ts index 9c71208e..439e474a 100644 --- a/packages/core/src/type.ts +++ b/packages/core/src/type.ts @@ -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 { [Type.kType]?: true - type: Field.Type | Keys | Field.NewType + // FIXME + type: Field.Type // | Keys | Field.NewType inner?: T extends (infer I)[] ? Type : Field.Type extends 'json' ? { [key in keyof T]: Type } : never array?: boolean } @@ -49,9 +50,10 @@ export namespace Type { throw new TypeError(`invalid primitive: ${value}`) } - export function fromField(field: Type | Field | Field.Type | Keys | Field.NewType): Type { + // FIXME: Type | Field | Field.Type | Keys | Field.NewType + export function fromField(field: any): Type { 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}`) diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index f6b67469..8fdad043 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -326,7 +326,7 @@ export class SQLiteDriver extends Driver { 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) } @@ -362,7 +362,7 @@ export class SQLiteDriver extends Driver { 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) {