diff --git a/.dumi/theme/slots/LangSwitch/index.tsx b/.dumi/theme/slots/LangSwitch/index.tsx deleted file mode 100644 index 4111f9eb6..000000000 --- a/.dumi/theme/slots/LangSwitch/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -const LangSwitch = () => { - const { hostname } = window.location - if (hostname === 'localhost') return null; - const en = window.location.href.replace(hostname, 'docs.nocobase.com') - const cn = window.location.href.replace(hostname, 'docs-cn.nocobase.com') - return EN | 中文 -} - -export default LangSwitch; diff --git a/.dumi/theme/slots/Previewer/index.tsx b/.dumi/theme/slots/Previewer/index.tsx deleted file mode 100644 index 9ec63f2cc..000000000 --- a/.dumi/theme/slots/Previewer/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { FC, useRef, useEffect, Suspense } from 'react'; -import { Root, createRoot } from 'react-dom/client'; -import { IPreviewerProps } from 'dumi'; -import DefaultPreviewer from 'dumi/theme-default/builtins/Previewer'; - -const Previewer: FC = ({ children, ...props }) => { - const ref = useRef(null); - useEffect(() => { - let root: Root - if (ref.current) { - root = createRoot(ref.current) - root.render(loading...}>{children}) - } - return () => { - if (root) { - root.unmount() - } - } - }, []); - return
; -}; - -export default Previewer; diff --git a/.dumi/theme/slots/PreviewerActions.tsx b/.dumi/theme/slots/PreviewerActions.tsx deleted file mode 100644 index 6640a73a5..000000000 --- a/.dumi/theme/slots/PreviewerActions.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import DumiPreviewerActions from 'dumi/theme-default/slots/PreviewerActions'; -import React, { useRef, useEffect, useState } from 'react'; -import { Spin } from 'antd' - -import { IPreviewerProps } from 'dumi'; - -const indexHtml = ` -
- - -` - -const mainTsx = ` -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -) -` - -const packageJson = ` -{ - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview" - }, - "dependencies": { - }, - "devDependencies": { - "flat": "^5.0.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "@types/react": "^18.2.15", - "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.3", - "less": "^4.2.0", - "typescript": "^5.0.2", - "vite": "^4.4.5" - } -} -` - -const tsConfigJson = ` -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": [ - "ES2020", - "DOM", - "DOM.Iterable" - ], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true, - "strict": false, - "noUnusedLocals": true, - "noUnusedParameters": true, - "allowSyntheticDefaultImports": true, - "noFallthroughCasesInSwitch": true - }, - "include": [ - "src", - "vite.config.ts" - ] -} -` - -const viteConfigTs = ` -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -export default defineConfig({ - plugins: [react()], -}) -` - -const sandboxTask = ` -{ - "setupTasks": [ - { - "name": "Install Dependencies", - "command": "yarn install" - } - ], - "tasks": { - "dev": { - "name": "dev", - "command": "yarn dev", - "runAtStart": true, - "preview": { - "port": 5173 - } - }, - "build": { - "name": "build", - "command": "yarn build", - "runAtStart": false - }, - "preview": { - "name": "preview", - "command": "yarn preview", - "runAtStart": false - } - } -} -` - -function getCSBData(opts: IPreviewerProps, ext: string) { - - const files: Record< - string, - { - content: string; - isBinary: boolean; - } - > = {}; - const deps: Record = {}; - const entryFileName = `index${ext}`; - - Object.entries(opts.asset.dependencies).forEach(([name, { type, value }]) => { - if (type === 'NPM') { - // generate dependencies - deps[name] = value; - } else { - // append other imported local files - files[name === entryFileName ? `src/App${ext}` : name] = { - content: value, - isBinary: false, - }; - } - }); - - // append package.json - let pkg = JSON.parse(packageJson) - try { - for (let key in deps) { - if (!pkg['devDependencies'][key]) { - pkg.dependencies[key] = deps[key] - } - } - } catch (e) { - console.log(e) - } - files['package.json'] = { - content: JSON.stringify( - { - name: opts.title, - ...pkg, - }, - null, - 2, - ), - isBinary: false, - }; - - files['index.html'] = { content: indexHtml, isBinary: false }; - files['src/main.tsx'] = { content: mainTsx, isBinary: false }; - files['package.json'] = { content: JSON.stringify(pkg, null, 2), isBinary: false }; - files['.codesandbox/task.json'] = { content: sandboxTask, isBinary: false }; - files['tsconfig.json'] = { content: tsConfigJson, isBinary: false }; - files['vite.config.ts'] = { content: viteConfigTs, isBinary: false }; - - return { files }; -} - - -export function openCodeSandbox(opts: IPreviewerProps) { - const isTSX = Boolean(opts.asset.dependencies?.['index.tsx']); - const ext = isTSX ? '.tsx' : '.jsx'; - return fetch("https://codesandbox.io/api/v1/sandboxes/define?json=1", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json" - }, - body: JSON.stringify(getCSBData(opts, ext)) - }) - .then(x => x.json()) - .then(data => { - window.open(`https://codesandbox.io/p/sandbox/${data.sandbox_id}?file=/src/App${ext}`); - }); -} - - -const PreviewerActions: typeof DumiPreviewerActions = (props) => { - const div = useRef(null); - const [loading, setLoading] = useState(false); - - useEffect(() => { - if (div.current) { - const element = div.current.querySelector('.dumi-default-previewer-action-btn'); - element?.addEventListener('click', (e) => { - e.stopImmediatePropagation(); - setLoading(true); - openCodeSandbox(props).finally(() => { - setLoading(false); - }); - }) - } - }, [div]) - - return
-}; - -export default PreviewerActions; diff --git a/.dumirc.ts b/.dumirc.ts deleted file mode 100644 index 45a2ef213..000000000 --- a/.dumirc.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { getUmiConfig } from '@nocobase/devtools/umiConfig'; -import { defineConfig } from 'dumi'; -import { defineThemeConfig } from 'dumi-theme-nocobase'; -import { nav, sidebar } from './docs/config'; - -const umiConfig = getUmiConfig(); - -const lang = process.env.DOC_LANG || 'en-US'; - -console.log('process.env.DOC_LANG', process.env.DOC_LANG); - -// 设置多语言的 title -function setTitle(menuChildren) { - if (!menuChildren) return; - menuChildren.forEach((item) => { - if (typeof item === 'object') { - item.title = item[`title.${lang}`] || item.title; - if (item.children) { - setTitle(item.children); - } - } - }); -} -if (lang !== 'en-US') { - Object.values(sidebar).forEach(setTitle); -} - -export default defineConfig({ - hash: true, - alias: { - ...umiConfig.alias, - }, - ssr: { - - }, - exportStatic: { - ignorePreRenderError: true - }, - cacheDirectoryPath: `node_modules/.docs-${lang}-cache`, - outputPath: `./docs/dist/${lang}`, - resolve: { - docDirs: [`./docs/${lang}`] - }, - locales: [ - { id: 'en-US', name: 'English' }, - { id: 'zh-CN', name: '中文' }, - ], - themeConfig: defineThemeConfig({ - title: 'NocoBase', - logo: 'https://www.nocobase.com/images/logo.png', - nav: nav.map((item) => ({ ...item, title: (item[`title.${lang}`] || item.title) })), - sidebarEnhance: sidebar as any, - github: 'https://github.com/nocobase/nocobase', - footer: 'nocobase | Copyright © 2022', - localesEnhance: [ - { id: 'zh-CN', switchPrefix: '中', hostname: 'docs-cn.nocobase.com' }, - { id: 'en-US', switchPrefix: 'en', hostname: 'docs.nocobase.com' } - ], - }), - // mfsu: true, // 报错 -}); diff --git a/.github/workflows/deploy-client-docs.yml b/.github/workflows/deploy-client-docs.yml index a3a8bea87..bd2a6f242 100644 --- a/.github/workflows/deploy-client-docs.yml +++ b/.github/workflows/deploy-client-docs.yml @@ -5,12 +5,14 @@ on: - 'main' paths: - 'packages/core/client/**' + - 'packages/core/client/docs/**' - '.github/workflows/deploy-client-docs.yml' pull_request: branches: - '**' paths: - 'packages/core/client/**' + - 'packages/core/client/docs/**' - '.github/workflows/deploy-client-docs.yml' jobs: diff --git a/docs/config.ts b/docs/config.ts deleted file mode 100644 index 07b6e2ae0..000000000 --- a/docs/config.ts +++ /dev/null @@ -1,296 +0,0 @@ -const nav = [ - { - title: 'Welcome', - 'title.zh-CN': '欢迎', - link: '/welcome/introduction', - }, - { - title: 'User manual', - 'title.zh-CN': '使用手册', - link: '/manual/quick-start/the-first-app', - }, - { - title: 'Plugin Development', - 'title.zh-CN': '插件开发', - link: '/development', - }, - { - title: 'API reference', - 'title.zh-CN': 'API 参考', - link: '/api', - }, - { - title: 'Schema components', - 'title.zh-CN': 'Schema 组件库', - link: '/components', - }, -]; -const sidebar = { - '/welcome': [ - { - title: 'Welcome', - 'title.zh-CN': '欢迎', - 'title.tr-TR': 'Hoşgeldiniz', - type: 'group', - children: [ - '/welcome/introduction', - '/welcome/introduction/features', - '/welcome/introduction/why', - // '/welcome/introduction/learning-guide', - ], - }, - { - title: 'Getting started', - 'title.zh-CN': '快速开始', - 'title.tr-TR': 'Başlangıç', - type: 'group', - children: [ - { - title: 'Installation', - 'title.zh-CN': '安装', - 'title.TR-TR': 'Kurulum', - children: [ - '/welcome/getting-started/installation', - '/welcome/getting-started/installation/docker-compose', - '/welcome/getting-started/installation/create-nocobase-app', - '/welcome/getting-started/installation/git-clone', - ], - }, - { - title: 'Upgrading', - 'title.zh-CN': '升级', - 'title.TR-TR': 'Güncelleme', - children: [ - '/welcome/getting-started/upgrading', - '/welcome/getting-started/upgrading/docker-compose', - '/welcome/getting-started/upgrading/create-nocobase-app', - '/welcome/getting-started/upgrading/git-clone', - ], - }, - ], - }, - { - title: 'Releases', - 'title.zh-CN': '产品发布', - 'title.TR-TR': 'Sürüm', - type: 'group', - children: [ - { - type: 'item', - title: 'Changelog', - 'title.zh-CN': '更新日志', - link: 'https://github.com/nocobase/nocobase/blob/main/CHANGELOG.md', - }, - // '/welcome/release/index', - // '/welcome/release/v08-changelog', - '/welcome/release/v14-changelog', - '/welcome/release/v13-changelog', - '/welcome/release/v12-changelog', - '/welcome/release/v11-changelog', - '/welcome/release/v10-changelog', - ], - }, - { - title: 'Community', - 'title.zh-CN': '社区', - 'title.TR-TR': 'Topluluk', - type: 'group', - children: [ - '/welcome/community/contributing', - // '/welcome/community/faq', - '/welcome/community/translations', - - '/welcome/community/thanks', - ], - }, - ], - '/manual': [ - { - title: 'Quick Start', - 'title.zh-CN': '快速上手', - 'title.TR-TR': 'Hızlı Başlangıç', - type: 'group', - children: [ - '/manual/quick-start/the-first-app', - '/manual/quick-start/functional-zoning', - '/manual/quick-start/ui-editor-mode', - ], - }, - { - title: 'Core Concepts', - 'title.zh-CN': '核心概念', - 'title.TR-TR': 'Temel Kavramlar', - type: 'group', - children: [ - '/manual/core-concepts/collections', - '/manual/core-concepts/blocks', - '/manual/core-concepts/actions', - '/manual/core-concepts/menus', - '/manual/core-concepts/containers', - ], - }, - ], - '/development': [ - { - title: 'Getting started', - 'title.zh-CN': '快速开始', - 'title.TR-TR': 'Başlarken', - type: 'group', - children: [ - '/development', - '/development/your-fisrt-plugin', - '/development/app-ds', - '/development/plugin-ds', - '/development/life-cycle', - // '/development/learning-guide', - ], - }, - { - title: 'Server', - 'title.zh-CN': '服务端', - 'title.TR-TR': 'Sunucu', - type: 'group', - children: [ - '/development/server', - { - title: 'Collections & Fields', - 'title.zh-CN': '数据表和字段', - 'title.TR-TR': 'Koleksiyonlar & Alanlar', - children: [ - '/development/server/collections', - '/development/server/collections/options', - '/development/server/collections/configure', - '/development/server/collections/association-fields', - '/development/server/collections/field-extension', - '/development/server/collections/collection-template', - ], - }, - // '/development/server/collections-fields', - '/development/server/resources-actions', - '/development/server/middleware', - '/development/server/commands', - '/development/server/events', - '/development/server/i18n', - '/development/server/migration', - '/development/server/test', - ], - }, - { - title: 'Client', - 'title.zh-CN': '客户端', - 'title.TR-TR': 'Ziyaretçi(Client)', - type: 'group', - children: [ - '/development/client', - { - title: 'UI designer', - 'title.zh-CN': 'UI 设计器', - 'title.TR-TR': 'Kullanıcı Arayüz Tasarımcısı', - children: [ - // '/development/client/ui-schema-designer', - '/development/client/ui-schema-designer/what-is-ui-schema', - '/development/client/ui-schema-designer/extending-schema-components', - // '/development/client/ui-schema-designer/insert-adjacent', - '/development/client/ui-schema-designer/designable', - '/development/client/ui-schema-designer/component-library', - // '/development/client/ui-schema-designer/collection-manager', - // '/development/client/ui-schema-designer/acl', - '/development/client/ui-schema-designer/x-designer', - '/development/client/ui-schema-designer/x-initializer', - ], - }, - '/development/client/ui-router', - '/development/client/plugin-settings', - '/development/client/i18n', - '/development/client/test', - ], - }, - ], - '/api': [ - '/api', - '/api/env', - { - title: 'HTTP API', - type: 'subMenu', - children: ['/api/http', '/api/http/rest-api'], - }, - { - title: '@nocobase/server', - type: 'subMenu', - children: [ - '/api/server/application', - // '/api/server/plugin-manager', - '/api/server/plugin', - ], - }, - { - title: '@nocobase/database', - type: 'subMenu', - children: [ - '/api/database', - '/api/database/collection', - '/api/database/field', - '/api/database/repository', - '/api/database/relation-repository/has-one-repository', - '/api/database/relation-repository/has-many-repository', - '/api/database/relation-repository/belongs-to-repository', - '/api/database/relation-repository/belongs-to-many-repository', - '/api/database/operators', - ], - }, - { - title: '@nocobase/resourcer', - type: 'subMenu', - children: ['/api/resourcer', '/api/resourcer/resource', '/api/resourcer/action', '/api/resourcer/middleware'], - }, - { - title: '@nocobase/acl', - type: 'subMenu', - children: ['/api/acl/acl', '/api/acl/acl-role', '/api/acl/acl-resource'], - }, - { - title: '@nocobase/client', - type: 'subMenu', - children: [ - // '/api/client', - '/api/client/application', - '/api/client/router', - { - title: 'SchemaDesigner', - 'title.zh-CN': 'SchemaDesigner', - 'title.TR-TR': 'Şema Tasarımcısı', - - children: [ - '/api/client/schema-designer/schema-component', - '/api/client/schema-designer/schema-initializer', - '/api/client/schema-designer/schema-settings', - ], - }, - { - title: 'Extensions', - 'title.zh-CN': 'Extensions', - 'title.TR-TR': 'Eklentiler', - children: [ - // '/api/client/extensions/schema-component', - '/api/client/extensions/collection-manager', - '/api/client/extensions/block-provider', - '/api/client/extensions/acl', - ], - }, - ], - }, - { - title: '@nocobase/cli', - link: '/api/cli', - }, - { - title: '@nocobase/actions', - link: '/api/actions', - }, - { - title: '@nocobase/sdk', - link: '/api/sdk', - }, - ], -}; -export { nav, sidebar }; diff --git a/docs/en-US/api/acl/acl-resource.md b/docs/en-US/api/acl/acl-resource.md deleted file mode 100644 index 3d2dc6da2..000000000 --- a/docs/en-US/api/acl/acl-resource.md +++ /dev/null @@ -1,59 +0,0 @@ -# ACLResource - -ACLResource is the resource class in ACL system. In ACL systems, the corresponding resource is created automatically when granting permission to user. - -## Class Methods - -### `constructor()` - -Constructor. - -**Signature** -* `constructor(options: AclResourceOptions)` - -**Type** -```typescript -type ResourceActions = { [key: string]: RoleActionParams }; - -interface AclResourceOptions { - name: string; // Name of the resource - role: ACLRole; // Role to which the resource belongs - actions?: ResourceActions; -} -``` - -**Detailed Information** - -Refer to [`aclRole.grantAction`](./acl-role.md#grantaction) for details about `RoleActionParams`. - -### `getActions()` - -Get all actions of the resource, the return is `ResourceActions` object. - -### `getAction()` - -Get the parameter configuration of the action by name, the return is `RoleActionParams` object. - -**Detailed Information** - -Refer to [`aclRole.grantAction`](./acl-role.md#grantaction) for -`RoleActionParams`. - -### `setAction()` - -Set the parameter configuration of an action inside the resource, the return is `RoleActionParams` object. - -**Signature** -* `setAction(name: string, params: RoleActionParams)` - -**Detailed Information** - -* name - Name of the action to set -* Refer to [`aclRole.grantAction`](./acl-role.md#grantaction) for details about `RoleActionParams`. - -### `setActions()` - -**Signature** -* `setActions(actions: ResourceActions)` - -A shortcut for calling `setAction` in batches. diff --git a/docs/en-US/api/acl/acl-role.md b/docs/en-US/api/acl/acl-role.md deleted file mode 100644 index cbdd522d9..000000000 --- a/docs/en-US/api/acl/acl-role.md +++ /dev/null @@ -1,87 +0,0 @@ -# ACLRole - -ACLRole is the user role class in ACL system. In ACL systems, roles are usually defined by `acl.define`. - -## Class Methods - -### `constructor()` - -Constructor. - -**Signature** -* `constructor(public acl: ACL, public name: string)` - -**Detailed Information** -* acl - ACL instance -* name - Name of the role - -### `grantAction()` - -Grant the action permission to the role. - -**Signature** -* `grantAction(path: string, options?: RoleActionParams)` - -**Type** -```typescript -interface RoleActionParams { - fields?: string[]; - filter?: any; - own?: boolean; - whitelist?: string[]; - blacklist?: string[]; - [key: string]: any; -} -``` - -**Detailed Information** - -* path - Action path of the resource, such as `posts:edit`, which means the `edit` action of the `posts` resource. Use colon `:` to separate the name of resource and action. - -When RoleActionParams is to grant permission, the corresponding action can be configured with parameters to achieve finer-grained permission control. - -* fields - Accessible fields - ```typescript - acl.define({ - role: 'admin', - actions: { - 'posts:view': { - // admin user can request posts:view action, but limited to the configured fields - fields: ["id", "title", "content"], - }, - }, - }); - ``` -* filter - Permission resource filtering configuration - ```typescript - acl.define({ - role: 'admin', - actions: { - 'posts:view': { - // admin user can request posts:view action, but the listed results is filtered by conditions in the filter - filter: { - createdById: '{{ ctx.state.currentUser.id }}', // Template syntax is supported to take the value in ctx, and will be replaced when checking permissions - }, - }, - }, - }); - ``` -* own - Whether to access only your own data - ```typescript - const actionsWithOwn = { - 'posts:view': { - "own": true // - } - } - - // Equivalent to - const actionsWithFilter = { - 'posts:view': { - "filter": { - "createdById": "{{ ctx.state.currentUser.id }}" - } - } - } - ``` -* whitelist - Whitelist, only the fields in whitelist can be accessed -* blacklist - Blacklist, fields in blacklist cannot be accessed diff --git a/docs/en-US/api/acl/acl.md b/docs/en-US/api/acl/acl.md deleted file mode 100644 index 568b1766d..000000000 --- a/docs/en-US/api/acl/acl.md +++ /dev/null @@ -1,259 +0,0 @@ -# ACL - -## Overview - -ACL is the permission control module in NocoBase. After registering roles and resources in ACL and configuring corresponding permissions, you can authenticate permissions for roles. - -### Basic Usage - -```javascript -const { ACL } = require('@nocobase/acl'); - -const acl = new ACL(); - -// Define a role named member -const memberRole = acl.define({ - role: 'member', -}); - -// Grant the role of member list permission of the posts resource -memberRole.grantAction('posts:list'); - -acl.can('member', 'posts:list'); // true -acl.can('member', 'posts:edit'); // null -``` - -### Concepts - -* Role (`ACLRole`): Object that needs permission authentication. -* Resource (`ACLResource`):In NocoBase ACL, a resource usually corresponds to a database table; it is conceptually analogous to the Resource in Restful API. -* Action: Actions to be taken on resources, such as `create`, `read`, `update`, `delete`, etc. -* Strategy (`ACLAvailableStrategy`): Normally each role has its own permission strategy, which defines the default permissions of the role. -* Grant Action: Call the `grantAction` function in `ACLRole` instance to grant access to `Action` for the role. -* Authentication: Call the `can` function in `ACL` instance, and return the authentication result of the user. - -## Class Methods - -### `constructor()` - -To create a `ACL` instance. - -```typescript -import { ACL } from '@nocobase/database'; - -const acl = new ACL(); -``` - -### `define()` - -Define a ACL role. - -**Signature** - -* `define(options: DefineOptions): ACLRole` - -**Type** - -```typescript -interface DefineOptions { - role: string; - allowConfigure?: boolean; - strategy?: string | AvailableStrategyOptions; - actions?: ResourceActionsOptions; - routes?: any; -} -``` - -**Detailed Information** - -* `role` - Name of the role - -```typescript -// Define a role named admin -acl.define({ - role: 'admin', -}); -``` - -* `allowConfigure` - Whether to allow permission configuration -* `strategy` - Permission strategy of the role - * It can be a name of strategy in `string`, means to use a strategy that is already defined. - * Or `AvailableStrategyOptions` means to define a new strategy for this role, refer to [`setAvailableActions()`](#setavailableactions). -* `actions` - Pass in the `actions` objects accessible to the role when defining the role, then call `aclRole.grantAction` in turn to grant resource permissions. Refer to [`aclRole.grantAction`](./acl-role.md#grantaction) for details - -```typescript -acl.define({ - role: 'admin', - actions: { - 'posts:edit': {} - }, -}); -// Equivalent to -const role = acl.define({ - role: 'admin', -}); - -role.grantAction('posts:edit', {}); -``` - -### `getRole()` - -Get registered role objects by role name. - -**Signature** - -* `getRole(name: string): ACLRole` - -### `removeRole()` - -Remove role by role name. - -**Signature** - -* `removeRole(name: string)` - -### `can()` - -Authentication function. - -**Signature** - -* `can(options: CanArgs): CanResult | null` - -**Type** - -```typescript -interface CanArgs { - role: string; // Name of the role - resource: string; // Name of the resource - action: string; // Name of the action -} - -interface CanResult { - role: string; // Name of the role - resource: string; // Name of the resource - action: string; // Name of the action - params?: any; // Parameters passed in when registering the permission -} - -``` - -**Detailed Information** - -The `can` method first checks if the role has the corresponding `Action` permission registered; if not, it checks if the `strategy` and the role matches. It means that the role has no permissions if it returns `null`; else it returns the `CanResult` object, which means that the role has permissions. - -**Example** - -```typescript -// Define role and register permissions -acl.define({ - role: 'admin', - actions: { - 'posts:edit': { - fields: ['title', 'content'], - }, - }, -}); - -const canResult = acl.can({ - role: 'admin', - resource: 'posts', - action: 'edit', -}); -/** - * canResult = { - * role: 'admin', - * resource: 'posts', - * action: 'edit', - * params: { - * fields: ['title', 'content'], - * } - * } - */ - -acl.can({ - role: 'admin', - resource: 'posts', - action: 'destroy', -}); // null -``` - -### `use()` - -**Signature** - -* `use(fn: any)` -Add middleware function into middlewares. - -### `middleware()` - -Return a middleware function to be used in `@nocobase/server`. After using this `middleware`, `@nocobase/server` will perform permission authentication before each request is processed. - -### `allow()` - -Set the resource as publicly accessible. - -**Signature** - -* `allow(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc)` - -**Type** - -```typescript -type ConditionFunc = (ctx: any) => Promise | boolean; -``` - -**Detailed Information** - -* resourceName - Name of the resource -* actionNames - Name of the resource action -* condition? - Configuration of the validity condition - * Pass in a `string` to use a condition that is already defined; Use the `acl.allowManager.registerCondition` method to register a condition. - ```typescript - acl.allowManager.registerAllowCondition('superUser', async () => { - return ctx.state.user?.id === 1; - }); - - // Open permissions of the users:list with validity condition superUser - acl.allow('users', 'list', 'superUser'); - ``` - * Pass in ConditionFunc, which can take the `ctx` parameter; return `boolean` that indicate whether it is in effect. - ```typescript - // user:list accessible to user with ID of 1 - acl.allow('users', 'list', (ctx) => { - return ctx.state.user?.id === 1; - }); - ``` - -**Example** - -```typescript -// Register users:login to be publicly accssible -acl.allow('users', 'login'); -``` - -### `setAvailableActions()` - -**Signature** - -* `setAvailableStrategy(name: string, options: AvailableStrategyOptions)` - -Register an available permission strategy. - -**Type** - -```typescript -interface AvailableStrategyOptions { - displayName?: string; - actions?: false | string | string[]; - allowConfigure?: boolean; - resource?: '*'; -} -``` - -**Detailed Information** - -* displayName - Name of the strategy -* allowConfigure - Whether this strategy has permission of **resource configuration**; if set to `true`, the permission that requests to register as `configResources` resource in `ACL` will return pass -* actions - List of actions in the strategy, wildcard `*` is supported -* resource - Definition of resource in the strategy, wildcard `*` is supported diff --git a/docs/en-US/api/actions.md b/docs/en-US/api/actions.md deleted file mode 100644 index eb644c364..000000000 --- a/docs/en-US/api/actions.md +++ /dev/null @@ -1,357 +0,0 @@ -# Built-in Common Resource Actions - -## Overview - -NocoBase has built-in operation methods for commonly used actions of data resources, such as CRUD, and automatically maps related actions through data table resources. - -```javascript -import { Application } from "@nocobase/server"; - -const app = new Application({ - database: { - dialect: 'sqlite', - storage: './db.sqlite', - }, - registerActions: true // Register built-in resource actions, true by default -}); - -``` - -Built-in actions are registered to the `resourcer` instance in `application`. Generally, built-in actions are not called directly unless you need to extend the default action, then you can call the default method within a custom action method. - -## Resource Actions - -### `list()` - -Get a list of data. The URL for the corresponding resource action is `GET /api/:list`. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `filter` | `Filter` | - | Filtering parameter | -| `fields` | `string[]` | - | Fields to get | -| `except` | `string[]` | - | Fields to exclude | -| `appends` | `string[]` | - | Association fields to append | -| `sort` | `string[]` | - | Sorting parameter | -| `page` | `number` | 1 | Page break | -| `pageSize` | `number` | 20 | Size per page | - -**Example** - -When there is a need to provide an interface for querying a list of data that is not output in JSON format by default, it can be extended based on the built-in default method: - -```ts -import actions from '@nocobase/actions'; - -app.actions({ - async ['books:list'](ctx, next) { - ctx.action.mergeParams({ - except: ['content'] - }); - - await actions.list(ctx, async () => { - const { rows } = ctx.body; - // transform JSON to CSV output - ctx.body = rows.map(row => Object.keys(row).map(key => row[key]).join(',')).join('\n'); - ctx.type = 'text/csv'; - - await next(); - }); - } -}); -``` - -Example of a request that will get a file in CSV format: - -```shell -curl -X GET http://localhost:13000/api/books:list -``` - -### `get()` - -Get a single piece of data. The URL for the corresponding resource action is `GET /api/:get`. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `filterByTk` | `number \| string` | - | Filtering primary key | -| `filter` | `Filter` | - | Filtering parameter | -| `fields` | `string[]` | - | Fields to get | -| `except` | `string[]` | - | Fields to exclude | -| `appends` | `string[]` | - | Association fields to append | -| `sort` | `string[]` | - | Sorting parameter | -| `page` | `number` | 1 | Page break | -| `pageSize` | `number` | 20 | Size per page | - -**Example** - -Extend the build-in file management plugin of NocoBase to return file stream when the client requests to download a file with the resource identifier: - -```ts -import path from 'path'; -import actions from '@nocobase/actions'; -import { STORAGE_TYPE_LOCAL } from '@nocobase/plugin-file-manager'; - -app.actions({ - async ['attachments:get'](ctx, next) { - ctx.action.mergeParams({ - appends: ['storage'], - }); - - await actions.get(ctx, async () => { - if (ctx.accepts('json', 'application/octet-stream') === 'json') { - return next(); - } - - const { body: attachment } = ctx; - const { storage } = attachment; - - if (storage.type !== STORAGE_TYPE_LOCAL) { - return ctx.redirect(attachment.url); - } - - ctx.body = fs.createReadStream(path.resolve(storage.options.documentRoot?, storage.path)); - ctx.attachment(attachment.filename); - ctx.type = 'application/octet-stream'; - - await next(); - }); - } -}); -``` - -Example request that will get the file stream: - -```shell -curl -X GET -H "Accept: application/octet-stream" http://localhost:13000/api/attachments:get?filterByTk=1 -``` - -### `create()` - -Create a single piece of data. The URL for the corresponding resource action is `POST /api/:create`. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `values` | `Object` | - | The data to create | - -**Example** - -Create data with binary content as attachment to the uploaded file, it is similar to the file management plugin: - -```ts -import multer from '@koa/multer'; -import actions from '@nocobase/actions'; - -app.actions({ - async ['files:create'](ctx, next) { - if (ctx.request.type === 'application/json') { - return actions.create(ctx, next); - } - - if (ctx.request.type !== 'multipart/form-data') { - return ctx.throw(406); - } - - // Only use multer() as example here to save and process file, it does not represent the full logic - multer().single('file')(ctx, async () => { - const { file, body } = ctx.request; - const { filename, mimetype, size, path } = file; - - ctx.action.mergeParams({ - values: { - filename, - mimetype, - size, - path: file.path, - meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; - } - }); - - await actions.create(ctx, next); - }); - } -}); -``` - -Example request to create plain data for a file table, you can submit it with an attachment: - -```shell -# Create plain data only -curl -X POST -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:create" - -# Submit with attachment -curl -X POST -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:create" -``` - -### `update()` - -Update one or more pieces of data. The corresponding URL is `PUT /api/:update`. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `filter` | `Filter` | - | Filtering parameter | -| `filterByTk` | `number \| string` | - | Filtering primary key | -| `values` | `Object` | - | Data values to update | - -Note: Either or both `filter` or `filterByTk` should be provided. - -**Example** - -Similar to the example of `create()`, you can extend updating file record to a file that carries data with binary content: - -```ts -import multer from '@koa/multer'; -import actions from '@nocobase/actions'; - -app.actions({ - async ['files:update'](ctx, next) { - if (ctx.request.type === 'application/json') { - return actions.update(ctx, next); - } - - if (ctx.request.type !== 'multipart/form-data') { - return ctx.throw(406); - } - - // Only use multer() as example here to save and process file, it does not represent the full logic - multer().single('file')(ctx, async () => { - const { file, body } = ctx.request; - const { filename, mimetype, size, path } = file; - - ctx.action.mergeParams({ - values: { - filename, - mimetype, - size, - path: file.path, - meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; - } - }); - - await actions.update(ctx, next); - }); - } -}); -``` - -Example request to create plain data for a file table, you can submit it with an attachment: - -```shell -# Create plain data only -curl -X PUT -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:update" - -# Submit with attachment -curl -X PUT -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:update" -``` - -### `destroy()` - -Delete one or more pieces of data. The corresponding URL is `DELETE /api/:destroy`. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `filter` | `Filter` | - | Filtering parameter | -| `filterByTk` | `number \| string` | - | Filtering primary key | - -Note: Either or both `filter` or `filterByTk` should be provided. - -**Example** - -Similar to the file management plug-in extension, a deletion of file data also requires the deletion of the corresponding file operation processing simultaneously: - -```ts -import actions from '@nocobase/actions'; - -app.actions({ - async ['files:destroy'](ctx, next) { - // const repository = getRepositoryFromParams(ctx); - - // const { filterByTk, filter } = ctx.action.params; - - // const items = await repository.find({ - // fields: [repository.collection.filterTargetKey], - // appends: ['storage'], - // filter, - // filterByTk, - // context: ctx, - // }); - - // await items.reduce((promise, item) => promise.then(async () => { - // await item.removeFromStorage(); - // await item.destroy(); - // }), Promise.resolve()); - - await actions.destroy(ctx, async () => { - // do something - await next(); - }); - } -}); -``` - -### `move()` - -The corresponding URL is `POST /api/:move`. - -This method is used to move data and adjust the order of data. For example, if you drag an element above or below another element in a page, you can call this method to achieve order adjustment. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `sourceId` | `targetKey` | - | ID of the element to move | -| `targetId` | `targetKey` | - | ID of the element to switch position with the moving element | -| `sortField` | `string` | `sort` | The stored field names of sorting | -| `targetScope` | `string` | - | The scope of sorting, a resource can be sorted by different scopes | -| `sticky` | `boolean` | - | Whether or not to top the moving element | -| `method` | `insertAfter` \| `prepend` | - | Type of insertion, before or after the target element | - -## Resource Actions of Association Resource - -### `add()` - -Add an association to an object. The corresponding URL is `POST /api/:add`. Apply to `hasMany` and `belongsToMany` associations. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `values` | `TargetKey \| TargetKey[]` | - | ID of the association object to add | - -### `remove()` - -Remove the association to an object. The corresponding URL is `POST /api/:remove`. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `values` | `TargetKey \| TargetKey[]` | - | ID of the associated object to remove | - -### `set()` - -Set the associated association object. The corresponding URL is `POST /api/:set`. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `values` | `TargetKey \| TargetKey[]` | - | ID of the association object to set | - -### `toggle()` - -Toggle the associated association object. The corresponding URL is `POST /api/:toggle`. `toggle` internally determines if the associated object already exists, removes it if it does, otherwise adds it. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `values` | `TargetKey` | - | ID of the association object to toggle | diff --git a/docs/en-US/api/cli.md b/docs/en-US/api/cli.md deleted file mode 100644 index a96084f02..000000000 --- a/docs/en-US/api/cli.md +++ /dev/null @@ -1,350 +0,0 @@ -# @nocobase/cli - -The NocoBase CLI is designed to help you develop, build, and deploy NocoBase applications. - - - -NocoBase CLI supports ts-node and node two operation modes. - -- ts-node mode (Default): Used for development environment, support real-time compilation, with relatively slow response -- node mode:Used for production environment, with quick response, but you need to execute `yarn nocobase build` to compile the entire source code first - - - -## Instructions For Use - -```bash -$ yarn nocobase -h - -Usage: nocobase [command] [options] - -Options: - -h, --help - -Commands: - console - db:auth Verify if the database is successfully connected - db:sync Generate relevant data tables and fields through the configuration of collections - install Install - start Start application in production environment - build Compile and package - clean Delete the compiled files - dev Start application for development environment with real-time compilation - doc Documentation development - test Testing - umi - upgrade Upgrade - migrator Data migration - pm Plugin manager - help -``` - -## Application in Scaffolding - -`scripts` in the application scaffolding `package.json` is as below: - -```json -{ - "scripts": { - "dev": "nocobase dev", - "start": "nocobase start", - "clean": "nocobase clean", - "build": "nocobase build", - "test": "nocobase test", - "pm": "nocobase pm", - "postinstall": "nocobase postinstall" - } -} -``` - -## Command Line Extensions - -NocoBase CLI is built based on [commander](https://github.com/tj/commander.js). You can write the extended commands freely in `app/server/index.ts`: - -```ts -const app = new Application(config); - -app.command('hello').action(() => {}); -``` - -or in the plugin: - -```ts -class MyPlugin extends Plugin { - beforeLoad() { - this.app.command('hello').action(() => {}); - } -} -``` - -Run in the terminal: - -```bash -$ yarn nocobase hello -``` - -## Built-in Commands - -Sorted by frequency of use. - -### `dev` - -Start application and compile code in real time in development environment. - - -NocoBase is installed automatically if it is not installed (Refer to the `install` command). - - -```bash -Usage: nocobase dev [options] - -Options: - -p, --port [port] - --client - --server - -h, --help -``` - -Example: - -```bash -# Launch application for development environment, with real-time compilation -yarn nocobase dev -# Start the server side only -yarn nocobase dev --server -# Start the client side only -yarn nocobase dev --client -``` - -### `start` - -Start application in production environment, the code needs yarn build. - - - -- NocoBase is installed automatically if it is not installed (Refer to the `install` command). -- The source code needs to be re-packaged if it has any modification (Refer to the `build` command). - - - -```bash -$ yarn nocobase start -h - -Usage: nocobase start [options] - -Options: - -p, --port - -s, --silent - -h, --help -``` - -Example: - -```bash -# Launch application for production environment -yarn nocobase start -``` - -### `install` - -Install. - -```bash -$ yarn nocobase install -h - -Usage: nocobase install [options] - -Options: - -f, --force - -c, --clean - -s, --silent - -l, --lang [lang] - -e, --root-email - -p, --root-password - -n, --root-nickname [rootNickname] - -h, --help -``` - -Example: - -```bash -# Initial installation -yarn nocobase install -l zh-CN -e admin@nocobase.com -p admin123 -# Delete all data tables from NocoBase and reinstall -yarn nocobase install -f -l zh-CN -e admin@nocobase.com -p admin123 -# Clear database and reinstall -yarn nocobase install -c -l zh-CN -e admin@nocobase.com -p admin123 -``` - - - -Difference between `-f/--force` and `-c/--clean`: -- `-f/--force` Delete data tables of NocoBase -- `-c/--clean` Clear database, all data tables are deleted - - - -### `upgrade` - -Upgrade. - -```bash -yarn nocobase upgrade -``` - -### `test` - -jest test, which supports all [jest-cli](https://jestjs.io/docs/cli) options, also supports `-c, --db-clean`. - -```bash -$ yarn nocobase test -h - -Usage: nocobase test [options] - -Options: - -c, --db-clean Clear database before running all tests - -h, --help -``` - -Example: - -```bash -# Run all test files -yarn nocobase test -# Run all test files in the specified folder -yarn nocobase test packages/core/server -# Run all tests in the specified file -yarn nocobase test packages/core/database/src/__tests__/database.test.ts - -# Clear database before running all tests -yarn nocobase test -c -yarn nocobase test packages/core/server -c -``` - -### `build` - -The source code needs to be compiled and packaged before the code is deployed to the production environment; and you need to re-build the code if it has any modification. - -```bash -# All packages -yarn nocobase build -# Specified packages -yarn nocobase build app/server app/client -``` - -### `clean` - -Delete the compiled files. - -```bash -yarn clean -# Equivalent to -yarn rimraf -rf packages/*/*/{lib,esm,es,dist} -``` - -### `doc` - -Documentation development. - -```bash -# Start the documentation -yarn doc --lang=zh-CN # Equivalent to yarn doc dev -# Build the documentation, and output it to . /docs/dist/ directory by default -yarn doc build -# View the final result of the output documentation of dist -yarn doc serve --lang=zh-CN -``` - -### `db:auth` - -Verify if the database is successfully connected. - -```bash -$ yarn nocobase db:auth -h - -Usage: nocobase db:auth [options] - -Options: - -r, --retry [retry] Number of retries - -h, --help -``` - -### `db:sync` - -Generate relevant data tables and fields through the configuration of collections. - -```bash -$ yarn nocobase db:sync -h - -Usage: nocobase db:sync [options] - -Options: - -f, --force - -h, --help display help for command -``` - -### `migrator` - -Data migration. - -```bash -$ yarn nocobase migrator - -Positional arguments: - - up Applies pending migrations - down Revert migrations - pending Lists pending migrations - executed Lists executed migrations - create Create a migration file -``` - -### `pm` - -Plugin manager. - -```bash -# Create plugin -yarn pm create hello -# Register plugin -yarn pm add hello -# Enable plugin -yarn pm enable hello -# Disable plugin -yarn pm disable hello -# Remove plugin -yarn pm remove hello -``` - -Not achieved yet: - -```bash -# Upgrade plugin -yarn pm upgrade hello -# Publish plugin -yarn pm publish hello -``` - -### `umi` - -`app/client` is built based on [umi](https://umijs.org/), you can run other relevant commands through `nocobase umi`. - -```bash -# Generate the .umi cache needed for the development environment -yarn nocobase umi generate tmp -``` - -### `help` - -The help command, you can also use the option parameter, `-h` and `--help`. - -```bash -# View all cli -yarn nocobase help -# Use -h instead -yarn nocobase -h -# Or --help -yarn nocobase --help -# View options of command db:sync -yarn nocobase db:sync -h -``` diff --git a/docs/en-US/api/client/application.md b/docs/en-US/api/client/application.md deleted file mode 100644 index 340ed42f9..000000000 --- a/docs/en-US/api/client/application.md +++ /dev/null @@ -1,60 +0,0 @@ -# Application - -## Constructor - -### `constructor()` - -Create an application instance. - -**Signature** - -* `constructor(options: ApplicationOptions)` - -**Example** - -```ts -const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`../plugins/${name}`); - }, -}); -``` - -## Methods - -### use() - -Add Providers, build-in Providers are: - -- APIClientProvider -- I18nextProvider -- AntdConfigProvider -- SystemSettingsProvider -- PluginManagerProvider -- SchemaComponentProvider -- BlockSchemaComponentProvider -- AntdSchemaComponentProvider -- ACLProvider -- RemoteDocumentTitleProvider - -### render() - -Component to render the App. - -```ts -import { Application } from '@nocobase/client'; - -export const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`../plugins/${name}`); - }, -}); - -export default app.render(); -``` diff --git a/docs/en-US/api/client/extensions/acl.md b/docs/en-US/api/client/extensions/acl.md deleted file mode 100644 index 09d8e9a94..000000000 --- a/docs/en-US/api/client/extensions/acl.md +++ /dev/null @@ -1,23 +0,0 @@ -# ACL - -## Components - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -## Hooks - -### `useACLContext()` - -### `useACLRoleContext()` - -### `useRoleRecheck()` diff --git a/docs/en-US/api/client/extensions/block-provider.md b/docs/en-US/api/client/extensions/block-provider.md deleted file mode 100644 index 65baa50cd..000000000 --- a/docs/en-US/api/client/extensions/block-provider.md +++ /dev/null @@ -1,25 +0,0 @@ -# BlockProvider - -## Kernel Methods - -### `` - -### `useBlockRequestContext()` - -## Build-in BlockProvider Components - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` diff --git a/docs/en-US/api/client/extensions/collection-manager.md b/docs/en-US/api/client/extensions/collection-manager.md deleted file mode 100644 index 899af11fc..000000000 --- a/docs/en-US/api/client/extensions/collection-manager.md +++ /dev/null @@ -1,268 +0,0 @@ -# CollectionManager - -## Components - -### CollectionManagerProvider - -```jsx | pure - -``` - -### CollectionProvider - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - -``` - -If there is no collection parameter passed in, get the collection from CollectionManagerProvider with the corresponding name. - -```jsx | pure -const collections = [ - { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], - } -]; - - - -``` - -### CollectionFieldProvider - -```jsx | pure -const field = { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, -}; - -``` - -If there is no field parameter passed in, get the field from CollectionProvider with the corresponding name. - - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - - - -``` - -### CollectionField - -Universal field component that needs to be used with ``, but only in schema scenarios. Get the field schema from CollectionProvider with the corresponding name. Extend the configuration via the schema where the CollectionField is located. - -```ts -{ - name: 'title', - 'x-decorator': 'FormItem', - 'x-decorator-props': {}, - 'x-component': 'CollectionField', - 'x-component-props': {}, - properties: {}, -} -``` - -## Hooks - -### useCollectionManager() - -Use with ``. - -```jsx | pure -const { collections, get } = useCollectionManager(); -``` - -### useCollection() - -Use with ``. - -```jsx | pure -const { name, fields, getField, findField, resource } = useCollection(); -``` - -### useCollectionField() - -Use with ``. - -```jsx | pure -const { name, uiSchema, resource } = useCollectionField(); -``` - -The resource needs to be used with `` to provide context of the record of the current data table row. - -# CollectionManager - -## Components - -### CollectionManagerProvider - -```jsx | pure - -``` - -### CollectionProvider - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - -``` - -If there is no collection parameter passed in, get the collection from CollectionManagerProvider with the corresponding name. - -```jsx | pure -const collections = [ - { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], - } -]; - - - -``` - -### CollectionFieldProvider - -```jsx | pure -const field = { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, -}; - -``` - -If there is no field parameter passed in, get the field from CollectionProvider with the corresponding name. - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - - - -``` - -### CollectionField - -Universal field component that needs to be used with ``, but only in schema scenarios. Get the field schema from CollectionProvider with the corresponding name. Extend the configuration via the schema where the CollectionField is located. - -```ts -{ - name: 'title', - 'x-decorator': 'FormItem', - 'x-decorator-props': {}, - 'x-component': 'CollectionField', - 'x-component-props': {}, - properties: {}, -} -``` - -## Hooks - -### useCollectionManager() - -Use with ``. - -```jsx | pure -const { collections, get } = useCollectionManager(); -``` - -### useCollection() - -Use with ``. - -```jsx | pure -const { name, fields, getField, findField, resource } = useCollection(); -``` - -### useCollectionField() - -Use with ``. - -```jsx | pure -const { name, uiSchema, resource } = useCollectionField(); -``` - -The resource needs to be used with `` to provide context of the record of the current data table row. diff --git a/docs/en-US/api/client/extensions/schema-component.md b/docs/en-US/api/client/extensions/schema-component.md deleted file mode 100644 index 832818f75..000000000 --- a/docs/en-US/api/client/extensions/schema-component.md +++ /dev/null @@ -1,14 +0,0 @@ -# 适配的 Schema 组件 - -## Common - -- DndContext -- SortableItem - -## And Design - -- Action -- BlockItem -- Calendar -- CardItem -- Cascader diff --git a/docs/en-US/api/client/index.md b/docs/en-US/api/client/index.md deleted file mode 100644 index 3a2170da0..000000000 --- a/docs/en-US/api/client/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Overview - -test diff --git a/docs/en-US/api/client/router.md b/docs/en-US/api/client/router.md deleted file mode 100644 index 38436ea7c..000000000 --- a/docs/en-US/api/client/router.md +++ /dev/null @@ -1,180 +0,0 @@ -# Router - -## API - -### Initial - -```tsx | pure - -const app = new Application({ - router: { - type: 'browser' // type default value is `browser` - } -}) - -// or -const app = new Application({ - router: { - type: 'memory', - initialEntries: ['/'] - } -}) -``` - -### add Route - -#### basic - -```tsx | pure -import { RouteObject } from 'react-router-dom' -const app = new Application() - -const Hello = () => { - return
Hello
-} - -// first argument is `name` of route, second argument is `RouteObject` -app.router.add('root', { - path: '/', - element: -}) - -app.router.add('root', { - path: '/', - Component: Hello -}) -``` - -#### Component is String - -```tsx | pure -app.addComponents({ - Hello -}) -app.router.add('root', { - path: '/', - Component: 'Hello' -}) -``` - -#### nested - -```tsx | pure -import { Outlet } from 'react-router-dom' - -const Layout = () => { - return
- Home - about - - -
-} - -const Home = () => { - return
Home
-} - -const About = () => { - return
About
-} - -app.router.add('root', { - element: -}) -app.router.add('root.home', { - path: '/home', - element: -}) -app.router.add('root.about', { - path: '/about', - element: -}) -``` - -It will generate the following routes: - -```tsx | pure -{ - element: , - children: [ - { - path: '/home', - element: - }, - { - path: '/about', - element: - } - ] -} -``` - -### remove Route - -```tsx | pure -// remove route by name -app.router.remove('root.home') -app.router.remove('hello') -``` - -#### Router in plugin - -```tsx | pure -class MyPlugin extends Plugin { - async load() { - // add route - this.app.router.add('hello', { - path: '/hello', - element:
hello
, - }) - - // remove route - this.app.router.remove('world'); - } -} -``` - -## Example - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Link, Outlet } from 'react-router-dom'; -import { Application } from '@nocobase/client'; - -const Home = () =>

Home

; -const About = () =>

About

; - -const Layout = () => { - return
-
Home, About
- -
-} - -const app = new Application({ - router: { - type: 'memory', - initialEntries: ['/'] - } -}) - -app.router.add('root', { - element: -}) - -app.router.add('root.home', { - path: '/', - element: -}) - -app.router.add('root.about', { - path: '/about', - element: -}) - -export default app.getRootComponent(); -``` diff --git a/docs/en-US/api/client/schema-designer/schema-component.md b/docs/en-US/api/client/schema-designer/schema-component.md deleted file mode 100644 index 9ea98588f..000000000 --- a/docs/en-US/api/client/schema-designer/schema-component.md +++ /dev/null @@ -1,13 +0,0 @@ -# SchemaComponent - -## Core Components - -### `` -### `` -### `` - -## Core Methods - -### `createDesignable()` -### `useDesignable()` -### `useCompile()` diff --git a/docs/en-US/api/client/schema-designer/schema-initializer.md b/docs/en-US/api/client/schema-designer/schema-initializer.md deleted file mode 100644 index 7e047db62..000000000 --- a/docs/en-US/api/client/schema-designer/schema-initializer.md +++ /dev/null @@ -1,19 +0,0 @@ -# SchemaInitializer - -Used for the initialization of various schemas. Newly added schema can be inserted anywhere in an existing schema node, including: - -```ts -{ - properties: { - // beforeBegin - Insert in front of the current node - node1: { - properties: { - // afterBegin - Insert in front of the first child node of the current node - // ... - // beforeEnd - After the last child node of the current node - }, - }, - // afterEnd - After the current node - }, -} -``` diff --git a/docs/en-US/api/client/schema-designer/schema-settings.md b/docs/en-US/api/client/schema-designer/schema-settings.md deleted file mode 100644 index b014e7435..000000000 --- a/docs/en-US/api/client/schema-designer/schema-settings.md +++ /dev/null @@ -1,25 +0,0 @@ -# SchemaSettings - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` diff --git a/docs/en-US/api/database/collection.md b/docs/en-US/api/database/collection.md deleted file mode 100644 index afad2a7f6..000000000 --- a/docs/en-US/api/database/collection.md +++ /dev/null @@ -1,510 +0,0 @@ -# Collection - -## Overview - -`Collection` is used to define the data model in the system, such as model name, fields, indexes, associations, and other information. It is usually called through the `collection` method of the `Database` instance as a proxy entry. - -```javascript -const { Database } = require('@nocobase/database') - -// Create database instance -const db = new Database({...}); - -// Define data model -db.collection({ - name: 'users', - // Define model fields - fields: [ - // Scalar field - { - name: 'name', - type: 'string', - }, - - // Association field - { - name: 'profile', - type: 'hasOne' // 'hasMany', 'belongsTo', 'belongsToMany' - } - ], -}); -``` - -Refer to [Fields](/api/database/field.md) for more field types. - -## Constructor - -**Signature** - -* `constructor(options: CollectionOptions, context: CollectionContext)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.name` | `string` | - | Identifier of the collection | -| `options.tableName?` | `string` | - | Database table name, the value of `options.name` is used if not set | -| `options.fields?` | `FieldOptions[]` | - | Definition of fields, refer to [Field](./field) for details | -| `options.model?` | `string \| ModelStatic` | - | Model type of Sequelize; in case `string` is used, this model name needs to be registered in the db before being called | -| `options.repository?` | `string \| RepositoryType` | - | Data repository type; in case `string` is used, this repository type needs to be registered in the db before being called | -| `options.sortable?` | `string \| boolean \| { name?: string; scopeKey?: string }` | - | Configure which fields are sortable; not sortable by default | -| `options.autoGenId?` | `boolean` | `true` | Whether to automatically generate unique primary key; `true` by default | -| `context.database` | `Database` | - | The context database in which it resides | - -**Example** - -Create a table posts: - -```ts -const posts = new Collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - }, - { - type: 'double', - name: 'price', - } - ] -}, { - // An existing database instance - database: db -}); -``` - -## Instance Members - -### `options` - -Initial parameters for data table configuration, which are consistent with the `options` parameter of the constructor. - -### `context` - -The contextual environment to which the current data table belongs, currently mainly the database instance. - -### `name` - -Name of the data table. - -### `db` - -The database instance to which it belongs. - -### `filterTargetKey` - -Name of the field that is used as the primary key. - -### `isThrough` - -Whether it is an intermediate table. - -### `model` - -Match the Model type of Sequelize. - -### `repository` - -Data repository instance. - -## Field Configuration Methods - -### `getField()` - -Get a field object whose corresponding name has been defined in the data table. - -**Signature** - -* `getField(name: string): Field` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the field | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -const field = posts.getField('title'); -``` - -### `setField()` - -Set a field to the data table. - -**Signature** - -* `setField(name: string, options: FieldOptions): Field` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the field | -| `options` | `FieldOptions` | - | Configuration of the field, refer to [Field](./field) for details | - -**Example** - -```ts -const posts = db.collection({ name: 'posts' }); - -posts.setField('title', { type: 'string' }); -``` - -### `setFields()` - -Set multiple fields to the data table. - -**Signature** - -* `setFields(fields: FieldOptions[], resetFields = true): Field[]` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `fields` | `FieldOptions[]` | - | Configuration of the fields, refer to [Field](./field) for details | -| `resetFields` | `boolean` | `true` | Whether to reset existing fields | - -**Example** - -```ts -const posts = db.collection({ name: 'posts' }); - -posts.setFields([ - { type: 'string', name: 'title' }, - { type: 'double', name: 'price' } -]); -``` - -### `removeField()` - -Remove a field object whose corresponding name has been defined in the data table. - -**Signature** - -* `removeField(name: string): void | Field` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the field | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.removeField('title'); -``` - -### `resetFields()` - -Reset (Empty) fields of the data table. - -**Signature** - -* `resetFields(): void` - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.resetFields(); -``` - -### `hasField()` - -Check if the data table has defined a field object with the corresponding name. - -**Signature** - -* `hasField(name: string): boolean` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the field | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.hasField('title'); // true -``` - -### `findField()` - -Find field objects in the data table that match the conditions. - -**Signature** - -* `findField(predicate: (field: Field) => boolean): Field | undefined` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `predicate` | `(field: Field) => boolean` | - | The condition | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.findField(field => field.name === 'title'); -``` - -### `forEachField()` - -Iterate over field objects in the data table. - -**Signature** - -* `forEachField(callback: (field: Field) => void): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `callback` | `(field: Field) => void` | - | Callback function | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.forEachField(field => console.log(field.name)); -``` - -## Index Configuration Methods - -### `addIndex()` - -Add data table index. - -**Signature** - -* `addIndex(index: string | string[] | { fields: string[], unique?: boolean,[key: string]: any })` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `index` | `string \| string[]` | - | Names of fields to be indexed | -| `index` | `{ fields: string[], unique?: boolean, [key: string]: any }` | - | Full configuration | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.addIndex({ - fields: ['title'], - unique: true -}); -``` - -### `removeIndex()` - -Remove data table index. - -**Signature** - -* `removeIndex(fields: string[])` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `fields` | `string[]` | - | Names of fields to remove indexes | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ], - indexes: [ - { - fields: ['title'], - unique: true - } - ] -}); - -posts.removeIndex(['title']); -``` - -## Table Configuration Methods - -### `remove()` - -Remove data table. - -**Signature** - -* `remove(): void` - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.remove(); -``` - -## Database Operation Methods - -### `sync()` - -Synchronize the definitions in data table to the database. In addition to the default `Model.sync` logic in Sequelize, the data tables corresponding to the relational fields will also be handled together. - -**Signature** - -* `sync(): Promise` - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -await posts.sync(); -``` - -### `existsInDb()` - -Check whether the data table exists in the database. - -**Signature** - -* `existsInDb(options?: Transactionable): Promise` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options?.transaction` | `Transaction` | - | Transaction instance | - -**Example** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -const existed = await posts.existsInDb(); - -console.log(existed); // false -``` - -### `removeFromDb()` - -**Signature** - -* `removeFromDb(): Promise` - -**Example** - -```ts -const books = db.collection({ - name: 'books' -}); - -// Synchronize the table books to the database -await db.sync(); - -// Remove the table books from the database -await books.removeFromDb(); -``` diff --git a/docs/en-US/api/database/field.md b/docs/en-US/api/database/field.md deleted file mode 100644 index 6b52f1f8b..000000000 --- a/docs/en-US/api/database/field.md +++ /dev/null @@ -1,558 +0,0 @@ -# Field - -## Overview - -Data table field management class (abstract class). It is also the base class for all field types, and any other field types are implemented by inheriting from this class. - -Refer to [Extended Field Types](/development/guide/collections-fields#extended-field-types) to see how to customize fields. - -## Constructor - -It is usually not called directly by the developer, but mainly through the `db.collection({ fields: [] })` method as a proxy entry. - -Extended field is implemented mainly by inheriting the `Field` abstract class and registering it to a Database instance. - -**Signature** - -* `constructor(options: FieldOptions, context: FieldContext)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options` | `FieldOptions` | - | Field configuration object | -| `options.name` | `string` | - | Field name | -| `options.type` | `string` | - | Field type, corresponding to the name of the field type registered in the db | -| `context` | `FieldContext` | - | Field context object | -| `context.database` | `Database` | - | Database instance | -| `context.collection` | `Collection` | - | Data table instance | - -## Instance Members - -### `name` - -Field name. - -### `type` - -Field type. - -### `dataType` - -Data type of the field. - -### `options` - -Configuration parameters to initialize the field. - -### `context` - -Field context object. - -## Configuration Methods - -### `on()` - -Quick definition method based on data table events. It is equivalent to `db.on(this.collection.name + '.' + eventName, listener)`. - -It is usually not necessary to override this method when inheriting. - -**Signature** - -* `on(eventName: string, listener: (...args: any[]) => void)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `eventName` | `string` | - | Event name | -| `listener` | `(...args: any[]) => void` | - | Event listener | - -### `off()` - -Quick removal method based on data table events. It is equivalent to `db.off(this.collection.name + '.' + eventName, listener)`. - -It is usually not necessary to override this method when inheriting. - -**Signature** - -* `off(eventName: string, listener: (...args: any[]) => void)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `eventName` | `string` | - | Event name | -| `listener` | `(...args: any[]) => void` | - | Event listener | - -### `bind()` - -The execution content that is triggered when a field is added to data table. Typically used to add data table event listeners and other processing. - -The corresponding `super.bind()` method needs to be called first when inheriting. - -**Signature** - -* `bind()` - -### `unbind()` - -The execution content that is triggered when a field is removed from data table. Typically used to remove data table event listeners and other processing. - -The corresponding `super.unbind()` method needs to be called first when inheriting. - -**Signature** - -* `unbind()` - -### `get()` - -Get the values of a configuration item of the field. - -**Signature** - -* `get(key: string): any` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `key` | `string` | - | Name of the configuration item | - -**Example** - -```ts -const field = db.collection('users').getField('name'); - -// Get and return the values of the configuration item 'name' -console.log(field.get('name')); -``` - -### `merge()` - -Merge the values of a configuration item of the field. - -**Signature** - -* `merge(options: { [key: string]: any }): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options` | `{ [key: string]: any }` | - | The configuration item to merge | - -**Example** - -```ts -const field = db.collection('users').getField('name'); - -field.merge({ - // Add an index configuration - index: true -}); -``` - -### `remove()` - -Remove a field from data table (from memory only). - -**Example** - -```ts -const books = db.getCollections('books'); - -books.getField('isbn').remove(); - -// really remove from db -await books.sync(); -``` - -## Database Methods - -### `removeFromDb()` - -Remove a field from the database. - -**Signature** - -* `removeFromDb(options?: Transactionable): Promise` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.transaction?` | `Transaction` | - | Transaction instance | - -### `existsInDb()` - -Check if a field exists in the database. - -**Signature** - -* `existsInDb(options?: Transactionable): Promise` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.transaction?` | `Transaction` | - | Transaction instance | - -## Built-in Field Types - -NocoBase has some built-in common field types, the corresponding type name can be used directly to specify the type of field upon definition. Fields of different types are configured differently, please refer to the list below. - -The configuration items of all field types are passed through to Sequelize in addition to those described below. Therefore, all field configuration items supported by Sequelize can be used here (e.g. `allowNull`, `defaultValue`, etc.). - -Moreover, server-side field types are mainly used for solving the problems of database storage and some algorithms, they are barely relevant to the field display types and the use of components in front-end. The front-end field types can be found in the corresponding tutorials. - -### `'boolean'` - -Boolean type. - -**Example** - -```js -db.collection({ - name: 'books', - fields: [ - { - type: 'boolean', - name: 'published' - } - ] -}); -``` - -### `'integer'` - -Integer type (32 bits). - -**Example** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'integer', - name: 'pages' - } - ] -}); -``` - -### `'bigInt'` - -Long integer type (64 bits). - -**Example** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'bigInt', - name: 'words' - } - ] -}); -``` - -### `'double'` - -Double-precision floating-point format (64 bits). - -**Example** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'double', - name: 'price' - } - ] -}); -``` - -### `'real'` - -Real type (PG only). - -### `'decimal'` - -Decimal type. - -### `'string'` - -String type. Equivalent to the `VARCHAR` type for most databases. - -**Example** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'string', - name: 'title' - } - ] -}); -``` - -### `'text'` - -Text type. Equivalent to the `TEXT` type for most databases. - -**Example** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'text', - name: 'content' - } - ] -}); -``` - -### `'password'` - -Password type (NocoBase extension). Password encryption based on the `scrypt` method of Node.js native crypto packages. - -**Example** - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'password', - name: 'password', - length: 64, // Length, default is 64 - randomBytesSize: 8 // Length of random bytes, default is 8 - } - ] -}); -``` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `length` | `number` | 64 | Length of characters | -| `randomBytesSize` | `number` | 8 | Length of random bytes | - -### `'date'` - -Date type. - -### `'time'` - -Time type. - -### `'array'` - -Array type (PG only). - -### `'json'` - -JSON type. - -### `'jsonb'` - -JSONB type (PG only, others will be compatible with the `'json'` type). - -### `'uuid'` - -UUID type. - -### `'uid'` - -UID type (NocoBase extension). Short random string identifier type. - -### `'formula'` - -Formula type (NocoBase extension). Mathematical formula calculation can be configured based on [mathjs](https://www.npmjs.com/package/mathjs), and the formula can refer to the values of other columns in the same record to participate in the calculation. - -**Example** - -```ts -db.collection({ - name: 'orders', - fields: [ - { - type: 'double', - name: 'price' - }, - { - type: 'integer', - name: 'quantity' - }, - { - type: 'formula', - name: 'total', - expression: 'price * quantity' - } - ] -}); -``` - -### `'radio'` - -Radio type (NocoBase extension). The field value is 'true' for at most one row of data for the full table, all others are 'false' or 'null'. - -**Example** - -There is only one user marked as root in the entire system, once the root value of any other user is changed to `true`, all other records with root of `true` will be changed to `false`: - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'radio', - name: 'root', - } - ] -}); -``` - -### `'sort'` - -Sorting type (NocoBase extension). Sorting based on integer numbers, automatically generating new serial numbers for new records, and rearranging serial numbers when moving data. - -If data table has the `sortable` option defined, the corresponding fields will be generated automatically. - -**Example** - -Posts are sortable based on the users they belong to. - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'belongsTo', - name: 'user', - }, - { - type: 'sort', - name: 'priority', - scopeKey: 'userId' // Sort data grouped by the values of userId - } - ] -}); -``` - -### `'virtual'` - -Virtual type. No Data is actually stored, it is used only when special getter/setter is defined. - -### `'belongsTo'` - -Many-to-one association type. Foreign key is stored in its own table, as opposed to `'hasOne'`/`'hasMany'`. - -**Example** - -Any post belongs to an author: - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'belongsTo', - name: 'author', - target: 'users', // Default table name is the plural form of - foreignKey: 'authorId', // Default is ' + Id' - sourceKey: 'id' // Default is id of the table - } - ] -}); -``` - -### `'hasOne'` - -One-to-one association type. Foreign key is stored in the association table, as opposed to `'belongsTo'`. - -**Example** - -Any user has a profile: - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'hasOne', - name: 'profile', - target: 'profiles', // Can be omitted - } - ] -}) -``` - -### `'hasMany'` - -One-to-many association type. The foreign key is stored in the association table, as opposed to `'belongsTo'`. - -**Example** - -Any user can have multiple posts: - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'hasMany', - name: 'posts', - foreignKey: 'authorId', - sourceKey: 'id' - } - ] -}); -``` - -### `'belongsToMany'` - -Many-to-many association type. Intermediate table is used to store both foreign keys. If no existing table is specified as intermediate table, it will be created automatically. - -**Example** - -Any post can have multiple tags added to it, and any tag can be added to multiple posts: - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'belongsToMany', - name: 'tags', - target: 'tags', // Can be omitted if name is the same - through: 'postsTags', // Intermediate table will be generated automatically if not specified - foreignKey: 'postId', // Foreign key in the intermediate table referring to the table itself - sourceKey: 'id', // Primary key of the table itself - otherKey: 'tagId' // Foreign key in the intermediate table referring to the association table - } - ] -}); - -db.collection({ - name: 'tags', - fields: [ - { - type: 'belongsToMany', - name: 'posts', - through: 'postsTags', // Refer to the same intermediate table in the same set of relation - } - ] -}); -``` diff --git a/docs/en-US/api/database/index.md b/docs/en-US/api/database/index.md deleted file mode 100644 index 1cb691f7d..000000000 --- a/docs/en-US/api/database/index.md +++ /dev/null @@ -1,1171 +0,0 @@ -# Database - -## Overview - -Database is the database interaction tool provided by NocoBase, it provides very convenient database interaction features for no-code and low-code applications. The supported databases are: - -* SQLite 3.8.8+ -* MySQL 8.0.17+ -* PostgreSQL 10.0+ - -### Connect to Database - -In `Database` constructor, database connection can be configured by passing the `options` parameter. - -```javascript -const { Database } = require('@nocobase/database'); - -// SQLite database configuration parameters -const database = new Database({ - dialect: 'sqlite', - storage: 'path/to/database.sqlite' -}) - -// MySQL \ PostgreSQL database configuration parameters -const database = new Database({ - dialect: /* 'postgres' or 'mysql' */, - database: 'database', - username: 'username', - password: 'password', - host: 'localhost', - port: 'port' -}) - -``` - -Refer to [Constructor](#constructor) for detailed configurations. - -### Define Data Structure - -`Database` defines database structure through `Collection`, one `Collection` object represents one table in the database. - -```javascript -// Define Collection -const UserCollection = database.collection({ - name: 'users', - fields: [ - { - name: 'name', - type: 'string', - }, - { - name: 'age', - type: 'integer', - }, - ], -}); - -``` - -After the database structure is defined, use `sync()` method to synchronize the database structure. - -```javascript -await database.sync(); -``` - -Refer to [Collection](/api/database/collection.md) for detailed usage of `Collection`. - -### CRUD Data - -`Database` operates data through `Repository`. - -```javascript - -const UserRepository = UserCollection.repository(); - -// Create -await UserRepository.create({ - name: 'Mark', - age: 18, -}); - -// Query -const user = await UserRepository.findOne({ - filter: { - name: 'Mark', - }, -}); - -// Update -await UserRepository.update({ - values: { - age: 20, - }, -}); - -// Delete -await UserRepository.destroy(user.id); -``` - -Refer to [Repository](/api/database/repository.md) for details of data CRUD. - -## Constructor - -**Signature** - -* `constructor(options: DatabaseOptions)` - -Create a database instance. - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.host` | `string` | `'localhost'` | Database host | -| `options.port` | `number` | - | Database service port, default port depends on the type of database used | -| `options.username` | `string` | - | Database username | -| `options.password` | `string` | - | Database password| -| `options.database` | `string` | - | Database name | -| `options.dialect` | `string` | `'mysql'` | Database type | -| `options.storage?` | `string` | `':memory:'` | Storage mode for SQLite | -| `options.logging?` | `boolean` | `false` | Whether to enable logging | -| `options.define?` | `Object` | `{}` | Default table definition parameters | -| `options.tablePrefix?` | `string` | `''` | NocoBase extension, table prefix | -| `options.migrator?` | `UmzugOptions` | `{}` | NocoBase extension, parameters for migrator, refer to [Umzug](https://github.com/sequelize/umzug/blob/main/src/types.ts#L15) for the implementation | - -## Migration Methods - -### `addMigration()` - -Add single migration file. - -**Signature** - -* `addMigration(options: MigrationItem)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.name` | `string` | - | Name of the migration file | -| `options.context?` | `string` | - | Context of the migration file | -| `options.migration?` | `typeof Migration` | - | Custom type of the migration file | -| `options.up` | `Function` | - | `up` method of the migration file | -| `options.down` | `Function` | - | `down` method of the migration file | - -**Example** - -```ts -db.addMigration({ - name: '20220916120411-test-1', - async up() { - const queryInterface = this.context.db.sequelize.getQueryInterface(); - await queryInterface.query(/* your migration sqls */); - } -}); -``` - -### `addMigrations()` - -Add the migration files in the specified directory. - -**Signature** - -* `addMigrations(options: AddMigrationsOptions): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.directory` | `string` | `''` | Directory where the migration files are located | -| `options.extensions` | `string[]` | `['js', 'ts']` | File extensions | -| `options.namespace?` | `string` | `''` | Namespace | -| `options.context?` | `Object` | `{ db }` | Context of the migration files | - -**Example** - -```ts -db.addMigrations({ - directory: path.resolve(__dirname, './migrations'), - namespace: 'test' -}); -``` - -## Tool Methods - -### `inDialect()` - -Check whether the current database type is the specified type. - -**Signature** - -* `inDialect(dialect: string[]): boolean` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `dialect` | `string[]` | - | Database type, options are `mysql`, `postgres` and `sqlite` | - -### `getTablePrefix()` - -Get the table name prefix in the configuration. - -**Signature** - -* `getTablePrefix(): string` - -## Data Table Configurations - -### `collection()` - -Define a data table. This is like the `define` method of Sequelize, which only creates table structure in memory. Call the `sync` method if needs to be persisted to the database. - -**Signature** - -* `collection(options: CollectionOptions): Collection` - -**Parameter** - -All configuration parameters of `options` is consistent with the constructor of the `Collection` class, refer to [Collection](/api/server/database/collection#Constructor). - -**Event** - -* `'beforeDefineCollection'`: Trigger before defining the table. -* `'afterDefineCollection'`: Trigger after defining the table. - -**Example** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'string', - name: 'title', - }, - { - type: 'float', - name: 'price', - } - ] -}); - -// sync collection as table to db -await db.sync(); -``` - -### `getCollection()` - -Get a defined data table. - -**Signature** - -* `getCollection(name: string): Collection` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Table name | - -**Example** - -```ts -const collection = db.getCollection('books'); -``` - -### `hasCollection()` - -Check whether a specified data table is defined. - -**Signature** - -* `hasCollection(name: string): boolean` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Table name | - -**Example** - -```ts -db.collection({ name: 'books' }); - -db.hasCollection('books'); // true - -db.hasCollection('authors'); // false -``` - -### `removeCollection()` - -Remove a defined data table. It is to remove from memory only, call the `sync` method if needs to be persisted to the database. - -**Signature** - -* `removeCollection(name: string): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Table name | - -**Event** - -* `'beforeRemoveCollection'`: Trigger before removing the table. -* `'afterRemoveCollection'`: Trigger after removing the table - -**Example** - -```ts -db.collection({ name: 'books' }); - -db.removeCollection('books'); -``` - -### `import()` - -Load all files in the import file directory into memory as the configuration of collection. - -**Signature** - -* `async import(options: { directory: string; extensions?: ImportFileExtension[] }): Promise>` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.directory` | `string` | - | Path of directory to be imported | -| `options.extensions` | `string[]` | `['ts', 'js']` | Scan for specific suffixes | - -**Example** - -The collection defined by file `./collections/books.ts` is as below: - -```ts -export default { - name: 'books', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}; -``` - -Import the relevant configurations when loading the plugin: - -```ts -class Plugin { - async load() { - await this.app.db.import({ - directory: path.resolve(__dirname, './collections'), - }); - } -} -``` - -## Extension Registrations and Acquisitions - -### `registerFieldTypes()` - -Register custom field type. - -**Signature** - -* `registerFieldTypes(fieldTypes: MapOf): void` - -**Parameter** - -`fieldTypes` is a key-value pair, where key is the field type name, and value is the field type class. - -**Example** - -```ts -import { Field } from '@nocobase/database'; - -class MyField extends Field { - // ... -} - -db.registerFieldTypes({ - myField: MyField, -}); -``` - -### `registerModels()` - -Register custom data model class. - -**Signature** - -* `registerModels(models: MapOf>): void` - -**Parameter** - -`models` is a key-value pair, where key is the data model name, and value is the data model class. - -**Example** - -```ts -import { Model } from '@nocobase/database'; - -class MyModel extends Model { - // ... -} - -db.registerModels({ - myModel: MyModel, -}); - -db.collection({ - name: 'myCollection', - model: 'myModel' -}); -``` - -### `registerRepositories()` - -Register custom data repository class. - -**Signature** - -* `registerRepositories(repositories: MapOf): void` - -**Parameter** - -`repositories` is a key-value pair, where key is the data repository name, and value is the data repository class. - -**Example** - -```ts -import { Repository } from '@nocobase/database'; - -class MyRepository extends Repository { - // ... -} - -db.registerRepositories({ - myRepository: MyRepository, -}); - -db.collection({ - name: 'myCollection', - repository: 'myRepository' -}); -``` - -### `registerOperators()` - -Register custom data query operator. - -**Signature** - -* `registerOperators(operators: MapOf)` - -**Parameter** - -`operators` is a key-value pair, where key is the operator name, and value is the generating function of the comparison operator statement. - -**Example** - -```ts -db.registerOperators({ - $dateOn(value) { - return { - [Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }], - }; - } -}); - -db.getRepository('books').count({ - filter: { - createdAt: { - // registered operator - $dateOn: '2020-01-01', - } - } -}); -``` - -### `getModel()` - -Get the defined data model class. If no custom model class has been registered before, the default model class of Sequelize will be returned. The default name is the same as the name defined by collection. - -**Signature** - -* `getModel(name: string): Model` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Registered model name | - -**Example** - -```ts -db.registerModels({ - books: class MyModel extends Model {} -}); - -const ModelClass = db.getModel('books'); - -console.log(ModelClass.prototype instanceof MyModel) // true -``` - -Note: The model class retrieved from collection is not strictly equivalent to the model class at registration, but is inherited from the model class at registration. Since the properties of Sequelize's model class are modified during initialization, NocoBase automatically handles this inheritance relationship. All the definitions work fine except that the classes are not equal. - -### `getRepository()` - -Get the defined data repository class. If no custom data repository class has been registered before, the default data repository class of NocoBase will be returned. The default name is the same as the name defined by collection. - -Data repository class is mainly used for the CRUD operations based on data model, refer to [Repository](/api/server/database/repository). - -**Signature** - -* `getRepository(name: string): Repository` -* `getRepository(name: string, relationId?: string | number): Repository` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Registered data repository name | -| `relationId` | `string` \| `number` | - | Foreign key value for relational data | - -When the name contains relationship in the form of `'tables.relactions'`, the related data repository class is returned. If the second parameter is provided, the data repository will be used (query, modify, etc.) based on the foreign key values of the relational data. - -**Example** - -Suppose there are two data tables posts and authors, and there is a foreign key in the posts table points to the authors table: - -```ts -const AuthorsRepo = db.getRepository('authors'); -const author1 = AuthorsRepo.create({ name: 'author1' }); - -const PostsRepo = db.getRepository('authors.posts', author1.id); -const post1 = AuthorsRepo.create({ title: 'post1' }); -asset(post1.authorId === author1.id); // true -``` - -## Database Events - -### `on()` - -Listen for database events. - -**Signature** - -* `on(event: string, listener: (...args: any[]) => void | Promise): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| event | string | - | Event name | -| listener | Function | - | Event listener | - -Event name supports Model event of Sequelize by default. Global event is listened to by the name ``, single Model event is listened to by the name `. `. - -Refer to the [Built-in Events](#built-in-events) section for parameter descriptions and detailed examples of all built-in event types. - -### `off()` - -Remove the event listener function. - -**Signature** - -* `off(name: string, listener: Function)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| name | string | - | Event name | -| listener | Function | - | Event listener | - -**Example** - -```ts -const listener = async (model, options) => { - console.log(model); -}; - -db.on('afterCreate', listener); - -db.off('afterCreate', listener); -``` - -## Database Operations - -### `auth()` - -Database connection verification. It can be used to ensure that the application has established connection to the data. - -**Signature** - -* `auth(options: QueryOptions & { retry?: number } = {}): Promise` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options?` | `Object` | - | Verification option | -| `options.retry?` | `number` | `10` | Number of retries in case of verification failure | -| `options.transaction?` | `Transaction` | - | Transaction object | -| `options.logging?` | `boolean \| Function` | `false` | Whether to print the log | - -**Example** - -```ts -await db.auth(); -``` - -### `reconnect()` - -Reconnect to the database. - -**Example** - -```ts -await db.reconnect(); -``` - -### `closed()` - -Check whether database has closed the connection. - -**Signature** - -* `closed(): boolean` - -### `close()` - -Closes database connection. Equivalent to `sequelize.close()`. - -### `sync()` - -Synchronizes database table structure. Equivalent to `sequelize.sync()`, refer to [Sequelize documentation](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-method-sync) for parameters. - -### `clean()` - -Empty the database, it will delete all data tables. - -**Signature** - -* `clean(options: CleanOptions): Promise` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.drop` | `boolean` | `false` | Whether to remove all data tables | -| `options.skip` | `string[]` | - | Names of tables to skip | -| `options.transaction` | `Transaction` | - | Transaction object | - -**Example** - -Remove all tables except for the `users` table. - -```ts -await db.clean({ - drop: true, - skip: ['users'] -}) -``` - -## Package-Level Export - -### `defineCollection()` - -Create the configuration content of a data table. - -**Signature** - -* `defineCollection(name: string, config: CollectionOptions): CollectionOptions` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `collectionOptions` | `CollectionOptions` | - | All parameters are the same with `db.collection()` | - -**Example** - -For the data table configuration file to be imported by `db.import()`: - -```ts -import { defineCollection } from '@nocobase/database'; - -export default defineCollection({ - name: 'users', - fields: [ - { - type: 'string', - name: 'name', - }, - ], -}); -``` - -### `extendCollection()` - -Extent the configuration content of a data table that is already in memory, mainly for the content of files imported by the `import()` method. This is the top-level package export method of `@nocobase/database`, and is not called through db instance. The `extend` alias can also be used. - -**Signature** - -* `extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions): ExtendedCollectionOptions` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `collectionOptions` | `CollectionOptions` | - | All the same with `db.collection()` | -| `mergeOptions?` | `MergeOptions` | - | [deepmerge](https://npmjs.com/package/deepmerge) options of npm package | - -**Example** - -Original definition of the table books (books.ts): - -```ts -export default { - name: 'books', - fields: [ - { name: 'title', type: 'string' } - ] -} -``` - -Extend the definition of the table books (books.extend.ts): - -```ts -import { extend } from '@nocobase/database'; - -// Extend again -export default extend({ - name: 'books', - fields: [ - { name: 'price', type: 'number' } - ] -}); -``` - -If the above two files are imported when calling `import()`, after being extended again by `extend()`, the table books will have `title` and `price` two fields. - -This method is especially useful when extending the table structure that is already defined by existing plugin. - -## Built-in Events - -The following events will be triggered in the corresponding lifecycle of database, subscribe by the `on()` method and perform specific processing to meet some business needs. - -### `'beforeSync'` / `'afterSync'` - -Events triggered before or after a new table structure configuration (fields, indexes, etc.) is synchronized to the database. This is usually triggered when executing `collection.sync()` (internal call) for the logical processing of the extension of some special fields. - -**Signature** - -```ts -on(eventName: `${string}.beforeSync` | 'beforeSync' | `${string}.afterSync` | 'afterSync', listener: SyncListener): this -``` - -**Type** - -```ts -import type { SyncOptions, HookReturn } from 'sequelize/types'; - -type SyncListener = (options?: SyncOptions) => HookReturn; -``` - -**Example** - -```ts -const users = db.collection({ - name: 'users', - fields: [ - { type: 'string', name: 'username' } - ] -}); - -db.on('beforeSync', async (options) => { - // do something -}); - -db.on('users.afterSync', async (options) => { - // do something -}); - -await users.sync(); -``` - -### `'beforeValidate'` / `'afterValidate'` - -Before creating or updating data, there is a validation against the data based on the rules defined by the collection, and the corresponding events are triggered before and after the validation. This is triggered when `repository.create()` or `repository.update()` is called. - -**Signature** - -```ts -on(eventName: `${string}.beforeValidate` | 'beforeValidate' | `${string}.afterValidate` | 'afterValidate', listener: ValidateListener): this -``` - -**Type** - -```ts -import type { ValidationOptions } from 'sequelize/types/lib/instance-validator'; -import type { HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -type ValidateListener = (model: Model, options?: ValidationOptions) => HookReturn; -``` - -**Example** - -```ts -db.collection({ - name: 'tests', - fields: [ - { - type: 'string', - name: 'email', - validate: { - isEmail: true, - }, - } - ], -}); - -// all models -db.on('beforeValidate', async (model, options) => { - // do something -}); -// tests model -db.on('tests.beforeValidate', async (model, options) => { - // do something -}); - -// all models -db.on('afterValidate', async (model, options) => { - // do something -}); -// tests model -db.on('tests.afterValidate', async (model, options) => { - // do something -}); - -const repository = db.getRepository('tests'); -await repository.create({ - values: { - email: 'abc', // checks for email format - }, -}); -// or -await repository.update({ - filterByTk: 1, - values: { - email: 'abc', // checks for email format - }, -}); -``` - -### `'beforeCreate'` / `'afterCreate'` - -Events triggered before or after creating one piece of data. This is triggered when `repository.create()` is called. - -**Signature** - -```ts -on(eventName: `${string}.beforeCreate` | 'beforeCreate' | `${string}.afterCreate` | 'afterCreate', listener: CreateListener): this -``` - -**Type** - -```ts -import type { CreateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type CreateListener = (model: Model, options?: CreateOptions) => HookReturn; -``` - -**Example** - -```ts -db.on('beforeCreate', async (model, options) => { - // do something -}); - -db.on('books.afterCreate', async (model, options) => { - const { transaction } = options; - const result = await model.constructor.findByPk(model.id, { - transaction - }); - console.log(result); -}); -``` - -### `'beforeUpdate'` / `'afterUpdate'` - -Events triggered before or after updating one piece of data. This is triggered when `repository.update()` is called. - -**Signature** - -```ts -on(eventName: `${string}.beforeUpdate` | 'beforeUpdate' | `${string}.afterUpdate` | 'afterUpdate', listener: UpdateListener): this -``` - -**Type** - -```ts -import type { UpdateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type UpdateListener = (model: Model, options?: UpdateOptions) => HookReturn; -``` - -**Example** - -```ts -db.on('beforeUpdate', async (model, options) => { - // do something -}); - -db.on('books.afterUpdate', async (model, options) => { - // do something -}); -``` - -### `'beforeSave'` / `'afterSave'` - -Events triggered before or after creating or updating one piece of data. This is triggered when `repository.create()` or `repository.update()` is called. - -**Signature** - -```ts -on(eventName: `${string}.beforeSave` | 'beforeSave' | `${string}.afterSave` | 'afterSave', listener: SaveListener): this -``` - -**Type** - -```ts -import type { SaveOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type SaveListener = (model: Model, options?: SaveOptions) => HookReturn; -``` - -**Example** - -```ts -db.on('beforeSave', async (model, options) => { - // do something -}); - -db.on('books.afterSave', async (model, options) => { - // do something -}); -``` - -### `'beforeDestroy'` / `'afterDestroy'` - -Events triggered before or after deleting one piece of data. This is triggered when `repository.destroy()` is called. - -**Signature** - -```ts -on(eventName: `${string}.beforeDestroy` | 'beforeDestroy' | `${string}.afterDestroy` | 'afterDestroy', listener: DestroyListener): this -``` - -**Type** - -```ts -import type { DestroyOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type DestroyListener = (model: Model, options?: DestroyOptions) => HookReturn; -``` - -**Example** - -```ts -db.on('beforeDestroy', async (model, options) => { - // do something -}); - -db.on('books.afterDestroy', async (model, options) => { - // do something -}); -``` - -### `'afterCreateWithAssociations'` - -Events triggered after creating one piece of data that carries hierarchical data. This is triggered when `repository.create()` is called. - -**Signature** - -```ts -on(eventName: `${string}.afterCreateWithAssociations` | 'afterCreateWithAssociations', listener: CreateWithAssociationsListener): this -``` - -**Type** - -```ts -import type { CreateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type CreateWithAssociationsListener = (model: Model, options?: CreateOptions) => HookReturn; -``` - -**Example** - -```ts -db.on('afterCreateWithAssociations', async (model, options) => { - // do something -}); - -db.on('books.afterCreateWithAssociations', async (model, options) => { - // do something -}); -``` - -### `'afterUpdateWithAssociations'` - -Events triggered after updating one piece of data that carries hierarchical data. This is triggered when `repository.update()` is called. - -**Signature** - -```ts -on(eventName: `${string}.afterUpdateWithAssociations` | 'afterUpdateWithAssociations', listener: CreateWithAssociationsListener): this -``` - -**Type** - -```ts -import type { UpdateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type UpdateWithAssociationsListener = (model: Model, options?: UpdateOptions) => HookReturn; -``` - -**Example** - -```ts -db.on('afterUpdateWithAssociations', async (model, options) => { - // do something -}); - -db.on('books.afterUpdateWithAssociations', async (model, options) => { - // do something -}); -``` - -### `'afterSaveWithAssociations'` - -Events triggered after creating or updating one piece of data that carries hierarchical data. This is triggered when `repository.create()` or `repository.update()` is called. - -**Signature** - -```ts -on(eventName: `${string}.afterSaveWithAssociations` | 'afterSaveWithAssociations', listener: SaveWithAssociationsListener): this -``` - -**Type** - -```ts -import type { SaveOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type SaveWithAssociationsListener = (model: Model, options?: SaveOptions) => HookReturn; -``` - -**Example** - -```ts -db.on('afterSaveWithAssociations', async (model, options) => { - // do something -}); - -db.on('books.afterSaveWithAssociations', async (model, options) => { - // do something -}); -``` - -### `'beforeDefineCollection'` - -Events triggered before defining a data table, such as when `db.collection()` is called. - -Note: This is a synchronous event. - -**Signature** - -```ts -on(eventName: 'beforeDefineCollection', listener: BeforeDefineCollectionListener): this -``` - -**Type** - -```ts -import type { CollectionOptions } from '@nocobase/database'; - -export type BeforeDefineCollectionListener = (options: CollectionOptions) => void; -``` - -**Example** - -```ts -db.on('beforeDefineCollection', (options) => { - // do something -}); -``` - -### `'afterDefineCollection'` - -Events triggered after defining a data table, such as when `db.collection()` is called. - -Note: This is a synchronous event. - -**Signature** - -```ts -on(eventName: 'afterDefineCollection', listener: AfterDefineCollectionListener): this -``` - -**Type** - -```ts -import type { Collection } from '@nocobase/database'; - -export type AfterDefineCollectionListener = (options: Collection) => void; -``` - -**Example** - -```ts -db.on('afterDefineCollection', (collection) => { - // do something -}); -``` - -### `'beforeRemoveCollection'` / `'afterRemoveCollection'` - -Events triggered before or after removing a data table from memory, such as when `db.removeCollection()` is called. - -Note: This is a synchronous event. - -**Signature** - -```ts -on(eventName: 'beforeRemoveCollection' | 'afterRemoveCollection', listener: RemoveCollectionListener): this -``` - -**Type** - -```ts -import type { Collection } from '@nocobase/database'; - -export type RemoveCollectionListener = (options: Collection) => void; -``` - -**Example** - -```ts -db.on('beforeRemoveCollection', (collection) => { - // do something -}); - -db.on('afterRemoveCollection', (collection) => { - // do something -}); -``` diff --git a/docs/en-US/api/database/operators.md b/docs/en-US/api/database/operators.md deleted file mode 100644 index 88f3c6918..000000000 --- a/docs/en-US/api/database/operators.md +++ /dev/null @@ -1,815 +0,0 @@ -# Filter Operators - -Used in the filter parameters of the `find`, `findOne`, `findAndCount`, `count`, etc. APIs of repository: - -```ts -const repository = db.getRepository('books'); - -repository.find({ - filter: { - title: { - $eq: 'Spring and Autumn', - } - } -}); -``` - -To support JSON, NocoBase identifies query operators as a string prefixed with $. - -Moreover, NocoBase provides API to extend operators. Refer to [`db.registerOperators()`](../database#registeroperators). - -## General Operators - -### `$eq` - -Check if the field value is equal to the specified value. Equivalent to `=` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $eq: 'Spring and Autumn', - } - } -}); -``` - -Equal to `title: 'Spring and Autumn'` - -### `$ne` - -Check if the field value is not equal to the specified value. Equivalent to `!=` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $ne: 'Spring and Autumn', - } - } -}); -``` - -### `$is` - -Check if the field value is the specified value. Equivalent to `IS` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $is: null, - } - } -}); -``` - -### `$not` - -Check if the field value is not the specified value. Equivalent to `IS NOT` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $not: null, - } - } -}); -``` - -### `$col` - -Check if the field value is equal to the value of another field. Equivalent to `=` in SQL. - - -**Example** - -```ts -repository.find({ - filter: { - title: { - $col: 'name', - } - } -}); -``` - -### `$in` - -Check if the field value is in the specified array. Equivalent to `IN` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $in: ['Spring and Autumn', 'Warring States'], - } - } -}); -``` - -### `$notIn` - -Check if the field value is not in the specified array. Equivalent to `NOT IN` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notIn: ['Spring and Autumn', 'Warring States'], - } - } -}); -``` - -### `$empty` - -Check if the general field is empty. For string field, check if it is an empty string; for array field, check if it is an empty array. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $empty: true, - } - } -}); -``` - -### `$notEmpty` - -Check if the general field is not empty. For string field, check if it is not an empty string; for array field, check if it is not an empty array. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notEmpty: true, - } - } -}); -``` - -## Logical Operators - -### `$and` - -Logical AND. Equivalent to `AND` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - $and: [ - { title: 'Book of Songs' }, - { isbn: '1234567890' }, - ] - } -}); -``` - -### `$or` - -Logical OR. Equivalent to `OR` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - $or: [ - { title: 'Book of Songs' }, - { publishedAt: { $lt: '0000-00-00T00:00:00Z' } }, - ] - } -}); -``` - -## Boolean Field Operators - -For boolean fields: `type: 'boolean'` - -### `$isFalsy` - -Check if a Boolean field value is false. Boolean field values of `false`, `0` and `NULL` are all judged to be `$isFalsy: true`. - -**Example** - -```ts -repository.find({ - filter: { - isPublished: { - $isFalsy: true, - } - } -}) -``` - -### `$isTruly` - -Check if a Boolean field value is true. Boolean field values of `true` and `1` are all judged to be `$isTruly: true`. - -**Example** - -```ts -repository.find({ - filter: { - isPublished: { - $isTruly: true, - } - } -}) -``` - -## Numeric Type Field Operators - -For numeric type fields, including: - -- `type: 'integer'` -- `type: 'float'` -- `type: 'double'` -- `type: 'real'` -- `type: 'decimal'` - -### `$gt` - -Check if the field value is greater than the specified value. Equivalent to `>` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - price: { - $gt: 100, - } - } -}); -``` - -### `$gte` - -Check if the field value is equal to or greater than the specified value. Equivalent to `>=` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - price: { - $gte: 100, - } - } -}); -``` - -### `$lt` - -Check if the field value is less than the specified value. Equivalent to `<` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - price: { - $lt: 100, - } - } -}); -``` - -### `$lte` - -Check if the field value is equal to or less than the specified value. Equivalent to `<=` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - price: { - $lte: 100, - } - } -}); -``` - -### `$between` - -Check if the field value is between the specified two values. Equivalent to `BETWEEN` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - price: { - $between: [100, 200], - } - } -}); -``` - -### `$notBetween` - -Check if the field value is not between the specified two values. Equivalent to `NOT BETWEEN` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - price: { - $notBetween: [100, 200], - } - } -}); -``` - -## String Type Field Operators - -For string type fields, including `string`. - -### `$includes` - -Check if the string field contains the specified substring. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $includes: 'Three Character Classic', - } - } -}) -``` - -### `$notIncludes` - -Check if the string field does not contain the specified substring. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notIncludes: 'Three Character Classic', - } - } -}) -``` - -### `$startsWith` - -Check if the string field starts with the specified substring. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $startsWith: 'Three Character Classic', - } - } -}) -``` - -### `$notStatsWith` - -Check if the string field does not start with the specified substring. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notStatsWith: 'Three Character Classic', - } - } -}) -``` - -### `$endsWith` - -Check if the string field ends with the specified substring. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $endsWith: 'Three Character Classic', - } - } -}) -``` - -### `$notEndsWith` - -Check if the string field does not end with the specified substring. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notEndsWith: 'Three Character Classic', - } - } -}) -``` - -### `$like` - -Check if the field value contains the specified string. Equivalent to `LIKE` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $like: 'Computer', - } - } -}); -``` - -### `$notLike` - -Check if the field value does not contain the specified string. Equivalent to `NOT LIKE` in SQL. - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notLike: 'Computer', - } - } -}); -``` - -### `$iLike` - -Check if a field value contains the specified string, case ignored. Equivalent to `ILIKE` in SQL (PG only). - -**Example** - -```ts -repository.find({ - filter: { - title: { - $iLike: 'Computer', - } - } -}); -``` - -### `$notILike` - -Check if a field value does not contain the specified string, case ignored. Equivalent to `NOT ILIKE` in SQL (PG only). - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notILike: 'Computer', - } - } -}); -``` - -### `$regexp` - -Check if the field value matches the specified regular expression. Equivalent to `REGEXP` in SQL (PG only). - -**Example** - -```ts -repository.find({ - filter: { - title: { - $regexp: '^Computer', - } - } -}); -``` - -### `$notRegexp` - -Check if the field value does not match the specified regular expression. Equivalent to `NOT REGEXP` in SQL (PG only). - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notRegexp: '^Computer', - } - } -}); -``` - -### `$iRegexp` - -Check if the field value matches the specified regular expression, case ignored. Equivalent to `~*` in SQL (PG only). - -**Example** - -```ts -repository.find({ - filter: { - title: { - $iRegexp: '^COMPUTER', - } - } -}); -``` - -### `$notIRegexp` - -Check if the field value does not match the specified regular expression, case ignored. Equivalent to `!~*` in SQL (PG only). - -**Example** - -```ts -repository.find({ - filter: { - title: { - $notIRegexp: '^COMPUTER', - } - } -}); -``` - -## Date Type Field Operators - -For date type fields: `type: 'date'` - -### `$dateOn` - -Check if the date field value is within a certain day. - -**Example** - -```ts -repository.find({ - filter: { - createdAt: { - $dateOn: '2021-01-01', - } - } -}) -``` - -### `$dateNotOn` - -Check if the date field value is not within a certain day. - -**Example** - -```ts -repository.find({ - filter: { - createdAt: { - $dateNotOn: '2021-01-01', - } - } -}) -``` - -### `$dateBefore` - -Check if the date field value is before a certain value, i.e., less than the one passed in. - -**Example** - -```ts -repository.find({ - filter: { - createdAt: { - $dateBefore: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -### `$dateNotBefore` - -Check if the date field value is not before a certain value, i.e., equal to or greater than the one passed in. - -**Example** - -```ts -repository.find({ - filter: { - createdAt: { - $dateNotBefore: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -### `$dateAfter` - -Check if the date field value is after a certain value, i.e., greater than the one passed in. - -**Example** - -```ts -repository.find({ - filter: { - createdAt: { - $dateAfter: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -### `$dateNotAfter` - -Check if the date field value is not after a certain value, i.e., equal to or greater than the one passed in. - -**Example** - -```ts -repository.find({ - filter: { - createdAt: { - $dateNotAfter: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -## Array Type Field Operators - -For array type fields: `type: 'array'` - -### `$match` - -Check if the array field values match values of the specified array. - -**Example** - -```ts -repository.find({ - filter: { - tags: { - $match: ['literature', 'history'], - } - } -}) -``` - -### `$notMatch` - -Check if the array field values do not match values of the specified array. - -**Example** - -```ts -repository.find({ - filter: { - tags: { - $notMatch: ['literature', 'history'], - } - } -}) -``` - -### `$anyOf` - -Check if the array field values contain any of the values of the specified array. - -**Example** - -```ts -repository.find({ - filter: { - tags: { - $anyOf: ['literature', 'history'], - } - } -}) -``` - -### `$noneOf` - -Check if the array field values contain none of the values of the specified array. - -**Example** - -```ts -repository.find({ - filter: { - tags: { - $noneOf: ['literature', 'history'], - } - } -}) -``` - -### `$arrayEmpty` - -Check if the array field is empty. - -**Example** - -```ts -repository.find({ - filter: { - tags: { - $arrayEmpty: true, - } - } -}); -``` - -### `$arrayNotEmpty` - -Check if the array field is not empty. - -**Example** - -```ts -repository.find({ - filter: { - tags: { - $arrayNotEmpty: true, - } - } -}); -``` - -## Relational Field Type Operators - -For checking if a relationship exists, field types include: - -- `type: 'hasOne'` -- `type: 'hasMany'` -- `type: 'belongsTo'` -- `type: 'belongsToMany'` - -### `$exists` - -There is relational data existing. - -**Example** - -```ts -repository.find({ - filter: { - author: { - $exists: true, - } - } -}); -``` - -### `$notExists` - -There is no relational data existing. - -**Example** - -```ts -repository.find({ - filter: { - author: { - $notExists: true, - } - } -}); -``` diff --git a/docs/en-US/api/database/relation-repository/belongs-to-many-repository.md b/docs/en-US/api/database/relation-repository/belongs-to-many-repository.md deleted file mode 100644 index bfd6f89db..000000000 --- a/docs/en-US/api/database/relation-repository/belongs-to-many-repository.md +++ /dev/null @@ -1,186 +0,0 @@ -# BelongsToManyRepository - -`BelongsToManyRepository` is the `Relation Repository` for handling `BelongsToMany` relationships. - -Unlike other relationship types, the `BelongsToMany` type of relationship needs to be recorded through an intermediate table. The intermediate table can be created automatically or explicitly specified when defining association relationships in NocoBase. - -## Class Methods - -### `find()` - -Find associated objects. - -**Signature** - -* `async find(options?: FindOptions): Promise` - -**Detailed Information** - -Query parameters are the same as [`Repository.find()`](../repository.md#find). - -### `findOne()` - -Find associated objects, only to return one record. - -**Signature** - -* `async findOne(options?: FindOneOptions): Promise` - - - -### `count()` - -Return the number of records matching the query criteria. - -**Signature** - -* `async count(options?: CountOptions)` - -**Type** - -```typescript -interface CountOptions extends Omit, Transactionable { - filter?: Filter; -} -``` - -### `findAndCount()` - -Find datasets from the database with the specified filtering conditions and return the number of results. - -**Signature** - -* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` - -**Type** - -```typescript -type FindAndCountOptions = CommonFindOptions -``` - -### `create()` - -Create associated objects. - -**Signature** - -* `async create(options?: CreateOptions): Promise` - - - -### `update()` - -Update associated objects that match the conditions. - -**Signature** - -* `async update(options?: UpdateOptions): Promise` - - - -### `destroy()` - -Delete associated objects. - -**Signature** - -* `async destroy(options?: TargetKey | TargetKey[] | DestroyOptions): Promise` - - - -### `add()` - -Add new associated objects. - -**Signature** - -* `async add( - options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions - ): Promise` - -**Type** - -```typescript -type PrimaryKeyWithThroughValues = [TargetKey, Values]; - -interface AssociatedOptions extends Transactionable { - tk?: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[]; -} -``` - -**Detailed Information** - -Pass the `targetKey` of the associated object directly, or pass the `targetKey` along with the field values of the intermediate table. - -**Example** - -```typescript -const t1 = await Tag.repository.create({ - values: { name: 't1' }, -}); - -const t2 = await Tag.repository.create({ - values: { name: 't2' }, -}); - -const p1 = await Post.repository.create({ - values: { title: 'p1' }, -}); - -const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.id); - -// Pass in the targetKey -PostTagRepository.add([ - t1.id, t2.id -]); - -// Pass in intermediate table fields -PostTagRepository.add([ - [t1.id, { tagged_at: '123' }], - [t2.id, { tagged_at: '456' }], -]); -``` - -### `set()` - -Set the associated objects. - -**Signature** - -* async set( - options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions, - ): Promise - -**Detailed Information** - -Parameters are the same as [add()](#add). - -### `remove()` - -Remove the association with the given objects. - -**Signature** - -* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**Type** - -```typescript -interface AssociatedOptions extends Transactionable { - tk?: TargetKey | TargetKey[]; -} -``` - -### `toggle()` - -Toggle the associated object. - -In some business scenarios, it is often needed to toggle the associated object. For example, user adds a product into collection, and the user cancels the collection and collect it again. Using the `toggle` method can quickly implement similar functions. - -**Signature** - -* `async toggle(options: TargetKey | { tk?: TargetKey; transaction?: Transaction }): Promise` - -**Detailed Information** - -The `toggle` method automatically checks whether the associated object already exists, and removes it if it does, or adds it if it does not. 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 deleted file mode 100644 index 0a6b1df83..000000000 --- a/docs/en-US/api/database/relation-repository/belongs-to-repository.md +++ /dev/null @@ -1,3 +0,0 @@ -## BelongsToRepository - -The interface is the same as [HasOneRepository](./has-one-repository.md). `BelongsToRepository` is the `Repository` for handling `BelongsTo` relationships, and it provides some convenient methods to handle `BelongsTo` relationships. diff --git a/docs/en-US/api/database/relation-repository/has-many-repository.md b/docs/en-US/api/database/relation-repository/has-many-repository.md deleted file mode 100644 index 1f1f30840..000000000 --- a/docs/en-US/api/database/relation-repository/has-many-repository.md +++ /dev/null @@ -1,133 +0,0 @@ - -# HasManyRepository - -`HasManyRepository` is the `Relation Repository` for handling `HasMany` relationships. - -## Class Methods - -### `find()` - -Find associated objects. - -**Signature** - -* `async find(options?: FindOptions): Promise` - -**Detailed Information** - -Query parameters are the same as [`Repository.find()`](../repository.md#find). - -### `findOne()` - -Find associated objects, only to return one record. - -**Signature** - -* `async findOne(options?: FindOneOptions): Promise` - - - -### `count()` - -Return the number of records matching the query criteria. - -**Signature** - -* `async count(options?: CountOptions)` - -**Type** - -```typescript -interface CountOptions extends Omit, Transactionable { - filter?: Filter; -} -``` - -### `findAndCount()` - -Find datasets from the database with the specified filtering conditions and return the number of results. - -**Signature** - -* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` - -**Type** - -```typescript -type FindAndCountOptions = CommonFindOptions -``` - -### `create()` - -Create associated objects. - -**Signature** - -* `async create(options?: CreateOptions): Promise` - - - -### `update()` - -Update associated objects that match the conditions. - -**Signature** - -* `async update(options?: UpdateOptions): Promise` - - - -### `destroy()` - -Delete associated objects. - -**Signature** - -* `async destroy(options?: TK | DestroyOptions): Promise` - - - -### `add()` - -Add association relationships between objects. - -**Signature** - -* `async add(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**Type** - -```typescript -interface AssociatedOptions extends Transactionable { - tk?: TargetKey | TargetKey[]; -} -``` - -**Detailed Information** - -* `tk` - The targetKey value of the associated object, either as a single value or an array. - - -### `remove()` - -Remove the association with the given objects. - -**Signature** - -* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**Detailed Information** - -Same parameters as the [`add()`](#add) method. - -### `set()` - -Set the associated object of the current relationship. - -**Signature** - -* `async set(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**Detailed Information** - -Same parameters as the [`add()`](#add) method. 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 deleted file mode 100644 index 34148cc63..000000000 --- a/docs/en-US/api/database/relation-repository/has-one-repository.md +++ /dev/null @@ -1,184 +0,0 @@ -# HasOneRepository - -## Overview - -`HasOneRepository` is the associated repository of type `HasOne`. - -```typescript -const User = db.collection({ - name: 'users', - fields: [ - { type: 'hasOne', name: 'profile' }, - { type: 'string', name: 'name' }, - ], -}); - -const Profile = db.collection({ - name: 'profiles', - fields: [{ type: 'string', name: 'avatar' }], -}); - -const user = await User.repository.create({ - values: { name: 'u1' }, -}); - -// Get the associated repository -const userProfileRepository = User.repository.relation('profile').of(user.get('id')); - -// Or to initialize directly -new HasOneRepository(User, 'profile', user.get('id')); -``` - -## Class Methods - -### `find()` - -Find associated objects. - -**Signature** - -* `async find(options?: SingleRelationFindOption): Promise | null>` - -**Type** - -```typescript -interface SingleRelationFindOption extends Transactionable { - fields?: Fields; - except?: Except; - appends?: Appends; - filter?: Filter; -} -``` - -**Detailed Information** - -Query parameters are the same as [`Repository.find()`](../repository.md#find). - -**Example** - -```typescript -const profile = await UserProfileRepository.find(); -// Return null if the associated object does not exist -``` - -### `create()` - -Create associated objects. - -**Signature** - -* `async create(options?: CreateOptions): Promise` - - - -**Example** - -```typescript -const profile = await UserProfileRepository.create({ - values: { avatar: 'avatar1' }, -}); - -console.log(profile.toJSON()); -/* -{ - id: 1, - avatar: 'avatar1', - userId: 1, - updatedAt: 2022-09-24T13:59:40.025Z, - createdAt: 2022-09-24T13:59:40.025Z -} -*/ -``` - -### `update()` - -Update associated objects. - -**Signature** - -* `async update(options: UpdateOptions): Promise` - - - -**Example** - -```typescript -const profile = await UserProfileRepository.update({ - values: { avatar: 'avatar2' }, -}); - -profile.get('avatar'); // 'avatar2' -``` - -### `remove()` - -Remove associated objects. Only to unassociate, not to delete the associated object. - -**Signature** - -* `async remove(options?: Transactionable): Promise` - -**Detailed Information** - -* `transaction`: Transaction object. If no transaction parameter is passed, the method will automatically create an internal transaction. - -**Example** - -```typescript -await UserProfileRepository.remove(); -await UserProfileRepository.find() == null; // true - -await Profile.repository.count() === 1; // true -``` - -### `destroy()` - -Delete associated objects. - -**Signature** - -* `async destroy(options?: Transactionable): Promise` - -**Detailed Information** - -* `transaction`: Transaction object. If no transaction parameter is passed, the method will automatically create an internal transaction. - -**Example** - -```typescript -await UserProfileRepository.destroy(); -await UserProfileRepository.find() == null; // true -await Profile.repository.count() === 0; // true -``` - -### `set()` - -Set associated objects. - -**Signature** - -* `async set(options: TargetKey | SetOption): Promise` - -**Type** - -```typescript -interface SetOption extends Transactionable { - tk?: TargetKey; -} -```` -**Detailed Information** - -* tk: Set the targetKey of the associated object. -* transaction: Transaction object. If no transaction parameter is passed, the method will automatically create an internal transaction. - -**Example** - -```typescript -const newProfile = await Profile.repository.create({ - values: { avatar: 'avatar2' }, -}); - -await UserProfileRepository.set(newProfile.get('id')); - -(await UserProfileRepository.find()).get('id') === newProfile.get('id'); // true -``` diff --git a/docs/en-US/api/database/relation-repository/index.md b/docs/en-US/api/database/relation-repository/index.md deleted file mode 100644 index 27140968d..000000000 --- a/docs/en-US/api/database/relation-repository/index.md +++ /dev/null @@ -1,45 +0,0 @@ -# RelationRepository - -`RelationRepository` 是关系类型的 `Repository` 对象,`RelationRepository` 可以实现在不加载关联的情况下对关联数据进行操作。基于 `RelationRepository`,每种关联都派生出对应的实现,分别为 - -* [`HasOneRepository`](#has-one-repository) -* `HasManyRepository` -* `BelongsToRepository` -* `BelongsToManyRepository` - - -## 构造函数 - -**签名** - -* `constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `sourceCollection` | `Collection` | - | 关联中的参照关系(referencing relation)对应的 Collection | -| `association` | `string` | - | 关联名称 | -| `sourceKeyValue` | `string \| number` | - | 参照关系中对应的 key 值 | - - -## 基类属性 - -### `db: Database` - -数据库对象 - -### `sourceCollection` -关联中的参照关系(referencing relation)对应的 Collection - -### `targetCollection` -关联中被参照关系(referenced relation)对应的 Collection - -### `association` -sequelize 中的与当前关联对应的 association 对象 - -### `associationField` -collection 中的与当前关联对应的字段 - -### `sourceKeyValue` -参照关系中对应的 key 值 diff --git a/docs/en-US/api/database/repository.md b/docs/en-US/api/database/repository.md deleted file mode 100644 index 800a812f7..000000000 --- a/docs/en-US/api/database/repository.md +++ /dev/null @@ -1,684 +0,0 @@ -# Repository - -## Overview - -On a given `Collection` object, you can get its `Repository` object to perform read and write operations on the data table. - -```javascript -const { UserCollection } = require("./collections"); - -const UserRepository = UserCollection.repository; - -const user = await UserRepository.findOne({ - filter: { - id: 1 - }, -}); - -user.name = "new name"; -await user.save(); -``` - -### Query - -#### Basic Query - -On the `Repository` object, call the `find*` methods to perform query. The `filter` parameter is supported by all query methods to filter the data. - -```javascript -// SELECT * FROM users WHERE id = 1 -userRepository.find({ - filter: { - id: 1 - } -}); - -``` - -#### Operator - -The `filter` parameter in the `Repository` also provides a variety of operators to perform more diverse queries. - -```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: "%张%" } } - ] - } -}); - -``` - -Refer to [Filter Operators](/api/database/operators) for more details on operators. - -#### Field Control - -Control the output fields by the `fields`, `except`, and `appends` parameters when performing query. - -* `fields`: Specify output fields -* `except`: Exclude output fields -* `appends`: Append output associated fields - -```javascript -// The result contains only the id and name fields -userRepository.find({ - fields: ["id", "name"], -}); - -// The result does not contain only the password field -userRepository.find({ - except: ["password"], -}); - -// The result contains data associated with the posts object -userRepository.find({ - appends: ["posts"], -}); -``` - -#### Associated Field Query - -The `filter` parameter supports filtering by associated fields, for example: - -```javascript -// Find the user objects whose associated posts have title of "post title" -userRepository.find({ - filter: { - "posts.title": "post title" - } -}); -``` - -Associated fields can also be nested: - -```javascript -// Find the user objects whose associated posts have comments containing "keywords" -await userRepository.find({ - filter: { - "posts.comments.content": { - $like: "%keywords%" - } - } -}); -``` - -#### Sort - -Sort query results by the `sort` parameter. - -```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"], -}); -``` - -Sort by the field of the associated object is also supported: - -```javascript -await userRepository.find({ - sort: 'profile.createdAt' -}); -``` - -### Create - -#### Basic Create - -Create new data objects via `Repository`. - -```javascript - -await userRepository.create({ - name: "Mark", - age: 18, -}); -// INSERT INTO users (name, age) VALUES ('Mark', 18) - - -// Bulk creation -await userRepository.create([ - { - name: "Mark", - age: 18, - }, - { - name: "Alex", - age: 20, - }, -]) -``` - -#### Create Association - -Create associated objects at the same time of creating data. Like query, nested use of associated objects is also supported. For example: - -```javascript -await userRepository.create({ - name: "Mark", - age: 18, - posts: [ - { - title: "post title", - content: "post content", - tags: [ - { - name: "tag1", - }, - { - name: "tag2", - }, - ], - }, - ], -}); -// When creating a user, create a post to associate with the user, and create tags to associate with the post -``` - -If the associated object is already in the database, you can pass its ID to create an association with it. - -```javascript -const tag1 = await tagRepository.findOne({ - filter: { - name: "tag1" - }, -}); - -await userRepository.create({ - name: "Mark", - age: 18, - posts: [ - { - title: "post title", - content: "post content", - tags: [ - { - id: tag1.id, // Create an association with an existing associated object - }, - { - name: "tag2", - }, - ], - }, - ], -}); -``` - -### Update - -#### Basic Update - -After getting the data object, you can modify the properties directly on the data object (`Model`), and then call the `save` method to save the changes. - -```javascript -const user = await userRepository.findOne({ - filter: { - name: "Mark", - }, -}); - - -user.age = 20; -await user.save(); -``` - -The data object `Model` is inherited from Sequelize Model, refer to [Sequelize Model](https://sequelize.org/master/manual/model-basics.html) for the operations on `Model`. - -Or update data via `Repository`: - -```javascript -// Update the records that meet the filtering condition -await userRepository.update({ - filter: { - name: "Mark", - }, - values: { - age: 20, - }, -}); -``` - -Control which fields to update by the `whitelist` and `blacklist` parameters, for example: - -```javascript -await userRepository.update({ - filter: { - name: "Mark", - }, - values: { - age: 20, - name: "Alex", - }, - whitelist: ["age"], // Only update the age field -}); -```` - -#### Update Associated Field - -Associated objects can be set while updating, for example: - -```javascript -const tag1 = tagRepository.findOne({ - filter: { - id: 1 - }, -}); - -await postRepository.update({ - filter: { - id: 1 - }, - values: { - title: "new post title", - tags: [ - { - id: tag1.id // Associate with tag1 - }, - { - name: "tag2", // Create new tag and associate with it - }, - ], - }, -}); - - -await postRepository.update({ - filter: { - id: 1 - }, - values: { - tags: null // Disassociate post from tags - }, -}) -``` - -### Delete - -Call the `destroy()` method in `Repository` to perform the deletion operation. Filtering condition has to be specified to delete. - -```javascript -await userRepository.destroy({ - filter: { - status: "blocked", - }, -}); -``` - -## Constructor - -It is usually not called directly by the developer, the instantiation is done mainly by specifying a corresponding repository type that is already registered in the parameter of `db.colletion()`. Repository type is registered through `db.registerRepositories()`. - -**Signature** - -* `constructor(collection: Collection)` - -**Example** - -```ts -import { Repository } from '@nocobase/database'; - -class MyRepository extends Repository { - async myQuery(sql) { - return this.database.sequelize.query(sql); - } -} - -db.registerRepositories({ - books: MyRepository -}); - -db.collection({ - name: 'books', - // here link to the registered repository - repository: 'books' -}); - -await db.sync(); - -const books = db.getRepository('books') as MyRepository; -await books.myQuery('SELECT * FROM books;'); -``` - -## Instance Members - -### `database` - -The database management instance where the context is located. - -### `collection` - -The corresponding data table management instance. - -### `model` - -The corresponding data model class. - -## Instance Methods - -### `find()` - -Find datasets from the database with the specified filtering conditions and sorting, etc. - -**Signature** - -* `async find(options?: FindOptions): Promise` - -**Type** - -```typescript -type Filter = FilterWithOperator | FilterWithValue | FilterAnd | FilterOr; -type Appends = string[]; -type Except = string[]; -type Fields = string[]; -type Sort = string[] | string; - -interface SequelizeFindOptions { - limit?: number; - offset?: number; -} - -interface FilterByTk { - filterByTk?: TargetKey; -} - -interface CommonFindOptions extends Transactionable { - filter?: Filter; - fields?: Fields; - appends?: Appends; - except?: Except; - sort?: Sort; -} - -type FindOptions = SequelizeFindOptions & CommonFindOptions & FilterByTk; -``` - -**Detailed Information** - -#### `filter: Filter` - -Query conditions for filtering data results. In the query parameters that passed in, `key` is the name of the field, `value` is the corresponding value. Operators can be used in conjunction with other filtering conditions. - -```typescript -// Find records with name "foo" and age above 18 -repository.find({ - filter: { - name: "foo", - age: { - $gt: 18, - }, - } -}) -``` - -Refer to [Operators](./operators.md) for more information. - -#### `filterByTk: TargetKey` - -Query data by `TargetKey`, this is shortcut for the `filter` parameter. The field of `TargetKey` can be [configured](./collection.md#filtertargetkey) in `Collection`, the default is `primaryKey`. - -```typescript -// By default, find records with id 1 -repository.find({ - filterByTk: 1, -}); -``` - -#### `fields: string[]` - -Query columns. It is used to control which data fields to output. With this parameter, only the specified fields will be returned. - -#### `except: string[]` - -Exclude columns. It is used to control which data fields to output. With this parameter, the specified fields will not be returned. - -#### `appends: string[]` - -Append columns. It is used to load associated data. With this parameter, the specified associated fields will be returned together. - -#### `sort: string[] | string` - -Specify the sorting method of the query results. The input parameter is the name of the field, by default is to sort in the ascending order (`asc`); a `-` symbol needs to be added before the field name to sort in the descending order (`desc`). For example, `['-id', 'name']` means to sort by `id desc, name asc`. - -#### `limit: number` - -Limit the number of results, same as `limit` in `SQL`. - -#### `offset: number` - -The offset of the query, same as `offset` in `SQL`. - -**Example** - -```ts -const posts = db.getRepository('posts'); - -const results = await posts.find({ - filter: { - createdAt: { - $gt: '2022-01-01T00:00:00.000Z', - } - }, - fields: ['title'], - appends: ['user'], -}); -``` - -### `findOne()` - -Find a single piece of data from the database for specific conditions. Equivalent to `Model.findOne()` in Sequelize. - -**Signature** - -* `async findOne(options?: FindOneOptions): Promise` - - - -**Example** - -```ts -const posts = db.getRepository('posts'); - -const result = await posts.findOne({ - filterByTk: 1, -}); -``` - -### `count()` - -Query a certain amount of data from the database for specific conditions. Equivalent to `Model.count()` in Sequelize. - -**Signature** - -* `count(options?: CountOptions): Promise` - -**Type** - -```typescript -interface CountOptions extends Omit, Transactionable { - filter?: Filter; -} -``` - -**Example** - -```ts -const books = db.getRepository('books'); - -const count = await books.count({ - filter: { - title: 'Three character classic' - } -}); -``` - -### `findAndCount()` - -Find datasets from the database with the specified filtering conditions and return the number of results. Equivalent to `Model.findAndCountAll()` in Sequelize. - -**Signature** - -* `async findAndCount(options?: FindAndCountOptions): Promise<[Model[], number]>` - -**Type** - -```typescript -type FindAndCountOptions = Omit & CommonFindOptions; -``` - -**Detailed Information** - -The query parameters are the same as `find()`. An array is returned with the first element of the query results, and the second element of the total number of results. - -### `create()` - -Inserts a newly created data into the data table. Equivalent to `Model.create()` in Sequelize. When the data object to be created carries any associated field, the corresponding associated data record is created or updated along with it. - -**Signature** - -* `async create(options: CreateOptions): Promise` - - - -**Example** - -```ts -const posts = db.getRepository('posts'); - -const result = await posts.create({ - values: { - title: 'NocoBase 1.0 Release Notes', - tags: [ - // Update data when there is a primary key and value of the associated table - { id: 1 }, - // Create data when there is no primary key and value - { name: 'NocoBase' }, - ] - }, -}); -``` - -### `createMany()` - -Inserts multiple newly created data into the data table. This is equivalent to calling the `create()` method multiple times. - -**Signature** - -* `createMany(options: CreateManyOptions): Promise` - -**Type** - -```typescript -interface CreateManyOptions extends BulkCreateOptions { - records: Values[]; -} -``` - -**Detailed Information** - -* `records`: An array of data objects to be created. -* `transaction`: Transaction object. If no transaction parameter is passed, the method will automatically create an internal transaction. - -**Example** - -```ts -const posts = db.getRepository('posts'); - -const results = await posts.createMany({ - records: [ - { - title: 'NocoBase 1.0 Release Notes', - tags: [ - // Update data when there is a primary key and value of the associated table - { id: 1 }, - // Create data when there is no primary key and value - { name: 'NocoBase' }, - ] - }, - { - title: 'NocoBase 1.1 Release Notes', - tags: [ - { id: 1 } - ] - }, - ], -}); -``` - -### `update()` - -Update data in the data table. Equivalent to `Model.update()` in Sequelize. When the data object to be updated carries any associated field, the corresponding associated data record is created or updated along with it. - -**Signature** - -* `async update(options: UpdateOptions): Promise` - - - -**Example** - -```ts -const posts = db.getRepository('posts'); - -const result = await posts.update({ - filterByTk: 1, - values: { - title: 'NocoBase 1.0 Release Notes', - tags: [ - // Update data when there is a primary key and value of the associated table - { id: 1 }, - // Create data when there is no primary key and value - { name: 'NocoBase' }, - ] - }, -}); -``` - -### `destroy()` - -Delete data from the data table. Equivalent to `Model.destroy()` in Sequelize. - -**Signature** - -* `async destroy(options?: TargetKey | TargetKey[] | DestoryOptions): Promise` - -**Type** - -```typescript -interface DestroyOptions extends SequelizeDestroyOptions { - filter?: Filter; - filterByTk?: TargetKey | TargetKey[]; - truncate?: boolean; - context?: any; -} -``` - -**Detailed Information** - -* `filter`:Specify the filtering conditions of the records to be deleted. Refer to the [`find()`](#find) method for the detailed usage of the filter. -* `filterByTk`:Specify the filtering conditions by TargetKey. -* `truncate`: Whether to empty the table data, this parameter is valid if no `filter` or `filterByTk` parameter is passed. -* `transaction`: Transaction object. If no transaction parameter is passed, the method will automatically create an internal transaction. diff --git a/docs/en-US/api/database/shared.md b/docs/en-US/api/database/shared.md deleted file mode 100644 index fd2b409fb..000000000 --- a/docs/en-US/api/database/shared.md +++ /dev/null @@ -1,8 +0,0 @@ -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.values` | `M` | `{}` | 插入的数据对象 | -| `options.whitelist?` | `string[]` | - | `values` 字段的白名单,只有名单内的字段会被存储 | -| `options.blacklist?` | `string[]` | - | `values` 字段的黑名单,名单内的字段不会被存储 | -| `options.transaction?` | `Transaction` | - | 事务 | diff --git a/docs/en-US/api/database/shared/create-options.md b/docs/en-US/api/database/shared/create-options.md deleted file mode 100644 index 51b245a45..000000000 --- a/docs/en-US/api/database/shared/create-options.md +++ /dev/null @@ -1,21 +0,0 @@ -**类型** -```typescript -type WhiteList = string[]; -type BlackList = string[]; -type AssociationKeysToBeUpdate = string[]; - -interface CreateOptions extends SequelizeCreateOptions { - values?: Values; - whitelist?: WhiteList; - blacklist?: BlackList; - updateAssociationValues?: AssociationKeysToBeUpdate; - context?: any; -} -``` - -**详细信息** - -* `values`:要创建的记录的数据对象。 -* `whitelist`:指定要创建的记录的数据对象中,哪些字段**可以被写入**。若不传入此参数,则默认允许所有字段写入。 -* `blacklist`:指定要创建的记录的数据对象中,哪些字段**不允许被写入**。若不传入此参数,则默认允许所有字段写入。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/en-US/api/database/shared/destroy-options.md b/docs/en-US/api/database/shared/destroy-options.md deleted file mode 100644 index aaf8e63b9..000000000 --- a/docs/en-US/api/database/shared/destroy-options.md +++ /dev/null @@ -1,17 +0,0 @@ -**类型** - -```typescript -interface DestroyOptions extends SequelizeDestroyOptions { - filter?: Filter; - filterByTk?: TargetKey | TargetKey[]; - truncate?: boolean; - context?: any; -} -``` - -**详细信息** - -* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。 -* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。 -* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/en-US/api/database/shared/find-one.md b/docs/en-US/api/database/shared/find-one.md deleted file mode 100644 index 47e5f2192..000000000 --- a/docs/en-US/api/database/shared/find-one.md +++ /dev/null @@ -1,8 +0,0 @@ -**类型** -```typescript -type FindOneOptions = Omit; -``` - -**参数** - -大部分参数与 `find()` 相同,不同之处在于 `findOne()` 只返回单条数据,所以不需要 `limit` 参数,且查询时始终为 `1`。 diff --git a/docs/en-US/api/database/shared/find-options.md b/docs/en-US/api/database/shared/find-options.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/en-US/api/database/shared/transaction.md b/docs/en-US/api/database/shared/transaction.md deleted file mode 100644 index 27068fcf9..000000000 --- a/docs/en-US/api/database/shared/transaction.md +++ /dev/null @@ -1 +0,0 @@ -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/en-US/api/database/shared/update-options.md b/docs/en-US/api/database/shared/update-options.md deleted file mode 100644 index ea03c3ea7..000000000 --- a/docs/en-US/api/database/shared/update-options.md +++ /dev/null @@ -1,24 +0,0 @@ -**类型** -```typescript -interface UpdateOptions extends Omit { - values: Values; - filter?: Filter; - filterByTk?: TargetKey; - whitelist?: WhiteList; - blacklist?: BlackList; - updateAssociationValues?: AssociationKeysToBeUpdate; - context?: any; -} -``` - -**详细信息** - - -* `values`:要更新的记录的数据对象。 -* `filter`:指定要更新的记录的过滤条件, Filter 详细用法可参考 [`find()`](#find) 方法。 -* `filterByTk`:按 TargetKey 指定要更新的记录的过滤条件。 -* `whitelist`: `values` 字段的白名单,只有名单内的字段会被写入。 -* `blacklist`: `values` 字段的黑名单,名单内的字段不会被写入。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 - -`filterByTk` 与 `filter` 至少要传其一。 diff --git a/docs/en-US/api/env.md b/docs/en-US/api/env.md deleted file mode 100644 index 0c3261448..000000000 --- a/docs/en-US/api/env.md +++ /dev/null @@ -1,236 +0,0 @@ -# Environment Variables - -## Global Environment Variables - -Saved in the `.env` file - -### APP_ENV - -Application environment, default is `development`, options include - -- `production` production environment -- `development` development environment - -```bash -APP_ENV=production -``` - -### APP_HOST - -Application host, default is `0.0.0.0` - -```bash -APP_HOST=192.168.3.154 -``` - -### APP_PORT - -Application port, default is `13000` - -```bash -APP_PORT=13000 -``` - -### APP_KEY - -Secret key, for scenarios such as jwt - -```bash -APP_KEY=app-key-test -``` - -### API_BASE_PATH - -NocoBase API address prefix, default is `/api/` - -```bash -API_BASE_PATH=/api/ -``` - -### PLUGIN_PACKAGE_PREFIX - -Plugin package prefix, default is `@nocobase/plugin-,@nocobase/preset-` - -For example, add plugin `hello` into project `my-nocobase-app`, the plugin package name is `@my-nocobase-app/plugin-hello`. - -PLUGIN_PACKAGE_PREFIX is configured as follows: - -```bash -PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase-preset-,@my-nocobase-app/plugin- -``` - -The correspondence between plugin name and package name is: - -- `users` plugin package name is `@nocobase/plugin-users` -- `nocobase` plugin package name is `@nocobase/preset-nocobase` -- `hello` plugin package name is `@my-nocobase-app/plugin-hello` - -### DB_DIALECT - -Database type, default is `sqlite`, options include - -- `sqlite` -- `mysql` -- `postgres` - -```bash -DB_DIALECT=mysql -``` - -### DB_STORAGE - -Database file path (required when using a SQLite database) - -```bash -### Relative path -DB_HOST=storage/db/nocobase.db -# Absolute path -DB_HOST=/your/path/nocobase.db -``` - -### DB_HOST - -Database host (required when using MySQL or PostgreSQL databases) - -Default is `localhost` - -```bash -DB_HOST=localhost -``` - -### DB_PORT - -Database port (required when using MySQL or PostgreSQL databases) - -- Default port of MySQL is 3306 -- Default port of PostgreSQL is 5432 - -```bash -DB_PORT=3306 -``` - -### DB_DATABASE - -Database name (required when using MySQL or PostgreSQL databases) - -```bash -DB_DATABASE=nocobase -``` - -### DB_USER - -Database user (required when using MySQL or PostgreSQL databases) - -```bash -DB_USER=nocobase -``` - -### DB_PASSWORD - -Database password (required when using MySQL or PostgreSQL databases) - -```bash -DB_PASSWORD=nocobase -``` - -### DB_TABLE_PREFIX - -Data table prefix - -```bash -DB_TABLE_PREFIX=nocobase_ -``` - -### DB_LOGGING - -Database log switch, default is `off`, options include - -- `on` on -- `off` off - -```bash -DB_LOGGING=on -``` - -### LOGGER_TRANSPORT - -Log transport, default is `console,dailyRotateFile`, options include - -- `console` -- `dailyRotateFile` - -### LOGGER_BASE_PATH - -Base path to save file based logs, default is `storage/logs` - -## Temporary Environment Variables - -The installation of NocoBase can be assited by setting temporary environment variables, such as: - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - INIT_ROOT_NICKNAME="Super Admin" \ - nocobase install - -# Equivalent to -yarn nocobase install \ - --lang=zh-CN \ - --root-email=demo@nocobase.com \ - --root-password=admin123 \ - --root-nickname="Super Admin" - -# Equivalent to -yarn nocobase install -l zh-CN -e demo@nocobase.com -p admin123 -n "Super Admin" -``` - -### INIT_APP_LANG - -Language at the time of installation, default is `en-US`, options include - -- `en-US` English -- `zh-CN` Chinese (Simplified) - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - nocobase install -``` - -### INIT_ROOT_EMAIL - -Root user mailbox - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - nocobase install -``` - -### INIT_ROOT_PASSWORD - -Root user password - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - nocobase install -``` - -### INIT_ROOT_NICKNAME - -Root user nickname - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - INIT_ROOT_NICKNAME="Super Admin" \ - nocobase install -``` diff --git a/docs/en-US/api/http/index.md b/docs/en-US/api/http/index.md deleted file mode 100644 index ae90bf97f..000000000 --- a/docs/en-US/api/http/index.md +++ /dev/null @@ -1,301 +0,0 @@ -# Overview - -HTTP API of NocoBase is designed based on Resource & Action, a superset of REST API. The operation includes but not limited to create, read, update and delete. Resource Action can be extended arbitrarily in NocoBase. - -## Resource - -In NocoBase, resource has two expressions: - -- `` -- `.` - - - -- Collection is the set of all abstract data -- Association is the associated data of collection -- Resource includes both collection and collection.association - - - -### Example - -- `posts` Post -- `posts.user` Post user -- `posts.tags` Post tags - -## Action - -Action on resource is expressed by `:` - -- `:` -- `.:` - -Built-in global actions for collection or association: - -- `create` -- `get` -- `list` -- `update` -- `destroy` -- `move` - -Built-in association actions for association only: - -- `set` -- `add` -- `remove` -- `toggle` - -### Example - -- `posts:create` Create post -- `posts.user:get` Get post user -- `posts.tags:add` Add tags to post (associate existing tags with post) - -## Request URL - -```bash - /api/: - /api/:/ - /api///: - /api///:/ -``` - -### Example - -posts resource - -```bash -POST /api/posts:create -GET /api/posts:list -GET /api/posts:get/1 -POST /api/posts:update/1 -POST /api/posts:destroy/1 -``` - -posts.comments resource - -```bash -POST /api/posts/1/comments:create -GET /api/posts/1/comments:list -GET /api/posts/1/comments:get/1 -POST /api/posts/1/comments:update/1 -POST /api/posts/1/comments:destroy/1 -``` - -posts.tags resource - -```bash -POST /api/posts/1/tags:create -GET /api/posts/1/tags:get -GET /api/posts/1/tags:list -POST /api/posts/1/tags:update -POST /api/posts/1/tags:destroy -POST /api/posts/1/tags:add -GET /api/posts/1/tags:remove -``` - -## Locate Resource - -- Collection resource locates the data to be processed by `collectionIndex`, `collectionIndex` must be unique. -- Association resource locates the data to be processed by `collectionIndex` and `associationIndex` jointly, `associationIndex` may not be unique, but the joint index of `collectionIndex` and `associationIndex` must be unique. - -When viewing details of association resource, the requested URL needs to provide both `` and ``, `` is necessary as `` may not be unique. - -For example, `tables.fields` represents the fields of a data table: - -```bash -GET /api/tables/table1/fields/title -GET /api/tables/table2/fields/title -``` - -Both table1 and table2 have the title field, title is unique in one table, but other tables may also have fields of that name. - -## Request Parameters - -Request parameters can be placed in the headers, parameters (query string), and body (GET requests do not have a body) of the request. - -Some special request parameters: - -- `filter` Data filtering, used in actions related to query. -- `filterByTk` Filter by tk field, used in actions to specify details of data. -- `sort` Sorting, used in actions related to query. -- `fields` Date to output, used in actions related to query -- `appends` Fields of additional relationship, used in actions related to query. -- `except` Exclude some fields (not to output), used in actions related to query. -- `whitelist` Fields whitelist, used in actions related to data creation and update. -- `blacklist` Fields blacklist, used in actions related to data creation and update. - -### filter - -Data filtering. - -```bash -# simple -GET /api/posts?filter[status]=publish -# json string format is recommended, which requires encodeURIComponent encoding -GET /api/posts?filter={"status":"published"} - -# filter operators -GET /api/posts?filter[status.$eq]=publish -GET /api/posts?filter={"status.$eq":"published"} - -# $and -GET /api/posts?filter={"$and": [{"status.$eq":"published"}, {"title.$includes":"a"}]} -# $or -GET /api/posts?filter={"$or": [{"status.$eq":"pending"}, {"status.$eq":"draft"}]} - -# association field -GET /api/posts?filter[user.email.$includes]=gmail -GET /api/posts?filter={"user.email.$includes":"gmail"} -``` - -[Click here for more information about filter operators](http-api/filter-operators) - -### filterByTk - -Filter by tk field. In the default settings: - -- collection resource: tk is the primary key of the data table. -- association resource: tk is the targetKey field of the association. - -```bash -GET /api/posts:get?filterByTk=1&fields=name,title&appends=tags -``` - -### sort - -Sorting. To sort in the descending order, put `-` in front of the field. - -```bash -# Sort createAt field in the ascending order -GET /api/posts:get?sort=createdAt -# Sort createAt field in the descending order -GET /api/posts:get?sort=-createdAt -# Sort multiple fields jointly, createAt field descending, title A-Z ascending -GET /api/posts:get?sort=-createdAt,title -``` - -### fields - -Data to output. - -```bash -GET /api/posts:list?fields=name,title - -Response 200 (application/json) -{ - "data": [ - { - "name": "", - "title": "" - } - ], - "meta": {} -} -``` - -### appends - -Fields of additional relationship. - -### except - -Exclude some fields (not to output), used in actions related to query. - -### whitelist - -Whitelist. - -```bash -POST /api/posts:create?whitelist=title - -{ - "title": "My first post", - "date": "2022-05-19" # The date field will be filtered out and not be written to the database -} -``` - -### blacklist - -Blacklist. - -```bash -POST /api/posts:create?blacklist=date - -# The date field will be filtered out and not be written to the database -{ - "title": "My first post" -} -``` - -## Request Response - -Format of the response: - -```ts -type ResponseResult = { - data?: any; // Main data - meta?: any; // Additional Data - errors?: ResponseError[]; // Errors -}; - -type ResponseError = { - code?: string; - message: string; -}; -``` - -### Example - -View list: - -```bash -GET /api/posts:list - -Response 200 (application/json) - -{ - data: [ - { - id: 1 - } - ], - meta: { - count: 1 - page: 1, - pageSize: 1, - totalPage: 1 - }, -} -``` - -View details: - -```bash -GET /api/posts:get/1 - -Response 200 (application/json) - -{ - data: { - id: 1 - } -} -``` - -Error: - -```bash -POST /api/posts:create - -Response 400 (application/json) - -{ - errors: [ - { - message: 'name must be required', - }, - ], -} -``` diff --git a/docs/en-US/api/http/rest-api.md b/docs/en-US/api/http/rest-api.md deleted file mode 100644 index 2076082a4..000000000 --- a/docs/en-US/api/http/rest-api.md +++ /dev/null @@ -1,180 +0,0 @@ -# REST API - -HTTP API of NocoBase is a superset of REST API, and the standard CRUD API also supports the RESTful style. - -## Collection Resources - -### Create Collection - -HTTP API - -```bash -POST /api/:create - -{} # JSON body -``` - -REST API - -```bash -POST /api/ - -{} # JSON body -``` - -### View Collection List - -HTTP API - -```bash -GET /api/:list -``` - -REST API - -```bash -GET /api/ -``` - -### View Collection Details - -HTTP API - -```bash -GET /api/:get?filterByTk= -GET /api/:get/ -``` - -REST API - -```bash -GET /api// -``` - -### Update Collection - -HTTP API - -```bash -POST /api/:update?filterByTk= - -{} # JSON body - -# Or -POST /api/:update/ - -{} # JSON body -``` - -REST API - -```bash -PUT /api// - -{} # JSON body -``` - -### Delete Collection - -HTTP API - -```bash -POST /api/:destroy?filterByTk= -# Or -POST /api/:destroy/ -``` - -REST API - -```bash -DELETE /api// -``` - -## Association Resources - -### Create Association - -HTTP API - -```bash -POST /api///:create - -{} # JSON body -``` - -REST API - -```bash -POST /api/// - -{} # JSON body -``` - -### View Association List - -HTTP API - -```bash -GET /api///:list -``` - -REST API - -```bash -GET /api/// -``` - -### View Association Details - -HTTP API - -```bash -GET /api///:get?filterByTk= -# Or -GET /api///:get/ -``` - -REST API - -```bash -GET /api///:get/ -``` - -### Update Association - -HTTP API - -```bash -POST /api///:update?filterByTk= - -{} # JSON body - -# Or -POST /api///:update/ - -{} # JSON body -``` - -REST API - -```bash -PUT /api///:update/ - -{} # JSON -``` - -### Delete Association - -HTTP API - -```bash -POST /api///:destroy?filterByTk= -# Or -POST /api///:destroy/ -``` - -REST API - -```bash -DELETE /api//// -``` diff --git a/docs/en-US/api/index.md b/docs/en-US/api/index.md deleted file mode 100644 index 9f434786f..000000000 --- a/docs/en-US/api/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# Overview - -| Modules | Package Name | Description | -|-----------------------------------| --------------------- | ------------------- | -| [Server](/api/server/application) | `@nocobase/server` | Server-side application | -| [Database](/api/database) | `@nocobase/database` | Database access layer | -| [Resourcer](/api/resourcer) | `@nocobase/resourcer` | Resource and route mapping | -| [ACL](/api/acl) | `@nocobase/acl` | Access Control List | -| [Client](/api/client/application) | `@nocobase/client` | Client-side application | -| [CLI](/api/cli) | `@nocobase/cli` | NocoBase command line tools | -| [SDK](/api/sdk) | `@nocobase/sdk` | NocoBase SDK | -| [Actions](/api/actions) | `@nocobase/actions` | Built-in common resource actions | diff --git a/docs/en-US/api/resourcer/action.md b/docs/en-US/api/resourcer/action.md deleted file mode 100644 index 9afed49b8..000000000 --- a/docs/en-US/api/resourcer/action.md +++ /dev/null @@ -1,148 +0,0 @@ -# Action - -Action is the description of the operation process on resource, including database processing and so on. It is like the service layer in other frameworks, and the most simplified implementation can be a Koa middleware function. In the resourcer, common action functions defined for particular resources are wrapped into instances of the type Action, and when the request matches the action of the corresponding resource, the corresponding action is executed. - -## Constructor - -Instead of being instantiated directly, the Action is usually instantiated automatically by the resourcer by calling the static method `toInstanceMap()` of `Action`. - -### `constructor(options: ActionOptions)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `handler` | `Function` | - | Handler function | -| `middlewares?` | `Middleware \| Middleware[]` | - | Middlewares for the action | -| `values?` | `Object` | - | Default action data | -| `fields?` | `string[]` | - | Default list of targeted fields | -| `appends?` | `string[]` | - | Default list of associated fields to append | -| `except?` | `string[]` | - | Default list of fields to exclude | -| `filter` | `FilterOptions` | - | Default filtering options | -| `sort` | `string[]` | - | Default sorting options | -| `page` | `number` | - | Default page number | -| `pageSize` | `number` | - | Default page size | -| `maxPageSize` | `number` | - | Default maximum page size | - -## Instance Members - -### `actionName` - -Name of the action that corresponds to when it is instantiated. It is parsed and fetched from the request at instantiation. - -### `resourceName` - -Name of the resource that corresponds to when the action is instantiated. It is parsed and fetched from the request at instantiation. - -### `resourceOf` - -Value of the primary key of the relational resource that corresponds to when the action is instantiated. It is parsed and fetched from the request at instantiation. - -### `readonly middlewares` - -List of middlewares targeting the action. - -### `params` - -Action parameters. It contains all relevant parameters for the corresponding action, which are initialized at instantiation according to the defined action parameters. Later when parameters passed from the front-end are parsed in requests, the corresponding parameters are merged according to the merge strategy. Similar merging process is done if there is other middleware processing. When it comes to the hander, the `params` are the final parameters that have been merged for several times. - -The merging process of parameters provides scalability for action processing, and the parameters can be pre-parsed and processed according to business requirements by means of custom middleware. For example, parameter validation for form submission can be implemented in this part. - -Refer to [/api/actions] for the pre-defined parameters of different actions. - -The parameters also contain a description of the request resource route: - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `resourceName` | `string` | - | Name of the resource | -| `resourceIndex` | `string \| number` | - | Value of the primary key of the resource | -| `associatedName` | `string` | - | Name of the associated resource it belongs to | -| `associatedIndex` | `string \| number` | - | Value of the primary key of the associated resource it belongs to | -| `associated` | `Object` | - | Instance of the associated resource it belongs to | -| `actionName` | `string` | - | Name of the action | - -**Example** - -```ts -app.resourcer.define('books', { - actions: { - publish(ctx, next) { - ctx.body = ctx.action.params.values; - // { - // id: 1234567890 - // publishedAt: '2019-01-01', - // } - } - }, - middlewares: [ - async (ctx, next) => { - ctx.action.mergeParams({ - values: { - id: Math.random().toString(36).substr(2, 10), - publishedAt: new Date(), - } - }); - await next(); - } - ] -}); -``` - -## Instance Methods - -### `mergeParams()` - -Merge additional parameters to the current set of parameters according to different strategies. - -**Signature** - -* `mergeParams(params: ActionParams, strategies: MergeStrategies = {})` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `params` | `ActionParams` | - | Additional set of parameters | -| `strategies` | `MergeStrategies` | - | Merge strategies for each parameter | - -The default merge strategy for built-in actions is as follows: - -| Name | Type | Default | Merge Strategy |Description | -| --- | --- | --- | --- | --- | -| `filterByTk` | `number \| string` | - | SQL `and` | Get value of the primary key | -| `filter` | `FilterOptions` | - | SQL `and` | Get filtering options | -| `fields` | `string[]` | - | Take the union | List of fields | -| `appends` | `string[]` | `[]` | Take the union | List of associated fields to append | -| `except` | `string[]` | `[]` | Take the union | List of associated fields to exclude | -| `whitelist` | `string[]` | `[]` | Take the intersection | Whitelist of fields that can be handled | -| `blacklist` | `string[]` | `[]` | Take the union | Blacklist of fields that can be handled | -| `sort` | `string[]` | - | SQL `order by` | Get the sorting options | -| `page` | `number` | - | Override | Page number | -| `pageSize` | `number` | - | Override | Page size | -| `values` | `Object` | - | Deep merge | Operation of the submitted data | - -**Example** - -```ts -ctx.action.mergeParams({ - filter: { - name: 'foo', - }, - fields: ['id', 'name'], - except: ['name'], - sort: ['id'], - page: 1, - pageSize: 10, - values: { - name: 'foo', - }, -}, { - filter: 'and', - fields: 'union', - except: 'union', - sort: 'overwrite', - page: 'overwrite', - pageSize: 'overwrite', - values: 'deepMerge', -}); -``` diff --git a/docs/en-US/api/resourcer/index.md b/docs/en-US/api/resourcer/index.md deleted file mode 100644 index acc964b61..000000000 --- a/docs/en-US/api/resourcer/index.md +++ /dev/null @@ -1,285 +0,0 @@ -# Resourcer - -## Overview - -The interfaces in NocoBase follow a resource-oriented design pattern. Resourcer is mainly used to manage the resources and routes of API. - -```javascript -const Koa = require('koa'); -const { Resourcer } = require('@nocobase/resourcer'); - -const resourcer = new Resourcer(); - -// Define a resource interface -resourcer.define({ - name: 'users', - actions: { - async list(ctx) { - ctx.body = [ - { - name: "u1", - age: 18 - }, - { - name: "u2", - age: 20 - } - ] - } - }, -}); - -const app = new Koa(); - -// Use the resourcer in koa instance -app.use( - resourcer.middleware({ - prefix: '/api', // Route prefix of the resourcer - }), -); - -app.listen(3000); -``` - -Once the service is started, make request using `curl`: - -```bash ->$ curl localhost:3000/api/users -[{"name":"u1","age":18},{"name":"u2","age":20}] -``` - -More instructions of resourcer can be found in [Resources and Actions](/development/guide/resources-actions). Resourcer is built into [NocoBase Application](/api/server/application#resourcer), you can access it through `app.resourcer`. - -## Constructor - -To create resourcer instances. - -**Signature** - -* `constructor(options: ResourcerOptions)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `prefix` | `string` | - | Route prefix | -| `accessors` | `Object` | _The following values of members_ | Name identifier of the default operation method | -| `accessors.list` | `string` | `'list'` | Name identifier of the list operation method | -| `accessors.get` | `string` | `'get'` | Name identifier of the get operation method | -| `accessors.create` | `string` | `'create'` | Name identifier of the create operation method | -| `accessors.update` | `string` | `'update'` | Name identifier of the update operation method | -| `accessors.delete` | `string` | `'destroy'` | Name identifier of the delete operation method | -| `accessors.add` | `string` | `'add'` | Name identifier of the add association operation method | -| `accessors.remove` | `string` | `'remove'` | Name identifier of the remove association operation method | -| `accessors.set` | `string` | `'set'` | Name identifier of the global set association operation method | - -**Example** - -Pass in through the `resourcer` option when creating app: - -```ts -const app = new Application({ - // Correspond to the configuration item of the default resourcer instance - resourcer: { - prefix: process.env.API_BASE_PATH - } -}); -``` - -## Instance Methods - -### `define()` - -Define and register a resource object with the resourcer. Usually used instead of the constructor of the `Resource` class. - -**Signature** - -* `define(options: ResourceOptions): Resource` - -**Parameter** - -Refer to [Resource Constructor](/api/server/resourcer/resource#constructor) for details. - -**Example** - -```ts -app.resourcer.define({ - name: 'books', - actions: { - // Extended action - publish(ctx, next) { - ctx.body = 'ok'; - } - } -}); -``` - -### `isDefined()` - -Check whether the resource with the corresponding name has been registered. - -**Signature** - -* `isDefined(name: string): boolean` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the resource | - -**Example** - -```ts -app.resourcer.isDefined('books'); // true -``` - -### `registerAction()` - -Register an action with the resourcer. The action is accessible to a specified resource, or all resources if no resource name is specified. - -**Signature** - -* `registerAction(name: string, handler: HandlerType): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the action | -| `handler` | `HandlerType` | - | Handler of the action | - -A value of `name` starting with `:` means that the action is only accessible to `` rescource, otherwise it is considered as a global action. - -**Example** - -```ts -// All resources can take the upload action after registration -app.resourcer.registerAction('upload', async (ctx, next) => { - ctx.body = 'ok'; -}); - -// Register the upload action only for attachments resource -app.resourcer.registerAction('attachments:upload', async (ctx, next) => { - ctx.body = 'ok'; -}); -``` - -### `registerActions()` - -Register a set of actions with the resourcer. - -**Signature** - -* `registerActions(actions: { [name: string]: HandlerType }): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `actions` | `{ [name: string]: HandlerType }` | - | Set of actions | - -**Example** - -```ts -app.resourcer.registerActions({ - upload: async (ctx, next) => { - ctx.body = 'ok'; - }, - 'attachments:upload': async (ctx, next) => { - ctx.body = 'ok'; - } -}); -``` - -### `getResource()` - -Get the resource object with the corresponding name. - -**Signature** - -* `getResource(name: string): Resource` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the resource | - -**Example** - -```ts -app.resourcer.getResource('books'); -``` - -### `getAction()` - -Get the action handler function with the corresponding name. - -**Signature** - -* `getAction(name: string): HandlerType` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the action | - -A value of `name` starting with `:` means that the action is only accessible to `` rescource, otherwise it is considered as a global action. - -**Example** - -```ts -app.resourcer.getAction('upload'); -app.resourcer.getAction('attachments:upload'); -``` - -### `use()` - -Register a middleware in the form of Koa; the middleware forms a queue which is executed before the action handlers of all resources. - -**Signature** - -* `use(middleware: Middleware | Middleware[]): void` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `middleware` | `Middleware \| Middleware[]` | - | Middleware | - -**Example** - -```ts -app.resourcer.use(async (ctx, next) => { - console.log(ctx.req.url); - await next(); -}); -``` - -### `middleware()` - -Generate a Koa-compatible middleware for injecting routing processing of resources into the application. - -**Signature** - -* `middleware(options: KoaMiddlewareOptions): KoaMiddleware` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.prefix?` | `string` | `''` | Route prefix | -| `options.accessors?` | `Object` | `{}` | Name mapping for common methods, with the same parameter structure as `accessors` of the constructor | - -**Example** - -```ts -const koa = new Koa(); - -const resourcer = new Resourcer(); - -// Generate Koa-compatible middleware -koa.use(resourcer.middleware()); -``` diff --git a/docs/en-US/api/resourcer/middleware.md b/docs/en-US/api/resourcer/middleware.md deleted file mode 100644 index 6401cfcf8..000000000 --- a/docs/en-US/api/resourcer/middleware.md +++ /dev/null @@ -1,168 +0,0 @@ -# Middleware - -It is similar to the middleware of Koa, but with more enhanced features for easy extensions. - -The defined middleware can be inserted for use in multiple places, such as the resourcer, and it is up to the developer for when to invoke it. - -## Constructor - -**Signature** - -* `constructor(options: Function)` -* `constructor(options: MiddlewareOptions)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options` | `Function` | - | Handler function of middlware | -| `options` | `MiddlewareOptions ` | - | Configuration items of middlware | -| `options.only` | `string[]` | - | Only the specified actions are allowed | -| `options.except` | `string[]` | - | The specified actions are excluded | -| `options.handler` | `Function` | - | Handler function | - -**Example** - -Simple definition: - -```ts -const middleware = new Middleware((ctx, next) => { - await next(); -}); -``` - -Definition with relevant parameters: - -```ts -const middleware = new Middleware({ - only: ['create', 'update'], - async handler(ctx, next) { - await next(); - }, -}); -``` - -## Instance Methods - -### `getHandler()` - -Get the orchestrated handler functions. - -**Example** - -The following middleware will output `1` and then `2` when requested. - -```ts -const middleware = new Middleware((ctx, next) => { - console.log(1); - await next(); -}); - -middleware.use(async (ctx, next) => { - console.log(2); - await next(); -}); - -app.resourcer.use(middleware.getHandler()); -``` - -### `use()` - -Add a middleware function to the current middleware. Used to provide extension points for the middleware. See `getHandler()` for the examples. - -**Signature** - -* `use(middleware: Function)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `middleware` | `Function` | - | Handler function of the middleware | - -### `disuse()` - -Remove the middleware functions that have been added to the current middleware. - -**Signature** - -* `disuse(middleware: Function)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `middleware` | `Function` | - | Handler function of the middleware | - -**Example** - -The following example will only output `1` when requested, the output of `2` in fn1 will not be executed. - -```ts -const middleware = new Middleware((ctx, next) => { - console.log(1); - await next(); -}); - -async function fn1(ctx, next) { - console.log(2); - await next(); -} - -middleware.use(fn1); - -app.resourcer.use(middleware.getHandler()); - -middleware.disuse(fn1); -``` - -### `canAccess()` - -Check whether the current middleware is to be invoked for a specific action, it is usually handled by the resourcer internally. - -**Signature** - -* `canAccess(name: string): boolean` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the action | - -## Other Exports - -### `branch()` - -Create a branch middleware for branching in the middleware. - -**Signature** - -* `branch(map: { [key: string]: Function }, reducer: Function, options): Function` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `map` | `{ [key: string]: Function }` | - | Mapping table of the branch handler function, key names are given by subsequent calculation functions when called | -| `reducer` | `(ctx) => string` | - | Calculation function, it is used to calculate the key name of the branch based on the context | -| `options?` | `Object` | - | Configuration items of the branch | -| `options.keyNotFound?` | `Function` | `ctx.throw(404)` | Handler function when key name is not found | -| `options.handlerNotSet?` | `Function` | `ctx.throw(404)` | The function when no handler function is defined | - -**Example** - -When authenticating user, determine what to do next according to the value of the `authenticator` parameter in the query section of the request URL. - -```ts -app.resourcer.use(branch({ - 'password': async (ctx, next) => { - // ... - }, - 'sms': async (ctx, next) => { - // ... - }, -}, (ctx) => { - return ctx.action.params.authenticator ?? 'password'; -})); -``` diff --git a/docs/en-US/api/resourcer/resource.md b/docs/en-US/api/resourcer/resource.md deleted file mode 100644 index c06b09992..000000000 --- a/docs/en-US/api/resourcer/resource.md +++ /dev/null @@ -1,114 +0,0 @@ -# Resource - -Resource is used to define resource instance. Resource instances managed by resourcer can be accessed through HTTP requests. - -## Constructor - -To create resource instance. Normally it is not used directly, but replaced by the call of the `define()` interface of resourcer. - -**Signature** - -* `constructor(options: ResourceOptions, resourcer: Resourcer)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.name` | `string` | - | Name of the resource, corresponding to the resource address in the route of the URL | -| `options.type` | `string` | `'single'` | Type of the resource, options are `'single'`, `'hasOne'`, `'hasMany'`, `'belongsTo'`, `'belongsToMany'` | -| `options.actions` | `Object` | - | List of actions that can be taken on the resource, see the example for details | -| `options.middlewares` | `MiddlewareType \| MiddlewareType[]` | - | List of middlewares for any operational access to the resource that is defining,see the example for details | -| `options.only` | `ActionName[]` | `[]` | Whitelist for global actions, only actions contained in the array (if `length > 0`) can be accessed | -| `options.except` | `ActionName[]` | `[]` | Blacklist for global actions, all actions except those contained in the array (if `length > 0`) can be accessed | -| `resourcer` | `Resourcer` | - | The resourcer instance | - -**Example** - -```ts -app.resourcer.define({ - name: 'books', - actions: { - // Extended action - publish(ctx, next) { - ctx.body = 'ok'; - } - }, - middleware: [ - // Extended middleware - async (ctx, next) => { - await next(); - } - ] -}); -``` - -## Instance Members - -### `options` - -Configuration items for the current resource. - -### `resourcer` - -The resourcer instance to which the resource belongs. - -### `middlewares` - -The registered middlewares. - -### `actions` - -The registered mapping table of actions. - -### `except` - -Actions that are excluded. - -## Instance Methods - -### `getName()` - -Get the name of the current resource. - -**Signature** - -* `getName(): string` - -**Example** - -```ts -const resource = app.resourcer.define({ - name: 'books' -}); - -resource.getName(); // 'books' -``` - -### `getAction()` - -Get action with the corresponding name. - -**Signature** - -* `getAction(name: string): Action` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `name` | `string` | - | Name of the action | - -**Example** - -```ts -const resource = app.resourcer.define({ - name: 'books', - actions: { - publish(ctx, next) { - ctx.body = 'ok'; - } - } -}); - -resource.getAction('publish'); // [Function: publish] -``` diff --git a/docs/en-US/api/sdk.md b/docs/en-US/api/sdk.md deleted file mode 100644 index 3ef4bfaed..000000000 --- a/docs/en-US/api/sdk.md +++ /dev/null @@ -1,281 +0,0 @@ -# @nocobase/sdk - -## APIClient - -```ts -class APIClient { - // axios instance - axios: AxiosInstance; - // Constructor - constructor(instance?: AxiosInstance | AxiosRequestConfig); - // Request from client, support AxiosRequestConfig and ResourceActionOptions - request, D = any>(config: AxiosRequestConfig | ResourceActionOptions): Promise; - // Get resource - resource(name: string, of?: any): R; -} -``` - -Instance initialization: - -```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, but you can also customize the Storage, for example: - -```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 record token -api.auth.signIn({ email, password }); -// Sign out and remove token -api.auth.signOut(); -// Set token -api.auth.setToken('123'); -// Set role (When multiple roles are needed) -api.auth.setRole('admin'); -// Set locale (When multiple languages are needed) -api.auth.setLocale('zh-CN'); -``` - -Auth customization: - -```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/server/app-manager.md b/docs/en-US/api/server/app-manager.md deleted file mode 100644 index 76a11afbe..000000000 --- a/docs/en-US/api/server/app-manager.md +++ /dev/null @@ -1 +0,0 @@ -# AppManager \ No newline at end of file diff --git a/docs/en-US/api/server/application.md b/docs/en-US/api/server/application.md deleted file mode 100644 index 8ad154f09..000000000 --- a/docs/en-US/api/server/application.md +++ /dev/null @@ -1,303 +0,0 @@ -# Application - -## Overview - -### Web Service - -NocoBase Application is a web framework implemented based on [Koa](https://koajs.com/), compatible with Koa API. - -```javascript -// index.js -const { Application } = require('@nocobase/server'); - -// Create App instance and configure the database -const app = new Application({ - database: { - dialect: 'sqlite', - storage: ':memory:', - } -}); - -// Register middleware, response to requests -app.use(async ctx => { - ctx.body = 'Hello World'; -}); - -// Run in the CLI mode -app.runAsCLI(); -``` - -After running `node index.js start` in CLI to start service, use `curl` to request service. - -```bash -$> curl localhost:3000 -Hello World -``` - -### CLI Tool - -NocoBase Application has a built-in `cli commander`, which can be run as CLI tool. - -```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() -``` - -Run in CLI: - -```bash -$> node cmd.js hello -hello world -``` - -### Inject Plugin - -NocoBase Application is designed as a highly extensible framework, plugins can be written and injected to the applicationto to extend its functionality. For example, the above-mentioned web service can be replaced with a plugin. - -```javascript -const { Application, Plugin } = require('@nocobase/server'); - -// Write plugin by inheriting the Plugin class -class HelloWordPlugin extends Plugin { - load() { - this.app.use(async (ctx, next) => { - ctx.body = "Hello World"; - }) - } -} - -const app = new Application({ - database: { - dialect: 'sqlite', - storage: ':memory:', - } -}); - -// Inject plugin -app.plugin(HelloWordPlugin, { name: 'hello-world-plugin'} ); - -app.runAsCLI() -``` - -### More Examples - -Please refer to the detailed guides of [plugin development](./plugin.md). Read more [examples](https://github.com/nocobase/nocobase/blob/main/examples/index.md) of the Application class. - -## Lifecycle - -Application has three lifecycle stages depends on the running mode. - -### Install - -Use the `install` command in `cli` to invoke the installation. Generally, if needs to write new tables or data to the database before using the plugin, you have to do it during installation. Installation is also required when using NocoBase for the first time. - -* Call the `load` method to load registered plugins. -* Trigger the `beforeInstall` event. -* Call the `db.sync` method to synchronize database. -* Call the `pm.install` method to execute the `install` methods of registered plugins. -* Write the version of `nocobase`. -* Trigger the `afterInstall` event. -* Call the `stop` method to end installation. - -### Start - -Use the `start` command in `cli` to start NocoBase Web service. - -* Call the `load` method to load registered plugins. -* Call the `start` medthod: - * Trigger the `beforeStart` event - * Start port listening - * Trigger the `afterStart` event - -### Upgrade - -Use the `upgrade` command in `cli` to upgrade NocoBase Web service when needed. - -* Call the `load` method to load registered plugins. -* Trigger the `beforeUpgrade` event. -* Call the `db.migrator.up` method to migrate database. -* Call the `db.sync` method to synchronize database. -* Call the `version.update` method to update the version of `nocobase`. -* Trigger the `afterUpgrade` event. -* Call the `stop` medthod to end upgrade. - -## Constructor - -### `constructor()` - -Create an application instance. - -**Signature** - -* `constructor(options: ApplicationOptions)` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.database` | `IDatabaseOptions` or `Database` | `{}` | Database configuration | -| `options.resourcer` | `ResourcerOptions` | `{}` | Resource route configuration | -| `options.logger` | `AppLoggerOptions` | `{}` | Log | -| `options.cors` | [`CorsOptions`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/koa__cors/index.d.ts#L24) | `{}` | Cross-domain configuration, refer to [@koa/cors](https://npmjs.com/package/@koa/cors) | -| `options.dataWrapping` | `boolean` | `true` | Whether or not to wrap the response data, `true` will wrap the usual `ctx.body` into a `{ data, meta }` structure | -| `options.registerActions` | `boolean` | `true` | Whether or not to register the default [actions](#) | -| `options.i18n` | `I18nOptions` | `{}` | Internationalization configuration, refer to [i18next](https://www.i18next.com/overview/api) | -| `options.plugins` | `PluginConfiguration[]` | `[]` | Configuration of the plugins enabled by default | - -Type - -```ts -interface ApplicationOptions { - -} -``` - -## Instance Members - -### `cli` - -CLI tool instance, refer to the npm package [Commander](https://www.npmjs.com/package/commander)。 - -### `db` - -Database instance, refer to [Database](/api/database) for the related API. - -### `resourcer` - -Resource route management instance created automatically during app initialization, refer to [Resourcer](/api/resourcer) for the related API. - -### `acl` - -ACL instance, refer to [ACL](/api/acl) for the related API. - -### `logger` - -Winston instance, refer to [Winston](https://github.com/winstonjs/winston#table-of-contents) for the related API. - -### `i18n` - -I18next instance, refer to [I18next](https://www.i18next.com/overview/api) for the related API. - -### `pm` - -Plugin manager instance, refer to [PluginManager](./plugin-manager) for the related API. - -### `version` - -App version instance, refer to [ApplicationVersion](./application-version) for the related API. - -### `middleware` - -Built-in middleware includes: - -- logger -- i18next -- bodyParser -- cors -- dataWrapping -- db2resource -- restApiMiddleware - -### `context` - -Context inherited from koa, accessible via `app.context`, is used to inject context-accessible content to each request. Refer to [Koa Context](https://koajs.com/#app-context). - -NocoBase injects the following members to context by default, which can be used directly in the request handler function: - -| Variable Name | Type | Description | -| --- | --- | --- | -| `ctx.app` | `Application` | Application instance | -| `ctx.db` | `Database` | Database instance | -| `ctx.resourcer` | `Resourcer` | Resource route manager instance | -| `ctx.action` | `Action` | Resource action related object instance | -| `ctx.logger` | `Winston` | Log instance | -| `ctx.i18n` | `I18n` | Internationlization instance | -| `ctx.t` | `i18n.t` | Shortcut of internationalized translation function | -| `ctx.getBearerToken()` | `Function` | Get the bearer token in the header of request | - -## Instance Methods - -### `use()` - -Register middleware, compatible with all [Koa plugins](https://www.npmjs.com/search?q=koa). - -### `on()` - -Subscribe to application-level events, mainly are related to lifecycle. It is equivalent to `eventEmitter.on()`. Refer to [events](#events) for all subscribable events. - -### `command()` - -Customize command. - -### `findCommand()` - -Find defined command. - -### `runAsCLI()` - -Run as CLI. - -### `load()` - -Load application configuration. - -**Signature** - -* `async load(): Promise` - -### `reload()` - -Reload application configuration. - -### `install()` - -Initialize the installation of the application, meanwhile, install the plugin. - -### `upgrade()` - -Upgrade application, meanwhile, upgrade plugin. - -### `start()` - -Start application, listening will also be started if the listening port is configured, then the application can accept HTTP requests. - -**Signature** - -* `async start(options: StartOptions): Promise` - -**Parameter** - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `options.listen?` | `ListenOptions` | `{}` | HTTP Listening parameters object | -| `options.listen.port?` | `number` | 13000 | Port | -| `options.listen.host?` | `string` | `'localhost'` | Domain name | - -### `stop()` - -Stop application. This method will deconnect database, close HTTP port, but will not delete data. - -### `destroy()` - -Delete application. This methos will delete the corresponding database of application. - -## Events - -### `'beforeLoad'` / `'afterLoad'` -### `'beforeInstall'` / `'afterInstall'` -### `'beforeUpgrade'` / `'afterUpgrade'` -### `'beforeStart'` / `'afterStart'` -### `'beforeStop'` / `'afterStop'` -### `'beforeDestroy'` / `'afterDestroy'` diff --git a/docs/en-US/api/server/i18n.md b/docs/en-US/api/server/i18n.md deleted file mode 100644 index 05b5b6193..000000000 --- a/docs/en-US/api/server/i18n.md +++ /dev/null @@ -1 +0,0 @@ -# I18n diff --git a/docs/en-US/api/server/plugin-manager.md b/docs/en-US/api/server/plugin-manager.md deleted file mode 100644 index e161d37f0..000000000 --- a/docs/en-US/api/server/plugin-manager.md +++ /dev/null @@ -1,59 +0,0 @@ -# PluginManager - -应用插件管理器的实例,由应用自动创建,可以通过 `app.pm` 访问。 - -## 实例方法 - -### `create()` - -在本地创建一个插件脚手架 - -**签名** - -```ts -create(name, options): void; -``` - -### `addStatic()` - -**签名** - -```ts -addStatic(plugin: any, options?: PluginOptions): Plugin; -``` - -**示例** - -```ts -pm.addStatic('nocobase'); -``` - -### `add()` - -**签名** - -```ts -async add(plugin: any, options?: PluginOptions): Promise; -async add(plugin: string[], options?: PluginOptions): Promise; -``` - -**示例** - -```ts -await pm.add(['test'], { - builtIn: true, - enabled: true, -}); -``` - -### `get()` - -获取插件实例 - -### `enable()` - -### `disable()` - -### `remove()` - -### `upgrade()` diff --git a/docs/en-US/api/server/plugin.md b/docs/en-US/api/server/plugin.md deleted file mode 100644 index 5b41ca603..000000000 --- a/docs/en-US/api/server/plugin.md +++ /dev/null @@ -1,47 +0,0 @@ -# Plugin - -## Overview - -Plugins in NocoBase are in the form of `Class`. Custom plugins need to inherit the `Plugin` class. - -```typescript -import { Plugin } from '@nocobase/server'; - -class MyPlugin extends Plugin { - // ... -} - -app.plugin(MyPlugin, { name: 'my-plugin' }); -``` - -## Plugin Lifecycle - -Each plugin contains lifecycle methods, you can override these methods in order to execute them at certain stages during runtime. Lifecycle methods will be called by `Application` at certain stages, refer to [`Application` LifeCycle](./application.md). - -### `beforeLoad()` - -To implement the logic before plugin is loaded, such as event or class registration. The core interface can be accessed here, while other plugins are not available. - -### `load()` - -To implement the logic to load plugin, configurations and so on. Other plugin instances can be called in `load`, but not in `beforeLoad`. - -### `install()` - -To implement the logic to install plugin, such as data initialization. - -### `afterAdd()` - -To implement the logic after the add/addStatic of plugin. - -### `afterEnable()` - -To implement the logic after plugin is enabled. - -### `afterDisable()` - -To implement the logic after plugin is disabled. - -### `remove()` - -To implement the logic to remove plugin. diff --git a/docs/en-US/components.md b/docs/en-US/components.md deleted file mode 100644 index 0eac4eb52..000000000 --- a/docs/en-US/components.md +++ /dev/null @@ -1 +0,0 @@ -# Components diff --git a/docs/en-US/development/app-ds.md b/docs/en-US/development/app-ds.md deleted file mode 100644 index b75ecf808..000000000 --- a/docs/en-US/development/app-ds.md +++ /dev/null @@ -1,64 +0,0 @@ -# App directory structure - -Either [Git source](/welcome/getting-started/installation/git-clone) or [create-nocobase-app](/welcome/getting-started/installation/create-nocobase-app), the directory structure of the created NocoBase application is the same, with the following structure. - -```bash -├── my-nocobase-app - ├── packages # NocoBase uses the Monorepo approach to manage code, dividing different modules into different packages - ├── app - ├── client # client-side module - ├── server # server-side modules - ├─ plugins # plugin directory - ├── storage # for database files, attachments, cache, etc. - ├─ db - ├── .env # environment variables - ├── .buildrc.ts # package configuration for packages, supports cjs, esm and umd packages. - ├── jest.config.js - ├── jest.setup.ts - ├── lerna.json - ├── package.json - ├── tsconfig.jest.json - ├─ tsconfig.json - ├── tsconfig.server.json -``` - -## Packages directory - -```bash -├─ packages - ├─ app - ├── client - ├─ public - ├─ src - ├─ pages - ├─ index.tsx - ├─ .umirc.ts - ├─ package.json - ├─ server - ├─ src - ├─ config - ├─ index.ts - ├─ package.json - ├─ /plugins - ├─ my-plugin - ├─ src - ├─ package.json -``` - -NocoBase uses the Monorepo approach to manage code, dividing different modules into different packages. - -- `app/client` is the client module of the application, built on [umi](https://umijs.org/zh-CN). -- `app/server` is the server-side module of the application. -- The `plugins/*` directory can hold various plugins. - -## storages directory - -Store database files, attachments, cache, etc. - -## .env files - -Environment variables. - -## .buildrc.ts file - -Package configuration for packages, supporting cjs, esm and umd formats. diff --git a/docs/en-US/development/client/i18n.md b/docs/en-US/development/client/i18n.md deleted file mode 100644 index 49136d010..000000000 --- a/docs/en-US/development/client/i18n.md +++ /dev/null @@ -1,140 +0,0 @@ -# Internationalization - -Client-side internationalization for multiple languages is implemented based on the npm package [react-i18next](https://npmjs.com/package/react-i18next), which provides a wrapper for the `` component at the top level of the application, allowing the relevant methods to be used directly at any location. - -Adding language packages: - -```tsx | pure -import { i18n } from '@nocobase/client'; - -i18n.addResources('zh-CN', 'test', { - Hello: '你好', - World: '世界', -}); -``` - -Note: Here the second parameter filled in `'test'` is the language namespace, usually the plugin itself defines the language resources should create a specific namespace according to their own plugin package name, in order to avoid conflicts with other language resources. The default namespace in NocoBase is `'client'` and most common and basic language translations are placed in this namespace. When the required language is not provided, it can be defined by extension in the plugin's own namespace. - -To call the translation function in the component: - -```tsx | pure -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -export default function MyComponent() { - // Use the previously defined namespace - const { t } = useTranslation('test'); - - return ( -
-

{t('World')}

-
- ); -} -``` - -The template method `'{{t()}}'` can be used directly in the SchemaComponent component, and the translation functions in the template will automatically be executed. - -```tsx | pure -import React from 'react'; -import { SchemaComponent } from '@nocobase/client'; - -export default function MySchemaComponent() { - return ( - - ); -} -``` - -In some special cases where it is also necessary to define multilingualism as a template, the NocoBase built-in `compile()` method can be used to compile to multilingual results. - -```tsx | pure -import React from 'react'; -import { useCompile } from '@nocobase/client'; - -const title = '{{t("Hello", { ns: "test" })}}'; - -export default function MyComponent() { - const { compile } = useCompile(); - - return ( -
{compile(title)}
- ); -} -``` - -## Suggested configuration - -With English text as the key and translation as the value, the benefit of this, even if multiple languages are missing, it will be displayed in English and will not cause reading barriers, e.g. - -```ts -i18n.addResources('zh-CN', 'my-plugin', { - 'Show dialog': '显示对话框', - 'Hide dialog': '隐藏对话框' -}); -``` - -To make it easier to manage multilingual files, it is recommended to create a `locale` folder in the plugin and place all the corresponding language files in it for easy management. - -```bash -|- /my-plugin - |- /src - |- /client - |- locale # Multilingual folder - |- zh-CN.ts - |- en-US.ts -``` - -## Example - -### Client-side components with multiple languages - -For example, the order status component, with different text displays depending on the value. - -```tsx | pure -import React from 'react'; -import { Select } from 'antd'; -import { i18n } from '@nocobase/client'; -import { useTranslation } from 'react-i18next'; - -i18n.addResources('zh-CN', 'sample-shop-i18n', { - Pending: '已下单', - Paid: '已支付', - Delivered: '已发货', - Received: '已签收' -}); - -const ORDER_STATUS_LIST = [ - { value: -1, label: 'Canceled (untranslated)' }, - { value: 0, label: 'Pending' }, - { value: 1, label: 'Paid' }, - { value: 2, label: 'Delivered' }, - { value: 3, label: 'Received' }, -] - -function OrderStatusSelect() { - const { t } = useTranslation('sample-shop-i18n'); - - return ( - - ); -} - -export default function () { - return ( - - ); -} -``` diff --git a/docs/en-US/development/client/index.md b/docs/en-US/development/client/index.md deleted file mode 100644 index 9cfbc00dd..000000000 --- a/docs/en-US/development/client/index.md +++ /dev/null @@ -1,72 +0,0 @@ -# Overview - -Most of the extensions for the NocoBase client are provided as Providers. - -## Built-in Providers - -- APIClientProvider -- I18nextProvider -- AntdConfigProvider -- SystemSettingsProvider -- PluginManagerProvider -- SchemaComponentProvider -- BlockSchemaComponentProvider -- AntdSchemaComponentProvider -- DocumentTitleProvider -- ACLProvider - -## Registration of client-side Provider modules - -Static Providers are registered with app.use() and dynamic Providers are adapted with dynamicImport. - -```tsx | pure -import React from 'react'; -import { Application } from '@nocobase/client'; - -const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`... /plugins/${name}`); - }, -}); - -// When visiting the /hello page, display Hello world! -const HelloProvider = React.memo((props) => { - const location = useLocation(); - if (location.pathname === '/hello') { - return
Hello world!
- } - return <>{props.children} -}); -HelloProvider.displayName = 'HelloProvider' - -app.use(HelloProvider); -``` - -## Client-side of plugins - -Directory structure of the client-side of an empty plugin is as follows - -```bash -|- /my-plugin - |- /src - |- /client - |- index.tsx - |- client.d.ts - |- client.js -``` - -``client/index.tsx`` reads as follows. - -```tsx | pure -import React from 'react'; - -// This is an empty Provider, only children are passed, no custom Context is provided -export default React.memo((props) => { - return <>{props.children}; -}); -``` - -After the plugin pm.add, it writes the `my-plugin.ts` file to the `packages/app/client/src/plugins` directory. diff --git a/docs/en-US/development/client/m.svg b/docs/en-US/development/client/m.svg deleted file mode 100644 index 932d4de2b..000000000 --- a/docs/en-US/development/client/m.svg +++ /dev/null @@ -1 +0,0 @@ -
Pylons Application
restApi
action
acl
parseToken
db2resource
dataWrapping
Before
After
Request
Response
before
after
resourcer.use
checkRole
app.use
\ No newline at end of file diff --git a/docs/en-US/development/client/plugin-settings.md b/docs/en-US/development/client/plugin-settings.md deleted file mode 100644 index f938bbec3..000000000 --- a/docs/en-US/development/client/plugin-settings.md +++ /dev/null @@ -1,75 +0,0 @@ -# Plugin Settings Manager - - - -## Example - -### Basic Usage - -```tsx | pure -import { Plugin } from '@nocobase/client'; -import React from 'react'; - -const HelloSettingPage = () =>
Hello Setting page
; - -export class HelloPlugin extends Plugin { - async load() { - this.app.pluginSettingsManager.add('hello', { - title: 'Hello', // menu title and page title - icon: 'ApiOutlined', // menu icon - Component: HelloSettingPage, - }) - } -} -``` - -### Multiple Level Routes - -```tsx | pure -import { Outlet } from 'react-router-dom' -const SettingPageLayout = () =>
This
public part, the following is the outlet of the sub-route:
; - -class HelloPlugin extends Plugin { - async load() { - this.app.pluginSettingsManager.add('hello', { - title: 'HelloWorld', - icon: '', - Component: SettingPageLayout - }) - - this.app.pluginSettingsManager.add('hello.demo1', { - title: 'Demo1 Page', - Component: () =>
Demo1 Page Content
- }) - - this.app.pluginSettingsManager.add('hello.demo2', { - title: 'Demo2 Page', - Component: () =>
Demo2 Page Content
- }) - } -} -``` - -### Get Route Path - -If you want to get the jump link of the setting page, you can get it through the `getRoutePath` method. - -```tsx | pure -import { useApp } from '@nocobase/client' - -const app = useApp(); -app.pluginSettingsManager.getRoutePath('hello'); // /admin/settings/hello -app.pluginSettingsManager.getRoutePath('hello.demo1'); // /admin/settings/hello/demo1 -``` - -### Get Config - -If you want to get the added configuration (already filtered by permissions), you can get it through the `get` method. - -```tsx | pure -const app = useApp(); -app.pluginSettingsManager.get('hello'); // { title: 'HelloWorld', icon: '', Component: HelloSettingPage, children: [{...}] } -``` - - -See [samples/hello](https://github.com/nocobase/nocobase/blob/main/packages/plugins/%40nocobase/plugin-sample-hello/src/client/index.tsx) for full examples. diff --git a/docs/en-US/development/client/plugin-settings/settings-tab.jpg b/docs/en-US/development/client/plugin-settings/settings-tab.jpg deleted file mode 100644 index 34a8da116..000000000 Binary files a/docs/en-US/development/client/plugin-settings/settings-tab.jpg and /dev/null differ diff --git a/docs/en-US/development/client/test.md b/docs/en-US/development/client/test.md deleted file mode 100644 index a6c082f9f..000000000 --- a/docs/en-US/development/client/test.md +++ /dev/null @@ -1,40 +0,0 @@ -# Testing - -Testing is based on the [Jest](https://jestjs.io/) testing framework. Also included are common React testing libraries such as [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) - -## Example - -```tsx | pure -import { render } from '@testing-library/react'; -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { RouteSwitch } from '../RouteSwitch'; -import { RouteSwitchProvider } from '../RouteSwitchProvider'; - -const Home = () =>

Home

; -const About = () =>

About

; - -describe('route-switch', () => { - it('case 1', () => { - const App = () => { - return ( - - - - - - ); - }; - const { container } = render(); - expect(container).toMatchSnapshot(); - }); -}); -``` diff --git a/docs/en-US/development/client/ui-router.md b/docs/en-US/development/client/ui-router.md deleted file mode 100644 index ace071fb2..000000000 --- a/docs/en-US/development/client/ui-router.md +++ /dev/null @@ -1,66 +0,0 @@ -# UI Routing - -NocoBase Client's Router is based on [React Router](https://v5.reactrouter.com/web/guides/quick-start) and can be configured via `app.router` to configure ui routes with the following example. - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Link, Outlet } from 'react-router-dom'; -import { Application } from '@nocobase/client'; - -const Home = () =>

Home

; -const About = () =>

About

; - -const Layout = () => { - return
-
Home, About
- -
-} - -const app = new Application({ - router: { - type: 'memory', - initialEntries: ['/'] - } -}) - -app.router.add('root', { - element: -}) - -app.router.add('root.home', { - path: '/', - element: -}) - -app.router.add('root.about', { - path: '/about', - element: -}) - -export default app.getRootComponent(); -``` - -In a full NocoBase application, the Route can be extended in a similar way as follows. - -```tsx | pure -import { Plugin } from '@nocobase/client'; - -class MyPlugin extends Plugin { - async load() { - // add - this.app.router.add('hello', { - path: '/hello', - element:
hello
, - }) - - // remove - this.app.router.remove('hello'); - } -} -``` - -See [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page) for the full example diff --git a/docs/en-US/development/client/ui-schema-designer/acl.md b/docs/en-US/development/client/ui-schema-designer/acl.md deleted file mode 100644 index d1c6127f6..000000000 --- a/docs/en-US/development/client/ui-schema-designer/acl.md +++ /dev/null @@ -1,2 +0,0 @@ -# ACL - diff --git a/docs/en-US/development/client/ui-schema-designer/block-provider.md b/docs/en-US/development/client/ui-schema-designer/block-provider.md deleted file mode 100644 index 42fa25421..000000000 --- a/docs/en-US/development/client/ui-schema-designer/block-provider.md +++ /dev/null @@ -1,2 +0,0 @@ -# 区块 - diff --git a/docs/en-US/development/client/ui-schema-designer/collection-manager.md b/docs/en-US/development/client/ui-schema-designer/collection-manager.md deleted file mode 100644 index e4154eb14..000000000 --- a/docs/en-US/development/client/ui-schema-designer/collection-manager.md +++ /dev/null @@ -1,127 +0,0 @@ -# Collection Manager - -```tsx -import React from 'react'; -import { observer, ISchema, useForm } from '@formily/react'; -import { - SchemaComponent, - SchemaComponentProvider, - Form, - Action, - CollectionProvider, - CollectionField, -} from '@nocobase/client'; -import { FormItem, Input } from '@formily/antd-v5'; - -export default observer(() => { - const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title1', - interface: 'input', - uiSchema: { - title: 'Title1', - type: 'string', - 'x-component': 'Input', - required: true, - description: 'description1', - } as ISchema, - }, - { - type: 'string', - name: 'title2', - interface: 'input', - uiSchema: { - title: 'Title2', - type: 'string', - 'x-component': 'Input', - description: 'description', - default: 'ttt', - }, - }, - { - type: 'string', - name: 'title3', - }, - ], - }; - - const schema: ISchema = { - type: 'object', - properties: { - form1: { - type: 'void', - 'x-component': 'Form', - properties: { - // 字段 title1 直接使用全局提供的 uiSchema - title1: { - 'x-component': 'CollectionField', - 'x-decorator': 'FormItem', - default: '111', - }, - // 等同于 - // title1: { - // type: 'string', - // title: 'Title', - // required: true, - // 'x-component': 'Input', - // 'x-decorator': 'FormItem', - // }, - title2: { - 'x-component': 'CollectionField', - 'x-decorator': 'FormItem', - title: 'Title4', // 覆盖全局已定义的 Title2 - required: true, // 扩展的配置参数 - description: 'description4', - }, - // 等同于 - // title2: { - // type: 'string', - // title: 'Title22', - // required: true, - // 'x-component': 'Input', - // 'x-decorator': 'FormItem', - // }, - // 字段 title3 没有提供 uiSchema,自行处理 - title3: { - 'x-component': 'Input', - 'x-decorator': 'FormItem', - title: 'Title3', - required: true, - }, - action1: { - // type: 'void', - 'x-component': 'Action', - title: 'Submit', - 'x-component-props': { - type: 'primary', - useAction: '{{ useSubmit }}', - }, - }, - }, - }, - }, - }; - - const useSubmit = () => { - const form = useForm(); - return { - async run() { - form.submit(() => { - console.log(form.values); - }); - }, - }; - }; - - return ( - - - - - - ); -}); -``` \ No newline at end of file diff --git a/docs/en-US/development/client/ui-schema-designer/component-library.md b/docs/en-US/development/client/ui-schema-designer/component-library.md deleted file mode 100644 index 223835882..000000000 --- a/docs/en-US/development/client/ui-schema-designer/component-library.md +++ /dev/null @@ -1,82 +0,0 @@ -# Schema component library - -## Wrapper Components - -- BlockItem -- FormItem -- CardItem - -## Layout - -- Page -- Grid -- Tabs -- Space - -## Field components - -The field components are generally not used alone, but in the data presentation component - -- CollectionField: universal component -- Cascader -- Checkbox -- ColorSelect -- DatePicker -- Filter -- Formula -- IconPicker -- Input -- InputNumber -- Markdown -- Password -- Percent -- Radio -- RecordPicker -- RichText -- Select -- TimePicker -- TreeSelect -- Upload - -## Data presentation component - -Need to be used with the field component - -- Calendar -- Form -- Kanban -- Table -- TableV2 - -## Action (onClick event-based component) - -- Action -- Action.Drawer -- Action.Modal -- ActionBar:For action layout -- Menu - -## Other - -- G2plot -- Markdown.Void - -## Usage scenarios for `x-designer` and `x-initializer` - -`x-designer` takes effect when `x-decorator` or `x-component` is a component of - -- BlockItem -- CardItem -- FormItem -- Table.Column -- Tabs.TabPane - -`x-initializer` takes effect when `x-decorator` or `x-component` is a component of - -- ActionBar -- BlockItem -- CardItem -- FormItem -- Grid -- Table -- Tabs diff --git a/docs/en-US/development/client/ui-schema-designer/demo1.tsx b/docs/en-US/development/client/ui-schema-designer/demo1.tsx deleted file mode 100644 index 0b6b08ce4..000000000 --- a/docs/en-US/development/client/ui-schema-designer/demo1.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { ArrayField } from '@formily/core'; -import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; -import { Table, TableColumnType } from 'antd'; -import React from 'react'; - -const ArrayTable = observer( - (props: any) => { - const { rowKey } = props; - const field = useField(); - const schema = useFieldSchema(); - const columnSchemas = schema.reduceProperties((buf, s) => { - if (s['x-component'] === 'ArrayTable.Column') { - buf.push(s); - } - return buf; - }, []); - - const columns = columnSchemas.map((s) => { - return { - render: (value, record) => { - return ; - }, - } as TableColumnType; - }); - - return ; - }, - { displayName: 'ArrayTable' }, -); - -const Value = connect((props) => { - return
  • value: {props.value}
  • ; -}); - -const schema: ISchema = { - type: 'object', - properties: { - objArr: { - type: 'array', - default: [ - { __path: '0', id: 1, value: 't1' }, - { - __path: '1', - id: 2, - value: 't2', - children: [ - { - __path: '1.children.0', - id: 5, - value: 't5', - parentId: 2, - }, - ], - }, - { - __path: '2', - id: 3, - value: 't3', - children: [ - { - __path: '2.children.0', - id: 4, - value: 't4', - parentId: 3, - children: [ - { - __path: '2.children.0.children.0', - id: 6, - value: 't6', - parentId: 4, - }, - { - __path: '2.children.0.children.1', - id: 7, - value: 't7', - parentId: 4, - }, - ], - }, - ], - }, - ], - 'x-component': 'ArrayTable', - 'x-component-props': { - rowKey: 'id', - }, - properties: { - c1: { - type: 'void', - 'x-component': 'ArrayTable.Column', - properties: { - value: { - type: 'string', - 'x-component': 'Value', - }, - }, - }, - }, - }, - }, -}; - -export default () => { - return ( - - - - ); -}; diff --git a/docs/en-US/development/client/ui-schema-designer/designable.md b/docs/en-US/development/client/ui-schema-designer/designable.md deleted file mode 100644 index f725ecd79..000000000 --- a/docs/en-US/development/client/ui-schema-designer/designable.md +++ /dev/null @@ -1,349 +0,0 @@ -# Schema design capabilities - -The design capabilities of Schema are mainly - -- Neighborhood insertion, which can be used to - - Insertion of new schema nodes - - Drag-and-drop movement of existing schema nodes -- schema parameter modification - -The core designer APIs and parameters are - -- Designer API: `createDesignable()` & `useDesignable()` -- Schema parameters: `x-designer`, used to adapt the designer component - -## Designer API - -### createDesignable() - -```ts -import { Schema } from '@nocobase/client'; - -const current = new Schema({ - type: 'void', - 'x-component': 'div', -}); - -const { - designable, // whether it is configurable - remove, - insertAdjacent, // insert at a position, four positions: beforeBegin, afterBegin, beforeEnd, afterEnd - insertBeforeBegin, // insert in front of the current node - insertAfterBegin, // insert in front of the first child node of the current node - insertBeforeEnd, // after the last child of the current node - insertAfterEnd, // after the current node -} = createDesignable({ - current, -}); - -const newSchema = { - type: 'void', - name: 'hello', - 'x-component': 'Hello', -}; - -insertAfterBegin(newSchema); - -console.log(current.toJSON()); -{ - type: 'void', - 'x-component': 'div', - properties: { - hello: { - type: 'void', - 'x-component': 'Hello', - }, - }, -} -``` - -### useDesignable() - -React Hook scenarios can also use `useDesignable()` to get the API of the current schema component designer - -```ts -const { - designable, // whether it is configurable - remove, - insertAdjacent, // insert at a position, four positions: beforeBegin, afterBegin, beforeEnd, afterEnd - insertBeforeBegin, // insert in front of the current node - insertAfterBegin, // insert in front of the first child node of the current node - insertBeforeEnd, // after the last child of the current node - insertAfterEnd, // after the current node -} = useDesignable(); - -const schema = { - name: uid(), - 'x-component': 'Hello', -}; - -// Insert in front of the current node -insertBeforeBegin(schema); -// Equivalent to -insertAdjacent('beforeBegin', schema); - -// insert in front of the first child of the current node -insertAfterBegin(schema); -// Equivalent to -insertAdjacent('afterBegin', schema); - -// after the last child of the current node -insertBeforeEnd(schema); -// Equivalent to -insertAdjacent('beforeEnd', schema); - -// After the current node -insertAfterEnd(schema); -// Equivalent to -insertAdjacent('afterEnd', schema); -``` -## Neighborhood insertion - -Similar to the DOM's [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) concept, Schema also provides the `insertAdjacent()` method for solving the insertion of adjacent positions. - -The four adjacent positions - -```ts -{ - properties: { - // beforeBegin insert before the current node - node1: { - properties: { - // afterBegin inserted before the first child of the current node - // ... - // beforeEnd after the last child of the current node - }, - }, - // afterEnd after the current node - }, -} -``` -Like HTML tags, the components of the Schema component library can be combined with each other and inserted in reasonable proximity as needed via the insertAdjacent API. - -### Inserting a new schema node - -Within a Schema component, a new node can be inserted directly into the adjacent position of the current Schema with `useDesignable()`. - -Example - -```tsx -import React from 'react'; -import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client'; -import { observer, Schema, useFieldSchema } from '@formily/react'; -import { Button, Space } from 'antd'; -import { uid } from '@formily/shared'; - -const Hello = observer((props) => { - const { insertAdjacent } = useDesignable(); - const fieldSchema = useFieldSchema(); - return ( -
    -

    {fieldSchema.name}

    - - - - - - -
    {props.children}
    -
    - ); -}, { displayName: 'Hello' }); - -const Page = observer((props) => { - return
    {props.children}
    ; -}, { displayName: 'Page' }); - -export default () => { - return ( - - - - ); -} -``` - -### Drag-and-drop movement of existing schema nodes - -Methods such as insertAdjacent can also be used to drag and drop nodes - -```tsx -import React from 'react'; -import { uid } from '@formily/shared'; -import { observer, useField, useFieldSchema } from '@formily/react'; -import { DndContext, DragEndEvent, useDraggable, useDroppable } from '@dnd-kit/core'; -import { SchemaComponent, SchemaComponentProvider, createDesignable, useDesignable } from '@nocobase/client'; - -const useDragEnd = () => { - const { refresh } = useDesignable(); - - return ({ active, over }: DragEndEvent) => { - const activeSchema = active?.data?.current?.schema; - const overSchema = over?.data?.current?.schema; - - if (!activeSchema || !overSchema) { - return; - } - - const dn = createDesignable({ - current: overSchema, - }); - - dn.on('insertAdjacent', refresh); - dn.insertBeforeBeginOrAfterEnd(activeSchema); - }; -}; - -const Page = observer((props) => { - return {props.children}; -}, { displayName: 'Page' }); - -function Draggable(props) { - const { attributes, listeners, setNodeRef, transform } = useDraggable({ - id: props.id, - data: props.data, - }); - const style = transform - ? { - transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, - } - : undefined; - - return ( - - ); -} - -function Droppable(props) { - const { isOver, setNodeRef } = useDroppable({ - id: props.id, - data: props.data, - }); - const style = { - color: isOver ? 'green' : undefined, - }; - - return ( -
    - {props.children} -
    - ); -} - -const Block = observer((props) => { - const field = useField(); - const fieldSchema = useFieldSchema(); - return ( - -
    - Block {fieldSchema.name}{' '} - - Drag - -
    -
    - ); -}, { displayName: 'Block' }); - -export default function App() { - return ( - - - - ); -} -``` -## Applications of `x-designer` - -`x-designer` is usually used only in wrapper components such as BlockItem, CardItem, FormItem, etc. - -```ts -{ - type: 'object', - properties: { - title: { - type: 'string', - title: '标题', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - 'x-designer': 'FormItem.Designer', - }, - status: { - type: 'string', - title: '状态', - 'x-decorator': 'FormItem', - 'x-component': 'Select', - 'x-designer': 'FormItem.Designer', - }, - }, -} -``` - -Note: The Schema designer provided by NocoBase is directly embedded in the interface in the form of a toolbar. When the UI configuration is activated (`designable = true`), the `x-designer` component (designer toolbar) will be displayed and the current schema component can be updated through the toolbar. - -- Drag and Drop: DndContext + DragHandler -- Inserting new nodes: SchemaInitializer -- Parameter configuration: SchemaSettings diff --git a/docs/en-US/development/client/ui-schema-designer/designable.png b/docs/en-US/development/client/ui-schema-designer/designable.png deleted file mode 100644 index 6d428a6ac..000000000 Binary files a/docs/en-US/development/client/ui-schema-designer/designable.png and /dev/null differ diff --git a/docs/en-US/development/client/ui-schema-designer/extending-schema-components.md b/docs/en-US/development/client/ui-schema-designer/extending-schema-components.md deleted file mode 100644 index 6178f50ac..000000000 --- a/docs/en-US/development/client/ui-schema-designer/extending-schema-components.md +++ /dev/null @@ -1,516 +0,0 @@ -# Extending Schema Components - -In addition to the native html tags, developers can also adapt more custom components to enrich the Schema component library. - -Common methods used to extend components are - -- [connect](https://react.formilyjs.org/api/shared/connect) to access third-party components without intrusion, generally used to adapt field components, and [mapProps](https://react.formilyjs.org/api/shared/map-props)[, mapReadPretty](https://react.formilyjs.org/api/shared/map-read-pretty) are used with -- [observer](https://react.formilyjs.org/api/shared/observer) when the component uses an observable object internally and you want the component to respond to changes to the observable object - -## The simplest extension - -Register a ready-made React component directly into it. - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const Hello = () =>

    Hello, world!

    ; - -const schema = { - type: 'void', - name: 'hello', - 'x-component': 'Hello', -}; - -export default () => { - return ( - - - - ); -}; -``` - -## Access to third-party components via connect - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Input } from 'antd' -import { connect, mapProps, mapReadPretty } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const ReadPretty = (props) => { - return
    {props.value}
    -}; - -const SingleText = connect( - Input, - mapProps((props, field) => { - return { - ...props, - suffix: '后缀', - } - }), - mapReadPretty(ReadPretty), -); - -const schema = { - type: 'object', - properties: { - t1: { - type: 'string', - default: 'hello t1', - 'x-component': 'SingleText', - }, - t2: { - type: 'string', - default: 'hello t2', - 'x-component': 'SingleText', - 'x-pattern': 'readPretty', - }, - } -}; - -export default () => { - return ( - - - - ); -}; -``` - -## Using observer response data - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Input } from 'antd'; -import { connect, observer, useForm } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const SingleText = connect(Input); - -const UsedObserver = observer((props) => { - const form = useForm(); - return
    UsedObserver: {form.values.t1}
    -}, { displayName: 'UsedObserver' }); - -const NotUsedObserver = (props) => { - const form = useForm(); - return
    NotUsedObserver: {form.values.t1}
    -}; - -const schema = { - type: 'object', - properties: { - t1: { - type: 'string', - 'x-component': 'SingleText', - }, - t2: { - type: 'string', - 'x-component': 'UsedObserver', - }, - t3: { - type: 'string', - 'x-component': 'NotUsedObserver', - }, - } -}; - -const components = { - SingleText, - UsedObserver, - NotUsedObserver -}; - -export default () => { - return ( - - - - ); -}; -``` - -## Nested Schema - -- `props.children` nesting for void and object types properties, see [nesting of void and object-type schema](#void-and-object-type-schema-nesting) for examples -- `` custom nesting, for all types, see [nesting of array-type schema](#nesting-of-array-type-schema) - -Note: - -- `properties` of schema other than void and object types cannot be rendered directly by `props.children`, but nesting can be resolved using `` -- Only schema of type void and object can be used with onlyRenderProperties -```tsx | pure - -``` - -### Nesting of void and object type schema - -The properties node can be adapted directly via props.children - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -// Hello 组件适配了 children,可以嵌套 properties 了 -const Hello = (props) =>

    Hello, {props.children}!

    ; -const World = () => world; - -const schema = { - type: 'object', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, -}; - -export default () => { - return ( - - - - ); -}; -``` - -Comparison of rendering results by property type - -```tsx -import React from 'react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const Hello = (props) =>

    Hello, {props.children}!

    ; -const World = () => world; - -const schema = { - type: 'object', - properties: { - title1: { - type: 'void', - 'x-content': 'Void schema,渲染 properties', - }, - void: { - type: 'void', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'void', - 'x-component': 'World', - }, - }, - }, - title2: { - type: 'void', - 'x-content': 'Object schema,渲染 properties', - }, - object: { - type: 'object', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, - }, - title3: { - type: 'void', - 'x-content': 'Array schema,不渲染 properties', - }, - array: { - type: 'array', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, - }, - title4: { - type: 'void', - 'x-content': 'String schema,不渲染 properties', - }, - string: { - type: 'string', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, - }, - } -}; - -export default () => { - return ( - - - - ); -}; -``` -### Nesting of array type schema - -Custom nesting can be solved with `` - -#### Array element is a string or number - -```tsx -import React from 'react'; -import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const useValueSchema = () => { - const schema = useFieldSchema(); - return schema.reduceProperties((buf, s) => { - if (s['x-component'] === 'Value') { - return s; - } - return buf; - }); -}; - -const ArrayList = observer((props) => { - const field = useField(); - const schema = useValueSchema(); - return ( - <> - String Array -
      - {field.value?.map((item, index) => { - // Only one element - return - })} -
    - - ); -}, { displayName: 'ArrayList' }); - -const Value = connect((props) => { - return
  • value: {props.value}
  • -}); - -const schema = { - type: 'object', - properties: { - strArr: { - type: 'array', - default: [1, 2, 3], - 'x-component': 'ArrayList', - properties: { - value: { - type: 'number', - 'x-component': 'Value', - }, - } - }, - } -}; - -export default () => { - return ( - - - - ); -}; -``` - -#### When the Array element is an Object - -```tsx -import React from 'react'; -import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const ArrayList = observer((props) => { - const field = useField(); - const schema = useFieldSchema(); - // The schema of array type cannot be onlyRenderProperties and needs to be converted to object type - const objSchema = new Schema({ - type: 'object', - properties: schema.properties, - }); - return ( -
      - {field.value?.map((item, index) => { - // When the Array element is an Object - return ( - - ) - })} -
    - ); -}, { displayName: 'ArrayList' }); - -const Value = connect((props) => { - return
  • value: {props.value}
  • -}); - -const schema = { - type: 'object', - properties: { - objArr: { - type: 'array', - default: [ - { value: 't1' }, - { value: 't2' }, - { value: 't3' }, - ], - 'x-component': 'ArrayList', - properties: { - value: { - type: 'number', - 'x-component': 'Value', - }, - } - } - } -}; - -export default () => { - return ( - - - - ); -}; -``` - -#### Tree structure data - -```tsx -import { ArrayField } from '@formily/core'; -import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; -import { Table, TableColumnType } from 'antd'; -import React from 'react'; - -const ArrayTable = observer((props: any) => { - const { rowKey } = props; - const field = useField(); - const schema = useFieldSchema(); - const columnSchemas = schema.reduceProperties((buf, s) => { - if (s['x-component'] === 'ArrayTable.Column') { - buf.push(s); - } - return buf; - }, []); - - const columns = columnSchemas.map((s) => { - return { - render: (value, record) => { - return ; - }, - } as TableColumnType; - }); - - return
    ; -}, { displayName: 'ArrayTable' }); - -const Value = connect((props) => { - return
  • value: {props.value}
  • ; -}); - -const schema: ISchema = { - type: 'object', - properties: { - objArr: { - type: 'array', - default: [ - { __path: '0', id: 1, value: 't1' }, - { - __path: '1', - id: 2, - value: 't2', - children: [ - { - __path: '1.children.0', - id: 5, - value: 't5', - parentId: 2, - }, - ], - }, - { - __path: '2', - id: 3, - value: 't3', - children: [ - { - __path: '2.children.0', - id: 4, - value: 't4', - parentId: 3, - children: [ - { - __path: '2.children.0.children.0', - id: 6, - value: 't6', - parentId: 4, - }, - { - __path: '2.children.0.children.1', - id: 7, - value: 't7', - parentId: 4, - }, - ], - }, - ], - }, - ], - 'x-component': 'ArrayTable', - 'x-component-props': { - rowKey: 'id', - }, - properties: { - c1: { - type: 'void', - 'x-component': 'ArrayTable.Column', - properties: { - value: { - type: 'string', - 'x-component': 'Value', - }, - }, - }, - }, - }, - }, -}; - -export default () => { - return ( - - - - ); -}; -``` diff --git a/docs/en-US/development/client/ui-schema-designer/index.md b/docs/en-US/development/client/ui-schema-designer/index.md deleted file mode 100644 index bd5b6fdf7..000000000 --- a/docs/en-US/development/client/ui-schema-designer/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Overview - - diff --git a/docs/en-US/development/client/ui-schema-designer/insert-adjacent.md b/docs/en-US/development/client/ui-schema-designer/insert-adjacent.md deleted file mode 100644 index f910e012a..000000000 --- a/docs/en-US/development/client/ui-schema-designer/insert-adjacent.md +++ /dev/null @@ -1,144 +0,0 @@ -# 邻近位置插入 - -与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `dn.insertAdjacent()` 方法用于解决邻近位置的插入问题。 - -## 邻近位置 - -```ts -{ - properties: { - // beforeBegin 在当前节点的前面插入 - node1: { - properties: { - // afterBegin 在当前节点的第一个子节点前面插入 - // ... - // beforeEnd 在当前节点的最后一个子节点后面 - }, - }, - // afterEnd 在当前节点的后面 - }, -} -``` - -## useDesignable() - -获取当前 schema 节点设计器的 API - -```ts -const { - designable, // 是否可以配置 - insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd - insertBeforeBegin, // 在当前节点的前面插入 - insertAfterBegin, // 在当前节点的第一个子节点前面插入 - insertBeforeEnd, // 在当前节点的最后一个子节点后面 - insertAfterEnd, // 在当前节点的后面 -} = useDesignable(); - -const schema = { - name: uid(), - 'x-component': 'Hello', -}; - -// 在当前节点的前面插入 -insertBeforeBegin(schema); -// 等同于 -insertAdjacent('beforeBegin', schema); - -// 在当前节点的第一个子节点前面插入 -insertAfterBegin(schema); -// 等同于 -insertAdjacent('afterBegin', schema); - -// 在当前节点的最后一个子节点后面 -insertBeforeEnd(schema); -// 等同于 -insertAdjacent('beforeEnd', schema); - -// 在当前节点的后面 -insertAfterEnd(schema); -// 等同于 -insertAdjacent('afterEnd', schema); -``` - -示例 - -```tsx -import React from 'react'; -import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client'; -import { observer, Schema, useFieldSchema } from '@formily/react'; -import { Button, Space } from 'antd'; -import { uid } from '@formily/shared'; - -const Hello = observer((props) => { - const { insertAdjacent } = useDesignable(); - const fieldSchema = useFieldSchema(); - return ( -
    -

    {fieldSchema.name}

    - - - - - - -
    {props.children}
    -
    - ); -}, { displayName: 'Hello' }); - -const Page = observer((props) => { - return
    {props.children}
    ; -}, { displayName: 'Page' }); - -export default () => { - return ( - - - - ); -} -``` diff --git a/docs/en-US/development/client/ui-schema-designer/what-is-ui-schema.md b/docs/en-US/development/client/ui-schema-designer/what-is-ui-schema.md deleted file mode 100644 index 1a4562475..000000000 --- a/docs/en-US/development/client/ui-schema-designer/what-is-ui-schema.md +++ /dev/null @@ -1,329 +0,0 @@ -# What is UI Schema? - -A protocol for describing front-end components, based on Formily Schema 2.0, JSON Schema-like style. - -```ts -interface ISchema { - type: 'void' | 'string' | 'number' | 'object' | 'array'; - name?: string; - title?: any; - // wrapper component - ['x-decorator']? : string; - // Wrapper component properties - ['x-decorator-props']? : any; - // component - ['x-component']? : string; - // Component properties - ['x-component-props']? : any; - // display state, default is 'visible' - ['x-display']? : 'none' | 'hidden' | 'visible'; - // child node of the component, simply use - ['x-content']? : any; - // children node schema - properties?: Record; - - // The following is used only for field components - - // field linkage - ['x-reactions']? : SchemaReactions; - // Field UI interaction mode, default is 'editable' - ['x-pattern']? : 'editable' | 'disabled' | 'readPretty'; - // Field validation - ['x-validator']? : Validator; - // default data - default: ? :any; - - // Designer related - - // Designer component (toolbar), including: drag and drop to move, insert new nodes, modify parameters, remove, etc. - ['x-designer']? : any; - // Initializer component (toolbar), determines what can be inserted inside the current schema - ['x-initializer']? : any; -} -``` - -## The simplest component - -All native html tags can be converted to schema writing. For example - -```ts -{ - type: 'void', - 'x-component': 'h1', - 'x-content': 'Hello, world! -} -``` - -JSX examples - -```tsx | pure -

    Hello, world!

    -``` - -## children components can be written in properties - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - }, - }, -} -``` - -JSX is equivalent to - -```tsx | pure -
    - -
    -``` - -## The clever use of Decorator - -The combination of decorator + component allows you to put two components in a single schema node, reducing the complexity of the schema structure and increasing the reusability of the components. - -For example, in a form scenario, you can combine a FormItem component with any field component, where the FormItem is the Decorator. - -```ts -{ - type: 'void', - ['x-component']: 'div', - properties: { - title: { - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - content: { - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input.TextArea', - }, - }, -} -``` - -JSX is equivalent to - -```tsx | pure -
    - - - - - - -
    -``` - -It is also possible to provide a CardItem component that wraps all blocks, so that all blocks are Card wrapped. - -```ts -{ - type: 'void', - ['x-component']: 'div', - properties: { - title: { - type: 'string', - 'x-decorator': 'CardItem', - 'x-component': 'Table', - }, - content: { - type: 'string', - 'x-decorator': 'CardItem', - 'x-component': 'Kanban', - }, - }, -} -``` - -JSX is equivalent to - -```tsx | pure -
    - -
    - - - - - -``` - -## Display state of the component - -- `'x-display': 'visible'`: the component is displayed -- `'x-display': 'hidden'`: component is hidden, data is not hidden -- `'x-display': 'none'`: component is hidden, data is also hidden - -### `'x-display': 'visible'` - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - 'x-display': 'visible' - }, - }, -} -``` - -JSX is equivalent to - -```tsx | pure -
    - -
    -``` - -### `'x-display': 'hidden'` - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - 'x-display': 'hidden' - }, - }, -} -``` - -JSX is equivalent to - -```tsx | pure -
    - {/* The input component is not output here, the corresponding field model with name=title still exists */} -
    -``` - -### `'x-display': 'none'` - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - 'x-display': 'none' - }, - }, -} -``` - -JSX is equivalent to - -```tsx | pure -
    - {/* The input component is not output here, and the corresponding field model with name=title does not exist anymore */} -
    -``` - -## Display modes for components - -For field components, there are three display modes: - -- `'x-pattern': 'editable'` Editable -- `'x-pattern': 'disabled'` Non-editable -- `'x-pattern': 'readPretty'` Friendly reading - -As in the case of the `` component, the editable and disabled modes are `` and the readPretty mode is `
    `. - -### `'x-pattern': 'editable'` - -```ts -const schema = { - name: 'test', - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - default: 'Hello', - 'x-component': 'SingleText', - 'x-pattern': 'editable' - }, - }, -}; -``` - -JSX is equivalent to - -```tsx | pure -
    - -
    -``` - -### `'x-pattern': 'disabled'` - -```ts -const schema = { - name: 'test', - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - default: 'Hello', - 'x-component': 'SingleText', - 'x-pattern': 'disabled' - }, - }, -}; -``` - -JSX is equivalent to - -```tsx | pure -
    - -
    -``` - -### `'x-pattern': 'readPretty'` - -```ts -const schema = { - name: 'test', - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - default: 'Hello', - 'x-component': 'SingleText', - 'x-pattern': 'readPretty', - }, - }, -}; -``` - -JSX is equivalent to - -```tsx | pure -
    -
    Hello
    -
    -``` diff --git a/docs/en-US/development/client/ui-schema-designer/x-designer.md b/docs/en-US/development/client/ui-schema-designer/x-designer.md deleted file mode 100644 index 7ee74ef75..000000000 --- a/docs/en-US/development/client/ui-schema-designer/x-designer.md +++ /dev/null @@ -1,61 +0,0 @@ -# x-designer - -## Built-in x-designer component - -- Action.Designer -- Calendar.Designer -- Filter.Action.Designer -- Form.Designer -- FormItem.Designer -- FormV2.Designer -- FormV2.ReadPrettyDesigner -- DetailsDesigner -- G2Plot.Designer -- Kanban.Designer -- Kanban.Card.Designer -- Markdown.Void.Designer -- Menu.Designer -- TableV2.Column.Designer -- TableV2.ActionColumnDesigner -- TableBlockDesigner -- TableSelectorDesigner -- Tabs.Designer - -## Replacement - -```tsx | pure -import React, { useContext } from 'react'; -import { useFieldSchema } from '@formily/react'; -import { - SchemaComponentOptions, - GeneralSchemaDesigner, - SchemaSettings, - useCollection -} from '@nocobase/client'; -import React from 'react'; - -const CustomActionDesigner = () => { - const { name, title } = useCollection(); - const fieldSchema = useFieldSchema(); - return ( - - - - ); -}; - -export default React.memo((props) => { - return ( - {props.children} - ); -}); -``` diff --git a/docs/en-US/development/client/ui-schema-designer/x-initializer.md b/docs/en-US/development/client/ui-schema-designer/x-initializer.md deleted file mode 100644 index 46e632072..000000000 --- a/docs/en-US/development/client/ui-schema-designer/x-initializer.md +++ /dev/null @@ -1,35 +0,0 @@ -# x-initializer - -## Built-in x-initializer component - -- BlockInitializers -- CalendarActionInitializers -- CreateFormBlockInitializers -- CustomFormItemInitializers -- DetailsActionInitializers -- FormActionInitializers -- FormItemInitializers -- KanbanActionInitializers -- ReadPrettyFormActionInitializers -- ReadPrettyFormItemInitializers -- RecordBlockInitializers -- RecordFormBlockInitializers -- SubTableActionInitializers -- TableActionColumnInitializers -- TableActionInitializers -- TableColumnInitializers -- TableSelectorInitializers -- TabPaneInitializers - -## Replacement - -```tsx |pure -import React, { useContext } from 'react'; -import { Plugin } from '@nocobase/client'; - -class MyPlugin extends Plugin { - async load() { - this.app.schemaInitializerManager.add('BlockInitializers', BlockInitializers) - } -} -``` diff --git a/docs/en-US/development/deps.md b/docs/en-US/development/deps.md deleted file mode 100644 index 375ce6d96..000000000 --- a/docs/en-US/development/deps.md +++ /dev/null @@ -1,105 +0,0 @@ -# Dependency management - -The dependencies of the plugin are divided into its own dependencies and global dependencies. Global dependencies are provided by `@nocobase/server` and `@nocobase/client`, and will not be packaged into the plugin product. Its own dependencies will be packaged into the product. - -Because the dependencies of the plugin itself will be packaged into the product (including the npm packages that the server depends on, which will also be packaged into `dist/node_modules`), when developing the plugin, all dependencies should be placed in `devDependencies`. - - -When installing the following dependencies, pay attention to the **version** and keep consistent with `@nocobase/server` and `@nocobase/client`. - - -```js -// nocobase -'@nocobase/acl', -'@nocobase/actions', -'@nocobase/auth', -'@nocobase/cache', -'@nocobase/client', -'@nocobase/database', -'@nocobase/evaluators', -'@nocobase/logger', -'@nocobase/resourcer', -'@nocobase/sdk', -'@nocobase/server', -'@nocobase/test', -'@nocobase/utils', - -// @nocobase/auth -'jsonwebtoken', - -// @nocobase/cache -'cache-manager', -'cache-manager-fs-hash', - -// @nocobase/database -'sequelize', -'umzug', -'async-mutex', - -// @nocobase/evaluators -'@formulajs/formulajs', -'mathjs', - -// @nocobase/logger -'winston', -'winston-daily-rotate-file', - -// koa -'koa', -'@koa/cors', -'@koa/router', -'multer', -'@koa/multer', -'koa-bodyparser', -'koa-static', -'koa-send', - -// react -'react', -'react-dom', -'react/jsx-runtime', - -// react-router -'react-router', -'react-router-dom', - -// antd -'antd', -'antd-style', -'@ant-design/icons', -'@ant-design/cssinjs', - -// i18next -'i18next', -'react-i18next', - -// dnd-kit -'@dnd-kit/accessibility', -'@dnd-kit/core', -'@dnd-kit/modifiers', -'@dnd-kit/sortable', -'@dnd-kit/utilities', - -// formily -'@formily/antd-v5', -'@formily/core', -'@formily/react', -'@formily/json-schema', -'@formily/path', -'@formily/validator', -'@formily/shared', -'@formily/reactive', -'@formily/reactive-react', - -// utils -'dayjs', -'mysql2', -'pg', -'pg-hstore', -'sqlite3', -'supertest', -'axios', -'@emotion/css', -'ahooks', -'lodash' -``` diff --git a/docs/en-US/development/index.md b/docs/en-US/development/index.md deleted file mode 100644 index fc9639c95..000000000 --- a/docs/en-US/development/index.md +++ /dev/null @@ -1,61 +0,0 @@ -# Introduction - -NocoBase adopts microkernel architecture, functions are extended in the form of plugins. Front and back ends are separated. Various plugin interfaces are provided, and plugins are divided by functional modules with pluggable features. - - - -The pluggable design reduces the coupling between modules and increases the reuse rate. As the plugin library continues to expand, common scenarios require only a combination of plugins to complete the base build. NocoBase's no-code platform, for example, is a combination of various plugins. - - - -## Plugin Manager - -NocoBase provides a powerful plugin manager for managing plugins. The flow of the plugin manager is as follows - - - -No-code Users can manage the activation and deactivation of local plugins through the interface at - - - -Developers can also manage the complete plugin process by way of the CLI: - -```bash -# Create the plugin -yarn pm create hello -# Register the plugin -yarn pm add hello -# Activate the plugin -yarn pm enable hello -# Disable the plugin -yarn pm disable hello -# Remove the plugin -yarn pm remove hello -``` - -For more plugin examples, see [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples). - -## Extensibility - -Whether it is generic functionality or personalization, it is recommended to write it as a plugin. NocoBase is extensible in all aspects. - -- It can be user-intuitive interface-related page modules, block types, action types, field types, etc. -- Filters, validators, access restrictions, etc. for enhancing or restricting the HTTP API -- It can also be enhancements to underlying data tables, migrations, events, command lines, etc. - - -Distribution of modules. - -- Server - - Collections & Fields: mainly used for system table configuration. Business tables are recommended to be configured in "Plugin Settings Manager - Collection manager". - - Resources & Actions: Mainly used to extend the Action API - - Middleware: Middleware - - Events: Events - - I18n: server-side internationalization - - Commands: Custom command lines - - Migrations: Migration scripts -- Client - - UI Schema Designer: Page Designer - - UI Router: When there is a need for custom pages - - Plugin Settings Manager: Provides configuration pages for plugins - - I18n: Client side internationalization diff --git a/docs/en-US/development/index/app-flow.svg b/docs/en-US/development/index/app-flow.svg deleted file mode 100644 index e167cda39..000000000 --- a/docs/en-US/development/index/app-flow.svg +++ /dev/null @@ -1 +0,0 @@ -
    beforeLoad
    loop: plugin.beforeLoad()
    load/reload
    loop: plugin.load()
    afterLoad
    Reinstall?
    beforeInstall
    install
    afterInstall
    beforeUpgrade
    upgrade
    afterUpgrade
    Restart?
    beforeStart
    start
    afterStart
    beforeStop
    stop
    afterStop
    beforeDestroy
    destroy
    afterDestroy
    \ No newline at end of file diff --git a/docs/en-US/development/index/pm-built-in.jpg b/docs/en-US/development/index/pm-built-in.jpg deleted file mode 100644 index 2fe60fdac..000000000 Binary files a/docs/en-US/development/index/pm-built-in.jpg and /dev/null differ diff --git a/docs/en-US/development/index/pm-flow.svg b/docs/en-US/development/index/pm-flow.svg deleted file mode 100644 index 23a738cfc..000000000 --- a/docs/en-US/development/index/pm-flow.svg +++ /dev/null @@ -1 +0,0 @@ -
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    plugin.afterAdd()
    app/client plugins
    pm.enable
    plugin.install()
    plugin.afterEnable()
    pm.disable
    plugin.afterDisable()
    pm.remove
    plugin.remove()
    pm.upgrade
    \ No newline at end of file diff --git a/docs/en-US/development/index/pm-ui.jpg b/docs/en-US/development/index/pm-ui.jpg deleted file mode 100644 index 4c8fdd3c1..000000000 Binary files a/docs/en-US/development/index/pm-ui.jpg and /dev/null differ diff --git a/docs/en-US/development/learning-guide.md b/docs/en-US/development/learning-guide.md deleted file mode 100644 index 115402259..000000000 --- a/docs/en-US/development/learning-guide.md +++ /dev/null @@ -1,125 +0,0 @@ -# 学习路线指南 - -## 1. 从安装运行 NocoBase 开始 - -**相关文档:快速开始** - -主要命令包括: - -下载 - -```bash -yarn create/git clone -yarn install -``` - -安装 - -```bash -yarn nocobase install -``` - -运行 - -```bash -# for development -yarn dev - -# for production -yarn build -yarn start -``` - -## 2. 了解 NocoBase 平台提供的核心功能 - -**相关文档:使用手册** - -主要的三部分包括: - -- UI 设计器:主要包括区块、字段和操作 -- 插件管理器:功能需求扩展 -- 配置中心:已激活插件提供的配置功能 - -## 3. 进一步了解插件管理器的使用 - -**相关文档:插件开发** - -NocoBase 提供了简易的插件管理器界面,但是在界面上只能处理本地插件的 enable、disable 和 remove,完整的操作需要通过 CLI - -```bash -# 创建插件 -yarn pm create hello -# 注册插件 -yarn pm add hello -# 激活插件 -yarn pm enable hello -# 禁用插件 -yarn pm disable hello -# 删除插件 -yarn pm remove hello -``` - -更多插件示例,查看 packages/samples,通过 samples 插件能够了解插件的基本用法,就可以进一步开发插件了。 - -## 4. 开发新插件,了解模块分布 - -**相关文档:扩展指南** - -[编写第一个插件](/development/your-fisrt-plugin) 章节,虽然简单的讲述了插件的主要开发流程,但是为了更快速的介入插件细节,你可能需要进一步了解 NocoBase 框架的模块分布: - -- Server - - Collections & Fields:主要用于系统表配置,业务表建议在「配置中心 - 数据表配置」里配置 - - Resources & Actions:主要用于扩展 Action API - - Middleware:中间件 - - Events:事件 - - I18n:服务端国际化 -- Client - - UI Schema Designer:页面设计器 - - UI Router:有自定义页面需求时 - - Plugin Settings Manager:为插件提供配置页面 - - I18n:客户端国际化 -- Devtools - - Commands:自定义命令行 - - Migrations:迁移脚本 - -## 5. 查阅各模块主要 API - -**相关文档:API 参考** - -查看各模块的 packages/samples,进一步了解模块主要 API 的用法 - -- Server - - Collections & Fields - - db.collection - - db.import - - Resources & Actions - - app.resourcer.define - - app.resourcer.registerActions - - Middleware - - app.use - - app.acl.use - - app.resourcer.use - - Events - - app.on - - app.db.on - - I18n - - app.i18n - - ctx.i18n -- Client - - UI Schema Designer - - SchemaComponent - - SchemaInitializer - - SchemaSettings - - UI Router - - RouteSwitchProvider - - RouteSwitch - - I18n - - app.i18n - - useTranslation -- Devtools - - Commands - - app.command - - app.findCommand - - Migrations - - app.db.addMigration - - app.db.addMigrations diff --git a/docs/en-US/development/life-cycle.md b/docs/en-US/development/life-cycle.md deleted file mode 100644 index f250b26a9..000000000 --- a/docs/en-US/development/life-cycle.md +++ /dev/null @@ -1,41 +0,0 @@ -# Life cycle - -## Lifecycle of applications - - - -## Lifecycle of plugins - - - -## Lifecycle methods for plugins - -```ts -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class MyPlugin extends Plugin { - afterAdd() { - // After the plugin pm.add is registered. Mainly used to place the app.beforeLoad event. - beforeLoad() { } - beforeLoad() { - // Before all plugins are loaded. Generally used for registering classes and event listeners - } - async load() { - // Load configuration - } - async install(options?: InstallOptions) { - // Logic for installing - } - async afterEnable() { - // After activation - } - async afterDisable() { - // After disable - } - async remove() { - // Logic for removing - } -} - -export default MyPlugin; -``` diff --git a/docs/en-US/development/others/build.md b/docs/en-US/development/others/build.md deleted file mode 100644 index 1294b9a65..000000000 --- a/docs/en-US/development/others/build.md +++ /dev/null @@ -1 +0,0 @@ -# Building \ No newline at end of file diff --git a/docs/en-US/development/others/testing.md b/docs/en-US/development/others/testing.md deleted file mode 100644 index 1f37805b5..000000000 --- a/docs/en-US/development/others/testing.md +++ /dev/null @@ -1,163 +0,0 @@ -# 单元测试 - -## 介绍 - -NocoBase 的测试基于 [Jest](https://jestjs.io/) 测试框架。同时,为了方便的编写测试,我们提供了两个工具类,在测试环境模拟正常的数据库和应用的服务端。 - -### MockDatabase - -模拟数据库类继承自 [`Database`](/api/database) 类,大部分内容没有区别,主要在构造函数默认内置了随机表前缀,在每个测试用例初始化数据库时相关数据表都通过前缀名称与其他用例进行隔离,在运行测试用例时互不影响。 - -```ts -import { MockDatabase } from '@nocobase/test'; - -describe('my suite', () => { - let db; - - beforeEach(async () => { - db = new MockDatabase(); - - db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] - }); - - await db.sync(); - }); - - test('my case', async () => { - const postRepository = db.getRepository('posts'); - const p1 = await postRepository.create({ - values: { - title: 'hello' - } - }); - - expect(p1.get('title')).toEqual('hello'); - }); -}); -``` - -### MockServer - -模拟服务器也继承自 [Application](/api/server/application) 类,除了内置的数据库实例是通过模拟数据库类生成的以外,还提供了比较方便的生成基于 [superagent](https://www.npmjs.com/package/superagent) 请求代理功能,针对从发送请求到获取响应的写法也集成了 `.resource('posts').create()`,比较简化。 - -```ts -import { mockServer } from '@nocobase/test'; - -describe('my suite', () => { - let app; - let agent; - let db; - - beforeEach(async () => { - app = mockServer(); - agent = app.agent(); - - db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] - }); - - await db.sync(); - await app.load(); - }); - - test('my case', async () => { - const { body } = await agent.resource('posts').create({ - values: { - title: 'hello' - } - }); - - expect(body.data.title).toEqual('hello'); - }); -}); -``` - -## 示例 - -我们以之前在 [资源与操作](development/guide/resources-actions) 章节的功能为例,来写一个插件的测试: - -```ts -import { mockServer } from '@nocobase/test'; -import Plugin from '../../src/server'; - -describe('shop actions', () => { - let app; - let agent; - let db; - - beforeEach(async () => { - app = mockServer(); - app.plugin(Plugin); - agent = app.agent(); - db = app.db; - - await app.load(); - await db.sync(); - }); - - afterEach(async () => { - await app.destroy(); - }); - - test('product order case', async () => { - const { body: product } = await agent.resource('products').create({ - values: { - title: 'iPhone 14 Pro', - price: 7999, - enabled: true, - inventory: 1 - } - }); - expect(product.data.price).toEqual(7999); - - const { body: order } = await agent.resource('orders').create({ - values: { - productId: product.data.id - } - }); - expect(order.data.totalPrice).toEqual(7999); - expect(order.data.status).toEqual(0); - - const { body: deliveredOrder } = await agent.resource('orders').deliver({ - filterByTk: order.data.id, - values: { - provider: 'SF', - trackingNumber: '123456789' - } - }); - expect(deliveredOrder.data.status).toBe(2); - expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789'); - }); -}); -``` - -编写完成后,在命令行中允许测试命令: - -```bash -yarn test packages/samples/shop-actions -``` - -该测试将验证: - -1. 商品可以创建成功; -2. 订单可以创建成功; -3. 订单可以发货成功; - -当然这只是个最基本的例子,从业务上来说并不完善,但作为示例已经可以说明整个测试的流程。 - -## 小结 - -本章涉及的示例代码整合在对应的包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。 diff --git a/docs/en-US/development/plugin-ds.md b/docs/en-US/development/plugin-ds.md deleted file mode 100644 index 6c05646a9..000000000 --- a/docs/en-US/development/plugin-ds.md +++ /dev/null @@ -1,45 +0,0 @@ -# Plugin directory structure - -An empty plugin can be created quickly with `yarn pm create my-plugin`, with the following directory structure. - -```bash -|- /my-plugin - |- /src - |- /client # client-side of the plugin - |- /server # server-side of the plugin - |- client.d.ts - |- client.js - |- package.json # plugin package information - |- server.d.ts - |- server.js - |- build.config.ts # or `build.config.js`, modify configuration -``` - -The tutorial for `/src/server` refers to the [server](./server) section, and the tutorial for `/src/client` refers to the [client](./client) section. - -If you want to customize the packaging configuration, you can create a `config.js` file in the root directory, with the following content: - -```js -import { defineConfig } from '@nocobase/build'; - -export default defineConfig({ - modifyViteConfig: (config) => { - // vite is used to package the `src/client` side code - - // Modify the Vite configuration, for more information, please refer to: https://vitejs.dev/guide/ - return config - }, - modifyTsupConfig: (config) => { - // tsup is used to package the `src/server` side code - - // Modify the tsup configuration, for more information, please refer to: https://tsup.egoist.dev/#using-custom-configuration - return config - }, - beforeBuild: (log) => { - // The callback function before the build starts, you can do some operations before the build starts - }, - afterBuild: (log: PkgLog) => { - // The callback function after the build is completed, you can do some operations after the build is completed - }; -}) -``` diff --git a/docs/en-US/development/server/collections-fields.md b/docs/en-US/development/server/collections-fields.md deleted file mode 100644 index e5570aba8..000000000 --- a/docs/en-US/development/server/collections-fields.md +++ /dev/null @@ -1,319 +0,0 @@ -## Collections and Fields - -## Basic Concepts - -Data modeling is the lowest level foundation of an application. In NocoBase applications we model data through data tables (Collection) and fields (Field), and the modeling is also mapped to database tables for persistence. - -### Collection - -Collection is a collection of all similar data, which corresponds to the concept of database tables in NocoBase. Such as orders, products, users, comments, etc. can form a collection definition. Different collections are distinguished by name and contain fields defined by `fields`, such as - -```ts -db.collection({ - name: 'posts', - fields: [ - { name: 'title', type: 'string' } - { name: 'content', type: 'text' }, - // ... - ] -}); -``` - -The collection is only in memory after the definition, you need to call the [``db.sync()`'' (/api/database#sync) method to synchronize it to the database. - -### Field - -Corresponding to the concept of database table "fields", each data table (Collection) can have a number of Fields, for example. - -```ts -db.collection({ - name: 'users', - fields: [ - { type: 'string', name: 'name' } - { type: 'integer', name: 'age' } - // Other fields - ], -}); -``` - -The field name (`name`) and field type (`type`) are required, and different fields are distinguished by the field name (`name`). All field types and their configurations are described in the [List of built-in field types](/api/database/field#List of built-in field types) section of the API reference. - -## Example - -For developers, we usually build functional collections that are different from normal collections and solidify these collections as part of the plugin and combine them with other data processing processes to form a complete functionality. - -Let's take a simple online store plugin as an example to show how to model and manage the collections of the plugin. Assuming you have already learned about [Develop your first plugin](/development/your-first-plugin), we continue to build on the previous plugin code, except that the name of the plugin is changed from `hello` to `shop-modeling`. - -### Define and create collections in the plugin - -For a store, you first need to create a collection of products, named `products`. Instead of calling [`db.collection()`](/api/database#collection) directly, in the plugin we will use a more convenient method to import multiple files of defined data tables at once. So let's start by creating a file for the product collection definition named ``collections/products.ts`` and fill it with the following content. - -```ts -export default { - name: 'products', - fields: [ - { - type: 'string', - name: 'title' - }, - { - type: 'integer', - name: 'price' - }, - { - type: 'boolean', - name: 'enabled' - }, - { - type: 'integer', - name: 'inventory' - } - ] -}; -``` - -As you can see, the collections structure definition can be used directly in standard JSON format, where `name` and `fields` are required representing the collection's name and the field definitions in the collection. Field definitions similar to Sequelize create system fields such as primary key (`id`), data creation time (`createdAt`) and data update time (`updatedAt`) by default, which can be overridden by a configuration with the same name if there is a special need. - -The data table defined in this file we can introduce and complete the definition in the `load()` cycle of the main plugin class using `db.import()`. This is shown below. - -```ts -import path from 'path'; -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - await this.db.import({ - directory: path.resolve(__dirname, 'collections'), - }); - - this.app.acl.allow('products', '*'); - this.app.acl.allow('categories', '*'); - this.app.acl.allow('orders', '*'); - } -} -``` - -In the meantime, for testing purposes, we will temporarily allow all access permissions for the data in these collections, and later we will detail how to manage data permissions in [Permissions Management](/development/guide/acl). - -This way, when the plugin is loaded by the main application, the `products` collection we defined is also loaded into the memory of the database management instance. At the same time, the NocoBase constraint-based resource mapping of the collections automatically generates the corresponding CRUD HTTP API after the application's service is started. - -When the following URLs are requested from the client, the corresponding responses are obtained. - -* `GET /api/products:list`: Get a list of all product data -* `GET /api/products:get?filterByTk=`: Get the product data for the specified ID -* `POST /api/products`: Create a new product data -* `PUT /api/products:update?filterByTk=`: Update a product data -* `DELETE /api/products:destroy?filterByTk=`: Delete a product data - -### Defining associated collections and fields - -In the above example, we only defined a product collection, but in reality a product also needs to be associated to a category, a brand, a supplier, etc. For example, we can define a `categories` collection to store the categories, and then add a `category` field to the product collection to associate it with the category collection. - -Add a new file `collections/categories.ts` and fill in the content. - -```ts -export default { - name: 'categories', - fields: [ - { - type: 'string', - name: 'title' - }, - { - type: 'hasMany', - name: 'products', - } - ] -}; -``` - -We have defined two fields for the `categories` collection, one for the title and another one-to-many field for all the products associated under that category, which will be described later. Since we have already used the `db.import()` method in the plugin's main class to import all the data table definitions under the `collections` directory, the new `categories` collection added here will also be automatically imported into the database management instance. - -Modify the file `collections/products.ts`` to add a `category` field to the `fields`. - -```ts -{ - name: 'products', - fields: [ - // ... - { - type: 'belongsTo', - name: 'category', - target: 'categories', - } - ] -} -``` - -As you can see, the `category` field we added to the `products` collection is a `belongsTo` type field, and its `target` property points to the `categories` collection, thus defining a many-to-one relationship between the `products` collection and the `categories` collection. Combined with the `hasMany` field defined in the `categories` collection, we can achieve a relationship where one product can be associated to multiple categories and multiple products under one category. Usually `belongsTo` and `hasMany` can appear in pairs, defined in two separate collections. - -Once the relationship between the two collections is defined, we can also request the associated data directly through the HTTP API - -* `GET /api/products:list?appends=category`: Get all products data, including the associated categories data -* `GET /api/products:get?filterByTk=&appends=category`: Get the product data for the specified ID, including the associated category data. -* `GET /api/categories//products:list`: Get all the products under the specified category -* `POST /api/categories//products`: Create a new product under the specified category - -Similar to the general ORM framework, NocoBase has four built-in relational field types, for more information you can refer to the section about API field types. - -* [`belongsTo` type](/api/database/field#belongsto) -* [`belongsToMany` type](/api/database/field#belongstomany) -* [`hasMany` type](/api/database/field#hasmany) -* [`hasOne` type](/api/database/field#hasone) - -### Extend an existing collection - -In the above example, we already have a product collection and a category collection, in order to provide the sales process we also need an order collection. We can add a new `orders.ts` file to the `collections` directory and define an `orders` collection as follows - -```ts -export default { - name: 'orders', - fields: [ - { - type: 'uuid', - name: 'id', - primaryKey: true - }, - { - type: 'belongsTo', - name: 'product' - }, - { - type: 'integer', - name: 'quantity' - }, - { - type: 'integer', - name: 'totalPrice' - }, - { - type: 'integer', - name: 'status' - }, - { - type: 'string', - name: 'address' - }, - { - type: 'belongsTo', - name: 'user' - } - ] -} -``` - -For the sake of simplicity, the association between the order collection and the product collection we simply define as a many-to-one relationship, while in the actual business may be used in a complex modeling approach such as many-to-many or snapshot. As you can see, an order in addition to corresponding to a commodity, we also added a relationship definition corresponding to the users, which is a collection managed by the NocoBase built-in plugins (refer to [code for users plugin](https://github.com/nocobase/nocobase/tree/main/packages/) for details plugins/users)). If we want to extend the definition of the "multiple orders owned by a user" relationship for the existing users collection, we can continue to add a new collection file `collections/users.ts` inside the current shop-modeling plugin, which is different from exporting the JSON collection directly. Unlike the direct export of a JSON, the `@nocobase/database` package's `extend()` method is used here to extend the definition of an existing collection: ``ts - -```ts -import { extend } from '@nocobase/database'; - -export extend({ - name: 'users', - fields: [ - { - type: 'hasMany', - name: 'orders' - } - ] -}); -``` - -This way, the existing users table also has an `orders` associated field, and we can retrieve all the order data for a given user via `GET /api/users//orders:list`. - -This method is very useful when extending collections already defined by other plugins, so that other existing plugins do not depend on the new plugin in reverse, but only form one-way dependencies, facilitating a certain degree of decoupling at the extension level. - -### Extend field types - -We use `uuid` type for `id` field when we define order table, which is a built-in field type. Sometimes we may feel that UUID looks too long and waste space, and the query performance is not good, we want to use a more suitable field type, such as a complex numbering logic with date information, or Snowflake algorithm, we need to extend a custom field type. - -Suppose we need to apply the Snowflake ID generation algorithm directly to extend a ``snowflake`` field type, we can create a ``fields/snowflake.ts`` file. - -```ts -// Import the algorithm toolkit -import { Snowflake } from 'nodejs-snowflake'; -// Import field type base class -import { DataTypes, Field, BaseColumnFieldOptions } from '@nocobase/database'; - -export interface SnowflakeFieldOptions extends BaseColumnFieldOptions { - type: 'snowflake'; - epoch: number; - instanceId: number; -} - -export class SnowflakeField extends Field { - get dataType() { - return DataTypes.BIGINT; - } - - constructor(options: SnowflakeFieldOptions, context) { - super(options, context); - - const { - epoch: custom_epoch, - instanceId: instance_id = process.env.INSTANCE_ID ? Number.parseInt(process.env.INSTANCE_ID) : 0, - } = options; - this.generator = new Snowflake({ custom_epoch, instance_id }); - } - - setValue = (instance) => { - const { name } = this.options; - instance.set(name, this.generator.getUniqueID()); - }; - - bind() { - super.bind(); - this.on('beforeCreate', this.setValue); - } - - unbind() { - super.unbind(); - this.off('beforeCreate', this.setValue); - } -} - -export default SnowflakeField; -``` - -Afterwards, register the new field type into the collection in the main plugin file. - -```ts -import SnowflakeField from '. /fields/snowflake'; - -export default class ShopPlugin extends Plugin { - initialize() { - // ... - this.db.registerFieldTypes({ - snowflake: SnowflakeField - }); - // ... - } -} -``` - -This allows us to use the `snowflake` field type in the order table: - -```ts -export default { - name: 'orders', - fields: [ - { - type: 'snowflake' - name: 'id', - primaryKey: true - }, - // ... . other fields - ] -} -``` - -## Summary - -With the above example, we basically understand how to model data in a plugin, including. - -* Defining collections and common fields -* Defining association collections and fields relationships -* Extending fields of an existing collections -* Extending new field types - -We have put the code covered in this chapter into a complete sample package [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling), which can be run directly locally to see the results. - diff --git a/docs/en-US/development/server/collections/association-fields.md b/docs/en-US/development/server/collections/association-fields.md deleted file mode 100644 index c6a873e07..000000000 --- a/docs/en-US/development/server/collections/association-fields.md +++ /dev/null @@ -1,153 +0,0 @@ -# Association Fields - -In a relational database, the standard way to build a table relationship is to add a foreign key field followed by a foreign key constraint. For example, Knex builds a table with the following example. - -```ts -knex.schema.table('posts', function (table) { - table.integer('userId').unsigned(); - table.foreign('userId').references('users.id'); -}); -``` - -This procedure creates a userId field in the posts table and sets the foreign key constraint posts.userId to refer to users.id. In NocoBase's Collection, such a relational constraint is created by configuring the relational field, e.g. - -```ts -{ - name: 'posts', - fields: [ - { - type: 'belongsTo', - name: 'user', - target: 'users', - foreignKey: 'userId', - }, - ], -} -``` - -## Relationship parameters - -### BelongsTo - -```ts -interface BelongsTo { - type: 'belongsTo'; - name: string; - // defaults to name's plural - target?: string; - // The default value is the primary key of the target model, usually 'id' - targetKey?: any; - // defaults to target + 'Id' - foreignKey?: any; -} - -// The authors table's primary key id is concatenated with the books table's foreign key authorId -{ - name: 'books', - fields: [ - { - type: 'belongsTo', - name: 'author', - target: 'authors', - targetKey: 'id', // authors table's primary key - foreignKey: 'authorId', // foreign key in books table - } - ], -} -``` - -### HasOne - -```ts -interface HasOne { - type: 'hasOne'; - name: string; - // defaults to name's plural - target?: string; - // The default value is the primary key of the source model, usually 'id' - sourceKey?: string; - // default value is the singular form of source collection name + 'Id' - foreignKey?: string; -foreignKey?} - -// The users table's primary key id is concatenated with the profiles' foreign key userId -{ - name: 'users', - fields: [ - { - type: 'hasOne', - name: 'profile', - target: 'profiles', - sourceKey: 'id', // users table's primary key - foreignKey: 'userId', // foreign key in profiles table - } - ], -} -``` - -### HasMany - -```ts -interface HasMany { - type: 'hasMany'; - name: string; - // defaults to name - target?: string; - // The default value is the primary key of the source model, usually 'id' - sourceKey?: string; - // the default value is the singular form of the source collection name + 'Id' - foreignKey?: string; -} - -// The posts table's primary key id is concatenated with the comments table's postId -{ - name: 'posts', - fields: [ - { - type: 'hasMany', - name: 'comments', - target: 'comments', - sourceKey: 'id', // posts table's primary key - foreignKey: 'postId', // foreign key in the comments table - } - ], -} -``` - -### BelongsToMany - -```ts -interface BelongsToMany { - type: 'belongsToMany'; - name: string; - // default value is name - target?: string; - // defaults to the source collection name and target in the natural order of the first letter of the string - through?: string; - // defaults to the singular form of source collection name + 'Id' - foreignKey?: string; - // The default value is the primary key of the source model, usually id - sourceKey?: string; - // the default value is the singular form of target + 'Id' - otherKey?: string; - // the default value is the primary key of the target model, usually id - targetKey?: string; -} - -// tags table's primary key, posts table's primary key and posts_tags two foreign keys are linked -{ - name: 'posts', - fields: [ - { - type: 'believesToMany', - name: 'tags', - target: 'tags', - through: 'posts_tags', // intermediate table - foreignKey: 'tagId', // foreign key 1, in posts_tags table - otherKey: 'postId', // foreignKey2, in posts_tags table - targetKey: 'id', // tags table's primary key - sourceKey: 'id', // posts table's primary key - } - ], -} -``` diff --git a/docs/en-US/development/server/collections/cm.svg b/docs/en-US/development/server/collections/cm.svg deleted file mode 100644 index 9be5cbf5f..000000000 --- a/docs/en-US/development/server/collections/cm.svg +++ /dev/null @@ -1 +0,0 @@ -
    Collection Manager Plugin

    Provide rest api to manage collections
    REST API
    Plugin A
    db.collection()
    Collections
    UI: Collections & Fields
    UI: Graphical interface
    Plugin B
    db.collection()
    Third party
    \ No newline at end of file diff --git a/docs/en-US/development/server/collections/collection-field.svg b/docs/en-US/development/server/collections/collection-field.svg deleted file mode 100644 index 77c6d5f6f..000000000 --- a/docs/en-US/development/server/collections/collection-field.svg +++ /dev/null @@ -1 +0,0 @@ -
    Field Type
    Field Component
    Field Interface
    Collection Field
    \ No newline at end of file diff --git a/docs/en-US/development/server/collections/collection-template.md b/docs/en-US/development/server/collections/collection-template.md deleted file mode 100644 index eeeb27eca..000000000 --- a/docs/en-US/development/server/collections/collection-template.md +++ /dev/null @@ -1,82 +0,0 @@ -# Collection templates - - -📢 Collection templates are scheduled to be available in Q4 2022. - - -In real business scenarios, different collections may have their own initialization rules and business logic, and NocoBase addresses such issues by providing collection templates. - -## General collections - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ], -}); -``` - -## Tree structure collections - -```ts -db.collection({ - name: 'categories', - tree: 'adjacency-list', - fields: [ - { - type: 'string', - name: 'name', - }, - { - type: 'string', - name: 'description', - }, - { - type: 'belongsTo', - name: 'parent', - target: 'categories', - foreignKey: 'parentId', - }, - { - type: 'hasMany', - name: 'children', - target: 'categories', - foreignKey: 'parentId', - }, - ], -}); -``` - -## Parent-child inheritance collections - -```ts -db.collection({ - name: 'a', - fields: [ - - ], -}); - -db.collection({ - name: 'b', - inherits: 'a', - fields: [ - - ], -}); -``` - -## More templates - -As in the case of calendar collections, each initialized collection needs to be initialized with special cron and exclude fields, and the definition of such fields is done by the template - -```ts -db.collection({ - name: 'events', - template: 'calendar', -}); -``` diff --git a/docs/en-US/development/server/collections/configure.md b/docs/en-US/development/server/collections/configure.md deleted file mode 100644 index a3346e519..000000000 --- a/docs/en-US/development/server/collections/configure.md +++ /dev/null @@ -1,62 +0,0 @@ -# How to configure collections? - -NocoBase has three ways to configure collections. - - - -## Configuring collections through the interface - -Business data is generally recommended to be configured using the interface, and the NocoBase platform provides two interfaces to configure collections. - -### Regular table interface - - - -### Graphical configuration interface - - - -## Defined in the plugin code - -Generally used to configure plugin functions or system configuration tables where users can read and write data, but cannot modify the data structure. - -```ts -export class MyPlugin extends Plugin { - load() { - this.db.collection(); - this.db.import(); - } -} -``` - -Related API Reference - -- [db.collection()](/api/database#collection) -- [db.import()](/api/database#import) - -The collection configured in the plugin is automatically synchronized with the database when the plugin is activated, giving birth to the corresponding data tables and fields. - -## Managing data tables via REST API - -Third parties can also manage data tables via the HTTP interface (permissions required) - -### Collections - -```bash -GET /api/collections -POST /api/collections -GET /api/collections/ -PUT /api/collections/ -DELETE /api/collections/ -``` - -### Collection fields - -```bash -GET /api/collections//fields -POST /api/collections//fields -GET /api/collections//fields/ -PUT /api/collections//fields/ -DELETE /api/collections//fields/ -``` - diff --git a/docs/en-US/development/server/collections/field-extension.md b/docs/en-US/development/server/collections/field-extension.md deleted file mode 100644 index 3c95ee52e..000000000 --- a/docs/en-US/development/server/collections/field-extension.md +++ /dev/null @@ -1,39 +0,0 @@ -# How to extend fields - -The composition of a Collection Field in NocoBase consists of - - - -## Extend Field Type - -For example, to extend the password type field ``type: 'password'` - -```ts -export class MyPlugin extends Plugin { - beforeLoad() { - this.db.registerFieldTypes({ - password: PasswordField - }); - } -} - -export class PasswordField extends Field { - get dataType() { - return DataTypes.STRING; - } -} -``` - -- [More implementations of the built-in field types can be found here](https://github.com/nocobase/nocobase/tree/main/packages/core/database/src/fields) -- Also see the full samples plugin [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling) - -## Extend Field Component - -Related extension documentation can be found at - -- [Extending Schema Components](/development/client/ui-schema-designer/extending-schema-components) -- [Schema component library](/development/client/ui-schema-designer/component-library) - -## Extend Field Interface - -- [Built-in field interfaces view here](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces) diff --git a/docs/en-US/development/server/collections/graph.jpg b/docs/en-US/development/server/collections/graph.jpg deleted file mode 100644 index 16c2f95d1..000000000 Binary files a/docs/en-US/development/server/collections/graph.jpg and /dev/null differ diff --git a/docs/en-US/development/server/collections/index.md b/docs/en-US/development/server/collections/index.md deleted file mode 100644 index 27e7d7dd9..000000000 --- a/docs/en-US/development/server/collections/index.md +++ /dev/null @@ -1,194 +0,0 @@ -# Core concepts - -## Collection - -Collection is a collection of all kinds of data, such as orders, products, users, comments, etc. Different collections are distinguished by name, e.g. - -```ts -// Orders -{ - name: 'orders', -} -// Products -{ - name: 'products', -} -// Users -{ - name: 'users', -} -// Comments -{ - name: 'comments', -} -``` - -## Collection Field - -Each Collection has a number of Fields. - -```ts -// Collection configuration -{ - name: 'users', - fields: [ - { type: 'string', name: 'name' }, - { type: 'integer', name: 'age' }, - // Other fields - ], -} -// sample data -[ - { - name: 'Jason', - age: 20, - }, - { { - name: 'Li Si', - age: 18, - } -]; -``` - -The composition of a Collection Field in NocoBase consists of - - - -### Field Type - -Different fields are distinguished by name, and type indicates the data type of the field, which is divided into Attribute Type and Association Type, e.g. - -**Attribute - Attribute Type** - -- string -- text -- date -- boolean -- time -- float -- json -- location -- password -- virtual -- ... - -**Relationship - Association Type** - -- hasOne -- hasMany -- belongsTo -- belongsToMany -- ... - -### Field Component - -The field has a data type, the IO of the field value is fine, but it is not enough, if you need to display the field on the interface, you need another dimension of configuration -- `uiSchema`, e.g. - -```tsx | pure -// Email field, displayed with Input component, using email validation rules -{ - type: 'string', - name: 'email', - uiSchema: { - 'x-component': 'Input', - 'x-component-props': { size: 'large' }, - 'x-validator': 'email', - 'x-pattern': 'editable', // editable state, and readonly state, read-pretty state - }, -} - -// Example data -{ - email: 'admin@nocobase.com', -} - -// Component example - -``` - -The uiSchema is used to configure the components of the field to be displayed on the interface, each field component will correspond to a value and includes several maintained configurations: - -- The component of the field -- The parameters of the component -- The field's validation rules -- The mode of the field (editable, readonly, read-pretty) -- The default value of the field -- Other - -[see the UI Schema chapter for more information](/development/client/ui-schema-designer/what-is-ui-schema). - -The built-in field components of NocoBase are - -- Input -- InputNumber -- Select -- Radio -- Checkbox -- ... - -### Field Interface - -With Field Type and Field Component you can freely combine several fields, we call this combined template Field Interface, e.g. - -```ts -// email field, string + input, email validation rules -{ - type: 'string', - name: 'email', - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'email', - }, -} - -// phone field, string + input, phone validation rules -{ - type: 'string', - name: 'phone', - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'phone', - }, -} -``` - -The above email and phone require a full uiSchema to be configured each time which is very tedious. To simplify the configuration, another concept Field interface is introduced, which can template some parameters, e.g. - -```ts -// Template for the email field -interface email { - type: 'string'; - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'email', - }; -} - -// Template for the phone field -interface phone { - type: 'string'; - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'phone', - }; -} - -// Simplified field configuration -// email -{ - interface: 'email', - name: 'email', -} - -// phone -{ - interface: 'phone', - name: 'phone', -} -``` - -[More Field Interface here](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces) diff --git a/docs/en-US/development/server/collections/options.md b/docs/en-US/development/server/collections/options.md deleted file mode 100644 index 319edf654..000000000 --- a/docs/en-US/development/server/collections/options.md +++ /dev/null @@ -1,137 +0,0 @@ -# Collection protocol - -Collection is the backbone of NocoBase, a protocol for describing data structures (collections and fields), very close to the concept of a relational database, but not limited to relational databases, but can also be a data source for NoSQL databases, HTTP APIs, etc. - - - -At this stage, the Collection protocol is based on the relational database interface (db.collections), and data sources such as NoSQL databases and HTTP APIs will be implemented gradually in the future. - -Collection protocol mainly includes two parts: CollectionOptions and FieldOptions. Because Field is extensible, the parameters of FieldOptions are very flexible. - -## CollectionOptions - -```ts -interface CollectionOptions { - name: string; - title?: string; - // Tree structure table, TreeRepository - tree?: 'adjacency-list' | 'closure-table' | 'materialized-path' | 'nested-set'; - // parent-child inheritance - inherits?: string | string[]; - fields?: FieldOptions[]; - timestamps?: boolean; - paranoid?: boolean; - sortable?: CollectionSortable; - model?: string; - repository?: string; - [key: string]: any; -} - -type CollectionSortable = string | boolean | { name?: string; scopeKey?: string }; -``` - -## FieldOptions - -Generic field parameters - -```ts -interface FieldOptions { - name: string; - type: string; - hidden?: boolean; - index?: boolean; - interface?: string; - uiSchema?: ISchema; -``` - -[Introduction to UI Schema here](/development/client/ui-schema-designer/what-is-ui-schema) - -### Field Type - -Field Type includes Attribute Type and Association Type. - -**Attribute Type** - -- 'boolean' -- 'integer' -- 'bigInt' -- 'double' -- 'real' -- 'decimal' -- 'string' -- 'text' -- 'password' -- 'date' -- 'time' -- 'array' -- 'json' -- 'jsonb' -- 'uuid' -- 'uid' -- 'formula' -- 'radio' -- 'sort' -- 'virtual' - -**Association Type** - -- 'belongsTo' -- 'hasOne' -- 'hasMany' -- 'belongsToMany' - -### Field Interface - -**Basic** - -- input -- textarea -- phone -- email -- integer -- number -- percent -- password -- icon - -**Choices** - -- checkbox -- select -- multipleSelect -- radioGroup -- checkboxGroup -- chinaRegion - -**Media** - -- attachment -- markdown -- richText - -**Date & Time** - -- datetime -- time - -**Relation** - -- linkTo - `type: 'believesToMany'` -- oho - `type: 'hasOne'` -- obo - `type: 'believesTo'` -- o2m - `type: 'hasMany'` -- m2o - `type: 'believesTo'` -- m2m - `type: 'believesToMany'` - -**Advanced** - -- formula -- sequence - -**System info** - -- id -- createdAt -- createdBy -- updatedAt -- updatedBy diff --git a/docs/en-US/development/server/collections/schema.svg b/docs/en-US/development/server/collections/schema.svg deleted file mode 100644 index 230a9bbca..000000000 --- a/docs/en-US/development/server/collections/schema.svg +++ /dev/null @@ -1 +0,0 @@ -
    Collection
    Field Type
    Field Component
    UI Schema
    Field Interface
    Collection Field
    Relational Database
    PostgreSQL
    NoSQL
    API
    SQLite
    MySQL
    Others
    Resource
    Client
    \ No newline at end of file diff --git a/docs/en-US/development/server/collections/table.jpg b/docs/en-US/development/server/collections/table.jpg deleted file mode 100644 index 55fe4c183..000000000 Binary files a/docs/en-US/development/server/collections/table.jpg and /dev/null differ diff --git a/docs/en-US/development/server/commands.md b/docs/en-US/development/server/commands.md deleted file mode 100644 index 069e1f354..000000000 --- a/docs/en-US/development/server/commands.md +++ /dev/null @@ -1,100 +0,0 @@ -# Commands - -NocoBase Server Application is a powerful and extensible CLI tool in addition to being used as a WEB server. - -Create a new `app.js` file with the following code. - -```ts -const Application = require('@nocobase/server'); - -// omit the specific configuration here -const app = new Application({/*... */}); - -app.runAsCLI(); -``` - -app.js run as ``runAsCLI()`` is a CLI, it will work like this in the command line tool. - -```bash -node app.js install # install -node app.js start # start -``` - -To better develop, build and deploy NocoBase applications, NocoBase has many built-in commands, see the [NocoBase CLI](/api/cli) section for details. - -## How to customize Command? - -NocoBase CLI is designed to be very similar to [Laravel Artisan](https://laravel.com/docs/9.x/artisan), both are extensible. NocoBase CLI is based on [commander](https://www.npmjs.com/ package/commander) implementation, which extends Command like this - -```ts -export class MyPlugin extends Plugin { - load() { - this.app - .command('echo') - .option('--v, --version'); - .action(async ([options]) => { - console.log('Hello World!'); - if (options.version) { - console.log('Current version:', app.getVersion()); - } - }); - } -} -``` - -This method defines the following command. - -```bash -yarn nocobase echo -# Hello World! -yarn nocobase echo -v -# Hello World! -# Current version: 0.8.0-alpha.1 -``` - -More API details can be found in the [Application.command()](/api/server/application#command) section. - -## Example - -### Defining a command for exporting collections - -If we want to export the data in the application's collections to a JSON file, we can define a subcommand as follows. - -```ts -import path from 'path'; -import * as fs from 'fs/promises'; - -class MyPlugin extends Plugin { - load() { - this.app - .command('export') - .option('-o, --output-dir') - .action(async (options, . .collections) => { - const { outputDir = path.join(process.env.PWD, 'storage') } = options; - await collections.reduce((promise, collection) => promise.then(async () => { - if (!this.db.hasCollection(collection)) { - console.warn('No such collection:', collection); - return; - } - - const repo = this.db.getRepository(collection); - const data = repo.find(); - await fs.writeFile(path.join(outputDir, `${collection}.json`), JSON.stringify(data), { mode: 0o644 }); - }), Promise.resolve()); - }); - } -} -``` - -After registering and activating the plugin call from the command line. - -```bash -mkdir -p . /storage/backups -yarn nocobase export -o . /storage/backups users -``` - -After execution, it will generate `. /storage/backups/users.json` file containing the data from the collections. - -## Summary - -The sample code covered in this chapter is integrated in the [packages/samples/command](https://github.com/nocobase/nocobase/tree/main/packages/samples/command) package and can be run directly locally to see the results. diff --git a/docs/en-US/development/server/events.md b/docs/en-US/development/server/events.md deleted file mode 100644 index 15e2a6a96..000000000 --- a/docs/en-US/development/server/events.md +++ /dev/null @@ -1,168 +0,0 @@ -# Events - -NocoBase provides a very large number of event listeners in the lifecycle of applications, plugins, and database, and these methods will only be executed when an event is triggered. - -## How to add event listeners? - -The registration of events is usually placed in afterAdd or beforeLoad - -```ts -export class MyPlugin extends Plugin { - // After the plugin is added, afterAdd() is executed with or without activation - afterAdd() { - this.app.on(); - this.db.on(); - } - - // beforeLoad() will only be executed after the plugin is activated - beforeLoad() { - this.app.on(); - this.db.on(); - } -} -``` - -### `db.on` - -Database related events are related to Collection configuration, CRUD of Repository, including: - -- 'beforeSync' / 'afterSync' -- 'beforeValidate' / 'afterValidate' -- 'beforeCreate' / 'afterCreate' -- 'beforeUpdate' / 'afterUpdate' -- 'beforeSave' / 'afterSave' -- 'beforeDestroy' / 'afterDestroy' -- 'afterCreateWithAssociations' -- 'afterUpdateWithAssociations' -- 'afterSaveWithAssociations' -- 'beforeDefineCollection' -- 'afterDefineCollection' -- 'beforeRemoveCollection' / 'afterRemoveCollection' - -See [Database API](/api/database) for more details. - -### `app.on()` - -The app's events are related to the application's lifecycle, and the relevant events are: - -- 'beforeLoad' / 'afterLoad' -- 'beforeInstall' / 'afterInstall' -- 'beforeUpgrade' / 'afterUpgrade' -- 'beforeStart' / 'afterStart' -- 'beforeStop' / 'afterStop' -- 'beforeDestroy' / 'afterDestroy' - -Refer to [Application API](/api/server/application#Events) for more details. - -## Example - -Let's continue with a simple online store as an example, the related collections modeling can be reviewed in the [Collections and Fields](/development/) section for examples. - -### Deducting product inventory after creating an order - -Usually we have different collections for products and orders. The problem of overselling can be solved by subtracting the inventory of the item after the customer has placed the order. At this point we can define the corresponding event for the action of Creating Order and solve the inventory modification problem at this time together with: - -```ts -class ShopPlugin extends Plugin { - beforeLoad() { - this.db.on('orders.afterCreate', async (order, options) => { - const product = await order.getProduct({ - transaction: options.transaction - }); - - await product.update({ - inventory: product.inventory - order.quantity - }, { - transaction: options.transaction - }); - }); - } -} -``` - -Since the default Sequelize event carries information about the transaction, we can use transaction directly to ensure that both data actions are performed in the same transaction. - -Similarly, you can change the order status to shipped after creating the shipping record: ```ts - -```ts -class ShopPlugin extends Plugin { - load() { - this.db.on('deliveries.afterCreate', async (delivery, options) => { - const orderRepo = this.db.getRepository('orders'); - await orderRepo.update({ - filterByTk: delivery.orderId, - value: { - status: 2 - } - transaction: options.transaction - }); - }); - } -} -``` - -### Timed tasks that exist alongside the application - -Without considering complex cases such as using workflow plugins, we can also implement a simple timed task mechanism via application-level events, and it can be bound to the application's process and stop when it exits. For example, if we want to scan all orders at regular intervals and automatically sign them after the sign-off time. - -```ts -class ShopPlugin extends Plugin { - timer = null; - orderReceiveExpires = 86400 * 7; - - checkOrder = async () => { - const expiredDate = new Date(Date.now() - this.orderReceiveExpires); - const deliveryRepo = this.db.getRepository('deliveries'); - const expiredDeliveries = await deliveryRepo.find({ - fields: ['id', 'orderId'], - filter: { - status: 0, - createdAt: { - $lt: expiredDate - } - } - }); - await deliveryRepo.update({ - filter: { - id: expiredDeliveries.map(item => item.get('id')), - }, - values: { - status: 1 - } - }); - const orderRepo = this.db.getRepository('orders'); - const [updated] = await orderRepo.update({ - filter: { - status: 2, - id: expiredDeliveries.map(item => item.get('orderId')) - }, - values: { - status: 3 - } - }); - - console.log('%d orders expired', updated); - }; - - load() { - this.app.on('beforeStart', () => { - // execute every minute - this.timer = setInterval(this.checkOrder, 1000 * 60); - }); - - this.app.on('beforeStop', () => { - clearInterval(this.timer); - this.timer = null; - }); - } -} -``` - -## Summary - -The above example gives us a basic understanding of what events do and the ways they can be used to extend. - -* Database related events -* Application related events - -The sample code covered in this chapter is integrated in the corresponding package [packages/samples/shop-events](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-events), which can be run directly in run locally to see the results. diff --git a/docs/en-US/development/server/i18n.md b/docs/en-US/development/server/i18n.md deleted file mode 100644 index e0b68cc59..000000000 --- a/docs/en-US/development/server/i18n.md +++ /dev/null @@ -1,134 +0,0 @@ -# Internationalization - -Internationalization in NocoBase is implemented based on [i18next](https://npmjs.com/package/i18next). - -## How to register a multilingual package? - -```ts -export class MyPlugin extends Plugin { - load() { - this.app.i18n.addResources('zh-CN', 'test', { - Hello: '你好', - World: '世界', - }); - this.app.i18n.addResources('en-US', 'test', { - Hello: 'Hello', - World: 'World', - }); - } -} -``` - -## Two i18n instances - -### app.i18n - -Global i18n instance, typically used in the CLI. - -```ts -app.i18n.t('World') // "世界" or "World" -``` - -### ctx.i18n - -CloneInstance of global i18n with a completely independent context for each request, typically used to respond to multilingual messages based on the client language. - -```ts -app.use(async (ctx, next) => { - ctx.body = `${ctx.i18n.t('Hello')} ${ctx.i18n.t('World')}`; - await next(); -}); -``` - -The client request parameters can be placed in the query string - -```bash -GET /?locale=en-US HTTP/1.1 -Host: localhost:13000 -``` - -or in the request headers - -```bash -GET / HTTP/1.1 -Host: localhost:13000 -X-Locale: en-US -``` - -## Suggested configuration - -With English text as the key and translation as the value, this has the advantage that even if multiple languages are missing, it will be displayed in English and will not cause reading barriers, e.g. - -```ts -i18n.addResources('zh-CN', 'your-namespace', { - 'Show dialog': '显示对话框', - 'Hide dialog': '隐藏对话框' -}); -``` - -To make it easier to manage multilingual files, it is recommended to create a `locale` folder in the plugin and place all the corresponding language files in it: - -```bash -|- /my-plugin - |- /src - |- /server - |- locale # Multi-language folder - |- en-cn.ts - |- en-US.ts -``` - -## Example - -### Server-side error alert - -For example, when a user places an order for a product in the store, if the product is not in stock, or not on the shelf, then the order interface should return the appropriate error when it is called. - -```ts -const namespace = 'shop'; - -export default class ShopPlugin extends Plugin { - async load() { - this.app.i18n.addResources('zh-CN', namespace, { - 'No such product': '商品不存在', - 'Product not on sale': '商品已下架', - 'Out of stock': '库存不足', - }); - - this.app.resource({ - name: 'orders', - actions: { - async create(ctx, next) { - const productRepo = ctx.db.getRepository('products'); - const product = await productRepo.findOne({ - filterByTk: ctx.action.params.values.productId - productId }); - - if (!product) { - return ctx.throw(404, ctx.t('No such product')); - } - - if (!product.enabled) { - return ctx.throw(400, ctx.t('Product not on sale')); - } - - if (!product.inventory) { - return ctx.throw(400, ctx.t('Out of stock')); - } - - const orderRepo = ctx.db.getRepository('orders'); - ctx.body = await orderRepo.create({ - values: { - productId: product.id, - quantity: 1, - totalPrice: product.price, - userId: ctx.state.currentUser.id - } - }); - - next(); - } - } - }); - } -} -``` diff --git a/docs/en-US/development/server/index.md b/docs/en-US/development/server/index.md deleted file mode 100644 index b220c425d..000000000 --- a/docs/en-US/development/server/index.md +++ /dev/null @@ -1,72 +0,0 @@ -# Overview - -The server-side for an initialized, empty plugin has the following directory structure. - -```bash -|- /my-plugin - |- /src - |- /server # Server-side code of the plugin - |- plugin.ts # Classes of the plugin - |- index.ts # server-side entry - |- server.d.ts - |- server.js -``` - -`plugin.ts` provides calls to various methods of the plugin lifecycle - -```ts -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class MyPlugin extends Plugin { - afterAdd() { - // After the plugin pm.add is registered. This is mainly used to place a listener for the app beforeLoad event - this.app.on('beforeLoad'); - } - beforeLoad() { - // Custom classes or methods - this.db.registerFieldTypes() - this.db.registerModels() - this.db.registerRepositories() - this.db.registerOperators() - // Event Listening - this.app.on(); - this.db.on(); - } - async load() { - // Define collection - this.db.collection(); - // Import collection - this.db.import(); - this.db.addMigrations(); - - // Define resource - this.resourcer.define(); - // resource action - this.resourcer.registerActions(); - - // Register middleware - this.resourcer.use(); - this.acl.use(); - this.app.use(); - - // Customize the multilingual package - this.app.i18n.addResources(); - // Customize command line - this.app.command(); - } - async install(options?: InstallOptions) { - // Logic for installing - } - async afterEnable() { - // After activation - } - async afterDisable() { - // After disable - } - async remove() { - // Logic for removing - } -} - -export default MyPlugin; -``` diff --git a/docs/en-US/development/server/middleware.md b/docs/en-US/development/server/middleware.md deleted file mode 100644 index 80f156b53..000000000 --- a/docs/en-US/development/server/middleware.md +++ /dev/null @@ -1,160 +0,0 @@ -# Middleware - -## How to register middleware? - -The registration method for middleware is usually written in the load method - -```ts -export class MyPlugin extends Plugin { - load() { - this.app.acl.use(); - this.app.resourcer.use(); - this.app.use(); - } -} -``` - -Notes. - -1. `app.acl.use()` Add a resource-permission-level middleware to be executed before permission determination -2. `app.resourcer.use()` Adds a resource-level middleware that is executed only when a defined resource is requested -3. `app.use()` Add an application-level middleware to be executed on every request - -## Onion Circle Model - -```ts -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(1); - await next(); - ctx.body.push(2); -}); - -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(3); - await next(); - ctx.body.push(4); -}); -``` - -Visit http://localhost:13000/api/hello to see that the browser responds with the following data - -```js -{"data": [1,3,4,2]} -``` - -## Built-in middlewares and execution order - -1. `cors` -2. `bodyParser` -3. `i18n` -4. `dataWrapping` -5. `db2resource` 6. -6. `restApi` 1. - 1. `parseToken` 2. - 2. `checkRole` - 3. `acl` 1. - 1. `acl.use()` Additional middleware added - 4. `resourcer.use()` Additional middleware added - 5. `action handler` -7. `app.use()` Additional middleware added - -You can also use `before` or `after` to insert the middleware into the location of one of the preceding `tag`, e.g. - -```ts -app.use(m1, { tag: 'restApi' }); -app.resourcer.use(m2, { tag: 'parseToken' }); -app.resourcer.use(m3, { tag: 'checkRole' }); -// m4 will come before m1 -app.use(m4, { before: 'restApi' }); -// m5 will be inserted between m2 and m3 -app.resourcer.use(m5, { after: 'parseToken', before: 'checkRole' }); -``` - -If no location is specifically specified, the order of execution of the added middlewares is - -1. middlewares added by `acl.use` will be executed first -2. then the ones added by `resourcer.use`, including the middleware handler and action handler -3. and finally the ones added by `app.use` - -```ts -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(1); - await next(); - ctx.body.push(2); -}); - -app.resourcer.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(3); - await next(); - ctx.body.push(4); -}); - -app.acl.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(5); - await next(); - ctx.body.push(6); -}); - -app.resourcer.define({ - name: 'test', - actions: { - async list(ctx, next) { - ctx.body = ctx.body || []; - ctx.body.push(7); - await next(); - ctx.body.push(8); - }, - }, -}); -``` - -Visit http://localhost:13000/api/hello to see that the browser responds with the data - -```js -{"data": [1,2]} -``` - -Visiting http://localhost:13000/api/test:list to see, the browser responds with the following data - -```js -{"data": [5,3,7,1,2,8,4,6]} -``` - -### Resource undefined, middlewares added by resourcer.use() will not be executed - -```ts -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(1); - await next(); - ctx.body.push(2); -}); - -app.resourcer.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(3); - await next(); - ctx.body.push(4); -}); -``` - -Visit http://localhost:13000/api/hello to see that the browser responds with the following data - -```js -{"data": [1,2]} -``` - -In the above example, the hello resource is not defined and will not enter the resourcer, so the middleware in the resourcer will not be executed - -## Middleware Usage - -TODO - -## Example - -- [samples/ratelimit](https://github.com/nocobase/nocobase/blob/main/packages/samples/ratelimit/) IP rate-limiting diff --git a/docs/en-US/development/server/migration.md b/docs/en-US/development/server/migration.md deleted file mode 100644 index eb7b3e341..000000000 --- a/docs/en-US/development/server/migration.md +++ /dev/null @@ -1,50 +0,0 @@ -# Database Migration - -There may be some incompatible changes during the plugin update iteration, these incompatible upgrade scripts can be handled by writing migration files. - -## How to add a migration file? - -```ts -export class MyPlugin extends Plugin { - load() { - // Load a single Migration file - this.db.addMigration(); - // Load multiple Migration files - this.db.addMigrations(); - } -} -``` - -API reference. - -- [db.addMigration()](/api/database#addmigration) -- [db.addMigrations()](/api/database#addmigrations) - -## When to execute? - -```bash -## When the app is upgraded, migrator.up() and db.sync() are executed -yarn nocobase upgrade -# Trigger migration separately -yarn nocobase migrator up -``` - -## When do I need to write a migration file? - -It is usually used to update the system configuration stored in the database during the upgrade process. If it is just a collection configuration change, you don't need to configure migration, just execute ``yarn nocobase db:sync`` to synchronize to the database. - -## Migration file - -```ts -import { Migration } from '@nocobase/server'; - -export default class CustomMigration extends Migration { - async up() { - // - } - - async down() { - // - } -} -``` diff --git a/docs/en-US/development/server/resources-actions.md b/docs/en-US/development/server/resources-actions.md deleted file mode 100644 index 1d79b0718..000000000 --- a/docs/en-US/development/server/resources-actions.md +++ /dev/null @@ -1,464 +0,0 @@ -# Resources and Actions - -In the web development world, you may have heard of the concept of RESTful, and NocoBase borrows this concept of resources to map various entities in the system, such as data in a database, a file in a file system or a service. However, NocoBase does not fully follow the RESTful conventions based on practical considerations, but rather extends the specifications from the [Google Cloud API Design Guide](https://cloud.google.com/apis/design) to fit more scenarios. - -## Basic concepts - -The same concept as resources in RESTful, which are externally available objects in the system that can be manipulated, such as data tables, files, and other custom objects. - -Actions refer to reading and writing to resources, usually for accessing data, creating data, updating data, deleting data, etc. NocoBase implements access to resources by defining actions, the core of which is actually a Koa-compatible middleware function for handling requests. - -### Automatic mapping of collections to resources - -NocoBase automatically maps collections to resources by default, and also provides a server-side data interface. So by default, as long as a collection is defined using `db.collection()`, you can access the data resources of this collection via NocoBase HTTP API. The name of the automatically generated resource is the same as the collection name, for example, the collection defined by `db.collection({ name: 'users' })` has the corresponding resource name `users`. - -Also, there are built-in common CRUD actions for these data resources, and built-in actions methods for associative data for relational data resources. - -The default actions for a simple data resource: - -* [`list`](/api/actions#list): Query the list of data in the collection -* [`get`](/api/actions#get): Query a single record in the collection -* [`create`](/api/actions#create): Create a single record to the collection -* [`update`](/api/actions#update): Update a single record on the collection -* [`destroy`](/api/actions#destroy): Delete a single record from the collection - -In addition to simple CRUD actions, relational resources have default relational actions: - -* [`add`](/api/actions#add): Add a association to the data -* [`remove`](/api/actions#remove): Removes an association from the data -* [`set`](/api/actions#set): Set the association to the data -* [`toggle`](/api/actions#toggle): Add or remove associations to data - -For example, to define an article collection and synchronize it to the database. - -```ts -app.db.collection({ - name: 'posts', - fields: [ - { type: 'string', name: 'title' } - ] -}); - -await app.db.sync(); -``` - -All CRUD methods for the `posts` data resource can then be called directly via the HTTP API: ```bash - -```bash -# create -curl -X POST -H "Content-Type: application/json" -d '{"title": "first"}' http://localhost:13000/api/posts:create -# list -curl http://localhost:13000/api/posts:list -# update -curl -X PUT -H "Content-Type: application/json" -d '{"title": "second"}' http://localhost:13000/api/posts:update -# destroy -curl -X DELETE http://localhost:13000/api/posts:destroy?filterByTk=1 -``` - -### Customize Actions - -It is also possible to extend specific resources with more actions when the default provided actions such as CRUD do not satisfy the business scenario. For example, additional processing of built-in actions, or the need to set default parameters. - -Custom actions for specific resources, such as overriding the `create` action in the article collection. - -```ts -// Equivalent to app.resourcer.registerActions() -// Register the create action method for article resources -app.actions({ - async ['posts:create'](ctx, next) { - const postRepo = ctx.db.getRepository('posts'); - await postRepo.create({ - values: { - ... . ctx.action.params.values, - // restrict the current user to be the creator of the post - userId: ctx.state.currentUserId - } - }); - - await next(); - } -}); -``` - -This adds a reasonable restriction in the business that users cannot create articles as other users. - -Custom operations for all global resources, such as adding `export` action to all collections. - -```ts -app.actions({ - // Add export method to all resources for exporting data - async export(ctx, next) { - const repo = ctx.db.getRepository(ctx.action.resource); - const results = await repo.find({ - filter: ctx.action.params.filter - }); - ctx.type = 'text/csv'; - // Splice to CSV format - ctx.body = results - .map(row => Object.keys(row) - .reduce((arr, col) => [... . arr, row[col]], []).join(',') - ).join('\n'); - - next(); - } -}); -``` - -The data in CSV format can then be exported as follows from the HTTP API. - -```bash -curl http://localhost:13000/api/:export -``` - -### Action parameters - -Once the client's request reaches the server, the relevant request parameters are parsed by rule and placed on the request's `ctx.action.params` object. there are three main sources for Action parameters. - -1. default parameters at the time of Action definition -2. carried by the client request -3. other middleware preprocessing - -The parameters from these three parts are combined in this order and eventually passed into the action's execution function before being processed by the real action handler. This is also true in multiple middleware, where the parameters from the previous middleware are continued to be passed to the next middleware with `ctx`. - -The parameters available for built-in actions can be found in the [@nocobase/actions](/api/actions) package. Except for custom actions, client requests mainly use these parameters, and custom actions can be extended with the required parameters according to business requirements. - -Middleware preprocessing mainly uses the `ctx.action.mergeParams()` method and has different merge strategies depending on the parameter types, see also the [mergeParams()](/api/resourcer/action#mergeparams) method for details. - -The default parameters of the built-in Action can only be executed with the `mergeParams()` method for each parameter's default policy when merging, in order to achieve the purpose of limiting certain operations on the server side. For example - -```ts -app.resource({ - name: 'posts', - actions: { - create: { - whitelist: ['title', 'content'], - blacklist: ['createdAt', 'createdById'], - } - } -}); -``` - -The above defines the `create` action for the `posts` resource, where `whitelist` and `blacklist` are whitelisted and blacklisted respectively for the `values` parameter, i.e. only the `title` and `content` fields in the `values` parameter are allowed, and the ` createdAt` and `createdById` fields in the `values` parameter are disabled. - -### Custom resources - -Data-based resources are also divided into standalone resources and association resources. - -* Standalone resources: `` -* Association resources: `. ` - -```ts -// Equivalent to app.resourcer.define() - -// Define article resources -app.resource({ - name: 'posts' -}); - -// Define the article's author resource -app.resource({ - name: 'posts.user' -}); - -// Define the article's comment resource -app.resource({ - name: 'posts.coments' -}); -``` - -The cases where customization is needed are mainly for non-database table-like resources, such as in-memory data, proxy interfaces for other services, etc., and for cases where specific actions need to be defined for existing table-like resources. - -For example, to define a database-independent resource that sends a notification action. - -```ts -app.resource({ - name: 'notifications', - actions: { - async send(ctx, next) { - await someProvider.send(ctx.request.body); - next(); - } - } -}); -``` - -Then it can be accessed in the HTTP API as follows - -```bash -curl -X POST -d '{"title": "Hello", "to": "hello@nocobase.com"}' 'http://localhost:13000/api/notifications:send' -``` - -## Example - -Let's continue the simple store scenario from the previous [Collections and fields example](/development/server/collections-fields#Example) to further understand the concepts related to resources and actions. It is assumed here that we base further resource and action definitions on the previous collection's example, so the definition of collection is not repeated here. - -As long as the corresponding collections are defined, we can use default actions directly for data resources such as products, orders, etc. in order to complete the most basic CRUD scenarios. - -### Overriding default actions - - -Sometimes, there are operations that are not simply for a single record, or the parameters of the default actions need to have some control, we can override the default actions. For example, when we create an order, instead of the client submitting `userId` to represent the ownership of the order, the server should determine the ownership of the order based on the currently logged-in user, so we can override the default `create` action. For simple extensions, we write directly in the main class of the plugin. - -```ts -import { Plugin } from '@nocobase/server'; -import actions from '@nocobase/actions'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - async create(ctx, next) { - ctx.action.mergeParams({ - values: { - userId: ctx.state.user.id - } - }); - - return actions.create(ctx, next); - } - } - }); - } -} -``` - -In this way, we override the default `create` action for order data resources during plugin loading, but the default logic is still called after modifying the action parameters, so there is no need to write it yourself. The `mergeParams()` method that modifies the submit parameters is useful for the built-in default actions, which we will describe later. - -### Custom actions for collection resources - -When the built-in actions do not meet the business needs, we can extend the functionality of the resource by customizing the actions. For example, usually an order will have many statuses, if we design the values of the `status` field as a series of enumerated values. - -* `-1`: cancelled -* `0`: order placed, not paid -* `1`: Paid, not shipped -* `2`: shipped, not signed -* `3`: signed, order completed - -Then we can realize the change of order status through custom actions, such as a shipping action on the order. Although the simple case can be realized through the `update` action, if there are more complicated cases such as payment and signing, using only `update` will cause the problem of unclear semantics and confusing parameters, so we can realize it through custom actions. - -First we add a definition of a shipping information collection, saved to `collections/deliveries.ts`. - -```ts -export default { - name: 'deliveries', - fields: [ - { - type: 'belongsTo', - name: 'order' - }, - { - type: 'string', - name: 'provider' - }, - { - type: 'string', - name: 'trackingNumber' - }, - { - type: 'integer', - name: 'status' - } - ] -}; -``` - -Also extend the orders collection with an associated field for shipping information (`collections/orders.ts`). - -```ts -export default { - name: 'orders', - fields: [ - // ... . other fields - { - type: 'hasOne', - name: 'delivery' - } - ] -}; -``` - -Then we add the corresponding action definition in the main class of the plugin: - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - async deliver(ctx, next) { - const { filterByTk } = ctx.action.params; - const orderRepo = ctx.db.getRepository('orders'); - - const [order] = await orderRepo.update({ - filterByTk, - values: { - status: 2, - delivery: { - ... . ctx.action.params.values, - status: 0 - status: 0 } - } - }); - - ctx.body = order; - - next(); - } - } - }); - } -} -``` - -The Repository uses the data repository class of collection, from which most of the data reading and writing actions are done, see the [Repository API](/api/database/repository) section for details. - -Once defined, we can call the "ship" action from the client via the HTTP API: - -```bash -curl \ - -X POST \ - -H 'Content-Type: application/json' \ - -d '{"provider": "SF", "trackingNumber": "SF1234567890"}' \ - '/api/orders:deliver/' -``` - -Similarly, we can define more similar actions, such as payment, signup, etc. - -### Parameter merging - -Suppose we want to allow users to query their own and only their own orders, and we need to restrict them from querying cancelled orders, then we can define with the default parameters of the action. - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - // default parameters for list actions - list: { - filter: { - // Filter operator extended by the users plugin - $isCurrentUser: true, - status: { - $ne: -1 - } - }, - fields: ['id', 'status', 'createdAt', 'updatedAt'] - } - } - }); - } -} -``` - -When the user queries from the client, additional parameters can also be added to the requested URL, such as - -```bash -curl 'http://localhost:13000/api/orders:list?productId=1&fields=id,status,quantity,totalPrice&appends=product' -``` - -The actual query criteria will be combined as - -```json -{ - "filter": { - "$and": { - "$isCurrentUser": true, - "status": { - "$ne": -1 - }, - "productId": 1 - } - }, - "fields": ["id", "status", "quantity", "totalPrice", "createdAt", "updatedAt"], - "appends": ["product"] -} -``` - -and get the expected query results. - -Alternatively, if we need to restrict the interface for creating orders to fields such as order number (`id`), total price (`totalPrice`), etc. that cannot be submitted by the client, this can be controlled by defining default parameters for the `create` action as follows - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - create: { - blacklist: ['id', 'totalPrice', 'status', 'createdAt', 'updatedAt'], - values: { - status: 0 - } - } - } - }); - } -} -``` - -This way, even if the client intentionally submits these fields, they will be filtered out and will not exist in the `ctx.action.params` parameter set. - -If there are more complex restrictions, such as only being able to place an order if the item is on the shelf and in stock, this can be achieved by configuring the middleware to - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - create: { - middlewares: [ - async (ctx, next) => { - const { productId } = ctx.action.params.values; - - const product = await ctx.db.getRepository('products').findOne({ - filterByTk: productId, - filter: { - enabled: true, - inventory: { - $gt: 0 - } - } - }); - - if (!product) { - return ctx.throw(404); - } - - await next(); - } - ] - } - } - }); - } -} -``` - -Putting some of the business logic (especially the preprocessing) into middleware makes our code clearer and easier to maintain. - -## Summary - -With the above example we have described how to define resources and related actions. To review this chapter. - -* Automatic mapping of collections to resources -* Built-in default resource actions -* Custom actions on resources -* Parameter merging order and strategy for operations - -The code covered in this chapter is included in a complete sample package [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions ), which can be run directly locally to see the results. diff --git a/docs/en-US/development/server/test.md b/docs/en-US/development/server/test.md deleted file mode 100644 index de5fe7dc7..000000000 --- a/docs/en-US/development/server/test.md +++ /dev/null @@ -1,162 +0,0 @@ -# Testing - -The tests are based on the [Jest](https://jestjs.io/) framework. To facilitate writing tests, `mockDatabase()` and `mockServer()` are provided for database and server-side application testing. - -## `mockDatabase()` - -provides a fully isolated db testing environment by default - -```ts -import { mockDatabase } from '@nocobase/test'; - -describe('my db suite', () => { - let db; - - beforeEach(async () => { - db = mockDatabase(); - db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] - }); - await db.sync(); - }); - - afterEach(async () => { - await db.close(); - }); - - test('my case', async () => { - const repository = db.getRepository('posts'); - const post = await repository.create({ - values: { - title: 'hello' - } - }); - - expect(post.get('title')).toEqual('hello'); - }); -}); -``` - -## `mockServer()` - -provides a mock server-side application instance, corresponding to app.db as a `mockDatabase()` instance, and also provides a convenient `app.agent()` for testing the HTTP API, and wraps `app.agent().resource()` for the Resource Action of NocoBase for testing the Action. - -```ts -import { mockServer } from '@nocobase/test'; - -class MyPlugin extends Plugin { - -} - -describe('my suite', () => { - let app; - let agent; - - beforeEach(async () => { - app = mockServer(); - agent = app.agent(); - // Add the plugins to be registered - app.plugin(MyPlugin, { name: 'my-plugin' }); - // Load the configuration - app.load(); - // Clear the database and install - app.install({ clean: true }); - }); - - afterEach(async () => { - await app.destroy(); - }); - - test('my case', async () => { - await agent.resource('posts').create({ - values: { - title: 'hello' - } - }); - await agent.get('/users:check').set({ Authorization: 'Bearer abc' }); - }); -}); -``` - -## Example - -Let's write a test of the plugin using in previous chapter [Resources and Actions](resources-actions) as an example. - -```ts -import { mockServer } from '@nocobase/test'; -import Plugin from '... /... /src/server'; - -describe('shop actions', () => { - let app; - let agent; - let db; - - beforeEach(async () => { - app = mockServer(); - app.plugin(Plugin); - agent = app.agent(); - db = app.db; - - await app.load(); - await db.sync(); - }); - - afterEach(async () => { - await app.destroy(); - }); - - test('product order case', async () => { - const { body: product } = await agent.resource('products').create({ - values: { - title: 'iPhone 14 Pro', - price: 7999, - enabled: true, - inventory: 1 - } - }); - expect(product.data.price).toEqual(7999); - - const { body: order } = await agent.resource('orders').create({ - values: { - productId: product.data.id - } - }); - expect(order.data.totalPrice).toEqual(7999); - expect(order.data.status).toEqual(0); - - const { body: deliveredOrder } = await agent.resource('orders').deliver({ - filterByTk: order.data.id, - values: { - provider: 'SF', - trackingNumber: '123456789' - } - }); - expect(deliveredOrder.data.status).toBe(2); - expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789'); - }); -}); -``` - -Once finished, allow the test command on the command line: - -```bash -yarn test packages/samples/shop-actions -``` - -This test will verify that: - -1. products can be created successfully. -2. orders can be created successfully. -3. orders can be shipped successfully. - -Of course this is just a minimal example, not perfect business-wise, but as an example it can already illustrate the whole testing process. - -## Summary - -The sample code covered in this chapter is integrated in the corresponding package [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions), which can be run directly locally to see the results. diff --git a/docs/en-US/development/your-fisrt-plugin.md b/docs/en-US/development/your-fisrt-plugin.md deleted file mode 100644 index d09e939e0..000000000 --- a/docs/en-US/development/your-fisrt-plugin.md +++ /dev/null @@ -1,136 +0,0 @@ -# Develop the first plugin - -Before start, you need to install NocoBase:. - -- [create-nocobase-app installation](/welcome/getting-started/installation/create-nocobase-app) -- [Git source installation](/welcome/getting-started/installation/git-clone) - -Once NocoBase is installed, we can start our plugin development journey. - -## Create a plugin - -First, you can quickly create an empty plugin via the CLI with the following command. - -```bash -yarn pm create @my-project/plugin-hello -``` - -The directory where the plugin located is `packages/plugins/@my-project/plugin-hello` and the plugin directory structure is - -```bash -|- /hello - |- /src - |- /client # plugin client code - |- /server # plugin server code - |- client.d.ts - |- client.js - |- package.json # plugin package information - |- server.d.ts - |- server.js -``` - -Visit the Plugin Manager to view the plugin you just added, the default address is http://localhost:13000/admin/pm/list/local/ - - - -If the plugin is not shown in the plugin manager, you can add it manually with the `pm add` command. - -```bash -yarn pm add @my-project/plugin-hello -``` - -## Code the plugin - -Look at the `packages/plugins/@my-project/plugin-hello/src/server/plugin.ts` file and modify it to - -```ts -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class PluginHelloServer extends Plugin { - afterAdd() {} - - beforeLoad() {} - - async load() { - this.db.collection({ - name: 'hello', - fields: [{ type: 'string', name: 'name' }], - }); - this.app.acl.allow('hello', '*'); - } - - async install(options?: InstallOptions) {} - - async afterEnable() {} - - async afterDisable() {} - - async remove() {} -} - -export default PluginHelloServer; -``` - -## Activate the plugin - -**Operated by command** - -```bash -yarn pm enable @my-project/plugin-hello -``` - -**Operated by UI** - -Visit the Plugin Manager to view the plugin you just added and click enable. -The Plugin Manager page defaults to http://localhost:13000/admin/pm/list/local/ - - - -Node: When the plugin is activated, the hello collection that you just configured is automatically created. - -## Debug the Plugin - -If the app is not started, you need to start the app first - -```bash -# for development -yarn dev - -# for production -yarn build -yarn start -``` - -Insert data into the hello collection of the plugin - -```bash -curl --location --request POST 'http://localhost:13000/api/hello:create' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "Hello world" -}' -``` - -View the data - -```bash -curl --location --request GET 'http://localhost:13000/api/hello:list' -``` - -## Build the plugin - -```bash -yarn build plugins/@my-project/plugin-hello --tar - -# step-by-step -yarn build plugins/@my-project/plugin-hello -yarn nocobase tar plugins/@my-project/plugin-hello -``` - -The default saved path for the plugin tar is `storage/tar/@my-project/plugin-hello.tar.gz` - -## Upload to other NocoBase applications - -Only supported in v0.14 and above - - diff --git a/docs/en-US/index.md b/docs/en-US/index.md deleted file mode 100644 index 49a1e7610..000000000 --- a/docs/en-US/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Index - - diff --git a/docs/en-US/manual/blocks-guide/charts.md b/docs/en-US/manual/blocks-guide/charts.md deleted file mode 100755 index bd927e0e5..000000000 --- a/docs/en-US/manual/blocks-guide/charts.md +++ /dev/null @@ -1,210 +0,0 @@ -# Charts - -Currently, chart blocks in NocoBase need to be implemented via a configuration file or by writing code. The chart library uses [g2plot](https://g2plot.antv.vision/en/examples), which theoretically supports all charts on [https://g2plot.antv.vision/en/examples](https://g2plot.antv.vision/en/examples). The currently configurable charts include - -- Column charts -- Bar charts -- Line charts -- Pie charts -- Area charts - -## Add and edit charts - -![chart-edit.gif](./charts/chart-edit.gif) - -## Chart Configuration - -The initial chart configuration is static JSON data - -```json -{ - "data": [ - { - "type": "furniture & appliances", - "sales": 38 - }, - { - "type": "食品油副食", - "sales": 52 - }, - { - "type": "Fresh Fruit", - "sales": 61 - }, - { - "type": "美容洗护", - "sales": 145 - }, - { - "type": "Maternity & Baby Products", - "sales": 48 - }, - { - "type": "Imported Food", - "sales": 38 - }, - { - "type": "Food & Beverage", - "sales": 38 - }, - { - "type": "Home Cleaning", - "sales": 38 - } - ], - "xField": "type", - "yField": "sales", - "label": { - "position": "middle", - "style": { - "fill": "#FFFFFF", - "opacity": 0.6 - } - }, - "xAxis": { - "label": { - "autoHide": true, - "autoRotate": false - } - }, - "meta": { - "type": { - "alias": "category" - }, - "sales": { - "alias": "sales" - } - } -} - -``` - -Data supports expression, NocoBase has a built-in `requestChartData(config)` function for custom chart data requests. Parameters are described in: [https://github.com/axios/axios#request-config](https://github.com/axios/axios#request-config) - -Example. - -```json -{ - "data": "{{requestChartData({ url: 'collectionName:getColumnChartData' })}}", - "xField": "type", - "yField": "sales", - "label": { - "position": "middle", - "style": { - "fill": "#FFFFFF", - "opacity": 0.6 - } - }, - "xAxis": { - "label": { - "autoHide": true, - "autoRotate": false - } - }, - "meta": { - "type": { - "alias": "category" - }, - "sales": { - "alias": "sales" - } - } -} - -``` - -HTTP API example. - -```bash -GET /api/collectionName:getColumnChartData - -Response Body -{ - "data": [ - { - "type": "furniture & appliances", - "sales": 38 - }, - { - "type": "食品油副食", - "sales": 52 - }, - { - "type": "Fresh Fruit", - "sales": 61 - }, - { - "type": "美容洗护", - "sales": 145 - }, - { - "type": "Maternity & Baby Products", - "sales": 48 - }, - { - "type": "Imported Food", - "sales": 38 - }, - { - "type": "Food & Beverage", - "sales": 38 - }, - { - "type": "Home Cleaning", - "sales": 38 - } - ] -} - -``` - -## Server-side implementation - -Add a custom getColumnChartData method to the data table named collectionName. - -```js -app.resourcer.registerActionHandlers({ - 'collectionName:getColumnChartData': (ctx, next) => { - // The data to be output - ctx.body = []; - await next(); - }, -}); - -``` - -## Video - -### Static data - - - -### Dynamic data - - -### More charts - -Theoretically supports all charts on [https://g2plot.antv.vision/en/examples](https://g2plot.antv.vision/en/examples) - - - -## JS Expressions - -Syntax - -```js -{ - "key1": "{{ js expression }}" -} -``` - - - diff --git a/docs/en-US/manual/blocks-guide/charts/chart-edit.gif b/docs/en-US/manual/blocks-guide/charts/chart-edit.gif deleted file mode 100755 index 86a3540a3..000000000 Binary files a/docs/en-US/manual/blocks-guide/charts/chart-edit.gif and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/a-b-c.md b/docs/en-US/manual/core-concepts/a-b-c.md deleted file mode 100755 index d1b6e9951..000000000 --- a/docs/en-US/manual/core-concepts/a-b-c.md +++ /dev/null @@ -1,21 +0,0 @@ -# A·B·C - -At the no-code level, the core concept of NocoBase can be summarized as `A·B·C`. - -`A·B·C` stands for `Action·Block·Collection`. We design data structure by `Collection`, organize and display data by `Block`, and interact with data by `Action`. - -## **Separate "data structure" and "user interface"** - -When defining data, focus on defining data; when defining views, focus on defining views. - -Abstract the business by defining the data; then define blocks to organize the content to present the data in the way you want. - -## **One Data table, Many Presentations** - -Abstract a unified data model for the business, and then with blocks you can build a variety of presentations for the same data table for different scenarios, different roles, and different combinations. - -## **Driven by Action** - -`Collection`defines the structure of the data, and the `Block`organize the presentation of the data. So, what drives data interactions and changes? The answer is `Action`. - -`Block`present the data to the user, and `Action`send the user's instructions to the server to complete the interaction or change of the data. diff --git a/docs/en-US/manual/core-concepts/actions.md b/docs/en-US/manual/core-concepts/actions.md deleted file mode 100755 index b4551a486..000000000 --- a/docs/en-US/manual/core-concepts/actions.md +++ /dev/null @@ -1,31 +0,0 @@ -# Actions - -An `action` is a collection of actions that accomplish a specific goal. An `action` is used in NocoBase to process data or communicate with the server. Actions are usually triggered by clicking a button. - -## Action types - -NocoBase currently supports more than 10 types of actions, and more can be supported in the future by way of plugins. - -| Name | Description | -| --- | --- | -| Filter | Specifies the range of data to be displayed | -| Add | Opens a popup window for adding new data, which usually contains a form block. | -| View | Opens a popup window to view the specified data, which usually contains a detail block. | -| Edit | Opens a popup window to modify the specified data, which usually contains a form block. | -| Delete | Opens a dialog box to delete the specified data, and then delete it after confirmation. | -| Export | Exports data to Excel, often combined with filtering. | -| Print | Opens a browser print window to print the specified data, often combined with a detail block. | -| Submit | Submit the data of the specified form block to the server. | -| Refresh | Refreshes the data in the current block. | -| Import | Import data from an Excel template | -| Bulk Edit | Batch Edit Data | -| Bulk Update | Batch Update Data | -| Popup | Open a popup window or drawer in which you can place blocks | -| Update record | Automatically update specified fields when clicked | -| Customize request | Send requests to third parties | - -## Configure actions - -In UI Editor mode, move the mouse over an action button and the configuration items supported by that action will appear in the upper right corner. For example, for the filter action. - -![action-config-5.jpg](./actions/action-config-5.jpg) \ No newline at end of file diff --git a/docs/en-US/manual/core-concepts/actions/action-config-5.jpg b/docs/en-US/manual/core-concepts/actions/action-config-5.jpg deleted file mode 100755 index b9429826f..000000000 Binary files a/docs/en-US/manual/core-concepts/actions/action-config-5.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/blocks.md b/docs/en-US/manual/core-concepts/blocks.md deleted file mode 100755 index ed694e978..000000000 --- a/docs/en-US/manual/core-concepts/blocks.md +++ /dev/null @@ -1,86 +0,0 @@ -# Blocks - -Blocks are views used to display and manipulate data. In NocoBase, pages, popups and drawers are considered as containers of blocks, and the container is like a canvas in which various blocks can be placed. - -Thanks to NocoBase's design of separating data and view, pages carry data through blocks and organize and manage data in different forms according to different block types. - -## Structure of blocks - -A block consists of three parts. - -1. content area: the body of the block -2. action area: various action buttons can be placed to manipulate the block data -3. configuration area: buttons for operating the block configuration - -![6.block.jpg](./blocks/6.block.jpg) - -## Block types - -![add-block.jpg](./blocks/add-block.jpg) - -NocoBase currently has 10+ types of blocks built in, more can be supported in the future by way of plugins. - -- **Data blocks:** blocks designed for organizing data. - - **Table:** A block that present multiple data in a table, either a single collection or multiple collections that are related to each other. - - **Form:** A block for entering or editing data in a form, either for a particular collection or for multiple collections that are related to each other in a unified way. - - **Details:** A block to display a specific record, either for a particular collection or for multiple collection that are related to each other. - - **Calendar:** A block that displays multiple records in the form of a calendar, suitable for certain data with important characteristics in terms of date. - - **Kanban:** A block that displays multiple data in the form of a Kanban board, suitable for managing production processes. -- **Chart blocks:** Blocks designed for graphical presentation of statistical data. Currently supports: bar graphs, bar charts, line graphs, pie charts, area charts, etc. -- **Other blocks:** Blocks designed to display special data. - - **Markdown:** Text content written in Markdown. - - **Audit Log**: Show the change records of all data in a collection, including new, edit and delete. - -## Add block - -Enter the UI Editor mode and click the Add block button on the page and in the pop-up window to add the block. The options are divided into 4 steps. - -1. Select block type: Currently available block types include Table, Form, Details, Calendar, Kanban, Markdown -2. Select Collection: All collections will be listed here -3. Choose the creation method: create a blank block, or duplicate a block template , or reference a block template -4. Select Template: If you selected Create from Template in step 3, select the template in step 4 - -![6.block-add.jpg](./blocks/6.block-add.jpg) - -## Configure Blocks - -The configuration of blocks consists of three elements. - -- Configure block content -- Configure block actions -- Configure block properties - -### Configure block content - -Take the table block as an example, the content of the block is the columns to be displayed in the table. Click Configure columns to configure the columns to be displayed. - -![6.block-content.gif](./blocks/6.block-content.gif) - -### Configure block actions - -Take table block as an example, there are filter, add, delete, view, edit, customize and other actions available. Click the Configure actions button to configure the actions. Each of the action buttons can be configured for their own properties. - -![6.block-content.gif](./blocks/6.block-content%201.gif) - -### Configure block properties - -Move the cursor to the upper right corner of the block and you will see the block configuration button. Using the table block as an example, the following properties can be configured. - -- Drag & drop sorting -- Set the data scope -- Set default sorting rules -- Records per page - -![6.collection-setting.gif](./blocks/6.collection-setting.gif) - -## Adjust the layout - -It is possible to put either just one block or multiple blocks in combination within the page. You can adjust the position and width of the blocks by dragging and dropping them. - -![block-drag.gif](./blocks/block-drag.gif) - -## Block templates - -You can save a block as a template, which can be copied or referenced later. - -For example, if a form is used for both adding and editing data, then you can save this form as a template and reference it in the Add Data and Edit Data blocks. \ No newline at end of file diff --git a/docs/en-US/manual/core-concepts/blocks/6.block-add.jpg b/docs/en-US/manual/core-concepts/blocks/6.block-add.jpg deleted file mode 100755 index cd609413f..000000000 Binary files a/docs/en-US/manual/core-concepts/blocks/6.block-add.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/blocks/6.block-content 1.gif b/docs/en-US/manual/core-concepts/blocks/6.block-content 1.gif deleted file mode 100755 index 8f79be9fa..000000000 Binary files a/docs/en-US/manual/core-concepts/blocks/6.block-content 1.gif and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/blocks/6.block-content.gif b/docs/en-US/manual/core-concepts/blocks/6.block-content.gif deleted file mode 100755 index 8f79be9fa..000000000 Binary files a/docs/en-US/manual/core-concepts/blocks/6.block-content.gif and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/blocks/6.block.jpg b/docs/en-US/manual/core-concepts/blocks/6.block.jpg deleted file mode 100755 index b609af5a0..000000000 Binary files a/docs/en-US/manual/core-concepts/blocks/6.block.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/blocks/6.collection-setting.gif b/docs/en-US/manual/core-concepts/blocks/6.collection-setting.gif deleted file mode 100755 index d969e34e8..000000000 Binary files a/docs/en-US/manual/core-concepts/blocks/6.collection-setting.gif and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/blocks/add-block.jpg b/docs/en-US/manual/core-concepts/blocks/add-block.jpg deleted file mode 100755 index 2b463f2cf..000000000 Binary files a/docs/en-US/manual/core-concepts/blocks/add-block.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/blocks/block-drag.gif b/docs/en-US/manual/core-concepts/blocks/block-drag.gif deleted file mode 100755 index 69563d0dc..000000000 Binary files a/docs/en-US/manual/core-concepts/blocks/block-drag.gif and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/collections.md b/docs/en-US/manual/core-concepts/collections.md deleted file mode 100755 index 3defda4cb..000000000 --- a/docs/en-US/manual/core-concepts/collections.md +++ /dev/null @@ -1,60 +0,0 @@ -# Collections - -Before developing a system, we usually have to abstract the business and build a data model. It's called collections in NocoBase. Collections in NocoBase consists of fields (columns) and records (rows). The concept of a collection is similar to the concept of a data table in a relational database, but the concept of fields is slightly different. - -For example, in a collection describing an order, each column contains information about a specific attribute of the order, such as the delivery address, while each row contains all the information about a specific order, such as order number, customer name, phone number, delivery address, etc. - -## Separate "data structure" and "user interface" - -NocoBase's `Data` and `View` are separated, managed and presented by `Collections` and `Blocks` respectively. - -This means that, - -- you can create **one collection** and design **one set of interfaces** for it, to enable the presentation and manipulation of data. -- You can also create **one collection** and design **many sets of interfaces** for it, for the presentation and manipulation of data in different scenarios or roles. -- You can also create **multiple collections** and then design **one set of interfaces** for them to display and manipulate multiple data tables at the same time. -- You can even create **multiple collections** and then design **multiple sets of interfaces** for them, each of which can operate on multiple data tables and perform unique functions. - -Simply put, the separation of data and interfaces makes **the organization and management of data more flexible**, and how you present the data depends on how you configure the interfaces. - -## Field Types - -NocoBase currently supports the following dozens of fields, and more can be supported in the future by way of plug-ins. - -| Name | Type | -| --- | --- | -| single-line text | basic type | -| Icon | Basic Type | -| Multi-line text | Basic type | -| Password | Basic type | -| Mobile Number | Basic Type | -| Number | Basic Type | -| Integer | Basic Type | -| Email | Basic Type | -| Percent | Basic Type | -| Drop-down menu (single selection) | Select type | -| Drop-down menu (multiple choice) | Select type | -| China Administrative Region | Select Type | -| Check | Select Type | -| Radio | Select Type | -| Checkbox | Select Type | -| Link to | Relationship Type | -| One-to-One (has one) | Relationship Type | -| One-to-One (belongs to) | Relationship Type | -| One-to-many | Relationship Type | -| Many-to-one | relationship type | -| Many-to-many | relationship type | -| Formula | advanced type | -| AutoCoding | Advanced Types | -| JSON | Advanced Types | -| Markdown | Multimedia | -| Rich Text | Multimedia | -| Attachments | Multimedia | -| Date | Date & Time | -| Time | Date & Time | -| ID | System Information | -| Created by | System Information | -| Date Created | System Information | -| Last Modified By | System Information | -| Last Modified Date | System Information | -| Formula | Advanced Type | diff --git a/docs/en-US/manual/core-concepts/containers.md b/docs/en-US/manual/core-concepts/containers.md deleted file mode 100755 index e89102fb8..000000000 --- a/docs/en-US/manual/core-concepts/containers.md +++ /dev/null @@ -1,23 +0,0 @@ -# Containers - -Pages, popups, and drawers are considered as containers of blocks in NocoBase. The container is like a canvas in which various blocks can be placed. - -## Pages - -![container-page.jpg](./containers/container-page.jpg) - -## Popups - -![container-dialog.jpg](./containers/container-dialog.jpg) - -## Drawers - -![container-drawer.jpg](./containers/container-drawer.jpg) - -## Tabs are supported inside containers - -Multiple tabs can be added within popups, drawers, and pages. Add different blocks to each tab to display different content and actions. For example, in a customer information popup, add 3 tabs to display customer's personal information, order history, customer reviews. - -![7.tabs.gif](./containers/7.tabs.gif) - -![container-tab-2.jpg](./containers/container-tab-2.jpg) diff --git a/docs/en-US/manual/core-concepts/containers/7.tabs.gif b/docs/en-US/manual/core-concepts/containers/7.tabs.gif deleted file mode 100755 index df7c895aa..000000000 Binary files a/docs/en-US/manual/core-concepts/containers/7.tabs.gif and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/containers/container-dialog.jpg b/docs/en-US/manual/core-concepts/containers/container-dialog.jpg deleted file mode 100755 index b5984954d..000000000 Binary files a/docs/en-US/manual/core-concepts/containers/container-dialog.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/containers/container-drawer.jpg b/docs/en-US/manual/core-concepts/containers/container-drawer.jpg deleted file mode 100755 index b550ea29e..000000000 Binary files a/docs/en-US/manual/core-concepts/containers/container-drawer.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/containers/container-page.jpg b/docs/en-US/manual/core-concepts/containers/container-page.jpg deleted file mode 100755 index c765777ea..000000000 Binary files a/docs/en-US/manual/core-concepts/containers/container-page.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/containers/container-tab-2.jpg b/docs/en-US/manual/core-concepts/containers/container-tab-2.jpg deleted file mode 100755 index 07b1a982b..000000000 Binary files a/docs/en-US/manual/core-concepts/containers/container-tab-2.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/menus.md b/docs/en-US/manual/core-concepts/menus.md deleted file mode 100755 index b60130b1c..000000000 --- a/docs/en-US/manual/core-concepts/menus.md +++ /dev/null @@ -1,54 +0,0 @@ -# Menus - -The default menu location for NocoBase is at the top and on the left. The top is the first level menu and the left side is the menu for the second level and lower levels. - -Three types of menu items are supported. - -- Menu groups -- Pages -- Links - -Once you enter the UI Editor mode, you can add and edit menus, as well as sort menu items. - -NocoBase currently supports three types of menu items. - -- Page: jumps to the content page the menu item is associated. -- Group: grouping menu items and placing similar menus in a uniform location. -- Link: jumps to a specified URL. - -Take the warehouse system as an example, if you have storage management in your business, storage management contains in and out logs, inventory queries, jump to the ERP application storage and other functions. Then you can set the menu like this. - -``` -- Storage space management (grouping) - - Inventory query (page) - - Inbound and outbound log (page) - - Jump ERP application storage space (link) - -``` - -## Default position - -In NocoBase's built-in page templates, the menu appears at the top and on the left. - -![menu-position.jpg](./menus/menu-position.jpg) - -## Add Menu Item - -![5.menu-add.jpg](./menus/5.menu-add.jpg) - -Click Add menu item to select the type to add. Support infinite level submenu. - -## Configure and Sort - -Move the cursor over the menu item and the Sort and Configure buttons will appear in the upper right corner. Press and hold the Sort button to drag and drop the sorting. - -Configurations that are operable on menu items: - -- Edit -- Move to -- Insert before -- Insert after -- Insert Inner -- Delete - -![menu-move.gif](./menus/menu-move.gif) diff --git a/docs/en-US/manual/core-concepts/menus/5.menu-add.jpg b/docs/en-US/manual/core-concepts/menus/5.menu-add.jpg deleted file mode 100755 index da89a6f93..000000000 Binary files a/docs/en-US/manual/core-concepts/menus/5.menu-add.jpg and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/menus/menu-move.gif b/docs/en-US/manual/core-concepts/menus/menu-move.gif deleted file mode 100755 index 1e278d868..000000000 Binary files a/docs/en-US/manual/core-concepts/menus/menu-move.gif and /dev/null differ diff --git a/docs/en-US/manual/core-concepts/menus/menu-position.jpg b/docs/en-US/manual/core-concepts/menus/menu-position.jpg deleted file mode 100755 index 8e21a0a18..000000000 Binary files a/docs/en-US/manual/core-concepts/menus/menu-position.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/functional-zoning.md b/docs/en-US/manual/quick-start/functional-zoning.md deleted file mode 100755 index 79c4f4044..000000000 --- a/docs/en-US/manual/quick-start/functional-zoning.md +++ /dev/null @@ -1,9 +0,0 @@ -# Functional zoning - -NocoBase has a built-in layout template by default, and the interface of this layout template is divided into three main areas. - -1. Configuration entry area. Users with permissions can see the UI Editor, Collections & Fields, Roles and Permissions, block templates, Workflows, and other extended configuration options here. -2. Menu area. At the top is the first level menu, and on the left side are the menus for the second level and lower tiers. Each menu item can be configured as a menu group, page, or link. -3. Block container. This is the block container for the page, in which various blocks can be placed. - -![3.zone.jpg](./functional-zoning/3.zone.jpg) \ No newline at end of file diff --git a/docs/en-US/manual/quick-start/functional-zoning/3.zone.jpg b/docs/en-US/manual/quick-start/functional-zoning/3.zone.jpg deleted file mode 100755 index 9507d2c7e..000000000 Binary files a/docs/en-US/manual/quick-start/functional-zoning/3.zone.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/plugins/plugin-manager.jpg b/docs/en-US/manual/quick-start/plugins/plugin-manager.jpg deleted file mode 100755 index ff17fe5e1..000000000 Binary files a/docs/en-US/manual/quick-start/plugins/plugin-manager.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app.md b/docs/en-US/manual/quick-start/the-first-app.md deleted file mode 100755 index 89e358da4..000000000 --- a/docs/en-US/manual/quick-start/the-first-app.md +++ /dev/null @@ -1,91 +0,0 @@ -# The First APP - -Let's build an order management system using NocoBase. - -## 1. Create data collections and fields - -In this order management system, we need to have the information of `Customers`,`Products`,`Orders` which are interrelated with each other. We need to create 4 data tables and their fields as follows: - -- Customers - - Name - - Address - - Gender - - Phone - - *Orders* (All orders purchased, data from `Orders` , each customer data contains multiple order data) -- Products - - Product name - - Description - - Images - - Price -- Orders - - Serial number - - Total - - Note - - Address - - *Customer* (The customer to which the order belongs to, which is a many-to-one relationship. Each order belongs to one customer, and one customer may have multiple orders) - - *Order List* (The items and quantities in this order are associated with `Order List`, which is a **one to many** relationship. Each order contains multiple order items, and each order items belongs to only one order) -- Order List - - Product (The product contained in this item whith is associated with `Products`, which is a **many-to-one** relationship. Each order item contains one product, and each product may belong to multiple order item) - - Quantity - -Where the fields in italics are relational fields, associated to other data tables. - -Next, click the "Collections & Fields" button to enter the Configuration screen and create the first Collection `Customers`. - -![create-collection.gif](./the-first-app/create-collection.gif) - -Then click on "Configure fields" to add a name field for `Customers`, which is a Single line text type. - -![create-field.gif](./the-first-app/create-field.gif) - -In the same way, add Address, Gender, and Phone for `Customers`, which are the Text, Radio group type, and Phone type respectively. - -![fields-list.jpg](./the-first-app/fields-list.jpg) - -In the same way, create Collections `Products`, `Orders`, `Order List` and their fields. - -![collection-list.jpg](./the-first-app/collection-list.jpg) - -In this case, for relational fields, if you are not familiar with the concepts of one-to-many, many-to-many, etc., you can directly use the Link to type to create associations between collections. If you are familiar with these concepts, please use the correct types of One to many, Many to one, etc. to establish the association between collections. For example, in this example, we associate `Orders` with `Order list`Order list with the relationship One to many. - -![collection-list.jpg](./the-first-app/order-list-relation.jpg) - - -In the graphical interface, you can visualize the relationship between the various collections. (Note: Graph-collection plugin is not yet open source) -![graph-collection.jpg](./the-first-app/graph-collection.jpg) - -Once the data collections and fields are created, we start making the interface. - -## 2. Configure menus and pages - -We need three pages for customers, orders, and products to display and manage our data. - -Click the UI Editor button to enter the interface configuration mode. In this mode, we can add menu items, add pages, and arrange blocks within the pages. - -![1.editor.gif](./the-first-app/1.editor.gif) - -Click Add menu item, add menu groups "Customers" and "Orders & Products", then add submenu pages "All Orders" and "Products". - -![1.menu.gif](./the-first-app/1.menu.gif) - -After adding menus and pages, we can add and configure blocks within the pages. - -## 3. Adding and Configuring Blocks - -NocoBase currently supports table, kanban, calendar, form, items, and other types of blocks that present data from a data collection and allow manipulation of the data. Obviously, customers, orders, and products are suitable for displaying and manipulating in a table block. - -We add a table block to the "All Orders" page, select Collection `Orders` as the data source, and configure the columns to be displayed for this table block. - -![1.block.gif](./the-first-app/1.block.gif) - -Configure actions for this table block, including filter, add, delete, view, and edit. - -![1.action.gif](./the-first-app/1.action.gif) - -Configure form and item blocks for add, edit, view actions. - -![1.action-block.gif](./the-first-app/1.action-block.gif) - -Then, lay out the form blocks on the Products and Customers pages with the same method. When you are done, exit the UI Editor mode and enter the usage mode, and a simple order management system is completed. - -![demo-finished.jpg](./the-first-app/demo-finished.jpg) diff --git a/docs/en-US/manual/quick-start/the-first-app/1.action-block.gif b/docs/en-US/manual/quick-start/the-first-app/1.action-block.gif deleted file mode 100755 index db4d312de..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/1.action-block.gif and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/1.action.gif b/docs/en-US/manual/quick-start/the-first-app/1.action.gif deleted file mode 100755 index f8a8f2ba6..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/1.action.gif and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/1.block.gif b/docs/en-US/manual/quick-start/the-first-app/1.block.gif deleted file mode 100755 index b2cbbcb79..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/1.block.gif and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/1.editor.gif b/docs/en-US/manual/quick-start/the-first-app/1.editor.gif deleted file mode 100755 index d5e7a84df..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/1.editor.gif and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/1.menu.gif b/docs/en-US/manual/quick-start/the-first-app/1.menu.gif deleted file mode 100755 index d1f43c62a..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/1.menu.gif and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/collection-list.jpg b/docs/en-US/manual/quick-start/the-first-app/collection-list.jpg deleted file mode 100755 index b291ed3b8..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/collection-list.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/create-collection.gif b/docs/en-US/manual/quick-start/the-first-app/create-collection.gif deleted file mode 100755 index d8eae060c..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/create-collection.gif and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/create-field.gif b/docs/en-US/manual/quick-start/the-first-app/create-field.gif deleted file mode 100755 index ba3f95278..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/create-field.gif and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/demo-finished.jpg b/docs/en-US/manual/quick-start/the-first-app/demo-finished.jpg deleted file mode 100755 index b880c89de..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/demo-finished.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/fields-list.jpg b/docs/en-US/manual/quick-start/the-first-app/fields-list.jpg deleted file mode 100755 index 13a382880..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/fields-list.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/graph-collection.jpg b/docs/en-US/manual/quick-start/the-first-app/graph-collection.jpg deleted file mode 100644 index 682fb9320..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/graph-collection.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/the-first-app/order-list-relation.jpg b/docs/en-US/manual/quick-start/the-first-app/order-list-relation.jpg deleted file mode 100644 index 64c02fdd2..000000000 Binary files a/docs/en-US/manual/quick-start/the-first-app/order-list-relation.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode.md b/docs/en-US/manual/quick-start/ui-editor-mode.md deleted file mode 100755 index 8ca8aeb36..000000000 --- a/docs/en-US/manual/quick-start/ui-editor-mode.md +++ /dev/null @@ -1,51 +0,0 @@ -# UI Editor Mode - -NocoBase uses a WYSIWYG approach to configure the interface. Click the `UI Editor` button in the upper right corner to switch between configuration mode and usage mode. Once you enter the configuration mode, many orange configuration portals will appear everywhere on the interface. - -![ui-editor.gif](./ui-editor-mode/ui-editor.gif) - -Usually, the configuration entry appears in the upper right corner of the element. - -## Menu item configuration - -Move the mouse over the menu item, in the upper right corner you can see the drag and drop sort button, configuration item button, you can edit, move, insert, delete. - -![menu-config.jpg](./ui-editor-mode/menu-config.jpg) - -## Block configuration - -Move the mouse over a block and you will see drag-and-drop sort button, add block button and configure item button in the upper right corner. - -![block-config.jpg](./ui-editor-mode/block-config.jpg) - -Different blocks also have their own unique configuration items. For example, in the table block, you can see the configuration items of the table header in the upper right corner by hovering over the table header; you can also see the configuration items of the table columns in the far right side of the table header. - -![block-config-2.jpg](./ui-editor-mode/block-config-2.jpg) - -![block-config-3.jpg](./ui-editor-mode/block-config-3.jpg) - -## Action configuration - -In the block you can see the configuration entries for the actions, which appear in different places in different blocks. - -For example, in the table block, the actions for the table data can be seen in the upper right: - -![action-config-1.jpg](./ui-editor-mode/action-config-1.jpg) - -In the table header of the actions column you can see the action for the single row of data: ! - -![action-config-2.jpg](./ui-editor-mode/action-config-2.jpg) - -You can see the actions for the details in the upper right corner of the details block: - -![action-config-3.jpg](./ui-editor-mode/action-config-3.jpg) - -The actions for the form can be seen at the bottom of the form block: - -![action-config-4.jpg](./ui-editor-mode/action-config-4.jpg) - -## Tab configuration - -Multiple tabs can be added in a popup or drawer to host different blocks. - -![tab-config.jpg](./ui-editor-mode/tab-config.jpg) \ No newline at end of file diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-1.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-1.jpg deleted file mode 100755 index c31a20f7a..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-1.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-2.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-2.jpg deleted file mode 100755 index 22b14119f..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-2.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-3.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-3.jpg deleted file mode 100755 index 59e5a2527..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-3.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-4.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-4.jpg deleted file mode 100755 index f799395bf..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-4.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/block-config-2.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/block-config-2.jpg deleted file mode 100755 index 0e041e35e..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/block-config-2.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/block-config-3.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/block-config-3.jpg deleted file mode 100755 index 0b1244cc0..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/block-config-3.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/block-config.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/block-config.jpg deleted file mode 100755 index e1a94876c..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/block-config.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/menu-config.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/menu-config.jpg deleted file mode 100755 index 8765202f0..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/menu-config.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/tab-config.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/tab-config.jpg deleted file mode 100755 index ac46ee284..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/tab-config.jpg and /dev/null differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/ui-editor.gif b/docs/en-US/manual/quick-start/ui-editor-mode/ui-editor.gif deleted file mode 100755 index 0ea3bf781..000000000 Binary files a/docs/en-US/manual/quick-start/ui-editor-mode/ui-editor.gif and /dev/null differ diff --git a/docs/en-US/welcome/community/contributing.md b/docs/en-US/welcome/community/contributing.md deleted file mode 100644 index 9f72dff8a..000000000 --- a/docs/en-US/welcome/community/contributing.md +++ /dev/null @@ -1,48 +0,0 @@ -# Contributing - -- Fork the source code to your own repository -- Modify source code -- Submit pull request -- Sign the CLA - -## Download - -```bash -# Replace the following git address with your own repo -git clone https://github.com/nocobase/nocobase.git -cd nocobase -yarn install -``` - -## Development and Testing - -```bash -# Install and start the application -yarn dev -# Run all tests -yarn test -# Run all test files in the folder -yarn test -# Run a single test file -yarn test -``` - -## Documentation preview - -```bash -# Start documentation -yarn doc --lang=zh-CN -yarn doc --lang=en-US -``` - -The documentation is in the docs directory and follows Markdown syntax - -```bash -|- /docs/ - |- en-US - |- zh-CN -``` - -## Others - -For more CLI instructions, please [refer to the NocoBase CLI chapter](https://docs-cn.nocobase.com/api/cli). diff --git a/docs/en-US/welcome/community/faq.md b/docs/en-US/welcome/community/faq.md deleted file mode 100644 index 4514b4c10..000000000 --- a/docs/en-US/welcome/community/faq.md +++ /dev/null @@ -1 +0,0 @@ -# FAQ diff --git a/docs/en-US/welcome/community/thanks.md b/docs/en-US/welcome/community/thanks.md deleted file mode 100644 index 325efc399..000000000 --- a/docs/en-US/welcome/community/thanks.md +++ /dev/null @@ -1,12 +0,0 @@ -# Acknowledgements - -NocoBase uses excellent and proven open source products, to whom we express our sincere gratitude. - -- [Ant Design](https://ant.design/) -- [Dnd Kit](https://dndkit.com/) -- [Formily](https://github.com/alibaba/formily) -- [I18next](https://www.i18next.com/) -- [Koa](https://koajs.com/) -- [React](https://reactjs.org/) -- [Sequelize](https://sequelize.org/) -- [UmiJS](https://umijs.org/) diff --git a/docs/en-US/welcome/community/translations.md b/docs/en-US/welcome/community/translations.md deleted file mode 100644 index 272b9391c..000000000 --- a/docs/en-US/welcome/community/translations.md +++ /dev/null @@ -1,159 +0,0 @@ -# Translations - -The default language of NocoBase is English, currently English, Simplified Chinese, Japanese, Russian, Turkish are supported. You can help NocoBase to translate into your language. - -The NocoBase language files are located at the following locations. - -```shell -packages/core/**/src/locale -packages/plugins/**/src/locale -``` - -Among other things, the translation of the NocoBase core is mainly located here - -https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/locale - -Please copy en_US.ts, name it with the name of the language you want to add, and then translate the strings in it. Once the translation is done, please submit it to NocoBase via pull request and we will add it to the list of languages. Then you will see the new languages in the system configuration, where you can configure which languages you want to display for users to choose. - - - -The following table lists the Language Culture Name, Locale File Name, Display Name. - -| Language Culture Name | Locale File Name | Display Name | -| :-------------------- | :--------------- | :---------------------------- | -| af-ZA | af_ZA.ts | Afrikaans - South Africa | -| sq-AL | sq_AL.ts | Albanian - Albania | -| ar-DZ | ar_DZ.ts | Arabic - Algeria | -| ar-BH | ar_BH.ts | Arabic - Bahrain | -| ar-EG | ar_EG.ts | Arabic - Egypt | -| ar-IQ | ar_IQ.ts | Arabic - Iraq | -| ar-JO | ar_JO.ts | Arabic - Jordan | -| ar-KW | ar_KW.ts | Arabic - Kuwait | -| ar-LB | ar_LB.ts | Arabic - Lebanon | -| ar-LY | ar_LY.ts | Arabic - Libya | -| ar-MA | ar_MA.ts | Arabic - Morocco | -| ar-OM | ar_OM.ts | Arabic - Oman | -| ar-QA | ar_QA.ts | Arabic - Qatar | -| ar-SA | ar_SA.ts | Arabic - Saudi Arabia | -| ar-SY | ar_SY.ts | Arabic - Syria | -| ar-TN | ar_TN.ts | Arabic - Tunisia | -| ar-AE | ar_AE.ts | Arabic - United Arab Emirates | -| ar-YE | ar_YE.ts | Arabic - Yemen | -| hy-AM | hy_AM.ts | Armenian - Armenia | -| Cy-az-AZ | Cy_az_AZ.ts | Azeri (Cyrillic) - Azerbaijan | -| Lt-az-AZ | Lt_az_AZ.ts | Azeri (Latin) - Azerbaijan | -| eu-ES | eu_ES.ts | Basque - Basque | -| be-BY | be_BY.ts | Belarusian - Belarus | -| bg-BG | bg_BG.ts | Bulgarian - Bulgaria | -| ca-ES | ca_ES.ts | Catalan - Catalan | -| zh-CN | zh_CN.ts | Chinese - China | -| zh-HK | zh_HK.ts | Chinese - Hong Kong SAR | -| zh-MO | zh_MO.ts | Chinese - Macau SAR | -| zh-SG | zh_SG.ts | Chinese - Singapore | -| zh-TW | zh_TW.ts | Chinese - Taiwan | -| zh-CHS | zh_CHS.ts | Chinese (Simplified) | -| zh-CHT | zh_CHT.ts | Chinese (Traditional) | -| hr-HR | hr_HR.ts | Croatian - Croatia | -| cs-CZ | cs_CZ.ts | Czech - Czech Republic | -| da-DK | da_DK.ts | Danish - Denmark | -| div-MV | div_MV.ts | Dhivehi - Maldives | -| nl-BE | nl_BE.ts | Dutch - Belgium | -| nl-NL | nl_NL.ts | Dutch - The Netherlands | -| en-AU | en_AU.ts | English - Australia | -| en-BZ | en_BZ.ts | English - Belize | -| en-CA | en_CA.ts | English - Canada | -| en-CB | en_CB.ts | English - Caribbean | -| en-IE | en_IE.ts | English - Ireland | -| en-JM | en_JM.ts | English - Jamaica | -| en-NZ | en_NZ.ts | English - New Zealand | -| en-PH | en_PH.ts | English - Philippines | -| en-ZA | en_ZA.ts | English - South Africa | -| en-TT | en_TT.ts | English - Trinidad and Tobago | -| en-GB | en_GB.ts | English - United Kingdom | -| en-US | en_US.ts | English - United States | -| en-ZW | en_ZW.ts | English - Zimbabwe | -| et-EE | et_EE.ts | Estonian - Estonia | -| fo-FO | fo_FO.ts | Faroese - Faroe Islands | -| fa-IR | fa_IR.ts | Farsi - Iran | -| fi-FI | fi_FI.ts | Finnish - Finland | -| fr-BE | fr_BE.ts | French - Belgium | -| fr-CA | fr_CA.ts | French - Canada | -| fr-FR | fr_FR.ts | French - France | -| fr-LU | fr_LU.ts | French - Luxembourg | -| fr-MC | fr_MC.ts | French - Monaco | -| fr-CH | fr_CH.ts | French - Switzerland | -| gl-ES | gl_ES.ts | Galician - Galician | -| ka-GE | ka_GE.ts | Georgian - Georgia | -| de-AT | de_AT.ts | German - Austria | -| de-DE | de_DE.ts | German - Germany | -| de-LI | de_LI.ts | German - Liechtenstein | -| de-LU | de_LU.ts | German - Luxembourg | -| de-CH | de_CH.ts | German - Switzerland | -| el-GR | el_GR.ts | Greek - Greece | -| gu-IN | gu_IN.ts | Gujarati - India | -| he-IL | he_IL.ts | Hebrew - Israel | -| hi-IN | hi_IN.ts | Hindi - India | -| hu-HU | hu_HU.ts | Hungarian - Hungary | -| is-IS | is_IS.ts | Icelandic - Iceland | -| id-ID | id_ID.ts | Indonesian - Indonesia | -| it-IT | it_IT.ts | Italian - Italy | -| it-CH | it_CH.ts | Italian - Switzerland | -| ja-JP | ja_JP.ts | Japanese - Japan | -| kn-IN | kn_IN.ts | Kannada - India | -| kk-KZ | kk_KZ.ts | Kazakh - Kazakhstan | -| kok-IN | kok_IN.ts | Konkani - India | -| ko-KR | ko_KR.ts | Korean - Korea | -| ky-KZ | ky_KZ.ts | Kyrgyz - Kazakhstan | -| lv-LV | lv_LV.ts | Latvian - Latvia | -| lt-LT | lt_LT.ts | Lithuanian - Lithuania | -| mk-MK | mk_MK.ts | Macedonian (FYROM) | -| ms-BN | ms_BN.ts | Malay - Brunei | -| ms-MY | ms_MY.ts | Malay - Malaysia | -| mr-IN | mr_IN.ts | Marathi - India | -| mn-MN | mn_MN.ts | Mongolian - Mongolia | -| nb-NO | nb_NO.ts | Norwegian (BokmÃ¥l) - Norway | -| nn-NO | nn_NO.ts | Norwegian (Nynorsk) - Norway | -| pl-PL | pl_PL.ts | Polish - Poland | -| pt-BR | pt_BR.ts | Portuguese - Brazil | -| pt-PT | pt_PT.ts | Portuguese - Portugal | -| pa-IN | pa_IN.ts | Punjabi - India | -| ro-RO | ro_RO.ts | Romanian - Romania | -| ru-RU | ru_RU.ts | Russian - Russia | -| sa-IN | sa_IN.ts | Sanskrit - India | -| Cy-sr-SP | Cy_sr_SP.ts | Serbian (Cyrillic) - Serbia | -| Lt-sr-SP | Lt_sr_SP.ts | Serbian (Latin) - Serbia | -| sk-SK | sk_SK.ts | Slovak - Slovakia | -| sl-SI | sl_SI.ts | Slovenian - Slovenia | -| es-AR | es_AR.ts | Spanish - Argentina | -| es-BO | es_BO.ts | Spanish - Bolivia | -| es-CL | es_CL.ts | Spanish - Chile | -| es-CO | es_CO.ts | Spanish - Colombia | -| es-CR | es_CR.ts | Spanish - Costa Rica | -| es-DO | es_DO.ts | Spanish - Dominican Republic | -| es-EC | es_EC.ts | Spanish - Ecuador | -| es-SV | es_SV.ts | Spanish - El Salvador | -| es-GT | es_GT.ts | Spanish - Guatemala | -| es-HN | es_HN.ts | Spanish - Honduras | -| es-MX | es_MX.ts | Spanish - Mexico | -| es-NI | es_NI.ts | Spanish - Nicaragua | -| es-PA | es_PA.ts | Spanish - Panama | -| es-PY | es_PY.ts | Spanish - Paraguay | -| es-PE | es_PE.ts | Spanish - Peru | -| es-PR | es_PR.ts | Spanish - Puerto Rico | -| es-ES | es_ES.ts | Spanish - Spain | -| es-UY | es_UY.ts | Spanish - Uruguay | -| es-VE | es_VE.ts | Spanish - Venezuela | -| sw-KE | sw_KE.ts | Swahili - Kenya | -| sv-FI | sv_FI.ts | Swedish - Finland | -| sv-SE | sv_SE.ts | Swedish - Sweden | -| syr-SY | syr_SY.ts | Syriac - Syria | -| ta-IN | ta_IN.ts | Tamil - India | -| tt-RU | tt_RU.ts | Tatar - Russia | -| te-IN | te_IN.ts | Telugu - India | -| th-TH | th_TH.ts | Thai - Thailand | -| tr-TR | tr_TR.ts | Turkish - Turkey | -| uk-UA | uk_UA.ts | Ukrainian - Ukraine | -| ur-PK | ur_PK.ts | Urdu - Pakistan | -| Cy-uz-UZ | Cy_uz_UZ.ts | Uzbek (Cyrillic) - Uzbekistan | -| Lt-uz-UZ | Lt_uz_UZ.ts | Uzbek (Latin) - Uzbekistan | -| vi-VN | vi_VN.ts | Vietnamese - Vietnam | diff --git a/docs/en-US/welcome/community/translations/enabled-languages.jpg b/docs/en-US/welcome/community/translations/enabled-languages.jpg deleted file mode 100644 index fcf326305..000000000 Binary files a/docs/en-US/welcome/community/translations/enabled-languages.jpg and /dev/null differ diff --git a/docs/en-US/welcome/community/translations/language-settings-1.jpg b/docs/en-US/welcome/community/translations/language-settings-1.jpg deleted file mode 100644 index eb352a0f8..000000000 Binary files a/docs/en-US/welcome/community/translations/language-settings-1.jpg and /dev/null differ diff --git a/docs/en-US/welcome/community/translations/language-settings-2.jpg b/docs/en-US/welcome/community/translations/language-settings-2.jpg deleted file mode 100644 index 3e9f7b08e..000000000 Binary files a/docs/en-US/welcome/community/translations/language-settings-2.jpg and /dev/null differ diff --git a/docs/en-US/welcome/getting-started/deployment.md b/docs/en-US/welcome/getting-started/deployment.md deleted file mode 100644 index ed3c9a4c8..000000000 --- a/docs/en-US/welcome/getting-started/deployment.md +++ /dev/null @@ -1,2 +0,0 @@ -# Deployment - diff --git a/docs/en-US/welcome/getting-started/installation/create-nocobase-app.md b/docs/en-US/welcome/getting-started/installation/create-nocobase-app.md deleted file mode 100644 index 92a834865..000000000 --- a/docs/en-US/welcome/getting-started/installation/create-nocobase-app.md +++ /dev/null @@ -1,86 +0,0 @@ -# `create-nocobase-app` - -## 0. Prerequisites - -Make sure you have: - -- Installed Node.js 16+, Yarn 1.22.x -- Configured and started one of the required database SQLite 3.x, MySQL 8.x, PostgreSQL 10.x - -Please make sure you have Node.js 16.x or above installed. You can download and install the latest LTS version from the official website. It is recommended to use nvm (or nvm-windows for Win systems) to manage Node.js versions if you plan to work with Node.js for a long time. - -```bash -$ node -v - -v16.13.2 -``` - -Install yarn package manager - -```bash -$ npm install --global yarn -$ yarn -v - -1.22.10 -``` - -## 1. Create a NocoBase project - -```bash -# SQLite -yarn create nocobase-app my-nocobase-app -d sqlite -# MySQL -yarn create nocobase-app my-nocobase-app -d mysql \ - -e DB_HOST=localhost \ - -e DB_PORT=3306 \ - -e DB_DATABASE=nocobase \ - -e DB_USER=nocobase \ - -e DB_PASSWORD=nocobase -# PostgreSQL -yarn create nocobase-app my-nocobase-app -d postgres \ - -e DB_HOST=localhost \ - -e DB_PORT=5432 \ - -e DB_DATABASE=nocobase \ - -e DB_USER=nocobase \ - -e DB_PASSWORD=nocobase -``` - -## 2. Switch to the project directory - -```bash -cd my-nocobase-app -``` - -## 3. Install dependencies - -📢 This next step may take more than ten minutes due to network environment, system configuration, and other factors. - -```bash -yarn install -``` - -## 4. Install NocoBase - -```bash -yarn nocobase install --lang=zh-CN -``` - -## 5. Start NocoBase - -Development - -```bash -yarn dev -``` - -Production - -```bash -yarn start -``` - -Note: For production, if the code has been modified, you need to execute `yarn build` and restart NocoBase. - -## 6. Log in to NocoBase - -Open [http://localhost:13000](http://localhost:13000) in a web browser. The initial account and password are `admin@nocobase.com` and `admin123`. diff --git a/docs/en-US/welcome/getting-started/installation/docker-compose.md b/docs/en-US/welcome/getting-started/installation/docker-compose.md deleted file mode 100644 index 5b788504f..000000000 --- a/docs/en-US/welcome/getting-started/installation/docker-compose.md +++ /dev/null @@ -1,142 +0,0 @@ -# Docker (👍 Recommended) - -## 0. Prerequisites - -⚡⚡ Please make sure you have installed [Docker](https://docs.docker.com/get-docker/) - -## 1. Download NocoBase - -Download with Git (or Download Zip,and extract it to the nocobase directory) - -```bash -git clone https://github.com/nocobase/nocobase.git nocobase -``` - -## 2. Select database (choose one) - -Change the directory to the folder downloaded in the first step. - -```bash -# MacOS, Linux... -cd /your/path/nocobase -# Windows -cd C:\your\path\nocobase -``` - -The docker configuration of different databases is slightly different, please choose to switch to the corresponding directory. - -### SQLite - -```bash -cd docker/app-sqlite -``` - -### MySQL - -```bash -cd docker/app-mysql -``` - -### PostgreSQL - -```bash -cd docker/app-postgres -``` - -## 3. Configure docker-compose.yml (optional) - - - -Non-developers skip this step. If you know development, you can also learn more about how to configure `docker-compose.yml`. - - - -Directory structure (related to docker) - -```bash -├── nocobase - ├── docker - ├── app-sqlite - ├── storage - ├── docker-compose.yml - ├── app-mysql - ├── storage - ├── docker-compose.yml - ├── app-postgres - ├── storage - ├── docker-compose.yml -``` - -Configuration notes for `docker-compose.yml`: - -SQLite only has app service, PostgreSQL and MySQL will have corresponding postgres or mysql service, you can use the example database service or configure it yourself. - -```yml -services: - app: - postgres: - mysql: -``` - -App port, the URL is `http://your-ip:13000/` - -```yml -services: - app: - ports: - - "13000:80" -``` - -NocoBase version ([click here for the latest version](https://hub.docker.com/r/nocobase/nocobase/tags)). When upgrading, you need to change to the latest version. - -```yml -services: - app: - image: nocobase/nocobase:main -``` - -Environment variables - -```yml -services: - app: - image: nocobase/nocobase:main - environment: - - DB_DIALECT=postgres - - DB_HOST=postgres - - DB_DATABASE=nocobase - - DB_USER=nocobase - - DB_PASSWORD=nocobase - - LOCAL_STORAGE_BASE_URL=/storage/uploads -``` - -- `DB_*` is the database related, if it is not the default database service of the example, please change it according to the actual situation. -- `LOCAL_STORAGE_BASE_URL` is the base URL for local storage, if it is not a local installation, you need to change it to the corresponding ip or domain name. - -## 4. Install and start NocoBase - -It may take a few minutes - -```bash -# pull service images -$ docker-compose pull -# run in the background -$ docker-compose up -d -# view app logs -$ docker-compose logs app - -app-sqlite-app-1 | nginx started -app-sqlite-app-1 | yarn run v1.22.15 -app-sqlite-app-1 | $ cross-env DOTENV_CONFIG_PATH=.env node -r dotenv/config packages/app/server/lib/index.js install -s -app-sqlite-app-1 | Done in 2.72s. -app-sqlite-app-1 | yarn run v1.22.15 -app-sqlite-app-1 | $ pm2-runtime start --node-args="-r dotenv/config" packages/app/server/lib/index.js -- start -app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: Launching in no daemon mode -app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: App [index:0] starting in -fork mode- -app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: App [index:0] online -app-sqlite-app-1 | 🚀 NocoBase server running at: http://localhost:13000/ -``` - -## 4. Log in to NocoBase - -Open [http://localhost:13000](http://localhost:13000) in a web browser. The initial account and password are `admin@nocobase.com` and `admin123`. diff --git a/docs/en-US/welcome/getting-started/installation/git-clone.md b/docs/en-US/welcome/getting-started/installation/git-clone.md deleted file mode 100644 index 8c43c212d..000000000 --- a/docs/en-US/welcome/getting-started/installation/git-clone.md +++ /dev/null @@ -1,64 +0,0 @@ -# Git source code - -## 0. Prerequisites - -Make sure you have: - -- Git, Node.js 16+, Yarn 1.22.x installed -- Configured and started the required database SQLite 3.x, MySQL 8.x, PostgreSQL 10.x choose one - -## 1. Download with Git - -```bash -git clone https://github.com/nocobase/nocobase.git my-nocobase-app -``` - -## 2. Switch to the project directory - -```bash -cd my-nocobase-app -``` - -## 3. Install dependencies - -```bash -yarn install -``` - -## 4. Set environment variables - -The environment variables required by NocoBase are stored in the root `.env` file, modify the environment variables according to the actual situation, if you don't know how to change them, [click here for environment variables description](/api/env), or you can leave it as default. - -```bash -# Using sqlite database -DB_DIALECT=sqlite -# sqlite file path -DB_STORAGE=storage/db/nocobase.sqlite -``` - -## 5. Install NocoBase - -```bash -yarn nocobase install --lang=zh-CN -``` - -## 6. Start NocoBase - -Development - -```bash -yarn dev -``` - -Production - -```bash -# Compile -yarn build -# Start -yarn start -``` - -## 7. Log in to NocoBase - -Open [http://localhost:13000](http://localhost:13000) in a web browser. The initial account and password are `admin@nocobase.com` and `admin123`. diff --git a/docs/en-US/welcome/getting-started/installation/index.md b/docs/en-US/welcome/getting-started/installation/index.md deleted file mode 100644 index c029d7ec9..000000000 --- a/docs/en-US/welcome/getting-started/installation/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Overview - -## Installation Methods - -NocoBase supports three installation methods. - -- [Docker (recommended)](./docker-compose.md) -- [create-nocobase-app](./create-nocobase-app.md) -- [Git source code](./git-clone.md) - -## How to choose - -**Docker (recommended)**: - -Suitable for no-code scenarios, no code to write. When upgrading, just download the latest image and reboot. - -**create-nocobase-app**: - -The business code of the project is completely independent and supports low-code development. - -**Git source code**: - -If you want to experience the latest unreleased version, or want to participate in the contribution, you need to make changes and debug on the source code, it is recommended to choose this installation method, which requires a high level of development skills, and if the code has been updated, you can git pull the latest code. diff --git a/docs/en-US/welcome/getting-started/upgrading/create-nocobase-app.md b/docs/en-US/welcome/getting-started/upgrading/create-nocobase-app.md deleted file mode 100644 index bbb9d64de..000000000 --- a/docs/en-US/welcome/getting-started/upgrading/create-nocobase-app.md +++ /dev/null @@ -1,22 +0,0 @@ -# Upgrading for `create-nocobase-app` - - - -After v0.12, apps installed via `create-nocobase-app` no longer have a `packages/app` directory, and code customized in `packages/app` needs to be moved to the custom plugin. - - - -## Upgrading - -After v0.12, upgrading the application can be done by running the `yarn nocobase upgrade` command. - -```bash -# Switch to the corresponding directory -cd my-nocobase-app -# Execute the update command -yarn nocobase upgrade -# Start -yarn dev -``` - -If there are problems with upgrading, you can also [recreate new app](/welcome/getting-started/installation/create-nocobase-app) and refer to the old version of .env to change the environment variables. The database information needs to be configured correctly. When using a SQLite database, you need to copy the database files to the `./storage/db/` directory. Finally, run `yarn nocobase upgrade` to upgrade. diff --git a/docs/en-US/welcome/getting-started/upgrading/docker-compose.md b/docs/en-US/welcome/getting-started/upgrading/docker-compose.md deleted file mode 100644 index c10ce4494..000000000 --- a/docs/en-US/welcome/getting-started/upgrading/docker-compose.md +++ /dev/null @@ -1,38 +0,0 @@ -# Upgrading for Docker compose - - - -The Docker installation described in this document is based on the `docker-compose.yml` configuration file, which is also available in the [NocoBase GitHub repository](https://github.com/nocobase/nocobase/tree/main/docker). - - - -## 1. Switch to the directory where you installed it before - -You can also switch to the directory where `docker-compose.yml` is located, depending on the situation. - -```bash -# SQLite -cd nocobase/docker/app-sqlite -# MySQL -cd nocobase/docker/app-mysql -# PostgreSQL -cd nocobase/docker/app-postgres -``` - -## 2. Update the image version number - -`docker-compose.yml` file, replace the image of the app container with the latest version. - -```yml -services: - app: - image: nocobase/nocobase:main -``` - -## 3. Restart the container - -```bash -docker-compose pull -docker-compose up -d app -docker-compose logs app -``` diff --git a/docs/en-US/welcome/getting-started/upgrading/git-clone.md b/docs/en-US/welcome/getting-started/upgrading/git-clone.md deleted file mode 100644 index 27a6e27f9..000000000 --- a/docs/en-US/welcome/getting-started/upgrading/git-clone.md +++ /dev/null @@ -1,52 +0,0 @@ -# Upgrading for Git source code - -## 1. switch to the NocoBase project directory - -```bash -cd my-nocobase-app -``` - -## 2. Pull the latest code - -```bash -git pull -``` - -## 3. Delete cache and dependencies (optional) - -```bash -# delete nocobase cache -yarn nocobase clean -# delete dependencies -yarn rimraf -rf node_modules -``` - -## 4. Update dependencies - -```bash -yarn install -``` - -## 5. Execute the update command - -```bash -yarn nocobase upgrade -``` - -## 6. Start NocoBase - -development environment - -```bash -yarn dev -``` - -Production environment - -```bash -# compile -yarn build - -# Start -yarn start # Not supported on Windows platforms yet -``` diff --git a/docs/en-US/welcome/getting-started/upgrading/index.md b/docs/en-US/welcome/getting-started/upgrading/index.md deleted file mode 100644 index 30aed2c8d..000000000 --- a/docs/en-US/welcome/getting-started/upgrading/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Overview - -NocoBase supports three types of installation, with slight differences in upgrades. - -- [Upgrading for Docker compose](./docker-compose.md) -- [Upgrading for create-nocobase-app](./create-nocobase-app.md) -- [Upgrading for Git source code](./git-clone.md) diff --git a/docs/en-US/welcome/introduction/features.md b/docs/en-US/welcome/introduction/features.md deleted file mode 100644 index 2b7746d25..000000000 --- a/docs/en-US/welcome/introduction/features.md +++ /dev/null @@ -1,21 +0,0 @@ -# Distinctive features - -## 1. Model-driven, separate "user interface" from "data structure" - -Most form-, table-, or process-driven no-code products create data structures directly in the user interface, such as Airtable, where adding a new column to a table is adding a new field. This has the advantage of simplicity of use, but the disadvantage of limited functionality and flexibility to meet the needs of more complex scenarios. - -NocoBase adopts the design idea of separating the data structure from the user interface, allowing you to create any number of blocks (data views) for the data collections, with different type, styles, content, and actions in each block. This balances the simplicity of no-code operation with the flexibility of native development. - -![model](https://nocobase-file.oss-cn-beijing.aliyuncs.com/model-l.png) - -## 2. What you see is what you get - -NocoBase enables the development of complex and distinctive business systems, but this does not mean that complex and specialized operations are required. With a single click, configuration options are displayed on the usage interface, and administrators with system configuration privileges can directly configure the user interface in a WYSIWYG manner. - -![wysiwyg](https://nocobase-file.oss-cn-beijing.aliyuncs.com/wysiwyg.gif) - -## 3. Functions as plugins - -NocoBase adopts plugin architecture, all new functions can be realized by developing and installing plugins, and expanding the functions is as easy as installing an APP on your phone. - -![plugins](https://nocobase-file.oss-cn-beijing.aliyuncs.com/plugins-l.png) diff --git a/docs/en-US/welcome/introduction/features/2.collection-block.png b/docs/en-US/welcome/introduction/features/2.collection-block.png deleted file mode 100755 index 7389064cb..000000000 Binary files a/docs/en-US/welcome/introduction/features/2.collection-block.png and /dev/null differ diff --git a/docs/en-US/welcome/introduction/features/2.user-root.gif b/docs/en-US/welcome/introduction/features/2.user-root.gif deleted file mode 100755 index 92d668c14..000000000 Binary files a/docs/en-US/welcome/introduction/features/2.user-root.gif and /dev/null differ diff --git a/docs/en-US/welcome/introduction/index.md b/docs/en-US/welcome/introduction/index.md deleted file mode 100644 index 8321bc907..000000000 --- a/docs/en-US/welcome/introduction/index.md +++ /dev/null @@ -1,28 +0,0 @@ -# Introduction - -![NocoBase](https://nocobase-file.oss-cn-beijing.aliyuncs.com/main-l.png) - -**Note:** 📌 - -NocoBase is in early stage of development and is subject to frequent changes, please use caution in production environments. - -## What is NocoBase - -NocoBase is a scalability-first, open-source no-code development platform. -Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! - -Homepage: -https://www.nocobase.com/ - -Online Demo: -https://demo.nocobase.com/new - -Documents: -https://docs.nocobase.com/ - -Contact Us: -hello@nocobase.com - -## Commercial Version & Business Services - -If you need commercial version and commercial services, please feel free to contact us by email: hello@nocobase.com diff --git a/docs/en-US/welcome/introduction/why.md b/docs/en-US/welcome/introduction/why.md deleted file mode 100644 index 3cbd5c8e2..000000000 --- a/docs/en-US/welcome/introduction/why.md +++ /dev/null @@ -1,19 +0,0 @@ -# Why NocoBase - -## Open source, autonomous and controllable -NocoBase is open source under the Apache-2.0 & AGPL-3.0 licenses, and can be used free of charge as long as you follow the licenses. Advanced features are provided through the commercial version, which also provides full source code and is privately deployed to keep data private and secure. - -## Strong no-code capability -NocoBase has three core concepts: collection, block, and action. By defining the data collections, to abstract the business; through the block to present the data; through the action to send the user's instructions to the server to complete the data interaction or change. - -## High scalability -In the actual business, the ideal situation is to use no-code to meet 80% of the needs, the rest usually need to extend the development. NocoBase adopts the microkernel architecture, with a sound plugin system, all kinds of functions are extended in the form of plugins. NocoBase is based on Node.js and uses mainstream frameworks and technologies, including Koa, Sequelize, React and so on, which makes it extremely easy to expand. - -## Integrate with existing systems -Organizations usually have various systems and databases already in place, and NocoBase supports using third-party databases or APIs as data sources, as well as embedding NocoBase into third-party systems or embedding third-party systems into NocoBase. - -## Extremely simple and lightweight -NocoBase uses JavaScript/TypeScript technology stack, one person can complete the front-end and back-end development. It has low server requirements and can be deployed on a single low-configuration server. - -## Pay once, use forever -NocoBase only charges for premium features. For a one-time fee, you get access to the full source code and 1 year of upgrade rights and technical support. After the expiration date, if you do not renew your subscription, you can stay in the current version and use it for free forever. diff --git a/docs/en-US/welcome/release/collection-templates.md b/docs/en-US/welcome/release/collection-templates.md deleted file mode 100644 index 1b93bf9a0..000000000 --- a/docs/en-US/welcome/release/collection-templates.md +++ /dev/null @@ -1,79 +0,0 @@ -# v0.9.0: Collection 模板 - - - -## 为什么需要 Collection 模板? - -待补充 - -## 配置参数说明 - -```ts -interface ICollectionTemplate { - name: string; - title?: string; - /** 排序 */ - order?: number; - /** 默认配置 */ - default?: CollectionOptions; - /** UI 可配置的 CollectionOptions 参数(添加或编辑的 Collection 表单的字段) */ - configurableProperties?: Record; - /** 当前模板可用的字段类型 */ - availableFieldInterfaces?: AvailableFieldInterfacesInclude | AvailableFieldInterfacesExclude; -} - -interface AvailableFieldInterfacesInclude { - include?: any[]; -} - -interface AvailableFieldInterfacesExclude { - exclude?: any[]; -} - -interface CollectionOptions { - /** - * 自动生成 id - * @default true - * */ - autoGenId?: boolean; - /** 创建人 */ - createdBy?: boolean; - /** 最后更新人 */ - updatedBy?: boolean; - /** 创建日期 */ - createdAt?: boolean; - /** 更新日期 */ - updatedAt?: boolean; - /** 可排序 */ - sortable?: boolean; - /* 树结构 */ - tree?: string; - /* 日志 */ - logging?: boolean; - /** 继承 */ - inherits: string | string[]; - /* 字段列表 */ - fields?: FieldOptions[]; -} -``` - -## 示例 - -创建时都不需要 autoGenId,并且只提供 title 和 name 配置项 - -```ts -import { collectionConfigurableProperties } from '@nocobase/client'; - -{ - default: { - autoGenId: false, - fields: [], - }, - configurableProperties: { - ...collectionConfigurableProperties('name', 'title'), - }, -} -``` - -完整插件示例参考:[samples/custom-collection-template](https://github.com/nocobase/nocobase/tree/feat/collection-templates/packages/samples/custom-collection-template) - diff --git a/docs/en-US/welcome/release/formulas.md b/docs/en-US/welcome/release/formulas.md deleted file mode 100644 index 3cd461b0b..000000000 --- a/docs/en-US/welcome/release/formulas.md +++ /dev/null @@ -1,33 +0,0 @@ -# v0.9.0:字段的计算公式插件 - -NocoBase 目前提供了两种计算公式插件: - -- `@nocobase/plugin-math-formula-field` Math 公式 -- `@nocobase/plugin-excel-formula-field` Excel 公式(感谢 [azriel46d](https://github.com/nocobase/nocobase/pull/906) 贡献) - -## Math Formula - -基于 [Math.js](https://mathjs.org/) 实现,它具有支持符号计算的灵活表达式解析器,大量内置函数和常量,并提供了集成的解决方案来处理不同的数据类型,例如数字,大数,复数,分数,单位和矩阵。 - -```ts -import { evaluate } from 'mathjs'; -// expressions -evaluate('1.2 * (2 + 4.5)') // 7.8 -evaluate('12.7 cm to inch') // 5 inch -evaluate('sin(45 deg) ^ 2') // 0.5 -evaluate('9 / 3 + 2i') // 3 + 2i -evaluate('det([-1, 2; 3, 1])') // -7 -``` - - - -## Excel Formula - -基于 [Formula.js](https://formulajs.info/) 实现,详细用法参考 [Formula.js functions](https://formulajs.info/functions/)。 - -```ts -SUM(-5, 15, 32) // 42 -IF(true, 'Hello!', 'Goodbye!') // Hello! -``` - - diff --git a/docs/en-US/welcome/release/formulas/excel-form.jpg b/docs/en-US/welcome/release/formulas/excel-form.jpg deleted file mode 100644 index 41413a0c4..000000000 Binary files a/docs/en-US/welcome/release/formulas/excel-form.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/formulas/math-form.jpg b/docs/en-US/welcome/release/formulas/math-form.jpg deleted file mode 100644 index ecf54c476..000000000 Binary files a/docs/en-US/welcome/release/formulas/math-form.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/gantt/introduction.md b/docs/en-US/welcome/release/gantt/introduction.md deleted file mode 100644 index 484eff651..000000000 --- a/docs/en-US/welcome/release/gantt/introduction.md +++ /dev/null @@ -1,21 +0,0 @@ -# Gantt block - -## Create gantt block - -![](./static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png) - -## Gantt block (No data) - -![](./static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png) - -## Block settings - -![](./static/YJZZb0aO3oG9n6x4aZwcAB07nng.png) - -## General tasks - -![](./static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png) - -## Tree tasks - -![](./static/V9w1b43YsoIRYpxtFdscpf2MnQf.png) diff --git a/docs/en-US/welcome/release/gantt/static/IwJzb9PsFovXwZxNqJnc6HaFnyg.png b/docs/en-US/welcome/release/gantt/static/IwJzb9PsFovXwZxNqJnc6HaFnyg.png deleted file mode 100644 index a8893aa82..000000000 Binary files a/docs/en-US/welcome/release/gantt/static/IwJzb9PsFovXwZxNqJnc6HaFnyg.png and /dev/null differ diff --git a/docs/en-US/welcome/release/gantt/static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png b/docs/en-US/welcome/release/gantt/static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png deleted file mode 100644 index 2d309ad42..000000000 Binary files a/docs/en-US/welcome/release/gantt/static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png and /dev/null differ diff --git a/docs/en-US/welcome/release/gantt/static/V9w1b43YsoIRYpxtFdscpf2MnQf.png b/docs/en-US/welcome/release/gantt/static/V9w1b43YsoIRYpxtFdscpf2MnQf.png deleted file mode 100644 index c454d0c43..000000000 Binary files a/docs/en-US/welcome/release/gantt/static/V9w1b43YsoIRYpxtFdscpf2MnQf.png and /dev/null differ diff --git a/docs/en-US/welcome/release/gantt/static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png b/docs/en-US/welcome/release/gantt/static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png deleted file mode 100644 index 4e93257fd..000000000 Binary files a/docs/en-US/welcome/release/gantt/static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png and /dev/null differ diff --git a/docs/en-US/welcome/release/gantt/static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png b/docs/en-US/welcome/release/gantt/static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png deleted file mode 100644 index 901010eff..000000000 Binary files a/docs/en-US/welcome/release/gantt/static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png and /dev/null differ diff --git a/docs/en-US/welcome/release/gantt/static/YJZZb0aO3oG9n6x4aZwcAB07nng.png b/docs/en-US/welcome/release/gantt/static/YJZZb0aO3oG9n6x4aZwcAB07nng.png deleted file mode 100644 index a603d847c..000000000 Binary files a/docs/en-US/welcome/release/gantt/static/YJZZb0aO3oG9n6x4aZwcAB07nng.png and /dev/null differ diff --git a/docs/en-US/welcome/release/index.md b/docs/en-US/welcome/release/index.md deleted file mode 100644 index 0b9215d36..000000000 --- a/docs/en-US/welcome/release/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Changelog - -Visit: https://github.com/nocobase/nocobase/blob/main/CHANGELOG.md \ No newline at end of file diff --git a/docs/en-US/welcome/release/inherits.md b/docs/en-US/welcome/release/inherits.md deleted file mode 100644 index aee2c72e7..000000000 --- a/docs/en-US/welcome/release/inherits.md +++ /dev/null @@ -1,74 +0,0 @@ -# v0.9.0: 数据表继承 - -数据表继承基于 [PostgreSQL 的 INHERITS 语法](https://www.postgresql.org/docs/current/tutorial-inheritance.html) 实现,仅限于 PostgreSQL 数据库安装的 NocoBase 时才会提供。 - -## 示例 - -我们从一个例子开始,假设要做一个教学系统,有三类用户:学生、家长和老师。 - -如果没有继承,要分别为三类用户建表: - -- 学生:姓名、年龄、性别、身份证 -- 家长:姓名、年龄、性别、职业、学历 -- 老师:姓名、年龄、性别、教龄、已婚 - -有了数据表继承之后,共同的信息就可以提炼出来: - -- 用户:姓名、年龄、性别 -- 学生:身份证 -- 家长:职业、学历 -- 老师:教龄、已婚 - -ER 图如下: - - - -注:子表 ID 和父表 ID 共享序列 - -## 配置数据表继承 - -Inherits 字段选择需要继承的数据表 - - - -通过代码配置如下: - -```ts -db.collection({ - name: 'users', -}); - -db.collection({ - name: 'students', - inherits: 'users', -}); -``` - -注意: - -- 继承的表并不能随意选择,主键必须是唯一序列,比如 uuid 或者所有继承线路上的表的 id 自增序列都用同一个 -- Inherits 参数不能被编辑 -- 如果有继承关系,被继承的父表不能被删除 - -## 数据表字段列表 - -字段列表里同步显示继承的父表字段,父表字段不可以修改,但可以重写(Override) - - - -重写父表字段的注意事项: -- 子表字段标识与父表字段一样时为重写 -- 重写字段的类型必须保持一致 -- 关系字段除了 target collection 以外的其他参数需要保持一致 - -## 父表的子表区块 - -在父表区块里可以配置子表的区块 - - - -## 新增继承的父表字段的配置 - -当有继承的父表时,配置字段时,会提供从父表继承的字段的配置 - - diff --git a/docs/en-US/welcome/release/inherits/configure-fields.jpg b/docs/en-US/welcome/release/inherits/configure-fields.jpg deleted file mode 100644 index adf256da3..000000000 Binary files a/docs/en-US/welcome/release/inherits/configure-fields.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/inherits/er.svg b/docs/en-US/welcome/release/inherits/er.svg deleted file mode 100644 index f1ba5c251..000000000 --- a/docs/en-US/welcome/release/inherits/er.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -UsersPKidnameagegenderStudentsPKididentityNumberParentsPKidoccupationTeachersPKidmarried \ No newline at end of file diff --git a/docs/en-US/welcome/release/inherits/form.jpg b/docs/en-US/welcome/release/inherits/form.jpg deleted file mode 100644 index 2de047cd5..000000000 Binary files a/docs/en-US/welcome/release/inherits/form.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/inherits/inherit-fields.jpg b/docs/en-US/welcome/release/inherits/inherit-fields.jpg deleted file mode 100644 index acdaddda9..000000000 Binary files a/docs/en-US/welcome/release/inherits/inherit-fields.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/inherits/inherit.jpg b/docs/en-US/welcome/release/inherits/inherit.jpg deleted file mode 100644 index ecc419daa..000000000 Binary files a/docs/en-US/welcome/release/inherits/inherit.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/inherits/inherited-blocks.jpg b/docs/en-US/welcome/release/inherits/inherited-blocks.jpg deleted file mode 100644 index e95928c20..000000000 Binary files a/docs/en-US/welcome/release/inherits/inherited-blocks.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/logger.md b/docs/en-US/welcome/release/logger.md deleted file mode 100644 index 34214734f..000000000 --- a/docs/en-US/welcome/release/logger.md +++ /dev/null @@ -1,67 +0,0 @@ -# v0.9.0: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) -- [LOGGER_BASE_PATH](/api/env#logger_base_path) - -## 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/en-US/welcome/release/tree-collection.md b/docs/en-US/welcome/release/tree-collection.md deleted file mode 100644 index 5b17858f7..000000000 --- a/docs/en-US/welcome/release/tree-collection.md +++ /dev/null @@ -1,45 +0,0 @@ -# Tree collection - -## Collection options - -```ts -{ - name: 'categories', - tree: 'adjacency-list', - fields: [ - { - type: 'belongsTo', - name: 'parent', - treeParent: true, - }, - { - type: 'hasMany', - name: 'children', - treeChildren: true, - }, - ], -} -``` - -## UI - -### Create tree collection - - - - -### Default fields - - - -### Table block - - - -### Add child - - - -### Expend/Collapse - - diff --git a/docs/en-US/welcome/release/tree-collection/add-child.jpg b/docs/en-US/welcome/release/tree-collection/add-child.jpg deleted file mode 100644 index 0c1285d5d..000000000 Binary files a/docs/en-US/welcome/release/tree-collection/add-child.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/tree-collection/expend-collapse.jpg b/docs/en-US/welcome/release/tree-collection/expend-collapse.jpg deleted file mode 100644 index de703b86b..000000000 Binary files a/docs/en-US/welcome/release/tree-collection/expend-collapse.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/tree-collection/init.jpg b/docs/en-US/welcome/release/tree-collection/init.jpg deleted file mode 100644 index 86b4d5b42..000000000 Binary files a/docs/en-US/welcome/release/tree-collection/init.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/tree-collection/tree-collection.jpg b/docs/en-US/welcome/release/tree-collection/tree-collection.jpg deleted file mode 100644 index 1733aca53..000000000 Binary files a/docs/en-US/welcome/release/tree-collection/tree-collection.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/tree-collection/tree-table.jpg b/docs/en-US/welcome/release/tree-collection/tree-table.jpg deleted file mode 100644 index 4eeb1e6f2..000000000 Binary files a/docs/en-US/welcome/release/tree-collection/tree-table.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/v08-1-collection-templates/v08-1-collection-templates.jpg b/docs/en-US/welcome/release/v08-1-collection-templates/v08-1-collection-templates.jpg deleted file mode 100644 index dd0a99af8..000000000 Binary files a/docs/en-US/welcome/release/v08-1-collection-templates/v08-1-collection-templates.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/v08-changelog.md b/docs/en-US/welcome/release/v08-changelog.md deleted file mode 100644 index 1d302a59a..000000000 --- a/docs/en-US/welcome/release/v08-changelog.md +++ /dev/null @@ -1,222 +0,0 @@ -# v0.8:Plugin manager & docs - -Starting with v0.8, NocoBase begins to provide an available plugin manager and development documentation. Here are the main changes in v0.8. - -## Tweaks to the top right corner of the interface - -- UI Editor -- Plugin Manager -- Plugin Settings Manager -- Personal Center - - - -## The new plugin manager - -v0.8 provides a powerful plugin manager for managing plugins in a no-code way. - -### Plugin manager flow - - - -### Plugin Manager interface - -Currently it is mainly used for disabling, activating and deleting local plugins. Built-in plugins cannot be deleted. - - - -### Plugin Manager command - -In addition to being able to activate and disable plugins from the no-code interface, you can also manage plugins more completely from the command line. - -``` -# Create a plugin -yarn pm create hello -# Register the plugin -yarn pm add hello -# Activate the plugin -yarn pm enable hello -# Disable the plugin -yarn pm disable hello -# Remove the plugin -yarn pm remove hello - -``` - -Note: Releases and upgrades for plugins will be supported in subsequent releases. - -``` -# Publish the plugin -yarn pm publish hello -# Publish the plugin -yarn pm upgrade hello - -``` - -For more plugin examples, see [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples). - -## Changes of plugin - -### Plugin’s directory structure - -``` -|- /hello - |- /src - |- /client # Plugin client - |- /server # Plugin server - |- client.d.ts - |- client.js - |- package.json # Plugin package information - |- server.d.ts - |- server.js - -``` - -### Plugin’s name specification - -NocoBase plugin is also an NPM package, the correspondence rule between plugin name and NPM package name is `${PLUGIN_PACKAGE_PREFIX}-${pluginName}`. - -`PLUGIN_PACKAGE_PREFIX` is the plugin package prefix, which can be customized in .env, [click here for PLUGIN_PACKAGE_PREFIX description](https://www.notion.so/api/env#plugin_package_prefix). - -For example, a project named `my-nocobase-app` adds the `hello` plugin with package name `@my-nocobase-app/plugin-hello`. - -`PLUGIN_PACKAGE_PREFIX` is configured as follows. - -``` -PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin- - -``` - -The correspondence between plugin names and package names is - -- `users` plugin package name is `@nocobase/plugin-users` -- `nocobase` plugin package name is `@nocobase/preset-nocobase` -- `hello` plugin package named `@my-nocobase-app/plugin-hello` - -### Plugin’s lifecycle - -v0.8 provides a more complete approach to the plugin lifecycle. - -``` -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class HelloPlugin extends Plugin { - afterAdd() { - // After the plugin has been added via pm.add - } - - beforeLoad() { - // Before all plugins are loaded, generally used to register classes and event listeners - } - - async load() { - // Load configuration - } - - async install(options?: InstallOptions) { - // Install logic - } - - async afterEnable() { - // After activation - } - - async afterDisable() { - // After disable - } - - async remove() { - // Remove logic - } -} - -export default HelloPlugin; - -``` - -### Front- and back-end entrance for plugins - -The lifecycle of the plugin is controlled by the server - -``` -import { Application } from '@nocobase/server'; - -const app = new Application({ - // ... -}); - -class MyPlugin extends Plugin { - afterAdd() {} - beforeLoad() {} - load() {} - install() {} - afterEnable() {} - afterDisable() {} - remove() {} -} - -app.plugin(MyPlugin, { name: 'my-plugin' }); - -``` - -The client side of the plugin exists as Context.Provider (similar to Middleware on the server side) - -``` -import React from 'react'; -import { Application } from '@nocobase/client'; - -const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`../plugins/${name}`); - }, -}); - -// When you visit the /hello page, it displays Hello world! -const HelloProvider = React.memo((props) => { - const location = useLocation(); - if (location.pathname === '/hello') { - return
    Hello world!
    - } - return <>{props.children} -}); -HelloProvider.displayName = 'HelloProvider' - -app.use(HelloProvider); - -``` - -## Custom business code - -v0.7 plugins are not complete, custom business code may be scattered in `packages/app/client` and `packages/app/server`, which is not conducive to upgrade and maintenance. v0.8 recommends organizing as a plugin package and using `yarn pm` to manage plugins. - -## More complete documentation is provided - -- **Welcome**: a quick look at NocoBase -- **Manual**: learn more about the core features provided by the NocoBase platform -- **Plugin Development Tutorial**: Advanced dive into plugin development -- **API Reference**: Check the API usage during plugin development -- **Client Components Library** (in preparation): provides examples and usage of NocoBase components - -## More plugin examples are provided - -- [command](https://github.com/nocobase/nocobase/tree/develop/packages/samples/command) -- [custom-block](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-block) -- [custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page) -- [custom-signup-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-signup-page) -- [hello](https://github.com/nocobase/nocobase/tree/develop/packages/samples/hello) -- [ratelimit](https://github.com/nocobase/nocobase/tree/develop/packages/samples/ratelimit) -- [shop-actions](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-actions) -- [shop-events](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-events) -- [shop-i18n](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-i18n) -- [shop-modeling](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-modeling) - -## Other new features and functionality - -- Import from Excel -- Bulk Update & Edit -- Graphical collection -- Workflow support for viewing execution history -- JSON field diff --git a/docs/en-US/welcome/release/v08-changelog/pm-flow.svg b/docs/en-US/welcome/release/v08-changelog/pm-flow.svg deleted file mode 100644 index d15cf800c..000000000 --- a/docs/en-US/welcome/release/v08-changelog/pm-flow.svg +++ /dev/null @@ -1 +0,0 @@ -
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    app/client plugins
    pm.enable
    pm.disable
    pm.remove
    \ No newline at end of file diff --git a/docs/en-US/welcome/release/v08-changelog/pm-ui.jpg b/docs/en-US/welcome/release/v08-changelog/pm-ui.jpg deleted file mode 100644 index 4c8fdd3c1..000000000 Binary files a/docs/en-US/welcome/release/v08-changelog/pm-ui.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/v08-changelog/topright.jpg b/docs/en-US/welcome/release/v08-changelog/topright.jpg deleted file mode 100644 index 8ce510973..000000000 Binary files a/docs/en-US/welcome/release/v08-changelog/topright.jpg and /dev/null differ diff --git a/docs/en-US/welcome/release/v10-changelog.md b/docs/en-US/welcome/release/v10-changelog.md deleted file mode 100644 index c05f3f1da..000000000 --- a/docs/en-US/welcome/release/v10-changelog.md +++ /dev/null @@ -1,233 +0,0 @@ -# v0.10: Update instructions - -## New features in the second quarter - -- Association field component improvements, support for multiple component switching - - Select - - Record picker - - Sub-form/Sub-details - - Sub-table - - File manager - - Title(read only) -- Quick creation of relational data, supports two quick creation modes - - Add in drop-down menu to quickly create a new record based on the title field - - Add in pop-up window to configure complex add forms -- Duplicate action, supports two modes - - Direct duplicate - - Copy into the form and continue to fill in -- Form data templates -- Filter data scope support variables -- List block -- Grid card block -- Mobile client plugin -- User authentication plugin, support for different authentication methods - - Email/Password - - SMS - - OIDC - - SAML -- Workflow nodes - - Manual node upgrade, support for adding and editing from existing collections - - Loop node - - Aggregate node -- File manager - - Provide file collection template - - Provide file manager component - -## Upgrading applications - -### Upgrading for Docker compose - -No change, upgrade reference [Upgrading for Docker compose](/welcome/getting-started/upgrading/docker-compose) - -### Upgrading for Git source code - -v0.10 has a major upgrade of dependencies, so to prevent errors when upgrading the source code, you need to delete the following directories before upgrading - -```bash -# Remove .umi cache -yarn rimraf -rf "./**/{.umi,.umi-production}" -# Delete compiled files -yarn rimraf -rf "./packages/*/*/{lib,esm,es,dist,node_modules}" -# Remove dependencies -yarn rimraf -rf node_modules -``` - -See [Upgrading for Git source code](/welcome/getting-started/upgrading/git-clone) for more details - -### Upgrading for create-nocobase-app - -It is recommended that `yarn create` re-download the new version and modify the `.env` configuration, for more details refer to [major version upgrade guide](/welcome/getting-started/upgrading/create-nocobase-app#major-upgrade) - -## Upcoming deprecated and potentially incompatible changes - -### Sub-table field component - -Not compatible with new version, block fields need to be removed and reassigned (UI reassignment only) - -### Attachment upload api changes - -In addition to the built-in attachments table, users can also custom file collection, the upload api for attachments has been changed from `/api/attachments:upload` to `/api/:create`, upload is deprecated, still compatible with v0.10 but will be Removed. - -### signin/signup api changes - -The nocobase kernel provides a more powerful [auth module](https://github.com/nocobase/nocobase/tree/main/packages/plugins/auth) with the following changes to the user login, registration, verification, and logout apis: - -```bash -/api/users:signin -> /api/auth:signIn -/api/users:signup -> /api/auth:signUp -/api/users:signout -> /api/auth:signOut -/api/users:check -> /api/auth:check -``` - -Note: The above users interface, which is deprecated, is still compatible with v0.10, but will be removed in the next major release. - -### Adjustments to date field filtering - -If date related filtering was previously configured in the data range, it needs to be deleted and reconfigured. - -## Third-party plugin upgrade guide - -### Dependencies upgrade - -dependencies mainly including - -- `react` upgrade to v18 -- `react-dom` upgrade to v18 -- `react-router` upgrade to v6.11 -- `umi` upgrade to v4 -- `dumi` upgrade to v2 - -The `package.json` dependencies should be changed to the latest version, e.g: - -```diff -{ - "devDependencies": { -+ "react": "^18". -+ "react-dom": "^18". -+ "react-router-dom": "^6.11.2". -- "react": "^17". -- "react-dom": "^17". -- "react-router-dom": "^5". - } -} -``` - -### Code changes - -Because react-router has been upgraded, the related code also needs to be changed, the main changes include - -### Layout Component - -Layout component needs to use `` instead of `props.children`. - -```diff -import React from 'react'; -+ import { Outlet } from 'react-router-dom'; - -export default function Layout(props) { - return ( -
    -- { props.children } -+ -
    - ); -} -``` - -if you use `React.cloneElement` to render the route component, you need to change it like this: - -```diff -import React from 'react'; -+ import { Outlet } from 'react-router-dom'; - -export default function RouteComponent(props) { - return ( -
    -- { React.cloneElement(props.children, { someProp: 'p1' }) } -+ -
    - ); -} -``` - -Change the route component to get the value from `useOutletContext` - -```diff -import React from 'react'; -+ import { useOutletContext } from 'react-router-dom'; - -- export function Comp(props){ -+ export function Comp() { -+ const props = useOutletContext(); - return props.someProp; -} -``` - -### Redirect - -`` is changed to ``. - -```diff -- -+ -``` - -### useHistory - -`useNavigate` is changed to `useHistory`. - -```diff -- import { useHistory } from 'react-router-dom'; -+ import { useNavigate} from 'react-router-dom'; - -- const history = useHistory(); -+ const navigate = useNavigate(); - -- history.push('/about') -+ navigate('/about') - -- history.replace('/about') -+ navigate('/about', { replace: true }) -``` - -### useLocation - -`useLocation()` is changed to `useLocation`. - -```diff -- const location= useLocation(); -+ const location= useLocation(); -``` - -`const { query } = useLocation()` is changed to `useSearchParams()`。 - -```diff -- const location = useLocation(); -- const query = location.query; -- const name = query.name; -+ const [searchParams, setSearchParams] = useSearchParams(); -+ searchParams.get('name'); -``` - -### path - -All of the following are valid route paths in v6: - -``` -/groups -/groups/admin -/users/:id -/users/:id/messages -/files/* -/files/:id/* -``` - -The following RegExp-style route paths are not valid in v6: - -``` -/tweets/:id(\d+) -/files/*/cat.jpg -/files-* -``` - -For more api changes, please refer to [react-router@6](https://reactrouter.com/en/main/upgrading/v5)。 diff --git a/docs/en-US/welcome/release/v11-changelog.md b/docs/en-US/welcome/release/v11-changelog.md deleted file mode 100644 index 7fec00b00..000000000 --- a/docs/en-US/welcome/release/v11-changelog.md +++ /dev/null @@ -1,120 +0,0 @@ -# v0.11: Update instructions - -## New features - -- New client application, plugin and router -- Ant design upgrade to v5 -- New plugin - - Data visualization - - API keys - - Google map - -## Incompatible changes - -### New client application, plugin and router - -#### Plugin changes - -before you had to pass a component and the component needed to pass `props.children`, for example: - -```tsx | pure -const HelloProvider = (props) => { - // do something logic - return
    - {props.children} -
    ; -} - -export default HelloProvider -``` - -now you need to change to the plugin way, for example: - -```diff | pure -+import { Plugin } from '@nocobase/client' - -const HelloProvider = (props) => { - // do something logic - return
    - {props.children} -
    ; -} - -+ export class HelloPlugin extends Plugin { -+ async load() { -+ this.app.addProvider(HelloProvider); -+ } -+ } - -- export default HelloProvider; -+ export default HelloPlugin; -``` - -plugins are very powerful and can do a lot of things in the `load` phase: - -- modify routes -- add Components -- add Providers -- add Scopes -- load other plugins - -#### Routing changes - -if you used `RouteSwitchContext` to modify the route before, you now need to replace it with a plugin: - -```tsx | pure -import { RouteSwitchContext } from '@nocobase/client'; - -const HelloProvider = () => { - const { routes, ...others } = useContext(RouteSwitchContext); - routes[1].routes.unshift({ - path: '/hello', - component: Hello, - }); - - return
    - - {props.children} - -
    -} -``` - -now you need to change to the plugin way, for example: - -```diff | pure -- import { RouteSwitchContext } from '@nocobase/client'; -+ import { Plugin } from '@nocobase/client'; - -const HelloProvider = (props) => { -- const { routes, ...others } = useContext(RouteSwitchContext); -- routes[1].routes.unshift({ -- path: '/hello', -- component: Hello, -- }); - - return
    -- - {props.children} -- -
    -} - -+ export class HelloPlugin extends Plugin { -+ async load() { -+ this.app.router.add('admin.hello', { -+ path: '/hello', -+ Component: Hello, -+ }); -+ this.app.addProvider(HelloProvider); -+ } -+ } -+ export default HelloPlugin; -``` - -more details can be found in [packages/core/client/src/application/index.md](https://github.com/nocobase/nocobase/blob/main/packages/core/client/src/application/index.md) - -### antd upgrade to v5 - -- antd related details view the official website [V4 to V5](https://ant.design/docs/react/migration-v5) -- `@formily/antd` replace with `@formily/antd-v5` diff --git a/docs/en-US/welcome/release/v12-changelog.md b/docs/en-US/welcome/release/v12-changelog.md deleted file mode 100644 index 3cf30b1d0..000000000 --- a/docs/en-US/welcome/release/v12-changelog.md +++ /dev/null @@ -1,181 +0,0 @@ -# v0.12: New plugin build tool - -## New Features - -- New plugin build tool. The built plugins will be able to be used directly on the production environment without the need for a second build. - -## Application upgrades - -### Upgrade of Docker installation - -No change, refer to [Docker Image Upgrade Guide](/welcome/getting-started/upgrading/docker-compose) for upgrade. - -### Upgrading source code installation - -The plugin build tool has been freshly upgraded, and the cache needs to be cleared after pulling new sources. - -```bash -git pull # Pull the new source code. -yarn clean # Clear the cache. -``` - -For more details, see [Git source upgrade guide](/welcome/getting-started/upgrading/git-clone). - -### Upgrading a create-nocobase-app installation - -Redownload the new version via `yarn create` and update the .env configuration, see [major version upgrade guide](/welcome/getting-started/upgrading/create-nocobase-app#Major version upgrade) for more details. - -## Incompatible changes - -### @nocobase/app-client and @nocobase/app-server merged into @nocobase-app - -Apps installed via create-nocobase-app no longer have a packages/app directory, and custom code in packages/app needs to be moved to the custom plugin. - -### The dist/client path of the app has changed. - -If you are configuring nginx yourself, you will need to make a similar adjustment - -```diff -server { -- root /app/nocobase/packages/app/client/dist; -+ root /app/nocobase/node_modules/@nocobase/app/dist/client; - - location / { -- root /app/nocobase/packages/app/client/dist; -+ root /app/nocobase/node_modules/@nocobase/app/dist/client; - try_files $uri $uri/ /index.html; - add_header Last-Modified $date_gmt; - add_header Cache-Control 'no-store, no-cache'; - if_modified_since off; - expires off; - etag off; - } -} -``` - -### Third party plugins need to be rebuilt - -Refer to the third-party plugin upgrade guide below - -## Third-party plugin upgrade guide - -### The plugin directory must have both `src/client` and `src/server` directories. - -```js -// src/client/index.ts -import { Plugin } from '@nocobase/client'; - -class MyPlugin extends Plugin { - async load() { - // ... - } -} - -export default MyPlugin; -``` - -```js -// src/server/index.ts -import { Plugin } from '@nocobase/server'; - -class MyPlugin extends Plugin { - async load() { - // ... - } -} - -export default MyPlugin; -``` - -Specific demo code can be referred to: [sample-hello](https://github.com/nocobase/nocobase/tree/main/packages/samples/hello) - -### Plugin's multilingual placement `src/locale` directory - -Both frontend and backend, multi-language translation files are placed in the `src/locale` directory, so the plugin doesn't need to load multi-language packages by itself. - -### Adjustment of plugin dependencies - -The dependencies of the plugin are divided into its own dependencies and global dependencies. Global dependencies are directly used globally and will not be packaged into the plugin product, while its own dependencies will be packaged into the product. After the plug-in is built, the production environment is plug-and-play, and there is no need to install dependencies or build twice. Adjustments to plugin dependencies include: - -- Put `@nocobase/*` related packages into `peerDependencies` and specify the version number as `0.x`; -- Place other dependencies in `devDependencies`, not `dependencies`, as the plugin will extract all the dependencies required by the production environment after packaging. - - -```diff -{ - "devDependencies": { - "@formily/react": "2.x", - "@formily/shared": "2.x", - "ahooks": "3.x", - "antd": "5.x", - "dayjs": "1.x", - "i18next": "22.x", - "react": "18.x", - "react-dom": "18.x", - "react-i18next": "11.x" - }, - "peerDependencies": { - "@nocobase/actions": "0.x", - "@nocobase/client": "0.x", - "@nocobase/database": "0.x", - "@nocobase/resourcer": "0.x", - "@nocobase/server": "0.x", - "@nocobase/test": "0.x", - "@nocobase/utils": "0.x" - } -} -``` - -### The output path of the plugin has been changed from `lib` to `dist` - -dist directory structure - -```bash -|- dist - |- client # Client-side, umd - |- index.js - |- index.d.ts - |- server # Server-side, cjs - |- index.js - |- index.d.ts - |- others - |- locale # multilingual package - |- node_modules # server dependencies -``` - -Other related adjustments include: - -Adjustment of the main parameter of package.json - -```diff -{ - - "main": "./lib/server/index.js", - + "main": "./dist/server/index.js", -} -``` - -client.d.ts - -```ts -export * from './dist/client'; -export { default } from './dist/client'; -``` - -client.js - -```js -module.exports = require('./dist/client/index.js'); -``` - -server.d.ts - -```ts -export * from './dist/server'; -export { default } from './dist/server'; -``` - -server.js - -```js -module.exports = require('./dist/server/index.js'); -``` diff --git a/docs/en-US/welcome/release/v13-changelog.md b/docs/en-US/welcome/release/v13-changelog.md deleted file mode 100644 index 166f7e5e4..000000000 --- a/docs/en-US/welcome/release/v13-changelog.md +++ /dev/null @@ -1,19 +0,0 @@ -# v0.13: New application status flow - -## New Features - -### Application status flow - - - -### Demo video - - - -## Upgrading - -- [Upgrading for Docker compose](/welcome/getting-started/upgrading/docker-compose) -- [Upgrading for create-nocobase-app](/welcome/getting-started/upgrading/create-nocobase-app) -- [Upgrading for Git source code](/welcome/getting-started/upgrading/git-clone) diff --git a/docs/en-US/welcome/release/v14-changelog.md b/docs/en-US/welcome/release/v14-changelog.md deleted file mode 100644 index a81dcac9c..000000000 --- a/docs/en-US/welcome/release/v14-changelog.md +++ /dev/null @@ -1,82 +0,0 @@ -# v0.14: New plugin manager, supports adding plugins through UI - -This release enables plug-and-play plugins in production environments. You can now add plugins directly through the UI, and support downloading from the npm registry (which can be private), local uploads, and URL downloads. - -## New features - -### New plugin manager interface - - - -### Uploaded plugins are located in the storage/plugins directory. - -The storage/plugins directory is used to upload plugins, and is organized as npm packages. - -```bash -|- /storage/ - |- /plugins/ - |- /@nocobase/ - |- /plugin-hello1/ - |- /plugin-hello2/ - |- /@foo/ - |- /bar/ - |- /my-nocobase-plugin-hello/ -``` - -### Plugin updates - -Currently, only plugins under storage/plugins can be updated, as shown here: - - - -Note: In order to facilitate maintenance and upgrading, and to avoid unavailability of the storage plugins due to upgrading, you can put the new plugin directly into storage/plugins and then perform the upgrade operation. - -## Incompatible changes - -### Changes to plugin names - -- PLUGIN_PACKAGE_PREFIX environment variable is no longer provided. -- Plugin names and package names are unified, old plugin names can still exist as aliases. - -### Improvements to pm.add - -```bash -# Use packageName instead of pluginName, lookup locally, error if not found -pm add packageName - -# Download from remote only if registry is provided, can also specify version -pm add packageName --registry=xx --auth-token=yy --version=zz - -# You can also provide a local zip, add multiple times and replace it with the last one -pm add /a/plugin.zip - -# Remote zip, replace it with the same name -pm add http://url/plugin.zip -``` - -### Nginx configuration changes - -Add `/static/plugins/` location - -```conf -server { - location ^~ /static/plugins/ { - proxy_pass http://127.0.0.1:13000/static/plugins/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_connect_timeout 600; - proxy_send_timeout 600; - proxy_read_timeout 600; - send_timeout 600; - } -} -``` - -More see full version of [nocobase.conf](https://github.com/nocobase/nocobase/blob/main/docker/nocobase/nocobase.conf) - -## Plugin development guide - -[Develop the first plugin](/development/your-fisrt-plugin) diff --git a/docs/zh-CN/api/acl/acl-resource.md b/docs/zh-CN/api/acl/acl-resource.md deleted file mode 100644 index 305b80bed..000000000 --- a/docs/zh-CN/api/acl/acl-resource.md +++ /dev/null @@ -1,57 +0,0 @@ -# ACLResource - -ACLResource,ACL 系统中的资源类。在 ACL 系统中,为用户授予权限时会自动创建对应的资源。 - - -## 类方法 - -### `constructor()` -构造函数 - -**签名** -* `constructor(options: AclResourceOptions)` - -**类型** -```typescript -type ResourceActions = { [key: string]: RoleActionParams }; - -interface AclResourceOptions { - name: string; // 资源名称 - role: ACLRole; // 资源所属角色 - actions?: ResourceActions; -} -``` - -**详细信息** - -`RoleActionParams`详见 [`aclRole.grantAction`](./acl-role.md#grantaction) - -### `getActions()` - -获取资源的所有 Action,返回结果为 `ResourceActions` 对象。 - -### `getAction()` -根据名称返回 Action 的参数配置,返回结果为 `RoleActionParams` 对象。 - -**详细信息** - -`RoleActionParams`详见 [`aclRole.grantAction`](./acl-role.md#grantaction) - -### `setAction()` - -在资源内部设置一个 Action 的参数配置,返回结果为 `RoleActionParams` 对象。 - -**签名** -* `setAction(name: string, params: RoleActionParams)` - -**详细信息** - -* name - 要设置的 action 名称 -* `RoleActionParams`详见 [`aclRole.grantAction`](./acl-role.md#grantaction) - -### `setActions()` - -**签名** -* `setActions(actions: ResourceActions)` - -批量调用 `setAction` 的便捷方法 diff --git a/docs/zh-CN/api/acl/acl-role.md b/docs/zh-CN/api/acl/acl-role.md deleted file mode 100644 index c6d8a1bbe..000000000 --- a/docs/zh-CN/api/acl/acl-role.md +++ /dev/null @@ -1,87 +0,0 @@ -# ACLRole - -ACLRole,ACL 系统中的用户角色类。在 ACL 系统中,通常使用 `acl.define` 定义角色。 - -## 类方法 - -### `constructor()` -构造函数 - -**签名** -* `constructor(public acl: ACL, public name: string)` - -**详细信息** -* acl - ACL 实例 -* name - 角色名称 - -### `grantAction()` - -为角色授予 Action 权限 - -**签名** -* `grantAction(path: string, options?: RoleActionParams)` - -**类型** -```typescript -interface RoleActionParams { - fields?: string[]; - filter?: any; - own?: boolean; - whitelist?: string[]; - blacklist?: string[]; - [key: string]: any; -} -``` - -**详细信息** - -* path - 资源Action路径,如 `posts:edit`,表示 `posts` 资源的 `edit` Action, 资源名称和 Action 之间使用 `:` 冒号分隔。 - -RoleActionParams 为授权时,对应 action 的可配置参数,用以实现更细粒度的权限控制。 - -* fields - 可访问的字段 - ```typescript - acl.define({ - role: 'admin', - actions: { - 'posts:view': { - // admin 用户可以请求 posts:view action,但是只有 fields 配置的字段权限 - fields: ["id", "title", "content"], - }, - }, - }); - ``` -* filter - 权限资源过滤配置 - ```typescript - acl.define({ - role: 'admin', - actions: { - 'posts:view': { - // admin 用户可以请求 posts:view action,但是列出的结果必须满足 filter 设置的条件。 - filter: { - createdById: '{{ ctx.state.currentUser.id }}', // 支持模板语法,可以取 ctx 中的值,将在权限判断时替换 - }, - }, - }, - }); - ``` -* own - 是否只能访问自己的数据 - ```typescript - const actionsWithOwn = { - 'posts:view': { - "own": true // - } - } - - // 等价于 - const actionsWithFilter = { - 'posts:view': { - "filter": { - "createdById": "{{ ctx.state.currentUser.id }}" - } - } - } - ``` -* whitelist - 白名单,只有在白名单中的字段才能被访问 -* blacklist - 黑名单,黑名单中的字段不能被访问 - diff --git a/docs/zh-CN/api/acl/acl.md b/docs/zh-CN/api/acl/acl.md deleted file mode 100644 index 720c905f5..000000000 --- a/docs/zh-CN/api/acl/acl.md +++ /dev/null @@ -1,253 +0,0 @@ -# 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。 -* Action:对资源的操作,如 `create`、`read`、`update`、`delete` 等。 -* 策略 (`ACLAvailableStrategy`): 通常每个角色都有自己的权限策略,策略中定义了默认情况下的用户权限。 -* 授权:在 `ACLRole` 实例中调用 `grantAction` 函数,为角色授予 `Action` 的访问权限。 -* 鉴权:在 `ACL` 实例中调用 `can` 函数,函数返回结果即为用户的鉴权结果。 - - -## 类方法 - -### `constructor()` - -构造函数,创建一个 `ACL` 实例。 - -```typescript -import { ACL } from '@nocobase/database'; - -const acl = new ACL(); -``` - -### `define()` - -定义一个 ACL 角色 - -**签名** -* `define(options: DefineOptions): ACLRole` - -**类型** - -```typescript -interface DefineOptions { - role: string; - allowConfigure?: boolean; - strategy?: string | AvailableStrategyOptions; - actions?: ResourceActionsOptions; - routes?: any; -} -``` - -**详细信息** - -* `role` - 角色名称 - -```typescript -// 定义一个名称为 admin 的角色 -acl.define({ - role: 'admin', -}); -``` - -* `allowConfigure` - 是否允许配置权限 -* `strategy` - 角色的权限策略 - * 可以为 `string`,为要使用的策略名,表示使用已定义的策略。 - * 可以为 `AvailableStrategyOptions`,为该角色定义一个新的策略,参考[`setAvailableActions()`](#setavailableactions)。 -* `actions` - 定义角色时,可传入角色可访问的 `actions` 对象, - 之后会依次调用 `aclRole.grantAction` 授予资源权限。详见 [`aclRole.grantAction`](./acl-role.md#grantaction) - -```typescript -acl.define({ - role: 'admin', - actions: { - 'posts:edit': {} - }, -}); -// 等同于 -const role = acl.define({ - role: 'admin', -}); - -role.grantAction('posts:edit', {}); -``` - -### `getRole()` - -根据角色名称返回已注册的角色对象 - -**签名** -* `getRole(name: string): ACLRole` - -### `removeRole()` - -根据角色名称移除角色 - -**签名** -* `removeRole(name: string)` - -### `can()` -鉴权函数 - -**签名** -* `can(options: CanArgs): CanResult | null` - -**类型** - -```typescript -interface CanArgs { - role: string; // 角色名称 - resource: string; // 资源名称 - action: string; //操作名称 -} - -interface CanResult { - role: string; // 角色名称 - resource: string; // 资源名称 - action: string; // 操作名称 - params?: any; // 注册权限时传入的参数 -} - -``` - -**详细信息** - -`can` 方法首先会判断角色是否有注册对应的 `Action` 权限,如果没有则会去判断角色的 `strategy` 是否匹配。 -调用返回为`null`时,表示角色无权限,反之返回 `CanResult`对象,表示角色有权限。 - -**示例** -```typescript -// 定义角色,注册权限 -acl.define({ - role: 'admin', - actions: { - 'posts:edit': { - fields: ['title', 'content'], - }, - }, -}); - -const canResult = acl.can({ - role: 'admin', - resource: 'posts', - action: 'edit', -}); -/** - * canResult = { - * role: 'admin', - * resource: 'posts', - * action: 'edit', - * params: { - * fields: ['title', 'content'], - * } - * } - */ - -acl.can({ - role: 'admin', - resource: 'posts', - action: 'destroy', -}); // null -``` -### `use()` - -**签名** -* `use(fn: any)` -向 middlewares 中添加中间件函数。 - -### `middleware()` - -返回一个中间件函数,用于在 `@nocobase/server` 中使用。使用此 `middleware` 之后,`@nocobase/server` 在每次请求处理之前都会进行权限判断。 - -### `allow()` - -设置资源为可公开访问 - -**签名** -* `allow(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc)` - -**类型** -```typescript -type ConditionFunc = (ctx: any) => Promise | boolean; -``` - -**详细信息** - -* resourceName - 资源名称 -* actionNames - 资源动作名 -* condition? - 配置生效条件 - * 传入 `string`,表示使用已定义的条件,注册条件使用 `acl.allowManager.registerCondition` 方法。 - ```typescript - acl.allowManager.registerAllowCondition('superUser', async () => { - return ctx.state.user?.id === 1; - }); - - // 开放 users:list 的权限,条件为 superUser - acl.allow('users', 'list', 'superUser'); - ``` - * 传入 ConditionFunc,可接收 `ctx` 参数,返回 `boolean`,表示是否生效。 - ```typescript - // 当用户ID为1时,可以访问 user:list - acl.allow('users', 'list', (ctx) => { - return ctx.state.user?.id === 1; - }); - ``` - -**示例** - -```typescript -// 注册 users:login 可以被公开访问 -acl.allow('users', 'login'); -``` - -### `setAvailableActions()` - -**签名** - -* `setAvailableStrategy(name: string, options: AvailableStrategyOptions)` - -注册一个可用的权限策略 - -**类型** - -```typescript -interface AvailableStrategyOptions { - displayName?: string; - actions?: false | string | string[]; - allowConfigure?: boolean; - resource?: '*'; -} -``` - -**详细信息** - -* displayName - 策略名称 -* allowConfigure - 此策略是否拥有 **配置资源** 的权限,设置此项为`true`之后,请求判断在 `ACL` 中注册成为 `configResources` 资源的权限,会返回通过。 -* actions - 策略内的 actions 列表,支持通配符 `*` -* resource - 策略内的 resource 定义,支持通配符 `*` - diff --git a/docs/zh-CN/api/actions.md b/docs/zh-CN/api/actions.md deleted file mode 100644 index 0aa20b605..000000000 --- a/docs/zh-CN/api/actions.md +++ /dev/null @@ -1,356 +0,0 @@ -# @nocobase/actions - -## 概览 - -针对常用的 CRUD 等数据资源的操作,NocoBase 内置了对应操作方法,并通过数据表资源自动映射相关的操作。 - -```javascript -import { Application } from "@nocobase/server"; - -const app = new Application({ - database: { - dialect: 'sqlite', - storage: './db.sqlite', - }, - registerActions: true // 注册内置资源操作,默认为 True -}); - -``` - - -内置的操作方法都是注册在 `application` 中的 `resourcer` 实例上。 -通常情况下无需直接调用内置的 action 方法,在需要扩展默认操作行为时,可以在自定义的操作方法内调用默认方法。 - -## 资源操作 - -### `list()` - -获取数据列表。对应资源操作的 URL 为 `GET /api/:list`。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `filter` | `Filter` | - | 过滤参数 | -| `fields` | `string[]` | - | 要获取的字段 | -| `except` | `string[]` | - | 要排除的字段 | -| `appends` | `string[]` | - | 要附加的关系字段 | -| `sort` | `string[]` | - | 排序参数 | -| `page` | `number` | 1 | 分页 | -| `pageSize` | `number` | 20 | 每页数据条数 | - -**示例** - -当需要提供一个查询数据列表的接口,但不是默认以 JSON 格式输出时,可以基于内置默认方法进行扩展: - -```ts -import actions from '@nocobase/actions'; - -app.actions({ - async ['books:list'](ctx, next) { - ctx.action.mergeParams({ - except: ['content'] - }); - - await actions.list(ctx, async () => { - const { rows } = ctx.body; - // transform JSON to CSV output - ctx.body = rows.map(row => Object.keys(row).map(key => row[key]).join(',')).join('\n'); - ctx.type = 'text/csv'; - - await next(); - }); - } -}); -``` - -请求示例,将获得 CSV 格式文件的返回: - -```shell -curl -X GET http://localhost:13000/api/books:list -``` - -### `get()` - -获取单条数据。对应资源操作的 URL 为 `GET /api/:get`。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `filterByTk` | `number \| string` | - | 过滤主键 | -| `filter` | `Filter` | - | 过滤参数 | -| `fields` | `string[]` | - | 要获取的字段 | -| `except` | `string[]` | - | 要排除的字段 | -| `appends` | `string[]` | - | 要附加的关系字段 | -| `sort` | `string[]` | - | 排序参数 | -| `page` | `number` | 1 | 分页 | -| `pageSize` | `number` | 20 | 每页数据条数 | - -**示例** - -基于 NocoBase 内置的文件管理插件,可以扩展当客户端请求以资源标识下载一个文件时,返回文件流: - -```ts -import path from 'path'; -import actions from '@nocobase/actions'; -import { STORAGE_TYPE_LOCAL } from '@nocobase/plugin-file-manager'; - -app.actions({ - async ['attachments:get'](ctx, next) { - ctx.action.mergeParams({ - appends: ['storage'], - }); - - await actions.get(ctx, async () => { - if (ctx.accepts('json', 'application/octet-stream') === 'json') { - return next(); - } - - const { body: attachment } = ctx; - const { storage } = attachment; - - if (storage.type !== STORAGE_TYPE_LOCAL) { - return ctx.redirect(attachment.url); - } - - ctx.body = fs.createReadStream(path.resolve(storage.options.documentRoot?, storage.path)); - ctx.attachment(attachment.filename); - ctx.type = 'application/octet-stream'; - - await next(); - }); - } -}); -``` - -请求示例,将获得文件流的返回: - -```shell -curl -X GET -H "Accept: application/octet-stream" http://localhost:13000/api/attachments:get?filterByTk=1 -``` - -### `create()` - -创建单条数据。对应资源操作的 URL 为 `POST /api/:create`。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `values` | `Object` | - | 要创建的数据 | - -**示例** - -类似文件管理插件,创建带有二进制内容的数据作为上传文件的附件: - -```ts -import multer from '@koa/multer'; -import actions from '@nocobase/actions'; - -app.actions({ - async ['files:create'](ctx, next) { - if (ctx.request.type === 'application/json') { - return actions.create(ctx, next); - } - - if (ctx.request.type !== 'multipart/form-data') { - return ctx.throw(406); - } - - // 文件保存处理仅用 multer() 作为示例,不代表完整的逻辑 - multer().single('file')(ctx, async () => { - const { file, body } = ctx.request; - const { filename, mimetype, size, path } = file; - - ctx.action.mergeParams({ - values: { - filename, - mimetype, - size, - path: file.path, - meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; - } - }); - - await actions.create(ctx, next); - }); - } -}); -``` - -请求示例,可以创建文件表的普通数据,也可以含附件一起提交: - -```shell -# 仅创建普通数据 -curl -X POST -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:create" - -# 含附件一起提交 -curl -X POST -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:create" -``` - -### `update()` - -更新一条或多条数据。对应的 URL 为 `PUT /api/:update`。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `filter` | `Filter` | - | 过滤参数 | -| `filterByTk` | `number \| string` | - | 过滤主键 | -| `values` | `Object` | - | 更新数据值 | - -注:参数中的 `filter` 和 `filterByTk` 至少提供一项。 - -**示例** - -类似 `create()` 的例子,更新文件记录可以扩展为可携带二进制内容的数据作为更新的文件: - -```ts -import multer from '@koa/multer'; -import actions from '@nocobase/actions'; - -app.actions({ - async ['files:update'](ctx, next) { - if (ctx.request.type === 'application/json') { - return actions.update(ctx, next); - } - - if (ctx.request.type !== 'multipart/form-data') { - return ctx.throw(406); - } - - // 文件保存处理仅用 multer() 作为示例,不代表完整的逻辑 - multer().single('file')(ctx, async () => { - const { file, body } = ctx.request; - const { filename, mimetype, size, path } = file; - - ctx.action.mergeParams({ - values: { - filename, - mimetype, - size, - path: file.path, - meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; - } - }); - - await actions.update(ctx, next); - }); - } -}); -``` - -请求示例,可以创建文件表的普通数据,也可以含附件一起提交: - -```shell -# 仅创建普通数据 -curl -X PUT -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:update" - -# 含附件一起提交 -curl -X PUT -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:update" -``` - -### `destroy()` - -删除一条或多条数据。对应的 URL 为 `DELETE /api/:destroy`。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `filter` | `Filter` | - | 过滤参数 | -| `filterByTk` | `number \| string` | - | 过滤主键 | - -注:参数中的 `filter` 和 `filterByTk` 至少提供一项。 - -**示例** - -类似对文件管理插件扩展一个删除文件数据也需要同时删除对应文件的操作处理: - -```ts -import actions from '@nocobase/actions'; - -app.actions({ - async ['files:destroy'](ctx, next) { - // const repository = getRepositoryFromParams(ctx); - - // const { filterByTk, filter } = ctx.action.params; - - // const items = await repository.find({ - // fields: [repository.collection.filterTargetKey], - // appends: ['storage'], - // filter, - // filterByTk, - // context: ctx, - // }); - - // await items.reduce((promise, item) => promise.then(async () => { - // await item.removeFromStorage(); - // await item.destroy(); - // }), Promise.resolve()); - - await actions.destroy(ctx, async () => { - // do something - await next(); - }); - } -}); -``` - -### `move()` -对应的 URL 为 `POST /api/:move`。 - -此方法用于移动数据,调整数据的排序。例如在页面中,拖拽一个元素到另一个元素的上方或下方,可调用此方法实现顺序调整。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -|----------|-------------| -- |---------------| -| `sourceId` | `targetKey` | - | 移动的元素ID | -| `targetId` | `targetKey` | - | 与移动元素交换位置的元素ID | -| `sortField` | `string` | `sort` | 排序存储的字段名 | -| `targetScope` | `string` | - | 排序的scope,一个 resource 可以按照不同的 scope 排序 | -| `sticky` | `boolean` | - | 是否置顶移动的元素 | -| `method` | `insertAfter` \| `prepend` | - | 插入类型,插入目标元素之前还是之后 | - -## 关系资源资源操作 - -### `add()` - -添加与对象的关联关系,对应的 URL 为 `POST /api/:add`。适用于 `hasMany` 和 `belongsToMany` 关联。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -|----------|-------------| --- | --- | -| `values` | `TargetKey \| TargetKey[]` | - | 添加的关联对象ID | - -### `remove()` -移除与对象的关联关系,对应的 URL 为 `POST /api/:remove`。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -|----------|-------------| --- | --- | -| `values` | `TargetKey \| TargetKey[]` | - | 移除的关联对象ID | - -### `set()` -设置关联的关联对象,对应的 URL 为 `POST /api/:set`。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -|----------|-------------| --- | --- | -| `values` | `TargetKey \| TargetKey[]` | - | 设置的关联对象的ID | - -### `toggle()` - -切换关联的关联对象,对应的 URL 为 `POST /api/:toggle`。`toggle` 在内部判断关联对象是否已经存在,如果存在则移除,如果不存在则添加。 - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -|----------|-------------| -- | --- | -| `values` | `TargetKey` | - | 切换的关联对象的ID | diff --git a/docs/zh-CN/api/cli.md b/docs/zh-CN/api/cli.md deleted file mode 100644 index 5464c4361..000000000 --- a/docs/zh-CN/api/cli.md +++ /dev/null @@ -1,350 +0,0 @@ -# @nocobase/cli - -NocoBase CLI 旨在帮助你开发、构建和部署 NocoBase 应用。 - - - -NocoBase CLI 支持 ts-node 和 node 两种运行模式 - -- ts-node 模式(默认):用于开发环境,支持实时编译,但是响应较慢 -- node 模式:用于生产环境,响应迅速,但需要先执行 `yarn nocobase build` 将全部源码进行编译 - - - -## 使用说明 - -```bash -$ yarn nocobase -h - -Usage: nocobase [command] [options] - -Options: - -h, --help - -Commands: - console - db:auth 校验数据库是否连接成功 - db:sync 通过 collections 配置生成相关数据表和字段 - install 安装 - start 生产环境启动应用 - build 编译打包 - clean 删除编译之后的文件 - dev 启动应用,用于开发环境,支持实时编译 - doc 文档开发 - test 测试 - umi - upgrade 升级 - migrator 数据迁移 - pm 插件管理器 - help -``` - -## 在脚手架里应用 - -应用脚手架 `package.json` 里的 `scripts` 如下: - -```json -{ - "scripts": { - "dev": "nocobase dev", - "start": "nocobase start", - "clean": "nocobase clean", - "build": "nocobase build", - "test": "nocobase test", - "pm": "nocobase pm", - "postinstall": "nocobase postinstall" - } -} -``` - -## 命令行扩展 - -NocoBase CLI 基于 [commander](https://github.com/tj/commander.js) 构建,你可以自由扩展命令,扩展的 command 可以写在 `app/server/index.ts` 里: - -```ts -const app = new Application(config); - -app.command('hello').action(() => {}); -``` - -或者,写在插件里: - -```ts -class MyPlugin extends Plugin { - beforeLoad() { - this.app.command('hello').action(() => {}); - } -} -``` - -终端运行 - -```bash -$ yarn nocobase hello -``` - -## 内置命令行 - -按使用频率排序 - -### `dev` - -开发环境下,启动应用,代码实时编译。 - - -NocoBase 未安装时,会自动安装(参考 install 命令) - - -```bash -Usage: nocobase dev [options] - -Options: - -p, --port [port] - --client - --server - -h, --help -``` - -示例 - -```bash -# 启动应用,用于开发环境,实时编译 -yarn nocobase dev -# 只启动服务端 -yarn nocobase dev --server -# 只启动客户端 -yarn nocobase dev --client -``` - -### `start` - -生产环境下,启动应用,代码需要 yarn build。 - - - -- NocoBase 未安装时,会自动安装(参考 install 命令) -- 源码有修改时,需要重新打包(参考 build 命令) - - - -```bash -$ yarn nocobase start -h - -Usage: nocobase start [options] - -Options: - -p, --port - -s, --silent - -h, --help -``` - -示例 - -```bash -# 启动应用,用于生产环境, -yarn nocobase start -``` - -### `install` - -安装 - -```bash -$ yarn nocobase install -h - -Usage: nocobase install [options] - -Options: - -f, --force - -c, --clean - -s, --silent - -l, --lang [lang] - -e, --root-email - -p, --root-password - -n, --root-nickname [rootNickname] - -h, --help -``` - -示例 - -```bash -# 初始安装 -yarn nocobase install -l zh-CN -e admin@nocobase.com -p admin123 -# 删除 NocoBase 的所有数据表,并重新安装 -yarn nocobase install -f -l zh-CN -e admin@nocobase.com -p admin123 -# 清空数据库,并重新安装 -yarn nocobase install -c -l zh-CN -e admin@nocobase.com -p admin123 -``` - - - -`-f/--force` 和 `-c/--clean` 的区别 -- `-f/--force` 删除 NocoBase 的数据表 -- `-c/--clean` 清空数据库,所有数据表都会被删除 - - - -### `upgrade` - -升级 - -```bash -yarn nocobase upgrade -``` - -### `test` - -jest 测试,支持所有 [jest-cli](https://jestjs.io/docs/cli) 的 options,除此之外还扩展了 `-c, --db-clean` 的支持。 - -```bash -$ yarn nocobase test -h - -Usage: nocobase test [options] - -Options: - -c, --db-clean 运行所有测试前清空数据库 - -h, --help -``` - -示例 - -```bash -# 运行所有测试文件 -yarn nocobase test -# 运行指定文件夹下所有测试文件 -yarn nocobase test packages/core/server -# 运行指定文件里的所有测试 -yarn nocobase test packages/core/database/src/__tests__/database.test.ts - -# 运行测试前,清空数据库 -yarn nocobase test -c -yarn nocobase test packages/core/server -c -``` - -### `build` - -代码部署到生产环境前,需要将源码编译打包,如果代码有修改,也需要重新构建。 - -```bash -# 所有包 -yarn nocobase build -# 指定包 -yarn nocobase build app/server app/client -``` - -### `clean` - -删除编译之后的文件 - -```bash -yarn clean -# 等同于 -yarn rimraf -rf packages/*/*/{lib,esm,es,dist} -``` - -### `doc` - -文档开发 - -```bash -# 启动文档 -yarn doc --lang=zh-CN # 等同于 yarn doc dev -# 构建文档,默认输出到 ./docs/dist/ 目录下 -yarn doc build -# 查看 dist 输出的文档最终效果 -yarn doc serve --lang=zh-CN -``` - -### `db:auth` - -校验数据库是否连接成功 - -```bash -$ yarn nocobase db:auth -h - -Usage: nocobase db:auth [options] - -Options: - -r, --retry [retry] 重试次数 - -h, --help -``` - -### `db:sync` - -通过 collections 配置生成数据表和字段 - -```bash -$ yarn nocobase db:sync -h - -Usage: nocobase db:sync [options] - -Options: - -f, --force - -h, --help display help for command -``` - -### `migrator` - -数据迁移 - -```bash -$ yarn nocobase migrator - -Positional arguments: - - up Applies pending migrations - down Revert migrations - pending Lists pending migrations - executed Lists executed migrations - create Create a migration file -``` - -### `pm` - -插件管理器 - -```bash -# 创建插件 -yarn pm create hello -# 注册插件 -yarn pm add hello -# 激活插件 -yarn pm enable hello -# 禁用插件 -yarn pm disable hello -# 删除插件 -yarn pm remove hello -``` - -未实现 - -```bash -# 升级插件 -yarn pm upgrade hello -# 发布插件 -yarn pm publish hello -``` - -### `umi` - -`app/client` 基于 [umi](https://umijs.org/) 构建,可以通过 `nocobase umi` 来执行其他相关命令。 - -```bash -# 生成开发环境所需的 .umi 缓存 -yarn nocobase umi generate tmp -``` - -### `help` - -帮助命令,也可以用 option 参数,`-h` 和 `--help` - -```bash -# 查看所有 cli -yarn nocobase help -# 也可以用 -h -yarn nocobase -h -# 或者 --help -yarn nocobase --help -# 查看 db:sync 命令的 option -yarn nocobase db:sync -h -``` diff --git a/docs/zh-CN/api/client/application.md b/docs/zh-CN/api/client/application.md deleted file mode 100644 index 36ee426bb..000000000 --- a/docs/zh-CN/api/client/application.md +++ /dev/null @@ -1,60 +0,0 @@ -# Application - -## 构造函数 - -### `constructor()` - -创建一个应用实例。 - -**签名** - -* `constructor(options: ApplicationOptions)` - -**示例** - -```ts -const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`../plugins/${name}`); - }, -}); -``` - -## 方法 - -### use() - -添加 Providers,内置 Providers 有: - -- APIClientProvider -- I18nextProvider -- AntdConfigProvider -- SystemSettingsProvider -- PluginManagerProvider -- SchemaComponentProvider -- BlockSchemaComponentProvider -- AntdSchemaComponentProvider -- ACLProvider -- RemoteDocumentTitleProvider - -### render() - -渲染 App 组件 - -```ts -import { Application } from '@nocobase/client'; - -export const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`../plugins/${name}`); - }, -}); - -export default app.render(); -``` diff --git a/docs/zh-CN/api/client/extensions/acl.md b/docs/zh-CN/api/client/extensions/acl.md deleted file mode 100644 index 09d8e9a94..000000000 --- a/docs/zh-CN/api/client/extensions/acl.md +++ /dev/null @@ -1,23 +0,0 @@ -# ACL - -## Components - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -## Hooks - -### `useACLContext()` - -### `useACLRoleContext()` - -### `useRoleRecheck()` diff --git a/docs/zh-CN/api/client/extensions/block-provider.md b/docs/zh-CN/api/client/extensions/block-provider.md deleted file mode 100644 index 6b3334a1a..000000000 --- a/docs/zh-CN/api/client/extensions/block-provider.md +++ /dev/null @@ -1,25 +0,0 @@ -# BlockProvider - -## 内核方法 - -### `` - -### `useBlockRequestContext()` - -## 内置 BlockProvider 组件 - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` diff --git a/docs/zh-CN/api/client/extensions/collection-manager.md b/docs/zh-CN/api/client/extensions/collection-manager.md deleted file mode 100644 index d4f65dfbf..000000000 --- a/docs/zh-CN/api/client/extensions/collection-manager.md +++ /dev/null @@ -1,267 +0,0 @@ -# CollectionManager - -## Components - -### CollectionManagerProvider - -```jsx | pure - -``` - -### CollectionProvider - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - -``` - -如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。 - -```jsx | pure -const collections = [ - { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], - } -]; - - - -``` - -### CollectionFieldProvider - -```jsx | pure -const field = { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, -}; - -``` - -如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。 - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - - - -``` - -### CollectionField - -万能字段组件,需要与 `` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。 - -```ts -{ - name: 'title', - 'x-decorator': 'FormItem', - 'x-decorator-props': {}, - 'x-component': 'CollectionField', - 'x-component-props': {}, - properties: {}, -} -``` - -## Hooks - -### useCollectionManager() - -与 `` 搭配使用 - -```jsx | pure -const { collections, get } = useCollectionManager(); -``` - -### useCollection() - -与 `` 搭配使用 - -```jsx | pure -const { name, fields, getField, findField, resource } = useCollection(); -``` - -### useCollectionField() - -与 `` 搭配使用 - -```jsx | pure -const { name, uiSchema, resource } = useCollectionField(); -``` - -resource 需要与 `` 搭配使用,用于提供当前数据表行记录的上下文。如: - -# CollectionManager - -## Components - -### CollectionManagerProvider - -```jsx | pure - -``` - -### CollectionProvider - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - -``` - -如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。 - -```jsx | pure -const collections = [ - { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], - } -]; - - - -``` - -### CollectionFieldProvider - -```jsx | pure -const field = { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, -}; - -``` - -如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。 - -```jsx | pure -const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { - type: 'string', - 'x-component': 'Input' - }, - }, - ], -}; - - - -``` - -### CollectionField - -万能字段组件,需要与 `` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。 - -```ts -{ - name: 'title', - 'x-decorator': 'FormItem', - 'x-decorator-props': {}, - 'x-component': 'CollectionField', - 'x-component-props': {}, - properties: {}, -} -``` - -## Hooks - -### useCollectionManager() - -与 `` 搭配使用 - -```jsx | pure -const { collections, get } = useCollectionManager(); -``` - -### useCollection() - -与 `` 搭配使用 - -```jsx | pure -const { name, fields, getField, findField, resource } = useCollection(); -``` - -### useCollectionField() - -与 `` 搭配使用 - -```jsx | pure -const { name, uiSchema, resource } = useCollectionField(); -``` - -resource 需要与 `` 搭配使用,用于提供当前数据表行记录的上下文。 \ No newline at end of file diff --git a/docs/zh-CN/api/client/extensions/schema-component.md b/docs/zh-CN/api/client/extensions/schema-component.md deleted file mode 100644 index 832818f75..000000000 --- a/docs/zh-CN/api/client/extensions/schema-component.md +++ /dev/null @@ -1,14 +0,0 @@ -# 适配的 Schema 组件 - -## Common - -- DndContext -- SortableItem - -## And Design - -- Action -- BlockItem -- Calendar -- CardItem -- Cascader diff --git a/docs/zh-CN/api/client/index.md b/docs/zh-CN/api/client/index.md deleted file mode 100644 index 3a2170da0..000000000 --- a/docs/zh-CN/api/client/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Overview - -test diff --git a/docs/zh-CN/api/client/router.md b/docs/zh-CN/api/client/router.md deleted file mode 100644 index 14f2e4956..000000000 --- a/docs/zh-CN/api/client/router.md +++ /dev/null @@ -1,183 +0,0 @@ -# Router - -## API - -### 初始化 - -```tsx | pure - -const app = new Application({ - router: { - type: 'browser' // type 的默认值就是 `browser` - } -}) - -// or -const app = new Application({ - router: { - type: 'memory', - initialEntries: ['/'] - } -}) -``` - -### 添加路由 - -#### 基础用法 - -```tsx | pure -import { RouteObject } from 'react-router-dom' -const app = new Application() - -const Hello = () => { - return
    Hello
    -} - -// 第一个参数是名称, 第二个参数是 `RouteObject` -app.router.add('root', { - path: '/', - element: -}) - -app.router.add('root', { - path: '/', - Component: Hello -}) -``` - -#### 支持 Component 是字符串 - -```tsx | pure -// register Hello -app.addComponents({ - Hello -}) - -// Component is `Hello` string -app.router.add('root', { - path: '/', - Component: 'Hello' -}) -``` - -#### 嵌套路由 - -```tsx | pure -import { Outlet } from 'react-router-dom' - -const Layout = () => { - return
    - Home - about - - -
    -} - -const Home = () => { - return
    Home
    -} - -const About = () => { - return
    About
    -} - -app.router.add('root', { - element: -}) -app.router.add('root.home', { - path: '/home', - element: -}) -app.router.add('root.about', { - path: '/about', - element: -}) -``` - -它将会被渲染为如下形式: - -```tsx | pure -{ - element: , - children: [ - { - path: '/home', - element: - }, - { - path: '/about', - element: - } - ] -} -``` - -### 删除路由 - -```tsx | pure -// 传递 name 即可删除 -app.router.remove('root.home') -app.router.remove('hello') -``` - -#### 插件中修改路由 - -```tsx | pure -class MyPlugin extends Plugin { - async load() { - // add route - this.app.router.add('hello', { - path: '/hello', - element:
    hello
    , - }) - - // remove route - this.app.router.remove('world'); - } -} -``` - -## 示例 - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Link, Outlet } from 'react-router-dom'; -import { Application } from '@nocobase/client'; - -const Home = () =>

    Home

    ; -const About = () =>

    About

    ; - -const Layout = () => { - return
    -
    Home, About
    - -
    -} - -const app = new Application({ - router: { - type: 'memory', - initialEntries: ['/'] - } -}) - -app.router.add('root', { - element: -}) - -app.router.add('root.home', { - path: '/', - element: -}) - -app.router.add('root.about', { - path: '/about', - element: -}) - -export default app.getRootComponent(); -``` diff --git a/docs/zh-CN/api/client/schema-designer/schema-component.md b/docs/zh-CN/api/client/schema-designer/schema-component.md deleted file mode 100644 index 61f646103..000000000 --- a/docs/zh-CN/api/client/schema-designer/schema-component.md +++ /dev/null @@ -1,13 +0,0 @@ -# SchemaComponent - -## 核心组件 - -### `` -### `` -### `` - -## 核心方法 - -### `createDesignable()` -### `useDesignable()` -### `useCompile()` diff --git a/docs/zh-CN/api/client/schema-designer/schema-initializer.md b/docs/zh-CN/api/client/schema-designer/schema-initializer.md deleted file mode 100644 index 32a6a844a..000000000 --- a/docs/zh-CN/api/client/schema-designer/schema-initializer.md +++ /dev/null @@ -1,19 +0,0 @@ -# SchemaInitializer - -用于各种 schema 的初始化。新增的 schema 可以插入到某个已有 schema 节点的任意位置,包括: - -```ts -{ - properties: { - // beforeBegin 在当前节点的前面插入 - node1: { - properties: { - // afterBegin 在当前节点的第一个子节点前面插入 - // ... - // beforeEnd 在当前节点的最后一个子节点后面 - }, - }, - // afterEnd 在当前节点的后面 - }, -} -``` diff --git a/docs/zh-CN/api/client/schema-designer/schema-settings.md b/docs/zh-CN/api/client/schema-designer/schema-settings.md deleted file mode 100644 index b014e7435..000000000 --- a/docs/zh-CN/api/client/schema-designer/schema-settings.md +++ /dev/null @@ -1,25 +0,0 @@ -# SchemaSettings - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` - -### `` diff --git a/docs/zh-CN/api/database/collection.md b/docs/zh-CN/api/database/collection.md deleted file mode 100644 index 87faee2bd..000000000 --- a/docs/zh-CN/api/database/collection.md +++ /dev/null @@ -1,511 +0,0 @@ -# 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)。 - -## 构造函数 - -**签名** - -* `constructor(options: CollectionOptions, context: CollectionContext)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.name` | `string` | - | collection 标识 | -| `options.tableName?` | `string` | - | 数据库表名,如不传则使用 `options.name` 的值 | -| `options.fields?` | `FieldOptions[]` | - | 字段定义,详见 [Field](./field) | -| `options.model?` | `string \| ModelStatic` | - | Sequelize 的 Model 类型,如果使用的是 `string`,则需要调用之前在 db 上注册过该模型名称 | -| `options.repository?` | `string \| RepositoryType` | - | 数据仓库类型,如果使用 `string`,则需要调用之前在 db 上注册过该仓库类型 | -| `options.sortable?` | `string \| boolean \| { name?: string; scopeKey?: string }` | - | 数据可排序字段配置,默认不排序 | -| `options.autoGenId?` | `boolean` | `true` | 是否自动生成唯一主键,默认为 `true` | -| `context.database` | `Database` | - | 所在的上下文环境数据库 | - -**示例** - -创建一张文章表: - -```ts -const posts = new Collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - }, - { - type: 'double', - name: 'price', - } - ] -}, { - // 已存在的数据库实例 - database: db -}); -``` - -## 实例成员 - -### `options` - -数据表配置初始参数。与构造函数的 `options` 参数一致。 - -### `context` - -当前数据表所属的上下文环境,目前主要是数据库实例。 - -### `name` - -数据表名称。 - -### `db` - -所属数据库实例。 - -### `filterTargetKey` - -作为主键的字段名。 - -### `isThrough` - -是否为中间表。 - -### `model` - -匹配 Sequelize 的 Model 类型。 - -### `repository` - -数据仓库实例。 - -## 字段配置方法 - -### `getField()` - -获取数据表已定义对应名称的字段对象。 - -**签名** - -* `getField(name: string): Field` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 字段名称 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -const field = posts.getField('title'); -``` - -### `setField()` - -对数据表设置字段。 - -**签名** - -* `setField(name: string, options: FieldOptions): Field` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 字段名称 | -| `options` | `FieldOptions` | - | 字段配置,详见 [Field](./field) | - -**示例** - -```ts -const posts = db.collection({ name: 'posts' }); - -posts.setField('title', { type: 'string' }); -``` - -### `setFields()` - -对数据表批量设置多个字段。 - -**签名** - -* `setFields(fields: FieldOptions[], resetFields = true): Field[]` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `fields` | `FieldOptions[]` | - | 字段配置,详见 [Field](./field) | -| `resetFields` | `boolean` | `true` | 是否重置已存在的字段 | - -**示例** - -```ts -const posts = db.collection({ name: 'posts' }); - -posts.setFields([ - { type: 'string', name: 'title' }, - { type: 'double', name: 'price' } -]); -``` - -### `removeField()` - -移除数据表已定义对应名称的字段对象。 - -**签名** - -* `removeField(name: string): void | Field` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 字段名称 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.removeField('title'); -``` - -### `resetFields()` - -重置(清空)数据表的字段。 - -**签名** - -* `resetFields(): void` - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.resetFields(); -``` - -### `hasField()` - -判断数据表是否已定义对应名称的字段对象。 - -**签名** - -* `hasField(name: string): boolean` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 字段名称 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.hasField('title'); // true -``` - -### `findField()` - -查找数据表中符合条件的字段对象。 - -**签名** - -* `findField(predicate: (field: Field) => boolean): Field | undefined` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `predicate` | `(field: Field) => boolean` | - | 查找条件 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.findField(field => field.name === 'title'); -``` - -### `forEachField()` - -遍历数据表中的字段对象。 - -**签名** - -* `forEachField(callback: (field: Field) => void): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `callback` | `(field: Field) => void` | - | 回调函数 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.forEachField(field => console.log(field.name)); -``` - -## 索引配置方法 - -### `addIndex()` - -添加数据表索引。 - -**签名** - -* `addIndex(index: string | string[] | { fields: string[], unique?: boolean,[key: string]: any })` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `index` | `string \| string[]` | - | 需要配置索引的字段名 | -| `index` | `{ fields: string[], unique?: boolean, [key: string]: any }` | - | 完整配置 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.addIndex({ - fields: ['title'], - unique: true -}); -``` - -### `removeIndex()` - -移除数据表索引。 - -**签名** - -* `removeIndex(fields: string[])` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `fields` | `string[]` | - | 需要移除索引的字段名组合 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ], - indexes: [ - { - fields: ['title'], - unique: true - } - ] -}); - -posts.removeIndex(['title']); -``` - -## 表配置方法 - -### `remove()` - -删除数据表。 - -**签名** - -* `remove(): void` - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -posts.remove(); -``` - -## 数据库操作方法 - -### `sync()` - -同步数据表定义到数据库。除了 Sequelize 中默认的 `Model.sync` 的逻辑,还会一并处理关系字段对应的数据表。 - -**签名** - -* `sync(): Promise` - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -await posts.sync(); -``` - -### `existsInDb()` - -判断数据表是否存在于数据库中。 - -**签名** - -* `existsInDb(options?: Transactionable): Promise` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options?.transaction` | `Transaction` | - | 事务实例 | - -**示例** - -```ts -const posts = db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}); - -const existed = await posts.existsInDb(); - -console.log(existed); // false -``` - -### `removeFromDb()` - -**签名** - -* `removeFromDb(): Promise` - -**示例** - -```ts -const books = db.collection({ - name: 'books' -}); - -// 同步书籍表到数据库 -await db.sync(); - -// 删除数据库中的书籍表 -await books.removeFromDb(); -``` diff --git a/docs/zh-CN/api/database/field.md b/docs/zh-CN/api/database/field.md deleted file mode 100644 index 91d8c7c65..000000000 --- a/docs/zh-CN/api/database/field.md +++ /dev/null @@ -1,558 +0,0 @@ -# Field - -## 概览 - -数据表字段管理类(抽象类)。同时是所有字段类型的基类,其他任意字段类型均通过继承该类来实现。 - -如何自定义字段可参考[扩展字段类型](/development/guide/collections-fields#扩展字段类型) - -## 构造函数 - -通常不会直接由开发者调用,主要通过 `db.collection({ fields: [] })` 方法作为代理入口调用。 - -在扩展字段时主要通过继承 `Field` 抽象类,再注册到 Database 实例中来实现。 - -**签名** - -* `constructor(options: FieldOptions, context: FieldContext)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options` | `FieldOptions` | - | 字段配置对象 | -| `options.name` | `string` | - | 字段名称 | -| `options.type` | `string` | - | 字段类型,对应在 db 中注册的字段类型名称 | -| `context` | `FieldContext` | - | 字段上下文对象 | -| `context.database` | `Database` | - | 数据库实例 | -| `context.collection` | `Collection` | - | 数据表实例 | - -## 实例成员 - -### `name` - -字段名称。 - -### `type` - -字段类型。 - -### `dataType` - -字段数据库存储类型。 - -### `options` - -字段初始化配置参数。 - -### `context` - -字段上下文对象。 - -## 配置方法 - -### `on()` - -基于数据表事件的快捷定义方式。相当于 `db.on(this.collection.name + '.' + eventName, listener)`。 - -继承时通常无需覆盖此方法。 - -**签名** - -* `on(eventName: string, listener: (...args: any[]) => void)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `eventName` | `string` | - | 事件名称 | -| `listener` | `(...args: any[]) => void` | - | 事件监听器 | - -### `off()` - -基于数据表事件的快捷移除方式。相当于 `db.off(this.collection.name + '.' + eventName, listener)`。 - -继承时通常无需覆盖此方法。 - -**签名** - -* `off(eventName: string, listener: (...args: any[]) => void)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `eventName` | `string` | - | 事件名称 | -| `listener` | `(...args: any[]) => void` | - | 事件监听器 | - -### `bind()` - -当字段被添加到数据表时触发的执行内容。通常用于添加数据表事件监听器和其他处理。 - -继承时需要先调用对应的 `super.bind()` 方法。 - -**签名** - -* `bind()` - -### `unbind()` - -当字段从数据表中移除时触发的执行内容。通常用于移除数据表事件监听器和其他处理。 - -继承时需要先调用对应的 `super.unbind()` 方法。 - -**签名** - -* `unbind()` - -### `get()` - -获取字段的配置项的值。 - -**签名** - -* `get(key: string): any` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `key` | `string` | - | 配置项名称 | - -**示例** - -```ts -const field = db.collection('users').getField('name'); - -// 获取字段名称配置项的值,返回 'name' -console.log(field.get('name')); -``` - -### `merge()` - -合并字段的配置项的值。 - -**签名** - -* `merge(options: { [key: string]: any }): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options` | `{ [key: string]: any }` | - | 要合并的配置项对象 | - -**示例** - -```ts -const field = db.collection('users').getField('name'); - -field.merge({ - // 添加一个索引配置 - index: true -}); -``` - -### `remove()` - -从数据表中移除字段(仅从内存中移除)。 - -**示例** - -```ts -const books = db.getCollections('books'); - -books.getField('isbn').remove(); - -// really remove from db -await books.sync(); -``` - -## 数据库方法 - -### `removeFromDb()` - -从数据库中移除字段。 - -**签名** - -* `removeFromDb(options?: Transactionable): Promise` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.transaction?` | `Transaction` | - | 事务实例 | - -### `existsInDb()` - -判断字段是否存在于数据库中。 - -**签名** - -* `existsInDb(options?: Transactionable): Promise` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.transaction?` | `Transaction` | - | 事务实例 | - -## 内置字段类型列表 - -NocoBase 内置了一些常用的字段类型,可以直接在定义数据表的字段时使用对应的 type 名称来指定类型。不同类型的字段参数配置不同,具体可参考下面的列表。 - -所有字段类型的配置项除了以下额外介绍的以外,都会透传至 Sequelize,所以所有 Sequelize 支持的字段配置项都可以在这里使用(如 `allowNull`、`defaultValue` 等)。 - -另外 server 端的字段类型主要解决数据库存储和部分算法的问题,与前端的字段展示类型和使用组件基本无关。前端字段类型可以参考教程对应说明。 - -### `'boolean'` - -逻辑值类型。 - -**示例** - -```js -db.collection({ - name: 'books', - fields: [ - { - type: 'boolean', - name: 'published' - } - ] -}); -``` - -### `'integer'` - -整型(32 位)。 - -**示例** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'integer', - name: 'pages' - } - ] -}); -``` - -### `'bigInt'` - -长整型(64 位)。 - -**示例** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'bigInt', - name: 'words' - } - ] -}); -``` - -### `'double'` - -双精度浮点型(64 位)。 - -**示例** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'double', - name: 'price' - } - ] -}); -``` - -### `'real'` - -实数类型(仅 PG 适用)。 - -### `'decimal'` - -十进制小数类型。 - -### `'string'` - -字符串类型。相当于大部分数据库的 `VARCHAR` 类型。 - -**示例** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'string', - name: 'title' - } - ] -}); -``` - -### `'text'` - -文本类型。相当于大部分数据库的 `TEXT` 类型。 - -**示例** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'text', - name: 'content' - } - ] -}); -``` - -### `'password'` - -密码类型(NocoBase 扩展)。基于 Node.js 原生的 crypto 包的 `scrypt` 方法进行密码加密。 - -**示例** - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'password', - name: 'password', - length: 64, // 长度,默认 64 - randomBytesSize: 8 // 随机字节长度,默认 8 - } - ] -}); -``` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `length` | `number` | 64 | 字符长度 | -| `randomBytesSize` | `number` | 8 | 随机字节大小 | - -### `'date'` - -日期类型。 - -### `'time'` - -时间类型。 - -### `'array'` - -数组类型(仅 PG 适用)。 - -### `'json'` - -JSON 类型。 - -### `'jsonb'` - -JSONB 类型(仅 PG 适用,其他会被兼容为 `'json'` 类型)。 - -### `'uuid'` - -UUID 类型。 - -### `'uid'` - -UID 类型(NocoBase 扩展)。短随机字符串标识符类型。 - -### `'formula'` - -公式类型(NocoBase 扩展)。可配置基于 [mathjs](https://www.npmjs.com/package/mathjs) 的数学公式计算,公式中可以引用同一条记录中其他列的数值参与计算。 - -**示例** - -```ts -db.collection({ - name: 'orders', - fields: [ - { - type: 'double', - name: 'price' - }, - { - type: 'integer', - name: 'quantity' - }, - { - type: 'formula', - name: 'total', - expression: 'price * quantity' - } - ] -}); -``` - -### `'radio'` - -单选类型(NocoBase 扩展)。全表最多有一行数据的该字段值为 `true`,其他都为 `false` 或 `null`。 - -**示例** - -整个系统只有一个被标记为 root 的用户,任意另一个用户的 root 值被改为 `true` 之后,其他所有 root 为 `true` 的记录均会被修改为 `false`: - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'radio', - name: 'root', - } - ] -}); -``` - -### `'sort'` - -排序类型(NocoBase 扩展)。基于整型数字进行排序,为新记录自动生成新序号,当移动数据时进行序号重排。 - -数据表如果定义了 `sortable` 选项,也会自动生成对应字段。 - -**示例** - -文章基于所属用户可排序: - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'belongsTo', - name: 'user', - }, - { - type: 'sort', - name: 'priority', - scopeKey: 'userId' // 以 userId 相同值分组的数据进行排序 - } - ] -}); -``` - -### `'virtual'` - -虚拟类型。不实际储存数据,仅用于特殊 getter/setter 定义时使用。 - -### `'belongsTo'` - -多对一关联类型。外键储存在自身表,与 hasOne/hasMany 相对。 - -**示例** - -任意文章属于某个作者: - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'belongsTo', - name: 'author', - target: 'users', // 不配置默认为 name 复数名称的表名 - foreignKey: 'authorId', // 不配置默认为 + Id 的格式 - sourceKey: 'id' // 不配置默认为 target 表的 id - } - ] -}); -``` - -### `'hasOne'` - -一对一关联类型。外键储存在关联表,与 belongsTo 相对。 - -**示例** - -任意用户都有一份个人资料: - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'hasOne', - name: 'profile', - target: 'profiles', // 可省略 - } - ] -}) -``` - -### `'hasMany'` - -一对多关联类型。外键储存在关联表,与 belongsTo 相对。 - -**示例** - -任意用户可以拥有多篇文章: - -```ts -db.collection({ - name: 'users', - fields: [ - { - type: 'hasMany', - name: 'posts', - foreignKey: 'authorId', - sourceKey: 'id' - } - ] -}); -``` - -### `'belongsToMany'` - -多对多关联类型。使用中间表储存双方外键,如不指定已存在的表为中间表的话,将会自动创建中间表。 - -**示例** - -任意文章可以加任意多个标签,任意标签也可以被任意多篇文章添加: - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'belongsToMany', - name: 'tags', - target: 'tags', // 同名可省略 - through: 'postsTags', // 中间表不配置将自动生成 - foreignKey: 'postId', // 自身表在中间表的外键 - sourceKey: 'id', // 自身表的主键 - otherKey: 'tagId' // 关联表在中间表的外键 - } - ] -}); - -db.collection({ - name: 'tags', - fields: [ - { - type: 'belongsToMany', - name: 'posts', - through: 'postsTags', // 同一组关系指向同一张中间表 - } - ] -}); -``` diff --git a/docs/zh-CN/api/database/index.md b/docs/zh-CN/api/database/index.md deleted file mode 100644 index 900dc1dcd..000000000 --- a/docs/zh-CN/api/database/index.md +++ /dev/null @@ -1,1174 +0,0 @@ -# Database - -## 概览 - -Database 是 Nocobase 提供的数据库交互工具,为无代码、低代码应用提供了非常方便的数据库交互功能。目前支持的数据库为: - -* SQLite 3.8.8+ -* MySQL 8.0.17+ -* PostgreSQL 10.0+ - - -### 连接数据库 - -在 `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)。 - - -## 构造函数 - -**签名** - -* `constructor(options: DatabaseOptions)` - -创建一个数据库实例。 - -**参数** - - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.host` | `string` | `'localhost'` | 数据库主机 | -| `options.port` | `number` | - | 数据库服务端口,根据使用的数据库有对应默认端口 | -| `options.username` | `string` | - | 数据库用户名 | -| `options.password` | `string` | - | 数据库密码 | -| `options.database` | `string` | - | 数据库名称 | -| `options.dialect` | `string` | `'mysql'` | 数据库类型 | -| `options.storage?` | `string` | `':memory:'` | SQLite 的存储模式 | -| `options.logging?` | `boolean` | `false` | 是否开启日志 | -| `options.define?` | `Object` | `{}` | 默认的表定义参数 | -| `options.tablePrefix?` | `string` | `''` | NocoBase 扩展,表名前缀 | -| `options.migrator?` | `UmzugOptions` | `{}` | NocoBase 扩展,迁移管理器相关参数,参考 [Umzug](https://github.com/sequelize/umzug/blob/main/src/types.ts#L15) 实现 | - -## 迁移相关方法 - -### `addMigration()` - -添加单个迁移文件。 - -**签名** - -* `addMigration(options: MigrationItem)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.name` | `string` | - | 迁移文件名称 | -| `options.context?` | `string` | - | 迁移文件的上下文 | -| `options.migration?` | `typeof Migration` | - | 迁移文件的自定义类 | -| `options.up` | `Function` | - | 迁移文件的 `up` 方法 | -| `options.down` | `Function` | - | 迁移文件的 `down` 方法 | - -**示例** - -```ts -db.addMigration({ - name: '20220916120411-test-1', - async up() { - const queryInterface = this.context.db.sequelize.getQueryInterface(); - await queryInterface.query(/* your migration sqls */); - } -}); -``` - -### `addMigrations()` - -添加指定目录下的迁移文件。 - -**签名** - -* `addMigrations(options: AddMigrationsOptions): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.directory` | `string` | `''` | 迁移文件所在目录 | -| `options.extensions` | `string[]` | `['js', 'ts']` | 文件扩展名 | -| `options.namespace?` | `string` | `''` | 命名空间 | -| `options.context?` | `Object` | `{ db }` | 迁移文件的上下文 | - -**示例** - -```ts -db.addMigrations({ - directory: path.resolve(__dirname, './migrations'), - namespace: 'test' -}); -``` - -## 工具方法 - -### `inDialect()` - -判断当前数据库类型是否为指定类型。 - -**签名** - -* `inDialect(dialect: string[]): boolean` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `dialect` | `string[]` | - | 数据库类型,可选值为 `mysql`/`postgres`/`sqlite` | - -### `getTablePrefix()` - -获取配置中的表名前缀。 - -**签名** - -* `getTablePrefix(): string` - -## 数据表配置 - -### `collection()` - -定义一个数据表。该调用类似与 Sequelize 的 `define` 方法,只在内存中创建表结构,如需持久化到数据库,需要调用 `sync` 方法。 - -**签名** - -* `collection(options: CollectionOptions): Collection` - -**参数** - -`options` 所有配置参数与 `Collection` 类的构造函数一致,参考 [Collection](/api/server/database/collection#构造函数)。 - -**事件** - -* `'beforeDefineCollection'`:在定义表之前触发。 -* `'afterDefineCollection'`:在定义表之后触发。 - -**示例** - -```ts -db.collection({ - name: 'books', - fields: [ - { - type: 'string', - name: 'title', - }, - { - type: 'float', - name: 'price', - } - ] -}); - -// sync collection as table to db -await db.sync(); -``` - -### `getCollection()` - -获取已定义的数据表。 - -**签名** - -* `getCollection(name: string): Collection` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 表名 | - -**示例** - -```ts -const collection = db.getCollection('books'); -``` - -### `hasCollection()` - -判断是否已定义指定的数据表。 - -**签名** - -* `hasCollection(name: string): boolean` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 表名 | - -**示例** - -```ts -db.collection({ name: 'books' }); - -db.hasCollection('books'); // true - -db.hasCollection('authors'); // false -``` - -### `removeCollection()` - -移除已定义的数据表。仅在内存中移除,如需持久化,需要调用 `sync` 方法。 - -**签名** - -* `removeCollection(name: string): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 表名 | - -**事件** - -* `'beforeRemoveCollection'`:在移除表之前触发。 -* `'afterRemoveCollection'`:在移除表之后触发。 - -**示例** - -```ts -db.collection({ name: 'books' }); - -db.removeCollection('books'); -``` - -### `import()` - -导入文件目录下所有文件作为 collection 配置载入内存。 - -**签名** - -* `async import(options: { directory: string; extensions?: ImportFileExtension[] }): Promise>` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.directory` | `string` | - | 要导入的目录路径 | -| `options.extensions` | `string[]` | `['ts', 'js']` | 扫描特定后缀 | - -**示例** - -`./collections/books.ts` 文件定义的 collection 如下: - -```ts -export default { - name: 'books', - fields: [ - { - type: 'string', - name: 'title', - } - ] -}; -``` - -在插件加载时导入相关配置: - -```ts -class Plugin { - async load() { - await this.app.db.import({ - directory: path.resolve(__dirname, './collections'), - }); - } -} -``` - -## 扩展注册与获取 - -### `registerFieldTypes()` - -注册自定义字段类型。 - -**签名** - -* `registerFieldTypes(fieldTypes: MapOf): void` - -**参数** - -`fieldTypes` 是一个键值对,键为字段类型名称,值为字段类型类。 - -**示例** - -```ts -import { Field } from '@nocobase/database'; - -class MyField extends Field { - // ... -} - -db.registerFieldTypes({ - myField: MyField, -}); -``` - -### `registerModels()` - -注册自定义数据模型类。 - -**签名** - -* `registerModels(models: MapOf>): void` - -**参数** - -`models` 是一个键值对,键为数据模型名称,值为数据模型类。 - -**示例** - -```ts -import { Model } from '@nocobase/database'; - -class MyModel extends Model { - // ... -} - -db.registerModels({ - myModel: MyModel, -}); - -db.collection({ - name: 'myCollection', - model: 'myModel' -}); -``` - -### `registerRepositories()` - -注册自定义数据仓库类。 - -**签名** - -* `registerRepositories(repositories: MapOf): void` - -**参数** - -`repositories` 是一个键值对,键为数据仓库名称,值为数据仓库类。 - -**示例** - -```ts -import { Repository } from '@nocobase/database'; - -class MyRepository extends Repository { - // ... -} - -db.registerRepositories({ - myRepository: MyRepository, -}); - -db.collection({ - name: 'myCollection', - repository: 'myRepository' -}); -``` - -### `registerOperators()` - -注册自定义数据查询操作符。 - -**签名** - -* `registerOperators(operators: MapOf)` - -**参数** - -`operators` 是一个键值对,键为操作符名称,值为操作符比较语句生成函数。 - -**示例** - -```ts -db.registerOperators({ - $dateOn(value) { - return { - [Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }], - }; - } -}); - -db.getRepository('books').count({ - filter: { - createdAt: { - // registered operator - $dateOn: '2020-01-01', - } - } -}); -``` - -### `getModel()` - -获取已定义的数据模型类。如果没有在之前注册自定义模型类,将返回 Sequelize 默认的模型类。默认名称与 collection 定义的名称相同。 - -**签名** - -* `getModel(name: string): Model` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 已注册的模型名 | - -**示例** - -```ts -db.registerModels({ - books: class MyModel extends Model {} -}); - -const ModelClass = db.getModel('books'); - -console.log(ModelClass.prototype instanceof MyModel) // true -``` - -注:从 collection 中获取的模型类并不与注册时的模型类严格相等,而是继承自注册时的模型类。由于 Sequelize 的模型类在初始化过程中属性会被修改,所以 NocoBase 自动处理了这个继承关系。除类不相等以外,其他所有定义都可以正常使用。 - -### `getRepository()` - -获取自定义的数据仓库类。如果没有在之前注册自定义数据仓库类,将返回 NocoBase 默认的数据仓库类。默认名称与 collection 定义的名称相同。 - -数据仓库类主要用于基于数据模型的增删改查等操作,参考 [数据仓库](/api/server/database/repository)。 - -**签名** - -* `getRepository(name: string): Repository` -* `getRepository(name: string, relationId?: string | number): Repository` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 已注册的数据仓库名 | -| `relationId` | `string` \| `number` | - | 关系数据的外键值 | - -当名称是形如 `'tables.relactions'` 的带关联的名称时,将返回关联的数据仓库类。如果提供了第二个参数,数据仓库在使用时(查询、修改等)会基于关系数据的外键值。 - -**示例** - -假设有两张数据表_文章_与_作者_,并且文章表中有一个外键指向作者表: - -```ts -const AuthorsRepo = db.getRepository('authors'); -const author1 = AuthorsRepo.create({ name: 'author1' }); - -const PostsRepo = db.getRepository('authors.posts', author1.id); -const post1 = AuthorsRepo.create({ title: 'post1' }); -asset(post1.authorId === author1.id); // true -``` - -## 数据库事件 - -### `on()` - -监听数据库事件。 - -**签名** - -* `on(event: string, listener: (...args: any[]) => void | Promise): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| event | string | - | 事件名称 | -| listener | Function | - | 事件监听器 | - -事件名称默认支持 Sequelize 的 Model 事件。针对全局事件,通过 `` 的名称方式监听,针对单 Model 事件,通过 `.` 的名称方式监听。 - -所有内置的事件类型的参数说明和详细示例参考 [内置事件](#内置事件) 部分内容。 - -### `off()` - -移除事件监听函数。 - -**签名** - -* `off(name: string, listener: Function)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| name | string | - | 事件名称 | -| listener | Function | - | 事件监听器 | - -**示例** - -```ts -const listener = async (model, options) => { - console.log(model); -}; - -db.on('afterCreate', listener); - -db.off('afterCreate', listener); -``` - -## 数据库操作 - -### `auth()` - -数据库连接验证。可以用于确保应用与数据已建立连接。 - -**签名** - -* `auth(options: QueryOptions & { retry?: number } = {}): Promise` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options?` | `Object` | - | 验证选项 | -| `options.retry?` | `number` | `10` | 验证失败时重试次数 | -| `options.transaction?` | `Transaction` | - | 事务对象 | -| `options.logging?` | `boolean \| Function` | `false` | 是否打印日志 | - -**示例** - -```ts -await db.auth(); -``` - -### `reconnect()` - -重新连接数据库。 - -**示例** - -```ts -await db.reconnect(); -``` - -### `closed()` - -判断数据库是否已关闭连接。 - -**签名** - -* `closed(): boolean` - -### `close()` - -关闭数据库连接。等同于 `sequelize.close()`。 - -### `sync()` - -同步数据库表结构。等同于 `sequelize.sync()`,参数参考 [Sequelize 文档](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-method-sync)。 - -### `clean()` - -清空数据库,将删除所有数据表。 - -**签名** - -* `clean(options: CleanOptions): Promise` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.drop` | `boolean` | `false` | 是否移除所有数据表 | -| `options.skip` | `string[]` | - | 跳过的表名配置 | -| `options.transaction` | `Transaction` | - | 事务对象 | - -**示例** - -移除除 `users` 表以外的所有表。 - -```ts -await db.clean({ - drop: true, - skip: ['users'] -}) -``` - -## 包级导出 - -### `defineCollection()` - -创建一个数据表的配置内容。 - -**签名** - -* `defineCollection(name: string, config: CollectionOptions): CollectionOptions` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `collectionOptions` | `CollectionOptions` | - | 与所有 `db.collection()` 的参数相同 | - -**示例** - -对于要被 `db.import()` 导入的数据表配置文件: - -```ts -import { defineCollection } from '@nocobase/database'; - -export default defineCollection({ - name: 'users', - fields: [ - { - type: 'string', - name: 'name', - }, - ], -}); -``` - -### `extendCollection()` - -扩展已在内存中的表结构配置内容,主要用于 `import()` 方法导入的文件内容。该方法是 `@nocobase/database` 包导出的顶级方法,不通过 db 实例调用。也可以使用 `extend` 别名。 - -**签名** - -* `extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions): ExtendedCollectionOptions` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `collectionOptions` | `CollectionOptions` | - | 与所有 `db.collection()` 的参数相同 | -| `mergeOptions?` | `MergeOptions` | - | npm 包 [deepmerge](https://npmjs.com/package/deepmerge) 的参数 | - -**示例** - -原始 books 表定义(books.ts): - -```ts -export default { - name: 'books', - fields: [ - { name: 'title', type: 'string' } - ] -} -``` - -扩展 books 表定义(books.extend.ts): - -```ts -import { extend } from '@nocobase/database'; - -// 再次扩展 -export default extend({ - name: 'books', - fields: [ - { name: 'price', type: 'number' } - ] -}); -``` - -以上两个文件如在调用 `import()` 时导入,通过 `extend()` 再次扩展以后,books 表将拥有 `title` 和 `price` 两个字段。 - -此方法在扩展已有插件已定义的表结构时非常有用。 - -## 内置事件 - -数据库会在相应的生命周期触发以下对应的事件,通过 `on()` 方法订阅后进行特定的处理可满足一些业务需要。 - -### `'beforeSync'` / `'afterSync'` - -当新的表结构配置(字段、索引等)被同步到数据库前后触发,通常在执行 `collection.sync()`(内部调用)时会触发,一般用于一些特殊的字段扩展的逻辑处理。 - -**签名** - -```ts -on(eventName: `${string}.beforeSync` | 'beforeSync' | `${string}.afterSync` | 'afterSync', listener: SyncListener): this -``` - -**类型** - -```ts -import type { SyncOptions, HookReturn } from 'sequelize/types'; - -type SyncListener = (options?: SyncOptions) => HookReturn; -``` - -**示例** - -```ts -const users = db.collection({ - name: 'users', - fields: [ - { type: 'string', name: 'username' } - ] -}); - -db.on('beforeSync', async (options) => { - // do something -}); - -db.on('users.afterSync', async (options) => { - // do something -}); - -await users.sync(); -``` - -### `'beforeValidate'` / `'afterValidate'` - -创建或更新数据前会有基于 collection 定义的规则对数据的验证过程,在验证前后会触发对应事件。当调用 `repository.create()` 或 `repository.update()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.beforeValidate` | 'beforeValidate' | `${string}.afterValidate` | 'afterValidate', listener: ValidateListener): this -``` - -**类型** - -```ts -import type { ValidationOptions } from 'sequelize/types/lib/instance-validator'; -import type { HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -type ValidateListener = (model: Model, options?: ValidationOptions) => HookReturn; -``` - -**示例** - -```ts -db.collection({ - name: 'tests', - fields: [ - { - type: 'string', - name: 'email', - validate: { - isEmail: true, - }, - } - ], -}); - -// all models -db.on('beforeValidate', async (model, options) => { - // do something -}); -// tests model -db.on('tests.beforeValidate', async (model, options) => { - // do something -}); - -// all models -db.on('afterValidate', async (model, options) => { - // do something -}); -// tests model -db.on('tests.afterValidate', async (model, options) => { - // do something -}); - -const repository = db.getRepository('tests'); -await repository.create({ - values: { - email: 'abc', // checks for email format - }, -}); -// or -await repository.update({ - filterByTk: 1, - values: { - email: 'abc', // checks for email format - }, -}); -``` - -### `'beforeCreate'` / `'afterCreate'` - -创建一条数据前后会触发对应事件,当调用 `repository.create()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.beforeCreate` | 'beforeCreate' | `${string}.afterCreate` | 'afterCreate', listener: CreateListener): this -``` - -**类型** - -```ts -import type { CreateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type CreateListener = (model: Model, options?: CreateOptions) => HookReturn; -``` - -**示例** - -```ts -db.on('beforeCreate', async (model, options) => { - // do something -}); - -db.on('books.afterCreate', async (model, options) => { - const { transaction } = options; - const result = await model.constructor.findByPk(model.id, { - transaction - }); - console.log(result); -}); -``` - -### `'beforeUpdate'` / `'afterUpdate'` - -更新一条数据前后会触发对应事件,当调用 `repository.update()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.beforeUpdate` | 'beforeUpdate' | `${string}.afterUpdate` | 'afterUpdate', listener: UpdateListener): this -``` - -**类型** - -```ts -import type { UpdateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type UpdateListener = (model: Model, options?: UpdateOptions) => HookReturn; -``` - -**示例** - -```ts -db.on('beforeUpdate', async (model, options) => { - // do something -}); - -db.on('books.afterUpdate', async (model, options) => { - // do something -}); -``` - -### `'beforeSave'` / `'afterSave'` - -创建或更新一条数据前后会触发对应事件,当调用 `repository.create()` 或 `repository.update()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.beforeSave` | 'beforeSave' | `${string}.afterSave` | 'afterSave', listener: SaveListener): this -``` - -**类型** - -```ts -import type { SaveOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type SaveListener = (model: Model, options?: SaveOptions) => HookReturn; -``` - -**示例** - -```ts -db.on('beforeSave', async (model, options) => { - // do something -}); - -db.on('books.afterSave', async (model, options) => { - // do something -}); -``` - -### `'beforeDestroy'` / `'afterDestroy'` - -删除一条数据前后会触发对应事件,当调用 `repository.destroy()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.beforeDestroy` | 'beforeDestroy' | `${string}.afterDestroy` | 'afterDestroy', listener: DestroyListener): this -``` - -**类型** - -```ts -import type { DestroyOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type DestroyListener = (model: Model, options?: DestroyOptions) => HookReturn; -``` - -**示例** - -```ts -db.on('beforeDestroy', async (model, options) => { - // do something -}); - -db.on('books.afterDestroy', async (model, options) => { - // do something -}); -``` - -### `'afterCreateWithAssociations'` - -创建一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.create()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.afterCreateWithAssociations` | 'afterCreateWithAssociations', listener: CreateWithAssociationsListener): this -``` - -**类型** - -```ts -import type { CreateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type CreateWithAssociationsListener = (model: Model, options?: CreateOptions) => HookReturn; -``` - -**示例** - -```ts -db.on('afterCreateWithAssociations', async (model, options) => { - // do something -}); - -db.on('books.afterCreateWithAssociations', async (model, options) => { - // do something -}); -``` - -### `'afterUpdateWithAssociations'` - -更新一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.update()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.afterUpdateWithAssociations` | 'afterUpdateWithAssociations', listener: CreateWithAssociationsListener): this -``` - -**类型** - -```ts -import type { UpdateOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type UpdateWithAssociationsListener = (model: Model, options?: UpdateOptions) => HookReturn; -``` - -**示例** - -```ts -db.on('afterUpdateWithAssociations', async (model, options) => { - // do something -}); - -db.on('books.afterUpdateWithAssociations', async (model, options) => { - // do something -}); -``` - -### `'afterSaveWithAssociations'` - -创建或更新一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.create()` 或 `repository.update()` 时会触发。 - -**签名** - -```ts -on(eventName: `${string}.afterSaveWithAssociations` | 'afterSaveWithAssociations', listener: SaveWithAssociationsListener): this -``` - -**类型** - -```ts -import type { SaveOptions, HookReturn } from 'sequelize/types'; -import type { Model } from '@nocobase/database'; - -export type SaveWithAssociationsListener = (model: Model, options?: SaveOptions) => HookReturn; -``` - -**示例** - -```ts -db.on('afterSaveWithAssociations', async (model, options) => { - // do something -}); - -db.on('books.afterSaveWithAssociations', async (model, options) => { - // do something -}); -``` - -### `'beforeDefineCollection'` - -当定义一个数据表之前触发,如调用 `db.collection()` 时。 - -注:该事件是同步事件。 - -**签名** - -```ts -on(eventName: 'beforeDefineCollection', listener: BeforeDefineCollectionListener): this -``` - -**类型** - -```ts -import type { CollectionOptions } from '@nocobase/database'; - -export type BeforeDefineCollectionListener = (options: CollectionOptions) => void; -``` - -**示例** - -```ts -db.on('beforeDefineCollection', (options) => { - // do something -}); -``` - -### `'afterDefineCollection'` - -当定义一个数据表之后触发,如调用 `db.collection()` 时。 - -注:该事件是同步事件。 - -**签名** - -```ts -on(eventName: 'afterDefineCollection', listener: AfterDefineCollectionListener): this -``` - -**类型** - -```ts -import type { Collection } from '@nocobase/database'; - -export type AfterDefineCollectionListener = (options: Collection) => void; -``` - -**示例** - -```ts -db.on('afterDefineCollection', (collection) => { - // do something -}); -``` - -### `'beforeRemoveCollection'` / `'afterRemoveCollection'` - -当从内存中移除一个数据表前后触发,如调用 `db.removeCollection()` 时。 - -注:该事件是同步事件。 - -**签名** - -```ts -on(eventName: 'beforeRemoveCollection' | 'afterRemoveCollection', listener: RemoveCollectionListener): this -``` - -**类型** - -```ts -import type { Collection } from '@nocobase/database'; - -export type RemoveCollectionListener = (options: Collection) => void; -``` - -**示例** - -```ts -db.on('beforeRemoveCollection', (collection) => { - // do something -}); - -db.on('afterRemoveCollection', (collection) => { - // do something -}); -``` diff --git a/docs/zh-CN/api/database/operators.md b/docs/zh-CN/api/database/operators.md deleted file mode 100644 index 264bbec4d..000000000 --- a/docs/zh-CN/api/database/operators.md +++ /dev/null @@ -1,814 +0,0 @@ -# Filter Operators - -用于 Repository 的 `find`、`findOne`、`findAndCount`、`count` 等 API 的 filter 参数中: - -```ts -const repository = db.getRepository('books'); - -repository.find({ - filter: { - title: { - $eq: '春秋', - } - } -}); -``` - -为了支持 JSON 化,NocoBase 中将查询运算符以 $ 为前缀的字符串标识。 - -另外,NocoBase 也提供了扩展运算符的 API,详见 [`db.registerOperators()`](../database#registeroperators)。 - -## 通用运算符 - -### `$eq` - -判断字段值是否相等于指定值。相当于 SQL 的 `=`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $eq: '春秋', - } - } -}); -``` - -等同于 `title: '春秋'`。 - -### `$ne` - -判断字段值是否不等于指定值。相当于 SQL 的 `!=`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $ne: '春秋', - } - } -}); -``` - -### `$is` - -判断字段值是否为指定值。相当于 SQL 的 `IS`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $is: null, - } - } -}); -``` - -### `$not` - -判断字段值是否不为指定值。相当于 SQL 的 `IS NOT`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $not: null, - } - } -}); -``` - -### `$col` - -判断字段值是否等于另一个字段的值。相当于 SQL 的 `=`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $col: 'name', - } - } -}); -``` - -### `$in` - -判断字段值是否在指定数组中。相当于 SQL 的 `IN`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $in: ['春秋', '战国'], - } - } -}); -``` - -### `$notIn` - -判断字段值是否不在指定数组中。相当于 SQL 的 `NOT IN`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notIn: ['春秋', '战国'], - } - } -}); -``` - -### `$empty` - -判断一般字段是否为空,如果是字符串字段,判断是否为空串,如果是数组字段,判断是否为空数组。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $empty: true, - } - } -}); -``` - -### `$notEmpty` - -判断一般字段是否不为空,如果是字符串字段,判断是否不为空串,如果是数组字段,判断是否不为空数组。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notEmpty: true, - } - } -}); -``` - -## 逻辑运算符 - -### `$and` - -逻辑 AND。相当于 SQL 的 `AND`。 - -**示例** - -```ts -repository.find({ - filter: { - $and: [ - { title: '诗经' }, - { isbn: '1234567890' }, - ] - } -}); -``` - -### `$or` - -逻辑 OR。相当于 SQL 的 `OR`。 - -**示例** - -```ts -repository.find({ - filter: { - $or: [ - { title: '诗经' }, - { publishedAt: { $lt: '0000-00-00T00:00:00Z' } }, - ] - } -}); -``` - -## 布尔类型字段运算符 - -用于布尔类型字段 `type: 'boolean'` - -### `$isFalsy` - -判断布尔类型字段值是否为假。布尔字段值为 `false`、`0` 和 `NULL` 的情况都会被判断为 `$isFalsy: true`。 - -**示例** - -```ts -repository.find({ - filter: { - isPublished: { - $isFalsy: true, - } - } -}) -``` - -### `$isTruly` - -判断布尔类型字段值是否为真。布尔字段值为 `true` 和 `1` 的情况都会被判断为 `$isTruly: true`。 - -**示例** - -```ts -repository.find({ - filter: { - isPublished: { - $isTruly: true, - } - } -}) -``` - -## 数字类型字段运算符 - -用于数字类型字段,包括: - -- `type: 'integer'` -- `type: 'float'` -- `type: 'double'` -- `type: 'real'` -- `type: 'decimal'` - -### `$gt` - -判断字段值是否大于指定值。相当于 SQL 的 `>`。 - -**示例** - -```ts -repository.find({ - filter: { - price: { - $gt: 100, - } - } -}); -``` - -### `$gte` - -判断字段值是否大于等于指定值。相当于 SQL 的 `>=`。 - -**示例** - -```ts -repository.find({ - filter: { - price: { - $gte: 100, - } - } -}); -``` - -### `$lt` - -判断字段值是否小于指定值。相当于 SQL 的 `<`。 - -**示例** - -```ts -repository.find({ - filter: { - price: { - $lt: 100, - } - } -}); -``` - -### `$lte` - -判断字段值是否小于等于指定值。相当于 SQL 的 `<=`。 - -**示例** - -```ts -repository.find({ - filter: { - price: { - $lte: 100, - } - } -}); -``` - -### `$between` - -判断字段值是否在指定的两个值之间。相当于 SQL 的 `BETWEEN`。 - -**示例** - -```ts -repository.find({ - filter: { - price: { - $between: [100, 200], - } - } -}); -``` - -### `$notBetween` - -判断字段值是否不在指定的两个值之间。相当于 SQL 的 `NOT BETWEEN`。 - -**示例** - -```ts -repository.find({ - filter: { - price: { - $notBetween: [100, 200], - } - } -}); -``` - -## 字符串类型字段运算符 - -用于字符串类型字段,包括 `string` - -### `$includes` - -判断字符串字段是否包含指定子串。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $includes: '三字经', - } - } -}) -``` - -### `$notIncludes` - -判断字符串字段是否不包含指定子串。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notIncludes: '三字经', - } - } -}) -``` - -### `$startsWith` - -判断字符串字段是否以指定子串开头。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $startsWith: '三字经', - } - } -}) -``` - -### `$notStatsWith` - -判断字符串字段是否不以指定子串开头。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notStatsWith: '三字经', - } - } -}) -``` - -### `$endsWith` - -判断字符串字段是否以指定子串结尾。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $endsWith: '三字经', - } - } -}) -``` - -### `$notEndsWith` - -判断字符串字段是否不以指定子串结尾。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notEndsWith: '三字经', - } - } -}) -``` - -### `$like` - -判断字段值是否包含指定的字符串。相当于 SQL 的 `LIKE`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $like: '计算机', - } - } -}); -``` - -### `$notLike` - -判断字段值是否不包含指定的字符串。相当于 SQL 的 `NOT LIKE`。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notLike: '计算机', - } - } -}); -``` - -### `$iLike` - -判断字段值是否包含指定的字符串,忽略大小写。相当于 SQL 的 `ILIKE`(仅 PG 适用)。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $iLike: 'Computer', - } - } -}); -``` - -### `$notILike` - -判断字段值是否不包含指定的字符串,忽略大小写。相当于 SQL 的 `NOT ILIKE`(仅 PG 适用)。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notILike: 'Computer', - } - } -}); -``` - -### `$regexp` - -判断字段值是否匹配指定的正则表达式。相当于 SQL 的 `REGEXP`(仅 PG 适用)。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $regexp: '^计算机', - } - } -}); -``` - -### `$notRegexp` - -判断字段值是否不匹配指定的正则表达式。相当于 SQL 的 `NOT REGEXP`(仅 PG 适用)。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notRegexp: '^计算机', - } - } -}); -``` - -### `$iRegexp` - -判断字段值是否匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `~*`(仅 PG 适用)。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $iRegexp: '^COMPUTER', - } - } -}); -``` - -### `$notIRegexp` - -判断字段值是否不匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `!~*`(仅 PG 适用)。 - -**示例** - -```ts -repository.find({ - filter: { - title: { - $notIRegexp: '^COMPUTER', - } - } -}); -``` - -## 日期类型字段运算符 - -用于日期类型字段 `type: 'date'` - -### `$dateOn` - -判断日期字段是否在某天内。 - -**示例** - -```ts -repository.find({ - filter: { - createdAt: { - $dateOn: '2021-01-01', - } - } -}) -``` - -### `$dateNotOn` - -判断日期字段是否不在某天内。 - -**示例** - -```ts -repository.find({ - filter: { - createdAt: { - $dateNotOn: '2021-01-01', - } - } -}) -``` - -### `$dateBefore` - -判断日期字段是否在某个值之前。相当于小于传入的日期值。 - -**示例** - -```ts -repository.find({ - filter: { - createdAt: { - $dateBefore: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -### `$dateNotBefore` - -判断日期字段是否不在某个值之前。相当于大于等于传入的日期值。 - -**示例** - -```ts -repository.find({ - filter: { - createdAt: { - $dateNotBefore: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -### `$dateAfter` - -判断日期字段是否在某个值之后。相当于大于传入的日期值。 - -**示例** - -```ts -repository.find({ - filter: { - createdAt: { - $dateAfter: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -### `$dateNotAfter` - -判断日期字段是否不在某个值之后。相当于小于等于传入的日期值。 - -**示例** - -```ts -repository.find({ - filter: { - createdAt: { - $dateNotAfter: '2021-01-01T00:00:00.000Z', - } - } -}) -``` - -## 数组类型字段运算符 - -用于数组类型字段 `type: 'array'` - -### `$match` - -判断数组字段的值是否匹配指定数组中的值。 - -**示例** - -```ts -repository.find({ - filter: { - tags: { - $match: ['文学', '历史'], - } - } -}) -``` - -### `$notMatch` - -判断数组字段的值是否不匹配指定数组中的值。 - -**示例** - -```ts -repository.find({ - filter: { - tags: { - $notMatch: ['文学', '历史'], - } - } -}) -``` - -### `$anyOf` - -判断数组字段的值是否包含指定数组中的任意值。 - -**示例** - -```ts -repository.find({ - filter: { - tags: { - $anyOf: ['文学', '历史'], - } - } -}) -``` - -### `$noneOf` - -判断数组字段的值是否不包含指定数组中的任意值。 - -**示例** - -```ts -repository.find({ - filter: { - tags: { - $noneOf: ['文学', '历史'], - } - } -}) -``` - -### `$arrayEmpty` - -判断数组字段是否为空。 - -**示例** - -```ts -repository.find({ - filter: { - tags: { - $arrayEmpty: true, - } - } -}); -``` - -### `$arrayNotEmpty` - -判断数组字段是否不为空。 - -**示例** - -```ts -repository.find({ - filter: { - tags: { - $arrayNotEmpty: true, - } - } -}); -``` - -## 关系字段类型运算符 - -用于判断关系是否存在,字段类型包括: - -- `type: 'hasOne'` -- `type: 'hasMany'` -- `type: 'belongsTo'` -- `type: 'belongsToMany'` - -### `$exists` - -有关系数据 - -**示例** - -```ts -repository.find({ - filter: { - author: { - $exists: true, - } - } -}); -``` - -### `$notExists` - -无关系数据 - -**示例** - -```ts -repository.find({ - filter: { - author: { - $notExists: true, - } - } -}); -``` diff --git a/docs/zh-CN/api/database/relation-repository/belongs-to-many-repository.md b/docs/zh-CN/api/database/relation-repository/belongs-to-many-repository.md deleted file mode 100644 index 7afe08eb3..000000000 --- a/docs/zh-CN/api/database/relation-repository/belongs-to-many-repository.md +++ /dev/null @@ -1,181 +0,0 @@ -# BelongsToManyRepository -`BelongsToManyRepository` 是用于处理 `BelongsToMany` 关系的 `Relation Repository`。 - -不同于其他关系类型,`BelongsToMany` 类型的关系需要通过中间表来记录。 -在 NocoBase 中定义关联关系,可自动创建中间表,也可以明确指定中间表。 - -## 类方法 - -### `find()` - -查找关联对象 - -**签名** - -* `async find(options?: FindOptions): Promise` - -**详细信息** - -查询参数与 [`Repository.find()`](../repository.md#find) 一致。 - -### `findOne()` - -查找关联对象,仅返回一条记录 - -**签名** - -* `async findOne(options?: FindOneOptions): Promise` - - - - -### `count()` - -返回符合查询条件的记录数 - -**签名** - -* `async count(options?: CountOptions)` - -**类型** -```typescript -interface CountOptions extends Omit, Transactionable { - filter?: Filter; -} -``` - -### `findAndCount()` - -从数据库查询特定条件的数据集和结果数。 - -**签名** - -* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` - -**类型** -```typescript -type FindAndCountOptions = CommonFindOptions -``` - -### `create()` - -创建关联对象 - -**签名** - -* `async create(options?: CreateOptions): Promise` - - - -### `update()` - -更新符合条件的关联对象 - -**签名** - -* `async update(options?: UpdateOptions): Promise` - - - -### `destroy()` - -删除符合条件的关联对象 - -**签名** - -* `async destroy(options?: TargetKey | TargetKey[] | DestroyOptions): Promise` - - - -### `add()` - -添加新的关联对象 - -**签名** - -* `async add( - options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions - ): Promise` - -**类型** - -```typescript -type PrimaryKeyWithThroughValues = [TargetKey, Values]; - -interface AssociatedOptions extends Transactionable { - tk?: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[]; -} -``` - -**详细信息** - -可以直接传入关联对象的 `targetKey`,也可将 `targetKey` 与中间表的字段值一并传入。 - -**示例** -```typescript -const t1 = await Tag.repository.create({ - values: { name: 't1' }, -}); - -const t2 = await Tag.repository.create({ - values: { name: 't2' }, -}); - -const p1 = await Post.repository.create({ - values: { title: 'p1' }, -}); - -const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.id); - -// 传入 targetKey -PostTagRepository.add([ - t1.id, t2.id -]); - -// 传入中间表字段 -PostTagRepository.add([ - [t1.id, { tagged_at: '123' }], - [t2.id, { tagged_at: '456' }], -]); -``` - -### `set()` - -设置关联对象 - -**签名** -* async set( - options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions, - ): Promise - -**详细信息** - -参数同 [add()](#add) - -### `remove()` - -移除与给定对象之间的关联关系 - -**签名** -* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**类型** -```typescript -interface AssociatedOptions extends Transactionable { - tk?: TargetKey | TargetKey[]; -} -``` - -### `toggle()` - -切换关联对象。 - -在一些业务场景中,经常需要切换关联对象,比如用户收藏商品,用户可以取消收藏,也可以再次收藏。使用 `toggle` 方法可以快速实现类似功能。 - -**签名** - -* `async toggle(options: TargetKey | { tk?: TargetKey; transaction?: Transaction }): Promise` - -**详细信息** - -`toggle` 方法会自动判断关联对象是否已经存在,如果存在则移除,如果不存在则添加。 diff --git a/docs/zh-CN/api/database/relation-repository/belongs-to-repository.md b/docs/zh-CN/api/database/relation-repository/belongs-to-repository.md deleted file mode 100644 index 30373bba0..000000000 --- a/docs/zh-CN/api/database/relation-repository/belongs-to-repository.md +++ /dev/null @@ -1,4 +0,0 @@ -## BelongsToRepository - -其接口与 [HasOneRepository](./has-one-repository.md) 一致。 -`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。 diff --git a/docs/zh-CN/api/database/relation-repository/has-many-repository.md b/docs/zh-CN/api/database/relation-repository/has-many-repository.md deleted file mode 100644 index 2e4b2be42..000000000 --- a/docs/zh-CN/api/database/relation-repository/has-many-repository.md +++ /dev/null @@ -1,132 +0,0 @@ - -# HasManyRepository - -`HasManyRepository` 是用于处理 `HasMany` 关系的 `Relation Repository`。 - -## 类方法 - -### `find()` - -查找关联对象 - -**签名** - -* `async find(options?: FindOptions): Promise` - -**详细信息** - -查询参数与 [`Repository.find()`](../repository.md#find) 一致。 - -### `findOne()` - -查找关联对象,仅返回一条记录 - -**签名** - -* `async findOne(options?: FindOneOptions): Promise` - - - - -### `count()` - -返回符合查询条件的记录数 - -**签名** - -* `async count(options?: CountOptions)` - -**类型** -```typescript -interface CountOptions extends Omit, Transactionable { - filter?: Filter; -} -``` - -### `findAndCount()` - -从数据库查询特定条件的数据集和结果数。 - -**签名** - -* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` - -**类型** -```typescript -type FindAndCountOptions = CommonFindOptions -``` - - -### `create()` - -创建关联对象 - -**签名** - -* `async create(options?: CreateOptions): Promise` - - - -### `update()` - -更新符合条件的关联对象 - -**签名** - -* `async update(options?: UpdateOptions): Promise` - - - -### `destroy()` - -删除符合条件的关联对象 - -**签名** - -* `async destroy(options?: TK | DestroyOptions): Promise` - - - -### `add()` - -添加对象关联关系 - -**签名** -* `async add(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**类型** -```typescript -interface AssociatedOptions extends Transactionable { - tk?: TargetKey | TargetKey[]; -} -``` - -**详细信息** - -* `tk` - 关联对象的 targetKey 值,可以是单个值,也可以是数组。 - - -### `remove()` - -移除与给定对象之间的关联关系 - -**签名** -* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**详细信息** - -参数同 [`add()`](#add) 方法。 - -### `set()` - -设置当前关系的关联对象 - -**签名** - -* `async set(options: TargetKey | TargetKey[] | AssociatedOptions)` - -**详细信息** - -参数同 [`add()`](#add) 方法。 - - diff --git a/docs/zh-CN/api/database/relation-repository/has-one-repository.md b/docs/zh-CN/api/database/relation-repository/has-one-repository.md deleted file mode 100644 index 66ea4a65e..000000000 --- a/docs/zh-CN/api/database/relation-repository/has-one-repository.md +++ /dev/null @@ -1,189 +0,0 @@ -# HasOneRepository -## 概览 - -`HasOneRepository` 为 `HasOne` 类型的关联 Repository。 - -```typescript -const User = db.collection({ - name: 'users', - fields: [ - { type: 'hasOne', name: 'profile' }, - { type: 'string', name: 'name' }, - ], -}); - -const Profile = db.collection({ - name: 'profiles', - fields: [{ type: 'string', name: 'avatar' }], -}); - -const user = await User.repository.create({ - values: { name: 'u1' }, -}); - - -// 获取到关联 Repository -const userProfileRepository = User.repository.relation('profile').of(user.get('id')); - -// 也可直接初始化 -new HasOneRepository(User, 'profile', user.get('id')); - - - -``` - -## 类方法 - -### `find()` - -查找关联对象 - -**签名** - -* `async find(options?: SingleRelationFindOption): Promise | null>` - -**类型** - -```typescript -interface SingleRelationFindOption extends Transactionable { - fields?: Fields; - except?: Except; - appends?: Appends; - filter?: Filter; -} -``` - -**详细信息** - -查询参数与 [`Repository.find()`](../repository.md#find) 一致。 - -**示例** - -```typescript -const profile = await UserProfileRepository.find(); -// 关联对象不存在时,返回 null -``` - -### `create()` -创建关联对象 - -**签名** - -* `async create(options?: CreateOptions): Promise` - - - -**示例** - -```typescript -const profile = await UserProfileRepository.create({ - values: { avatar: 'avatar1' }, -}); - -console.log(profile.toJSON()); -/* -{ - id: 1, - avatar: 'avatar1', - userId: 1, - updatedAt: 2022-09-24T13:59:40.025Z, - createdAt: 2022-09-24T13:59:40.025Z -} -*/ - -``` - -### `update()` - -更新关联对象 - -**签名** - -* `async update(options: UpdateOptions): Promise` - - - - -**示例** - -```typescript -const profile = await UserProfileRepository.update({ - values: { avatar: 'avatar2' }, -}); - -profile.get('avatar'); // 'avatar2' -``` - -### `remove()` - -移除关联对象,仅解除关联关系,不删除关联对象 - -**签名** - -* `async remove(options?: Transactionable): Promise` - -**详细信息** - -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 - -**示例** - -```typescript -await UserProfileRepository.remove(); -await UserProfileRepository.find() == null; // true - -await Profile.repository.count() === 1; // true -``` - -### `destroy()` - -删除关联对象 - -**签名** - -* `async destroy(options?: Transactionable): Promise` - - -**详细信息** - -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 - -**示例** - -```typescript -await UserProfileRepository.destroy(); -await UserProfileRepository.find() == null; // true -await Profile.repository.count() === 0; // true -``` - -### `set()` - -设置关联对象 - -**签名** - -* `async set(options: TargetKey | SetOption): Promise` - -**类型** - -```typescript -interface SetOption extends Transactionable { - tk?: TargetKey; -} -```` -**详细信息** - -* tk: 设置关联对象的 targetKey -* transaction: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 - -**示例** - -```typescript -const newProfile = await Profile.repository.create({ - values: { avatar: 'avatar2' }, -}); - -await UserProfileRepository.set(newProfile.get('id')); - -(await UserProfileRepository.find()).get('id') === newProfile.get('id'); // true -``` diff --git a/docs/zh-CN/api/database/relation-repository/index.md b/docs/zh-CN/api/database/relation-repository/index.md deleted file mode 100644 index 27140968d..000000000 --- a/docs/zh-CN/api/database/relation-repository/index.md +++ /dev/null @@ -1,45 +0,0 @@ -# RelationRepository - -`RelationRepository` 是关系类型的 `Repository` 对象,`RelationRepository` 可以实现在不加载关联的情况下对关联数据进行操作。基于 `RelationRepository`,每种关联都派生出对应的实现,分别为 - -* [`HasOneRepository`](#has-one-repository) -* `HasManyRepository` -* `BelongsToRepository` -* `BelongsToManyRepository` - - -## 构造函数 - -**签名** - -* `constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `sourceCollection` | `Collection` | - | 关联中的参照关系(referencing relation)对应的 Collection | -| `association` | `string` | - | 关联名称 | -| `sourceKeyValue` | `string \| number` | - | 参照关系中对应的 key 值 | - - -## 基类属性 - -### `db: Database` - -数据库对象 - -### `sourceCollection` -关联中的参照关系(referencing relation)对应的 Collection - -### `targetCollection` -关联中被参照关系(referenced relation)对应的 Collection - -### `association` -sequelize 中的与当前关联对应的 association 对象 - -### `associationField` -collection 中的与当前关联对应的字段 - -### `sourceKeyValue` -参照关系中对应的 key 值 diff --git a/docs/zh-CN/api/database/repository.md b/docs/zh-CN/api/database/repository.md deleted file mode 100644 index 14e1535a7..000000000 --- a/docs/zh-CN/api/database/repository.md +++ /dev/null @@ -1,677 +0,0 @@ -# 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 为 'post title' 的对象 -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", - }, -}); -``` - -## 构造函数 - -通常不会直接由开发者调用,主要通过 `db.registerRepositories()` 注册类型以后,在 `db.colletion()` 的参数中指定对应已注册的仓库类型,并完成实例化。 - -**签名** - -* `constructor(collection: Collection)` - -**示例** - -```ts -import { Repository } from '@nocobase/database'; - -class MyRepository extends Repository { - async myQuery(sql) { - return this.database.sequelize.query(sql); - } -} - -db.registerRepositories({ - books: MyRepository -}); - -db.collection({ - name: 'books', - // here link to the registered repository - repository: 'books' -}); - -await db.sync(); - -const books = db.getRepository('books') as MyRepository; -await books.myQuery('SELECT * FROM books;'); -``` - -## 实例成员 - -### `database` - -上下文所在的数据库管理实例。 - -### `collection` - -对应的数据表管理实例。 - -### `model` - -对应的数据模型类。 - -## 实例方法 - - -### `find()` - -从数据库查询数据集,可指定筛选条件、排序等。 - -**签名** - -* `async find(options?: FindOptions): Promise` - -**类型** -```typescript -type Filter = FilterWithOperator | FilterWithValue | FilterAnd | FilterOr; -type Appends = string[]; -type Except = string[]; -type Fields = string[]; -type Sort = string[] | string; - -interface SequelizeFindOptions { - limit?: number; - offset?: number; -} - -interface FilterByTk { - filterByTk?: TargetKey; -} - -interface CommonFindOptions extends Transactionable { - filter?: Filter; - fields?: Fields; - appends?: Appends; - except?: Except; - sort?: Sort; -} - -type FindOptions = SequelizeFindOptions & CommonFindOptions & FilterByTk; -``` - -**详细信息** - -#### `filter: Filter` -查询条件,用于过滤数据结果。传入的查询参数中,`key` 为查询的字段名,`value` 可传要查询的值, -也可配合使用操作符进行其他条件的数据筛选。 - -```typescript -// 查询 name 为 foo,并且 age 大于 18 的记录 -repository.find({ - filter: { - name: "foo", - age: { - $gt: 18, - }, - } -}) -``` -更多操作符请参考 [查询操作符](./operators.md)。 - -#### `filterByTk: TargetKey` -通过 `TargetKey` 查询数据,为 `filter` 参数的便捷方法。`TargetKey` 具体是哪一个字段, -可在 `Collection` 中进行[配置](./collection.md#filtertargetkey),默认为 `primaryKey`。 - -```typescript - -// 默认情况下,查找 id 为 1 的记录 -repository.find({ - filterByTk: 1, -}); - -``` - -#### `fields: string[]` -查询列,用于控制数据字段结果。传入此参数之后,只会返回指定的字段。 - -#### `except: string[]` -排除列,用于控制数据字段结果。传入此参数之后,传入的字段将不会输出。 - -#### `appends: string[]` -追加列,用于加载关联数据。传入此参数之后,指定的关联字段将一并输出。 - -#### `sort: string[] | string` -指定查询结果排序方式,传入参数为字段名称,默认按照升序 `asc` 排序,若需按降序 `desc` 排序, -可在字段名称前加上 `-` 符号,如:`['-id', 'name']`,表示按 `id desc, name asc` 排序。 - -#### `limit: number` -限制结果数量,同 `SQL` 中的 `limit` - -#### `offset: number` -查询偏移量,同 `SQL` 中的 `offset` - -**示例** - -```ts -const posts = db.getRepository('posts'); - -const results = await posts.find({ - filter: { - createdAt: { - $gt: '2022-01-01T00:00:00.000Z', - } - }, - fields: ['title'], - appends: ['user'], -}); -``` - -### `findOne()` - -从数据库查询特定条件的单条数据。相当于 Sequelize 中的 `Model.findOne()`。 - -**签名** - -* `async findOne(options?: FindOneOptions): Promise` - - - -**示例** - -```ts -const posts = db.getRepository('posts'); - -const result = await posts.findOne({ - filterByTk: 1, -}); -``` - -### `count()` - -从数据库查询特定条件的数据总数。相当于 Sequelize 中的 `Model.count()`。 - -**签名** - -* `count(options?: CountOptions): Promise` - -**类型** -```typescript -interface CountOptions extends Omit, Transactionable { - filter?: Filter; -} -``` - -**示例** - -```ts -const books = db.getRepository('books'); - -const count = await books.count({ - filter: { - title: '三字经' - } -}); -``` - - -### `findAndCount()` - -从数据库查询特定条件的数据集和结果数。相当于 Sequelize 中的 `Model.findAndCountAll()`。 - -**签名** - -* `async findAndCount(options?: FindAndCountOptions): Promise<[Model[], number]>` - -**类型** -```typescript -type FindAndCountOptions = Omit & CommonFindOptions; -``` - -**详细信息** - -查询参数与 `find()` 相同。返回值为一个数组,第一个元素为查询结果,第二个元素为结果总数。 - -### `create()` - -向数据表插入一条新创建的数据。相当于 Sequelize 中的 `Model.create()`。当要创建的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。 - -**签名** - -* `async create(options: CreateOptions): Promise` - - - -**示例** - -```ts -const posts = db.getRepository('posts'); - -const result = await posts.create({ - values: { - title: 'NocoBase 1.0 发布日志', - tags: [ - // 有关系表主键值时为更新该条数据 - { id: 1 }, - // 没有主键值时为创建新数据 - { name: 'NocoBase' }, - ] - }, -}); -``` - -### `createMany()` - -向数据表插入多条新创建的数据。相当于多次调用 `create()` 方法。 - -**签名** - -* `createMany(options: CreateManyOptions): Promise` - -**类型** -```typescript -interface CreateManyOptions extends BulkCreateOptions { - records: Values[]; -} -``` - -**详细信息** - -* `records`:要创建的记录的数据对象数组。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 - -**示例** - -```ts -const posts = db.getRepository('posts'); - -const results = await posts.createMany({ - records: [ - { - title: 'NocoBase 1.0 发布日志', - tags: [ - // 有关系表主键值时为更新该条数据 - { id: 1 }, - // 没有主键值时为创建新数据 - { name: 'NocoBase' }, - ] - }, - { - title: 'NocoBase 1.1 发布日志', - tags: [ - { id: 1 } - ] - }, - ], -}); -``` - -### `update()` - -更新数据表中的数据。相当于 Sequelize 中的 `Model.update()`。当要更新的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。 - -**签名** - -* `async update(options: UpdateOptions): Promise` - - - -**示例** - -```ts -const posts = db.getRepository('posts'); - -const result = await posts.update({ - filterByTk: 1, - values: { - title: 'NocoBase 1.0 发布日志', - tags: [ - // 有关系表主键值时为更新该条数据 - { id: 1 }, - // 没有主键值时为创建新数据 - { name: 'NocoBase' }, - ] - }, -}); -``` - -### `destroy()` - -删除数据表中的数据。相当于 Sequelize 中的 `Model.destroy()`。 - -**签名** - -* `async destory(options?: TargetKey | TargetKey[] | DestoryOptions): Promise` - -**类型** - -```typescript -interface DestroyOptions extends SequelizeDestroyOptions { - filter?: Filter; - filterByTk?: TargetKey | TargetKey[]; - truncate?: boolean; - context?: any; -} -``` - -**详细信息** - -* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。 -* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。 -* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/zh-CN/api/database/shared.md b/docs/zh-CN/api/database/shared.md deleted file mode 100644 index fd2b409fb..000000000 --- a/docs/zh-CN/api/database/shared.md +++ /dev/null @@ -1,8 +0,0 @@ -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.values` | `M` | `{}` | 插入的数据对象 | -| `options.whitelist?` | `string[]` | - | `values` 字段的白名单,只有名单内的字段会被存储 | -| `options.blacklist?` | `string[]` | - | `values` 字段的黑名单,名单内的字段不会被存储 | -| `options.transaction?` | `Transaction` | - | 事务 | diff --git a/docs/zh-CN/api/database/shared/create-options.md b/docs/zh-CN/api/database/shared/create-options.md deleted file mode 100644 index 51b245a45..000000000 --- a/docs/zh-CN/api/database/shared/create-options.md +++ /dev/null @@ -1,21 +0,0 @@ -**类型** -```typescript -type WhiteList = string[]; -type BlackList = string[]; -type AssociationKeysToBeUpdate = string[]; - -interface CreateOptions extends SequelizeCreateOptions { - values?: Values; - whitelist?: WhiteList; - blacklist?: BlackList; - updateAssociationValues?: AssociationKeysToBeUpdate; - context?: any; -} -``` - -**详细信息** - -* `values`:要创建的记录的数据对象。 -* `whitelist`:指定要创建的记录的数据对象中,哪些字段**可以被写入**。若不传入此参数,则默认允许所有字段写入。 -* `blacklist`:指定要创建的记录的数据对象中,哪些字段**不允许被写入**。若不传入此参数,则默认允许所有字段写入。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/zh-CN/api/database/shared/destroy-options.md b/docs/zh-CN/api/database/shared/destroy-options.md deleted file mode 100644 index aaf8e63b9..000000000 --- a/docs/zh-CN/api/database/shared/destroy-options.md +++ /dev/null @@ -1,17 +0,0 @@ -**类型** - -```typescript -interface DestroyOptions extends SequelizeDestroyOptions { - filter?: Filter; - filterByTk?: TargetKey | TargetKey[]; - truncate?: boolean; - context?: any; -} -``` - -**详细信息** - -* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。 -* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。 -* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/zh-CN/api/database/shared/find-one.md b/docs/zh-CN/api/database/shared/find-one.md deleted file mode 100644 index 47e5f2192..000000000 --- a/docs/zh-CN/api/database/shared/find-one.md +++ /dev/null @@ -1,8 +0,0 @@ -**类型** -```typescript -type FindOneOptions = Omit; -``` - -**参数** - -大部分参数与 `find()` 相同,不同之处在于 `findOne()` 只返回单条数据,所以不需要 `limit` 参数,且查询时始终为 `1`。 diff --git a/docs/zh-CN/api/database/shared/find-options.md b/docs/zh-CN/api/database/shared/find-options.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/zh-CN/api/database/shared/transaction.md b/docs/zh-CN/api/database/shared/transaction.md deleted file mode 100644 index 27068fcf9..000000000 --- a/docs/zh-CN/api/database/shared/transaction.md +++ /dev/null @@ -1 +0,0 @@ -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/zh-CN/api/database/shared/update-options.md b/docs/zh-CN/api/database/shared/update-options.md deleted file mode 100644 index ea03c3ea7..000000000 --- a/docs/zh-CN/api/database/shared/update-options.md +++ /dev/null @@ -1,24 +0,0 @@ -**类型** -```typescript -interface UpdateOptions extends Omit { - values: Values; - filter?: Filter; - filterByTk?: TargetKey; - whitelist?: WhiteList; - blacklist?: BlackList; - updateAssociationValues?: AssociationKeysToBeUpdate; - context?: any; -} -``` - -**详细信息** - - -* `values`:要更新的记录的数据对象。 -* `filter`:指定要更新的记录的过滤条件, Filter 详细用法可参考 [`find()`](#find) 方法。 -* `filterByTk`:按 TargetKey 指定要更新的记录的过滤条件。 -* `whitelist`: `values` 字段的白名单,只有名单内的字段会被写入。 -* `blacklist`: `values` 字段的黑名单,名单内的字段不会被写入。 -* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 - -`filterByTk` 与 `filter` 至少要传其一。 diff --git a/docs/zh-CN/api/env.md b/docs/zh-CN/api/env.md deleted file mode 100644 index 361540336..000000000 --- a/docs/zh-CN/api/env.md +++ /dev/null @@ -1,236 +0,0 @@ -# 环境变量 - -## 全局环境变量 - -保存在 `.env` 文件里 - -### APP_ENV - -应用环境,默认值 `development`,可选项包括: - -- `production` 生产环境 -- `development` 开发环境 - -```bash -APP_ENV=production -``` - -### APP_HOST - -应用主机,默认值 `0.0.0.0` - -```bash -APP_HOST=192.168.3.154 -``` - -### APP_PORT - -应用端口,默认值 `13000` - -```bash -APP_PORT=13000 -``` - -### APP_KEY - -秘钥,用于 jwt 等场景 - -```bash -APP_KEY=app-key-test -``` - -### API_BASE_PATH - -NocoBase API 地址前缀,默认值 `/api/` - -```bash -API_BASE_PATH=/api/ -``` - -### PLUGIN_PACKAGE_PREFIX - -插件包前缀,默认值 `@nocobase/plugin-,@nocobase/preset-` - -例如,有一名为 `my-nocobase-app` 的项目,新增了 `hello` 插件,包名为 `@my-nocobase-app/plugin-hello`。 - -PLUGIN_PACKAGE_PREFIX 配置如下: - -```bash -PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin- -``` - -插件名和包名的对应关系为: - -- `users` 插件包名为 `@nocobase/plugin-users` -- `nocobase` 插件包名为 `@nocobase/preset-nocobase` -- `hello` 插件包名为 `@my-nocobase-app/plugin-hello` - -### DB_DIALECT - -数据库类型,默认值 `sqlite`,可选项包括: - -- `sqlite` -- `mysql` -- `postgres` - -```bash -DB_DIALECT=mysql -``` - -### DB_STORAGE - -数据库文件路径(使用 SQLite 数据库时配置) - -```bash -# 相对路径 -DB_HOST=storage/db/nocobase.db -# 绝对路径 -DB_HOST=/your/path/nocobase.db -``` - -### DB_HOST - -数据库主机(使用 MySQL 或 PostgreSQL 数据库时需要配置) - -默认值 `localhost` - -```bash -DB_HOST=localhost -``` - -### DB_PORT - -数据库端口(使用 MySQL 或 PostgreSQL 数据库时需要配置) - -- MySQL 默认端口 3306 -- PostgreSQL 默认端口 5432 - -```bash -DB_PORT=3306 -``` - -### DB_DATABASE - -数据库名(使用 MySQL 或 PostgreSQL 数据库时需要配置) - -```bash -DB_DATABASE=nocobase -``` - -### DB_USER - -数据库用户(使用 MySQL 或 PostgreSQL 数据库时需要配置) - -```bash -DB_USER=nocobase -``` - -### DB_PASSWORD - -数据库密码(使用 MySQL 或 PostgreSQL 数据库时需要配置) - -```bash -DB_PASSWORD=nocobase -``` - -### DB_TABLE_PREFIX - -数据表前缀 - -```bash -DB_TABLE_PREFIX=nocobase_ -``` - -### DB_LOGGING - -数据库日志开关,默认值 `off`,可选项包括: - -- `on` 打开 -- `off` 关闭 - -```bash -DB_LOGGING=on -``` - -### LOGGER_TRANSPORT - -日志 transport,默认值 `console,dailyRotateFile`,可选项: - -- `console` -- `dailyRotateFile` - -### LOGGER_BASE_PATH - -基于文件的日志存储路径,默认为 `storage/logs`。 - -## 临时环境变量 - -安装 NocoBase 时,可以通过设置临时的环境变量来辅助安装,如: - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - INIT_ROOT_NICKNAME="Super Admin" \ - nocobase install - -# 等同于 -yarn nocobase install \ - --lang=zh-CN \ - --root-email=demo@nocobase.com \ - --root-password=admin123 \ - --root-nickname="Super Admin" - -# 等同于 -yarn nocobase install -l zh-CN -e demo@nocobase.com -p admin123 -n "Super Admin" -``` - -### INIT_APP_LANG - -安装时的语言,默认值 `en-US`,可选项包括: - -- `en-US` -- `zh-CN` - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - nocobase install -``` - -### INIT_ROOT_EMAIL - -Root 用户邮箱 - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - nocobase install -``` - -### INIT_ROOT_PASSWORD - -Root 用户密码 - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - nocobase install -``` - -### INIT_ROOT_NICKNAME - -Root 用户昵称 - -```bash -yarn cross-env \ - INIT_APP_LANG=zh-CN \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - INIT_ROOT_NICKNAME="Super Admin" \ - nocobase install -``` diff --git a/docs/zh-CN/api/http/index.md b/docs/zh-CN/api/http/index.md deleted file mode 100644 index b41f22fb2..000000000 --- a/docs/zh-CN/api/http/index.md +++ /dev/null @@ -1,301 +0,0 @@ -# 概述 - -NocoBase 的 HTTP API 基于 Resource & Action 设计,是 REST API 的超集,操作不局限于增删改查,在 NocoBase 里,Resource Action 可以任意的扩展。 - -## 资源 Resource - -在 NocoBase 里,资源(resource)有两种表达方式: - -- `` -- `.` - - - -- collection 是所有抽象数据的集合 -- association 为 collection 的关联数据 -- resource 包括 collection 和 collection.association 两类 - - - -### 示例 - -- `posts` 文章 -- `posts.user` 文章用户 -- `posts.tags` 文章标签 - -## 操作 Action - -以 `:` 的方式表示资源操作 - -- `:` -- `.:` - -内置的全局操作,可用于 collection 或 association - -- `create` -- `get` -- `list` -- `update` -- `destroy` -- `move` - -内置的关联操作,仅用于 association - -- `set` -- `add` -- `remove` -- `toggle` - -### 示例 - -- `posts:create` 创建文章 -- `posts.user:get` 查看文章用户 -- `posts.tags:add` 附加文章标签(将现有的标签与文章关联) - -## 请求 URL - -```bash - /api/: - /api/:/ - /api///: - /api///:/ -``` - -### 示例 - -posts 资源 - -```bash -POST /api/posts:create -GET /api/posts:list -GET /api/posts:get/1 -POST /api/posts:update/1 -POST /api/posts:destroy/1 -``` - -posts.comments 资源 - -```bash -POST /api/posts/1/comments:create -GET /api/posts/1/comments:list -GET /api/posts/1/comments:get/1 -POST /api/posts/1/comments:update/1 -POST /api/posts/1/comments:destroy/1 -``` - -posts.tags 资源 - -```bash -POST /api/posts/1/tags:create -GET /api/posts/1/tags:get -GET /api/posts/1/tags:list -POST /api/posts/1/tags:update -POST /api/posts/1/tags:destroy -POST /api/posts/1/tags:add -GET /api/posts/1/tags:remove -``` - -## 资源定位 - -- collection 资源,通过 `collectionIndex` 定位到待处理的数据,`collectionIndex` 必须唯一 -- association 资源,通过 `collectionIndex` 和 `associationIndex` 联合定位待处理的数据,`associationIndex` 可能不是唯一的,但是 `collectionIndex` 和 `associationIndex` 的联合索引必须唯一 - -查看 association 资源详情时,请求的 URL 需要同时提供 `` 和 ``,`` 并不多余,因为 `` 可能不是唯一的。 - -例如 `tables.fields` 表示数据表的字段 - -```bash -GET /api/tables/table1/fields/title -GET /api/tables/table2/fields/title -``` - -table1 和 table2 都有 title 字段,title 在 table1 里是唯一的,但是其他表也可能有 title 字段 - -## 请求参数 - -请求的参数可以放在 Request 的 headers、parameters(query string)、body(GET 请求没有 body) 里。 - -几个特殊的 Parameters 请求参数 - -- `filter` 数据过滤,用于查询相关操作里; -- `filterByTk` 根据 tk 字段字过滤,用于指定详情数据的操作里; -- `sort` 排序,用于查询相关操作里。 -- `fields` 输出哪些数据,用于查询相关操作里; -- `appends` 附加关系字段,用于查询相关操作里; -- `except` 排除哪些字段(不输出),用于查询相关操作里; -- `whitelist` 字段白名单,用于数据的创建和更新相关操作里; -- `blacklist` 字段黑名单,用于数据的创建和更新相关操作里; - -### filter - -数据过滤 - -```bash -# simple -GET /api/posts?filter[status]=publish -# 推荐使用 json string 的格式,需要 encodeURIComponent 编码 -GET /api/posts?filter={"status":"published"} - -# filter operators -GET /api/posts?filter[status.$eq]=publish -GET /api/posts?filter={"status.$eq":"published"} - -# $and -GET /api/posts?filter={"$and": [{"status.$eq":"published"}, {"title.$includes":"a"}]} -# $or -GET /api/posts?filter={"$or": [{"status.$eq":"pending"}, {"status.$eq":"draft"}]} - -# association field -GET /api/posts?filter[user.email.$includes]=gmail -GET /api/posts?filter={"user.email.$includes":"gmail"} -``` - -[点此查看更多关于 filter operators 的内容](http-api/filter-operators) - -### filterByTk - -根据 tk 字段过滤,默认情况: - -- collection 资源,tk 为数据表的主键; -- association 资源,tk 为 association 的 targetKey 字段。 - -```bash -GET /api/posts:get?filterByTk=1&fields=name,title&appends=tags -``` - -### sort - -排序。降序时,字段前面加上减号 `-`。 - -```bash -# createAt 字段升序 -GET /api/posts:get?sort=createdAt -# createAt 字段降序 -GET /api/posts:get?sort=-createdAt -# 多个字段联合排序,createAt 字段降序、title A-Z 升序 -GET /api/posts:get?sort=-createdAt,title -``` - -### fields - -输出哪些数据 - -```bash -GET /api/posts:list?fields=name,title - -Response 200 (application/json) -{ - "data": [ - { - "name": "", - "title": "" - } - ], - "meta": {} -} -``` - -### appends - -附加关系字段 - -### except - -排除哪些字段(不输出),用于查询相关操作里; - -### whitelist - -白名单 - -```bash -POST /api/posts:create?whitelist=title - -{ - "title": "My first post", - "date": "2022-05-19" # date 字段会被过滤掉,不会写入数据库 -} -``` - -### blacklist - -黑名单 - -```bash -POST /api/posts:create?blacklist=date - -# date 字段会被过滤掉,不会写入数据库 -{ - "title": "My first post" -} -``` - -## 请求响应 - -响应的格式 - -```ts -type ResponseResult = { - data?: any; // 主体数据 - meta?: any; // 附加数据 - errors?: ResponseError[]; // 报错 -}; - -type ResponseError = { - code?: string; - message: string; -}; -``` - -### 示例 - -查看列表 - -```bash -GET /api/posts:list - -Response 200 (application/json) - -{ - data: [ - { - id: 1 - } - ], - meta: { - count: 1 - page: 1, - pageSize: 1, - totalPage: 1 - }, -} -``` - -查看详情 - -```bash -GET /api/posts:get/1 - -Response 200 (application/json) - -{ - data: { - id: 1 - }, -} -``` - -报错 - -```bash -POST /api/posts:create - -Response 400 (application/json) - -{ - errors: [ - { - message: 'name must be required', - }, - ], -} -``` \ No newline at end of file diff --git a/docs/zh-CN/api/http/rest-api.md b/docs/zh-CN/api/http/rest-api.md deleted file mode 100644 index 2d7fd86d5..000000000 --- a/docs/zh-CN/api/http/rest-api.md +++ /dev/null @@ -1,184 +0,0 @@ -# REST API - -NocoBase 的 HTTP API 是 REST API 的超集,标准的 CRUD API 也支持 RESTful 风格。 - -## Collection 资源 - ---- - -### 创建 collection - -HTTP API - -```bash -POST /api/:create - -{} # JSON body -``` - -REST API - -```bash -POST /api/ - -{} # JSON body -``` - -### 查看 collection 列表 - -HTTP API - -```bash -GET /api/:list -``` - -REST API - -```bash -GET /api/ -``` - -### 查看 collection 详情 - -HTTP API - -```bash -GET /api/:get?filterByTk= -GET /api/:get/ -``` - -REST API - -```bash -GET /api// -``` - -### 更新 collection - -HTTP API - -```bash -POST /api/:update?filterByTk= - -{} # JSON body - -# 或者 -POST /api/:update/ - -{} # JSON body -``` - -REST API - -```bash -PUT /api// - -{} # JSON body -``` - -### 删除 collection - -HTTP API - -```bash -POST /api/:destroy?filterByTk= -# 或者 -POST /api/:destroy/ -``` - -REST API - -```bash -DELETE /api// -``` - -## Association 资源 - ---- - -### 创建 Association - -HTTP API - -```bash -POST /api///:create - -{} # JSON body -``` - -REST API - -```bash -POST /api/// - -{} # JSON body -``` - -### 查看 Association 列表 - -HTTP API - -```bash -GET /api///:list -``` - -REST API - -```bash -GET /api/// -``` - -### 查看 Association 详情 - -HTTP API - -```bash -GET /api///:get?filterByTk= -# 或者 -GET /api///:get/ -``` - -REST API - -```bash -GET /api///:get/ -``` - -### 更新 Association - -HTTP API - -```bash -POST /api///:update?filterByTk= - -{} # JSON body - -# 或者 -POST /api///:update/ - -{} # JSON body -``` - -REST API - -```bash -PUT /api///:update/ - -{} # JSON 数据 -``` - -### 删除 Association - -HTTP API - -```bash -POST /api///:destroy?filterByTk= -# 或者 -POST /api///:destroy/ -``` - -REST API - -```bash -DELETE /api//// -``` diff --git a/docs/zh-CN/api/index.md b/docs/zh-CN/api/index.md deleted file mode 100644 index f23665749..000000000 --- a/docs/zh-CN/api/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# 概览 - -| 模块 | 包名 | 描述 | -|-----------------------------------| --------------------- | ------------------- | -| [Server](/api/server/application) | `@nocobase/server` | 服务端应用 | -| [Database](/api/database) | `@nocobase/database` | 数据库访问层 | -| [Resourcer](/api/resourcer) | `@nocobase/resourcer` | 资源与路由映射 | -| [ACL](/api/acl) | `@nocobase/acl` | 访问控制表 | -| [Client](/api/client/application) | `@nocobase/client` | 客户端应用 | -| [CLI](/api/cli) | `@nocobase/cli` | NocoBase 命令行工具 | -| [SDK](/api/sdk) | `@nocobase/sdk` | NocoBase SDK | -| [Actions](/api/actions) | `@nocobase/actions` | 内置常用资源操作 | diff --git a/docs/zh-CN/api/resourcer/action.md b/docs/zh-CN/api/resourcer/action.md deleted file mode 100644 index 9b5a0968a..000000000 --- a/docs/zh-CN/api/resourcer/action.md +++ /dev/null @@ -1,148 +0,0 @@ -# Action - -Action 是对资源的操作过程的描述,通常包含数据库处理等,类似其他框架中的 service 层,最简化的实现可以是一个 Koa 的中间件函数。在资源管理器里,针对特定资源定义的普通操作函数会被包装成 Action 类型的实例,当请求匹配对应资源的操作时,执行对应的操作过程。 - -## 构造函数 - -通常不需要直接实例化 Action,而是由资源管理器自动调用 `Action` 的静态方法 `toInstanceMap()` 进行实例化。 - -### `constructor(options: ActionOptions)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `handler` | `Function` | - | 操作函数 | -| `middlewares?` | `Middleware \| Middleware[]` | - | 针对操作的中间件 | -| `values?` | `Object` | - | 默认的操作数据 | -| `fields?` | `string[]` | - | 默认针对的字段组 | -| `appends?` | `string[]` | - | 默认附加的关联字段组 | -| `except?` | `string[]` | - | 默认排除的字段组 | -| `filter` | `FilterOptions` | - | 默认的过滤参数 | -| `sort` | `string[]` | - | 默认的排序参数 | -| `page` | `number` | - | 默认的页码 | -| `pageSize` | `number` | - | 默认的每页数量 | -| `maxPageSize` | `number` | - | 默认最大每页数量 | - -## 实例成员 - -### `actionName` - -被实例化后对应的操作名称。在实例化时从请求中解析获取。 - -### `resourceName` - -被实例化后对应的资源名称。在实例化时从请求中解析获取。 - -### `resourceOf` - -被实例化后对应的关系资源的主键值。在实例化时从请求中解析获取。 - -### `readonly middlewares` - -针对操作的中间件列表。 - -### `params` - -操作参数。包含对应操作的所有相关参数,实例化时根据定义的 action 参数初始化,之后请求中解析前端传入的参数并根据对应参数的合并策略合并。如果有其他中间件的处理,也会有类似的合并过程。直到 handler 处理时,访问 params 得到的是经过多次合并的最终参数。 - -参数的合并过程提供了针对操作处理的可扩展性,可以通过自定义中间件的方式按业务需求进行参数的前置解析和处理,例如表单提交的参数验证就可以在此环节实现。 - -预设的参数可以参考 [/api/actions] 中不同操作的参数。 - -参数中还包含请求资源路由的描述部分,具体如下: - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `resourceName` | `string` | - | 资源名称 | -| `resourceIndex` | `string \| number` | - | 资源的主键值 | -| `associatedName` | `string` | - | 所属关系资源的名称 | -| `associatedIndex` | `string \| number` | - | 所属关系资源的主键值 | -| `associated` | `Object` | - | 所属关系资源的实例 | -| `actionName` | `string` | - | 操作名称 | - -**示例** - -```ts -app.resourcer.define('books', { - actions: { - publish(ctx, next) { - ctx.body = ctx.action.params.values; - // { - // id: 1234567890 - // publishedAt: '2019-01-01', - // } - } - }, - middlewares: [ - async (ctx, next) => { - ctx.action.mergeParams({ - values: { - id: Math.random().toString(36).substr(2, 10), - publishedAt: new Date(), - } - }); - await next(); - } - ] -}); -``` - -## 实例方法 - -### `mergeParams()` - -将额外的参数合并至当前参数集,且可以根据不同的策略进行合并。 - -**签名** - -* `mergeParams(params: ActionParams, strategies: MergeStrategies = {})` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `params` | `ActionParams` | - | 额外的参数集 | -| `strategies` | `MergeStrategies` | - | 针对每个参数的合并策略 | - -内置操作的默认合并策略如下表: - -| 参数名 | 类型 | 默认值 | 合并策略 | 描述 | -| --- | --- | --- | --- | --- | -| `filterByTk` | `number \| string` | - | SQL `and` | 查询主键值 | -| `filter` | `FilterOptions` | - | SQL `and` | 查询过滤参数 | -| `fields` | `string[]` | - | 取并集 | 字段组 | -| `appends` | `string[]` | `[]` | 取并集 | 附加的关联字段组 | -| `except` | `string[]` | `[]` | 取并集 | 排除的字段组 | -| `whitelist` | `string[]` | `[]` | 取交集 | 可处理字段的白名单 | -| `blacklist` | `string[]` | `[]` | 取并集 | 可处理字段的黑名单 | -| `sort` | `string[]` | - | SQL `order by` | 查询排序参数 | -| `page` | `number` | - | 覆盖 | 页码 | -| `pageSize` | `number` | - | 覆盖 | 每页数量 | -| `values` | `Object` | - | 深度合并 | 操作提交的数据 | - -**示例** - -```ts -ctx.action.mergeParams({ - filter: { - name: 'foo', - }, - fields: ['id', 'name'], - except: ['name'], - sort: ['id'], - page: 1, - pageSize: 10, - values: { - name: 'foo', - }, -}, { - filter: 'and', - fields: 'union', - except: 'union', - sort: 'overwrite', - page: 'overwrite', - pageSize: 'overwrite', - values: 'deepMerge', -}); -``` diff --git a/docs/zh-CN/api/resourcer/index.md b/docs/zh-CN/api/resourcer/index.md deleted file mode 100644 index 958c66068..000000000 --- a/docs/zh-CN/api/resourcer/index.md +++ /dev/null @@ -1,284 +0,0 @@ -# Resourcer - -## 概览 - -Nocobase 中的接口遵循面向资源的设计模式。Resourcer 主要用于管理 API 资源与路由。 - -```javascript -const Koa = require('koa'); -const { Resourcer } = require('@nocobase/resourcer'); - -const resourcer = new 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 管理器实例。 - -**签名** - -* `constructor(options: ResourcerOptions)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `prefix` | `string` | - | 路由路径前缀 | -| `accessors` | `Object` | _以下成员值_ | 默认操作方法名称标识 | -| `accessors.list` | `string` | `'list'` | 列举操作方法名称标识 | -| `accessors.get` | `string` | `'get'` | 获取操作方法名称标识 | -| `accessors.create` | `string` | `'create'` | 创建操作方法名称标识 | -| `accessors.update` | `string` | `'update'` | 更新操作方法名称标识 | -| `accessors.delete` | `string` | `'destroy'` | 删除操作方法名称标识 | -| `accessors.add` | `string` | `'add'` | 增加关联操作方法名称标识 | -| `accessors.remove` | `string` | `'remove'` | 移除关联操作方法名称标识 | -| `accessors.set` | `string` | `'set'` | 全量设置关联操作方法名称标识 | - -**示例** - -在创建 app 时,可以通过 `resourcer` 选项传入: - -```ts -const app = new Application({ - // 对应默认 resourcer 实例的配置项 - resourcer: { - prefix: process.env.API_BASE_PATH - } -}); -``` - -## 实例方法 - -### `define()` - -定义并向资源管理器注册一个资源对象。通常代替 `Resource` 类的构造函数使用。 - -**签名** - -* `define(options: ResourceOptions): Resource` - -**参数** - -详见 [Resource 构造函数](/api/server/resourcer/resource#构造函数)。 - -**示例** - -```ts -app.resourcer.define({ - name: 'books', - actions: { - // 扩展的 action - publish(ctx, next) { - ctx.body = 'ok'; - } - } -}); -``` - -### `isDefined()` - -检查对应名称的资源是否已被注册。 - -**签名** - -* `isDefined(name: string): boolean` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 资源名称 | - -**示例** - -```ts -app.resourcer.isDefined('books'); // true -``` - -### `registerAction()` - -向资源管理器注册一个操作,可以指定针对特定的资源,如不指定资源名称,则认为是针对全局所有资源都可访问的操作。 - -**签名** - -* `registerAction(name: string, handler: HandlerType): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 操作名称 | -| `handler` | `HandlerType` | - | 操作处理函数 | - -`name` 的值如果以 `:` 开头则代表仅针对 `` 资源可访问,否则认为是全局操作。 - -**示例** - -```ts -// 注册后任意资源都可以进行 upload 操作 -app.resourcer.registerAction('upload', async (ctx, next) => { - ctx.body = 'ok'; -}); - -// 仅针对 attachments 资源注册 upload 操作 -app.resourcer.registerAction('attachments:upload', async (ctx, next) => { - ctx.body = 'ok'; -}); -``` - -### `registerActions()` - -向资源管理器注册多个操作的集合方法。 - -**签名** - -* `registerActions(actions: { [name: string]: HandlerType }): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `actions` | `{ [name: string]: HandlerType }` | - | 操作集合 | - -**示例** - -```ts -app.resourcer.registerActions({ - upload: async (ctx, next) => { - ctx.body = 'ok'; - }, - 'attachments:upload': async (ctx, next) => { - ctx.body = 'ok'; - } -}); -``` - -### `getResource()` - -获取对应名称的资源对象。 - -**签名** - -* `getResource(name: string): Resource` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 资源名称 | - -**示例** - -```ts -app.resourcer.getResource('books'); -``` - -### `getAction()` - -获取对应名称的操作处理函数。 - -**签名** - -* `getAction(name: string): HandlerType` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 操作名称 | - -`name` 的值如果以 `:` 开头则代表仅针对 `` 资源的操作,否则认为是全局操作。 - -**示例** - -```ts -app.resourcer.getAction('upload'); -app.resourcer.getAction('attachments:upload'); -``` - -### `use()` - -以 Koa 的形式注册一个中间件,中间件形成一个队列,并排在所有资源的操作处理函数之前执行。 - -**签名** - -* `use(middleware: Middleware | Middleware[]): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `middleware` | `Middleware \| Middleware[]` | - | 中间件 | - -**示例** - -```ts -app.resourcer.use(async (ctx, next) => { - console.log(ctx.req.url); - await next(); -}); -``` - -### `middleware()` - -生成一个兼容 Koa 的中间件,用于将资源的路由处理注入到应用中。 - -**签名** - -* `middleware(options: KoaMiddlewareOptions): KoaMiddleware` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.prefix?` | `string` | `''` | 路径前缀。 | -| `options.accessors?` | `Object` | `{}` | 常用方法的名称映射,与构造函数的 `accessors` 参数结构相同。 | - -**示例** - -```ts -const koa = new Koa(); - -const resourcer = new Resourcer(); - -// 生成兼容 Koa 的中间件 -koa.use(resourcer.middleware()); -``` diff --git a/docs/zh-CN/api/resourcer/middleware.md b/docs/zh-CN/api/resourcer/middleware.md deleted file mode 100644 index 1285acf81..000000000 --- a/docs/zh-CN/api/resourcer/middleware.md +++ /dev/null @@ -1,168 +0,0 @@ -# Middleware - -与 Koa 的中间件类似,但提供了更多增强的功能,可以方便的进行更多的扩展。 - -中间件定义后可以在资源管理器等多处进行插入使用,由开发者自行控制调用的时机。 - -## 构造函数 - -**签名** - -* `constructor(options: Function)` -* `constructor(options: MiddlewareOptions)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options` | `Function` | - | 中间件处理函数 | -| `options` | `MiddlewareOptions ` | - | 中间件配置项 | -| `options.only` | `string[]` | - | 仅允许指定的操作 | -| `options.except` | `string[]` | - | 排除指定的操作 | -| `options.handler` | `Function` | - | 处理函数 | - -**示例** - -简单定义: - -```ts -const middleware = new Middleware((ctx, next) => { - await next(); -}); -``` - -使用相关参数: - -```ts -const middleware = new Middleware({ - only: ['create', 'update'], - async handler(ctx, next) { - await next(); - }, -}); -``` - -## 实例方法 - -### `getHandler()` - -返回已经过编排的处理函数。 - -**示例** - -以下中间件在请求时会先输出 `1`,再输出 `2`。 - -```ts -const middleware = new Middleware((ctx, next) => { - console.log(1); - await next(); -}); - -middleware.use(async (ctx, next) => { - console.log(2); - await next(); -}); - -app.resourcer.use(middleware.getHandler()); -``` - -### `use()` - -对当前中间件添加中间件函数。用于提供中间件的扩展点。示例见 `getHandler()`。 - -**签名** - -* `use(middleware: Function)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `middleware` | `Function` | - | 中间件处理函数 | - -### `disuse()` - -移除当前中间件已添加的中间件函数。 - -**签名** - -* `disuse(middleware: Function)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `middleware` | `Function` | - | 中间件处理函数 | - -**示例** - -以下示例在请求处理时只输出 `1`,不执行 fn1 中的 `2` 输出。 - -```ts -const middleware = new Middleware((ctx, next) => { - console.log(1); - await next(); -}); - -async function fn1(ctx, next) { - console.log(2); - await next(); -} - -middleware.use(fn1); - -app.resourcer.use(middleware.getHandler()); - -middleware.disuse(fn1); -``` - -### `canAccess()` - -判断当前中间件针对特定操作是否要被调用,通常由资源管理器内部处理。 - -**签名** - -* `canAccess(name: string): boolean` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 操作名称 | - -## 其他导出 - -### `branch()` - -创建一个分支中间件,用于在中间件中进行分支处理。 - -**签名** - -* `branch(map: { [key: string]: Function }, reducer: Function, options): Function` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `map` | `{ [key: string]: Function }` | - | 分支处理函数映射表,键名由后续计算函数在调用时给出 | -| `reducer` | `(ctx) => string` | - | 计算函数,用于基于上下文计算出分支的键名 | -| `options?` | `Object` | - | 分支配置项 | -| `options.keyNotFound?` | `Function` | `ctx.throw(404)` | 未找到键名时的处理函数 | -| `options.handlerNotSet?` | `Function` | `ctx.throw(404)` | 未定义处理函数时的处理 | - -**示例** - -用户验证时,根据请求 URL 中 query 部分的 `authenticator` 参数的值决定后续需要如何处理: - -```ts -app.resourcer.use(branch({ - 'password': async (ctx, next) => { - // ... - }, - 'sms': async (ctx, next) => { - // ... - }, -}, (ctx) => { - return ctx.action.params.authenticator ?? 'password'; -})); -``` diff --git a/docs/zh-CN/api/resourcer/resource.md b/docs/zh-CN/api/resourcer/resource.md deleted file mode 100644 index 4e83b2d21..000000000 --- a/docs/zh-CN/api/resourcer/resource.md +++ /dev/null @@ -1,114 +0,0 @@ -# Resource - -Resource 用于定义资源实例。被 Resourcer 管理的资源实例都可以通过 HTTP 请求访问。 - -## 构造函数 - -用于创建 Resource 实例。通常由 Resourcer 管理器的 `define()` 接口调用替代,不需要直接使用。 - -**签名** - -* `constructor(options: ResourceOptions, resourcer: Resourcer)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.name` | `string` | - | 资源名称,对应 URL 路由中的资源地址部分。 | -| `options.type` | `string` | `'single'` | 资源类型,可选项为 `'single'`、`'hasOne'`、`'hasMany'`、`'belongsTo'`、`'belongsToMany'`。 | -| `options.actions` | `Object` | - | 对资源可进行的操作列表,详见示例部分。 | -| `options.middlewares` | `MiddlewareType \| MiddlewareType[]` | - | 对当前定义资源进行任意操作访问时的中间件列表,详见示例部分。 | -| `options.only` | `ActionName[]` | `[]` | 针对全局操作的白名单列表,当数组中有值时(`length > 0`),只有数组中的操作可被访问。 | -| `options.except` | `ActionName[]` | `[]` | 针对全局操作的黑名单列表,当数组中有值时(`length > 0`),除数组中的操作外,其他操作可被访问。 | -| `resourcer` | `Resourcer` | - | 所属资源管理器实例。 | - -**示例** - -```ts -app.resourcer.define({ - name: 'books', - actions: { - // 扩展的 action - publish(ctx, next) { - ctx.body = 'ok'; - } - }, - middleware: [ - // 扩展的中间件 - async (ctx, next) => { - await next(); - } - ] -}); -``` - -## 实例成员 - -### `options` - -当前资源的配置项。 - -### `resourcer` - -所属的资源管理器实例。 - -### `middlewares` - -已注册的中间件列表。 - -### `actions` - -已注册的操作映射表。 - -### `except` - -操作排除的名单列表。 - -## 实例方法 - -### `getName()` - -获取当前资源的名称。 - -**签名** - -* `getName(): string` - -**示例** - -```ts -const resource = app.resourcer.define({ - name: 'books' -}); - -resource.getName(); // 'books' -``` - -### `getAction()` - -根据名称获取当前资源的操作。 - -**签名** - -* `getAction(name: string): Action` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `name` | `string` | - | 操作名称。 | - -**示例** - -```ts -const resource = app.resourcer.define({ - name: 'books', - actions: { - publish(ctx, next) { - ctx.body = 'ok'; - } - } -}); - -resource.getAction('publish'); // [Function: publish] -``` diff --git a/docs/zh-CN/api/sdk.md b/docs/zh-CN/api/sdk.md deleted file mode 100644 index d4fc6fa70..000000000 --- a/docs/zh-CN/api/sdk.md +++ /dev/null @@ -1,281 +0,0 @@ -# @nocobase/sdk - -## APIClient - -```ts -class APIClient { - // axios 实例 - axios: AxiosInstance; - // 构造器 - constructor(instance?: AxiosInstance | AxiosRequestConfig); - // 客户端请求,支持 AxiosRequestConfig 和 ResourceActionOptions - request, D = any>(config: AxiosRequestConfig | ResourceActionOptions): Promise; - // 获取资源 - resource(name: string, of?: any): R; -} -``` - -初始化实例 - -```ts -import axios from 'axios'; -import { APIClient } from '@nocobase/sdk'; - -// 提供 AxiosRequestConfig 配置参数 -const api = new APIClient({ - baseURL: 'https://localhost:8000/api', -}); - -// 提供 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 默认使用的 localStorage,你也可以自定义 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 -// 登录并记录 token -api.auth.signIn({ email, password }); -// 注销并删除 token -api.auth.signOut(); -// 设置 token -api.auth.setToken('123'); -// 设置 role(当需要多角色时) -api.auth.setRole('admin'); -// 设置 locale(当需要多语言时) -api.auth.setLocale('zh-CN'); -``` - -自定义 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/zh-CN/api/server/app-manager.md b/docs/zh-CN/api/server/app-manager.md deleted file mode 100644 index 76a11afbe..000000000 --- a/docs/zh-CN/api/server/app-manager.md +++ /dev/null @@ -1 +0,0 @@ -# AppManager \ No newline at end of file diff --git a/docs/zh-CN/api/server/application.md b/docs/zh-CN/api/server/application.md deleted file mode 100644 index 0b8d3965e..000000000 --- a/docs/zh-CN/api/server/application.md +++ /dev/null @@ -1,303 +0,0 @@ -# Application - -## 概览 - -### 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` 方法,结束更新。 - -## 构造函数 - -### `constructor()` - -创建一个应用实例。 - -**签名** - -* `constructor(options: ApplicationOptions)` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `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](#) | -| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.i18next.com/overview/api) | -| `options.plugins` | `PluginConfiguration[]` | `[]` | 默认启用的插件配置 | - -Type - -```ts -interface ApplicationOptions { - -} -``` - -## 实例成员 - -### `cli` - -命令行工具实例,参考 npm 包 [Commander](https://www.npmjs.com/package/commander)。 - -### `db` - -数据库实例,相关 API 参考 [Database](/api/database)。 - -### `resourcer` - -应用初始化自动创建的资源路由管理实例,相关 API 参考 [Resourcer](/api/resourcer)。 - -### `acl` - -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)。 - -### `pm` - -插件管理器实例,相关 API 参考 [PluginManager](./plugin-manager)。 - -### `version` - -应用版本实例,相关 API 参考 [ApplicationVersion](./application-version)。 - -### `middleware` - -内置的中间件有: - -- logger -- i18next -- bodyParser -- cors -- dataWrapping -- db2resource -- restApiMiddleware - -### `context` - -继承自 koa 的 context,可以通过 `app.context` 访问,用于向每个请求注入上下文可访问的内容。参考 [Koa Context](https://koajs.com/#app-context)。 - -NocoBase 默认对 context 注入了以下成员,可以在请求处理函数中直接使用: - -| 变量名 | 类型 | 描述 | -| --- | --- | --- | -| `ctx.app` | `Application` | 应用实例 | -| `ctx.db` | `Database` | 数据库实例 | -| `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 | -| `ctx.action` | `Action` | 资源操作相关对象实例 | -| `ctx.logger` | `Winston` | 日志实例 | -| `ctx.i18n` | `I18n` | 国际化实例 | -| `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 | -| `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token | - -## 实例方法 - -### `use()` - -注册中间件,兼容所有 [Koa 插件](https://www.npmjs.com/search?q=koa) - -### `on()` - -订阅应用级事件,主要与生命周期相关,等同于 `eventEmitter.on()`。所有可订阅事件参考 [事件](#事件)。 - -### `command()` - -自定义 command - -### `findCommand()` - -查找已定义 command - -### `runAsCLI()` - -以 CLI 的方式运行。 - -### `load()` - -加载应用配置。 - -**签名** - -* `async load(): Promise` - -### `reload()` - -重载应用配置。 - -### `install()` - -初始化安装应用,同步安装插件。 - -### `upgrade()` - -升级应用,同步升级插件。 - -### `start()` - -启动应用,如果配置了监听的端口,将启动监听,之后应用即可接受 HTTP 请求。 - -**签名** - -* `async start(options: StartOptions): Promise` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options.listen?` | `ListenOptions` | `{}` | HTTP 监听参数对象 | -| `options.listen.port?` | `number` | 13000 | 端口 | -| `options.listen.host?` | `string` | `'localhost'` | 域名 | - -### `stop()` - -停止应用,此方法会关闭数据库连接,关闭 HTTP 端口,不会删除数据。 - -### `destroy()` - -删除应用,此方法会删除应用对应的数据库。 - -## 事件 - -### `'beforeLoad'` / `'afterLoad'` -### `'beforeInstall'` / `'afterInstall'` -### `'beforeUpgrade'` / `'afterUpgrade'` -### `'beforeStart'` / `'afterStart'` -### `'beforeStop'` / `'afterStop'` -### `'beforeDestroy'` / `'afterDestroy'` diff --git a/docs/zh-CN/api/server/i18n.md b/docs/zh-CN/api/server/i18n.md deleted file mode 100644 index 05b5b6193..000000000 --- a/docs/zh-CN/api/server/i18n.md +++ /dev/null @@ -1 +0,0 @@ -# I18n diff --git a/docs/zh-CN/api/server/plugin-manager.md b/docs/zh-CN/api/server/plugin-manager.md deleted file mode 100644 index e161d37f0..000000000 --- a/docs/zh-CN/api/server/plugin-manager.md +++ /dev/null @@ -1,59 +0,0 @@ -# PluginManager - -应用插件管理器的实例,由应用自动创建,可以通过 `app.pm` 访问。 - -## 实例方法 - -### `create()` - -在本地创建一个插件脚手架 - -**签名** - -```ts -create(name, options): void; -``` - -### `addStatic()` - -**签名** - -```ts -addStatic(plugin: any, options?: PluginOptions): Plugin; -``` - -**示例** - -```ts -pm.addStatic('nocobase'); -``` - -### `add()` - -**签名** - -```ts -async add(plugin: any, options?: PluginOptions): Promise; -async add(plugin: string[], options?: PluginOptions): Promise; -``` - -**示例** - -```ts -await pm.add(['test'], { - builtIn: true, - enabled: true, -}); -``` - -### `get()` - -获取插件实例 - -### `enable()` - -### `disable()` - -### `remove()` - -### `upgrade()` diff --git a/docs/zh-CN/api/server/plugin.md b/docs/zh-CN/api/server/plugin.md deleted file mode 100644 index 4bb5aaf9c..000000000 --- a/docs/zh-CN/api/server/plugin.md +++ /dev/null @@ -1,48 +0,0 @@ -# Plugin - -## 概览 - -Nocobase 中的插件为 `Class` 的形式。如需自定义插件,需要继承 `Plugin` 类。 - -```typescript -import { Plugin } from '@nocobase/server'; - -class MyPlugin extends Plugin { - // ... -} - -app.plugin(MyPlugin, { name: 'my-plugin' }); -``` - -## 插件生命周期 - -每个插件都包含生命周期方法,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。 -生命周期方法将由 `Application` 在特定阶段调用,可参考 [`Application`生命周期](./application.md)。 - -### `beforeLoad()` - -插件加载前,如事件或类注册,在此可访问核心接口,其他插件此阶段不可用。 - -### `load()` - -加载插件,配置之类。在 `load` 中可调用其他插件实例,而在 `beforeLoad` 中是不行的。 - -### `install()` - -插件安装逻辑,如初始化数据 - -### `afterAdd()` - -插件 add/addStatic 之后 - -### `afterEnable()` - -插件激活之后的逻辑 - -### `afterDisable()` - -插件禁用之后的逻辑 - -### `remove()` - -用于实现插件删除逻辑 diff --git a/docs/zh-CN/components.md b/docs/zh-CN/components.md deleted file mode 100644 index 0eac4eb52..000000000 --- a/docs/zh-CN/components.md +++ /dev/null @@ -1 +0,0 @@ -# Components diff --git a/docs/zh-CN/development/app-ds.md b/docs/zh-CN/development/app-ds.md deleted file mode 100644 index 1a8244ac8..000000000 --- a/docs/zh-CN/development/app-ds.md +++ /dev/null @@ -1,64 +0,0 @@ -# 项目目录结构 - -无论是 [Git 源码](/welcome/getting-started/installation/git-clone) 还是 [create-nocobase-app](/welcome/getting-started/installation/create-nocobase-app) 创建的 NocoBase 应用,目录结构都是一样的,结构如下: - -```bash -├── my-nocobase-app - ├── packages # 采用 Monorepo 的方式管理代码,将不同模块划分到不同包里 - ├── app - ├── client # 客户端模块 - ├── server # 服务端模块 - ├── plugins # 插件目录 - ├── storage # 用于存放数据库文件、附件、缓存等 - ├── db - ├── .env # 环境变量 - ├── .buildrc.ts # packages 的打包配置,支持 cjs、esm 和 umd 三种格式的打包。 - ├── jest.config.js - ├── jest.setup.ts - ├── lerna.json - ├── package.json - ├── tsconfig.jest.json - ├── tsconfig.json - ├── tsconfig.server.json -``` - -## packages 目录 - -```bash -├── packages - ├── app - ├── client - ├── public - ├── src - ├── pages - ├── index.tsx - ├── .umirc.ts - ├── package.json - ├── server - ├── src - ├── config - ├── index.ts - ├── package.json - ├── /plugins - ├── my-plugin - ├── src - ├── package.json -``` - -NocoBase 采用 Monorepo 的方式管理代码,将不同模块划分到不同包里。 - -- `app/client` 为应用的客户端模块,基于 [umi](https://umijs.org/zh-CN) 构建; -- `app/server` 为应用的服务端模块; -- `plugins/*` 目录里可以放各种插件。 - -## storages 目录 - -用于存放数据库文件、附件、缓存等。 - -## .env 文件 - -环境变量。 - -## .buildrc.ts 文件 - -packages 的打包配置,支持 cjs、esm 和 umd 三种格式的打包。 diff --git a/docs/zh-CN/development/client/i18n.md b/docs/zh-CN/development/client/i18n.md deleted file mode 100644 index 10d10c8f0..000000000 --- a/docs/zh-CN/development/client/i18n.md +++ /dev/null @@ -1,140 +0,0 @@ -# 国际化 - -客户端的国际化多语言基于 npm 包 [react-i18next](https://npmjs.com/package/react-i18next) 实现,在应用顶层提供了 `` 组件的包装,可以在任意位置直接使用相关的方法。 - -添加语言包: - -```tsx | pure -import { i18n } from '@nocobase/client'; - -i18n.addResources('zh-CN', 'test', { - Hello: '你好', - World: '世界', -}); -``` - -注:这里第二个参数填写的 `'test'` 是语言的命名空间,通常插件自己定义的语言资源都应该按自己插件包名创建特定的命名空间,以避免和其他语言资源冲突。NocoBase 中默认的命名空间是 `'client'`,大部分常用和基础的语言翻译都放置在此命名空间。当没有提供所需语言时,可在插件自身的命名空间内进行扩展定义。 - -在组件中调用翻译函数: - -```tsx | pure -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -export default function MyComponent() { - // 使用之前定义的命名空间 - const { t } = useTranslation('test'); - - return ( -
    -

    {t('World')}

    -
    - ); -} -``` - -在 SchemaComponent 组件中可以直接使用模板方法 `'{{t()}}'`,模板中的翻译函数会自动被执行: - -```tsx | pure -import React from 'react'; -import { SchemaComponent } from '@nocobase/client'; - -export default function MySchemaComponent() { - return ( - - ); -} -``` - -在某些特殊情况下也需要以模板的方式定义多语言时,可以使用 NocoBase 内置的 `compile()` 方法编译为多语言结果: - -```tsx | pure -import React from 'react'; -import { useCompile } from '@nocobase/client'; - -const title = '{{t("Hello", { ns: "test" })}}'; - -export default function MyComponent() { - const { compile } = useCompile(); - - return ( -
    {compile(title)}
    - ); -} -``` - -## 建议配置 - -以英文文案为 key,翻译为 value,这样的好处,即使多语言缺失,也会以英文显示,不会造成阅读障碍,如: - -```ts -i18n.addResources('zh-CN', 'my-plugin', { - 'Show dialog': '显示对话框', - 'Hide dialog': '隐藏对话框' -}); -``` - -为了更方便管理多语言文件,推荐在插件中创建一个 `locale` 文件夹,并把对应语言文件都放置在其中方便管理: - -```bash -|- /my-plugin - |- /src - |- /client - |- locale # 多语言文件夹 - |- zh-CN.ts - |- en-US.ts -``` - -## 示例 - -### 客户端组件多语言 - -例如订单状态的组件,根据不同值有不同的文本显示: - -```tsx | pure -import React from 'react'; -import { Select } from 'antd'; -import { i18n } from '@nocobase/client'; -import { useTranslation } from 'react-i18next'; - -i18n.addResources('zh-CN', 'sample-shop-i18n', { - Pending: '已下单', - Paid: '已支付', - Delivered: '已发货', - Received: '已签收' -}); - -const ORDER_STATUS_LIST = [ - { value: -1, label: 'Canceled (untranslated)' }, - { value: 0, label: 'Pending' }, - { value: 1, label: 'Paid' }, - { value: 2, label: 'Delivered' }, - { value: 3, label: 'Received' }, -] - -function OrderStatusSelect() { - const { t } = useTranslation('sample-shop-i18n'); - - return ( - - ); -} - -export default function () { - return ( - - ); -} -``` diff --git a/docs/zh-CN/development/client/index.md b/docs/zh-CN/development/client/index.md deleted file mode 100644 index 7e2025268..000000000 --- a/docs/zh-CN/development/client/index.md +++ /dev/null @@ -1,72 +0,0 @@ -# 概述 - -NocoBase 客户端的扩展大多以 Provider 的形式提供。 - -## 内置的 Providers - -- APIClientProvider -- I18nextProvider -- AntdConfigProvider -- SystemSettingsProvider -- PluginManagerProvider -- SchemaComponentProvider -- BlockSchemaComponentProvider -- AntdSchemaComponentProvider -- DocumentTitleProvider -- ACLProvider - -## 客户端 Provider 模块的注册 - -静态的 Provider 通过 app.use() 注册,动态的 Provider 通过 dynamicImport 适配。 - -```tsx | pure -import React from 'react'; -import { Application } from '@nocobase/client'; - -const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`../plugins/${name}`); - }, -}); - -// 访问 /hello 页面时,显示 Hello world! -const HelloProvider = React.memo((props) => { - const location = useLocation(); - if (location.pathname === '/hello') { - return
    Hello world!
    - } - return <>{props.children} -}); -HelloProvider.displayName = 'HelloProvider' - -app.use(HelloProvider); -``` - -## 插件的客户端 - -初始化的空插件,客户端相关目录结构如下: - -```bash -|- /my-plugin - |- /src - |- /client - |- index.tsx - |- client.d.ts - |- client.js -``` - -`client/index.tsx` 内容如下: - -```tsx | pure -import React from 'react'; - -// 这是一个空的 Provider,只有 children 传递,并未提供自定义的 Context -export default React.memo((props) => { - return <>{props.children}; -}); -``` - -插件 pm.add 之后,会向 `packages/app/client/src/plugins` 目录写入 `my-plugin.ts` 文件 diff --git a/docs/zh-CN/development/client/m.svg b/docs/zh-CN/development/client/m.svg deleted file mode 100644 index 932d4de2b..000000000 --- a/docs/zh-CN/development/client/m.svg +++ /dev/null @@ -1 +0,0 @@ -
    Pylons Application
    restApi
    action
    acl
    parseToken
    db2resource
    dataWrapping
    Before
    After
    Request
    Response
    before
    after
    resourcer.use
    checkRole
    app.use
    \ No newline at end of file diff --git a/docs/zh-CN/development/client/plugin-settings.md b/docs/zh-CN/development/client/plugin-settings.md deleted file mode 100644 index 73dc392a9..000000000 --- a/docs/zh-CN/development/client/plugin-settings.md +++ /dev/null @@ -1,75 +0,0 @@ -# 配置中心 - - - -## 示例 - -### 基础用法 - -```tsx | pure -import { Plugin } from '@nocobase/client'; -import React from 'react'; - -const HelloSettingPage = () =>
    Hello Setting page
    ; - -export class HelloPlugin extends Plugin { - async load() { - this.app.pluginSettingsManager.add('hello', { - title: 'Hello', // 设置页面的标题和菜单名称 - icon: 'ApiOutlined', // 设置页面菜单图标 - Component: HelloSettingPage, - }) - } -} -``` - -### 多层级路由 - -```tsx | pure -import { Outlet } from 'react-router-dom' -const SettingPageLayout = () =>
    公共部分,下面是子路由的出口:
    ; - -class HelloPlugin extends Plugin { - async load() { - this.app.pluginSettingsManager.add('hello', { - title: 'HelloWorld', // 设置页面的标题和菜单名称 - icon: '', // 菜单图标 - Component: SettingPageLayout - }) - - this.app.pluginSettingsManager.add('hello.demo1', { - title: 'Demo1 Page', - Component: () =>
    Demo1 Page Content
    - }) - - this.app.pluginSettingsManager.add('hello.demo2', { - title: 'Demo2 Page', - Component: () =>
    Demo2 Page Content
    - }) - } -} -``` - -### 获取路由路径 - - -如果想获取设置页面的跳转链接,可以通过 `getRoutePath` 方法获取。 - -```tsx | pure -import { useApp } from '@nocobase/client' - -const app = useApp(); -app.pluginSettingsManager.getRoutePath('hello'); // /admin/settings/hello -app.pluginSettingsManager.getRoutePath('hello.demo1'); // /admin/settings/hello/demo1 -``` - -### 获取配置 - -如果想获取添加的配置(已进行权限过滤),可以通过 `get` 方法获取。 - -```tsx | pure -const app = useApp(); -app.pluginSettingsManager.get('hello'); // { title: 'HelloWorld', icon: '', Component: HelloSettingPage, children: [{...}] } -``` - -完整示例查看 [samples/hello](https://github.com/nocobase/nocobase/blob/main/packages/plugins/%40nocobase/plugin-sample-hello/src/client/index.tsx)。 diff --git a/docs/zh-CN/development/client/plugin-settings/settings-tab.jpg b/docs/zh-CN/development/client/plugin-settings/settings-tab.jpg deleted file mode 100644 index 34a8da116..000000000 Binary files a/docs/zh-CN/development/client/plugin-settings/settings-tab.jpg and /dev/null differ diff --git a/docs/zh-CN/development/client/test.md b/docs/zh-CN/development/client/test.md deleted file mode 100644 index 1af06a831..000000000 --- a/docs/zh-CN/development/client/test.md +++ /dev/null @@ -1,40 +0,0 @@ -# 测试 - -测试基于 [Jest](https://jestjs.io/) 测试框架。同时还包括了常用的 React 测试库,如 [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) - -## 示例 - -```tsx | pure -import { render } from '@testing-library/react'; -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { RouteSwitch } from '../RouteSwitch'; -import { RouteSwitchProvider } from '../RouteSwitchProvider'; - -const Home = () =>

    Home

    ; -const About = () =>

    About

    ; - -describe('route-switch', () => { - it('case 1', () => { - const App = () => { - return ( - - - - - - ); - }; - const { container } = render(); - expect(container).toMatchSnapshot(); - }); -}); -``` diff --git a/docs/zh-CN/development/client/ui-router.md b/docs/zh-CN/development/client/ui-router.md deleted file mode 100644 index b6ace0db3..000000000 --- a/docs/zh-CN/development/client/ui-router.md +++ /dev/null @@ -1,66 +0,0 @@ -# UI 路由 - -NocoBase Client 的 Router 基于 [React Router](https://v5.reactrouter.com/web/guides/quick-start),可以通过 `app.router` 来配置 ui routes,例子如下: - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Link, Outlet } from 'react-router-dom'; -import { Application } from '@nocobase/client'; - -const Home = () =>

    Home

    ; -const About = () =>

    About

    ; - -const Layout = () => { - return
    -
    Home, About
    - -
    -} - -const app = new Application({ - router: { - type: 'memory', - initialEntries: ['/'] - } -}) - -app.router.add('root', { - element: -}) - -app.router.add('root.home', { - path: '/', - element: -}) - -app.router.add('root.about', { - path: '/about', - element: -}) - -export default app.getRootComponent(); -``` - -在完整的 NocoBase 应用里,可以类似以下的的方式扩展 Route: - -```tsx | pure -import { Plugin } from '@nocobase/client'; - -class MyPlugin extends Plugin { - async load() { - // 添加一条路由 - this.app.router.add('hello', { - path: '/hello', - element:
    hello
    , - }) - - // 删除一条路由 - this.app.router.remove('hello'); - } -} -``` - -完整示例查看 [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page) diff --git a/docs/zh-CN/development/client/ui-schema-designer/acl.md b/docs/zh-CN/development/client/ui-schema-designer/acl.md deleted file mode 100644 index d1c6127f6..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/acl.md +++ /dev/null @@ -1,2 +0,0 @@ -# ACL - diff --git a/docs/zh-CN/development/client/ui-schema-designer/block-provider.md b/docs/zh-CN/development/client/ui-schema-designer/block-provider.md deleted file mode 100644 index 42fa25421..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/block-provider.md +++ /dev/null @@ -1,2 +0,0 @@ -# 区块 - diff --git a/docs/zh-CN/development/client/ui-schema-designer/collection-manager.md b/docs/zh-CN/development/client/ui-schema-designer/collection-manager.md deleted file mode 100644 index e4154eb14..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/collection-manager.md +++ /dev/null @@ -1,127 +0,0 @@ -# Collection Manager - -```tsx -import React from 'react'; -import { observer, ISchema, useForm } from '@formily/react'; -import { - SchemaComponent, - SchemaComponentProvider, - Form, - Action, - CollectionProvider, - CollectionField, -} from '@nocobase/client'; -import { FormItem, Input } from '@formily/antd-v5'; - -export default observer(() => { - const collection = { - name: 'tests', - fields: [ - { - type: 'string', - name: 'title1', - interface: 'input', - uiSchema: { - title: 'Title1', - type: 'string', - 'x-component': 'Input', - required: true, - description: 'description1', - } as ISchema, - }, - { - type: 'string', - name: 'title2', - interface: 'input', - uiSchema: { - title: 'Title2', - type: 'string', - 'x-component': 'Input', - description: 'description', - default: 'ttt', - }, - }, - { - type: 'string', - name: 'title3', - }, - ], - }; - - const schema: ISchema = { - type: 'object', - properties: { - form1: { - type: 'void', - 'x-component': 'Form', - properties: { - // 字段 title1 直接使用全局提供的 uiSchema - title1: { - 'x-component': 'CollectionField', - 'x-decorator': 'FormItem', - default: '111', - }, - // 等同于 - // title1: { - // type: 'string', - // title: 'Title', - // required: true, - // 'x-component': 'Input', - // 'x-decorator': 'FormItem', - // }, - title2: { - 'x-component': 'CollectionField', - 'x-decorator': 'FormItem', - title: 'Title4', // 覆盖全局已定义的 Title2 - required: true, // 扩展的配置参数 - description: 'description4', - }, - // 等同于 - // title2: { - // type: 'string', - // title: 'Title22', - // required: true, - // 'x-component': 'Input', - // 'x-decorator': 'FormItem', - // }, - // 字段 title3 没有提供 uiSchema,自行处理 - title3: { - 'x-component': 'Input', - 'x-decorator': 'FormItem', - title: 'Title3', - required: true, - }, - action1: { - // type: 'void', - 'x-component': 'Action', - title: 'Submit', - 'x-component-props': { - type: 'primary', - useAction: '{{ useSubmit }}', - }, - }, - }, - }, - }, - }; - - const useSubmit = () => { - const form = useForm(); - return { - async run() { - form.submit(() => { - console.log(form.values); - }); - }, - }; - }; - - return ( - - - - - - ); -}); -``` \ No newline at end of file diff --git a/docs/zh-CN/development/client/ui-schema-designer/component-library.md b/docs/zh-CN/development/client/ui-schema-designer/component-library.md deleted file mode 100644 index db99b2c6b..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/component-library.md +++ /dev/null @@ -1,82 +0,0 @@ -# Schema 组件库 - -## 包装器组件 - -- BlockItem -- FormItem -- CardItem - -## 布局 - -- Page -- Grid -- Tabs -- Space - -## 字段组件 - -字段组件一般不单独使用,而是用在数据展示组件当中 - -- CollectionField:万能组件 -- Cascader -- Checkbox -- ColorSelect -- DatePicker -- Filter -- Formula -- IconPicker -- Input -- InputNumber -- Markdown -- Password -- Percent -- Radio -- RecordPicker -- RichText -- Select -- TimePicker -- TreeSelect -- Upload - -## 数据展示组件 - -需要与字段组件搭配使用 - -- Calendar -- Form -- Kanban -- Table -- TableV2 - -## 操作(onClick 事件型组件) - -- Action -- Action.Drawer -- Action.Modal -- ActionBar:用于操作布局 -- Menu - -## 其他 - -- G2plot -- Markdown.Void - -## `x-designer` 和 `x-initializer` 的使用场景 - -`x-decorator` 或 `x-component` 是以下组件时,`x-designer` 生效: - -- BlockItem -- CardItem -- FormItem -- Table.Column -- Tabs.TabPane - -`x-decorator` 或 `x-component` 是以下组件时,`x-initializer` 生效: - -- ActionBar -- BlockItem -- CardItem -- FormItem -- Grid -- Table -- Tabs \ No newline at end of file diff --git a/docs/zh-CN/development/client/ui-schema-designer/demo1.tsx b/docs/zh-CN/development/client/ui-schema-designer/demo1.tsx deleted file mode 100644 index 0b6b08ce4..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/demo1.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { ArrayField } from '@formily/core'; -import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; -import { Table, TableColumnType } from 'antd'; -import React from 'react'; - -const ArrayTable = observer( - (props: any) => { - const { rowKey } = props; - const field = useField(); - const schema = useFieldSchema(); - const columnSchemas = schema.reduceProperties((buf, s) => { - if (s['x-component'] === 'ArrayTable.Column') { - buf.push(s); - } - return buf; - }, []); - - const columns = columnSchemas.map((s) => { - return { - render: (value, record) => { - return ; - }, - } as TableColumnType; - }); - - return
    ; - }, - { displayName: 'ArrayTable' }, -); - -const Value = connect((props) => { - return
  • value: {props.value}
  • ; -}); - -const schema: ISchema = { - type: 'object', - properties: { - objArr: { - type: 'array', - default: [ - { __path: '0', id: 1, value: 't1' }, - { - __path: '1', - id: 2, - value: 't2', - children: [ - { - __path: '1.children.0', - id: 5, - value: 't5', - parentId: 2, - }, - ], - }, - { - __path: '2', - id: 3, - value: 't3', - children: [ - { - __path: '2.children.0', - id: 4, - value: 't4', - parentId: 3, - children: [ - { - __path: '2.children.0.children.0', - id: 6, - value: 't6', - parentId: 4, - }, - { - __path: '2.children.0.children.1', - id: 7, - value: 't7', - parentId: 4, - }, - ], - }, - ], - }, - ], - 'x-component': 'ArrayTable', - 'x-component-props': { - rowKey: 'id', - }, - properties: { - c1: { - type: 'void', - 'x-component': 'ArrayTable.Column', - properties: { - value: { - type: 'string', - 'x-component': 'Value', - }, - }, - }, - }, - }, - }, -}; - -export default () => { - return ( - - - - ); -}; diff --git a/docs/zh-CN/development/client/ui-schema-designer/designable.md b/docs/zh-CN/development/client/ui-schema-designer/designable.md deleted file mode 100644 index 676405e3a..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/designable.md +++ /dev/null @@ -1,353 +0,0 @@ -# Schema 的设计能力 - -Schema 的设计能力主要体现在 - -- 邻近位置插入,可用于 - - 插入新的 schema 节点 - - 现有 schema 节点的拖拽移动 -- schema 参数修改 - -设计器核心 API 和参数有: - -- 设计器 API:`createDesignable()` & `useDesignable()` -- Schema 参数:`x-designer`,用于适配设计器组件 - -## 设计器 API - -### createDesignable() - -```ts -import { Schema } from '@nocobase/client'; - -const current = new Schema({ - type: 'void', - 'x-component': 'div', -}); - -const { - designable, // 是否可以配置 - remove, - insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd - insertBeforeBegin, // 在当前节点的前面插入 - insertAfterBegin, // 在当前节点的第一个子节点前面插入 - insertBeforeEnd, // 在当前节点的最后一个子节点后面 - insertAfterEnd, // 在当前节点的后面 -} = createDesignable({ - current, -}); - -const newSchema = { - type: 'void', - name: 'hello', - 'x-component': 'Hello', -}; - -insertAfterBegin(newSchema); - -console.log(current.toJSON()); -{ - type: 'void', - 'x-component': 'div', - properties: { - hello: { - type: 'void', - 'x-component': 'Hello', - }, - }, -} -``` - -### useDesignable() - -React Hook 场景也可以用 `useDesignable()` 获取当前 schema 组件设计器的 API - -```ts -const { - designable, // 是否可以配置 - remove, - insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd - insertBeforeBegin, // 在当前节点的前面插入 - insertAfterBegin, // 在当前节点的第一个子节点前面插入 - insertBeforeEnd, // 在当前节点的最后一个子节点后面 - insertAfterEnd, // 在当前节点的后面 -} = useDesignable(); - -const schema = { - name: uid(), - 'x-component': 'Hello', -}; - -// 在当前节点的前面插入 -insertBeforeBegin(schema); -// 等同于 -insertAdjacent('beforeBegin', schema); - -// 在当前节点的第一个子节点前面插入 -insertAfterBegin(schema); -// 等同于 -insertAdjacent('afterBegin', schema); - -// 在当前节点的最后一个子节点后面 -insertBeforeEnd(schema); -// 等同于 -insertAdjacent('beforeEnd', schema); - -// 在当前节点的后面 -insertAfterEnd(schema); -// 等同于 -insertAdjacent('afterEnd', schema); -``` - -## 邻近位置插入 - -与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `insertAdjacent()` 方法用于解决邻近位置的插入问题。 - -四个邻近位置 - -```ts -{ - properties: { - // beforeBegin 在当前节点的前面插入 - node1: { - properties: { - // afterBegin 在当前节点的第一个子节点前面插入 - // ... - // beforeEnd 在当前节点的最后一个子节点后面 - }, - }, - // afterEnd 在当前节点的后面 - }, -} -``` - -和 HTML 标签一样,Schema 组件库的组件也是可以相互组合,通过 insertAdjacent API 按实际需要插入在合理的邻近位置。 - -### 插入新的 schema 节点 - -在 Schema 组件里,可以直接通过 `useDesignable()` 在当前 Schema 的相邻位置插入新节点: - - -示例 - -```tsx -import React from 'react'; -import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client'; -import { observer, Schema, useFieldSchema } from '@formily/react'; -import { Button, Space } from 'antd'; -import { uid } from '@formily/shared'; - -const Hello = observer((props) => { - const { insertAdjacent } = useDesignable(); - const fieldSchema = useFieldSchema(); - return ( -
    -

    {fieldSchema.name}

    - - - - - - -
    {props.children}
    -
    - ); -}, { displayName: 'Hello' }); - -const Page = observer((props) => { - return
    {props.children}
    ; -}, { displayName: 'Page' }); - -export default () => { - return ( - - - - ); -} -``` - -### 现有 schema 节点的拖拽移动 - -insertAdjacent 等方法也可用于节点的拖拽移动 - -```tsx -import React from 'react'; -import { uid } from '@formily/shared'; -import { observer, useField, useFieldSchema } from '@formily/react'; -import { DndContext, DragEndEvent, useDraggable, useDroppable } from '@dnd-kit/core'; -import { SchemaComponent, SchemaComponentProvider, createDesignable, useDesignable } from '@nocobase/client'; - -const useDragEnd = () => { - const { refresh } = useDesignable(); - - return ({ active, over }: DragEndEvent) => { - const activeSchema = active?.data?.current?.schema; - const overSchema = over?.data?.current?.schema; - - if (!activeSchema || !overSchema) { - return; - } - - const dn = createDesignable({ - current: overSchema, - }); - - dn.on('insertAdjacent', refresh); - dn.insertBeforeBeginOrAfterEnd(activeSchema); - }; -}; - -const Page = observer((props) => { - return {props.children}; -}, { displayName: 'Page' }); - -function Draggable(props) { - const { attributes, listeners, setNodeRef, transform } = useDraggable({ - id: props.id, - data: props.data, - }); - const style = transform - ? { - transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, - } - : undefined; - - return ( - - ); -} - -function Droppable(props) { - const { isOver, setNodeRef } = useDroppable({ - id: props.id, - data: props.data, - }); - const style = { - color: isOver ? 'green' : undefined, - }; - - return ( -
    - {props.children} -
    - ); -} - -const Block = observer((props) => { - const field = useField(); - const fieldSchema = useFieldSchema(); - return ( - -
    - Block {fieldSchema.name}{' '} - - Drag - -
    -
    - ); -}, { displayName: 'Block' }); - -export default function App() { - return ( - - - - ); -} -``` - -## `x-designer` 的应用 - -`x-designer` 通常只在 BlockItem、CardItem、FormItem 等包装器组件中使用。 - -```ts -{ - type: 'object', - properties: { - title: { - type: 'string', - title: '标题', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - 'x-designer': 'FormItem.Designer', - }, - status: { - type: 'string', - title: '状态', - 'x-decorator': 'FormItem', - 'x-component': 'Select', - 'x-designer': 'FormItem.Designer', - }, - }, -} -``` - -说明:NocoBase 提供的 Schema 设计器是以工具栏形式直接嵌入于界面,当激活界面配置时(`designable = true`),`x-designer` 组件(设计器工具栏)会显示出来,就可以通过工具栏更新当前 schema 组件了,工具栏提供的设计能力包括: - -- 拖拽移动:DndContext + DragHandler -- 插入新节点:SchemaInitializer -- 参数配置:SchemaSettings diff --git a/docs/zh-CN/development/client/ui-schema-designer/designable.png b/docs/zh-CN/development/client/ui-schema-designer/designable.png deleted file mode 100644 index 6d428a6ac..000000000 Binary files a/docs/zh-CN/development/client/ui-schema-designer/designable.png and /dev/null differ diff --git a/docs/zh-CN/development/client/ui-schema-designer/extending-schema-components.md b/docs/zh-CN/development/client/ui-schema-designer/extending-schema-components.md deleted file mode 100644 index 89bf45c3e..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/extending-schema-components.md +++ /dev/null @@ -1,518 +0,0 @@ -# 扩展 Schema 组件 - -除了原生的 html 标签,开发也可以适配更多的自定义组件,用于丰富 Schema 组件库。 - -扩展组件时,常用的方法: - -- [connect](https://react.formilyjs.org/api/shared/connect) 无侵入接入第三方组件,一般用于适配字段组件,和 [mapProps](https://react.formilyjs.org/api/shared/map-props)[、mapReadPretty](https://react.formilyjs.org/api/shared/map-read-pretty) 搭配使用 -- [observer](https://react.formilyjs.org/api/shared/observer) 当组件内部使用了 observable 对象,而你希望组件响应 observable 对象的变化时 - -## 最简单的扩展 - -直接将现成的 React 组件注册进来。 - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const Hello = () =>

    Hello, world!

    ; - -const schema = { - type: 'void', - name: 'hello', - 'x-component': 'Hello', -}; - -export default () => { - return ( - - - - ); -}; -``` - -## 通过 connect 接入第三方组件 - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Input } from 'antd' -import { connect, mapProps, mapReadPretty } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const ReadPretty = (props) => { - return
    {props.value}
    -}; - -const SingleText = connect( - Input, - mapProps((props, field) => { - return { - ...props, - suffix: '后缀', - } - }), - mapReadPretty(ReadPretty), -); - -const schema = { - type: 'object', - properties: { - t1: { - type: 'string', - default: 'hello t1', - 'x-component': 'SingleText', - }, - t2: { - type: 'string', - default: 'hello t2', - 'x-component': 'SingleText', - 'x-pattern': 'readPretty', - }, - } -}; - -export default () => { - return ( - - - - ); -}; -``` - -## 使用 observer 响应数据 - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { Input } from 'antd'; -import { connect, observer, useForm } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const SingleText = connect(Input); - -const UsedObserver = observer((props) => { - const form = useForm(); - return
    UsedObserver: {form.values.t1}
    -}, { displayName: 'UsedObserver' }); - -const NotUsedObserver = (props) => { - const form = useForm(); - return
    NotUsedObserver: {form.values.t1}
    -}; - -const schema = { - type: 'object', - properties: { - t1: { - type: 'string', - 'x-component': 'SingleText', - }, - t2: { - type: 'string', - 'x-component': 'UsedObserver', - }, - t3: { - type: 'string', - 'x-component': 'NotUsedObserver', - }, - } -}; - -const components = { - SingleText, - UsedObserver, - NotUsedObserver -}; - -export default () => { - return ( - - - - ); -}; -``` - -## 嵌套的 Schema - -- `props.children` 嵌套,适用于 void 和 object 类型的 properties,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套) -- `` 自定义嵌套,所有类型都适用,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套) - -注意: - -- 除了 void 和 object 类型以外的 schema 的 `properties` 无法直接通过 `props.children` 渲染,但是可以使用 `` 解决嵌套问题 -- 仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用 - -```tsx | pure - -``` - -### void 和 object 类型 schema 的嵌套 - -直接通过 props.children 就可以适配 properties 节点了 - -```tsx -/** - * defaultShowCode: true - */ -import React from 'react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -// Hello 组件适配了 children,可以嵌套 properties 了 -const Hello = (props) =>

    Hello, {props.children}!

    ; -const World = () => world; - -const schema = { - type: 'object', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, -}; - -export default () => { - return ( - - - - ); -}; -``` - -各类型 properties 渲染结果对比 - -```tsx -import React from 'react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const Hello = (props) =>

    Hello, {props.children}!

    ; -const World = () => world; - -const schema = { - type: 'object', - properties: { - title1: { - type: 'void', - 'x-content': 'Void schema,渲染 properties', - }, - void: { - type: 'void', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'void', - 'x-component': 'World', - }, - }, - }, - title2: { - type: 'void', - 'x-content': 'Object schema,渲染 properties', - }, - object: { - type: 'object', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, - }, - title3: { - type: 'void', - 'x-content': 'Array schema,不渲染 properties', - }, - array: { - type: 'array', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, - }, - title4: { - type: 'void', - 'x-content': 'String schema,不渲染 properties', - }, - string: { - type: 'string', - name: 'hello', - 'x-component': 'Hello', - properties: { - world: { - type: 'string', - 'x-component': 'World', - }, - }, - }, - } -}; - -export default () => { - return ( - - - - ); -}; -``` - -### array 类型 schema 的嵌套 - -可以通过 `` 解决自定义嵌套问题 - -#### Array 元素是 string 或 number 时 - -```tsx -import React from 'react'; -import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const useValueSchema = () => { - const schema = useFieldSchema(); - return schema.reduceProperties((buf, s) => { - if (s['x-component'] === 'Value') { - return s; - } - return buf; - }); -}; - -const ArrayList = observer((props) => { - const field = useField(); - const schema = useValueSchema(); - return ( - <> - String Array -
      - {field.value?.map((item, index) => { - // 只有一个元素 - return - })} -
    - - ); -}, { displayName: 'ArrayList' }); - -const Value = connect((props) => { - return
  • value: {props.value}
  • -}); - -const schema = { - type: 'object', - properties: { - strArr: { - type: 'array', - default: [1, 2, 3], - 'x-component': 'ArrayList', - properties: { - value: { - type: 'number', - 'x-component': 'Value', - }, - } - }, - } -}; - -export default () => { - return ( - - - - ); -}; -``` - -#### Array 元素是 Object 时 - -```tsx -import React from 'react'; -import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; - -const ArrayList = observer((props) => { - const field = useField(); - const schema = useFieldSchema(); - // array 类型的 schema 无法 onlyRenderProperties,需要转化为 object 类型 - const objSchema = new Schema({ - type: 'object', - properties: schema.properties, - }); - return ( -
      - {field.value?.map((item, index) => { - // array 元素是 object - return ( - - ) - })} -
    - ); -}, { displayName: 'ArrayList' }); - -const Value = connect((props) => { - return
  • value: {props.value}
  • -}); - -const schema = { - type: 'object', - properties: { - objArr: { - type: 'array', - default: [ - { value: 't1' }, - { value: 't2' }, - { value: 't3' }, - ], - 'x-component': 'ArrayList', - properties: { - value: { - type: 'number', - 'x-component': 'Value', - }, - } - } - } -}; - -export default () => { - return ( - - - - ); -}; -``` - -#### Tree 结构数据 - -```tsx -import { ArrayField } from '@formily/core'; -import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; -import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; -import { Table, TableColumnType } from 'antd'; -import React from 'react'; - -const ArrayTable = observer((props: any) => { - const { rowKey } = props; - const field = useField(); - const schema = useFieldSchema(); - const columnSchemas = schema.reduceProperties((buf, s) => { - if (s['x-component'] === 'ArrayTable.Column') { - buf.push(s); - } - return buf; - }, []); - - const columns = columnSchemas.map((s) => { - return { - render: (value, record) => { - return ; - }, - } as TableColumnType; - }); - - return
    ; -}, { displayName: 'ArrayTable' }); - -const Value = connect((props) => { - return
  • value: {props.value}
  • ; -}); - -const schema: ISchema = { - type: 'object', - properties: { - objArr: { - type: 'array', - default: [ - { __path: '0', id: 1, value: 't1' }, - { - __path: '1', - id: 2, - value: 't2', - children: [ - { - __path: '1.children.0', - id: 5, - value: 't5', - parentId: 2, - }, - ], - }, - { - __path: '2', - id: 3, - value: 't3', - children: [ - { - __path: '2.children.0', - id: 4, - value: 't4', - parentId: 3, - children: [ - { - __path: '2.children.0.children.0', - id: 6, - value: 't6', - parentId: 4, - }, - { - __path: '2.children.0.children.1', - id: 7, - value: 't7', - parentId: 4, - }, - ], - }, - ], - }, - ], - 'x-component': 'ArrayTable', - 'x-component-props': { - rowKey: 'id', - }, - properties: { - c1: { - type: 'void', - 'x-component': 'ArrayTable.Column', - properties: { - value: { - type: 'string', - 'x-component': 'Value', - }, - }, - }, - }, - }, - }, -}; - -export default () => { - return ( - - - - ); -}; -``` diff --git a/docs/zh-CN/development/client/ui-schema-designer/index.md b/docs/zh-CN/development/client/ui-schema-designer/index.md deleted file mode 100644 index bd5b6fdf7..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Overview - - diff --git a/docs/zh-CN/development/client/ui-schema-designer/insert-adjacent.md b/docs/zh-CN/development/client/ui-schema-designer/insert-adjacent.md deleted file mode 100644 index f538666b8..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/insert-adjacent.md +++ /dev/null @@ -1,144 +0,0 @@ -# 邻近位置插入 - -与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `dn.insertAdjacent()` 方法用于解决邻近位置的插入问题。 - -## 邻近位置 - -```ts -{ - properties: { - // beforeBegin 在当前节点的前面插入 - node1: { - properties: { - // afterBegin 在当前节点的第一个子节点前面插入 - // ... - // beforeEnd 在当前节点的最后一个子节点后面 - }, - }, - // afterEnd 在当前节点的后面 - }, -} -``` - -## useDesignable() - -获取当前 schema 节点设计器的 API - -```ts -const { - designable, // 是否可以配置 - insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd - insertBeforeBegin, // 在当前节点的前面插入 - insertAfterBegin, // 在当前节点的第一个子节点前面插入 - insertBeforeEnd, // 在当前节点的最后一个子节点后面 - insertAfterEnd, // 在当前节点的后面 -} = useDesignable(); - -const schema = { - name: uid(), - 'x-component': 'Hello', -}; - -// 在当前节点的前面插入 -insertBeforeBegin(schema); -// 等同于 -insertAdjacent('beforeBegin', schema); - -// 在当前节点的第一个子节点前面插入 -insertAfterBegin(schema); -// 等同于 -insertAdjacent('afterBegin', schema); - -// 在当前节点的最后一个子节点后面 -insertBeforeEnd(schema); -// 等同于 -insertAdjacent('beforeEnd', schema); - -// 在当前节点的后面 -insertAfterEnd(schema); -// 等同于 -insertAdjacent('afterEnd', schema); -``` - -示例 - -```tsx -import React from 'react'; -import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client'; -import { observer, Schema, useFieldSchema } from '@formily/react'; -import { Button, Space } from 'antd'; -import { uid } from '@formily/shared'; - -const Hello = observer((props) => { - const { insertAdjacent } = useDesignable(); - const fieldSchema = useFieldSchema(); - return ( -
    -

    {fieldSchema.name}

    - - - - - - -
    {props.children}
    -
    - ); -}, { displayName: 'Hello' }); - -const Page = observer((props) => { - return
    {props.children}
    ; -}, { displayName: 'Page' }); - -export default () => { - return ( - - - - ); -} -``` diff --git a/docs/zh-CN/development/client/ui-schema-designer/what-is-ui-schema.md b/docs/zh-CN/development/client/ui-schema-designer/what-is-ui-schema.md deleted file mode 100644 index c941ca06c..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/what-is-ui-schema.md +++ /dev/null @@ -1,329 +0,0 @@ -# UI Schema 是什么? - -一种描述前端组件的协议,基于 Formily Schema 2.0,类 JSON Schema 风格。 - -```ts -interface ISchema { - type: 'void' | 'string' | 'number' | 'object' | 'array'; - name?: string; - title?: any; - // 包装器组件 - ['x-decorator']?: string; - // 包装器组件属性 - ['x-decorator-props']?: any; - // 组件 - ['x-component']?: string; - // 组件属性 - ['x-component-props']?: any; - // 展示状态,默认为 'visible' - ['x-display']?: 'none' | 'hidden' | 'visible'; - // 组件的子节点,简单使用 - ['x-content']?: any; - // children 节点 schema - properties?: Record; - - // 以下仅字段组件时使用 - - // 字段联动 - ['x-reactions']?: SchemaReactions; - // 字段 UI 交互模式,默认为 'editable' - ['x-pattern']?: 'editable' | 'disabled' | 'readPretty'; - // 字段校验 - ['x-validator']?: Validator; - // 默认数据 - default: ?:any; - - // 设计器相关 - - // 设计器组件(工具栏),包括:拖拽移动、插入新节点、修改参数、移除等 - ['x-designer']?: any; - // 初始化器组件(工具栏),决定当前 schema 内部可以插入什么 - ['x-initializer']?: any; -} -``` - -## 最简单的组件 - -所有的原生 html 标签都可以转为 schema 的写法。如: - -```ts -{ - type: 'void', - 'x-component': 'h1', - 'x-content': 'Hello, world!', -} -``` - -JSX 示例 - -```tsx | pure -

    Hello, world!

    -``` - -## children 组件可以写在 properties 里 - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - }, - }, -} -``` - -JSX 等同于 - -```tsx | pure -
    - -
    -``` - -## decorator 的巧妙用法 - -decorator + component 的组合,可以将两个组件放在一个 schema 节点里,降低 schema 结构复杂度,提高组件的复用率。 - -例如表单场景里,可以将 FormItem 组件与任意字段组件组合,在这里 FormItem 就是 Decorator。 - -```ts -{ - type: 'void', - ['x-component']: 'div', - properties: { - title: { - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, - content: { - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input.TextArea', - }, - }, -} -``` - -JSX 等同于 - -```tsx | pure -
    - - - - - - -
    -``` - -也可以提供一个 CardItem 组件,用于包裹所有区块,这样所有区块就都是 Card 包裹的了。 - -```ts -{ - type: 'void', - ['x-component']: 'div', - properties: { - table: { - type: 'array', - 'x-decorator': 'CardItem', - 'x-component': 'Table', - }, - kanban: { - type: 'array', - 'x-decorator': 'CardItem', - 'x-component': 'Kanban', - }, - }, -} -``` - -JSX 等同于 - -```tsx | pure -
    - -
    - - - - - -``` - -## 组件的展示状态 - -- `'x-display': 'visible'`:显示组件 -- `'x-display': 'hidden'`:隐藏组件,数据不隐藏 -- `'x-display': 'none'`:隐藏组件,数据也隐藏 - -### `'x-display': 'visible'` - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - 'x-display': 'visible' - }, - }, -} -``` - -JSX 等同于 - -```tsx | pure -
    - -
    -``` - -### `'x-display': 'hidden'` - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - 'x-display': 'hidden' - }, - }, -} -``` - -JSX 等同于 - -```tsx | pure -
    - {/* 此处不输出 input 组件,对应的 name=title 的字段模型还存在 */} -
    -``` - -### `'x-display': 'none'` - -```ts -{ - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - 'x-component': 'input', - 'x-display': 'none' - }, - }, -} -``` - -JSX 等同于 - -```tsx | pure -
    - {/* 此处不输出 input 组件,对应的 name=title 的字段模型也不存在了 */} -
    -``` - -## 组件的显示模式 - -用于字段组件,有三种显示模式: - -- `'x-pattern': 'editable'` 可编辑 -- `'x-pattern': 'disabled'` 不可编辑 -- `'x-pattern': 'readPretty'` 友好阅读 - -如单行文本 `` 组件,编辑和不可编辑模式为 ``,友好阅读模式为 `
    ` - -### `'x-pattern': 'editable'` - -```ts -const schema = { - name: 'test', - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - default: 'Hello', - 'x-component': 'SingleText', - 'x-pattern': 'editable' - }, - }, -}; -``` - -JSX 等同于 - -```tsx | pure -
    - -
    -``` - -### `'x-pattern': 'disabled'` - -```ts -const schema = { - name: 'test', - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - default: 'Hello', - 'x-component': 'SingleText', - 'x-pattern': 'disabled' - }, - }, -}; -``` - -JSX 等同于 - -```tsx | pure -
    - -
    -``` - -### `'x-pattern': 'readPretty'` - -```ts -const schema = { - name: 'test', - type: 'void', - 'x-component': 'div', - 'x-component-props': { className: 'form-item' }, - properties: { - title: { - type: 'string', - default: 'Hello', - 'x-component': 'SingleText', - 'x-pattern': 'readPretty', - }, - }, -}; -``` - -JSX 等同于 - -```tsx | pure -
    -
    Hello
    -
    -``` diff --git a/docs/zh-CN/development/client/ui-schema-designer/x-designer.md b/docs/zh-CN/development/client/ui-schema-designer/x-designer.md deleted file mode 100644 index 276fe609c..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/x-designer.md +++ /dev/null @@ -1,61 +0,0 @@ -# x-designer 组件 - -## 内置 x-designer 组件 - -- Action.Designer -- Calendar.Designer -- Filter.Action.Designer -- Form.Designer -- FormItem.Designer -- FormV2.Designer -- FormV2.ReadPrettyDesigner -- DetailsDesigner -- G2Plot.Designer -- Kanban.Designer -- Kanban.Card.Designer -- Markdown.Void.Designer -- Menu.Designer -- TableV2.Column.Designer -- TableV2.ActionColumnDesigner -- TableBlockDesigner -- TableSelectorDesigner -- Tabs.Designer - -## 替换 - -```tsx | pure -import React, { useContext } from 'react'; -import { useFieldSchema } from '@formily/react'; -import { - SchemaComponentOptions, - GeneralSchemaDesigner, - SchemaSettings, - useCollection -} from '@nocobase/client'; -import React from 'react'; - -const CustomActionDesigner = () => { - const { name, title } = useCollection(); - const fieldSchema = useFieldSchema(); - return ( - - - - ); -}; - -export default React.memo((props) => { - return ( - {props.children} - ); -}); -``` diff --git a/docs/zh-CN/development/client/ui-schema-designer/x-initializer.md b/docs/zh-CN/development/client/ui-schema-designer/x-initializer.md deleted file mode 100644 index 1a16683c6..000000000 --- a/docs/zh-CN/development/client/ui-schema-designer/x-initializer.md +++ /dev/null @@ -1,35 +0,0 @@ -# x-initializer 组件 - -## 内置 x-initializer 组件 - -- BlockInitializers -- CalendarActionInitializers -- CreateFormBlockInitializers -- CustomFormItemInitializers -- DetailsActionInitializers -- FormActionInitializers -- FormItemInitializers -- KanbanActionInitializers -- ReadPrettyFormActionInitializers -- ReadPrettyFormItemInitializers -- RecordBlockInitializers -- RecordFormBlockInitializers -- SubTableActionInitializers -- TableActionColumnInitializers -- TableActionInitializers -- TableColumnInitializers -- TableSelectorInitializers -- TabPaneInitializers - -## 替换 - -```tsx | pure -import React, { useContext } from 'react'; -import { Plugin } from '@nocobase/client'; - -class MyPlugin extends Plugin { - async load() { - this.app.schemaInitializerManager.add('BlockInitializers', BlockInitializers) - } -} -``` diff --git a/docs/zh-CN/development/deps.md b/docs/zh-CN/development/deps.md deleted file mode 100644 index 607844c16..000000000 --- a/docs/zh-CN/development/deps.md +++ /dev/null @@ -1,107 +0,0 @@ -# 依赖管理 - -插件的依赖分为自身的依赖和全局依赖,全局依赖由 `@nocobase/server` 和 `@nocobase/client` 提供,不会打包到插件产物中,自身的依赖会被打包到产物中。 - -因为自身的依赖会被打包到产物中(包括 server 依赖的 npm 包,也会被打包到 `dist/node_modules`),所以在开发插件时,将所有依赖放到 `devDependencies` 中即可。 - - -当插件安装如下依赖时,要注意 **版本** 和 `@nocobase/server` 和 `@nocobase/client` 的保持一致。 - - -### 全局依赖 - -```js -// nocobase -'@nocobase/acl', -'@nocobase/actions', -'@nocobase/auth', -'@nocobase/cache', -'@nocobase/client', -'@nocobase/database', -'@nocobase/evaluators', -'@nocobase/logger', -'@nocobase/resourcer', -'@nocobase/sdk', -'@nocobase/server', -'@nocobase/test', -'@nocobase/utils', - -// @nocobase/auth -'jsonwebtoken', - -// @nocobase/cache -'cache-manager', -'cache-manager-fs-hash', - -// @nocobase/database -'sequelize', -'umzug', -'async-mutex', - -// @nocobase/evaluators -'@formulajs/formulajs', -'mathjs', - -// @nocobase/logger -'winston', -'winston-daily-rotate-file', - -// koa -'koa', -'@koa/cors', -'@koa/router', -'multer', -'@koa/multer', -'koa-bodyparser', -'koa-static', -'koa-send', - -// react -'react', -'react-dom', -'react/jsx-runtime', - -// react-router -'react-router', -'react-router-dom', - -// antd -'antd', -'antd-style', -'@ant-design/icons', -'@ant-design/cssinjs', - -// i18next -'i18next', -'react-i18next', - -// dnd-kit -'@dnd-kit/accessibility', -'@dnd-kit/core', -'@dnd-kit/modifiers', -'@dnd-kit/sortable', -'@dnd-kit/utilities', - -// formily -'@formily/antd-v5', -'@formily/core', -'@formily/react', -'@formily/json-schema', -'@formily/path', -'@formily/validator', -'@formily/shared', -'@formily/reactive', -'@formily/reactive-react', - -// utils -'dayjs', -'mysql2', -'pg', -'pg-hstore', -'sqlite3', -'supertest', -'axios', -'@emotion/css', -'ahooks', -'lodash' -``` diff --git a/docs/zh-CN/development/http-api/action-api.md b/docs/zh-CN/development/http-api/action-api.md deleted file mode 100644 index 26fd5e1b8..000000000 --- a/docs/zh-CN/development/http-api/action-api.md +++ /dev/null @@ -1,139 +0,0 @@ -# Action API - -## Common - ---- - -Collection 和 Association 资源通用。 - -### `create` - -```bash -POST /api/users:create?whitelist=a,b&blacklist=c,d - -{} # Request Body -``` - -- Parameters - - whitelist 白名单 - - blacklist 黑名单 -- Request body: 待插入的 JSON 数据 -- Response body data: 已创建的数据 JSON - -#### 新增用户 - -```bash -POST /api/users:create - -Request Body -{ - "email": "demo@nocobase.com", - "name": "Admin" -} - -Response 200 (application/json) -{ - "data": {}, -} -``` - -#### 新增用户文章 - -```bash -POST /api/users/1/posts:create - -Request Body -{ - "title": "My first post" -} - -Response 200 (application/json) -{ - "data": {} -} -``` - -#### Request Body 里的 association - -```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 白名单 - - blacklist 黑名单 - - filterByTk 根据 tk 字段过滤,默认情况 tk 为数据表的主键 - - filter 过滤,支持 json string -- Request body: 待更新的 JSON 数据 - -#### Request Body 里的 association - -```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/zh-CN/development/http-api/filter-operators.md b/docs/zh-CN/development/http-api/filter-operators.md deleted file mode 100644 index 332f28a50..000000000 --- a/docs/zh-CN/development/http-api/filter-operators.md +++ /dev/null @@ -1,59 +0,0 @@ -# Filter operators - -## 通用 - -- $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 - -- $isTruly -- $isFalsy - -## date - -- $dateOn -- $dateNotOn -- $dateBefore -- $dateNotBefore -- $dateAfter -- $dateNotAfter - -## string - -- $includes -- $notIncludes -- $startsWith -- $notStartsWith -- $endWith -- $notEndWith diff --git a/docs/zh-CN/development/http-api/index.md b/docs/zh-CN/development/http-api/index.md deleted file mode 100644 index a3117cf49..000000000 --- a/docs/zh-CN/development/http-api/index.md +++ /dev/null @@ -1,301 +0,0 @@ -# 概述 - -NocoBase 的 HTTP API 基于 Resource & Action 设计,是 REST API 的超集,操作不局限于增删改查,在 NocoBase 里,Resource Action 可以任意的扩展。 - -## 资源 Resource - -在 NocoBase 里,资源(resource)有两种表达方式: - -- `` -- `.` - - - -- collection 是所有抽象数据的集合 -- association 为 collection 的关联数据 -- resource 包括 collection 和 collection.association 两类 - - - -### 示例 - -- `posts` 文章 -- `posts.user` 文章用户 -- `posts.tags` 文章标签 - -## 操作 Action - -以 `:` 的方式表示资源操作 - -- `:` -- `.:` - -内置的全局操作,可用于 collection 或 association - -- `create` -- `get` -- `list` -- `update` -- `destroy` -- `move` - -内置的关联操作,仅用于 association - -- `set` -- `add` -- `remove` -- `toggle` - -### 示例 - -- `posts:create` 创建文章 -- `posts.user:get` 查看文章用户 -- `posts.tags:add` 附加文章标签(将现有的标签与文章关联) - -## 请求 URL - -```bash - /api/: - /api/:/ - /api///: - /api///:/ -``` - -### 示例 - -posts 资源 - -```bash -POST /api/posts:create -GET /api/posts:list -GET /api/posts:get/1 -POST /api/posts:update/1 -POST /api/posts:destroy/1 -``` - -posts.comments 资源 - -```bash -POST /api/posts/1/comments:create -GET /api/posts/1/comments:list -GET /api/posts/1/comments:get/1 -POST /api/posts/1/comments:update/1 -POST /api/posts/1/comments:destroy/1 -``` - -posts.tags 资源 - -```bash -POST /api/posts/1/tags:create -GET /api/posts/1/tags:get -GET /api/posts/1/tags:list -POST /api/posts/1/tags:update -POST /api/posts/1/tags:destroy -POST /api/posts/1/tags:add -GET /api/posts/1/tags:remove -``` - -## 资源定位 - -- collection 资源,通过 `collectionIndex` 定位到待处理的数据,`collectionIndex` 必须唯一 -- association 资源,通过 `collectionIndex` 和 `associationIndex` 联合定位待处理的数据,`associationIndex` 可能不是唯一的,但是 `collectionIndex` 和 `associationIndex` 的联合索引必须唯一 - -查看 association 资源详情时,请求的 URL 需要同时提供 `` 和 ``,`` 并不多余,因为 `` 可能不是唯一的。 - -例如 `tables.fields` 表示数据表的字段 - -```bash -GET /api/tables/table1/fields/title -GET /api/tables/table2/fields/title -``` - -table1 和 table2 都有 title 字段,title 在 table1 里是唯一的,但是其他表也可能有 title 字段 - -## 请求参数 - -请求的参数可以放在 Request 的 headers、parameters(query string)、body(GET 请求没有 body) 里。 - -几个特殊的 Parameters 请求参数 - -- `filter` 数据过滤,用于查询相关操作里; -- `filterByTk` 根据 tk 字段字过滤,用于指定详情数据的操作里; -- `sort` 排序,用于查询相关操作里。 -- `fields` 输出哪些数据,用于查询相关操作里; -- `appends` 附加关系字段,用于查询相关操作里; -- `except` 排除哪些字段(不输出),用于查询相关操作里; -- `whitelist` 字段白名单,用于数据的创建和更新相关操作里; -- `blacklist` 字段黑名单,用于数据的创建和更新相关操作里; - -### filter - -数据过滤 - -```bash -# simple -GET /api/posts?filter[status]=publish -# 推荐使用 json string 的格式,需要 encodeURIComponent 编码 -GET /api/posts?filter={"status":"published"} - -# filter operators -GET /api/posts?filter[status.$eq]=publish -GET /api/posts?filter={"status.$eq":"published"} - -# $and -GET /api/posts?filter={"$and": [{"status.$eq":"published"}, {"title.$includes":"a"}]} -# $or -GET /api/posts?filter={"$or": [{"status.$eq":"pending"}, {"status.$eq":"draft"}]} - -# association field -GET /api/posts?filter[user.email.$includes]=gmail -GET /api/posts?filter={"user.email.$includes":"gmail"} -``` - -[点此查看更多关于 filter operators 的内容](http-api/filter-operators) - -### filterByTk - -根据 tk 字段过滤,默认情况: - -- collection 资源,tk 为数据表的主键; -- association 资源,tk 为 association 的 targetKey 字段。 - -```bash -GET /api/posts:get?filterByTk=1&fields=name,title&appends=tags -``` - -### sort - -排序。降序时,字段前面加上减号 `-`。 - -```bash -# createAt 字段升序 -GET /api/posts:get?sort=createdAt -# createAt 字段降序 -GET /api/posts:get?sort=-createdAt -# 多个字段联合排序,createAt 字段降序、title A-Z 升序 -GET /api/posts:get?sort=-createdAt,title -``` - -### fields - -输出哪些数据 - -```bash -GET /api/posts:list?fields=name,title - -Response 200 (application/json) -{ - "data": [ - { - "name": "", - "title": "" - } - ], - "meta": {} -} -``` - -### appends - -附加关系字段 - -### except - -排除哪些字段(不输出),用于查询相关操作里; - -### whitelist - -白名单 - -```bash -POST /api/posts:create?whitelist=title - -{ - "title": "My first post", - "date": "2022-05-19" # date 字段会被过滤掉,不会写入数据库 -} -``` - -### blacklist - -黑名单 - -```bash -POST /api/posts:create?blacklist=date - -{ - "title": "My first post", - "date": "2022-05-19" # date 字段会被过滤掉,不会写入数据库 -} -``` - -## 请求响应 - -响应的格式 - -```ts -type ResponseResult = { - data?: any; // 主体数据 - meta?: any; // 附加数据 - errors?: ResponseError[]; // 报错 -}; - -type ResponseError = { - code?: string; - message: string; -}; -``` - -### 示例 - -查看列表 - -```bash -GET /api/posts:list - -Response 200 (application/json) - -{ - data: [ - { - id: 1 - } - ], - meta: { - count: 1 - page: 1, - pageSize: 1, - totalPage: 1 - }, -} -``` - -查看详情 - -```bash -GET /api/posts:get/1 - -Response 200 (application/json) - -{ - data: { - id: 1 - }, -} -``` - -报错 - -```bash -POST /api/posts:create - -Response 400 (application/json) - -{ - errors: [ - { - message: 'name must be required', - }, - ], -} -``` \ No newline at end of file diff --git a/docs/zh-CN/development/http-api/rest-api.md b/docs/zh-CN/development/http-api/rest-api.md deleted file mode 100644 index 2d7fd86d5..000000000 --- a/docs/zh-CN/development/http-api/rest-api.md +++ /dev/null @@ -1,184 +0,0 @@ -# REST API - -NocoBase 的 HTTP API 是 REST API 的超集,标准的 CRUD API 也支持 RESTful 风格。 - -## Collection 资源 - ---- - -### 创建 collection - -HTTP API - -```bash -POST /api/:create - -{} # JSON body -``` - -REST API - -```bash -POST /api/ - -{} # JSON body -``` - -### 查看 collection 列表 - -HTTP API - -```bash -GET /api/:list -``` - -REST API - -```bash -GET /api/ -``` - -### 查看 collection 详情 - -HTTP API - -```bash -GET /api/:get?filterByTk= -GET /api/:get/ -``` - -REST API - -```bash -GET /api// -``` - -### 更新 collection - -HTTP API - -```bash -POST /api/:update?filterByTk= - -{} # JSON body - -# 或者 -POST /api/:update/ - -{} # JSON body -``` - -REST API - -```bash -PUT /api// - -{} # JSON body -``` - -### 删除 collection - -HTTP API - -```bash -POST /api/:destroy?filterByTk= -# 或者 -POST /api/:destroy/ -``` - -REST API - -```bash -DELETE /api// -``` - -## Association 资源 - ---- - -### 创建 Association - -HTTP API - -```bash -POST /api///:create - -{} # JSON body -``` - -REST API - -```bash -POST /api/// - -{} # JSON body -``` - -### 查看 Association 列表 - -HTTP API - -```bash -GET /api///:list -``` - -REST API - -```bash -GET /api/// -``` - -### 查看 Association 详情 - -HTTP API - -```bash -GET /api///:get?filterByTk= -# 或者 -GET /api///:get/ -``` - -REST API - -```bash -GET /api///:get/ -``` - -### 更新 Association - -HTTP API - -```bash -POST /api///:update?filterByTk= - -{} # JSON body - -# 或者 -POST /api///:update/ - -{} # JSON body -``` - -REST API - -```bash -PUT /api///:update/ - -{} # JSON 数据 -``` - -### 删除 Association - -HTTP API - -```bash -POST /api///:destroy?filterByTk= -# 或者 -POST /api///:destroy/ -``` - -REST API - -```bash -DELETE /api//// -``` diff --git a/docs/zh-CN/development/index.md b/docs/zh-CN/development/index.md deleted file mode 100644 index 72b0ba32e..000000000 --- a/docs/zh-CN/development/index.md +++ /dev/null @@ -1,62 +0,0 @@ -# 介绍 - -NocoBase 采用微内核架构,各类功能以插件形式扩展。前后端分离,提供了各种插件化接口。插件按功能模块划分,具有可插拔的特点。 - - - -插件化的设计降低了模块之间的耦合度,提高了复用率。随着插件库的不断扩充,常见的场景只需要组合插件即可完成基础搭建。例如 NocoBase 的无代码平台,就是由各种插件组合起来。 - - - -## 插件管理器 - -NocoBase 提供了强大的插件管理器用于管理插件,插件管理器的流程如下: - - - -无代码用户可以通过界面管理本地插件的激活和禁用: - - - -开发者也可以通过 CLI 的方式管理完整的插件流程: - -```bash -# 创建插件 -yarn pm create hello -# 注册插件 -yarn pm add hello -# 激活插件 -yarn pm enable hello -# 禁用插件 -yarn pm disable hello -# 删除插件 -yarn pm remove hello -``` - -更多插件示例,查看 [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples)。 - -## 扩展能力 - -无论是通用性的功能,还是个性化定制,都建议以插件的形式编写,NocoBase 的扩展性体现在方方面面: - -- 可以是用户直观可见的界面相关的页面模块、区块类型、操作类型、字段类型等 -- 也可以是用于增强或限制 HTTP API 的过滤器、校验器、访问限制等 -- 也可以是更底层的数据表、迁移、事件、命令行等功能的增强 - - -各模块分布: - -- Server - - Collections & Fields:主要用于系统表配置,业务表建议在「配置中心 - 数据表配置」里配置 - - Resources & Actions:主要用于扩展 Action API - - Middleware:中间件 - - Events:事件 - - I18n:服务端国际化 - - Commands:自定义命令行 - - Migrations:迁移脚本 -- Client - - UI Schema Designer:页面设计器 - - UI Router:有自定义页面需求时 - - Plugin Settings Manager:为插件提供配置页面 - - I18n:客户端国际化 - diff --git a/docs/zh-CN/development/index/app-flow.svg b/docs/zh-CN/development/index/app-flow.svg deleted file mode 100644 index e167cda39..000000000 --- a/docs/zh-CN/development/index/app-flow.svg +++ /dev/null @@ -1 +0,0 @@ -
    beforeLoad
    loop: plugin.beforeLoad()
    load/reload
    loop: plugin.load()
    afterLoad
    Reinstall?
    beforeInstall
    install
    afterInstall
    beforeUpgrade
    upgrade
    afterUpgrade
    Restart?
    beforeStart
    start
    afterStart
    beforeStop
    stop
    afterStop
    beforeDestroy
    destroy
    afterDestroy
    \ No newline at end of file diff --git a/docs/zh-CN/development/index/pm-built-in.jpg b/docs/zh-CN/development/index/pm-built-in.jpg deleted file mode 100644 index 2fe60fdac..000000000 Binary files a/docs/zh-CN/development/index/pm-built-in.jpg and /dev/null differ diff --git a/docs/zh-CN/development/index/pm-flow.svg b/docs/zh-CN/development/index/pm-flow.svg deleted file mode 100644 index 23a738cfc..000000000 --- a/docs/zh-CN/development/index/pm-flow.svg +++ /dev/null @@ -1 +0,0 @@ -
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    plugin.afterAdd()
    app/client plugins
    pm.enable
    plugin.install()
    plugin.afterEnable()
    pm.disable
    plugin.afterDisable()
    pm.remove
    plugin.remove()
    pm.upgrade
    \ No newline at end of file diff --git a/docs/zh-CN/development/index/pm-ui.jpg b/docs/zh-CN/development/index/pm-ui.jpg deleted file mode 100644 index 4c8fdd3c1..000000000 Binary files a/docs/zh-CN/development/index/pm-ui.jpg and /dev/null differ diff --git a/docs/zh-CN/development/learning-guide.md b/docs/zh-CN/development/learning-guide.md deleted file mode 100644 index 115402259..000000000 --- a/docs/zh-CN/development/learning-guide.md +++ /dev/null @@ -1,125 +0,0 @@ -# 学习路线指南 - -## 1. 从安装运行 NocoBase 开始 - -**相关文档:快速开始** - -主要命令包括: - -下载 - -```bash -yarn create/git clone -yarn install -``` - -安装 - -```bash -yarn nocobase install -``` - -运行 - -```bash -# for development -yarn dev - -# for production -yarn build -yarn start -``` - -## 2. 了解 NocoBase 平台提供的核心功能 - -**相关文档:使用手册** - -主要的三部分包括: - -- UI 设计器:主要包括区块、字段和操作 -- 插件管理器:功能需求扩展 -- 配置中心:已激活插件提供的配置功能 - -## 3. 进一步了解插件管理器的使用 - -**相关文档:插件开发** - -NocoBase 提供了简易的插件管理器界面,但是在界面上只能处理本地插件的 enable、disable 和 remove,完整的操作需要通过 CLI - -```bash -# 创建插件 -yarn pm create hello -# 注册插件 -yarn pm add hello -# 激活插件 -yarn pm enable hello -# 禁用插件 -yarn pm disable hello -# 删除插件 -yarn pm remove hello -``` - -更多插件示例,查看 packages/samples,通过 samples 插件能够了解插件的基本用法,就可以进一步开发插件了。 - -## 4. 开发新插件,了解模块分布 - -**相关文档:扩展指南** - -[编写第一个插件](/development/your-fisrt-plugin) 章节,虽然简单的讲述了插件的主要开发流程,但是为了更快速的介入插件细节,你可能需要进一步了解 NocoBase 框架的模块分布: - -- Server - - Collections & Fields:主要用于系统表配置,业务表建议在「配置中心 - 数据表配置」里配置 - - Resources & Actions:主要用于扩展 Action API - - Middleware:中间件 - - Events:事件 - - I18n:服务端国际化 -- Client - - UI Schema Designer:页面设计器 - - UI Router:有自定义页面需求时 - - Plugin Settings Manager:为插件提供配置页面 - - I18n:客户端国际化 -- Devtools - - Commands:自定义命令行 - - Migrations:迁移脚本 - -## 5. 查阅各模块主要 API - -**相关文档:API 参考** - -查看各模块的 packages/samples,进一步了解模块主要 API 的用法 - -- Server - - Collections & Fields - - db.collection - - db.import - - Resources & Actions - - app.resourcer.define - - app.resourcer.registerActions - - Middleware - - app.use - - app.acl.use - - app.resourcer.use - - Events - - app.on - - app.db.on - - I18n - - app.i18n - - ctx.i18n -- Client - - UI Schema Designer - - SchemaComponent - - SchemaInitializer - - SchemaSettings - - UI Router - - RouteSwitchProvider - - RouteSwitch - - I18n - - app.i18n - - useTranslation -- Devtools - - Commands - - app.command - - app.findCommand - - Migrations - - app.db.addMigration - - app.db.addMigrations diff --git a/docs/zh-CN/development/life-cycle.md b/docs/zh-CN/development/life-cycle.md deleted file mode 100644 index d793f615e..000000000 --- a/docs/zh-CN/development/life-cycle.md +++ /dev/null @@ -1,41 +0,0 @@ -# 生命周期 - -## 应用的生命周期 - - - -## 插件的生命周期 - - - -## 插件的生命周期方法 - -```ts -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class MyPlugin extends Plugin { - afterAdd() { - // 插件 pm.add 注册进来之后。主要用于放置 app.beforeLoad 的事件。 - } - beforeLoad() { - // 所有插件执行 load 之前。一般用于注册类和事件监听 - } - async load() { - // 加载配置 - } - async install(options?: InstallOptions) { - // 安装逻辑 - } - async afterEnable() { - // 激活之后 - } - async afterDisable() { - // 禁用之后 - } - async remove() { - // 删除逻辑 - } -} - -export default MyPlugin; -``` diff --git a/docs/zh-CN/development/others/build.md b/docs/zh-CN/development/others/build.md deleted file mode 100644 index 1294b9a65..000000000 --- a/docs/zh-CN/development/others/build.md +++ /dev/null @@ -1 +0,0 @@ -# Building \ No newline at end of file diff --git a/docs/zh-CN/development/others/testing.md b/docs/zh-CN/development/others/testing.md deleted file mode 100644 index 1f37805b5..000000000 --- a/docs/zh-CN/development/others/testing.md +++ /dev/null @@ -1,163 +0,0 @@ -# 单元测试 - -## 介绍 - -NocoBase 的测试基于 [Jest](https://jestjs.io/) 测试框架。同时,为了方便的编写测试,我们提供了两个工具类,在测试环境模拟正常的数据库和应用的服务端。 - -### MockDatabase - -模拟数据库类继承自 [`Database`](/api/database) 类,大部分内容没有区别,主要在构造函数默认内置了随机表前缀,在每个测试用例初始化数据库时相关数据表都通过前缀名称与其他用例进行隔离,在运行测试用例时互不影响。 - -```ts -import { MockDatabase } from '@nocobase/test'; - -describe('my suite', () => { - let db; - - beforeEach(async () => { - db = new MockDatabase(); - - db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] - }); - - await db.sync(); - }); - - test('my case', async () => { - const postRepository = db.getRepository('posts'); - const p1 = await postRepository.create({ - values: { - title: 'hello' - } - }); - - expect(p1.get('title')).toEqual('hello'); - }); -}); -``` - -### MockServer - -模拟服务器也继承自 [Application](/api/server/application) 类,除了内置的数据库实例是通过模拟数据库类生成的以外,还提供了比较方便的生成基于 [superagent](https://www.npmjs.com/package/superagent) 请求代理功能,针对从发送请求到获取响应的写法也集成了 `.resource('posts').create()`,比较简化。 - -```ts -import { mockServer } from '@nocobase/test'; - -describe('my suite', () => { - let app; - let agent; - let db; - - beforeEach(async () => { - app = mockServer(); - agent = app.agent(); - - db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] - }); - - await db.sync(); - await app.load(); - }); - - test('my case', async () => { - const { body } = await agent.resource('posts').create({ - values: { - title: 'hello' - } - }); - - expect(body.data.title).toEqual('hello'); - }); -}); -``` - -## 示例 - -我们以之前在 [资源与操作](development/guide/resources-actions) 章节的功能为例,来写一个插件的测试: - -```ts -import { mockServer } from '@nocobase/test'; -import Plugin from '../../src/server'; - -describe('shop actions', () => { - let app; - let agent; - let db; - - beforeEach(async () => { - app = mockServer(); - app.plugin(Plugin); - agent = app.agent(); - db = app.db; - - await app.load(); - await db.sync(); - }); - - afterEach(async () => { - await app.destroy(); - }); - - test('product order case', async () => { - const { body: product } = await agent.resource('products').create({ - values: { - title: 'iPhone 14 Pro', - price: 7999, - enabled: true, - inventory: 1 - } - }); - expect(product.data.price).toEqual(7999); - - const { body: order } = await agent.resource('orders').create({ - values: { - productId: product.data.id - } - }); - expect(order.data.totalPrice).toEqual(7999); - expect(order.data.status).toEqual(0); - - const { body: deliveredOrder } = await agent.resource('orders').deliver({ - filterByTk: order.data.id, - values: { - provider: 'SF', - trackingNumber: '123456789' - } - }); - expect(deliveredOrder.data.status).toBe(2); - expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789'); - }); -}); -``` - -编写完成后,在命令行中允许测试命令: - -```bash -yarn test packages/samples/shop-actions -``` - -该测试将验证: - -1. 商品可以创建成功; -2. 订单可以创建成功; -3. 订单可以发货成功; - -当然这只是个最基本的例子,从业务上来说并不完善,但作为示例已经可以说明整个测试的流程。 - -## 小结 - -本章涉及的示例代码整合在对应的包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。 diff --git a/docs/zh-CN/development/plugin-ds.md b/docs/zh-CN/development/plugin-ds.md deleted file mode 100644 index a91e82fc7..000000000 --- a/docs/zh-CN/development/plugin-ds.md +++ /dev/null @@ -1,45 +0,0 @@ -# 插件目录结构 - -可以通过 `yarn pm create my-plugin` 快速创建一个空插件,目录结构如下: - -```bash -|- /my-plugin - |- /src - |- /client # 插件客户端代码 - |- /server # 插件服务端代码 - |- client.d.ts - |- client.js - |- package.json # 插件包信息 - |- server.d.ts - |- server.js - |- build.config.ts # 或者 `build.config.js` ,用于修改打包配置,实现自定义逻辑 -``` - -`/src/server` 的教程参考 [服务端](./server) 章节,`/src/client` 的教程参考 [客户端](./client) 章节。 - -如果你想要自定义打包配置,可以在根目录下创建 `config.js` 文件,内容如下: - -```js -import { defineConfig } from '@nocobase/build'; - -export default defineConfig({ - modifyViteConfig: (config) => { - // vite 是用来打包 `src/client` 端代码的 - - // 修改 Vite 配置,具体可参考:https://vitejs.dev/guide/ - return config - }, - modifyTsupConfig: (config) => { - // tsup 是用来打包 `src/server` 端代码的 - - // 修改 tsup 配置,具体可参考:https://tsup.egoist.dev/#using-custom-configuration - return config - }, - beforeBuild: (log) => { - // 构建开始前的回调函数,可以在构建开始前做一些操作 - }, - afterBuild: (log: PkgLog) => { - // 构建完成后的回调函数,可以在构建完成后做一些操作 - }; -}); -``` diff --git a/docs/zh-CN/development/server/collections-fields.md b/docs/zh-CN/development/server/collections-fields.md deleted file mode 100644 index 5d237566e..000000000 --- a/docs/zh-CN/development/server/collections-fields.md +++ /dev/null @@ -1,319 +0,0 @@ -# 数据表与字段 - -## 基础概念 - -数据建模是一个应用最底层的基础,在 NocoBase 应用中我们通过数据表(Collection)和字段(Field)来进行数据建模,并且建模也将映射到数据库表以持久化。 - -### Collection - -Collection 是所有同类数据的集合,在 NocoBase 中对应数据库表的概念,如订单、商品、用户、评论等都可以形成 Collection 定义。不同 Collection 通过 name 区分,包含的字段由 `fields` 定义,如: - -```ts -db.collection({ - name: 'posts', - fields: [ - { name: 'title', type: 'string' }, - { name: 'content', type: 'text' }, - // ... - ] -}); -``` - -定义完成后 collection 暂时只处于内存中,还需要调用 [`db.sync()`](/api/database#sync) 方法将其同步到数据库中。 - -### Field - -对应数据库表“字段”的概念,每个数据表(Collection)都可以有若干 Fields,例如: - -```ts -db.collection({ - name: 'users', - fields: [ - { type: 'string', name: 'name' }, - { type: 'integer', name: 'age' }, - // 其他字段 - ], -}); -``` - -其中字段名称(`name`)和字段类型(`type`)是必填项,不同字段通过字段名(`name`)区分,除 `name` 与 `type` 以外,根据不同字段类型可以有更多的配置信息。所有数据库字段类型及配置详见 API 参考的[内置字段类型列表](/api/database/field#内置字段类型列表)部分。 - -## 示例 - -对于开发者,通常我们会建立与普通数据表不同的一些功能型数据表,并把这些数据表固化成插件的一部分,并结合其他数据处理流程以形成完整的功能。 - -接下来我们以一个简单的在线商店插件为例来介绍如何建模并管理插件的数据表。假设你已经学习过 [编写第一个插件](/development/your-first-plugin),我们继续在之前的插件代码上开发,只不过插件的名称从 `hello` 改为 `shop-modeling`。 - -### 插件中定义并创建数据表 - -对于一个店铺,首先需要建立一张商品的数据表,命名为 `products`。与直接调用 [`db.collection()`](/api/database#collection) 这样的方法稍有差异,在插件中我们会使用更方便的方法一次性导入多个文件定义的数据表。所以我们先为商品数据表的定义创建一个文件命名为 `collections/products.ts`,填入以下内容: - -```ts -export default { - name: 'products', - fields: [ - { - type: 'string', - name: 'title' - }, - { - type: 'integer', - name: 'price' - }, - { - type: 'boolean', - name: 'enabled' - }, - { - type: 'integer', - name: 'inventory' - } - ] -}; -``` - -可以看到,NocoBase 的数据表结构定义可以直接使用标准的 JSON 格式,其中 `name` 和 `fields` 都是必填项,代表数据表名和该表中的字段定义。字段定义中与 Sequelize 类似会默认创建主键(`id`)、数据创建时间(`createdAt`)和数据更新时间(`updatedAt`)等系统字段,如有特殊需要可以以同名的配置覆盖定义。 - -该文件定义的数据表我们可以在插件主类的 `load()` 周期中使用 `db.import()` 引入并完成定义。如下所示: - -```ts -import path from 'path'; -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - await this.db.import({ - directory: path.resolve(__dirname, 'collections'), - }); - - this.app.acl.allow('products', '*'); - this.app.acl.allow('categories', '*'); - this.app.acl.allow('orders', '*'); - } -} -``` - -同时我们为了方便测试,先暂时允许针对这几张表里的数据资源的所有访问权限,后面我们会在 [权限管理](/development/guide/acl) 中详细介绍如何管理资源的权限。 - -这样在插件被主应用加载时,我们定义的 `products` 表也就被加载到数据库管理实例的内存中了。同时,基于 NocoBase 约定式的数据表资源映射,在应用的服务启动以后,会自动生成对应的 CRUD HTTP API。 - -当从客户端请求以下 URL 时,会得到对应的响应结果: - -* `GET /api/products:list`:获取所有商品数据列表 -* `GET /api/products:get?filterByTk=`:获取指定 ID 的商品数据 -* `POST /api/products`:创建一条新的商品数据 -* `PUT /api/products:update?filterByTk=`:更新一条商品数据 -* `DELETE /api/products:destroy?filterByTk=`:删除一条商品数据 - -### 定义关系表和关联字段 - -在上面的例子中,我们只定义了一个商品数据表,但是实际上一个商品还需要关联到一个分类,一个品牌,一个供应商等等。这些关联关系可以通过定义关系表来实现,比如我们可以定义一个 `categories` 表,用来存储商品的分类,然后在商品表中添加一个 `category` 字段来关联到分类表。 - -新增文件 `collections/categories.ts`,并填入内容: - -```ts -export default { - name: 'categories', - fields: [ - { - type: 'string', - name: 'title' - }, - { - type: 'hasMany', - name: 'products', - } - ] -}; -``` - -我们为 `categories` 表定义了两个字段,一个是标题,另一个是该分类下关联的所有产品的一对多字段,会在后面一起介绍。因为我们已经在插件的主类中使用了 `db.import()` 方法导入 `collections` 目录下的所有数据表定义,所以这里新增的 `categories` 表也会被自动导入到数据库管理实例中。 - -修改文件 `collections/products.ts`,在 `fields` 中添加一个 `category` 字段: - -```ts -{ - name: 'products', - fields: [ - // ... - { - type: 'belongsTo', - name: 'category', - target: 'categories', - } - ] -} -``` - -可以看到,我们为 `products` 表新增的 `category` 字段是一个 `belongsTo` 类型的字段,它的 `target` 属性指向了 `categories` 表,这样就定义了一个 `products` 表和 `categories` 表之间的多对一关系。同时结合我们在 `categories` 表中定义的 `hasMany` 字段,就可以实现一个商品可以关联到多个分类,一个分类下可以有多个商品的关系。通常 `belongsTo` 和 `hasMany` 可以成对出现,分别定义在两张表中。 - -定义好两张表之间的关系后,同样的我们就可以直接通过 HTTP API 来请求关联数据了: - -* `GET /api/products:list?appends=category`:获取所有商品数据,同时包含关联的分类数据 -* `GET /api/products:get?filterByTk=&appends=category`:获取指定 ID 的商品数据,同时包含关联的分类数据 -* `GET /api/categories//products:list`:获取指定分类下的所有商品数据 -* `POST /api/categories//products`:在指定分类下创建新的商品 - -与一般的 ORM 框架类似,NocoBase 内置了四种关系字段类型,更多信息可以参考 API 字段类型相关的章节: - -* [`belongsTo` 类型](/api/database/field#belongsto) -* [`belongsToMany` 类型](/api/database/field#belongstomany) -* [`hasMany` 类型](/api/database/field#hasmany) -* [`hasOne` 类型](/api/database/field#hasone) - -### 扩展已有数据表 - -在上面的例子中,我们已经有了商品表和分类表,为了提供销售流程,我们还需要一个订单表。我们可以在 `collections` 目录下新增一个 `orders.ts` 文件,然后定义一个 `orders` 表: - -```ts -export default { - name: 'orders', - fields: [ - { - type: 'uuid', - name: 'id', - primaryKey: true - }, - { - type: 'belongsTo', - name: 'product' - }, - { - type: 'integer', - name: 'quantity' - }, - { - type: 'integer', - name: 'totalPrice' - }, - { - type: 'integer', - name: 'status' - }, - { - type: 'string', - name: 'address' - }, - { - type: 'belongsTo', - name: 'user' - } - ] -} -``` - -为了简化,订单表与商品的关联我们只简单的定义为多对一关系,而在实际业务中可能会用到多对多或快照等复杂的建模方式。可以看到,一个订单除了对应某个商品,我们还增加了一个对应用户的关系定义,用户是 NocoBase 内置插件管理的数据表(详细参考[用户插件的代码](https://github.com/nocobase/nocobase/tree/main/packages/plugins/users))。如果我们希望针对已存在的用户表扩展定义“一个用户所拥有的多个订单”的关系,可以在当前的 shop-modeling 插件内继续新增一个数据表文件 `collections/users.ts`,与直接导出 JSON 数据表配置不同的是,这里使用 `@nocobase/database` 包的 `extend()` 方法,进行对已有数据表的扩展定义: - -```ts -import { extend } from '@nocobase/database'; - -export extend({ - name: 'users', - fields: [ - { - type: 'hasMany', - name: 'orders' - } - ] -}); -``` - -这样,原先已存在的用户表也就拥有了一个 `orders` 关联字段,我们可以通过 `GET /api/users//orders:list` 来获取指定用户的所有订单数据。 - -这个方法在扩展其他插件已定义的数据表时非常有用,使得其他已有插件不会反向依赖新的插件,仅形成单向依赖关系,方便在扩展层面进行一定程度的解耦。 - -### 扩展字段类型 - -我们在定义订单表的时候针对 `id` 字段使用了 `uuid` 类型,这是一个内置的字段类型。有时候我们也会觉得 UUID 看起来太长比较浪费空间,且查询性能不佳,希望用一个更适合的字段类型,比如一个含日期信息等复杂的编号逻辑,或者是 Snowflake 算法,我们就需要扩展一个自定义字段类型。 - -假设我们需要直接应用 Snowflake ID 生成算法,扩展出一个 `snowflake` 字段类型,我们可以创建一个 `fields/snowflake.ts` 文件: - -```ts -import { DataTypes } from 'sequelize'; -// 引入算法工具包 -import { Snowflake } from 'nodejs-snowflake'; -// 引入字段类型基类 -import { Field, BaseColumnFieldOptions } from '@nocobase/database'; - -export interface SnowflakeFieldOptions extends BaseColumnFieldOptions { - type: 'snowflake'; - epoch: number; - instanceId: number; -} - -export class SnowflakeField extends Field { - get dataType() { - return DataTypes.BIGINT; - } - - constructor(options: SnowflakeFieldOptions, context) { - super(options, context); - - const { - epoch: custom_epoch, - instanceId: instance_id = process.env.INSTANCE_ID ? Number.parseInt(process.env.INSTANCE_ID) : 0, - } = options; - this.generator = new Snowflake({ custom_epoch, instance_id }); - } - - setValue = (instance) => { - const { name } = this.options; - instance.set(name, this.generator.getUniqueID()); - }; - - bind() { - super.bind(); - this.on('beforeCreate', this.setValue); - } - - unbind() { - super.unbind(); - this.off('beforeCreate', this.setValue); - } -} - -export default SnowflakeField; -``` - -之后在插件主文件向数据库注册新的字段类型: - -```ts -import SnowflakeField from './fields/snowflake'; - -export default class ShopPlugin extends Plugin { - initialize() { - // ... - this.db.registerFieldTypes({ - snowflake: SnowflakeField - }); - // ... - } -} -``` - -这样,我们就可以在订单表中使用 `snowflake` 字段类型了: - -```ts -export default { - name: 'orders', - fields: [ - { - type: 'snowflake' - name: 'id', - primaryKey: true - }, - // ...other fields - ] -} -``` - -## 小结 - -通过上面的示例,我们基本了解了如何在一个插件中进行数据建模,包括: - -* 定义数据表和普通字段 -* 定义关联表和关联字段关系 -* 扩展已有的数据表的字段 -* 扩展新的字段类型 - -我们将本章所涉及的代码放到了一个完整的示例包 [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling) 中,可以直接在本地运行,查看效果。 diff --git a/docs/zh-CN/development/server/collections/association-fields.md b/docs/zh-CN/development/server/collections/association-fields.md deleted file mode 100644 index 0e7c4a081..000000000 --- a/docs/zh-CN/development/server/collections/association-fields.md +++ /dev/null @@ -1,153 +0,0 @@ -# 关系字段配置 - -在关系数据库里,标准的建表关系的方式是添加一个外键字段,然后再加一个外键约束。例如 Knex 建表的例子: - -```ts -knex.schema.table('posts', function (table) { - table.integer('userId').unsigned(); - table.foreign('userId').references('users.id'); -}); -``` - -这个过程会在 posts 表里创建一个 userId 字段,并且设置上外键约束 posts.userId 引用 users.id。而在 NocoBase 的 Collection 中,是通过配置关系字段来建立上这样一种关系约束,如: - -```ts -{ - name: 'posts', - fields: [ - { - type: 'belongsTo', - name: 'user', - target: 'users', - foreignKey: 'userId', - }, - ], -} -``` - -## 关系参数说明 - -### BelongsTo - -```ts -interface BelongsTo { - type: 'belongsTo'; - name: string; - // 默认值为 name 复数 - target?: string; - // 默认值为 target model 的主键,一般为 'id' - targetKey?: any; - // 默认值为 target + 'Id' - foreignKey?: any; -} - -// authors 表主键 id 和 books 表外键 authorId 相连 -{ - name: 'books', - fields: [ - { - type: 'belongsTo', - name: 'author', - target: 'authors', - targetKey: 'id', // authors 表主键 - foreignKey: 'authorId', // 外键在 books 表 - } - ], -} -``` - -### HasOne - -```ts -interface HasOne { - type: 'hasOne'; - name: string; - // 默认值为 name 复数 - target?: string; - // 默认值为 source model 的主键,一般为 'id' - sourceKey?: string; - // 默认值为 source collection name 的单数形态 + 'Id' - foreignKey?: string; -} - -// users 表主键 id 和 profiles 外键 userId 相连 -{ - name: 'users', - fields: [ - { - type: 'hasOne', - name: 'profile', - target: 'profiles', - sourceKey: 'id', // users 表主键 - foreignKey: 'userId', // 外键在 profiles 表 - } - ], -} -``` - -### HasMany - -```ts -interface HasMany { - type: 'hasMany'; - name: string; - // 默认值为 name - target?: string; - // 默认值为 source model 的主键,一般为 'id' - sourceKey?: string; - // 默认值为 source collection name 的单数形态 + 'Id' - foreignKey?: string; -} - -// posts 表主键 id 和 comments 表 postId 相连 -{ - name: 'posts', - fields: [ - { - type: 'hasMany', - name: 'comments', - target: 'comments', - sourceKey: 'id', // posts 表主键 - foreignKey: 'postId', // 外键在 comments 表 - } - ], -} -``` - -### BelongsToMany - -```ts -interface BelongsToMany { - type: 'belongsToMany'; - name: string; - // 默认值为 name - target?: string; - // 默认值为 source collection name 和 target 的首字母自然顺序拼接的字符串 - through?: string; - //默认值为 source collection name 的单数形态 + 'Id' - foreignKey?: string; - // 默认值为 source model 的主键,一般为 id - sourceKey?: string; - //默认值为 target 的单数形态 + 'Id' - otherKey?: string; - // 默认值为 target model 的主键,一般为 id - targetKey?: string; -} - -// tags 表主键、posts 表主键和 posts_tags 两个外键相连 -{ - name: 'posts', - fields: [ - { - type: 'belongsToMany', - name: 'tags', - target: 'tags', - through: 'posts_tags', // 中间表 - foreignKey: 'tagId', // 外键1,在 posts_tags 表里 - otherKey: 'postId', // 外键2,在 posts_tags 表里 - targetKey: 'id', // tags 表主键 - sourceKey: 'id', // posts 表主键 - } - ], -} -``` diff --git a/docs/zh-CN/development/server/collections/cm.svg b/docs/zh-CN/development/server/collections/cm.svg deleted file mode 100644 index 9be5cbf5f..000000000 --- a/docs/zh-CN/development/server/collections/cm.svg +++ /dev/null @@ -1 +0,0 @@ -
    Collection Manager Plugin

    Provide rest api to manage collections
    REST API
    Plugin A
    db.collection()
    Collections
    UI: Collections & Fields
    UI: Graphical interface
    Plugin B
    db.collection()
    Third party
    \ No newline at end of file diff --git a/docs/zh-CN/development/server/collections/collection-field.svg b/docs/zh-CN/development/server/collections/collection-field.svg deleted file mode 100644 index 77c6d5f6f..000000000 --- a/docs/zh-CN/development/server/collections/collection-field.svg +++ /dev/null @@ -1 +0,0 @@ -
    Field Type
    Field Component
    Field Interface
    Collection Field
    \ No newline at end of file diff --git a/docs/zh-CN/development/server/collections/collection-template.md b/docs/zh-CN/development/server/collections/collection-template.md deleted file mode 100644 index 93389bf82..000000000 --- a/docs/zh-CN/development/server/collections/collection-template.md +++ /dev/null @@ -1,82 +0,0 @@ -# Collection 模板 - - -📢 Collection 模板计划在 2022 年第四季度提供。 - - -在实际的业务场景中,不同的 collection 可能有自己的初始化规则和业务逻辑,NocoBase 通过提供 Collection 模板来解决这类问题。 - -## 常规表 - -```ts -db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ], -}); -``` - -## 树结构表 - -```ts -db.collection({ - name: 'categories', - tree: 'adjacency-list', - fields: [ - { - type: 'string', - name: 'name', - }, - { - type: 'string', - name: 'description', - }, - { - type: 'belongsTo', - name: 'parent', - target: 'categories', - foreignKey: 'parentId', - }, - { - type: 'hasMany', - name: 'children', - target: 'categories', - foreignKey: 'parentId', - }, - ], -}); -``` - -## 父子继承表 - -```ts -db.collection({ - name: 'a', - fields: [ - - ], -}); - -db.collection({ - name: 'b', - inherits: 'a', - fields: [ - - ], -}); -``` - -## 更多模板 - -如日历表,每个初始化的表都需要初始化特殊的 cron 和 exclude 字段,而这种字段的定义就由模板来完成 - -```ts -db.collection({ - name: 'events', - template: 'calendar', -}); -``` diff --git a/docs/zh-CN/development/server/collections/configure.md b/docs/zh-CN/development/server/collections/configure.md deleted file mode 100644 index c6824ea41..000000000 --- a/docs/zh-CN/development/server/collections/configure.md +++ /dev/null @@ -1,62 +0,0 @@ -# 如何配置数据表? - -NocoBase 有三种方式配置数据表: - - - -## 通过界面配置数据表 - -业务数据一般建议使用界面配置,NocoBase 平台提供了两种界面配置数据表 - -### 常规的表格界面 - - - -### 图形化配置界面 - - - -## 在插件代码里定义 - -一般用于配置插件功能表或系统配置表,用户可以读写数据,但不能修改表结构。 - -```ts -export class MyPlugin extends Plugin { - load() { - this.db.collection(); - this.db.import(); - } -} -``` - -相关 API 参考 - -- [db.collection()](/api/database#collection) -- [db.import()](/api/database#import) - -在插件里配置的 collection,插件激活时自动与数据库同步,生相对应的数据表和字段。 - -## 通过 REST API 管理数据表 - -第三方还可以通过 HTTP 接口管理数据表(需要开放权限) - -### Collections - -```bash -GET /api/collections -POST /api/collections -GET /api/collections/ -PUT /api/collections/ -DELETE /api/collections/ -``` - -### Collection fields - -```bash -GET /api/collections//fields -POST /api/collections//fields -GET /api/collections//fields/ -PUT /api/collections//fields/ -DELETE /api/collections//fields/ -``` - diff --git a/docs/zh-CN/development/server/collections/field-extension.md b/docs/zh-CN/development/server/collections/field-extension.md deleted file mode 100644 index 4ab6216c9..000000000 --- a/docs/zh-CN/development/server/collections/field-extension.md +++ /dev/null @@ -1,39 +0,0 @@ -# 字段扩展 - -在 NocoBase 中 Collection Field 的构成包括: - - - -## Field Type 扩展 - -例如扩展密码类型字段 `type: 'password'` - -```ts -export class MyPlugin extends Plugin { - beforeLoad() { - this.db.registerFieldTypes({ - password: PasswordField - }); - } -} - -export class PasswordField extends Field { - get dataType() { - return DataTypes.STRING; - } -} -``` - -- [更多内置 field types 的实现点此查看](https://github.com/nocobase/nocobase/tree/main/packages/core/database/src/fields) -- 也可以查看完整的 samples 插件 [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling) - -## Field Component 扩展 - -相关扩展文档查看: - -- [扩展 Schema 组件](/development/client/ui-schema-designer/extending-schema-components) -- [Schema 组件库](/development/client/ui-schema-designer/component-library) - -## Field Interface 扩展 - -- [内置 field interfaces 点此查看](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces) \ No newline at end of file diff --git a/docs/zh-CN/development/server/collections/graph.jpg b/docs/zh-CN/development/server/collections/graph.jpg deleted file mode 100644 index 16c2f95d1..000000000 Binary files a/docs/zh-CN/development/server/collections/graph.jpg and /dev/null differ diff --git a/docs/zh-CN/development/server/collections/index.md b/docs/zh-CN/development/server/collections/index.md deleted file mode 100644 index 9680a71fd..000000000 --- a/docs/zh-CN/development/server/collections/index.md +++ /dev/null @@ -1,194 +0,0 @@ -# 核心概念 - -## Collection - -Collection 是所有种类数据的集合,中文翻译为「数据表」,如订单、商品、用户、评论等都是 Collection。不同 Collection 通过 name 区分,如: - -```ts -// 订单 -{ - name: 'orders', -} -// 商品 -{ - name: 'products', -} -// 用户 -{ - name: 'users', -} -// 评论 -{ - name: 'comments', -} -``` - -## Collection Field - -每个 Collection 都有若干 Fields。 - -```ts -// Collection 配置 -{ - name: 'users', - fields: [ - { type: 'string', name: 'name' }, - { type: 'integer', name: 'age' }, - // 其他字段 - ], -} -// 示例数据 -[ - { - name: '张三', - age: 20, - }, - { - name: '李四', - age: 18, - } -]; -``` - -在 NocoBase 中 Collection Field 的构成包括: - - - -### Field Type - -不同字段通过 name 区分,type 表示字段的数据类型,分为 Attribute Type 和 Association Type,如: - -**属性 - Attribute Type** - -- string -- text -- date -- boolean -- time -- float -- json -- location -- password -- virtual -- ... - -**关系 - Association Type** - -- hasOne -- hasMany -- belongsTo -- belongsToMany -- ... - -### Field Component - -字段有了数据类型,字段值的 IO 没问题了,但是还不够,如果需要将字段展示在界面上,还需要另一个维度的配置 —— `uiSchema`,如: - -```tsx | pure -// 邮箱字段,用 Input 组件展示,使用 email 校验规则 -{ - type: 'string', - name: 'email', - uiSchema: { - 'x-component': 'Input', - 'x-component-props': { size: 'large' }, - 'x-validator': 'email', - 'x-pattern': 'editable', // 可编辑状态,还有 readonly 不可编辑状态、read-pretty 阅读态 - }, -} - -// 数据示例 -{ - email: 'admin@nocobase.com', -} - -// 组件示例 - -``` - -uiSchema 用于配置字段展示在界面上的组件,每个字段组件都会对应一个 value,包括几个维护的配置: - -- 字段的组件 -- 组件的参数 -- 字段的校验规则 -- 字段的模式(editable、readonly、read-pretty) -- 字段的默认值 -- 其他 - -[更多信息查看 UI Schema 章节](/development/client/ui-schema-designer/what-is-ui-schema)。 - -NocoBase 内置的字段组件有: - -- Input -- InputNumber -- Select -- Radio -- Checkbox -- ... - -### Field Interface - -有了 Field Type 和 Field Component 就可以自由组合出若干字段,我们将这种组合之后的模板称之为 Field Interface,如: - -```ts -// 邮箱字段 string + input,email 校验规则 -{ - type: 'string', - name: 'email', - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'email', - }, -} - -// 手机字段 string + input,phone 校验规则 -{ - type: 'string', - name: 'phone', - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'phone', - }, -} -``` - -上面 email 和 phone 每次都需要配置完整的 uiSchema 非常繁琐,为了简化配置,又引申出另一个概念 Field interface,可以将一些参数模板化,如: - -```ts -// email 字段的模板 -interface email { - type: 'string'; - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'email', - }; -} - -// phone 字段的模板 -interface phone { - type: 'string'; - uiSchema: { - 'x-component': 'Input', - 'x-component-props': {}, - 'x-validator': 'phone', - }; -} - -// 简化之后的字段配置 -// email -{ - interface: 'email', - name: 'email', -} - -// phone -{ - interface: 'phone', - name: 'phone', -} -``` - -[更多 Field Interface 点此查看](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces) diff --git a/docs/zh-CN/development/server/collections/options.md b/docs/zh-CN/development/server/collections/options.md deleted file mode 100644 index d94a44eb7..000000000 --- a/docs/zh-CN/development/server/collections/options.md +++ /dev/null @@ -1,137 +0,0 @@ -# Collection 协议 - -Collection 是 NocoBase 的中枢,是一种用于描述数据结构(数据表和字段)的协议,和关系型数据库的概念非常接近,但不仅限于关系型数据库,也可以是 NoSQL 数据库、HTTP API 等数据源。 - - - -现阶段基于 Collection 协议实现了关系型数据库的对接(db.collections),NoSQL 数据库、HTTP API 等数据源在未来也会逐步实现。 - -Collection 协议主要包括 CollectionOptions 和 FieldOptions 两部分,因为 Field 是可扩展的,所以 FieldOptions 的参数非常灵活。 - -## CollectionOptions - -```ts -interface CollectionOptions { - name: string; - title?: string; - // 树结构表,TreeRepository - tree?: 'adjacency-list' | 'closure-table' | 'materialized-path' | 'nested-set'; - // 父子继承 - inherits?: string | string[]; - fields?: FieldOptions[]; - timestamps?: boolean; - paranoid?: boolean; - sortable?: CollectionSortable; - model?: string; - repository?: string; - [key: string]: any; -} - -type CollectionSortable = string | boolean | { name?: string; scopeKey?: string }; -``` - -## FieldOptions - -通用的字段参数 - -```ts -interface FieldOptions { - name: string; - type: string; - hidden?: boolean; - index?: boolean; - interface?: string; - uiSchema?: ISchema; -``` - -[UI Schema 的介绍点此查看](/development/client/ui-schema-designer/what-is-ui-schema) - -### Field Type - -Field Type 包括 Attribute Type 和 Association Type 两类: - -**Attribute Type** - -- 'boolean' -- 'integer' -- 'bigInt' -- 'double' -- 'real' -- 'decimal' -- 'string' -- 'text' -- 'password' -- 'date' -- 'time' -- 'array' -- 'json' -- 'jsonb' -- 'uuid' -- 'uid' -- 'formula' -- 'radio' -- 'sort' -- 'virtual' - -**Association Type** - -- 'belongsTo' -- 'hasOne' -- 'hasMany' -- 'belongsToMany' - -### Field Interface - -**Basic** - -- input -- textarea -- phone -- email -- integer -- number -- percent -- password -- icon - -**Choices** - -- checkbox -- select -- multipleSelect -- radioGroup -- checkboxGroup -- chinaRegion - -**Media** - -- attachment -- markdown -- richText - -**Date & Time** - -- datetime -- time - -**Relation** - -- linkTo - `type: 'belongsToMany'` -- oho - `type: 'hasOne'` -- obo - `type: 'belongsTo'` -- o2m - `type: 'hasMany'` -- m2o - `type: 'belongsTo'` -- m2m - `type: 'belongsToMany'` - -**Advanced** - -- formula -- sequence - -**System info** - -- id -- createdAt -- createdBy -- updatedAt -- updatedBy diff --git a/docs/zh-CN/development/server/collections/schema.svg b/docs/zh-CN/development/server/collections/schema.svg deleted file mode 100644 index 230a9bbca..000000000 --- a/docs/zh-CN/development/server/collections/schema.svg +++ /dev/null @@ -1 +0,0 @@ -
    Collection
    Field Type
    Field Component
    UI Schema
    Field Interface
    Collection Field
    Relational Database
    PostgreSQL
    NoSQL
    API
    SQLite
    MySQL
    Others
    Resource
    Client
    \ No newline at end of file diff --git a/docs/zh-CN/development/server/collections/table.jpg b/docs/zh-CN/development/server/collections/table.jpg deleted file mode 100644 index 55fe4c183..000000000 Binary files a/docs/zh-CN/development/server/collections/table.jpg and /dev/null differ diff --git a/docs/zh-CN/development/server/commands.md b/docs/zh-CN/development/server/commands.md deleted file mode 100644 index 3edfcab05..000000000 --- a/docs/zh-CN/development/server/commands.md +++ /dev/null @@ -1,100 +0,0 @@ -# 命令行 - -NocoBase Server Application 除了用作 WEB 服务器以外,也是个强大可扩展的 CLI 工具。 - -新建一个 `app.js` 文件,代码如下: - -```ts -const Application = require('@nocobase/server'); - -// 此处省略具体配置 -const app = new Application({/*...*/}); - -app.runAsCLI(); -``` - -以 `runAsCLI()` 方式运行的 app.js 是一个 CLI,在命令行工具就可以像这样操作了: - -```bash -node app.js install # 安装 -node app.js start # 启动 -``` - -为了更好的开发、构建和部署 NocoBase 应用,NocoBase 内置了许多命令,详情查看 [NocoBase CLI](/api/cli) 章节。 - -## 如何自定义 Command? - -NocoBase CLI 的设计思想与 [Laravel Artisan](https://laravel.com/docs/9.x/artisan) 非常相似,都是可扩展的。NocoBase CLI 基于 [commander](https://www.npmjs.com/package/commander) 实现,可以这样扩展 Command: - -```ts -export class MyPlugin extends Plugin { - load() { - this.app - .command('echo') - .option('-v, --version'); - .action(async ([options]) => { - console.log('Hello World!'); - if (options.version) { - console.log('Current version:', app.getVersion()); - } - }); - } -} -``` - -这个方法定义了以下命令: - -```bash -yarn nocobase echo -# Hello World! -yarn nocobase echo -v -# Hello World! -# Current version: 0.8.0-alpha.1 -``` - -更多 API 细节可参考 [Application.command()](/api/server/application#command) 部分。 - -## 示例 - -### 定义导出数据表的命令 - -如果我们希望把应用的数据表中的数据导出成 JSON 文件,可以定义一个如下的子命令: - -```ts -import path from 'path'; -import * as fs from 'fs/promises'; - -class MyPlugin extends Plugin { - load() { - this.app - .command('export') - .option('-o, --output-dir') - .action(async (options, ...collections) => { - const { outputDir = path.join(process.env.PWD, 'storage') } = options; - await collections.reduce((promise, collection) => promise.then(async () => { - if (!this.db.hasCollection(collection)) { - console.warn('No such collection:', collection); - return; - } - - const repo = this.db.getRepository(collection); - const data = repo.find(); - await fs.writeFile(path.join(outputDir, `${collection}.json`), JSON.stringify(data), { mode: 0o644 }); - }), Promise.resolve()); - }); - } -} -``` - -注册和激活插件之后在命令行调用: - -```bash -mkdir -p ./storage/backups -yarn nocobase export -o ./storage/backups users -``` - -执行后会生成 `./storage/backups/users.json` 文件包含数据表中的数据。 - -## 小结 - -本章所涉及示例代码整合在 [packages/samples/command](https://github.com/nocobase/nocobase/tree/main/packages/samples/command) 包中,可以直接在本地运行,查看效果。 diff --git a/docs/zh-CN/development/server/events.md b/docs/zh-CN/development/server/events.md deleted file mode 100644 index 37fb877fc..000000000 --- a/docs/zh-CN/development/server/events.md +++ /dev/null @@ -1,168 +0,0 @@ -# 事件 - -NocoBase 在应用、插件、数据库的生命周期中提供了非常多的事件监听,这些方法只有在触发了事件之后才会执行。 - -## 如何添加事件监听? - -事件的注册一般放于 afterAdd 或 beforeLoad 中 - -```ts -export class MyPlugin extends Plugin { - // 插件添加进来之后,有没有激活都执行 afterAdd() - afterAdd() { - this.app.on(); - this.db.on(); - } - - // 只有插件激活之后才会执行 beforeLoad() - beforeLoad() { - this.app.on(); - this.db.on(); - } -} -``` - -### `db.on` - -数据库相关事件与 Collection 配置、Repository 的 CRUD 相关,包括: - -- 'beforeSync' / 'afterSync' -- 'beforeValidate' / 'afterValidate' -- 'beforeCreate' / 'afterCreate' -- 'beforeUpdate' / 'afterUpdate' -- 'beforeSave' / 'afterSave' -- 'beforeDestroy' / 'afterDestroy' -- 'afterCreateWithAssociations' -- 'afterUpdateWithAssociations' -- 'afterSaveWithAssociations' -- 'beforeDefineCollection' -- 'afterDefineCollection' -- 'beforeRemoveCollection' / 'afterRemoveCollection - -更多详情参考 [Database API](/api/database#内置事件)。 - -### `app.on()` - -app 的事件与应用的生命周期相关,相关事件有: - -- 'beforeLoad' / 'afterLoad' -- 'beforeInstall' / 'afterInstall' -- 'beforeUpgrade' / 'afterUpgrade' -- 'beforeStart' / 'afterStart' -- 'beforeStop' / 'afterStop' -- 'beforeDestroy' / 'afterDestroy' - -更多详情参考 [Application API](/api/server/application#事件)。 - -## 示例 - -我们继续以简单的在线商店来举例,相关的数据表建模可以回顾 [数据表和字段](/development/) 部分的示例。 - -### 创建订单后减商品库存 - -通常我们的商品和订单是不同的数据表。客户在下单以后把商品的库存减掉可以解决超卖的问题。这时候我们可以针对创建订单这个数据操作定义相应的事件,在这个时机一并解决库存修改的问题: - -```ts -class ShopPlugin extends Plugin { - beforeLoad() { - this.db.on('orders.afterCreate', async (order, options) => { - const product = await order.getProduct({ - transaction: options.transaction - }); - - await product.update({ - inventory: product.inventory - order.quantity - }, { - transaction: options.transaction - }); - }); - } -} -``` - -因为默认 Sequelize 的事件中就携带事务等信息,所以我们可以直接使用 transaction 以保证两个数据操作都在同一事务中进行。 - -同样的,也可以在创建发货记录后修改订单状态为已发货: - -```ts -class ShopPlugin extends Plugin { - load() { - this.db.on('deliveries.afterCreate', async (delivery, options) => { - const orderRepo = this.db.getRepository('orders'); - await orderRepo.update({ - filterByTk: delivery.orderId, - value: { - status: 2 - } - transaction: options.transaction - }); - }); - } -} -``` - -### 随应用同时存在的定时任务 - -在不考虑使用工作流插件等复杂情况下,我们也可以通过应用级的事件实现一个简单的定时任务机制,且可以与应用的进程绑定,退出后就停止。比如我们希望定时扫描所有订单,超过签收时间后自动签收: - -```ts -class ShopPlugin extends Plugin { - timer = null; - orderReceiveExpires = 86400 * 7; - - checkOrder = async () => { - const expiredDate = new Date(Date.now() - this.orderReceiveExpires); - const deliveryRepo = this.db.getRepository('deliveries'); - const expiredDeliveries = await deliveryRepo.find({ - fields: ['id', 'orderId'], - filter: { - status: 0, - createdAt: { - $lt: expiredDate - } - } - }); - await deliveryRepo.update({ - filter: { - id: expiredDeliveries.map(item => item.get('id')), - }, - values: { - status: 1 - } - }); - const orderRepo = this.db.getRepository('orders'); - const [updated] = await orderRepo.update({ - filter: { - status: 2, - id: expiredDeliveries.map(item => item.get('orderId')) - }, - values: { - status: 3 - } - }); - - console.log('%d orders expired', updated); - }; - - load() { - this.app.on('beforeStart', () => { - // 每分钟执行一次 - this.timer = setInterval(this.checkOrder, 1000 * 60); - }); - - this.app.on('beforeStop', () => { - clearInterval(this.timer); - this.timer = null; - }); - } -} -``` - -## 小结 - -通过上面的示例,我们基本了解了事件的作用和可以用于扩展的方式: - -* 数据库相关的事件 -* 应用相关的事件 - -本章涉及的示例代码整合在对应的包 [packages/samples/shop-events](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-events) 中,可以直接在本地运行,查看效果。 diff --git a/docs/zh-CN/development/server/i18n.md b/docs/zh-CN/development/server/i18n.md deleted file mode 100644 index 51c66de60..000000000 --- a/docs/zh-CN/development/server/i18n.md +++ /dev/null @@ -1,134 +0,0 @@ -# 国际化 - -NocoBase 国际化基于 [i18next](https://npmjs.com/package/i18next) 实现。 - -## 如何注册多语言包? - -```ts -export class MyPlugin extends Plugin { - load() { - this.app.i18n.addResources('zh-CN', 'test', { - Hello: '你好', - World: '世界', - }); - this.app.i18n.addResources('en-US', 'test', { - Hello: 'Hello', - World: 'World', - }); - } -} -``` - -## 两个 i18n 实例 - -### app.i18n - -全局 i18n 实例,一般用于 CLI 中。 - -```ts -app.i18n.t('World') // “世界”或“World” -``` - -### ctx.i18n - -全局 i18n 的 cloneInstance,每个请求的 context 完全独立,通常用于根据客户端语言响应多语言信息。 - -```ts -app.use(async (ctx, next) => { - ctx.body = `${ctx.i18n.t('Hello')} ${ctx.i18n.t('World')}`; - await next(); -}); -``` - -客户端请求参数可以放在 query string 里 - -```bash -GET /?locale=en-US HTTP/1.1 -Host: localhost:13000 -``` - -或放在 request headers 里 - -```bash -GET / HTTP/1.1 -Host: localhost:13000 -X-Locale: en-US -``` - -## 建议配置 - -以英文文案为 key,翻译为 value,这样的好处,即使多语言缺失,也会以英文显示,不会造成阅读障碍,如: - -```ts -i18n.addResources('zh-CN', 'your-namespace', { - 'Show dialog': '显示对话框', - 'Hide dialog': '隐藏对话框' -}); -``` - -为了更方便管理多语言文件,推荐在插件中创建一个 `locale` 文件夹,并把对应语言文件都放置在其中: - -```bash -|- /my-plugin - |- /src - |- /server - |- locale # 多语言文件夹 - |- zh-CN.ts - |- en-US.ts -``` - -## 示例 - -### 服务端错误提示 - -例如用户在店铺对某个商品下单时,如果商品的库存不够,或者未上架,那么下单接口被调用时,应该返回相应的错误。 - -```ts -const namespace = 'shop'; - -export default class ShopPlugin extends Plugin { - async load() { - this.app.i18n.addResources('zh-CN', namespace, { - 'No such product': '商品不存在', - 'Product not on sale': '商品已下架', - 'Out of stock': '库存不足', - }); - - this.app.resource({ - name: 'orders', - actions: { - async create(ctx, next) { - const productRepo = ctx.db.getRepository('products'); - const product = await productRepo.findOne({ - filterByTk: ctx.action.params.values.productId - }); - - if (!product) { - return ctx.throw(404, ctx.t('No such product')); - } - - if (!product.enabled) { - return ctx.throw(400, ctx.t('Product not on sale')); - } - - if (!product.inventory) { - return ctx.throw(400, ctx.t('Out of stock')); - } - - const orderRepo = ctx.db.getRepository('orders'); - ctx.body = await orderRepo.create({ - values: { - productId: product.id, - quantity: 1, - totalPrice: product.price, - userId: ctx.state.currentUser.id - } - }); - - next(); - } - } - }); - } -} -``` diff --git a/docs/zh-CN/development/server/index.md b/docs/zh-CN/development/server/index.md deleted file mode 100644 index ce23cdea5..000000000 --- a/docs/zh-CN/development/server/index.md +++ /dev/null @@ -1,72 +0,0 @@ -# 概述 - -初始化的空插件,服务端相关目录结构如下: - -```bash -|- /my-plugin - |- /src - |- /server # 插件服务端代码 - |- plugin.ts # 插件类 - |- index.ts # 服务端入口 - |- server.d.ts - |- server.js -``` - -`plugin.ts` 提供了插件生命周期的各种方法的调用 - -```ts -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class MyPlugin extends Plugin { - afterAdd() { - // 插件 pm.add 注册进来之后。主要用于放置 app beforeLoad 事件的监听 - this.app.on('beforeLoad'); - } - beforeLoad() { - // 自定义类或方法 - this.db.registerFieldTypes() - this.db.registerModels() - this.db.registerRepositories() - this.db.registerOperators() - // 事件监听 - this.app.on(); - this.db.on(); - } - async load() { - // 定义 collection - this.db.collection(); - // 导入 collection - this.db.import(); - this.db.addMigrations(); - - // 定义 resource - this.resourcer.define(); - // resource action - this.resourcer.registerActions(); - - // 注册 middleware - this.resourcer.use(); - this.acl.use(); - this.app.use(); - - // 自定义多语言包 - this.app.i18n.addResources(); - // 自定义命令行 - this.app.command(); - } - async install(options?: InstallOptions) { - // 安装逻辑 - } - async afterEnable() { - // 激活之后 - } - async afterDisable() { - // 禁用之后 - } - async remove() { - // 删除逻辑 - } -} - -export default MyPlugin; -``` diff --git a/docs/zh-CN/development/server/middleware.md b/docs/zh-CN/development/server/middleware.md deleted file mode 100644 index 02d334e91..000000000 --- a/docs/zh-CN/development/server/middleware.md +++ /dev/null @@ -1,160 +0,0 @@ -# 中间件 - -## 如何注册中间件? - -中间件的注册方法一般写在 load 方法里 - -```ts -export class MyPlugin extends Plugin { - load() { - this.app.acl.use(); - this.app.resourcer.use(); - this.app.use(); - } -} -``` - -说明: - -1. `app.acl.use()` 添加资源权限级中间件,在权限判断之前执行 -2. `app.resourcer.use()` 添加资源级中间件,只有请求已定义的 resource 时才执行 -3. `app.use()` 添加应用级中间件,每次请求都执行 - -## 洋葱圈模型 - -```ts -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(1); - await next(); - ctx.body.push(2); -}); - -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(3); - await next(); - ctx.body.push(4); -}); -``` - -访问 http://localhost:13000/api/hello 查看,浏览器响应的数据是: - -```js -{"data": [1,3,4,2]} -``` - -## 内置中间件及执行顺序 - -1. `cors` -2. `bodyParser` -3. `i18n` -4. `dataWrapping` -5. `db2resource` -6. `restApi` - 1. `parseToken` - 2. `checkRole` - 3. `acl` - 1. `acl.use()` 添加的其他中间件 - 4. `resourcer.use()` 添加的其他中间件 - 5. `action handler` -7. `app.use()` 添加的其他中间件 - -也可以使用 `before` 或 `after` 将中间件插入到前面的某个 `tag` 标记的位置,如: - -```ts -app.use(m1, { tag: 'restApi' }); -app.resourcer.use(m2, { tag: 'parseToken' }); -app.resourcer.use(m3, { tag: 'checkRole' }); -// m4 将排在 m1 前面 -app.use(m4, { before: 'restApi' }); -// m5 会插入到 m2 和 m3 之间 -app.resourcer.use(m5, { after: 'parseToken', before: 'checkRole' }); -``` - -如果未特殊指定位置,新增的中间件的执行顺序是: - -1. 优先执行 acl.use 添加的, -2. 然后是 resourcer.use 添加的,包括 middleware handler 和 action handler, -3. 最后是 app.use 添加的。 - -```ts -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(1); - await next(); - ctx.body.push(2); -}); - -app.resourcer.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(3); - await next(); - ctx.body.push(4); -}); - -app.acl.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(5); - await next(); - ctx.body.push(6); -}); - -app.resourcer.define({ - name: 'test', - actions: { - async list(ctx, next) { - ctx.body = ctx.body || []; - ctx.body.push(7); - await next(); - ctx.body.push(8); - }, - }, -}); -``` - -访问 http://localhost:13000/api/hello 查看,浏览器响应的数据是: - -```js -{"data": [1,2]} -``` - -访问 http://localhost:13000/api/test:list 查看,浏览器响应的数据是: - -```js -{"data": [5,3,7,1,2,8,4,6]} -``` - -### resource 未定义,不执行 resourcer.use() 添加的中间件 - -```ts -app.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(1); - await next(); - ctx.body.push(2); -}); - -app.resourcer.use(async (ctx, next) => { - ctx.body = ctx.body || []; - ctx.body.push(3); - await next(); - ctx.body.push(4); -}); -``` - -访问 http://localhost:13000/api/hello 查看,浏览器响应的数据是: - -```js -{"data": [1,2]} -``` - -以上示例,hello 资源未定义,不会进入 resourcer,所以就不会执行 resourcer 里的中间件 - -## 中间件用途 - -待补充 - -## 示例 - -- [samples/ratelimit](https://github.com/nocobase/nocobase/blob/main/packages/samples/ratelimit/) IP rate-limiting diff --git a/docs/zh-CN/development/server/migration.md b/docs/zh-CN/development/server/migration.md deleted file mode 100644 index 1ec0a8139..000000000 --- a/docs/zh-CN/development/server/migration.md +++ /dev/null @@ -1,50 +0,0 @@ -# 数据库迁移 - -插件在更新迭代过程中,可能会出现某些不兼容的改动,这些不兼容的升级脚本可以通过编写 migration 文件来处理。 - -## 如何添加迁移文件? - -```ts -export class MyPlugin extends Plugin { - load() { - // 加载单个 Migration 文件 - this.db.addMigration(); - // 加载多个 Migration 文件 - this.db.addMigrations(); - } -} -``` - -API 参考: - -- [db.addMigration()](/api/database#addmigration) -- [db.addMigrations()](/api/database#addmigrations) - -## 什么时候执行? - -```bash -# app 升级时,会执行 migrator.up() 和 db.sync() -yarn nocobase upgrade -# 单独触发 migration -yarn nocobase migrator up -``` - -## 什么时候需要写 migration 文件? - -一般用于升级过程中,存于数据库的系统配置的更新。如果只是 collection 配置的变更,无需配置 migration,直接执行 `yarn nocobase db:sync` 就可以同步给数据库了。 - -## Migration 文件 - -```ts -import { Migration } from '@nocobase/server'; - -export default class CustomMigration extends Migration { - async up() { - // - } - - async down() { - // - } -} -``` diff --git a/docs/zh-CN/development/server/resources-actions.md b/docs/zh-CN/development/server/resources-actions.md deleted file mode 100644 index 68b5eb5fa..000000000 --- a/docs/zh-CN/development/server/resources-actions.md +++ /dev/null @@ -1,463 +0,0 @@ -# 资源与操作 - -在 Web 开发领域,你可能听说过 RESTful 的概念,NocoBase 也借用了这个资源的概念来映射系统中的各种实体,比如数据库中的数据、文件系统中的文件或某个服务等。但 NocoBase 基于实践考虑,并未完全遵循 RESTful 的约定,而是参考 [Google Cloud API 设计指南](https://cloud.google.com/apis/design) 的规范做了一些扩展,以适应更多的场景。 - -## 基础概念 - -与 RESTful 中资源的概念相同,是系统中对外提供的可操作的对象,可以是数据表、文件、和其他自定义的对象。 - -操作主要指对资源的读取和写入,通常用于查阅数据、创建数据、更新数据、删除数据等。NocoBase 通过定义操作来实现对资源的访问,操作的核心其实是一个用于处理请求且兼容 Koa 的中间件函数。 - -### 数据表自动映射为资源 - -目前的资源主要针对数据表中的数据,NocoBase 在默认情况下都会将数据库中的数据表自动映射为资源,同时也提供了服务端的数据接口。所以在默认情况下,只要使用了 `db.collection()` 定义了数据表,就可以通过 NocoBase 的 HTTP API 访问到这个数据表的数据资源了。自动生成的资源的名称与数据表定义的表名相同,比如 `db.collection({ name: 'users' })` 定义的数据表,对应的资源名称就是 `users`。 - -同时,还为这些数据资源内置了常用的 CRUD 操作,关系型数据资源也内置了关联数据的操作方法。 - -简单数据资源的默认操作: - -* [`list`](/api/actions#list):查询数据表中的数据列表 -* [`get`](/api/actions#get):查询数据表中的单条数据 -* [`create`](/api/actions#create):对数据表创建单条数据 -* [`update`](/api/actions#update):对数据表更新单条数据 -* [`destroy`](/api/actions#destroy):对数据表删除单条数据 - -关系资源除了简单的 CRUD 操作,还有默认的关系操作: - -* [`add`](/api/actions#add):对数据添加关联 -* [`remove`](/api/actions#remove):对数据移除关联 -* [`set`](/api/actions#set):对数据设置关联 -* [`toggle`](/api/actions#toggle):对数据添加或移除关联 - -比如定义一个文章数据表并同步到数据库: - -```ts -app.db.collection({ - name: 'posts', - fields: [ - { type: 'string', name: 'title' } - ] -}); - -await app.db.sync(); -``` - -之后针对 `posts` 数据资源的所有 CRUD 方法就可以直接通过 HTTP API 被调用了: - -```bash -# create -curl -X POST -H "Content-Type: application/json" -d '{"title":"first"}' http://localhost:13000/api/posts:create -# list -curl http://localhost:13000/api/posts:list -# update -curl -X PUT -H "Content-Type: application/json" -d '{"title":"second"}' http://localhost:13000/api/posts:update -# destroy -curl -X DELETE http://localhost:13000/api/posts:destroy?filterByTk=1 -``` - -### 自定义 Action - -当默认提供的 CRUD 等操作不满足业务场景的情况下,也可以对特定资源扩展更多的操作。比如是对内置操作的额外处理,或者需要设置默认参数。 - -针对特定资源的自定义操作,如覆盖文章表里的`创建`操作: - -```ts -// 等同于 app.resourcer.registerActions() -// 注册针对文章资源的 create 操作方法 -app.actions({ - async ['posts:create'](ctx, next) { - const postRepo = ctx.db.getRepository('posts'); - await postRepo.create({ - values: { - ...ctx.action.params.values, - // 限定当前用户是文章的创建者 - userId: ctx.state.currentUserId - } - }); - - await next(); - } -}); -``` - -这样在业务中就增加了合理的限制,用户不能以其他用户身份创建文章。 - -针对全局所有资源的自定义操作,如对所有数据表都增加`导出`的操作: - -```ts -app.actions({ - // 对所有资源都增加了 export 方法,用于导出数据 - async export(ctx, next) { - const repo = ctx.db.getRepository(ctx.action.resource); - const results = await repo.find({ - filter: ctx.action.params.filter - }); - ctx.type = 'text/csv'; - // 拼接为 CSV 格式 - ctx.body = results - .map(row => Object.keys(row) - .reduce((arr, col) => [...arr, row[col]], []).join(',') - ).join('\n'); - - next(); - } -}); -``` - -然后可以按以下 HTTP API 的方式导出 CSV 格式的数据: - -```bash -curl http://localhost:13000/api/:export -``` - -### Action 参数 - -客户端的请求到达服务端后,相关的请求参数会被按规则解析并放在请求的 `ctx.action.params` 对象上。Action 参数主要有三个来源: - -1. Action 定义时默认参数 -2. 客户端请求携带 -3. 其他中间件预处理 - -在真正操作处理函数处理之前,上面这三个部分的参数会按此顺序被合并到一起,最终传入操作的执行函数中。在多个中间件中也是如此,上一个中间件处理完的参数会被继续随 `ctx` 传递到下一个中间件中。 - -针对内置的操作可使用的参数,可以参考 [@nocobase/actions](/api/actions) 包的内容。除自定义操作以外,客户端请求主要使用这些参数,自定义的操作可以根据业务需求扩展需要的参数。 - -中间件预处理主要使用 `ctx.action.mergeParams()` 方法,且根据不同的参数类型有不同的合并策略,具体也可以参考 [mergeParams()](/api/resourcer/action#mergeparams) 方法的内容。 - -内置 Action 的默认参数在合并时只能以 `mergeParams()` 方法针对各个参数的默认策略执行,以达到服务端进行一定操作限制的目的。例如: - -```ts -app.resource({ - name: 'posts', - actions: { - create: { - whitelist: ['title', 'content'], - blacklist: ['createdAt', 'createdById'], - } - } -}); -``` - -如上定义了针对 `posts` 资源的 `create` 操作,其中 `whitelist` 和 `blacklist` 分别是针对 `values` 参数的白名单和黑名单,即只允许 `values` 参数中的 `title` 和 `content` 字段,且禁止 `values` 参数中的 `createdAt` 和 `createdById` 字段。 - -### 自定义资源 - -数据型的资源还分为独立资源和关系资源: - -* 独立资源:`` -* 关系资源:`.` - -```ts -// 等同于 app.resourcer.define() - -// 定义文章资源 -app.resource({ - name: 'posts' -}); - -// 定义文章的作者资源 -app.resource({ - name: 'posts.user' -}); - -// 定义文章的评论资源 -app.resource({ - name: 'posts.coments' -}); -``` - -需要自定义的情况主要针对于非数据库表类资源,比如内存中的数据、其他服务的代理接口等,以及需要对已有数据表类资源定义特定操作的情况。 - -例如定义一个与数据库无关的发送通知操作的资源: - -```ts -app.resource({ - name: 'notifications', - actions: { - async send(ctx, next) { - await someProvider.send(ctx.request.body); - next(); - } - } -}); -``` - -则在 HTTP API 中可以这样访问: - -```bash -curl -X POST -d '{"title": "Hello", "to": "hello@nocobase.com"}' 'http://localhost:13000/api/notifications:send' -``` - -## 示例 - -我们继续之前 [数据表与字段示例](/development/server/collections-fields#示例) 中的简单店铺场景,进一步理解资源与操作相关的概念。这里假设我们基于之前数据表的示例进行进一步资源和操作的定义,所以这里不再重复定义数据表的内容。 - -只要定义了对应的数据表,我们对商品、订单等数据资源就可以直接使用默认操作以完成最基础的 CRUD 场景。 - -### 覆盖默认操作 - -有时候,不只是简单的针对单条数据的操作,或者默认操作的参数需要有一定控制,我们就可以覆盖默认的操作。比如我们创建订单时,不应该由客户端提交 `userId` 来代表订单的归属,而是应该由服务端根据当前登录用户来确定订单归属,这时我们就可以覆盖默认的 `create` 操作。对于简单的扩展,我们直接在插件的主类中编写: - -```ts -import { Plugin } from '@nocobase/server'; -import actions from '@nocobase/actions'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - async create(ctx, next) { - ctx.action.mergeParams({ - values: { - userId: ctx.state.user.id - } - }); - - return actions.create(ctx, next); - } - } - }); - } -} -``` - -这样,我们在插件加载过程中针对订单数据资源就覆盖了默认的 `create` 操作,但在修改操作参数以后仍调用了默认逻辑,无需自行编写。修改提交参数的 `mergeParams()` 方法对内置默认操作来说非常有用,我们会在后面介绍。 - -### 数据表资源的自定义操作 - -当内置操作不能满足业务需求时,我们可以通过自定义操作来扩展资源的功能。例如通常一个订单会有很多状态,如果我们对 `status` 字段的取值设计为一系列枚举值: - -* `-1`:已取消 -* `0`:已下单,未付款 -* `1`:已付款,未发货 -* `2`:已发货,未签收 -* `3`:已签收,订单完成 - -那么我们就可以通过自定义操作来实现订单状态的变更,比如对订单进行一个发货的操作,虽然简单的情况下可以通过 `update` 操作来实现,但是如果还有支付、签收等更复杂的情况,仅使用 `update` 会造成语义不清晰且参数混乱的问题,因此我们可以通过自定义操作来实现。 - -首先我们增加一张发货信息表的定义,保存到 `collections/deliveries.ts`: - -```ts -export default { - name: 'deliveries', - fields: [ - { - type: 'belongsTo', - name: 'order' - }, - { - type: 'string', - name: 'provider' - }, - { - type: 'string', - name: 'trackingNumber' - }, - { - type: 'integer', - name: 'status' - } - ] -}; -``` - -同时对订单表也扩展一个发货信息的关联字段(`collections/orders.ts`): - -```ts -export default { - name: 'orders', - fields: [ - // ...other fields - { - type: 'hasOne', - name: 'delivery' - } - ] -}; -``` - -然后我们在插件的主类中增加对应的操作定义: - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - async deliver(ctx, next) { - const { filterByTk } = ctx.action.params; - const orderRepo = ctx.db.getRepository('orders'); - - const [order] = await orderRepo.update({ - filterByTk, - values: { - status: 2, - delivery: { - ...ctx.action.params.values, - status: 0 - } - } - }); - - ctx.body = order; - - next(); - } - } - }); - } -} -``` - -其中,Repository 是使用数据表数据仓库类,大部分进行数据读写的操作都会由此完成,详细可以参考 [Repository API](/api/database/repository) 部分。 - -定义好之后我们从客户端就可以通过 HTTP API 来调用“发货”这个操作了: - -```bash -curl \ - -X POST \ - -H 'Content-Type: application/json' \ - -d '{"provider": "SF", "trackingNumber": "SF1234567890"}' \ - '/api/orders:deliver/' -``` - -同样的,我们还可以定义更多类似的操作,比如支付、签收等。 - -### 参数合并 - -假设我们要让用户可以查询自己的且只能查询自己的订单,同时我们需要限制用户不能查询已取消的订单,那么我们可以通过 action 的默认参数来定义: - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - // list 操作的默认参数 - list: { - filter: { - // 由 users 插件扩展的过滤器运算符 - $isCurrentUser: true, - status: { - $ne: -1 - } - }, - fields: ['id', 'status', 'createdAt', 'updatedAt'] - } - } - }); - } -} -``` - -当用户从客户端查询时,也可以在请求的 URL 上加入其他的参数,比如: - -```bash -curl 'http://localhost:13000/api/orders:list?productId=1&fields=id,status,quantity,totalPrice&appends=product' -``` - -实际的查询条件会合并为: - -```json -{ - "filter": { - "$and": { - "$isCurrentUser": true, - "status": { - "$ne": -1 - }, - "productId": 1 - } - }, - "fields": ["id", "status", "quantity", "totalPrice", "createdAt", "updatedAt"], - "appends": ["product"] -} -``` - -并得到预期的查询结果。 - -另外,如果我们需要对创建订单的接口限制不能由客户端提交订单编号(`id`)、总价(`totalPrice`)等字段,可以通过对 `create` 操作定义默认参数控制: - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - create: { - blacklist: ['id', 'totalPrice', 'status', 'createdAt', 'updatedAt'], - values: { - status: 0 - } - } - } - }); - } -} -``` - -这样即使客户端故意提交了这些字段,也会被过滤掉,不会存在于 `ctx.action.params` 参数集中。 - -如果还要有更复杂的限制,比如只能在商品上架且有库存的情况下才能下单,可以通过配置中间件来实现: - -```ts -import { Plugin } from '@nocobase/server'; - -export default class ShopPlugin extends Plugin { - async load() { - // ... - this.app.resource({ - name: 'orders', - actions: { - create: { - middlewares: [ - async (ctx, next) => { - const { productId } = ctx.action.params.values; - - const product = await ctx.db.getRepository('products').findOne({ - filterByTk: productId, - filter: { - enabled: true, - inventory: { - $gt: 0 - } - } - }); - - if (!product) { - return ctx.throw(404); - } - - await next(); - } - ] - } - } - }); - } -} -``` - -把部分业务逻辑(尤其是前置处理)放到中间件中,可以让我们的代码更加清晰,也更容易维护。 - -## 小结 - -通过上面的示例我们介绍了如何定义资源和相关的操作,回顾一下本章内容: - -* 数据表自动映射为资源 -* 内置默认的资源操作 -* 对资源自定义操作 -* 操作的参数合并顺序与策略 - -本章所涉及到的相关代码放到了一个完整的示例包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。 diff --git a/docs/zh-CN/development/server/test.md b/docs/zh-CN/development/server/test.md deleted file mode 100644 index 936f0aefb..000000000 --- a/docs/zh-CN/development/server/test.md +++ /dev/null @@ -1,166 +0,0 @@ -# 测试 - -测试基于 [Jest](https://jestjs.io/) 测试框架。为了方便的编写测试,提供了 `mockDatabase()` 和 `mockServer()` 用于数据库和服务端应用的测试。 - - -避免破坏生产环境数据,建议使用 `.env.test` 文件来配置测试环境的环境变量,并使用独立的测试数据库进行测试。 - - -## `mockDatabase()` - -默认提供一种完全隔离的 db 测试环境 - -```ts -import { mockDatabase } from '@nocobase/test'; - -describe('my db suite', () => { - let db; - - beforeEach(async () => { - db = mockDatabase(); - db.collection({ - name: 'posts', - fields: [ - { - type: 'string', - name: 'title', - } - ] - }); - await db.sync(); - }); - - afterEach(async () => { - await db.close(); - }); - - test('my case', async () => { - const repository = db.getRepository('posts'); - const post = await repository.create({ - values: { - title: 'hello' - } - }); - - expect(post.get('title')).toEqual('hello'); - }); -}); -``` - -## `mockServer()` - -提供模拟的服务端应用实例,对应的 app.db 为 `mockDatabase()` 实例,同时还提供了便捷的 `app.agent()` 用于测试 HTTP API,针对 NocoBase 的 Resource Action 还封装了 `app.agent().resource()` 用于测试资源的 Action。 - -```ts -import { mockServer } from '@nocobase/test'; - -class MyPlugin extends Plugin { - -} - -describe('my suite', () => { - let app; - let agent; - - beforeEach(async () => { - app = mockServer(); - agent = app.agent(); - // 添加待注册的插件 - app.plugin(MyPlugin, { name: 'my-plugin' }); - // 加载配置 - app.load(); - // 清空数据库并安装 - app.install({ clean: true }); - }); - - afterEach(async () => { - await app.destroy(); - }); - - test('my case', async () => { - await agent.resource('posts').create({ - values: { - title: 'hello' - } - }); - await agent.get('/users:check').set({ Authorization: 'Bearer abc' }); - }); -}); -``` - -## 示例 - -我们以之前在 [资源与操作](resources-actions) 章节的功能为例,来写一个插件的测试: - -```ts -import { mockServer } from '@nocobase/test'; -import Plugin from '../../src/server'; - -describe('shop actions', () => { - let app; - let agent; - let db; - - beforeEach(async () => { - app = mockServer(); - app.plugin(Plugin); - agent = app.agent(); - db = app.db; - - await app.load(); - await db.sync(); - }); - - afterEach(async () => { - await app.destroy(); - }); - - test('product order case', async () => { - const { body: product } = await agent.resource('products').create({ - values: { - title: 'iPhone 14 Pro', - price: 7999, - enabled: true, - inventory: 1 - } - }); - expect(product.data.price).toEqual(7999); - - const { body: order } = await agent.resource('orders').create({ - values: { - productId: product.data.id - } - }); - expect(order.data.totalPrice).toEqual(7999); - expect(order.data.status).toEqual(0); - - const { body: deliveredOrder } = await agent.resource('orders').deliver({ - filterByTk: order.data.id, - values: { - provider: 'SF', - trackingNumber: '123456789' - } - }); - expect(deliveredOrder.data.status).toBe(2); - expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789'); - }); -}); -``` - -编写完成后,在命令行中允许测试命令: - -```bash -yarn test packages/samples/shop-actions -``` - -该测试将验证: - -1. 商品可以创建成功; -2. 订单可以创建成功; -3. 订单可以发货成功; - -当然这只是个最基本的例子,从业务上来说并不完善,但作为示例已经可以说明整个测试的流程。 - -## 小结 - -本章涉及的示例代码整合在对应的包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。 diff --git a/docs/zh-CN/development/your-fisrt-plugin.md b/docs/zh-CN/development/your-fisrt-plugin.md deleted file mode 100644 index 5227c9d8d..000000000 --- a/docs/zh-CN/development/your-fisrt-plugin.md +++ /dev/null @@ -1,136 +0,0 @@ -# 编写第一个插件 - -在开始前,需要先安装好 NocoBase: - -- [create-nocobase-app 安装](/welcome/getting-started/installation/create-nocobase-app) -- [Git 源码安装](/welcome/getting-started/installation/git-clone) - -安装好 NocoBase 之后,我们就可以开始插件开发之旅了。 - -## 创建插件 - -通过 CLI 快速地创建一个空插件,命令如下: - -```bash -yarn pm create @my-project/plugin-hello -``` - -插件所在目录 `packages/plugins/@my-project/plugin-hello`,插件目录结构为: - -```bash -|- /packages/plugins/@my-project/plugin-hello - |- /src - |- /client # 插件客户端代码 - |- /server # 插件服务端代码 - |- client.d.ts - |- client.js - |- package.json # 插件包信息 - |- server.d.ts - |- server.js -``` - -访问插件管理器界面,查看刚添加的插件,默认地址为 http://localhost:13000/admin/pm/list/local/ - - - -如果创建的插件未在插件管理器里显示,可以通过 `pm add` 命令手动添加 - -```bash -yarn pm add @my-project/plugin-hello -``` - -## 编写插件 - -查看 `packages/plugins/@my-project/plugin-hello/src/server/plugin.ts` 文件,并修改为: - -```ts -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class PluginHelloServer extends Plugin { - afterAdd() {} - - beforeLoad() {} - - async load() { - this.db.collection({ - name: 'hello', - fields: [{ type: 'string', name: 'name' }], - }); - this.app.acl.allow('hello', '*'); - } - - async install(options?: InstallOptions) {} - - async afterEnable() {} - - async afterDisable() {} - - async remove() {} -} - -export default PluginHelloServer; -``` - -## 激活插件 - -**通过命令操作** - -```bash -yarn pm enable @my-project/plugin-hello -``` - -**通过界面操作** - -访问插件管理器界面,查看刚添加的插件,点击激活。 -插件管理器页面默认为 http://localhost:13000/admin/pm/list/local/ - - - -备注:插件激活时,会自动创建刚才编辑插件配置的 hello 表。 - -## 调试插件 - -如果应用未启动,需要先启动应用 - -```bash -# for development -yarn dev - -# for production -yarn build -yarn start -``` - -向插件的 hello 表里插入数据 - -```bash -curl --location --request POST 'http://localhost:13000/api/hello:create' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "Hello world" -}' -``` - -查看 hello 表数据 - -```bash -curl --location --request GET 'http://localhost:13000/api/hello:list' -``` - -## 构建并打包插件 - -```bash -yarn build plugins/@my-project/plugin-hello --tar - -# 分步骤 -yarn build plugins/@my-project/plugin-hello -yarn nocobase tar plugins/@my-project/plugin-hello -``` - -打包的插件默认保存路径为 `storage/tar/@my-project/plugin-hello.tar.gz` - -## 上传至其他 NocoBase 应用 - -仅 v0.14 及以上版本支持 - - diff --git a/docs/zh-CN/index.md b/docs/zh-CN/index.md deleted file mode 100644 index 49a1e7610..000000000 --- a/docs/zh-CN/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Index - - diff --git a/docs/zh-CN/manual/blocks-guide/charts.md b/docs/zh-CN/manual/blocks-guide/charts.md deleted file mode 100755 index 4d406649a..000000000 --- a/docs/zh-CN/manual/blocks-guide/charts.md +++ /dev/null @@ -1,212 +0,0 @@ -# 图表 - -目前,NocoBase 图表区块需要通过配置文件或编写代码来实现。图表库使用的是 [g2plot](https://g2plot.antv.vision/en/examples),理论上支持 https://g2plot.antv.vision/en/examples 上的所有图表。目前可以配置的图表包括: - -- 柱状图 -- 条形图 -- 折线图 -- 饼图 -- 面积图 - -## 添加和编辑图表 - -![chart-edit.gif](./charts/chart-edit.gif) - -## 图表配置 - -初始化的图表配置是静态的 JSON 数据 - -```json -{ - "data": [ - { - "type": "furniture & appliances", - "sales": 38 - }, - { - "type": "食品油副食", - "sales": 52 - }, - { - "type": "Fresh Fruit", - "sales": 61 - }, - { - "type": "美容洗护", - "sales": 145 - }, - { - "type": "Maternity & Baby Products", - "sales": 48 - }, - { - "type": "Imported Food", - "sales": 38 - }, - { - "type": "Food & Beverage", - "sales": 38 - }, - { - "type": "Home Cleaning", - "sales": 38 - } - ], - "xField": "type", - "yField": "sales", - "label": { - "position": "middle", - "style": { - "fill": "#FFFFFF", - "opacity": 0.6 - } - }, - "xAxis": { - "label": { - "autoHide": true, - "autoRotate": false - } - }, - "meta": { - "type": { - "alias": "category" - }, - "sales": { - "alias": "sales" - } - } -} - -``` - -data 支持表达式的写法,NocoBase 内置了 `requestChartData(config)` 函数,用于自定义图表数据的请求。Config 参数说明见: [https://github.com/axios/axios#request-config](https://github.com/axios/axios#request-config) - -示例: - -```json -{ - "data": "{{requestChartData({ url: 'collectionName:getColumnChartData' })}}", - "xField": "type", - "yField": "sales", - "label": { - "position": "middle", - "style": { - "fill": "#FFFFFF", - "opacity": 0.6 - } - }, - "xAxis": { - "label": { - "autoHide": true, - "autoRotate": false - } - }, - "meta": { - "type": { - "alias": "category" - }, - "sales": { - "alias": "sales" - } - } -} - -``` - -HTTP API 示例: - -```bash -GET /api/collectionName:getColumnChartData - -Response Body -{ - "data": [ - { - "type": "furniture & appliances", - "sales": 38 - }, - { - "type": "食品油副食", - "sales": 52 - }, - { - "type": "Fresh Fruit", - "sales": 61 - }, - { - "type": "美容洗护", - "sales": 145 - }, - { - "type": "Maternity & Baby Products", - "sales": 48 - }, - { - "type": "Imported Food", - "sales": 38 - }, - { - "type": "Food & Beverage", - "sales": 38 - }, - { - "type": "Home Cleaning", - "sales": 38 - } - ] -} - -``` - -## Server 端实现 - -为名为 collectionName 的数据表,添加自定义的 getColumnChartData 方法: - -```js -app.resourcer.registerActionHandlers({ - 'collectionName:getColumnChartData': (ctx, next) => { - // The data to be output - ctx.body = []; - await next(); - }, -}); - -``` - -## 视频 - -### 静态数据 - - - - -### 动态数据 - - - - -### 更多图表 -理论上支持 https://g2plot.antv.vision/en/examples 上的所有图表 - - - -## JS 表达式 - -Syntax - -```js -{ - "key1": "{{ js expression }}" -} -``` - - - diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-Dynamic-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-Dynamic-1.m4v deleted file mode 100644 index 76d8c994b..000000000 Binary files a/docs/zh-CN/manual/blocks-guide/charts/Chart-Dynamic-1.m4v and /dev/null differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-Js-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-Js-1.m4v deleted file mode 100644 index 482b078eb..000000000 Binary files a/docs/zh-CN/manual/blocks-guide/charts/Chart-Js-1.m4v and /dev/null differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-More-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-More-1.m4v deleted file mode 100644 index 944d8da25..000000000 Binary files a/docs/zh-CN/manual/blocks-guide/charts/Chart-More-1.m4v and /dev/null differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-Static-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-Static-1.m4v deleted file mode 100644 index 8009d7a07..000000000 Binary files a/docs/zh-CN/manual/blocks-guide/charts/Chart-Static-1.m4v and /dev/null differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/chart-edit.gif b/docs/zh-CN/manual/blocks-guide/charts/chart-edit.gif deleted file mode 100755 index 86a3540a3..000000000 Binary files a/docs/zh-CN/manual/blocks-guide/charts/chart-edit.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/a-b-c.md b/docs/zh-CN/manual/core-concepts/a-b-c.md deleted file mode 100755 index 0c707572f..000000000 --- a/docs/zh-CN/manual/core-concepts/a-b-c.md +++ /dev/null @@ -1,21 +0,0 @@ -# A·B·C - -在无代码层面,NocoBase 的核心概念可以总结为 `A·B·C`。 - -`A·B·C` 是`Action·Block·Collection` 的缩写,即`操作·区块·数据表`。通过 `Collection` 设计数据结构,通过 `Block` 组织与展示数据,通过 `Action` 交互数据。 - -## 数据与视图分离 - -定义数据时,专注于定义数据;定义视图时,专注于定义视图。 - -通过定义数据,来抽象业务;再通过定义区块去组织内容以你所期望的方式呈现数据。 - -## 一种数据,多种呈现 - -为业务抽象出统一的数据模型,然后通过区块可以为同一个数据表建立各种各样的呈现方式,用于不同的场景、不同的角色、不同的组合。 - -## 操作驱动 - -数据表来定义数据的结构,区块来组织数据的呈现方式。那么,什么驱动数据的交互和变更?答案是操作。 - -区块将数据呈现给用户,操作则是将用户的指令发送给服务器完成数据的交互或变更。 diff --git a/docs/zh-CN/manual/core-concepts/actions.md b/docs/zh-CN/manual/core-concepts/actions.md deleted file mode 100755 index e47ce928c..000000000 --- a/docs/zh-CN/manual/core-concepts/actions.md +++ /dev/null @@ -1,31 +0,0 @@ -# 操作 - -`操作`是完成某个特定目标的动作集合。在 NocoBase 中通过 `操作`来处理数据或者与服务器通信。 操作通常会通过点击某个按钮触发。 - -## 操作类型 - -NocoBase 目前支持 10 几种操作,未来可以通过插件的方式支持更多种。 - -| 名称 | 描述 | -| --- | --- | -| 筛选 | 指定数据的显示范围 | -| 添加 | 打开添加新数据的弹窗,在弹窗里通常包含一个表单区块 | -| 查看 | 打开查看指定数据的弹窗,在弹窗里通常包含一个详情区块 | -| 编辑 | 打开修改指定数据的弹窗,在弹窗里通常包含一个表单区块 | -| 删除 | 打开删除指定数据的对话框,确认后删除 | -| 导出 | 将数据导出为 Excel,常和筛选组合使用 | -| 打印 | 打开浏览器打印窗口,打印指定的数据,常和详情区块组合使用 | -| 提交 | 将指定表单区块的数据提交到服务端 | -| 刷新 | 刷新当前区块内的数据 | -| 导入 | 从 Excel 模板中导入数据 | -| 批量编辑 | 批量编辑数据 | -| 批量更新 | 批量更新数据 | -| 打开弹窗 | 打开弹窗或抽屉,在里面可以放置区块 | -| 更新数据 | 点击后自动更新指定的字段 | -| 自定义请求 | 向第三方发送请求 | - -## 配置操作 - -在界面配置模式下,将鼠标移到操作按钮上,右上角就会出现该操作支持的配置项。比如筛选操作: - -![action-config-5.jpg](./actions/action-config-5.jpg) \ No newline at end of file diff --git a/docs/zh-CN/manual/core-concepts/actions/action-config-5.jpg b/docs/zh-CN/manual/core-concepts/actions/action-config-5.jpg deleted file mode 100755 index b9429826f..000000000 Binary files a/docs/zh-CN/manual/core-concepts/actions/action-config-5.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks.md b/docs/zh-CN/manual/core-concepts/blocks.md deleted file mode 100755 index 68ff9eae8..000000000 --- a/docs/zh-CN/manual/core-concepts/blocks.md +++ /dev/null @@ -1,87 +0,0 @@ -# 区块 - -区块是用来展示和操作数据的视图。在 NocoBase 里,将页面、弹窗、抽屉看作是区块的容器,容器就像一张画布,在里面可以放置各种各样的区块。 - -得益于 NocoBase 将数据与视图分离的设计,页面通过区块承载数据,并根据不同的区块类型,以不同的形式组织和管理数据。 - -## 区块结构 - -一个完整的区块由三部分组成: - -1. 内容区:区块的主体 -2. 操作区:可以放置各种操作按钮,用于操作区块数据 -3. 配置区:操作区块配置的按钮 - -![6.block.jpg](./blocks/6.block.jpg) - -## 区块类型 - -![add-block.jpg](./blocks/add-block.jpg) - -NocoBase 目前内置 10 几种区块,未来可以通过插件的方式支持更多种。 - -- **数据区块**:为组织数据而设计的区块。 - - **表格**:以表格形式展示多条数据的区块,既可以展示一个数据表,也可以展示相互之间有关联关系的多个数据表。 - - **表单**:以各种类型的输入框录入或编辑数据的区块,既可以为某一个数据表进行录入,也可以对相互之间有关联关系的多个数据表统一录入。 - - **详情**:展示一条特定数据的区块,既可以对某一个数据表的某一条数据进行展示,也可以对相互之间有关联关系的多个数据表中的多条数据统一展示。 - - **日历**:以日历的形式展示多条数据的区块,适合某些在日期上具备重要特征的数据。 - - **看板**:以看板的形式展示多条数据的区块,适合用来对生产过程进行管理。 -- **图表区块**:为图形化展示统计数据而设计的区块。目前支持:柱状图、条形图、折线图、饼图、面积图等。 -- **其他区块**:为展示特殊数据而设计的区块。 - - **Markdown**:用 Markdown 书写的文本内容。 - - **操作记录**:展示一个数据表中的所有数据的变更记录,包括新建、编辑和删除。 - -## 添加区块 - -进入界面配置模式,在页面和弹窗内点击 Add block 按钮即可添加区块。选项分为 4 步: - -1. 选择区块类型:目前可用的区块类型包括表格、表单、详情、日历、看板、Markdown -2. 选择 Collection:此处会列出所有的 Collection -3. 选择创建方式:创建空白区块,或者从复制区块模板,或者引用区块模板 -4. 选择模板:若第 3 步选择了从模板创建,则在第 4 步选择模板 - -![6.block-add.jpg](./blocks/6.block-add.jpg) - -## 配置区块 - -配置区块包括三方面的内容: - -- 配置区块内容 -- 配置区块操作 -- 配置区块属性 - -### 配置区块内容 - -以表格区块为例,区块内容是指表格中要显示的列。点击 Configure columns 即可配置要显示的列: - -![6.block-content.gif](./blocks/6.block-content.gif) - -### 配置区块操作 - -以表格区块为例,有筛选、添加、删除、查看、编辑、自定义等操作可选。点击 Configure actions 按钮可以配置操作。其中,每个操作按钮都可以单独配置属性: - -![6.block-content.gif](./blocks/6.block-content1.gif) - -### 配置区块属性 - -将光标移到区块右上角,会看到区块配置按钮。以表格区块为例,可以配置的属性有: - -- Block title -- Drag & drop sorting -- Set the data scope -- Set default sorting rules -- Records per page - -## 调整布局 - -页面内既可以只放一个区块,也可以放多个区块进行组合。你可以通过拖拽完成区块位置和宽度的调整。 - -![block-drag.gif](./blocks/block-drag.gif) - -## 区块模板 - -你可以将一个区块保存为模板,以后可以复制或引用这个模板。 - -比如,一个数据表的表单,既用于新增数据,又用于编辑数据,那就可以将这个表单保存为模板,在新增数据和编辑数据的界面里引用它。 - -![block-template.jpg](./blocks/block-template.jpg) diff --git a/docs/zh-CN/manual/core-concepts/blocks/6.block-add.jpg b/docs/zh-CN/manual/core-concepts/blocks/6.block-add.jpg deleted file mode 100755 index cd609413f..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/6.block-add.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/6.block-content.gif b/docs/zh-CN/manual/core-concepts/blocks/6.block-content.gif deleted file mode 100755 index 8f79be9fa..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/6.block-content.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/6.block-content1.gif b/docs/zh-CN/manual/core-concepts/blocks/6.block-content1.gif deleted file mode 100755 index 8f79be9fa..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/6.block-content1.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/6.block.jpg b/docs/zh-CN/manual/core-concepts/blocks/6.block.jpg deleted file mode 100755 index b609af5a0..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/6.block.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/6.collection-setting.gif b/docs/zh-CN/manual/core-concepts/blocks/6.collection-setting.gif deleted file mode 100755 index d969e34e8..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/6.collection-setting.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/add-block.jpg b/docs/zh-CN/manual/core-concepts/blocks/add-block.jpg deleted file mode 100755 index 2b463f2cf..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/add-block.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/block-drag.gif b/docs/zh-CN/manual/core-concepts/blocks/block-drag.gif deleted file mode 100755 index 69563d0dc..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/block-drag.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/block-template.jpg b/docs/zh-CN/manual/core-concepts/blocks/block-template.jpg deleted file mode 100755 index 86da281e0..000000000 Binary files a/docs/zh-CN/manual/core-concepts/blocks/block-template.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/collections.md b/docs/zh-CN/manual/core-concepts/collections.md deleted file mode 100755 index 2b2cfe9e4..000000000 --- a/docs/zh-CN/manual/core-concepts/collections.md +++ /dev/null @@ -1,59 +0,0 @@ -# 数据表 - -开发一个系统之前,我们通常要对业务进行抽象,建立数据模型。NocoBase 的数据表由字段(列)和记录(行)组成。数据表的概念与关系型数据库的数据表概念相近,但是字段的概念略有不同。 - -例如,在一个描述订单的数据表中,每列包含的是订单某个特定属性的信息,如收件地址;而每行则包含了某个特定订单的所有信息,如订单号、顾客姓名、电话、收件地址等。 - -## 数据与视图分离 - -NocoBase 的`数据`和`视图`是分离的,分别由数据表和区块来管理和呈现。 - -这就意味着: - -- 你可以创建**一个**数据表,并为其设计**一套**界面,实现数据的展示和操作; -- 你也可以创建**一个**数据表,然后为其设计**多套**界面,用于不同的场景或角色下对数据的展示和操作; -- 你还可以创建**多个**数据表,然后为其设计**一套**界面,实现多个数据表的同时展示和操作; -- 你甚至可以创建**多个**数据表,然后为其设计**多套**界面,每套界面都可以操作多个数据表并完成独特的功能; - -简单说,数据与界面的分离使得**数据的组织和管理更加灵活**,如何呈现数据就看你如何配置界面。 - -## 字段类型 - -NocoBase 目前支持以下几十种字段,未来可以通过插件的方式支持更多种。 - -| 名称 | 类型 | -| --- | --- | -| 单行文本 | 基本类型 | -| 图标 | 基本类型 | -| 多行文本 | 基本类型 | -| 密码 | 基本类型 | -| 手机号码 | 基本类型 | -| 数字 | 基本类型 | -| 整数 | 基本类型 | -| 电子邮箱 | 基本类型 | -| 百分比 | 基本类型 | -| 下拉菜单(单选) | 选择类型 | -| 下拉菜单(多选) | 选择类型 | -| 中国行政区 | 选择类型 | -| 勾选 | 选择类型 | -| 单选框 | 选择类型 | -| 复选框 | 选择类型 | -| 关联 | 关系类型 | -| 一对一(belongs to) | 关系类型 | -| 一对一(has one) | 关系类型 | -| 一对多 | 关系类型 | -| 多对一 | 关系类型 | -| 多对多 | 关系类型 | -| 公式 | 高级类型 | -| 自动编码 | 高级类型 | -| JSON | 高级类型 | -| Markdown | 多媒体 | -| 富文本 | 多媒体 | -| 附件 | 多媒体 | -| 日期 | 日期&时间 | -| 时间 | 日期&时间 | -| ID | 系统信息 | -| 创建人 | 系统信息 | -| 创建日期 | 系统信息 | -| 最后修改人 | 系统信息 | -| 最后修改日期 | 系统信息 | \ No newline at end of file diff --git a/docs/zh-CN/manual/core-concepts/containers.md b/docs/zh-CN/manual/core-concepts/containers.md deleted file mode 100755 index a6a3bda29..000000000 --- a/docs/zh-CN/manual/core-concepts/containers.md +++ /dev/null @@ -1,23 +0,0 @@ -# 容器 - -在 NocoBase 里,将页面、弹窗、抽屉看作是区块的容器,容器就像一张画布,在里面可以放置各种各样的区块 - -## 页面 - -![container-page.jpg](./containers/container-page.jpg) - -## 弹窗 - -![container-dialog.jpg](./containers/container-dialog.jpg) - -## 抽屉 - -![container-drawer.jpg](./containers/container-drawer.jpg) - -## 容器内支持标签页 - -在弹窗、抽屉、页面内,可以添加多个标签页。向每个标签页里添加不同的区块,从而显示不同的内容和操作。比如,在一个顾客信息的弹窗里,添加 3 个标签页,分别用来显示顾客的个人信息、订单记录、顾客评价: - -![7.tabs.gif](./containers/7.tabs.gif) - -![container-tab-2.jpg](./containers/container-tab-2.jpg) diff --git a/docs/zh-CN/manual/core-concepts/containers/7.tabs.gif b/docs/zh-CN/manual/core-concepts/containers/7.tabs.gif deleted file mode 100755 index df7c895aa..000000000 Binary files a/docs/zh-CN/manual/core-concepts/containers/7.tabs.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/containers/container-dialog.jpg b/docs/zh-CN/manual/core-concepts/containers/container-dialog.jpg deleted file mode 100755 index b5984954d..000000000 Binary files a/docs/zh-CN/manual/core-concepts/containers/container-dialog.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/containers/container-drawer.jpg b/docs/zh-CN/manual/core-concepts/containers/container-drawer.jpg deleted file mode 100755 index b550ea29e..000000000 Binary files a/docs/zh-CN/manual/core-concepts/containers/container-drawer.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/containers/container-page.jpg b/docs/zh-CN/manual/core-concepts/containers/container-page.jpg deleted file mode 100755 index c765777ea..000000000 Binary files a/docs/zh-CN/manual/core-concepts/containers/container-page.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/containers/container-tab-2.jpg b/docs/zh-CN/manual/core-concepts/containers/container-tab-2.jpg deleted file mode 100755 index 07b1a982b..000000000 Binary files a/docs/zh-CN/manual/core-concepts/containers/container-tab-2.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/menus.md b/docs/zh-CN/manual/core-concepts/menus.md deleted file mode 100755 index f8c14f25b..000000000 --- a/docs/zh-CN/manual/core-concepts/menus.md +++ /dev/null @@ -1,43 +0,0 @@ -# 菜单 - -目前 NocoBase 支持三种类型的菜单项: - -- 页面:跳转至菜单关联的 NocoBase 的页面; -- 分组:对菜单进行分组,将同类菜单放到统一的位置; -- 链接:跳转至指定的 URL; - -以仓储系统为例,如果你的业务里有储位管理,储位管理里又包含出入库日志、库存查询、跳转 ERP 申请储位等功能。那么可以这样设置菜单: - -``` -- 储位管理(分组) - - 库存查询(页面) - - 出入库日志(页面) - - 跳转ERP申请储位(链接) -``` - -## 默认位置 - -在 NocoBase 内置的页面模板中,菜单会出现在顶部和左侧。 - -![menu-position.jpg](./menus/menu-position.jpg) - -## 添加 - -![5.menu-add.jpg](./menus/5.menu-add.jpg) - -点击 Add menu item,选择添加的类型。支持无限级子菜单。 - -## 配置和排序 - -将光标移到菜单项上,右上角会出现排序和配置按钮。按住排序按钮,可以拖拽排序。 - -对菜单项可操作的配置: - -- Edit -- Move to -- Insert before -- Insert after -- Insert Inner -- Delete - -![menu-move.gif](./menus/menu-move.gif) diff --git a/docs/zh-CN/manual/core-concepts/menus/5.menu-add.jpg b/docs/zh-CN/manual/core-concepts/menus/5.menu-add.jpg deleted file mode 100755 index da89a6f93..000000000 Binary files a/docs/zh-CN/manual/core-concepts/menus/5.menu-add.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/menus/menu-move.gif b/docs/zh-CN/manual/core-concepts/menus/menu-move.gif deleted file mode 100755 index 1e278d868..000000000 Binary files a/docs/zh-CN/manual/core-concepts/menus/menu-move.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/menus/menu-position.jpg b/docs/zh-CN/manual/core-concepts/menus/menu-position.jpg deleted file mode 100755 index 8e21a0a18..000000000 Binary files a/docs/zh-CN/manual/core-concepts/menus/menu-position.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/functional-zoning.md b/docs/zh-CN/manual/quick-start/functional-zoning.md deleted file mode 100755 index b6846a853..000000000 --- a/docs/zh-CN/manual/quick-start/functional-zoning.md +++ /dev/null @@ -1,9 +0,0 @@ -# 功能分区 - -NocoBase 默认内置一个布局模板,这个布局模板的界面主要分为三个区域: - -1. 配置入口区。具备系统配置权限的用户,可以在这里看到界面配置、插件管理器、设置中心的入口。 -2. 菜单区。顶部是一级菜单,左侧是二级及以下层级的菜单。每个菜单项都可以配置为菜单分组、页面、外部链接。 -3. 区块容器。这里是页面的区块容器,在里面可以放置各种各样的区块。 - -![3.zone.jpg](./functional-zoning/3.zone.jpg) \ No newline at end of file diff --git a/docs/zh-CN/manual/quick-start/functional-zoning/3.zone.jpg b/docs/zh-CN/manual/quick-start/functional-zoning/3.zone.jpg deleted file mode 100755 index 9507d2c7e..000000000 Binary files a/docs/zh-CN/manual/quick-start/functional-zoning/3.zone.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/plugins/plugin-manager.jpg b/docs/zh-CN/manual/quick-start/plugins/plugin-manager.jpg deleted file mode 100755 index ff17fe5e1..000000000 Binary files a/docs/zh-CN/manual/quick-start/plugins/plugin-manager.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app.md b/docs/zh-CN/manual/quick-start/the-first-app.md deleted file mode 100755 index af30f0bbf..000000000 --- a/docs/zh-CN/manual/quick-start/the-first-app.md +++ /dev/null @@ -1,96 +0,0 @@ -# 第一个 APP - -让我们用 NocoBase 搭建一个订单管理系统。 - -## 1. 创建数据表和字段 - -在这个订单管理系统中,我们需要掌握`Customers`、`Products`、`Orders`的信息,他们彼此之间互相关联。经过分析,我们需要建立 4 个数据表,它们的字段分别为: - -- Customers - - 姓名 - - 地址 - - 性别 - - 电话 - - *订单(购买过的所有订单,数据来自`Orders`,每条顾客数据包含多条订单数据)* -- Products - - 商品名称 - - 描述 - - 图片 - - 价格 -- Orders - - 订单编号 - - 总价 - - 备注 - - 地址 - - *顾客(该订单所属的顾客,数据来自`Customers`,每条订单数据属于一条顾客数据)* - - *订单明细(该订单中的商品,数据来自`Order Details`,每条订单数据包含多条订单明细数据)* -- Order list - - *订单(该明细所属的订单,数据来自`Orders`,每条订单明细数据属于一条订单数据)* - - *商品(该明细所包含的商品,数据来自`Products`,每条订单明细数据包含一条商品数据)* - - 数量 - -其中,斜体的字段是关系字段,关联到其他数据表。 - -接下来,点击“数据表配置”按钮,进入数据表配置界面,创建第一个 Collection `Customers`。 - -![create-collection.gif](./the-first-app/create-collection.gif) - -然后点击“字段配置”,为`Customers` 添加 name 字段,它是单行文本类型。 - -![create-field.gif](./the-first-app/create-field.gif) - -用同样的方法,为`Customers` 添加 Address、Gender、Phone,它们分别是单行文本、单项选择类型、手机号码类型。 - -![fields-list.jpg](./the-first-app/fields-list.jpg) - -用同样的方法,创建 Collection `Products`、`Orders`、`Order list` 以及它们的字段。 - - - -![collection-list.jpg](./the-first-app/collection-list.jpg) - -其中,对于关系字段,如果你不熟悉一对多、多对多等概念,可以直接使用 Link to 类型来建立数据表之间的关联。如果你熟悉这几个概念,请正确使用 One to many, Many to one 等类型来建立数据表之间的关联。比如在这个例子中,我们将 `Orders` 与 `Order list` 关联,关系为 One to many。 - -![collection-list.jpg](./the-first-app/order-list-relation.jpg) - - -在图形化的数据表里,可以很直观的看出各个表之间的关系。(注:Graph-collection 插件暂未开源) - -![graph-collection.jpg](./the-first-app/graph-collection.jpg) - - -将数据表和字段创建完成后,我们开始制作界面。 - -## 2. 配置菜单和页面 - -我们需要顾客、订单、商品三个页面展示和管理我们的数据。 - -点击界面配置按钮,进入界面配置模式。在界面配置模式下,我们可以添加菜单项,添加页面,在页面内布置区块。 - -![1.editor.gif](./the-first-app/1.editor.gif) - -点击添加菜单项,添加菜单分组 “Customers” 和 “Orders & Products” ,然后添加子菜单页面 “All Orders” 和 “Products”。 - -![1.menu.gif](./the-first-app/1.menu.gif) - -添加完菜单和页面之后,我们可以在页面内添加和配置区块了。 - -## 3. 添加和配置区块 - -NocoBase 目前支持表格、看板、日历、表单、详情等类型的区块,它们可以将数据表中的数据展示出来,并可以对数据进行操作。显然,顾客、订单、商品 都适合用表格的方式展示和操作。 - -我们在“所有订单”页面,添加一个表格区块,数据源选择 Collection `Orders` ,并为这个表格区块配置需要显示的列。 - -![1.block.gif](./the-first-app/1.block.gif) - -给这个表格区块配置操作,包括筛选、添加、删除、查看、编辑。 - -![1.action.gif](./the-first-app/1.action.gif) - -为新增、编辑、查看等操作配置表单和详情区块。 - -![1.action-block.gif](./the-first-app/1.action-block.gif) - -然后,用同样的方法,在 Products 和 Customers 页面布置表格区块。完成后,退出界面配置模式,进入使用模式,一个简单的订单管理系统就完成了。 - -![demo-finished.jpg](./the-first-app/demo-finished.jpg) diff --git a/docs/zh-CN/manual/quick-start/the-first-app/1.action-block.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.action-block.gif deleted file mode 100755 index db4d312de..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/1.action-block.gif and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/1.action.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.action.gif deleted file mode 100755 index f8a8f2ba6..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/1.action.gif and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/1.block.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.block.gif deleted file mode 100755 index b2cbbcb79..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/1.block.gif and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/1.editor.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.editor.gif deleted file mode 100755 index d5e7a84df..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/1.editor.gif and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/1.menu.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.menu.gif deleted file mode 100755 index d1f43c62a..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/1.menu.gif and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/collection-list.jpg b/docs/zh-CN/manual/quick-start/the-first-app/collection-list.jpg deleted file mode 100755 index b291ed3b8..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/collection-list.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/create-collection.gif b/docs/zh-CN/manual/quick-start/the-first-app/create-collection.gif deleted file mode 100755 index d8eae060c..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/create-collection.gif and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/create-field.gif b/docs/zh-CN/manual/quick-start/the-first-app/create-field.gif deleted file mode 100755 index ba3f95278..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/create-field.gif and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/demo-finished.jpg b/docs/zh-CN/manual/quick-start/the-first-app/demo-finished.jpg deleted file mode 100755 index b880c89de..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/demo-finished.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/fields-list.jpg b/docs/zh-CN/manual/quick-start/the-first-app/fields-list.jpg deleted file mode 100755 index 13a382880..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/fields-list.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/graph-collection.jpg b/docs/zh-CN/manual/quick-start/the-first-app/graph-collection.jpg deleted file mode 100644 index 682fb9320..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/graph-collection.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/order-list-relation.jpg b/docs/zh-CN/manual/quick-start/the-first-app/order-list-relation.jpg deleted file mode 100644 index 64c02fdd2..000000000 Binary files a/docs/zh-CN/manual/quick-start/the-first-app/order-list-relation.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode.md b/docs/zh-CN/manual/quick-start/ui-editor-mode.md deleted file mode 100755 index 56a777db7..000000000 --- a/docs/zh-CN/manual/quick-start/ui-editor-mode.md +++ /dev/null @@ -1,51 +0,0 @@ -# 界面配置模式 - -NocoBase 采用所见即所得的方式来配置界面。点击右上角的`UI Editor`按钮,即可切换配置模式和使用模式。进入配置模式之后,界面上各处将会出现橙色的配置入口。 - -![ui-editor.gif](./ui-editor-mode/ui-editor.gif) - -通常,配置项入口会出现在元素的右上角。 - -## 菜单项配置 - -将鼠标移到菜单项上,在右上角即可以看到拖拽排序按钮、配置项按钮,可以编辑、移动、插入、删除。 - -![menu-config.jpg](./ui-editor-mode/menu-config.jpg) - -## 区块配置 - -将鼠标移到一个区块上,在右上角即可以看到拖拽排序按钮、新增区块按钮、配置项按钮。 - -![block-config.jpg](./ui-editor-mode/block-config.jpg) - -不同的区块还会有一些自己独有的配置项。比如表格区块,将鼠标移到表头上即可以在右上角看到表头的配置项;在表头最右侧还可以看到表格列的配置项。 - -![block-config-2.jpg](./ui-editor-mode/block-config-2.jpg) - -![block-config-3.jpg](./ui-editor-mode/block-config-3.jpg) - -## 操作配置 - -在区块中可以看到操作的配置入口,这些入口在不同的区块里会出现在不同的位置。 - -比如表格区块,在右上方可以看到针对表格数据的操作: - -![action-config-1.jpg](./ui-editor-mode/action-config-1.jpg) - -在操作列的表头里可以看到针对单行数据的操作: - -![action-config-2.jpg](./ui-editor-mode/action-config-2.jpg) - -在详情区块的右上角可以看到针对详情的操作: - -![action-config-3.jpg](./ui-editor-mode/action-config-3.jpg) - -在表单区块底部可以看到针对表单的操作: - -![action-config-4.jpg](./ui-editor-mode/action-config-4.jpg) - -## 标签页配置 - -在弹窗或抽屉里,可以添加多个标签页,用于承载不同的区块。 - -![tab-config.jpg](./ui-editor-mode/tab-config.jpg) diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-1.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-1.jpg deleted file mode 100755 index c31a20f7a..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-1.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-2.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-2.jpg deleted file mode 100755 index 22b14119f..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-2.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-3.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-3.jpg deleted file mode 100755 index 59e5a2527..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-3.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-4.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-4.jpg deleted file mode 100755 index f799395bf..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-4.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-2.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-2.jpg deleted file mode 100755 index 0e041e35e..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-2.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-3.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-3.jpg deleted file mode 100755 index 0b1244cc0..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-3.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config.jpg deleted file mode 100755 index e1a94876c..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/menu-config.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/menu-config.jpg deleted file mode 100755 index 8765202f0..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/menu-config.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/tab-config.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/tab-config.jpg deleted file mode 100755 index ac46ee284..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/tab-config.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/ui-editor.gif b/docs/zh-CN/manual/quick-start/ui-editor-mode/ui-editor.gif deleted file mode 100755 index 0ea3bf781..000000000 Binary files a/docs/zh-CN/manual/quick-start/ui-editor-mode/ui-editor.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/community/contributing.md b/docs/zh-CN/welcome/community/contributing.md deleted file mode 100644 index 76c982967..000000000 --- a/docs/zh-CN/welcome/community/contributing.md +++ /dev/null @@ -1,48 +0,0 @@ -# 贡献 - -- Fork 源代码到自己的仓库 -- 修改源代码 -- 提交 Pull Request -- 签署 CLA - -## 下载项目 - -```bash -# 替换为自己的仓库地址 -git clone https://github.com/nocobase/nocobase.git -cd nocobase -yarn install -``` - -## 应用开发与测试 - -```bash -# 安装并启动应用 -yarn dev -# 运行所有测试 -yarn test -# 运行文件夹下所有测试文件 -yarn test -# 运行单个测试文件 -yarn test -``` - -## 文档预览 - -```bash -# 启动文档 -yarn doc --lang=zh-CN -yarn doc --lang=en-US -``` - -文档在 docs 目录下,遵循 Markdown 语法 - -```bash -|- /docs/ - |- en-US - |- zh-CN -``` - -## 其他 - -更多 Commands 使用说明 [参考 NocoBase CLI 章节](https://docs-cn.nocobase.com/api/cli)。 diff --git a/docs/zh-CN/welcome/community/faq.md b/docs/zh-CN/welcome/community/faq.md deleted file mode 100644 index b43101d19..000000000 --- a/docs/zh-CN/welcome/community/faq.md +++ /dev/null @@ -1 +0,0 @@ -# 常见问题 diff --git a/docs/zh-CN/welcome/community/thanks.md b/docs/zh-CN/welcome/community/thanks.md deleted file mode 100644 index f835eba67..000000000 --- a/docs/zh-CN/welcome/community/thanks.md +++ /dev/null @@ -1,12 +0,0 @@ -# 致谢 - -NocoBase 使用了优秀的、成熟的开源产品,向他们表示诚挚的谢意: - -- [Ant Design](https://ant.design/) -- [Dnd Kit](https://dndkit.com/) -- [Formily](https://github.com/alibaba/formily) -- [I18next](https://www.i18next.com/) -- [Koa](https://koajs.com/) -- [React](https://reactjs.org/) -- [Sequelize](https://sequelize.org/) -- [UmiJS](https://umijs.org/) diff --git a/docs/zh-CN/welcome/community/translations.md b/docs/zh-CN/welcome/community/translations.md deleted file mode 100644 index f86540a3e..000000000 --- a/docs/zh-CN/welcome/community/translations.md +++ /dev/null @@ -1,159 +0,0 @@ -# 翻译 - -NocoBase 默认语言是英语,目前支持英语、简体中文、日语、俄语、土耳其语。你可以帮助 NocoBase 翻译成你的语言。 - -NocoBase 语言文件在以下位置: - -```shell -packages/core/**/src/locale -packages/plugins/**/src/locale -``` - -其中,NocoBase 核心的翻译主要在这里: - -https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/locale - -请复制 en_US.ts,命名为想要新增的语言的名字,然后对其中的字符串进行翻译。翻译完成之后,请通过 pull request 提交给 NocoBase,我们将会把它加入语言列表。之后你将在系统配置中看到新增的语言,在这里你可以配置需要显示哪些语言供用户选择。 - - - -下面的表格中列出了 Language Culture Name, Locale File Name, Display Name. - -| Language Culture Name | Locale File Name | Display Name | -| :-------------------- | :--------------- | :---------------------------- | -| af-ZA | af_ZA.ts | Afrikaans - South Africa | -| sq-AL | sq_AL.ts | Albanian - Albania | -| ar-DZ | ar_DZ.ts | Arabic - Algeria | -| ar-BH | ar_BH.ts | Arabic - Bahrain | -| ar-EG | ar_EG.ts | Arabic - Egypt | -| ar-IQ | ar_IQ.ts | Arabic - Iraq | -| ar-JO | ar_JO.ts | Arabic - Jordan | -| ar-KW | ar_KW.ts | Arabic - Kuwait | -| ar-LB | ar_LB.ts | Arabic - Lebanon | -| ar-LY | ar_LY.ts | Arabic - Libya | -| ar-MA | ar_MA.ts | Arabic - Morocco | -| ar-OM | ar_OM.ts | Arabic - Oman | -| ar-QA | ar_QA.ts | Arabic - Qatar | -| ar-SA | ar_SA.ts | Arabic - Saudi Arabia | -| ar-SY | ar_SY.ts | Arabic - Syria | -| ar-TN | ar_TN.ts | Arabic - Tunisia | -| ar-AE | ar_AE.ts | Arabic - United Arab Emirates | -| ar-YE | ar_YE.ts | Arabic - Yemen | -| hy-AM | hy_AM.ts | Armenian - Armenia | -| Cy-az-AZ | Cy_az_AZ.ts | Azeri (Cyrillic) - Azerbaijan | -| Lt-az-AZ | Lt_az_AZ.ts | Azeri (Latin) - Azerbaijan | -| eu-ES | eu_ES.ts | Basque - Basque | -| be-BY | be_BY.ts | Belarusian - Belarus | -| bg-BG | bg_BG.ts | Bulgarian - Bulgaria | -| ca-ES | ca_ES.ts | Catalan - Catalan | -| zh-CN | zh_CN.ts | Chinese - China | -| zh-HK | zh_HK.ts | Chinese - Hong Kong SAR | -| zh-MO | zh_MO.ts | Chinese - Macau SAR | -| zh-SG | zh_SG.ts | Chinese - Singapore | -| zh-TW | zh_TW.ts | Chinese - Taiwan | -| zh-CHS | zh_CHS.ts | Chinese (Simplified) | -| zh-CHT | zh_CHT.ts | Chinese (Traditional) | -| hr-HR | hr_HR.ts | Croatian - Croatia | -| cs-CZ | cs_CZ.ts | Czech - Czech Republic | -| da-DK | da_DK.ts | Danish - Denmark | -| div-MV | div_MV.ts | Dhivehi - Maldives | -| nl-BE | nl_BE.ts | Dutch - Belgium | -| nl-NL | nl_NL.ts | Dutch - The Netherlands | -| en-AU | en_AU.ts | English - Australia | -| en-BZ | en_BZ.ts | English - Belize | -| en-CA | en_CA.ts | English - Canada | -| en-CB | en_CB.ts | English - Caribbean | -| en-IE | en_IE.ts | English - Ireland | -| en-JM | en_JM.ts | English - Jamaica | -| en-NZ | en_NZ.ts | English - New Zealand | -| en-PH | en_PH.ts | English - Philippines | -| en-ZA | en_ZA.ts | English - South Africa | -| en-TT | en_TT.ts | English - Trinidad and Tobago | -| en-GB | en_GB.ts | English - United Kingdom | -| en-US | en_US.ts | English - United States | -| en-ZW | en_ZW.ts | English - Zimbabwe | -| et-EE | et_EE.ts | Estonian - Estonia | -| fo-FO | fo_FO.ts | Faroese - Faroe Islands | -| fa-IR | fa_IR.ts | Farsi - Iran | -| fi-FI | fi_FI.ts | Finnish - Finland | -| fr-BE | fr_BE.ts | French - Belgium | -| fr-CA | fr_CA.ts | French - Canada | -| fr-FR | fr_FR.ts | French - France | -| fr-LU | fr_LU.ts | French - Luxembourg | -| fr-MC | fr_MC.ts | French - Monaco | -| fr-CH | fr_CH.ts | French - Switzerland | -| gl-ES | gl_ES.ts | Galician - Galician | -| ka-GE | ka_GE.ts | Georgian - Georgia | -| de-AT | de_AT.ts | German - Austria | -| de-DE | de_DE.ts | German - Germany | -| de-LI | de_LI.ts | German - Liechtenstein | -| de-LU | de_LU.ts | German - Luxembourg | -| de-CH | de_CH.ts | German - Switzerland | -| el-GR | el_GR.ts | Greek - Greece | -| gu-IN | gu_IN.ts | Gujarati - India | -| he-IL | he_IL.ts | Hebrew - Israel | -| hi-IN | hi_IN.ts | Hindi - India | -| hu-HU | hu_HU.ts | Hungarian - Hungary | -| is-IS | is_IS.ts | Icelandic - Iceland | -| id-ID | id_ID.ts | Indonesian - Indonesia | -| it-IT | it_IT.ts | Italian - Italy | -| it-CH | it_CH.ts | Italian - Switzerland | -| ja-JP | ja_JP.ts | Japanese - Japan | -| kn-IN | kn_IN.ts | Kannada - India | -| kk-KZ | kk_KZ.ts | Kazakh - Kazakhstan | -| kok-IN | kok_IN.ts | Konkani - India | -| ko-KR | ko_KR.ts | Korean - Korea | -| ky-KZ | ky_KZ.ts | Kyrgyz - Kazakhstan | -| lv-LV | lv_LV.ts | Latvian - Latvia | -| lt-LT | lt_LT.ts | Lithuanian - Lithuania | -| mk-MK | mk_MK.ts | Macedonian (FYROM) | -| ms-BN | ms_BN.ts | Malay - Brunei | -| ms-MY | ms_MY.ts | Malay - Malaysia | -| mr-IN | mr_IN.ts | Marathi - India | -| mn-MN | mn_MN.ts | Mongolian - Mongolia | -| nb-NO | nb_NO.ts | Norwegian (BokmÃ¥l) - Norway | -| nn-NO | nn_NO.ts | Norwegian (Nynorsk) - Norway | -| pl-PL | pl_PL.ts | Polish - Poland | -| pt-BR | pt_BR.ts | Portuguese - Brazil | -| pt-PT | pt_PT.ts | Portuguese - Portugal | -| pa-IN | pa_IN.ts | Punjabi - India | -| ro-RO | ro_RO.ts | Romanian - Romania | -| ru-RU | ru_RU.ts | Russian - Russia | -| sa-IN | sa_IN.ts | Sanskrit - India | -| Cy-sr-SP | Cy_sr_SP.ts | Serbian (Cyrillic) - Serbia | -| Lt-sr-SP | Lt_sr_SP.ts | Serbian (Latin) - Serbia | -| sk-SK | sk_SK.ts | Slovak - Slovakia | -| sl-SI | sl_SI.ts | Slovenian - Slovenia | -| es-AR | es_AR.ts | Spanish - Argentina | -| es-BO | es_BO.ts | Spanish - Bolivia | -| es-CL | es_CL.ts | Spanish - Chile | -| es-CO | es_CO.ts | Spanish - Colombia | -| es-CR | es_CR.ts | Spanish - Costa Rica | -| es-DO | es_DO.ts | Spanish - Dominican Republic | -| es-EC | es_EC.ts | Spanish - Ecuador | -| es-SV | es_SV.ts | Spanish - El Salvador | -| es-GT | es_GT.ts | Spanish - Guatemala | -| es-HN | es_HN.ts | Spanish - Honduras | -| es-MX | es_MX.ts | Spanish - Mexico | -| es-NI | es_NI.ts | Spanish - Nicaragua | -| es-PA | es_PA.ts | Spanish - Panama | -| es-PY | es_PY.ts | Spanish - Paraguay | -| es-PE | es_PE.ts | Spanish - Peru | -| es-PR | es_PR.ts | Spanish - Puerto Rico | -| es-ES | es_ES.ts | Spanish - Spain | -| es-UY | es_UY.ts | Spanish - Uruguay | -| es-VE | es_VE.ts | Spanish - Venezuela | -| sw-KE | sw_KE.ts | Swahili - Kenya | -| sv-FI | sv_FI.ts | Swedish - Finland | -| sv-SE | sv_SE.ts | Swedish - Sweden | -| syr-SY | syr_SY.ts | Syriac - Syria | -| ta-IN | ta_IN.ts | Tamil - India | -| tt-RU | tt_RU.ts | Tatar - Russia | -| te-IN | te_IN.ts | Telugu - India | -| th-TH | th_TH.ts | Thai - Thailand | -| tr-TR | tr_TR.ts | Turkish - Turkey | -| uk-UA | uk_UA.ts | Ukrainian - Ukraine | -| ur-PK | ur_PK.ts | Urdu - Pakistan | -| Cy-uz-UZ | Cy_uz_UZ.ts | Uzbek (Cyrillic) - Uzbekistan | -| Lt-uz-UZ | Lt_uz_UZ.ts | Uzbek (Latin) - Uzbekistan | -| vi-VN | vi_VN.ts | Vietnamese - Vietnam | diff --git a/docs/zh-CN/welcome/community/translations/enabled-languages.jpg b/docs/zh-CN/welcome/community/translations/enabled-languages.jpg deleted file mode 100644 index fcf326305..000000000 Binary files a/docs/zh-CN/welcome/community/translations/enabled-languages.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/community/translations/language-settings-1.jpg b/docs/zh-CN/welcome/community/translations/language-settings-1.jpg deleted file mode 100644 index eb352a0f8..000000000 Binary files a/docs/zh-CN/welcome/community/translations/language-settings-1.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/community/translations/language-settings-2.jpg b/docs/zh-CN/welcome/community/translations/language-settings-2.jpg deleted file mode 100644 index 3e9f7b08e..000000000 Binary files a/docs/zh-CN/welcome/community/translations/language-settings-2.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/getting-started/installation/create-nocobase-app.md b/docs/zh-CN/welcome/getting-started/installation/create-nocobase-app.md deleted file mode 100644 index b7d33b11a..000000000 --- a/docs/zh-CN/welcome/getting-started/installation/create-nocobase-app.md +++ /dev/null @@ -1,93 +0,0 @@ -# `create-nocobase-app` 安装 - -## 0. 先决条件 - -请确保你已经: - -- 安装了 Node.js 16+、Yarn 1.22.x -- 配置并启动了所需数据库 SQLite 3.x、MySQL 8.x、PostgreSQL 10.x 任选其一 - -如果你没有安装 Node.js 可以从官网下载并安装最新的 LTS 版本。如果你打算长期与 Node.js 打交道,推荐使用 nvm(Win 系统可以使用 nvm-windows )来管理 Node.js 版本。 - -```bash -$ node -v - -v16.13.2 -``` - -推荐使用 yarn 包管理器。 - -```bash -$ npm install --global yarn -$ yarn -v - -1.22.10 -``` - -由于国内网络环境的原因,强烈建议你更换国内镜像。 - -```bash -$ yarn config set registry https://registry.npmmirror.com/ -$ yarn config set sqlite3_binary_host_mirror https://npmmirror.com/mirrors/sqlite3/ -``` - -## 1. 创建 NocoBase 项目 - -```bash -# SQLite -yarn create nocobase-app my-nocobase-app -d sqlite -# MySQL -yarn create nocobase-app my-nocobase-app -d mysql \ - -e DB_HOST=localhost \ - -e DB_PORT=3306 \ - -e DB_DATABASE=nocobase \ - -e DB_USER=nocobase \ - -e DB_PASSWORD=nocobase -# PostgreSQL -yarn create nocobase-app my-nocobase-app -d postgres \ - -e DB_HOST=localhost \ - -e DB_PORT=5432 \ - -e DB_DATABASE=nocobase \ - -e DB_USER=nocobase \ - -e DB_PASSWORD=nocobase -``` - -## 2. 切换目录 - -```bash -cd my-nocobase-app -``` - -## 3. 安装依赖 - -📢 由于网络环境、系统配置等因素影响,接下来这一步骤可能需要十几分钟时间。 - -```bash -yarn install -``` - -## 4. 安装 NocoBase - -```bash -yarn nocobase install --lang=zh-CN -``` - -## 5. 启动 NocoBase - -开发环境 - -```bash -yarn dev -``` - -生产环境 - -```bash -yarn start -``` - -注:生产环境,如果代码有修改,需要执行 `yarn build`,再重新启动 NocoBase。 - -## 6. 登录 NocoBase - -使用浏览器打开 http://localhost:13000/ 初始化账号和密码是 `admin@nocobase.com` 和 `admin123`。 diff --git a/docs/zh-CN/welcome/getting-started/installation/docker-compose.md b/docs/zh-CN/welcome/getting-started/installation/docker-compose.md deleted file mode 100644 index 1e4700c49..000000000 --- a/docs/zh-CN/welcome/getting-started/installation/docker-compose.md +++ /dev/null @@ -1,142 +0,0 @@ -# Docker 安装 - -## 0. 先决条件 - -⚡⚡ 请确保你已经安装了 [Docker](https://docs.docker.com/get-docker/) - -## 1. 将 NocoBase 下载到本地 - -使用 Git 下载(或直接[下载 Zip 包](https://gitee.com/nocobase/nocobase/repository/archive/main.zip),并解压到 nocobase 目录下) - -```bash -git clone https://gitee.com/nocobase/nocobase.git nocobase -``` - -## 2. 选择数据库(任选其一) - -将目录切换到第一步下载的文件夹里(根据实际情况调整)。 - -```bash -# MacOS, Linux... -cd /your/path/nocobase -# Windows -cd C:\your\path\nocobase -``` - -不同数据库的 docker 配置有些许差异,请选择切换到对应的目录下。 - -### SQLite - -```bash -cd docker/app-sqlite -``` - -### MySQL - -```bash -cd docker/app-mysql -``` - -### PostgreSQL - -```bash -cd docker/app-postgres -``` - -## 3. 配置 docker-compose.yml(非必须) - - - -非开发人员,跳过这一步。如果你懂得开发,也可以进一步了解怎么配置 `docker-compose.yml`。 - - - -目录结构(与 docker 相关) - -```bash -├── nocobase - ├── docker - ├── app-sqlite - ├── storage - ├── docker-compose.yml - ├── app-mysql - ├── storage - ├── docker-compose.yml - ├── app-postgres - ├── storage - ├── docker-compose.yml -``` - -`docker-compose.yml` 的配置说明: - -SQLite 只有 app 服务,PostgreSQL 和 MySQL 会有对应的 postgres 或 mysql 服务,可以使用例子的数据库服务,或者自己配置。 - -```yml -services: - app: - postgres: - mysql: -``` - -app 端口,例子为 13000 端口,访问地址为 `http://your-ip:13000/` - -```yml -services: - app: - ports: - - "13000:80" -``` - -NocoBase 版本([点此查看最新版本](https://hub.docker.com/r/nocobase/nocobase/tags)),升级时,需要修改为最新版本。 - -```yml -services: - app: - image: nocobase/nocobase:main -``` - -环境变量 - -```yml -services: - app: - image: nocobase/nocobase:main - environment: - - DB_DIALECT=postgres - - DB_HOST=postgres - - DB_DATABASE=nocobase - - DB_USER=nocobase - - DB_PASSWORD=nocobase - - LOCAL_STORAGE_BASE_URL=/storage/uploads -``` - -- `DB_*` 为数据库相关,如果不是例子默认的数据库服务,请根据实际情况修改; -- `LOCAL_STORAGE_BASE_URL` 为本地存储的根 URL,如果不是本地安装,需要改为对应的 ip 或域名。 - -## 4. 安装并启动 NocoBase - -安装过程可能需要等待几分钟 - -```bash -# 拉取最新镜像 -$ docker-compose pull -# 在后台运行 -$ docker-compose up -d -# 查看 app 进程的情况 -$ docker-compose logs app - -app-sqlite-app-1 | nginx started -app-sqlite-app-1 | yarn run v1.22.15 -app-sqlite-app-1 | $ cross-env DOTENV_CONFIG_PATH=.env node -r dotenv/config packages/app/server/lib/index.js install -s -app-sqlite-app-1 | Done in 2.72s. -app-sqlite-app-1 | yarn run v1.22.15 -app-sqlite-app-1 | $ pm2-runtime start --node-args="-r dotenv/config" packages/app/server/lib/index.js -- start -app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: Launching in no daemon mode -app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: App [index:0] starting in -fork mode- -app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: App [index:0] online -app-sqlite-app-1 | 🚀 NocoBase server running at: http://localhost:13000/ -``` - -## 5. 登录 NocoBase - -使用浏览器打开 http://localhost:13000/ 初始化账号和密码是 `admin@nocobase.com` 和 `admin123`。 diff --git a/docs/zh-CN/welcome/getting-started/installation/git-clone.md b/docs/zh-CN/welcome/getting-started/installation/git-clone.md deleted file mode 100644 index dcb3c3a8c..000000000 --- a/docs/zh-CN/welcome/getting-started/installation/git-clone.md +++ /dev/null @@ -1,66 +0,0 @@ -# Git 源码安装 - -## 0. 先决条件 - -请确保你已经: - -- 安装了 Git、Node.js 16+、Yarn 1.22.x -- 配置并启动了所需数据库 SQLite 3.x、MySQL 8.x、PostgreSQL 10.x 任选其一 - -## 1. 将 NocoBase 下载到本地 - -```bash -git clone https://github.com/nocobase/nocobase.git my-nocobase-app -``` - -## 2. 切换目录 - -```bash -cd my-nocobase-app -``` - -## 3. 安装依赖 - -📢 由于网络环境、系统配置等因素影响,接下来这一步骤可能需要十几分钟时间。 - -```bash -yarn install -``` - -## 4. 设置环境变量 - -NocoBase 所需的环境变量储存在根目录 `.env` 文件里,根据实际情况修改环境变量,如果你不知道怎么改,[点此查看环境变量说明](/api/env),也可以保持默认。 - -```bash -# 使用 sqlite 数据库 -DB_DIALECT=sqlite -# sqlite 文件地址 -DB_STORAGE=storage/db/nocobase.sqlite -``` - -## 5. 安装 NocoBase - -```bash -yarn nocobase install --lang=zh-CN -``` - -## 6. 启动 NocoBase - -开发环境 - -```bash -yarn dev -``` - -生产环境 - -```bash -# 编译 -yarn build -# 启动 -yarn start -``` - -## 7. 登录 NocoBase - -使用浏览器打开 http://localhost:13000/ 初始化账号和密码是 `admin@nocobase.com` 和 `admin123`。 diff --git a/docs/zh-CN/welcome/getting-started/installation/index.md b/docs/zh-CN/welcome/getting-started/installation/index.md deleted file mode 100644 index 6940b17b5..000000000 --- a/docs/zh-CN/welcome/getting-started/installation/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# 安装概述 - -## 安装方式 - -NocoBase 支持三种安装方式: - -- [Docker 安装(推荐)](./docker-compose.md) -- [create-nocobase-app 安装](./create-nocobase-app.md) -- [Git 源码安装](./git-clone.md) - -## 如何选择 - -**Docker 安装(推荐)**: - -适合无代码场景,不需要写代码。升级时,下载最新镜像并重启即可。 - -**create-nocobase-app 安装**: - -项目的业务代码完全独立,支持低代码开发。 - -**Git 源码安装**: - -如果你想体验最新未发布版本,或者想参与贡献,需要在源码上进行修改、调试,建议选择这种安装方式,对开发技术水平要求较高,如果代码有更新,可以走 git 流程拉取最新代码。 diff --git a/docs/zh-CN/welcome/getting-started/upgrading/create-nocobase-app.md b/docs/zh-CN/welcome/getting-started/upgrading/create-nocobase-app.md deleted file mode 100644 index a0ae06137..000000000 --- a/docs/zh-CN/welcome/getting-started/upgrading/create-nocobase-app.md +++ /dev/null @@ -1,20 +0,0 @@ -# `create-nocobase-app` 安装的升级 - - -v0.12 之后的版本,通过 create-nocobase-app 安装的应用不再有 packages/app 目录了,在 packages/app 里自定义的代码,需要移至自定义插件中。 - - -## 版本升级 - -v0.12 之后的版本,应用的升级直接执行 `yarn nocobase upgrade` 升级命令即可 - -```bash -# 切换到对应的目录 -cd my-nocobase-app -# 执行更新命令 -yarn nocobase upgrade -# 启动 -yarn dev -``` - -如果升级存在问题,也可以[重新创建新应用](/welcome/getting-started/installation/create-nocobase-app),并参考旧版本的 .env 修改环境变量。数据库信息需要配置正确,使用 SQLite 数据库时,需要将数据库文件复制到 `./storage/db/` 目录。最后再执行 `yarn nocobase upgrade` 进行升级。 diff --git a/docs/zh-CN/welcome/getting-started/upgrading/docker-compose.md b/docs/zh-CN/welcome/getting-started/upgrading/docker-compose.md deleted file mode 100644 index 473759cb7..000000000 --- a/docs/zh-CN/welcome/getting-started/upgrading/docker-compose.md +++ /dev/null @@ -1,41 +0,0 @@ -# Docker 安装的升级 - - - -本篇文档所讲的 Docker 安装是基于 `docker-compose.yml` 配置文件,在 [NocoBase GitHub 仓库](https://github.com/nocobase/nocobase/tree/main/docker) 里也有提供。 - - - -## 1. 切换到之前安装时的目录 - -也可以根据实际情况,切换到 `docker-compose.yml` 所在的目录 - -```bash -# SQLite -cd nocobase/docker/app-sqlite -# MySQL -cd nocobase/docker/app-mysql -# PostgreSQL -cd nocobase/docker/app-postgres -``` - -## 2. 更新 image 版本号 - -`docker-compose.yml` 文件,app 容器的 image 替换为最新版本 - -```yml -services: - app: - image: nocobase/nocobase:main # main 分支 -``` - -## 3. 重启容器 - -```bash -# 拉取最新镜像 -docker-compose pull -# 启动 -docker-compose up -d app -# 查看 app 进程的情况 -docker-compose logs app -``` diff --git a/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md b/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md deleted file mode 100644 index cdd72832b..000000000 --- a/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md +++ /dev/null @@ -1,53 +0,0 @@ -# Git 源码安装的升级 - -## 1. 切换到 NocoBase 项目目录 - -```bash -cd my-nocobase-app -``` - -## 2. 拉取最新代码 - -```bash -git pull -``` - -## 3. 删除缓存和旧依赖(非必须) - -如果正常的升级流程失败,可以尝试清空缓存和依赖之后重新下载 - -```bash -# 删除 nocobase 缓存 -yarn nocobase clean -# 删除依赖 -yarn rimraf -rf node_modules -``` - -## 4. 更新依赖 - -```bash -yarn install -``` - -## 5. 执行更新命令 - -```bash -yarn nocobase upgrade -``` - -## 6. 启动 NocoBase - -开发环境 - -```bash -yarn dev -``` - -生产环境 - -```bash -# 编译 -yarn build -# 启动 -yarn start -``` diff --git a/docs/zh-CN/welcome/getting-started/upgrading/index.md b/docs/zh-CN/welcome/getting-started/upgrading/index.md deleted file mode 100644 index b914a5ebc..000000000 --- a/docs/zh-CN/welcome/getting-started/upgrading/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# 升级概述 - -NocoBase 支持三种安装方式,升级时略有不同。 - -- [Docker 安装的升级](./docker-compose.md) -- [create-nocobase-app 安装的升级](./create-nocobase-app.md) -- [Git 源码安装的升级](./git-clone.md) diff --git a/docs/zh-CN/welcome/introduction/features.md b/docs/zh-CN/welcome/introduction/features.md deleted file mode 100644 index dd0186636..000000000 --- a/docs/zh-CN/welcome/introduction/features.md +++ /dev/null @@ -1,20 +0,0 @@ -# 与众不同之处 - -## 1. 模型驱动,“数据结构”与“使用界面”分离 - -多数以表单、表格或者流程驱动的无代码产品都是在使用界面上直接创建数据结构,比如 Airtable 在表格里新增一列就是新增一个字段。这样的好处是使用简单,不足是功能和灵活性受限,难以满足较复杂场景的需求。 - -NocoBase 采用数据结构与使用界面分离的设计思路,可以为数据表创建任意数量、任意形态的区块(数据视图),每个区块里可以定义不同的样式、文案、操作。这样既兼顾了无代码的简单操作,又具备了原生开发的灵活性。 - -![model](https://nocobase-file.oss-cn-beijing.aliyuncs.com/model-l.png) - -## 2. 所见即所得 -NocoBase 可以开发复杂和有特色的业务系统,但这并不意味着需要复杂和专业的操作。只需一次点击,就可以在使用界面上显示出配置选项,具备系统配置权限的管理员可以用所见即所得的操作方式,直接配置用户的使用界面。 - -![wysiwyg](https://nocobase-file.oss-cn-beijing.aliyuncs.com/wysiwyg.gif) - -## 3. 功能即插件 - -NocoBase 采用插件化架构,所有新功能都可以通过开发和安装插件来实现,扩展功能就像在手机上安装 APP 一样简单。 - -![plugins](https://nocobase-file.oss-cn-beijing.aliyuncs.com/plugins-l.png) diff --git a/docs/zh-CN/welcome/introduction/features/2.collection-block.png b/docs/zh-CN/welcome/introduction/features/2.collection-block.png deleted file mode 100755 index 7389064cb..000000000 Binary files a/docs/zh-CN/welcome/introduction/features/2.collection-block.png and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/features/2.user-root.gif b/docs/zh-CN/welcome/introduction/features/2.user-root.gif deleted file mode 100755 index 92d668c14..000000000 Binary files a/docs/zh-CN/welcome/introduction/features/2.user-root.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/index.md b/docs/zh-CN/welcome/introduction/index.md deleted file mode 100644 index 6bf085965..000000000 --- a/docs/zh-CN/welcome/introduction/index.md +++ /dev/null @@ -1,29 +0,0 @@ -# 介绍 - -![NocoBase](https://nocobase-file.oss-cn-beijing.aliyuncs.com/main-l.png) - -**注意:** 📌 - -NocoBase 正处在早期开发阶段,可能变动频繁,请谨慎用于生产环境。 - -## NocoBase 是什么 - -NocoBase 是一个极易扩展的开源无代码开发平台。 -不必投入几年时间、数百万资金研发,花几分钟时间部署 NocoBase,马上拥有一个私有、可控、极易扩展的无代码开发平台。 - -中文官网: -https://cn.nocobase.com/ - -在线体验: -https://demo-cn.nocobase.com/new - -文档: -https://docs-cn.nocobase.com/ - -## 商业服务 - -如果你需要商业版本和商业服务,欢迎通过邮件联系我们:hello@nocobase.com - -或者添加我们的微信: - -![](https://nocobase-file.oss-cn-beijing.aliyuncs.com/wechat.png) diff --git a/docs/zh-CN/welcome/introduction/why.md b/docs/zh-CN/welcome/introduction/why.md deleted file mode 100644 index 4201cc253..000000000 --- a/docs/zh-CN/welcome/introduction/why.md +++ /dev/null @@ -1,19 +0,0 @@ -# 为什么选择 NocoBase - -## 开源,自主可控 -NocoBase 采用 Apache-2.0 & AGPL-3.0 许可协议开源,在遵循协议的前提下可以免费使用。高级功能通过商业版提供,同样提供全部源代码,私有化部署,保障数据私有和安全。 - -## 无代码能力强 -NocoBase 有三个核心概念:数据、区块、操作。通过定义数据模型,来抽象业务;通过区块来呈现数据;通过操作将用户的指令发送给服务器完成数据的交互或变更。 - -## 扩展能力强 -在实际业务中,理想情况是使用无代码搭建来满足 80% 的需求,剩下的部分通常需要扩展开发。NocoBase 采用微内核架构,具备健全的插件体系,各类功能以插件形式扩展。基于 Node.js,使用主流框架和技术,包括 Koa、Sequelize、React 等,极易扩展。 - -## 与已有系统融合 -企业中通常已经有了各种系统和数据库,NocoBase 支持将第三方数据库或者 API 作为数据源,也支持将 NocoBase 嵌入第三方系统中,或者将第三方系统嵌入 NocoBase 中。 - -## 极致简单轻量 -NocoBase 采用 JavaScript/TypeScript 技术栈,一个人即可完成前后端开发。对服务器要求低,可以部署在单台低配置服务器上。 - -## 一次付费,终身使用 -NocoBase 只为高级功能收费。一次付费后,获取全部源代码,并可以获得 1 年的升级权利和技术支持。到期后如果不再续费,可停留在当前版本永久免费使用。 diff --git a/docs/zh-CN/welcome/release/collection-templates.md b/docs/zh-CN/welcome/release/collection-templates.md deleted file mode 100644 index 1b93bf9a0..000000000 --- a/docs/zh-CN/welcome/release/collection-templates.md +++ /dev/null @@ -1,79 +0,0 @@ -# v0.9.0: Collection 模板 - - - -## 为什么需要 Collection 模板? - -待补充 - -## 配置参数说明 - -```ts -interface ICollectionTemplate { - name: string; - title?: string; - /** 排序 */ - order?: number; - /** 默认配置 */ - default?: CollectionOptions; - /** UI 可配置的 CollectionOptions 参数(添加或编辑的 Collection 表单的字段) */ - configurableProperties?: Record; - /** 当前模板可用的字段类型 */ - availableFieldInterfaces?: AvailableFieldInterfacesInclude | AvailableFieldInterfacesExclude; -} - -interface AvailableFieldInterfacesInclude { - include?: any[]; -} - -interface AvailableFieldInterfacesExclude { - exclude?: any[]; -} - -interface CollectionOptions { - /** - * 自动生成 id - * @default true - * */ - autoGenId?: boolean; - /** 创建人 */ - createdBy?: boolean; - /** 最后更新人 */ - updatedBy?: boolean; - /** 创建日期 */ - createdAt?: boolean; - /** 更新日期 */ - updatedAt?: boolean; - /** 可排序 */ - sortable?: boolean; - /* 树结构 */ - tree?: string; - /* 日志 */ - logging?: boolean; - /** 继承 */ - inherits: string | string[]; - /* 字段列表 */ - fields?: FieldOptions[]; -} -``` - -## 示例 - -创建时都不需要 autoGenId,并且只提供 title 和 name 配置项 - -```ts -import { collectionConfigurableProperties } from '@nocobase/client'; - -{ - default: { - autoGenId: false, - fields: [], - }, - configurableProperties: { - ...collectionConfigurableProperties('name', 'title'), - }, -} -``` - -完整插件示例参考:[samples/custom-collection-template](https://github.com/nocobase/nocobase/tree/feat/collection-templates/packages/samples/custom-collection-template) - diff --git a/docs/zh-CN/welcome/release/formulas.md b/docs/zh-CN/welcome/release/formulas.md deleted file mode 100644 index 3cd461b0b..000000000 --- a/docs/zh-CN/welcome/release/formulas.md +++ /dev/null @@ -1,33 +0,0 @@ -# v0.9.0:字段的计算公式插件 - -NocoBase 目前提供了两种计算公式插件: - -- `@nocobase/plugin-math-formula-field` Math 公式 -- `@nocobase/plugin-excel-formula-field` Excel 公式(感谢 [azriel46d](https://github.com/nocobase/nocobase/pull/906) 贡献) - -## Math Formula - -基于 [Math.js](https://mathjs.org/) 实现,它具有支持符号计算的灵活表达式解析器,大量内置函数和常量,并提供了集成的解决方案来处理不同的数据类型,例如数字,大数,复数,分数,单位和矩阵。 - -```ts -import { evaluate } from 'mathjs'; -// expressions -evaluate('1.2 * (2 + 4.5)') // 7.8 -evaluate('12.7 cm to inch') // 5 inch -evaluate('sin(45 deg) ^ 2') // 0.5 -evaluate('9 / 3 + 2i') // 3 + 2i -evaluate('det([-1, 2; 3, 1])') // -7 -``` - - - -## Excel Formula - -基于 [Formula.js](https://formulajs.info/) 实现,详细用法参考 [Formula.js functions](https://formulajs.info/functions/)。 - -```ts -SUM(-5, 15, 32) // 42 -IF(true, 'Hello!', 'Goodbye!') // Hello! -``` - - diff --git a/docs/zh-CN/welcome/release/formulas/excel-form.jpg b/docs/zh-CN/welcome/release/formulas/excel-form.jpg deleted file mode 100644 index 41413a0c4..000000000 Binary files a/docs/zh-CN/welcome/release/formulas/excel-form.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/formulas/math-form.jpg b/docs/zh-CN/welcome/release/formulas/math-form.jpg deleted file mode 100644 index ecf54c476..000000000 Binary files a/docs/zh-CN/welcome/release/formulas/math-form.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/gantt/introduction.md b/docs/zh-CN/welcome/release/gantt/introduction.md deleted file mode 100644 index 7fdcb58b6..000000000 --- a/docs/zh-CN/welcome/release/gantt/introduction.md +++ /dev/null @@ -1,21 +0,0 @@ -# 甘特图区块 - -## 创建甘特图区块 - -![](./static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png) - -## 甘特图区块(空数据) - -![](./static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png) - -## 甘特图区块配置 - -![](./static/YJZZb0aO3oG9n6x4aZwcAB07nng.png) - -## 普通任务 - -![](./static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png) - -## 父子任务 - -![](./static/V9w1b43YsoIRYpxtFdscpf2MnQf.png) diff --git a/docs/zh-CN/welcome/release/gantt/static/IwJzb9PsFovXwZxNqJnc6HaFnyg.png b/docs/zh-CN/welcome/release/gantt/static/IwJzb9PsFovXwZxNqJnc6HaFnyg.png deleted file mode 100644 index a8893aa82..000000000 Binary files a/docs/zh-CN/welcome/release/gantt/static/IwJzb9PsFovXwZxNqJnc6HaFnyg.png and /dev/null differ diff --git a/docs/zh-CN/welcome/release/gantt/static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png b/docs/zh-CN/welcome/release/gantt/static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png deleted file mode 100644 index 2d309ad42..000000000 Binary files a/docs/zh-CN/welcome/release/gantt/static/UxT7b8mVCo1isIxrsjtcZ8hpnlf.png and /dev/null differ diff --git a/docs/zh-CN/welcome/release/gantt/static/V9w1b43YsoIRYpxtFdscpf2MnQf.png b/docs/zh-CN/welcome/release/gantt/static/V9w1b43YsoIRYpxtFdscpf2MnQf.png deleted file mode 100644 index c454d0c43..000000000 Binary files a/docs/zh-CN/welcome/release/gantt/static/V9w1b43YsoIRYpxtFdscpf2MnQf.png and /dev/null differ diff --git a/docs/zh-CN/welcome/release/gantt/static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png b/docs/zh-CN/welcome/release/gantt/static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png deleted file mode 100644 index 4e93257fd..000000000 Binary files a/docs/zh-CN/welcome/release/gantt/static/VGTZbOs38obgyqxm5YfcGS1Vnhb.png and /dev/null differ diff --git a/docs/zh-CN/welcome/release/gantt/static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png b/docs/zh-CN/welcome/release/gantt/static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png deleted file mode 100644 index 901010eff..000000000 Binary files a/docs/zh-CN/welcome/release/gantt/static/VM3qbBhLeoEcKwxwAZkcRHBynOd.png and /dev/null differ diff --git a/docs/zh-CN/welcome/release/gantt/static/YJZZb0aO3oG9n6x4aZwcAB07nng.png b/docs/zh-CN/welcome/release/gantt/static/YJZZb0aO3oG9n6x4aZwcAB07nng.png deleted file mode 100644 index a603d847c..000000000 Binary files a/docs/zh-CN/welcome/release/gantt/static/YJZZb0aO3oG9n6x4aZwcAB07nng.png and /dev/null differ diff --git a/docs/zh-CN/welcome/release/index.md b/docs/zh-CN/welcome/release/index.md deleted file mode 100644 index a04764e07..000000000 --- a/docs/zh-CN/welcome/release/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# 更新日志 - -访问:https://github.com/nocobase/nocobase/blob/main/CHANGELOG.md \ No newline at end of file diff --git a/docs/zh-CN/welcome/release/inherits.md b/docs/zh-CN/welcome/release/inherits.md deleted file mode 100644 index aee2c72e7..000000000 --- a/docs/zh-CN/welcome/release/inherits.md +++ /dev/null @@ -1,74 +0,0 @@ -# v0.9.0: 数据表继承 - -数据表继承基于 [PostgreSQL 的 INHERITS 语法](https://www.postgresql.org/docs/current/tutorial-inheritance.html) 实现,仅限于 PostgreSQL 数据库安装的 NocoBase 时才会提供。 - -## 示例 - -我们从一个例子开始,假设要做一个教学系统,有三类用户:学生、家长和老师。 - -如果没有继承,要分别为三类用户建表: - -- 学生:姓名、年龄、性别、身份证 -- 家长:姓名、年龄、性别、职业、学历 -- 老师:姓名、年龄、性别、教龄、已婚 - -有了数据表继承之后,共同的信息就可以提炼出来: - -- 用户:姓名、年龄、性别 -- 学生:身份证 -- 家长:职业、学历 -- 老师:教龄、已婚 - -ER 图如下: - - - -注:子表 ID 和父表 ID 共享序列 - -## 配置数据表继承 - -Inherits 字段选择需要继承的数据表 - - - -通过代码配置如下: - -```ts -db.collection({ - name: 'users', -}); - -db.collection({ - name: 'students', - inherits: 'users', -}); -``` - -注意: - -- 继承的表并不能随意选择,主键必须是唯一序列,比如 uuid 或者所有继承线路上的表的 id 自增序列都用同一个 -- Inherits 参数不能被编辑 -- 如果有继承关系,被继承的父表不能被删除 - -## 数据表字段列表 - -字段列表里同步显示继承的父表字段,父表字段不可以修改,但可以重写(Override) - - - -重写父表字段的注意事项: -- 子表字段标识与父表字段一样时为重写 -- 重写字段的类型必须保持一致 -- 关系字段除了 target collection 以外的其他参数需要保持一致 - -## 父表的子表区块 - -在父表区块里可以配置子表的区块 - - - -## 新增继承的父表字段的配置 - -当有继承的父表时,配置字段时,会提供从父表继承的字段的配置 - - diff --git a/docs/zh-CN/welcome/release/inherits/configure-fields.jpg b/docs/zh-CN/welcome/release/inherits/configure-fields.jpg deleted file mode 100644 index adf256da3..000000000 Binary files a/docs/zh-CN/welcome/release/inherits/configure-fields.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/inherits/er.svg b/docs/zh-CN/welcome/release/inherits/er.svg deleted file mode 100644 index f1ba5c251..000000000 --- a/docs/zh-CN/welcome/release/inherits/er.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -UsersPKidnameagegenderStudentsPKididentityNumberParentsPKidoccupationTeachersPKidmarried \ No newline at end of file diff --git a/docs/zh-CN/welcome/release/inherits/form.jpg b/docs/zh-CN/welcome/release/inherits/form.jpg deleted file mode 100644 index 2de047cd5..000000000 Binary files a/docs/zh-CN/welcome/release/inherits/form.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/inherits/inherit-fields.jpg b/docs/zh-CN/welcome/release/inherits/inherit-fields.jpg deleted file mode 100644 index acdaddda9..000000000 Binary files a/docs/zh-CN/welcome/release/inherits/inherit-fields.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/inherits/inherit.jpg b/docs/zh-CN/welcome/release/inherits/inherit.jpg deleted file mode 100644 index ecc419daa..000000000 Binary files a/docs/zh-CN/welcome/release/inherits/inherit.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/inherits/inherited-blocks.jpg b/docs/zh-CN/welcome/release/inherits/inherited-blocks.jpg deleted file mode 100644 index e95928c20..000000000 Binary files a/docs/zh-CN/welcome/release/inherits/inherited-blocks.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/logger.md b/docs/zh-CN/welcome/release/logger.md deleted file mode 100644 index 34214734f..000000000 --- a/docs/zh-CN/welcome/release/logger.md +++ /dev/null @@ -1,67 +0,0 @@ -# v0.9.0: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) -- [LOGGER_BASE_PATH](/api/env#logger_base_path) - -## 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/tree-collection.md b/docs/zh-CN/welcome/release/tree-collection.md deleted file mode 100644 index 5b17858f7..000000000 --- a/docs/zh-CN/welcome/release/tree-collection.md +++ /dev/null @@ -1,45 +0,0 @@ -# Tree collection - -## Collection options - -```ts -{ - name: 'categories', - tree: 'adjacency-list', - fields: [ - { - type: 'belongsTo', - name: 'parent', - treeParent: true, - }, - { - type: 'hasMany', - name: 'children', - treeChildren: true, - }, - ], -} -``` - -## UI - -### Create tree collection - - - - -### Default fields - - - -### Table block - - - -### Add child - - - -### Expend/Collapse - - diff --git a/docs/zh-CN/welcome/release/tree-collection/add-child.jpg b/docs/zh-CN/welcome/release/tree-collection/add-child.jpg deleted file mode 100644 index 0c1285d5d..000000000 Binary files a/docs/zh-CN/welcome/release/tree-collection/add-child.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/tree-collection/expend-collapse.jpg b/docs/zh-CN/welcome/release/tree-collection/expend-collapse.jpg deleted file mode 100644 index de703b86b..000000000 Binary files a/docs/zh-CN/welcome/release/tree-collection/expend-collapse.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/tree-collection/init.jpg b/docs/zh-CN/welcome/release/tree-collection/init.jpg deleted file mode 100644 index 86b4d5b42..000000000 Binary files a/docs/zh-CN/welcome/release/tree-collection/init.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/tree-collection/tree-collection.jpg b/docs/zh-CN/welcome/release/tree-collection/tree-collection.jpg deleted file mode 100644 index 1733aca53..000000000 Binary files a/docs/zh-CN/welcome/release/tree-collection/tree-collection.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/tree-collection/tree-table.jpg b/docs/zh-CN/welcome/release/tree-collection/tree-table.jpg deleted file mode 100644 index 4eeb1e6f2..000000000 Binary files a/docs/zh-CN/welcome/release/tree-collection/tree-table.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/v08-1-collection-templates/v08-1-collection-templates.jpg b/docs/zh-CN/welcome/release/v08-1-collection-templates/v08-1-collection-templates.jpg deleted file mode 100644 index dd0a99af8..000000000 Binary files a/docs/zh-CN/welcome/release/v08-1-collection-templates/v08-1-collection-templates.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/v08-changelog.md b/docs/zh-CN/welcome/release/v08-changelog.md deleted file mode 100644 index d715df275..000000000 --- a/docs/zh-CN/welcome/release/v08-changelog.md +++ /dev/null @@ -1,217 +0,0 @@ -# v0.8:插件管理器和文档 - -从 v0.8 开始,NocoBase 开始提供可用的插件管理器和开发文档。以下是 v0.8 的主要变化。 - -## 界面右上角的调整 - -- 界面配置 -- 插件管理器 -- 配置中心 -- 个人中心 - - - -## 全新的插件管理器 - -v0.8 提供了强大的插件管理器用于无代码的方式管理插件。 - -### 插件管理器流程 - - - -### 插件管理器界面 - -目前主要用于本地插件的禁用、激活和删除。内置插件不能删除,插件市场敬请期待。 - - - -### 插件管理器命令行 - -除了可以在无代码界面激活、禁用插件,也可以通过命令行更完整的管理插件。 - -```bash -# 创建插件 -yarn pm create hello -# 注册插件 -yarn pm add hello -# 激活插件 -yarn pm enable hello -# 禁用插件 -yarn pm disable hello -# 删除插件 -yarn pm remove hello -``` - -备注:插件的发布和升级会在后续的版本里支持。 - -```bash -# 发布插件 -yarn pm publish hello -# 升级插件 -yarn pm upgrade hello -``` - -更多插件示例,查看 [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples)。 - -## 插件的变化 - -### 插件目录结构 - -```bash -|- /hello - |- /src - |- /client # 插件客户端代码 - |- /server # 插件服务端代码 - |- client.d.ts - |- client.js - |- package.json # 插件包信息 - |- server.d.ts - |- server.js -``` - -### 插件名称规范 - -NocoBase 插件也是 NPM 包,插件名和 NPM 包名的对应规则为 `${PLUGIN_PACKAGE_PREFIX}-${pluginName}`。 - -`PLUGIN_PACKAGE_PREFIX` 为插件包前缀,可以在 .env 里自定义,[点此查看 PLUGIN_PACKAGE_PREFIX 说明](/api/env#plugin_package_prefix)。 - -例如,有一名为 `my-nocobase-app` 的项目,新增了 `hello` 插件,包名为 `@my-nocobase-app/plugin-hello`。 - -PLUGIN_PACKAGE_PREFIX 配置如下: - -```bash -PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin- -``` - -插件名和包名的对应关系为: - -- `users` 插件包名为 `@nocobase/plugin-users` -- `nocobase` 插件包名为 `@nocobase/preset-nocobase` -- `hello` 插件包名为 `@my-nocobase-app/plugin-hello` - -### 插件的生命周期 - -v0.8 提供了更完整的插件生命周期方法 - -```ts -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class HelloPlugin extends Plugin { - afterAdd() { - // 插件通过 pm.add 添加之后 - } - - beforeLoad() { - // 所有插件执行 load 之前,一般用于注册类和事件监听 - } - - async load() { - // 加载配置 - } - - async install(options?: InstallOptions) { - // 安装逻辑 - } - - async afterEnable() { - // 激活之后 - } - - async afterDisable() { - // 禁用之后 - } - - async remove() { - // 删除逻辑 - } -} - -export default HelloPlugin; -``` - -### 插件的前后端入口 - -插件的生命周期由服务端控制 - -```ts -import { Application } from '@nocobase/server'; - -const app = new Application({ - // ... -}); - -class MyPlugin extends Plugin { - afterAdd() {} - beforeLoad() {} - load() {} - install() {} - afterEnable() {} - afterDisable() {} - remove() {} -} - -app.plugin(MyPlugin, { name: 'my-plugin' }); -``` - -插件的客户端以 Context.Provider 形式存在(类似于服务端的 Middleware) - -```tsx | pure -import React from 'react'; -import { Application } from '@nocobase/client'; - -const app = new Application({ - apiClient: { - baseURL: process.env.API_BASE_URL, - }, - dynamicImport: (name: string) => { - return import(`../plugins/${name}`); - }, -}); - -// 访问 /hello 页面时,显示 Hello world! -const HelloProvider = React.memo((props) => { - const location = useLocation(); - if (location.pathname === '/hello') { - return
    Hello world!
    - } - return <>{props.children} -}); -HelloProvider.displayName = 'HelloProvider' - -app.use(HelloProvider); -``` - -## 自定义的业务代码 - -v0.7 的插件并不完整,自定义的业务代码可能分散在 `packages/app/client` 和 `packages/app/server` 里,不利于升级、维护。v0.8 推荐以插件包的形式整理,并使用 `yarn pm` 来管理插件。 - -## 提供了更完整的文档 - -- **欢迎**:快速了解 NocoBase -- **用户使用手册**:进一步了解 NocoBase 平台提供的核心功能 -- **插件开发教程**:进阶深入插件开发 -- **API 参考**:插件开发过程中,查阅各 API 用法 -- **客户端组件库**(正在准备中):提供 NocoBase 各组件的示例和用法 - -备注:文档还有很多细节待补充,也会根据大家进一步反馈,继续调整。 - -## 提供了更多插件示例 - -- [command](https://github.com/nocobase/nocobase/tree/develop/packages/samples/command "command") -- [custom-block](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-block "custom-block") -- [custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page "custom-page") -- [custom-signup-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-signup-page "custom-signup-page") -- [hello](https://github.com/nocobase/nocobase/tree/develop/packages/samples/hello "hello") -- [ratelimit](https://github.com/nocobase/nocobase/tree/develop/packages/samples/ratelimit "ratelimit") -- [shop-actions](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-actions "shop-actions") -- [shop-events](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-events "shop-events") -- [shop-i18n](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-i18n "shop-i18n") -- [shop-modeling](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-modeling "shop-modeling") - -## 其他新特性和功能 - -- 导入 -- 批量更新 & 编辑 -- 图形化数据表配置 -- 工作流支持查看执行历史 -- JSON 字段 diff --git a/docs/zh-CN/welcome/release/v08-changelog/pm-flow.svg b/docs/zh-CN/welcome/release/v08-changelog/pm-flow.svg deleted file mode 100644 index d15cf800c..000000000 --- a/docs/zh-CN/welcome/release/v08-changelog/pm-flow.svg +++ /dev/null @@ -1 +0,0 @@ -
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    app/client plugins
    pm.enable
    pm.disable
    pm.remove
    \ No newline at end of file diff --git a/docs/zh-CN/welcome/release/v08-changelog/pm-ui.jpg b/docs/zh-CN/welcome/release/v08-changelog/pm-ui.jpg deleted file mode 100644 index 4c8fdd3c1..000000000 Binary files a/docs/zh-CN/welcome/release/v08-changelog/pm-ui.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/v08-changelog/topright.jpg b/docs/zh-CN/welcome/release/v08-changelog/topright.jpg deleted file mode 100644 index 8ce510973..000000000 Binary files a/docs/zh-CN/welcome/release/v08-changelog/topright.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/release/v10-changelog.md b/docs/zh-CN/welcome/release/v10-changelog.md deleted file mode 100644 index 5dc68bc6c..000000000 --- a/docs/zh-CN/welcome/release/v10-changelog.md +++ /dev/null @@ -1,232 +0,0 @@ -# v0.10:更新说明 - -## 第二季度的新特性 - -- 关系字段组件改进,支持多种组件切换 - - 下拉选择器 - - 数据选择器 - - 子表单/子详情 - - 子表格 - - 文件管理器 - - 标题(仅阅读模式) -- 快捷创建关系数据,支持两种快捷创建模式 - - 下拉菜单里添加,基于标题字段快速创建一条新纪录 - - 弹窗里添加,可以配置复杂的添加表单 -- 复制操作,支持两种复制模式 - - 直接复制 - - 复制到表单里并继续填写 -- 表单数据模板 -- 筛选数据范围支持变量 -- 列表区块 -- 网格卡片区块 -- 移动端插件 -- 用户认证插件,支持不同的登录方式 - - Email/Password - - SMS - - OIDC - - SAML -- 工作流新增节点 - - 人工节点升级,支持从现有数据表里新增和编辑 - - 循环节点 - - 聚合查询节点 -- 文件管理器 - - 提供文件表模板 - - 提供文件管理器组件 - -## 应用升级 - -### Docker 安装的升级 - -无变化,升级参考 [Docker 镜像升级指南](/welcome/getting-started/upgrading/docker-compose) - -### 源码安装的升级 - -v0.10 进行了依赖的重大升级,源码升级时,以防出错,需要删掉以下目录之后再升级 - -```bash -# 删除 .umi 相关缓存 -yarn rimraf -rf "./**/{.umi,.umi-production}" -# 删除编译文件 -yarn rimraf -rf "packages/*/*/{lib,esm,es,dist,node_modules}" -# 删除依赖 -yarn rimraf -rf node_modules -``` - -更多详情参考 [Git 源码升级指南](/welcome/getting-started/upgrading/git-clone) - -### create-nocobase-app 安装的升级 - -建议 `yarn create` 重新下载新版本,再更新 .env 配置,更多详情参考 [大版本升级指南](/welcome/getting-started/upgrading/create-nocobase-app#大版本升级) - -## 即将遗弃和可能不兼容的变化 - -### 子表格字段组件 - -不兼容新版,区块字段需要删除重配(只需要 UI 重配) - -### 附件上传接口的变更 - -除了内置的 attachments 表以外,用户也可以自定义文件表,附件的上传接口由 `/api/attachments:upload` 变更为 `/api/:create`,upload 已废弃,依旧兼容 v0.10,但会在下个大版本里移除。 - -### 登录、注册接口的变更 - -nocobase 内核提供了更强大的 [auth 模块](https://github.com/nocobase/nocobase/tree/main/packages/plugins/auth),用户登录、注册、校验、注销接口变更如下: - -```bash -/api/users:signin -> /api/auth:signIn -/api/users:signup -> /api/auth:signUp -/api/users:signout -> /api/auth:signOut -/api/users:check -> /api/auth:check -``` - -注:以上 users 接口,已废弃,依旧兼容 v0.10,但会在下个大版本里移除。 - -### 日期字段筛选的调整 - -如果之前数据范围里配置了日期相关筛选,需要删掉重新配置。 - -## 第三方插件升级指南 - -### 依赖升级 - -v0.10 依赖升级,主要包括 - -- `react` 升级到 v18 -- `react-dom` 升级到 v18 -- `react-router` 升级到 v6.11 -- `umi` 升级到 v4 -- `dumi` 升级到 v2 - -插件的 `package.json` 相关依赖要更改为最新版,如: - -```diff -{ - "devDependencies": { -+ "react": "^18". -+ "react-dom": "^18". -+ "react-router-dom": "^6.11.2". -- "react": "^17". -- "react-dom": "^17". -- "react-router-dom": "^5". - } -} -``` - -### 代码修改 - -由于 react-router 的升级,代码层面也需要改动,主要变更包括 - -#### Layout 布局组件 - -Layout 布局组件需要使用 `` 代替 `props.children`。 - -```diff -import React from 'react'; -+ import { Outlet } from 'react-router-dom'; - -export default function Layout(props) { - return ( -
    -- { props.children } -+ -
    - ); -} -``` - -使用了 `React.cloneElement` 方式渲染的路由组件改造,示例: - -```diff -import React from 'react'; -+ import { Outlet } from 'react-router-dom'; - -export default function RouteComponent(props) { - return ( -
    -- { React.cloneElement(props.children, { someProp: 'p1' }) } -+ -
    - ); -} -``` - -组件改成从 `useOutletContext` 取值 - -```diff -import React from 'react'; -+ import { useOutletContext } from 'react-router-dom'; - -- export function Comp(props){ -+ export function Comp() { -+ const props = useOutletContext(); - return props.someProp; -} -``` - -#### Redirect - -`` 转为 ``。 - -```diff -- -+ -``` - -#### useHistory - -`useNavigate` 代替 `useHistory`。 - -```diff -- import { useHistory } from 'react-router-dom'; -+ import { useNavigate} from 'react-router-dom'; - -- const history = useHistory(); -+ const navigate = useNavigate(); - -- history.push('/about') -+ navigate('/about') - -- history.replace('/about') -+ navigate('/about', { replace: true }) -``` - -#### useLocation - -`useLocation()` 改为 `useLocation`。 - -```diff -- const location= useLocation(); -+ const location= useLocation(); -``` - -`const { query } = useLocation()` 改为 `useSearchParams()`。 - -```diff -- const location = useLocation(); -- const query = location.query; -- const name = query.name; -+ const [searchParams, setSearchParams] = useSearchParams(); -+ searchParams.get('name'); -``` - -#### path - -支持下面的 `path` 方式 - -``` -/groups -/groups/admin -/users/:id -/users/:id/messages -/files/* -/files/:id/* -``` - -不再支持如下方式 -``` -/tweets/:id(\d+) -/files/*/cat.jpg -/files-* -``` - -更多改动和 api 变更,请查阅 [react-router@6](https://reactrouter.com/en/main/upgrading/v5)。 diff --git a/docs/zh-CN/welcome/release/v11-changelog.md b/docs/zh-CN/welcome/release/v11-changelog.md deleted file mode 100644 index 1eacd3e0d..000000000 --- a/docs/zh-CN/welcome/release/v11-changelog.md +++ /dev/null @@ -1,120 +0,0 @@ -# v0.11:更新说明 - -## 新特性 - -- 全新的客户端 Application、Plugin 和 Router -- antd 升级到 v5 -- 新插件 - - 数据可视化 - - API 秘钥 - - Google 地图 - -## 不兼容的变化 - -### 全新的客户端 Application、Plugin 和 Router - -#### 插件的变化 - -以前必须传递一个组件,并且组件需要透传 `props.children`,例如: - -```tsx | pure -const HelloProvider = (props) => { - // do something logic - return
    - {props.children} -
    ; -} - -export default HelloProvider -``` - -现在需要改为插件的方式,例如: - -```diff | pure -+import { Plugin } from '@nocobase/client' - -const HelloProvider = (props) => { - // do something logic - return
    - {props.children} -
    ; -} - -+ export class HelloPlugin extends Plugin { -+ async load() { -+ this.app.addProvider(HelloProvider); -+ } -+ } - -- export default HelloProvider; -+ export default HelloPlugin; -``` - -插件的功能很强大,可以在 `load` 阶段做很多事情: - -- 修改路由 -- 增加 Components -- 增加 Providers -- 增加 Scopes -- 加载其他插件 - -#### 路由的变化 - -如果之前使用了 `RouteSwitchContext` 进行路由修改,现在需要通过插件替换: - -```tsx | pure -import { RouteSwitchContext } from '@nocobase/client'; - -const HelloProvider = () => { - const { routes, ...others } = useContext(RouteSwitchContext); - routes[1].routes.unshift({ - path: '/hello', - component: Hello, - }); - - return
    - - {props.children} - -
    -} -``` - -需要改为: - -```diff | pure -- import { RouteSwitchContext } from '@nocobase/client'; -+ import { Plugin } from '@nocobase/client'; - -const HelloProvider = (props) => { -- const { routes, ...others } = useContext(RouteSwitchContext); -- routes[1].routes.unshift({ -- path: '/hello', -- component: Hello, -- }); - - return
    -- - {props.children} -- -
    -} - -+ export class HelloPlugin extends Plugin { -+ async load() { -+ this.app.router.add('admin.hello', { -+ path: '/hello', -+ Component: Hello, -+ }); -+ this.app.addProvider(HelloProvider); -+ } -+ } -+ export default HelloPlugin; -``` - -更多文档和示例见 [packages/core/client/src/application/index.md](https://github.com/nocobase/nocobase/blob/main/packages/core/client/src/application/index.md) - -### antd 升级到 v5 - -- antd 相关详情查看官网 [从 v4 到 v5](https://ant.design/docs/react/migration-v5-cn) -- `@formily/antd` 替换为 `@formily/antd-v5` diff --git a/docs/zh-CN/welcome/release/v12-changelog.md b/docs/zh-CN/welcome/release/v12-changelog.md deleted file mode 100644 index f7ad0384e..000000000 --- a/docs/zh-CN/welcome/release/v12-changelog.md +++ /dev/null @@ -1,180 +0,0 @@ -# v0.12:全新的插件构建工具 - -## 新特性 - -- 全新的插件构建工具。构建好的插件将可以直接在生产环境上使用,无需二次构建。 - -## 应用升级 - -### Docker 安装的升级 - -无变化,升级参考 [Docker 镜像升级指南](/welcome/getting-started/upgrading/docker-compose) - -### 源码安装的升级 - -插件构建工具已全新升级,在拉取新源码之后,需要清除缓存。 - -```bash -git pull # 拉取新源码 -yarn clean # 清除缓存 -``` - -更多详情参考 [Git 源码升级指南](/welcome/getting-started/upgrading/git-clone) - -### create-nocobase-app 安装的升级 - -通过 `yarn create` 重新下载新版本,再更新 .env 配置,更多详情参考 [大版本升级指南](/welcome/getting-started/upgrading/create-nocobase-app#大版本升级) - -## 不兼容的变化 - -### @nocobase/app-client 和 @nocobase/app-server 合并为 @nocobase-app - -通过 create-nocobase-app 安装的应用不再有 packages/app 目录了,在 packages/app 里自定义的代码,需要移至自定义插件中。 - -### app 的 dist/client 路径变更 - -如果是自己配置的 nginx,需要做类似调整 - -```diff -server { -- root /app/nocobase/packages/app/client/dist; -+ root /app/nocobase/node_modules/@nocobase/app/dist/client; - - location / { -- root /app/nocobase/packages/app/client/dist; -+ root /app/nocobase/node_modules/@nocobase/app/dist/client; - try_files $uri $uri/ /index.html; - add_header Last-Modified $date_gmt; - add_header Cache-Control 'no-store, no-cache'; - if_modified_since off; - expires off; - etag off; - } -} -``` - -### 第三方插件需要重新构建 - -参考下文的第三方插件升级指南 - -## 第三方插件升级指南 - -### 插件目录必须同时有 `src/client` 和 `src/server` 目录 - -```js -// src/client/index.ts -import { Plugin } from '@nocobase/client'; - -class MyPlugin extends Plugin { - async load() { - // ... - } -} - -export default MyPlugin; -``` - -```js -// src/server/index.ts -import { Plugin } from '@nocobase/server'; - -class MyPlugin extends Plugin { - async load() { - // ... - } -} - -export default MyPlugin; -``` - -具体 Demo 代码可以参考:[sample-hello](https://github.com/nocobase/nocobase/tree/main/packages/samples/hello) - -### 插件的多语言放置 `src/locale` 目录 - -无论前后端,多语言翻译文件都统一放在 `src/locale` 目录,插件无需自己加载多语言包。 - -### 插件依赖的调整 - -插件的依赖分为自身的依赖和全局依赖,全局依赖直接使用全局,不会打包到插件产物中,自身的依赖会被打包到产物中。插件构建之后,生产环境即插即用,无需再安装依赖或二次构建。插件依赖的调整包括: - -- 将 `@nocobase/*` 相关包放到 `peerDependencies` 里,并指定版本号为 `0.x`; -- 其他依赖放到 `devDependencies` 里,不要放 `dependencies` 里,因为插件打包之后会将生产环境所需依赖全部提取了。 - -```diff -{ - "devDependencies": { - "@formily/react": "2.x", - "@formily/shared": "2.x", - "ahooks": "3.x", - "antd": "5.x", - "dayjs": "1.x", - "i18next": "22.x", - "react": "18.x", - "react-dom": "18.x", - "react-i18next": "11.x" - }, - "peerDependencies": { - "@nocobase/actions": "0.x", - "@nocobase/client": "0.x", - "@nocobase/database": "0.x", - "@nocobase/resourcer": "0.x", - "@nocobase/server": "0.x", - "@nocobase/test": "0.x", - "@nocobase/utils": "0.x" - } -} -``` - -### 插件的构建产物从 `lib` 目录变更为 `dist` 目录 - -dist 目录介绍 - -```bash -|- dist - |- client # 前端,umd 规范 - |- index.js - |- index.d.ts - |- server # 后端,cjs 规范 - |- index.js - |- index.d.ts - |- 其他文件 - |- locale # 多语言目录 - |- node_modules # 后端依赖 -``` - -其他相关调整包括: - -package.json 的 main 参数调整 - -```diff -{ - - "main": "./lib/server/index.js", - + "main": "./dist/server/index.js", -} -``` - -client.d.ts - -```ts -export * from './dist/client'; -export { default } from './dist/client'; -``` - -client.js - -```js -module.exports = require('./dist/client/index.js'); -``` - -server.d.ts - -```ts -export * from './dist/server'; -export { default } from './dist/server'; -``` - -server.js - -```js -module.exports = require('./dist/server/index.js'); -``` diff --git a/docs/zh-CN/welcome/release/v13-changelog.md b/docs/zh-CN/welcome/release/v13-changelog.md deleted file mode 100644 index ae2d7323c..000000000 --- a/docs/zh-CN/welcome/release/v13-changelog.md +++ /dev/null @@ -1,19 +0,0 @@ -# v0.13:全新的应用状态流转 - -## 新特性 - -### 应用状态流转 - - - -### 演示视频 - - - -## 应用升级 - -- [Docker 安装的升级](/welcome/getting-started/upgrading/docker-compose) -- [create-nocobase-app 安装的升级](/welcome/getting-started/upgrading/create-nocobase-app) -- [Git 源码安装的升级](/welcome/getting-started/upgrading/git-clone) diff --git a/docs/zh-CN/welcome/release/v14-changelog.md b/docs/zh-CN/welcome/release/v14-changelog.md deleted file mode 100644 index a0ea5783b..000000000 --- a/docs/zh-CN/welcome/release/v14-changelog.md +++ /dev/null @@ -1,124 +0,0 @@ -# v0.14:全新的插件管理器,支持通过界面添加插件 - -v0.14 实现了生产环境下插件的即插即用,可以直接通过界面添加插件,支持从 npm registry(可以是私有的)下载、本地上传、URL 下载。 - -## 新特性 - -### 全新的插件管理器界面 - - - -### 上传的插件位于 storage/plugins 目录 - -提供 storage/plugins 目录用于上传即插即用的插件,目录以 npm packages 的方式组织 - -```bash -|- /storage/ - |- /plugins/ - |- /@nocobase/ - |- /plugin-hello1/ - |- /plugin-hello2/ - |- /my-nocobase-plugin-hello1/ - |- /my-nocobase-plugin-hello2/ -``` - -### 插件的更新 - -目前仅 storage/plugins 下的插件才有更新操作,如图: - - - -备注:为了便于维护和升级,避免因为升级导致 storage 插件不可用,也可以直接将新插件放到 storage/plugins 目录下,再执行升级操作 - -## 不兼容的变化 - -### 插件目录变更 - -开发中的插件统一都放到 packages/plugins 目录下,以 npm packages 的方式组织 - -```diff -|- /packages/ -- |- /plugins/acl/ -+ |- /plugins/@nocobase/plugin-acl/ -- |- /samples/hello/ -+ |- /plugins/@nocobase/plugin-sample-hello/ -``` - -全新的目录结构为 - -```bash -# 开发中的插件 -|- /packages/ - |- /plugins/ - |- /@nocobase/ - |- /plugin-hello1/ - |- /plugin-hello2/ - |- /my-nocobase-plugin-hello1/ - |- /my-nocobase-plugin-hello2/ - -# 通过界面添加的插件 -|- /storage/ - |- /plugins/ - |- /@nocobase/ - |- /plugin-hello1/ - |- /plugin-hello2/ - |- /my-nocobase-plugin-hello1/ - |- /my-nocobase-plugin-hello2/ -``` - -### 插件名的变化 - -- 不再提供 PLUGIN_PACKAGE_PREFIX 环境变量 -- 插件名和包名统一,旧的插件名仍然可以以别名的形式存在 - -### pm add 的改进 - -变更情况 - -```diff -- pm add sample-hello -+ pm add @nocobase/plugin-sample-hello -``` - -pm add 参数说明 - -```bash -# 用 packageName 代替 pluginName,从本地查找,找不到报错 -pm add packageName - -# 只有提供了 registry 时,才从远程下载,也可以指定版本 -pm add packageName --registry=xx --auth-token=yy --version=zz - -# 也可以提供本地压缩包,多次 add 用最后的替换 -pm add /a/plugin.zip - -# 远程压缩包,同名直接替换 -pm add http://url/plugin.zip -``` - -### Nginx 配置的变化 - -新增 `/static/plugins/` location - -```conf -server { - location ^~ /static/plugins/ { - proxy_pass http://127.0.0.1:13000/static/plugins/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_connect_timeout 600; - proxy_send_timeout 600; - proxy_read_timeout 600; - send_timeout 600; - } -} -``` - -更多查看完整版的 [nocobase.conf](https://github.com/nocobase/nocobase/blob/main/docker/nocobase/nocobase.conf) - -## 插件开发指南 - -[编写第一个插件](/development/your-fisrt-plugin) diff --git a/docs/zh-CN/welcome/release/v15-changelog.md b/docs/zh-CN/welcome/release/v15-changelog.md deleted file mode 100644 index 99e85254c..000000000 --- a/docs/zh-CN/welcome/release/v15-changelog.md +++ /dev/null @@ -1,278 +0,0 @@ -# v0.15 - -## 不兼容的变化 - -### SchemaInitializer 的注册和实现变更 - -#### 定义方式变更 - -以前 `SchemaInitializer` 支持 2 种定义方式,分别为对象和组件。例如: - -```tsx -const BlockInitializers = { - title: '{{t("Add block")}}', - icon: 'PlusOutlined', - items: [ - // ... - ], - // ... -} -``` - -```tsx -const BlockInitializers = () => { - return -} -``` - -现在仅支持 `new SchemaInitializer()` 的实例。例如: - -```tsx -const blockInitializers = new SchemaInitializer({ - name: 'BlockInitializers', // 名称,和原来保持一致 - title: '{{t("Add block")}}', - icon: 'PlusOutlined', - items: [ - // ... - ], - // ... -}); -``` - -#### 参数变更 - -整体来说,`new SchemaInitializer()` 的参数参考了之前对象定义方式,但又有新的变更。具体如下: - -- 新增 `name` 必填参数,用于 `x-initializer` 的值。 -- 新增 `Component` 参数,用于定制化渲染的按钮。默认为 `SchemaInitializerButton`。 -- 新增 `componentProps`、`style` 用于配置 `Component` 的属性和样式。 -- 新增 `ItemsComponent` 参数,用于定制化渲染的列表。默认为 `SchemaInitializerItems`。 -- 新增 `itemsComponentProps`、`itemsComponentStyle` 用于配置 `ItemsComponent` 的属性和样式。 -- 新增 `popover` 参数,用于配置是否显示 `popover` 效果。 -- 新增 `useInsert` 参数,用于当 `insert` 函数需要使用 hooks 时。 -- 更改 将 `dropdown` 参数改为了 `popoverProps`,使用 `Popover` 代替了 `Dropdown`。 -- items 参数变更 - - 新增 `useChildren` 函数,用于动态控制子项。 - - 更改 `visible` 参数改为了 `useVisible` 函数,用于动态控制是否显示。 - - 更改 将 `component` 参数改为了 `Component`,用于列表项的渲染。 - - 更改 将 `key` 参数改为了 `name`,用于列表项的唯一标识。 - -案例1: - -```diff -- export const BlockInitializers = { -+ export const blockInitializers = new SchemaInitializer({ -+ name: 'BlockInitializers', - 'data-testid': 'add-block-button-in-page', - title: '{{t("Add block")}}', - icon: 'PlusOutlined', - wrap: gridRowColWrap, - items: [ - { -- key: 'dataBlocks', -+ name: 'data-blocks', - type: 'itemGroup', - title: '{{t("Data blocks")}}', - children: [ - { -- key: 'table', -+ name: 'table', -- type: 'item', // 当有 Component 参数时,就不需要此了 - title: '{{t("Table")}}', -- component: TableBlockInitializer, -+ Component: TableBlockInitializer, - }, - { - key: 'form', - type: 'item', - title: '{{t("Form")}}', - component: FormBlockInitializer, - } - ], - }, - ], -}); -``` - -案例2: - -原来是组件定义的方式: - -```tsx -export const BulkEditFormItemInitializers = (props: any) => { - const { t } = useTranslation(); - const { insertPosition, component } = props; - const associationFields = useAssociatedFormItemInitializerFields({ readPretty: true, block: 'Form' }); - return ( - - ); -}; -``` - -现在需要改为 `new SchemaInitializer()` 的方式: - -```tsx -const bulkEditFormItemInitializers = new SchemaInitializer({ - name: 'BulkEditFormItemInitializers', - 'data-testid': 'configure-fields-button-of-bulk-edit-form-item', - wrap: gridRowColWrap, - icon: 'SettingOutlined', - // 原 insertPosition 和 component 是透传的,这里不用管,也是透传的 - items: [ - { - type: 'itemGroup', - title: t('Display fields'), - name: 'display-fields', // 记得加上 name - useChildren: useCustomBulkEditFormItemInitializerFields, // 使用到了 useChildren - }, - { - type: 'divider', - }, - { - title: t('Add text'), - name: 'add-text', - Component: BlockItemInitializer, // component 替换为 Component - }, - ] -}); -``` - -关于参数的具体说明可以参考 `SchemaInitializer` 的类型定义,以及 [SchemaInitializer 文档](https://client.docs.nocobase.com/client/schema-initializer)。 - -#### 实现原理变更 - -以前是将所有 `items` 转为 `Menu` 组件的 items JSON 对象,最后渲染成 Menu 列表。 - -现在默认情况下仅仅是渲染 `items` 列表项的 `Component` 组件,至于 `Component` 组件内部如何渲染取决于自身,最后也不会拼接成一个 JSON 对象。 - -具体说明参考 `SchemaInitializer` 的 [Nested items 示例](https://client.docs.nocobase.com/client/schema-initializer#nested-items)。 - -#### 列表中的组件获取参数方式变更 - -之前是通过 `props` 获取 `insert` 函数,现在需要通过 `useSchemaInitializer()` 获取。例如: - -```diff -const FormBlockInitializer = (props) => { -- const { insert } = props; -+ const { insert } = useSchemaInitializer(); - // ... -} - -export const blockInitializers = new SchemaInitializer({ - name: 'BlockInitializers', - items: [ - { - name: 'form', - Component: FormBlockInitializer - } - ] -}); -``` - -#### 注册方式变更 - -以前是通过 `SchemaInitializerProvider` 进行注册。例如: - -```tsx - -``` - -现在需要改为插件的方式。例如: - -```tsx -import { Plugin } from '@nocobase/client'; - -class MyPlugin extends Plugin { - async load() { - this.app.schemaInitializerManager.add(blockInitializers); - this.app.addComponents({ ManualActionDesigner }); - } -} -``` - -#### 修改方式变更 - -以前是通过 `SchemaInitializerContext` 获取到全部的 `Initializers` 然后进行增删改。例如下面代码是为了往 `BlockInitializers` 中的 `media` 下添加 `Hello`: - -```tsx -const items = useContext(SchemaInitializerContext); -const mediaItems = items.BlockInitializers.items.find((item) => item.key === 'media'); - -if (process.env.NODE_ENV !== 'production' && !mediaItems) { - throw new Error('media block initializer not found'); -} - -const children = mediaItems.children; -if (!children.find((item) => item.key === 'hello')) { - children.push({ - key: 'hello', - type: 'item', - title: '{{t("Hello block")}}', - component: HelloBlockInitializer, - }); -} -``` - -新的方式则通过插件的方式更简洁的进行修改。例如: - -```tsx -class MyPlugin extends Plugin { - async load() { - // 获取 BlockInitializers - const blockInitializers = this.app.schemaInitializerManager.get('BlockInitializers'); - - // 添加 Hello - blockInitializers.add('media.hello', { - title: '{{t("Hello block")}}', - Component: HelloBlockInitializer, - }) - } -} -``` - -#### 使用方式变更 - -之前使用 `useSchemaInitializer` 的方式进行渲染,现在需要改为 `useSchemaInitializerRender`,并且参数需要增加 `x-initializer-props`。例如: - -```diff -- const { render } = useSchemaInitializer(fieldSchema['x-initializer']); -+ const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']); - -render(); -render({ style: { marginLeft: 8 } }) -``` - - -更多说明可以参考 [SchemaInitializer 文档](https://client.docs.nocobase.com/client/schema-initializer)。 diff --git a/package.json b/package.json index 7c8d4a02c..b402fad1c 100644 --- a/package.json +++ b/package.json @@ -85,8 +85,6 @@ "auto-changelog": "^2.4.0", "axios": "^0.26.1", "commander": "^9.2.0", - "dumi": "^2.2.0", - "dumi-theme-nocobase": "^0.2.18", "eslint-plugin-jest-dom": "^5.0.1", "eslint-plugin-testing-library": "^5.11.0", "execa": "^5.1.1", diff --git a/packages/core/cli/package.json b/packages/core/cli/package.json index 3d84da21f..ad87c1208 100644 --- a/packages/core/cli/package.json +++ b/packages/core/cli/package.json @@ -20,7 +20,7 @@ "pm2": "^5.2.0", "portfinder": "^1.0.28", "serve": "^13.0.2", - "tsx": "^3.12.7" + "tsx": "^4.6.2" }, "devDependencies": { "@nocobase/devtools": "0.17.0-alpha.2" diff --git a/packages/core/cli/src/commands/doc.js b/packages/core/cli/src/commands/doc.js index 1fcda2a02..ebd80e97a 100644 --- a/packages/core/cli/src/commands/doc.js +++ b/packages/core/cli/src/commands/doc.js @@ -58,7 +58,7 @@ module.exports = (cli) => { } else { run('dumi', [command], { env: { - APP_ROOT: process.env.APP_ROOT, + APP_ROOT: process.env.APP_ROOT || './packages/core/client', DOC_LANG: process.env.DOC_LANG, }, }); diff --git a/packages/core/client/docs/en-US/core/ui-schema/schema-component.md b/packages/core/client/docs/en-US/core/ui-schema/schema-component.md index 70c16cfe0..9a6aa8310 100644 --- a/packages/core/client/docs/en-US/core/ui-schema/schema-component.md +++ b/packages/core/client/docs/en-US/core/ui-schema/schema-component.md @@ -26,9 +26,13 @@ Schema 渲染的上下文。 ## Hooks -### useSchemaComponentContext() +### useSchemaOptionsContext() -用于获取 `SchemaComponentContext` 的值,是 `useContext(SchemaComponentContext)` 的封装。 +用于获取注册的 `scope` 和 `components`。 + +```tsx | pure +const { scope, components } = useSchemaOptionsContext(); +``` ## 组件 diff --git a/packages/core/client/docs/zh-CN/core/ui-schema/schema-component.md b/packages/core/client/docs/zh-CN/core/ui-schema/schema-component.md index 70c16cfe0..9a6aa8310 100644 --- a/packages/core/client/docs/zh-CN/core/ui-schema/schema-component.md +++ b/packages/core/client/docs/zh-CN/core/ui-schema/schema-component.md @@ -26,9 +26,13 @@ Schema 渲染的上下文。 ## Hooks -### useSchemaComponentContext() +### useSchemaOptionsContext() -用于获取 `SchemaComponentContext` 的值,是 `useContext(SchemaComponentContext)` 的封装。 +用于获取注册的 `scope` 和 `components`。 + +```tsx | pure +const { scope, components } = useSchemaOptionsContext(); +``` ## 组件 diff --git a/packages/core/client/docs/zh-CN/core/utils.md b/packages/core/client/docs/zh-CN/core/utils.md index 711c8ad0a..9fb88088e 100644 --- a/packages/core/client/docs/zh-CN/core/utils.md +++ b/packages/core/client/docs/zh-CN/core/utils.md @@ -5,3 +5,9 @@ ### tval 用于输出多语言的字符串。 + +- 类型 + + + +- 示例 diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 8152cd17a..11efd9491 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -74,7 +74,7 @@ "@types/react-big-calendar": "^1.6.4", "axios-mock-adapter": "^1.20.0", "dumi": "^2.2.0", - "dumi-theme-nocobase": "^0.2.18" + "dumi-theme-nocobase": "^0.2.19" }, "gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644" } diff --git a/yarn.lock b/yarn.lock index 8cea7f4dd..02c6bff92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2599,6 +2599,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.13.tgz#70ef455455654c7800c31ae55ae295d81712238c" +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + "@esbuild/android-arm@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" @@ -2611,6 +2616,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.13.tgz#15db83099855fc4193658a40687893ee5c95d7a9" +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + "@esbuild/android-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" @@ -2623,6 +2633,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.13.tgz#473d589219e1c06e305cf61ca77b8f69d9b6ffab" +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + "@esbuild/darwin-arm64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" @@ -2635,6 +2650,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.13.tgz#0f525b2c1821a0591a06963582e5dc749ba51d45" +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + "@esbuild/darwin-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" @@ -2647,6 +2667,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.13.tgz#81965b690bae86bf1289b2ce0732506fd41fb545" +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + "@esbuild/freebsd-arm64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" @@ -2659,6 +2684,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.13.tgz#895bb37fdea886db09549119158e044f146861f0" +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + "@esbuild/freebsd-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" @@ -2671,6 +2701,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.13.tgz#0b1dfde3ff1b18f03f71e460f91dc463e6a23903" +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + "@esbuild/linux-arm64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" @@ -2683,6 +2718,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.13.tgz#350febed5d32d8ec1a424a4c4d7c9ba885604960" +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + "@esbuild/linux-arm@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" @@ -2695,6 +2735,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.13.tgz#47639d73d894026350eaccf7c174f1d26b747d6a" +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + "@esbuild/linux-ia32@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" @@ -2707,6 +2752,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.13.tgz#a901a16349c58bf6f873bced36bdf46a5f4dac5d" +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + "@esbuild/linux-loong64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" @@ -2719,6 +2769,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.13.tgz#faa08db402c18e351234719e00aba98867aa34ce" +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + "@esbuild/linux-mips64el@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" @@ -2731,6 +2786,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.13.tgz#2123a54b49ddc1a1dff057bba8a9a5e9f26e5009" +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + "@esbuild/linux-ppc64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" @@ -2743,6 +2803,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.13.tgz#9a9befd275a6a3f5baeed89aaafb746df7ba735d" +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + "@esbuild/linux-riscv64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" @@ -2755,6 +2820,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.13.tgz#6644a5b5840fa0c3ffade6f87d943413ece520a8" +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + "@esbuild/linux-s390x@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" @@ -2767,6 +2837,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.13.tgz#c1367a0a02b37f6b0382e71d9c9d97352ca23013" +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + "@esbuild/linux-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" @@ -2779,6 +2854,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.13.tgz#892674f0918ee3f5e523270cf49a69a557fb64c0" +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + "@esbuild/netbsd-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" @@ -2791,6 +2871,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.13.tgz#67954292195ecbdae33ab09a9ae6a7f566e49d04" +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + "@esbuild/openbsd-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" @@ -2803,6 +2888,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.13.tgz#b3eef873dfab547fbe7bcdb3573e1c59dea676b7" +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + "@esbuild/sunos-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" @@ -2815,6 +2905,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.13.tgz#b368080f42dbb5ae926d0567c02bcd68a34c5efd" +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + "@esbuild/win32-arm64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" @@ -2827,6 +2922,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.13.tgz#11dedda0e8cfb5f781411ea362b2040304be0fc3" +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + "@esbuild/win32-ia32@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" @@ -2839,6 +2939,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.13.tgz#6b8aa95515c05827b7c24c9db9581943566e0dcb" +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + "@esbuild/win32-x64@0.17.19": version "0.17.19" resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" @@ -2851,6 +2956,11 @@ version "0.18.13" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.13.tgz#031f69b1f4cf62a18c38d502458c0b8b02625461" +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -10777,10 +10887,10 @@ dumi-assets-types@2.0.0-alpha.0: version "2.0.0-alpha.0" resolved "https://registry.npmmirror.com/dumi-assets-types/-/dumi-assets-types-2.0.0-alpha.0.tgz#46bf619ed1cb6d27bbe6a9cfe4be51e5e9589981" -dumi-theme-nocobase@^0.2.18: - version "0.2.18" - resolved "https://registry.yarnpkg.com/dumi-theme-nocobase/-/dumi-theme-nocobase-0.2.18.tgz#91f9462ad738f347c1e6fb4e304170a891aa8804" - integrity sha512-M+FP/gL9yRazibyBXPUV4XRGaJb+MT+o4VU+oMja1RAWcCvzTYNeNQsXifpAvVnUdaGwNlESyJCU9hlLzB8ARQ== +dumi-theme-nocobase@^0.2.19: + version "0.2.19" + resolved "https://registry.yarnpkg.com/dumi-theme-nocobase/-/dumi-theme-nocobase-0.2.19.tgz#e03ff2f122c7c33bad392b77984453a72716dc06" + integrity sha512-YsHZhmdtMxnGnr+nAPzGLj0nhhTiQJQ9tAx5zekgpmHMY5G0iiV7IyjGxZhvk4/fDMn8vsify/qgA4VM5gItDQ== dependencies: "@ant-design/icons" "^5.1.3" "@babel/runtime" "^7.22.3" @@ -11320,6 +11430,34 @@ esbuild@^0.18.2: "@esbuild/win32-ia32" "0.18.13" "@esbuild/win32-x64" "0.18.13" +esbuild@~0.18.20: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -12448,6 +12586,11 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + ftp@^0.3.10: version "0.3.10" resolved "https://registry.npmmirror.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" @@ -12618,6 +12761,13 @@ get-tsconfig@^4.4.0: dependencies: resolve-pkg-maps "^1.0.0" +get-tsconfig@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.2.tgz#0dcd6fb330391d46332f4c6c1bf89a6514c2ddce" + integrity sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A== + dependencies: + resolve-pkg-maps "^1.0.0" + get-uri@3: version "3.0.2" resolved "https://registry.npmmirror.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" @@ -22812,7 +22962,7 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tsx@^3.12.2, tsx@^3.12.7: +tsx@^3.12.2: version "3.12.7" resolved "https://registry.yarnpkg.com/tsx/-/tsx-3.12.7.tgz#b3b8b0fc79afc8260d1e14f9e995616c859a91e9" dependencies: @@ -22822,6 +22972,16 @@ tsx@^3.12.2, tsx@^3.12.7: optionalDependencies: fsevents "~2.3.2" +tsx@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.6.2.tgz#8e9c1456ad4f1102c5c42c5be7fd428259b7d39b" + integrity sha512-QPpBdJo+ZDtqZgAnq86iY/PD2KYCUPSUGIunHdGwyII99GKH+f3z3FZ8XNFLSGQIA4I365ui8wnQpl8OKLqcsg== + dependencies: + esbuild "~0.18.20" + get-tsconfig "^4.7.2" + optionalDependencies: + fsevents "~2.3.3" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"