diff --git a/docs/en-US/api/acl/acl-role.md b/docs/en-US/api/acl/acl-role.md index 708131702..c6d8a1bbe 100644 --- a/docs/en-US/api/acl/acl-role.md +++ b/docs/en-US/api/acl/acl-role.md @@ -1,4 +1,4 @@ -# ACL Role +# ACLRole ACLRole,ACL 系统中的用户角色类。在 ACL 系统中,通常使用 `acl.define` 定义角色。 diff --git a/docs/en-US/api/acl/acl.md b/docs/en-US/api/acl/acl.md index dc55f7fca..bdaa1d968 100644 --- a/docs/en-US/api/acl/acl.md +++ b/docs/en-US/api/acl/acl.md @@ -1,8 +1,29 @@ # ACL +## 概览 + ACL 为 Nocobase 中的权限控制模块。在 ACL 中注册角色、资源以及配置相应权限之后,即可对角色进行权限判断。 -## 概念解释 +### 基本使用 + +```javascript +const { ACL } = require('@nocobase/acl'); + +const acl = new ACL(); + +// 定义一个名称为 member 的角色 +const memberRole = acl.define({ + role: 'member', +}); + +// 使 member 角色拥有 posts 资源的 list 权限 +memberRole.grantAction('posts:list'); + +acl.can('member', 'posts:list'); // true +acl.can('member', 'posts:edit'); // null +``` + +### 概念解释 * 角色 (`ACLRole`):权限判断的对象 * 资源 (`ACLResource`):在 Nocobase ACL 中,资源通常对应一个数据库表,概念上可类比为 Restful API 中的 Resource。 diff --git a/docs/en-US/api/actions.md b/docs/en-US/api/actions.md index 4f64fb1cd..c752fc74e 100644 --- a/docs/en-US/api/actions.md +++ b/docs/en-US/api/actions.md @@ -1,20 +1,27 @@ # 内置常用资源操作 +## 概览 + 针对常用的 CRUD 等数据资源的操作,NocoBase 内置了对应操作方法,并通过数据表资源自动映射相关的操作。 -所有的操作方法都是注册在 resourcer 实例上,也是标准兼容 Koa 的中间件函数(`(ctx, next) => Promise`)。操作的参数由路由解析后附加在 `ctx.action` 对象上,后续参数相关介绍均基于此对象。 +```javascript +import { Application } from "@nocobase/server"; -通常情况下无需直接调用内置的 action 方法,在需要扩展默认操作行为时,可以在自定义的操作方法内调用默认方法。 +const app = new Application({ + database: { + dialect: 'sqlite', + storage: './db.sqlite', + }, + registerActions: true // 注册内置资源操作,默认为 True +}); -## 包结构 - -可通过以下方式引入相关实体: - -```ts -import actions from '@nocobase/actions'; ``` -## 单一数据资源操作 + +内置的操作方法都是注册在 `application` 中的 `resourcer` 实例上。 +通常情况下无需直接调用内置的 action 方法,在需要扩展默认操作行为时,可以在自定义的操作方法内调用默认方法。 + +## 资源操作 ### `list()` diff --git a/docs/en-US/api/database/collection.md b/docs/en-US/api/database/collection.md index 8be6651ea..67f9918dd 100644 --- a/docs/en-US/api/database/collection.md +++ b/docs/en-US/api/database/collection.md @@ -1,13 +1,40 @@ # Collection -数据表结构管理类。 +## 概览 -大部分接口通常不会直接由开发者调用,除非进行较底层的扩展开发。 +`Collection` 用于定义系统中的数据模型,如模型名称、字段、索引、关联等信息。 +一般通过 `Database` 实例的 `collection` 方法作为代理入口调用。 + +```javascript +const { Database } = require('@nocobase/database') + +// 创建数据库实例 +const db = new Database({...}); + +// 定义数据模型 +db.collection({ + name: 'users', + // 定义模型字段 + fields: [ + // 标量字段 + { + name: 'name', + type: 'string', + }, + + // 关联字段 + { + name: 'profile', + type: 'hasOne' // 'hasMany', 'belongsTo', 'belongsToMany' + } + ], +}); +``` + +更多字段类型请参考 [Fields](/api/database/field.md)。 ## 构造函数 -通常不会直接使用,主要通过 `Database` 实例的 `collection` 方法作为代理入口调用。 - **签名** * `constructor(options: CollectionOptions, context: CollectionContext)` diff --git a/docs/en-US/api/database/field.md b/docs/en-US/api/database/field.md index 340b97cde..c868aff3f 100644 --- a/docs/en-US/api/database/field.md +++ b/docs/en-US/api/database/field.md @@ -1,7 +1,11 @@ # Field +## 概览 + 数据表字段管理类(抽象类)。同时是所有字段类型的基类,其他任意字段类型均通过继承该类来实现。 +如何自定义字段可参考[扩展字段类型](/development/guide/collections-fields#扩展字段类型) + ## 构造函数 通常不会直接由开发者调用,主要通过 `db.collection({ fields: [] })` 方法作为代理入口调用。 diff --git a/docs/en-US/api/database/index.md b/docs/en-US/api/database/index.md index 975fa52aa..2ec633ebc 100644 --- a/docs/en-US/api/database/index.md +++ b/docs/en-US/api/database/index.md @@ -1,23 +1,106 @@ # Database -NocoBase 内置的数据库访问类,通过封装 [Sequelize](https://sequelize.org/) 提供了更加简单的数据库访问接口和统一化的 JSON 数据库表配置方式,同时也提供了扩展字段类型和查询操作符的能力。 +## 概览 -Database 类继承自 EventEmitter,可以通过 `db.on('event', callback)` 监听数据库事件,以及 `db.off('event', callback)` 移除监听。 +Database 是 Nocobase 提供的数据库交互工具,为无代码、低代码应用提供了非常方便的数据库交互功能。目前支持的数据库为: -## 包结构 +* SQLite 3.8.8+ +* MySQL 8.0.17+ +* PostgreSQL 10.0+ -可通过以下方式引入相关实体: -```ts -import Database, { - Field, - Collection, - Repository, - RelationRepository, - extend -} from '@nocobase/database'; +### 连接数据库 + +在 `Database` 构造函数中,可以通过传入 `options` 参数来配置数据库连接。 + +```javascript +const { Database } = require('@nocobase/database'); + +// SQLite 数据库配置参数 +const database = new Database({ + dialect: 'sqlite', + storage: 'path/to/database.sqlite' +}) + +// MySQL \ PostgreSQL 数据库配置参数 +const database = new Database({ + dialect: /* 'postgres' 或者 'mysql' */, + database: 'database', + username: 'username', + password: 'password', + host: 'localhost', + port: 'port' +}) + ``` +详细的配置参数请参考 [构造函数](#构造函数)。 + +### 数据模型定义 + +`Database` 通过 `Collection` 定义数据库结构,一个 `Collection` 对象代表了数据库中的一张表。 + +```javascript +// 定义 Collection +const UserCollection = database.collection({ + name: 'users', + fields: [ + { + name: 'name', + type: 'string', + }, + { + name: 'age', + type: 'integer', + }, + ], +}); + +``` + +数据库结构定义完成之后,可使用 `sync()` 方法来同步数据库结构。 + +```javascript +await database.sync(); +``` + +更加详细的 `Collection` 使用方法请参考 [Collection](/api/database/collection.md)。 + +### 数据读写 + +`Database` 通过 `Repository` 对数据进行操作。 + +```javascript + +const UserRepository = UserCollection.repository(); + +// 创建 +await UserRepository.create({ + name: '张三', + age: 18, +}); + +// 查询 +const user = await UserRepository.findOne({ + filter: { + name: '张三', + }, +}); + +// 修改 +await UserRepository.update({ + values: { + age: 20, + }, +}); + +// 删除 +await UserRepository.destroy(user.id); +``` + +更加详细的数据 CRUD 使用方法请参考 [Repository](/api/database/repository.md)。 + + ## 构造函数 **签名** @@ -28,7 +111,6 @@ import Database, { **参数** -`options` 参数与 [Sequelize 的构造参数](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-constructor-constructor)一致的部分会透传至 Sequelize,同时 NocoBase 也会使用一些额外的参数: | 参数名 | 类型 | 默认值 | 描述 | | --- | --- | --- | --- | @@ -44,46 +126,6 @@ import Database, { | `options.tablePrefix?` | `string` | `''` | NocoBase 扩展,表名前缀 | | `options.migrator?` | `UmzugOptions` | `{}` | NocoBase 扩展,迁移管理器相关参数,参考 [Umzug](https://github.com/sequelize/umzug/blob/main/src/types.ts#L15) 实现 | -**示例** - -```ts -import Database from '@nocobase/database'; - -const app = new Database({ - dialect: 'mysql', - host: 'localhost', - port: 3306, - username: 'root', - password: '123456', - database: 'test', - tablePrefix: 'my_' -}); -``` - -## 实例成员 - -### `sequelize` - -初始化后的 Sequelize 实例,在需要使用 sequelize 底层方法时可以调用,相关信息可以直接参考 sequelize 的文档。 - -### `options` - -初始化的配置参数,包含了 Sequelize 的配置参数和 NocoBase 的额外配置参数。 - -### `version` - -连接的数据库的版本信息对象,可通过 `await db.version.satisfies()` 检查是否满足特定数据库版本要求。 - -**示例** - -```ts -const r = await this.db.version.satisfies({ - mysql: '>=8.0.17', - sqlite: '3.x', - postgres: '>=10', -}); -``` - ## 迁移相关方法 ### `addMigration()` diff --git a/docs/en-US/api/database/operators.md b/docs/en-US/api/database/operators.md index 468d5f0e4..7bb84f1ab 100644 --- a/docs/en-US/api/database/operators.md +++ b/docs/en-US/api/database/operators.md @@ -1,6 +1,6 @@ # Filter Operators -用于 Repository 的 find、findOne、findAndCount、count 等 API 的 filter 参数里。如: +用于 Repository 的 find、findOne、findAndCount、count 等 API 的 filter 参数中: ```ts const repository = db.getRepository('books'); @@ -14,9 +14,7 @@ repository.find({ }); ``` -相当于 Sequelize Where 查询的 [Op](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#operators) 对象。 - -为了支持 JSON 化,NocoBase 中将查询运算符转换为以 $ 为前缀的字符串标识。 +为了支持 JSON 化,NocoBase 中将查询运算符以 $ 为前缀的字符串标识。 另外,NocoBase 也提供了扩展运算符的 API,详见 [`db.registerOperators()`](../database#registeroperators)。 diff --git a/docs/en-US/api/database/relation-repository/belongs-to-repository.md b/docs/en-US/api/database/relation-repository/belongs-to-repository.md index 15824a3d2..30373bba0 100644 --- a/docs/en-US/api/database/relation-repository/belongs-to-repository.md +++ b/docs/en-US/api/database/relation-repository/belongs-to-repository.md @@ -1,3 +1,4 @@ ## BelongsToRepository -`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。其接口与 [HasOneRepository](./has-one-repository.md) 一致。 +其接口与 [HasOneRepository](./has-one-repository.md) 一致。 +`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。 diff --git a/docs/en-US/api/database/relation-repository/has-one-repository.md b/docs/en-US/api/database/relation-repository/has-one-repository.md index fd1037604..66ea4a65e 100644 --- a/docs/en-US/api/database/relation-repository/has-one-repository.md +++ b/docs/en-US/api/database/relation-repository/has-one-repository.md @@ -1,4 +1,6 @@ # HasOneRepository +## 概览 + `HasOneRepository` 为 `HasOne` 类型的关联 Repository。 ```typescript @@ -19,8 +21,14 @@ const user = await User.repository.create({ values: { name: 'u1' }, }); -// 创建 HasOneRepository 实例 -const userProfileRepository = new HasOneRepository(User, 'profile', user.get('id')); + +// 获取到关联 Repository +const userProfileRepository = User.repository.relation('profile').of(user.get('id')); + +// 也可直接初始化 +new HasOneRepository(User, 'profile', user.get('id')); + + ``` diff --git a/docs/en-US/api/database/repository.md b/docs/en-US/api/database/repository.md index f80fde92f..d7ca19c95 100644 --- a/docs/en-US/api/database/repository.md +++ b/docs/en-US/api/database/repository.md @@ -1,6 +1,331 @@ # Repository -数据表数据仓库管理类。大部分基于数据表的数据存取等操作均通过该类实现。 +## 概览 + +在一个给定的 `Collection` 对象上,可以获取到它的 `Repository` 对象来对数据表进行读写操作。 + +```javascript +const { UserCollection } = require("./collections"); + +const UserRepository = UserCollection.repository; + +const user = await UserRepository.findOne({ + filter: { + id: 1 + }, +}); + +user.name = "new name"; +await user.save(); +``` + +### 查询 + +#### 基础查询 + +在 `Repository` 对象上,调用 `find*` 相关方法,可执行查询操作,查询方法都支持传入 `filter` 参数,用于过滤数据。 + +```javascript +// SELECT * FROM users WHERE id = 1 +userRepository.find({ + filter: { + id: 1 + } +}); + +``` +#### 操作符 + +`Repository` 中的 `filter` 参数,还提供了多种操作符,执行更加多样的查询操作。 + +```javascript +// SELECT * FROM users WHERE age > 18 +userRepository.find({ + filter: { + age: { + $gt: 18 + } + } +}); + +// SELECT * FROM users WHERE age > 18 OR name LIKE '%张%' +userRepository.find({ + filter: { + $or: [ + { age: { $gt: 18 } }, + { name: { $like: "%张%" } } + ] + } +}); + +``` + +操作符的更多详细信息请参考 [Filter Operators](/api/database/operators)。 + +#### 字段控制 + +在查询操作时,通过 `fields`, `except`, `appends` 参数可以控制输出字段。 + +* `fields`: 指定输出字段 +* `except`: 排除输出字段 +* `appends`: 追加输出关联字段 + +```javascript +// 获取的结果只包含 id 和 name 字段 +userRepository.find({ + fields: ["id", "name"], +}); + +// 获取的结果不包含 password 字段 +userRepository.find({ + except: ["password"], +}); + +// 获取的结果会包含关联对象 posts 的数据 +userRepository.find({ + appends: ["posts"], +}); +``` + +#### 关联字段查询 + +`filter` 参数支持按关联字段进行过滤,例如: + +```javascript +// 查询 user 对象,其所关联的 posts 存在 title 为 'title1' 的对象 +userRepository.find({ + filter: { + "posts.title": "post title" + } +}); +``` + +关联字段也可进行嵌套 + +```javascript +// 查询 user 对象,查询结果满足其 posts 的 comments 包含 keywords +await userRepository.find({ + filter: { + "posts.comments.content": { + $like: "%keywords%" + } + } +}); +``` + +#### 排序 + +通过 `sort` 参数,可以对查询结果进行排序。 + +```javascript + +// SELECT * FROM users ORDER BY age +await userRepository.find({ + sort: 'age' +}); + + +// SELECT * FROM users ORDER BY age DESC +await userRepository.find({ + sort: '-age' +}); + +// SELECT * FROM users ORDER BY age DESC, name ASC +await userRepository.find({ + sort: ['-age', "name"], +}); +``` + +也可按照关联对象的字段进行排序 + +```javascript +await userRepository.find({ + sort: 'profile.createdAt' +}); +``` + +### 创建 + +#### 基础创建 + +通过 `Repository` 创建新的数据对象。 + +```javascript + +await userRepository.create({ + name: "张三", + age: 18, +}); +// INSERT INTO users (name, age) VALUES ('张三', 18) + + +// 支持批量创建 +await userRepository.create([ + { + name: "张三", + age: 18, + }, + { + name: "李四", + age: 20, + }, +]) + +``` + +#### 创建关联 + +创建时可以同时创建关联对象,和查询类似,也支持关联对象的嵌套使用,例如: + +```javascript +await userRepository.create({ + name: "张三", + age: 18, + posts: [ + { + title: "post title", + content: "post content", + tags: [ + { + name: "tag1", + }, + { + name: "tag2", + }, + ], + }, + ], +}); +// 创建用户的同时,创建 post 与用户关联,创建 tags 与 post 相关联。 +``` +若关联对象已在数据库中,可传入其ID,创建时会建立与关联对象的关联关系。 + +```javascript +const tag1 = await tagRepository.findOne({ + filter: { + name: "tag1" + }, +}); + +await userRepository.create({ + name: "张三", + age: 18, + posts: [ + { + title: "post title", + content: "post content", + tags: [ + { + id: tag1.id, // 建立与已存在关联对象的关联关系 + }, + { + name: "tag2", + }, + ], + }, + ], +}); +``` + +### 更新 + +#### 基础更新 + +获取到数据对象后,可直接在数据对象(`Model`)上修改属性,然后调用 `save` 方法保存修改。 + +```javascript +const user = await userRepository.findOne({ + filter: { + name: "张三", + }, +}); + + +user.age = 20; +await user.save(); +``` + +数据对象 `Model` 继承自 Sequelize Model,对 `Model` 的操作可参考 [Sequelize Model](https://sequelize.org/master/manual/model-basics.html)。 + +也可通过 `Repository` 更新数据: + +```javascript +// 修改满足筛选条件的数据记录 +await userRepository.update({ + filter: { + name: "张三", + }, + values: { + age: 20, + }, +}); +``` + +更新时,可以通过 `whitelist` 、`blacklist` 参数控制更新字段,例如: + +```javascript +await userRepository.update({ + filter: { + name: "张三", + }, + values: { + age: 20, + name: "李四", + }, + whitelist: ["age"], // 仅更新 age 字段 +}); +```` + +#### 更新关联字段 + +在更新时,可以设置关联对象,例如: + +```javascript +const tag1 = tagRepository.findOne({ + filter: { + id: 1 + }, +}); + +await postRepository.update({ + filter: { + id: 1 + }, + values: { + title: "new post title", + tags: [ + { + id: tag1.id // 与 tag1 建立关联 + }, + { + name: "tag2", // 创建新的 tag 并建立关联 + }, + ], + }, +}); + + +await postRepository.update({ + filter: { + id: 1 + }, + values: { + tags: null // 解除 post 与 tags 的关联 + }, +}) +``` + +### 删除 + +可调用 `Repository` 中的 `destroy()`方法进行删除操作。删除时需指定筛选条件: + +```javascript +await userRepository.destroy({ + filter: { + status: "blocked", + }, +}); +``` ## 构造函数 @@ -56,7 +381,7 @@ await books.myQuery('SELECT * FROM books;'); ### `find()` -从数据库查询特定条件的数据集。相当于 Sequelize 中的 `Model.findAll()`。 +从数据库查询数据集,可指定筛选条件、排序等。 **签名** diff --git a/docs/en-US/api/env.md b/docs/en-US/api/env.md index e3140bfff..9cc424176 100644 --- a/docs/en-US/api/env.md +++ b/docs/en-US/api/env.md @@ -152,6 +152,17 @@ DB_TABLE_PREFIX=nocobase_ DB_LOGGING=on ``` +### LOGGER_TRANSPORT + +日志 transport,默认值 `console,dailyRotateFile`,可选项 + +- `console` +- `dailyRotateFile` + +### DAILY_ROTATE_FILE_DIRNAME + +`dailyRotateFile` 日志的存储路径,默认为 `storage/logs` + ## 临时环境变量 安装 NocoBase 时,可以通过设置临时的环境变量来辅助安装,如: diff --git a/docs/en-US/api/http/action-api.md b/docs/en-US/api/http/action-api.md deleted file mode 100644 index 1445d05e5..000000000 --- a/docs/en-US/api/http/action-api.md +++ /dev/null @@ -1,142 +0,0 @@ -# Action API - -## Common - ---- - -Collection and Association resources are common. - -### `create` - -```bash -POST /api/users:create?whitelist=a,b&blacklist=c,d - -{} # Request Body -``` - -- Parameters - - whitelist White list - - blacklist Black list -- Request body: JSON data to be inserted -- Response body data: Created data JSON - -#### Add a User - -```bash -POST /api/users:create - -Request Body -{ - "email": "demo@nocobase.com", - "name": "Admin" -} - -Response 200 (application/json) -{ - "data": {}, -} -``` - -#### Add a user's article - -```bash -POST /api/users/1/posts:create - -Request Body -{ - "title": "My first post" -} - -Response 200 (application/json) -{ - "data": {}, -} -``` - -#### Association in Request Body - -```bash -POST /api/posts:create - -Request Body -{ - "title": "My first post", - "user": 1 -} - -Response 200 (application/json) -{ - "data": { - "id": 1, - "title": "My first post", - "userId": 1, - "user": { - "id": 1 - } - } -} -``` - -### `update` - -```bash -POST /api/users:create?filterByTk=1&whitelist=a,b&blacklist=c,d - -{} # Request Body -``` - -- Parameters - - whitelist White list - - blacklist Black list - - filterByTk Filter by tk field, by default tk is the primary key of the data table - - filter Filter,support json string -- Request body: JSON data to be updated - -#### Association in Request Body - -```bash -POST /api/posts:update/1 - -Request Body -{ - "title": "My first post 2", - "user": 2 -} - -Response 200 (application/json) -{ - "data": [ - { - "id": 1, - "title": "My first post 2", - "userId": 2, - "user": { - "id": 2 - } - } - ] -} -``` - -### `list` - -### `get` - -### `destroy` - -### `move` - -## Association - ---- - -### `add` - -### `set` - -### `remove` - -### `toggle` - - - diff --git a/docs/en-US/api/http/filter-operators.md b/docs/en-US/api/http/filter-operators.md deleted file mode 100644 index a31112c5c..000000000 --- a/docs/en-US/api/http/filter-operators.md +++ /dev/null @@ -1,59 +0,0 @@ -# Filter operators - -## Common - -- $eq -- $ne -- $gte -- $gt -- $lte -- $lt -- $not -- $is -- $in -- $notIn -- $like -- $notLike -- $iLike -- $notILike -- $and -- $or -- $empty -- $notEmpty - -## array - -- $match -- $notMatch -- $anyOf -- $noneOf -- $arrayEmpty -- $arrayNotEmpty - -## association - -- $exists -- $notExists - -## boolean - -- $isTruthy -- $isFalsy - -## date - -- $dateOn -- $dateNotOn -- $dateBefore -- $dateNotBefore -- $dateAfter -- $dateNotAfter - -## string - -- $includes -- $notIncludes -- $startsWith -- $notStartsWith -- $endWith -- $notEndWith diff --git a/docs/en-US/api/http/javascript-sdk.md b/docs/en-US/api/http/javascript-sdk.md deleted file mode 100644 index 329441c51..000000000 --- a/docs/en-US/api/http/javascript-sdk.md +++ /dev/null @@ -1,281 +0,0 @@ -# JavaScript SDK - -## APIClient - -```ts -class APIClient { - // axios instance - axios: AxiosInstance; - // constructors - constructor(instance?: AxiosInstance | AxiosRequestConfig); - // Client-side requests, support for AxiosRequestConfig and ResourceActionOptions - request, D = any>(config: AxiosRequestConfig | ResourceActionOptions): Promise; - // Get Resources - resource(name: string, of?: any): R; -} -``` - -Initialize instance - -```ts -import axios from 'axios'; -import { APIClient } from '@nocobase/sdk'; - -// Provide AxiosRequestConfig configuration parameters -const api = new APIClient({ - baseURL: 'https://localhost:8000/api', -}); - -// Provide AxiosInstance -const instance = axios.create({ - baseURL: 'https://localhost:8000/api', -}); -const api = new APIClient(instance); -``` - -## Mock - -```ts -import { APIClient } from '@nocobase/sdk'; -import MockAdapter from 'axios-mock-adapter'; - -const api = new APIClient({ - baseURL: 'https://localhost:8000/api', -}); - -const mock = new MockAdapter(api.axios); - -mock.onGet('users:get').reply(200, { - data: { id: 1, name: 'John Smith' }, -}); - -await api.request({ url: 'users:get' }); -``` - -## Storage - -APIClient uses localStorage by default, you can also custom storage. - -```ts -import { Storage } from '@nocobase/sdk'; - -class MemoryStorage extends Storage { - items = new Map(); - - clear() { - this.items.clear(); - } - - getItem(key: string) { - return this.items.get(key); - } - - setItem(key: string, value: string) { - return this.items.set(key, value); - } - - removeItem(key: string) { - return this.items.delete(key); - } -} - -const api = new APIClient({ - baseURL: 'https://localhost:8000/api', - storageClass: CustomStorage, -}); -``` - -## Auth - -```ts -// sign in and remember the current token -api.auth.signIn({ email, password }); -// sign out and delete the token -api.auth.signOut(); -// set the token -api.auth.setToken('123'); -// set the role (multiple roles) -api.auth.setRole('admin'); -// set the locale (multiple languages) -api.auth.setLocale('zh-CN'); -``` - -Custom Auth - -```ts -import { Auth } from '@nocobase/sdk'; - -class CustomAuth extends Auth { - -} - -const api = new APIClient({ - baseURL: 'https://localhost:8000/api', - authClass: CustomAuth, -}); -``` - -## Request - -```ts -// url -await api.request({ - url: 'users:list', - // request params - params: { - filter: { - 'email.$includes': 'noco', - }, - }, - // request body - data, -}); - -// resource & action -await api.request({ - resource: 'users', - action: 'list', - // action params - params: { - filter: { - 'email.$includes': 'noco', - }, - page: 1, - }, -}); -``` - -## Resource action - -```ts -await api.resource('collection')[action](); -await api.resource('collection.association', collectionId)[action](); -``` - -## Action API - -```ts -await api.resource('collection').create(); -await api.resource('collection').get(); -await api.resource('collection').list(); -await api.resource('collection').update(); -await api.resource('collection').destroy(); -await api.resource('collection.association', collectionId).create(); -await api.resource('collection.association', collectionId).get(); -await api.resource('collection.association', collectionId).list(); -await api.resource('collection.association', collectionId).update(); -await api.resource('collection.association', collectionId).destroy(); -``` - -### `get` - -```ts -interface Resource { - get: (options?: GetActionOptions) => Promise; -} - -interface GetActionOptions { - filter?: any; - filterByTk?: any; - fields?: string || string[]; - appends?: string || string[]; - expect?: string || string[]; - sort?: string[]; -} -``` - -### `list` - -```ts -interface Resource { - list: (options?: ListActionOptions) => Promise; -} - -interface ListActionOptions { - filter?: any; - filterByTk?: any; - fields?: string || string[]; - appends?: string || string[]; - expect?: string || string[]; - sort?: string[]; - page?: number; - pageSize?: number; - paginate?: boolean; -} -``` - -### `create` - -```ts -interface Resource { - create: (options?: CreateActionOptions) => Promise; -} - -interface CreateActionOptions { - whitelist?: string[]; - blacklist?: string[]; - values?: {[key: sting]: any}; -} -``` - -### `update` - -```ts -interface Resource { - update: (options?: UpdateActionOptions) => Promise; -} - -interface UpdateActionOptions { - filter?: any; - filterByTk?: any; - whitelist?: string[]; - blacklist?: string[]; - values?: {[key: sting]: any}; -} -``` - -### `destroy` - -```ts -interface Resource { - destroy: (options?: DestroyActionOptions) => Promise; -} - -interface DestroyActionOptions { - filter?: any; - filterByTk?: any; -} -``` - -### `move` - -```ts -interface Resource { - move: (options?: MoveActionOptions) => Promise; -} - -interface MoveActionOptions { - sourceId: any; - targetId?: any; - /** @default 'sort' */ - sortField?: any; - targetScope?: {[key: string]: any}; - sticky?: boolean; - method?: 'insertAfter' | 'prepend'; -} -``` - -### `` - -```ts -interface AttachmentResource { - -} - -interface UploadActionOptions { - -} - -api.resource('attachments').upload(); -api.resource('attachments').upload(); -``` diff --git a/docs/en-US/api/index.md b/docs/en-US/api/index.md index cb8199c15..f23665749 100644 --- a/docs/en-US/api/index.md +++ b/docs/en-US/api/index.md @@ -1,8 +1,8 @@ # 概览 -| 模块 | 包名 | 描述 | -| --------------------------------- | --------------------- | ------------------- | -| [Server](/api/server) | `@nocobase/server` | 服务端应用 | +| 模块 | 包名 | 描述 | +|-----------------------------------| --------------------- | ------------------- | +| [Server](/api/server/application) | `@nocobase/server` | 服务端应用 | | [Database](/api/database) | `@nocobase/database` | 数据库访问层 | | [Resourcer](/api/resourcer) | `@nocobase/resourcer` | 资源与路由映射 | | [ACL](/api/acl) | `@nocobase/acl` | 访问控制表 | diff --git a/docs/en-US/api/resourcer/index.md b/docs/en-US/api/resourcer/index.md index d1eb0c49b..f617c29c9 100644 --- a/docs/en-US/api/resourcer/index.md +++ b/docs/en-US/api/resourcer/index.md @@ -1,25 +1,57 @@ # Resourcer -Resourcer 主要用于管理 API 资源与路由,也是 NocoBase 的内置模块,app 默认会自动创建一个 Resourcer 实例,大部分情况你可以通过 `app.resourcer` 访问。 +## 概览 -资源路由管理器主要通过 [资源](/api/server/resourcer/resource) + [操作](/api/server/resourcer/action) 的概念定义服务端 API 接口,与 RESTful 的概念相似。大部分资源通过映射数据库表生成,包含常规的 CRUD 操作,以覆盖常见场景。但如果有额外需求,也可以在此基础上扩展更多的资源类型和操作类型。 +Nocobase 中的接口遵循面向资源的设计模式。Resourcer 主要用于管理 API 资源与路由。 -## 包结构 +```javascript +const Koa = require('koa'); +const { Resourcer } = require('@nocobase/resourcer'); -可通过以下方式引入相关实体: +const resourcer = new Resourcer(); -```ts -import Resourcer, { - Resource, - Action, - Middleware, - branch -} from '@nocobase/resourcer'; +// 定义一个资源接口 +resourcer.define({ + name: 'users', + actions: { + async list(ctx) { + ctx.body = [ + { + name: "u1", + age: 18 + }, + { + name: "u2", + age: 20 + } + ] + } + }, +}); + +const app = new Koa(); + +// 可在 koa 实例中使用 +app.use( + resourcer.middleware({ + prefix: '/api', // resourcer 路由前缀 + }), +); + +app.listen(3000); ``` +启动服务后,使用`curl`发起请求: +```bash +>$ curl localhost:3000/api/users +[{"name":"u1","age":18},{"name":"u2","age":20}] +``` +更多 Resourcer 的使用说明可参考[资源与操作](/development/guide/resources-actions)。 Resourcer 内置于 [NocoBase Application](/api/server/application#resourcer) ,可以通过 `app.resourcer` 访问。 + + ## 构造函数 -用于创建 Resourcer 管理器实例。由于 app 默认创建一个内置实例,所以通常不会直接使用构造函数。 +用于创建 Resourcer 管理器实例。 **签名** diff --git a/docs/en-US/api/server/application.md b/docs/en-US/api/server/application.md index 8aac7a008..0b8d3965e 100644 --- a/docs/en-US/api/server/application.md +++ b/docs/en-US/api/server/application.md @@ -1,6 +1,136 @@ # Application -基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容所有的 Koa 插件。 +## 概览 + +### Web服务 +Nocobase Application 是基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容 Koa 的 API。 + +```javascript +// index.js +const { Application } = require('@nocobase/server'); + +// 创建App实例,并配置数据库连接信息 +const app = new Application({ + database: { + dialect: 'sqlite', + storage: ':memory:', + } +}); + +// 注册中间件 响应请求 +app.use(async ctx => { + ctx.body = 'Hello World'; +}); + +// 以命令行模式启动 +app.runAsCLI(); +``` + +在命令行中运行 `node index.js start` 启动服务后,使用 `curl` 请求服务。 + +```bash +$> curl localhost:3000 +Hello World +``` + +### 命令行工具 +Nocobase Application 中也内置了 `cli commander`,可以当作命令行工具运行。 + +```javascript +// cmd.js +const {Application} = require('@nocobase/server'); +const app = new Application({ + database: { + dialect: 'sqlite', + storage: ':memory:', + } +}); + +app.cli.command('hello').action(async () => { + console.log("hello world") +}); + +app.runAsCLI() +``` + +在命令行中运行 + +```bash +$> node cmd.js hello +hello world +``` + +### 插件注入 + +Nocobase Application 被设计为高度可扩展的框架,可以编写插件注入到应用中扩展功能。 +例如上面的 Web 服务可以替换为插件形式。 + +```javascript +const { Application, Plugin } = require('@nocobase/server'); + +// 通过继承 Plugin 类来编写插件 +class HelloWordPlugin extends Plugin { + load() { + this.app.use(async (ctx, next) => { + ctx.body = "Hello World"; + }) + } +} + +const app = new Application({ + database: { + dialect: 'sqlite', + storage: ':memory:', + } +}); + +// 注入插件 +app.plugin(HelloWordPlugin, { name: 'hello-world-plugin'} ); + +app.runAsCLI() +``` + +### 更多示例 + +更加详细的插件开发文档请参考 [插件开发](./plugin.md)。 +Application 类的更多示例可参考 [examples](https://github.com/nocobase/nocobase/blob/main/examples/index.md) + +## 生命周期 + +根据不同运行模式,Application 有三种生命周期: + +### 安装 +使用 `cli` 中的 `install` 命令调用安装。 +一般来说,插件在使用之前若需要在数据库中写入新表或者数据,都需要在安装时执行。在初次使用 Nocobase 时也需要调用安装。 + +* 调用 `load` 方法,载入已注册的插件。 +* 触发 `beforeInstall` 事件。 +* 调用 `db.sync` 方法,同步数据库。 +* 调用 `pm.install` 方法,执行已注册插件的 `install` 方法。 +* 写入 `nocobase` 版本。 +* 触发 `afterInstall`。 +* 调用 `stop` 方法,结束安装。 + +### 启动 +使用 `cli` 中的 `start` 命令来启动 Nocobase Web 服务。 + +* 调用 `load` 方法,载入已注册的插件。 +* 调用 `start` 方法 + * 触发 `beforeStart` + * 启动端口监听 + * 触发 `afterStart` + +### 更新 + +当需要更新 Nocobase 时,可使用 `cli` 中的 `upgrade` 命令。 + +* 调用 `load` 方法,载入已注册的插件。 +* 触发 `beforeUpgrade`。 +* 调用 `db.migrator.up` 方法,执行数据库迁移。 +* 调用 `db.sync` 方法,同步数据库。 +* 调用 `version.update` 方法,更新 `nocobase` 版本。 +* 触发 `afterUpgrade`。 +* 调用 `stop` 方法,结束更新。 ## 构造函数 @@ -18,6 +148,7 @@ | --- | --- | --- | --- | | `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 | | `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 | +| `options.logger` | `AppLoggerOptions` | `{}` | 日志 | | `options.cors` | [`CorsOptions`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/koa__cors/index.d.ts#L24) | `{}` | 跨域配置,参考 [@koa/cors](https://npmjs.com/package/@koa/cors) | | `options.dataWrapping` | `boolean` | `true` | 是否包装响应数据,`true` 则将把通常的 `ctx.body` 包装为 `{ data, meta }` 的结构。 | | `options.registerActions` | `boolean` | `true` | 是否注册默认的 [actions](#) | @@ -32,29 +163,6 @@ interface ApplicationOptions { } ``` -**示例** - -```ts -import Application from '@nocobase/server'; - -const app = new Application({ - database: { - dialect: 'mysql', - host: 'localhost', - port: 3306, - username: 'root', - password: '123456', - database: 'test', - }, - resourcer: { - prefix: '/api', - }, - cors: { - origin: '*', - } -}); -``` - ## 实例成员 ### `cli` @@ -73,6 +181,11 @@ const app = new Application({ ACL 实例,相关 API 参考 [ACL](/api/acl)。 + +### `logger` + +Winston 实例,相关 API 参考 [Winston](https://github.com/winstonjs/winston#table-of-contents)。 + ### `i18n` I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。 @@ -89,11 +202,12 @@ I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/ap 内置的中间件有: +- logger - i18next - bodyParser - cors - dataWrapping -- collection2resource +- db2resource - restApiMiddleware ### `context` @@ -108,6 +222,7 @@ NocoBase 默认对 context 注入了以下成员,可以在请求处理函数 | `ctx.db` | `Database` | 数据库实例 | | `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 | | `ctx.action` | `Action` | 资源操作相关对象实例 | +| `ctx.logger` | `Winston` | 日志实例 | | `ctx.i18n` | `I18n` | 国际化实例 | | `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 | | `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token | diff --git a/docs/en-US/api/server/plugin.md b/docs/en-US/api/server/plugin.md index 2f7a9705c..4bb5aaf9c 100644 --- a/docs/en-US/api/server/plugin.md +++ b/docs/en-US/api/server/plugin.md @@ -1,51 +1,40 @@ # Plugin -## 示例 +## 概览 -```ts -const app = new Application(); +Nocobase 中的插件为 `Class` 的形式。如需自定义插件,需要继承 `Plugin` 类。 + +```typescript +import { Plugin } from '@nocobase/server'; class MyPlugin extends Plugin { - afterAdd() {} - beforeLoad() {} - load() {} - install() {} - afterEnable() {} - afterDisable() {} - remove() {} + // ... } app.plugin(MyPlugin, { name: 'my-plugin' }); ``` -## 属性 +## 插件生命周期 -### `options` - -插件配置信息 - -### `name` - -插件标识,只读 - -## 实例方法 - -### `afterAdd()` - -插件 add/addStatic 之后 +每个插件都包含生命周期方法,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。 +生命周期方法将由 `Application` 在特定阶段调用,可参考 [`Application`生命周期](./application.md)。 ### `beforeLoad()` -插件加载前,如事件或类注册 +插件加载前,如事件或类注册,在此可访问核心接口,其他插件此阶段不可用。 ### `load()` -加载插件,配置之类 +加载插件,配置之类。在 `load` 中可调用其他插件实例,而在 `beforeLoad` 中是不行的。 ### `install()` 插件安装逻辑,如初始化数据 +### `afterAdd()` + +插件 add/addStatic 之后 + ### `afterEnable()` 插件激活之后的逻辑 @@ -56,4 +45,4 @@ app.plugin(MyPlugin, { name: 'my-plugin' }); ### `remove()` -用于实现插件删除逻辑 \ No newline at end of file +用于实现插件删除逻辑 diff --git a/docs/en-US/welcome/release/logger.md b/docs/en-US/welcome/release/logger.md new file mode 100644 index 000000000..9a92db953 --- /dev/null +++ b/docs/en-US/welcome/release/logger.md @@ -0,0 +1,67 @@ +# v0.8.1:NocoBase 的 Logging 系统 + +## `@nocobase/logger` + +基于 Winston 实现,提供了便捷的创建 logger 实例的方法。 + +```ts +const logger = createLogger(); +logger.info('Hello distributed log files!'); + +const { instance, middleware } = createAppLogger(); // 用于 @nocobase/server +app.logger = instance; +app.use(middleware); +``` + +## 新增的环境变量 + +logger 相关环境变量有: + +- [LOGGER_TRANSPORT](/api/env#logger_transport) +- [DAILY_ROTATE_FILE_DIRNAME](/api/env#daily_rotate_file_dirname) + +## Application 的 logger 配置 + +```ts +const app = new Application({ + logger: { + async skip(ctx) { + return false; + }, + requestWhitelist: [], + responseWhitelist: [], + transports: ['console', 'dailyRotateFile'], + }, +}) +``` + +更多配置项参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents) + +## app.logger & ctx.logger + +ctx.logger 带有 reqId,整个 ctx 周期里都是一个 reqId + +```ts +ctx.logger = app.logger.child({ reqId: ctx.reqId }); +``` + +`app.logger` 和 `ctx.logger` 都是 Winston 实例,详细用法参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents) + + +## 自定义 Transports + +除了 Winston 的方式以外,NocoBase 还提供了一种更便捷的方式 + +```ts +import { Transports } from '@nocobase/logger'; + +Transports['custom'] = () => { + return new winston.transports.Console(); +}; + +const app = new Application({ + logger: { + transports: ['custom'], + }, +}) +``` diff --git a/docs/zh-CN/welcome/release/logger.md b/docs/zh-CN/welcome/release/logger.md new file mode 100644 index 000000000..9a92db953 --- /dev/null +++ b/docs/zh-CN/welcome/release/logger.md @@ -0,0 +1,67 @@ +# v0.8.1:NocoBase 的 Logging 系统 + +## `@nocobase/logger` + +基于 Winston 实现,提供了便捷的创建 logger 实例的方法。 + +```ts +const logger = createLogger(); +logger.info('Hello distributed log files!'); + +const { instance, middleware } = createAppLogger(); // 用于 @nocobase/server +app.logger = instance; +app.use(middleware); +``` + +## 新增的环境变量 + +logger 相关环境变量有: + +- [LOGGER_TRANSPORT](/api/env#logger_transport) +- [DAILY_ROTATE_FILE_DIRNAME](/api/env#daily_rotate_file_dirname) + +## Application 的 logger 配置 + +```ts +const app = new Application({ + logger: { + async skip(ctx) { + return false; + }, + requestWhitelist: [], + responseWhitelist: [], + transports: ['console', 'dailyRotateFile'], + }, +}) +``` + +更多配置项参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents) + +## app.logger & ctx.logger + +ctx.logger 带有 reqId,整个 ctx 周期里都是一个 reqId + +```ts +ctx.logger = app.logger.child({ reqId: ctx.reqId }); +``` + +`app.logger` 和 `ctx.logger` 都是 Winston 实例,详细用法参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents) + + +## 自定义 Transports + +除了 Winston 的方式以外,NocoBase 还提供了一种更便捷的方式 + +```ts +import { Transports } from '@nocobase/logger'; + +Transports['custom'] = () => { + return new winston.transports.Console(); +}; + +const app = new Application({ + logger: { + transports: ['custom'], + }, +}) +```