Skip to content

Commit

Permalink
refa: refactor database to association api
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 26, 2024
1 parent 174ac2b commit 6c0eaa0
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 43 deletions.
7 changes: 2 additions & 5 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Session } from './session'
import { Processor } from './middleware'
import { SchemaService } from './schema'
import { Permissions } from './permission'
import { DatabaseService } from './database'
import { KoishiDatabase } from './database'

export type EffectScope = cordis.EffectScope<Context>
export type ForkScope = cordis.ForkScope<Context>
Expand Down Expand Up @@ -49,7 +49,6 @@ export class Context extends satori.Context {

constructor(config: Context.Config = {}) {
super(config)
this.mixin('model', ['getSelfIds', 'broadcast'])
this.mixin('$processor', ['match', 'middleware'])
this.mixin('$filter', [
'any', 'never', 'union', 'intersect', 'exclude',
Expand All @@ -64,7 +63,7 @@ export class Context extends satori.Context {
this.provide('database', undefined, true)
this.provide('model', undefined, true)
this.provide('$commander', new Commander(this, this.config), true)
this.plugin(DatabaseService)
this.provide('koishi.database', new KoishiDatabase(this), true)
}

/** @deprecated use `ctx.root` instead */
Expand Down Expand Up @@ -115,8 +114,6 @@ Session.prototype[Context.filter] = function (this: Session, ctx: Context) {
}

export namespace Context {
export type Associate<P extends string, C extends Context = Context> = satori.Context.Associate<P, C>

export interface Config extends Config.Basic, Config.Advanced {
i18n?: I18n.Config
delay?: Config.Delay
Expand Down
96 changes: 61 additions & 35 deletions packages/core/src/database.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
import * as utils from '@koishijs/utils'
import { Dict, MaybeArray } from 'cosmokit'
import { Database, Driver, Update } from 'minato'
import { Driver, FlatKeys, FlatPick, Update } from 'minato'
import * as minato from 'minato'
import { Fragment, Universal } from '@satorijs/core'
import { Context } from './context'

declare module './context' {
interface Events {
'model'(name: keyof Tables): void
interface Context {
[minato.Types]: Types
[minato.Tables]: Tables
[Context.Database]: Context.Database<this>
}

interface Context {
database: DatabaseService
model: DatabaseService
broadcast(content: Fragment, forced?: boolean): Promise<string[]>
broadcast(channels: readonly string[], content: Fragment, forced?: boolean): Promise<string[]>
namespace Context {
interface Database<C extends Context = Context> {
getUser<K extends FlatKeys<User>>(platform: string, pid: string, modifier?: Driver.Cursor<K>): Promise<FlatPick<User, K>>
setUser(platform: string, pid: string, data: Update<User>): Promise<void>
createUser(platform: string, pid: string, data: Partial<User>): Promise<User>
getChannel<K extends FlatKeys<Channel>>(platform: string, id: MaybeArray<string>, modifier?: Driver.Cursor<K>): Promise<FlatPick<Channel, K> | FlatPick<Channel, K>[]>
getAssignedChannels<K extends Channel.Field>(fields?: K[], selfIdMap?: Dict<string[]>): Promise<Pick<Channel, K>[]>
setChannel(platform: string, id: string, data: Update<Channel>): Promise<void>
createChannel(platform: string, id: string, data: Partial<Channel>): Promise<Channel>
broadcast(content: Fragment, forced?: boolean): Promise<string[]>
broadcast(channels: readonly string[], content: Fragment, forced?: boolean): Promise<string[]>
}
}
}

export interface Types extends minato.Types {}

export interface Tables extends minato.Tables {
user: User
binding: Binding
channel: Channel
}

export interface User {
id: number
name: string
Expand Down Expand Up @@ -66,17 +84,25 @@ export namespace Channel {
export type Observed<K extends Field = Field> = utils.Observed<Pick<Channel, K>, Promise<void>>
}

export interface Tables {
user: User
binding: Binding
channel: Channel
}

export class DatabaseService extends Database<Tables, Context> {
constructor(ctx: Context) {
super(ctx)
// Do not set "database" inject for this service.
export class KoishiDatabase {
constructor(public ctx: Context) {
ctx.plugin(minato.Database)

ctx.set('koishi.database', this)

ctx.mixin('koishi.database', {
getUser: 'database.getUser',
setUser: 'database.setUser',
createUser: 'database.createUser',
getChannel: 'database.getChannel',
getAssignedChannels: 'database.getAssignedChannels',
setChannel: 'database.setChannel',
createChannel: 'database.createChannel',
broadcast: 'broadcast',
})

this.extend('user', {
ctx.model.extend('user', {
id: 'unsigned(8)',
name: { type: 'string', length: 255 },
flag: 'unsigned(8)',
Expand All @@ -88,7 +114,7 @@ export class DatabaseService extends Database<Tables, Context> {
autoInc: true,
})

this.extend('binding', {
ctx.model.extend('binding', {
aid: 'unsigned(8)',
bid: 'unsigned(8)',
pid: 'string(255)',
Expand All @@ -97,7 +123,7 @@ export class DatabaseService extends Database<Tables, Context> {
primary: ['pid', 'platform'],
})

this.extend('channel', {
ctx.model.extend('channel', {
id: 'string(255)',
platform: 'string(255)',
flag: 'unsigned(8)',
Expand All @@ -111,8 +137,8 @@ export class DatabaseService extends Database<Tables, Context> {
})

ctx.on('login-added', ({ platform }) => {
if (platform in this.tables.user.fields) return
this.migrate('user', { [platform]: 'string(255)' }, async (db) => {
if (platform in ctx.model.tables.user.fields) return
ctx.model.migrate('user', { [platform]: 'string(255)' }, async (db) => {
const users = await db.get('user', { [platform]: { $exists: true } }, ['id', platform as never])
await db.upsert('binding', users.filter(u => u[platform]).map((user) => ({
aid: user.id,
Expand All @@ -124,29 +150,29 @@ export class DatabaseService extends Database<Tables, Context> {
})
}

async getUser<K extends User.Field>(platform: string, pid: string, modifier?: Driver.Cursor<K>): Promise<Pick<User, K>> {
const [binding] = await this.get('binding', { platform, pid }, ['aid'])
async getUser<K extends FlatKeys<User>>(platform: string, pid: string, modifier?: Driver.Cursor<K>): Promise<FlatPick<User, K>> {
const [binding] = await this.ctx.database.get('binding', { platform, pid }, ['aid'])
if (!binding) return
const [user] = await this.get('user', { id: binding.aid }, modifier)
const [user] = await this.ctx.database.get('user', { id: binding.aid }, modifier)
return user
}

async setUser(platform: string, pid: string, data: Update<User>) {
const [binding] = await this.get('binding', { platform, pid }, ['aid'])
const [binding] = await this.ctx.database.get('binding', { platform, pid }, ['aid'])
if (!binding) throw new Error('user not found')
return this.set('user', binding.aid, data)
return this.ctx.database.set('user', binding.aid, data)
}

async createUser(platform: string, pid: string, data: Partial<User>) {
const user = await this.create('user', data)
await this.create('binding', { aid: user.id, bid: user.id, pid, platform })
const user = await this.ctx.database.create('user', data)
await this.ctx.database.create('binding', { aid: user.id, bid: user.id, pid, platform })
return user
}

getChannel<K extends Channel.Field>(platform: string, id: string, modifier?: Driver.Cursor<K>): Promise<Pick<Channel, K | 'id' | 'platform'>>
getChannel<K extends Channel.Field>(platform: string, ids: string[], modifier?: Driver.Cursor<K>): Promise<Pick<Channel, K>[]>
getChannel<K extends FlatKeys<Channel>>(platform: string, id: string, modifier?: Driver.Cursor<K>): Promise<FlatPick<Channel, K | 'id' | 'platform'>>
getChannel<K extends FlatKeys<Channel>>(platform: string, ids: string[], modifier?: Driver.Cursor<K>): Promise<FlatPick<Channel, K>[]>
async getChannel(platform: string, id: MaybeArray<string>, modifier?: Driver.Cursor<Channel.Field>) {
const data = await this.get('channel', { platform, id }, modifier)
const data = await this.ctx.database.get('channel', { platform, id }, modifier)
if (Array.isArray(id)) return data
if (data[0]) Object.assign(data[0], { platform, id })
return data[0]
Expand All @@ -163,17 +189,17 @@ export class DatabaseService extends Database<Tables, Context> {

getAssignedChannels<K extends Channel.Field>(fields?: K[], selfIdMap?: Dict<string[]>): Promise<Pick<Channel, K>[]>
async getAssignedChannels(fields?: Channel.Field[], selfIdMap: Dict<string[]> = this.getSelfIds()) {
return this.get('channel', {
return this.ctx.database.get('channel', {
$or: Object.entries(selfIdMap).map(([platform, assignee]) => ({ platform, assignee })),
}, fields)
}

setChannel(platform: string, id: string, data: Update<Channel>) {
return this.set('channel', { platform, id }, data)
return this.ctx.database.set('channel', { platform, id }, data)
}

createChannel(platform: string, id: string, data: Partial<Channel>) {
return this.create('channel', { platform, id, ...data })
return this.ctx.database.create('channel', { platform, id, ...data })
}

async broadcast(...args: [Fragment, boolean?] | [readonly string[], Fragment, boolean?]) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ export * from './session'
export * from './permission'
export * from './command'

export { Tables, Types } from './database'

export { version }
8 changes: 7 additions & 1 deletion packages/core/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ declare module '@satorijs/core' {
export class SchemaService {
_data: Dict<Schema> = Object.create(null)

constructor(public ctx: Context) {}
constructor(public ctx: Context) {
this.extend('intercept.http', Schema.object({
timeout: Schema.natural().role('ms').description('等待连接建立的最长时间。'),
proxyAgent: Schema.string().description('使用的代理服务器地址。'),
keepAlive: Schema.boolean().description('是否保持连接。'),
}))
}

extend(name: string, schema: Schema, order = 0) {
const caller = this[Context.current]
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ function collectFields<T extends keyof Tables>(argv: Argv, collectors: FieldColl
return fields
}

export interface Session extends Context.Associate<'session'> {}

export class Session<U extends User.Field = never, G extends Channel.Field = never, C extends Context = Context> extends satori.Session<C> {
static shadow = Symbol.for('session.shadow')

Expand Down

0 comments on commit 6c0eaa0

Please sign in to comment.