Commit cec83e5c authored by nanahira's avatar nanahira

put away from koishi

parent 9e28dd4b
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
"testEnvironment": "node" "testEnvironment": "node"
}, },
"devDependencies": { "devDependencies": {
"@koishijs/plugin-database-memory": "^1.2.0", "@cosmotype/driver-memory": "^1.0.2",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/lodash": "^4.14.180", "@types/lodash": "^4.14.180",
"@types/node": "^17.0.23", "@types/node": "^17.0.23",
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
"typescript": "^4.6.3" "typescript": "^4.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"koishi": "^4.6.0" "cosmotype": "^1.0.3"
}, },
"dependencies": { "dependencies": {
"lodash": "^4.17.21", "lodash": "^4.17.21",
......
import { ModelClassType, ModelFieldDef } from './def'; import { ModelClassType, ModelFieldDef } from './def';
import { Metadata } from './meta/meta'; import { Metadata } from './meta/meta';
import { Flatten, Keys, Tables } from 'koishi'; import { Flatten, Keys } from 'cosmotype';
import { inferType } from './utils'; import { inferType } from './utils';
export const DefineModel = (name: Keys<Tables>): ClassDecorator => export class ModelDecorators<Tables = any> {
Metadata.set('ModelTableName', name); DefineModel = (name: Keys<Tables>): ClassDecorator =>
Metadata.set('ModelTableName', name);
export const ModelField = ModelField =
<T = any>(def?: ModelFieldDef<T>): PropertyDecorator => <T = any>(def?: ModelFieldDef<T>): PropertyDecorator =>
(obj, key) => (obj, key) =>
Metadata.set( Metadata.set(
'ModelField', 'ModelField',
def || inferType(Reflect.getMetadata('design:type', obj, key)), def || inferType(Reflect.getMetadata('design:type', obj, key)),
'ModelFieldKeys', 'ModelFieldKeys',
)(obj, key); )(obj, key);
export const Primary = (): PropertyDecorator => Primary = (): PropertyDecorator =>
Metadata.set('ModelPrimaryKey', { autoIncrement: false }); Metadata.set('ModelPrimaryKey', { autoIncrement: false });
export const PrimaryGenerated = (): PropertyDecorator => PrimaryGenerated = (): PropertyDecorator =>
Metadata.set('ModelPrimaryKey', { autoIncrement: true }); Metadata.set('ModelPrimaryKey', { autoIncrement: true });
export const Foreign = <K extends Keys<Tables>>( Foreign = <K extends Keys<Tables>>(
referencingTable: K, referencingTable: K,
referencingColumn: Keys<Flatten<Tables[K]>>, referencingColumn: Keys<Flatten<Tables[K]>>,
): PropertyDecorator => ): PropertyDecorator =>
Metadata.set('ModelForeignKey', [referencingTable, referencingColumn]); Metadata.set('ModelForeignKey', [referencingTable, referencingColumn]);
export const Unique = ( Unique = (identifier: string | number = '_default'): PropertyDecorator =>
identifier: string | number = '_default', Metadata.append('ModelUnique', identifier);
): PropertyDecorator => Metadata.append('ModelUnique', identifier);
export const ChildModel = ChildModel =
(cls?: ModelClassType): PropertyDecorator => (cls?: ModelClassType): PropertyDecorator =>
(obj, key) => { (obj, key) => {
if (!cls) { if (!cls) {
cls = Reflect.getMetadata('design:type', obj, key); cls = Reflect.getMetadata('design:type', obj, key);
} }
Metadata.set('ChildModel', cls, 'ChildModelKeys')(obj, key); Metadata.set('ChildModel', cls, 'ChildModelKeys')(obj, key);
}; };
}
import { Context, Field, Flatten, Keys, Model, Tables } from 'koishi'; import { Database, Field, Flatten, Keys, Model } from 'cosmotype';
import { ModelClassType } from './def'; import { ModelClassType } from './def';
import { reflector } from './meta/meta'; import { reflector } from './meta/meta';
import _ from 'lodash';
class ModelRegistrar<T = any> { class TableRegistrar<Tables, T = any> {
constructor(private cls: ModelClassType<T>, private prefix = '') {} constructor(private cls: ModelClassType<T>, private prefix = '') {}
getTableName() { getTableName() {
...@@ -123,7 +122,7 @@ class ModelRegistrar<T = any> { ...@@ -123,7 +122,7 @@ class ModelRegistrar<T = any> {
for (const key in childDict) { for (const key in childDict) {
const child = childDict[key]; const child = childDict[key];
const prefix = this.prefix + key + '.'; const prefix = this.prefix + key + '.';
const childReg = new ModelRegistrar(child, prefix); const childReg = new TableRegistrar<Tables>(child, prefix);
internal = { ...internal, ...childReg.getInternal() }; internal = { ...internal, ...childReg.getInternal() };
} }
return internal; return internal;
...@@ -135,7 +134,7 @@ class ModelRegistrar<T = any> { ...@@ -135,7 +134,7 @@ class ModelRegistrar<T = any> {
for (const key of Object.keys(children)) { for (const key of Object.keys(children)) {
const child = children[key]; const child = children[key];
if (child) { if (child) {
const childRegistrar = new ModelRegistrar( const childRegistrar = new TableRegistrar<Tables>(
child, child,
this.prefix + key + '.', this.prefix + key + '.',
); );
...@@ -159,35 +158,39 @@ class ModelRegistrar<T = any> { ...@@ -159,35 +158,39 @@ class ModelRegistrar<T = any> {
} }
} }
export function registerModel( export class ModelRegistrar<Tables> {
ctx: Context, constructor(private readonly model: Database<Tables>) {}
cls: { new (...args: any[]): any },
) {
const registrar = new ModelRegistrar(cls);
const tableName = registrar.getTableName();
if (!tableName) {
throw new Error(`Model of ${cls.name} is not defined`);
}
ctx.model.extend(tableName, ...registrar.getModelResult());
Object.assign(ctx.model.tables[tableName].internal, registrar.getInternal());
}
export function mixinModel<K extends Keys<Tables>>( registerModel(cls: ModelClassType<Flatten<Tables[Keys<Tables>]>>);
ctx: Context, registerModel(cls: ModelClassType) {
tableName: K, const registrar = new TableRegistrar(cls);
classDict: { const tableName = registrar.getTableName();
[F in Keys<Tables[K]>]?: ModelClassType<Flatten<Tables[K][F]>>; if (!tableName) {
}, throw new Error(`Model of ${cls.name} is not defined`);
) { }
for (const _key in classDict) { this.model.extend(tableName, ...registrar.getModelResult());
const key = _key as Keys<Tables[K]>;
const cls = classDict[key];
const registrar = new ModelRegistrar<any>(cls, key + '.');
const result = registrar.getModelResult();
ctx.model.extend(tableName, ...result);
Object.assign( Object.assign(
ctx.model.tables[tableName].internal, this.model.tables[tableName].internal,
registrar.getInternal(), registrar.getInternal(),
); );
} }
mixinModel<K extends Keys<Tables>>(
tableName: K,
classDict: {
[F in Keys<Tables[K]>]?: ModelClassType<Flatten<Tables[K][F]>>;
},
) {
for (const _key in classDict) {
const key = _key as Keys<Tables[K]>;
const cls = classDict[key];
const registrar = new TableRegistrar<any>(cls, key + '.');
const result = registrar.getModelResult();
this.model.extend(tableName, ...result);
Object.assign(
this.model.tables[tableName].internal,
registrar.getInternal(),
);
}
}
} }
import { import MemoryDriver from '@cosmotype/driver-memory';
ChildModel, import { ModelDecorators } from '../src/decorators';
DefineModel, import { Database } from 'cosmotype';
Foreign, import { ModelRegistrar } from '../src/register';
ModelField,
PrimaryGenerated,
Unique,
} from '../src/decorators';
import { App } from 'koishi';
import { registerModel } from '../src/register';
import * as MemoryDatabase from '@koishijs/plugin-database-memory';
declare module 'koishi' { interface Tables {
// eslint-disable-next-line @typescript-eslint/no-empty-interface dress: Dress;
interface Tables {
dress: Dress;
}
} }
const decorators = new ModelDecorators<Tables>();
class DressProperty { class DressProperty {
@ModelField('string(8)') @decorators.ModelField('string(8)')
color: string; color: string;
@ModelField('integer(7)') @decorators.ModelField('integer(7)')
size: string; size: string;
getProperty() { getProperty() {
...@@ -28,40 +19,41 @@ class DressProperty { ...@@ -28,40 +19,41 @@ class DressProperty {
} }
} }
@DefineModel('dress') @decorators.DefineModel('dress')
class Dress { class Dress {
@PrimaryGenerated() @decorators.PrimaryGenerated()
@ModelField('integer(11)') @decorators.ModelField('integer(11)')
id: number; id: number;
@Unique() @decorators.Unique()
@ModelField() @decorators.ModelField()
name: string; // test if it can infer type name: string; // test if it can infer type
getName() { getName() {
return this.name; return this.name;
} }
@ModelField('integer(11)') @decorators.ModelField('integer(11)')
@Foreign('dress', 'id') @decorators.Foreign('dress', 'id')
parentId: number; parentId: number;
@ChildModel() @decorators.ChildModel()
properties: DressProperty; properties: DressProperty;
} }
describe('Model test', () => { describe('Model test', () => {
let app: App; let model: Database<Tables>;
beforeEach(async () => { beforeEach(async () => {
app = new App(); model = new Database();
registerModel(app, Dress); const registrar = new ModelRegistrar(model);
app.plugin(MemoryDatabase); registrar.registerModel(Dress);
await app.start(); const database = new MemoryDriver(model, {});
await database.start();
}); });
it('should register model fields', () => { it('should register model fields', () => {
const { dress } = app.model.tables; const { dress } = model.tables;
expect(dress.fields.id.type).toBe('integer'); expect(dress.fields.id.type).toBe('integer');
expect(dress.fields.id.length).toBe(11); expect(dress.fields.id.length).toBe(11);
expect(dress.fields.name.type).toBe('string'); expect(dress.fields.name.type).toBe('string');
...@@ -74,7 +66,7 @@ describe('Model test', () => { ...@@ -74,7 +66,7 @@ describe('Model test', () => {
}); });
it('should register model extras', () => { it('should register model extras', () => {
const { dress } = app.model.tables; const { dress } = model.tables;
expect(dress.primary[0]).toBe('id'); expect(dress.primary[0]).toBe('id');
expect(dress.unique[0][0]).toBe('name'); expect(dress.unique[0][0]).toBe('name');
expect(dress.foreign.parentId).toStrictEqual(['dress', 'id']); expect(dress.foreign.parentId).toStrictEqual(['dress', 'id']);
...@@ -83,7 +75,7 @@ describe('Model test', () => { ...@@ -83,7 +75,7 @@ describe('Model test', () => {
}); });
it('should make class instance', async () => { it('should make class instance', async () => {
await app.database.upsert('dress', [ await model.upsert('dress', [
{ {
id: 777, id: 777,
name: 'Dress of Shigma', name: 'Dress of Shigma',
...@@ -93,7 +85,7 @@ describe('Model test', () => { ...@@ -93,7 +85,7 @@ describe('Model test', () => {
}, },
}, },
]); ]);
const [dress] = await app.database.get('dress', { id: 777 }); const [dress] = await model.get('dress', { id: 777 });
expect(dress).toBeInstanceOf(Dress); expect(dress).toBeInstanceOf(Dress);
expect(dress.id).toBe(777); expect(dress.id).toBe(777);
expect(dress.getName()).toBe('Dress of Shigma'); expect(dress.getName()).toBe('Dress of Shigma');
......
import { ChildModel, ModelField } from '../src/decorators'; import MemoryDriver from '@cosmotype/driver-memory';
import { App } from 'koishi'; import { ModelDecorators } from '../src/decorators';
import { mixinModel } from '../src/register'; import { Database } from 'cosmotype';
import * as MemoryDatabase from '@koishijs/plugin-database-memory'; import { ModelRegistrar } from '../src/register';
declare module 'koishi' { interface User {
// eslint-disable-next-line @typescript-eslint/no-empty-interface id: string;
interface User { dress: Dress;
dress: Dress; shirt: Shirt;
shirt: Shirt; }
}
interface Tables {
user: User;
} }
const decorators = new ModelDecorators<Tables>();
class WearingPreference { class WearingPreference {
@ModelField('string(8)') @decorators.ModelField('string(8)')
color: string; color: string;
@ModelField('string(12)') @decorators.ModelField('string(12)')
shape: string; shape: string;
format() { format() {
...@@ -23,14 +27,14 @@ class WearingPreference { ...@@ -23,14 +27,14 @@ class WearingPreference {
} }
class Wearing { class Wearing {
@ModelField('string(3)') @decorators.ModelField('string(3)')
size: string; size: string;
getSize() { getSize() {
return this.size; return this.size;
} }
@ChildModel() @decorators.ChildModel()
preference: WearingPreference; preference: WearingPreference;
} }
...@@ -46,17 +50,30 @@ class Shirt extends Wearing { ...@@ -46,17 +50,30 @@ class Shirt extends Wearing {
} }
describe('Model test', () => { describe('Model test', () => {
let app: App; let model: Database<Tables>;
beforeEach(async () => { beforeEach(async () => {
app = new App(); model = new Database();
app.plugin(MemoryDatabase); const registrar = new ModelRegistrar(model);
mixinModel(app, 'user', { dress: Dress, shirt: Shirt }); model.extend(
await app.start(); 'user',
{
id: 'string(8)',
},
{
primary: 'id',
},
);
registrar.mixinModel('user', {
dress: Dress,
shirt: Shirt,
});
const database = new MemoryDriver(model, {});
await database.start();
}); });
it('should register model fields', () => { it('should register model fields', () => {
const { user } = app.model.tables; const { user } = model.tables;
expect(user.fields['dress.size'].type).toBe('string'); expect(user.fields['dress.size'].type).toBe('string');
expect(user.fields['dress.size'].length).toBe(3); expect(user.fields['dress.size'].length).toBe(3);
expect(user.fields['shirt.size'].type).toBe('string'); expect(user.fields['shirt.size'].type).toBe('string');
...@@ -81,7 +98,7 @@ describe('Model test', () => { ...@@ -81,7 +98,7 @@ describe('Model test', () => {
}); });
it('should make class instance', async () => { it('should make class instance', async () => {
await app.database.upsert('user', [ await model.upsert('user', [
{ {
id: '111111', id: '111111',
dress: { dress: {
...@@ -100,7 +117,7 @@ describe('Model test', () => { ...@@ -100,7 +117,7 @@ describe('Model test', () => {
}, },
}, },
]); ]);
const [user] = await app.database.get('user', { id: '111111' }); const [user] = await model.get('user', { id: '111111' });
expect(user.dress).toBeInstanceOf(Dress); expect(user.dress).toBeInstanceOf(Dress);
expect(user.dress.getDressSize()).toBe('S'); expect(user.dress.getDressSize()).toBe('S');
expect(user.dress.preference).toBeInstanceOf(WearingPreference); expect(user.dress.preference).toBeInstanceOf(WearingPreference);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment