├── .github ├── ISSUE_TEMPLATE │ └── .gitkeep └── workflows │ ├── greetings.yml │ └── dependency-review.yml ├── .eslintignore ├── lib ├── operations │ └── index.ts ├── index.ts ├── Engine.ts ├── ConditionOperators.ts ├── helpers │ ├── genId.ts │ └── MapStore.ts ├── Action.ts ├── Condition.ts └── Rule.ts ├── .commitlintrc.json ├── .husky ├── pre-commit └── commit-msg ├── .lintstagedrc ├── .browserslistrc ├── .editorconfig ├── tests └── happypath.spec.ts ├── .prettierrc.json ├── .npmrc ├── .gitignore ├── scripts ├── prepare.sh ├── .gitconfig └── preinstall.sh ├── .babelrc ├── jest.config.ts ├── readme.md ├── .vscode ├── extensions.json └── settings.json ├── tsconfig.json ├── .eslintrc ├── rollup.config.js ├── CHANGELOG.md ├── package.json ├── playground ├── index.esm.js ├── index.common.js ├── index.js └── index.html └── .gitattributes /.github/ISSUE_TEMPLATE/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | .vscode 3 | playground -------------------------------------------------------------------------------- /lib/operations/index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,ts,jsx,tsx}": [ 3 | "pnpm run lint", 4 | "git add" 5 | ] 6 | } -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import Engine from "./Engine"; 2 | 3 | export { Engine }; 4 | export default Engine; 5 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | defaults and supports es6-module 4 | maintained node versions -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | charset = utf-8 4 | 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit ${1} 5 | -------------------------------------------------------------------------------- /tests/happypath.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe } from "@jest/globals"; 2 | 3 | describe("Happy Path", () => { 4 | test("should be happy", () => { 5 | expect(true).toBe(true); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "auto", 3 | "printWidth": 100, 4 | "singleQuote": false, 5 | "trailingComma": "all", 6 | "semi": true, 7 | "bracketSpacing": true, 8 | "tabWidth": 2 9 | } 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # docs: https://docs.npmjs.com/cli/v8/configuring-npm/npmrc 2 | 3 | engine-strict=true 4 | 5 | # taobao registry 6 | registry=https://registry.npmmirror.com/ 7 | 8 | auto-install-peers = true 9 | hoist = false 10 | -------------------------------------------------------------------------------- /lib/Engine.ts: -------------------------------------------------------------------------------- 1 | import { ConditionOperatorEnum } from "./ConditionOperators"; 2 | 3 | import Rule from "./Rule"; 4 | 5 | export default class Engine { 6 | public rule = new Rule(this, { operator: ConditionOperatorEnum.NONE }); 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | 3 | *.log 4 | *.cache 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | coverage 12 | junit.xml 13 | 14 | dist 15 | 16 | node_modules 17 | 18 | .rollup.cache 19 | .tsbuildinfo 20 | -------------------------------------------------------------------------------- /scripts/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # install volta and make it available in the PATH for the next steps to use 3 | curl https://get.volta.sh | bash 4 | 5 | export VOLTA_HOME="$HOME/.volta" 6 | export PATH=$LOCAL/bin:$VOLTA_HOME/bin:$PATH 7 | 8 | # install node and yarn 9 | # volta install node 10 | # volta install yarn 11 | -------------------------------------------------------------------------------- /lib/ConditionOperators.ts: -------------------------------------------------------------------------------- 1 | export enum ConditionOperatorEnum { 2 | // Only one of the conditions has to be met 3 | ANY = "ANY", 4 | // All conditions have to be met 5 | ALL = "ALL", 6 | // None of the conditions can be met 7 | NONE = "NONE", 8 | } 9 | 10 | export type ConditionOperatorValues = keyof typeof ConditionOperatorEnum; 11 | -------------------------------------------------------------------------------- /scripts/.gitconfig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [alias] 4 | l=log 5 | rl=reflog 6 | m=checkout master 7 | p=push 8 | pl=pull 9 | pr="! git push --set-upstream origin $(git rev-parse --abbrev-ref HEAD);" 10 | al=add --all 11 | c=commit -m 12 | s=status 13 | 14 | co=checkout 15 | cb=checkout -b 16 | reb=rebase -i --autosquash 17 | 18 | # git c -> cz 19 | c=cz -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | } 9 | } 10 | ], 11 | "@babel/preset-typescript" 12 | ], 13 | "plugins": [ 14 | "@babel/plugin-transform-runtime" 15 | ] 16 | } -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | 3 | export default { 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | testMatch: ["**/tests/**/*.spec.ts"], 7 | testPathIgnorePatterns: ["/node_modules/"], 8 | coverageDirectory: "./coverage", 9 | coveragePathIgnorePatterns: ["/node_modules", "/tests", "/coverage"], 10 | reporters: ["default", "jest-junit"], 11 | }; 12 | -------------------------------------------------------------------------------- /scripts/preinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | setup_git_extend() { 4 | # ref: https://github.com/gitster/git/commit/9b25a0b52e09400719366f0a33d0d0da98bbf7b0 5 | git config --local include.path ../scripts/.gitconfig 6 | } 7 | 8 | cat < { 3 | toArray: () => Value[]; 4 | toJSON: () => Record; 5 | jsonToMap: (json: Record) => void; 6 | } 7 | export default class MapStore 8 | extends Map 9 | implements EnhanceMap 10 | { 11 | toArray(): T[] { 12 | return Array.from(this.values()); 13 | } 14 | 15 | toJSON(): Record { 16 | const result: Record = {} as Record; 17 | for (const [key, value] of this.entries()) { 18 | result[key] = value; 19 | } 20 | return result; 21 | } 22 | 23 | jsonToMap(json: Record): void { 24 | for (const [key, value] of Object.entries(json)) { 25 | this.set(key as Key, value as T); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: "Dependency Review" 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: "Checkout Repository" 18 | uses: actions/checkout@v3 19 | - name: "Dependency Review" 20 | uses: actions/dependency-review-action@v2 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | /* Projects */ 5 | "incremental": true, 6 | "tsBuildInfoFile": "./.tsbuildinfo", 7 | /* Language and Environment */ 8 | "target": "es2016", 9 | "lib": [ 10 | "ESNext", 11 | "DOM" 12 | ], 13 | "experimentalDecorators": true, 14 | /* Modules */ 15 | "module": "ESNext", 16 | "rootDir": "./", 17 | "moduleResolution": "Node", 18 | "baseUrl": "./", 19 | "resolveJsonModule": true, 20 | /* Emit */ 21 | // 使用rollup-plugin-dts替代typescript的d.ts生成 22 | // "declaration": false, 23 | // "declarationMap": false, 24 | // "sourceMap": false, 25 | // "outDir": "./dist", 26 | // "removeComments": true, 27 | "importHelpers": true, 28 | "esModuleInterop": true, 29 | "forceConsistentCasingInFileNames": true, 30 | /* Type Checking */ 31 | "strict": true, 32 | "skipLibCheck": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": true, 4 | "source.fixAll.stylelint": true 5 | }, 6 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue"], 7 | "stylelint.enable": true, 8 | "stylelint.packageManager": "pnpm", 9 | "stylelint.validate": ["css", "scss", "postcss"], 10 | // ref: https://en.wikipedia.org/wiki/Newline#History 11 | "files.eol": "\n", 12 | // Render vertical rulers after a certain number of monospace characters. 13 | // Use multiple values for multiple rulers. 14 | // No rulers are drawn if array is empty. 15 | "editor.rulers": [100], 16 | "workbench.colorCustomizations": { 17 | "editorRuler.foreground": "#9a5151" 18 | }, 19 | "editor.renderWhitespace": "selection", 20 | // ref: vscode tips 21 | // https://www.youtube.com/watch?v=EA_KnOZI-ck 22 | "editor.suggest.preview": true, 23 | "cSpell.words": [ 24 | "commitlint", 25 | "filesize", 26 | "pnpm", 27 | "preinstall", 28 | "stylelint", 29 | "stylesheet", 30 | "taobao" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | // "jsx": true, 13 | "impliedStrict": true 14 | }, 15 | "allowImportExportEverywhere": true 16 | }, 17 | "plugins": ["@typescript-eslint", "prettier"], 18 | "extends": [ 19 | "eslint:recommended", 20 | "plugin:@typescript-eslint/recommended", 21 | "plugin:prettier/recommended" 22 | ], 23 | "rules": { 24 | "@typescript-eslint/consistent-type-definitions": "off", 25 | "@typescript-eslint/no-unused-vars": "error", 26 | "@typescript-eslint/no-explicit-any": [ 27 | "warn", 28 | { 29 | // https://typescript-eslint.io/rules/no-explicit-any/#ignorerestargs 30 | "ignoreRestArgs": true, 31 | "fixToUnknown": false 32 | } 33 | ], 34 | "@typescript-eslint/no-non-null-assertion": "off", 35 | "prettier/prettier": [ 36 | "error", 37 | { 38 | "endOfLine": "auto" 39 | } 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "rollup-plugin-commonjs"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | import nodeResolve, { DEFAULTS } from "@rollup/plugin-node-resolve"; 4 | import sourcemaps from "rollup-plugin-sourcemaps"; 5 | import { terser } from "rollup-plugin-terser"; 6 | import babel from "@rollup/plugin-babel"; 7 | import json from "@rollup/plugin-json"; 8 | import filesize from "rollup-plugin-filesize"; 9 | import dts from "rollup-plugin-dts"; 10 | import pkg from "./package.json" assert { type: "json" }; 11 | 12 | // eslint-disable-next-line no-undef 13 | const BUILD_TARGET_ENV = process.env.TARGET; 14 | const isDev = BUILD_TARGET_ENV === "dev"; 15 | const buildOutputDir = isDev ? "./playground/" : "./dist/"; 16 | 17 | /** 18 | * sourceMap is true by default, but we want to disable it for production builds 19 | */ 20 | const sourcemap = !isDev; 21 | 22 | /** 23 | * We want to use the same extensions as the node-resolve plugin 24 | */ 25 | const extensions = [...DEFAULTS.extensions, ".ts", ".tsx"]; 26 | 27 | const bundleConfig = { 28 | input: "lib/index.ts", 29 | output: [ 30 | { 31 | sourcemap: sourcemap, 32 | file: buildOutputDir + pkg.exports.default, 33 | format: "umd", 34 | name: "RuleEngine", 35 | exports: "named", 36 | }, 37 | { sourcemap: sourcemap, format: "esm", file: buildOutputDir + pkg.exports.import }, 38 | { 39 | sourcemap: sourcemap, 40 | format: "cjs", 41 | file: buildOutputDir + pkg.exports.require, 42 | exports: "named", 43 | }, 44 | ], 45 | plugins: [ 46 | commonjs(), 47 | typescript(), 48 | nodeResolve({ extensions, moduleDirectories: ["node_modules"] }), 49 | json(), 50 | sourcemaps(), 51 | filesize(), 52 | terser(), 53 | babel({ exclude: "node_modules/**", extensions, babelHelpers: "runtime" }), 54 | ], 55 | external: [...Object.keys(pkg.peerDependencies || {}), /@babel\/runtime/], 56 | watch: { 57 | include: "lib/**", 58 | }, 59 | }; 60 | 61 | const dtsConfig = { 62 | input: "lib/index.ts", 63 | output: [{ file: "./dist/index.d.ts", format: "es" }], 64 | plugins: [dts()], 65 | }; 66 | 67 | const configs = [bundleConfig]; 68 | if (!isDev) { 69 | configs.push(dtsConfig); 70 | } 71 | export default configs; 72 | -------------------------------------------------------------------------------- /lib/Action.ts: -------------------------------------------------------------------------------- 1 | import Engine from "./Engine"; 2 | import { genId } from "./helpers/genId"; 3 | import Rule from "./Rule"; 4 | 5 | export interface ActionDescriptor { 6 | field?: string; 7 | operator?: string; 8 | payload?: unknown; 9 | dependencies?: string[]; 10 | reverseDependencies?: string[]; 11 | context?: Record; 12 | } 13 | 14 | export default class Action { 15 | public id = ""; 16 | public field = ""; 17 | public operator = ""; 18 | public payload: unknown = ""; 19 | public context: Record = {}; 20 | public dependencies: string[] = []; 21 | public reverseDependencies: string[] = []; 22 | 23 | public actionMaps = new Map(); 24 | public children: Action[] = []; 25 | 26 | constructor( 27 | private readonly engine: Engine, 28 | private readonly rule: Rule, 29 | descriptor: ActionDescriptor = {}, 30 | ) { 31 | this.init(descriptor); 32 | } 33 | 34 | private init(descriptor: ActionDescriptor) { 35 | this.id = genId(); 36 | 37 | this.field = descriptor.field || ""; 38 | this.operator = descriptor.operator || ""; 39 | this.payload = descriptor.payload || ""; 40 | this.context = descriptor.context || {}; 41 | this.dependencies = descriptor.dependencies || []; 42 | this.reverseDependencies = descriptor.reverseDependencies || []; 43 | } 44 | 45 | public addActions(actionDescriptor: ActionDescriptor[]) { 46 | return actionDescriptor.map((action) => this.addAction(action)); 47 | } 48 | public addAction(actionDescriptor: ActionDescriptor) { 49 | const action = new Action(this.engine, this.rule, actionDescriptor); 50 | 51 | this.actionMaps.set(action.id, action); 52 | this.children.push(action); 53 | 54 | return action; 55 | } 56 | 57 | public removeAction(actionId: string) { 58 | this.actionMaps.delete(actionId); 59 | this.children = this.children.filter((action) => action.id !== actionId); 60 | } 61 | 62 | public updateAction(actionId: string, actionDescriptor: ActionDescriptor) { 63 | const action = this.actionMaps.get(actionId); 64 | if (action) { 65 | Object.assign(action, actionDescriptor); 66 | } 67 | } 68 | 69 | public fromJSON(json: ActionDescriptor | ActionDescriptor[]) { 70 | this.addActions(Array.isArray(json) ? json : [json]); 71 | } 72 | 73 | public getMeta() { 74 | return { 75 | id: this.id, 76 | field: this.field, 77 | operator: this.operator, 78 | payload: this.payload, 79 | context: this.context, 80 | dependencies: this.dependencies, 81 | reverseDependencies: this.reverseDependencies, 82 | }; 83 | } 84 | 85 | public getMetaWithActions() { 86 | return { 87 | action: this.getMeta(), 88 | actions: this.children.map((action) => action.getMeta()), 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## 1.4.0 (2023-07-06) 6 | 7 | 8 | ### Features 9 | 10 | * action features ([b1b8dbb](https://github.com/AldrichSavin/js-rule-2/commit/b1b8dbbdaefcc6c80be8835fb1cb2ff164562f10)) 11 | * added playground ([165fcec](https://github.com/AldrichSavin/js-rule-2/commit/165fcec0d2a070b0e192755fc72fcb1360661ead)) 12 | * basic playground example ([9c1b394](https://github.com/AldrichSavin/js-rule-2/commit/9c1b39455c09aee0226efa442df02549bb5bcccf)) 13 | * init code ([9203cc0](https://github.com/AldrichSavin/js-rule-2/commit/9203cc02ede9989072b6f209ce294ff11bfadb66)) 14 | * new genid ([22b9905](https://github.com/AldrichSavin/js-rule-2/commit/22b99051534c6787f5b2ccde27fef39909ee1cd5)) 15 | * playground example ([0aee310](https://github.com/AldrichSavin/js-rule-2/commit/0aee31020a64318a333bfbe2ddaf01d0946f2c43)) 16 | * remove fromJSON impl ([53f1f6b](https://github.com/AldrichSavin/js-rule-2/commit/53f1f6b55ece8218bf8602e938795fa10dd667c3)) 17 | * rule engine infrastructure ([d2084e6](https://github.com/AldrichSavin/js-rule-2/commit/d2084e6c490003bcd3902c6ae8519e8ad0eed99a)) 18 | * rule instance ([508410a](https://github.com/AldrichSavin/js-rule-2/commit/508410a811e3f073a63585f703b73c802618130c)) 19 | * 配置rollup build ([1f51b8b](https://github.com/AldrichSavin/js-rule-2/commit/1f51b8bd625754b4d871fb446ddb20c411d54820)) 20 | 21 | ### [1.3.1](https://github.com/branlice/js-rule-2/compare/release-20221028-v1.3.0...release-20221028-v1.3.1) (2022-10-28) 22 | 23 | ## [1.3.0](https://github.com/branlice/js-rule-2/compare/release-20221028-v1.2.0...release-20221028-v1.3.0) (2022-10-28) 24 | 25 | 26 | ### Features 27 | 28 | * rule engine infrastructure ([d2084e6](https://github.com/branlice/js-rule-2/commit/d2084e6c490003bcd3902c6ae8519e8ad0eed99a)) 29 | 30 | ## 1.2.0 (2022-10-28) 31 | 32 | 33 | ### Features 34 | 35 | * added playground ([165fcec](https://github.com/branlice/js-rule-2/commit/165fcec0d2a070b0e192755fc72fcb1360661ead)) 36 | * init code ([9203cc0](https://github.com/branlice/js-rule-2/commit/9203cc02ede9989072b6f209ce294ff11bfadb66)) 37 | * remove fromJSON impl ([53f1f6b](https://github.com/branlice/js-rule-2/commit/53f1f6b55ece8218bf8602e938795fa10dd667c3)) 38 | * rule instance ([508410a](https://github.com/branlice/js-rule-2/commit/508410a811e3f073a63585f703b73c802618130c)) 39 | * 配置rollup build ([1f51b8b](https://github.com/branlice/js-rule-2/commit/1f51b8bd625754b4d871fb446ddb20c411d54820)) 40 | 41 | ## 1.1.0 (2022-10-27) 42 | 43 | 44 | ### Features 45 | 46 | * rule instance ([508410a](https://github.com/branlice/js-rule-2/commit/508410a811e3f073a63585f703b73c802618130c)) 47 | * 配置rollup build ([7e14a96](https://github.com/branlice/js-rule-2/commit/7e14a96198dd7c04431cda53fa268030c02f5ec9)) 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-rule-2", 3 | "version": "1.4.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "pnpm clean:dev && rollup -c --environment TARGET:dev && pnpm playground", 8 | "build": "pnpm clean && pnpm clean:cache && rollup -c", 9 | "watch": "pnpm clean && rollup -c --watch --environment TARGET:dev", 10 | "build:release": "pnpm build && pnpm jest:coverage && pnpm changelog", 11 | "jest": "jest", 12 | "jest:watch": "jest --watch", 13 | "jest:coverage": "jest --coverage", 14 | "clean": "rimraf dist", 15 | "clean:dev": "rimraf ./playground/*.js ./playground/*.map", 16 | "clean:cache": "rimraf .rollup.cache", 17 | "playground": "live-server --port=9999 --watch=playground,lib,playground --verbose --entry-file=playground/index.html", 18 | "prepare": "husky install", 19 | "preinstall": "only-allow pnpm && sh scripts/preinstall.sh", 20 | "lint": "eslint --ext .js,.ts,.tsx lib", 21 | "lint:fix": "pnpm lint --fix", 22 | "format": "prettier lib --write --list-different", 23 | "lint-staged": "lint-staged", 24 | "changelog": "standard-version -t $(date +release-%Y%m%d-v)", 25 | "changelog:first": "standard-version --first-release -t $(date +release-%Y%m%d-v)", 26 | "install:volta": "sh ./scripts/prepare.sh" 27 | }, 28 | "main": "dist/index.js", 29 | "types": "dist/index.d.ts", 30 | "exports": { 31 | "import": "index.esm.js", 32 | "require": "index.common.js", 33 | "default": "index.js" 34 | }, 35 | "keywords": [ 36 | "js-rule" 37 | ], 38 | "author": "branlice", 39 | "license": "ISC", 40 | "volta": { 41 | "node": "16.18.0", 42 | "yarn": "3.2.4" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "7.19.6", 46 | "@babel/plugin-transform-runtime": "7.19.6", 47 | "@babel/preset-env": "7.19.4", 48 | "@babel/preset-typescript": "7.18.6", 49 | "@commitlint/cli": "17.1.2", 50 | "@commitlint/config-conventional": "17.1.0", 51 | "@jest/globals": "29.2.2", 52 | "@rollup/plugin-babel": "6.0.2", 53 | "@rollup/plugin-json": "5.0.1", 54 | "@rollup/plugin-node-resolve": "15.0.1", 55 | "@rollup/plugin-typescript": "9.0.2", 56 | "@types/jest": "29.2.0", 57 | "@typescript-eslint/eslint-plugin": "5.41.0", 58 | "@typescript-eslint/parser": "5.41.0", 59 | "babel-jest": "29.2.2", 60 | "eslint": "8.26.0", 61 | "eslint-config-prettier": "8.5.0", 62 | "eslint-plugin-prettier": "4.2.1", 63 | "husky": "8.0.1", 64 | "jest": "29.2.2", 65 | "jest-junit": "14.0.1", 66 | "lint-staged": "13.0.3", 67 | "live-server": "1.2.2", 68 | "only-allow": "1.1.1", 69 | "prettier": "2.7.1", 70 | "rimraf": "3.0.2", 71 | "rollup": "3.2.3", 72 | "rollup-plugin-commonjs": "10.1.0", 73 | "rollup-plugin-dts": "5.0.0", 74 | "rollup-plugin-filesize": "9.1.2", 75 | "rollup-plugin-node-resolve": "5.2.0", 76 | "rollup-plugin-sourcemaps": "0.6.3", 77 | "rollup-plugin-terser": "7.0.2", 78 | "standard-version": "9.5.0", 79 | "ts-jest": "29.0.3", 80 | "ts-node": "10.9.1", 81 | "typescript": "4.8.4" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/Condition.ts: -------------------------------------------------------------------------------- 1 | import Engine from "./Engine"; 2 | import { genId } from "./helpers/genId"; 3 | import Rule from "./Rule"; 4 | 5 | export interface ConditionDescriptor { 6 | field?: string; 7 | operator?: string; 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | value?: any; 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | context?: Record; 12 | } 13 | 14 | export default class Condition { 15 | public id = ""; 16 | 17 | public field = ""; 18 | public operator = ""; 19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 20 | public value: any = ""; 21 | 22 | /** 23 | * 规则的field关联的Condition数量映射 24 | */ 25 | public fields = new Map(); 26 | 27 | public conditionsMap = new Map(); 28 | public children: Condition[] = []; 29 | public context: Record = {}; 30 | 31 | constructor( 32 | private readonly engine: Engine, 33 | private readonly rule: Rule, 34 | descriptor: ConditionDescriptor = {}, 35 | ) { 36 | this.init(descriptor); 37 | } 38 | 39 | private init(descriptor: ConditionDescriptor) { 40 | this.id = genId(); 41 | 42 | this.field = descriptor.field || ""; 43 | this.operator = descriptor.operator || ""; 44 | this.value = descriptor.value || ""; 45 | this.context = descriptor.context || {}; 46 | } 47 | 48 | public addCondition(conditionDescriptor: ConditionDescriptor = {}) { 49 | const condition = new Condition(this.engine, this.rule, conditionDescriptor); 50 | 51 | this.conditionsMap.set(condition.id, condition); 52 | this.children.push(condition); 53 | 54 | this.fields.set(condition.field, (this.fields.get(condition.field) || 0) + 1); 55 | 56 | return condition; 57 | } 58 | 59 | public removeCondition(conditionId: string) { 60 | this.conditionsMap.delete(conditionId); 61 | 62 | this.children = this.children.filter((condition) => condition.id !== conditionId); 63 | 64 | this.fields.set(conditionId, (this.fields.get(conditionId) || 0) - 1); 65 | 66 | if (this.fields.get(conditionId) === 0) { 67 | this.fields.delete(conditionId); 68 | } 69 | } 70 | 71 | public getConditions() { 72 | return this.children; 73 | } 74 | 75 | public getCondition(conditionId: string) { 76 | return this.conditionsMap.get(conditionId); 77 | } 78 | 79 | public getConditionsByField(field: string) { 80 | return this.children.filter((condition) => condition.field === field); 81 | } 82 | 83 | public getConditionsByOperator(operator: string) { 84 | return this.children.filter((condition) => condition.operator === operator); 85 | } 86 | 87 | public getMetaWithConditions() { 88 | return { 89 | ...this.getMeta(), 90 | conditions: this.children.map((condition) => condition.getMeta()), 91 | }; 92 | } 93 | 94 | public fieldExists(field: string) { 95 | return this.fields.has(field); 96 | } 97 | 98 | public getFieldsObject() { 99 | return Object.fromEntries(this.fields); 100 | } 101 | 102 | public getMeta() { 103 | return { 104 | id: this.id, 105 | field: this.field, 106 | operator: this.operator, 107 | value: this.value, 108 | context: this.context, 109 | }; 110 | } 111 | 112 | public fromJSON(conditionDescriptor: ConditionDescriptor = {}) { 113 | this.addCondition(conditionDescriptor); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/Rule.ts: -------------------------------------------------------------------------------- 1 | import Condition from "./Condition"; 2 | import Action from "./Action"; 3 | import Engine from "./Engine"; 4 | import { ConditionOperatorEnum, ConditionOperatorValues } from "./ConditionOperators"; 5 | import { genId } from "./helpers/genId"; 6 | 7 | export interface RuleOptions { 8 | operator?: ConditionOperatorValues | ConditionOperatorEnum; 9 | } 10 | 11 | const flattedRules = new Map(); 12 | 13 | export default class Rule { 14 | public condition: Condition | null = null; 15 | public action: Action | null = null; 16 | /** 17 | * top rule does not exist id 18 | */ 19 | public id = ""; 20 | /** 21 | * all any none 22 | */ 23 | public children: Rule[] = []; 24 | public rulesMap = new Map(); 25 | public conditionOperator: ConditionOperatorEnum | ConditionOperatorValues = 26 | ConditionOperatorEnum.NONE; 27 | 28 | constructor(private readonly engine: Engine, private readonly options: RuleOptions = {}) { 29 | this.prepare(); 30 | this.init(); 31 | } 32 | 33 | private prepare() { 34 | this.condition = new Condition(this.engine, this); 35 | this.action = new Action(this.engine, this); 36 | } 37 | 38 | private init() { 39 | this.conditionOperator = this.options.operator || ConditionOperatorEnum.NONE; 40 | this.id = genId(); 41 | } 42 | 43 | public addNewRule( 44 | options: RuleOptions = { 45 | operator: ConditionOperatorEnum.ANY, 46 | }, 47 | ) { 48 | const rule = new Rule(this.engine, options); 49 | 50 | /** 51 | * 为了构造树形结构 52 | */ 53 | this.children.push(rule); 54 | 55 | /** 56 | * 为了方便查找 rule 57 | */ 58 | this.rulesMap.set(rule.id, rule); 59 | flattedRules.set(rule.id!, rule); 60 | 61 | return rule; 62 | } 63 | 64 | public removeRule(ruleOrRuleId: Rule | Rule["id"]) { 65 | const ruleId = typeof ruleOrRuleId === "string" ? ruleOrRuleId : ruleOrRuleId.id; 66 | this.children = this.children.filter((rule) => rule.id !== ruleId); 67 | this.rulesMap.delete(ruleId); 68 | flattedRules.delete(ruleId!); 69 | } 70 | 71 | public getRule(ruleId: Rule["id"]) { 72 | return flattedRules.get(ruleId!); 73 | } 74 | 75 | public getAllRules() { 76 | return flattedRules; 77 | } 78 | 79 | public getAllRulesArray() { 80 | return Array.from(flattedRules.values()); 81 | } 82 | 83 | public getAllRulesWithMeta() { 84 | return this.getAllRulesArray().map((rule) => ({ 85 | rule: rule.getMeta(), 86 | rules: rule.children.map((child) => child.getMeta()), 87 | })); 88 | } 89 | 90 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 91 | // @ts-ignore 92 | public getRulesWithMeta() { 93 | return this.children.map((child) => child.getMeta()); 94 | } 95 | 96 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 97 | // @ts-ignore 98 | public getMeta() { 99 | return { 100 | id: this.id, 101 | operator: this.conditionOperator, 102 | condition: this.condition?.getMetaWithConditions().conditions, 103 | conditionFields: this.condition?.getFieldsObject(), 104 | action: this.action?.getMetaWithActions().actions, 105 | children: 106 | Array.isArray(this.children) && this.children.length > 0 107 | ? this.getRulesWithMeta.bind(this)() 108 | : [], 109 | }; 110 | } 111 | 112 | /** 113 | * TODO 114 | */ 115 | public fromJSON() { 116 | this.addNewRule({ operator: ConditionOperatorEnum.ANY }); 117 | this.condition?.fromJSON({}); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /playground/index.esm.js: -------------------------------------------------------------------------------- 1 | let t;function e(){return Math.random().toString(36).slice(2)}!function(t){t.ANY="ANY",t.ALL="ALL",t.NONE="NONE"}(t||(t={}));class i{id="";field="";operator="";value="";fields=new Map;conditionsMap=new Map;children=[];context={};constructor(t,e,i={}){this.engine=t,this.rule=e,this.init(i)}init(t){this.id=e(),this.field=t.field||"",this.operator=t.operator||"",this.value=t.value||"",this.context=t.context||{}}addCondition(t={}){const e=new i(this.engine,this.rule,t);return this.conditionsMap.set(e.id,e),this.children.push(e),this.fields.set(e.field,(this.fields.get(e.field)||0)+1),e}removeCondition(t){this.conditionsMap.delete(t),this.children=this.children.filter((e=>e.id!==t)),this.fields.set(t,(this.fields.get(t)||0)-1),0===this.fields.get(t)&&this.fields.delete(t)}getConditions(){return this.children}getCondition(t){return this.conditionsMap.get(t)}getConditionsByField(t){return this.children.filter((e=>e.field===t))}getConditionsByOperator(t){return this.children.filter((e=>e.operator===t))}getMetaWithConditions(){return{...this.getMeta(),conditions:this.children.map((t=>t.getMeta()))}}fieldExists(t){return this.fields.has(t)}getFieldsObject(){return Object.fromEntries(this.fields)}getMeta(){return{id:this.id,field:this.field,operator:this.operator,value:this.value,context:this.context}}fromJSON(t={}){this.addCondition(t)}}class n{id="";field="";operator="";payload="";context={};dependencies=[];reverseDependencies=[];actionMaps=new Map;children=[];constructor(t,e,i={}){this.engine=t,this.rule=e,this.init(i)}init(t){this.id=e(),this.field=t.field||"",this.operator=t.operator||"",this.payload=t.payload||"",this.context=t.context||{},this.dependencies=t.dependencies||[],this.reverseDependencies=t.reverseDependencies||[]}addActions(t){return t.map((t=>this.addAction(t)))}addAction(t){const e=new n(this.engine,this.rule,t);return this.actionMaps.set(e.id,e),this.children.push(e),e}removeAction(t){this.actionMaps.delete(t),this.children=this.children.filter((e=>e.id!==t))}updateAction(t,e){const i=this.actionMaps.get(t);i&&Object.assign(i,e)}fromJSON(t){this.addActions(Array.isArray(t)?t:[t])}getMeta(){return{id:this.id,field:this.field,operator:this.operator,payload:this.payload,context:this.context,dependencies:this.dependencies,reverseDependencies:this.reverseDependencies}}getMetaWithActions(){return{action:this.getMeta(),actions:this.children.map((t=>t.getMeta()))}}}const s=new Map;class r{condition=null;action=null;id="";children=[];rulesMap=new Map;conditionOperator=t.NONE;constructor(t,e={}){this.engine=t,this.options=e,this.prepare(),this.init()}prepare(){this.condition=new i(this.engine,this),this.action=new n(this.engine,this)}init(){this.conditionOperator=this.options.operator||t.NONE,this.id=e()}addNewRule(e={operator:t.ANY}){const i=new r(this.engine,e);return this.children.push(i),this.rulesMap.set(i.id,i),s.set(i.id,i),i}removeRule(t){const e="string"==typeof t?t:t.id;this.children=this.children.filter((t=>t.id!==e)),this.rulesMap.delete(e),s.delete(e)}getRule(t){return s.get(t)}getAllRules(){return s}getAllRulesArray(){return Array.from(s.values())}getAllRulesWithMeta(){return this.getAllRulesArray().map((t=>({rule:t.getMeta(),rules:t.children.map((t=>t.getMeta()))})))}getRulesWithMeta(){return this.children.map((t=>t.getMeta()))}getMeta(){return{id:this.id,operator:this.conditionOperator,condition:this.condition?.getMetaWithConditions().conditions,conditionFields:this.condition?.getFieldsObject(),action:this.action?.getMetaWithActions().actions,children:Array.isArray(this.children)&&this.children.length>0?this.getRulesWithMeta.bind(this)():[]}}fromJSON(){this.addNewRule({operator:t.ANY}),this.condition?.fromJSON({})}}class o{rule=new r(this,{operator:t.NONE})}export{o as Engine,o as default}; 2 | -------------------------------------------------------------------------------- /playground/index.common.js: -------------------------------------------------------------------------------- 1 | "use strict";let e;function t(){return Math.random().toString(36).slice(2)}Object.defineProperty(exports,"__esModule",{value:!0}),function(e){e.ANY="ANY",e.ALL="ALL",e.NONE="NONE"}(e||(e={}));class i{id="";field="";operator="";value="";fields=new Map;conditionsMap=new Map;children=[];context={};constructor(e,t,i={}){this.engine=e,this.rule=t,this.init(i)}init(e){this.id=t(),this.field=e.field||"",this.operator=e.operator||"",this.value=e.value||"",this.context=e.context||{}}addCondition(e={}){const t=new i(this.engine,this.rule,e);return this.conditionsMap.set(t.id,t),this.children.push(t),this.fields.set(t.field,(this.fields.get(t.field)||0)+1),t}removeCondition(e){this.conditionsMap.delete(e),this.children=this.children.filter((t=>t.id!==e)),this.fields.set(e,(this.fields.get(e)||0)-1),0===this.fields.get(e)&&this.fields.delete(e)}getConditions(){return this.children}getCondition(e){return this.conditionsMap.get(e)}getConditionsByField(e){return this.children.filter((t=>t.field===e))}getConditionsByOperator(e){return this.children.filter((t=>t.operator===e))}getMetaWithConditions(){return{...this.getMeta(),conditions:this.children.map((e=>e.getMeta()))}}fieldExists(e){return this.fields.has(e)}getFieldsObject(){return Object.fromEntries(this.fields)}getMeta(){return{id:this.id,field:this.field,operator:this.operator,value:this.value,context:this.context}}fromJSON(e={}){this.addCondition(e)}}class n{id="";field="";operator="";payload="";context={};dependencies=[];reverseDependencies=[];actionMaps=new Map;children=[];constructor(e,t,i={}){this.engine=e,this.rule=t,this.init(i)}init(e){this.id=t(),this.field=e.field||"",this.operator=e.operator||"",this.payload=e.payload||"",this.context=e.context||{},this.dependencies=e.dependencies||[],this.reverseDependencies=e.reverseDependencies||[]}addActions(e){return e.map((e=>this.addAction(e)))}addAction(e){const t=new n(this.engine,this.rule,e);return this.actionMaps.set(t.id,t),this.children.push(t),t}removeAction(e){this.actionMaps.delete(e),this.children=this.children.filter((t=>t.id!==e))}updateAction(e,t){const i=this.actionMaps.get(e);i&&Object.assign(i,t)}fromJSON(e){this.addActions(Array.isArray(e)?e:[e])}getMeta(){return{id:this.id,field:this.field,operator:this.operator,payload:this.payload,context:this.context,dependencies:this.dependencies,reverseDependencies:this.reverseDependencies}}getMetaWithActions(){return{action:this.getMeta(),actions:this.children.map((e=>e.getMeta()))}}}const s=new Map;class r{condition=null;action=null;id="";children=[];rulesMap=new Map;conditionOperator=e.NONE;constructor(e,t={}){this.engine=e,this.options=t,this.prepare(),this.init()}prepare(){this.condition=new i(this.engine,this),this.action=new n(this.engine,this)}init(){this.conditionOperator=this.options.operator||e.NONE,this.id=t()}addNewRule(t={operator:e.ANY}){const i=new r(this.engine,t);return this.children.push(i),this.rulesMap.set(i.id,i),s.set(i.id,i),i}removeRule(e){const t="string"==typeof e?e:e.id;this.children=this.children.filter((e=>e.id!==t)),this.rulesMap.delete(t),s.delete(t)}getRule(e){return s.get(e)}getAllRules(){return s}getAllRulesArray(){return Array.from(s.values())}getAllRulesWithMeta(){return this.getAllRulesArray().map((e=>({rule:e.getMeta(),rules:e.children.map((e=>e.getMeta()))})))}getRulesWithMeta(){return this.children.map((e=>e.getMeta()))}getMeta(){return{id:this.id,operator:this.conditionOperator,condition:this.condition?.getMetaWithConditions().conditions,conditionFields:this.condition?.getFieldsObject(),action:this.action?.getMetaWithActions().actions,children:Array.isArray(this.children)&&this.children.length>0?this.getRulesWithMeta.bind(this)():[]}}fromJSON(){this.addNewRule({operator:e.ANY}),this.condition?.fromJSON({})}}class o{rule=new r(this,{operator:e.NONE})}exports.Engine=o,exports.default=o; 2 | -------------------------------------------------------------------------------- /playground/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).RuleEngine={})}(this,(function(e){"use strict";let t;function i(){return Math.random().toString(36).slice(2)}!function(e){e.ANY="ANY",e.ALL="ALL",e.NONE="NONE"}(t||(t={}));class n{id="";field="";operator="";value="";fields=new Map;conditionsMap=new Map;children=[];context={};constructor(e,t,i={}){this.engine=e,this.rule=t,this.init(i)}init(e){this.id=i(),this.field=e.field||"",this.operator=e.operator||"",this.value=e.value||"",this.context=e.context||{}}addCondition(e={}){const t=new n(this.engine,this.rule,e);return this.conditionsMap.set(t.id,t),this.children.push(t),this.fields.set(t.field,(this.fields.get(t.field)||0)+1),t}removeCondition(e){this.conditionsMap.delete(e),this.children=this.children.filter((t=>t.id!==e)),this.fields.set(e,(this.fields.get(e)||0)-1),0===this.fields.get(e)&&this.fields.delete(e)}getConditions(){return this.children}getCondition(e){return this.conditionsMap.get(e)}getConditionsByField(e){return this.children.filter((t=>t.field===e))}getConditionsByOperator(e){return this.children.filter((t=>t.operator===e))}getMetaWithConditions(){return{...this.getMeta(),conditions:this.children.map((e=>e.getMeta()))}}fieldExists(e){return this.fields.has(e)}getFieldsObject(){return Object.fromEntries(this.fields)}getMeta(){return{id:this.id,field:this.field,operator:this.operator,value:this.value,context:this.context}}fromJSON(e={}){this.addCondition(e)}}class s{id="";field="";operator="";payload="";context={};dependencies=[];reverseDependencies=[];actionMaps=new Map;children=[];constructor(e,t,i={}){this.engine=e,this.rule=t,this.init(i)}init(e){this.id=i(),this.field=e.field||"",this.operator=e.operator||"",this.payload=e.payload||"",this.context=e.context||{},this.dependencies=e.dependencies||[],this.reverseDependencies=e.reverseDependencies||[]}addActions(e){return e.map((e=>this.addAction(e)))}addAction(e){const t=new s(this.engine,this.rule,e);return this.actionMaps.set(t.id,t),this.children.push(t),t}removeAction(e){this.actionMaps.delete(e),this.children=this.children.filter((t=>t.id!==e))}updateAction(e,t){const i=this.actionMaps.get(e);i&&Object.assign(i,t)}fromJSON(e){this.addActions(Array.isArray(e)?e:[e])}getMeta(){return{id:this.id,field:this.field,operator:this.operator,payload:this.payload,context:this.context,dependencies:this.dependencies,reverseDependencies:this.reverseDependencies}}getMetaWithActions(){return{action:this.getMeta(),actions:this.children.map((e=>e.getMeta()))}}}const r=new Map;class o{condition=null;action=null;id="";children=[];rulesMap=new Map;conditionOperator=t.NONE;constructor(e,t={}){this.engine=e,this.options=t,this.prepare(),this.init()}prepare(){this.condition=new n(this.engine,this),this.action=new s(this.engine,this)}init(){this.conditionOperator=this.options.operator||t.NONE,this.id=i()}addNewRule(e={operator:t.ANY}){const i=new o(this.engine,e);return this.children.push(i),this.rulesMap.set(i.id,i),r.set(i.id,i),i}removeRule(e){const t="string"==typeof e?e:e.id;this.children=this.children.filter((e=>e.id!==t)),this.rulesMap.delete(t),r.delete(t)}getRule(e){return r.get(e)}getAllRules(){return r}getAllRulesArray(){return Array.from(r.values())}getAllRulesWithMeta(){return this.getAllRulesArray().map((e=>({rule:e.getMeta(),rules:e.children.map((e=>e.getMeta()))})))}getRulesWithMeta(){return this.children.map((e=>e.getMeta()))}getMeta(){return{id:this.id,operator:this.conditionOperator,condition:this.condition?.getMetaWithConditions().conditions,conditionFields:this.condition?.getFieldsObject(),action:this.action?.getMetaWithActions().actions,children:Array.isArray(this.children)&&this.children.length>0?this.getRulesWithMeta.bind(this)():[]}}fromJSON(){this.addNewRule({operator:t.ANY}),this.condition?.fromJSON({})}}class d{rule=new o(this,{operator:t.NONE})}e.Engine=d,e.default=d,Object.defineProperty(e,"__esModule",{value:!0})})); 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## GITATTRIBUTES FOR WEB PROJECTS 2 | # 3 | # These settings are for any web project. 4 | # 5 | # Details per file setting: 6 | # text These files should be normalized (i.e. convert CRLF to LF). 7 | # binary These files are binary and should be left untouched. 8 | # 9 | # Note that binary is a macro for -text -diff. 10 | ###################################################################### 11 | 12 | # Auto detect 13 | ## Handle line endings automatically for files detected as 14 | ## text and leave all files detected as binary untouched. 15 | ## This will handle all files NOT defined below. 16 | * text=auto 17 | 18 | # Source code 19 | *.bash text eol=lf 20 | *.bat text eol=crlf 21 | *.cmd text eol=crlf 22 | *.coffee text 23 | *.css text diff=css 24 | *.htm text diff=html 25 | *.html text diff=html 26 | *.inc text 27 | *.ini text 28 | *.js text 29 | *.json text 30 | *.jsx text 31 | *.less text 32 | *.ls text 33 | *.map text -diff 34 | *.od text 35 | *.onlydata text 36 | *.php text diff=php 37 | *.pl text 38 | *.ps1 text eol=crlf 39 | *.py text diff=python 40 | *.rb text diff=ruby 41 | *.sass text 42 | *.scm text 43 | *.scss text diff=css 44 | *.sh text eol=lf 45 | .husky/* text eol=lf 46 | *.sql text 47 | *.styl text 48 | *.tag text 49 | *.ts text 50 | *.tsx text 51 | *.xml text 52 | *.xhtml text diff=html 53 | 54 | # Docker 55 | Dockerfile text 56 | 57 | # Documentation 58 | *.ipynb text 59 | *.markdown text diff=markdown 60 | *.md text diff=markdown 61 | *.mdwn text diff=markdown 62 | *.mdown text diff=markdown 63 | *.mkd text diff=markdown 64 | *.mkdn text diff=markdown 65 | *.mdtxt text 66 | *.mdtext text 67 | *.txt text 68 | AUTHORS text 69 | CHANGELOG text 70 | CHANGES text 71 | CONTRIBUTING text 72 | COPYING text 73 | copyright text 74 | *COPYRIGHT* text 75 | INSTALL text 76 | license text 77 | LICENSE text 78 | NEWS text 79 | readme text 80 | *README* text 81 | TODO text 82 | 83 | # Templates 84 | *.dot text 85 | *.ejs text 86 | *.erb text 87 | *.haml text 88 | *.handlebars text 89 | *.hbs text 90 | *.hbt text 91 | *.jade text 92 | *.latte text 93 | *.mustache text 94 | *.njk text 95 | *.phtml text 96 | *.svelte text 97 | *.tmpl text 98 | *.tpl text 99 | *.twig text 100 | *.vue text 101 | 102 | # Configs 103 | *.cnf text 104 | *.conf text 105 | *.config text 106 | .editorconfig text 107 | .env text 108 | .gitattributes text 109 | .gitconfig text 110 | .htaccess text 111 | *.lock text -diff 112 | package.json text eol=lf 113 | package-lock.json text eol=lf -diff 114 | pnpm-lock.yaml text eol=lf -diff 115 | .prettierrc text 116 | yarn.lock text -diff 117 | *.toml text 118 | *.yaml text 119 | *.yml text 120 | browserslist text 121 | Makefile text 122 | makefile text 123 | 124 | # Heroku 125 | Procfile text 126 | 127 | # Graphics 128 | *.ai binary 129 | *.bmp binary 130 | *.eps binary 131 | *.gif binary 132 | *.gifv binary 133 | *.ico binary 134 | *.jng binary 135 | *.jp2 binary 136 | *.jpg binary 137 | *.jpeg binary 138 | *.jpx binary 139 | *.jxr binary 140 | *.pdf binary 141 | *.png binary 142 | *.psb binary 143 | *.psd binary 144 | # SVG treated as an asset (binary) by default. 145 | *.svg text 146 | # If you want to treat it as binary, 147 | # use the following line instead. 148 | # *.svg binary 149 | *.svgz binary 150 | *.tif binary 151 | *.tiff binary 152 | *.wbmp binary 153 | *.webp binary 154 | 155 | # Audio 156 | *.kar binary 157 | *.m4a binary 158 | *.mid binary 159 | *.midi binary 160 | *.mp3 binary 161 | *.ogg binary 162 | *.ra binary 163 | 164 | # Video 165 | *.3gpp binary 166 | *.3gp binary 167 | *.as binary 168 | *.asf binary 169 | *.asx binary 170 | *.avi binary 171 | *.fla binary 172 | *.flv binary 173 | *.m4v binary 174 | *.mng binary 175 | *.mov binary 176 | *.mp4 binary 177 | *.mpeg binary 178 | *.mpg binary 179 | *.ogv binary 180 | *.swc binary 181 | *.swf binary 182 | *.webm binary 183 | 184 | # Archives 185 | *.7z binary 186 | *.gz binary 187 | *.jar binary 188 | *.rar binary 189 | *.tar binary 190 | *.zip binary 191 | 192 | # Fonts 193 | *.ttf binary 194 | *.eot binary 195 | *.otf binary 196 | *.woff binary 197 | *.woff2 binary 198 | 199 | # Executables 200 | *.exe binary 201 | *.pyc binary 202 | 203 | # RC files (like .babelrc or .eslintrc) 204 | *.*rc text 205 | 206 | # Ignore files (like .npmignore or .gitignore) 207 | *.*ignore text 208 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Playground 8 | 9 | 13 | 14 | 15 | 16 | 17 |
18 |

Used to display the rendering results of the rules engine

19 |

Rule Engine

20 |
21 | 24 |
 25 |                 
    26 |
27 |
28 | 32 |
33 |
34 | 35 | 145 | 146 | 191 | --------------------------------------------------------------------------------