feat: create nocobase app with simple & quickstart option (#87)

* feat: create nocobase app with simple & quickstart option

* chore: delete template file

* create-nocobase-app: add env API_PORT fallback

* chore: log

* env default fallback

* move config dir

* change has yarn

* chore: prettier

* fix: npm running issue

* database testing support sqlite

* once...

* chore: typo

* fix: sqlite test

* update readme

* feat: copy .env.example to .env at create-nocobase-app

* create-nocobase-app: change sqlite3 to github master

* create-nocobase-app: .env template

* create-nocobase-app: update .env

* chore: typo

* update README

* chore: Application constructor

* feat: sqlite demo data support

* fix test

* fix: application error

* chore: plugin-client run sql

* fix: application createCli

* fix: can choose whether to register actions

* chore: model compile error

* fix: support sqlite

* fix: demo data set index sequence on postgresql

* chore: code reduce

* fix: operators are compatible with sqlite

* add impor demo option to init command

* update env

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
ChengLei Shao 2021-10-18 12:49:37 +08:00 committed by GitHub
parent 12b3590845
commit 05ecb25d1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1433 additions and 659 deletions

2
.gitignore vendored
View File

@ -20,3 +20,5 @@ uploads/
/.idea
.vercel
packages/database/src/__tests__/.db
db.sqlite

View File

@ -60,19 +60,34 @@ Node:
Database:
- PostgreSQL 10.x+
- Sqlite 3+
Installation
----------
### Create a project with `create-nocobase-app`
#### Quickstart
~~~shell
mkdir my-nocobase-app && cd my-nocobase-app
yarn create nocobase-app
cp .env.example .env
yarn create nocobase-app my-nocobase-app --quickstart
~~~
#### Start with configuration
~~~shell
# 1. create project
yarn create nocobase-app my-nocobase-app
cd my-nocobase-app
# 2. edit configuration in .env file
vim .env
# 3. start a database (optional)
docker-compose up -d postgres
yarn install
yarn nocobase init
# 4. create initialization data
yarn nocobase init --import-demo
# 5. start project
yarn start
~~~

View File

@ -62,23 +62,38 @@ Node:
Database:
- PostgreSQL 10.x+
- Sqlite 3+
安装 & 运行
----------
### 通过 `create-nocobase-app` 创建项目
#### 快速启动方式
~~~shell
mkdir my-nocobase-app && cd my-nocobase-app
yarn create nocobase-app
cp .env.example .env
yarn create nocobase-app my-nocobase-app --quickstart
~~~
#### 自定义启动方式
~~~shell
# 1. 创建项目
yarn create nocobase-app my-nocobase-app
cd my-nocobase-app
# 2. 修改.env中对应的数据库配置
vim .env
# 3. 启动预置数据库(可选)
docker-compose up -d postgres
yarn install
yarn nocobase init
# 4. 初始化数据
yarn nocobase init --import-demo
# 5. 启动项目
yarn start
~~~
浏览器内打开 http://localhost:8000
使用浏览器打开 http://localhost:8000
### 使用 docker compose

View File

@ -68,6 +68,7 @@
"react-router-dom": "^5.2.0",
"react-test-renderer": "^17.0.2",
"rimraf": "^3.0.2",
"sqlite3": "^5.0.2",
"supertest": "^6.1.3",
"ts-jest": "^26.5.6",
"ts-node": "^9.1.1",

View File

@ -4,7 +4,18 @@ import path from 'path';
const start = Date.now();
const api = new Server({
database: {
database: process.env.DB_DIALECT === 'sqlite' ? {
dialect: process.env.DB_DIALECT as any,
storage: path.resolve(process.cwd(), './db.sqlite'),
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
define: {},
sync: {
force: false,
alter: {
drop: false,
},
},
} : {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
@ -59,7 +70,7 @@ for (const plugin of plugins) {
api.plugin(
require(`@nocobase/plugin-client/${libDir}/server`).default, {
dist: path.resolve(process.cwd(), './dist'),
importDemo: true,
// importDemo: true,
});
if (process.argv.length < 3) {
@ -67,7 +78,6 @@ if (process.argv.length < 3) {
process.argv.push('start', '--port', process.env.API_PORT);
}
console.log(process.argv);
api.parse(process.argv).then(() => {
console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);

View File

@ -15,7 +15,10 @@
},
"license": "MIT",
"dependencies": {
"@umijs/utils": "3.5.17"
"@umijs/utils": "3.5.17",
"commander": "^8.2.0",
"execa": "^5.1.1",
"ora": "^5.4.1"
},
"bin": {
"create-nocobase-app": "bin/create-nocobase-app.js"

View File

@ -2,11 +2,18 @@ import { Generator } from '@umijs/utils';
import { join } from 'path';
export default class AppGenerator extends Generator {
private tplContext = {};
setTplContext(context) {
this.tplContext = context;
}
async writing() {
this.copyDirectory({
context: {
version: require('../../package').version,
conventionRoutes: this.args.conventionRoutes,
...this.tplContext
},
path: join(__dirname, '../../templates/AppGenerator'),
target: this.cwd,

View File

@ -1,30 +1,107 @@
import { chalk, yParser } from '@umijs/utils';
import { existsSync } from 'fs';
import { join } from 'path';
import { chalk } from '@umijs/utils';
import commander from 'commander';
import path from 'path';
import ora from 'ora';
import { hasYarn, runInit, runInstall, runStart } from './utils';
import execa from 'execa';
const args = yParser(process.argv.slice(2), {
alias: {
version: ['v'],
help: ['h'],
},
boolean: ['version'],
});
const packageJson = require('../package.json');
if (args.version && !args._[0]) {
args._[0] = 'version';
const local = existsSync(join(__dirname, '../.local'))
? chalk.cyan('@local')
: '';
const { name, version } = require('../package.json');
console.log(`${name}@${version}${local}`);
} else {
require('./')
.default({
cwd: process.cwd(),
args,
})
.catch((err: Error) => {
console.error(`Create failed, ${err.message}`);
console.error(err);
const program = new commander.Command(packageJson.name)
.version(packageJson.version)
.option('--simple', 'create nocobase app without install dependencies')
.option('--quickstart', 'create quickstart nocobase app')
.arguments('<project-directory>')
.usage(`${chalk.green('<project-directory>')}`)
.action(async (directory, options) => {
console.log(
`Creating a new Nocobase application at ${chalk.green(directory)}.`,
);
console.log('Creating files.');
const fullPath = path.join(process.cwd(), directory);
await require('./index').default({
cwd: fullPath,
args: {},
tplContext: options.quickstart
? { quickstart: true }
: { quickstart: false },
});
}
const cmd = chalk.cyan(hasYarn() ? 'yarn' : 'npm run');
if (options.simple) {
console.log();
console.log('Done. You can start by doing:');
console.log();
console.log(` ${chalk.cyan('cd')} ${directory}`);
console.log(` ${cmd} install`);
console.log(` ${cmd} nocobase init --import-demo`);
console.log(` ${cmd} start`);
console.log();
return;
}
const installPrefix = chalk.yellow('Installing dependencies:');
const loader = ora(installPrefix).start();
const logInstall = (chunk = '') => {
loader.text = `${installPrefix} ${chunk
.toString()
.split('\n')
.join(' ')}`;
};
const runner = runInstall(fullPath);
runner?.stdout?.on('data', logInstall);
runner?.stderr?.on('data', logInstall);
await runner;
loader.stop();
console.log(`Dependencies installed ${chalk.green('successfully')}.`);
console.log();
console.log(`Your application was created at ${chalk.green(directory)}.\n`);
if (options.quickstart) {
// Using Sqlite as Database
const prefix = chalk.yellow('Nocobase init');
const initLoader = ora(prefix).start();
try {
const initLog = (chunk = '') => {
initLoader.text = `${prefix} ${chunk
.toString()
.split('\n')
.join(' ')}`;
};
const init = runInit(fullPath);
init.stderr.on('data', initLog);
init.stdout.on('data', initLog);
await init;
initLoader.stop();
} catch (e) {
initLoader.stop();
console.log();
console.log(e.message);
process.exit(1);
}
console.log(`Running your application.`);
await execa('npm', ['run', 'start'], {
stdio: 'inherit',
cwd: fullPath,
});
} else {
console.log();
console.log('You can start by doing:');
console.log();
console.log(` ${chalk.cyan('cd')} ${directory}`);
console.log(` ${cmd} nocobase init --import-demo`);
console.log(` ${cmd} start`);
console.log();
}
})
.showHelpAfterError()
.parse(process.argv);

View File

@ -4,13 +4,17 @@ import AppGenerator from './AppGenerator/AppGenerator';
export default async ({
cwd,
args,
tplContext
}: {
cwd: string;
args: yargs.Arguments;
tplContext: object
}) => {
const generator = new AppGenerator({
cwd,
args,
});
generator.setTplContext(tplContext);
await generator.run();
};

View File

@ -0,0 +1,28 @@
import execa from 'execa';
export function hasYarn() {
return (process.env.npm_config_user_agent || '').indexOf('yarn') === 0;
}
function runYarn(path: string, args: string[]) {
if (hasYarn()) {
return execa('yarn', args, {
cwd: path,
stdin: 'ignore',
});
}
return execa('npm', args, { cwd: path, stdin: 'ignore' });
}
export function runInstall(path) {
return runYarn(path, ['install']);
}
export function runStart(path) {
return runYarn(path, ['run', 'start']);
}
export function runInit(path) {
return runYarn(path, ['run', 'nocobase', 'init', '--import-demo']);
}

View File

@ -1,34 +1,25 @@
########## DOCKER COMPOSE ENV ##########
DB_POSTGRES_PORT=15432
APP_PORT=13000
ADMINER_PORT=18080
ADMINER_PORT=8080
DB_MYSQL_PORT=3306
DB_POSTGRES_PORT=5432
VERDACCIO_PORT=4873
APP_PORT=13001
API_PORT=13002
########## NOCOBASE ENV ##########
# DATABASE
DB_DIALECT=postgres
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=nocobase
DB_USER=nocobase
DB_PASSWORD=nocobase
# set to 'on' to enable log
DB_LOG_SQL=
# for localhost
DB_PORT=15432
DB_HOST=localhost
# for docker
# DB_PORT=5432
# DB_HOST=postgres
# API & APP
NOCOBASE_ENV=
API_PORT=13001
API_URL=/api/
# ADMIN USER (Initialization only)
ADMIN_EMAIL=admin@nocobase.com
@ -41,4 +32,11 @@ STORAGE_TYPE=local
# LOCAL STORAGE
LOCAL_STORAGE_USE_STATIC_SERVER=true
LOCAL_STORAGE_BASE_URL=http://localhost:23000
LOCAL_STORAGE_BASE_URL=http://localhost:13002
# ALI OSS STORAGE
ALI_OSS_STORAGE_BASE_URL=
ALI_OSS_REGION=oss-cn-beijing
ALI_OSS_ACCESS_KEY_ID=
ALI_OSS_ACCESS_KEY_SECRET=
ALI_OSS_BUCKET=

View File

@ -0,0 +1,47 @@
########## DOCKER COMPOSE ENV ##########
ADMINER_PORT=8080
DB_MYSQL_PORT=3306
DB_POSTGRES_PORT=5432
VERDACCIO_PORT=4873
APP_PORT=13001
API_PORT=13002
########## NOCOBASE ENV ##########
# DATABASE
{{#quickstart}}
DB_DIALECT=sqlite
DB_STORAGE=db.sqlite
{{/quickstart}}
{{^quickstart}}
DB_DIALECT=postgres
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=nocobase
DB_USER=nocobase
DB_PASSWORD=nocobase
{{/quickstart}}
# set to 'on' to enable log
DB_LOG_SQL=
# ADMIN USER (Initialization only)
ADMIN_EMAIL=admin@nocobase.com
ADMIN_PASSWORD=admin
# STORAGE (Initialization only)
# local or ali-oss
STORAGE_TYPE=local
# LOCAL STORAGE
LOCAL_STORAGE_USE_STATIC_SERVER=true
LOCAL_STORAGE_BASE_URL=http://localhost:13002
# ALI OSS STORAGE
ALI_OSS_STORAGE_BASE_URL=
ALI_OSS_REGION=oss-cn-beijing
ALI_OSS_ACCESS_KEY_ID=
ALI_OSS_ACCESS_KEY_SECRET=
ALI_OSS_BUCKET=

View File

@ -11,12 +11,12 @@ export default defineConfig({
type: 'none',
},
define: {
'process.env.API_URL': process.env.API_URL,
'process.env.API_PORT': process.env.API_PORT,
'process.env.API_URL': process.env.API_URL || "http://127.0.0.1",
'process.env.API_PORT': process.env.API_PORT || "13001",
},
proxy: {
'/api': {
'target': `http://localhost:${process.env.API_PORT}/`,
'target': `http://localhost:${process.env.API_PORT || "13001"}/`,
'changeOrigin': true,
'pathRewrite': { '^/api': '/api' },
},

View File

@ -5,7 +5,7 @@
"start": "concurrently \"npm run start-server\" \"umi dev\"",
"start-client": "umi dev",
"start-server": "ts-node-dev -r dotenv/config --project tsconfig.apis.json ./src/apis/index.ts",
"nocobase": "ts-node-dev -r dotenv/config --project tsconfig.apis.json ./src/apis/index.ts",
"nocobase": "ts-node -r dotenv/config --project tsconfig.apis.json ./src/apis/index.ts",
"serve": "node -r dotenv/config ./lib/apis/index.js",
"build": "npm run build-server && npm run build-client",
"build-client": "umi build",
@ -27,7 +27,8 @@
]
},
"dependencies": {
"@nocobase/plugin-action-logs": "^{{{ version }}}",
{{#quickstart}}"sqlite3": "https://github.com/mapbox/node-sqlite3/tarball/master",
{{/quickstart}}"@nocobase/plugin-action-logs": "^{{{ version }}}",
"@nocobase/plugin-china-region": "^{{{ version }}}",
"@nocobase/plugin-client": "^{{{ version }}}",
"@nocobase/plugin-collections": "^{{{ version }}}",

View File

@ -0,0 +1,36 @@
import { DatabaseOptions } from '@nocobase/database';
{{#quickstart}}
export default {
dialect: process.env.DB_DIALECT,
dialectModule: require('sqlite3'),
storage: process.env.DB_STORAGE
} as DatabaseOptions;
{{/quickstart}}
{{^quickstart}}
export default {
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',
},
pool: {
max: 5,
min: 0,
acquire: 60000,
idle: 10000,
},
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
define: {},
sync: {
force: false,
alter: {
drop: false,
},
},
} as DatabaseOptions;
{{/quickstart}}

View File

@ -1,35 +1,10 @@
import Server from '@nocobase/server';
import path from 'path';
import Application from '@nocobase/server';
const start = Date.now();
const api = new Server({
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',
},
pool: {
max: 5,
min: 0,
acquire: 60000,
idle: 10000,
},
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
define: {},
sync: {
force: false,
alter: {
drop: false,
},
},
},
const api = new Application({
database: require('./config/db').default,
resourcer: {
prefix: '/api',
},
@ -49,24 +24,19 @@ const plugins = [
];
for (const plugin of plugins) {
api.plugin(
require(`${plugin}/lib/server`).default,
);
api.plugin(require(`${plugin}/lib/server`).default);
}
api.plugin(
require(`@nocobase/plugin-client/lib/server`).default, {
api.plugin(require(`@nocobase/plugin-client/lib/server`).default, {
dist: path.resolve(process.cwd(), './dist'),
importDemo: true,
});
if (process.argv.length < 3) {
// @ts-ignore
process.argv.push('start', '--port', process.env.API_PORT);
process.argv.push('start', '--port', process.env.API_PORT || '13001');
}
console.log(process.argv);
api.parse(process.argv).then(() => {
console.log(`Start-up time: ${(Date.now() - start) / 1000}s`);
});

View File

@ -10,7 +10,7 @@
"bcrypt": "^5.0.0",
"deepmerge": "^4.2.2",
"glob": "^7.1.6",
"sequelize": "^6.3.3"
"sequelize": "6.7.0"
},
"repository": {
"type": "git",

View File

@ -11,13 +11,13 @@ describe('radio', () => {
fields: [
{
type: 'string',
name: 'name'
name: 'name',
},
{
type: 'hasMany',
name: 'posts'
}
]
name: 'posts',
},
],
});
db.table({
@ -38,25 +38,25 @@ describe('radio', () => {
},
{
type: 'radio',
name: 'pinned'
name: 'pinned',
},
{
type: 'radio',
name: 'latest',
defaultValue: true
defaultValue: true,
},
{
type: 'radio',
name: 'pinned_in_status',
scope: ['status']
scope: ['status'],
},
{
type: 'radio',
name: 'pinned_in_user',
scope: ['user'],
defaultValue: true
}
]
defaultValue: true,
},
],
});
await db.sync({ force: true });
@ -79,7 +79,7 @@ describe('radio', () => {
expect(posts.map(({ pinned, latest }) => ({ pinned, latest }))).toEqual([
{ pinned: true, latest: false },
{ pinned: false, latest: true }
{ pinned: false, latest: true },
]);
});
@ -107,7 +107,7 @@ describe('radio', () => {
expect(posts.map(({ pinned, latest }) => ({ pinned, latest }))).toEqual([
{ pinned: false, latest: false },
{ pinned: true, latest: false },
{ pinned: false, latest: true }
{ pinned: false, latest: true },
]);
});
@ -117,25 +117,50 @@ describe('radio', () => {
const Post = db.getModel('posts');
const bulkCreated = await Post.bulkCreate([
{ title: 'title1', status: 'published', user_id: 1 },
{ title: 'title2', status: 'published', user_id: 2, pinned_in_status: true },
{ title: 'title3', status: 'draft', user_id: 1, pinned_in_status: true },
{
title: 'title2',
status: 'published',
user_id: 2,
pinned_in_status: true,
},
{
title: 'title3',
status: 'draft',
user_id: 1,
pinned_in_status: true,
},
]);
expect(bulkCreated.map(({ pinned_in_status, pinned_in_user }) => ({ pinned_in_status, pinned_in_user }))).toEqual([
expect(
bulkCreated.map(({ pinned_in_status, pinned_in_user }) => ({
pinned_in_status,
pinned_in_user,
})),
).toEqual([
{ pinned_in_status: false, pinned_in_user: false },
{ pinned_in_status: true, pinned_in_user: true },
{ pinned_in_status: true, pinned_in_user: true }
{ pinned_in_status: true, pinned_in_user: true },
]);
const user1Post = await users[1].createPost({ title: 'title4', status: 'draft', pinned_in_status: true });
const user1Post = await users[1].createPost({
title: 'title4',
status: 'draft',
pinned_in_status: true,
});
expect(user1Post.pinned_in_status).toBe(true);
expect(user1Post.pinned_in_user).toBe(true);
const posts = await Post.findAll({ order: [['id', 'ASC']] });
expect(posts.map(({ title, pinned_in_status, pinned_in_user }) => ({ title, pinned_in_status, pinned_in_user }))).toMatchObject([
expect(
posts.map(({ title, pinned_in_status, pinned_in_user }) => ({
title,
pinned_in_status,
pinned_in_user,
})),
).toMatchObject([
{ title: 'title1', pinned_in_status: false, pinned_in_user: false },
{ title: 'title2', pinned_in_status: true, pinned_in_user: false },
{ title: 'title3', pinned_in_status: false, pinned_in_user: true },
{ title: 'title4', pinned_in_status: true, pinned_in_user: true }
{ title: 'title4', pinned_in_status: true, pinned_in_user: true },
]);
});
});
@ -145,13 +170,13 @@ describe('radio', () => {
const Post = db.getModel('posts');
await Post.bulkCreate([
{ title: 'title1', pinned: true },
{ title: 'title2' }
{ title: 'title2' },
]);
const created = await Post.create({ pinned: false });
expect(created.pinned).toBe(false);
const posts = await Post.findAll({ order: [['title', 'ASC']] });
const posts = await Post.findAll({ order: [['id', 'ASC']] });
expect(posts.map(({ pinned }) => pinned)).toEqual([true, false, false]);
});
@ -160,13 +185,13 @@ describe('radio', () => {
const Post = db.getModel('posts');
await Post.bulkCreate([
{ title: 'title1', pinned: true },
{ title: 'title2' }
{ title: 'title2' },
]);
const created = await Post.create({ pinned: true });
expect(created.pinned).toBe(true);
const posts = await Post.findAll({ order: [['title', 'ASC']] });
const posts = await Post.findAll({ order: [['id', 'ASC']] });
expect(posts.map(({ pinned }) => pinned)).toEqual([false, false, true]);
});
@ -176,7 +201,7 @@ describe('radio', () => {
await Post.bulkCreate([
{ title: 'bug1' },
{ title: 'bug2' },
{ title: 'bug3' }
{ title: 'bug3' },
]);
const post = await Post.findByPk(2);
@ -186,7 +211,7 @@ describe('radio', () => {
});
await post.update({
status: 'draft'
status: 'draft',
});
const posts = await Post.findAll({ order: [['id', 'ASC']] });

View File

@ -33,20 +33,28 @@ describe('field types', () => {
const table = db.table({
name: 'test',
});
const field = buildField({
type: actual,
name: 'test',
}, {
sourceTable: table,
database: db,
});
const field = buildField(
{
type: actual,
name: 'test',
},
{
sourceTable: table,
database: db,
},
);
expect(field).toBeInstanceOf(expected);
if (field instanceof Column) {
const { type } = field.getAttributeOptions() as any;
if (actual instanceof ABSTRACT) {
expect(type).toBeInstanceOf(field.getDataType());
// postgres 的 text 不限制长度,无需参数
if (db.sequelize.getDialect() !== 'postgres' || getDataTypeKey(type) !== 'TEXT') {
if (
db.sequelize.getDialect() !== 'postgres' ||
getDataTypeKey(type) !== 'TEXT'
) {
// 非严谨比较undefined == null
expect(type).toEqual(actual);
}
@ -58,7 +66,7 @@ describe('field types', () => {
}
}
db.close();
}
};
it('shound be boolean', () => {
assertTypeInstanceOf(Boolean, 'boolean');
@ -69,9 +77,12 @@ describe('field types', () => {
assertTypeInstanceOf(Integer, 'int');
assertTypeInstanceOf(Integer, 'integer');
assertTypeInstanceOf(Integer, DataTypes.INTEGER);
assertTypeInstanceOf(Integer, DataTypes.INTEGER({
length: 5,
}));
assertTypeInstanceOf(
Integer,
DataTypes.INTEGER({
length: 5,
}),
);
});
it('shound be tiny integer', () => {
@ -80,9 +91,12 @@ describe('field types', () => {
assertTypeInstanceOf(Integer, 'tinyinteger');
assertTypeInstanceOf(Integer, 'tinyInteger');
assertTypeInstanceOf(Integer, DataTypes.TINYINT);
assertTypeInstanceOf(Integer, DataTypes.TINYINT({
length: 5,
}));
assertTypeInstanceOf(
Integer,
DataTypes.TINYINT({
length: 5,
}),
);
});
it('shound be small integer', () => {
@ -91,9 +105,12 @@ describe('field types', () => {
assertTypeInstanceOf(Integer, 'smallinteger');
assertTypeInstanceOf(Integer, 'smallInteger');
assertTypeInstanceOf(Integer, DataTypes.SMALLINT);
assertTypeInstanceOf(Integer, DataTypes.SMALLINT({
length: 5,
}));
assertTypeInstanceOf(
Integer,
DataTypes.SMALLINT({
length: 5,
}),
);
});
it('shound be medium integer', () => {
@ -102,9 +119,12 @@ describe('field types', () => {
assertTypeInstanceOf(Integer, 'MediumInteger');
assertTypeInstanceOf(Integer, 'MediumInteger');
assertTypeInstanceOf(Integer, DataTypes.MEDIUMINT);
assertTypeInstanceOf(Integer, DataTypes.MEDIUMINT({
length: 5,
}));
assertTypeInstanceOf(
Integer,
DataTypes.MEDIUMINT({
length: 5,
}),
);
});
it('shound be big integer', () => {
@ -113,16 +133,22 @@ describe('field types', () => {
assertTypeInstanceOf(Integer, 'biginteger');
assertTypeInstanceOf(Integer, 'bigInteger');
assertTypeInstanceOf(Integer, DataTypes.BIGINT);
assertTypeInstanceOf(Integer, DataTypes.BIGINT({
length: 5,
}));
assertTypeInstanceOf(
Integer,
DataTypes.BIGINT({
length: 5,
}),
);
});
it('shound be float', () => {
assertTypeInstanceOf(Float, 'float');
assertTypeInstanceOf(Float, DataTypes.FLOAT({
length: 5,
}));
assertTypeInstanceOf(
Float,
DataTypes.FLOAT({
length: 5,
}),
);
});
it('shound be double', () => {
@ -132,9 +158,12 @@ describe('field types', () => {
it('shound be real', () => {
assertTypeInstanceOf(Real, 'real');
assertTypeInstanceOf(Real, DataTypes.REAL({
length: 5,
}));
assertTypeInstanceOf(
Real,
DataTypes.REAL({
length: 5,
}),
);
});
it('shound be decimal', () => {
@ -151,9 +180,12 @@ describe('field types', () => {
it('shound be text', () => {
assertTypeInstanceOf(Text, 'text');
assertTypeInstanceOf(Text, DataTypes.TEXT);
assertTypeInstanceOf(Text, DataTypes.TEXT({
length: 'long',
}));
assertTypeInstanceOf(
Text,
DataTypes.TEXT({
length: 'long',
}),
);
});
it('shound be time', () => {
@ -185,6 +217,9 @@ describe('field types', () => {
});
it('shound be jsonb', () => {
if (process.env.DB_DIALECT === 'sqlite') {
return;
}
assertTypeInstanceOf(Jsonb, 'jsonb');
assertTypeInstanceOf(Jsonb, DataTypes.JSONB);
});
@ -273,7 +308,7 @@ describe('field types', () => {
name: 'password',
},
],
})
});
db.table({
name: 'bars',
tableName: 'formula_bars',
@ -281,9 +316,9 @@ describe('field types', () => {
{
type: 'string',
name: 'col2',
}
},
],
})
});
await db.sync({ force: true });
});
afterEach(async () => {
@ -316,7 +351,7 @@ describe('field types', () => {
await formula.updateAssociations({
bar: {
col2: 'val2',
}
},
});
const f = await Formula.findOne({
where: {
@ -324,7 +359,7 @@ describe('field types', () => {
},
include: {
association: 'bar',
}
},
});
expect(f.reference1).toBe('val1');
expect(f.reference2).toBe('val2');

View File

@ -1,5 +1,7 @@
import Database from '../database';
import { Dialect } from 'sequelize';
import path from 'path';
import { uid } from '../utils';
function getTestKey() {
const { id } = require.main;
@ -9,36 +11,32 @@ function getTestKey() {
.replace(/src\/__tests__/g, '')
.replace('.test.ts', '')
.replace(/[^\w]/g, '_');
return key
return key;
}
const config = {
let config: any = {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: Number.parseInt(process.env.DB_PORT, 10),
dialect: process.env.DB_DIALECT as Dialect,
define: {
hooks: {
beforeCreate(model, options) {
},
},
},
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
};
console.log(config);
if (process.env.DB_DIALECT === 'sqlite') {
config = {
dialect: process.env.DB_DIALECT as Dialect,
storage: path.resolve(__dirname, `./.db/${uid()}.sqlite`),
logging: process.env.DB_LOG_SQL === 'on' ? console.log : false,
};
}
export function getDatabase() {
return new Database({
...config,
sync: {
force: true,
alter: {
drop: true,
}
},
hooks: {
beforeDefine(model, options) {
@ -46,8 +44,10 @@ export function getDatabase() {
options.tableNamePrefix = `${getTestKey()}_`;
// @ts-ignore
options.hookModelName = options.tableName;
options.tableName = `${getTestKey()}_${options.tableName || options.name.plural}`;
options.tableName = `${getTestKey()}_${
options.tableName || options.name.plural
}`;
},
},
});
};
}

View File

@ -2,8 +2,6 @@ import Database from '../..';
import { getDatabase } from '..';
import Model, { ModelCtor } from '../../model';
let db: Database;
beforeEach(async () => {
@ -39,8 +37,8 @@ describe('model', () => {
scope: {
title: 'title1',
},
}
]
},
],
});
db.table({
@ -51,7 +49,7 @@ describe('model', () => {
type: 'string',
name: 'name',
},
]
],
});
db.table({
@ -87,7 +85,7 @@ describe('model', () => {
name: 'current_user_comments',
target: 'comments',
},
]
],
});
db.table({
@ -98,7 +96,7 @@ describe('model', () => {
type: 'string',
name: 'name',
},
]
],
});
db.table({
@ -131,8 +129,8 @@ describe('model', () => {
{
type: 'string',
name: 'content',
}
]
},
],
});
db.table({
@ -147,7 +145,7 @@ describe('model', () => {
{
type: 'hasMany',
name: 'fields',
}
},
],
});
@ -161,7 +159,7 @@ describe('model', () => {
{
type: 'string',
name: 'name',
}
},
],
});
@ -177,7 +175,7 @@ describe('model', () => {
type: 'hasMany',
name: 'columns',
sourceKey: 'name',
}
},
],
});
@ -192,7 +190,7 @@ describe('model', () => {
{
type: 'string',
name: 'name',
}
},
],
});
@ -208,7 +206,7 @@ describe('model', () => {
const user = await User.create();
const post = await Post.create();
await post.updateAssociations({
user: user.id
user: user.id,
});
const authorizedPost = await Post.findByPk(post.id);
@ -219,7 +217,7 @@ describe('model', () => {
const Post = db.getModel('posts');
const post = await Post.create();
await post.updateAssociations({
user: {}
user: {},
});
const authorizedPost = await Post.findByPk(post.id);
@ -231,7 +229,7 @@ describe('model', () => {
const user = await User.create();
const post = await Post.create();
await post.updateAssociations({
user
user,
});
const authorizedPost = await Post.findByPk(post.id);
@ -244,10 +242,10 @@ describe('model', () => {
const user2 = await Post.create();
const post = await Post.create();
await post.updateAssociations({
user: user1.id
user: user1.id,
});
await post.updateAssociations({
user: user2.id
user: user2.id,
});
const authorizedPost = await Post.findByPk(post.id);
@ -261,26 +259,26 @@ describe('model', () => {
const post = await Post.create();
const comments = await Comment.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
comments: comments.map(item => item.id)
comments: comments.map((item) => item.id),
});
const postComments = await Comment.findAll({
where: { post_id: post.id },
attributes: ['id']
attributes: ['id'],
});
expect(postComments.map(item => item.id)).toEqual([1, 2, 3, 4]);
expect(postComments.map((item) => item.id)).toEqual([1, 2, 3, 4]);
});
it('update with new object', async () => {
const [Post, Comment] = db.getModels(['posts', 'comments']);
const post = await Post.create();
await post.updateAssociations({
comments: [{}, {}, {}, {}]
comments: [{}, {}, {}, {}],
});
const postCommentIds = await Comment.findAll({
where: { post_id: post.id },
attributes: ['id']
attributes: ['id'],
});
expect(postCommentIds.map(item => item.id)).toEqual([1, 2, 3, 4]);
expect(postCommentIds.map((item) => item.id)).toEqual([1, 2, 3, 4]);
});
it('update with new model', async () => {
@ -288,13 +286,13 @@ describe('model', () => {
const post = await Post.create();
const comments = await Comment.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
comments
comments,
});
const postCommentIds = await Comment.findAll({
where: { post_id: post.id },
attributes: ['id']
attributes: ['id'],
});
expect(postCommentIds.map(item => item.id)).toEqual([1, 2, 3, 4]);
expect(postCommentIds.map((item) => item.id)).toEqual([1, 2, 3, 4]);
});
it('update with exist rows/primaryKeys', async () => {
@ -302,19 +300,19 @@ describe('model', () => {
const post = await Post.create();
const comments = await Comment.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
comments
comments,
});
await post.updateAssociations({
comments
comments,
});
await post.updateAssociations({
comments: comments.map(item => item.id)
comments: comments.map((item) => item.id),
});
const postCommentIds = await Comment.findAll({
where: { post_id: post.id },
attributes: ['id']
attributes: ['id'],
});
expect(postCommentIds.map(item => item.id)).toEqual([1, 2, 3, 4]);
expect(postCommentIds.map((item) => item.id)).toEqual([1, 2, 3, 4]);
});
it('update with exist objects', async () => {
@ -322,23 +320,25 @@ describe('model', () => {
const post = await Post.create();
const comments = await Comment.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
comments
comments,
});
await post.updateAssociations({
comments: comments.map(item => ({
comments: comments.map((item) => ({
...item.get(),
content: `content${item.id}`
}))
content: `content${item.id}`,
})),
});
const postComments = await Comment.findAll({
where: { post_id: post.id },
attributes: ['id', 'content']
attributes: ['id', 'content'],
});
expect(postComments.map(({ id, content }) => ({ id, content }))).toEqual([
expect(
postComments.map(({ id, content }) => ({ id, content })),
).toEqual([
{ id: 1, content: 'content1' },
{ id: 2, content: 'content2' },
{ id: 3, content: 'content3' },
{ id: 4, content: 'content4' }
{ id: 4, content: 'content4' },
]);
});
@ -348,39 +348,43 @@ describe('model', () => {
const post2 = await Post.create();
const comments = await Comment.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
comments
comments,
});
const postComments = await Comment.findAll({
where: { post_id: post.id }
where: { post_id: post.id },
});
expect(postComments.map(({ id, post_id }) => ({ id, post_id }))).toEqual([
expect(
postComments.map(({ id, post_id }) => ({ id, post_id })),
).toEqual([
{ id: 1, post_id: post.id },
{ id: 2, post_id: post.id },
{ id: 3, post_id: post.id },
{ id: 4, post_id: post.id }
{ id: 4, post_id: post.id },
]);
await post2.updateAssociations({
comments: postComments.map(item => ({
comments: postComments.map((item) => ({
...item.get(),
content: `content${item.id}`
}))
content: `content${item.id}`,
})),
});
const updatedComments = await Comment.findAll();
const post1CommentsCount = await Comment.count({
where: { post_id: post.id }
where: { post_id: post.id },
});
expect(post1CommentsCount).toBe(0);
const post2Comments = await Comment.findAll({
where: { post_id: post2.id },
attributes: ['id', 'content']
attributes: ['id', 'content'],
});
expect(post2Comments.map(({ id, content }) => ({ id, content }))).toEqual([
expect(
post2Comments.map(({ id, content }) => ({ id, content })),
).toEqual([
{ id: 1, content: 'content1' },
{ id: 2, content: 'content2' },
{ id: 3, content: 'content3' },
{ id: 4, content: 'content4' }
{ id: 4, content: 'content4' },
]);
});
@ -389,17 +393,17 @@ describe('model', () => {
const post = await Post.create();
const comments = await Comment.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
comments
comments,
});
await post.updateAssociations({
comments: comments
.filter(({ id }) => Boolean(id % 2))
.concat(...[await Comment.create()])
.concat(...[await Comment.create()]),
});
const postComments = await Comment.findAll({
where: { post_id: post.id },
attributes: ['id']
attributes: ['id'],
});
expect(postComments.map(({ id }) => id)).toEqual([1, 3, 5]);
});
@ -407,37 +411,47 @@ describe('model', () => {
describe('belongsToMany', () => {
it('update with primary key', async () => {
const [Post, Tag, PostTag] = db.getModels(['posts', 'tags', 'posts_tags']);
const [Post, Tag, PostTag] = db.getModels([
'posts',
'tags',
'posts_tags',
]);
const post = await Post.create();
const tags = await Tag.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
tags: tags.map(item => item.id)
tags: tags.map((item) => item.id),
});
const tagged = await PostTag.findAll({
where: { post_id: post.id },
attributes: ['tag_id']
attributes: ['tag_id'],
});
expect(tagged.map(item => item.tag_id)).toEqual([1, 2, 3, 4]);
expect(tagged.map((item) => item.tag_id)).toEqual([1, 2, 3, 4]);
});
it('update with exist rows/primaryKeys', async () => {
const [Post, Tag, PostTag] = db.getModels(['posts', 'tags', 'posts_tags']);
const [Post, Tag, PostTag] = db.getModels([
'posts',
'tags',
'posts_tags',
]);
const post = await Post.create();
const tags = await Tag.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
tags: tags.map(item => item.id)
tags: tags.map((item) => item.id),
});
await post.updateAssociations({
tags: tags.map(item => item.id)
tags: tags.map((item) => item.id),
});
await post.updateAssociations({
tags
tags,
});
const tagged = await PostTag.findAll({
where: { post_id: post.id },
attributes: ['tag_id', 'post_id']
attributes: ['tag_id', 'post_id'],
});
expect(tagged.map(({ post_id, tag_id }) => ({ post_id, tag_id }))).toEqual([
expect(
tagged.map(({ post_id, tag_id }) => ({ post_id, tag_id })),
).toEqual([
{ tag_id: 1, post_id: 1 },
{ tag_id: 2, post_id: 1 },
{ tag_id: 3, post_id: 1 },
@ -446,35 +460,47 @@ describe('model', () => {
});
it('update with exist rows/primaryKeys and new objects', async () => {
const [Post, Tag, PostTag] = db.getModels(['posts', 'tags', 'posts_tags']);
const [Post, Tag, PostTag] = db.getModels([
'posts',
'tags',
'posts_tags',
]);
const post = await Post.create();
const tags = await Tag.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
tags: tags.map(item => item.id)
tags: tags.map((item) => item.id),
});
await post.updateAssociations({
tags: tags.filter(({ id }) => Boolean(id % 2)).concat(await Tag.create({}))
tags: tags
.filter(({ id }) => Boolean(id % 2))
.concat(await Tag.create({})),
});
const tagged = await PostTag.findAll({
where: { post_id: post.id },
attributes: ['tag_id']
attributes: ['tag_id'],
});
expect(tagged.map(({ tag_id }) => tag_id)).toEqual([1, 3, 5]);
});
it('update other with exist rows/primaryKeys', async () => {
const [Post, Tag, PostTag] = db.getModels(['posts', 'tags', 'posts_tags']);
const [Post, Tag, PostTag] = db.getModels([
'posts',
'tags',
'posts_tags',
]);
const post = await Post.create();
const post2 = await Post.create();
const tags = await Tag.bulkCreate([{}, {}, {}, {}]);
await post.updateAssociations({
tags: tags.map(item => item.id)
tags: tags.map((item) => item.id),
});
await post2.updateAssociations({
tags
tags,
});
const tagged = await PostTag.findAll();
expect(tagged.map(({ post_id, tag_id }) => ({ post_id, tag_id }))).toEqual([
expect(
tagged.map(({ post_id, tag_id }) => ({ post_id, tag_id })),
).toEqual([
{ tag_id: 1, post_id: 1 },
{ tag_id: 2, post_id: 1 },
{ tag_id: 3, post_id: 1 },
@ -492,17 +518,20 @@ describe('model', () => {
const post = await Post.create();
const tag = await Tag.create();
await post.updateAssociations({
tags: [{
name: 'xxx',
posts_tags: {
name: 'name134',
}
}, {
id: tag.id,
posts_tags: {
name: 'name234',
}
}],
tags: [
{
name: 'xxx',
posts_tags: {
name: 'name134',
},
},
{
id: tag.id,
posts_tags: {
name: 'name234',
},
},
],
});
const PostTag = db.getModel('posts_tags');
const [t1, t2] = await PostTag.findAll({
@ -518,7 +547,11 @@ describe('model', () => {
describe('scope', () => {
it('scope', async () => {
const [User, Post, Comment] = db.getModels(['users', 'posts', 'comments']);
const [User, Post, Comment] = db.getModels([
'users',
'posts',
'comments',
]);
const user1 = await User.create();
const user2 = await User.create();
const user3 = await User.create();
@ -565,8 +598,8 @@ describe('model', () => {
return {
where: {
name: name,
}
}
},
};
});
const [User, Post] = db.getModels(['users', 'posts']);
@ -589,29 +622,22 @@ describe('model', () => {
posts: post,
});
await post.updateAssociations({
tags: [
{ name: 'tag1' },
{ name: 'tag2' },
{ name: 'tag3' },
],
tags: [{ name: 'tag1' }, { name: 'tag2' }, { name: 'tag3' }],
});
// where & include
const options = Post.parseApiJson({
filter: {
title: 'title112233',
user: { // belongsTo
user: {
// belongsTo
name: 'name112233',
},
tags: { // belongsToMany
tags: {
// belongsToMany
scopeName: 'tag3',
},
},
fields: [
'title',
'tags_count',
'tags.name',
'user.name'
],
fields: ['title', 'tags_count', 'tags.name', 'user.name'],
sort: '-tags_count,tags.name,user.posts_count',
context: {
scopeName: 'tag3',
@ -631,7 +657,7 @@ describe('model', () => {
// console.log(JSON.stringify(rows[0].toJSON(), null, 2));
rows.forEach(row => {
rows.forEach((row) => {
// expect(row.toJSON()).toEqual({ title: 'title112233', 'tags_count': 3, user: { name: 'name112233', posts_count: 1 } });
expect(row.get('title')).toBe('title112233');
expect(row.user.get('name')).toBe('name112233');
@ -694,7 +720,7 @@ describe('model', () => {
include: [
User.withCountAttribute({
association: 'posts',
alias: 'posts2_count'
alias: 'posts2_count',
}),
],
},
@ -736,7 +762,9 @@ describe('model', () => {
include: [
{
association: 'user',
attributes: ['id', 'name',
attributes: [
'id',
'name',
User.withCountAttribute({
sourceAlias: 'user',
association: 'posts',
@ -752,7 +780,7 @@ describe('model', () => {
expect(post.get('tags_count')).toBe(5);
expect(post.get('tags_name1_count')).toBe(1);
expect(post.user.get('posts_count')).toBe(1);
post.tags.forEach(tag => {
post.tags.forEach((tag) => {
expect(tag.get('posts_count')).toBe(1);
});
});
@ -904,9 +932,7 @@ describe('model', () => {
name: 'examples',
});
await table.updateAssociations({
fields: [
{ name: 'name' },
]
fields: [{ name: 'name' }],
});
const field = await Field.findOne({
where: {
@ -924,9 +950,7 @@ describe('model', () => {
name: 'examples',
});
await row.updateAssociations({
columns: [
{ name: 'name' },
]
columns: [{ name: 'name' }],
});
const column = await Column.findOne({
where: {
@ -950,7 +974,7 @@ describe('model', () => {
id: field.id,
name: 'nam1234',
},
]
],
});
const f = await Field.findOne({
@ -973,7 +997,6 @@ describe('model', () => {
});
describe('blongsTo', () => {
it('shoud associated id when association is integer', async () => {
const [User, Post] = db.getModels(['users', 'posts']);
const user = await User.create();
@ -1143,7 +1166,7 @@ describe('model', () => {
const post = await Post.create();
await post.updateAssociations({
tags: {
name: 'tag1'
name: 'tag1',
},
});
const count = await post.countTags();
@ -1156,10 +1179,10 @@ describe('model', () => {
await post.updateAssociations({
tags: [
{
name: 'tag2'
name: 'tag2',
},
{
name: 'tag3'
name: 'tag3',
},
],
});
@ -1239,9 +1262,11 @@ describe('belongsToMany', () => {
});
it('update with new object', async () => {
await post.updateAssociations({
tags: [{
name: 'tag3',
}],
tags: [
{
name: 'tag3',
},
],
});
expect(await post.countTags()).toBe(1);
});

View File

@ -29,6 +29,7 @@ afterEach(async () => {
describe('op', () => {
it('test', async () => {
if ((process.env.DB_DIALECT = 'sqlite')) return;
const Test = db.getModel('tests');
await Test.bulkCreate([
{
@ -42,7 +43,7 @@ describe('op', () => {
},
{
arr: ['dd'],
}
},
]);
const options = Test.parseApiJson({
filter: {

View File

@ -168,7 +168,7 @@ type HookType =
const hookType = this._getHookType(event);
if (hookType) {
const state = this.hookTypes.get(hookType);
console.log('sequelize.addHook', event, hookType)
this.sequelize.addHook(hookType, async (...args: any[]) => {
let modelName: string;
switch (state) {

View File

@ -1,5 +1,10 @@
import {
Model as SequelizeModel, Op, Sequelize, ProjectionAlias, Utils, SaveOptions
Model as SequelizeModel,
Op,
Sequelize,
ProjectionAlias,
Utils,
SaveOptions,
} from 'sequelize';
import Database from './database';
import {
@ -12,48 +17,50 @@ import {
import { toInclude } from './utils';
export interface ApiJsonOptions {
/**
*
*
*
*
* ['col', 'association.col1', 'association_count'],
*
*
*
* {
* only: ['col1'],
* appends: ['association_count'],
* }
*
*
*
* {
* except: ['col1'],
* appends: ['association_count'],
* }
*/
fields?: string[] | {
only?: string[];
appends?: string[];
} | {
except?: string[];
appends?: string[];
};
fields?:
| string[]
| {
only?: string[];
appends?: string[];
}
| {
except?: string[];
appends?: string[];
};
/**
*
*
*
*
* {
* col1: {
* $eq: 'val1'
* },
* }
*
*
* scope scope col scope
* {
* scope1: value
* }
*
*
* json &
* {
* 'association.col1': {
@ -67,7 +74,7 @@ export interface ApiJsonOptions {
* $eq: 'val1'
* },
* }
*
*
* json &
* {
* association: {
@ -81,9 +88,9 @@ export interface ApiJsonOptions {
/**
*
*
*
* TODO
*
*
* ['col1', '-col2', 'association.col1', '-association.col2']
*/
sort?: any;
@ -100,7 +107,6 @@ export interface ApiJsonOptions {
}
export interface WithCountAttributeOptions {
/**
*
*/
@ -108,9 +114,9 @@ export interface WithCountAttributeOptions {
/**
* SourceModel
*
*
* include 使 include association
*
*
* include: {
* association: 'user', // Post.belongsTo(User)
* attributes: [
@ -143,12 +149,11 @@ export interface UpdateAssociationOptions extends SaveOptions {
/**
* Model
*
*
* TODO: 自定义 model
*/
// @ts-ignore
export abstract class Model extends SequelizeModel {
/**
* ts
*/
@ -156,7 +161,7 @@ export abstract class Model extends SequelizeModel {
/**
* Model database
*
*
* Model.sequelize database public static readonly
*/
public static database: Database;
@ -171,17 +176,25 @@ export abstract class Model extends SequelizeModel {
/**
* sub query
*
*
* TODO: 关联字段暂不支持主键以外的字段
*
* @param options
*
* @param options
*/
static withCountAttribute(options?: string | WithCountAttributeOptions): (string | ProjectionAlias) {
static withCountAttribute(
options?: string | WithCountAttributeOptions,
): string | ProjectionAlias {
if (typeof options === 'string') {
options = { association: options };
}
const { sourceAlias, association, where = {}, alias, ...restOptions } = options;
const {
sourceAlias,
association,
where = {},
alias,
...restOptions
} = options;
const associator = this.associations[association];
const table = this.database.getTable(this.name);
const field = table.getField(association);
@ -193,17 +206,20 @@ export abstract class Model extends SequelizeModel {
};
} else if (associator.associationType === 'BelongsToMany') {
where[targetKey] = {
// @ts-ignore
[Op.in]: Sequelize.literal(`(${associator.through.model.selectQuery({
attributes: [otherKey],
where: {
[foreignKey]: {
[Op.eq]: Sequelize.col(`${sourceAlias || this.name}.${sourceKey}`),
[Op.in]: Sequelize.literal(
`(${(associator as any).through.model.selectQuery({
attributes: [otherKey],
where: {
[foreignKey]: {
[Op.eq]: Sequelize.col(
`${sourceAlias || this.name}.${sourceKey}`,
),
},
// @ts-ignore
...(associator.through.scope || {}),
},
// @ts-ignore
...(associator.through.scope || {}),
},
})})`),
})})`,
),
};
}
@ -221,41 +237,47 @@ export abstract class Model extends SequelizeModel {
attributes: [[Sequelize.literal(countLiteral), 'count']],
where: {
// @ts-ignore
...where, ...(associator.scope || {}),
...where,
...((associator as any).scope || {}),
},
})})`
})})`,
),
alias || Utils.underscoredIf(`${association}Count`, this.options.underscored),
alias ||
Utils.underscoredIf(`${association}Count`, this.options.underscored),
].filter(Boolean);
return attribute as unknown as ProjectionAlias;
return (attribute as unknown) as ProjectionAlias;
}
/**
* Model SQL
*
* @param options
*
* @param options
*/
static selectQuery(options = {}): string {
// @ts-ignore
return this.queryGenerator.selectQuery(
this.getTableName(),
options,
this,
).replace(/;$/, '');
return this.queryGenerator
.selectQuery(this.getTableName(), options, this)
.replace(/;$/, '');
}
static parseApiJson(options: ApiJsonOptions) {
const { fields, filter, sort, context, page, perPage } = options;
const data = toInclude({ fields, filter, sort }, {
model: this,
associations: this.associations,
dialect: this.sequelize.getDialect(),
ctx: context,
database: this.database,
});
const data = toInclude(
{ fields, filter, sort },
{
model: this,
associations: this.associations,
dialect: this.sequelize.getDialect(),
ctx: context,
database: this.database,
},
);
if (page || perPage) {
data.limit = perPage === -1 ? MAX_LIMIT : Math.min(perPage || DEFAULT_LIMIT, MAX_LIMIT);
data.limit =
perPage === -1
? MAX_LIMIT
: Math.min(perPage || DEFAULT_LIMIT, MAX_LIMIT);
data.offset = data.limit * (page > 0 ? page - 1 : DEFAULT_OFFSET);
}
if (data.attributes && data.attributes.length === 0) {
@ -269,11 +291,12 @@ export abstract class Model extends SequelizeModel {
const Model = table.getModel();
const associations = table.getAssociations();
const where = {};
scope.forEach(col => {
scope.forEach((col) => {
const association = associations.get(col);
const dataKey = association && association instanceof BELONGSTO
? association.options.foreignKey
: col;
const dataKey =
association && association instanceof BELONGSTO
? association.options.foreignKey
: col;
if (!Model.rawAttributes[dataKey]) {
return;
}
@ -285,7 +308,11 @@ export abstract class Model extends SequelizeModel {
return where;
}
async updateSingleAssociation(key: string, data: any, options: UpdateAssociationOptions = {}) {
async updateSingleAssociation(
key: string,
data: any,
options: UpdateAssociationOptions = {},
) {
const {
fields,
transaction = await this.sequelize.transaction(),
@ -302,20 +329,25 @@ export abstract class Model extends SequelizeModel {
return;
}
if (typeof data === 'number' || typeof data === 'string' || data instanceof SequelizeModel) {
if (
typeof data === 'number' ||
typeof data === 'string' ||
data instanceof SequelizeModel
) {
await this[accessors.set](data, opts);
} else if (typeof data === 'object') {
const Target = association.getTargetModel();
const targetAttribute = association instanceof BELONGSTO
? association.options.targetKey
: association.options.sourceKey;
const targetAttribute =
association instanceof BELONGSTO
? association.options.targetKey
: association.options.sourceKey;
if (data[targetAttribute]) {
if (Object.keys(data).length > 0) {
const target = await Target.findOne({
where: {
[targetAttribute]: data[targetAttribute],
},
transaction
transaction,
});
if (target) {
await this[accessors.set](data[targetAttribute], opts);
@ -337,7 +369,11 @@ export abstract class Model extends SequelizeModel {
}
}
async updateMultipleAssociation(associationName: string, data: any, options: UpdateAssociationOptions = {}) {
async updateMultipleAssociation(
associationName: string,
data: any,
options: UpdateAssociationOptions = {},
) {
const items = Array.isArray(data) ? data : data == null ? [] : [data];
const {
@ -371,7 +407,7 @@ export abstract class Model extends SequelizeModel {
const toUpsertObjects = [];
// 遍历所有值成员准备数据
items.forEach(item => {
items.forEach((item) => {
if (item instanceof SequelizeModel) {
if (targetKeyIsPk) {
toSetPks.add(item.getDataValue(targetPk));
@ -381,14 +417,18 @@ export abstract class Model extends SequelizeModel {
return;
}
if (typeof item === 'number' || typeof item === 'string') {
let targetKeyType = getDataTypeKey(Target.rawAttributes[targetKey].type).toLocaleLowerCase();
let targetKeyType = getDataTypeKey(
Target.rawAttributes[targetKey].type,
).toLocaleLowerCase();
if (targetKeyType === 'integer') {
targetKeyType = 'number';
}
// 如果传值类型与之前在 Model 上定义的 targetKey 不同,则报错。
// 不应兼容定义的 targetKey 不是 primaryKey 却传了 primaryKey 的值的情况。
if (typeof item !== targetKeyType) {
throw new Error(`target key type [${typeof item}] does not match to [${targetKeyType}]`);
throw new Error(
`target key type [${typeof item}] does not match to [${targetKeyType}]`,
);
}
if (targetKeyIsPk) {
toSetPks.add(item);
@ -404,31 +444,35 @@ export abstract class Model extends SequelizeModel {
/* 仅传关联键处理开始 */
// 查找已存在的数据
const byPkExistItems = toSetPks.size ? await Target.findAll({
...opts,
// @ts-ignore
where: {
[targetPk]: {
[Op.in]: Array.from(toSetPks)
}
},
attributes: [targetPk]
}) : [];
byPkExistItems.forEach(item => {
const byPkExistItems = toSetPks.size
? await Target.findAll({
...opts,
// @ts-ignore
where: {
[targetPk]: {
[Op.in]: Array.from(toSetPks),
},
},
attributes: [targetPk],
})
: [];
byPkExistItems.forEach((item) => {
toSetItems.add(item);
});
const byUkExistItems = toSetUks.size ? await Target.findAll({
...opts,
// @ts-ignore
where: {
[targetKey]: {
[Op.in]: Array.from(toSetUks)
}
},
attributes: [targetPk, targetKey]
}) : [];
byUkExistItems.forEach(item => {
const byUkExistItems = toSetUks.size
? await Target.findAll({
...opts,
// @ts-ignore
where: {
[targetKey]: {
[Op.in]: Array.from(toSetUks),
},
},
attributes: [targetPk, targetKey],
})
: [];
byUkExistItems.forEach((item) => {
toSetItems.add(item);
});
/* 仅传关联键处理结束 */
@ -458,7 +502,7 @@ export abstract class Model extends SequelizeModel {
if (association instanceof BELONGSTOMANY) {
belongsToManyList.push({
item,
target
target,
});
}
@ -474,7 +518,6 @@ export abstract class Model extends SequelizeModel {
const ThroughModel = (association as BELONGSTOMANY).getThroughModel();
const throughName = (association as BELONGSTOMANY).getThroughName();
for (const { item, target } of belongsToManyList) {
const throughValues = item[throughName];
if (throughValues && typeof throughValues === 'object') {
@ -484,7 +527,7 @@ export abstract class Model extends SequelizeModel {
[foreignKey]: this.get(sourceKey),
[otherKey]: target.get(targetKey),
},
transaction
transaction,
});
await through.update(throughValues, opts);
// TODO有 BUG未知
@ -498,7 +541,11 @@ export abstract class Model extends SequelizeModel {
}
}
async updateAssociation(key: string, data: any, options: UpdateAssociationOptions = {}) {
async updateAssociation(
key: string,
data: any,
options: UpdateAssociationOptions = {},
) {
const table = this.database.getTable(this.constructor.name);
const association = table.getAssociations().get(key);
switch (true) {
@ -513,7 +560,7 @@ export abstract class Model extends SequelizeModel {
/**
*
*
*
* @param data
*/
async updateAssociations(data: any, options: UpdateAssociationOptions = {}) {
@ -526,7 +573,7 @@ export abstract class Model extends SequelizeModel {
}
await this.updateAssociation(key, data[key], {
...options,
transaction
transaction,
});
}
@ -544,6 +591,8 @@ export abstract class Model extends SequelizeModel {
/**
* ModelCtor Model
*/
export type ModelCtor<M extends Model> = typeof Model & { new(): M } & { [key: string]: any };
export type ModelCtor<M extends Model> = typeof Model & { new (): M } & {
[key: string]: any;
};
export default Model;

View File

@ -53,36 +53,58 @@ op.set('$isFalsy', () => ({
// 字符串
// 包含:指对应字段的值包含某个子串
op.set('$includes', (value: string) => ({ [Op.iLike]: `%${value}%` }));
op.set('$includes', (value: string, { dialect }) => ({
[dialect === 'postgres' ? Op.iLike : Op.like]: `%${value}%`,
}));
// 不包含:指对应字段的值不包含某个子串(慎用:性能问题)
op.set('$notIncludes', (value: string) => ({ [Op.notILike]: `%${value}%` }));
op.set('$notIncludes', (value: string, { dialect }) => ({
[dialect === 'postgres' ? Op.notILike : Op.notLike]: `%${value}%`,
}));
// 以之起始
op.set('$startsWith', (value: string) => ({ [Op.iLike]: `${value}%` }));
op.set('$startsWith', (value: string, { dialect }) => ({
[dialect === 'postgres' ? Op.iLike : Op.like]: `${value}%`,
}));
// 不以之起始
op.set('$notStartsWith', (value: string) => ({ [Op.notILike]: `${value}%` }));
op.set('$notStartsWith', (value: string, { dialect }) => ({
[dialect === 'postgres' ? Op.notILike : Op.notLike]: `${value}%`,
}));
// 以之结束
op.set('$endsWith', (value: string) => ({ [Op.iLike]: `%${value}` }));
op.set('$endsWith', (value: string, { dialect }) => ({
[dialect === 'postgres' ? Op.iLike : Op.like]: `%${value}`,
}));
// 不以之结束
op.set('$notEndsWith', (value: string) => ({ [Op.notILike]: `%${value}` }));
op.set('$notEndsWith', (value: string, { dialect }) => ({
[dialect === 'postgres' ? Op.notILike : Op.notLike]: `%${value}`,
}));
// 仅日期
// 在某日
op.set('$dateOn', (value: string) => ({ [Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }] }));
op.set('$dateOn', (value: string) => ({
[Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }],
}));
// 不在某日
op.set('$dateNotOn', (value: string) => ({ [Op.or]: [{ [Op.lt]: stringToDate(value) }, { [Op.gte]: getNextDay(value) }] }));
op.set('$dateNotOn', (value: string) => ({
[Op.or]: [{ [Op.lt]: stringToDate(value) }, { [Op.gte]: getNextDay(value) }],
}));
// 某日前
op.set('$dateBefore', (value: string) => ({ [Op.lt]: stringToDate(value) }));
// 某日后
op.set('$dateAfter', (value: string) => ({ [Op.gte]: getNextDay(value) }));
// 不早于(含当天)
op.set('$dateNotBefore', (value: string) => ({ [Op.gte]: stringToDate(value) }));
op.set('$dateNotBefore', (value: string) => ({
[Op.gte]: stringToDate(value),
}));
// 不晚于(含当天)
op.set('$dateNotAfter', (value: string) => ({ [Op.lt]: getNextDay(value) }));
// 在期间
op.set('$dateBetween', ([from, to]: string[]) => ({ [Op.and]: [{ [Op.gte]: stringToDate(from) }, { [Op.lt]: getNextDay(to) }] }));
op.set('$dateBetween', ([from, to]: string[]) => ({
[Op.and]: [{ [Op.gte]: stringToDate(from) }, { [Op.lt]: getNextDay(to) }],
}));
// 不在期间
op.set('$dateNotBetween', ([from, to]: string[]) => ({ [Op.or]: [{ [Op.lt]: stringToDate(from) }, { [Op.gte]: getNextDay(to) }] }));
op.set('$dateNotBetween', ([from, to]: string[]) => ({
[Op.or]: [{ [Op.lt]: stringToDate(from) }, { [Op.gte]: getNextDay(to) }],
}));
// 多选JSON类型
@ -96,9 +118,13 @@ op.set('$anyOf', (values: any[], options) => {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = values.map(value => `(${column})::jsonb @> '${JSON.stringify(value)}'`).join(' OR ');
console.log(sql);
const column = fieldPath
.split('.')
.map((name) => `"${name}"`)
.join('.');
const sql = values
.map((value) => `(${column})::jsonb @> '${JSON.stringify(value)}'`)
.join(' OR ');
return Sequelize.literal(sql);
});
// 包含组中所有值
@ -113,9 +139,13 @@ op.set('$noneOf', (values: any[], options) => {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = values.map(value => `(${column})::jsonb @> '${JSON.stringify(value)}'`).join(' OR ');
console.log(sql);
const column = fieldPath
.split('.')
.map((name) => `"${name}"`)
.join('.');
const sql = values
.map((value) => `(${column})::jsonb @> '${JSON.stringify(value)}'`)
.join(' OR ');
return Sequelize.literal(`not (${sql})`);
});
// 与组中值匹配
@ -125,8 +155,13 @@ op.set('$match', (values: any[], options) => {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(array)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`
const column = fieldPath
.split('.')
.map((name) => `"${name}"`)
.join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(
array,
)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`;
return Sequelize.literal(sql);
});
op.set('$notMatch', (values: any[], options) => {
@ -135,8 +170,13 @@ op.set('$notMatch', (values: any[], options) => {
return Sequelize.literal('');
}
const { field, fieldPath } = options;
const column = fieldPath.split('.').map(name => `"${name}"`).join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(array)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`
const column = fieldPath
.split('.')
.map((name) => `"${name}"`)
.join('.');
const sql = `(${column})::jsonb @> '${JSON.stringify(
array,
)}' AND (${column})::jsonb <@ '${JSON.stringify(array)}'`;
return Sequelize.literal(`not (${sql})`);
// return Sequelize.literal(`(not (${sql})) AND ${column} IS NULL`);
});
@ -153,4 +193,4 @@ export default class Operator {
public static register(key: string, fn: Function) {
op.set(key, fn);
}
};
}

View File

@ -12,16 +12,14 @@ import {
Relation,
BELONGSTO,
BELONGSTOMANY,
SORT
SORT,
} from './fields';
import Database from './database';
import { Model, ModelCtor } from './model';
import _ from 'lodash';
import merge from 'deepmerge';
export interface MergeOptions extends merge.Options {
}
export interface MergeOptions extends merge.Options {}
const registeredModels = new Map<string, any>();
@ -45,15 +43,15 @@ export function getRegisteredModel(key) {
return key;
}
export interface TableOptions extends Omit<ModelOptions<Model>, 'name' | 'modelName'> {
export interface TableOptions
extends Omit<ModelOptions<Model>, 'name' | 'modelName'> {
/**
* ModelOptions name
*
*
* name tableNamemodelName tableName modelName
* name tables models
* tableName nametableNamemodelName
*
*
* TODO: name, tableName, modelNamefreezeTableNameunderscored
*/
name: string;
@ -77,7 +75,7 @@ export interface TableOptions extends Omit<ModelOptions<Model>, 'name' | 'modelN
/**
* Tabel
*/
export interface TabelContext {
export interface TableContext {
database: Database;
}
@ -92,11 +90,10 @@ export type Reinitialize = boolean | 'modelOnly';
/**
*
*
*
* Model
*/
export class Table {
protected database: Database;
protected options: TableOptions;
@ -134,19 +131,16 @@ export class Table {
public relationTables = new Set<string>();
get sortable(): boolean {
return Array.from(this.fields.values()).some(field => field instanceof SORT);
return Array.from(this.fields.values()).some(
(field) => field instanceof SORT,
);
}
constructor(options: TableOptions, context: TabelContext) {
constructor(options: TableOptions, context: TableContext) {
const { database } = context;
database.runHooks('beforeTableInit', options);
database.emit('beforeTableInit', options);
const {
model,
fields = [],
indexes = [],
sortable,
} = options;
const { model, fields = [], indexes = [], sortable } = options;
this.options = options;
this.database = database;
// 初始化的时候获取
@ -183,7 +177,9 @@ export class Table {
public modelInit(reinitialize: Reinitialize = false) {
if (reinitialize || !this.Model) {
let DefaultModel = this.defaultModel;
this.Model = DefaultModel ? (class extends DefaultModel {}) : (class extends Model { });
this.Model = DefaultModel
? class extends DefaultModel {}
: class extends Model {};
this.Model.database = this.database;
// 关系的建立是在 model.init 之后在配置中表字段Column和关系Relation都在 fields
// 所以需要单独提炼出 associations 字段,并在 Model.init 之后执行 Model.associate
@ -194,7 +190,10 @@ export class Table {
if (this.database.isDefined(target)) {
const TargetModel = this.database.getModel(target);
// 如果关系表在之后才定义,未设置 targetKey 时targetKey 默认值需要在 target model 初始化之后才能取到
if (association instanceof BELONGSTO || association instanceof BELONGSTOMANY) {
if (
association instanceof BELONGSTO ||
association instanceof BELONGSTOMANY
) {
association.updateOptionsAfterTargetModelBeDefined();
}
this.Model[type](TargetModel, association.getAssociationOptions());
@ -202,7 +201,7 @@ export class Table {
this.associating.delete(key);
}
}
}
};
}
this.Model.init(this.getModelAttributes(), this.getModelOptions());
@ -229,7 +228,7 @@ export class Table {
}
/**
*
*
* @param key key
*/
public getOptions(key?: any): TableOptions {
@ -245,12 +244,9 @@ export class Table {
}
public getModelOptions(): InitOptions {
const {
name,
underscored = true,
...restOptions
} = this.options;
const hooks = _.get(this.getModel(), 'options.hooks') || this.options.hooks || {};
const { name, underscored = true, ...restOptions } = this.options;
const hooks =
_.get(this.getModel(), 'options.hooks') || this.options.hooks || {};
return {
underscored,
modelName: name,
@ -294,9 +290,11 @@ export class Table {
this.fields.clear();
this.associating.clear();
this.associations.clear();
for (const key in fields) {
this.addField(fields[key], false);
for (const filed of fields) {
this.addField(filed, false);
}
this.modelInit(true);
}
@ -310,9 +308,9 @@ export class Table {
/**
*
*
* @param options
* @param reinitialize
*
* @param options
* @param reinitialize
*/
public addField(options: FieldOptions, reinitialize: Reinitialize = true) {
this.database.runHooks('beforeAddField', options, this);
@ -327,7 +325,9 @@ export class Table {
if (!this.options.fields) {
this.options.fields = [];
}
const existIndex = this.options.fields.findIndex(field => field.name === name);
const existIndex = this.options.fields.findIndex(
(field) => field.name === name,
);
if (existIndex !== -1) {
this.options.fields.splice(existIndex, 1, options);
} else {
@ -344,10 +344,13 @@ export class Table {
if (index === true) {
this.addIndex(name, false);
} else if (typeof index === 'object') {
this.addIndex({
fields: [name],
...index,
}, false);
this.addIndex(
{
fields: [name],
...index,
},
false,
);
}
this.modelAttributes[name] = field.getAttributeOptions();
}
@ -359,14 +362,20 @@ export class Table {
/**
*
*
* @param indexOptions
* @param reinitialize
*
* @param indexOptions
* @param reinitialize
*/
public addIndex(indexOptions: string | ModelIndexesOptions, reinitialize: Reinitialize = true) {
const options = typeof indexOptions === 'string' ? {
fields: [indexOptions],
} : indexOptions;
public addIndex(
indexOptions: string | ModelIndexesOptions,
reinitialize: Reinitialize = true,
) {
const options =
typeof indexOptions === 'string'
? {
fields: [indexOptions],
}
: indexOptions;
// @ts-ignore
const index = Utils.nameIndex(options, this.options.name);
console.log(this.options, { index, options });
@ -380,11 +389,14 @@ export class Table {
/**
*
*
* @param indexes
* @param reinitialize
*
* @param indexes
* @param reinitialize
*/
public addIndexes(indexes: Array<string | ModelIndexesOptions>, reinitialize: Reinitialize = true) {
public addIndexes(
indexes: Array<string | ModelIndexesOptions>,
reinitialize: Reinitialize = true,
) {
for (const index in indexes) {
this.addIndex(indexes[index], false);
}
@ -393,20 +405,16 @@ export class Table {
/**
* API
*
* @param options
*
* @param options
*/
public extend(options: TableOptions, mergeOptions: MergeOptions = {}) {
const {
fields = [],
indexes = [],
model,
...restOptions
} = options;
const { fields = [], indexes = [], model, ...restOptions } = options;
if (model) {
this.defaultModel = getRegisteredModel(model);
}
const { arrayMerge = (target: any[], source: any[]) => source } = mergeOptions;
const { arrayMerge = (target: any[], source: any[]) => source } =
mergeOptions;
this.options = merge(this.options, restOptions, {
arrayMerge,
...mergeOptions,
@ -422,8 +430,8 @@ export class Table {
/**
*
*
* @param options
*
* @param options
*/
public async sync(options: SyncOptions = {}) {
const tables = [];

View File

@ -58,6 +58,7 @@ export function toWhere(options: any, context: ToWhereContext = {}) {
ctx,
model,
database,
dialect: database.sequelize.getDialect(),
fieldPath: name ? `${name}.${prefix}` : prefix,
});
if (result.constructor.name === 'Literal') {

View File

@ -0,0 +1,65 @@
import Application from '../../../server/src/application';
import { getInitSqls, runSql } from '../server';
test('import demo data', async () => {
const app = new Application({
database: {
dialect: 'sqlite',
dialectModule: require('sqlite3'),
storage: 'db.sqlite',
logging: false,
},
});
const plugins = [
'@nocobase/plugin-ui-router',
'@nocobase/plugin-ui-schema',
'@nocobase/plugin-collections',
'@nocobase/plugin-users',
'@nocobase/plugin-action-logs',
'@nocobase/plugin-file-manager',
'@nocobase/plugin-permissions',
'@nocobase/plugin-export',
'@nocobase/plugin-system-settings',
'@nocobase/plugin-china-region',
];
for (const plugin of plugins) {
const pluginPath = `${plugin}/src/server`.replace(
'@nocobase/',
'../../../',
);
app.plugin((await import(pluginPath)).default);
}
await app.load();
await app.db.sync({ force: true });
await app.emitAsync('db.init');
const database = app.db;
const sqls = getInitSqls();
for (const sqlGroup of sqls.part1) {
for (const sql of sqlGroup.split(';')) {
await runSql(sql, database);
}
}
await app.db.getModel('collections').load({
skipExisting: true,
});
await app.db.sync();
for (const sqlGroup of sqls.part2) {
for (const sql of sqlGroup.split(';')) {
try {
await runSql(sql, database);
} catch (e) {
console.error(e);
}
}
}
});

View File

@ -1,4 +1,4 @@
TRUNCATE "ui_schemas" RESTART IDENTITY CASCADE;
DELETE FROM "ui_schemas";
INSERT INTO "ui_schemas" ("key", "name", "title", "type", "x-component", "options", "async", "sort", "created_at", "updated_at", "parent_key") VALUES
('dtu7erjj0q8', NULL, '昵称', 'string', 'Input', '{}', '0', 1, '2021-09-02 15:07:56.984+00', '2021-09-02 15:07:56.984+00', NULL),
('5nqs5glgn5c', NULL, '邮箱', 'string', 'Input', '{"require":true}', '0', 2, '2021-09-02 15:07:57.015+00', '2021-09-02 15:07:57.015+00', NULL),

View File

@ -1,4 +1,4 @@
TRUNCATE "collections" RESTART IDENTITY CASCADE;
DELETE FROM "collections";
INSERT INTO "collections" ("name", "logging", "title", "privilege", "sortable", "options", "sort", "created_at", "updated_at", "ui_schema_key") VALUES
('users', '1', '用户', 'undelete', '"sort"', '{"createdBy":false,"updatedBy":false}', 2, '2021-09-02 15:07:56.914+00', '2021-09-15 00:59:20.676+00', NULL),
('t_fsveob6p269', '1', '顾客', NULL, '"sort"', '{}', 5, '2021-09-12 01:05:52.722+00', '2021-09-18 04:15:23.113+00', NULL),

View File

@ -1,4 +1,4 @@
TRUNCATE "fields" RESTART IDENTITY CASCADE;
DELETE FROM "fields";
INSERT INTO "fields" ("key", "name", "interface", "data_type", "privilege", "state", "options", "sort", "created_at", "updated_at", "parent_key", "collection_name", "ui_schema_key", "reverse_key") VALUES
('aun85p76s0v', 'f_l8uuiwcnlw9', 'textarea', 'text', NULL, 1, '{}', 8, '2021-09-12 08:18:46.92+00', '2021-09-12 08:18:46.935+00', NULL, 't_2uhu4szs1kq', 'c2pallk0lge', NULL),
('sj5p2y4ac06', '70enejazumv', 'updatedAt', 'date', 'undelete', 1, '{"field":"updated_at"}', 2, '2021-09-03 08:17:30.543+00', '2021-09-03 08:17:30.555+00', NULL, 't_2uhu4szs1kq', 'lchrxtjm3d5', NULL),

View File

@ -1,4 +1,4 @@
TRUNCATE "routes" RESTART IDENTITY CASCADE;
DELETE FROM "routes";
INSERT INTO "routes" ("key", "type", "options", "sort", "created_at", "updated_at", "parent_key", "ui_schema_key") VALUES
('r_94b8nz6evyh', 'redirect', '{"from":"/","to":"/admin","exact":true}', 1, '2021-09-02 15:07:57.162+00', '2021-09-02 15:07:57.162+00', NULL, NULL),
('r_w1sa2lfk44v', 'route', '{"path":"/admin/:name(.+)?","component":"AdminLayout","title":"后台"}', 2, '2021-09-02 15:07:57.17+00', '2021-09-02 15:07:57.185+00', NULL, 'qqzzjakwkwl'),

View File

@ -1,3 +1,4 @@
DELETE FROM "attachments";
INSERT INTO "attachments" ("id", "title", "filename", "extname", "size", "mimetype", "path", "meta", "url", "created_at", "updated_at", "created_by_id", "updated_by_id", "storage_id") VALUES
(2, 'Nocobase', 'dac1165de10a3373a87ae4e5da9788ab.png', '.png', NULL, 'image/png', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/dac1165de10a3373a87ae4e5da9788ab.png', '2021-09-04 01:33:38.878+00', '2021-09-04 01:33:38.878+00', NULL, NULL, 2),
(3, 'grapes-276070_1280', '3451976fea63a882b5fd661aa211693e.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/3451976fea63a882b5fd661aa211693e.jpg', '2021-09-04 01:34:50.388+00', '2021-09-04 01:34:50.388+00', NULL, 1, 2),
@ -27,4 +28,3 @@ INSERT INTO "attachments" ("id", "title", "filename", "extname", "size", "mimety
(26, 'kaffeerad-1543158_1280', '8b94dd8cbecdbbd03e4c2fcfbd1067e7.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/8b94dd8cbecdbbd03e4c2fcfbd1067e7.jpg', '2021-09-12 07:47:39.63+00', '2021-09-12 07:47:39.63+00', NULL, 1, 2),
(28, 'apple-1609693_1280', '31b7dee511a8d6364fee18411763c188.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/31b7dee511a8d6364fee18411763c188.jpg', '2021-09-12 07:48:41.367+00', '2021-09-12 07:48:41.367+00', NULL, 1, 2),
(29, 'cappuccino-1609932_1280', '8f480c4f1bd3d292ef78fb6847669e51.jpg', '.jpg', NULL, 'image/jpeg', '', '{}', 'https://nocobase.oss-cn-beijing.aliyuncs.com/8f480c4f1bd3d292ef78fb6847669e51.jpg', '2021-09-12 07:48:41.372+00', '2021-09-12 07:48:41.372+00', NULL, 1, 2);
SELECT setval('attachments_id_seq', (SELECT MAX(id) FROM "attachments"), true);

View File

@ -1,3 +1,4 @@
DELETE FROM "t_2uhu4szs1kq";
INSERT INTO "t_2uhu4szs1kq" ("id", "created_at", "updated_at", "sort", "created_by_id", "updated_by_id", "f_hwenour8ara", "f_hznqtmqljb2", "f_u4i0jrp4uo6", "f_l8uuiwcnlw9") VALUES
(1, '2021-09-03 08:19:53.163+00', '2021-09-18 07:50:34.039+00', 70, 1, 1, '这是我们要做的任务这是我们要做的', '2021-09-17 00:00:00+00', 'f1g3r41rdh8', '利本毛所线表体定质花则根物大教斯经前飞能D发科程W目。 阶直少看员片飞飞今西取亲本就条持层品平米今ON孤你七2。 花离定可除展通向业很,斗术真节西严特矿,导养-N群长置便。 深规马细三强低按段事作般习就代它完技O布D枝快。 便报动斗改克离为具影研立特养命规么才设步局方总较共张时什Q肃声的者起开出话。'),
(2, '2021-09-12 08:01:16.467+00', '2021-09-18 07:50:34.039+00', 71, 1, 1, '批量上传附件', '2021-09-24 00:00:00+00', 'f1g3r41rdh8', '利本毛所线表体定质花则根物大教斯经前飞能D发科程W目。 阶直少看员片飞飞今西取亲本就条持层品平米今ON孤你七2。 花离定可除展通向业很,斗术真节西严特矿,导养-N群长置便。 深规马细三强低按段事作般习就代它完技O布D枝快。 便报动斗改克离为具影研立特养命规么才设步局方总较共张时什Q肃声的者起开出话。'),
@ -5,4 +6,3 @@ INSERT INTO "t_2uhu4szs1kq" ("id", "created_at", "updated_at", "sort", "created_
(4, '2021-09-12 08:02:37.852+00', '2021-09-18 07:50:34.039+00', 72, 1, 1, '包派极都火题折究条', '2021-10-08 00:00:00+00', 'zfowtv6fnel', '利本毛所线表体定质花则根物大教斯经前飞能D发科程W目。 阶直少看员片飞飞今西取亲本就条持层品平米今ON孤你七2。 花离定可除展通向业很,斗术真节西严特矿,导养-N群长置便。 深规马细三强低按段事作般习就代它完技O布D枝快。 便报动斗改克离为具影研立特养命规么才设步局方总较共张时什Q肃声的者起开出话。'),
(6, '2021-09-17 01:35:18.379+00', '2021-09-18 07:50:34.039+00', 73, 1, 1, '达到顶峰五十五分', '2021-09-17 00:00:00+00', 'zfowtv6fnel', NULL),
(3, '2021-09-12 08:02:20.501+00', '2021-09-18 07:50:34.042+00', 68, 1, 1, '往你周观青整积元路公', '2021-09-24 00:00:00+00', 'lebkfnj3d9i', '利本毛所线表体定质花则根物大教斯经前飞能D发科程W目。 阶直少看员片飞飞今西取亲本就条持层品平米今ON孤你七2。 花离定可除展通向业很,斗术真节西严特矿,导养-N群长置便。 深规马细三强低按段事作般习就代它完技O布D枝快。 便报动斗改克离为具影研立特养命规么才设步局方总较共张时什Q肃声的者起开出话。');
SELECT setval('t_2uhu4szs1kq_id_seq', (SELECT MAX(id) FROM "t_2uhu4szs1kq"), true);

View File

@ -1,3 +1,4 @@
DELETE FROM "t_fsveob6p269";
INSERT INTO "t_fsveob6p269" ("id", "created_at", "updated_at", "sort", "created_by_id", "updated_by_id", "f_vgw4f62h16e", "f_3det6kr3vn8", "f_x9kmuovlm79", "f_bysgds7j36p", "f_kk4nspj1i28", "f_61znsfh307m") VALUES
(2, '2021-09-12 07:41:10.508+00', '2021-09-12 07:41:10.508+00', 2, 1, 1, '庆贝儿', '18910347327', 'khan8z430yx', '2000-02-03 00:00:00+00', '件段两声提局则着除所法军为后出子石了期七小南领能革表相F声化等水坑但F连。 今劳查速布你安约定值斗运通声资区的选心认部样7展期辅些正记。
@ -18,4 +19,3 @@ INSERT INTO "t_fsveob6p269" ("id", "created_at", "updated_at", "sort", "created_
(6, '2021-09-12 07:43:57.617+00', '2021-09-12 08:28:50.544+00', 6, 1, 1, '宓中仕', '13527645198', 'khan8z430yx', '1999-02-26 00:00:00+00', '压界点小名理它车机两得原查去证行村太孟道。求真强记织管装总论形感增委越术市点便定肃容红个而写。代往门张照布活期头劳量人导8新建给。造装话和很片算只类论所全变容时过地传达术族刷车进变了邮盯。 在给情她积音新问整史图火验往受王多式儿花6集听壳是呆。越县管积小与权内有红压往你周观青整积元路公包派极都火题折究条。
S点其会 便广M来霸豆板枝E事眼目外4 ', 'z7wq1mkwmmx');
SELECT setval('t_fsveob6p269_id_seq', (SELECT MAX(id) FROM "t_fsveob6p269"), true);

View File

@ -1,3 +1,4 @@
DELETE FROM "t_geso7fru7a9";
INSERT INTO "t_geso7fru7a9" ("id", "created_at", "updated_at", "sort", "created_by_id", "updated_by_id", "f_umjewufbyhj", "f_nlgk67tpdql", "f_niymyj0no38") VALUES
(1, '2021-09-12 01:24:52.21+00', '2021-09-12 07:38:07.64+00', 1, 1, 1, '2021-09-10 11:26:28+00', 'x4qnavatfai', '751834123525'),
(2, '2021-09-12 07:36:07.647+00', '2021-09-12 07:38:15.812+00', 2, 1, 1, '2021-09-24 15:41:03+00', 'i2xjqmnwrsu', '751834123526'),
@ -15,4 +16,3 @@ INSERT INTO "t_geso7fru7a9" ("id", "created_at", "updated_at", "sort", "created_
(14, '2021-09-14 02:06:10.875+00', '2021-09-14 02:06:10.875+00', 14, 1, 1, '2021-09-07 10:06:06+00', 'x4qnavatfai', '751834123537'),
(15, '2021-09-14 02:06:29.102+00', '2021-09-14 02:06:29.102+00', 15, 1, 1, '2021-09-25 10:06:18+00', 'x4qnavatfai', '751834123538'),
(16, '2021-09-14 02:06:45.054+00', '2021-09-14 02:06:45.054+00', 16, 1, 1, '2021-09-09 10:06:41+00', 'i2xjqmnwrsu', '751834123539');
SELECT setval('t_geso7fru7a9_id_seq', (SELECT MAX(id) FROM "t_geso7fru7a9"), true);

View File

@ -1,3 +1,4 @@
DELETE FROM "t_bphcojtl8bx";
INSERT INTO "t_bphcojtl8bx" ("created_at", "updated_at", "f_cn6a3p0wq2p", "f_4x6u1waa8r6") VALUES
('2021-09-12 01:24:52.263+00', '2021-09-12 01:24:52.263+00', 1, 1),
('2021-09-12 07:36:07.681+00', '2021-09-12 07:36:07.681+00', 1, 2),

View File

@ -1,3 +1,4 @@
DELETE FROM "t_jh7a28dsfzi";
INSERT INTO "t_jh7a28dsfzi" ("created_at", "updated_at", "f_xg3mysbjfra", "f_gc7ppj0b7n1") VALUES
('2021-09-12 01:21:03.65+00', '2021-09-12 01:21:03.65+00', 1, 6),
('2021-09-12 01:21:03.65+00', '2021-09-12 01:21:03.65+00', 1, 5),

View File

@ -1,3 +1,4 @@
DELETE FROM "t_p13gbi31uux";
INSERT INTO "t_p13gbi31uux" ("created_at", "updated_at", "f_86s65mbw93e", "f_hwx3hgg5b5t") VALUES
('2021-09-12 01:21:03.666+00', '2021-09-12 01:21:03.666+00', 1, '11'),
('2021-09-12 01:21:03.666+00', '2021-09-12 01:21:03.666+00', 1, '1101'),

View File

@ -1,3 +1,4 @@
DELETE FROM "roles";
INSERT INTO "roles" ("name", "title", "sort", "created_at", "updated_at", "created_by_id", "updated_by_id") VALUES
('r_tfs4qtaxjcs', '管理员', 1, '2021-09-14 02:12:19.98+00', '2021-09-14 02:12:19.98+00', 1, 1),
('r_np59b00ex8z', '普通成员', 2, '2021-09-14 02:12:35.799+00', '2021-09-14 02:12:35.799+00', 1, 1);

View File

@ -1,3 +1,4 @@
DELETE FROM "action_permissions";
INSERT INTO "action_permissions" ("id", "action_name", "created_at", "updated_at", "collection_name", "user_id", "created_by_id", "updated_by_id", "role_name", "scope_id") VALUES
(1, 'create', '2021-09-14 02:19:38.019+00', '2021-09-14 02:19:45.64+00', 't_geso7fru7a9', NULL, 1, 1, NULL, NULL),
(2, 'get', '2021-09-14 02:19:38.04+00', '2021-09-14 02:19:45.64+00', 't_geso7fru7a9', NULL, 1, 1, NULL, NULL),
@ -15,4 +16,3 @@ INSERT INTO "action_permissions" ("id", "action_name", "created_at", "updated_at
(10, 'get', '2021-09-14 02:19:53.274+00', '2021-09-14 02:20:00.371+00', 't_fsveob6p269', NULL, 1, 1, NULL, NULL),
(11, 'update', '2021-09-14 02:19:53.284+00', '2021-09-14 02:20:00.371+00', 't_fsveob6p269', NULL, 1, 1, NULL, NULL),
(12, 'destroy', '2021-09-14 02:19:53.294+00', '2021-09-14 02:20:00.371+00', 't_fsveob6p269', NULL, 1, 1, NULL, NULL);
SELECT setval('action_permissions_id_seq', (SELECT MAX(id) FROM "action_permissions"), true);

View File

@ -1,3 +1,4 @@
DELETE FROM "field_permissions";
INSERT INTO "field_permissions" ("created_at", "updated_at", "action_permission_id", "field_key") VALUES
('2021-09-14 02:19:38.034+00', '2021-09-14 02:19:38.034+00', 1, 'nv0iw8wdxmz'),
('2021-09-14 02:19:38.034+00', '2021-09-14 02:19:38.034+00', 1, 'lu1ibrb0yi2'),

View File

@ -1,3 +1,4 @@
DELETE FROM "roles_ui_schemas";
INSERT INTO "roles_ui_schemas" ("created_at", "updated_at", "ui_schema_key", "role_name") VALUES
('2021-09-14 02:19:13.669+00', '2021-09-14 02:19:13.669+00', '0b73gccskc2', 'r_tfs4qtaxjcs'),
('2021-09-14 02:19:13.669+00', '2021-09-14 02:19:13.669+00', 'hall72478p5', 'r_tfs4qtaxjcs'),

View File

@ -0,0 +1,5 @@
SELECT setval('action_permissions_id_seq', (SELECT MAX(id) FROM "action_permissions"), true);
SELECT setval('attachments_id_seq', (SELECT MAX(id) FROM "attachments"), true);
SELECT setval('t_2uhu4szs1kq_id_seq', (SELECT MAX(id) FROM "t_2uhu4szs1kq"), true);
SELECT setval('t_fsveob6p269_id_seq', (SELECT MAX(id) FROM "t_fsveob6p269"), true);
SELECT setval('t_geso7fru7a9_id_seq', (SELECT MAX(id) FROM "t_geso7fru7a9"), true);

View File

@ -5,20 +5,46 @@ import { PluginOptions } from '@nocobase/server';
import { readFileSync } from 'fs';
import glob from 'glob';
function getInitSqls() {
const part1 = [];
const part2 = [];
const files1 = glob.sync(path.resolve(__dirname, './db/part1/*.sql'));
for (const file of files1) {
const sql = readFileSync(file).toString();
part1.push(sql);
export function getInitSqls(): {
[key: string]: string[];
} {
const dirs = ['part1', 'part2', 'postgres'];
return dirs
.map((dir) => {
return {
dir,
files: glob
.sync(path.resolve(__dirname, `./db/${dir}/*.sql`))
.map((fileName) => readFileSync(fileName).toString()),
};
})
.reduce((carry, dirFiles) => {
carry[dirFiles.dir] = dirFiles.files;
return carry;
}, {});
}
export function runSql(sql, database) {
const trimmed = sql.trim();
if (trimmed.length == 0) {
return;
}
const files2 = glob.sync(path.resolve(__dirname, './db/part2/*.sql'));
for (const file of files2) {
const sql = readFileSync(file).toString();
part2.push(sql);
return database.sequelize.query(trimmed, {
raw: true,
logging: false,
});
}
async function runSqlFile(content, database) {
for (const sqlGroup of content) {
for (const sql of sqlGroup.split(';')) {
try {
await runSql(sql, database);
} catch (e) {
console.error({ e, sql });
}
}
}
return { part1, part2 };
}
export default {
@ -39,33 +65,32 @@ export default {
}
});
const app = this.app;
this.app.on('db.init', async () => {
if (this.options.importDemo !== true) {
const cmd = app.findCommand('init');
if (cmd) {
cmd.option('--import-demo');
}
this.app.on('db.init', async (opts, cli) => {
const importDemo = opts.importDemo || this.options.importDemo;
console.log({ importDemo });
if (importDemo !== true) {
return;
}
const transaction = await app.db.sequelize.transaction();
const sqls = getInitSqls();
try {
for (const sql of sqls.part1) {
await app.db.sequelize.query(sql, { transaction });
}
await transaction.commit();
} catch (error) {
await transaction.rollback();
}
const database = app.db;
await runSqlFile(sqls.part1, database);
await app.db.getModel('collections').load({
skipExisting: true,
});
await app.db.sync();
const transaction2 = await app.db.sequelize.transaction();
try {
for (const sql of sqls.part2) {
await app.db.sequelize.query(sql, { transaction: transaction2 });
}
await transaction2.commit();
} catch (error) {
await transaction2.rollback();
await runSqlFile(sqls.part2, database);
if (app.db.sequelize.getDialect() == 'postgres') {
await runSqlFile(sqls.postgres, database);
}
});
}
},
} as PluginOptions;

View File

@ -8,8 +8,8 @@ describe('user fields', () => {
beforeEach(async () => {
api = mockServer();
api.registerPlugin('users', require('../server').default);
await api.loadPlugins();
api.plugin(require('../server').default);
await api.load();
db = api.db;
});

View File

@ -17,8 +17,8 @@ export default {
const User = database.getModel('users');
await User.create({
nickname: '超级管理员',
email: process.env.ADMIN_EMAIL,
password: process.env.ADMIN_PASSWORD,
email: process.env.ADMIN_EMAIL || 'admin@nocobase.com',
password: process.env.ADMIN_PASSWORD || 'admin',
});
});

View File

@ -2,8 +2,7 @@ import supertest from 'supertest';
import { Application } from '../application';
import { Plugin } from '../plugin';
class MyPlugin extends Plugin {
}
class MyPlugin extends Plugin {}
describe('application', () => {
let app: Application;
@ -12,21 +11,15 @@ describe('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',
},
dialect: 'sqlite',
dialectModule: require('sqlite3'),
storage: ':memory:',
},
resourcer: {
prefix: '/api',
},
dataWrapping: false,
registerActions: false,
});
app.resourcer.registerActionHandlers({
list: async (ctx, next) => {
@ -84,7 +77,7 @@ describe('application', () => {
{
type: 'hasMany',
name: 'bars',
}
},
],
});
const response = await agent.get('/api/foos/1/bars');
@ -92,7 +85,7 @@ describe('application', () => {
});
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.db.table({
name: 'tests',
@ -104,7 +97,7 @@ describe('application', () => {
});
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.db.table({
name: 'bars',
@ -115,7 +108,7 @@ describe('application', () => {
{
type: 'hasMany',
name: 'bars',
}
},
],
});
await next();

View File

@ -1,12 +1,15 @@
import Koa from 'koa';
import cors from '@koa/cors';
import bodyParser from 'koa-bodyparser';
import { Command, CommandOptions } from 'commander';
import Database, { DatabaseOptions, TableOptions } from '@nocobase/database';
import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
import { dataWrapping, table2resource } from './middlewares';
import { PluginType, Plugin, PluginOptions } from './plugin';
import { registerActions } from '@nocobase/actions';
import {
createCli,
createDatabase,
createResourcer,
registerMiddlewares,
} from './helper';
export interface ResourcerOptions {
prefix?: string;
@ -18,6 +21,7 @@ export interface ApplicationOptions {
bodyParser?: any;
cors?: any;
dataWrapping?: boolean;
registerActions?: boolean;
}
interface DefaultState {
@ -46,7 +50,7 @@ interface ActionsOptions {
export class Application<
StateT = DefaultState,
ContextT = DefaultContext,
ContextT = DefaultContext
> extends Koa {
public readonly db: Database;
@ -59,91 +63,14 @@ export class Application<
constructor(options: ApplicationOptions) {
super();
if (options.database instanceof Database) {
this.db = options.database;
} else {
this.db = new Database(options.database);
this.db = createDatabase(options);
this.resourcer = createResourcer(options);
this.cli = createCli(this, options);
registerMiddlewares(this, options);
if (options.registerActions !== false) {
registerActions(this);
}
this.resourcer = new Resourcer({ ...options.resourcer });
this.cli = new Command();
if (options.bodyParser !== false) {
this.use(
bodyParser({
...options.bodyParser,
}),
);
}
this.use(
cors({
exposeHeaders: ['content-disposition'],
...options.cors,
}),
);
this.use<DefaultState, DefaultContext>(async (ctx, next) => {
ctx.db = this.db;
ctx.resourcer = this.resourcer;
await next();
});
if (options.dataWrapping !== false) {
this.use(dataWrapping());
}
this.use(table2resource());
this.use(this.resourcer.restApiMiddleware());
registerActions(this);
this.cli
.command('db:sync')
.option('-f, --force')
.action(async (...args) => {
console.log('db sync...');
const cli = args.pop();
const force = cli.opts()?.force;
await this.db.sync(
force
? {
force: true,
alter: {
drop: true,
},
}
: {},
);
await this.destroy();
});
this.cli
.command('init')
// .option('-f, --force')
.action(async (...args) => {
const cli = args.pop();
await this.db.sync({
force: true,
alter: {
drop: true,
},
});
await this.emitAsync('db.init');
await this.destroy();
});
this.cli
.command('start')
.option('-p, --port [port]')
.action(async (...args) => {
const cli = args.pop();
console.log(args);
const opts = cli.opts();
await this.emitAsync('beforeStart');
this.listen(opts.port || 3000);
console.log(`http://localhost:${opts.port || 3000}/`);
});
}
use<NewStateT = {}, NewContextT = {}>(
@ -170,6 +97,10 @@ export class Application<
return this.cli.command(nameAndArgs, opts);
}
findCommand(name: string): Command {
return (this.cli as any)._findCommand(name);
}
plugin(options?: PluginType | PluginOptions, ext?: PluginOptions): Plugin {
if (typeof options === 'string') {
return this.plugin(require(options).default, ext);

View File

@ -0,0 +1,100 @@
import { DefaultContext, DefaultState } from 'koa';
import bodyParser from 'koa-bodyparser';
import cors from '@koa/cors';
import Database from '@nocobase/database';
import Resourcer from '@nocobase/resourcer';
import { Command } from 'commander';
import Application, { ApplicationOptions } from './application';
import { dataWrapping } from './middlewares/data-wrapping';
import { table2resource } from './middlewares/table2resource';
export function createDatabase(options: ApplicationOptions) {
if (options.database instanceof Database) {
return options.database;
} else {
return new Database(options.database);
}
}
export function createResourcer(options: ApplicationOptions) {
return new Resourcer({ ...options.resourcer });
}
export function createCli(app, options: ApplicationOptions) {
const cli = new Command();
cli
.command('db:sync')
.option('-f, --force')
.action(async (...args) => {
console.log('db sync...');
const cli = args.pop();
const force = cli.opts()?.force;
await app.db.sync(
force
? {
force: true,
alter: {
drop: true,
},
}
: {},
);
await app.destroy();
});
cli
.command('init')
.option('-f, --force')
.action(async (...args) => {
await app.db.sync({
force: true,
});
await app.emitAsync('db.init', ...args);
await app.destroy();
});
cli
.command('start')
.option('-p, --port [port]')
.action(async (...args) => {
const cli = args.pop();
const opts = cli.opts();
await app.emitAsync('beforeStart');
app.listen(opts.port || 3000);
console.log(`http://localhost:${opts.port || 3000}/`);
});
return cli;
}
export function registerMiddlewares(
app: Application,
options: ApplicationOptions,
) {
if (options.bodyParser !== false) {
app.use(
bodyParser({
...options.bodyParser,
}),
);
}
app.use(
cors({
exposeHeaders: ['content-disposition'],
...options.cors,
}),
);
app.use<DefaultState, DefaultContext>(async (ctx, next) => {
ctx.db = app.db;
ctx.resourcer = app.resourcer;
await next();
});
if (options.dataWrapping !== false) {
app.use(dataWrapping());
}
app.use(table2resource());
app.use(app.resourcer.restApiMiddleware());
}

305
yarn.lock
View File

@ -4080,10 +4080,17 @@
"@types/prop-types" "*"
"@types/react" "*"
"@types/react-dom@^16.9.8", "@types/react-dom@^17.0.0", "@types/react-dom@^17.0.3":
"@types/react-dom@^16.9.8":
version "16.9.14"
resolved "https://registry.nlark.com/@types/react-dom/download/@types/react-dom-16.9.14.tgz?cache=0&sync_timestamp=1629708935856&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Freact-dom%2Fdownload%2F%40types%2Freact-dom-16.9.14.tgz#674b8f116645fe5266b40b525777fc6bb8eb3bcd"
integrity sha1-Z0uPEWZF/lJmtAtSV3f8a7jrO80=
dependencies:
"@types/react" "^16"
"@types/react-dom@^17.0.3":
version "17.0.9"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==
resolved "https://registry.nlark.com/@types/react-dom/download/@types/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
integrity sha1-RBqYHanXvhFwQuGm/T2sSzD1Wt0=
dependencies:
"@types/react" "*"
@ -4156,7 +4163,7 @@
"@types/history" "*"
"@types/react" "*"
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^16.9.43", "@types/react@^17.0.0":
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17.0.0":
version "17.0.18"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.18.tgz#4109cbbd901be9582e5e39e3d77acd7b66bb7fbe"
integrity sha512-YTLgu7oS5zvSqq49X5Iue5oAbVGhgPc5Au29SJC4VeE17V6gASoOxVkUDy9pXFMRFxCWCD9fLeweNFizo3UzOg==
@ -4165,6 +4172,15 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^16", "@types/react@^16.9.43":
version "16.14.17"
resolved "https://registry.npmmirror.com/@types/react/download/@types/react-16.14.17.tgz?cache=0&sync_timestamp=1634220794290&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Freact%2Fdownload%2F%40types%2Freact-16.14.17.tgz#c57fcfb05efa6423f5b65fcd4a75f63f05b162bf"
integrity sha1-xX/PsF76ZCP1tl/NSnX2PwWxYr8=
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@ -5869,7 +5885,7 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.0.2:
base64-js@^1.0.2, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@ -5937,11 +5953,27 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
bl@^4.1.0:
version "4.1.0"
resolved "https://registry.nlark.com/bl/download/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha1-RRU1JkGCvsL7vIOmKrmM8R2fezo=
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
blessed@0.1.81:
version "0.1.81"
resolved "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129"
integrity sha1-+WLWh+wsNpVwrnGvhDJW5tDKESk=
block-stream@*:
version "0.0.9"
resolved "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
dependencies:
inherits "~2.0.0"
bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@ -6168,6 +6200,14 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.npm.taobao.org/buffer/download/buffer-5.7.1.tgz?cache=0&sync_timestamp=1606098073225&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbuffer%2Fdownload%2Fbuffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha1-umLnwTEzBTWCGXFghRqPZI6Z7tA=
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
builtin-modules@^3.0.0, builtin-modules@^3.1.0:
version "3.2.0"
resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
@ -6617,6 +6657,18 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.nlark.com/cli-cursor/download/cli-cursor-3.1.0.tgz?cache=0&sync_timestamp=1629747481175&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcli-cursor%2Fdownload%2Fcli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
integrity sha1-JkMFp65JDR0Dvwybp8kl0XU68wc=
dependencies:
restore-cursor "^3.1.0"
cli-spinners@^2.5.0:
version "2.6.1"
resolved "https://registry.npmmirror.com/cli-spinners/download/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d"
integrity sha1-rclU6+KBw3pjGb+kAebdJIj/tw0=
cli-tableau@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz#baa78d83e08a2d7ab79b7dad9406f0254977053f"
@ -6864,6 +6916,11 @@ commander@^2.19.0, commander@^2.20.0:
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^8.2.0:
version "8.2.0"
resolved "https://registry.nlark.com/commander/download/commander-8.2.0.tgz#37fe2bde301d87d47a53adeff8b5915db1381ca8"
integrity sha1-N/4r3jAdh9R6U63v+LWRXbE4HKg=
commander@~2.14.1:
version "2.14.1"
resolved "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
@ -7339,7 +7396,7 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -7994,7 +8051,7 @@ detect-indent@^5.0.0:
resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
detect-libc@^1.0.3:
detect-libc@^1.0.2, detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
@ -8990,6 +9047,21 @@ execa@^4.0.0:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
execa@^5.1.1:
version "5.1.1"
resolved "https://registry.nlark.com/execa/download/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha1-+ArZy/Qpj3vR1MlVXCHpN0HEEd0=
dependencies:
cross-spawn "^7.0.3"
get-stream "^6.0.0"
human-signals "^2.1.0"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.1"
onetime "^5.1.2"
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
exenv@^1.2.0:
version "1.2.2"
resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
@ -9537,6 +9609,16 @@ fsevents@~2.1.2:
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
fstream@^1.0.0, fstream@^1.0.12:
version "1.0.12"
resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
dependencies:
graceful-fs "^4.1.2"
inherits "~2.0.0"
mkdirp ">=0.5 0"
rimraf "2"
ftp@^0.3.10:
version "0.3.10"
resolved "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
@ -9677,6 +9759,11 @@ get-stream@^5.0.0, get-stream@^5.1.0:
dependencies:
pump "^3.0.0"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.nlark.com/get-stream/download/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha1-omLY7vZ6ztV8KFKtYWdSakPL97c=
get-uri@3:
version "3.0.2"
resolved "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c"
@ -9808,6 +9895,18 @@ glob-to-regexp@^0.3.0:
resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
glob@^7.0.3:
version "7.2.0"
resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.7"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
@ -10483,6 +10582,11 @@ human-signals@^1.1.1:
resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.nlark.com/human-signals/download/human-signals-2.1.0.tgz?cache=0&sync_timestamp=1624364695595&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fhuman-signals%2Fdownload%2Fhuman-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha1-3JH8ukLk0G5Kuu0zs+ejwC9RTqA=
humanize-ms@^1.2.0, humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
@ -10516,7 +10620,7 @@ identity-obj-proxy@3.0.0:
dependencies:
harmony-reflect "^1.4.6"
ieee754@^1.1.4:
ieee754@^1.1.13, ieee754@^1.1.4:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@ -10683,7 +10787,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -11062,6 +11166,11 @@ is-installed-globally@^0.3.1:
global-dirs "^2.0.1"
is-path-inside "^3.0.1"
is-interactive@^1.0.0:
version "1.0.0"
resolved "https://registry.nlark.com/is-interactive/download/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
integrity sha1-zqbmrlyHCnsKAAQHC3tYfgJSkS4=
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
@ -11269,6 +11378,11 @@ is-unc-path@^1.0.0:
dependencies:
unc-path-regex "^0.1.2"
is-unicode-supported@^0.1.0:
version "0.1.0"
resolved "https://registry.nlark.com/is-unicode-supported/download/is-unicode-supported-0.1.0.tgz?cache=0&sync_timestamp=1625294161966&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fis-unicode-supported%2Fdownload%2Fis-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha1-PybHaoCVk7Ur+i7LVxDtJ3m1Iqc=
is-url@1.2.4:
version "1.2.4"
resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
@ -12949,6 +13063,14 @@ log-driver@^1.2.7:
resolved "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
log-symbols@^4.1.0:
version "4.1.0"
resolved "https://registry.npm.taobao.org/log-symbols/download/log-symbols-4.1.0.tgz?cache=0&sync_timestamp=1618723146520&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flog-symbols%2Fdownload%2Flog-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
integrity sha1-P727lbRoOsn8eFER55LlWNSr1QM=
dependencies:
chalk "^4.1.0"
is-unicode-supported "^0.1.0"
long-timeout@0.1.1:
version "0.1.1"
resolved "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514"
@ -13686,7 +13808,7 @@ mkdirp@*, mkdirp@1.0.4, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1:
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1:
version "0.5.5"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@ -13885,6 +14007,15 @@ needle@2.4.0:
iconv-lite "^0.4.4"
sax "^1.2.4"
needle@^2.2.1:
version "2.9.1"
resolved "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684"
integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@ -13923,7 +14054,7 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
node-addon-api@^3.1.0:
node-addon-api@^3.0.0, node-addon-api@^3.1.0:
version "3.2.1"
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
@ -13955,6 +14086,24 @@ node-fetch@^2.5.0, node-fetch@^2.6.1:
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-gyp@3.x:
version "3.8.0"
resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
dependencies:
fstream "^1.0.0"
glob "^7.0.3"
graceful-fs "^4.1.2"
mkdirp "^0.5.0"
nopt "2 || 3"
npmlog "0 || 1 || 2 || 3 || 4"
osenv "0"
request "^2.87.0"
rimraf "2"
semver "~5.3.0"
tar "^2.0.0"
which "1"
node-gyp@^5.0.2:
version "5.1.1"
resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e"
@ -14034,6 +14183,22 @@ node-notifier@^8.0.0:
uuid "^8.3.0"
which "^2.0.2"
node-pre-gyp@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
needle "^2.2.1"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4"
node-releases@^1.1.73:
version "1.1.74"
resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.74.tgz#e5866488080ebaa70a93b91144ccde06f3c3463e"
@ -14086,6 +14251,13 @@ nodemon@^2.0.12:
undefsafe "^2.0.3"
update-notifier "^4.1.0"
"nopt@2 || 3":
version "3.0.6"
resolved "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k=
dependencies:
abbrev "1"
nopt@^4.0.1:
version "4.0.3"
resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
@ -14218,7 +14390,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
semver "^5.6.0"
validate-npm-package-name "^3.0.0"
npm-packlist@^1.4.4:
npm-packlist@^1.1.6, npm-packlist@^1.4.4:
version "1.4.8"
resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
@ -14243,14 +14415,14 @@ npm-run-path@^2.0.0:
dependencies:
path-key "^2.0.0"
npm-run-path@^4.0.0:
npm-run-path@^4.0.0, npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^3.0.0"
npmlog@^4.1.2:
"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2, npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@ -14426,7 +14598,7 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
onetime@^5.1.0:
onetime@^5.1.0, onetime@^5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
@ -14462,6 +14634,21 @@ optionator@^0.9.1:
type-check "^0.4.0"
word-wrap "^1.2.3"
ora@^5.4.1:
version "5.4.1"
resolved "https://registry.nlark.com/ora/download/ora-5.4.1.tgz?cache=0&sync_timestamp=1631556513877&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fora%2Fdownload%2Fora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
integrity sha1-GyZ4Qmr0rEpQkAjl5KyemVnbnhg=
dependencies:
bl "^4.1.0"
chalk "^4.1.0"
cli-cursor "^3.1.0"
cli-spinners "^2.5.0"
is-interactive "^1.0.0"
is-unicode-supported "^0.1.0"
log-symbols "^4.1.0"
strip-ansi "^6.0.0"
wcwidth "^1.0.1"
ordered-read-streams@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
@ -14500,7 +14687,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
osenv@^0.1.4, osenv@^0.1.5:
osenv@0, osenv@^0.1.4, osenv@^0.1.5:
version "0.1.5"
resolved "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
@ -16793,7 +16980,7 @@ rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0:
rc-resize-observer "^1.0.0"
rc-util "^5.0.7"
rc@^1.2.8:
rc@^1.2.7, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@ -17217,7 +17404,7 @@ readable-stream@1.1.x:
isarray "0.0.1"
string_decoder "~0.10.x"
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.6.0:
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -17709,6 +17896,14 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.nlark.com/restore-cursor/download/restore-cursor-3.1.0.tgz?cache=0&sync_timestamp=1629746923086&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frestore-cursor%2Fdownload%2Frestore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
integrity sha1-OfZ8VLOnpYzqUjbZXPADQjljH34=
dependencies:
onetime "^5.1.0"
signal-exit "^3.0.2"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@ -17748,6 +17943,13 @@ right-align@^0.1.1:
dependencies:
align-text "^0.1.1"
rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
dependencies:
glob "^7.1.3"
rimraf@2.6.3:
version "2.6.3"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@ -17755,13 +17957,6 @@ rimraf@2.6.3:
dependencies:
glob "^7.1.3"
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
dependencies:
glob "^7.1.3"
rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@ -18239,6 +18434,11 @@ semver@7.x, semver@^7.2, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.
dependencies:
lru-cache "^6.0.0"
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
semver@~7.2.0:
version "7.2.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.2.3.tgz#3641217233c6382173c76bf2c7ecd1e1c16b0d8a"
@ -18254,6 +18454,25 @@ sequelize-pool@^6.0.0:
resolved "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz#caaa0c1e324d3c2c3a399fed2c7998970925d668"
integrity sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==
sequelize@6.7.0:
version "6.7.0"
resolved "https://registry.npmmirror.com/sequelize/download/sequelize-6.7.0.tgz#6af3149ad9b39c4bfc8b563f25e1598e2a0cf881"
integrity sha1-avMUmtmznEv8i1Y/JeFZjioM+IE=
dependencies:
debug "^4.1.1"
dottie "^2.0.0"
inflection "1.13.1"
lodash "^4.17.20"
moment "^2.26.0"
moment-timezone "^0.5.31"
retry-as-promised "^3.2.0"
semver "^7.3.2"
sequelize-pool "^6.0.0"
toposort-class "^1.0.1"
uuid "^8.1.0"
validator "^13.6.0"
wkx "^0.5.0"
sequelize@^6.3.3:
version "6.6.5"
resolved "https://registry.npmjs.org/sequelize/-/sequelize-6.6.5.tgz#6f618e99f3df1fc81f28709e8a3139cec3ef1f0c"
@ -18685,6 +18904,16 @@ sprintf-js@~1.0.2:
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
sqlite3@^5.0.2:
version "5.0.2"
resolved "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.2.tgz#00924adcc001c17686e0a6643b6cbbc2d3965083"
integrity sha512-1SdTNo+BVU211Xj1csWa8lV6KM0CtucDwRyA0VHl91wEH1Mgh7RxUpI4rVvG7OhHrzCSGaVyW5g8vKvlrk9DJA==
dependencies:
node-addon-api "^3.0.0"
node-pre-gyp "^0.11.0"
optionalDependencies:
node-gyp "3.x"
sqlstring@^2.3.2:
version "2.3.2"
resolved "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514"
@ -19229,6 +19458,28 @@ tapable@^1.0.0:
resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tar@^2.0.0:
version "2.2.2"
resolved "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==
dependencies:
block-stream "*"
fstream "^1.0.12"
inherits "2"
tar@^4:
version "4.4.19"
resolved "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3"
integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==
dependencies:
chownr "^1.1.4"
fs-minipass "^1.2.7"
minipass "^2.9.0"
minizlib "^1.3.3"
mkdirp "^0.5.5"
safe-buffer "^5.2.1"
yallist "^3.1.1"
tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
version "4.4.17"
resolved "https://registry.npmjs.org/tar/-/tar-4.4.17.tgz#44be5e3fa8353ee1d11db3b1401561223a5c3985"
@ -20599,7 +20850,7 @@ warning@^4.0.1, warning@^4.0.3:
dependencies:
loose-envify "^1.0.0"
wcwidth@^1.0.0:
wcwidth@^1.0.0, wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
@ -20699,7 +20950,7 @@ which-module@^2.0.0:
resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which@^1.2.9, which@^1.3.0, which@^1.3.1:
which@1, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==