nocobase/packages/server/src/application.ts

288 lines
6.8 KiB
TypeScript
Raw Normal View History

import Koa from 'koa';
import cors from '@koa/cors';
import bodyParser from 'koa-bodyparser';
2021-09-14 03:09:26 +00:00
import { Command, CommandOptions } from 'commander';
import Database, { DatabaseOptions, TableOptions } from '@nocobase/database';
import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
2021-09-09 05:05:33 +00:00
import { dataWrapping, table2resource } from './middlewares';
2021-09-14 03:09:26 +00:00
import { PluginType, Plugin, PluginOptions } from './plugin';
2021-09-15 16:38:48 +00:00
import { registerActions } from '@nocobase/actions';
2021-09-09 05:05:33 +00:00
export interface ResourcerOptions {
prefix?: string;
}
export interface ApplicationOptions {
2021-09-09 05:05:33 +00:00
database?: DatabaseOptions;
resourcer?: ResourcerOptions;
bodyParser?: any;
cors?: any;
dataWrapping?: boolean;
}
2021-09-16 07:57:21 +00:00
interface DefaultState {
currentUser?: any;
[key: string]: any;
}
interface DefaultContext {
db: Database;
resourcer: Resourcer;
[key: string]: any;
}
interface MiddlewareOptions {
name?: string;
resourceName?: string;
resourceNames?: string[];
insertBefore?: string;
insertAfter?: string;
}
interface ActionsOptions {
resourceName?: string;
resourceNames?: string[];
}
export class Application<
StateT = DefaultState,
2021-10-01 15:31:49 +00:00
ContextT = DefaultContext,
> extends Koa {
2021-09-14 03:09:26 +00:00
public readonly db: Database;
public readonly resourcer: Resourcer;
public readonly cli: Command;
2021-09-14 03:09:26 +00:00
protected plugins = new Map<string, Plugin>();
constructor(options: ApplicationOptions) {
super();
2021-09-09 05:05:33 +00:00
2021-09-15 16:38:48 +00:00
if (options.database instanceof Database) {
this.db = options.database;
} else {
this.db = new Database(options.database);
}
2021-09-09 05:05:33 +00:00
this.resourcer = new Resourcer({ ...options.resourcer });
this.cli = new Command();
if (options.bodyParser !== false) {
this.use(
bodyParser({
...options.bodyParser,
}),
);
}
2021-09-09 05:05:33 +00:00
this.use(
cors({
exposeHeaders: ['content-disposition'],
...options.cors,
}),
);
2021-09-16 07:57:21 +00:00
this.use<DefaultState, DefaultContext>(async (ctx, next) => {
2021-09-14 03:09:26 +00:00
ctx.db = this.db;
2021-09-09 05:05:33 +00:00
ctx.resourcer = this.resourcer;
await next();
});
2021-09-09 05:05:33 +00:00
if (options.dataWrapping !== false) {
2021-09-15 16:38:48 +00:00
this.use(dataWrapping());
2021-09-09 05:05:33 +00:00
}
2021-09-15 16:38:48 +00:00
this.use(table2resource());
2021-09-09 05:05:33 +00:00
this.use(this.resourcer.restApiMiddleware());
2021-09-15 16:38:48 +00:00
registerActions(this);
this.cli
2021-09-16 07:57:21 +00:00
.command('db:sync')
.option('-f, --force')
.action(async (...args) => {
2021-09-15 16:38:48 +00:00
console.log('db sync...');
const cli = args.pop();
2021-09-14 03:09:26 +00:00
const force = cli.opts()?.force;
await this.db.sync(
force
? {
2021-10-01 15:31:49 +00:00
force: true,
alter: {
drop: true,
},
}
2021-09-14 03:09:26 +00:00
: {},
);
await this.destroy();
});
this.cli
2021-09-16 07:57:21 +00:00
.command('init')
// .option('-f, --force')
.action(async (...args) => {
const cli = args.pop();
2021-09-14 03:09:26 +00:00
await this.db.sync({
force: true,
alter: {
drop: true,
},
});
await this.emitAsync('db.init');
2021-09-09 05:05:33 +00:00
await this.destroy();
});
this.cli
.command('start')
.option('-p, --port [port]')
.action(async (...args) => {
const cli = args.pop();
console.log(args);
const opts = cli.opts();
2021-09-22 16:16:04 +00:00
await this.emitAsync('beforeStart');
this.listen(opts.port || 3000);
console.log(`http://localhost:${opts.port || 3000}/`);
});
2020-12-18 11:54:53 +00:00
}
2021-09-16 07:57:21 +00:00
use<NewStateT = {}, NewContextT = {}>(
middleware: Koa.Middleware<StateT & NewStateT, ContextT & NewContextT>,
options?: MiddlewareOptions,
2021-09-22 16:16:04 +00:00
) {
2021-09-16 07:57:21 +00:00
// @ts-ignore
return super.use(middleware);
}
2021-09-14 03:09:26 +00:00
collection(options: TableOptions) {
return this.db.table(options);
}
resource(options: ResourceOptions) {
return this.resourcer.define(options);
}
2021-09-16 07:57:21 +00:00
actions(handlers: any, options?: ActionsOptions) {
2021-09-15 16:38:48 +00:00
return this.resourcer.registerActions(handlers);
}
2021-09-14 03:09:26 +00:00
command(nameAndArgs: string, opts?: CommandOptions) {
return this.cli.command(nameAndArgs, opts);
}
2021-10-01 15:31:49 +00:00
plugin(options?: PluginType | PluginOptions, ext?: PluginOptions): Plugin {
2021-09-22 16:16:04 +00:00
if (typeof options === 'string') {
2021-10-01 15:31:49 +00:00
return this.plugin(require(options).default, ext);
2021-09-14 03:09:26 +00:00
}
let instance: Plugin;
2021-09-22 16:16:04 +00:00
if (typeof options === 'function') {
try {
2021-09-14 03:09:26 +00:00
// @ts-ignore
2021-09-22 16:16:04 +00:00
instance = new options({
name: options.name,
2021-10-01 15:31:49 +00:00
...ext,
2021-09-22 16:16:04 +00:00
app: this,
});
if (!(instance instanceof Plugin)) {
throw new Error('plugin must be instanceof Plugin');
}
} catch (err) {
// console.log(err);
instance = new Plugin({
name: options.name,
2021-10-01 15:31:49 +00:00
...ext,
2021-09-22 16:16:04 +00:00
// @ts-ignore
load: options,
2021-09-14 03:09:26 +00:00
app: this,
});
}
2021-09-22 16:16:04 +00:00
} else if (typeof options === 'object') {
const plugin = options.plugin || Plugin;
instance = new plugin({
name: options.plugin ? plugin.name : undefined,
2021-09-14 03:09:26 +00:00
...options,
2021-10-01 15:31:49 +00:00
...ext,
2021-09-14 03:09:26 +00:00
app: this,
});
}
const name = instance.getName();
if (this.plugins.has(name)) {
throw new Error(`plugin name [${name}] is repeated`);
}
this.plugins.set(name, instance);
return instance;
}
async load() {
await this.emitAsync('plugins.beforeLoad');
for (const [name, plugin] of this.plugins) {
await this.emitAsync(`plugins.${name}.beforeLoad`);
await plugin.load();
await this.emitAsync(`plugins.${name}.afterLoad`);
}
await this.emitAsync('plugins.afterLoad');
}
2021-09-05 06:17:45 +00:00
async emitAsync(event: string | symbol, ...args: any[]): Promise<boolean> {
// @ts-ignore
const events = this._events;
let callbacks = events[event];
if (!callbacks) {
return false;
}
// helper function to reuse as much code as possible
const run = (cb) => {
switch (args.length) {
// fast cases
case 0:
cb = cb.call(this);
break;
case 1:
cb = cb.call(this, args[0]);
break;
case 2:
cb = cb.call(this, args[0], args[1]);
break;
case 3:
cb = cb.call(this, args[0], args[1], args[2]);
break;
// slower
default:
cb = cb.apply(this, args);
}
2021-09-09 05:05:33 +00:00
if (cb && (cb instanceof Promise || typeof cb.then === 'function')) {
2021-09-05 06:17:45 +00:00
return cb;
}
return Promise.resolve(true);
};
if (typeof callbacks === 'function') {
await run(callbacks);
} else if (typeof callbacks === 'object') {
callbacks = callbacks.slice().filter(Boolean);
await callbacks.reduce((prev, next) => {
return prev.then((res) => {
2021-09-09 05:05:33 +00:00
return run(next).then((result) =>
Promise.resolve(res.concat(result)),
);
2021-09-05 06:17:45 +00:00
});
}, Promise.resolve([]));
}
return true;
}
2021-09-15 16:38:48 +00:00
async parse(argv = process.argv) {
2021-09-27 16:18:09 +00:00
await this.load();
return this.cli.parseAsync(argv);
2020-12-18 11:54:53 +00:00
}
2021-09-09 05:05:33 +00:00
async destroy() {
2021-09-14 03:09:26 +00:00
await this.db.close();
2021-09-09 05:05:33 +00:00
}
}
export default Application;