Skip to content

Commit

Permalink
feat: impl Inject Model (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
killagu authored Jul 20, 2022
1 parent 39a0817 commit ced2ce2
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 15 deletions.
6 changes: 3 additions & 3 deletions core/orm-decorator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ export class App extends Bone {
## Use Model

```ts
import { ContextProto } from '@eggjs/tegg';
import { ContextProto, Inject } from '@eggjs/tegg';
import { App } from './model/App';

@ContextProto()
export class AppService {
// TODO impl inject Bone for context
App: typeof App = App;
@Inject()
App: typeof App;

async createApp(data: {
name: string;
Expand Down
4 changes: 4 additions & 0 deletions core/orm-decorator/src/decorator/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ export interface ModelParams {
tableName?: string;
}

export const MODEL_PROTO_IMPL_TYPE = 'MODEL_PROTO';

export function Model(param?: ModelParams) {
return function(clazz: EggProtoImplClass) {
ModelInfoUtil.setIsModel(true, clazz);
const func = ContextProto({
name: clazz.name,
accessLevel: AccessLevel.PUBLIC,
protoImplType: MODEL_PROTO_IMPL_TYPE,
});
if (param?.tableName) {
ModelInfoUtil.setTableName(param.tableName, clazz);
Expand Down
4 changes: 2 additions & 2 deletions core/orm-decorator/src/util/ModelMetadataUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { ModelMetadata } from '../model/ModelMetadata';
export const MODEL_METADATA = Symbol.for('EggPrototype#model#metadata');

export class ModelMetadataUtil {
static setControllerMetadata(clazz: EggProtoImplClass, metaData: ModelMetadata) {
static setModelMetadata(clazz: EggProtoImplClass, metaData: ModelMetadata) {
MetadataUtil.defineMetaData(MODEL_METADATA, metaData, clazz);
}

static getControllerMetadata(clazz): ModelMetadata | undefined {
static getModelMetadata(clazz): ModelMetadata | undefined {
return MetadataUtil.getOwnMetaData(MODEL_METADATA, clazz);
}
}
6 changes: 6 additions & 0 deletions plugin/orm/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { DataSourceManager } from './lib/DataSourceManager';
import { LeoricRegister } from './lib/LeoricRegister';
import { ModelProtoManager } from './lib/ModelProtoManager';
import { ModelProtoHook } from './lib/ModelProtoHook';
import { MODEL_PROTO_IMPL_TYPE } from '@eggjs/tegg-orm-decorator';
import ContextModelProto from './lib/ContextModelProto';
import { ContextModeObject } from './lib/ContextModeObject';

export default class OrmAppBootHook {
private readonly app: Application;
Expand All @@ -17,10 +20,13 @@ export default class OrmAppBootHook {
this.modelProtoManager = new ModelProtoManager();
this.leoricRegister = new LeoricRegister(this.modelProtoManager, this.dataSourceManager);
this.modelProtoHook = new ModelProtoHook(this.modelProtoManager);
this.app.eggPrototypeCreatorFactory.registerPrototypeCreator(MODEL_PROTO_IMPL_TYPE, ContextModelProto.createProto);
this.app.leoricRegister = this.leoricRegister;
}

configWillLoad() {
this.app.eggPrototypeLifecycleUtil.registerLifecycle(this.modelProtoHook);
this.app.eggObjectFactory.registerEggObjectCreateMethod(ContextModelProto, ContextModeObject.createObject);
}

configDidLoad() {
Expand Down
70 changes: 70 additions & 0 deletions plugin/orm/lib/ContextModeObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import assert from 'assert';
import {
EggContext,
EggObject,
EggObjectLifeCycleContext,
EggObjectStatus,
} from '@eggjs/tegg-runtime';
import { EggPrototype } from '@eggjs/tegg-metadata';
import { EggPrototypeName, EggObjectName } from '@eggjs/tegg';
import { Id, IdenticalUtil } from '@eggjs/tegg-lifecycle';
import { Bone } from 'leoric';
import ContextModelProto from './ContextModelProto';
import { EGG_CONTEXT } from '@eggjs/egg-module-common';

export class ContextModeObject implements EggObject {
private status: EggObjectStatus = EggObjectStatus.PENDING;
id: Id;
readonly name: EggPrototypeName;
private _obj: typeof Bone;
readonly proto: ContextModelProto;
readonly ctx: EggContext;

constructor(name: EggObjectName, proto: ContextModelProto, ctx: EggContext) {
this.name = name;
this.proto = proto;
this.ctx = ctx;
this.id = IdenticalUtil.createObjectId(this.proto.id, this.ctx.id);
}

async init() {
const ctx = this.ctx;
const clazz = class ContextModelClass extends this.proto.model {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
static get name() {
return super.name;
}

static get ctx() {
return ctx.get(EGG_CONTEXT);
}

// custom setter always execute before define [CTX] when new Instance(super(opts) calling), if custom setter requires ctx, it should not be undefined
get ctx() {
return ctx.get(EGG_CONTEXT);
}
};
this._obj = clazz;
this.status = EggObjectStatus.READY;
}

injectProperty() {
throw new Error('never call ModelObject#injectProperty');
}

get isReady() {
return this.status === EggObjectStatus.READY;
}

get obj() {
return this._obj;
}

static async createObject(name: EggObjectName, proto: EggPrototype, _: EggObjectLifeCycleContext, ctx?: EggContext): Promise<ContextModeObject> {
assert(ctx, 'ctx must be defined for ContextModelObject');
const modelObject = new ContextModeObject(name, proto as ContextModelProto, ctx);
await modelObject.init();
return modelObject;
}
}
59 changes: 59 additions & 0 deletions plugin/orm/lib/ContextModelProto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { EggPrototype, LoadUnit, EggPrototypeLifecycleContext } from '@eggjs/tegg-metadata';
import {
AccessLevel,
EggPrototypeName,
ObjectInitType,
QualifierInfo,
QualifierUtil,
MetadataUtil,
MetaDataKey,
} from '@eggjs/tegg';
import { Id, IdenticalUtil } from '@eggjs/tegg-lifecycle';
import { Bone } from 'leoric';

export default class ContextModelProto implements EggPrototype {
private readonly qualifiers: QualifierInfo[];
readonly accessLevel = AccessLevel.PUBLIC;
id: Id;
readonly initType = ObjectInitType.CONTEXT;
readonly injectObjects = [];
readonly loadUnitId: string;
readonly moduleName: string;
readonly name: EggPrototypeName;
readonly model: typeof Bone;

constructor(loadUnit: LoadUnit, model: typeof Bone) {
this.model = model;
this.id = IdenticalUtil.createProtoId(loadUnit.id, `leoric:${model.name}`);
this.loadUnitId = loadUnit.id;
this.moduleName = loadUnit.name;
this.name = model.name;
this.qualifiers = QualifierUtil.getProtoQualifiers(model);
}

constructEggObject(): object {
return {};
}

getMetaData<T>(metadataKey: MetaDataKey): T | undefined {
return MetadataUtil.getMetaData(metadataKey, this.model);
}

verifyQualifier(qualifier: QualifierInfo): boolean {
const selfQualifiers = this.qualifiers.find(t => t.attribute === qualifier.attribute);
return selfQualifiers?.value === qualifier.value;
}

verifyQualifiers(qualifiers: QualifierInfo[]): boolean {
for (const qualifier of qualifiers) {
if (!this.verifyQualifier(qualifier)) {
return false;
}
}
return true;
}

static createProto(ctx: EggPrototypeLifecycleContext): ContextModelProto {
return new ContextModelProto(ctx.loadUnit, ctx.clazz as typeof Bone);
}
}
9 changes: 6 additions & 3 deletions plugin/orm/lib/LeoricRegister.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import Base from 'sdk-base';
import { ModelProtoManager } from './ModelProtoManager';
import { DataSourceManager, OrmConfig } from './DataSourceManager';
import Realm from 'leoric';
import { hookNames } from 'leoric/src/setup_hooks';
import { ModelMetadata, ModelMetadataUtil } from '@eggjs/tegg-orm-decorator';

export class LeoricRegister {
export class LeoricRegister extends Base {
private readonly modelProtoManager: ModelProtoManager;
private readonly dataSourceManager: DataSourceManager;
private readonly realmMap: Map<string, any>;
readonly realmMap: Map<string, any>;

constructor(modelProtoManager: ModelProtoManager, dataSourceManager: DataSourceManager) {
super();
this.modelProtoManager = modelProtoManager;
this.dataSourceManager = dataSourceManager;
this.realmMap = new Map();
Expand Down Expand Up @@ -51,7 +53,7 @@ export class LeoricRegister {

async register() {
for (const { proto, clazz } of this.modelProtoManager.getProtos()) {
const metadata = ModelMetadataUtil.getControllerMetadata(clazz);
const metadata = ModelMetadataUtil.getModelMetadata(clazz);
if (!metadata) throw new Error(`not found metadata for model ${proto.id}`);
const realm = this.getOrCreateRealm(metadata.dataSource);
realm.models[clazz.name] = clazz;
Expand All @@ -71,5 +73,6 @@ export class LeoricRegister {
}
await Promise.all(Array.from(this.realmMap.values())
.map(realm => realm.connect()));
this.ready(true);
}
}
2 changes: 1 addition & 1 deletion plugin/orm/lib/ModelProtoHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class ModelProtoHook implements LifecycleHook<EggPrototypeLifecycleContex
}
const builder = new ModelMetaBuilder(ctx.clazz);
const metadata = builder.build();
ModelMetadataUtil.setControllerMetadata(ctx.clazz, metadata);
ModelMetadataUtil.setModelMetadata(ctx.clazz, metadata);
this.modelProtoManager.addProto(ctx.clazz, obj);
}
}
1 change: 1 addition & 0 deletions plugin/orm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@eggjs/tegg-metadata": "^1.3.0",
"@eggjs/tegg-orm-decorator": "^1.3.1",
"@eggjs/tegg-runtime": "^1.3.0",
"@eggjs/tegg-lifecycle": "^1.0.0",
"@types/koa-router": "^7.0.40",
"koa-compose": "^3.2.1",
"leoric": "^2.6.1"
Expand Down
23 changes: 23 additions & 0 deletions plugin/orm/test/fixtures/apps/orm-app/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Application } from 'egg';
import { Logger } from 'leoric';

export default class OrmAppHook {
private readonly app: Application;

constructor(app: Application) {
this.app = app;
}

async didLoad() {
await this.app.leoricRegister.ready();
const app = this.app;
for (const realm of this.app.leoricRegister.realmMap.values()) {
realm.driver.logger = new Logger({
logQuery(sql, _, options) {
const path = options.Model?.ctx?.path;
app.logger.info('sql: %s path: %s', sql, path);
},
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ContextProto } from '@eggjs/tegg';
import { ContextProto, Inject } from '@eggjs/tegg';
import { App } from './model/App';

@ContextProto()
export class AppService {
// TODO impl inject Bone for context
App: typeof App = App;
@Inject()
App: typeof App;

async createApp(data: {
name: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ContextProto } from '@eggjs/tegg';
import { ContextProto, Inject } from '@eggjs/tegg';
import { Pkg } from './model/Pkg';

@ContextProto()
export class PkgService {
// TODO impl inject Bone for context
Pkg: typeof Pkg = Pkg;
@Inject()
Pkg: typeof Pkg;

async createPkg(data: {
name: string;
Expand Down
8 changes: 8 additions & 0 deletions plugin/orm/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,12 @@ describe('test/orm.test.ts', () => {
assert(findModel.name === 'egg_before_create_hook');
assert(findModel.desc === 'the framework');
});

it('ctx should inject with Model', async () => {
ctx = await app.mockModuleContext();
const appService = await ctx.getEggObject(AppService);
app.mockLog();
await appService.findApp('egg');
app.expectLog(/sql: SELECT \* FROM `apps` WHERE `name` = 'egg' LIMIT 1 path: \//);
});
});
10 changes: 10 additions & 0 deletions plugin/orm/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ import '@eggjs/tegg-plugin';

import { DataType } from 'leoric';
import { AttributeOptions } from '@eggjs/tegg-orm-decorator';
import { LeoricRegister } from '../lib/LeoricRegister';

declare module '@eggjs/tegg-orm-decorator' {
export declare function Attribute(dataType: DataType, options?: AttributeOptions): (target: any, propertyKey: PropertyKey) => void;
}

declare module 'egg' {
export interface TeggOrmApplication {
leoricRegister: LeoricRegister;
}

interface Application extends TeggOrmApplication {
}
}

0 comments on commit ced2ce2

Please sign in to comment.