├── packages ├── devkit │ ├── .npmignore │ ├── templates │ │ └── plugin │ │ │ ├── src │ │ │ ├── server │ │ │ │ ├── commands │ │ │ │ │ └── .gitkeep │ │ │ │ ├── migrations │ │ │ │ │ └── .gitkeep │ │ │ │ ├── collections │ │ │ │ │ └── .gitkeep │ │ │ │ ├── index.ts.tpl │ │ │ │ └── plugin.ts.tpl │ │ │ ├── client │ │ │ │ ├── index.ts.tpl │ │ │ │ └── plugin.tsx.tpl │ │ │ └── index.ts │ │ │ ├── README.md.tpl │ │ │ ├── .npmignore.tpl │ │ │ ├── README.zh-CN.md.tpl │ │ │ ├── client.js │ │ │ ├── server.js │ │ │ ├── client.d.ts │ │ │ ├── server.d.ts │ │ │ └── package.json.tpl │ ├── src │ │ ├── builder │ │ │ ├── build │ │ │ │ ├── utils │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── tarPlugin.ts │ │ │ ├── buildable-packages │ │ │ │ └── skip-package.ts │ │ │ └── interfaces.ts │ │ ├── index.ts │ │ ├── notify-updates.ts │ │ ├── commands │ │ │ ├── postinstall.ts │ │ │ ├── upgrade.ts │ │ │ ├── install.ts │ │ │ ├── tar.ts │ │ │ ├── clean.ts │ │ │ ├── global.ts │ │ │ ├── create-plugin.ts │ │ │ ├── index.ts │ │ │ ├── create-nginx-conf.ts │ │ │ └── build.ts │ │ ├── constants.ts │ │ └── cli.ts │ ├── bin │ │ └── cli.js │ ├── tsconfig.json │ └── tsup.config.ts ├── di │ ├── .npmignore │ ├── src │ │ ├── types │ │ │ ├── container-scope.type.ts │ │ │ ├── container-identifier.type.ts │ │ │ ├── abstract-constructable.type.ts │ │ │ └── service-identifier.type.ts │ │ ├── empty.const.ts │ │ ├── token.class.ts │ │ ├── interfaces │ │ │ └── service-options.interface.ts │ │ ├── error │ │ │ └── cannot-inject-value.error.ts │ │ └── index.ts │ └── package.json ├── tego │ ├── .npmignore │ ├── bin │ │ └── tego.js │ └── src │ │ └── __tests__ │ │ └── utils.test.ts ├── cache │ ├── .npmignore │ ├── src │ │ ├── index.ts │ │ ├── bloom-filter │ │ │ └── index.ts │ │ └── __tests__ │ │ │ └── bloom-filter.test.ts │ └── package.json ├── client │ ├── .npmignore │ └── package.json ├── core │ ├── .npmignore │ └── src │ │ ├── locale │ │ └── index.ts │ │ ├── errors │ │ ├── plugin-not-exist.ts │ │ ├── plugin-command-error.ts │ │ ├── application-not-install.ts │ │ └── handler.ts │ │ ├── gateway │ │ ├── index.ts │ │ └── types.ts │ │ ├── plugin-manager │ │ ├── index.ts │ │ ├── types.ts │ │ ├── middleware.ts │ │ └── options │ │ │ └── collection.ts │ │ ├── pub-sub-manager │ │ ├── index.ts │ │ └── types.ts │ │ ├── middlewares │ │ ├── index.ts │ │ ├── extract-client-ip.ts │ │ ├── data-template.ts │ │ └── i18n.ts │ │ ├── __tests__ │ │ ├── plugins │ │ │ ├── test-a.ts │ │ │ ├── test-b.ts │ │ │ ├── plugin1.ts │ │ │ ├── plugin2.ts │ │ │ └── plugin3.ts │ │ ├── fixtures │ │ │ └── long-json.ts │ │ ├── main-data-source.test.ts │ │ └── app-supervisor.test.ts │ │ ├── commands │ │ ├── destroy.ts │ │ ├── db-auth.ts │ │ ├── refresh.ts │ │ ├── db-clean.ts │ │ ├── upgrade.ts │ │ ├── stop.ts │ │ ├── migrator.ts │ │ ├── restart.ts │ │ ├── console.ts │ │ ├── install.ts │ │ └── db-sync.ts │ │ ├── helpers │ │ └── multiple-instance-manager.ts │ │ ├── index.ts │ │ ├── migrations │ │ ├── 20240106082756-update-plugins.ts │ │ ├── 20240705000001-remove-pkgs-approval.ts │ │ ├── 20230912193824-package-name-unique.ts │ │ └── 20230912294620-update-pkg.ts │ │ ├── cache │ │ └── index.ts │ │ ├── migration.ts │ │ ├── main-data-source.ts │ │ ├── acl │ │ └── index.ts │ │ └── environment.ts ├── loader │ ├── .npmignore │ └── package.json ├── logger │ ├── .npmignore │ ├── src │ │ ├── index.ts │ │ └── config.ts │ └── package.json ├── schema │ ├── .npmignore │ ├── src │ │ ├── json-schema │ │ │ ├── polyfills │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── global.d.ts │ │ ├── react │ │ │ ├── shared │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── global.d.ts │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── VoidField.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── ObjectField.tsx │ │ │ │ └── ArrayField.tsx │ │ │ └── __tests__ │ │ │ │ └── shared.tsx │ │ ├── reactive-react │ │ │ ├── shared │ │ │ │ ├── index.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── global.ts │ │ │ ├── index.ts │ │ │ ├── hooks │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ └── types.ts │ │ ├── core │ │ │ ├── effects │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── global.d.ts │ │ │ ├── __tests__ │ │ │ │ ├── shared.ts │ │ │ │ └── object.spec.ts │ │ │ └── models │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ ├── validator │ │ │ ├── index.ts │ │ │ ├── template.ts │ │ │ └── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ └── registry.spec.ts │ │ ├── shared │ │ │ ├── path.ts │ │ │ ├── uid.ts │ │ │ ├── case.ts │ │ │ ├── instanceof.ts │ │ │ ├── index.ts │ │ │ ├── global.ts │ │ │ ├── middleware.ts │ │ │ ├── deprecate.ts │ │ │ └── string.ts │ │ ├── reactive │ │ │ ├── global.d.ts │ │ │ ├── annotations │ │ │ │ └── index.ts │ │ │ ├── untracked.ts │ │ │ ├── index.ts │ │ │ ├── __tests__ │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── observable.ts │ │ │ ├── action.ts │ │ │ ├── batch.ts │ │ │ └── checkers.ts │ │ └── path │ │ │ ├── __tests__ │ │ │ └── basic.spec.ts │ │ │ └── contexts.ts │ └── package.json ├── sdk │ ├── .npmignore │ ├── src │ │ ├── index.ts │ │ └── getSubAppName.ts │ └── package.json ├── server │ ├── .npmignore │ └── package.json ├── test │ ├── .npmignore │ ├── e2e.d.ts │ ├── src │ │ ├── index.ts │ │ ├── e2e │ │ │ └── index.ts │ │ ├── client │ │ │ └── index.ts │ │ └── __tests__ │ │ │ └── __snapshots__ │ │ │ └── omitSomeFields.test.ts.snap │ ├── client.d.ts │ ├── e2e.js │ ├── server.d.ts │ ├── client.js │ ├── server.js │ ├── setup │ │ ├── settings.sqlite.js │ │ ├── server.ts │ │ └── settings.postgres.js │ └── playwright │ │ └── tests │ │ └── auth.setup.ts ├── utils │ ├── .npmignore │ └── src │ │ ├── __tests__ │ │ ├── test.ts │ │ ├── import-module.test.ts │ │ └── forEach.test.ts │ │ ├── log.ts │ │ ├── fs-exists.ts │ │ ├── url.ts │ │ ├── getCurrentStacks.ts │ │ ├── uid.ts │ │ ├── forEach.ts │ │ ├── i18n.ts │ │ ├── cluster.ts │ │ ├── merge.ts │ │ ├── types │ │ └── constructable.type.ts │ │ ├── measure-execution-time.ts │ │ ├── parseHTML.ts │ │ ├── mixin │ │ └── index.ts │ │ ├── number.ts │ │ ├── client.ts │ │ ├── requireModule.ts │ │ ├── isPortalInBody.ts │ │ └── index.ts ├── acl │ ├── .npmignore │ ├── src │ │ ├── no-permission-error.ts │ │ ├── index.ts │ │ ├── skip-middleware.ts │ │ ├── acl-available-action.ts │ │ └── __tests__ │ │ │ └── allow-manager.test.ts │ └── package.json ├── auth │ ├── .npmignore │ ├── src │ │ ├── base │ │ │ ├── token-blacklist-service.ts │ │ │ └── token-control-service.ts │ │ ├── index.ts │ │ └── actions.ts │ └── package.json ├── components │ ├── .npmignore │ ├── src │ │ ├── date-picker │ │ │ └── style.ts │ │ ├── time-picker │ │ │ └── style.ts │ │ ├── lightbox │ │ │ └── index.ts │ │ ├── form-item │ │ │ ├── hooks │ │ │ │ └── index.ts │ │ │ └── style │ │ │ │ ├── grid.tsx │ │ │ │ └── animation.tsx │ │ ├── __builtins__ │ │ │ ├── hooks │ │ │ │ ├── useToken.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useConfig.ts │ │ │ │ └── usePrefixCls.ts │ │ │ ├── index.ts │ │ │ ├── pickDataProps.ts │ │ │ └── loading.ts │ │ ├── select-table │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ └── useSize.tsx │ │ │ └── style.ts │ │ ├── form-grid │ │ │ └── style.ts │ │ ├── array-collapse │ │ │ └── style.ts │ │ ├── number-picker │ │ │ └── index.tsx │ │ ├── switch │ │ │ └── index.tsx │ │ ├── space │ │ │ └── index.tsx │ │ ├── preview-text │ │ │ └── style.ts │ │ ├── array-cards │ │ │ └── style.ts │ │ ├── form-button-group │ │ │ └── style.ts │ │ ├── radio │ │ │ └── index.tsx │ │ ├── checkbox │ │ │ └── index.tsx │ │ ├── tree-select │ │ │ └── index.tsx │ │ ├── cascader │ │ │ └── index.tsx │ │ ├── select │ │ │ └── index.tsx │ │ ├── transfer │ │ │ └── index.tsx │ │ └── input │ │ │ └── index.tsx │ └── package.json ├── database │ ├── .npmignore │ └── src │ │ ├── __tests__ │ │ ├── fixtures │ │ │ ├── collections │ │ │ │ ├── test.jpg │ │ │ │ ├── posts.ts │ │ │ │ ├── tags.js │ │ │ │ ├── user.json │ │ │ │ ├── extend.ts │ │ │ │ ├── delay-extend.ts │ │ │ │ ├── extend2.ts │ │ │ │ └── delay-extend2.ts │ │ │ ├── c2 │ │ │ │ └── a.ts │ │ │ ├── c0 │ │ │ │ └── a.ts │ │ │ ├── c1 │ │ │ │ └── b.ts │ │ │ └── migrations │ │ │ │ ├── m1.ts │ │ │ │ └── m2.ts │ │ ├── index.ts │ │ ├── inhertits │ │ │ ├── helper.ts │ │ │ └── inherited-map.test.ts │ │ ├── percent2float.test.ts │ │ ├── value-parsers │ │ │ └── base.test.ts │ │ ├── fields │ │ │ ├── uuid-field.test.ts │ │ │ ├── set.test.ts │ │ │ └── nanoid-field.test.ts │ │ ├── operator │ │ │ └── notIn.test.ts │ │ ├── hooks │ │ │ └── afterCreateWithAssociations.test.ts │ │ └── database.import.test.ts │ │ ├── errors │ │ ├── zero-column-table-error.ts │ │ └── identifier-error.ts │ │ ├── sql-collection │ │ └── index.ts │ │ ├── sql-parser │ │ └── readme.md │ │ ├── repositories │ │ └── view-repository.ts │ │ ├── fields │ │ ├── has-inverse-field.ts │ │ ├── text-field.ts │ │ ├── time-field.ts │ │ ├── string-field.ts │ │ ├── boolean-field.ts │ │ ├── virtual-field.ts │ │ ├── uuid-field.ts │ │ ├── set-field.ts │ │ ├── json-field.ts │ │ └── array-field.ts │ │ ├── operators │ │ ├── notIn.ts │ │ ├── eq.ts │ │ ├── association.ts │ │ ├── boolean.ts │ │ ├── utils.ts │ │ ├── ne.ts │ │ ├── index.ts │ │ └── jsonb.ts │ │ ├── relation-repository │ │ ├── hasone-repository.ts │ │ ├── types.ts │ │ └── belongs-to-repository.ts │ │ ├── listeners │ │ ├── index.ts │ │ └── adjacency-list.ts │ │ ├── value-parsers │ │ ├── json-value-parser.ts │ │ ├── to-one-value-parser.ts │ │ ├── base-value-parser.ts │ │ └── boolean-value-parser.ts │ │ ├── query-interface │ │ └── query-interface-builder.ts │ │ ├── decorators │ │ ├── target-collection-decorator.ts │ │ └── must-have-filter-decorator.ts │ │ └── view-collection.ts ├── evaluators │ ├── .npmignore │ ├── src │ │ ├── index.ts │ │ ├── utils │ │ │ ├── string.ts │ │ │ └── formulajs.ts │ │ ├── client │ │ │ ├── engines │ │ │ │ ├── string.ts │ │ │ │ └── formulajs.ts │ │ │ └── index.tsx │ │ └── server │ │ │ └── index.ts │ └── package.json ├── requirejs │ ├── .npmignore │ ├── src │ │ └── index.ts │ └── package.json ├── resourcer │ ├── .npmignore │ ├── src │ │ ├── __tests__ │ │ │ ├── actions │ │ │ │ ├── demo0.js │ │ │ │ └── demo1.ts │ │ │ ├── middlewares │ │ │ │ ├── demo1.ts │ │ │ │ └── demo0.js │ │ │ └── resources │ │ │ │ └── demo.ts │ │ └── index.ts │ └── package.json ├── actions │ ├── .npmignore │ ├── src │ │ ├── constants.ts │ │ └── actions │ │ │ ├── set.ts │ │ │ ├── remove.ts │ │ │ ├── first-or-create.ts │ │ │ ├── update-or-create.ts │ │ │ ├── get.ts │ │ │ ├── update.ts │ │ │ ├── index.ts │ │ │ ├── count.ts │ │ │ ├── destroy.ts │ │ │ ├── toggle.ts │ │ │ ├── create.ts │ │ │ ├── proxy-to-repository.ts │ │ │ └── add.ts │ └── package.json ├── data-source │ ├── .npmignore │ ├── src │ │ ├── resource-manager.ts │ │ ├── data-source-with-database.ts │ │ ├── collection-field.ts │ │ ├── sequelize-data-source.ts │ │ ├── index.ts │ │ ├── utils.ts │ │ ├── default-actions │ │ │ ├── move.ts │ │ │ ├── proxy-to-repository.ts │ │ │ └── utils.ts │ │ ├── repository.ts │ │ └── data-source-factory.ts │ └── package.json └── globals │ ├── .npmignore │ └── package.json ├── .husky ├── pre-commit └── commit-msg ├── commitlint.config.ts ├── playwright.config.ts ├── .github ├── dependabot.yml └── workflows │ ├── lint-pr-title.yaml │ ├── tests.yaml │ ├── full-build.yaml │ └── test-coverage.yaml ├── tsconfig.server.json ├── .prettierignore ├── vitest.config.ts ├── .dockerignore ├── .editorconfig ├── .gitignore ├── pnpm-workspace.yaml ├── SECURITY.md ├── .cursor └── rules │ └── ai-assistant.md └── .cursorignore /packages/devkit/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /packages/di/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /packages/tego/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /packages/cache/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/client/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/loader/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /packages/logger/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/schema/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/sdk/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/server/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/test/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/utils/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/acl/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src 3 | -------------------------------------------------------------------------------- /packages/auth/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src 3 | -------------------------------------------------------------------------------- /packages/components/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/database/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/evaluators/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/requirejs/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/resourcer/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src -------------------------------------------------------------------------------- /packages/test/e2e.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/e2e'; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | # pre-commit 2 | pnpm lint-staged 3 | -------------------------------------------------------------------------------- /packages/actions/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src 3 | -------------------------------------------------------------------------------- /packages/data-source/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src 3 | -------------------------------------------------------------------------------- /packages/globals/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src 3 | -------------------------------------------------------------------------------- /packages/test/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './server'; 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | # commit-msg 2 | pnpm commitlint --edit 3 | -------------------------------------------------------------------------------- /packages/core/src/locale/index.ts: -------------------------------------------------------------------------------- 1 | export * from './locale'; 2 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/test.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/server/commands/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/server/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/client'; 2 | -------------------------------------------------------------------------------- /packages/test/e2e.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/e2e'); 2 | -------------------------------------------------------------------------------- /packages/test/server.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/server'; 2 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/server/collections/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/README.md.tpl: -------------------------------------------------------------------------------- 1 | # {{{packageName}}} 2 | -------------------------------------------------------------------------------- /packages/test/client.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/client'); 2 | -------------------------------------------------------------------------------- /packages/test/server.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/server'); 2 | -------------------------------------------------------------------------------- /packages/devkit/src/builder/build/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/.npmignore.tpl: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src 3 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/README.zh-CN.md.tpl: -------------------------------------------------------------------------------- 1 | # {{{packageName}}} 2 | -------------------------------------------------------------------------------- /packages/devkit/bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import('../lib/cli.mjs'); 3 | -------------------------------------------------------------------------------- /commitlint.config.ts: -------------------------------------------------------------------------------- 1 | export default { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /packages/components/src/date-picker/style.ts: -------------------------------------------------------------------------------- 1 | import 'antd/lib/date-picker/style/index'; 2 | -------------------------------------------------------------------------------- /packages/components/src/time-picker/style.ts: -------------------------------------------------------------------------------- 1 | import 'antd/lib/time-picker/style/index'; 2 | -------------------------------------------------------------------------------- /packages/core/src/errors/plugin-not-exist.ts: -------------------------------------------------------------------------------- 1 | export class PluginNotExist extends Error {} 2 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | export { mockDatabase } from '../mock-database'; 2 | -------------------------------------------------------------------------------- /packages/requirejs/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './requirejs'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/schema/src/json-schema/polyfills/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SPECIFICATION_1_0'; 2 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/client.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/client/index.js'); 2 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/server.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/server/index.js'); 2 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/client/index.ts.tpl: -------------------------------------------------------------------------------- 1 | export { default } from './plugin'; 2 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/server/index.ts.tpl: -------------------------------------------------------------------------------- 1 | export { default } from './plugin'; 2 | -------------------------------------------------------------------------------- /packages/utils/src/__tests__/test.ts: -------------------------------------------------------------------------------- 1 | const test = 'hello'; 2 | 3 | export default { test }; 4 | -------------------------------------------------------------------------------- /packages/core/src/errors/plugin-command-error.ts: -------------------------------------------------------------------------------- 1 | export class PluginCommandError extends Error {} 2 | -------------------------------------------------------------------------------- /packages/evaluators/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './server'; 2 | export * from './server'; 3 | -------------------------------------------------------------------------------- /packages/schema/src/json-schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from './schema'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/schema/src/react/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context'; 2 | export * from './connect'; 3 | -------------------------------------------------------------------------------- /packages/actions/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_PAGE = 1; 2 | export const DEFAULT_PER_PAGE = 20; 3 | -------------------------------------------------------------------------------- /packages/database/src/errors/zero-column-table-error.ts: -------------------------------------------------------------------------------- 1 | export class ZeroColumnTableError extends Error {} 2 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './gc'; 2 | export * from './immediate'; 3 | -------------------------------------------------------------------------------- /packages/database/src/sql-collection/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sql-model'; 2 | export * from './sql-collection'; 3 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './server'; 2 | export { default } from './server'; 3 | -------------------------------------------------------------------------------- /packages/schema/src/core/effects/index.ts: -------------------------------------------------------------------------------- 1 | export * from './onFormEffects'; 2 | export * from './onFieldEffects'; 3 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tachybase/test/e2e'; 2 | 3 | export default defineConfig(); 4 | -------------------------------------------------------------------------------- /packages/components/src/lightbox/index.ts: -------------------------------------------------------------------------------- 1 | import Lightbox from './react-image-lightbox'; 2 | 3 | export { Lightbox }; 4 | -------------------------------------------------------------------------------- /packages/core/src/gateway/index.ts: -------------------------------------------------------------------------------- 1 | export { Gateway } from './gateway'; 2 | export { WSServer } from './ws-server'; 3 | -------------------------------------------------------------------------------- /packages/core/src/plugin-manager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './clientStaticUtils'; 2 | export * from './plugin-manager'; 3 | -------------------------------------------------------------------------------- /packages/di/src/types/container-scope.type.ts: -------------------------------------------------------------------------------- 1 | export type ContainerScope = 'singleton' | 'container' | 'transient'; 2 | -------------------------------------------------------------------------------- /packages/components/src/form-item/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useOverflow'; 2 | export * from './useFormItemLayout'; 3 | -------------------------------------------------------------------------------- /packages/database/src/sql-parser/readme.md: -------------------------------------------------------------------------------- 1 | use peggy to transform pegjs to sql parser 2 | https://github.com/peggyjs/peggy 3 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client'; 2 | export { default } from './dist/client'; 3 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/server.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/server'; 2 | export { default } from './dist/server'; 3 | -------------------------------------------------------------------------------- /packages/sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './APIClient'; 2 | 3 | export { default as getSubAppName } from './getSubAppName'; 4 | -------------------------------------------------------------------------------- /packages/cache/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cache-manager'; 2 | export * from './cache'; 3 | export * from './bloom-filter'; 4 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/index.ts: -------------------------------------------------------------------------------- 1 | export * from './observer'; 2 | export * from './hooks'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/utils/src/log.ts: -------------------------------------------------------------------------------- 1 | export const error = (message: Error | string, ...args: any[]) => { 2 | console.error(message, ...args); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/test/src/e2e/index.ts: -------------------------------------------------------------------------------- 1 | export * from './e2eUtils'; 2 | export * from './templatesOfCollection'; 3 | export * from './templatesOfPage'; 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /packages/components/src/__builtins__/hooks/useToken.ts: -------------------------------------------------------------------------------- 1 | import { theme } from 'antd'; 2 | 3 | const { useToken } = theme; 4 | export { useToken }; 5 | -------------------------------------------------------------------------------- /packages/core/src/pub-sub-manager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './handler-manager'; 2 | export * from './pub-sub-manager'; 3 | 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/data-source/src/resource-manager.ts: -------------------------------------------------------------------------------- 1 | import { Resourcer } from '@tachybase/resourcer'; 2 | 3 | export class ResourceManager extends Resourcer {} 4 | -------------------------------------------------------------------------------- /tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES6", 5 | "module": "CommonJS" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/posts.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'posts', 3 | fields: [{ type: 'string', name: 'title' }], 4 | }; 5 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/tags.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'tags', 3 | fields: [{ type: 'string', name: 'name' }], 4 | }; 5 | -------------------------------------------------------------------------------- /packages/database/src/repositories/view-repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '../repository'; 2 | 3 | export class ViewRepository extends Repository {} 4 | -------------------------------------------------------------------------------- /packages/schema/src/validator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validator'; 2 | export * from './parser'; 3 | export * from './registry'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/actions/src/actions/set.ts: -------------------------------------------------------------------------------- 1 | import { RelationRepositoryActionBuilder } from '../utils'; 2 | 3 | export const set = RelationRepositoryActionBuilder('set'); 4 | -------------------------------------------------------------------------------- /packages/core/src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data-wrapping'; 2 | export * from './db2resource'; 3 | export { parseVariables } from './parse-variables'; 4 | -------------------------------------------------------------------------------- /packages/database/src/fields/has-inverse-field.ts: -------------------------------------------------------------------------------- 1 | import { Field } from './field'; 2 | 3 | export interface HasInverseField { 4 | inverseField: () => Field; 5 | } 6 | -------------------------------------------------------------------------------- /packages/resourcer/src/__tests__/actions/demo0.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (ctx, next) { 2 | ctx.arr.push(7); 3 | await next(); 4 | ctx.arr.push(8); 5 | }; 6 | -------------------------------------------------------------------------------- /packages/resourcer/src/__tests__/actions/demo1.ts: -------------------------------------------------------------------------------- 1 | export default async function (ctx, next) { 2 | ctx.arr.push(9); 3 | await next(); 4 | ctx.arr.push(10); 5 | } 6 | -------------------------------------------------------------------------------- /packages/resourcer/src/__tests__/middlewares/demo1.ts: -------------------------------------------------------------------------------- 1 | export default async function (ctx, next) { 2 | ctx.arr.push(2); 3 | await next(); 4 | ctx.arr.push(3); 5 | } 6 | -------------------------------------------------------------------------------- /packages/schema/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shared/externals'; 2 | export * from './models/types'; 3 | export * from './effects'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/actions/src/actions/remove.ts: -------------------------------------------------------------------------------- 1 | import { RelationRepositoryActionBuilder } from '../utils'; 2 | 3 | export const remove = RelationRepositoryActionBuilder('remove'); 4 | -------------------------------------------------------------------------------- /packages/data-source/src/data-source-with-database.ts: -------------------------------------------------------------------------------- 1 | import Database from '@tachybase/database'; 2 | 3 | export interface DataSourceWithDatabase { 4 | db: Database; 5 | } 6 | -------------------------------------------------------------------------------- /packages/resourcer/src/__tests__/middlewares/demo0.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (ctx, next) { 2 | ctx.arr.push(1); 3 | await next(); 4 | ctx.arr.push(2); 5 | }; 6 | -------------------------------------------------------------------------------- /packages/schema/src/shared/path.ts: -------------------------------------------------------------------------------- 1 | import { Path as FormPath, Pattern as FormPathPattern } from '../path'; 2 | 3 | export { FormPath }; 4 | export type { FormPathPattern }; 5 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/global.d.ts: -------------------------------------------------------------------------------- 1 | import * as Types from './types'; 2 | 3 | declare global { 4 | namespace Formily.Reactive { 5 | export { Types }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/__builtins__/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useClickAway'; 2 | export * from './useConfig'; 3 | export * from './usePrefixCls'; 4 | export * from './useToken'; 5 | -------------------------------------------------------------------------------- /packages/devkit/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin-generator'; 2 | export { initEnv } from './util'; 3 | export * from './builder/build'; 4 | export * from './package-map-generator'; 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | packages/**/lib/** 6 | packages/**/esm/** 7 | packages/**/node_modules/** 8 | packages/**/dist/** 9 | storage 10 | -------------------------------------------------------------------------------- /packages/acl/src/no-permission-error.ts: -------------------------------------------------------------------------------- 1 | class NoPermissionError extends Error { 2 | constructor(...args) { 3 | super(...args); 4 | } 5 | } 6 | 7 | export { NoPermissionError }; 8 | -------------------------------------------------------------------------------- /packages/resourcer/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | export * from './middleware'; 3 | export * from './action'; 4 | export * from './resource'; 5 | export * from './resourcer'; 6 | -------------------------------------------------------------------------------- /packages/schema/src/react/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../json-schema'; 2 | export * from './components'; 3 | export * from './shared'; 4 | export * from './hooks'; 5 | export * from './types'; 6 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/c2/a.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../../../database'; 2 | 3 | export default { 4 | name: 'tests', 5 | fields: [{ type: 'string', name: 'n2' }], 6 | }; 7 | -------------------------------------------------------------------------------- /packages/logger/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './logger'; 3 | export * from './system-logger'; 4 | export * from './request-logger'; 5 | export * from './transports'; 6 | -------------------------------------------------------------------------------- /packages/di/src/types/container-identifier.type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A container identifier. This value must be unique across all containers. 3 | */ 4 | export type ContainerIdentifier = string | symbol; 5 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/annotations/index.ts: -------------------------------------------------------------------------------- 1 | export * from './observable'; 2 | export * from './box'; 3 | export * from './ref'; 4 | export * from './shallow'; 5 | export * from './computed'; 6 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/plugins/test-a.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from '../../plugin'; 2 | 3 | export default class TestA extends Plugin { 4 | getName(): string { 5 | return 'test-a'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/plugins/test-b.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from '../../plugin'; 2 | 3 | export default class TestB extends Plugin { 4 | getName(): string { 5 | return 'test-b'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/c0/a.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../../../database'; 2 | 3 | export default extend({ 4 | name: 'tests', 5 | fields: [{ type: 'string', name: 'n0' }], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/c1/b.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../../../database'; 2 | 3 | export default extend({ 4 | name: 'tests', 5 | fields: [{ type: 'string', name: 'n1' }], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "users", 3 | "fields": [ 4 | { 5 | "type": "string", 6 | "name": "name" 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import tegoConfig from '@tachybase/test/vitest'; 2 | 3 | import { defineConfig, mergeConfig } from 'vitest/config'; 4 | 5 | export default mergeConfig(tegoConfig, defineConfig({})); 6 | -------------------------------------------------------------------------------- /packages/actions/src/actions/first-or-create.ts: -------------------------------------------------------------------------------- 1 | import { proxyToRepository } from './proxy-to-repository'; 2 | 3 | export const firstOrCreate = proxyToRepository(['values', 'filterKeys'], 'firstOrCreate'); 4 | -------------------------------------------------------------------------------- /packages/actions/src/actions/update-or-create.ts: -------------------------------------------------------------------------------- 1 | import { proxyToRepository } from './proxy-to-repository'; 2 | 3 | export const updateOrCreate = proxyToRepository(['values', 'filterKeys'], 'updateOrCreate'); 4 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/migrations/m1.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '@tachybase/database'; 2 | 3 | export default class extends Migration { 4 | async up() {} 5 | 6 | async down() {} 7 | } 8 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/migrations/m2.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '@tachybase/database'; 2 | 3 | export default class extends Migration { 4 | async up() {} 5 | 6 | async down() {} 7 | } 8 | -------------------------------------------------------------------------------- /packages/database/src/errors/identifier-error.ts: -------------------------------------------------------------------------------- 1 | export class IdentifierError extends Error { 2 | constructor(message: string) { 3 | super(message); 4 | this.name = 'IdentifierError'; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/schema/src/json-schema/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import * as Types from './types'; 3 | 4 | declare global { 5 | namespace Formily.Schema { 6 | export { Types }; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/extend.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../../../database'; 2 | 3 | export default extend({ 4 | name: 'tags', 5 | fields: [{ type: 'string', name: 'color' }], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/di/src/empty.const.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Indicates that a service has not been initialized yet. 3 | * 4 | * _Note: This value is for internal use only._ 5 | */ 6 | export const EMPTY_VALUE = Symbol('EMPTY_VALUE'); 7 | -------------------------------------------------------------------------------- /packages/loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/loader", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts" 8 | } 9 | -------------------------------------------------------------------------------- /packages/schema/src/core/global.d.ts: -------------------------------------------------------------------------------- 1 | import * as Models from './models'; 2 | import * as Types from './types'; 3 | 4 | declare global { 5 | namespace Formily.Core { 6 | export { Types, Models }; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/auth/src/base/token-blacklist-service.ts: -------------------------------------------------------------------------------- 1 | export interface ITokenBlacklistService { 2 | has(token: string): Promise; 3 | add(values: { token: string; expiration: string | Date }): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/delay-extend.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../../../database'; 2 | 3 | export default extend({ 4 | name: 'images', 5 | fields: [{ type: 'string', name: 'url' }], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/extend2.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../../../database'; 2 | 3 | export default extend({ 4 | name: 'tags', 5 | fields: [{ type: 'string', name: 'color2' }], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fixtures/collections/delay-extend2.ts: -------------------------------------------------------------------------------- 1 | import { extend } from '../../../database'; 2 | 3 | export default extend({ 4 | name: 'images', 5 | fields: [{ type: 'string', name: 'url2' }], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/components/src/select-table/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useCheckSlackly'; 2 | export * from './useFilterOptions'; 3 | export * from './useFlatOptions'; 4 | export * from './useSize'; 5 | export * from './useTitleAddon'; 6 | -------------------------------------------------------------------------------- /packages/evaluators/src/utils/string.ts: -------------------------------------------------------------------------------- 1 | import { evaluate } from '.'; 2 | 3 | export default evaluate.bind( 4 | function (expression: string, scope = {}) { 5 | return expression; 6 | }, 7 | { replaceValue: true }, 8 | ); 9 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/hooks/useLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect as _useLayoutEffect, useEffect } from 'react'; 2 | 3 | export const useLayoutEffect = typeof document !== 'undefined' ? _useLayoutEffect : useEffect; 4 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/untracked.ts: -------------------------------------------------------------------------------- 1 | import { createBoundaryFunction } from './internals'; 2 | import { untrackEnd, untrackStart } from './reaction'; 3 | 4 | export const untracked = createBoundaryFunction(untrackStart, untrackEnd); 5 | -------------------------------------------------------------------------------- /packages/tego/bin/tego.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const chalk = require('chalk'); 3 | 4 | if (__dirname.startsWith('/snapshot/')) { 5 | console.log(chalk.green('WAIT: ') + 'Engine is loading...'); 6 | } 7 | 8 | require('../lib/index.js'); 9 | -------------------------------------------------------------------------------- /packages/actions/src/actions/get.ts: -------------------------------------------------------------------------------- 1 | import { proxyToRepository } from './proxy-to-repository'; 2 | 3 | export const get = proxyToRepository( 4 | ['filterByTk', 'fields', 'appends', 'except', 'filter', 'targetCollection'], 5 | 'findOne', 6 | ); 7 | -------------------------------------------------------------------------------- /packages/components/src/__builtins__/hooks/useConfig.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { ConfigProvider } from 'antd'; 4 | 5 | const { ConfigContext } = ConfigProvider; 6 | export const useConfig = () => useContext(ConfigContext); 7 | -------------------------------------------------------------------------------- /packages/components/src/__builtins__/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dayjs'; 2 | export * from './hooks'; 3 | export * from './loading'; 4 | export * from './pickDataProps'; 5 | export * from './portal'; 6 | export * from './sort'; 7 | export * from './style'; 8 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/inhertits/helper.ts: -------------------------------------------------------------------------------- 1 | import TachybaseGlobal from '@tachybase/globals'; 2 | 3 | const pgOnly = () => (TachybaseGlobal.settings.database.dialect === 'postgres' ? describe : describe.skip); 4 | 5 | export default pgOnly; 6 | -------------------------------------------------------------------------------- /packages/resourcer/src/__tests__/resources/demo.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'demo', 3 | actions: { 4 | async list(ctx, next) { 5 | ctx.arr.push(1); 6 | await next(); 7 | ctx.arr.push(2); 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useForm'; 2 | export * from './useField'; 3 | export * from './useParentForm'; 4 | export * from './useFieldSchema'; 5 | export * from './useFormEffects'; 6 | export * from './useExpressionScope'; 7 | -------------------------------------------------------------------------------- /packages/core/src/errors/application-not-install.ts: -------------------------------------------------------------------------------- 1 | export class ApplicationNotInstall extends Error { 2 | code: string; 3 | 4 | constructor(message) { 5 | super(message); 6 | 7 | this.code = 'APP_NOT_INSTALLED_ERROR'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/useExpressionScope.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { SchemaExpressionScopeContext } from '../shared/context'; 4 | 5 | export const useExpressionScope = () => useContext(SchemaExpressionScopeContext); 6 | -------------------------------------------------------------------------------- /packages/utils/src/fs-exists.ts: -------------------------------------------------------------------------------- 1 | import { stat } from 'node:fs/promises'; 2 | 3 | export async function fsExists(path: string) { 4 | try { 5 | await stat(path); 6 | return true; 7 | } catch (error) { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore certain directories under packages 2 | packages/*/lib 3 | packages/*/esm 4 | packages/*/es 5 | packages/*/dist 6 | packages/*/node_modules 7 | 8 | /.vscode 9 | /.idea 10 | /.husky 11 | /.gitea 12 | 13 | *.sqlite 14 | /uploads 15 | -------------------------------------------------------------------------------- /packages/requirejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/requirejs", 3 | "version": "1.6.1", 4 | "license": "Apache-2.0", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "dependencies": {}, 8 | "devDependencies": {} 9 | } 10 | -------------------------------------------------------------------------------- /packages/schema/src/react/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import * as Types from './types'; 4 | 5 | declare global { 6 | namespace Formily.React { 7 | export { Types }; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/utils/src/url.ts: -------------------------------------------------------------------------------- 1 | export function isURL(string) { 2 | let url; 3 | 4 | try { 5 | url = new URL(string); 6 | } catch (e) { 7 | return false; 8 | } 9 | 10 | return url.protocol === 'http:' || url.protocol === 'https:'; 11 | } 12 | -------------------------------------------------------------------------------- /packages/devkit/src/notify-updates.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module'; 2 | 3 | import updateNotifier from 'update-notifier'; 4 | 5 | const require = createRequire(import.meta.url); 6 | 7 | updateNotifier({ pkg: require('../package.json') }).notify({ defer: true }); 8 | -------------------------------------------------------------------------------- /packages/utils/src/getCurrentStacks.ts: -------------------------------------------------------------------------------- 1 | export function getCurrentStacks() { 2 | const myObject = { stack: '' }; 3 | Error.captureStackTrace(myObject); 4 | const stackLines = myObject.stack.split('\n'); 5 | stackLines.splice(0, 3); 6 | return stackLines.join('\n'); 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/src/uid.ts: -------------------------------------------------------------------------------- 1 | let IDX = 36, 2 | HEX = ''; 3 | while (IDX--) HEX += IDX.toString(36); 4 | 5 | export function uid(len?: number) { 6 | let str = '', 7 | num = len || 11; 8 | while (num--) str += HEX[(Math.random() * 36) | 0]; 9 | return str; 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/acl/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './acl'; 2 | export * from './acl-available-action'; 3 | export * from './acl-available-strategy'; 4 | export * from './acl-resource'; 5 | export * from './acl-role'; 6 | export * from './no-permission-error'; 7 | export * from './skip-middleware'; 8 | -------------------------------------------------------------------------------- /packages/globals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/globals", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": {}, 9 | "devDependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/useFieldSchema.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Schema } from '../../json-schema'; 4 | import { SchemaContext } from '../shared'; 5 | 6 | export const useFieldSchema = (): Schema => { 7 | return useContext(SchemaContext); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/useForm.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Form } from '../../core'; 4 | import { FormContext } from '../shared'; 5 | 6 | export const useForm = (): Form => { 7 | return useContext(FormContext); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/schema/src/shared/uid.ts: -------------------------------------------------------------------------------- 1 | let IDX = 36, 2 | HEX = ''; 3 | while (IDX--) HEX += IDX.toString(36); 4 | 5 | export function uid(len?: number) { 6 | let str = '', 7 | num = len || 11; 8 | while (num--) str += HEX[(Math.random() * 36) | 0]; 9 | return str; 10 | } 11 | -------------------------------------------------------------------------------- /packages/database/src/operators/notIn.ts: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize'; 2 | 3 | export default { 4 | $notIn(val, ctx) { 5 | return { 6 | [Op.or]: { 7 | [Op.notIn]: val, 8 | [Op.is]: null, 9 | }, 10 | }; 11 | }, 12 | } as Record; 13 | -------------------------------------------------------------------------------- /packages/evaluators/src/client/engines/string.ts: -------------------------------------------------------------------------------- 1 | import string from '../../utils/string'; 2 | 3 | export default { 4 | label: `{{t('String template')}}`, 5 | tooltip: `{{t('Simple string replacement, can be used to interpolate variables in a string.')}}`, 6 | evaluate: string, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/useField.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { GeneralField } from '../../core'; 4 | import { FieldContext } from '../shared'; 5 | 6 | export const useField = (): T => { 7 | return useContext(FieldContext) as any; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/di/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/di", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/utils": "workspace:*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/auth/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './actions'; 2 | export * from './auth'; 3 | export * from './auth-manager'; 4 | export * from './base/auth'; 5 | export * from './base/token-blacklist-service'; 6 | export * from './base/token-control-service'; 7 | export * from './base/user-status-service'; 8 | -------------------------------------------------------------------------------- /packages/components/src/__builtins__/pickDataProps.ts: -------------------------------------------------------------------------------- 1 | export const pickDataProps = (props: any = {}) => { 2 | const results = {}; 3 | 4 | for (const key in props) { 5 | if (key.indexOf('data-') > -1) { 6 | results[key] = props[key]; 7 | } 8 | } 9 | 10 | return results; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/components/src/form-grid/style.ts: -------------------------------------------------------------------------------- 1 | import { genStyleHook } from './../__builtins__'; 2 | 3 | export default genStyleHook('form-grid', (token) => { 4 | const { componentCls } = token; 5 | return { 6 | [`${componentCls}-layout`]: { 7 | display: 'grid', 8 | }, 9 | }; 10 | }); 11 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/client/plugin.tsx.tpl: -------------------------------------------------------------------------------- 1 | import { Plugin } from '@tachybase/client'; 2 | 3 | class Plugin{{{pascalCaseName}}} extends Plugin { 4 | async afterAdd() {} 5 | 6 | async beforeLoad() {} 7 | 8 | async load() {} 9 | } 10 | 11 | export default Plugin{{{pascalCaseName}}}; 12 | -------------------------------------------------------------------------------- /packages/schema/src/core/__tests__/shared.ts: -------------------------------------------------------------------------------- 1 | export const attach = void }>(target: T): T => { 2 | target.onMount(); 3 | return target; 4 | }; 5 | 6 | export const sleep = (duration = 100) => 7 | new Promise((resolve) => { 8 | setTimeout(resolve, duration); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/utils/src/forEach.ts: -------------------------------------------------------------------------------- 1 | export const forEach = (obj: any, callback: (value: any, key: string | number) => void) => { 2 | if (Array.isArray(obj)) { 3 | obj.forEach(callback); 4 | } else { 5 | Object.keys(obj).forEach((key) => { 6 | callback(obj[key], key); 7 | }); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /packages/utils/src/i18n.ts: -------------------------------------------------------------------------------- 1 | import { TOptions } from 'i18next'; 2 | 3 | // TODO 类型定义 4 | export function tval(text: any | any[], options?: TOptions) { 5 | if (options) { 6 | return `{{t(${JSON.stringify(text)}, ${JSON.stringify(options)})}}`; 7 | } 8 | return `{{t(${JSON.stringify(text)})}}`; 9 | } 10 | -------------------------------------------------------------------------------- /packages/cache/src/bloom-filter/index.ts: -------------------------------------------------------------------------------- 1 | export interface BloomFilter { 2 | reserve(key: string, errorRate: number, capacity: number): Promise; 3 | add(key: string, val: string): Promise; 4 | mAdd(key: string, vals: string[]): Promise; 5 | exists(key: string, val: string): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/plugins/plugin1.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from '../../plugin'; 2 | 3 | export default class Plugin1 extends Plugin { 4 | async load() { 5 | this.app.collection({ 6 | name: 'tests', 7 | }); 8 | } 9 | 10 | getName(): string { 11 | return 'Plugin1'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/plugins/plugin2.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from '../../plugin'; 2 | 3 | export default class Plugin2 extends Plugin { 4 | async load() { 5 | this.app.collection({ 6 | name: 'tests', 7 | }); 8 | } 9 | 10 | getName(): string { 11 | return 'Plugin2'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/plugins/plugin3.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from '../../plugin'; 2 | 3 | export default class Plugin3 extends Plugin { 4 | async load() { 5 | this.app.collection({ 6 | name: 'tests', 7 | }); 8 | } 9 | 10 | getName(): string { 11 | return 'Plugin3'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/commands/destroy.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('destroy') 6 | .preload() 7 | .action(async (...cliArgs) => { 8 | await app.destroy({ 9 | cliArgs, 10 | }); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/evaluators/src/client/engines/formulajs.ts: -------------------------------------------------------------------------------- 1 | import formulajs from '../../utils/formulajs'; 2 | 3 | export default { 4 | label: 'Formula.js', 5 | tooltip: '{{t("Formula.js supports most Microsoft Excel formula functions.")}}', 6 | link: 'https://formulajs.info/functions/', 7 | evaluate: formulajs, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/test/setup/settings.sqlite.js: -------------------------------------------------------------------------------- 1 | const defaultSettings = require('../../tego/presets/settings'); 2 | 3 | /** @type {import('@tachybase/globals').Settings} */ 4 | module.exports = { 5 | ...defaultSettings, 6 | 7 | logger: { 8 | ...defaultSettings.logger, 9 | level: 'error', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/fixtures/long-json.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'title', 3 | content: getLongString(), 4 | }; 5 | 6 | function getLongString() { 7 | const size = 2 * 1024 * 1024; 8 | const buffer = Buffer.alloc(size, 'a'); 9 | const str = buffer.toString('utf-8'); 10 | return str; 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/commands/db-auth.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('db:auth') 6 | .option('-r, --retry [retry]') 7 | .action(async (opts) => { 8 | await app.db.auth({ retry: opts.retry || 10 }); 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/database/src/operators/eq.ts: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize'; 2 | 3 | export default { 4 | $eq(val: any) { 5 | if (Array.isArray(val)) { 6 | return { 7 | [Op.in]: val, 8 | }; 9 | } 10 | return { 11 | [Op.eq]: val, 12 | }; 13 | }, 14 | } as Record; 15 | -------------------------------------------------------------------------------- /packages/schema/src/core/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Heart'; 2 | export * from './LifeCycle'; 3 | export * from './Graph'; 4 | export * from './Query'; 5 | export * from './Form'; 6 | export * from './Field'; 7 | export * from './ArrayField'; 8 | export * from './ObjectField'; 9 | export * from './VoidField'; 10 | -------------------------------------------------------------------------------- /packages/schema/src/shared/case.ts: -------------------------------------------------------------------------------- 1 | import { camelCase } from 'camel-case'; 2 | import { lowerCase } from 'lower-case'; 3 | import { paramCase } from 'param-case'; 4 | import { pascalCase } from 'pascal-case'; 5 | import { upperCase } from 'upper-case'; 6 | 7 | export { lowerCase, upperCase, camelCase, pascalCase, paramCase }; 8 | -------------------------------------------------------------------------------- /packages/utils/src/cluster.ts: -------------------------------------------------------------------------------- 1 | export const isMain = () => { 2 | return currentProcessNum() === '0'; 3 | }; 4 | 5 | export const currentProcessNum = () => { 6 | if (typeof process.env.NODE_APP_INSTANCE === 'undefined') { 7 | return '0'; 8 | } else { 9 | return process.env.NODE_APP_INSTANCE; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/utils/src/merge.ts: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge'; 2 | 3 | const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray; 4 | 5 | export function merge(obj1: any, obj2: any, opts?: any) { 6 | return deepmerge(obj1, obj2, { 7 | arrayMerge: overwriteMerge, 8 | ...opts, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/database/src/operators/association.ts: -------------------------------------------------------------------------------- 1 | import { Op, Sequelize } from 'sequelize'; 2 | 3 | export default { 4 | $exists(value, ctx) { 5 | return { 6 | [Op.not]: null, 7 | }; 8 | }, 9 | $notExists(value, ctx) { 10 | return { 11 | [Op.is]: null, 12 | }; 13 | }, 14 | } as Record; 15 | -------------------------------------------------------------------------------- /packages/core/src/helpers/multiple-instance-manager.ts: -------------------------------------------------------------------------------- 1 | export class MultipleInstanceManager { 2 | map: Map = new Map(); 3 | 4 | constructor() {} 5 | 6 | get(key: string) { 7 | return this.map.get(key); 8 | } 9 | 10 | set(key: string, value: Item) { 11 | this.map.set(key, value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/utils/src/types/constructable.type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic type for class definitions. 3 | * Example usage: 4 | * ``` 5 | * function createSomeInstance(myClassDefinition: Constructable) { 6 | * return new myClassDefinition() 7 | * } 8 | * ``` 9 | */ 10 | export type Constructable = new (...args: any[]) => T; 11 | -------------------------------------------------------------------------------- /packages/components/src/array-collapse/style.ts: -------------------------------------------------------------------------------- 1 | import { genStyleHook } from './../__builtins__'; 2 | 3 | export default genStyleHook('', (token) => { 4 | const { componentCls } = token; 5 | return { 6 | [componentCls]: { 7 | '&-item': { 8 | marginBottom: '10px !important', 9 | }, 10 | }, 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /packages/di/src/types/abstract-constructable.type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic type for abstract class definitions. 3 | * 4 | * Explanation: This describes a newable Function with a prototype Which is 5 | * what an abstract class is - no constructor, just the prototype. 6 | */ 7 | export type AbstractConstructable = NewableFunction & { prototype: T }; 8 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/index.ts: -------------------------------------------------------------------------------- 1 | export * from './batch'; 2 | export * from './action'; 3 | export * from './untracked'; 4 | export * from './observable'; 5 | export * from './model'; 6 | export * from './autorun'; 7 | export * from './tracker'; 8 | export * from './observe'; 9 | export * from './externals'; 10 | export * from './types'; 11 | -------------------------------------------------------------------------------- /packages/utils/src/measure-execution-time.ts: -------------------------------------------------------------------------------- 1 | export async function measureExecutionTime(operation, operationName) { 2 | const startTime = Date.now(); 3 | await operation(); 4 | const endTime = Date.now(); 5 | const duration = (endTime - startTime).toFixed(0); 6 | console.log(`${operationName} completed in ${duration} milliseconds`); 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/number-picker/index.tsx: -------------------------------------------------------------------------------- 1 | import { connect, mapReadPretty } from '@tachybase/schema'; 2 | 3 | import { InputNumber } from 'antd'; 4 | 5 | import { PreviewText } from '../preview-text'; 6 | 7 | export const NumberPicker = connect(InputNumber, mapReadPretty(PreviewText.NumberPicker)); 8 | 9 | export default NumberPicker; 10 | -------------------------------------------------------------------------------- /packages/core/src/commands/refresh.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('refresh') 6 | .ipc() 7 | .action(async (cliArgs) => { 8 | await app.restart({ 9 | cliArgs, 10 | }); 11 | app.logger.info('refreshing...'); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/postinstall.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { generatePlaywrightPath } from '../util'; 4 | 5 | export default (cli: Command) => { 6 | cli 7 | .command('postinstall') 8 | .allowUnknownOption() 9 | .action(async () => { 10 | generatePlaywrightPath(true); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/components/src/select-table/style.ts: -------------------------------------------------------------------------------- 1 | import { genStyleHook } from './../__builtins__'; 2 | 3 | export default genStyleHook('select-table', (token) => { 4 | const { componentCls } = token; 5 | return { 6 | [componentCls]: { 7 | [`${componentCls}-search`]: { 8 | marginBottom: 8, 9 | }, 10 | }, 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/shared/immediate.ts: -------------------------------------------------------------------------------- 1 | export const immediate = (callback?: () => void) => { 2 | let disposed = false; 3 | Promise.resolve(0).then(() => { 4 | if (disposed) { 5 | disposed = false; 6 | return; 7 | } 8 | callback(); 9 | }); 10 | return () => { 11 | disposed = true; 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/database/src/fields/text-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field } from './field'; 4 | 5 | export class TextField extends Field { 6 | get dataType() { 7 | return DataTypes.TEXT; 8 | } 9 | } 10 | 11 | export interface TextFieldOptions extends BaseColumnFieldOptions { 12 | type: 'text'; 13 | } 14 | -------------------------------------------------------------------------------- /packages/database/src/fields/time-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field } from './field'; 4 | 5 | export class TimeField extends Field { 6 | get dataType() { 7 | return DataTypes.TIME; 8 | } 9 | } 10 | 11 | export interface TimeFieldOptions extends BaseColumnFieldOptions { 12 | type: 'time'; 13 | } 14 | -------------------------------------------------------------------------------- /packages/devkit/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join } from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | 4 | export const __filename: string = fileURLToPath(import.meta.url); 5 | export const __dirname: string = dirname(__filename); 6 | 7 | export const STATIC_PATH: string = join(__dirname, '../assets'); 8 | export const DEFAULT_DEV_HOST = '0.0.0.0'; 9 | -------------------------------------------------------------------------------- /packages/database/src/operators/boolean.ts: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize'; 2 | 3 | export default { 4 | $isFalsy() { 5 | return { 6 | [Op.or]: { 7 | [Op.is]: null, 8 | [Op.eq]: false, 9 | }, 10 | }; 11 | }, 12 | 13 | $isTruly() { 14 | return { 15 | [Op.eq]: true, 16 | }; 17 | }, 18 | } as Record; 19 | -------------------------------------------------------------------------------- /packages/actions/src/actions/update.ts: -------------------------------------------------------------------------------- 1 | import { proxyToRepository } from './proxy-to-repository'; 2 | 3 | export const update = proxyToRepository( 4 | [ 5 | 'filterByTk', 6 | 'values', 7 | 'whitelist', 8 | 'blacklist', 9 | 'filter', 10 | 'updateAssociationValues', 11 | 'forceUpdate', 12 | 'targetCollection', 13 | ], 14 | 'update', 15 | ); 16 | -------------------------------------------------------------------------------- /packages/database/src/fields/string-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field } from './field'; 4 | 5 | export class StringField extends Field { 6 | get dataType() { 7 | return DataTypes.STRING; 8 | } 9 | } 10 | 11 | export interface StringFieldOptions extends BaseColumnFieldOptions { 12 | type: 'string'; 13 | } 14 | -------------------------------------------------------------------------------- /packages/database/src/operators/utils.ts: -------------------------------------------------------------------------------- 1 | const getDialect = (ctx) => { 2 | return ctx.db.sequelize.getDialect(); 3 | }; 4 | 5 | const isPg = (ctx) => { 6 | return getDialect(ctx) === 'postgres'; 7 | }; 8 | 9 | const isMySQL = (ctx) => { 10 | return getDialect(ctx) === 'mysql' || getDialect(ctx) === 'mariadb'; 11 | }; 12 | 13 | export { getDialect, isPg, isMySQL }; 14 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/upgrade.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { promptForTs, runAppCommand } from '../util'; 4 | 5 | export default (cli: Command) => { 6 | cli 7 | .command('upgrade') 8 | .allowUnknownOption() 9 | .action(async (options) => { 10 | promptForTs(); 11 | await runAppCommand('upgrade'); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/sdk", 3 | "version": "1.6.1", 4 | "license": "Apache-2.0", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "dependencies": { 8 | "axios": "1.7.7", 9 | "qs": "^6.14.0" 10 | }, 11 | "devDependencies": { 12 | "axios-mock-adapter": "^2.1.0", 13 | "vitest": "^3.2.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/database/src/fields/boolean-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field } from './field'; 4 | 5 | export class BooleanField extends Field { 6 | get dataType() { 7 | return DataTypes.BOOLEAN; 8 | } 9 | } 10 | 11 | export interface BooleanFieldOptions extends BaseColumnFieldOptions { 12 | type: 'boolean'; 13 | } 14 | -------------------------------------------------------------------------------- /packages/database/src/fields/virtual-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field } from './field'; 4 | 5 | export class VirtualField extends Field { 6 | get dataType() { 7 | return DataTypes.VIRTUAL; 8 | } 9 | } 10 | 11 | export interface VirtualFieldOptions extends BaseColumnFieldOptions { 12 | type: 'virtual'; 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/commands/db-clean.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('db:clean') 6 | .auth() 7 | .option('-y, --yes') 8 | .action(async (opts) => { 9 | console.log('Clearing database'); 10 | await app.db.clean({ 11 | drop: opts.yes, 12 | }); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/core/src/commands/upgrade.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('upgrade') 6 | .ipc() 7 | .auth() 8 | .action(async (options) => { 9 | await app.upgrade(options); 10 | app.logger.info(`✨ TachyBase has been upgraded to v${await app.version.get()}`); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/schema/src/shared/instanceof.ts: -------------------------------------------------------------------------------- 1 | import { isFn, isStr } from './checkers'; 2 | import { globalThisPolyfill } from './global'; 3 | 4 | export const instOf = (value: any, cls: any) => { 5 | if (isFn(cls)) return value instanceof cls; 6 | if (isStr(cls)) { 7 | return globalThisPolyfill[cls] ? value instanceof globalThisPolyfill[cls] : false; 8 | } 9 | return false; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/data-source/src/collection-field.ts: -------------------------------------------------------------------------------- 1 | import { FieldOptions, IField } from './types'; 2 | 3 | export class CollectionField implements IField { 4 | options; 5 | constructor(options: FieldOptions) { 6 | this.updateOptions(options); 7 | } 8 | 9 | updateOptions(options: any) { 10 | this.options = { 11 | ...this.options, 12 | ...options, 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/sdk/src/getSubAppName.ts: -------------------------------------------------------------------------------- 1 | const getSubAppName = (publicPath = '/') => { 2 | const prefix = `${publicPath}apps/`; 3 | if (!window.location.pathname.startsWith(prefix)) { 4 | return; 5 | } 6 | const pathname = window.location.pathname.substring(prefix.length); 7 | const args = pathname.split('/', 1); 8 | return args[0] || ''; 9 | }; 10 | 11 | export default getSubAppName; 12 | -------------------------------------------------------------------------------- /packages/data-source/src/sequelize-data-source.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from './data-source'; 2 | import { SequelizeCollectionManager } from './sequelize-collection-manager'; 3 | 4 | export class SequelizeDataSource extends DataSource { 5 | async load() {} 6 | 7 | createCollectionManager(options?: any) { 8 | return new SequelizeCollectionManager(options.collectionManager); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/install.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { promptForTs, runAppCommand } from '../util'; 4 | 5 | export default (cli: Command) => { 6 | cli 7 | .command('install') 8 | .allowUnknownOption() 9 | .action(async (options) => { 10 | promptForTs(); 11 | await runAppCommand('install', process.argv.slice(2)); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/di/src/token.class.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to create unique typed service identifier. 3 | * Useful when service has only interface, but don't have a class. 4 | */ 5 | /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ 6 | export class Token { 7 | /** 8 | * @param name Token name, optional and only used for debugging purposes. 9 | */ 10 | constructor(public name?: string) {} 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './application'; 2 | export { Application as default } from './application'; 3 | export * as middlewares from './middlewares'; 4 | export * from './migration'; 5 | export * from './plugin'; 6 | export * from './plugin-manager'; 7 | export * from './gateway'; 8 | export * from './app-supervisor'; 9 | export * from './notice'; 10 | export { AesEncryptor } from './aes-encryptor'; 11 | -------------------------------------------------------------------------------- /packages/core/src/commands/stop.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('stop') 6 | .ipc() 7 | .action(async (...cliArgs) => { 8 | if (!(await app.isStarted())) { 9 | app.logger.info('app has not started'); 10 | return; 11 | } 12 | await app.stop({ 13 | cliArgs, 14 | }); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/devkit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["esnext"], 6 | "module": "ESNext", 7 | "moduleResolution": "Bundler", 8 | "strict": true, 9 | "declaration": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true 13 | }, 14 | "exclude": ["./dist"], 15 | "include": ["./src/**/*.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FormProvider'; 2 | export * from './FormConsumer'; 3 | export * from './ArrayField'; 4 | export * from './ObjectField'; 5 | export * from './VoidField'; 6 | export * from './RecursionField'; 7 | export * from './ExpressionScope'; 8 | export * from './RecordsScope'; 9 | export * from './RecordScope'; 10 | export * from './SchemaField'; 11 | export * from './Field'; 12 | -------------------------------------------------------------------------------- /packages/actions/src/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './list'; 2 | export * from './create'; 3 | export * from './update'; 4 | export * from './destroy'; 5 | export * from './get'; 6 | export * from './add'; 7 | export * from './set'; 8 | export * from './remove'; 9 | export * from './toggle'; 10 | export * from './move'; 11 | export * from './first-or-create'; 12 | export * from './update-or-create'; 13 | export * from './count'; 14 | -------------------------------------------------------------------------------- /packages/core/src/commands/migrator.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('migrator') 6 | .preload() 7 | .action(async (opts) => { 8 | console.log('migrating...'); 9 | await app.emitAsync('cli.beforeMigrator', opts); 10 | await app.db.migrator.runAsCLI(process.argv.slice(3)); 11 | await app.stop(); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/core/src/plugin-manager/types.ts: -------------------------------------------------------------------------------- 1 | export interface PluginData { 2 | name?: string; 3 | packageName?: string; 4 | version?: string; 5 | preVersion?: string; 6 | registry?: string; 7 | clientUrl?: string; 8 | compressedFileUrl?: string; 9 | enabled?: boolean; 10 | type?: 'url' | 'npm' | 'upload'; 11 | authToken?: string; 12 | installed?: boolean; 13 | builtIn?: boolean; 14 | options?: any; 15 | } 16 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/hooks/useDidUpdate.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | import { immediate } from '../shared'; 4 | import { useLayoutEffect } from './useLayoutEffect'; 5 | 6 | export const useDidUpdate = (callback?: () => void) => { 7 | const request = useRef(null); 8 | request.current = immediate(callback); 9 | useLayoutEffect(() => { 10 | request.current(); 11 | callback(); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/utils/src/parseHTML.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * parseHTML('{{version}}', { version: '1.0.0' }) -> '1.0.0' 3 | * @param html 4 | * @param variables 5 | * @returns 6 | */ 7 | export function parseHTML(html: string, variables: Record) { 8 | return html.replace(/\{\{(\w+)\}\}/g, function (match, key) { 9 | return typeof variables[key] !== 'undefined' ? variables[key] : match; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /packages/actions/src/actions/count.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '..'; 2 | import { getRepositoryFromParams } from '../utils'; 3 | 4 | export async function count(ctx: Context, next) { 5 | const repository = getRepositoryFromParams(ctx); 6 | const { filter } = ctx.action.params; 7 | 8 | const count = await repository.count({ 9 | filter, 10 | context: ctx, 11 | }); 12 | 13 | ctx.body = count; 14 | await next(); 15 | } 16 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/src/server/plugin.ts.tpl: -------------------------------------------------------------------------------- 1 | import { Plugin } from '@tego/server'; 2 | 3 | export class Plugin{{{pascalCaseName}}} extends Plugin { 4 | async afterAdd() {} 5 | 6 | async beforeLoad() {} 7 | 8 | async load() {} 9 | 10 | async install() {} 11 | 12 | async afterEnable() {} 13 | 14 | async afterDisable() {} 15 | 16 | async remove() {} 17 | } 18 | 19 | export default Plugin{{{pascalCaseName}}}; 20 | -------------------------------------------------------------------------------- /packages/utils/src/mixin/index.ts: -------------------------------------------------------------------------------- 1 | export function applyMixins(derivedCtor: any, constructors: any[]) { 2 | constructors.forEach((baseCtor) => { 3 | Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { 4 | Object.defineProperty( 5 | derivedCtor.prototype, 6 | name, 7 | Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null), 8 | ); 9 | }); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /packages/components/src/__builtins__/loading.ts: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | 3 | export const loading = async (title: React.ReactNode = 'Loading...', processor: () => Promise) => { 4 | let hide: any = null; 5 | const loading = setTimeout(() => { 6 | hide = message.loading(title); 7 | }, 100); 8 | try { 9 | return await processor(); 10 | } finally { 11 | hide?.(); 12 | clearTimeout(loading); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/useAttach.ts: -------------------------------------------------------------------------------- 1 | import { unstable_useCompatEffect } from '../../reactive-react'; 2 | 3 | interface IRecycleTarget { 4 | onMount: () => void; 5 | onUnmount: () => void; 6 | } 7 | 8 | export const useAttach = (target: T): T => { 9 | unstable_useCompatEffect(() => { 10 | target.onMount(); 11 | return () => target.onUnmount(); 12 | }, [target]); 13 | return target; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/__tests__/hasCollected.spec.ts: -------------------------------------------------------------------------------- 1 | import { autorun, hasCollected, observable } from '..'; 2 | 3 | test('hasCollected', () => { 4 | const obs = observable({ value: '' }); 5 | autorun(() => { 6 | expect( 7 | hasCollected(() => { 8 | obs.value; 9 | }), 10 | ).toBe(true); 11 | expect(hasCollected(() => {})).toBe(false); 12 | expect(hasCollected()).toBe(false); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/components/src/switch/index.tsx: -------------------------------------------------------------------------------- 1 | import { connect, mapProps } from '@tachybase/schema'; 2 | 3 | import { Switch as AntdSwitch } from 'antd'; 4 | 5 | export const Switch = connect( 6 | AntdSwitch, 7 | mapProps( 8 | { 9 | value: 'checked', 10 | }, 11 | (props) => { 12 | delete props['value']; 13 | return { 14 | ...props, 15 | }; 16 | }, 17 | ), 18 | ); 19 | 20 | export default Switch; 21 | -------------------------------------------------------------------------------- /packages/core/src/plugin-manager/middleware.ts: -------------------------------------------------------------------------------- 1 | import { Context, Next } from '@tachybase/actions'; 2 | import { koaMulter as multer } from '@tachybase/utils'; 3 | 4 | export async function uploadMiddleware(ctx: Context, next: Next) { 5 | if (ctx.action.resourceName === 'pm' && ['add', 'update'].includes(ctx.action.actionName)) { 6 | const upload = multer().single('file'); 7 | return upload(ctx, next); 8 | } 9 | return next(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/schema/src/core/models/types.ts: -------------------------------------------------------------------------------- 1 | export type { Form } from './Form'; 2 | export type { Field } from './Field'; 3 | export type { Query } from './Query'; 4 | export type { Heart } from './Heart'; 5 | export type { Graph } from './Graph'; 6 | export type { LifeCycle } from './LifeCycle'; 7 | export type { ArrayField } from './ArrayField'; 8 | export type { ObjectField } from './ObjectField'; 9 | export type { VoidField } from './VoidField'; 10 | -------------------------------------------------------------------------------- /packages/utils/src/__tests__/import-module.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | import { importModule } from '../requireModule'; 4 | 5 | describe('import module', () => { 6 | it('should import module with absolute path', async () => { 7 | const file = './test.ts'; 8 | const filePath = path.resolve(__dirname, file); 9 | 10 | const m = await importModule(filePath); 11 | expect(m.test).toEqual('hello'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/utils/src/number.ts: -------------------------------------------------------------------------------- 1 | import { getNumberPrecision, toFixed } from '@rc-component/mini-decimal'; 2 | 3 | export function toFixedByStep(value: any, step: string | number) { 4 | if (typeof value === 'undefined' || value === null || value === '') { 5 | return ''; 6 | } 7 | const precision = getNumberPrecision(step); 8 | // return parseFloat(String(value)).toFixed(precision); 9 | return toFixed(String(value), '.', precision); 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/migrations/20240106082756-update-plugins.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '../migration'; 2 | 3 | export default class extends Migration { 4 | on = 'afterSync'; // 'beforeLoad' or 'afterLoad' 5 | appVersion = '<0.18.0-alpha.10'; 6 | 7 | async up() { 8 | await this.pm.repository.update({ 9 | values: { 10 | installed: true, 11 | }, 12 | filter: { 13 | enabled: true, 14 | }, 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.tbdump 3 | .DS_Store 4 | .env 5 | .env.e2e 6 | .env.local 7 | .env.local.* 8 | .env.test 9 | .local 10 | .npmrc 11 | .pnpm-store 12 | .swc 13 | /.idea 14 | /.vscode 15 | /playwright 16 | /uploads 17 | cache/diskstore-* 18 | coverage 19 | dist/ 20 | docker/**/storage 21 | docs-dist/ 22 | es/ 23 | esm/ 24 | lib/ 25 | ncc-cache/ 26 | node_modules/ 27 | storage/* 28 | plugins/* 29 | tsconfig.paths.json 30 | v8-compile-cache-** 31 | docs-repo-temp/ 32 | -------------------------------------------------------------------------------- /packages/schema/src/path/__tests__/basic.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | 3 | import { Path } from '..'; 4 | 5 | const { isPathPattern, match } = Path; 6 | 7 | test('isPathPattern', () => { 8 | expect(isPathPattern('obj')).toBeTruthy(); 9 | expect(isPathPattern(['obj', 'aa'])).toBeTruthy(); 10 | expect(isPathPattern(/^obj/)).toBeTruthy(); 11 | 12 | const matcher = match('obj.aa'); 13 | expect(isPathPattern(matcher)).toBeTruthy(); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/components/src/space/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Space as AntdSpace, SpaceProps } from 'antd'; 4 | 5 | import { useFormLayout } from '../form-layout'; 6 | 7 | export const Space: React.FC> = (props) => { 8 | const layout = useFormLayout(); 9 | return React.createElement(AntdSpace, { 10 | size: props.size ?? layout?.spaceGap, 11 | ...props, 12 | }); 13 | }; 14 | 15 | export default Space; 16 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/tar.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { TachybaseBuilder } from '../builder'; 4 | 5 | export default (cli: Command) => { 6 | cli 7 | .command('tar') 8 | .allowUnknownOption() 9 | .argument('[packages...]') 10 | .action(async (pkgs) => { 11 | const tachybaseBuilder = new TachybaseBuilder({ 12 | onlyTar: true, 13 | }); 14 | 15 | await tachybaseBuilder.build(pkgs); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/data-source/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './collection-manager'; 2 | export * from './data-source'; 3 | export * from './data-source-manager'; 4 | export * from './resource-manager'; 5 | export * from './sequelize-collection-manager'; 6 | export * from './sequelize-data-source'; 7 | 8 | export * from './load-default-actions'; 9 | export * from './types'; 10 | 11 | export * from './data-source-with-database'; 12 | export * from './utils'; 13 | export * from './collection'; 14 | -------------------------------------------------------------------------------- /packages/actions/src/actions/destroy.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '../index'; 2 | import { getRepositoryFromParams } from '../utils'; 3 | 4 | export async function destroy(ctx: Context, next) { 5 | const repository = getRepositoryFromParams(ctx); 6 | const { filterByTk, filter } = ctx.action.params; 7 | 8 | const instance = await repository.destroy({ 9 | filter, 10 | filterByTk, 11 | context: ctx, 12 | }); 13 | 14 | ctx.body = instance; 15 | await next(); 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/commands/restart.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('restart') 6 | .ipc() 7 | .action(async (...cliArgs) => { 8 | if (!(await app.isStarted())) { 9 | app.logger.info('app has not started'); 10 | return; 11 | } 12 | await app.restart({ 13 | cliArgs, 14 | }); 15 | app.logger.info('app has been restarted'); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/database/src/operators/ne.ts: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize'; 2 | 3 | export default { 4 | $ne(val, ctx) { 5 | if (Array.isArray(val)) { 6 | return { 7 | [Op.notIn]: val, 8 | }; 9 | } 10 | return val === null 11 | ? { 12 | [Op.ne]: null, 13 | } 14 | : { 15 | [Op.or]: { 16 | [Op.ne]: val, 17 | [Op.is]: null, 18 | }, 19 | }; 20 | }, 21 | } as Record; 22 | -------------------------------------------------------------------------------- /packages/components/src/preview-text/style.ts: -------------------------------------------------------------------------------- 1 | import { genStyleHook } from './../__builtins__'; 2 | 3 | export default genStyleHook('preview-text', (token) => { 4 | const { componentCls, antCls, fontSize, fontWeightStrong } = token; 5 | return [ 6 | { 7 | [componentCls]: { 8 | fontSize, 9 | fontWeight: fontWeightStrong, 10 | 11 | [`${antCls}-tag:last-child`]: { 12 | marginInlineEnd: 0, 13 | }, 14 | }, 15 | }, 16 | ]; 17 | }); 18 | -------------------------------------------------------------------------------- /packages/core/src/cache/index.ts: -------------------------------------------------------------------------------- 1 | import { CacheManager, CacheManagerOptions } from '@tachybase/cache'; 2 | 3 | import Application from '../application'; 4 | 5 | export const createCacheManager = async (app: Application, options: CacheManagerOptions) => { 6 | const cacheManager = new CacheManager(options); 7 | const defaultCache = await cacheManager.createCache({ name: app.name }); 8 | app.cache = defaultCache; 9 | app.context.cache = defaultCache; 10 | return cacheManager; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/database/src/relation-repository/hasone-repository.ts: -------------------------------------------------------------------------------- 1 | import { HasOne } from 'sequelize'; 2 | 3 | import { SingleRelationRepository } from './single-relation-repository'; 4 | 5 | export class HasOneRepository extends SingleRelationRepository { 6 | filterOptions(sourceModel) { 7 | const association = this.association as HasOne; 8 | 9 | return { 10 | // @ts-ignore 11 | [association.foreignKey]: sourceModel.get(association.sourceKey), 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/lint-pr-title.yaml: -------------------------------------------------------------------------------- 1 | name: "Lint PR Title" 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - reopened 9 | - synchronize 10 | 11 | jobs: 12 | lint-pr-title: 13 | name: Validate PR title 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: read 17 | steps: 18 | - uses: amannn/action-semantic-pull-request@v5 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /packages/components/src/array-cards/style.ts: -------------------------------------------------------------------------------- 1 | import { genStyleHook } from '../__builtins__'; 2 | 3 | export default genStyleHook('array-cards', (token) => { 4 | const { componentCls, antCls } = token; 5 | const arrayBase = `${antCls}-formily-array-base`; 6 | return { 7 | [componentCls]: { 8 | '&-item': { 9 | marginBottom: '10px !important', 10 | }, 11 | 12 | [`${arrayBase}-copy`]: { 13 | marginInlineStart: 6, 14 | }, 15 | }, 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/__tests__/untracked.spec.ts: -------------------------------------------------------------------------------- 1 | import { autorun, observable, untracked } from '..'; 2 | 3 | test('basic untracked', () => { 4 | const obs = observable({}); 5 | const fn = vi.fn(); 6 | autorun(() => { 7 | untracked(() => { 8 | fn(obs.value); 9 | }); 10 | }); 11 | 12 | expect(fn).toBeCalledTimes(1); 13 | obs.value = 123; 14 | expect(fn).toBeCalledTimes(1); 15 | }); 16 | 17 | test('no params untracked', () => { 18 | untracked(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/acl/src/skip-middleware.ts: -------------------------------------------------------------------------------- 1 | export const skip = (options: ACLSkipOptions) => { 2 | return async function ACLSkipMiddleware(ctx, next) { 3 | const { resourceName, actionName } = ctx.action; 4 | if (resourceName === options.resourceName && actionName === options.actionName) { 5 | ctx.permission = { 6 | skip: true, 7 | }; 8 | } 9 | await next(); 10 | }; 11 | }; 12 | 13 | interface ACLSkipOptions { 14 | resourceName: string; 15 | actionName: string; 16 | } 17 | -------------------------------------------------------------------------------- /packages/database/src/listeners/index.ts: -------------------------------------------------------------------------------- 1 | import { Database } from '../database'; 2 | import { beforeDefineAdjacencyListCollection } from './adjacency-list'; 3 | import { appendChildCollectionNameAfterRepositoryFind } from './append-child-collection-name-after-repository-find'; 4 | 5 | export const registerBuiltInListeners = (db: Database) => { 6 | db.on('beforeDefineCollection', beforeDefineAdjacencyListCollection); 7 | db.on('afterRepositoryFind', appendChildCollectionNameAfterRepositoryFind(db)); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/core/src/middlewares/extract-client-ip.ts: -------------------------------------------------------------------------------- 1 | import { Context, Next } from '@tachybase/actions'; 2 | 3 | export function extractClientIp() { 4 | return async function extractClientIp(ctx: Context, next: Next) { 5 | const forwardedFor = ctx.get('X-Forwarded-For'); 6 | const ipArray = forwardedFor ? forwardedFor.split(',') : []; 7 | const clientIp = ipArray.length > 0 ? ipArray[0].trim() : ctx.request.ip; 8 | ctx.state.clientIp = clientIp; 9 | 10 | await next(); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/di/src/types/service-identifier.type.ts: -------------------------------------------------------------------------------- 1 | import { Constructable } from '@tachybase/utils'; 2 | 3 | import { Token } from '../token.class'; 4 | import { AbstractConstructable } from './abstract-constructable.type'; 5 | 6 | /** 7 | * Unique service identifier. 8 | * Can be some class type, or string id, or instance of Token. 9 | */ 10 | export type ServiceIdentifier = 11 | | Constructable 12 | | AbstractConstructable 13 | | CallableFunction 14 | | Token 15 | | string; 16 | -------------------------------------------------------------------------------- /packages/acl/src/acl-available-action.ts: -------------------------------------------------------------------------------- 1 | export interface AvailableActionOptions { 2 | /** 3 | * @deprecated 4 | */ 5 | type?: 'new-data' | 'old-data'; 6 | displayName?: string; 7 | aliases?: string[] | string; 8 | resource?: string; 9 | // 对新数据进行操作 10 | onNewRecord?: boolean; 11 | // 允许配置字段 12 | allowConfigureFields?: boolean; 13 | } 14 | 15 | export class ACLAvailableAction { 16 | constructor( 17 | public name: string, 18 | public options: AvailableActionOptions, 19 | ) {} 20 | } 21 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tego/client", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/components": "workspace:*", 10 | "@tachybase/evaluators": "workspace:*", 11 | "@tachybase/requirejs": "workspace:*", 12 | "@tachybase/schema": "workspace:*", 13 | "@tachybase/sdk": "workspace:*", 14 | "@tachybase/utils": "workspace:*" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/errors/handler.ts: -------------------------------------------------------------------------------- 1 | import { PluginCommandError } from './plugin-command-error'; 2 | 3 | type ErrorLevel = 'fatal' | 'silly' | 'warn'; 4 | 5 | export function getErrorLevel(e: Error): ErrorLevel { 6 | // @ts-ignore 7 | if (e.code === 'commander.unknownCommand') { 8 | return 'silly'; 9 | } 10 | 11 | if (e instanceof PluginCommandError) { 12 | return 'warn'; 13 | } 14 | 15 | if (e.name === 'RestoreCheckError') { 16 | return 'warn'; 17 | } 18 | 19 | return 'fatal'; 20 | } 21 | -------------------------------------------------------------------------------- /packages/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/logger", 3 | "version": "1.6.1", 4 | "description": "logging library", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/globals": "workspace:*", 10 | "chalk": "^4.1.2", 11 | "lodash": "^4.17.21", 12 | "triple-beam": "^1.4.1", 13 | "winston": "^3.17.0", 14 | "winston-daily-rotate-file": "^5.0.0", 15 | "winston-transport": "^4.9.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/schema/src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './array'; 2 | export * from './compare'; 3 | export * from './checkers'; 4 | export * from './clone'; 5 | export * from './isEmpty'; 6 | export * from './case'; 7 | export * from './string'; 8 | export * from './global'; 9 | export * from './path'; 10 | export * from './deprecate'; 11 | export * from './subscribable'; 12 | export * from './middleware'; 13 | export * from './merge'; 14 | export * from './instanceof'; 15 | export * from './defaults'; 16 | export * from './uid'; 17 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/clean.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import { rimrafSync } from 'rimraf'; 3 | 4 | /** 5 | * 6 | * @param {Command} cli 7 | */ 8 | export default (cli: Command) => { 9 | cli 10 | .command('clean') 11 | .option('--all') 12 | .allowUnknownOption() 13 | .action((opts) => { 14 | rimrafSync('packages/*/{lib,esm,es,dist,node_modules}', { glob: true }); 15 | if (opts.all) { 16 | rimrafSync('node_modules', { glob: true }); 17 | } 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/evaluators/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import { Registry } from '@tachybase/utils'; 2 | 3 | import { Evaluator } from '../utils'; 4 | import formulajs from '../utils/formulajs'; 5 | import string from '../utils/string'; 6 | 7 | export { evaluate, appendArrayColumn } from '../utils'; 8 | export type { Evaluator } from '../utils'; 9 | 10 | export const evaluators = new Registry(); 11 | 12 | evaluators.register('formula.js', formulajs); 13 | evaluators.register('string', string); 14 | 15 | export default evaluators; 16 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/RecordsScope.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { IRecordsScopeProps, ReactFC } from '../types'; 4 | import { ExpressionScope } from './ExpressionScope'; 5 | 6 | export const RecordsScope: ReactFC = (props) => { 7 | return ( 8 | 15 | {props.children} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/percent2float.test.ts: -------------------------------------------------------------------------------- 1 | import { percent2float } from '../utils'; 2 | 3 | describe('percent2float', () => { 4 | it('should be NaN', () => { 5 | expect(percent2float('123')).toBe(NaN); 6 | expect(percent2float('3a')).toBe(NaN); 7 | expect(percent2float('3a%')).toBe(NaN); 8 | }); 9 | 10 | it('should be a floating point number', () => { 11 | expect(percent2float('123%')).toBe(1.23); 12 | expect(percent2float('22.5507%')).toBe(0.225507); // not 0.22550699999999999 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/schema/src/shared/global.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | function globalSelf() { 3 | try { 4 | if (typeof self !== 'undefined') { 5 | return self; 6 | } 7 | } catch (e) {} 8 | try { 9 | if (typeof window !== 'undefined') { 10 | return window; 11 | } 12 | } catch (e) {} 13 | try { 14 | if (typeof global !== 'undefined') { 15 | return global; 16 | } 17 | } catch (e) {} 18 | return Function('return this')(); 19 | } 20 | export const globalThisPolyfill: Window = globalSelf(); 21 | -------------------------------------------------------------------------------- /packages/database/src/listeners/adjacency-list.ts: -------------------------------------------------------------------------------- 1 | import { CollectionOptions } from '../collection'; 2 | 3 | export const beforeDefineAdjacencyListCollection = (options: CollectionOptions) => { 4 | if (!options.tree) { 5 | return; 6 | } 7 | (options.fields || []).forEach((field) => { 8 | if (field.treeParent || field.treeChildren) { 9 | if (!field.target) { 10 | field.target = options.name; 11 | } 12 | if (!field.foreignKey) { 13 | field.foreignKey = 'parentId'; 14 | } 15 | } 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/actions/src/actions/toggle.ts: -------------------------------------------------------------------------------- 1 | import { BelongsToManyRepository } from '@tachybase/database'; 2 | 3 | import { Context } from '..'; 4 | import { getRepositoryFromParams } from '../utils'; 5 | 6 | export async function toggle(ctx: Context, next) { 7 | const repository = getRepositoryFromParams(ctx); 8 | 9 | if (!(repository instanceof BelongsToManyRepository)) { 10 | return await next(); 11 | } 12 | 13 | await (repository).toggle(ctx.action.params.values); 14 | ctx.body = 'ok'; 15 | await next(); 16 | } 17 | -------------------------------------------------------------------------------- /packages/auth/src/actions.ts: -------------------------------------------------------------------------------- 1 | import { Handlers } from '@tachybase/resourcer'; 2 | 3 | export const actions = { 4 | signIn: async (ctx, next) => { 5 | ctx.body = await ctx.auth.signIn(); 6 | await next(); 7 | }, 8 | signOut: async (ctx, next) => { 9 | await ctx.auth.signOut(); 10 | await next(); 11 | }, 12 | signUp: async (ctx, next) => { 13 | await ctx.auth.signUp(); 14 | await next(); 15 | }, 16 | check: async (ctx, next) => { 17 | ctx.body = ctx.auth.user || {}; 18 | await next(); 19 | }, 20 | } as Handlers; 21 | -------------------------------------------------------------------------------- /packages/cache/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/cache", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "bloom-filters": "^3.0.4", 10 | "cache-manager": "^5.7.6", 11 | "cache-manager-redis-yet": "^4.2.0", 12 | "deepmerge": "^4.3.1", 13 | "lodash": "4.17.21" 14 | }, 15 | "devDependencies": { 16 | "@types/lodash": "^4.17.20", 17 | "redis": "^5.8.2", 18 | "vitest": "^3.2.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/FormConsumer.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | 3 | import { observer } from '../../reactive-react'; 4 | import { isFn } from '../../shared'; 5 | import { useForm } from '../hooks'; 6 | import { IFormSpyProps, ReactFC } from '../types'; 7 | 8 | export const FormConsumer: ReactFC = observer((props) => { 9 | const children = isFn(props.children) ? props.children(useForm()) : null; 10 | return {children}; 11 | }); 12 | 13 | FormConsumer.displayName = 'FormConsumer'; 14 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/shared/global.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | function globalSelf() { 3 | try { 4 | if (typeof self !== 'undefined') { 5 | return self; 6 | } 7 | } catch (e) {} 8 | try { 9 | if (typeof window !== 'undefined') { 10 | return window; 11 | } 12 | } catch (e) {} 13 | try { 14 | if (typeof global !== 'undefined') { 15 | return global; 16 | } 17 | } catch (e) {} 18 | return Function('return this')(); 19 | } 20 | 21 | export const globalThisPolyfill: Window = globalSelf(); 22 | -------------------------------------------------------------------------------- /packages/test/setup/server.ts: -------------------------------------------------------------------------------- 1 | import os from 'node:os'; 2 | import path from 'node:path'; 3 | import TachybaseGlobal from '@tachybase/globals'; 4 | import { initEnv } from '@tego/devkit'; 5 | 6 | import settings from './settings.sqlite'; 7 | 8 | TachybaseGlobal.settings = settings; 9 | // process.env.DB_TEST_DISTRIBUTOR_PORT = '23450'; 10 | // process.env.DB_TEST_PREFIX = 'test'; 11 | process.env.TEGO_RUNTIME_HOME = path.join(os.tmpdir(), 'test-sqlite'); 12 | 13 | process.env.APP_ENV_PATH = process.env.APP_ENV_PATH || '.env.test'; 14 | 15 | initEnv(); 16 | -------------------------------------------------------------------------------- /packages/actions/src/actions/create.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '..'; 2 | import { getRepositoryFromParams } from '../utils'; 3 | 4 | export async function create(ctx: Context, next) { 5 | const repository = getRepositoryFromParams(ctx); 6 | const { whitelist, blacklist, updateAssociationValues, values } = ctx.action.params; 7 | 8 | const instance = await repository.create({ 9 | values, 10 | whitelist, 11 | blacklist, 12 | updateAssociationValues, 13 | context: ctx, 14 | }); 15 | 16 | ctx.body = instance; 17 | await next(); 18 | } 19 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/useFormEffects.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../core'; 2 | import { unstable_useCompatFactory } from '../../reactive-react'; 3 | import { uid } from '../../shared'; 4 | import { useForm } from './useForm'; 5 | 6 | export const useFormEffects = (effects?: (form: Form) => void) => { 7 | const form = useForm(); 8 | unstable_useCompatFactory(() => { 9 | const id = uid(); 10 | form.addEffects(id, effects); 11 | return { 12 | dispose() { 13 | form.removeEffects(id); 14 | }, 15 | }; 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/auth", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/actions": "workspace:*", 10 | "@tachybase/database": "workspace:*", 11 | "@tachybase/resourcer": "workspace:*", 12 | "@tachybase/utils": "workspace:*", 13 | "jsonwebtoken": "^8.5.1" 14 | }, 15 | "devDependencies": { 16 | "@types/jsonwebtoken": "^8.5.9", 17 | "vitest": "^3.2.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/schema/src/react/hooks/useParentForm.ts: -------------------------------------------------------------------------------- 1 | import { Form, GeneralField, isObjectField, ObjectField } from '../../core'; 2 | import { useField } from './useField'; 3 | import { useForm } from './useForm'; 4 | 5 | export const useParentForm = (): Form | ObjectField => { 6 | const field = useField(); 7 | const form = useForm(); 8 | const findObjectParent = (field: GeneralField) => { 9 | if (!field) return form; 10 | if (isObjectField(field)) return field; 11 | return findObjectParent(field?.parent); 12 | }; 13 | return findObjectParent(field); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/types.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface IObserverOptions { 4 | forwardRef?: boolean; 5 | scheduler?: (updater: () => void) => void; 6 | displayName?: string; 7 | } 8 | 9 | export interface IObserverProps { 10 | children?: (() => React.ReactElement) | React.ReactNode; 11 | } 12 | 13 | export type Modify = Omit & R; 14 | 15 | export type ReactPropsWithChildren

= Modify<{ children?: React.ReactNode | undefined }, P>; 16 | 17 | export type ReactFC

= React.FC>; 18 | -------------------------------------------------------------------------------- /packages/utils/src/client.ts: -------------------------------------------------------------------------------- 1 | export * from './collections-graph'; 2 | export * from './common'; 3 | export * from './date'; 4 | export * from './forEach'; 5 | export * from './getValuesByPath'; 6 | export * from './json-templates'; 7 | export * from './log'; 8 | export * from './merge'; 9 | export * from './number'; 10 | export * from './parse-filter'; 11 | export * from './registry'; 12 | export * from './isPortalInBody'; 13 | export * from './uid'; 14 | export * from './url'; 15 | export * from './parseHTML'; 16 | export * from './dom'; 17 | export * from './currencyUtils'; 18 | -------------------------------------------------------------------------------- /packages/components/src/select-table/hooks/useFlatOptions.tsx: -------------------------------------------------------------------------------- 1 | const useFlatOptions = (tree: any[]) => { 2 | return getFlatOptions(tree); 3 | }; 4 | 5 | const getFlatOptions = (tree: any[]) => { 6 | const flatData = (data?: any[]) => { 7 | let list: any[] = []; 8 | data?.forEach((item) => { 9 | list = [...list, item]; 10 | if (item?.children?.length) { 11 | list = [...list, ...flatData(item.children)]; 12 | } 13 | }); 14 | return list; 15 | }; 16 | return flatData(tree); 17 | }; 18 | 19 | export { useFlatOptions, getFlatOptions }; 20 | -------------------------------------------------------------------------------- /packages/database/src/value-parsers/json-value-parser.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueParser } from './base-value-parser'; 2 | 3 | export class JsonValueParser extends BaseValueParser { 4 | async setValue(value: any) { 5 | if (typeof value === 'string') { 6 | if (value.trim() === '') { 7 | this.value = null; 8 | } else { 9 | try { 10 | this.value = JSON.parse(value); 11 | } catch (error) { 12 | this.errors.push(error.message); 13 | } 14 | } 15 | } else { 16 | this.value = value; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/database/src/relation-repository/types.ts: -------------------------------------------------------------------------------- 1 | import { Transactionable } from 'sequelize'; 2 | 3 | import { TargetKey, Values } from '../repository'; 4 | 5 | export type PrimaryKeyWithThroughValues = [TargetKey, Values]; 6 | 7 | export interface AssociatedOptions extends Transactionable { 8 | tk?: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[]; 9 | } 10 | 11 | export type setAssociationOptions = 12 | | TargetKey 13 | | TargetKey[] 14 | | PrimaryKeyWithThroughValues 15 | | PrimaryKeyWithThroughValues[] 16 | | AssociatedOptions; 17 | -------------------------------------------------------------------------------- /packages/devkit/templates/plugin/package.json.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{{packageName}}}", 3 | "displayName": "Display Name for {{{packageName}}}", 4 | "version": "{{{packageVersion}}}", 5 | "description": "Description for {{{packageName}}}", 6 | "main": "dist/server/index.js", 7 | "devDependencies": { 8 | "@tachybase/client": "catalog:", 9 | "@tachybase/test": "catalog:", 10 | "@tego/client": "catalog:", 11 | "@tego/server": "catalog:" 12 | }, 13 | "description.zh-CN": "插件 {{{packageName}}} 的描述", 14 | "displayName.zh-CN": "插件 {{{packageName}}} 的显示名称" 15 | } 16 | -------------------------------------------------------------------------------- /packages/di/src/interfaces/service-options.interface.ts: -------------------------------------------------------------------------------- 1 | import { ServiceMetadata } from './service-metadata.interface'; 2 | 3 | /** 4 | * The public ServiceOptions is partial object of ServiceMetadata and either one 5 | * of the following is set: `type`, `factory`, `value` but not more than one. 6 | */ 7 | export type ServiceOptions = 8 | | Omit>, 'referencedBy' | 'type' | 'factory'> 9 | | Omit>, 'referencedBy' | 'value' | 'factory'> 10 | | Omit>, 'referencedBy' | 'value' | 'type'>; 11 | -------------------------------------------------------------------------------- /packages/utils/src/__tests__/forEach.test.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../forEach'; 2 | 3 | describe('forEach', () => { 4 | test('array', () => { 5 | const arr = [1, 2, 3]; 6 | const result = []; 7 | forEach(arr, (value, index) => { 8 | result.push(value); 9 | }); 10 | expect(result).toEqual(arr); 11 | }); 12 | 13 | test('object', () => { 14 | const obj = { a: 1, b: 2, c: 3 }; 15 | const result = []; 16 | forEach(obj, (value, key) => { 17 | result.push(value); 18 | }); 19 | expect(result).toEqual([1, 2, 3]); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/FormProvider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useAttach } from '../hooks/useAttach'; 4 | import { ContextCleaner, FormContext } from '../shared'; 5 | import { IProviderProps, ReactFC } from '../types'; 6 | 7 | export const FormProvider: ReactFC = (props) => { 8 | const form = useAttach(props.form); 9 | return ( 10 | 11 | {props.children} 12 | 13 | ); 14 | }; 15 | 16 | FormProvider.displayName = 'FormProvider'; 17 | -------------------------------------------------------------------------------- /packages/database/src/fields/uuid-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field, FieldContext } from './field'; 4 | 5 | export class UuidField extends Field { 6 | constructor(options?: any, context?: FieldContext) { 7 | super( 8 | { 9 | defaultValue: new DataTypes.UUIDV4(), 10 | ...options, 11 | }, 12 | context, 13 | ); 14 | } 15 | get dataType() { 16 | return DataTypes.UUID; 17 | } 18 | } 19 | 20 | export interface UUIDFieldOptions extends BaseColumnFieldOptions { 21 | type: 'uuid'; 22 | } 23 | -------------------------------------------------------------------------------- /packages/devkit/src/builder/build/index.ts: -------------------------------------------------------------------------------- 1 | import { Options as TsupConfig } from 'tsup'; 2 | import { InlineConfig as ViteConfig } from 'vite'; 3 | 4 | export * from './utils'; 5 | 6 | export type PkgLog = (msg: string, ...args: any[]) => void; 7 | 8 | interface UserConfig { 9 | modifyTsupConfig?: (config: TsupConfig) => TsupConfig; 10 | modifyViteConfig?: (config: ViteConfig) => ViteConfig; 11 | beforeBuild?: (log: PkgLog) => void | Promise; 12 | afterBuild?: (log: PkgLog) => void | Promise; 13 | } 14 | 15 | declare const defineConfig: (config: UserConfig) => UserConfig; 16 | -------------------------------------------------------------------------------- /packages/core/src/commands/console.ts: -------------------------------------------------------------------------------- 1 | import REPL from 'node:repl'; 2 | 3 | import Application from '../application'; 4 | 5 | export default (app: Application) => { 6 | app 7 | .command('console') 8 | .preload() 9 | .action(async () => { 10 | await app.start(); 11 | const repl = (REPL.start('tachybase > ').context.app = app); 12 | repl.on('exit', async function (err) { 13 | if (err) { 14 | console.error(err); 15 | process.exit(1); 16 | } 17 | await app.stop(); 18 | process.exit(0); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/database/src/relation-repository/belongs-to-repository.ts: -------------------------------------------------------------------------------- 1 | import { BelongsTo } from 'sequelize'; 2 | 3 | import { SingleRelationFindOption, SingleRelationRepository } from './single-relation-repository'; 4 | 5 | type BelongsToFindOptions = SingleRelationFindOption; 6 | 7 | export class BelongsToRepository extends SingleRelationRepository { 8 | async filterOptions(sourceModel) { 9 | const association = this.association as BelongsTo; 10 | 11 | return { 12 | // @ts-ignore 13 | [association.targetKey]: sourceModel.get(association.foreignKey), 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/observable.ts: -------------------------------------------------------------------------------- 1 | import * as annotations from './annotations'; 2 | import { MakeObModelSymbol } from './environment'; 3 | import { createObservable } from './internals'; 4 | 5 | export function observable(target: T): T { 6 | return createObservable(null, null, target); 7 | } 8 | 9 | observable.box = annotations.box; 10 | observable.ref = annotations.ref; 11 | observable.deep = annotations.observable; 12 | observable.shallow = annotations.shallow; 13 | observable.computed = annotations.computed; 14 | observable[MakeObModelSymbol] = annotations.observable; 15 | -------------------------------------------------------------------------------- /packages/database/src/query-interface/query-interface-builder.ts: -------------------------------------------------------------------------------- 1 | import Database from '../database'; 2 | import MysqlQueryInterface from './mysql-query-interface'; 3 | import PostgresQueryInterface from './postgres-query-interface'; 4 | import SqliteQueryInterface from './sqlite-query-interface'; 5 | 6 | export default function buildQueryInterface(db: Database) { 7 | const map = { 8 | mysql: MysqlQueryInterface, 9 | mariadb: MysqlQueryInterface, 10 | postgres: PostgresQueryInterface, 11 | sqlite: SqliteQueryInterface, 12 | }; 13 | 14 | return new map[db.options.dialect](db); 15 | } 16 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/** 3 | 4 | allowedDeprecatedVersions: 5 | cache-manager-redis-yet: 4.2.0 6 | 7 | neverBuiltDependencies: 8 | - canvas 9 | 10 | nodeLinker: isolated 11 | 12 | overrides: 13 | '@ant-design/cssinjs': 1.24.0 14 | '@types/node': 20.17.10 15 | '@types/react': 18.3.23 16 | '@types/react-dom': 18.3.7 17 | antd: 5.22.5 18 | dayjs: 1.11.13 19 | react: 18.3.1 20 | react-dom: 18.3.1 21 | react-router: 6.28.1 22 | react-router-dom: 6.28.1 23 | string-width: 4.2.3 24 | strip-ansi: 6.0.1 25 | wrap-ansi: 7.0.0 26 | prebuild-install: 7.1.3 27 | -------------------------------------------------------------------------------- /packages/core/src/middlewares/data-template.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '@tachybase/actions'; 2 | import { traverseJSON } from '@tachybase/database'; 3 | 4 | export const dataTemplate = async (ctx: Context, next) => { 5 | const { resourceName, actionName } = ctx.action; 6 | const { isTemplate, fields } = ctx.action.params; 7 | 8 | await next(); 9 | 10 | if (isTemplate && actionName === 'get' && fields.length > 0) { 11 | ctx.body = traverseJSON(JSON.parse(JSON.stringify(ctx.body)), { 12 | collection: ctx.db.getCollection(resourceName), 13 | include: fields, 14 | }); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /packages/database/src/decorators/target-collection-decorator.ts: -------------------------------------------------------------------------------- 1 | import lodash from 'lodash'; 2 | 3 | const injectTargetCollection = (originalMethod: any) => { 4 | const oldValue = originalMethod; 5 | 6 | const newMethod = function (...args: any[]) { 7 | const options = args[0]; 8 | const values = options.values; 9 | 10 | if (lodash.isPlainObject(values) && values.__collection) { 11 | options.targetCollection = values.__collection; 12 | } 13 | 14 | return oldValue.apply(this, args); 15 | }; 16 | 17 | return newMethod; 18 | }; 19 | 20 | export default injectTargetCollection; 21 | -------------------------------------------------------------------------------- /packages/core/src/migration.ts: -------------------------------------------------------------------------------- 1 | import { Migration as DbMigration } from '@tachybase/database'; 2 | 3 | import Application from './application'; 4 | import Plugin from './plugin'; 5 | import { PluginManager } from './plugin-manager'; 6 | 7 | export class Migration extends DbMigration { 8 | appVersion = ''; 9 | pluginVersion = ''; 10 | on = 'afterLoad'; 11 | 12 | get app() { 13 | return this.context.app as Application; 14 | } 15 | 16 | get pm() { 17 | return this.context.app.pm as PluginManager; 18 | } 19 | 20 | get plugin() { 21 | return this.context.plugin as Plugin; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/schema/src/path/contexts.ts: -------------------------------------------------------------------------------- 1 | export type Context = { 2 | flag: string; 3 | [key: string]: any; 4 | }; 5 | 6 | const ContextType = (flag: string, props?: any): Context => { 7 | return { 8 | flag, 9 | ...props, 10 | }; 11 | }; 12 | 13 | export const bracketContext = ContextType('[]'); 14 | 15 | export const bracketArrayContext = ContextType('[\\d]'); 16 | 17 | export const bracketDContext = ContextType('[[]]'); 18 | 19 | export const parenContext = ContextType('()'); 20 | 21 | export const braceContext = ContextType('{}'); 22 | 23 | export const destructorContext = ContextType('{x}'); 24 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/ExpressionScope.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | 3 | import { lazyMerge } from '../../shared'; 4 | import { SchemaExpressionScopeContext } from '../shared'; 5 | import { IExpressionScopeProps, ReactFC } from '../types'; 6 | 7 | export const ExpressionScope: ReactFC = (props) => { 8 | const scope = useContext(SchemaExpressionScopeContext); 9 | return ( 10 | 11 | {props.children} 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/acl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/acl", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/actions": "workspace:*", 10 | "@tachybase/resourcer": "workspace:*", 11 | "@tachybase/utils": "workspace:*", 12 | "koa-compose": "^4.1.0", 13 | "lodash": "4.17.21", 14 | "minimatch": "^5.1.6" 15 | }, 16 | "devDependencies": { 17 | "@types/lodash": "4.17.20", 18 | "@types/minimatch": "^6.0.0", 19 | "@types/node": "20.17.10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/devkit/src/builder/buildable-packages/skip-package.ts: -------------------------------------------------------------------------------- 1 | import { getPkgLog } from '../build/utils'; 2 | import { IBuildablePackage, IBuildContext } from '../interfaces'; 3 | 4 | export class SkipPackage implements IBuildablePackage { 5 | static name = 'skip'; 6 | name: string; 7 | dir: string; 8 | context: IBuildContext; 9 | 10 | constructor(name: string, dir: string, context: IBuildContext) { 11 | this.name = name; 12 | this.dir = dir; 13 | this.context = context; 14 | } 15 | async build() { 16 | const log = getPkgLog(this.name); 17 | log('skip ', this.name); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/resourcer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/resourcer", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/utils": "workspace:*", 10 | "glob": "11.0.0", 11 | "koa-compose": "^4.1.0", 12 | "lodash": "^4.17.21", 13 | "path-to-regexp": "^8.2.0", 14 | "qs": "^6.14.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "20.17.10", 18 | "koa": "^3.0.1", 19 | "koa-bodyparser": "^4.4.1", 20 | "supertest": "^7.1.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/actions/src/actions/proxy-to-repository.ts: -------------------------------------------------------------------------------- 1 | import lodash from 'lodash'; 2 | 3 | import { Context } from '../index'; 4 | import { getRepositoryFromParams } from '../utils'; 5 | 6 | export function proxyToRepository(paramKeys: string[], repositoryMethod: string) { 7 | return async function (ctx: Context, next) { 8 | const repository = getRepositoryFromParams(ctx); 9 | 10 | const callObj = lodash.pick(ctx.action.params, paramKeys); 11 | callObj.context = ctx; 12 | 13 | ctx.body = await repository[repositoryMethod](callObj); 14 | 15 | ctx.status = 200; 16 | await next(); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/components/src/__builtins__/hooks/usePrefixCls.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { ConfigProvider } from 'antd'; 4 | 5 | export const usePrefixCls = ( 6 | tag?: string, 7 | props?: { 8 | prefixCls?: string; 9 | }, 10 | ) => { 11 | if ('ConfigContext' in ConfigProvider) { 12 | // eslint-disable-next-line react-hooks/rules-of-hooks 13 | const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); 14 | return getPrefixCls(tag, props?.prefixCls); 15 | } else { 16 | const prefix = props?.prefixCls ?? 'ant-'; 17 | return `${prefix}${tag ?? ''}`; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /packages/data-source/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function parseCollectionName(collection: string) { 2 | if (!collection) { 3 | return []; 4 | } 5 | const dataSourceCollection = collection.split(':'); 6 | const collectionName = dataSourceCollection.pop(); 7 | const dataSourceName = dataSourceCollection[0] ?? 'main'; 8 | return [dataSourceName, collectionName]; 9 | } 10 | 11 | export function joinCollectionName(dataSourceName: string, collectionName: string) { 12 | if (!dataSourceName || dataSourceName === 'main') { 13 | return collectionName; 14 | } 15 | return `${dataSourceName}:${collectionName}`; 16 | } 17 | -------------------------------------------------------------------------------- /packages/components/src/form-button-group/style.ts: -------------------------------------------------------------------------------- 1 | import { genStyleHook } from './../__builtins__'; 2 | 3 | export default genStyleHook('form-button-group', (token) => { 4 | const { componentCls, antCls, colorBorder } = token; 5 | return { 6 | [componentCls]: { 7 | '&-sticky': { 8 | padding: '10px 0', 9 | borderTop: `1px solid ${colorBorder}`, 10 | zIndex: 999, 11 | 12 | [`${componentCls}-sticky-inner`]: { 13 | display: 'flex', 14 | [`${antCls}-formily-item`]: { 15 | flex: 2, 16 | }, 17 | }, 18 | }, 19 | }, 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/global.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { promptForTs, run } from '../util'; 4 | 5 | export default (cli: Command) => { 6 | const { APP_SERVER_ROOT, SERVER_TSCONFIG_PATH } = process.env; 7 | cli 8 | .allowUnknownOption() 9 | .option('-h, --help') 10 | .action((options) => { 11 | promptForTs(); 12 | run('tsx', [ 13 | '--tsconfig', 14 | SERVER_TSCONFIG_PATH ?? '', 15 | '-r', 16 | 'tsconfig-paths/register', 17 | `${APP_SERVER_ROOT}/src/index.ts`, 18 | ...process.argv.slice(2), 19 | ]); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security issues 2 | 3 | If you believe you have found a security issue in the Tego, please contact us immediately. 4 | 5 | When reporting a suspected security problem, please bear this in mind: 6 | 7 | * Make sure to provide as many details as possible about the vulnerability. 8 | * Please do not disclose publicly any security issues until we fix them and publish security releases. 9 | 10 | Contact us at https://github.com/tegojs/tego/issues. As soon as we receive the security report, we'll work promptly to confirm the issue and then to provide a security fix. You will receive a response from us within 7 working days. 11 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | tests: 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v3 16 | - name: Setup pnpm 17 | uses: pnpm/action-setup@v4 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 20 22 | cache: 'pnpm' 23 | - name: Install 24 | run: pnpm install 25 | - name: Tests 26 | run: pnpm vitest run --silent --reporter=dot 27 | -------------------------------------------------------------------------------- /packages/core/src/commands/install.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('install') 6 | .ipc() 7 | .auth() 8 | .option('-f, --force') 9 | .option('-c, --clean') 10 | .option('--lang ') 11 | .action(async (options) => { 12 | if (options.lang) { 13 | process.env.INIT_APP_LANG = options.lang; 14 | } 15 | await app.install(options); 16 | const reinstall = options.clean || options.force; 17 | app.logger.info(`app ${reinstall ? 'reinstalled' : 'installed'} successfully [v${app.getVersion()}]`); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/core/src/migrations/20240705000001-remove-pkgs-approval.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from '@tachybase/database'; 2 | 3 | import { Migration } from '../migration'; 4 | 5 | export default class extends Migration { 6 | on = 'beforeLoad'; 7 | appVersion = '<0.21.65'; 8 | 9 | async up() { 10 | console.log('remove workflow notice'); 11 | await this.pm.repository.destroy({ 12 | filter: { 13 | name: 'workflow-notice', 14 | }, 15 | }); 16 | 17 | console.log('remove approval'); 18 | await this.pm.repository.destroy({ 19 | filter: { 20 | name: 'approval', 21 | }, 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/database/src/operators/index.ts: -------------------------------------------------------------------------------- 1 | import array from './array'; 2 | import association from './association'; 3 | import boolean from './boolean'; 4 | import childCollection from './child-collection'; 5 | import date from './date'; 6 | import empty from './empty'; 7 | import eq from './eq'; 8 | import jsonb from './jsonb'; 9 | import ne from './ne'; 10 | import notIn from './notIn'; 11 | import string from './string'; 12 | 13 | export default { 14 | ...association, 15 | ...date, 16 | ...array, 17 | ...empty, 18 | ...string, 19 | ...eq, 20 | ...ne, 21 | ...notIn, 22 | ...boolean, 23 | ...childCollection, 24 | ...jsonb, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/database/src/view-collection.ts: -------------------------------------------------------------------------------- 1 | import { Collection, CollectionContext, CollectionOptions } from './collection'; 2 | 3 | export class ViewCollection extends Collection { 4 | constructor(options: CollectionOptions, context: CollectionContext) { 5 | options.autoGenId = false; 6 | options.timestamps = false; 7 | 8 | super(options, context); 9 | } 10 | 11 | isView() { 12 | return true; 13 | } 14 | 15 | protected sequelizeModelOptions(): any { 16 | const modelOptions = super.sequelizeModelOptions(); 17 | modelOptions.tableName = this.options.viewName || this.options.name; 18 | return modelOptions; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/main-data-source.ts: -------------------------------------------------------------------------------- 1 | import { DataSourceOptions, SequelizeDataSource } from '@tachybase/data-source'; 2 | 3 | export class MainDataSource extends SequelizeDataSource { 4 | init(options: DataSourceOptions = {}) { 5 | const { acl, resourceManager, database } = options; 6 | 7 | this.acl = acl; 8 | this.resourceManager = resourceManager; 9 | 10 | this.collectionManager = this.createCollectionManager({ 11 | collectionManager: { 12 | database, 13 | collectionsFilter: (collection) => { 14 | return collection.options.loadedFromCollectionManager; 15 | }, 16 | }, 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/schema", 3 | "version": "1.6.1", 4 | "license": "Apache-2.0", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "dependencies": { 8 | "camel-case": "^4.1.1", 9 | "hoist-non-react-statics": "^3.3.2", 10 | "lower-case": "^2.0.1", 11 | "param-case": "^3.0.4", 12 | "pascal-case": "^3.1.1", 13 | "upper-case": "^2.0.1" 14 | }, 15 | "devDependencies": { 16 | "@testing-library/react": "16.3.0", 17 | "immutable": "5.1.3", 18 | "moment": "2.30.1", 19 | "vitest": "3.2.4" 20 | }, 21 | "peerDependencies": { 22 | "react": ">=16.8.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/action.ts: -------------------------------------------------------------------------------- 1 | import { createBoundaryAnnotation } from './internals'; 2 | import { batchEnd, batchScopeEnd, batchScopeStart, batchStart, untrackEnd, untrackStart } from './reaction'; 3 | import { IAction } from './types'; 4 | 5 | export const action: IAction = createBoundaryAnnotation( 6 | () => { 7 | batchStart(); 8 | untrackStart(); 9 | }, 10 | () => { 11 | untrackEnd(); 12 | batchEnd(); 13 | }, 14 | ); 15 | 16 | action.scope = createBoundaryAnnotation( 17 | () => { 18 | batchScopeStart(); 19 | untrackStart(); 20 | }, 21 | () => { 22 | untrackEnd(); 23 | batchScopeEnd(); 24 | }, 25 | ); 26 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/create-plugin.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path'; 2 | 3 | import { Command } from 'commander'; 4 | 5 | import { PluginGenerator } from '../plugin-generator'; 6 | 7 | export default (cli: Command) => { 8 | cli 9 | .command('create-plugin') 10 | .argument('') 11 | .allowUnknownOption() 12 | .action(async (name, options) => { 13 | const generator = new PluginGenerator({ 14 | baseDir: process.cwd(), 15 | cwd: resolve(process.cwd(), name), 16 | args: options, 17 | context: { 18 | name, 19 | }, 20 | }); 21 | await generator.run(); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/components/src/radio/index.tsx: -------------------------------------------------------------------------------- 1 | import { connect, mapProps, mapReadPretty } from '@tachybase/schema'; 2 | 3 | import { Radio as AntdRadio } from 'antd'; 4 | 5 | import { PreviewText } from '../preview-text'; 6 | 7 | export const InternalRadio = connect( 8 | AntdRadio, 9 | mapProps({ 10 | value: 'checked', 11 | onInput: 'onChange', 12 | }), 13 | ); 14 | 15 | const Group = connect( 16 | AntdRadio.Group, 17 | mapProps({ 18 | dataSource: 'options', 19 | }), 20 | mapReadPretty(PreviewText.Select), 21 | ); 22 | 23 | export const Radio = Object.assign(InternalRadio, { 24 | __ANT_RADIO: true, 25 | Group, 26 | }); 27 | 28 | export default Radio; 29 | -------------------------------------------------------------------------------- /packages/core/src/commands/db-sync.ts: -------------------------------------------------------------------------------- 1 | import Application from '../application'; 2 | 3 | export default (app: Application) => { 4 | app 5 | .command('db:sync') 6 | .auth() 7 | .preload() 8 | .action(async (...cliArgs) => { 9 | const [opts] = cliArgs; 10 | console.log('db sync...'); 11 | 12 | const Collection = app.db.getCollection('collections'); 13 | if (Collection) { 14 | // @ts-ignore 15 | await Collection.repository.load(); 16 | } 17 | 18 | const force = false; 19 | await app.db.sync({ 20 | force, 21 | alter: { 22 | drop: force, 23 | }, 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/batch.ts: -------------------------------------------------------------------------------- 1 | import { isFn } from './checkers'; 2 | import { BatchCount, BatchEndpoints } from './environment'; 3 | import { createBoundaryAnnotation } from './internals'; 4 | import { batchEnd, batchScopeEnd, batchScopeStart, batchStart } from './reaction'; 5 | import { IBatch } from './types'; 6 | 7 | export const batch: IBatch = createBoundaryAnnotation(batchStart, batchEnd); 8 | batch.scope = createBoundaryAnnotation(batchScopeStart, batchScopeEnd); 9 | batch.endpoint = (callback?: () => void) => { 10 | if (!isFn(callback)) return; 11 | if (BatchCount.value === 0) { 12 | callback(); 13 | } else { 14 | BatchEndpoints.add(callback); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /packages/core/src/acl/index.ts: -------------------------------------------------------------------------------- 1 | import { ACL } from '@tachybase/acl'; 2 | 3 | import { availableActions } from './available-action'; 4 | 5 | const configureResources = [ 6 | 'roles', 7 | 'users', 8 | 'collections', 9 | 'fields', 10 | 'collections.fields', 11 | 'roles.collections', 12 | 'roles.resources', 13 | 'rolesResourcesScopes', 14 | 'availableActions', 15 | ]; 16 | 17 | export function createACL() { 18 | const acl = new ACL(); 19 | 20 | for (const [actionName, actionParams] of Object.entries(availableActions)) { 21 | acl.setAvailableAction(actionName, actionParams); 22 | } 23 | 24 | acl.registerConfigResources(configureResources); 25 | 26 | return acl; 27 | } 28 | -------------------------------------------------------------------------------- /packages/database/src/fields/set-field.ts: -------------------------------------------------------------------------------- 1 | import { ArrayField } from './array-field'; 2 | import { BaseColumnFieldOptions } from './field'; 3 | 4 | export interface SetFieldOptions extends BaseColumnFieldOptions { 5 | type: 'set'; 6 | } 7 | 8 | export class SetField extends ArrayField { 9 | beforeSave = (model) => { 10 | const oldValue = model.get(this.options.name); 11 | if (oldValue) { 12 | model.set(this.options.name, [...new Set(oldValue)]); 13 | } 14 | }; 15 | 16 | bind() { 17 | super.bind(); 18 | this.on('beforeSave', this.beforeSave); 19 | } 20 | 21 | unbind() { 22 | super.unbind(); 23 | this.off('beforeSave', this.beforeSave); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/schema/src/shared/middleware.ts: -------------------------------------------------------------------------------- 1 | export interface IMiddleware { 2 | (payload: Payload, next: (payload?: Payload) => Result): Result; 3 | } 4 | 5 | export const applyMiddleware = (payload: any, fns: IMiddleware[] = []) => { 6 | const compose = (payload: any, fns: IMiddleware[]): Promise => { 7 | const prevPayload = payload; 8 | return Promise.resolve(fns[0](payload, (payload) => compose(payload ?? prevPayload, fns.slice(1)))); 9 | }; 10 | return new Promise((resolve, reject) => { 11 | compose( 12 | payload, 13 | fns.concat((payload) => { 14 | resolve(payload); 15 | }), 16 | ).catch(reject); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/test/setup/settings.postgres.js: -------------------------------------------------------------------------------- 1 | const defaultSettings = require('../../tego/presets/settings'); 2 | 3 | /** @type {import('@tachybase/globals').Settings} */ 4 | module.exports = { 5 | ...defaultSettings, 6 | 7 | logger: { 8 | ...defaultSettings.logger, 9 | level: 'error', 10 | }, 11 | 12 | database: { 13 | dialect: 'postgres', 14 | host: 'localhost', 15 | port: 5432, 16 | database: 'postgres', 17 | user: 'tachybase', 18 | password: 'tachybase', 19 | underscored: false, 20 | timezone: '+00:00', 21 | ssl: { 22 | // ca: '', 23 | // key: '', 24 | // cert: '', 25 | // rejectUnauthorized: true, 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/utils/src/requireModule.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { pathToFileURL } from 'node:url'; 3 | 4 | export function requireModule(m: any) { 5 | if (typeof m === 'string') { 6 | m = require(m); 7 | } 8 | 9 | if (typeof m !== 'object') { 10 | return m; 11 | } 12 | 13 | return m.__esModule ? m.default : m; 14 | } 15 | 16 | export default requireModule; 17 | 18 | export async function importModule(m: string) { 19 | if (!process.env.VITEST) { 20 | return requireModule(m); 21 | } 22 | 23 | if (path.isAbsolute(m)) { 24 | m = pathToFileURL(m).href; 25 | } 26 | 27 | const r = (await import(m)).default; 28 | return r.__esModule ? r.default : r; 29 | } 30 | -------------------------------------------------------------------------------- /packages/acl/src/__tests__/allow-manager.test.ts: -------------------------------------------------------------------------------- 1 | import { ACL } from '..'; 2 | import { AllowManager } from '../allow-manager'; 3 | 4 | describe('allow manager', () => { 5 | let acl: ACL; 6 | 7 | beforeEach(() => { 8 | acl = new ACL(); 9 | }); 10 | 11 | it('should allow star resource', async () => { 12 | const allowManager = new AllowManager(acl); 13 | 14 | allowManager.allow('*', 'download', 'public'); 15 | 16 | expect(await allowManager.isAllowed('users', 'download', {})).toBeTruthy(); 17 | expect(await allowManager.isAllowed('users', 'fake-method', {})).toBeFalsy(); 18 | expect(await allowManager.isAllowed('users', 'other-method', {})).toBeFalsy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/data-source/src/default-actions/move.ts: -------------------------------------------------------------------------------- 1 | import actions, { Context } from '@tachybase/actions'; 2 | 3 | import { getRepositoryFromParams } from './utils'; 4 | 5 | const databaseMoveAction = actions.move; 6 | 7 | export async function move(ctx: Context, next) { 8 | const repository = getRepositoryFromParams(ctx); 9 | 10 | if (repository.move) { 11 | ctx.body = await repository.move(ctx.action.params); 12 | await next(); 13 | return; 14 | } 15 | 16 | if (repository.database) { 17 | ctx.databaseRepository = repository; 18 | return databaseMoveAction(ctx, next); 19 | } 20 | 21 | throw new Error(`Repository can not handle action move for ${ctx.action.resourceName}`); 22 | } 23 | -------------------------------------------------------------------------------- /packages/actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/actions", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/cache": "workspace:*", 10 | "@tachybase/database": "workspace:*", 11 | "@tachybase/resourcer": "workspace:*", 12 | "@tachybase/utils": "workspace:*", 13 | "koa": "^2.16.2", 14 | "lodash": "4.17.21", 15 | "sequelize": "^6.37.7" 16 | }, 17 | "devDependencies": { 18 | "@types/koa": "^2.15.0", 19 | "@types/lodash": "^4.17.20", 20 | "koa-bodyparser": "^4.4.1", 21 | "qs": "^6.14.0", 22 | "supertest": "^7.1.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/evaluators/src/utils/formulajs.ts: -------------------------------------------------------------------------------- 1 | import * as functions from '@formulajs/formulajs'; 2 | 3 | import { evaluate } from '.'; 4 | 5 | const fnNames = Object.keys(functions).filter((key) => key !== 'default'); 6 | const fns = fnNames.map((key) => functions[key]); 7 | 8 | export default evaluate.bind(function (expression: string, scope = {}) { 9 | const fn = new Function(...fnNames, ...Object.keys(scope), `return ${expression}`); 10 | const result = fn(...fns, ...Object.values(scope)); 11 | if (typeof result === 'number') { 12 | if (Number.isNaN(result) || !Number.isFinite(result)) { 13 | return null; 14 | } 15 | return functions.ROUND(result, 9); 16 | } 17 | return result; 18 | }, {}); 19 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useCompatEffect } from './useCompatEffect'; 2 | import { useCompatFactory } from './useCompatFactory'; 3 | import { useDidUpdate } from './useDidUpdate'; 4 | import { useForceUpdate } from './useForceUpdate'; 5 | import { useLayoutEffect } from './useLayoutEffect'; 6 | import { useObserver } from './useObserver'; 7 | 8 | export const unstable_useForceUpdate = useForceUpdate; 9 | export const unstable_useCompatEffect = useCompatEffect; 10 | export const unstable_useCompatFactory = useCompatFactory; 11 | export const unstable_useDidUpdate = useDidUpdate; 12 | export const unstable_useLayoutEffect = useLayoutEffect; 13 | export const unstable_useObserver = useObserver; 14 | -------------------------------------------------------------------------------- /packages/schema/src/validator/template.ts: -------------------------------------------------------------------------------- 1 | import { FormPath, isFn, isStr } from '../shared'; 2 | import { getValidateMessageTemplateEngine } from './registry'; 3 | import { IValidateResult, IValidatorRules } from './types'; 4 | 5 | export const render = (result: IValidateResult, rules: IValidatorRules): IValidateResult => { 6 | const { message } = result; 7 | if (isStr(message)) { 8 | const template = getValidateMessageTemplateEngine(); 9 | if (isFn(template)) { 10 | result.message = template(message, rules); 11 | } 12 | result.message = result.message.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, $0) => { 13 | return FormPath.getIn(rules, $0); 14 | }); 15 | } 16 | return result; 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/full-build.yaml: -------------------------------------------------------------------------------- 1 | name: Full Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - dev 7 | pull_request: 8 | branches: 9 | - main 10 | - dev 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | full-build: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v3 21 | - name: Setup pnpm 22 | uses: pnpm/action-setup@v4 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | cache: 'pnpm' 28 | - name: Install deps 29 | run: pnpm install 30 | - name: Build 31 | run: pnpm build 32 | -------------------------------------------------------------------------------- /packages/core/src/plugin-manager/options/collection.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from '@tachybase/database'; 2 | 3 | export default defineCollection({ 4 | name: 'applicationPlugins', 5 | dumpRules: 'required', 6 | repository: 'PluginManagerRepository', 7 | origin: '@tego/core', 8 | fields: [ 9 | { type: 'string', name: 'name', unique: true }, 10 | { type: 'string', name: 'packageName', unique: true }, 11 | { type: 'string', name: 'version' }, 12 | { type: 'boolean', name: 'enabled' }, 13 | { type: 'boolean', name: 'installed' }, 14 | { type: 'boolean', name: 'builtIn' }, 15 | { type: 'json', name: 'options' }, 16 | { type: 'boolean', name: 'subView', defaultValue: true }, 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /packages/components/src/checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import { connect, mapProps, mapReadPretty } from '@tachybase/schema'; 2 | 3 | import { Checkbox as AntdCheckbox } from 'antd'; 4 | 5 | import { PreviewText } from '../preview-text'; 6 | 7 | const InternalCheckbox = connect( 8 | AntdCheckbox, 9 | mapProps({ 10 | value: 'checked', 11 | onInput: 'onChange', 12 | }), 13 | ); 14 | 15 | const Group = connect( 16 | AntdCheckbox.Group, 17 | mapProps({ 18 | dataSource: 'options', 19 | }), 20 | mapReadPretty(PreviewText.Select, { 21 | mode: 'tags', 22 | }), 23 | ); 24 | 25 | export const Checkbox = Object.assign(InternalCheckbox, { 26 | __ANT_CHECKBOX: true, 27 | Group, 28 | }); 29 | 30 | export default Checkbox; 31 | -------------------------------------------------------------------------------- /packages/data-source/src/repository.ts: -------------------------------------------------------------------------------- 1 | import * as console from 'node:console'; 2 | 3 | import { IModel, IRepository } from './types'; 4 | 5 | export class Repository implements IRepository { 6 | async create(options) { 7 | console.log('Repository.create....'); 8 | } 9 | async update(options) {} 10 | async find(options?: any): Promise { 11 | return []; 12 | } 13 | async findOne(options?: any): Promise { 14 | return {}; 15 | } 16 | async destroy(options) {} 17 | 18 | count(options?: any): Promise { 19 | return Promise.resolve(undefined); 20 | } 21 | 22 | findAndCount(options?: any): Promise<[IModel[], number]> { 23 | return Promise.resolve([[], undefined]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/database/src/decorators/must-have-filter-decorator.ts: -------------------------------------------------------------------------------- 1 | const mustHaveFilter = () => (originalMethod: any, context: ClassMethodDecoratorContext) => { 2 | const oldValue = originalMethod; 3 | 4 | const newMethod = function (...args: any[]) { 5 | const options = args[0]; 6 | 7 | if (Array.isArray(options.values)) { 8 | return oldValue.apply(this, args); 9 | } 10 | 11 | if (!options?.filter && !options?.filterByTk && !options?.forceUpdate) { 12 | throw new Error(`must provide filter or filterByTk for ${String(context.name)} call, or set forceUpdate to true`); 13 | } 14 | 15 | return oldValue.apply(this, args); 16 | }; 17 | 18 | return newMethod; 19 | }; 20 | 21 | export default mustHaveFilter; 22 | -------------------------------------------------------------------------------- /packages/schema/src/shared/deprecate.ts: -------------------------------------------------------------------------------- 1 | import { isFn, isStr } from './checkers'; 2 | 3 | const caches = {}; 4 | 5 | export function deprecate( 6 | method: any, 7 | message?: string, 8 | help?: string, 9 | ) { 10 | if (isFn(method)) { 11 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 12 | return function (p1?: P1, p2?: P2, p3?: P3, p4?: P4, p5?: P5) { 13 | deprecate(message, help); 14 | return method.apply(this, arguments); 15 | }; 16 | } 17 | if (isStr(method) && !caches[method]) { 18 | caches[method] = true; 19 | console.warn(new Error(`${method} has been deprecated. Do not continue to use this api.${message || ''}`)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/test/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from '@testing-library/react'; 4 | 5 | export type { queries } from '@testing-library/dom'; 6 | 7 | function customRender(ui: React.ReactElement, options = {}) { 8 | return render(ui, { 9 | // wrap provider(s) here if needed 10 | wrapper: ({ children }) => children, 11 | ...options, 12 | }); 13 | } 14 | 15 | export * from '@testing-library/react'; 16 | export { default as userEvent } from '@testing-library/user-event'; 17 | // override render export 18 | export { customRender as render }; 19 | 20 | export const sleep = async (timeout = 0) => { 21 | return new Promise((resolve) => { 22 | setTimeout(resolve, timeout); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/data-source/src/data-source-factory.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from './data-source'; 2 | 3 | export class DataSourceFactory { 4 | public collectionTypes: Map = new Map(); 5 | 6 | register(type: string, dataSourceClass: typeof DataSource) { 7 | this.collectionTypes.set(type, dataSourceClass); 8 | } 9 | 10 | getClass(type: string): typeof DataSource { 11 | return this.collectionTypes.get(type); 12 | } 13 | 14 | create(type: string, options: any = {}): DataSource { 15 | const klass = this.collectionTypes.get(type); 16 | if (!klass) { 17 | throw new Error(`Data source type "${type}" not found`); 18 | } 19 | 20 | // @ts-ignore 21 | return new klass(options); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/schema/src/reactive-react/hooks/useObserver.ts: -------------------------------------------------------------------------------- 1 | import { Tracker } from '../../reactive'; 2 | import { IObserverOptions } from '../types'; 3 | import { useCompatFactory } from './useCompatFactory'; 4 | import { useForceUpdate } from './useForceUpdate'; 5 | 6 | export const useObserver = any>(view: T, options?: IObserverOptions): ReturnType => { 7 | const forceUpdate = useForceUpdate(); 8 | const tracker = useCompatFactory( 9 | () => 10 | new Tracker(() => { 11 | if (typeof options?.scheduler === 'function') { 12 | options.scheduler(forceUpdate); 13 | } else { 14 | forceUpdate(); 15 | } 16 | }, options?.displayName), 17 | ); 18 | return tracker.track(view); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/core/src/middlewares/i18n.ts: -------------------------------------------------------------------------------- 1 | import { Locale } from '../locale'; 2 | 3 | export async function i18n(ctx, next) { 4 | ctx.getCurrentLocale = () => { 5 | const lng = 6 | ctx.get('X-Locale') || 7 | (ctx.request.query.locale as string) || 8 | ctx.tego.i18n.language || 9 | ctx.acceptsLanguages().shift() || 10 | 'en-US'; 11 | return lng; 12 | }; 13 | const lng = ctx.getCurrentLocale(); 14 | const localeManager = ctx.tego.localeManager as Locale; 15 | const i18n = await localeManager.getI18nInstance(lng); 16 | ctx.i18n = i18n; 17 | ctx.t = i18n.t.bind(i18n); 18 | if (lng !== '*' && lng) { 19 | i18n.changeLanguage(lng); 20 | await localeManager.loadResourcesByLang(lng); 21 | } 22 | await next(); 23 | } 24 | -------------------------------------------------------------------------------- /packages/di/src/error/cannot-inject-value.error.ts: -------------------------------------------------------------------------------- 1 | import { Constructable } from '@tachybase/utils'; 2 | 3 | /** 4 | * Thrown when DI cannot inject value into property decorated by @Inject decorator. 5 | */ 6 | export class CannotInjectValueError extends Error { 7 | public name = 'CannotInjectValueError'; 8 | 9 | get message(): string { 10 | return ( 11 | `Cannot inject value into "${this.target.constructor.name}.${this.propertyName}". ` + 12 | `Please make sure you setup reflect-metadata properly and you don't use interfaces without service tokens as injection value.` 13 | ); 14 | } 15 | 16 | constructor( 17 | private target: Constructable, 18 | private propertyName: string, 19 | ) { 20 | super(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tego/server", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/acl": "workspace:*", 10 | "@tachybase/actions": "workspace:*", 11 | "@tachybase/auth": "workspace:*", 12 | "@tachybase/cache": "workspace:*", 13 | "@tachybase/data-source": "workspace:*", 14 | "@tachybase/database": "workspace:*", 15 | "@tachybase/di": "workspace:*", 16 | "@tachybase/evaluators": "workspace:*", 17 | "@tachybase/logger": "workspace:*", 18 | "@tachybase/resourcer": "workspace:*", 19 | "@tachybase/utils": "workspace:*", 20 | "@tego/core": "workspace:*" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/logger/src/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import TachybaseGlobal from '@tachybase/globals'; 3 | 4 | export const getLoggerLevel = () => TachybaseGlobal.settings.logger.level ?? 'info'; 5 | 6 | export const getLoggerFilePath = (...paths: string[]): string => { 7 | return path.resolve( 8 | path.resolve(process.env.TEGO_RUNTIME_HOME, TachybaseGlobal.settings.logger.basePath ?? 'storage/logs'), 9 | ...paths, 10 | ); 11 | }; 12 | 13 | export const getLoggerTransport = (): ('console' | 'file' | 'dailyRotateFile')[] => 14 | TachybaseGlobal.settings.logger.transport ?? ['console', 'dailyRotateFile']; 15 | 16 | export const getLoggerFormat = (): 'logfmt' | 'json' | 'delimiter' | 'console' => 17 | TachybaseGlobal.settings.logger.format ?? 'console'; 18 | -------------------------------------------------------------------------------- /packages/schema/src/shared/string.ts: -------------------------------------------------------------------------------- 1 | // ansiRegex 2 | const ansiRegex = () => { 3 | const pattern = [ 4 | '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', 5 | '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))', 6 | ].join('|'); 7 | 8 | return new RegExp(pattern, 'g'); 9 | }; 10 | 11 | // astralRegex 12 | const regex = '[\uD800-\uDBFF][\uDC00-\uDFFF]'; 13 | 14 | const astralRegex = (opts?: { exact: boolean }) => 15 | opts && opts.exact ? new RegExp(`^${regex}$`) : new RegExp(regex, 'g'); 16 | 17 | // stripAnsi 18 | const stripAnsi = (input: any) => (typeof input === 'string' ? input.replace(ansiRegex(), '') : input); 19 | 20 | export const stringLength = (input: string) => stripAnsi(input).replace(astralRegex(), ' ').length; 21 | -------------------------------------------------------------------------------- /packages/schema/src/validator/__tests__/parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { parseValidatorDescriptions } from '../parser'; 2 | 3 | describe('parseValidatorDescriptions', () => { 4 | test('basic', () => { 5 | expect(parseValidatorDescriptions('date')).toEqual([{ format: 'date' }]); 6 | const validator = () => { 7 | return ''; 8 | }; 9 | expect(parseValidatorDescriptions(validator)).toEqual([{ validator }]); 10 | expect(parseValidatorDescriptions(['date'])).toEqual([{ format: 'date' }]); 11 | expect(parseValidatorDescriptions([validator])).toEqual([{ validator }]); 12 | expect(parseValidatorDescriptions({ format: 'date' })).toEqual([{ format: 'date' }]); 13 | expect(parseValidatorDescriptions({ validator })).toEqual([{ validator }]); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/components/src/tree-select/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, mapProps, mapReadPretty } from '@tachybase/schema'; 3 | 4 | import { LoadingOutlined } from '@ant-design/icons'; 5 | import { TreeSelect as AntdTreeSelect } from 'antd'; 6 | 7 | import { PreviewText } from '../preview-text'; 8 | 9 | // FIXME types 10 | export const TreeSelect: any = connect( 11 | AntdTreeSelect, 12 | mapProps( 13 | { 14 | dataSource: 'treeData', 15 | }, 16 | (props, field) => { 17 | return { 18 | ...props, 19 | suffixIcon: field?.['loading'] || field?.['validating'] ? : props.suffixIcon, 20 | }; 21 | }, 22 | ), 23 | mapReadPretty(PreviewText.TreeSelect), 24 | ); 25 | 26 | export default TreeSelect; 27 | -------------------------------------------------------------------------------- /packages/utils/src/isPortalInBody.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断一个 DOM 对象是否是由 createPortal 挂在到了 body 上 3 | * @param domNode DOM 对象 4 | */ 5 | 6 | export const isPortalInBody = (dom: Element) => { 7 | while (dom) { 8 | // 如果有 `tb-action` 类名,说明是一个 Action 按钮,其本身已经阻止了冒泡,不需要再次阻止,如果阻止会导致点击无效 9 | if (dom.id === 'root' || dom.classList?.contains('tb-action')) { 10 | return false; 11 | } 12 | dom = dom.parentNode as Element; 13 | } 14 | 15 | // 测试环境下大部分都是直接 render 的组件,是没有以 root 为 ID 的根元素的 16 | if (process.env.__TEST__) { 17 | return false; 18 | } 19 | 20 | if (process.env.NODE_ENV !== 'production') { 21 | if (!document.querySelector('#root')) { 22 | throw new Error(`isPortalInBody: can not find element with id 'root'`); 23 | } 24 | } 25 | 26 | return true; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/components/src/form-item/style/grid.tsx: -------------------------------------------------------------------------------- 1 | import { GenerateStyle } from '../../__builtins__'; 2 | 3 | export const getGridStyle: GenerateStyle = (token) => { 4 | const { componentCls } = token; 5 | 6 | const colCls = `${componentCls}-item-col`; 7 | 8 | const genGrid = (grid: number) => { 9 | return { 10 | flex: `0 0 ${(grid / 24) * 100}%`, 11 | maxWidth: `${(grid / 24) * 100}%`, 12 | }; 13 | }; 14 | const genGrids = () => { 15 | return Array.from({ length: 24 }, (_, i) => { 16 | const gridCls = `${colCls}-${i + 1}`; 17 | return { 18 | [gridCls]: genGrid(i + 1), 19 | }; 20 | }).reduce((acc, cur) => ({ ...acc, ...cur }), {}); 21 | }; 22 | 23 | return { 24 | [componentCls]: { 25 | ...genGrids(), 26 | }, 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/core/src/pub-sub-manager/types.ts: -------------------------------------------------------------------------------- 1 | export interface PubSubManagerOptions { 2 | channelPrefix?: string; 3 | } 4 | 5 | export interface PubSubManagerPublishOptions { 6 | skipSelf?: boolean; 7 | onlySelf?: boolean; 8 | } 9 | 10 | export interface PubSubManagerSubscribeOptions { 11 | debounce?: number; 12 | // 回调的真实调用者 13 | callbackCaller?: any; 14 | } 15 | 16 | export type PubSubCallback = (message: any) => Promise; 17 | 18 | export interface IPubSubAdapter { 19 | isConnected(): Promise | boolean; 20 | connect(): Promise; 21 | close(): Promise; 22 | subscribe(channel: string, callback: PubSubCallback): Promise; 23 | unsubscribe(channel: string, callback: PubSubCallback): Promise; 24 | publish(channel: string, message: string | object): Promise; 25 | } 26 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/value-parsers/base.test.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueParser as ValueParser } from '../../value-parsers'; 2 | 3 | describe('number value parser', () => { 4 | let parser: ValueParser; 5 | 6 | beforeEach(() => { 7 | parser = new ValueParser({}, {}); 8 | }); 9 | 10 | it('should be converted to an array', () => { 11 | expect(parser.toArr('A/B', '/')).toEqual(['A', 'B']); 12 | expect(parser.toArr('A,B')).toEqual(['A', 'B']); 13 | expect(parser.toArr('A, B')).toEqual(['A', 'B']); 14 | expect(parser.toArr('A, B')).toEqual(['A', 'B']); 15 | expect(parser.toArr('A, B ')).toEqual(['A', 'B']); 16 | expect(parser.toArr('A, B ')).toEqual(['A', 'B']); 17 | expect(parser.toArr('A、 B')).toEqual(['A', 'B']); 18 | expect(parser.toArr('A ,, B')).toEqual(['A', 'B']); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/components/src/cascader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, mapProps, mapReadPretty } from '@tachybase/schema'; 3 | 4 | import { LoadingOutlined } from '@ant-design/icons'; 5 | import { Cascader as AntdCascader } from 'antd'; 6 | 7 | import { PreviewText } from '../preview-text'; 8 | 9 | export type { BaseOptionType, DefaultOptionType, FieldNames } from 'rc-cascader'; 10 | 11 | export const Cascader = connect( 12 | AntdCascader, 13 | mapProps( 14 | { 15 | dataSource: 'options', 16 | }, 17 | (props, field) => { 18 | return { 19 | ...props, 20 | suffixIcon: field?.['loading'] || field?.['validating'] ? : props.suffixIcon, 21 | }; 22 | }, 23 | ), 24 | mapReadPretty(PreviewText.Cascader), 25 | ); 26 | 27 | export default Cascader; 28 | -------------------------------------------------------------------------------- /packages/devkit/src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import chalk from 'chalk'; 3 | import { Command } from 'commander'; 4 | import semver from 'semver'; 5 | 6 | import commands from './commands'; 7 | import { __dirname } from './constants'; 8 | import { genTsConfigPaths, initEnv } from './util'; 9 | 10 | import './notify-updates'; 11 | 12 | import { createRequire } from 'node:module'; 13 | 14 | const require = createRequire(import.meta.url); 15 | 16 | const cli = new Command(); 17 | 18 | cli.version(require('../package.json').version); 19 | 20 | initEnv(); 21 | genTsConfigPaths(); 22 | 23 | commands(cli).then(() => { 24 | if (semver.satisfies(process.version, '<20')) { 25 | console.error(chalk.red('[tachybase cli]: Node.js version must be >= 20')); 26 | process.exit(1); 27 | } 28 | 29 | cli.parse(process.argv); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/test/playwright/tests/auth.setup.ts: -------------------------------------------------------------------------------- 1 | import { expect, test as setup } from '@playwright/test'; 2 | 3 | // 保存登录状态,避免每次都要登录 4 | setup('admin', async ({ page }) => { 5 | await page.goto('/'); 6 | await page.getByPlaceholder('Username/Email').click(); 7 | await page.getByPlaceholder('Username/Email').fill('admin@tachybase.com'); 8 | await page.getByPlaceholder('Password').click(); 9 | await page.getByPlaceholder('Password').fill('admin123'); 10 | await page.getByRole('button', { name: 'Sign in' }).click(); 11 | 12 | await expect(page.getByTestId('user-center-button')).toBeVisible(); 13 | 14 | // 开启配置状态 15 | await page.evaluate(() => { 16 | localStorage.setItem('TACHYBASE_DESIGNABLE', 'true'); 17 | }); 18 | await page.context().storageState({ 19 | path: process.env.PLAYWRIGHT_AUTH_FILE, 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/schema/src/react/__tests__/shared.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | 3 | import { render } from '@testing-library/react'; 4 | 5 | export class ErrorBoundary extends Component { 6 | state = { 7 | error: null, 8 | }; 9 | 10 | componentDidCatch(error: Error) { 11 | this.setState({ 12 | error, 13 | }); 14 | } 15 | 16 | render() { 17 | if (this.state.error) { 18 | return

{this.state.error.message}
; 19 | } 20 | return {this.props.children}; 21 | } 22 | } 23 | 24 | export const expectThrowError = (callback: () => React.ReactElement) => { 25 | const { queryByTestId } = render({callback()}); 26 | expect(queryByTestId('error-boundary-message')).toBeVisible(); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/VoidField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useField, useForm } from '../hooks'; 4 | import { useAttach } from '../hooks/useAttach'; 5 | import { FieldContext } from '../shared'; 6 | import { IVoidFieldProps, JSXComponent } from '../types'; 7 | import { ReactiveField } from './ReactiveField'; 8 | 9 | export const VoidField = (props: IVoidFieldProps) => { 10 | const form = useForm(); 11 | const parent = useField(); 12 | const field = useAttach(form.createVoidField({ basePath: parent?.address, ...props })); 13 | return ( 14 | 15 | {props.children} 16 | 17 | ); 18 | }; 19 | 20 | VoidField.displayName = 'VoidField'; 21 | -------------------------------------------------------------------------------- /packages/components/src/select/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, mapProps, mapReadPretty, ReactFC } from '@tachybase/schema'; 3 | 4 | import { LoadingOutlined } from '@ant-design/icons'; 5 | import { Select as AntdSelect } from 'antd'; 6 | import { SelectProps } from 'antd/lib/select'; 7 | 8 | import { PreviewText } from '../preview-text'; 9 | 10 | export const Select: ReactFC> = connect( 11 | AntdSelect, 12 | mapProps( 13 | { 14 | dataSource: 'options', 15 | loading: true, 16 | }, 17 | (props, field) => { 18 | return { 19 | ...props, 20 | suffixIcon: field?.['loading'] || field?.['validating'] ? : props.suffixIcon, 21 | }; 22 | }, 23 | ), 24 | mapReadPretty(PreviewText.Select), 25 | ); 26 | 27 | export default Select; 28 | -------------------------------------------------------------------------------- /packages/components/src/transfer/index.tsx: -------------------------------------------------------------------------------- 1 | import { connect, isVoidField, mapProps } from '@tachybase/schema'; 2 | 3 | import { Transfer as AntdTransfer } from 'antd'; 4 | 5 | export const Transfer = connect( 6 | AntdTransfer, 7 | mapProps( 8 | { 9 | value: 'targetKeys', 10 | }, 11 | (props, field) => { 12 | if (isVoidField(field)) return props; 13 | return { 14 | ...props, 15 | dataSource: 16 | field.dataSource?.map((item) => { 17 | return { 18 | ...item, 19 | title: item.title || item.label, 20 | key: item.key || item.value, 21 | }; 22 | }) || [], 23 | }; 24 | }, 25 | ), 26 | ); 27 | 28 | Transfer.defaultProps = { 29 | render: (item) => item.title ?? null, 30 | }; 31 | 32 | export default Transfer; 33 | -------------------------------------------------------------------------------- /packages/database/src/operators/jsonb.ts: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize'; 2 | 3 | import { isPg } from './utils'; 4 | 5 | function escapeLike(value: string) { 6 | return value.replace(/[_%]/g, '\\$&'); 7 | } 8 | 9 | export default { 10 | $containsJsonbValue(value, ctx) { 11 | if (Array.isArray(value)) { 12 | const conditions = value.map((item) => ({ 13 | ['::text']: { 14 | [isPg(ctx) ? Op.iLike : Op.like]: `%${escapeLike(item)}%`, 15 | }, 16 | })); 17 | return { 18 | [Op.or]: conditions, 19 | }; 20 | } 21 | 22 | return { 23 | // 先将 jsonb 转换为字符串, 实际测试发现这种语法可行 24 | ['::text']: { 25 | // 使用 Op.like 操作符来检查 JSONB 字段是否包含特定的字符串 26 | [isPg(ctx) ? Op.iLike : Op.like]: `%${escapeLike(value)}%`, 27 | }, 28 | }; 29 | }, 30 | } as Record; 31 | -------------------------------------------------------------------------------- /packages/data-source/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/data-source", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "dependencies": { 9 | "@tachybase/acl": "workspace:*", 10 | "@tachybase/actions": "workspace:*", 11 | "@tachybase/database": "workspace:*", 12 | "@tachybase/resourcer": "workspace:*", 13 | "@tachybase/utils": "workspace:*", 14 | "deepmerge": "^4.3.1", 15 | "koa-compose": "^4.1.0", 16 | "lodash": "4.17.21" 17 | }, 18 | "devDependencies": { 19 | "@types/koa": "^2.15.0", 20 | "@types/koa-bodyparser": "^4.3.12", 21 | "@types/lodash": "^4.17.20", 22 | "@types/supertest": "^6.0.3", 23 | "koa": "^2.16.2", 24 | "koa-bodyparser": "4.4.1", 25 | "supertest": "^7.1.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/devkit/src/builder/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 可构建的项目,可以设计成兼容 pnpm、npm、yarn 等 monorepo 3 | */ 4 | export interface IProject { 5 | dir: string; 6 | manifest: { 7 | name: string; 8 | }; 9 | } 10 | 11 | export type IBuildContext = 12 | | { 13 | onlyTar: true; 14 | sourcemap?: boolean; 15 | dts?: boolean; 16 | retry?: boolean; 17 | development?: boolean; 18 | tar?: boolean; 19 | } 20 | | { 21 | onlyTar?: false | undefined; 22 | sourcemap: boolean; 23 | dts: boolean; 24 | retry: boolean; 25 | development: boolean; 26 | tar: boolean; 27 | }; 28 | export interface IBuildablePackage { 29 | name: string; 30 | dir: string; 31 | context: IBuildContext; 32 | build(): Promise; 33 | } 34 | 35 | export interface IBuilder { 36 | build(): Promise; 37 | } 38 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fields/uuid-field.test.ts: -------------------------------------------------------------------------------- 1 | import { mockDatabase } from '../'; 2 | import { Database } from '../../database'; 3 | 4 | describe('string field', () => { 5 | let db: Database; 6 | 7 | beforeEach(async () => { 8 | db = mockDatabase(); 9 | await db.clean({ drop: true }); 10 | }); 11 | 12 | afterEach(async () => { 13 | await db.close(); 14 | }); 15 | 16 | it('should create uuid field', async () => { 17 | const Test = db.collection({ 18 | name: 'tests', 19 | fields: [ 20 | { 21 | name: 'uuid', 22 | type: 'uuid', 23 | }, 24 | ], 25 | }); 26 | 27 | await Test.sync(); 28 | const item = await Test.model.create(); 29 | 30 | expect(item['uuid']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/evaluators/src/client/index.tsx: -------------------------------------------------------------------------------- 1 | import { Registry } from '@tachybase/utils/client'; 2 | 3 | import formulajs from './engines/formulajs'; 4 | import string from './engines/string'; 5 | 6 | export interface Evaluator { 7 | label: string; 8 | tooltip?: string; 9 | link?: string; 10 | evaluate(exp: string, scope?: { [key: string]: any }): any; 11 | } 12 | 13 | export const evaluators = new Registry(); 14 | 15 | export { evaluate, appendArrayColumn } from '../utils'; 16 | 17 | evaluators.register('formula.js', formulajs); 18 | evaluators.register('string', string); 19 | 20 | export function getOptions() { 21 | return Array.from((evaluators as Registry).getEntities()).reduce( 22 | (result: any[], [value, options]) => result.concat({ value, ...options }), 23 | [], 24 | ); 25 | } 26 | 27 | export default evaluators; 28 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/operator/notIn.test.ts: -------------------------------------------------------------------------------- 1 | import Database from '../../database'; 2 | import { mockDatabase } from '../index'; 3 | 4 | describe('ne operator', () => { 5 | let db: Database; 6 | let Test; 7 | beforeEach(async () => { 8 | db = mockDatabase({}); 9 | await db.clean({ drop: true }); 10 | 11 | Test = db.collection({ 12 | name: 'tests', 13 | fields: [{ type: 'string', name: 'name' }], 14 | }); 15 | 16 | await db.sync(); 17 | }); 18 | 19 | afterEach(async () => { 20 | await db.close(); 21 | }); 22 | 23 | it('should notIn with null', async () => { 24 | await db.getRepository('tests').create({}); 25 | 26 | const results = await db.getRepository('tests').count({ 27 | filter: { 28 | 'name.$notIn': ['123'], 29 | }, 30 | }); 31 | 32 | expect(results).toEqual(1); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/database/src/value-parsers/to-one-value-parser.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from '../repository'; 2 | import { BaseValueParser } from './base-value-parser'; 3 | 4 | export class ToOneValueParser extends BaseValueParser { 5 | async setValue(value: any) { 6 | const dataIndex = this.ctx?.column?.dataIndex || []; 7 | if (Array.isArray(dataIndex) && dataIndex.length < 2) { 8 | this.errors.push(`data index invalid`); 9 | return; 10 | } 11 | const key = this.ctx.column.dataIndex[1]; 12 | const repository = this.field.database.getRepository(this.field.target) as Repository; 13 | const instance = await repository.findOne({ filter: { [key]: this.trim(value) } }); 14 | if (instance) { 15 | this.value = instance.get(this.field.targetKey || 'id'); 16 | } else { 17 | this.errors.push(`"${value}" does not exist`); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { generateAppDir } from '../util'; 4 | import build from './build'; 5 | import clean from './clean'; 6 | import createNginxConf from './create-nginx-conf'; 7 | import createPlugin from './create-plugin'; 8 | import dev from './dev'; 9 | import e2e from './e2e'; 10 | import global from './global'; 11 | import install from './install'; 12 | import postinstall from './postinstall'; 13 | import tar from './tar'; 14 | import ui from './ui'; 15 | import upgrade from './upgrade'; 16 | 17 | export default async (cli: Command) => { 18 | generateAppDir(); 19 | global(cli); 20 | createNginxConf(cli); 21 | build(cli); 22 | tar(cli); 23 | dev(cli); 24 | ui(cli); 25 | e2e(cli); 26 | clean(cli); 27 | upgrade(cli); 28 | install(cli); 29 | postinstall(cli); 30 | createPlugin(cli); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/inhertits/inherited-map.test.ts: -------------------------------------------------------------------------------- 1 | import InheritanceMap from '../../inherited-map'; 2 | 3 | describe('InheritedMap', () => { 4 | it('should setInherits', () => { 5 | const map = new InheritanceMap(); 6 | map.setInheritance('b', 'a'); 7 | 8 | const nodeA = map.getNode('a'); 9 | const nodeB = map.getNode('b'); 10 | 11 | expect(nodeA.children.has(nodeB)).toBe(true); 12 | expect(nodeB.parents.has(nodeA)).toBe(true); 13 | 14 | expect(map.isParentNode('a')).toBe(true); 15 | }); 16 | 17 | it('should get deep children', () => { 18 | const map = new InheritanceMap(); 19 | map.setInheritance('b', 'a'); 20 | map.setInheritance('c', 'b'); 21 | map.setInheritance('c1', 'b'); 22 | map.setInheritance('d', 'c'); 23 | 24 | const children = map.getChildren('a'); 25 | expect(children.size).toBe(4); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/components/src/input/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, mapProps, mapReadPretty, ReactFC } from '@tachybase/schema'; 3 | 4 | import { LoadingOutlined } from '@ant-design/icons'; 5 | import { Input as AntdInput } from 'antd'; 6 | import type { InputProps } from 'antd/es/input'; 7 | 8 | import { PreviewText } from '../preview-text'; 9 | 10 | const InternalInput: ReactFC = connect( 11 | AntdInput, 12 | mapProps((props, field) => { 13 | return { 14 | ...props, 15 | suffix: {field?.['loading'] || field?.['validating'] ? : props.suffix}, 16 | }; 17 | }), 18 | mapReadPretty(PreviewText.Input), 19 | ); 20 | const TextArea = connect(AntdInput.TextArea, mapReadPretty(PreviewText.Input)); 21 | 22 | export const Input = Object.assign(InternalInput, { 23 | TextArea, 24 | }); 25 | 26 | export default Input; 27 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/main-data-source.test.ts: -------------------------------------------------------------------------------- 1 | import { Application } from '@tego/core'; 2 | 3 | describe('MainDataSource', () => { 4 | let app: Application; 5 | 6 | beforeEach(async () => { 7 | app = new Application({ 8 | database: { 9 | dialect: 'sqlite', 10 | storage: ':memory:', 11 | logging: false, 12 | }, 13 | resourcer: { 14 | prefix: '/api', 15 | }, 16 | acl: false, 17 | dataWrapping: false, 18 | registerActions: false, 19 | }); 20 | }); 21 | 22 | afterEach(async () => { 23 | await app.destroy(); 24 | }); 25 | 26 | it('should create main data source when create application', async () => { 27 | const dataSourceManager = app.dataSourceManager; 28 | const mainDataSource = dataSourceManager.dataSources.get('main'); 29 | 30 | expect(mainDataSource).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/devkit/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | import { esbuildPluginFilePathExtensions } from 'esbuild-plugin-file-path-extensions'; 4 | import fg from 'fast-glob'; 5 | import { defineConfig } from 'tsup'; 6 | 7 | const entry = fg.globSync(['src/**'], { cwd: __dirname, absolute: true }); 8 | 9 | export default defineConfig({ 10 | entry, 11 | tsconfig: path.join(__dirname, 'tsconfig.json'), 12 | outDir: path.join(__dirname, 'lib'), 13 | splitting: false, 14 | silent: true, 15 | 16 | outExtension() { 17 | return { 18 | js: `.mjs`, 19 | }; 20 | }, 21 | esbuildPlugins: [ 22 | esbuildPluginFilePathExtensions({ 23 | esm: true, 24 | }), 25 | ], 26 | 27 | format: ['esm'], 28 | sourcemap: false, 29 | 30 | clean: true, 31 | bundle: true, 32 | loader: { 33 | '.d.ts': 'copy', 34 | }, 35 | skipNodeModulesBundle: true, 36 | }); 37 | -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/components", 3 | "version": "1.6.1", 4 | "license": "Apache-2.0", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "dependencies": { 8 | "@ant-design/cssinjs": "^1.24.0", 9 | "@ant-design/icons": "^6.0.2", 10 | "@dnd-kit/core": "^6.3.1", 11 | "@dnd-kit/sortable": "^8.0.0", 12 | "@dnd-kit/utilities": "^3.2.2", 13 | "@monaco-editor/react": "^4.7.0", 14 | "@tachybase/schema": "workspace:*", 15 | "antd": "5.22.5", 16 | "classnames": "^2.5.1", 17 | "dayjs": "1.11.13", 18 | "prop-types": "^15.8.1", 19 | "react-dom": "18.3.1", 20 | "react-modal": "^3.16.3", 21 | "react-sticky-box": "^1.0.2" 22 | }, 23 | "devDependencies": { 24 | "rc-cascader": "3.31.0", 25 | "rc-input-number": "9.5.0", 26 | "rc-picker": "4.11.3", 27 | "rc-tree-select": "5.27.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fields/set.test.ts: -------------------------------------------------------------------------------- 1 | import { mockDatabase } from '../'; 2 | import { Database } from '../../database'; 3 | 4 | describe('set field', () => { 5 | let db: Database; 6 | 7 | beforeEach(async () => { 8 | db = mockDatabase(); 9 | await db.clean({ drop: true }); 10 | }); 11 | 12 | afterEach(async () => { 13 | await db.close(); 14 | }); 15 | 16 | it('should set Set field', async () => { 17 | const A = db.collection({ 18 | name: 'a', 19 | fields: [ 20 | { 21 | type: 'set', 22 | name: 'set', 23 | }, 24 | ], 25 | }); 26 | 27 | await db.sync(); 28 | 29 | const a = await A.repository.create({}); 30 | 31 | a.set('set', ['a', 'b', 'c', 'a']); 32 | 33 | await a.save(); 34 | 35 | const setValue = a.get('set'); 36 | expect(setValue).toEqual(['a', 'b', 'c']); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/create-nginx-conf.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'node:fs'; 2 | import { resolve } from 'node:path'; 3 | import { URL } from 'node:url'; 4 | 5 | import { Command } from 'commander'; 6 | 7 | const __dirname = new URL('.', import.meta.url).pathname; 8 | 9 | export default (cli: Command) => { 10 | cli.command('create-nginx-conf').action(async (name, options) => { 11 | const file = resolve(__dirname, '../../tachybase.conf.tpl'); 12 | const data = readFileSync(file, 'utf-8'); 13 | const replaced = data 14 | .replace(/\{\{cwd\}\}/g, '/app/tachybase') 15 | .replace(/\{\{publicPath\}\}/g, process.env.APP_PUBLIC_PATH!) 16 | .replace(/\{\{apiPort\}\}/g, process.env.APP_PORT!); 17 | 18 | const targetFile = resolve(process.env.TEGO_RUNTIME_HOME!, 'storage', 'tachybase.conf'); 19 | writeFileSync(targetFile, replaced); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/Field.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | 3 | import { useField, useForm } from '../hooks'; 4 | import { FieldContext } from '../shared'; 5 | import { IFieldProps, JSXComponent } from '../types'; 6 | import { ReactiveField } from './ReactiveField'; 7 | 8 | export const Field = (props: IFieldProps) => { 9 | const form = useForm(); 10 | const parent = useField(); 11 | const field = form.createField({ basePath: parent?.address, ...props }); 12 | useEffect(() => { 13 | field?.onMount(); 14 | return () => { 15 | field?.onUnmount(); 16 | }; 17 | }, [field]); 18 | return ( 19 | 20 | {props.children} 21 | 22 | ); 23 | }; 24 | 25 | Field.displayName = 'Field'; 26 | -------------------------------------------------------------------------------- /packages/test/src/__tests__/__snapshots__/omitSomeFields.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`omitSomeFields > should omit key & collectionName 1`] = ` 4 | [ 5 | { 6 | "description": null, 7 | "fields": [ 8 | { 9 | "allowNull": false, 10 | "autoIncrement": true, 11 | "description": null, 12 | "interface": "id", 13 | "name": "id", 14 | "parentKey": null, 15 | "primaryKey": true, 16 | "reverseKey": null, 17 | "type": "bigInt", 18 | "uiSchema": { 19 | "title": "{{t("ID")}}", 20 | "type": "number", 21 | "x-component": "InputNumber", 22 | "x-read-pretty": true, 23 | }, 24 | }, 25 | ], 26 | "hidden": false, 27 | "inherit": false, 28 | "name": "t_0a1w7khj0y7", 29 | "title": "a", 30 | }, 31 | ] 32 | `; 33 | -------------------------------------------------------------------------------- /.cursor/rules/ai-assistant.md: -------------------------------------------------------------------------------- 1 | # AI Assistant Guide / AI 辅助开发指南 2 | 3 | ## Code Generation / 代码生成 4 | - When generating new components, follow the project's component structure and naming conventions. 5 | - 生成新组件时,遵循项目的组件结构和命名规范 6 | - Automatically add necessary type definitions and imports. 7 | - 自动添加必要的类型定义和导入 8 | - Consider internationalization and accessibility. 9 | - 考虑国际化和可访问性 10 | 11 | ## Code Refactoring / 代码重构 12 | - Maintain API compatibility when refactoring. 13 | - 重构时保持 API 兼容性 14 | - Update related tests and documentation. 15 | - 更新相关测试和文档 16 | - Check dependency relationships. 17 | - 检查依赖关系 18 | 19 | ## Troubleshooting / 问题排查 20 | - Check console errors and logs. 21 | - 查看控制台错误和日志 22 | - Check TypeScript type errors. 23 | - 检查 TypeScript 类型错误 24 | - Verify dependency version compatibility. 25 | - 验证依赖版本兼容性 26 | - Check GitHub Actions workflow status (if applicable). 27 | - 查看 GitHub Actions 工作流状态(如适用) 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | name: Test Coverage 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | test-coverage: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v3 13 | - name: Setup pnpm 14 | uses: pnpm/action-setup@v4 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | cache: 'pnpm' 20 | - name: Install 21 | run: pnpm install 22 | - name: Tests 23 | run: pnpm vitest --coverage.enabled true 24 | - name: 'Report Coverage' 25 | # Set if: always() to also generate the report if tests are failing 26 | # Only works if you set `reportOnFailure: true` in your vite config as specified above 27 | if: always() 28 | uses: davelosert/vitest-coverage-report-action@v2 29 | -------------------------------------------------------------------------------- /packages/actions/src/actions/add.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArrayFieldRepository, 3 | BelongsToManyRepository, 4 | HasManyRepository, 5 | MultipleRelationRepository, 6 | } from '@tachybase/database'; 7 | 8 | import { Context } from '..'; 9 | import { getRepositoryFromParams } from '../utils'; 10 | 11 | export async function add(ctx: Context, next) { 12 | const repository = getRepositoryFromParams(ctx); 13 | 14 | if ( 15 | !( 16 | repository instanceof MultipleRelationRepository || 17 | repository instanceof HasManyRepository || 18 | repository instanceof ArrayFieldRepository 19 | ) 20 | ) { 21 | return await next(); 22 | } 23 | const filterByTk = ctx.action.params.filterByTk || ctx.action.params.filterByTks || ctx.action.params.values; 24 | 25 | await (repository).add(filterByTk); 26 | 27 | ctx.status = 200; 28 | 29 | await next(); 30 | } 31 | -------------------------------------------------------------------------------- /packages/cache/src/__tests__/bloom-filter.test.ts: -------------------------------------------------------------------------------- 1 | import { BloomFilter } from '../bloom-filter'; 2 | import { CacheManager } from '../cache-manager'; 3 | 4 | describe('bloomFilter', () => { 5 | let bloomFilter: BloomFilter; 6 | let cacheManager: CacheManager; 7 | 8 | beforeEach(async () => { 9 | cacheManager = new CacheManager(); 10 | cacheManager.registerStore({ name: 'memory', store: 'memory' }); 11 | bloomFilter = await cacheManager.createBloomFilter({ store: 'memory' }); 12 | await bloomFilter.reserve('bloom-test', 0.01, 1000); 13 | }); 14 | 15 | afterEach(async () => { 16 | await cacheManager.flushAll(); 17 | }); 18 | 19 | it('should add and check', async () => { 20 | await bloomFilter.add('bloom-test', 'hello'); 21 | expect(await bloomFilter.exists('bloom-test', 'hello')).toBeTruthy(); 22 | expect(await bloomFilter.exists('bloom-test', 'world')).toBeFalsy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/core/src/gateway/types.ts: -------------------------------------------------------------------------------- 1 | import http, { IncomingMessage, ServerResponse } from 'node:http'; 2 | 3 | import { ApplicationOptions } from '../application'; 4 | 5 | export interface Handler { 6 | name: string; 7 | prefix: string; 8 | callback: (req: IncomingMessage, res: ServerResponse) => void; 9 | } 10 | export interface AppSelectorMiddlewareContext { 11 | req: IncomingRequest; 12 | resolvedAppName: string | null; 13 | } 14 | export interface RunOptions { 15 | mainAppOptions: ApplicationOptions; 16 | } 17 | export interface StartHttpServerOptions { 18 | port: number; 19 | host: string; 20 | callback?: (server: http.Server) => void; 21 | } 22 | export type AppSelectorMiddleware = (ctx: AppSelectorMiddlewareContext, next: () => Promise) => void; 23 | export type AppSelector = (req: IncomingRequest) => string | Promise; 24 | export interface IncomingRequest { 25 | url: string; 26 | headers: any; 27 | } 28 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/ObjectField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { ObjectField as ObjectFieldType } from '../../core'; 4 | import { useField, useForm } from '../hooks'; 5 | import { useAttach } from '../hooks/useAttach'; 6 | import { FieldContext } from '../shared'; 7 | import { IFieldProps, JSXComponent } from '../types'; 8 | import { ReactiveField } from './ReactiveField'; 9 | 10 | export const ObjectField = ( 11 | props: IFieldProps, 12 | ) => { 13 | const form = useForm(); 14 | const parent = useField(); 15 | const field = useAttach(form.createObjectField({ basePath: parent?.address, ...props })); 16 | return ( 17 | 18 | {props.children} 19 | 20 | ); 21 | }; 22 | 23 | ObjectField.displayName = 'ObjectField'; 24 | -------------------------------------------------------------------------------- /packages/data-source/src/default-actions/proxy-to-repository.ts: -------------------------------------------------------------------------------- 1 | import lodash from 'lodash'; 2 | 3 | import { DataSource } from '../data-source'; 4 | import { getRepositoryFromParams } from './utils'; 5 | 6 | export function proxyToRepository(paramKeys: string[] | ((ctx: any) => object), repositoryMethod: string) { 7 | return async function (ctx, next) { 8 | const repository = getRepositoryFromParams(ctx); 9 | const callObj = 10 | typeof paramKeys === 'function' ? paramKeys(ctx) : { ...lodash.pick(ctx.action.params, paramKeys), context: ctx }; 11 | const dataSource: DataSource = ctx.dataSource; 12 | 13 | if (!repository[repositoryMethod]) { 14 | throw new Error( 15 | `Repository can not handle action ${repositoryMethod} for ${ctx.action.resourceName} in ${dataSource.name}`, 16 | ); 17 | } 18 | 19 | ctx.body = await repository[repositoryMethod](callObj); 20 | await next(); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/database/src/fields/json-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field } from './field'; 4 | 5 | export class JsonField extends Field { 6 | get dataType() { 7 | const dialect = this.context.database.sequelize.getDialect(); 8 | const { jsonb } = this.options; 9 | if (dialect === 'postgres' && jsonb) { 10 | return DataTypes.JSONB; 11 | } 12 | return DataTypes.JSON; 13 | } 14 | } 15 | 16 | export interface JsonFieldOptions extends BaseColumnFieldOptions { 17 | type: 'json'; 18 | } 19 | 20 | export class JsonbField extends Field { 21 | get dataType() { 22 | const dialect = this.context.database.sequelize.getDialect(); 23 | if (dialect === 'postgres') { 24 | return DataTypes.JSONB; 25 | } 26 | return DataTypes.JSON; 27 | } 28 | } 29 | 30 | export interface JsonbFieldOptions extends BaseColumnFieldOptions { 31 | type: 'jsonb'; 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/migrations/20230912193824-package-name-unique.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from '@tachybase/database'; 2 | 3 | import { Migration } from '../migration'; 4 | 5 | export default class extends Migration { 6 | on = 'beforeLoad'; 7 | appVersion = '<0.14.0-alpha.2'; 8 | 9 | async up() { 10 | const tableNameWithSchema = this.pm.collection.getTableNameWithSchema(); 11 | const field = this.pm.collection.getField('packageName'); 12 | const exists = await field.existsInDb(); 13 | if (exists) { 14 | return; 15 | } 16 | try { 17 | await this.db.sequelize.getQueryInterface().addColumn(tableNameWithSchema, field.columnName(), { 18 | type: DataTypes.STRING, 19 | }); 20 | await this.db.sequelize.getQueryInterface().addConstraint(tableNameWithSchema, { 21 | type: 'unique', 22 | fields: [field.columnName()], 23 | }); 24 | } catch (error) { 25 | // 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/devkit/src/builder/build/tarPlugin.ts: -------------------------------------------------------------------------------- 1 | import { mkdir } from 'node:fs/promises'; 2 | import path, { dirname } from 'node:path'; 3 | 4 | import Arborist from '@npmcli/arborist'; 5 | import fs from 'fs-extra'; 6 | import packlist from 'npm-packlist'; 7 | import * as tar from 'tar'; 8 | 9 | import { TAR_OUTPUT_DIR } from './constant'; 10 | import { PkgLog } from './utils'; 11 | 12 | export async function tarPlugin(cwd: string, log: PkgLog) { 13 | log('tar package', cwd); 14 | const arborist = new Arborist({ path: cwd }); 15 | const node = await arborist.loadActual(); 16 | const files = await packlist(node); 17 | const pkg = fs.readJsonSync(path.join(cwd, 'package.json')); 18 | const tarball = path.join(TAR_OUTPUT_DIR, `${pkg.name}-${pkg.version}.tgz`); 19 | await mkdir(dirname(tarball), { recursive: true }); 20 | await tar.c( 21 | { 22 | gzip: true, 23 | file: tarball, 24 | cwd, 25 | }, 26 | files, 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/schema/src/validator/__tests__/registry.spec.ts: -------------------------------------------------------------------------------- 1 | import { getLocaleByPath, getValidateLocale, getValidateLocaleIOSCode, setValidateLanguage } from '..'; 2 | import locale from '../locale'; 3 | 4 | test('getValidateLocaleIOSCode', () => { 5 | expect(getValidateLocaleIOSCode('zh-CN')).toEqual('zh-CN'); 6 | expect(getValidateLocaleIOSCode('zh')).toEqual('zh'); 7 | expect(getValidateLocaleIOSCode('ZH')).toEqual('zh'); 8 | expect(getValidateLocaleIOSCode('cn')).toEqual('zh-CN'); 9 | expect(getValidateLocaleIOSCode('en')).toEqual('en'); 10 | expect(getValidateLocaleIOSCode('TW')).toEqual('zh-TW'); 11 | }); 12 | 13 | test('getLocaleByPath', () => { 14 | expect(getLocaleByPath('pattern', 'vi')).toEqual(locale.en.pattern); 15 | expect(getLocaleByPath('pattern')).toEqual(locale.en.pattern); 16 | }); 17 | 18 | test('getValidateLocale', () => { 19 | setValidateLanguage('vi'); 20 | expect(getValidateLocale('pattern')).toEqual(locale.en.pattern); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/core/src/environment.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '@tachybase/utils'; 2 | 3 | import _ from 'lodash'; 4 | 5 | export class Environment { 6 | private vars = {}; 7 | 8 | setVariable(key: string, value: string) { 9 | this.vars[key] = value; 10 | } 11 | 12 | removeVariable(key: string) { 13 | delete this.vars[key]; 14 | } 15 | 16 | getVariablesAndSecrets() { 17 | return this.vars; 18 | } 19 | 20 | getVariables() { 21 | return this.vars; 22 | } 23 | 24 | renderJsonTemplate(template: any, options?: { omit?: string[] }) { 25 | if (options?.omit) { 26 | const omitTemplate = _.omit(template, options.omit); 27 | const parsed = parse(omitTemplate)({ 28 | $env: this.vars, 29 | }); 30 | for (const key of options.omit) { 31 | _.set(parsed, key, _.get(template, key)); 32 | } 33 | return parsed; 34 | } 35 | return parse(template)({ 36 | $env: this.vars, 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/hooks/afterCreateWithAssociations.test.ts: -------------------------------------------------------------------------------- 1 | import { mockDatabase } from '../'; 2 | import { Database } from '../../database'; 3 | 4 | describe('afterCreateWithAssociations', () => { 5 | let db: Database; 6 | 7 | beforeEach(async () => { 8 | db = mockDatabase(); 9 | await db.clean({ drop: true }); 10 | }); 11 | 12 | afterEach(async () => { 13 | await db.close(); 14 | }); 15 | 16 | test('case 1', async () => { 17 | db.collection({ 18 | name: 'test', 19 | }); 20 | await db.sync(); 21 | const repo = db.getRepository('test'); 22 | db.on('test.afterCreateWithAssociations', async (model, { transaction }) => { 23 | throw new Error('test error'); 24 | }); 25 | try { 26 | await repo.create({ 27 | values: {}, 28 | }); 29 | } catch (error) { 30 | console.log(error); 31 | } 32 | const count = await repo.count(); 33 | expect(count).toBe(0); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/core/src/migrations/20230912294620-update-pkg.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '../migration'; 2 | import { PluginManager } from '../plugin-manager'; 3 | 4 | export default class extends Migration { 5 | on = 'afterSync'; // 'beforeLoad' or 'afterLoad' 6 | appVersion = '<0.14.0-alpha.2'; 7 | 8 | async up() { 9 | const plugins = await this.pm.repository.find(); 10 | for (const plugin of plugins) { 11 | const { name } = plugin; 12 | if (plugin.packageName) { 13 | continue; 14 | } 15 | try { 16 | const packageName = await PluginManager.getPackageName(name); 17 | await this.pm.repository.update({ 18 | filter: { 19 | name, 20 | }, 21 | values: { 22 | packageName, 23 | }, 24 | }); 25 | this.app.logger.info(`update ${packageName}`); 26 | } catch (error) { 27 | this.app.logger.warn(error.message); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/schema/src/react/components/ArrayField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { ArrayField as ArrayFieldType } from '../../core'; 4 | import { useField, useForm } from '../hooks'; 5 | import { useAttach } from '../hooks/useAttach'; 6 | import { FieldContext } from '../shared'; 7 | import { IFieldProps, JSXComponent } from '../types'; 8 | import { ReactiveField } from './ReactiveField'; 9 | 10 | export const ArrayField = ( 11 | props: IFieldProps, 12 | ) => { 13 | const form = useForm(); 14 | const parent = useField(); 15 | const field = useAttach( 16 | form.createArrayField({ 17 | basePath: parent?.address, 18 | ...props, 19 | }), 20 | ); 21 | return ( 22 | 23 | {props.children} 24 | 25 | ); 26 | }; 27 | 28 | ArrayField.displayName = 'ArrayField'; 29 | -------------------------------------------------------------------------------- /packages/auth/src/base/token-control-service.ts: -------------------------------------------------------------------------------- 1 | export interface TokenPolicyConfig { 2 | tokenExpirationTime: string; 3 | sessionExpirationTime: string; 4 | expiredTokenRenewLimit: string; 5 | } 6 | 7 | type millisecond = number; 8 | export type NumericTokenPolicyConfig = { 9 | [K in keyof TokenPolicyConfig]: millisecond; 10 | }; 11 | 12 | export type TokenInfo = { 13 | jti: string; 14 | userId: number; 15 | issuedTime: EpochTimeStamp; 16 | signInTime: EpochTimeStamp; 17 | renewed: boolean; 18 | }; 19 | 20 | export type JTIStatus = 'valid' | 'inactive' | 'blocked' | 'missing' | 'renewed' | 'expired'; 21 | export interface ITokenControlService { 22 | getConfig(): Promise; 23 | setConfig(config: TokenPolicyConfig): Promise; 24 | renew(jti: string): Promise<{ jti: string; issuedTime: EpochTimeStamp }>; 25 | add({ userId }: { userId: number }): Promise; 26 | removeSessionExpiredTokens(userId: number): Promise; 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/app-supervisor.test.ts: -------------------------------------------------------------------------------- 1 | import { AppSupervisor } from '../app-supervisor'; 2 | 3 | describe('App Supervisor', () => { 4 | let appSupervisor: AppSupervisor; 5 | 6 | beforeEach(() => { 7 | appSupervisor = AppSupervisor.getInstance(); 8 | }); 9 | 10 | afterEach(async () => { 11 | await appSupervisor.destroy(); 12 | }); 13 | 14 | it('should get application initializing status', async () => { 15 | expect(appSupervisor.getAppStatus('test')).toBe(undefined); 16 | 17 | appSupervisor.setAppBootstrapper(async () => { 18 | await new Promise((resolve) => setTimeout(resolve, 1000)); 19 | }); 20 | 21 | appSupervisor.getApp('test'); 22 | 23 | await new Promise((resolve) => setTimeout(resolve, 500)); 24 | expect(appSupervisor.getAppStatus('test')).toBe('initializing'); 25 | 26 | await new Promise((resolve) => setTimeout(resolve, 2000)); 27 | expect(appSupervisor.getAppStatus('test')).toBe('not_found'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/schema/src/reactive/checkers.ts: -------------------------------------------------------------------------------- 1 | const toString = Object.prototype.toString; 2 | export const isMap = (val: any): val is Map => val && val instanceof Map; 3 | export const isSet = (val: any): val is Set => val && val instanceof Set; 4 | export const isWeakMap = (val: any): val is WeakMap => val && val instanceof WeakMap; 5 | export const isWeakSet = (val: any): val is WeakSet => val && val instanceof WeakSet; 6 | export const isFn = (val: any): val is Function => typeof val === 'function'; 7 | export const isArr = Array.isArray; 8 | export const isPlainObj = (val: any): val is object => toString.call(val) === '[object Object]'; 9 | export const isValid = (val: any) => val !== null && val !== undefined; 10 | export const isCollectionType = (target: any) => { 11 | return isMap(target) || isWeakMap(target) || isSet(target) || isWeakSet(target); 12 | }; 13 | export const isNormalType = (target: any) => { 14 | return isPlainObj(target) || isArr(target); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/tego/src/__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { convertEnvToSettings } from '../utils'; 4 | 5 | describe('convertEnvToSettings', () => { 6 | it('should convert flat env into structured settings object', () => { 7 | const input = { 8 | LOGGER_TRANSPORT: 'console,dailyRotateFile', 9 | LOGGER_MAX_FILES: '7d', 10 | DB_STORAGE: 'storage/db/tachybase.sqlite', 11 | CACHE_DEFAULT_STORE: 'memory', 12 | INIT_APP_LANG: 'zh-CN', 13 | }; 14 | 15 | const result = convertEnvToSettings(input as any); 16 | 17 | expect(result.logger.transport).toEqual(['console', 'dailyRotateFile']); 18 | expect(result.logger.max_files).toBeUndefined(); // 未提供 19 | expect(result.logger.maxFiles).toBe('7d'); 20 | expect(result.database.storage).toBe('storage/db/tachybase.sqlite'); 21 | expect(result.cache.default_store).toBe('memory'); 22 | expect(result.env.INIT_APP_LANG).toBe('zh-CN'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | import './dayjs'; 2 | 3 | export * from './assign'; 4 | export * from './collections-graph'; 5 | export * from './common'; 6 | export * from './date'; 7 | export * from './forEach'; 8 | export * from './fs-exists'; 9 | export * from './json-templates'; 10 | export * from './koa-multer'; 11 | export * from './measure-execution-time'; 12 | export * from './merge'; 13 | export * from './mixin'; 14 | export * from './mixin/AsyncEmitter'; 15 | export * from './number'; 16 | export * from './parse-date'; 17 | export * from './parse-filter'; 18 | export * from './perf-hooks'; 19 | export * from './registry'; 20 | export * from './requireModule'; 21 | export * from './toposort'; 22 | export * from './uid'; 23 | export * from './url'; 24 | export * from './cluster'; 25 | export * from './plugin-symlink'; 26 | export * from './currencyUtils'; 27 | export * from './getCurrentStacks'; 28 | export * from './i18n'; 29 | export type { Constructable } from './types/constructable.type'; 30 | -------------------------------------------------------------------------------- /packages/database/src/value-parsers/base-value-parser.ts: -------------------------------------------------------------------------------- 1 | export class BaseValueParser { 2 | ctx: any; 3 | field: any; 4 | value: any; 5 | errors: string[] = []; 6 | 7 | constructor(field: any, ctx: any) { 8 | this.field = field; 9 | this.ctx = ctx; 10 | this.value = null; 11 | } 12 | 13 | trim(value: any) { 14 | return typeof value === 'string' ? value.trim() : value; 15 | } 16 | 17 | toArr(value: any, splitter?: string) { 18 | let values: string[] = []; 19 | if (!value) { 20 | values = []; 21 | } else if (typeof value === 'string') { 22 | values = value.split(splitter || /,|,|、/); 23 | } else if (Array.isArray(value)) { 24 | values = value; 25 | } 26 | return values.map((v) => this.trim(v)).filter(Boolean); 27 | } 28 | 29 | toString() { 30 | return this.value; 31 | } 32 | 33 | getValue() { 34 | return this.value; 35 | } 36 | 37 | async setValue(value: any) { 38 | this.value = value; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/di/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Container } from './container-instance.class'; 2 | 3 | export * from './decorators/inject-many.decorator'; 4 | export * from './decorators/inject.decorator'; 5 | export * from './decorators/service.decorator'; 6 | 7 | export * from './error/cannot-inject-value.error'; 8 | export * from './error/cannot-instantiate-value.error'; 9 | export * from './error/service-not-found.error'; 10 | 11 | export type { Handler } from './interfaces/handler.interface'; 12 | export type { ServiceMetadata } from './interfaces/service-metadata.interface'; 13 | export type { ServiceOptions } from './interfaces/service-options.interface'; 14 | export type { ServiceIdentifier } from './types/service-identifier.type'; 15 | 16 | export { ContainerInstance, Container } from './container-instance.class'; 17 | export { ContainerRegistry } from './container-registry.class'; 18 | export { Token } from './token.class'; 19 | 20 | export default Container; 21 | 22 | export * from './decorators'; 23 | -------------------------------------------------------------------------------- /packages/components/src/select-table/hooks/useSize.tsx: -------------------------------------------------------------------------------- 1 | import { SizeType } from 'antd/es/config-provider/SizeContext'; 2 | 3 | interface ISize { 4 | ( 5 | fieldSize: SizeType, 6 | searchSize: SizeType, 7 | tableSize: SizeType, 8 | ): { 9 | searchSize: SizeType; 10 | tableSize: SizeType; 11 | }; 12 | } 13 | 14 | const useSize: ISize = (fieldSize = 'middle', searchSize, tableSize) => { 15 | const fieldSizeMap: any = { 16 | small: { 17 | searchSize: 'small', 18 | tableSize: 'small', 19 | }, 20 | default: { 21 | searchSize: 'middle', 22 | tableSize: 'middle', 23 | }, 24 | large: { 25 | searchSize: 'large', 26 | tableSize: 'default', 27 | }, 28 | }; 29 | const { searchSize: fieldSearchSize, tableSize: fieldTableSize } = fieldSizeMap[fieldSize] || {}; 30 | 31 | return { 32 | searchSize: searchSize || fieldSearchSize, 33 | tableSize: tableSize || fieldTableSize, 34 | }; 35 | }; 36 | 37 | export { useSize }; 38 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/database.import.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | import Database from '../database'; 4 | import { mockDatabase } from './index'; 5 | 6 | describe('database', () => { 7 | let db: Database; 8 | 9 | beforeEach(async () => { 10 | db = mockDatabase(); 11 | await db.clean({ drop: true }); 12 | }); 13 | 14 | afterEach(async () => { 15 | await db.close(); 16 | }); 17 | 18 | test('import', async () => { 19 | await db.import({ 20 | directory: path.resolve(__dirname, './fixtures/c0'), 21 | }); 22 | await db.import({ 23 | directory: path.resolve(__dirname, './fixtures/c1'), 24 | }); 25 | await db.import({ 26 | directory: path.resolve(__dirname, './fixtures/c2'), 27 | }); 28 | 29 | const test = db.getCollection('tests'); 30 | 31 | expect(test.getField('n0')).toBeDefined(); 32 | expect(test.getField('n1')).toBeDefined(); 33 | expect(test.getField('n2')).toBeDefined(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/schema/src/core/__tests__/object.spec.ts: -------------------------------------------------------------------------------- 1 | import { createForm } from '..'; 2 | import { attach } from './shared'; 3 | 4 | test('create object field', () => { 5 | const form = attach(createForm()); 6 | const object = attach( 7 | form.createObjectField({ 8 | name: 'object', 9 | }), 10 | ); 11 | expect(object.value).toEqual({}); 12 | expect(object.addProperty).toBeDefined(); 13 | expect(object.removeProperty).toBeDefined(); 14 | expect(object.existProperty).toBeDefined(); 15 | }); 16 | 17 | test('create object field methods', () => { 18 | const form = attach(createForm()); 19 | const object = attach( 20 | form.createObjectField({ 21 | name: 'object', 22 | value: {}, 23 | }), 24 | ); 25 | expect(object.value).toEqual({}); 26 | object.addProperty('aaa', 123); 27 | expect(object.value).toEqual({ aaa: 123 }); 28 | object.removeProperty('aaa'); 29 | expect(object.value).toEqual({}); 30 | expect(object.existProperty('aaa')).toBeFalsy(); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/devkit/src/commands/build.ts: -------------------------------------------------------------------------------- 1 | import { type Command } from 'commander'; 2 | 3 | import { TachybaseBuilder } from '../builder'; 4 | 5 | export default (cli: Command) => { 6 | cli 7 | .command('build') 8 | .allowUnknownOption() 9 | .argument('[packages...]') 10 | .option('-r, --retry', 'retry the last failed package') 11 | .option('-s, --sourcemap', 'generate server sourcemap') 12 | .option('--no-dts', 'not generate dts') 13 | .option('--tar', 'tar the package') 14 | .option('--only-tar', 'only tar the package') 15 | .option('--development', 'development mode') 16 | .action(async (pkgs, options) => { 17 | const tachybaseBuilder = new TachybaseBuilder({ 18 | dts: options.dts, 19 | sourcemap: options.sourcemap, 20 | retry: options.retry, 21 | tar: options.tar, 22 | onlyTar: options.onlyTar, 23 | development: options.development, 24 | }); 25 | 26 | await tachybaseBuilder.build(pkgs); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/components/src/form-item/style/animation.tsx: -------------------------------------------------------------------------------- 1 | import { Keyframes } from '@ant-design/cssinjs'; 2 | 3 | import { GenerateStyle } from '../../__builtins__'; 4 | 5 | const antShowHelpIn = new Keyframes('antShowHelpIn', { 6 | '0%': { 7 | transform: 'translateY(-5px)', 8 | opacity: 0, 9 | }, 10 | to: { 11 | transform: ' translateY(0)', 12 | opacity: 1, 13 | }, 14 | }); 15 | 16 | export const getAnimationStyle: GenerateStyle = (token) => { 17 | const { componentCls } = token; 18 | const helpCls = `${componentCls}-help`; 19 | 20 | return { 21 | [helpCls]: { 22 | '&-appear, &-enter': { 23 | animationDuration: '0.3s', 24 | animationFillMode: 'both', 25 | animationPlayState: 'paused', 26 | opacity: 0, 27 | animationTimingFunction: 'cubic-bezier(0.645, 0.045, 0.355, 1)', 28 | 29 | '&-active': { 30 | animationPlayState: 'running', 31 | animationName: antShowHelpIn, 32 | }, 33 | }, 34 | }, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/database/src/fields/array-field.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | import { BaseColumnFieldOptions, Field } from './field'; 4 | 5 | export class ArrayField extends Field { 6 | get dataType() { 7 | if (this.database.sequelize.getDialect() === 'postgres') { 8 | return DataTypes.JSONB; 9 | } 10 | 11 | return DataTypes.JSON; 12 | } 13 | 14 | sortValue = (model) => { 15 | let oldValue = model.get(this.options.name); 16 | 17 | if (oldValue) { 18 | if (typeof oldValue === 'string') { 19 | oldValue = JSON.parse(oldValue); 20 | } 21 | const newValue = oldValue.sort(); 22 | model.set(this.options.name, newValue); 23 | } 24 | }; 25 | 26 | bind() { 27 | super.bind(); 28 | this.on('beforeSave', this.sortValue); 29 | } 30 | 31 | unbind() { 32 | super.unbind(); 33 | this.off('beforeSave', this.sortValue); 34 | } 35 | } 36 | 37 | export interface ArrayFieldOptions extends BaseColumnFieldOptions { 38 | type: 'array'; 39 | } 40 | -------------------------------------------------------------------------------- /packages/evaluators/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tachybase/evaluators", 3 | "version": "1.6.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "exports": { 7 | ".": { 8 | "require": { 9 | "types": "./lib/index.d.ts", 10 | "default": "./lib/index.js" 11 | }, 12 | "import": { 13 | "types": "./lib/index.d.ts", 14 | "default": "./lib/index.js" 15 | } 16 | }, 17 | "./client": { 18 | "require": { 19 | "types": "./lib/client/index.d.ts", 20 | "default": "./lib/client/index.js" 21 | }, 22 | "import": { 23 | "types": "./lib/client/index.d.ts", 24 | "default": "./lib/client/index.js" 25 | } 26 | } 27 | }, 28 | "main": "./lib/index.js", 29 | "types": "./lib/index.d.ts", 30 | "dependencies": { 31 | "@formulajs/formulajs": "4.5.3", 32 | "@tachybase/utils": "workspace:*", 33 | "lodash": "4.17.21" 34 | }, 35 | "devDependencies": { 36 | "@types/node": "20.17.10" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/database/src/value-parsers/boolean-value-parser.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueParser } from './base-value-parser'; 2 | 3 | export class BooleanValueParser extends BaseValueParser { 4 | async setValue(value: any) { 5 | // Boolean 6 | if (typeof value === 'boolean') { 7 | this.value = value; 8 | } 9 | // Number 10 | else if (typeof value === 'number' && [0, 1].includes(value)) { 11 | this.value = value === 1; 12 | } 13 | // String 14 | else if (typeof value === 'string') { 15 | if (!value) { 16 | this.value = null; 17 | } 18 | if (['1', 'y', 'yes', 'true', '是'].includes(value.toLowerCase())) { 19 | this.value = true; 20 | } else if (['0', 'n', 'no', 'false', '否'].includes(value.toLowerCase())) { 21 | this.value = false; 22 | } else { 23 | this.errors.push(`Invalid value - ${JSON.stringify(this.value)}`); 24 | } 25 | } else { 26 | this.errors.push(`Invalid value - ${JSON.stringify(this.value)}`); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/data-source/src/default-actions/utils.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '@tachybase/actions'; 2 | 3 | import { DataSource, IRepository } from '..'; 4 | 5 | export function pageArgsToLimitArgs( 6 | page: number, 7 | pageSize: number, 8 | ): { 9 | offset: number; 10 | limit: number; 11 | } { 12 | return { 13 | offset: (page - 1) * pageSize, 14 | limit: pageSize, 15 | }; 16 | } 17 | 18 | export function getRepositoryFromParams(ctx: Context): IRepository { 19 | const { resourceName, sourceId, actionName } = ctx.action; 20 | 21 | const dataSource: DataSource = ctx.dataSource; 22 | 23 | if (sourceId === '_' && ['get', 'list'].includes(actionName)) { 24 | const collection = dataSource.collectionManager.getCollection(resourceName); 25 | return dataSource.collectionManager.getRepository(collection.name); 26 | } 27 | 28 | if (sourceId) { 29 | return dataSource.collectionManager.getRepository(resourceName, sourceId); 30 | } 31 | 32 | return dataSource.collectionManager.getRepository(resourceName); 33 | } 34 | -------------------------------------------------------------------------------- /.cursorignore: -------------------------------------------------------------------------------- 1 | # Cursor AI 索引忽略文件 2 | # 这些文件和目录不会被 Cursor AI 索引,以提高性能和减少噪音 3 | 4 | # 依赖目录 5 | node_modules/ 6 | **/node_modules/ 7 | .pnpm-store/ 8 | 9 | # 构建产物 10 | dist/ 11 | lib/ 12 | es/ 13 | esm/ 14 | **/dist/ 15 | **/lib/ 16 | **/es/ 17 | **/esm/ 18 | **/.dumi/tmp 19 | **/.dumi/tmp-production 20 | **/.dumi/tmp-test 21 | **/.turbo 22 | **/.swc 23 | **/.umi 24 | **/.umi-production 25 | docs-dist/ 26 | ncc-cache/ 27 | 28 | # 测试和覆盖率 29 | coverage/ 30 | **/coverage/ 31 | playwright/ 32 | **/playwright/ 33 | *.test.ts.snap 34 | *.spec.ts.snap 35 | 36 | # 日志和临时文件 37 | *.log 38 | *.sqlite 39 | *.tbdump 40 | cache/ 41 | v8-compile-cache-** 42 | .DS_Store 43 | 44 | # 存储和上传文件 45 | storage/* 46 | uploads/ 47 | docker/**/storage 48 | 49 | # 文档构建产物 50 | docs-repo-temp/ 51 | 52 | # 插件临时文件 53 | plugins/* 54 | 55 | # Git 相关 56 | # 注意:COMMIT_EDITMSG 未被忽略,以支持 AI 补全功能 57 | .git/objects/** 58 | .git/refs/** 59 | .git/config 60 | .git/HEAD 61 | .git/index 62 | .git/hooks/** 63 | .git/logs/** 64 | .git/info/** 65 | 66 | # 其他 67 | .local 68 | docs-repo-temp/ 69 | 70 | -------------------------------------------------------------------------------- /packages/database/src/__tests__/fields/nanoid-field.test.ts: -------------------------------------------------------------------------------- 1 | import { mockDatabase } from '../'; 2 | import { Database } from '../../database'; 3 | 4 | describe('nanoid field', () => { 5 | let db: Database; 6 | 7 | beforeEach(async () => { 8 | db = mockDatabase(); 9 | await db.clean({ drop: true }); 10 | }); 11 | 12 | afterEach(async () => { 13 | await db.close(); 14 | }); 15 | 16 | it('should create nanoid field type', async () => { 17 | const Test = db.collection({ 18 | name: 'tests', 19 | autoGenId: false, 20 | fields: [ 21 | { 22 | type: 'nanoid', 23 | name: 'id', 24 | primaryKey: true, 25 | size: 21, 26 | customAlphabet: '1234567890abcdef', 27 | }, 28 | { 29 | type: 'nanoid', 30 | name: 'id2', 31 | }, 32 | ], 33 | }); 34 | await Test.sync(); 35 | const test = await Test.model.create(); 36 | expect(test.id).toHaveLength(21); 37 | expect(test.id2).toHaveLength(12); 38 | }); 39 | }); 40 | --------------------------------------------------------------------------------