tachybase_todo/docs/cores/packages/database.md
chenos d5d0e1036b
docs: add docs (#75)
* docs: add docs

* ignore dumi theme test

* fix: error TS2717: Subsequent property declarations must have the same type.

* update docs

* deploy gh-pages

* plugins docs

* hash & cname

* exportStatic

* ssr

* vercel

* vercel

* fix: deploy vercel

* Delete vercel.json

* docs

* fix APP_DIST

* on master branch
2021-04-17 21:33:21 +08:00

11 KiB
Raw Blame History

title order group
@nocobase/database 1
order title
1 核心库

@nocobase/database

介绍

用于配置数据表Table提供数据操作Model可单独使用。核心主要有两个东西

  • Table描述数据的类型、结构和关系
  • Model数据库操作
Model 是动态的,由 Table 初始化一般并不需要单独配置。Model 目前已经适配了 Sequelize.Model如果需要后续还可以适配 Mongoose.Model 等。Table 也可以更进一步抽象,提供更灵活的适配方法。

安装

yarn add @nocobase/database

Usage

import Database from '@nocobase/database';

const db = new Database({
  // 省略配置信息
});

const table = db.table({
  name: 'posts',
  fields: [
    { type: 'string', name: 'title' },
  ],
});

table.addField({ type: 'text', name: 'content' });

await table.sync();

const Post = db.getModel('posts');

const post = await Post.create({
  title: '这是标题',
  content: '这是内容',
});

Field Types

在 NocoBase 里Field Type 表示字段的数据类型。

在 JSON 里,数据有六种基本类型:

  • boolean
  • string
  • number
  • object
  • array
  • null

其中表示数据的结构有两种:

  • object
  • array

两种结构可以内嵌再结合其他数据类型可以呈现出无数种结构化的数据。JSON 数据是可以直接储存的,但是可能会产生冗余,所以在关系型数据库的设计规范中,会根据需要把 object 或 array 的数据存放在另一张表里,然后通过主外键字段建立关联。在关系型数据库里,数据有四种基本关系:

  • O2O/hasOne - 一对一
  • O2M/hasMany - 一对多
  • M2O/belongsTo - 多对一
  • M2M/belongsToMany - 多对多
  • Polymorphic - 多态(反模式)

字段的数据类型Field Type也由此扩展而来以下为 JSON 类型与字段数据类型的对应关系:

  • booleanboolean
  • stringstring、text、time、date
  • numberinteger、float、double、decimal、real
  • objecthasOne、belongsTo
  • arrayhasMany、belongsToMany
  • null不填写时为空需要区分空字符串
  • json一种特殊的数据类型即使是根节点也可以存任意类型数据

除此之外,字段类型还提供了 virtual 的情况,是一种虚拟字段

boolean

布尔型

integer

整型

float

单精度浮点型

double

双精度浮点型

decimal

货币类型

string

字符串

text

长文本

json 待完善

JSON 是个特殊的数据类型,在不同数据库里有所不同,目前只兼容了 PostgreSQL 的一些情况。

time

时间

date

日期

virtual

虚拟字段

interface VirtualOptions {

  name: string;

  type: 'virtual';

  get?(this: M): unknown;

  set?(this: M, val: unknown): void;
}

formula 待完善

计算公式,一种特殊的虚拟字段

interface FormulaOptions {

  name: string;

  type: 'formula';

  /**
   * 计算公式
   */
  formula: string;

  /**
   * 输出格式
   */
  format: 'string' | 'number';
}

reference 未实现

引用

interface ReferenceOptions {

  name: string;

  type: 'reference';

  /**
   * 数据路径
   */
  dataIndex: string;
}

password

密码

interface PasswordOptions {

  name: string;

  type: 'password';

  /**
   * 暂时只支持 bcrypt 算法
   * 
   * 默认值bcrypt
   */
  algo: 'bcrypt' | 'argon2';
}

sort

顺序类型

interface SortOptions {

  name: string;

  type: 'sort';

  /**
   * 限定范围
   */
  scope?: string[];

  /**
   * 新值创建策略
   * 
   * max: 使用最大值
   * min: 使用最小值
   * 
   * Defaults to 'max'
   */
  next?: 'min' | 'max';
}

radio

radio 类型

interface RadioOptions {

  name: string;

  type: 'radio';

  /**
   * 限定范围
   */
  scope?: string[];
}

hasOne

一对一

interface HasManyOptions {

  type: 'hasMany';

  /**
   * 关系字段名称
   */
  name: string;

  /**
   * 目标数据表名称
   * 
   * 默认值:<name>
   */
  target?: string;

  /**
   * 目标数据表字段(外键字段)
   * 
   * 默认值:<targetName>_<targetPrimaryKeyAttribute>
   */
  foreignKey?: string;

  /**
   * 来源数据表字段(一般为主键字段)
   * 
   * 默认值:<sourcePrimaryKeyAttribute>
   */
  sourceKey?: string;
}

hasMany

一对多

interface HasManyOptions {

  type: 'hasMany';

  /**
   * 关系字段名称
   */
  name: string;

  /**
   * 目标数据表名称
   * 
   * 默认值:<name>
   */
  target?: string;

  /**
   * 目标数据表字段(外键字段)
   * 
   * 默认值:<targetName>_<targetPrimaryKeyAttribute>
   */
  foreignKey?: string;

  /**
   * 来源数据表字段(一般为主键字段)
   * 
   * 默认值:<sourcePrimaryKeyAttribute>
   */
  sourceKey?: string;
}

belongsTo

多对一

interface BelongsToOptions {

  type: 'belongsTo';

  /**
   * 关系字段名称
   */
  name: string;

  /**
   * 目标数据表名称
   * 
   * 默认值:<name plural>
   */
  target?: string;

  /**
   * 来源数据表字段(外键字段)
   * 
   * 默认值:<sourceName>_<sourcePrimaryKeyAttribute>
   */
  foreignKey?: string;

  /**
   * 目标数据表字段(一般为主键字段)
   * 
   * 默认值:<targetPrimaryKeyAttribute>
   */
  targetKey?: string;
}

belongsToMany

多对多

interface BelongsToManyOptions {

  type: 'belongsToMany';

  /**
   * 关系字段名称
   */
  name: string;


  /**
   * 目标数据表名称
   * 
   * 默认值:<name>
   */
  target?: string;

  /**
   * 中间表名称
   * 
   * 默认值sourceName 和 targetName 按字母顺序排序之后连接
   */
  through?: string;

  /**
   * 来源数据表字段(一般为主键字段)
   * 
   * 默认值:<sourcePrimaryKeyAttribute>
   */
  sourceKey?: string;

  /**
   * 来源数据表字段(外键字段)
   * 
   * 默认值:<sourceName>_<sourcePrimaryKeyAttribute>
   */
  foreignKey?: string;

  /**
   * 目标数据表字段(一般为主键字段)
   * 
   * 默认值:<targetPrimaryKeyAttribute>
   */
  targetKey?: string;

  /**
   * 目标数据表字段(外键字段)
   * 
   * 默认值:<targetName>_<targetPrimaryKeyAttribute>
   */
  otherKey?: string;
}

字段扩展

以密码字段为例:

import bcrypt from 'bcrypt';
import { StringOptions, FieldContext, registerFields, getField } from '@nocobase/database';

export class PASSWORD extends STRING {

  getDataType() {
    return DataTypes.STRING;
  }

  constructor(options: StringOptions, context: FieldContext) {
    super(options, context);
    const Model = context.sourceTable.getModel();
    Model.addHook('beforeCreate', PASSWORD.hash.bind(this));
    Model.addHook('beforeUpdate', PASSWORD.hash.bind(this));
  }

  static async hash(this: PASSWORD, model) {
    const { name } = this.options;
    if (!model.changed(name as any)) {
      return;
    }
    const value = model.get(name) as string;
    if (value) {
      if (value.startsWith('$2b$10$') && value.length === 60) {
        return;
      }
      const hash = await bcrypt.hash(value, 10);
      model.set(name, hash);
    } else {
      model.set(name, null);
    }
  }

  static async verify(value: string, hash: string) {
    return await bcrypt.compare(value, hash);
  }
}

registerFields({ PASSWORD });

const db = new Database({
  // 省略配置信息
});

const table = db.table({
  name: 'users',
  fields: [
    { type: 'password', name: 'password' },
  ],
});

await table.sync();

const User = db.getModel('users');

const user = User.create({
  password: '123456',
});

const Pwd = getField('password');

await Pwd.verify('123456', user.password); // true

API

registerField

注册字段

registerFields

批量注册字段,用法同 registerField

getField

获取已注册字段类

虽然直接 import/require 已导出的字段类型也可以,但是更推荐用 getField 来处理。

registerModel

注册模型

registerModels

批量注册模型,用法同 registerModel

getRegisteredModel

获取已注册字段类型

extend 实验性

与 database.import 配合使用

Operator.register 实验性

注册自定义 op

database.constructor

初始化实例

database.import

批量导入配置

database.table

配置数据表

database.extend

配置扩展

database.isDefined

判断数据表是否已定义

database.getModel

获取已定义的 Model

database.getModels

批量获取已定义的 Models

database.getTable

获取已定义的 Table

database.getTables

批量获取已定义的 Tables

database.sync

数据表配置与数据库表结构同步

database.close

关闭数据库连接

database.addHook 待完善

为数据表配置提供的钩子,目前 HookType 有:

  • beforeTableInit
  • afterTableInit
  • beforeAddField
  • afterAddField

database.runHooks 待完善

运行当前钩子挂载的函数

table.getOptions

获取数据表配置

table.getModel

获取当前配置表对应的 Model

table.hasField

判断字段是否存在

table.getField

获取已配置字段

table.addField

新增字段(附加操作)

table.setFields

批量新增字段(替换操作)

table.getFields

获取当前表字段列表

table.addIndex

建立索引

table.addIndexes

批量建立索引,同 table.addIndex

table.extend

配置扩展

table.sync

当前表配置与数据库表结构同步

Model.database

获取当前 database 实例

Model.parseApiJson

将 filter、fields、sort 等参数转换为 where、attributes、include、order 等

filter

过滤条件,支持两种格式

{
  filter: {
    [field: string]: {
      [operator: Op]: any,
    }
  },
}

逻辑运算符 - and/or

{
  ['and'|'or']: [
    {
      [field: string]: {
        [operator: Op]: any,
      }
    },
    {
      ['and'|'or']: [
        {
          [field: string]: {
            [operator: Op]: any,
          }
        },
      ],
    },
  ],
}

fields

简单用法(同 only 用法):

{
  fields: ['col1', 'col2'],
}

复杂用法:

{
  fields: {
    // 附加,一般用于关系字段(默认不输出关系字段数据)
    appends: ['col1', 'col2'],
    // 白名单
    only: ['col1', 'col2'],
    // 黑名单
    except: ['col1', 'col2'],
  },
}

sort

排序

{
  sort: [
    '-created_at', // 优先创建时间倒序
    'id' // 其次 id 正序
  ],
}

model.database

获取当前 database 实例

model.updateAssociations

更新关系数据

model.getValuesByFieldNames 实验性

获取当前 model 在 scope 范围内的值