feat: improve code
This commit is contained in:
		
							parent
							
								
									8b77f0417b
								
							
						
					
					
						commit
						248d59f6ec
					
				
							
								
								
									
										
											BIN
										
									
								
								docs/API.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/API.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 49 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/MiniMIS.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/MiniMIS.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 68 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/MiniProgram.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/MiniProgram.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 151 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/NocoBase.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/NocoBase.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 317 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/SaaS1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/SaaS1.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 168 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/SaaS2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/SaaS2.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 127 KiB  | 
							
								
								
									
										43
									
								
								docs/guide/application.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								docs/guide/application.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 应用 - Application
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 进阶教程
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NocoBase 的 Application 的继承关系
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```ts
 | 
				
			||||||
 | 
					class Application extends Koa {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Koa extends EventEmitter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Koa 和 EventEmitter 详细 API 可以查阅相关文档,就不多说了。这里主要列一些重点:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					实例属性:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- app.db:数据库实例,每个 app 都有自己的 db,完全隔离。
 | 
				
			||||||
 | 
					- app.resources,数据资源实例,为数据提供 API 操作
 | 
				
			||||||
 | 
					- app.cli,commander 实例,提供命令行操作
 | 
				
			||||||
 | 
					- app.context,上下文
 | 
				
			||||||
 | 
					- app.plugins 插件
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					实例方法:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- app.constructor()
 | 
				
			||||||
 | 
					- app.collection()
 | 
				
			||||||
 | 
					- app.resource()
 | 
				
			||||||
 | 
					- app.command()
 | 
				
			||||||
 | 
					- app.plugin()
 | 
				
			||||||
 | 
					- app.load()
 | 
				
			||||||
 | 
					- app.start()
 | 
				
			||||||
 | 
					- app.use() 添加中间件的方法,由 Koa 提供
 | 
				
			||||||
 | 
					- app.on() 添加事件监听,由 EventEmitter 提供
 | 
				
			||||||
 | 
					- app.emit() 触发事件,由 EventEmitter 提供
 | 
				
			||||||
 | 
					- app.emitSync() 触发异步事件
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/guide/cli.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/guide/cli.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 命令行 - CLI
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 命令行 - CLI
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/guide/client.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/guide/client.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 客户端 - Client
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 客户端 - Client
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/guide/collection.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/guide/collection.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 数据集 - Collection
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 数据集 - Collection
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/guide/event.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/guide/event.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 事件 - Event
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 事件 - Event
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/guide/installation.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/guide/installation.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 安装 - Installation
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 安装 - Installation
 | 
				
			||||||
							
								
								
									
										8
									
								
								docs/guide/middleware.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/guide/middleware.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 中间件 - Middleware
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 中间件 - Middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								docs/guide/plugin.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/guide/plugin.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 插件 - Plugin
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 插件 - Plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/guide/resource.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/guide/resource.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 资源 & 操作 - Resource & Action
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 资源 & 操作 - Resource & Action
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/guide/test.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/guide/test.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: 测试 - Testing
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 教程
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 测试 - Testing
 | 
				
			||||||
							
								
								
									
										185
									
								
								docs/index.md
									
									
									
									
									
								
							
							
						
						
									
										185
									
								
								docs/index.md
									
									
									
									
									
								
							@ -1,22 +1,24 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
title: 基础概念
 | 
					title: 介绍
 | 
				
			||||||
toc: menu
 | 
					toc: menu
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NocoBase
 | 
					# NocoBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
考虑到大家是初次接触 NocoBase,开发文档的第一篇,从宏观的角度,带大家了解 NocoBase 的基础概念。NocoBase 采用微内核的架构,框架只保留核心概念,各类功能都以插件形式扩展。
 | 
					考虑到大家是初次接触 NocoBase,开发文档的第一篇,从宏观的角度,带大家了解 NocoBase 的基础概念。NocoBase 采用微内核架构,框架只保留核心,各类功能以插件形式扩展。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<img src="./NocoBase.png" style="max-width: 800px; width: 100%;">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 微服务 - Microservices
 | 
					## 微服务 - Microservices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
先来个例子一睹为快,新建一个 server.js 文件,代码如下:
 | 
					首先我们创建一个应用,新建一个 app.js 文件,代码如下:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
const Server = require('@nocobase/server');
 | 
					const { Application } = require('@nocobase/server');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const server = new Server();
 | 
					const app = new Application();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
server.collection({
 | 
					app.collection({
 | 
				
			||||||
  name: 'users',
 | 
					  name: 'users',
 | 
				
			||||||
  fields: [
 | 
					  fields: [
 | 
				
			||||||
    { type: 'string', name: 'username' },
 | 
					    { type: 'string', name: 'username' },
 | 
				
			||||||
@ -24,16 +26,16 @@ server.collection({
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
server.start(process.argv);
 | 
					app.start(process.argv);
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
终端运行
 | 
					终端运行
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# 根据配置生成数据库表结构
 | 
					# 根据配置生成数据库表结构
 | 
				
			||||||
node server.js db sync
 | 
					node app.js db sync
 | 
				
			||||||
# 启动应用
 | 
					# 启动应用
 | 
				
			||||||
node server.js start --port=3000
 | 
					node app.js start --port=3000
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
相关 users 表的 REST API 就生成了
 | 
					相关 users 表的 REST API 就生成了
 | 
				
			||||||
@ -49,7 +51,7 @@ DELETE  http://localhost:3000/api/users/1
 | 
				
			|||||||
除了内置的 REST API 以外,还可以自定义其他操作,如登录、注册、注销等。
 | 
					除了内置的 REST API 以外,还可以自定义其他操作,如登录、注册、注销等。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.registerActions({
 | 
					app.registerActions({
 | 
				
			||||||
  async login(ctx, next) {},
 | 
					  async login(ctx, next) {},
 | 
				
			||||||
  async register(ctx, next) {},
 | 
					  async register(ctx, next) {},
 | 
				
			||||||
  async logout(ctx, next) {},
 | 
					  async logout(ctx, next) {},
 | 
				
			||||||
@ -81,23 +83,23 @@ POST    http://localhost:3000/api/users:destroy/1
 | 
				
			|||||||
```ts
 | 
					```ts
 | 
				
			||||||
const { ClientSDK } = require('@nocobase/client');
 | 
					const { ClientSDK } = require('@nocobase/client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const client = new ClientSDK();
 | 
					const api = new ClientSDK();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
await client.resource('users').list();
 | 
					await api.resource('users').list();
 | 
				
			||||||
await client.resource('users').create();
 | 
					await api.resource('users').create();
 | 
				
			||||||
await client.resource('users').get();
 | 
					await api.resource('users').get();
 | 
				
			||||||
await client.resource('users').update();
 | 
					await api.resource('users').update();
 | 
				
			||||||
await client.resource('users').destroy();
 | 
					await api.resource('users').destroy();
 | 
				
			||||||
await client.resource('users').login();
 | 
					await api.resource('users').login();
 | 
				
			||||||
await client.resource('users').register();
 | 
					await api.resource('users').register();
 | 
				
			||||||
await client.resource('users').logout();
 | 
					await api.resource('users').logout();
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 数据集 - Collection
 | 
					## 数据集 - Collection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
上述例子,通过 `server.collection()` 方法定义数据的 Schema,Schema 的核心为字段配置,字段类型包括:
 | 
					上述例子,通过 `app.collection()` 方法定义数据的 Schema,Schema 的核心为字段配置,字段类型包括:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Attribute 属性
 | 
					属性 Attribute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Boolean 布尔型
 | 
					- Boolean 布尔型
 | 
				
			||||||
- String 字符串
 | 
					- String 字符串
 | 
				
			||||||
@ -115,7 +117,7 @@ Attribute 属性
 | 
				
			|||||||
- Password 密码
 | 
					- Password 密码
 | 
				
			||||||
- Sort 排序
 | 
					- Sort 排序
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Association/Realtion 关系
 | 
					关系 Association/Realtion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- HasOne 一对一
 | 
					- HasOne 一对一
 | 
				
			||||||
- HasMany 一对多
 | 
					- HasMany 一对多
 | 
				
			||||||
@ -127,7 +129,7 @@ Association/Realtion 关系
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
// 用户
 | 
					// 用户
 | 
				
			||||||
server.collection({
 | 
					app.collection({
 | 
				
			||||||
  name: 'users',
 | 
					  name: 'users',
 | 
				
			||||||
  fields: [
 | 
					  fields: [
 | 
				
			||||||
    { type: 'string', name: 'username', unique: true },
 | 
					    { type: 'string', name: 'username', unique: true },
 | 
				
			||||||
@ -137,7 +139,7 @@ server.collection({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 文章
 | 
					// 文章
 | 
				
			||||||
server.collection({
 | 
					app.collection({
 | 
				
			||||||
  name: 'posts',
 | 
					  name: 'posts',
 | 
				
			||||||
  fields: [
 | 
					  fields: [
 | 
				
			||||||
    { type: 'string', name: 'title' },
 | 
					    { type: 'string', name: 'title' },
 | 
				
			||||||
@ -149,7 +151,7 @@ server.collection({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 标签
 | 
					// 标签
 | 
				
			||||||
server.collection({
 | 
					app.collection({
 | 
				
			||||||
  name: 'tags',
 | 
					  name: 'tags',
 | 
				
			||||||
  fields: [
 | 
					  fields: [
 | 
				
			||||||
    { type: 'string', name: 'name' },
 | 
					    { type: 'string', name: 'name' },
 | 
				
			||||||
@ -158,7 +160,7 @@ server.collection({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 评论
 | 
					// 评论
 | 
				
			||||||
server.collection({
 | 
					app.collection({
 | 
				
			||||||
  name: 'comments',
 | 
					  name: 'comments',
 | 
				
			||||||
  fields: [
 | 
					  fields: [
 | 
				
			||||||
    { type: 'text', name: 'content' },
 | 
					    { type: 'text', name: 'content' },
 | 
				
			||||||
@ -169,16 +171,16 @@ server.collection({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
存在外键关联时,也无需顾虑建表和字段的顺序,`db sync` 时会自动处理。为了方便开发,提供了一些有用的属性或方法:
 | 
					存在外键关联时,也无需顾虑建表和字段的顺序,`db sync` 时会自动处理。为了方便开发,提供了一些有用的属性或方法:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `server.db` 数据库实例
 | 
					- `app.db` 数据库实例
 | 
				
			||||||
- `server.db.getModel()` 获取 Model
 | 
					- `app.db.getModel()` 获取 Model
 | 
				
			||||||
- `server.db.getTable()` 获取 Schema Table
 | 
					- `app.db.getTable()` 获取 Schema Table
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 资源 & 操作 - Resource & Action
 | 
					## 资源 & 操作 - Resource & Action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
不同于常规的 MVC + Router,NocoBase 的路由(Resourcer)基于资源(Resource)和操作(Action)设计,将 REST 和 RPC 结合起来,提供更为灵活且统一的 Resource Action API,Action 不局限于增删改查。资源可以通过 `server.resource` 方法定义,如:
 | 
					不同于常规的 MVC + Router,NocoBase 的路由(Resourcer)基于资源(Resource)和操作(Action)设计,将 REST 和 RPC 结合起来,提供更为灵活且统一的 Resource Action API,Action 不局限于增删改查。资源可以通过 `app.resource()` 方法定义,如:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.resource({
 | 
					app.resource({
 | 
				
			||||||
  name: 'users',
 | 
					  name: 'users',
 | 
				
			||||||
  actions: {
 | 
					  actions: {
 | 
				
			||||||
    list: {
 | 
					    list: {
 | 
				
			||||||
@ -210,53 +212,52 @@ server.resource({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`server.collection()` 和 `server.resource()` 的区别?
 | 
					`app.collection()` 和 `app.resource()` 的区别?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `server.collection()` 定义数据的 Schema(结构和关系)
 | 
					- `app.collection()` 定义数据的 Schema(结构和关系)
 | 
				
			||||||
- `server.resource()` 定义数据的 Action(操作方法)
 | 
					- `app.resource()` 定义数据的 Action(操作方法)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
一般情况无需显式声明 collection 的 resource,因为已定义的 collection 会自动同步给 resource。
 | 
					一般情况无需显式声明 collection 的 resource,因为已定义的 collection 会自动同步给 resource。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 事件 - Event
 | 
					## 事件 - Event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
在操作执行前、后都放置了相关的事件监听器,可以通过 `server.db.on` 和 `server.on` 添加。区别在于:
 | 
					在操作执行前、后都放置了相关事件监听器,可以通过 `app.db.on()` 和 `app.on()` 添加。区别在于:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `server.db.on` 添加数据库的监听器
 | 
					- `app.db.on()` 添加数据库层面的监听器
 | 
				
			||||||
- `server.on` 添加服务器的监听器
 | 
					- `app.on()` 添加服务器应用层面的监听器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
以 `users:login` 为例,在数据库里为「查询」操作,在服务器里为「登录」操作。如果需要记录登录操作日志,需要在 `server.on` 里处理。
 | 
					以 `users:login` 为例,在数据库里为「查询」操作,在应用里为「登录」操作。也就是说,如果需要记录登录操作日志,要在 `app.on()` 里处理。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
// 创建数据时,执行 User.create() 时触发
 | 
					// 创建数据时,执行 User.create() 时触发
 | 
				
			||||||
server.db.on('users:beforeCreate', async (model) => {});
 | 
					app.db.on('users:beforeCreate', async (model) => {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 客户端 `POST /api/users:login` 时触发
 | 
					// 客户端 `POST /api/users:login` 时触发
 | 
				
			||||||
server.on('users:beforeLogin', async (ctx, next) => {});
 | 
					app.on('users:beforeLogin', async (ctx, next) => {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 客户端 `POST /api/users` 时触发
 | 
					// 客户端 `POST /api/users` 时触发
 | 
				
			||||||
server.on('users:beforeCreate', async (ctx, next) => {});
 | 
					app.on('users:beforeCreate', async (ctx, next) => {});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 中间件 - Middleware
 | 
					## 中间件 - Middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Server 基于 Koa,所有 Koa 的插件(中间件)都可以直接使用,可以通过 server.use 添加。如:
 | 
					Server Application 基于 Koa,所有 Koa 的插件(中间件)都可以直接使用,可以通过 `app.use()` 添加。如:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.use(async (ctx, next) => {
 | 
					const responseTime = require('koa-response-time');
 | 
				
			||||||
  const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, '');
 | 
					app.use(responseTime());
 | 
				
			||||||
  if (token !== '123456') {
 | 
					
 | 
				
			||||||
    return ctx.throw(401, 'Unauthorized');
 | 
					app.use(async (ctx, next) => {
 | 
				
			||||||
  }
 | 
					  await next();
 | 
				
			||||||
  return next();
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
弥补 koa.use 的不足,提供了更完善的 middleware 适配器
 | 
					弥补 `app.use()` 不足,加了个 `middleware()` 适配器,可以用于限定 resource 和 action。除此之外,也可以控制中间件的插入位置。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
import { middleware } from '@nocobase/server';
 | 
					import { middleware } from '@nocobase/server';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
server.use(middleware(async (ctx, next) => {}, {
 | 
					app.use(middleware(async (ctx, next) => {}, {
 | 
				
			||||||
  name: 'middlewareName1',
 | 
					  name: 'middlewareName1',
 | 
				
			||||||
  resourceNames: [],
 | 
					  resourceNames: [],
 | 
				
			||||||
  actionNames: [],
 | 
					  actionNames: [],
 | 
				
			||||||
@ -267,15 +268,16 @@ server.use(middleware(async (ctx, next) => {}, {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## 命令行 - CLI
 | 
					## 命令行 - CLI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
除此之外,Server 还集成了 commander,可用于 cli 场景。目前内置的有:
 | 
					Application 除了可以做 HTTP Server 以外,也可以是 CLI(内置了 Commander)。目前内置的命令有:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `db sync --force` 用于配置与数据库表结构同步
 | 
					- `db sync --force` 用于配置与数据库表结构同步
 | 
				
			||||||
- `start --port` 启动应用
 | 
					- `start --port` 启动应用
 | 
				
			||||||
 | 
					- `plugin` 插件相关
 | 
				
			||||||
 | 
					
 | 
				
			||||||
自定义:
 | 
					自定义:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.command('foo').action(async () => {
 | 
					app.command('foo').action(async () => {
 | 
				
			||||||
  console.log('foo...');
 | 
					  console.log('foo...');
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@ -285,46 +287,50 @@ server.command('foo').action(async () => {
 | 
				
			|||||||
上文,讲述了核心的扩展接口,包括但不局限于:
 | 
					上文,讲述了核心的扩展接口,包括但不局限于:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Database/Collection
 | 
					- Database/Collection
 | 
				
			||||||
  - `server.db` database 实例
 | 
					  - `app.db` database 实例
 | 
				
			||||||
  - `server.collection()` 等同于 `server.db.table()`
 | 
					  - `app.collection()` 等同于 `app.db.table()`
 | 
				
			||||||
- Resource/Action
 | 
					- Resource/Action
 | 
				
			||||||
  - `server.resource()` 等同于 `server.resourcer.define()`
 | 
					  - `app.resource()` 等同于 `app.resourcer.define()`
 | 
				
			||||||
  - `server.registerActions()` 等同于 `server.resourcer.registerActions()`
 | 
					  - `app.registerActions()` 等同于 `app.resourcer.registerActions()`
 | 
				
			||||||
- Hook/Event
 | 
					- Hook/Event
 | 
				
			||||||
  - `server.on()` 添加服务器监听器
 | 
					  - `app.on()` 添加服务器监听器
 | 
				
			||||||
  - `server.db.on()` 添加数据库监听器
 | 
					  - `app.db.on()` 添加数据库监听器
 | 
				
			||||||
- Middleware
 | 
					- Middleware
 | 
				
			||||||
  - `server.use()` 添加中间件
 | 
					  - `app.use()` 添加中间件
 | 
				
			||||||
- CLI
 | 
					- CLI
 | 
				
			||||||
  - `server.cli` commander 实例
 | 
					  - `app.cli` commander 实例
 | 
				
			||||||
  - `server.command()` 等同于 `server.cli.command()`
 | 
					  - `app.command()` 等同于 `app.cli.command()`
 | 
				
			||||||
- Plugin
 | 
					- Plugin
 | 
				
			||||||
  - `server.pluginManager` 插件管理器
 | 
					  - `app.pluginManager` 插件管理器
 | 
				
			||||||
  - `server.plugin` 等同于 `server.pluginManager.add()`
 | 
					  - `app.plugin` 等同于 `app.pluginManager.add()`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
基于以上扩展接口,提供模块化、可插拔的插件,可以通过 `server.plugin()` 添加。
 | 
					基于以上扩展接口,提供了模块化、可插拔的插件,可以通过 `app.plugin()` 添加。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					完整的插件包括安装、升级、激活、载入、禁用、卸载流程,但是并不是所有插件都要这完整的流程。比如:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**最简单的插件**
 | 
					**最简单的插件**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.plugin(function pluginName1() {
 | 
					app.plugin(function pluginName1() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					这种方式添加的插件会直接载入。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**JSON 风格**
 | 
					**JSON 风格**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
包括安装、激活、载入、禁用、卸载流程的配置
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.plugin({
 | 
					const plugin = app.plugin({
 | 
				
			||||||
  async install() {},
 | 
					  async install() {},
 | 
				
			||||||
 | 
					  async upgrade() {},
 | 
				
			||||||
  async activate() {},
 | 
					  async activate() {},
 | 
				
			||||||
  async bootstrap() {},
 | 
					  async bootstrap() {},
 | 
				
			||||||
  async deactivate() {},
 | 
					  async deactivate() {},
 | 
				
			||||||
  async unstall() {},
 | 
					  async unstall() {},
 | 
				
			||||||
}, {
 | 
					}, {
 | 
				
			||||||
  name: 'pluginName1',
 | 
					  activate: false, // 默认为 true,不需要启用时可以禁用。
 | 
				
			||||||
 | 
					  name: 'plugin-name1',
 | 
				
			||||||
  displayName: '插件名称',
 | 
					  displayName: '插件名称',
 | 
				
			||||||
  version: '1.2.3',
 | 
					  version: '1.2.3',
 | 
				
			||||||
  dependencies: {
 | 
					  dependencies: {
 | 
				
			||||||
@ -332,21 +338,24 @@ server.plugin({
 | 
				
			|||||||
    pluginName3: '1.x',
 | 
					    pluginName3: '1.x',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					// 通过 api 激活插件
 | 
				
			||||||
 | 
					plugin.activate();
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**OOP 风格**
 | 
					**OOP 风格**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
class MyPlugin extends Plugin {
 | 
					class MyPlugin extends Plugin {
 | 
				
			||||||
  async bootstrap() {}
 | 
					 | 
				
			||||||
  async install() {}
 | 
					  async install() {}
 | 
				
			||||||
  async unstall() {}
 | 
					  async upgrade() {}
 | 
				
			||||||
 | 
					  async bootstrap() {}
 | 
				
			||||||
  async activate() {}
 | 
					  async activate() {}
 | 
				
			||||||
  async deactivate() {}
 | 
					  async deactivate() {}
 | 
				
			||||||
 | 
					  async unstall() {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
server.plugin(MyPlugin, {
 | 
					app.plugin(MyPlugin, {
 | 
				
			||||||
  name: 'pluginName1',
 | 
					  name: 'plugin-name1',
 | 
				
			||||||
  displayName: '插件名称',
 | 
					  displayName: '插件名称',
 | 
				
			||||||
  version: '1.2.3',
 | 
					  version: '1.2.3',
 | 
				
			||||||
  dependencies: {
 | 
					  dependencies: {
 | 
				
			||||||
@ -359,7 +368,7 @@ server.plugin(MyPlugin, {
 | 
				
			|||||||
**引用独立的 Package**
 | 
					**引用独立的 Package**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.plugin('@nocobase/plugin-action-logs');
 | 
					app.plugin('@nocobase/plugin-action-logs');
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
插件信息也可以直接写在 `package.json` 里
 | 
					插件信息也可以直接写在 `package.json` 里
 | 
				
			||||||
@ -376,12 +385,6 @@ server.plugin('@nocobase/plugin-action-logs');
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
通过 `server.plugin()` 添加的插件需要激活才能使用
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					 | 
				
			||||||
await server.pluginManager.activate(['pluginName1', 'pluginName2']);
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**插件 CLI**
 | 
					**插件 CLI**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
@ -454,7 +457,7 @@ describe('mock server', () => {
 | 
				
			|||||||
插件配置
 | 
					插件配置
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ts
 | 
					```ts
 | 
				
			||||||
server.plugin('@nocobase/plugin-client', {
 | 
					app.plugin('@nocobase/plugin-client', {
 | 
				
			||||||
  // 自定义 dist 路径
 | 
					  // 自定义 dist 路径
 | 
				
			||||||
  dist: path.resolve(__dirname, './node_modules/@nocobase/client/app'),
 | 
					  dist: path.resolve(__dirname, './node_modules/@nocobase/client/app'),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@ -566,30 +569,30 @@ nocobase-app 默认使用 umijs 作为项目构建工具,并集成了 Server 
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 小型管理信息系统
 | 
					### 小型管理信息系统
 | 
				
			||||||
 | 
					
 | 
				
			||||||
小型管理信息系统(包括后台数据管理系统),具备完整的前后端
 | 
					小型管理信息系统,具备完整的前后端。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<img src="./MiniMISServer.png" style="max-width: 400px; width: 100%;">
 | 
					<img src="./MiniMIS.png" style="max-width: 300px; width: 100%;">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### API 服务
 | 
					### API 服务
 | 
				
			||||||
 | 
					
 | 
				
			||||||
无需客户端,提供纯后端接口,也同样适用于 Open API
 | 
					无客户端,提供纯后端接口。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<img src="./APIServer.png" style="max-width: 400px; width: 100%;">
 | 
					<img src="./API.png" style="max-width: 280px; width: 100%;">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 小程序 + 后台管理
 | 
					### 小程序 + 后台管理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
只需要一套数据库,但有两套用户和权限,一套是用于后台用户,一套用于小程序用户
 | 
					只需要一套数据库,但有两套用户和权限,一套用于后台用户,一套用于小程序用户。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<img src="./MiniProgramServer.png" style="max-width: 600px; width: 100%;">
 | 
					<img src="./MiniProgram.png" style="max-width: 600px; width: 100%;">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### SaaS 服务(共享用户)
 | 
					### SaaS 服务(共享用户)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
每个应用有自己配套的数据库,各应用数据完全隔离。应用不需要用户和权限模块,SaaS 主站全局共享了。目前的大多数协作平台、无代码平台是这样的结构。
 | 
					每个应用有自己配套的数据库,各应用数据完全隔离。应用不需要用户和权限模块,SaaS 主站全局共享了。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<img src="./SaaSServer1.png" style="max-width: 600px; width: 100%;">
 | 
					<img src="./SaaS2.png" style="max-width: 450px; width: 100%;">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### SaaS 服务(独立用户)
 | 
					### SaaS 服务(独立用户)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
每个应用有自己的独立用户模块和权限,每个应用可以绑定自己的域名,更适用于搭建企业应用平台。
 | 
					每个应用有自己的独立用户模块和权限,应用可以绑定自己的域名。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<img src="./SaaSServer2.png" style="max-width: 600px; width: 100%;">
 | 
					<img src="./SaaS1.png" style="max-width: 450px; width: 100%;">
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								docs/plugins/action-logs.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/action-logs.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-action-logs'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-action-logs
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/automations.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/automations.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-automations'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-automations
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/client.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/client.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-client'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-client
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/collections.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/collections.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-collections'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-collections
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/export.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/export.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-export'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-export
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/file-manager.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/file-manager.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-file-manager'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-file-manager
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/permissions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/permissions.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-permissions'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-permissions
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/saas.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/saas.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-saas'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-saas
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/system-settings.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/system-settings.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-system-settings'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-system-settings
 | 
				
			||||||
							
								
								
									
										7
									
								
								docs/plugins/users.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/plugins/users.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: '@nocobase/plugin-users'
 | 
				
			||||||
 | 
					nav:
 | 
				
			||||||
 | 
					  title: 插件
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @nocobase/plugin-users
 | 
				
			||||||
@ -27,6 +27,7 @@
 | 
				
			|||||||
    "@types/file-saver": "^2.0.3",
 | 
					    "@types/file-saver": "^2.0.3",
 | 
				
			||||||
    "@types/jest": "^24.0.18",
 | 
					    "@types/jest": "^24.0.18",
 | 
				
			||||||
    "@types/koa": "^2.13.1",
 | 
					    "@types/koa": "^2.13.1",
 | 
				
			||||||
 | 
					    "@types/koa-mount": "^4.0.1",
 | 
				
			||||||
    "@types/lodash": "^4.14.169",
 | 
					    "@types/lodash": "^4.14.169",
 | 
				
			||||||
    "@types/mockjs": "^1.0.3",
 | 
					    "@types/mockjs": "^1.0.3",
 | 
				
			||||||
    "@types/node": "^12.6.8",
 | 
					    "@types/node": "^12.6.8",
 | 
				
			||||||
 | 
				
			|||||||
@ -16,13 +16,13 @@ describe('create', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('create', async () => {
 | 
					  it('create', async () => {
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'tests',
 | 
					      name: 'tests',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'name' },
 | 
					        { type: 'string', name: 'name' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await api.database.sync();
 | 
					    await api.db.sync();
 | 
				
			||||||
    const response = await api.resource('tests').create({
 | 
					    const response = await api.resource('tests').create({
 | 
				
			||||||
      values: { name: 'n1' },
 | 
					      values: { name: 'n1' },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -30,21 +30,21 @@ describe('create', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('associations', async () => {
 | 
					  it('associations', async () => {
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'comments',
 | 
					      name: 'comments',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'content' },
 | 
					        { type: 'string', name: 'content' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'posts',
 | 
					      name: 'posts',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'title' },
 | 
					        { type: 'string', name: 'title' },
 | 
				
			||||||
        { type: 'hasMany', name: 'comments' },
 | 
					        { type: 'hasMany', name: 'comments' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await api.database.sync();
 | 
					    await api.db.sync();
 | 
				
			||||||
    const [Post, Comment] = api.database.getModels(['posts', 'comments']);
 | 
					    const [Post, Comment] = api.db.getModels(['posts', 'comments']);
 | 
				
			||||||
    const response = await api.resource('posts').create({
 | 
					    const response = await api.resource('posts').create({
 | 
				
			||||||
      values: {
 | 
					      values: {
 | 
				
			||||||
        title: 't1',
 | 
					        title: 't1',
 | 
				
			||||||
 | 
				
			|||||||
@ -9,18 +9,18 @@ describe('destroy', () => {
 | 
				
			|||||||
      dataWrapping: false,
 | 
					      dataWrapping: false,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    registerActions(api);
 | 
					    registerActions(api);
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'posts',
 | 
					      name: 'posts',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'title' },
 | 
					        { type: 'string', name: 'title' },
 | 
				
			||||||
        { type: 'hasMany', name: 'comments' },
 | 
					        { type: 'hasMany', name: 'comments' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'comments',
 | 
					      name: 'comments',
 | 
				
			||||||
      fields: [{ type: 'string', name: 'content' }],
 | 
					      fields: [{ type: 'string', name: 'content' }],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await api.database.sync();
 | 
					    await api.db.sync();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(async () => {
 | 
					  afterEach(async () => {
 | 
				
			||||||
@ -28,7 +28,7 @@ describe('destroy', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('destroy', async () => {
 | 
					  it('destroy', async () => {
 | 
				
			||||||
    const Post = api.database.getModel('posts');
 | 
					    const Post = api.db.getModel('posts');
 | 
				
			||||||
    const post = await Post.create();
 | 
					    const post = await Post.create();
 | 
				
			||||||
    expect(
 | 
					    expect(
 | 
				
			||||||
      await Post.count({
 | 
					      await Post.count({
 | 
				
			||||||
@ -46,7 +46,7 @@ describe('destroy', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('destroy associations', async () => {
 | 
					  it('destroy associations', async () => {
 | 
				
			||||||
    const [Post, Comment] = api.database.getModels(['posts', 'comments']);
 | 
					    const [Post, Comment] = api.db.getModels(['posts', 'comments']);
 | 
				
			||||||
    const post = await Post.create();
 | 
					    const post = await Post.create();
 | 
				
			||||||
    const comment = await Comment.create();
 | 
					    const comment = await Comment.create();
 | 
				
			||||||
    await post.updateAssociations({
 | 
					    await post.updateAssociations({
 | 
				
			||||||
 | 
				
			|||||||
@ -9,20 +9,20 @@ describe('get', () => {
 | 
				
			|||||||
      dataWrapping: false,
 | 
					      dataWrapping: false,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    registerActions(api);
 | 
					    registerActions(api);
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'posts',
 | 
					      name: 'posts',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'title' },
 | 
					        { type: 'string', name: 'title' },
 | 
				
			||||||
        { type: 'hasMany', name: 'comments' },
 | 
					        { type: 'hasMany', name: 'comments' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'comments',
 | 
					      name: 'comments',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'content' },
 | 
					        { type: 'string', name: 'content' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await api.database.sync();
 | 
					    await api.db.sync();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(async () => {
 | 
					  afterEach(async () => {
 | 
				
			||||||
@ -30,7 +30,7 @@ describe('get', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('get', async () => {
 | 
					  it('get', async () => {
 | 
				
			||||||
    const Post = api.database.getModel('posts');
 | 
					    const Post = api.db.getModel('posts');
 | 
				
			||||||
    const post = await Post.create({ title: 't1' });
 | 
					    const post = await Post.create({ title: 't1' });
 | 
				
			||||||
    const response = await api.resource('posts').get({
 | 
					    const response = await api.resource('posts').get({
 | 
				
			||||||
      resourceKey: post.id,
 | 
					      resourceKey: post.id,
 | 
				
			||||||
@ -40,7 +40,7 @@ describe('get', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('get associations', async () => {
 | 
					  it('get associations', async () => {
 | 
				
			||||||
    const [Post, Comment] = api.database.getModels(['posts', 'comments']);
 | 
					    const [Post, Comment] = api.db.getModels(['posts', 'comments']);
 | 
				
			||||||
    const post = await Post.create();
 | 
					    const post = await Post.create();
 | 
				
			||||||
    const comment = await Comment.create({ content: 'c2' });
 | 
					    const comment = await Comment.create({ content: 'c2' });
 | 
				
			||||||
    await post.updateAssociations({
 | 
					    await post.updateAssociations({
 | 
				
			||||||
 | 
				
			|||||||
@ -9,14 +9,14 @@ describe('list', () => {
 | 
				
			|||||||
      dataWrapping: false,
 | 
					      dataWrapping: false,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    registerActions(api);
 | 
					    registerActions(api);
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'comments',
 | 
					      name: 'comments',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'content' },
 | 
					        { type: 'string', name: 'content' },
 | 
				
			||||||
        { type: 'string', name: 'status', defaultValue: 'draft' },
 | 
					        { type: 'string', name: 'status', defaultValue: 'draft' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'posts',
 | 
					      name: 'posts',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'title' },
 | 
					        { type: 'string', name: 'title' },
 | 
				
			||||||
@ -24,8 +24,8 @@ describe('list', () => {
 | 
				
			|||||||
        { type: 'string', name: 'status', defaultValue: 'draft' },
 | 
					        { type: 'string', name: 'status', defaultValue: 'draft' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await api.database.sync();
 | 
					    await api.db.sync();
 | 
				
			||||||
    const [Post, Comment] = api.database.getModels(['posts', 'comments']);
 | 
					    const [Post, Comment] = api.db.getModels(['posts', 'comments']);
 | 
				
			||||||
    for (let index = 1; index < 4; index++) {
 | 
					    for (let index = 1; index < 4; index++) {
 | 
				
			||||||
      const post = await Post.create({ title: `t${index}` });
 | 
					      const post = await Post.create({ title: `t${index}` });
 | 
				
			||||||
      await post.updateAssociations({
 | 
					      await post.updateAssociations({
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ describe('sort', () => {
 | 
				
			|||||||
        dataWrapping: false,
 | 
					        dataWrapping: false,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      registerActions(api);
 | 
					      registerActions(api);
 | 
				
			||||||
      api.database.table({
 | 
					      api.db.table({
 | 
				
			||||||
        name: 'tests',
 | 
					        name: 'tests',
 | 
				
			||||||
        fields: [
 | 
					        fields: [
 | 
				
			||||||
          { type: 'string', name: 'title' },
 | 
					          { type: 'string', name: 'title' },
 | 
				
			||||||
@ -19,8 +19,8 @@ describe('sort', () => {
 | 
				
			|||||||
          { type: 'sort', name: 'sort2' },
 | 
					          { type: 'sort', name: 'sort2' },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      await api.database.sync();
 | 
					      await api.db.sync();
 | 
				
			||||||
      const Test = api.database.getModel('tests');
 | 
					      const Test = api.db.getModel('tests');
 | 
				
			||||||
      for (let index = 1; index < 5; index++) {
 | 
					      for (let index = 1; index < 5; index++) {
 | 
				
			||||||
        await Test.create({ title: `t${index}` });
 | 
					        await Test.create({ title: `t${index}` });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -160,7 +160,7 @@ describe('sort', () => {
 | 
				
			|||||||
        dataWrapping: false,
 | 
					        dataWrapping: false,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      registerActions(api);
 | 
					      registerActions(api);
 | 
				
			||||||
      api.database.table({
 | 
					      api.db.table({
 | 
				
			||||||
        name: 'tests',
 | 
					        name: 'tests',
 | 
				
			||||||
        fields: [
 | 
					        fields: [
 | 
				
			||||||
          { type: 'string', name: 'title' },
 | 
					          { type: 'string', name: 'title' },
 | 
				
			||||||
@ -168,8 +168,8 @@ describe('sort', () => {
 | 
				
			|||||||
          { type: 'sort', name: 'sort', scope: ['state'] },
 | 
					          { type: 'sort', name: 'sort', scope: ['state'] },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      await api.database.sync();
 | 
					      await api.db.sync();
 | 
				
			||||||
      const Test = api.database.getModel('tests');
 | 
					      const Test = api.db.getModel('tests');
 | 
				
			||||||
      for (let index = 1; index < 5; index++) {
 | 
					      for (let index = 1; index < 5; index++) {
 | 
				
			||||||
        await Test.create({ title: `t1${index}`, state: 1 });
 | 
					        await Test.create({ title: `t1${index}`, state: 1 });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -9,20 +9,20 @@ describe('update', () => {
 | 
				
			|||||||
      dataWrapping: false,
 | 
					      dataWrapping: false,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    registerActions(api);
 | 
					    registerActions(api);
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'posts',
 | 
					      name: 'posts',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'title' },
 | 
					        { type: 'string', name: 'title' },
 | 
				
			||||||
        { type: 'hasMany', name: 'comments' },
 | 
					        { type: 'hasMany', name: 'comments' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    api.database.table({
 | 
					    api.db.table({
 | 
				
			||||||
      name: 'comments',
 | 
					      name: 'comments',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        { type: 'string', name: 'content' },
 | 
					        { type: 'string', name: 'content' },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await api.database.sync();
 | 
					    await api.db.sync();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(async () => {
 | 
					  afterEach(async () => {
 | 
				
			||||||
@ -30,7 +30,7 @@ describe('update', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('update', async () => {
 | 
					  it('update', async () => {
 | 
				
			||||||
    const Post = api.database.getModel('posts');
 | 
					    const Post = api.db.getModel('posts');
 | 
				
			||||||
    const post = await Post.create();
 | 
					    const post = await Post.create();
 | 
				
			||||||
    await api.resource('posts').update({
 | 
					    await api.resource('posts').update({
 | 
				
			||||||
      resourceKey: post.id,
 | 
					      resourceKey: post.id,
 | 
				
			||||||
@ -45,7 +45,7 @@ describe('update', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('update associations', async () => {
 | 
					  it('update associations', async () => {
 | 
				
			||||||
    const [Post, Comment] = api.database.getModels(['posts', 'comments']);
 | 
					    const [Post, Comment] = api.db.getModels(['posts', 'comments']);
 | 
				
			||||||
    const post = await Post.create();
 | 
					    const post = await Post.create();
 | 
				
			||||||
    const comment = await Comment.create();
 | 
					    const comment = await Comment.create();
 | 
				
			||||||
    await post.updateAssociations({
 | 
					    await post.updateAssociations({
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@ import { middlewares } from '@nocobase/server';
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await api.start(process.argv);
 | 
					  await api.start(process.argv);
 | 
				
			||||||
  console.log(api.database.getTables().map(t => t.getName()));
 | 
					  console.log(api.db.getTables().map(t => t.getName()));
 | 
				
			||||||
  console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);
 | 
					  console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);
 | 
				
			||||||
  console.log(`http://localhost:${process.env.API_PORT}/`);
 | 
					  console.log(`http://localhost:${process.env.API_PORT}/`);
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@ import * as uiSchema from './ui-schema';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
(async () => {
 | 
					(async () => {
 | 
				
			||||||
  await api.loadPlugins();
 | 
					  await api.loadPlugins();
 | 
				
			||||||
  const database: Database = api.database;
 | 
					  const database: Database = api.db;
 | 
				
			||||||
  await database.sync({
 | 
					  await database.sync({
 | 
				
			||||||
    // tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
 | 
					    // tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ import api from '../app';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
(async () => {
 | 
					(async () => {
 | 
				
			||||||
  await api.loadPlugins();
 | 
					  await api.loadPlugins();
 | 
				
			||||||
  const database: Database = api.database;
 | 
					  const database: Database = api.db;
 | 
				
			||||||
  await database.sync({
 | 
					  await database.sync({
 | 
				
			||||||
    // tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
 | 
					    // tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -29,6 +29,8 @@
 | 
				
			|||||||
    "@nocobase/client": "^0.4.0-alpha.7",
 | 
					    "@nocobase/client": "^0.4.0-alpha.7",
 | 
				
			||||||
    "@nocobase/preset-nocobase": "^0.4.0-alpha.7",
 | 
					    "@nocobase/preset-nocobase": "^0.4.0-alpha.7",
 | 
				
			||||||
    "@umijs/preset-react": "1.x",
 | 
					    "@umijs/preset-react": "1.x",
 | 
				
			||||||
 | 
					    "koa-mount": "^4.0.0",
 | 
				
			||||||
 | 
					    "ts-node-dev": "^1.1.8",
 | 
				
			||||||
    "umi": "^3.0.0"
 | 
					    "umi": "^3.0.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import Server from '@nocobase/server';
 | 
				
			|||||||
import { registerActions } from '@nocobase/actions';
 | 
					import { registerActions } from '@nocobase/actions';
 | 
				
			||||||
import dotenv from 'dotenv';
 | 
					import dotenv from 'dotenv';
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
 | 
					import mount from 'koa-mount';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const start = Date.now();
 | 
					const start = Date.now();
 | 
				
			||||||
console.log('starting... ', new Date().toUTCString());
 | 
					console.log('starting... ', new Date().toUTCString());
 | 
				
			||||||
@ -56,6 +57,8 @@ if (process.argv.length < 3) {
 | 
				
			|||||||
  process.argv.push('start', '--port', '2000');
 | 
					  process.argv.push('start', '--port', '2000');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(process.argv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api.start(process.argv).then(() => {
 | 
					api.start(process.argv).then(() => {
 | 
				
			||||||
  console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);
 | 
					  console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -2,11 +2,10 @@
 | 
				
			|||||||
title: Action - 操作
 | 
					title: Action - 操作
 | 
				
			||||||
nav:
 | 
					nav:
 | 
				
			||||||
  title: 组件
 | 
					  title: 组件
 | 
				
			||||||
  path: /client
 | 
					  path: /schemas
 | 
				
			||||||
group:
 | 
					group:
 | 
				
			||||||
  order: 1
 | 
					  title: 组件
 | 
				
			||||||
  title: Schemas
 | 
					  path: /schemas
 | 
				
			||||||
  path: /client/schemas
 | 
					 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Action - 操作
 | 
					# Action - 操作
 | 
				
			||||||
 | 
				
			|||||||
@ -29,6 +29,8 @@ const config = {
 | 
				
			|||||||
  logging: process.env.DB_LOG_SQL === 'on' ? console.log : false
 | 
					  logging: process.env.DB_LOG_SQL === 'on' ? console.log : false
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getDatabase() {
 | 
					export function getDatabase() {
 | 
				
			||||||
  return new Database({
 | 
					  return new Database({
 | 
				
			||||||
    ...config,
 | 
					    ...config,
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ describe('hook', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
    registerActions(api);
 | 
					    registerActions(api);
 | 
				
			||||||
    await api.loadPlugins();
 | 
					    await api.loadPlugins();
 | 
				
			||||||
    db = api.database;
 | 
					    db = api.db;
 | 
				
			||||||
    db.table({
 | 
					    db.table({
 | 
				
			||||||
      name: 'posts',
 | 
					      name: 'posts',
 | 
				
			||||||
      logging: true,
 | 
					      logging: true,
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ describe('automations', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  beforeEach(async () => {
 | 
					  beforeEach(async () => {
 | 
				
			||||||
    app = await getApp();
 | 
					    app = await getApp();
 | 
				
			||||||
    db = app.database;
 | 
					    db = app.db;
 | 
				
			||||||
    Automation = db.getModel('automations') as any;
 | 
					    Automation = db.getModel('automations') as any;
 | 
				
			||||||
    Test = db.getModel('tests');
 | 
					    Test = db.getModel('tests');
 | 
				
			||||||
    Target = db.getModel('targets');
 | 
					    Target = db.getModel('targets');
 | 
				
			||||||
 | 
				
			|||||||
@ -59,11 +59,11 @@ export async function getApp() {
 | 
				
			|||||||
    automations: plugin
 | 
					    automations: plugin
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  await app.loadPlugins();
 | 
					  await app.loadPlugins();
 | 
				
			||||||
  const testTables = app.database.import({
 | 
					  const testTables = app.db.import({
 | 
				
			||||||
    directory: path.resolve(__dirname, './collections')
 | 
					    directory: path.resolve(__dirname, './collections')
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    await app.database.sync();
 | 
					    await app.db.sync();
 | 
				
			||||||
  } catch (err) {
 | 
					  } catch (err) {
 | 
				
			||||||
    console.error(err);
 | 
					    console.error(err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -71,15 +71,15 @@ export async function getApp() {
 | 
				
			|||||||
  for (const table of testTables.values()) {
 | 
					  for (const table of testTables.values()) {
 | 
				
			||||||
    // TODO(bug): 由于每个用例结束后不会清理用于测试的数据表,导致再次创建和更新
 | 
					    // TODO(bug): 由于每个用例结束后不会清理用于测试的数据表,导致再次创建和更新
 | 
				
			||||||
    // 创建和更新里面仍会再次创建 fields,导致创建相关的数据重复,数据库报错。
 | 
					    // 创建和更新里面仍会再次创建 fields,导致创建相关的数据重复,数据库报错。
 | 
				
			||||||
    await app.database.getModel('collections').import(table.getOptions(), { update: true, migrate: false });
 | 
					    await app.db.getModel('collections').import(table.getOptions(), { update: true, migrate: false });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  app.context.db = app.database;
 | 
					  app.context.db = app.db;
 | 
				
			||||||
  app.use(bodyParser());
 | 
					  app.use(bodyParser());
 | 
				
			||||||
  app.use(middleware({
 | 
					  app.use(middleware({
 | 
				
			||||||
    prefix: '/api',
 | 
					    prefix: '/api',
 | 
				
			||||||
    resourcer: app.resourcer,
 | 
					    resourcer: app.resourcer,
 | 
				
			||||||
    database: app.database,
 | 
					    database: app.db,
 | 
				
			||||||
  }));
 | 
					  }));
 | 
				
			||||||
  return app;
 | 
					  return app;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ describe('automations.jobs', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  beforeEach(async () => {
 | 
					  beforeEach(async () => {
 | 
				
			||||||
    app = await getApp();
 | 
					    app = await getApp();
 | 
				
			||||||
    db = app.database;
 | 
					    db = app.db;
 | 
				
			||||||
    Job = db.getModel('automations_jobs') as any;
 | 
					    Job = db.getModel('automations_jobs') as any;
 | 
				
			||||||
    Test = db.getModel('tests');
 | 
					    Test = db.getModel('tests');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -6,14 +6,14 @@ import Application from '@nocobase/server';
 | 
				
			|||||||
registerModels({ ChinaRegion });
 | 
					registerModels({ ChinaRegion });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function (this: Application, options = {}) {
 | 
					export default async function (this: Application, options = {}) {
 | 
				
			||||||
  const { database } = this;
 | 
					  const { db } = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  database.import({
 | 
					  db.import({
 | 
				
			||||||
    directory: path.resolve(__dirname, 'collections'),
 | 
					    directory: path.resolve(__dirname, 'collections'),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this.on('china-region.init', async () => {
 | 
					  this.on('db.init', async () => {
 | 
				
			||||||
    const M = database.getModel('china_regions');
 | 
					    const M = db.getModel('china_regions');
 | 
				
			||||||
    await M.importData();
 | 
					    await M.importData();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,11 +9,11 @@ describe('collection hooks', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  beforeEach(async () => {
 | 
					  beforeEach(async () => {
 | 
				
			||||||
    app = await getApp();
 | 
					    app = await getApp();
 | 
				
			||||||
    db = app.database;
 | 
					    db = app.db;
 | 
				
			||||||
    agent = getAgent(app);
 | 
					    agent = getAgent(app);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(() => app.database.close());
 | 
					  afterEach(() => app.db.close());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('create table', async () => {
 | 
					  it('create table', async () => {
 | 
				
			||||||
    const Collection = db.getModel('collections');
 | 
					    const Collection = db.getModel('collections');
 | 
				
			||||||
 | 
				
			|||||||
@ -59,22 +59,22 @@ export async function getApp() {
 | 
				
			|||||||
  app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common });
 | 
					  app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common });
 | 
				
			||||||
  app.registerPlugin('collections', [plugin]);
 | 
					  app.registerPlugin('collections', [plugin]);
 | 
				
			||||||
  await app.loadPlugins();
 | 
					  await app.loadPlugins();
 | 
				
			||||||
  await app.database.sync();
 | 
					  await app.db.sync();
 | 
				
			||||||
  // 表配置信息存到数据库里
 | 
					  // 表配置信息存到数据库里
 | 
				
			||||||
  // const tables = app.database.getTables([]);
 | 
					  // const tables = app.db.getTables([]);
 | 
				
			||||||
  // for (const table of tables) {
 | 
					  // for (const table of tables) {
 | 
				
			||||||
  //   const Collection = app.database.getModel('collections');
 | 
					  //   const Collection = app.db.getModel('collections');
 | 
				
			||||||
  //   await Collection.import(table.getOptions(), { hooks: false });
 | 
					  //   await Collection.import(table.getOptions(), { hooks: false });
 | 
				
			||||||
  // }
 | 
					  // }
 | 
				
			||||||
  app.use(async (ctx, next) => {
 | 
					  app.use(async (ctx, next) => {
 | 
				
			||||||
    ctx.db = app.database;
 | 
					    ctx.db = app.db;
 | 
				
			||||||
    await next();
 | 
					    await next();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  app.use(bodyParser());
 | 
					  app.use(bodyParser());
 | 
				
			||||||
  app.use(middleware({
 | 
					  app.use(middleware({
 | 
				
			||||||
    prefix: '/api',
 | 
					    prefix: '/api',
 | 
				
			||||||
    resourcer: app.resourcer,
 | 
					    resourcer: app.resourcer,
 | 
				
			||||||
    database: app.database,
 | 
					    database: app.db,
 | 
				
			||||||
  }));
 | 
					  }));
 | 
				
			||||||
  return app;
 | 
					  return app;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import { createOrUpdate, findAll } from './actions';
 | 
				
			|||||||
import { create } from './actions/fields';
 | 
					import { create } from './actions/fields';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function (this: Application, options = {}) {
 | 
					export default async function (this: Application, options = {}) {
 | 
				
			||||||
  const database = this.database;
 | 
					  const database = this.db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  registerModels(models);
 | 
					  registerModels(models);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,12 +14,11 @@ export default async function (this: Application, options = {}) {
 | 
				
			|||||||
    directory: path.resolve(__dirname, 'collections'),
 | 
					    directory: path.resolve(__dirname, 'collections'),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this.on('pluginsLoaded', async () => {
 | 
					  this.on('afterLoadPlugins', async () => {
 | 
				
			||||||
    console.log('pluginsLoaded');
 | 
					 | 
				
			||||||
    await database.getModel('collections').load();
 | 
					    await database.getModel('collections').load();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this.on('collections.init', async () => {
 | 
					  this.on('db.init', async () => {
 | 
				
			||||||
    const userTable = database.getTable('users');
 | 
					    const userTable = database.getTable('users');
 | 
				
			||||||
    const config = userTable.getOptions();
 | 
					    const config = userTable.getOptions();
 | 
				
			||||||
    const Collection = database.getModel('collections');
 | 
					    const Collection = database.getModel('collections');
 | 
				
			||||||
 | 
				
			|||||||
@ -16,9 +16,9 @@ describe('action', () => {
 | 
				
			|||||||
    app = await getApp();
 | 
					    app = await getApp();
 | 
				
			||||||
    agent = getAgent(app);
 | 
					    agent = getAgent(app);
 | 
				
			||||||
    api = getAPI(app);
 | 
					    api = getAPI(app);
 | 
				
			||||||
    db = app.database;
 | 
					    db = app.db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const Storage = app.database.getModel('storages');
 | 
					    const Storage = app.db.getModel('storages');
 | 
				
			||||||
    await Storage.create({
 | 
					    await Storage.create({
 | 
				
			||||||
      name: `local_${Date.now().toString(36)}`,
 | 
					      name: `local_${Date.now().toString(36)}`,
 | 
				
			||||||
      type: STORAGE_TYPE_LOCAL,
 | 
					      type: STORAGE_TYPE_LOCAL,
 | 
				
			||||||
 | 
				
			|||||||
@ -67,23 +67,23 @@ export async function getApp() {
 | 
				
			|||||||
    'file-manager': [plugin]
 | 
					    'file-manager': [plugin]
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  await app.loadPlugins();
 | 
					  await app.loadPlugins();
 | 
				
			||||||
  app.database.import({
 | 
					  app.db.import({
 | 
				
			||||||
    directory: path.resolve(__dirname, './tables')
 | 
					    directory: path.resolve(__dirname, './tables')
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    await app.database.sync();
 | 
					    await app.db.sync();
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.error(error);
 | 
					    console.error(error);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  app.use(async (ctx, next) => {
 | 
					  app.use(async (ctx, next) => {
 | 
				
			||||||
    ctx.db = app.database;
 | 
					    ctx.db = app.db;
 | 
				
			||||||
    await next();
 | 
					    await next();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  app.use(bodyParser());
 | 
					  app.use(bodyParser());
 | 
				
			||||||
  app.use(middleware({
 | 
					  app.use(middleware({
 | 
				
			||||||
    prefix: '/api',
 | 
					    prefix: '/api',
 | 
				
			||||||
    resourcer: app.resourcer,
 | 
					    resourcer: app.resourcer,
 | 
				
			||||||
    database: app.database,
 | 
					    database: app.db,
 | 
				
			||||||
  }));
 | 
					  }));
 | 
				
			||||||
  return app;
 | 
					  return app;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -23,8 +23,9 @@ export default async function () {
 | 
				
			|||||||
  resourcer.registerActionHandler('upload', uploadAction);
 | 
					  resourcer.registerActionHandler('upload', uploadAction);
 | 
				
			||||||
  localMiddleware(this);
 | 
					  localMiddleware(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this.on('file-manager.init', async () => {
 | 
					  const Storage = database.getModel('storages');
 | 
				
			||||||
    const Storage = database.getModel('storages');
 | 
					
 | 
				
			||||||
 | 
					  this.on('db.init', async () => {
 | 
				
			||||||
    await Storage.create({
 | 
					    await Storage.create({
 | 
				
			||||||
      title: '本地存储',
 | 
					      title: '本地存储',
 | 
				
			||||||
      name: `local`,
 | 
					      name: `local`,
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ export function getDocumentRoot(storage): string {
 | 
				
			|||||||
// TODO(optimize): 初始化的时机不应该放在中间件里
 | 
					// TODO(optimize): 初始化的时机不应该放在中间件里
 | 
				
			||||||
export function middleware(app) {
 | 
					export function middleware(app) {
 | 
				
			||||||
  const storages = new Map<string, any>();
 | 
					  const storages = new Map<string, any>();
 | 
				
			||||||
  const StorageModel = app.database.getModel('storages');
 | 
					  const StorageModel = app.db.getModel('storages');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return app.use(async function (ctx, next) {
 | 
					  return app.use(async function (ctx, next) {
 | 
				
			||||||
    const items = await StorageModel.findAll({
 | 
					    const items = await StorageModel.findAll({
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								packages/plugin-saas/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								packages/plugin-saas/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					yarn-error.log
 | 
				
			||||||
 | 
					.env
 | 
				
			||||||
 | 
					src2
 | 
				
			||||||
							
								
								
									
										7
									
								
								packages/plugin-saas/.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/plugin-saas/.npmignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					docs
 | 
				
			||||||
 | 
					__tests__
 | 
				
			||||||
 | 
					tsconfig.json
 | 
				
			||||||
 | 
					src
 | 
				
			||||||
 | 
					.fatherrc.ts
 | 
				
			||||||
@ -25,7 +25,7 @@ export default async function (this: Application, options = {}) {
 | 
				
			|||||||
    await next();
 | 
					    await next();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this.on('system-settings.init', async () => {
 | 
					  this.on('db.init', async () => {
 | 
				
			||||||
    const setting = await SystemSetting.create({
 | 
					    const setting = await SystemSetting.create({
 | 
				
			||||||
      title: 'NocoBase',
 | 
					      title: 'NocoBase',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
				
			|||||||
@ -59,22 +59,22 @@ export async function getApp() {
 | 
				
			|||||||
  app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common });
 | 
					  app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common });
 | 
				
			||||||
  app.registerPlugin('collections', [plugin]);
 | 
					  app.registerPlugin('collections', [plugin]);
 | 
				
			||||||
  await app.loadPlugins();
 | 
					  await app.loadPlugins();
 | 
				
			||||||
  await app.database.sync();
 | 
					  await app.db.sync();
 | 
				
			||||||
  // 表配置信息存到数据库里
 | 
					  // 表配置信息存到数据库里
 | 
				
			||||||
  // const tables = app.database.getTables([]);
 | 
					  // const tables = app.db.getTables([]);
 | 
				
			||||||
  // for (const table of tables) {
 | 
					  // for (const table of tables) {
 | 
				
			||||||
  //   const Collection = app.database.getModel('collections');
 | 
					  //   const Collection = app.db.getModel('collections');
 | 
				
			||||||
  //   await Collection.import(table.getOptions(), { hooks: false });
 | 
					  //   await Collection.import(table.getOptions(), { hooks: false });
 | 
				
			||||||
  // }
 | 
					  // }
 | 
				
			||||||
  app.use(async (ctx, next) => {
 | 
					  app.use(async (ctx, next) => {
 | 
				
			||||||
    ctx.db = app.database;
 | 
					    ctx.db = app.db;
 | 
				
			||||||
    await next();
 | 
					    await next();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  app.use(bodyParser());
 | 
					  app.use(bodyParser());
 | 
				
			||||||
  app.use(middleware({
 | 
					  app.use(middleware({
 | 
				
			||||||
    prefix: '/api',
 | 
					    prefix: '/api',
 | 
				
			||||||
    resourcer: app.resourcer,
 | 
					    resourcer: app.resourcer,
 | 
				
			||||||
    database: app.database,
 | 
					    database: app.db,
 | 
				
			||||||
  }));
 | 
					  }));
 | 
				
			||||||
  return app;
 | 
					  return app;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,11 +9,11 @@ describe('routes', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  beforeEach(async () => {
 | 
					  beforeEach(async () => {
 | 
				
			||||||
    app = await getApp();
 | 
					    app = await getApp();
 | 
				
			||||||
    db = app.database;
 | 
					    db = app.db;
 | 
				
			||||||
    agent = getAgent(app);
 | 
					    agent = getAgent(app);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(() => app.database.close());
 | 
					  afterEach(() => app.db.close());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // it.only('create route', async () => {
 | 
					  // it.only('create route', async () => {
 | 
				
			||||||
  //   const Route = db.getModel('routes');
 | 
					  //   const Route = db.getModel('routes');
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ export default async function (this: Application, options = {}) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const Route = database.getModel('routes');
 | 
					  const Route = database.getModel('routes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this.on('ui-router.init', async () => {
 | 
					  this.on('db.init', async () => {
 | 
				
			||||||
    const data = [
 | 
					    const data = [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        type: 'redirect',
 | 
					        type: 'redirect',
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ describe('user fields', () => {
 | 
				
			|||||||
    api = mockServer();
 | 
					    api = mockServer();
 | 
				
			||||||
    api.registerPlugin('users', require('../server').default);
 | 
					    api.registerPlugin('users', require('../server').default);
 | 
				
			||||||
    await api.loadPlugins();
 | 
					    await api.loadPlugins();
 | 
				
			||||||
    db = api.database;
 | 
					    db = api.db;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(async () => {
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@ export default async function (this: Application, options = {}) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  registerFields(fields);
 | 
					  registerFields(fields);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this.on('users.init', async () => {
 | 
					  this.on('db.init', async () => {
 | 
				
			||||||
    const User = database.getModel('users');
 | 
					    const User = database.getModel('users');
 | 
				
			||||||
    await User.create({
 | 
					    await User.create({
 | 
				
			||||||
      nickname: '超级管理员',
 | 
					      nickname: '超级管理员',
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,9 @@
 | 
				
			|||||||
import supertest from 'supertest';
 | 
					import supertest from 'supertest';
 | 
				
			||||||
import { Application } from '../application';
 | 
					import { Application } from '../application';
 | 
				
			||||||
 | 
					import { Plugin } from '../plugin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MyPlugin extends Plugin {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('application', () => {
 | 
					describe('application', () => {
 | 
				
			||||||
  let app: Application;
 | 
					  let app: Application;
 | 
				
			||||||
@ -42,7 +46,7 @@ describe('application', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(async () => {
 | 
					  afterEach(async () => {
 | 
				
			||||||
    return app.database.close();
 | 
					    return app.db.close();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('resourcer.define', async () => {
 | 
					  it('resourcer.define', async () => {
 | 
				
			||||||
@ -63,7 +67,7 @@ describe('application', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('db.table', async () => {
 | 
					  it('db.table', async () => {
 | 
				
			||||||
    app.database.table({
 | 
					    app.db.table({
 | 
				
			||||||
      name: 'tests',
 | 
					      name: 'tests',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    const response = await agent.get('/api/tests');
 | 
					    const response = await agent.get('/api/tests');
 | 
				
			||||||
@ -71,10 +75,10 @@ describe('application', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('db.association', async () => {
 | 
					  it('db.association', async () => {
 | 
				
			||||||
    app.database.table({
 | 
					    app.db.table({
 | 
				
			||||||
      name: 'bars',
 | 
					      name: 'bars',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    app.database.table({
 | 
					    app.db.table({
 | 
				
			||||||
      name: 'foos',
 | 
					      name: 'foos',
 | 
				
			||||||
      fields: [
 | 
					      fields: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -90,7 +94,7 @@ describe('application', () => {
 | 
				
			|||||||
  it('db.middleware', async () => {
 | 
					  it('db.middleware', async () => {
 | 
				
			||||||
    const index = app.middleware.findIndex(m => m.name === 'table2resource');
 | 
					    const index = app.middleware.findIndex(m => m.name === 'table2resource');
 | 
				
			||||||
    app.middleware.splice(index, 0, async (ctx, next) => {
 | 
					    app.middleware.splice(index, 0, async (ctx, next) => {
 | 
				
			||||||
      app.database.table({
 | 
					      app.db.table({
 | 
				
			||||||
        name: 'tests',
 | 
					        name: 'tests',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      await next();
 | 
					      await next();
 | 
				
			||||||
@ -102,10 +106,10 @@ describe('application', () => {
 | 
				
			|||||||
  it('db.middleware', async () => {
 | 
					  it('db.middleware', async () => {
 | 
				
			||||||
    const index = app.middleware.findIndex(m => m.name === 'table2resource');
 | 
					    const index = app.middleware.findIndex(m => m.name === 'table2resource');
 | 
				
			||||||
    app.middleware.splice(index, 0, async (ctx, next) => {
 | 
					    app.middleware.splice(index, 0, async (ctx, next) => {
 | 
				
			||||||
      app.database.table({
 | 
					      app.db.table({
 | 
				
			||||||
        name: 'bars',
 | 
					        name: 'bars',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      app.database.table({
 | 
					      app.db.table({
 | 
				
			||||||
        name: 'foos',
 | 
					        name: 'foos',
 | 
				
			||||||
        fields: [
 | 
					        fields: [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,7 @@ describe('application', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterEach(async () => {
 | 
					  afterEach(async () => {
 | 
				
			||||||
    return app.database.close();
 | 
					    return app.db.close();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('resourcer.define', async () => {
 | 
					  it('resourcer.define', async () => {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										147
									
								
								packages/server/src/__tests__/plugin.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								packages/server/src/__tests__/plugin.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					import supertest from 'supertest';
 | 
				
			||||||
 | 
					import { Application } from '../application';
 | 
				
			||||||
 | 
					import { Plugin } from '../plugin';
 | 
				
			||||||
 | 
					import path from 'path';
 | 
				
			||||||
 | 
					import Plugin3 from './plugins/plugin3';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('plugin', () => {
 | 
				
			||||||
 | 
					  let app: Application;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    app = new Application({
 | 
				
			||||||
 | 
					      database: {
 | 
				
			||||||
 | 
					        username: process.env.DB_USER,
 | 
				
			||||||
 | 
					        password: process.env.DB_PASSWORD,
 | 
				
			||||||
 | 
					        database: process.env.DB_DATABASE,
 | 
				
			||||||
 | 
					        host: process.env.DB_HOST,
 | 
				
			||||||
 | 
					        port: process.env.DB_PORT as any,
 | 
				
			||||||
 | 
					        dialect: process.env.DB_DIALECT as any,
 | 
				
			||||||
 | 
					        dialectOptions: {
 | 
				
			||||||
 | 
					          charset: 'utf8mb4',
 | 
				
			||||||
 | 
					          collate: 'utf8mb4_unicode_ci',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      resourcer: {
 | 
				
			||||||
 | 
					        prefix: '/api',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      dataWrapping: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    return app.db.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('define', () => {
 | 
				
			||||||
 | 
					    it('plugin name', async () => {
 | 
				
			||||||
 | 
					      const plugin = app.plugin(function abc() {});
 | 
				
			||||||
 | 
					      expect(plugin).toBeInstanceOf(Plugin);
 | 
				
			||||||
 | 
					      expect(plugin.getName()).toBe('abc');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('plugin name', async () => {
 | 
				
			||||||
 | 
					      const plugin = app.plugin(function abc() {}, {
 | 
				
			||||||
 | 
					        name: 'plugin-name2'
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(plugin).toBeInstanceOf(Plugin);
 | 
				
			||||||
 | 
					      expect(plugin.getName()).toBe('plugin-name2');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('plugin name', async () => {
 | 
				
			||||||
 | 
					      const plugin = app.plugin(function () {}, {
 | 
				
			||||||
 | 
					        name: 'plugin-name3'
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(plugin).toBeInstanceOf(Plugin);
 | 
				
			||||||
 | 
					      expect(plugin.getName()).toBe('plugin-name3');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('plugin name', async () => {
 | 
				
			||||||
 | 
					      class MyPlugin extends Plugin {}
 | 
				
			||||||
 | 
					      const plugin = app.plugin(MyPlugin);
 | 
				
			||||||
 | 
					      expect(plugin).toBeInstanceOf(MyPlugin);
 | 
				
			||||||
 | 
					      expect(plugin.getName()).toBe('MyPlugin');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin name', async () => {
 | 
				
			||||||
 | 
					      const plugin = app.plugin(path.resolve(__dirname, './plugins/plugin1'));
 | 
				
			||||||
 | 
					      expect(plugin).toBeInstanceOf(Plugin);
 | 
				
			||||||
 | 
					      expect(plugin.getName()).toBe('abc');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin name', async () => {
 | 
				
			||||||
 | 
					      const plugin = app.plugin(path.resolve(__dirname, './plugins/plugin3'));
 | 
				
			||||||
 | 
					      expect(plugin).toBeInstanceOf(Plugin3);
 | 
				
			||||||
 | 
					      expect(plugin.getName()).toBe('Plugin3');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('load', () => {
 | 
				
			||||||
 | 
					    it('plugin load', async () => {
 | 
				
			||||||
 | 
					      app.plugin(function abc(this: Plugin) {
 | 
				
			||||||
 | 
					        this.app.collection({
 | 
				
			||||||
 | 
					          name: 'tests',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await app.load();
 | 
				
			||||||
 | 
					      const Test = app.db.getModel('tests');
 | 
				
			||||||
 | 
					      expect(Test).toBeDefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin load', async () => {
 | 
				
			||||||
 | 
					      app.plugin({
 | 
				
			||||||
 | 
					        load() {
 | 
				
			||||||
 | 
					          app.collection({
 | 
				
			||||||
 | 
					            name: 'tests',
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await app.load();
 | 
				
			||||||
 | 
					      const Test = app.db.getModel('tests');
 | 
				
			||||||
 | 
					      expect(Test).toBeDefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin load', async () => {
 | 
				
			||||||
 | 
					      app.plugin({
 | 
				
			||||||
 | 
					        load: () => {
 | 
				
			||||||
 | 
					          app.collection({
 | 
				
			||||||
 | 
					            name: 'tests',
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await app.load();
 | 
				
			||||||
 | 
					      const Test = app.db.getModel('tests');
 | 
				
			||||||
 | 
					      expect(Test).toBeDefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin load', async () => {
 | 
				
			||||||
 | 
					      app.plugin(class MyPlugin extends Plugin {
 | 
				
			||||||
 | 
					        async load() {
 | 
				
			||||||
 | 
					          this.app.collection({
 | 
				
			||||||
 | 
					            name: 'tests',
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await app.load();
 | 
				
			||||||
 | 
					      const Test = app.db.getModel('tests');
 | 
				
			||||||
 | 
					      expect(Test).toBeDefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin load', async () => {
 | 
				
			||||||
 | 
					      app.plugin(path.resolve(__dirname, './plugins/plugin1'));
 | 
				
			||||||
 | 
					      await app.load();
 | 
				
			||||||
 | 
					      const Test = app.db.getModel('tests');
 | 
				
			||||||
 | 
					      expect(Test).toBeDefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin load', async () => {
 | 
				
			||||||
 | 
					      app.plugin(path.resolve(__dirname, './plugins/plugin2'));
 | 
				
			||||||
 | 
					      await app.load();
 | 
				
			||||||
 | 
					      const Test = app.db.getModel('tests');
 | 
				
			||||||
 | 
					      expect(Test).toBeDefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('plugin load', async () => {
 | 
				
			||||||
 | 
					      app.plugin(path.resolve(__dirname, './plugins/plugin3'));
 | 
				
			||||||
 | 
					      await app.load();
 | 
				
			||||||
 | 
					      const Test = app.db.getModel('tests');
 | 
				
			||||||
 | 
					      expect(Test).toBeDefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										7
									
								
								packages/server/src/__tests__/plugins/plugin1.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/server/src/__tests__/plugins/plugin1.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					import { Plugin } from '../../plugin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function abc(this: Plugin) {
 | 
				
			||||||
 | 
					  this.app.db.table({
 | 
				
			||||||
 | 
					    name: 'tests',
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								packages/server/src/__tests__/plugins/plugin2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/server/src/__tests__/plugins/plugin2.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { IPlugin } from '../../plugin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  load() {
 | 
				
			||||||
 | 
					    this.app.db.table({
 | 
				
			||||||
 | 
					      name: 'tests',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					} as IPlugin;
 | 
				
			||||||
							
								
								
									
										9
									
								
								packages/server/src/__tests__/plugins/plugin3.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/server/src/__tests__/plugins/plugin3.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { Plugin } from '../../plugin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class Plugin3 extends Plugin {
 | 
				
			||||||
 | 
					  async load() {
 | 
				
			||||||
 | 
					    this.app.db.table({
 | 
				
			||||||
 | 
					      name: 'tests',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
import Koa from 'koa';
 | 
					import Koa from 'koa';
 | 
				
			||||||
import cors from '@koa/cors';
 | 
					import cors from '@koa/cors';
 | 
				
			||||||
import bodyParser from 'koa-bodyparser';
 | 
					import bodyParser from 'koa-bodyparser';
 | 
				
			||||||
import { Command } from 'commander';
 | 
					import { Command, CommandOptions } from 'commander';
 | 
				
			||||||
import Database, { DatabaseOptions } from '@nocobase/database';
 | 
					import Database, { DatabaseOptions, TableOptions } from '@nocobase/database';
 | 
				
			||||||
import Resourcer from '@nocobase/resourcer';
 | 
					import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
 | 
				
			||||||
import { dataWrapping, table2resource } from './middlewares';
 | 
					import { dataWrapping, table2resource } from './middlewares';
 | 
				
			||||||
 | 
					import { PluginType, Plugin, PluginOptions } from './plugin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ResourcerOptions {
 | 
					export interface ResourcerOptions {
 | 
				
			||||||
  prefix?: string;
 | 
					  prefix?: string;
 | 
				
			||||||
@ -19,18 +20,18 @@ export interface ApplicationOptions {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Application extends Koa {
 | 
					export class Application extends Koa {
 | 
				
			||||||
  public readonly database: Database;
 | 
					  public readonly db: Database;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public readonly resourcer: Resourcer;
 | 
					  public readonly resourcer: Resourcer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public readonly cli: Command;
 | 
					  public readonly cli: Command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected plugins = new Map<string, any>();
 | 
					  protected plugins = new Map<string, Plugin>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(options: ApplicationOptions) {
 | 
					  constructor(options: ApplicationOptions) {
 | 
				
			||||||
    super();
 | 
					    super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.database = new Database(options.database);
 | 
					    this.db = new Database(options.database);
 | 
				
			||||||
    this.resourcer = new Resourcer({ ...options.resourcer });
 | 
					    this.resourcer = new Resourcer({ ...options.resourcer });
 | 
				
			||||||
    this.cli = new Command();
 | 
					    this.cli = new Command();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,8 +49,7 @@ export class Application extends Koa {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.use(async (ctx, next) => {
 | 
					    this.use(async (ctx, next) => {
 | 
				
			||||||
      ctx.db = this.database;
 | 
					      ctx.db = this.db;
 | 
				
			||||||
      ctx.database = this.database;
 | 
					 | 
				
			||||||
      ctx.resourcer = this.resourcer;
 | 
					      ctx.resourcer = this.resourcer;
 | 
				
			||||||
      await next();
 | 
					      await next();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -65,9 +65,21 @@ export class Application extends Koa {
 | 
				
			|||||||
      .command('db sync')
 | 
					      .command('db sync')
 | 
				
			||||||
      .option('-f, --force')
 | 
					      .option('-f, --force')
 | 
				
			||||||
      .action(async (...args) => {
 | 
					      .action(async (...args) => {
 | 
				
			||||||
 | 
					        console.log('db sync');
 | 
				
			||||||
        const cli = args.pop();
 | 
					        const cli = args.pop();
 | 
				
			||||||
        await this.database.sync();
 | 
					        const force = cli.opts()?.force;
 | 
				
			||||||
        await this.database.close();
 | 
					        await this.load();
 | 
				
			||||||
 | 
					        await this.db.sync(
 | 
				
			||||||
 | 
					          force
 | 
				
			||||||
 | 
					            ? {
 | 
				
			||||||
 | 
					                force: true,
 | 
				
			||||||
 | 
					                alter: {
 | 
				
			||||||
 | 
					                  drop: true,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            : {},
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        await this.destroy();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.cli
 | 
					    this.cli
 | 
				
			||||||
@ -75,6 +87,13 @@ export class Application extends Koa {
 | 
				
			|||||||
      // .option('-f, --force')
 | 
					      // .option('-f, --force')
 | 
				
			||||||
      .action(async (...args) => {
 | 
					      .action(async (...args) => {
 | 
				
			||||||
        const cli = args.pop();
 | 
					        const cli = args.pop();
 | 
				
			||||||
 | 
					        await this.load();
 | 
				
			||||||
 | 
					        await this.db.sync({
 | 
				
			||||||
 | 
					          force: true,
 | 
				
			||||||
 | 
					          alter: {
 | 
				
			||||||
 | 
					            drop: true,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        await this.emitAsync('db.init');
 | 
					        await this.emitAsync('db.init');
 | 
				
			||||||
        await this.destroy();
 | 
					        await this.destroy();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@ -86,13 +105,66 @@ export class Application extends Koa {
 | 
				
			|||||||
        const cli = args.pop();
 | 
					        const cli = args.pop();
 | 
				
			||||||
        console.log(args);
 | 
					        console.log(args);
 | 
				
			||||||
        const opts = cli.opts();
 | 
					        const opts = cli.opts();
 | 
				
			||||||
        await this.loadPlugins();
 | 
					        await this.load();
 | 
				
			||||||
        await this.emitAsync('server.beforeStart');
 | 
					        await this.emitAsync('server.beforeStart');
 | 
				
			||||||
        this.listen(opts.port || 3000);
 | 
					        this.listen(opts.port || 3000);
 | 
				
			||||||
        console.log(`http://localhost:${opts.port || 3000}/`);
 | 
					        console.log(`http://localhost:${opts.port || 3000}/`);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  collection(options: TableOptions) {
 | 
				
			||||||
 | 
					    return this.db.table(options);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  resource(options: ResourceOptions) {
 | 
				
			||||||
 | 
					    return this.resourcer.define(options);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  command(nameAndArgs: string, opts?: CommandOptions) {
 | 
				
			||||||
 | 
					    return this.cli.command(nameAndArgs, opts);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  plugin(plugin: PluginType, options?: PluginOptions): Plugin {
 | 
				
			||||||
 | 
					    if (typeof plugin === 'string') {
 | 
				
			||||||
 | 
					      plugin = require(plugin).default;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let instance: Plugin;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      // @ts-ignore
 | 
				
			||||||
 | 
					      const p = new plugin();
 | 
				
			||||||
 | 
					      if (p instanceof Plugin) {
 | 
				
			||||||
 | 
					        // @ts-ignore
 | 
				
			||||||
 | 
					        instance = new plugin({}, {
 | 
				
			||||||
 | 
					          ...options,
 | 
				
			||||||
 | 
					          app: this,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw new Error('plugin must be instanceof Plugin');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      instance = new Plugin(plugin, {
 | 
				
			||||||
 | 
					        ...options,
 | 
				
			||||||
 | 
					        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');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async emitAsync(event: string | symbol, ...args: any[]): Promise<boolean> {
 | 
					  async emitAsync(event: string | symbol, ...args: any[]): Promise<boolean> {
 | 
				
			||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    const events = this._events;
 | 
					    const events = this._events;
 | 
				
			||||||
@ -144,57 +216,58 @@ export class Application extends Koa {
 | 
				
			|||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  registerPlugin(key: string | object, plugin?: any) {
 | 
					  // registerPlugin(key: string | object, plugin?: any) {
 | 
				
			||||||
    if (typeof key === 'object') {
 | 
					  //   if (typeof key === 'object') {
 | 
				
			||||||
      Object.keys(key).forEach((k) => {
 | 
					  //     Object.keys(key).forEach((k) => {
 | 
				
			||||||
        this.registerPlugin(k, key[k]);
 | 
					  //       this.registerPlugin(k, key[k]);
 | 
				
			||||||
      });
 | 
					  //     });
 | 
				
			||||||
    } else {
 | 
					  //   } else {
 | 
				
			||||||
      const config = {};
 | 
					  //     const config = {};
 | 
				
			||||||
      if (Array.isArray(plugin)) {
 | 
					  //     if (Array.isArray(plugin)) {
 | 
				
			||||||
        const [entry, options = {}] = plugin;
 | 
					  //       const [entry, options = {}] = plugin;
 | 
				
			||||||
        Object.assign(config, { entry, options });
 | 
					  //       Object.assign(config, { entry, options });
 | 
				
			||||||
      } else {
 | 
					  //     } else {
 | 
				
			||||||
        Object.assign(config, { entry: plugin, options: {} });
 | 
					  //       Object.assign(config, { entry: plugin, options: {} });
 | 
				
			||||||
      }
 | 
					  //     }
 | 
				
			||||||
      this.plugins.set(key, config);
 | 
					  //     this.plugins.set(key, config);
 | 
				
			||||||
    }
 | 
					  //   }
 | 
				
			||||||
  }
 | 
					  // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async loadPlugins() {
 | 
					  // async loadPlugins() {
 | 
				
			||||||
    await this.emitAsync('plugins.beforeLoad');
 | 
					  //   await this.emitAsync('plugins.beforeLoad');
 | 
				
			||||||
    const allPlugins = this.plugins.values();
 | 
					  //   const allPlugins = this.plugins.values();
 | 
				
			||||||
    for (const plugin of allPlugins) {
 | 
					  //   for (const plugin of allPlugins) {
 | 
				
			||||||
      plugin.instance = await this.loadPlugin(plugin);
 | 
					  //     plugin.instance = await this.loadPlugin(plugin);
 | 
				
			||||||
    }
 | 
					  //   }
 | 
				
			||||||
    await this.emitAsync('plugins.afterLoad');
 | 
					  //   await this.emitAsync('plugins.afterLoad');
 | 
				
			||||||
  }
 | 
					  // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async start(argv = process.argv) {
 | 
					  async start(argv = process.argv) {
 | 
				
			||||||
    return this.cli.parseAsync(argv);
 | 
					    return this.cli.parseAsync(argv);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async destroy() {
 | 
					  async destroy() {
 | 
				
			||||||
    await this.database.close()
 | 
					    await this.db.close();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected async loadPlugin({
 | 
					  // protected async loadPlugin({
 | 
				
			||||||
    entry,
 | 
					  //   entry,
 | 
				
			||||||
    options = {},
 | 
					  //   options = {},
 | 
				
			||||||
  }: {
 | 
					  // }: {
 | 
				
			||||||
    entry: string | Function;
 | 
					  //   entry: string | Function;
 | 
				
			||||||
    options: any;
 | 
					  //   options: any;
 | 
				
			||||||
  }) {
 | 
					  // }) {
 | 
				
			||||||
    let main: any;
 | 
					  //   let main: any;
 | 
				
			||||||
    if (typeof entry === 'function') {
 | 
					  //   if (typeof entry === 'function') {
 | 
				
			||||||
      main = entry;
 | 
					  //     main = entry;
 | 
				
			||||||
    } else if (typeof entry === 'string') {
 | 
					  //   } else if (typeof entry === 'string') {
 | 
				
			||||||
      const pathname = `${entry}/${__filename.endsWith('.ts') ? 'src' : 'lib'
 | 
					  //     const pathname = `${entry}/${
 | 
				
			||||||
        }/server`;
 | 
					  //       __filename.endsWith('.ts') ? 'src' : 'lib'
 | 
				
			||||||
      main = require(pathname).default;
 | 
					  //     }/server`;
 | 
				
			||||||
    }
 | 
					  //     main = require(pathname).default;
 | 
				
			||||||
    return main && (await main.call(this, options));
 | 
					  //   }
 | 
				
			||||||
  }
 | 
					  //   return main && (await main.call(this, options));
 | 
				
			||||||
 | 
					  // }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Application;
 | 
					export default Application;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { actions } from '@nocobase/actions';
 | 
					import { Context, Next } from '@nocobase/actions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function actionParams(options: any = {}) {
 | 
					export function actionParams(options: any = {}) {
 | 
				
			||||||
  return async (ctx: actions.Context, next: actions.Next) => {
 | 
					  return async (ctx: Context, next: Next) => {
 | 
				
			||||||
    const { actionName, resourceField, resourceName, fields = {} } = ctx.action.params;
 | 
					    const { actionName, resourceField, resourceName, fields = {} } = ctx.action.params;
 | 
				
			||||||
    const table = ctx.db.getTable(resourceField ? resourceField.options.target : resourceName);
 | 
					    const table = ctx.db.getTable(resourceField ? resourceField.options.target : resourceName);
 | 
				
			||||||
    // ctx.state.developerMode = {[Op.not]: null};
 | 
					    // ctx.state.developerMode = {[Op.not]: null};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
import send from 'koa-send';
 | 
					import send from 'koa-send';
 | 
				
			||||||
import serve from 'koa-static';
 | 
					import serve from 'koa-static';
 | 
				
			||||||
import actions from '@nocobase/actions';
 | 
					import { Context, Next } from '@nocobase/actions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AppDistServeOptions {
 | 
					export interface AppDistServeOptions {
 | 
				
			||||||
  root?: string;
 | 
					  root?: string;
 | 
				
			||||||
@ -9,7 +9,7 @@ export interface AppDistServeOptions {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function appDistServe(options: AppDistServeOptions = {}) {
 | 
					export function appDistServe(options: AppDistServeOptions = {}) {
 | 
				
			||||||
  return async (ctx: actions.Context, next: actions.Next) => {
 | 
					  return async (ctx: Context, next: Next) => {
 | 
				
			||||||
    if (!options.root || !options.useStaticServer) {
 | 
					    if (!options.root || !options.useStaticServer) {
 | 
				
			||||||
      return next();
 | 
					      return next();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Action } from '@nocobase/resourcer';
 | 
					import { Context, Next } from '@nocobase/actions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function dataWrapping(ctx, next) {
 | 
					export async function dataWrapping(ctx: Context, next: Next) {
 | 
				
			||||||
  await next();
 | 
					  await next();
 | 
				
			||||||
  if (ctx.withoutDataWrapping) {
 | 
					  if (ctx.withoutDataWrapping) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { actions } from '@nocobase/actions';
 | 
					import { Context, Next } from '@nocobase/actions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DemoBlackListedActionsOptions {
 | 
					export interface DemoBlackListedActionsOptions {
 | 
				
			||||||
  emails?: string[];
 | 
					  emails?: string[];
 | 
				
			||||||
@ -9,7 +9,7 @@ const defaultBlacklist = ['create', 'update', 'destroy', 'sort', 'upload'];
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function demoBlacklistedActions(options: DemoBlackListedActionsOptions = {}) {
 | 
					export function demoBlacklistedActions(options: DemoBlackListedActionsOptions = {}) {
 | 
				
			||||||
  const { emails, blacklist = defaultBlacklist } = options;
 | 
					  const { emails, blacklist = defaultBlacklist } = options;
 | 
				
			||||||
  return async (ctx: actions.Context, next: actions.Next) => {
 | 
					  return async (ctx: Context, next: Next) => {
 | 
				
			||||||
    const currentUser = ctx.state.currentUser;
 | 
					    const currentUser = ctx.state.currentUser;
 | 
				
			||||||
    if (currentUser && emails.includes(currentUser.email)) {
 | 
					    if (currentUser && emails.includes(currentUser.email)) {
 | 
				
			||||||
      return next();
 | 
					      return next();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										70
									
								
								packages/server/src/plugin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								packages/server/src/plugin.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					import { uid } from '@nocobase/database';
 | 
				
			||||||
 | 
					import { Application } from './application';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface PluginOptions {
 | 
				
			||||||
 | 
					  app?: Application;
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  activate?: boolean;
 | 
				
			||||||
 | 
					  displayName?: string;
 | 
				
			||||||
 | 
					  description?: string;
 | 
				
			||||||
 | 
					  version?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IPlugin {
 | 
				
			||||||
 | 
					  install?: (this: Plugin) => void;
 | 
				
			||||||
 | 
					  load?: (this: Plugin) => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PluginFn = (this: Plugin) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PluginType = string | PluginFn | typeof Plugin | IPlugin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Plugin implements IPlugin {
 | 
				
			||||||
 | 
					  options: PluginOptions = {};
 | 
				
			||||||
 | 
					  app: Application;
 | 
				
			||||||
 | 
					  callbacks: IPlugin = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(plugin?: PluginType, options?: PluginOptions) {
 | 
				
			||||||
 | 
					    this.app = options?.app;
 | 
				
			||||||
 | 
					    this.options = options || {};
 | 
				
			||||||
 | 
					    if (typeof plugin === 'function') {
 | 
				
			||||||
 | 
					      if (!this.options?.name && plugin.name) {
 | 
				
			||||||
 | 
					        this.options.name = plugin.name;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.callbacks.load = plugin as any;
 | 
				
			||||||
 | 
					    } else if (
 | 
				
			||||||
 | 
					      typeof plugin === 'object' &&
 | 
				
			||||||
 | 
					      plugin.constructor === {}.constructor
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      this.callbacks = plugin;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const cName = this.constructor.name;
 | 
				
			||||||
 | 
					    if (this.options && !this.options?.name && cName && cName !== 'Plugin') {
 | 
				
			||||||
 | 
					      this.options.name = cName;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getName() {
 | 
				
			||||||
 | 
					    return this.options.name || uid();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async activate() {
 | 
				
			||||||
 | 
					    this.options.activate = true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async install() {
 | 
				
			||||||
 | 
					    await this.call('install');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async call(name: string) {
 | 
				
			||||||
 | 
					    if (!this.callbacks[name]) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const callback = this.callbacks[name].bind(this);
 | 
				
			||||||
 | 
					    await callback();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async load() {
 | 
				
			||||||
 | 
					    await this.call('load');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										92
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								yarn.lock
									
									
									
									
									
								
							@ -3145,6 +3145,17 @@
 | 
				
			|||||||
    call-me-maybe "^1.0.1"
 | 
					    call-me-maybe "^1.0.1"
 | 
				
			||||||
    glob-to-regexp "^0.3.0"
 | 
					    glob-to-regexp "^0.3.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@nocobase/plugin-pages@^0.4.0-alpha.7":
 | 
				
			||||||
 | 
					  version "0.4.0-alpha.7"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/@nocobase/plugin-pages/-/plugin-pages-0.4.0-alpha.7.tgz#f3998d565eaca66c60b4e8f090ff2b9743cba615"
 | 
				
			||||||
 | 
					  integrity sha512-eOyvxmGuKLaEk9E5M31gA8XYLCEsg2GUYpbtwwqYfurzICxwezEKYJZ/2o4yVy4sSCCdqMMzvwpPJDR/+mmrJw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    "@nocobase/actions" "^0.4.0-alpha.7"
 | 
				
			||||||
 | 
					    "@nocobase/database" "^0.4.0-alpha.7"
 | 
				
			||||||
 | 
					    "@nocobase/resourcer" "^0.4.0-alpha.7"
 | 
				
			||||||
 | 
					    "@nocobase/server" "^0.4.0-alpha.7"
 | 
				
			||||||
 | 
					    crypto-random-string "^3.3.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@nodelib/fs.scandir@2.1.5":
 | 
					"@nodelib/fs.scandir@2.1.5":
 | 
				
			||||||
  version "2.1.5"
 | 
					  version "2.1.5"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
 | 
					  resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
 | 
				
			||||||
@ -3776,6 +3787,13 @@
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@types/koa" "*"
 | 
					    "@types/koa" "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/koa-mount@^4.0.1":
 | 
				
			||||||
 | 
					  version "4.0.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/@types/koa-mount/-/koa-mount-4.0.1.tgz#2994be86eaa3d9dc97365e6ebfa227cee3c5f157"
 | 
				
			||||||
 | 
					  integrity sha512-HNeg80CVS9Dfq8dGYqCZZCAUm7g6jPCNJ1ydqVLEJxLrjmeburpvq+lOZkE4rxBZ6O38dr3tj9IA3IfbdoI05w==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    "@types/koa" "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@types/koa@*", "@types/koa@^2.13.1":
 | 
					"@types/koa@*", "@types/koa@^2.13.1":
 | 
				
			||||||
  version "2.13.4"
 | 
					  version "2.13.4"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
 | 
					  resolved "https://registry.npmjs.org/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
 | 
				
			||||||
@ -4031,6 +4049,16 @@
 | 
				
			|||||||
  resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
 | 
					  resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
 | 
				
			||||||
  integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
 | 
					  integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/strip-bom@^3.0.0":
 | 
				
			||||||
 | 
					  version "3.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
 | 
				
			||||||
 | 
					  integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/strip-json-comments@0.0.30":
 | 
				
			||||||
 | 
					  version "0.0.30"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
 | 
				
			||||||
 | 
					  integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@types/superagent@*":
 | 
					"@types/superagent@*":
 | 
				
			||||||
  version "4.1.12"
 | 
					  version "4.1.12"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.12.tgz#fad68c6712936892ad24cf94f2f7a07cc749fd0f"
 | 
					  resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.12.tgz#fad68c6712936892ad24cf94f2f7a07cc749fd0f"
 | 
				
			||||||
@ -6206,7 +6234,7 @@ chokidar@^2.0.4:
 | 
				
			|||||||
  optionalDependencies:
 | 
					  optionalDependencies:
 | 
				
			||||||
    fsevents "^1.2.7"
 | 
					    fsevents "^1.2.7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
chokidar@^3.0.2, chokidar@^3.2.2:
 | 
					chokidar@^3.0.2, chokidar@^3.2.2, chokidar@^3.5.1:
 | 
				
			||||||
  version "3.5.2"
 | 
					  version "3.5.2"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
 | 
					  resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
 | 
				
			||||||
  integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
 | 
					  integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
 | 
				
			||||||
@ -7910,6 +7938,13 @@ dva@^2.6.0-beta.20:
 | 
				
			|||||||
    react-router-dom "^5.1.2"
 | 
					    react-router-dom "^5.1.2"
 | 
				
			||||||
    redux "^4.0.1"
 | 
					    redux "^4.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dynamic-dedupe@^0.3.0:
 | 
				
			||||||
 | 
					  version "0.3.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1"
 | 
				
			||||||
 | 
					  integrity sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    xtend "^4.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ecc-jsbn@~0.1.1:
 | 
					ecc-jsbn@~0.1.1:
 | 
				
			||||||
  version "0.1.2"
 | 
					  version "0.1.2"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
 | 
					  resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
 | 
				
			||||||
@ -11998,11 +12033,6 @@ jstoxml@^0.2.3:
 | 
				
			|||||||
    array-includes "^3.1.2"
 | 
					    array-includes "^3.1.2"
 | 
				
			||||||
    object.assign "^4.1.2"
 | 
					    object.assign "^4.1.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
just-has@^1.0.0:
 | 
					 | 
				
			||||||
  version "1.0.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.npmjs.org/just-has/-/just-has-1.0.0.tgz#7b0b5b4a1513e6684063521de74622c7629111cd"
 | 
					 | 
				
			||||||
  integrity sha512-VxEMStctzOh3nQWZTq0vZrQe/B9oi75rhL4lcgYm5qrTX2enp38KMiKjkefdRR2ca01jlRHi62mNjme8G/TgvQ==
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
katex@^0.12.0:
 | 
					katex@^0.12.0:
 | 
				
			||||||
  version "0.12.0"
 | 
					  version "0.12.0"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9"
 | 
					  resolved "https://registry.npmjs.org/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9"
 | 
				
			||||||
@ -13208,7 +13238,7 @@ mkdirp-promise@^5.0.1:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    mkdirp "*"
 | 
					    mkdirp "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mkdirp@*, mkdirp@1.x, mkdirp@^1.0.3:
 | 
					mkdirp@*, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4:
 | 
				
			||||||
  version "1.0.4"
 | 
					  version "1.0.4"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
 | 
					  resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
 | 
				
			||||||
  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 | 
					  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 | 
				
			||||||
@ -17042,7 +17072,7 @@ resolve@1.17.0:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    path-parse "^1.0.6"
 | 
					    path-parse "^1.0.6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1:
 | 
					resolve@^1.0.0, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1:
 | 
				
			||||||
  version "1.20.0"
 | 
					  version "1.20.0"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
 | 
					  resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
 | 
				
			||||||
  integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
 | 
					  integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
 | 
				
			||||||
@ -17900,6 +17930,14 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
 | 
				
			|||||||
    source-map-url "^0.4.0"
 | 
					    source-map-url "^0.4.0"
 | 
				
			||||||
    urix "^0.1.0"
 | 
					    urix "^0.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					source-map-support@^0.5.12:
 | 
				
			||||||
 | 
					  version "0.5.20"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
 | 
				
			||||||
 | 
					  integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    buffer-from "^1.0.0"
 | 
				
			||||||
 | 
					    source-map "^0.6.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.19:
 | 
					source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.19:
 | 
				
			||||||
  version "0.5.19"
 | 
					  version "0.5.19"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
 | 
					  resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
 | 
				
			||||||
@ -18376,16 +18414,16 @@ strip-indent@^3.0.0:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    min-indent "^1.0.0"
 | 
					    min-indent "^1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
 | 
				
			||||||
 | 
					  version "2.0.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 | 
				
			||||||
 | 
					  integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
 | 
					strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
 | 
				
			||||||
  version "3.1.1"
 | 
					  version "3.1.1"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
 | 
					  resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
 | 
				
			||||||
  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 | 
					  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
strip-json-comments@~2.0.1:
 | 
					 | 
				
			||||||
  version "2.0.1"
 | 
					 | 
				
			||||||
  resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 | 
					 | 
				
			||||||
  integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
strong-log-transformer@^2.0.0:
 | 
					strong-log-transformer@^2.0.0:
 | 
				
			||||||
  version "2.1.0"
 | 
					  version "2.1.0"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10"
 | 
					  resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10"
 | 
				
			||||||
@ -18961,6 +18999,22 @@ ts-loader@^8.0.7:
 | 
				
			|||||||
    micromatch "^4.0.0"
 | 
					    micromatch "^4.0.0"
 | 
				
			||||||
    semver "^7.3.4"
 | 
					    semver "^7.3.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ts-node-dev@^1.1.8:
 | 
				
			||||||
 | 
					  version "1.1.8"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz#95520d8ab9d45fffa854d6668e2f8f9286241066"
 | 
				
			||||||
 | 
					  integrity sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    chokidar "^3.5.1"
 | 
				
			||||||
 | 
					    dynamic-dedupe "^0.3.0"
 | 
				
			||||||
 | 
					    minimist "^1.2.5"
 | 
				
			||||||
 | 
					    mkdirp "^1.0.4"
 | 
				
			||||||
 | 
					    resolve "^1.0.0"
 | 
				
			||||||
 | 
					    rimraf "^2.6.1"
 | 
				
			||||||
 | 
					    source-map-support "^0.5.12"
 | 
				
			||||||
 | 
					    tree-kill "^1.2.2"
 | 
				
			||||||
 | 
					    ts-node "^9.0.0"
 | 
				
			||||||
 | 
					    tsconfig "^7.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ts-node@^10.2.1:
 | 
					ts-node@^10.2.1:
 | 
				
			||||||
  version "10.2.1"
 | 
					  version "10.2.1"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5"
 | 
					  resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5"
 | 
				
			||||||
@ -18979,7 +19033,7 @@ ts-node@^10.2.1:
 | 
				
			|||||||
    make-error "^1.1.1"
 | 
					    make-error "^1.1.1"
 | 
				
			||||||
    yn "3.1.1"
 | 
					    yn "3.1.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ts-node@^9.1.1:
 | 
					ts-node@^9.0.0, ts-node@^9.1.1:
 | 
				
			||||||
  version "9.1.1"
 | 
					  version "9.1.1"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
 | 
					  resolved "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
 | 
				
			||||||
  integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==
 | 
					  integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==
 | 
				
			||||||
@ -19000,6 +19054,16 @@ tsconfig-paths@^3.9.0:
 | 
				
			|||||||
    minimist "^1.2.0"
 | 
					    minimist "^1.2.0"
 | 
				
			||||||
    strip-bom "^3.0.0"
 | 
					    strip-bom "^3.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tsconfig@^7.0.0:
 | 
				
			||||||
 | 
					  version "7.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7"
 | 
				
			||||||
 | 
					  integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    "@types/strip-bom" "^3.0.0"
 | 
				
			||||||
 | 
					    "@types/strip-json-comments" "0.0.30"
 | 
				
			||||||
 | 
					    strip-bom "^3.0.0"
 | 
				
			||||||
 | 
					    strip-json-comments "^2.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tslib@2.0.1:
 | 
					tslib@2.0.1:
 | 
				
			||||||
  version "2.0.1"
 | 
					  version "2.0.1"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
 | 
					  resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user