├── .cliff-jumperrc.yml ├── .commitlintrc.json ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── labels.yml ├── problemMatchers │ ├── eslint.json │ └── tsc.json ├── renovate.json └── workflows │ ├── auto-deprecate.yml │ ├── codeql-analysis.yml │ ├── continuous-delivery.yml │ ├── continuous-integration.yml │ ├── deprecate-on-merge.yml │ ├── documentation.yml │ └── labelssync.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .npm-deprecaterc.yml ├── .prettierignore ├── .typedoc-json-parserrc.yml ├── .vscode ├── extensions.json └── settings.json ├── .yarn └── releases │ └── yarn-4.8.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cliff.toml ├── package.json ├── src ├── index.ts ├── lib │ └── structures │ │ ├── Josh.ts │ │ └── JoshError.ts └── tsconfig.json ├── tests ├── lib │ └── structures │ │ └── Josh.test.ts └── tsconfig.json ├── tsconfig.base.json ├── tsconfig.eslint.json ├── tsup.config.ts ├── typedoc.json ├── vitest.config.ts └── yarn.lock /.cliff-jumperrc.yml: -------------------------------------------------------------------------------- 1 | name: core 2 | packagePath: . 3 | org: joshdb 4 | monoRepo: false 5 | commitMessageTemplate: 'chore(release): release {{new-version}}' 6 | tagTemplate: v{{new-version}} 7 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": { 4 | "type-enum": [2, "always", ["chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test"]], 5 | "footer-leading-blank": [2, "always"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{js,ts}] 10 | indent_size = 2 11 | indent_style = space 12 | block_comment_start = /* 13 | block_comment = * 14 | block_comment_end = */ 15 | 16 | [*.{yml,yaml}] 17 | indent_size = 2 18 | indent_style = space 19 | 20 | [*.{md,rmd,mkd,mkdn,mdwn,mdown,markdown,litcoffee}] 21 | tab_width = 2 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore a blackhole 2 | node_modules/ 3 | .vs/ 4 | 5 | # Ignore build artifacts 6 | dist/ 7 | build/ 8 | docs/ 9 | coverage/ 10 | .turbo/ 11 | 12 | # Ignore heapsnapshot and log files 13 | *.heapsnapshot 14 | *.log 15 | 16 | # Ignore package locks 17 | package-lock.json 18 | 19 | # Ignore the GH cli downloaded by workflows 20 | gh 21 | 22 | # Ignore Yarn files 23 | .yarn/install-state.gz 24 | .yarn/build-state.yml 25 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@joshdb" 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /src/ @RealShadowNova @dan-online 2 | /tests/ @RealShadowNova @dan-online 3 | 4 | /.github/ @RealShadowNova 5 | /.husky/ @RealShadowNova 6 | /.vscode/ @RealShadowNova 7 | /.yarn/ @RealShadowNova 8 | LICENSE @eslachance 9 | package.json @RealShadowNova 10 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Workflow 4 | 5 | 1. Fork and clone this repository. 6 | 2. Create a new branch in your fork based off the **main** branch. 7 | 3. Make your changes, and push them. 8 | 4. Submit a Pull Request [here](https://github.com/josh-development/core/pulls)! 9 | 10 | ## Contributing to the code 11 | 12 | **The issue tracker is only for issue reporting or proposals/suggestions. If you have a question, you can find us in our [Discord Server](https://discord.evie.dev)**. 13 | 14 | To contribute to this repository, feel free to create a new fork of the repository and submit a pull request. We highly suggest [ESLint](https://eslint.org) to be installed in your text editor or IDE of your choice to ensure builds from GitHub Actions do not fail. 15 | 16 | **_Before committing and pushing your changes, please ensure that you do not have any linting errors by running `yarn lint`!_** 17 | 18 | ### Josh Concept Guidelines 19 | 20 | There are a number of guidelines considered when reviewing Pull Requests to be merged. _This is by no means an exhaustive list, but here are some things to consider before/while submitting your ideas._ 21 | 22 | - Everything in Josh should be generally useful for the majority of users. Don't let that stop you if you've got a good concept though, as your idea still might be a great addition. 23 | - Everything should follow [OOP paradigms](https://en.wikipedia.org/wiki/Object-oriented_programming) and generally rely on behavior over state where possible. This abstraction, and leads to efficiency and therefore scalability. 24 | - Everything should follow our ESLint rules as closely as possible, and should pass lint tests even if you must disable a rule for a single line. 25 | - Scripts that are to be ran outside of the scope source files should be added to [scripts](/scripts) directory and should be in the `mjs` file format. 26 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: blocked 2 | color: 'fc1423' 3 | - name: bug:confirmed 4 | color: 'd73a4a' 5 | - name: bug:unverified 6 | color: 'f9d0c4' 7 | - name: chore 8 | color: 'ffffff' 9 | - name: ci 10 | color: '0075ca' 11 | - name: dependencies 12 | color: '276bd1' 13 | - name: discussion 14 | color: 'b6b1f9' 15 | - name: documentation 16 | color: '0075ca' 17 | - name: duplicate 18 | color: 'cfd3d7' 19 | - name: error handling 20 | color: 'ffff00' 21 | - name: feature 22 | color: 'ffff00' 23 | - name: hacktoberfest-accepted 24 | color: 'EE4700' 25 | - name: has PR 26 | color: '4b1f8e' 27 | - name: help wanted 28 | color: '008672' 29 | - name: in progress 30 | color: 'ffccd7' 31 | - name: in review 32 | color: 'aed5fc' 33 | - name: invalid 34 | color: 'e4e669' 35 | - name: need repro 36 | color: 'c66037' 37 | - name: needs rebase & merge 38 | color: '24853c' 39 | - name: packages:core 40 | color: 'fbca04' 41 | - name: performance 42 | color: '80c042' 43 | - name: priority:critical 44 | color: 'b60205' 45 | - name: priority:high 46 | color: 'fc1423' 47 | - name: question (please use Discord instead) 48 | color: 'd876e3' 49 | - name: refactor 50 | color: '1d637f' 51 | - name: regression 52 | color: 'ea8785' 53 | - name: semver:major 54 | color: 'c10f47' 55 | - name: semver:minor 56 | color: 'e4f486' 57 | - name: semver:patch 58 | color: 'e8be8b' 59 | - name: tests 60 | color: 'f06dff' 61 | - name: typings 62 | color: '80c042' 63 | - name: wontfix 64 | color: 'ffffff' 65 | -------------------------------------------------------------------------------- /.github/problemMatchers/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "eslint-stylish", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^\\s].*)$", 8 | "file": 1 9 | }, 10 | { 11 | "regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$", 12 | "line": 1, 13 | "column": 2, 14 | "severity": 3, 15 | "message": 4, 16 | "code": 5, 17 | "loop": true 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.github/problemMatchers/tsc.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "tsc", 5 | "pattern": [ 6 | { 7 | "regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$", 8 | "file": 1, 9 | "location": 2, 10 | "severity": 3, 11 | "code": 4, 12 | "message": 5 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>josh-development/.github:josh-renovate"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/auto-deprecate.yml: -------------------------------------------------------------------------------- 1 | name: NPM Auto Deprecate 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | auto-deprecate: 9 | name: NPM Auto Deprecate 10 | runs-on: ubuntu-latest 11 | 12 | if: github.repository_owner == 'josh-development' 13 | steps: 14 | - name: Checkout Project 15 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 16 | - name: Use Node.js v20 17 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 18 | with: 19 | vnode-version: 20 20 | cache: yarn 21 | registry-url: https://registry.npmjs.org/ 22 | - name: Install Dependencies 23 | run: yarn --immutable 24 | - name: Deprecate Versions 25 | run: yarn npm-deprecate 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Code Scanning 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: '30 1 * * 0' 12 | 13 | jobs: 14 | codeql-analysis: 15 | name: CodeQL Analysis 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout Repository 19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 20 | - name: Initialize CodeQL 21 | uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3 22 | - name: Auto Build 23 | uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3 24 | - name: Perform CodeQL Analysis 25 | uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3 26 | -------------------------------------------------------------------------------- /.github/workflows/continuous-delivery.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Delivery 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | prNumber: 7 | description: The number of the PR that is being deployed 8 | required: true 9 | branch: 10 | description: The branch that is being deployed. 11 | required: false 12 | default: main 13 | push: 14 | branches: 15 | - main 16 | 17 | jobs: 18 | publish: 19 | name: Publish Next to NPM 20 | runs-on: ubuntu-latest 21 | 22 | if: github.repository_owner == 'josh-development' 23 | steps: 24 | - name: Checkout Project 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 26 | with: 27 | fetch-depth: 0 28 | ref: ${{ github.event.inputs.branch || 'main' }} 29 | - name: Add TypeScript Problem Matcher 30 | run: echo "::add-matcher::.github/problemMatchers/tsc.json" 31 | - name: Setup Node 32 | uses: josh-development/.github/setup-node@main 33 | - name: Bump Version & Publish 34 | run: | 35 | # Resolve the tag to be used. "next" for push events, "pr-{prNumber}" for dispatch events. 36 | TAG=$([[ ${{ github.event_name }} == 'push' ]] && echo 'next' || echo 'pr-${{ github.event.inputs.prNumber }}') 37 | 38 | yarn config set npmAuthToken ${NODE_AUTH_TOKEN} 39 | yarn config set npmPublishRegistry "https://registry.yarnpkg.com" 40 | 41 | yarn bump --preid "${TAG}.$(git rev-parse --verify --short HEAD)" 42 | 43 | yarn npm publish --tag ${TAG} 44 | env: 45 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 46 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | linting: 11 | name: Linting 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Project 15 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 16 | - name: Add ESLint Problem Matcher 17 | run: echo "::add-matcher::.github/problemMatchers/eslint.json" 18 | - name: Use Node.js v20 19 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 20 | with: 21 | vnode-version: 20 22 | cache: yarn 23 | registry-url: https://registry.yarnpkg.com 24 | - name: Install Dependencies 25 | run: yarn --immutable 26 | - name: Run ESLint 27 | run: yarn lint --fix=false 28 | 29 | building: 30 | name: Compile Source Code 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout Project 34 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 35 | - name: Add TypeScript Problem Matcher 36 | run: echo "::add-matcher::.github/problemMatchers/tsc.json" 37 | - name: Use Node.js v20 38 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 39 | with: 40 | vnode-version: 20 41 | cache: yarn 42 | registry-url: https://registry.yarnpkg.com 43 | - name: Install Dependencies 44 | if: ${{ !steps.cache-restore.outputs.cache-hit }} 45 | run: yarn --immutable 46 | - name: Build Code 47 | run: yarn build 48 | 49 | docs: 50 | name: Generate Documentation 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout Project 54 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 55 | - name: Use Node.js v20 56 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 57 | with: 58 | vnode-version: 20 59 | cache: yarn 60 | registry-url: https://registry.yarnpkg.com/ 61 | - name: Install Dependencies 62 | run: yarn --immutable 63 | - name: Generate Documentation 64 | run: yarn docs 65 | 66 | testing: 67 | name: Unit Tests 68 | runs-on: ubuntu-latest 69 | steps: 70 | - name: Checkout Project 71 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 72 | - name: Use Node.js v20 73 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 74 | with: 75 | vnode-version: 20 76 | cache: yarn 77 | registry-url: https://registry.yarnpkg.com 78 | - name: Install Dependencies 79 | run: yarn --immutable 80 | - name: Run tests 81 | run: yarn test --coverage 82 | - name: Store Code Coverage Report 83 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 84 | with: 85 | name: coverage 86 | path: coverage/ 87 | 88 | upload-coverage-report: 89 | name: Upload coverage report to codecov 90 | needs: [testing] 91 | runs-on: ubuntu-latest 92 | steps: 93 | - name: Checkout Project 94 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 95 | with: 96 | fetch-depth: 2 97 | - name: Download Coverage report 98 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 99 | with: 100 | name: coverage 101 | path: coverage/ 102 | - name: Codecov Upload 103 | uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5 104 | with: 105 | token: ${{ secrets.CODECOV_TOKEN }} 106 | directory: coverage/ 107 | -------------------------------------------------------------------------------- /.github/workflows/deprecate-on-merge.yml: -------------------------------------------------------------------------------- 1 | name: NPM Deprecate PR On Merge 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | 8 | jobs: 9 | deprecate-on-merge: 10 | name: NPM Deprecate PR On Merge 11 | runs-on: ubuntu-latest 12 | 13 | if: github.repository_owner == 'josh-development' 14 | steps: 15 | - name: Checkout Project 16 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 17 | - name: Setup Node 18 | uses: josh-development/.github/setup-node@main 19 | - name: Deprecate versions 20 | run: yarn npm-deprecate --name "*pr-${PR_NUMBER}*" -d -v 21 | env: 22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 23 | PR_NUMBER: ${{ github.event.number }} 24 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | build: 12 | name: Build Documentation 13 | runs-on: ubuntu-latest 14 | 15 | if: github.repository_owner == 'josh-development' 16 | outputs: 17 | NAME: ${{ steps.env.outputs.NAME }} 18 | TYPE: ${{ steps.env.outputs.TYPE }} 19 | SHA: ${{ steps.env.outputs.SHA }} 20 | steps: 21 | - name: Checkout Project 22 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 23 | - name: Setup Node 24 | uses: josh-development/.github/setup-node@main 25 | - name: Build Documentation 26 | run: yarn docs 27 | - name: Upload Documentation Artifacts 28 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 29 | with: 30 | name: docs 31 | path: docs/api.json 32 | - name: Set Output 33 | id: env 34 | run: | 35 | echo "::set-output name=NAME::${GITHUB_REF_NAME}" 36 | echo "::set-output name=TYPE::${GITHUB_REF_TYPE}" 37 | echo "::set-output name=SHA::${GITHUB_SHA}" 38 | upload: 39 | name: Upload Documentation 40 | needs: build 41 | runs-on: ubuntu-latest 42 | env: 43 | NAME: ${{ needs.build.outputs.NAME }} 44 | TYPE: ${{ needs.build.outputs.TYPE }} 45 | SHA: ${{ needs.build.outputs.SHA }} 46 | steps: 47 | - name: Checkout Project 48 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 49 | - name: Use Node.js v20 50 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 51 | with: 52 | vnode-version: 20 53 | cache: yarn 54 | registry-url: https://registry.npmjs.org/ 55 | - name: Install Dependencies 56 | run: yarn --immutable 57 | - name: Download Documentation Artifacts 58 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 59 | with: 60 | name: docs 61 | path: docs 62 | - name: Checkout Documentation Project 63 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 64 | with: 65 | repository: 'josh-development/docs' 66 | token: ${{ secrets.ACCESS_TOKEN }} 67 | path: 'out' 68 | 69 | - name: Move Documentation 70 | if: ${{ env.TYPE == 'tag' }} 71 | env: 72 | SEMVER: ${{ env.NAME }} 73 | run: | 74 | mkdir -p out/docs/core 75 | mv docs/api.json out/docs/core/${SEMVER}.json 76 | - name: Move Documentation 77 | if: ${{ env.TYPE == 'branch' }} 78 | run: | 79 | mkdir -p out/docs/core 80 | mv docs/api.json out/docs/core/${NAME}.json 81 | 82 | - name: Commit & Push 83 | run: | 84 | cd out 85 | git config user.name github-actions[bot] 86 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 87 | git config rebase.autostash true 88 | git config pull.rebase true 89 | git add . 90 | git commit -m "docs(core): build for ${TYPE} ${NAME}: ${SHA}" || true 91 | git pull 92 | git push 93 | -------------------------------------------------------------------------------- /.github/workflows/labelssync.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Label Sync 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | label-sync: 10 | name: Automatic Label Synchronization 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Project 14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 15 | - name: Run Label Sync 16 | uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5 17 | with: 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | yaml-file: .github/labels.yml 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore a blackhole 2 | node_modules/ 3 | .vs/ 4 | 5 | # Ignore build artifacts 6 | build/ 7 | coverage/ 8 | dist/ 9 | docs/ 10 | 11 | # Ignore heapsnapshot and log files 12 | *.heapsnapshot 13 | *.log 14 | 15 | # Ignore package locks 16 | package-lock.json 17 | 18 | # Ignore the GH cli downloaded by workflows 19 | gh 20 | 21 | # Ignore Yarn files 22 | .yarn/install-state.gz 23 | .yarn/build-state.yml 24 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | yarn commitlint --edit $1 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | yarn lint-staged 2 | -------------------------------------------------------------------------------- /.npm-deprecaterc.yml: -------------------------------------------------------------------------------- 1 | name: '*next*' 2 | package: 3 | - '@joshdb/core' 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore a blackhole 2 | node_modules/ 3 | .vs/ 4 | 5 | # Ignore build artifacts 6 | dist/ 7 | build/ 8 | docs/ 9 | coverage/ 10 | .turbo/ 11 | 12 | # Ignore heapsnapshot and log files 13 | *.heapsnapshot 14 | *.log 15 | 16 | # Ignore package locks 17 | package-lock.json 18 | 19 | # Ignore the GH cli downloaded by workflows 20 | gh 21 | 22 | # Ignore Yarn files 23 | .yarn/install-state.gz 24 | .yarn/build-state.yml 25 | 26 | # Ignore changelogs 27 | CHANGELOG.md 28 | 29 | # Ignore toml files 30 | *.toml 31 | -------------------------------------------------------------------------------- /.typedoc-json-parserrc.yml: -------------------------------------------------------------------------------- 1 | json: 'docs/api.json' 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "typescript" 4 | ], 5 | "editor.tabSize": 2, 6 | "editor.useTabStops": false, 7 | "editor.insertSpaces": true, 8 | "editor.detectIndentation": false, 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll": "explicit", 11 | "source.fixAll.eslint": "explicit", 12 | "source.organizeImports": "explicit" 13 | }, 14 | "files.eol": "\n", 15 | "typescript.tsdk": "node_modules/typescript/lib" 16 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: true 4 | 5 | nodeLinker: node-modules 6 | 7 | yarnPath: .yarn/releases/yarn-4.8.1.cjs 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Évelyne Lachance 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![Josh Logo](https://evie.codes/josh-light.png) 4 | 5 | # @joshdb/core 6 | 7 | **JavaScript Object Storage Helper** 8 | 9 | [![GitHub](https://img.shields.io/github/license/josh-development/core)](https://github.com/josh-development/core/blob/main/LICENSE.md) 10 | [![npm](https://img.shields.io/npm/v/@joshdb/core?color=crimson&logo=npm&style=flat-square)](https://www.npmjs.com/package/@joshdb/core) 11 | [![codecov](https://codecov.io/gh/josh-development/core/branch/main/graph/badge.svg?token=JnJcjxqT3k)](https://codecov.io/gh/josh-development/core) 12 | 13 | [![Support Server](https://discord.com/api/guilds/298508738623438848/embed.png?style=banner2)](https://discord.gg/N7ZKH3P) 14 | 15 |
16 | 17 | --- 18 | 19 | ## Description 20 | 21 | A simple, effective, and efficient database wrapper. 22 | 23 | ## Features 24 | 25 | - Written in TypeScript 26 | - Offers CommonJS, ESM and UMD bundles 27 | - Fully tested 28 | 29 | ## Installation 30 | 31 | You can use the following command to install this package, or replace `npm install` with your package manager of choice. 32 | 33 | ```sh 34 | npm install @joshdb/core@next 35 | ``` 36 | 37 | ## Documentation 38 | 39 | Unfortunately, the website to host the documentation is not yet ready, but you can check on it's progress [here](https://github.com/josh-development/website) 40 | 41 | ## FAQ 42 | 43 | ### What is Josh? 44 | 45 | Josh is quite simply the easiest storage system you'll ever encounter. Josh makes it simple to store any JSON-based data effectively into any popular database back-end using providers. 46 | 47 | ### Why use Josh? 48 | 49 | Josh makes it extremely easy and simple to get a project up and running as quickly as possible with very little effort when it comes to the database interaction layer. It does this by removed the complexity of setting up and ORM or learning a database language, while still giving you all the power. 50 | 51 | ### When NOT to use Josh? 52 | 53 | If you think you can do better, if you already know database interactions or ORMs, or you don't trust database wrappers made by other people... Well, in any of those cases I don't even know why you're on this page, go on now, `git out --of:here` and go make some good applications, you don't need this! 54 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | [changelog] 2 | header = """ 3 | # Changelog 4 | 5 | All notable changes to this project will be documented in this file.\n 6 | """ 7 | body = """ 8 | {% if version %}\ 9 | # [{{ version | trim_start_matches(pat="v") }}]\ 10 | {% if previous %}\ 11 | {% if previous.version %}\ 12 | (https://github.com/josh-development/core/compare/{{ previous.version }}...{{ version }})\ 13 | {% else %}\ 14 | (https://github.com/josh-development/core/tree/{{ version }})\ 15 | {% endif %}\ 16 | {% endif %} \ 17 | - ({{ timestamp | date(format="%Y-%m-%d") }}) 18 | {% else %}\ 19 | # [unreleased] 20 | {% endif %}\ 21 | {% for group, commits in commits | group_by(attribute="group") %} 22 | ## {{ group | upper_first }} 23 | {% for commit in commits %} 24 | - {% if commit.scope %}\ 25 | **{{commit.scope}}:** \ 26 | {% endif %}\ 27 | {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/josh-development/core/commit/{{ commit.id }}))\ 28 | {% if commit.breaking %}\ 29 | {% for breakingChange in commit.footers %}\ 30 | \n{% raw %} {% endraw %}- 💥 **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\ 31 | {% endfor %}\ 32 | {% endif %}\ 33 | {% endfor %} 34 | {% endfor %}\n 35 | """ 36 | trim = true 37 | footer = "" 38 | 39 | [git] 40 | conventional_commits = true 41 | filter_unconventional = true 42 | commit_parsers = [ 43 | { message = "^feat", group = "🚀 Features"}, 44 | { message = "^fix", group = "🐛 Bug Fixes"}, 45 | { message = "^docs", group = "📝 Documentation"}, 46 | { message = "^perf", group = "🏃 Performance"}, 47 | { message = "^refactor", group = "🏠 Refactor"}, 48 | { message = ".*deprecated", body = ".*deprecated", group = "🚨 Deprecation"}, 49 | { message = "^style", group = "🪞 Styling"}, 50 | { message = "^test", group = "🧪 Testing"}, 51 | { message = "^chore", skip = true}, 52 | { message = "^ci", skip = true}, 53 | { body = ".*security", group = "🛡️ Security"}, 54 | ] 55 | filter_commits = true 56 | tag_pattern = "v[0-9]*" 57 | ignore_tags = "" 58 | topo_order = false 59 | sort_commits = "newest" 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@joshdb/core", 3 | "version": "2.0.0", 4 | "description": "JavaScript Object Storage Helper", 5 | "author": "Évelyne Lachance (https://evie.codes/)", 6 | "contributors": [ 7 | "Hezekiah Hendry ", 8 | "DanCodes (https://dancodes.online/)", 9 | "Wilson (https://wilson.antti.codes/)" 10 | ], 11 | "license": "Apache-2.0", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "browser": "dist/index.global.js", 15 | "unpkg": "dist/index.global.js", 16 | "types": "dist/index.d.ts", 17 | "exports": { 18 | "import": "./dist/index.mjs", 19 | "require": "./dist/index.js", 20 | "types": "./dist/index.d.ts" 21 | }, 22 | "scripts": { 23 | "clean": "rimraf coverage dist docs node_modules/.cache", 24 | "docs": "typedoc-json-parser", 25 | "lint": "eslint src tests --ext ts --fix", 26 | "format": "prettier --write {src,tests}/**/*.ts", 27 | "test": "vitest run", 28 | "build": "tsup", 29 | "bump": "cliff-jumper", 30 | "check-update": "cliff-jumper --dry-run", 31 | "update": "yarn upgrade-interactive", 32 | "postinstall": "husky", 33 | "prepack": "yarn build && pinst --disable", 34 | "postpack": "pinst --enable" 35 | }, 36 | "dependencies": { 37 | "@joshdb/auto-ensure": "2.0.0-next.e20aea6.0", 38 | "@joshdb/map": "2.0.0-next.d8f6dae.0", 39 | "@joshdb/provider": "2.0.0-next.b88aca0.0", 40 | "@sapphire/utilities": "^3.18.2" 41 | }, 42 | "devDependencies": { 43 | "@commitlint/cli": "^19.8.1", 44 | "@commitlint/config-conventional": "^19.8.1", 45 | "@favware/cliff-jumper": "^3.0.3", 46 | "@favware/npm-deprecate": "^1.0.7", 47 | "@joshdb/eslint-config": "2.0.0-next.b88aca0.0", 48 | "@joshdb/prettier-config": "2.0.0-next.b88aca0.0", 49 | "@joshdb/ts-config": "2.0.0-next.b88aca0.0", 50 | "@types/node": "^20.17.57", 51 | "@typescript-eslint/eslint-plugin": "^7.18.0", 52 | "@typescript-eslint/parser": "^7.18.0", 53 | "@vitest/coverage-v8": "^1.6.1", 54 | "esbuild-plugin-polyfill-node": "^0.3.0", 55 | "eslint": "^8.57.1", 56 | "eslint-config-prettier": "^9.1.0", 57 | "eslint-plugin-prettier": "^5.4.1", 58 | "husky": "^9.1.7", 59 | "lint-staged": "^15.5.2", 60 | "pinst": "^3.0.0", 61 | "prettier": "^3.5.3", 62 | "rimraf": "^5.0.10", 63 | "tsup": "^8.5.0", 64 | "typedoc": "^0.28.5", 65 | "typedoc-json-parser": "^10.2.0", 66 | "typescript": "^5.8.3", 67 | "vitest": "1.6.1" 68 | }, 69 | "repository": { 70 | "type": "git", 71 | "url": "git+https://github.com/josh-development/core.git" 72 | }, 73 | "files": [ 74 | "dist", 75 | "!dist/*.tsbuildinfo" 76 | ], 77 | "engines": { 78 | "node": ">=20", 79 | "npm": ">=6" 80 | }, 81 | "keywords": [], 82 | "bugs": { 83 | "url": "https://github.com/josh-development/core/issues" 84 | }, 85 | "homepage": "https://josh.evie.dev", 86 | "publishConfig": { 87 | "access": "public" 88 | }, 89 | "lint-staged": { 90 | "*.ts": "eslint --fix --ext ts" 91 | }, 92 | "prettier": "@joshdb/prettier-config", 93 | "packageManager": "yarn@4.8.1" 94 | } 95 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/structures/Josh'; 2 | export * from './lib/structures/JoshError'; 3 | -------------------------------------------------------------------------------- /src/lib/structures/Josh.ts: -------------------------------------------------------------------------------- 1 | import { AutoEnsureMiddleware } from '@joshdb/auto-ensure'; 2 | import { MapProvider } from '@joshdb/map'; 3 | import type { KeyPath, KeyPathJSON, MathOperator, Path } from '@joshdb/provider'; 4 | import { 5 | CommonIdentifiers, 6 | JoshMiddleware, 7 | JoshMiddlewareStore, 8 | JoshProvider, 9 | Method, 10 | Payload, 11 | Trigger, 12 | isPayloadWithData, 13 | resolveCommonIdentifier 14 | } from '@joshdb/provider'; 15 | import type { Awaitable, NonNullObject, Primitive } from '@sapphire/utilities'; 16 | import { isFunction, isPrimitive } from '@sapphire/utilities'; 17 | import { JoshError, type JoshErrorOptions } from './JoshError'; 18 | 19 | /** 20 | * The base class that makes Josh work. 21 | * @see {@link Josh.Options} for all options available to the Josh class. 22 | * @since 2.0.0 23 | * 24 | * @example 25 | * ```javascript 26 | * // Using the default map non-persistent provider 27 | * const josh = new Josh({ 28 | * name: 'database', 29 | * // More options... 30 | * }); 31 | * ``` 32 | * 33 | * @example 34 | * ```javascript 35 | * // Using a provider. 36 | * const josh = new Josh({ 37 | * name: 'database', 38 | * provider: new Provider(), 39 | * // More options... 40 | * }); 41 | * ``` 42 | * @example 43 | * ```typescript 44 | * // TypeScript example 45 | * const josh = new Josh<{ username: string }>({ 46 | * name: 'database' 47 | * }); 48 | * ``` 49 | */ 50 | export class Josh { 51 | /** 52 | * This Josh's name. Used for middleware and/or provider information. 53 | * @since 2.0.0 54 | */ 55 | public name: string; 56 | 57 | /** 58 | * This Josh's options. Used throughout the instance. 59 | * @since 2.0.0 60 | */ 61 | public options: Josh.Options; 62 | 63 | /** 64 | * This Josh's middleware store. 65 | * 66 | * NOTE: Do not use this unless you know what your doing. 67 | * @since 2.0.0 68 | */ 69 | public middlewares: JoshMiddlewareStore; 70 | 71 | /** 72 | * This Josh's provider instance. 73 | * 74 | * NOTE: Do not use this unless you know what your doing. 75 | */ 76 | public provider: JoshProvider; 77 | 78 | public constructor(options: Josh.Options) { 79 | const { name, provider, middlewares, autoEnsure } = options; 80 | 81 | this.options = options; 82 | 83 | if (!name) { 84 | throw this.error(Josh.Identifiers.MissingName); 85 | } 86 | 87 | this.name = name; 88 | this.provider = provider ?? new MapProvider(); 89 | 90 | if (!(this.provider instanceof JoshProvider)) { 91 | process.emitWarning(this.resolveIdentifier(Josh.Identifiers.InvalidProvider)); 92 | } 93 | 94 | this.middlewares = new JoshMiddlewareStore({ provider: this.provider }); 95 | 96 | if (autoEnsure !== undefined) { 97 | this.use(new AutoEnsureMiddleware(autoEnsure)); 98 | } 99 | 100 | if (middlewares !== undefined && Array.isArray(middlewares)) { 101 | const filteredMiddleware = middlewares.filter((middleware) => { 102 | if (!(middleware instanceof JoshMiddleware)) { 103 | process.emitWarning(this.resolveIdentifier(Josh.Identifiers.InvalidMiddleware)); 104 | } 105 | 106 | return middleware instanceof JoshMiddleware; 107 | }); 108 | 109 | for (const middleware of filteredMiddleware) { 110 | this.use(middleware); 111 | } 112 | } 113 | } 114 | 115 | private get providerDataFailedError(): JoshError { 116 | return this.error(Josh.Identifiers.ProviderDataFailed); 117 | } 118 | 119 | /** 120 | * The initialization method for Josh. 121 | * @since 2.0.0 122 | * @returns The {@link Josh} instance. 123 | * 124 | * @example 125 | * ```javascript 126 | * await josh.init(); 127 | * ``` 128 | * @example 129 | * ```javascript 130 | * josh.init().then(() => console.log('Initialized Josh!')); 131 | * ``` 132 | */ 133 | public async init(): Promise { 134 | await this.provider.init({ name: this.name }); 135 | 136 | for (const middleware of this.middlewares.values()) { 137 | await middleware.init(this.middlewares); 138 | } 139 | 140 | return this; 141 | } 142 | 143 | /** 144 | * Adds a middleware by providing options and a hook. 145 | * @since 2.0.0 146 | * @param options The options for this middleware instance. 147 | * @param hook The hook to run for the payload. 148 | * @returns The {@link Josh} instance. 149 | * 150 | * @example 151 | * ```javascript 152 | * josh.use({ name: 'my-middleware' }, (payload) => payload); 153 | * ``` 154 | */ 155 | public use

(options: Josh.UseMiddlewareOptions, hook: (payload: P) => Awaitable

): this; 156 | 157 | /** 158 | * Adds a middleware by providing a JoshMiddleware instance. 159 | * @since 2.0.0 160 | * @param instance The middleware instance. 161 | * @returns The {@link Josh} instance. 162 | * 163 | * @example 164 | * ```javascript 165 | * josh.use(new MyMiddleware()); 166 | * ``` 167 | */ 168 | public use(instance: JoshMiddleware): this; 169 | public use

( 170 | optionsOrInstance: Josh.UseMiddlewareOptions | JoshMiddleware, 171 | hook?: (payload: P) => Awaitable

172 | ): this { 173 | if (optionsOrInstance instanceof JoshMiddleware) { 174 | this.middlewares.set(optionsOrInstance.name, optionsOrInstance); 175 | } else { 176 | if (hook === undefined) { 177 | throw this.error(Josh.Identifiers.UseMiddlewareHookNotFound); 178 | } 179 | 180 | const { name, trigger, method } = optionsOrInstance; 181 | const options: JoshMiddleware.Options = { name, conditions: { [Trigger.PreProvider]: [], [Trigger.PostProvider]: [] } }; 182 | // @ts-ignore Until provider updates 183 | const middleware = this.middlewares.get(options.name) ?? new JoshMiddleware({}, options); 184 | 185 | if (trigger !== undefined && method !== undefined) { 186 | options.conditions[trigger].push(method); 187 | } 188 | 189 | Object.defineProperty(middleware, method === undefined ? 'run' : method, { value: hook }); 190 | this.middlewares.set(middleware.name, middleware); 191 | } 192 | 193 | return this; 194 | } 195 | 196 | /** 197 | * Generate an automatic key. Generally an integer incremented by `1`, but depends on provider. 198 | * @since 2.0.0 199 | * @returns The newly generated automatic key. 200 | * 201 | * @example 202 | * ```javascript 203 | * const key = await josh.autoKey(); 204 | * 205 | * await josh.set(key, 'value'); 206 | * ``` 207 | */ 208 | public async autoKey(): Promise { 209 | let payload: Payload.AutoKey = { method: Method.AutoKey, errors: [], trigger: Trigger.PreProvider }; 210 | 211 | for (const middleware of Array.from(this.middlewares.values())) { 212 | await middleware.run(payload); 213 | } 214 | 215 | for (const middleware of this.middlewares.getPreMiddlewares(Method.AutoKey)) { 216 | payload = await middleware[Method.AutoKey](payload); 217 | } 218 | 219 | if (!isPayloadWithData(payload)) { 220 | payload = await this.provider[Method.AutoKey](payload); 221 | } 222 | 223 | payload.trigger = Trigger.PostProvider; 224 | 225 | for (const middleware of Array.from(this.middlewares.values())) { 226 | await middleware.run(payload); 227 | } 228 | 229 | for (const middleware of this.middlewares.getPostMiddlewares(Method.AutoKey)) { 230 | payload = await middleware[Method.AutoKey](payload); 231 | } 232 | 233 | this.runBehaviorOnPayloadError(payload); 234 | 235 | if (isPayloadWithData(payload)) { 236 | return payload.data; 237 | } 238 | 239 | throw this.providerDataFailedError; 240 | } 241 | 242 | /** 243 | * Clears all stored values from the provider. 244 | * 245 | * NOTE: This deletes **all** data and cannot be reversed. 246 | * @since 2.0.0 247 | * @returns The {@link Josh} instance. 248 | * 249 | * @example 250 | * ```javascript 251 | * await josh.set('key', 'value'); 252 | * 253 | * await josh.clear(); 254 | * 255 | * await josh.get('key'); // null 256 | * ``` 257 | */ 258 | public async clear(): Promise { 259 | let payload: Payload.Clear = { method: Method.Clear, errors: [], trigger: Trigger.PreProvider }; 260 | 261 | for (const middleware of Array.from(this.middlewares.values())) { 262 | await middleware.run(payload); 263 | } 264 | 265 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Clear)) { 266 | payload = await middleware[Method.Clear](payload); 267 | } 268 | 269 | payload = await this.provider[Method.Clear](payload); 270 | payload.trigger = Trigger.PostProvider; 271 | 272 | for (const middleware of Array.from(this.middlewares.values())) { 273 | await middleware.run(payload); 274 | } 275 | 276 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Clear)) { 277 | payload = await middleware[Method.Clear](payload); 278 | } 279 | 280 | this.runBehaviorOnPayloadError(payload); 281 | 282 | return this; 283 | } 284 | 285 | /** 286 | * Decrement a stored integer by `1`. 287 | * @since 2.0.0 288 | * @param key The key of the integer to decrement. 289 | * @param path The path to the integer for decrementing. 290 | * @returns The {@link Josh} instance. 291 | * 292 | * @example 293 | * ```javascript 294 | * await josh.set('key', 1); 295 | * 296 | * await josh.dec('key'); 297 | * 298 | * await josh.get('key'); // 0 299 | * ``` 300 | * 301 | * @example 302 | * ```javascript 303 | * await josh.set('key', 1, 'path'); 304 | * 305 | * await josh.dec('key', 'path'); 306 | * 307 | * await josh.get('key', 'path'); // 0 308 | * ``` 309 | * 310 | * @example 311 | * ```javascript 312 | * await josh.set('key', 1, 'path'); 313 | * 314 | * await josh.dec('key', ['path']); 315 | * 316 | * await josh.get('key', 'path'); // 0 317 | * ``` 318 | */ 319 | public async dec(key: string, path: Path = []): Promise { 320 | path = this.resolvePath(path); 321 | 322 | let payload: Payload.Dec = { method: Method.Dec, errors: [], trigger: Trigger.PreProvider, key, path }; 323 | 324 | for (const middleware of Array.from(this.middlewares.values())) { 325 | await middleware.run(payload); 326 | } 327 | 328 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Dec)) { 329 | payload = await middleware[Method.Dec](payload); 330 | } 331 | 332 | payload = await this.provider[Method.Dec](payload); 333 | payload.trigger = Trigger.PostProvider; 334 | 335 | for (const middleware of Array.from(this.middlewares.values())) { 336 | await middleware.run(payload); 337 | } 338 | 339 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Dec)) { 340 | payload = await middleware[Method.Dec](payload); 341 | } 342 | 343 | this.runBehaviorOnPayloadError(payload); 344 | 345 | return this; 346 | } 347 | 348 | /** 349 | * Deletes a key or path in a key value. 350 | * @since 2.0.0 351 | * @param key The key to delete from. 352 | * @param path The path to delete from. 353 | * @returns The {@link Josh} instance. 354 | * 355 | * @example 356 | * ```javascript 357 | * await josh.set('key', 'value'); 358 | * 359 | * await josh.delete('key'); 360 | * 361 | * await josh.get('key'); // null 362 | * ``` 363 | * 364 | * @example 365 | * ```javascript 366 | * await josh.set('key', { path: 'value' }); 367 | * 368 | * await josh.delete('key', 'path'); 369 | * 370 | * await josh.get('key'); // {} 371 | * ``` 372 | * 373 | * @example 374 | * ```javascript 375 | * await josh.set('key', { path: 'value' }); 376 | * 377 | * await josh.delete('key', ['path']); 378 | * 379 | * await josh.get('key'); // {} 380 | * ``` 381 | */ 382 | public async delete(key: string, path: Path = []): Promise { 383 | path = this.resolvePath(path); 384 | 385 | let payload: Payload.Delete = { method: Method.Delete, errors: [], trigger: Trigger.PreProvider, key, path }; 386 | 387 | for (const middleware of Array.from(this.middlewares.values())) { 388 | await middleware.run(payload); 389 | } 390 | 391 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Delete)) { 392 | payload = await middleware[Method.Delete](payload); 393 | } 394 | 395 | payload = await this.provider[Method.Delete](payload); 396 | payload.trigger = Trigger.PostProvider; 397 | 398 | for (const middleware of Array.from(this.middlewares.values())) { 399 | await middleware.run(payload); 400 | } 401 | 402 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Delete)) { 403 | payload = await middleware[Method.Delete](payload); 404 | } 405 | 406 | this.runBehaviorOnPayloadError(payload); 407 | 408 | return this; 409 | } 410 | 411 | /** 412 | * Deletes many keys. 413 | * @since 2.0.0 414 | * @param keys The keys to delete. 415 | * @returns The {@link Josh} instance. 416 | * 417 | * @example 418 | * ```javascript 419 | * await josh.set('key', 'value'); 420 | * 421 | * await josh.deleteMany(['key']); 422 | * 423 | * await josh.get('key'); // null 424 | * ``` 425 | */ 426 | public async deleteMany(keys: string[]): Promise { 427 | let payload: Payload.DeleteMany = { method: Method.DeleteMany, errors: [], trigger: Trigger.PreProvider, keys }; 428 | 429 | for (const middleware of Array.from(this.middlewares.values())) { 430 | await middleware.run(payload); 431 | } 432 | 433 | for (const middleware of this.middlewares.getPreMiddlewares(Method.DeleteMany)) { 434 | payload = await middleware[Method.DeleteMany](payload); 435 | } 436 | 437 | payload = await this.provider[Method.DeleteMany](payload); 438 | payload.trigger = Trigger.PostProvider; 439 | 440 | for (const middleware of Array.from(this.middlewares.values())) { 441 | await middleware.run(payload); 442 | } 443 | 444 | for (const middleware of this.middlewares.getPostMiddlewares(Method.DeleteMany)) { 445 | payload = await middleware[Method.DeleteMany](payload); 446 | } 447 | 448 | this.runBehaviorOnPayloadError(payload); 449 | 450 | return this; 451 | } 452 | 453 | /** 454 | * Loop over every key-value pair in this {@link Josh} and execute `hook` on it. 455 | * @since 2.0.0 456 | * @param hook The hook function to execute with each key. 457 | * @returns The {@link Josh} instance. 458 | * 459 | * @example 460 | * ```javascript 461 | * await josh.set('key', 'value'); 462 | * 463 | * await josh.each((value, key) => console.log(key + ' = ' + value)); // key = value 464 | * ``` 465 | */ 466 | public async each(hook: Payload.Hook): Promise { 467 | let payload: Payload.Each = { method: Method.Each, errors: [], trigger: Trigger.PreProvider, hook }; 468 | 469 | for (const middleware of Array.from(this.middlewares.values())) { 470 | await middleware.run(payload); 471 | } 472 | 473 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Each)) { 474 | payload = await middleware[Method.Each](payload); 475 | } 476 | 477 | if (!payload.metadata?.skipProvider) { 478 | payload = await this.provider[Method.Each](payload); 479 | } 480 | 481 | payload.trigger = Trigger.PostProvider; 482 | 483 | for (const middleware of Array.from(this.middlewares.values())) { 484 | await middleware.run(payload); 485 | } 486 | 487 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Each)) { 488 | payload = await middleware[Method.Each](payload); 489 | } 490 | 491 | this.runBehaviorOnPayloadError(payload); 492 | 493 | return this; 494 | } 495 | 496 | /** 497 | * Ensure a key exists and set a default value if it doesn't. 498 | * @since 2.0.0 499 | * @param key The key to ensure. 500 | * @param defaultValue The default value to set if the key doesn't exist. 501 | * @returns The value gotten from the key or the default value. 502 | * 503 | * @example 504 | * ```javascript 505 | * await josh.ensure('key', 'defaultValue'); // 'defaultValue' 506 | * ``` 507 | * 508 | * @example 509 | * ```javascript 510 | * await josh.set('key', 'value'); 511 | * 512 | * await josh.ensure('key', 'defaultValue'); // 'value' 513 | * ``` 514 | */ 515 | public async ensure(key: string, defaultValue: StoredValue): Promise { 516 | let payload: Payload.Ensure = { method: Method.Ensure, errors: [], trigger: Trigger.PreProvider, key, defaultValue }; 517 | 518 | for (const middleware of Array.from(this.middlewares.values())) { 519 | await middleware.run(payload); 520 | } 521 | 522 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Ensure)) { 523 | payload = await middleware[Method.Ensure](payload); 524 | } 525 | 526 | if (!isPayloadWithData(payload)) { 527 | payload = await this.provider[Method.Ensure](payload); 528 | } 529 | 530 | payload.trigger = Trigger.PostProvider; 531 | 532 | for (const middleware of Array.from(this.middlewares.values())) { 533 | await middleware.run(payload); 534 | } 535 | 536 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Ensure)) { 537 | payload = await middleware[Method.Ensure](payload); 538 | } 539 | 540 | this.runBehaviorOnPayloadError(payload); 541 | 542 | if (isPayloadWithData(payload)) { 543 | return payload.data; 544 | } 545 | 546 | throw this.providerDataFailedError; 547 | } 548 | 549 | /** 550 | * Get all stored values. 551 | * @since 2.0.0 552 | * @param returnBulkType The return bulk type. Defaults to {@link Bulk.Object} 553 | * @returns The bulk data. 554 | 555 | * @example 556 | * ```javascript 557 | * await josh.set('key', 'value'); 558 | * 559 | * await josh.entries(); // { key: 'value' } 560 | * await josh.entries(Bulk.OneDimensionalArray); // ['value'] 561 | * await josh.entries(Bulk.TwoDimensionalArray); // [['key', { path: 'value' }]] 562 | * ``` 563 | */ 564 | public async entries = Bulk.Object>( 565 | returnBulkType?: BulkType 566 | ): Promise[BulkType]> { 567 | let payload: Payload.Entries = { method: Method.Entries, errors: [], trigger: Trigger.PreProvider }; 568 | 569 | for (const middleware of Array.from(this.middlewares.values())) { 570 | await middleware.run(payload); 571 | } 572 | 573 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Entries)) { 574 | payload = await middleware[Method.Entries](payload); 575 | } 576 | 577 | if (!isPayloadWithData(payload)) { 578 | payload = await this.provider[Method.Entries](payload); 579 | } 580 | 581 | payload.trigger = Trigger.PostProvider; 582 | 583 | for (const middleware of Array.from(this.middlewares.values())) { 584 | await middleware.run(payload); 585 | } 586 | 587 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Entries)) { 588 | payload = await middleware[Method.Entries](payload); 589 | } 590 | 591 | this.runBehaviorOnPayloadError(payload); 592 | 593 | if (isPayloadWithData>(payload)) { 594 | return this.convertBulkData(payload.data, returnBulkType); 595 | } 596 | 597 | throw this.providerDataFailedError; 598 | } 599 | 600 | /** 601 | * Checks every stored value at a path against the given value. Identical behavior to [Array#every](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). 602 | * @since 2.0.0 603 | * @param path The path at which the stored value is at. 604 | * @param value The primitive value to check against the stored value. 605 | * @returns A boolean of whether every value matched. 606 | * 607 | * @example 608 | * ```javascript 609 | * await josh.every('path', 'value'); // true 610 | * await josh.every(['path'], 'value'); // true 611 | * ``` 612 | * 613 | * @example 614 | * ```javascript 615 | * await josh.set('key', { path: 'value' }); 616 | * await josh.set('key2', { path: 'value2' }); 617 | * 618 | * await josh.every('path', 'value'); // false 619 | * await josh.every(['path'], 'value'); // false 620 | * ``` 621 | * 622 | * @example 623 | * ```javascript 624 | * await josh.set('key', { path: 'value' }); 625 | * await josh.set('key2', { path: 'value' }); 626 | * 627 | * await josh.every('path', 'value'); // true 628 | * await josh.evert(['path'], 'value') // true 629 | * ``` 630 | */ 631 | public async every(path: Path, value: Primitive): Promise; 632 | 633 | /** 634 | * Checks every stored value with a function. Identical Behavior to [Array#every](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). 635 | * @since 2.0.0 636 | * @param hook The hook function to check against stored values. 637 | * @returns A boolean of whether every hook function returns true. 638 | * 639 | * @example 640 | * ```javascript 641 | * await josh.every((value) => value === 'value'); // true 642 | * ``` 643 | * 644 | * @example 645 | * ```javascript 646 | * await josh.set('key', 'value'); 647 | * await josh.set('key2', 'value2'); 648 | * 649 | * await josh.every((value) => value === 'value'); // false 650 | * ``` 651 | * 652 | * @example 653 | * ```javascript 654 | * await josh.set('key', 'value'); 655 | * await josh.set('key2', 'value'); 656 | * 657 | * await josh.every((value) => value === 'value'); // true 658 | * ``` 659 | */ 660 | public async every(hook: Payload.Hook): Promise; 661 | public async every(pathOrHook: Path | Payload.Hook, value?: Primitive): Promise { 662 | if (!isFunction(pathOrHook)) { 663 | if (value === undefined) { 664 | throw this.error(CommonIdentifiers.MissingValue); 665 | } 666 | 667 | if (!isPrimitive(value)) { 668 | throw this.error(CommonIdentifiers.InvalidValueType, { type: 'primitive' }); 669 | } 670 | } 671 | 672 | let payload: Payload.Every; 673 | 674 | if (isFunction(pathOrHook)) { 675 | payload = { 676 | method: Method.Every, 677 | errors: [], 678 | trigger: Trigger.PreProvider, 679 | type: Payload.Type.Hook, 680 | hook: pathOrHook 681 | }; 682 | } else { 683 | payload = { 684 | method: Method.Every, 685 | errors: [], 686 | trigger: Trigger.PreProvider, 687 | type: Payload.Type.Value, 688 | path: this.resolvePath(pathOrHook), 689 | value 690 | }; 691 | } 692 | 693 | for (const middleware of Array.from(this.middlewares.values())) { 694 | await middleware.run(payload); 695 | } 696 | 697 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Every)) { 698 | payload = await middleware[Method.Every](payload); 699 | } 700 | 701 | if (!isPayloadWithData(payload)) { 702 | payload = await this.provider[Method.Every](payload); 703 | } 704 | 705 | payload.trigger = Trigger.PostProvider; 706 | 707 | for (const middleware of Array.from(this.middlewares.values())) { 708 | await middleware.run(payload); 709 | } 710 | 711 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Every)) { 712 | payload = await middleware[Method.Every](payload); 713 | } 714 | 715 | this.runBehaviorOnPayloadError(payload); 716 | 717 | if (isPayloadWithData(payload)) { 718 | return payload.data; 719 | } 720 | 721 | throw this.providerDataFailedError; 722 | } 723 | 724 | /** 725 | * Filter stored values using a path and value. 726 | * @since 2.0.0 727 | * @param path A path to the value for equality check. 728 | * @param value The value to check equality. 729 | * @param returnBulkType The return bulk type. Defaults to {@link Bulk.Object} 730 | * @returns The bulk data. 731 | * 732 | * @example 733 | * ```javascript 734 | * await josh.set('key', { path: 'value' }); 735 | * 736 | * await josh.filter('path', 'value'); // { key: { path: 'value' } } 737 | * await josh.filter(['path'], 'value', Bulk.OneDimensionalArray); // [{ path: 'value' }] 738 | * ``` 739 | */ 740 | public async filter>( 741 | path: Path, 742 | value: Primitive, 743 | returnBulkType?: BulkType 744 | ): Promise[BulkType]>; 745 | 746 | /** 747 | * Filter stored data using a hook function. 748 | * @since 2.0.0 749 | * @param hook The hook function to check equality. 750 | * @param returnBulkType The return bulk type. Defaults to {@link Bulk.Object} 751 | * @returns The bulk data. 752 | * 753 | * @example 754 | * ```javascript 755 | * await josh.set('key', 'value'); 756 | * 757 | * await josh.filter((value) => value === 'value'); // { key: { path: 'value' } } 758 | * await josh.filter((value) => value === 'value', Bulk.TwoDimensionalArray); // [['key', 'value']] 759 | * ``` 760 | */ 761 | public async filter>( 762 | hook: Payload.Hook, 763 | returnBulkType?: BulkType 764 | ): Promise[BulkType]>; 765 | 766 | public async filter>( 767 | pathOrHook: Path | Payload.Hook, 768 | value?: Primitive, 769 | returnBulkType?: BulkType 770 | ): Promise[BulkType]> { 771 | if (!isFunction(pathOrHook)) { 772 | if (value === undefined) { 773 | throw this.error(CommonIdentifiers.MissingValue); 774 | } 775 | 776 | if (!isPrimitive(value)) { 777 | throw this.error(CommonIdentifiers.InvalidValueType, { type: 'primitive' }); 778 | } 779 | } 780 | 781 | let payload: Payload.Filter; 782 | 783 | if (isFunction(pathOrHook)) { 784 | payload = { 785 | method: Method.Filter, 786 | errors: [], 787 | trigger: Trigger.PreProvider, 788 | type: Payload.Type.Hook, 789 | hook: pathOrHook 790 | }; 791 | } else { 792 | payload = { 793 | method: Method.Filter, 794 | errors: [], 795 | trigger: Trigger.PreProvider, 796 | type: Payload.Type.Value, 797 | path: this.resolvePath(pathOrHook), 798 | value 799 | }; 800 | } 801 | 802 | for (const middleware of Array.from(this.middlewares.values())) { 803 | await middleware.run(payload); 804 | } 805 | 806 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Filter)) { 807 | payload = await middleware[Method.Filter](payload); 808 | } 809 | 810 | if (!isPayloadWithData(payload)) { 811 | payload = await this.provider[Method.Filter](payload); 812 | } 813 | 814 | payload.trigger = Trigger.PostProvider; 815 | 816 | for (const middleware of Array.from(this.middlewares.values())) { 817 | await middleware.run(payload); 818 | } 819 | 820 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Filter)) { 821 | payload = await middleware[Method.Filter](payload); 822 | } 823 | 824 | this.runBehaviorOnPayloadError(payload); 825 | 826 | if (isPayloadWithData>(payload)) { 827 | return this.convertBulkData(payload.data, returnBulkType); 828 | } 829 | 830 | throw this.providerDataFailedError; 831 | } 832 | 833 | /** 834 | * Find a stored value using a path and value. 835 | * @since 2.0.0 836 | * @param path A path to the value for equality check. 837 | * @param value The value to check equality. 838 | * @returns The found key/value pair or a null/null pair. 839 | * 840 | * @example 841 | * ```javascript 842 | * await josh.find('path', 'value'); // null 843 | * ``` 844 | * 845 | * @example 846 | * ```javascript 847 | * await josh.set('key', { path: 'value' }); 848 | * 849 | * await josh.find('path', 'value'); // { path: 'value' } 850 | * ``` 851 | */ 852 | public async find(path: Path, value: Primitive): Promise<[string, StoredValue] | [null, null]>; 853 | 854 | /** 855 | * Find a stored value using a hook function. 856 | * @since 2.0.0 857 | * @param hook The hook to check equality. 858 | * @returns The found key/value pair or null/null pair. 859 | * 860 | * @example 861 | * ```javascript 862 | * await josh.find((value) => value === 'value'); // null 863 | * ``` 864 | * 865 | * @example 866 | * ```javascript 867 | * await josh.set('key', 'value'); 868 | * 869 | * await josh.find((value) => value === 'value'); // 'value' 870 | * ``` 871 | */ 872 | public async find(hook: Payload.Hook): Promise<[string, StoredValue] | [null, null]>; 873 | public async find(pathOrHook: Path | Payload.Hook, value?: Primitive): Promise<[string, StoredValue] | [null, null]> { 874 | if (!isFunction(pathOrHook)) { 875 | if (value === undefined) { 876 | throw this.error(CommonIdentifiers.MissingValue); 877 | } 878 | 879 | if (!isPrimitive(value)) { 880 | throw this.error(CommonIdentifiers.InvalidValueType, { type: 'primitive' }); 881 | } 882 | } 883 | 884 | let payload: Payload.Find; 885 | 886 | if (isFunction(pathOrHook)) { 887 | payload = { 888 | method: Method.Find, 889 | errors: [], 890 | trigger: Trigger.PreProvider, 891 | type: Payload.Type.Hook, 892 | hook: pathOrHook 893 | }; 894 | } else { 895 | payload = { 896 | method: Method.Find, 897 | errors: [], 898 | trigger: Trigger.PreProvider, 899 | type: Payload.Type.Value, 900 | path: this.resolvePath(pathOrHook), 901 | value 902 | }; 903 | } 904 | 905 | for (const middleware of Array.from(this.middlewares.values())) { 906 | await middleware.run(payload); 907 | } 908 | 909 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Find)) { 910 | payload = await middleware[Method.Find](payload); 911 | } 912 | 913 | if (!isPayloadWithData(payload)) { 914 | payload = await this.provider[Method.Find](payload); 915 | } 916 | 917 | payload.trigger = Trigger.PostProvider; 918 | 919 | for (const middleware of Array.from(this.middlewares.values())) { 920 | await middleware.run(payload); 921 | } 922 | 923 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Find)) { 924 | payload = await middleware[Method.Find](payload); 925 | } 926 | 927 | this.runBehaviorOnPayloadError(payload); 928 | 929 | if (isPayloadWithData>(payload)) { 930 | return payload.data; 931 | } 932 | 933 | throw this.providerDataFailedError; 934 | } 935 | 936 | /** 937 | * Get a value using a key. 938 | * @since 2.0.0 939 | * @param key A key at which a value is. 940 | * @returns The value gotten or null. 941 | * 942 | * @example 943 | * ```javascript 944 | * await josh.set('key', 'value'); 945 | * 946 | * await josh.get('key'); // 'value' 947 | * await josh.get('notfound'); // null 948 | * ``` 949 | */ 950 | public async get(key: string): Promise; 951 | 952 | /** 953 | * Get a value using a key and/or path. 954 | * @since 2.0.0 955 | * @param key A key at which a value is. 956 | * @param path A path to the value. 957 | * @returns The value gotten or null. 958 | * 959 | * @example 960 | * ```javascript 961 | * await josh.set('key', 'value'); 962 | * 963 | * await josh.get('key'); // 'value' 964 | * 965 | * await josh.set('key', { path: 'value' }); 966 | * 967 | * await josh.get('key'); // { path: 'value' } 968 | * ``` 969 | * 970 | * @example 971 | * ```javascript 972 | * await josh.set('key', { path: 'value' }); 973 | * 974 | * await josh.get('key', 'path'); // 'value' 975 | * await josh.get('key', ['path']); // 'value' 976 | * ``` 977 | */ 978 | public async get(key: string, path?: Path): Promise; 979 | public async get(key: string, path: Path = []): Promise { 980 | path = this.resolvePath(path); 981 | 982 | let payload: Payload.Get = { method: Method.Get, errors: [], trigger: Trigger.PreProvider, key, path }; 983 | 984 | for (const middleware of Array.from(this.middlewares.values())) { 985 | await middleware.run(payload); 986 | } 987 | 988 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Get)) { 989 | payload = await middleware[Method.Get](payload); 990 | } 991 | 992 | if (!isPayloadWithData(payload)) { 993 | payload = await this.provider[Method.Get](payload); 994 | } 995 | 996 | payload.trigger = Trigger.PostProvider; 997 | 998 | for (const middleware of Array.from(this.middlewares.values())) { 999 | await middleware.run(payload); 1000 | } 1001 | 1002 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Get)) { 1003 | payload = await middleware[Method.Get](payload); 1004 | } 1005 | 1006 | this.runBehaviorOnPayloadError(payload); 1007 | 1008 | return isPayloadWithData(payload) ? payload.data! : null; 1009 | } 1010 | 1011 | /** 1012 | * Get stored values at multiple keys. 1013 | * @since 2.0.0 1014 | * @param keys An array of keys to get values from. 1015 | * @param returnBulkType The return bulk type. Defaults to {@link Bulk.Object} 1016 | * @returns The bulk data. 1017 | * 1018 | * @example 1019 | * ```javascript 1020 | * await josh.set('key', 'value'); 1021 | * 1022 | * await this.getMany(['key']); // { key: 'value' } 1023 | * await this.getMany(['key'], Bulk.OneDimensionalArray); // ['value'] 1024 | * ``` 1025 | */ 1026 | public async getMany>( 1027 | keys: string[], 1028 | returnBulkType?: BulkType 1029 | ): Promise[BulkType]> { 1030 | let payload: Payload.GetMany = { method: Method.GetMany, errors: [], trigger: Trigger.PreProvider, keys }; 1031 | 1032 | for (const middleware of Array.from(this.middlewares.values())) { 1033 | await middleware.run(payload); 1034 | } 1035 | 1036 | for (const middleware of this.middlewares.getPreMiddlewares(Method.GetMany)) { 1037 | payload = await middleware[Method.GetMany](payload); 1038 | } 1039 | 1040 | if (!isPayloadWithData(payload)) { 1041 | payload = await this.provider[Method.GetMany](payload); 1042 | } 1043 | 1044 | payload.trigger = Trigger.PostProvider; 1045 | 1046 | for (const middleware of Array.from(this.middlewares.values())) { 1047 | await middleware.run(payload); 1048 | } 1049 | 1050 | for (const middleware of this.middlewares.getPostMiddlewares(Method.GetMany)) { 1051 | payload = await middleware[Method.GetMany](payload); 1052 | } 1053 | 1054 | this.runBehaviorOnPayloadError(payload); 1055 | 1056 | if (isPayloadWithData>(payload)) { 1057 | return this.convertBulkData(payload.data, returnBulkType); 1058 | } 1059 | 1060 | throw this.providerDataFailedError; 1061 | } 1062 | 1063 | /** 1064 | * Check if a key and/or path exists. 1065 | * @since 2.0.0 1066 | * @param key A key to the value to check for. 1067 | * @param path A path to the value to check for. 1068 | * @returns Whether the value exists. 1069 | * 1070 | * @example 1071 | * ```javascript 1072 | * await josh.has('key'); // false 1073 | * 1074 | * await josh.set('key', 'value'); 1075 | * 1076 | * await josh.has('key'); // true 1077 | * ``` 1078 | * 1079 | * @example 1080 | * ```javascript 1081 | * await josh.has('key', 'path'); // false 1082 | * await josh.has('key', ['path']); // false 1083 | * 1084 | * await josh.set('key', { path: 'value' }); 1085 | * 1086 | * await josh.has('key', 'path'); // true 1087 | * await josh.has('key', ['path']); // true 1088 | * ``` 1089 | */ 1090 | public async has(key: string, path: Path = []): Promise { 1091 | path = this.resolvePath(path); 1092 | 1093 | let payload: Payload.Has = { method: Method.Has, errors: [], trigger: Trigger.PreProvider, key, path }; 1094 | 1095 | for (const middleware of Array.from(this.middlewares.values())) { 1096 | await middleware.run(payload); 1097 | } 1098 | 1099 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Has)) { 1100 | payload = await middleware[Method.Has](payload); 1101 | } 1102 | 1103 | if (!isPayloadWithData(payload)) { 1104 | payload = await this.provider[Method.Has](payload); 1105 | } 1106 | 1107 | payload.trigger = Trigger.PostProvider; 1108 | 1109 | for (const middleware of Array.from(this.middlewares.values())) { 1110 | await middleware.run(payload); 1111 | } 1112 | 1113 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Has)) { 1114 | payload = await middleware[Method.Has](payload); 1115 | } 1116 | 1117 | this.runBehaviorOnPayloadError(payload); 1118 | 1119 | if (isPayloadWithData(payload)) { 1120 | return payload.data; 1121 | } 1122 | 1123 | throw this.providerDataFailedError; 1124 | } 1125 | 1126 | /** 1127 | * Increment an integer by `1`. 1128 | * @since 2.0.0 1129 | * @param key The key to an integer value for incrementing. 1130 | * @param path The path to an integer value for incrementing. 1131 | * @returns The {@link Josh} instance. 1132 | * 1133 | * @example 1134 | * ```javascript 1135 | * await josh.set('key', 0); 1136 | * 1137 | * await josh.inc('key'); 1138 | * 1139 | * await josh.get('key'); // 1 1140 | * ``` 1141 | * 1142 | * @example 1143 | * ```javascript 1144 | * await josh.set('key', 0, 'path'); 1145 | * 1146 | * await josh.inc('key', 'path'); 1147 | * 1148 | * await josh.get('key', 'path'); // 1 1149 | * ``` 1150 | * 1151 | * @example 1152 | * ```javascript 1153 | * await josh.set('key', 0, 'path'); 1154 | * 1155 | * await josh.inc('key', ['path']); 1156 | * 1157 | * await josh.get('key', 'path'); // 1 1158 | * ``` 1159 | */ 1160 | public async inc(key: string, path: Path = []): Promise { 1161 | path = this.resolvePath(path); 1162 | 1163 | let payload: Payload.Inc = { method: Method.Inc, errors: [], trigger: Trigger.PreProvider, key, path }; 1164 | 1165 | for (const middleware of Array.from(this.middlewares.values())) { 1166 | await middleware.run(payload); 1167 | } 1168 | 1169 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Inc)) { 1170 | payload = await middleware[Method.Inc](payload); 1171 | } 1172 | 1173 | payload = await this.provider[Method.Inc](payload); 1174 | payload.trigger = Trigger.PostProvider; 1175 | 1176 | for (const middleware of Array.from(this.middlewares.values())) { 1177 | await middleware.run(payload); 1178 | } 1179 | 1180 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Inc)) { 1181 | payload = await middleware[Method.Inc](payload); 1182 | } 1183 | 1184 | this.runBehaviorOnPayloadError(payload); 1185 | 1186 | return this; 1187 | } 1188 | 1189 | /** 1190 | * Returns all stored keys. 1191 | * @since 2.0.0 1192 | * @returns The array of stored keys. 1193 | * 1194 | * @example 1195 | * ```javascript 1196 | * await josh.set('key', 'value'); 1197 | * 1198 | * await josh.keys(); // ['key'] 1199 | * ``` 1200 | */ 1201 | public async keys(): Promise { 1202 | let payload: Payload.Keys = { method: Method.Keys, errors: [], trigger: Trigger.PreProvider }; 1203 | 1204 | for (const middleware of Array.from(this.middlewares.values())) { 1205 | await middleware.run(payload); 1206 | } 1207 | 1208 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Keys)) { 1209 | payload = await middleware[Method.Keys](payload); 1210 | } 1211 | 1212 | if (!isPayloadWithData(payload)) { 1213 | payload = await this.provider[Method.Keys](payload); 1214 | } 1215 | 1216 | payload.trigger = Trigger.PostProvider; 1217 | 1218 | for (const middleware of Array.from(this.middlewares.values())) { 1219 | await middleware.run(payload); 1220 | } 1221 | 1222 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Keys)) { 1223 | payload = await middleware[Method.Keys](payload); 1224 | } 1225 | 1226 | this.runBehaviorOnPayloadError(payload); 1227 | 1228 | if (isPayloadWithData(payload)) { 1229 | return payload.data; 1230 | } 1231 | 1232 | throw this.providerDataFailedError; 1233 | } 1234 | 1235 | /** 1236 | * Map stored values by path or hook function. 1237 | * @since 2.0.0 1238 | * @param pathOrHook The path or hook to map by. 1239 | * @returns The mapped values. 1240 | * 1241 | * @example 1242 | * ```javascript 1243 | * await josh.set('key', { path: 'value' }); 1244 | * 1245 | * await josh.map('path'); // ['value'] 1246 | * ``` 1247 | * 1248 | * @example 1249 | * ```javascript 1250 | * await josh.set('key', { path: 'value' }); 1251 | * 1252 | * await josh.map(['path']); // 'value' 1253 | * ``` 1254 | * 1255 | * @example 1256 | * ```javascript 1257 | * await josh.set('key', 'value'); 1258 | * 1259 | * await josh.map((value) => value.toUpperCase()); // ['VALUE'] 1260 | * ``` 1261 | * @example 1262 | * ```typescript 1263 | * // TypeScript example 1264 | * await josh.set('key', { path: 'value' }); 1265 | * 1266 | * await josh.map((value) => value.path); // ['value'] 1267 | * ``` 1268 | */ 1269 | public async map(pathOrHook: Path | Payload.Hook): Promise { 1270 | let payload: Payload.Map; 1271 | 1272 | if (isFunction(pathOrHook)) { 1273 | payload = { 1274 | method: Method.Map, 1275 | errors: [], 1276 | trigger: Trigger.PreProvider, 1277 | type: Payload.Type.Hook, 1278 | hook: pathOrHook 1279 | }; 1280 | } else { 1281 | payload = { 1282 | method: Method.Map, 1283 | errors: [], 1284 | trigger: Trigger.PreProvider, 1285 | type: Payload.Type.Path, 1286 | path: this.resolvePath(pathOrHook) 1287 | }; 1288 | } 1289 | 1290 | for (const middleware of Array.from(this.middlewares.values())) { 1291 | await middleware.run(payload); 1292 | } 1293 | 1294 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Map)) { 1295 | payload = await middleware[Method.Map](payload); 1296 | } 1297 | 1298 | if (!isPayloadWithData(payload)) { 1299 | payload = await this.provider[Method.Map](payload); 1300 | } 1301 | 1302 | payload.trigger = Trigger.PostProvider; 1303 | 1304 | for (const middleware of Array.from(this.middlewares.values())) { 1305 | await middleware.run(payload); 1306 | } 1307 | 1308 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Map)) { 1309 | payload = await middleware[Method.Map](payload); 1310 | } 1311 | 1312 | if (payload.errors.length) { 1313 | const { behaviorOnPayloadError } = this.options; 1314 | 1315 | if (behaviorOnPayloadError !== undefined && behaviorOnPayloadError >= Josh.ErrorBehavior.Log) { 1316 | if (payload.errors.length === 1 && behaviorOnPayloadError === Josh.ErrorBehavior.Throw) { 1317 | throw payload.errors[0]; 1318 | } else { 1319 | for (const error of payload.errors) { 1320 | console.error(error); 1321 | } 1322 | 1323 | if (behaviorOnPayloadError === Josh.ErrorBehavior.Throw) { 1324 | throw this.error({ identifier: Josh.Identifiers.MultipleError, errors: payload.errors }); 1325 | } 1326 | } 1327 | } 1328 | } 1329 | 1330 | if (isPayloadWithData(payload)) { 1331 | return payload.data; 1332 | } 1333 | 1334 | throw this.providerDataFailedError; 1335 | } 1336 | 1337 | /** 1338 | * Executes math operations on a value with an operand at a specified key and/or path. 1339 | * @since 2.0.0 1340 | * @param key The key the value is at. 1341 | * @param operator The operator that will be used on the operand and value. 1342 | * @param operand The operand to apply to the value. 1343 | * @param path The path to the value. 1344 | * @returns The {@link Josh} instance. 1345 | * 1346 | * @example 1347 | * ```javascript 1348 | * await josh.set('key', 0); 1349 | * 1350 | * await josh.math('key', MathOperator.Addition, 1); 1351 | * 1352 | * await josh.get('key'); // 1 1353 | * ``` 1354 | * 1355 | * @example 1356 | * ```javascript 1357 | * await josh.set('key', 1); 1358 | * 1359 | * await josh.math('key', MathOperator.Subtraction, 1); 1360 | * 1361 | * await josh.get('key'); // 0 1362 | * ``` 1363 | * 1364 | * @example 1365 | * ```javascript 1366 | * await josh.set('key', 1); 1367 | * 1368 | * await josh.math('key', MathOperator.Multiplication, 2); 1369 | * 1370 | * await josh.get('key'); // 2 1371 | * ``` 1372 | * 1373 | * @example 1374 | * ```javascript 1375 | * await josh.set('key', 2); 1376 | * 1377 | * await josh.math('key' MathOperator.Division, 2); 1378 | * 1379 | * await josh.get('key'); // 1 1380 | * ``` 1381 | */ 1382 | public async math(key: string, operator: MathOperator, operand: number, path: Path = []): Promise { 1383 | path = this.resolvePath(path); 1384 | 1385 | let payload: Payload.Math = { method: Method.Math, errors: [], trigger: Trigger.PreProvider, key, path, operator, operand }; 1386 | 1387 | for (const middleware of Array.from(this.middlewares.values())) { 1388 | await middleware.run(payload); 1389 | } 1390 | 1391 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Math)) { 1392 | payload = await middleware[Method.Math](payload); 1393 | } 1394 | 1395 | payload = await this.provider[Method.Math](payload); 1396 | payload.trigger = Trigger.PostProvider; 1397 | 1398 | for (const middleware of Array.from(this.middlewares.values())) { 1399 | await middleware.run(payload); 1400 | } 1401 | 1402 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Math)) { 1403 | payload = await middleware[Method.Math](payload); 1404 | } 1405 | 1406 | this.runBehaviorOnPayloadError(payload); 1407 | 1408 | return this; 1409 | } 1410 | 1411 | /** 1412 | * Filter stored values and get both truthy and falsy results. 1413 | * @since 2.0.0 1414 | * @param hook The hook function to check equality. 1415 | * @param _value Unused. 1416 | * @param returnBulkType The return bulk type, Defaults to {@link Bulk.Object} 1417 | * @returns A partition of filtered bulk data. First bulk data is the truthy filter and the second bulk data is the falsy filter. 1418 | * 1419 | * @example 1420 | * ```javascript 1421 | * await josh.set('key', undefined, true); 1422 | * await josh.set('key2', undefined, false); 1423 | * 1424 | * await josh.partition((value) => value); // [{ key: true }, { key2: false }] 1425 | * ``` 1426 | */ 1427 | public async partition>( 1428 | hook: Payload.Hook, 1429 | _value: null, 1430 | returnBulkType?: BulkType 1431 | ): Promise<[ReturnBulk[BulkType], ReturnBulk[BulkType]]>; 1432 | 1433 | /** 1434 | * Filter stored values and get both truthy and falsy results. 1435 | * @since 2.0.0 1436 | * @param path A path to the value for equality check. 1437 | * @param value The value to check equality. 1438 | * @param returnBulkType The return bulk type. Defaults to {@link Bulk.Object} 1439 | * @returns A partition of filtered bulk data. First bulk data is the truthy filter and the second bulk data is the falsy filter. 1440 | * 1441 | * @example 1442 | * ```javascript 1443 | * await josh.set('key', { path: true }); 1444 | * await josh.set('key2', { path: false }); 1445 | * 1446 | * await josh.partition('path', true); // [{ key: true }, { key2: false }] 1447 | * ``` 1448 | */ 1449 | public async partition>( 1450 | path: Path, 1451 | value: Primitive, 1452 | returnBulkType?: BulkType 1453 | ): Promise<[ReturnBulk[BulkType], ReturnBulk[BulkType]]>; 1454 | 1455 | public async partition>( 1456 | pathOrHook: Path | Payload.Hook, 1457 | value?: Primitive, 1458 | returnBulkType?: BulkType 1459 | ): Promise<[ReturnBulk[BulkType], ReturnBulk[BulkType]]> { 1460 | if (!isFunction(pathOrHook)) { 1461 | if (value === undefined) { 1462 | throw this.error(CommonIdentifiers.MissingValue); 1463 | } 1464 | 1465 | if (!isPrimitive(value)) { 1466 | throw this.error(CommonIdentifiers.InvalidValueType, { type: 'primitive' }); 1467 | } 1468 | } 1469 | 1470 | let payload: Payload.Partition; 1471 | 1472 | if (isFunction(pathOrHook)) { 1473 | payload = { 1474 | method: Method.Partition, 1475 | errors: [], 1476 | trigger: Trigger.PreProvider, 1477 | type: Payload.Type.Hook, 1478 | hook: pathOrHook 1479 | }; 1480 | } else { 1481 | payload = { 1482 | method: Method.Partition, 1483 | errors: [], 1484 | trigger: Trigger.PreProvider, 1485 | type: Payload.Type.Value, 1486 | path: this.resolvePath(pathOrHook), 1487 | value 1488 | }; 1489 | } 1490 | 1491 | for (const middleware of Array.from(this.middlewares.values())) { 1492 | await middleware.run(payload); 1493 | } 1494 | 1495 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Partition)) { 1496 | payload = await middleware[Method.Partition](payload); 1497 | } 1498 | 1499 | if (!isPayloadWithData(payload)) { 1500 | payload = await this.provider[Method.Partition](payload); 1501 | } 1502 | 1503 | payload.trigger = Trigger.PostProvider; 1504 | 1505 | for (const middleware of Array.from(this.middlewares.values())) { 1506 | await middleware.run(payload); 1507 | } 1508 | 1509 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Partition)) { 1510 | payload = await middleware[Method.Partition](payload); 1511 | } 1512 | 1513 | this.runBehaviorOnPayloadError(payload); 1514 | 1515 | if (isPayloadWithData>(payload)) { 1516 | const { truthy, falsy } = payload.data; 1517 | 1518 | return [this.convertBulkData(truthy, returnBulkType), this.convertBulkData(falsy, returnBulkType)]; 1519 | } 1520 | 1521 | throw this.providerDataFailedError; 1522 | } 1523 | 1524 | /** 1525 | * Push a value to an array. 1526 | * @since 2.0.0 1527 | * @param key A key to the array. 1528 | * @param value The value to push. 1529 | * @param path A path to the array. 1530 | * @returns The {@link Josh} instance. 1531 | * 1532 | * @example 1533 | * ```javascript 1534 | * await josh.set('key', []); 1535 | * 1536 | * await josh.push('key', 'value'); 1537 | * 1538 | * await josh.get('key'); // ['value'] 1539 | * ``` 1540 | * 1541 | * @example 1542 | * ```javascript 1543 | * await josh.set('key', { path: [] }); 1544 | * 1545 | * await josh.push('key', 'firstValue', 'path'); 1546 | * await josh.push('key', 'secondValue', ['path']); 1547 | * 1548 | * await josh.get('key', 'path'); // ['firstValue', 'secondValue'] 1549 | * ``` 1550 | */ 1551 | public async push(key: string, value: Value, path: Path = []): Promise { 1552 | path = this.resolvePath(path); 1553 | 1554 | let payload: Payload.Push = { method: Method.Push, errors: [], trigger: Trigger.PreProvider, key, path, value }; 1555 | 1556 | for (const middleware of Array.from(this.middlewares.values())) { 1557 | await middleware.run(payload); 1558 | } 1559 | 1560 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Push)) { 1561 | payload = await middleware[Method.Push](payload); 1562 | } 1563 | 1564 | payload = await this.provider[Method.Push](payload); 1565 | payload.trigger = Trigger.PostProvider; 1566 | 1567 | for (const middleware of Array.from(this.middlewares.values())) { 1568 | await middleware.run(payload); 1569 | } 1570 | 1571 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Push)) { 1572 | payload = await middleware[Method.Push](payload); 1573 | } 1574 | 1575 | this.runBehaviorOnPayloadError(payload); 1576 | 1577 | return this; 1578 | } 1579 | 1580 | /** 1581 | * Gets random value(s). 1582 | * @param options The options for getting random values. 1583 | * @returns The random value(s) or null. 1584 | * 1585 | * @example 1586 | * ```javascript 1587 | * await josh.random(); // null 1588 | * ``` 1589 | * 1590 | * @example 1591 | * ```javascript 1592 | * await josh.set('key', 'value'); 1593 | * 1594 | * await josh.random(); // ['value'] 1595 | * ``` 1596 | */ 1597 | public async random(options?: Josh.RandomOptions): Promise { 1598 | const { count = 1, unique = false } = options ?? {}; 1599 | let payload: Payload.Random = { method: Method.Random, errors: [], trigger: Trigger.PreProvider, count, unique }; 1600 | 1601 | for (const middleware of Array.from(this.middlewares.values())) { 1602 | await middleware.run(payload); 1603 | } 1604 | 1605 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Random)) { 1606 | payload = await middleware[Method.Random](payload); 1607 | } 1608 | 1609 | if (!isPayloadWithData(payload)) { 1610 | payload = await this.provider[Method.Random](payload); 1611 | } 1612 | 1613 | payload.trigger = Trigger.PostProvider; 1614 | 1615 | for (const middleware of Array.from(this.middlewares.values())) { 1616 | await middleware.run(payload); 1617 | } 1618 | 1619 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Random)) { 1620 | payload = await middleware[Method.Random](payload); 1621 | } 1622 | 1623 | this.runBehaviorOnPayloadError(payload); 1624 | 1625 | if (isPayloadWithData(payload)) { 1626 | return payload.data.length ? payload.data : null; 1627 | } 1628 | 1629 | throw this.providerDataFailedError; 1630 | } 1631 | 1632 | /** 1633 | * Get a random key. 1634 | * 1635 | * NOTE: `options.duplicates` only makes checks on primitive value types. 1636 | * @since 2.0.0 1637 | * @returns The random key or `null`. 1638 | * 1639 | * @example 1640 | * ```javascript 1641 | * await josh.randomKey(); // null 1642 | * ``` 1643 | * 1644 | * @example 1645 | * ```javascript 1646 | * await josh.set('key', 'value'); 1647 | * 1648 | * await josh.randomKey(); // ['key'] 1649 | * ``` 1650 | */ 1651 | public async randomKey(options?: Josh.RandomOptions): Promise { 1652 | const { count = 1, unique = false } = options ?? {}; 1653 | let payload: Payload.RandomKey = { method: Method.RandomKey, errors: [], trigger: Trigger.PreProvider, count, unique }; 1654 | 1655 | for (const middleware of Array.from(this.middlewares.values())) { 1656 | await middleware.run(payload); 1657 | } 1658 | 1659 | for (const middleware of this.middlewares.getPreMiddlewares(Method.RandomKey)) { 1660 | payload = await middleware[Method.RandomKey](payload); 1661 | } 1662 | 1663 | if (!isPayloadWithData(payload)) { 1664 | payload = await this.provider[Method.RandomKey](payload); 1665 | } 1666 | 1667 | payload.trigger = Trigger.PostProvider; 1668 | 1669 | for (const middleware of Array.from(this.middlewares.values())) { 1670 | await middleware.run(payload); 1671 | } 1672 | 1673 | for (const middleware of this.middlewares.getPostMiddlewares(Method.RandomKey)) { 1674 | payload = await middleware[Method.RandomKey](payload); 1675 | } 1676 | 1677 | this.runBehaviorOnPayloadError(payload); 1678 | 1679 | if (isPayloadWithData(payload)) { 1680 | return payload.data.length ? payload.data : null; 1681 | } 1682 | 1683 | throw this.providerDataFailedError; 1684 | } 1685 | 1686 | /** 1687 | * Removes an element from an array at a key and/or path that matches the given value. 1688 | * @since 2.0.0 1689 | * @param key The key and/or path to the array to remove an element. 1690 | * @param value The value to match to an element in the array. 1691 | * @returns The {@link Josh} instance. 1692 | * 1693 | * @example 1694 | * ```javascript 1695 | * await josh.set('key', ['value']); 1696 | * 1697 | * await josh.remove('key', 'value'); 1698 | * 1699 | * await josh.get('key'); // [] 1700 | * ``` 1701 | */ 1702 | public async remove(key: string, value: Primitive, path?: Path): Promise; 1703 | 1704 | /** 1705 | * Removes an element from an array at a key and/or path that are validated by a hook function. 1706 | * @since 2.0.0 1707 | * @param key The key and/or path to the array to remove an element. 1708 | * @param hook The hook function to validate elements in the array. 1709 | * @returns The {@link Josh} instance. 1710 | * 1711 | * @example 1712 | * ```javascript 1713 | * await josh.set('key', ['value']); 1714 | * 1715 | * await josh.remove('key', (value) => value === 'value'); 1716 | * 1717 | * await josh.get('key'); // [] 1718 | * ``` 1719 | */ 1720 | public async remove(key: string, hook: Payload.Hook, path?: Path): Promise; 1721 | public async remove(key: string, valueOrHook: Primitive | Payload.Hook, path: Path = []): Promise { 1722 | path = this.resolvePath(path); 1723 | 1724 | if (!isFunction(valueOrHook)) { 1725 | if (!isPrimitive(valueOrHook)) { 1726 | throw this.error(CommonIdentifiers.InvalidValueType, { type: 'primitive' }); 1727 | } 1728 | } 1729 | 1730 | let payload: Payload.Remove; 1731 | 1732 | if (isFunction(valueOrHook)) { 1733 | payload = { 1734 | method: Method.Remove, 1735 | errors: [], 1736 | trigger: Trigger.PreProvider, 1737 | type: Payload.Type.Hook, 1738 | key, 1739 | path, 1740 | hook: valueOrHook 1741 | }; 1742 | } else { 1743 | payload = { 1744 | method: Method.Remove, 1745 | errors: [], 1746 | trigger: Trigger.PreProvider, 1747 | type: Payload.Type.Value, 1748 | key, 1749 | path, 1750 | value: valueOrHook 1751 | }; 1752 | } 1753 | 1754 | for (const middleware of Array.from(this.middlewares.values())) { 1755 | await middleware.run(payload); 1756 | } 1757 | 1758 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Remove)) { 1759 | payload = await middleware[Method.Remove](payload); 1760 | } 1761 | 1762 | payload = await this.provider[Method.Remove](payload); 1763 | payload.trigger = Trigger.PostProvider; 1764 | 1765 | for (const middleware of Array.from(this.middlewares.values())) { 1766 | await middleware.run(payload); 1767 | } 1768 | 1769 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Remove)) { 1770 | payload = await middleware[Method.Remove](payload); 1771 | } 1772 | 1773 | this.runBehaviorOnPayloadError(payload); 1774 | 1775 | return this; 1776 | } 1777 | 1778 | /** 1779 | * Sets a value using a key and/or path. 1780 | * @since 2.0.0 1781 | * @param key The key to set the value to. 1782 | * @param value The value to set at the key and/or path. 1783 | * @returns The {@link Josh} instance. 1784 | * 1785 | * @example 1786 | * ```javascript 1787 | * await josh.set('key', { path: 'value' }); 1788 | * ``` 1789 | * 1790 | * @example 1791 | * ```javascript 1792 | * await josh.set('key', 'value', 'path'); 1793 | * await josh.set('key', 'value', ['path']); 1794 | * ``` 1795 | */ 1796 | public async set(key: string, value: Value, path: Path = []): Promise { 1797 | path = this.resolvePath(path); 1798 | 1799 | let payload: Payload.Set = { method: Method.Set, errors: [], trigger: Trigger.PreProvider, key, path, value }; 1800 | 1801 | for (const middleware of Array.from(this.middlewares.values())) { 1802 | await middleware.run(payload); 1803 | } 1804 | 1805 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Set)) { 1806 | payload = await middleware[Method.Set](payload); 1807 | } 1808 | 1809 | payload = await this.provider[Method.Set](payload); 1810 | payload.trigger = Trigger.PostProvider; 1811 | 1812 | for (const middleware of Array.from(this.middlewares.values())) { 1813 | await middleware.run(payload); 1814 | } 1815 | 1816 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Set)) { 1817 | payload = await middleware[Method.Set](payload); 1818 | } 1819 | 1820 | this.runBehaviorOnPayloadError(payload); 1821 | 1822 | return this; 1823 | } 1824 | 1825 | /** 1826 | * Sets multiple keys and/or paths to their respective values. 1827 | * @since 2.0.0 1828 | * @param entries The entries of key/path/value to set. 1829 | * @param overwrite Whether to overwrite existing values (default true). 1830 | * @returns The {@link Josh} instance. 1831 | * 1832 | * @example 1833 | * ```javascript 1834 | * await josh.setMany([ 1835 | * ['key', 'value'], 1836 | * ]); 1837 | * await josh.setMany([ 1838 | * ['key', { path: 'value' }], 1839 | * ]); 1840 | * await josh.setMany([ 1841 | * [{ key: 'key', path: 'path' }, 'value'], 1842 | * ]); 1843 | * ``` 1844 | */ 1845 | public async setMany(entries: [KeyPath, unknown][], overwrite = true): Promise { 1846 | let payload: Payload.SetMany = { 1847 | method: Method.SetMany, 1848 | errors: [], 1849 | trigger: Trigger.PreProvider, 1850 | entries: entries.map(([keyPath, value]) => { 1851 | const [key, path] = this.resolveKeyPath(keyPath); 1852 | 1853 | return { key, path: this.resolvePath(path), value }; 1854 | }), 1855 | overwrite 1856 | }; 1857 | 1858 | for (const middleware of Array.from(this.middlewares.values())) { 1859 | await middleware.run(payload); 1860 | } 1861 | 1862 | for (const middleware of this.middlewares.getPreMiddlewares(Method.SetMany)) { 1863 | payload = await middleware[Method.SetMany](payload); 1864 | } 1865 | 1866 | payload = await this.provider[Method.SetMany](payload); 1867 | payload.trigger = Trigger.PostProvider; 1868 | 1869 | for (const middleware of Array.from(this.middlewares.values())) { 1870 | await middleware.run(payload); 1871 | } 1872 | 1873 | for (const middleware of this.middlewares.getPostMiddlewares(Method.SetMany)) { 1874 | payload = await middleware[Method.SetMany](payload); 1875 | } 1876 | 1877 | this.runBehaviorOnPayloadError(payload); 1878 | 1879 | return this; 1880 | } 1881 | 1882 | /** 1883 | * Get the amount of key/values 1884 | * @since 2.0.0 1885 | * @returns The number amount. 1886 | * 1887 | * @example 1888 | * ```javascript 1889 | * await josh.size(); // 0 1890 | * ``` 1891 | */ 1892 | public async size(): Promise { 1893 | let payload: Payload.Size = { method: Method.Size, errors: [], trigger: Trigger.PreProvider }; 1894 | 1895 | for (const middleware of Array.from(this.middlewares.values())) { 1896 | await middleware.run(payload); 1897 | } 1898 | 1899 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Size)) { 1900 | payload = await middleware[Method.Size](payload); 1901 | } 1902 | 1903 | if (!isPayloadWithData(payload)) { 1904 | payload = await this.provider[Method.Size](payload); 1905 | } 1906 | 1907 | payload.trigger = Trigger.PostProvider; 1908 | 1909 | for (const middleware of Array.from(this.middlewares.values())) { 1910 | await middleware.run(payload); 1911 | } 1912 | 1913 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Size)) { 1914 | payload = await middleware[Method.Size](payload); 1915 | } 1916 | 1917 | this.runBehaviorOnPayloadError(payload); 1918 | 1919 | if (isPayloadWithData(payload)) { 1920 | return payload.data; 1921 | } 1922 | 1923 | throw this.providerDataFailedError; 1924 | } 1925 | 1926 | /** 1927 | * Verify if a path's value matches a value. 1928 | * @since 2.0.0 1929 | * @param path A path to the value for equality check. 1930 | * @param value The value to check equality. 1931 | * 1932 | * @example 1933 | * ```javascript 1934 | * await josh.some('path', 'value'); // false 1935 | * ``` 1936 | * 1937 | * @example 1938 | * ```javascript 1939 | * await josh.set('key', 'value', 'path'); 1940 | * 1941 | * await josh.some('path', 'value'); // true 1942 | * ``` 1943 | * 1944 | */ 1945 | public async some(path: Path, value: Primitive): Promise; 1946 | 1947 | /** 1948 | * Verify if a stored value matches with a hook function, 1949 | * @since 2.0.0 1950 | * @param hook The hook to check equality. 1951 | * 1952 | * @example 1953 | * ```javascript 1954 | * await josh.some((value) => value === 'value'); // false 1955 | * ``` 1956 | * 1957 | * @example 1958 | * ```javascript 1959 | * await josh.set('key', 'value', 'path'); 1960 | * 1961 | * await josh.some('path', 'value'); // true 1962 | * ``` 1963 | */ 1964 | public async some(hook: Payload.Hook): Promise; 1965 | public async some(pathOrHook: Path | Payload.Hook, value?: Primitive): Promise { 1966 | if (!isFunction(pathOrHook)) { 1967 | if (value === undefined) { 1968 | throw this.error(CommonIdentifiers.MissingValue); 1969 | } 1970 | 1971 | if (!isPrimitive(value)) { 1972 | throw this.error(CommonIdentifiers.InvalidValueType, { type: 'primitive' }); 1973 | } 1974 | } 1975 | 1976 | let payload: Payload.Some; 1977 | 1978 | if (isFunction(pathOrHook)) { 1979 | payload = { 1980 | method: Method.Some, 1981 | errors: [], 1982 | trigger: Trigger.PreProvider, 1983 | type: Payload.Type.Hook, 1984 | hook: pathOrHook 1985 | }; 1986 | } else { 1987 | payload = { 1988 | method: Method.Some, 1989 | errors: [], 1990 | trigger: Trigger.PreProvider, 1991 | type: Payload.Type.Value, 1992 | path: this.resolvePath(pathOrHook), 1993 | value 1994 | }; 1995 | } 1996 | 1997 | for (const middleware of Array.from(this.middlewares.values())) { 1998 | await middleware.run(payload); 1999 | } 2000 | 2001 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Some)) { 2002 | payload = await middleware[Method.Some](payload); 2003 | } 2004 | 2005 | if (!isPayloadWithData(payload)) { 2006 | payload = await this.provider[Method.Some](payload); 2007 | } 2008 | 2009 | payload.trigger = Trigger.PostProvider; 2010 | 2011 | for (const middleware of Array.from(this.middlewares.values())) { 2012 | await middleware.run(payload); 2013 | } 2014 | 2015 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Some)) { 2016 | payload = await middleware[Method.Some](payload); 2017 | } 2018 | 2019 | this.runBehaviorOnPayloadError(payload); 2020 | 2021 | if (isPayloadWithData(payload)) { 2022 | return payload.data; 2023 | } 2024 | 2025 | throw this.providerDataFailedError; 2026 | } 2027 | 2028 | /** 2029 | * Update a stored value using a hook function. 2030 | * @since 2.0.0 2031 | * @param key The key to the stored value for updating. 2032 | * @param hook The hook to update the stored value. 2033 | * @returns The updated value or null. 2034 | * 2035 | * @example 2036 | * ```javascript 2037 | * await josh.set('key', 'value'); 2038 | * 2039 | * await josh.update('key', (value) => value.toUpperCase()); // 'VALUE' 2040 | * ``` 2041 | */ 2042 | public async update(key: string, hook: Payload.Hook): Promise { 2043 | let payload: Payload.Update = { method: Method.Update, errors: [], trigger: Trigger.PreProvider, key, hook }; 2044 | 2045 | for (const middleware of Array.from(this.middlewares.values())) { 2046 | await middleware.run(payload); 2047 | } 2048 | 2049 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Update)) { 2050 | payload = await middleware[Method.Update](payload); 2051 | } 2052 | 2053 | payload = await this.provider[Method.Update](payload); 2054 | payload.trigger = Trigger.PostProvider; 2055 | 2056 | for (const middleware of Array.from(this.middlewares.values())) { 2057 | await middleware.run(payload); 2058 | } 2059 | 2060 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Update)) { 2061 | payload = await middleware[Method.Update](payload); 2062 | } 2063 | 2064 | this.runBehaviorOnPayloadError(payload); 2065 | 2066 | return this; 2067 | } 2068 | 2069 | /** 2070 | * Get all stored values. 2071 | * @since 2.0.0 2072 | * @returns An array of stored values. 2073 | * 2074 | * @example 2075 | * ```javascript 2076 | * await josh.set('key', 'value'); 2077 | * await josh.set('anotherKey', 'anotherValue'); 2078 | * 2079 | * await josh.values(); // ['value', 'anotherValue'] 2080 | * ``` 2081 | */ 2082 | public async values(): Promise { 2083 | let payload: Payload.Values = { method: Method.Values, errors: [], trigger: Trigger.PreProvider }; 2084 | 2085 | for (const middleware of Array.from(this.middlewares.values())) { 2086 | await middleware.run(payload); 2087 | } 2088 | 2089 | for (const middleware of this.middlewares.getPreMiddlewares(Method.Values)) { 2090 | payload = await middleware[Method.Values](payload); 2091 | } 2092 | 2093 | if (!isPayloadWithData(payload)) { 2094 | payload = await this.provider[Method.Values](payload); 2095 | } 2096 | 2097 | payload.trigger = Trigger.PostProvider; 2098 | 2099 | for (const middleware of Array.from(this.middlewares.values())) { 2100 | await middleware.run(payload); 2101 | } 2102 | 2103 | for (const middleware of this.middlewares.getPostMiddlewares(Method.Values)) { 2104 | payload = await middleware[Method.Values](payload); 2105 | } 2106 | 2107 | this.runBehaviorOnPayloadError(payload); 2108 | 2109 | if (isPayloadWithData(payload)) { 2110 | return payload.data; 2111 | } 2112 | 2113 | throw this.providerDataFailedError; 2114 | } 2115 | 2116 | /** 2117 | * Import exported data to json 2118 | * @since 2.0.0 2119 | * @param options The options to import data. 2120 | * @returns The {@link Josh} instance. 2121 | * 2122 | * @example 2123 | * ```javascript 2124 | * const { readFileSync } = require('fs'); 2125 | * 2126 | * const json = JSON.parse(readFileSync("./export.json")) 2127 | * await josh.import({ json }); 2128 | * ``` 2129 | */ 2130 | public async import(options: Josh.ImportOptions): Promise { 2131 | let { json, overwrite, clear } = options; 2132 | 2133 | if (this.isLegacyExportJSON(json)) { 2134 | process.emitWarning(this.error(Josh.Identifiers.LegacyDeprecation).message); 2135 | json = this.convertLegacyExportJSON(json); 2136 | } 2137 | 2138 | if (clear) { 2139 | await this.provider[Method.Clear]({ method: Method.Clear, errors: [] }); 2140 | } 2141 | 2142 | await this.provider[Method.SetMany]({ 2143 | method: Method.SetMany, 2144 | errors: [], 2145 | entries: json.entries.map(([key, value]) => ({ key, path: [], value })), 2146 | overwrite: overwrite ?? false 2147 | }); 2148 | 2149 | return this; 2150 | } 2151 | 2152 | /** 2153 | * Exports all data from the provider. 2154 | * @since 2.0.0 2155 | * @returns The exported data json object. 2156 | * 2157 | * @example 2158 | * ```javascript 2159 | * const { writeFileSync } = require('fs'); 2160 | * 2161 | * const json = await josh.export(); 2162 | * writeFileSync("./export.json", JSON.stringify(json)); 2163 | * ``` 2164 | */ 2165 | public async export(): Promise> { 2166 | return { 2167 | name: this.name, 2168 | version: Josh.version, 2169 | exportedTimestamp: Date.now(), 2170 | entries: await this.entries(Bulk.TwoDimensionalArray) 2171 | }; 2172 | } 2173 | 2174 | /** 2175 | * Convert bulk data. 2176 | * @since 2.0.0 2177 | * @private 2178 | * @param data The data to convert. 2179 | * @param returnBulkType The return bulk type. Defaults to {@link Josh.Options.defaultBulkType} or {@link Bulk.Object} 2180 | * @returns The bulk data. 2181 | */ 2182 | private convertBulkData = Bulk.Object>( 2183 | data: ReturnBulk[Bulk.Object], 2184 | returnBulkType?: K 2185 | ): ReturnBulk[K] { 2186 | const { defaultBulkType } = this.options; 2187 | 2188 | switch (returnBulkType ?? defaultBulkType) { 2189 | case Bulk.Object: 2190 | return data; 2191 | 2192 | case Bulk.Map: 2193 | return new Map(Object.entries(data)); 2194 | 2195 | case Bulk.OneDimensionalArray: 2196 | return Object.values(data); 2197 | 2198 | case Bulk.TwoDimensionalArray: 2199 | return Object.entries(data); 2200 | 2201 | default: 2202 | return data; 2203 | } 2204 | } 2205 | 2206 | private runBehaviorOnPayloadError(payload: Payload): void { 2207 | if (payload.errors.length) { 2208 | const { behaviorOnPayloadError } = this.options; 2209 | 2210 | if (behaviorOnPayloadError !== undefined && behaviorOnPayloadError >= Josh.ErrorBehavior.Log) { 2211 | if (payload.errors.length === 1 && behaviorOnPayloadError === Josh.ErrorBehavior.Throw) { 2212 | throw payload.errors[0]; 2213 | } else { 2214 | for (const error of payload.errors) { 2215 | console.error(error); 2216 | } 2217 | 2218 | if (behaviorOnPayloadError === Josh.ErrorBehavior.Throw) { 2219 | throw this.error({ identifier: Josh.Identifiers.MultipleError, errors: payload.errors }); 2220 | } 2221 | } 2222 | } 2223 | } 2224 | } 2225 | 2226 | /** 2227 | * Resolves an identifier or options to an error. 2228 | * @since 2.0.0 2229 | * @param options The options for resolving the error. 2230 | * @param metadata The metadata for the error. 2231 | * @returns The resolved error. 2232 | */ 2233 | private error(options: string | JoshErrorOptions, metadata: Record = {}): JoshError { 2234 | if (typeof options === 'string') { 2235 | return new JoshError({ identifier: options, errors: [], message: this.resolveIdentifier(options, metadata) }); 2236 | } 2237 | 2238 | if ('message' in options) { 2239 | return new JoshError(options); 2240 | } 2241 | 2242 | return new JoshError({ ...options, message: this.resolveIdentifier(options.identifier, metadata) }); 2243 | } 2244 | 2245 | /** 2246 | * Convert a legacy export json object to a new export json object. 2247 | * @param json 2248 | * @returns 2249 | */ 2250 | private convertLegacyExportJSON(json: Josh.LegacyExportJSON): Josh.ExportJSON { 2251 | const { name, version, exportDate, keys } = json; 2252 | 2253 | return { name, version, exportedTimestamp: exportDate, entries: keys.map<[string, StoredValue]>(({ key, value }) => [key, value]) }; 2254 | } 2255 | 2256 | /** 2257 | * Check if a json object is a legacy export json object. 2258 | * @param json The json object to check. 2259 | * @returns Whether the json object is a legacy export json object. 2260 | */ 2261 | private isLegacyExportJSON( 2262 | json: Josh.ExportJSON | Josh.LegacyExportJSON 2263 | ): json is Josh.LegacyExportJSON { 2264 | return 'exportDate' in json && 'keys' in json; 2265 | } 2266 | 2267 | /** 2268 | * Resolve an identifier. 2269 | * @param identifier The identifier to resolve. 2270 | * @param metadata The metadata for the identifier. 2271 | * @returns The resolved identifier. 2272 | */ 2273 | private resolveIdentifier(identifier: string, metadata: Record = {}): string { 2274 | const result = resolveCommonIdentifier(identifier, metadata); 2275 | 2276 | if (result !== null) { 2277 | return result; 2278 | } 2279 | 2280 | switch (identifier) { 2281 | case Josh.Identifiers.InvalidMiddleware: 2282 | return 'The middleware must extend the exported "Middleware" class.'; 2283 | 2284 | case Josh.Identifiers.InvalidProvider: 2285 | return 'The "provider" option must extend the exported "JoshProvider" class to ensure compatibility, but continuing anyway.'; 2286 | 2287 | case Josh.Identifiers.LegacyDeprecation: 2288 | return 'You have imported data from a deprecated legacy format. This will be removed in the next semver major version.'; 2289 | 2290 | case Josh.Identifiers.MissingName: 2291 | return 'The "name" option is required to initiate a Josh instance.'; 2292 | 2293 | case Josh.Identifiers.MultipleError: 2294 | return 'Josh has encountered multiple errors. Please check the "errors" property of this error.'; 2295 | 2296 | case Josh.Identifiers.ProviderDataFailed: 2297 | return 'The provider failed to return data.'; 2298 | 2299 | case Josh.Identifiers.UseMiddlewareHookNotFound: 2300 | return 'The "hook" parameter for middleware was not found.'; 2301 | } 2302 | 2303 | throw new Error(`Unknown identifier: ${identifier}`); 2304 | } 2305 | 2306 | /** 2307 | * Resolves a key and/or path for universal use. 2308 | * @param keyPath The key or path to resolve. 2309 | * @returns The resolved key and path. 2310 | */ 2311 | private resolveKeyPath(keyPath: KeyPath): [string, string[]] { 2312 | if (typeof keyPath === 'object') { 2313 | return [keyPath.key, this.resolvePath(keyPath.path ?? [])]; 2314 | } 2315 | 2316 | const [key, ...path] = keyPath.split('.'); 2317 | 2318 | return [key, path]; 2319 | } 2320 | 2321 | /** 2322 | * Resolves a path for universal use. 2323 | * @param path The path to resolve. 2324 | * @returns The resolved path. 2325 | */ 2326 | private resolvePath(path: Path): string[] { 2327 | return Array.isArray(path) ? path : path.replace(/\[/g, '.').replace(/\]/g, '').split('.').filter(Boolean); 2328 | } 2329 | 2330 | /** 2331 | * The current version of {@link Josh} 2332 | * @since 2.0.0 2333 | */ 2334 | public static version = '[VI]{{inject}}[/VI]'; 2335 | 2336 | /** 2337 | * A static method to create multiple instances of {@link Josh}. 2338 | * @since 2.0.0 2339 | * @param names The names to give each instance of {@link Josh} 2340 | * @param options The options to give all the instances. 2341 | * @returns The created instances. 2342 | */ 2343 | public static multi = Record>( 2344 | names: string[], 2345 | options: Omit = {} 2346 | ): Instances { 2347 | // @ts-expect-error 2345 2348 | return names.reduce((instances, name) => ({ ...instances, [name]: new Josh({ ...options, name }) }), {}); 2349 | } 2350 | } 2351 | 2352 | export namespace Josh { 2353 | /** 2354 | * The options for {@link Josh}. 2355 | * @since 2.0.0 2356 | */ 2357 | export interface Options { 2358 | /** 2359 | * The name for the Josh instance. 2360 | * @since 2.0.0 2361 | */ 2362 | name?: string; 2363 | 2364 | /** 2365 | * The provider instance. 2366 | * @since 2.0.0 2367 | */ 2368 | provider?: JoshProvider; 2369 | 2370 | /** 2371 | * The middleware to use. 2372 | * @since 2.0.0 2373 | */ 2374 | middlewares?: JoshMiddleware[]; 2375 | 2376 | /** 2377 | * The context data for the auto-ensure middleware 2378 | * @since 2.0.0 2379 | */ 2380 | autoEnsure?: AutoEnsureMiddleware.ContextData; 2381 | 2382 | /** 2383 | * The behavior when encountering errors. 2384 | * @since 2.0.0 2385 | */ 2386 | behaviorOnPayloadError?: ErrorBehavior; 2387 | 2388 | /** 2389 | * The default bulk type to return in bulk supported methods. 2390 | * @since 2.0.0 2391 | */ 2392 | defaultBulkType?: Bulk; 2393 | } 2394 | 2395 | /** 2396 | * The options for the {@link Josh.use} method. 2397 | * @since 2.0.0 2398 | */ 2399 | export interface UseMiddlewareOptions { 2400 | /** 2401 | * The name for the middleware. 2402 | * @since 2.0.0 2403 | */ 2404 | name: string; 2405 | 2406 | /** 2407 | * The position for the middleware. 2408 | * @since 2.0.0 2409 | */ 2410 | position?: number; 2411 | 2412 | /** 2413 | * The trigger for the middleware hook. 2414 | * @since 2.0.0 2415 | */ 2416 | trigger?: Trigger; 2417 | 2418 | /** 2419 | * The trigger for the middleware hook. 2420 | * @since 2.0.0 2421 | */ 2422 | method?: Method; 2423 | } 2424 | 2425 | export interface ExportJSON { 2426 | /** 2427 | * The name of exported data. 2428 | * @since 2.0.0 2429 | */ 2430 | name: string; 2431 | 2432 | /** 2433 | * The version of Josh used to export the data. 2434 | * @since 2.0.0 2435 | */ 2436 | version: string; 2437 | 2438 | /** 2439 | * The timestamp of when the data was exported. 2440 | * @since 2.0.0 2441 | */ 2442 | exportedTimestamp: number; 2443 | 2444 | /** 2445 | * The exported data entries. 2446 | * @since 2.0.0 2447 | */ 2448 | entries: [string, StoredValue][]; 2449 | } 2450 | 2451 | export interface LegacyExportJSON { 2452 | /** 2453 | * The name of exported data. 2454 | * @since 2.0.0 2455 | */ 2456 | name: string; 2457 | 2458 | /** 2459 | * The version of Josh or Enmap used to export the data. 2460 | * @since 2.0.0 2461 | */ 2462 | version: string; 2463 | 2464 | /** 2465 | * The timestamp of when the data was exported. 2466 | * @since 2.0.0 2467 | */ 2468 | exportDate: number; 2469 | 2470 | /** 2471 | * The exported data. 2472 | * @since 2.0.0 2473 | */ 2474 | keys: { key: string; value: StoredValue }[]; 2475 | } 2476 | 2477 | export interface ImportOptions { 2478 | /** 2479 | * The data to import. 2480 | * @since 2.0.0 2481 | */ 2482 | json: ExportJSON | LegacyExportJSON; 2483 | 2484 | /** 2485 | * Whether to overwrite existing data. 2486 | * @since 2.0.0 2487 | */ 2488 | overwrite?: boolean; 2489 | 2490 | /** 2491 | * Whether to clear all data before importing. 2492 | * @since 2.0.0 2493 | */ 2494 | clear?: boolean; 2495 | } 2496 | export interface SetManyOptions extends KeyPathJSON { 2497 | /** 2498 | * The value to set. 2499 | * @since 2.0.0 2500 | */ 2501 | value: Value; 2502 | } 2503 | 2504 | export interface RandomOptions { 2505 | /** 2506 | * The amount of values to get. 2507 | * @since 2.0.0 2508 | */ 2509 | count?: number; 2510 | 2511 | /** 2512 | * Whether the values should be unique. 2513 | * @since 2.0.0 2514 | */ 2515 | unique?: boolean; 2516 | } 2517 | 2518 | export enum ErrorBehavior { 2519 | None, 2520 | 2521 | Log, 2522 | 2523 | Throw 2524 | } 2525 | 2526 | export enum Identifiers { 2527 | InvalidMiddleware = 'InvalidMiddleware', 2528 | 2529 | InvalidProvider = 'invalidProvider', 2530 | 2531 | LegacyDeprecation = 'legacyDeprecation', 2532 | 2533 | MissingName = 'missingName', 2534 | 2535 | MultipleError = 'multipleError', 2536 | 2537 | ProviderDataFailed = 'providerDataFailed', 2538 | 2539 | UseMiddlewareHookNotFound = 'useMiddlewareHookNotFound' 2540 | } 2541 | } 2542 | 2543 | export enum Bulk { 2544 | Object, 2545 | 2546 | Map, 2547 | 2548 | OneDimensionalArray, 2549 | 2550 | TwoDimensionalArray 2551 | } 2552 | 2553 | export interface ReturnBulk { 2554 | [Bulk.Object]: Record; 2555 | 2556 | [Bulk.Map]: Map; 2557 | 2558 | [Bulk.OneDimensionalArray]: Value[]; 2559 | 2560 | [Bulk.TwoDimensionalArray]: [string, Value][]; 2561 | 2562 | [K: string]: Record | Map | Value[] | [string, Value][]; 2563 | } 2564 | -------------------------------------------------------------------------------- /src/lib/structures/JoshError.ts: -------------------------------------------------------------------------------- 1 | import type { JoshProviderError } from '@joshdb/provider'; 2 | 3 | /** 4 | * The base class for errors in `Josh` 5 | * @since 2.0.0 6 | */ 7 | export class JoshError extends Error { 8 | /** 9 | * The identifier for this error. 10 | * @since 2.0.0 11 | */ 12 | public identifier: string; 13 | 14 | /** 15 | * The errors for this error. 16 | * @since 2.0.0 17 | */ 18 | public errors: JoshProviderError[]; 19 | 20 | public constructor(options: JoshErrorOptions) { 21 | const { name, message, identifier, errors } = options; 22 | 23 | super(message); 24 | this.name = name ?? 'JoshError'; 25 | this.identifier = identifier; 26 | this.errors = errors; 27 | } 28 | } 29 | 30 | /** 31 | * The options for `JoshError` 32 | * @since 2.0.0 33 | */ 34 | export interface JoshErrorOptions { 35 | /** 36 | * The name for this error. 37 | * @since 2.0.0 38 | */ 39 | name?: string; 40 | 41 | /** 42 | * The identifier for this error. 43 | * @since 2.0.0 44 | */ 45 | identifier: string; 46 | 47 | /** 48 | * The errors for this error. 49 | * @since 2.0.0 50 | */ 51 | errors: JoshProviderError[]; 52 | 53 | /** 54 | * The message for this error. 55 | * @since 2.0.0 56 | */ 57 | message?: string; 58 | } 59 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "../dist", 6 | "tsBuildInfoFile": "../dist/.tsbuildinfo" 7 | }, 8 | "include": ["."] 9 | } 10 | -------------------------------------------------------------------------------- /tests/lib/structures/Josh.test.ts: -------------------------------------------------------------------------------- 1 | import { AutoEnsureMiddleware } from '@joshdb/auto-ensure'; 2 | import type { Semver } from '@joshdb/provider'; 3 | import { ApplyMiddlewareOptions, CommonIdentifiers, JoshMiddleware, MathOperator, Method, Payload, Trigger } from '@joshdb/provider'; 4 | import type { NonNullObject } from '@sapphire/utilities'; 5 | import { Bulk, Josh, JoshError } from '../../../src'; 6 | 7 | @ApplyMiddlewareOptions({ name: 'test' }) 8 | class TestMiddleware extends JoshMiddleware { 9 | public version: Semver = { major: 2, minor: 0, patch: 0 }; 10 | 11 | public override run

(payload: P): P { 12 | if (payload.trigger === Trigger.PostProvider) { 13 | if (TestMiddleware.errorCount === 1) { 14 | payload.errors.push(this.error(CommonIdentifiers.MissingValue)); 15 | 16 | TestMiddleware.errorCount = 0; 17 | 18 | return payload; 19 | } 20 | 21 | if (TestMiddleware.errorCount === 2) { 22 | payload.errors.push(this.error(CommonIdentifiers.MissingValue)); 23 | payload.errors.push(this.error(CommonIdentifiers.InvalidCount)); 24 | 25 | TestMiddleware.errorCount = 0; 26 | 27 | return payload; 28 | } 29 | 30 | if (TestMiddleware.deleteData) { 31 | Reflect.deleteProperty(payload, 'data'); 32 | 33 | TestMiddleware.deleteData = false; 34 | } 35 | } 36 | 37 | return payload; 38 | } 39 | 40 | public fetchVersion() { 41 | return this.version; 42 | } 43 | 44 | public static errorCount: 0 | 1 | 2 = 0; 45 | 46 | public static deleteData = false; 47 | } 48 | 49 | describe('Josh', () => { 50 | describe('is a class', () => { 51 | test('GIVEN typeof Josh THEN returns function', () => { 52 | expect(typeof Josh).toBe('function'); 53 | }); 54 | 55 | test('GIVEN typeof ...prototype THEN returns object', () => { 56 | expect(typeof Josh.prototype).toBe('object'); 57 | }); 58 | }); 59 | 60 | describe('can be instantiated class', () => { 61 | beforeAll(() => { 62 | process.emitWarning = () => undefined; 63 | }); 64 | 65 | test('GIVEN class Josh THEN returns Josh', () => { 66 | const josh = new Josh({ name: 'name' }); 67 | 68 | expect(josh).toBeInstanceOf(Josh); 69 | }); 70 | 71 | test('GIVEN class Josh w/o name THEN throws error', () => { 72 | expect(() => new Josh({})).toThrowError('The "name" option is required to initiate a Josh instance'); 73 | }); 74 | 75 | test('GIVEN class Josh w/ autoEnsure THEN returns middleware size 1', () => { 76 | const josh = new Josh({ name: 'name', autoEnsure: { defaultValue: { foo: 'bar' } } }); 77 | 78 | expect(josh.middlewares.size).toBe(1); 79 | }); 80 | 81 | test('GIVEN class Josh w/ empty middlewares THEN returns instance', () => { 82 | const josh = new Josh({ name: 'name', middlewares: [] }); 83 | 84 | expect(josh.middlewares.size).toBe(0); 85 | }); 86 | 87 | test('GIVEN class Josh w/ middlewares THEN returns instance', () => { 88 | const josh = new Josh<{ test: boolean }>({ 89 | name: 'name', 90 | middlewares: [new AutoEnsureMiddleware<{ test: boolean }>({ defaultValue: { test: false } })] 91 | }); 92 | 93 | expect(josh.middlewares.size).toBe(1); 94 | }); 95 | 96 | test('GIVEN provider with invalid instance THEN emits warning', () => { 97 | const spy = vi.spyOn(process, 'emitWarning'); 98 | // @ts-expect-error - this is a test 99 | const josh = new Josh({ name: 'name', provider: new Map() }); 100 | 101 | expect(spy).toHaveBeenCalledOnce(); 102 | 103 | spy.mockClear(); 104 | }); 105 | 106 | test('GIVEN provider with invalid instance THEN emits warning', () => { 107 | const spy = vi.spyOn(process, 'emitWarning'); 108 | // @ts-expect-error - this is a test 109 | const josh = new Josh({ name: 'name', middlewares: [new Map()] }); 110 | 111 | expect(spy).toHaveBeenCalledOnce(); 112 | }); 113 | }); 114 | 115 | describe('middleware', () => { 116 | describe('use', () => { 117 | test('GIVEN josh THEN add middleware', () => { 118 | const josh = new Josh({ name: 'name' }); 119 | 120 | expect(josh.middlewares.size).toBe(0); 121 | 122 | josh.use(new AutoEnsureMiddleware({ defaultValue: { test: false } }) as unknown as JoshMiddleware); 123 | 124 | expect(josh.middlewares.size).toBe(1); 125 | }); 126 | 127 | test('GIVEN josh w/ hook middleware THEN add middleware', () => { 128 | const josh = new Josh({ name: 'name' }); 129 | 130 | expect(josh.middlewares.size).toBe(0); 131 | 132 | josh.use({ name: 'test' }, (payload) => payload); 133 | 134 | expect(josh.middlewares.get('test')?.conditions).toEqual({ [Trigger.PreProvider]: [], [Trigger.PostProvider]: [] }); 135 | 136 | expect(josh.middlewares.size).toBe(1); 137 | }); 138 | 139 | test('GIVEN josh w/ hook middleware w/ trigger THEN add middleware', () => { 140 | const josh = new Josh({ name: 'name' }); 141 | 142 | expect(josh.middlewares.size).toBe(0); 143 | 144 | josh.use({ name: 'test', trigger: Trigger.PreProvider, method: Method.Dec }, (payload) => payload); 145 | 146 | expect(josh.middlewares.size).toBe(1); 147 | expect(josh.middlewares.get('test')?.conditions[Trigger.PreProvider]).toEqual(['dec']); 148 | expect(josh.middlewares.get('test')?.conditions[Trigger.PostProvider]).toEqual([]); 149 | }); 150 | 151 | test('GIVEN josh w/ hook middleware w/ trigger THEN add middleware', () => { 152 | const josh = new Josh({ name: 'name' }); 153 | 154 | expect(josh.middlewares.size).toBe(0); 155 | 156 | josh.use({ name: 'test', trigger: Trigger.PostProvider, method: Method.Dec }, (payload) => payload); 157 | 158 | expect(josh.middlewares.size).toBe(1); 159 | expect(josh.middlewares.get('test')?.conditions[Trigger.PostProvider]).toEqual(['dec']); 160 | expect(josh.middlewares.get('test')?.conditions[Trigger.PreProvider]).toEqual([]); 161 | }); 162 | 163 | test('GIVEN josh w/ invalid hook middleware THEN add middleware', () => { 164 | const josh = new Josh({ name: 'name' }); 165 | 166 | expect(josh.middlewares.size).toBe(0); 167 | 168 | // @ts-expect-error this is a test 169 | expect(() => josh.use({ name: 'test' })).toThrowError('The "hook" parameter for middleware was not found.'); 170 | 171 | expect(josh.middlewares.size).toBe(0); 172 | }); 173 | }); 174 | }); 175 | 176 | describe('resolvePath', () => { 177 | const josh = new Josh({ name: 'name' }); 178 | 179 | test('resolvePath', () => { 180 | expect(josh['resolvePath']('test.path')).toEqual(['test', 'path']); 181 | expect(josh['resolvePath']('test.path.to.file')).toEqual(['test', 'path', 'to', 'file']); 182 | expect(josh['resolvePath']('test.path.to[0].index')).toEqual(['test', 'path', 'to', '0', 'index']); 183 | expect(josh['resolvePath']('test[0].path.to[1].index')).toEqual(['test', '0', 'path', 'to', '1', 'index']); 184 | }); 185 | }); 186 | 187 | describe('can manipulate data', () => { 188 | const josh = new Josh({ name: 'test', behaviorOnPayloadError: Josh.ErrorBehavior.Throw, middlewares: [new TestMiddleware({})] }); 189 | const errors = [josh.provider['error'](CommonIdentifiers.MissingValue), josh.provider['error'](CommonIdentifiers.InvalidCount)]; 190 | const multipleError = josh['error']({ identifier: Josh.Identifiers.MultipleError, errors }); 191 | 192 | beforeAll(async () => { 193 | await josh.init(); 194 | 195 | console.error = () => undefined; 196 | }); 197 | 198 | beforeEach(async () => { 199 | await josh.clear(); 200 | }); 201 | 202 | afterAll(async () => { 203 | await josh.clear(); 204 | }); 205 | 206 | describe(Method.AutoKey, () => { 207 | test('GIVEN payload w/ error THEN throws error', async () => { 208 | TestMiddleware.errorCount = 1; 209 | 210 | await expect(josh.autoKey()).rejects.toThrowError(errors[0]); 211 | }); 212 | 213 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 214 | TestMiddleware.errorCount = 2; 215 | 216 | await expect(josh.autoKey()).rejects.toThrowError(multipleError); 217 | }); 218 | 219 | test('GIVEN payload w/o data property THEN throws error', async () => { 220 | TestMiddleware.deleteData = true; 221 | 222 | await expect(josh.autoKey()).rejects.toThrowError(josh['providerDataFailedError']); 223 | }); 224 | 225 | test('GIVEN ... THEN returns data w/ generated key as data AND increments autoKeyCount', async () => { 226 | const data = await josh.autoKey(); 227 | 228 | expect(typeof data).toBe('string'); 229 | }); 230 | 231 | test('each value of autoKey should be unique', async () => { 232 | const arr = await Promise.all([...Array(10)].map(async () => josh.autoKey())); 233 | const isUnique = new Set(arr).size === arr.length; 234 | 235 | expect(isUnique).toBe(true); 236 | }); 237 | }); 238 | 239 | describe(Method.Clear, () => { 240 | test('GIVEN payload w/ error THEN throws error', async () => { 241 | TestMiddleware.errorCount = 1; 242 | 243 | await expect(josh.clear()).rejects.toThrowError(errors[0]); 244 | }); 245 | 246 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 247 | TestMiddleware.errorCount = 2; 248 | 249 | await expect(josh.clear()).rejects.toThrowError(multipleError); 250 | }); 251 | 252 | test('GIVEN josh w/o data THEN provider data cleared', async () => { 253 | const sizeBefore = await josh.size(); 254 | 255 | expect(sizeBefore).toBe(0); 256 | 257 | const result = await josh.clear(); 258 | 259 | expect(result).toBeInstanceOf(Josh); 260 | 261 | const sizeAfter = await josh.size(); 262 | 263 | expect(sizeAfter).toBe(0); 264 | }); 265 | 266 | test('GIVEN josh w/ data THEN provider data cleared', async () => { 267 | await josh.set('key', 'value'); 268 | 269 | const sizeBefore = await josh.size(); 270 | 271 | expect(sizeBefore).toBe(1); 272 | 273 | const result = await josh.clear(); 274 | 275 | expect(result).toBeInstanceOf(Josh); 276 | 277 | const sizeAfter = await josh.size(); 278 | 279 | expect(sizeAfter).toBe(0); 280 | }); 281 | }); 282 | 283 | describe(Method.Dec, () => { 284 | test('GIVEN payload w/ error THEN throws error', async () => { 285 | await josh.set('key', 1); 286 | 287 | TestMiddleware.errorCount = 1; 288 | 289 | await expect(josh.dec('key')).rejects.toThrowError(errors[0]); 290 | }); 291 | 292 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 293 | await josh.set('key', 1); 294 | 295 | TestMiddleware.errorCount = 2; 296 | 297 | await expect(josh.dec('key')).rejects.toThrowError(multipleError); 298 | }); 299 | 300 | test('GIVEN josh w/ number at key THEN decremented number at key', async () => { 301 | await josh.set('key', 1); 302 | 303 | const result = await josh.dec('key'); 304 | 305 | expect(result).toBeInstanceOf(Josh); 306 | 307 | const value = await josh.get('key'); 308 | 309 | expect(value).toBe(0); 310 | }); 311 | 312 | test('GIVEN josh w/ number at path THEN decremented number at path', async () => { 313 | await josh.set('key', 1, ['path']); 314 | 315 | const result = await josh.dec('key', ['path']); 316 | 317 | expect(result).toBeInstanceOf(Josh); 318 | 319 | const value = await josh.get('key', ['path']); 320 | 321 | expect(value).toBe(0); 322 | }); 323 | }); 324 | 325 | describe(Method.Delete, () => { 326 | test('GIVEN payload w/ error THEN throws error', async () => { 327 | TestMiddleware.errorCount = 1; 328 | 329 | await expect(josh.delete('key')).rejects.toThrowError(errors[0]); 330 | }); 331 | 332 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 333 | TestMiddleware.errorCount = 2; 334 | 335 | await expect(josh.delete('key')).rejects.toThrowError(multipleError); 336 | }); 337 | 338 | test('GIVEN josh w/ value at key THEN deletes value at key', async () => { 339 | await josh.set('key', 'value'); 340 | 341 | const hasBefore = await josh.has('key'); 342 | 343 | expect(hasBefore).toBe(true); 344 | 345 | const result = await josh.delete('key'); 346 | 347 | expect(result).toBeInstanceOf(Josh); 348 | 349 | const hasAfter = await josh.has('key'); 350 | 351 | expect(hasAfter).toBe(false); 352 | }); 353 | 354 | test('GIVEN josh w/ value at path THEN deletes value at path', async () => { 355 | await josh.set('key', 'value'); 356 | 357 | const hasBefore = await josh.has('key'); 358 | 359 | expect(hasBefore).toBe(true); 360 | 361 | const result = await josh.delete('key', ['path']); 362 | 363 | expect(result).toBeInstanceOf(Josh); 364 | 365 | const hasAfter = await josh.has('key', ['path']); 366 | 367 | expect(hasAfter).toBe(false); 368 | }); 369 | 370 | test('GIVEN josh w/ value at nested path THEN deletes value at nested path', async () => { 371 | await josh.set('key', 'value', ['path', 'nested']); 372 | 373 | const hasBefore = await josh.has('key', ['path', 'nested']); 374 | 375 | expect(hasBefore).toBe(true); 376 | 377 | const result = await josh.delete('key', ['path', 'nested']); 378 | 379 | expect(result).toBeInstanceOf(Josh); 380 | 381 | const hasAfter = await josh.has('key', ['path', 'nested']); 382 | 383 | expect(hasAfter).toBe(false); 384 | }); 385 | }); 386 | 387 | describe(Method.DeleteMany, () => { 388 | test('GIVEN payload w/ error THEN throws error', async () => { 389 | TestMiddleware.errorCount = 1; 390 | 391 | await expect(josh.deleteMany([])).rejects.toThrowError(errors[0]); 392 | }); 393 | 394 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 395 | TestMiddleware.errorCount = 2; 396 | 397 | await expect(josh.deleteMany([])).rejects.toThrowError(multipleError); 398 | }); 399 | 400 | test('GIVEN josh w/ value at key THEN deletes value at key', async () => { 401 | await josh.set('keymany', 'value'); 402 | 403 | const hasBefore = await josh.has('keymany'); 404 | 405 | expect(hasBefore).toBe(true); 406 | 407 | const result = await josh.deleteMany(['keymany']); 408 | 409 | expect(result).toBeInstanceOf(Josh); 410 | 411 | const hasAfter = await josh.has('keymany'); 412 | 413 | expect(hasAfter).toBe(false); 414 | }); 415 | 416 | test('GIVEN josh w/ multiple result at keys THEN deletes result at keys', async () => { 417 | await josh.set('keymany', 'value'); 418 | await josh.set('keymany2', 'value'); 419 | 420 | const hasBefore = (await josh.has('keymany')) && (await josh.has('keymany2')); 421 | 422 | expect(hasBefore).toBe(true); 423 | 424 | const result = await josh.deleteMany(['keymany', 'keymany2']); 425 | 426 | expect(result).toBeInstanceOf(Josh); 427 | 428 | const hasAfter = (await josh.has('keymany')) && (await josh.has('keymany2')); 429 | 430 | expect(hasAfter).toBe(false); 431 | }); 432 | }); 433 | 434 | describe(Method.Each, () => { 435 | test('GIVEN payload w/ error THEN throws error', async () => { 436 | TestMiddleware.errorCount = 1; 437 | 438 | await expect(josh.each((value) => value === 'value')).rejects.toThrowError(errors[0]); 439 | }); 440 | 441 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 442 | TestMiddleware.errorCount = 2; 443 | 444 | await expect(josh.each((value) => value === 'value')).rejects.toThrowError(multipleError); 445 | }); 446 | 447 | test('GIVEN josh w/o data THEN loops 0 times', async () => { 448 | const mockCallback = vi.fn(() => true); 449 | const result = await josh.each(mockCallback); 450 | 451 | expect(result).toBeInstanceOf(Josh); 452 | expect(mockCallback.mock.calls.length).toBe(0); 453 | }); 454 | 455 | test('GIVEN josh w/ data THEN loops x times THEN clears', async () => { 456 | const mockCallback = vi.fn(() => true); 457 | 458 | await josh.set('key1', 'value1'); 459 | await josh.set('key2', 'value2'); 460 | await josh.set('key3', 'value3'); 461 | 462 | const result = await josh.each(mockCallback); 463 | 464 | expect(result).toBeInstanceOf(Josh); 465 | expect(mockCallback.mock.calls.length).toBe(3); 466 | expect(mockCallback.mock.calls).toContainEqual(['value1', 'key1']); 467 | expect(mockCallback.mock.calls).toContainEqual(['value2', 'key2']); 468 | expect(mockCallback.mock.calls).toContainEqual(['value3', 'key3']); 469 | }); 470 | }); 471 | 472 | describe(Method.Ensure, () => { 473 | test('GIVEN payload w/ error THEN throws error', async () => { 474 | TestMiddleware.errorCount = 1; 475 | 476 | await expect(josh.ensure('key', 'defaultValue')).rejects.toThrowError(errors[0]); 477 | }); 478 | 479 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 480 | TestMiddleware.errorCount = 2; 481 | 482 | await expect(josh.ensure('key', 'defaultValue')).rejects.toThrowError(multipleError); 483 | }); 484 | 485 | test('GIVEN payload w/o data property THEN throws error', async () => { 486 | TestMiddleware.deleteData = true; 487 | 488 | await expect(josh.ensure('key', 'defaultValue')).rejects.toThrowError(josh['providerDataFailedError']); 489 | }); 490 | 491 | test('GIVEN josh w/o data at key THEN returns data as defaultValue AND sets default value at key', async () => { 492 | const sizeBefore = await josh.size(); 493 | 494 | expect(sizeBefore).toBe(0); 495 | 496 | const result = await josh.ensure('key', 'defaultValue'); 497 | 498 | expect(result).toBe('defaultValue'); 499 | 500 | const sizeAfter = await josh.size(); 501 | 502 | expect(sizeAfter).toBe(1); 503 | }); 504 | 505 | test('GIVEN josh w/ value at key THEN returns data as value at key', async () => { 506 | await josh.set('key', 'value'); 507 | 508 | const result = await josh.ensure('key', 'defaultValue'); 509 | 510 | expect(result).toBe('value'); 511 | }); 512 | }); 513 | 514 | describe(Method.Entries, () => { 515 | test('GIVEN payload w/ error THEN throws error', async () => { 516 | TestMiddleware.errorCount = 1; 517 | 518 | await expect(josh.entries()).rejects.toThrowError(errors[0]); 519 | }); 520 | 521 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 522 | TestMiddleware.errorCount = 2; 523 | 524 | await expect(josh.entries()).rejects.toThrowError(multipleError); 525 | }); 526 | 527 | test('GIVEN payload w/o data property THEN throws error', async () => { 528 | TestMiddleware.deleteData = true; 529 | 530 | await expect(josh.entries()).rejects.toThrowError(josh['providerDataFailedError']); 531 | }); 532 | 533 | test('GIVEN josh w/o data THEN returns data w/o data', async () => { 534 | const result = await josh.entries(); 535 | 536 | expect(result).toEqual({}); 537 | }); 538 | 539 | test('GIVEN josh w/ data THEN returns data as Bulk.Object', async () => { 540 | await josh.set('key', 'value'); 541 | 542 | const result = await josh.entries(); 543 | 544 | expect(result).toEqual({ key: 'value' }); 545 | }); 546 | 547 | test('GIVEN josh w/ data THEN returns data as Bulk.Object', async () => { 548 | await josh.set('key', 'value'); 549 | 550 | const result = await josh.entries(Bulk.Object); 551 | 552 | expect(result).toEqual({ key: 'value' }); 553 | }); 554 | 555 | test('GIVEN josh w/ data THEN returns data as Bulk.Map', async () => { 556 | await josh.set('key', 'value'); 557 | 558 | const result = await josh.entries(Bulk.Map); 559 | 560 | expect(result).toBeInstanceOf(Map); 561 | expect(Array.from(result.entries())).toEqual([['key', 'value']]); 562 | }); 563 | 564 | test('GIVEN josh w/ data THEN returns data as Bulk.OneDimensionalArray', async () => { 565 | await josh.set('key', 'value'); 566 | 567 | const result = await josh.entries(Bulk.OneDimensionalArray); 568 | 569 | expect(result).toBeInstanceOf(Array); 570 | expect(result).toEqual(['value']); 571 | }); 572 | 573 | test('GIVEN josh w/ data THEN returns data as Bulk.TwoDimensionalArray', async () => { 574 | await josh.set('key', 'value'); 575 | 576 | const result = await josh.entries(Bulk.TwoDimensionalArray); 577 | 578 | expect(result).toBeInstanceOf(Array); 579 | expect(result).toEqual([['key', 'value']]); 580 | }); 581 | }); 582 | 583 | describe(Method.Every, () => { 584 | test('GIVEN payload w/ error THEN throws error', async () => { 585 | TestMiddleware.errorCount = 1; 586 | 587 | await expect(josh.every((value) => value === 'value')).rejects.toThrowError(errors[0]); 588 | }); 589 | 590 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 591 | TestMiddleware.errorCount = 2; 592 | 593 | await expect(josh.every((value) => value === 'value')).rejects.toThrowError(multipleError); 594 | }); 595 | 596 | test('GIVEN payload w/o data property THEN throws error', async () => { 597 | TestMiddleware.deleteData = true; 598 | 599 | await expect(josh.every((value) => value === 'value')).rejects.toThrowError(josh['providerDataFailedError']); 600 | }); 601 | 602 | describe(Payload.Type.Hook.toString(), () => { 603 | test('GIVEN josh w/o data THEN returns true', async () => { 604 | const result = await josh.every((value) => value === 'value'); 605 | 606 | expect(result).toBe(true); 607 | }); 608 | 609 | test('GIVEN josh w/ data w/o value THEN rejects', async () => { 610 | const result = josh.every('path', undefined); 611 | 612 | await expect(result).rejects.toBeInstanceOf(JoshError); 613 | }); 614 | 615 | test('GIVEN josh w/ data w/ invalid primitive THEN rejects', async () => { 616 | const result = josh.every('path', null); 617 | 618 | await expect(result).rejects.toBeInstanceOf(JoshError); 619 | }); 620 | 621 | test('GIVEN josh w/ data THEN returns true', async () => { 622 | await josh.setMany([ 623 | ['firstKey', 'value'], 624 | ['secondKey', 'value'] 625 | ]); 626 | 627 | const result = await josh.every((value) => value === 'value'); 628 | 629 | expect(result).toBe(true); 630 | }); 631 | 632 | test('GIVEN josh w/ unique data THEN returns false', async () => { 633 | await josh.setMany([ 634 | ['firstKey', 'value'], 635 | ['secondKey', 'not value'] 636 | ]); 637 | 638 | const result = await josh.every((value) => value === 'value'); 639 | 640 | expect(result).toBe(false); 641 | }); 642 | }); 643 | 644 | describe(Payload.Type.Value.toString(), () => { 645 | test('GIVEN josh w/o data THEN returns true', async () => { 646 | const result = await josh.every('path', 'value'); 647 | 648 | expect(result).toBe(true); 649 | }); 650 | 651 | test('GIVEN josh w/ data w/o path THEN returns true', async () => { 652 | await josh.setMany([ 653 | ['firstKey', 'value'], 654 | ['secondKey', 'value'] 655 | ]); 656 | 657 | const result = await josh.every([], 'value'); 658 | 659 | expect(result).toBe(true); 660 | }); 661 | 662 | test('GIVEN josh w/ data THEN returns true', async () => { 663 | await josh.setMany([ 664 | ['firstKey', { path: 'value' }], 665 | ['secondKey', { path: 'value' }] 666 | ]); 667 | 668 | const result = await josh.every(['path'], 'value'); 669 | 670 | expect(result).toBe(true); 671 | }); 672 | }); 673 | }); 674 | 675 | describe(Method.Filter, () => { 676 | test('GIVEN payload w/ error THEN throws error', async () => { 677 | TestMiddleware.errorCount = 1; 678 | 679 | await expect(josh.filter((value) => value === 'value')).rejects.toThrowError(errors[0]); 680 | }); 681 | 682 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 683 | TestMiddleware.errorCount = 2; 684 | 685 | await expect(josh.filter((value) => value === 'value')).rejects.toThrowError(multipleError); 686 | }); 687 | 688 | test('GIVEN payload w/o data property THEN throws error', async () => { 689 | TestMiddleware.deleteData = true; 690 | 691 | await expect(josh.filter((value) => value === 'value')).rejects.toThrowError(josh['providerDataFailedError']); 692 | }); 693 | 694 | describe(Payload.Type.Hook.toString(), () => { 695 | test('GIVEN josh w/o data THEN returns data w/o data from filter', async () => { 696 | const result = await josh.filter((value) => value === 'value'); 697 | 698 | expect(result).toEqual({}); 699 | }); 700 | 701 | test('GIVEN josh w/ data w/o value THEN rejects', async () => { 702 | const result = josh.filter('path', undefined); 703 | 704 | await expect(result).rejects.toBeInstanceOf(JoshError); 705 | }); 706 | 707 | test('GIVEN josh w/ data w/ invalid primitive THEN rejects', async () => { 708 | const result = josh.filter('path', null); 709 | 710 | await expect(result).rejects.toBeInstanceOf(JoshError); 711 | }); 712 | 713 | test('GIVEN josh w/ data THEN returns data from filter', async () => { 714 | await josh.set('key', 'value'); 715 | 716 | const result = await josh.filter((value) => value === 'value'); 717 | 718 | expect(result).toEqual({ key: 'value' }); 719 | }); 720 | }); 721 | 722 | describe(Payload.Type.Value.toString(), () => { 723 | test('GIVEN josh w/o data THEN returns data w/o data from filter', async () => { 724 | const result = await josh.filter('path', 'value'); 725 | 726 | expect(result).toEqual({}); 727 | }); 728 | 729 | test('GIVEN josh w/ data THEN returns data from filter', async () => { 730 | await josh.set('key', 'value', 'path'); 731 | 732 | const result = await josh.filter('path', 'value'); 733 | 734 | expect(result).toEqual({ key: { path: 'value' } }); 735 | }); 736 | }); 737 | }); 738 | 739 | describe(Method.Find, () => { 740 | test('GIVEN payload w/ error THEN throws error', async () => { 741 | TestMiddleware.errorCount = 1; 742 | 743 | await expect(josh.find((value) => value === 'value')).rejects.toThrowError(errors[0]); 744 | }); 745 | 746 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 747 | TestMiddleware.errorCount = 2; 748 | 749 | await expect(josh.find((value) => value === 'value')).rejects.toThrowError(multipleError); 750 | }); 751 | 752 | test('GIVEN payload w/o data property THEN throws error', async () => { 753 | TestMiddleware.deleteData = true; 754 | 755 | await expect(josh.find((value) => value === 'value')).rejects.toThrowError(josh['providerDataFailedError']); 756 | }); 757 | 758 | describe(Payload.Type.Hook.toString(), () => { 759 | test('GIVEN josh w/o data THEN returns data w/o data from find', async () => { 760 | const result = await josh.find((value) => value === 'value'); 761 | 762 | expect(result).toEqual([null, null]); 763 | }); 764 | 765 | test('GIVEN josh w/ data w/o value THEN rejects', async () => { 766 | const result = josh.find('path', undefined); 767 | 768 | await expect(result).rejects.toBeInstanceOf(JoshError); 769 | }); 770 | 771 | test('GIVEN josh w/ data w/ invalid primitive THEN rejects', async () => { 772 | const result = josh.find('path', null); 773 | 774 | await expect(result).rejects.toBeInstanceOf(JoshError); 775 | }); 776 | 777 | test('GIVEN josh w/ data THEN returns data from find', async () => { 778 | await josh.set('key', 'value'); 779 | 780 | const result = await josh.find((value) => value === 'value'); 781 | 782 | expect(result).toEqual(['key', 'value']); 783 | }); 784 | }); 785 | 786 | describe(Payload.Type.Value.toString(), () => { 787 | test('GIVEN josh w/o data THEN returns data w/o data from find', async () => { 788 | const result = await josh.find('path', 'value'); 789 | 790 | expect(result).toEqual([null, null]); 791 | }); 792 | 793 | test('GIVEN josh w/ data THEN returns data w/o data from find', async () => { 794 | await josh.set('key', 'value', 'path'); 795 | 796 | const result = await josh.find('path', 'value'); 797 | 798 | expect(result).toEqual(['key', { path: 'value' }]); 799 | }); 800 | }); 801 | }); 802 | 803 | describe(Method.Get, () => { 804 | test('GIVEN payload w/ error THEN throws error', async () => { 805 | TestMiddleware.errorCount = 1; 806 | 807 | await expect(josh.get('key')).rejects.toThrowError(errors[0]); 808 | }); 809 | 810 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 811 | TestMiddleware.errorCount = 2; 812 | 813 | await expect(josh.get('key')).rejects.toThrowError(multipleError); 814 | }); 815 | 816 | test('GIVEN josh w/o data THEN returns data w/o data from get', async () => { 817 | const result = await josh.get('key'); 818 | 819 | expect(result).toBeNull(); 820 | }); 821 | 822 | test('GIVEN josh w/ value at key THEN returns data from get at key', async () => { 823 | await josh.set('key', 'value'); 824 | 825 | const result = await josh.get('key'); 826 | 827 | expect(result).toBe('value'); 828 | }); 829 | 830 | test('GIVEN josh w/ value at path THEN returns data from get at path', async () => { 831 | await josh.set('key', 'value', 'path'); 832 | 833 | const result = await josh.get('key', 'path'); 834 | 835 | expect(result).toBe('value'); 836 | }); 837 | }); 838 | 839 | describe(Method.GetMany, () => { 840 | test('GIVEN payload w/ error THEN throws error', async () => { 841 | TestMiddleware.errorCount = 1; 842 | 843 | await expect(josh.getMany([])).rejects.toThrowError(errors[0]); 844 | }); 845 | 846 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 847 | TestMiddleware.errorCount = 2; 848 | 849 | await expect(josh.getMany([])).rejects.toThrowError(multipleError); 850 | }); 851 | 852 | test('GIVEN payload w/o data property THEN throws error', async () => { 853 | TestMiddleware.deleteData = true; 854 | 855 | await expect(josh.getMany([])).rejects.toThrowError(josh['providerDataFailedError']); 856 | }); 857 | 858 | test('GIVEN josh w/o data THEN returns data w/o data from getMany', async () => { 859 | await josh.set('key', null); 860 | 861 | const result = await josh.getMany(['key']); 862 | 863 | expect(result).toEqual({ key: null }); 864 | }); 865 | 866 | test('GIVEN josh w/ data THEN returns data as Bulk.Object', async () => { 867 | await josh.set('key', 'value'); 868 | 869 | const result = await josh.getMany(['key']); 870 | 871 | expect(result).toEqual({ key: 'value' }); 872 | }); 873 | 874 | test('GIVEN josh w/ data THEN returns data as Bulk.Map', async () => { 875 | await josh.set('key', 'value'); 876 | 877 | const result = await josh.getMany(['key'], Bulk.Map); 878 | 879 | expect(result).toBeInstanceOf(Map); 880 | expect(Array.from(result.entries())).toEqual([['key', 'value']]); 881 | }); 882 | 883 | test('GIVEN josh w/ data THEN returns data as Bulk.OneDimensionalArray', async () => { 884 | await josh.set('key', 'value'); 885 | 886 | const result = await josh.getMany(['key'], Bulk.OneDimensionalArray); 887 | 888 | expect(result).toBeInstanceOf(Array); 889 | expect(result).toEqual(['value']); 890 | }); 891 | 892 | test('GIVEN josh w/ data THEN returns data as Bulk.OneDimensionalArray', async () => { 893 | await josh.set('key', 'value'); 894 | 895 | const result = await josh.getMany(['key'], Bulk.TwoDimensionalArray); 896 | 897 | expect(result).toBeInstanceOf(Array); 898 | expect(result).toEqual([['key', 'value']]); 899 | }); 900 | }); 901 | 902 | describe(Method.Has, () => { 903 | test('GIVEN payload w/ error THEN throws error', async () => { 904 | TestMiddleware.errorCount = 1; 905 | 906 | await expect(josh.has('key')).rejects.toThrowError(errors[0]); 907 | }); 908 | 909 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 910 | TestMiddleware.errorCount = 2; 911 | 912 | await expect(josh.has('key')).rejects.toThrowError(multipleError); 913 | }); 914 | 915 | test('GIVEN payload w/o data property THEN throws error', async () => { 916 | TestMiddleware.deleteData = true; 917 | 918 | await expect(josh.has('key')).rejects.toThrowError(josh['providerDataFailedError']); 919 | }); 920 | 921 | test('GIVEN josh w/o data at key THEN returns false', async () => { 922 | const has = await josh.has('key'); 923 | 924 | expect(has).toBe(false); 925 | }); 926 | 927 | test('GIVEN josh w/o data at path THEN returns false', async () => { 928 | await josh.set('key', 'value'); 929 | 930 | const has = await josh.has('key', ['path']); 931 | 932 | expect(has).toBe(false); 933 | }); 934 | 935 | test('GIVEN josh w/ data at key THEN returns true', async () => { 936 | await josh.set('key', 'value'); 937 | 938 | const has = await josh.has('key'); 939 | 940 | expect(has).toBe(true); 941 | }); 942 | 943 | test('GIVEN josh w/ data at path THEN returns true', async () => { 944 | await josh.set('key', 'value', 'path'); 945 | 946 | const has = await josh.has('key', ['path']); 947 | 948 | expect(has).toBe(true); 949 | }); 950 | }); 951 | 952 | describe(Method.Inc, () => { 953 | test('GIVEN payload w/ error THEN throws error', async () => { 954 | await josh.set('key', 0); 955 | 956 | TestMiddleware.errorCount = 1; 957 | 958 | await expect(josh.inc('key')).rejects.toThrowError(errors[0]); 959 | }); 960 | 961 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 962 | await josh.set('key', 0); 963 | 964 | TestMiddleware.errorCount = 2; 965 | 966 | await expect(josh.inc('key')).rejects.toThrowError(multipleError); 967 | }); 968 | 969 | test('GIVEN josh w/ number at key THEN incremented number at key', async () => { 970 | await josh.set('key', 0); 971 | 972 | const inced = await josh.inc('key'); 973 | 974 | expect(inced).toBeInstanceOf(Josh); 975 | 976 | const value = await josh.get('key'); 977 | 978 | expect(value).toBe(1); 979 | }); 980 | 981 | test('GIVEN josh w/ number at path THEN incremented number at key and path', async () => { 982 | await josh.set('key', 0, 'path'); 983 | 984 | const inced = await josh.inc('key', ['path']); 985 | 986 | expect(inced).toBeInstanceOf(Josh); 987 | 988 | const value = await josh.get<{ path: number }>('key'); 989 | 990 | expect(value ? value.path : 0).toBe(1); 991 | }); 992 | }); 993 | 994 | describe(Method.Keys, () => { 995 | test('GIVEN payload w/ error THEN throws error', async () => { 996 | TestMiddleware.errorCount = 1; 997 | 998 | await expect(josh.keys()).rejects.toThrowError(errors[0]); 999 | }); 1000 | 1001 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1002 | TestMiddleware.errorCount = 2; 1003 | 1004 | await expect(josh.keys()).rejects.toThrowError(multipleError); 1005 | }); 1006 | 1007 | test('GIVEN payload w/o data property THEN throws error', async () => { 1008 | TestMiddleware.deleteData = true; 1009 | 1010 | await expect(josh.keys()).rejects.toThrowError(josh['providerDataFailedError']); 1011 | }); 1012 | 1013 | test('GIVEN josh w/o data THEN returns data w/o data from keys', async () => { 1014 | const keys = await josh.keys(); 1015 | 1016 | expect(keys).toEqual([]); 1017 | }); 1018 | 1019 | test('GIVEN josh w/ data THEN returns data from keys', async () => { 1020 | await josh.set('key', 'value'); 1021 | 1022 | const keys = await josh.keys(); 1023 | 1024 | expect(keys).toEqual(['key']); 1025 | }); 1026 | }); 1027 | 1028 | describe(Method.Map, () => { 1029 | test('GIVEN payload w/ error THEN throws error', async () => { 1030 | TestMiddleware.errorCount = 1; 1031 | 1032 | await expect(josh.map((value) => value)).rejects.toThrowError(errors[0]); 1033 | }); 1034 | 1035 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1036 | TestMiddleware.errorCount = 2; 1037 | 1038 | await expect(josh.map((value) => value)).rejects.toThrowError(multipleError); 1039 | }); 1040 | 1041 | test('GIVEN payload w/o data property THEN throws error', async () => { 1042 | TestMiddleware.deleteData = true; 1043 | 1044 | await expect(josh.map((value) => value)).rejects.toThrowError(josh['providerDataFailedError']); 1045 | }); 1046 | 1047 | describe(Payload.Type.Hook.toString(), () => { 1048 | test('GIVEN josh w/o data THEN returns data w/o data', async () => { 1049 | const mapped = await josh.map((value) => value); 1050 | 1051 | expect(mapped).toEqual([]); 1052 | }); 1053 | 1054 | test('GIVEN josh w/ data THEN returns data', async () => { 1055 | await josh.set('key', 'value'); 1056 | 1057 | const mapped = await josh.map((value) => value); 1058 | 1059 | expect(mapped).toEqual(['value']); 1060 | }); 1061 | }); 1062 | 1063 | describe(Payload.Type.Path.toString(), () => { 1064 | test('GIVEN josh w/o data THEN returns data w/o data', async () => { 1065 | const mapped = await josh.map([]); 1066 | 1067 | expect(mapped).toEqual([]); 1068 | }); 1069 | 1070 | test('GIVEN josh w/ data THEN returns data', async () => { 1071 | await josh.set('key', 'value'); 1072 | 1073 | const mapped = await josh.map([]); 1074 | 1075 | expect(mapped).toEqual(['value']); 1076 | }); 1077 | 1078 | test('GIVEN josh w/ data at path THEN returns data', async () => { 1079 | await josh.set('key', 'value', 'path'); 1080 | 1081 | const mapped = await josh.map(['path']); 1082 | 1083 | expect(mapped).toEqual(['value']); 1084 | }); 1085 | }); 1086 | }); 1087 | 1088 | describe(Method.Math, () => { 1089 | test('GIVEN payload w/ error THEN throws error', async () => { 1090 | await josh.set('key', 0); 1091 | 1092 | TestMiddleware.errorCount = 1; 1093 | 1094 | await expect(josh.math('key', MathOperator.Addition, 1)).rejects.toThrowError(errors[0]); 1095 | }); 1096 | 1097 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1098 | await josh.set('key', 0); 1099 | 1100 | TestMiddleware.errorCount = 2; 1101 | 1102 | await expect(josh.math('key', MathOperator.Addition, 1)).rejects.toThrowError(multipleError); 1103 | }); 1104 | 1105 | test('GIVEN josh w/ data THEN returns data', async () => { 1106 | await josh.set('key', 0); 1107 | 1108 | const result = await josh.math('key', MathOperator.Addition, 1); 1109 | 1110 | expect(result).toBeInstanceOf(Josh); 1111 | 1112 | const value = await josh.get('key'); 1113 | 1114 | expect(value).toBe(1); 1115 | }); 1116 | }); 1117 | 1118 | describe(Method.Partition, () => { 1119 | test('GIVEN payload w/ error THEN throws error', async () => { 1120 | TestMiddleware.errorCount = 1; 1121 | 1122 | await expect(josh.partition((value) => value === 'value')).rejects.toThrowError(errors[0]); 1123 | }); 1124 | 1125 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1126 | TestMiddleware.errorCount = 2; 1127 | 1128 | await expect(josh.partition((value) => value === 'value')).rejects.toThrowError(multipleError); 1129 | }); 1130 | 1131 | test('GIVEN payload w/o data property THEN throws error', async () => { 1132 | TestMiddleware.deleteData = true; 1133 | 1134 | await expect(josh.partition((value) => value === 'value')).rejects.toThrowError(josh['providerDataFailedError']); 1135 | }); 1136 | 1137 | describe(Payload.Type.Hook.toString(), () => { 1138 | test('GIVEN josh w/o data THEN returns data w/o data', async () => { 1139 | const parted = await josh.partition((value) => value === 'value'); 1140 | 1141 | expect(parted).toEqual([{}, {}]); 1142 | }); 1143 | 1144 | test('GIVEN josh w/ data w/o value THEN rejects', async () => { 1145 | const parted = josh.partition('path', undefined); 1146 | 1147 | await expect(parted).rejects.toBeInstanceOf(JoshError); 1148 | }); 1149 | 1150 | test('GIVEN josh w/ data w/ invalid primitive THEN rejects', async () => { 1151 | const parted = josh.partition('path', null); 1152 | 1153 | await expect(parted).rejects.toBeInstanceOf(JoshError); 1154 | }); 1155 | 1156 | test('GIVEN josh w/ data THEN returns data', async () => { 1157 | await josh.set('key', 'value'); 1158 | 1159 | const parted = await josh.partition((value) => value === 'value'); 1160 | 1161 | expect(parted).toEqual([{ key: 'value' }, {}]); 1162 | }); 1163 | 1164 | test('GIVEN josh w/ data THEN returns data', async () => { 1165 | await josh.set('key', 'value'); 1166 | 1167 | const parted = await josh.partition((value) => value !== 'value'); 1168 | 1169 | expect(parted).toEqual([{}, { key: 'value' }]); 1170 | }); 1171 | }); 1172 | 1173 | describe(Payload.Type.Value.toString(), () => { 1174 | test('GIVEN josh w/o data THEN returns data w/o data', async () => { 1175 | const parted = await josh.partition([], 'value'); 1176 | 1177 | expect(parted).toEqual([{}, {}]); 1178 | }); 1179 | 1180 | test('GIVEN josh w/ data THEN returns data', async () => { 1181 | await josh.set('key', 'value'); 1182 | 1183 | const parted = await josh.partition([], 'value'); 1184 | 1185 | expect(parted).toEqual([{ key: 'value' }, {}]); 1186 | }); 1187 | 1188 | test('GIVEN josh w/ data THEN returns data', async () => { 1189 | await josh.set('key', 'value'); 1190 | 1191 | const parted = await josh.partition([], 'anotherValue'); 1192 | 1193 | expect(parted).toEqual([{}, { key: 'value' }]); 1194 | }); 1195 | }); 1196 | }); 1197 | 1198 | describe(Method.Push, () => { 1199 | test('GIVEN payload w/ error THEN throws error', async () => { 1200 | await josh.set('key', []); 1201 | 1202 | TestMiddleware.errorCount = 1; 1203 | 1204 | await expect(josh.push('key', 'value')).rejects.toThrowError(errors[0]); 1205 | }); 1206 | 1207 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1208 | await josh.set('key', []); 1209 | 1210 | TestMiddleware.errorCount = 2; 1211 | 1212 | await expect(josh.push('key', 'value')).rejects.toThrowError(multipleError); 1213 | }); 1214 | 1215 | test('GIVEN josh w/ array at key THEN returns data AND pushes value to array at key', async () => { 1216 | await josh.set('key', []); 1217 | 1218 | const pushed = await josh.push('key', 'value'); 1219 | 1220 | expect(pushed).toBeInstanceOf(Josh); 1221 | 1222 | const value = await josh.get('key'); 1223 | 1224 | expect(value).toEqual(['value']); 1225 | }); 1226 | 1227 | test('GIVEN josh w/ array at path THEN returns data AND pushes value to array at path', async () => { 1228 | await josh.set('key', { path: [] }); 1229 | 1230 | const pushed = await josh.push('key', 'value', 'path'); 1231 | 1232 | expect(pushed).toBeInstanceOf(Josh); 1233 | 1234 | const value = await josh.get<{ path: string[] }>('key'); 1235 | 1236 | expect(value ? value.path : []).toEqual(['value']); 1237 | }); 1238 | }); 1239 | 1240 | describe(Method.Random, () => { 1241 | test('GIVEN payload w/ error THEN throws error', async () => { 1242 | TestMiddleware.errorCount = 1; 1243 | 1244 | await expect(josh.random()).rejects.toThrowError(errors[0]); 1245 | }); 1246 | 1247 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1248 | TestMiddleware.errorCount = 2; 1249 | 1250 | await expect(josh.random()).rejects.toThrowError(multipleError); 1251 | }); 1252 | 1253 | test('GIVEN josh w/ data THEN returns data from random', async () => { 1254 | await josh.set('key', 'value'); 1255 | 1256 | const random = await josh.random(); 1257 | 1258 | expect(random).toEqual(['value']); 1259 | }); 1260 | 1261 | test('GIVEN josh w/o data THEN throw provider error', async () => { 1262 | const random = await josh.random(); 1263 | 1264 | expect(random).toBeNull(); 1265 | }); 1266 | 1267 | test('GIVEN josh w/ data THEN returns data from random', async () => { 1268 | await josh.set('key', 'value'); 1269 | 1270 | const random = await josh.random({ count: 1, duplicates: false }); 1271 | 1272 | expect(random).toEqual(['value']); 1273 | }); 1274 | 1275 | test('GIVEN josh w/ data THEN returns multiple data from random', async () => { 1276 | await josh.set('key', 'value'); 1277 | await josh.set('key2', 'value'); 1278 | 1279 | const random = await josh.random({ count: 2, duplicates: false }); 1280 | 1281 | expect(random).toEqual(['value', 'value']); 1282 | }); 1283 | 1284 | test('GIVEN josh w/ data w/ duplicates THEN returns multiple data from random', async () => { 1285 | await josh.set('key', 'value'); 1286 | await josh.set('key2', 'value'); 1287 | 1288 | const random = await josh.random({ count: 2, duplicates: true }); 1289 | 1290 | expect(random).toEqual(['value', 'value']); 1291 | }); 1292 | }); 1293 | 1294 | describe(Method.RandomKey, () => { 1295 | test('GIVEN payload w/ error THEN throws error', async () => { 1296 | TestMiddleware.errorCount = 1; 1297 | 1298 | await expect(josh.randomKey()).rejects.toThrowError(errors[0]); 1299 | }); 1300 | 1301 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1302 | TestMiddleware.errorCount = 2; 1303 | 1304 | await expect(josh.randomKey()).rejects.toThrowError(multipleError); 1305 | }); 1306 | 1307 | test('GIVEN josh w/ data THEN returns data from randomKey', async () => { 1308 | await josh.set('key', 'value'); 1309 | 1310 | const random = await josh.randomKey(); 1311 | 1312 | expect(random).toEqual(['key']); 1313 | }); 1314 | 1315 | test('GIVEN josh w/o data THEN throw provider error', async () => { 1316 | const random = await josh.randomKey(); 1317 | 1318 | expect(random).toBeNull(); 1319 | }); 1320 | 1321 | test('GIVEN josh w/ data THEN returns data from random', async () => { 1322 | await josh.set('key', 'value'); 1323 | 1324 | const random = await josh.randomKey({ count: 1, duplicates: false }); 1325 | 1326 | expect(random).toEqual(['key']); 1327 | }); 1328 | 1329 | test('GIVEN josh w/ data THEN returns multiple data from random', async () => { 1330 | await josh.set('key', 'value'); 1331 | await josh.set('key2', 'value'); 1332 | 1333 | const random = await josh.randomKey({ count: 2, duplicates: false }); 1334 | 1335 | expect(random?.length).toEqual(2); 1336 | }); 1337 | 1338 | test('GIVEN josh w/ data w/ duplicates THEN returns multiple data from random', async () => { 1339 | await josh.set('key', 'value'); 1340 | await josh.set('key2', 'value'); 1341 | 1342 | const random = await josh.randomKey({ count: 2, duplicates: true }); 1343 | 1344 | expect(random?.length).toEqual(2); 1345 | }); 1346 | }); 1347 | 1348 | describe(Method.Remove, () => { 1349 | test('GIVEN payload w/ error THEN throws error', async () => { 1350 | await josh.set('key', ['value']); 1351 | 1352 | TestMiddleware.errorCount = 1; 1353 | 1354 | await expect(josh.remove('key', 'value')).rejects.toThrowError(errors[0]); 1355 | }); 1356 | 1357 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1358 | await josh.set('key', ['value']); 1359 | 1360 | TestMiddleware.errorCount = 2; 1361 | 1362 | await expect(josh.remove('key', 'value')).rejects.toThrowError(multipleError); 1363 | }); 1364 | 1365 | test('GIVEN josh w/ data w/ invalid primitive THEN rejects', async () => { 1366 | const result = josh.remove('path', null); 1367 | 1368 | await expect(result).rejects.toBeInstanceOf(JoshError); 1369 | }); 1370 | 1371 | describe(Payload.Type.Hook.toString(), () => { 1372 | test('GIVEN josh w/ array at key THEN returns data AND removes value from array at key', async () => { 1373 | await josh.set('key', ['value']); 1374 | 1375 | const getBefore = await josh.get('key'); 1376 | 1377 | expect(getBefore).toEqual(['value']); 1378 | 1379 | const result = await josh.remove('key', (value: string) => value === 'value', []); 1380 | 1381 | expect(result).toBeInstanceOf(Josh); 1382 | 1383 | const getAfter = await josh.get('key'); 1384 | 1385 | expect(getAfter).toEqual([]); 1386 | }); 1387 | }); 1388 | 1389 | describe(Payload.Type.Value.toString(), () => { 1390 | test('GIVEN josh w/ array at key THEN returns data AND removes value from array at key', async () => { 1391 | await josh.set('key', ['value']); 1392 | 1393 | const getBefore = await josh.get('key'); 1394 | 1395 | expect(getBefore).toEqual(['value']); 1396 | 1397 | const result = await josh.remove('key', 'value', []); 1398 | 1399 | expect(result).toBeInstanceOf(Josh); 1400 | 1401 | const getAfter = await josh.get('key'); 1402 | 1403 | expect(getAfter).toEqual([]); 1404 | }); 1405 | }); 1406 | }); 1407 | 1408 | describe(Method.Set, () => { 1409 | test('GIVEN payload w/ error THEN throws error', async () => { 1410 | TestMiddleware.errorCount = 1; 1411 | 1412 | await expect(josh.set('key', 'value')).rejects.toThrowError(errors[0]); 1413 | }); 1414 | 1415 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1416 | TestMiddleware.errorCount = 2; 1417 | 1418 | await expect(josh.set('key', 'value')).rejects.toThrowError(multipleError); 1419 | }); 1420 | 1421 | test('GIVEN josh w/o data THEN returns data AND sets value at key', async () => { 1422 | const hasBefore = await josh.has('key'); 1423 | 1424 | expect(hasBefore).toBe(false); 1425 | 1426 | const result = await josh.set('key', 'value'); 1427 | 1428 | expect(result).toBeInstanceOf(Josh); 1429 | 1430 | const hasAfter = await josh.has('key'); 1431 | 1432 | expect(hasAfter).toBe(true); 1433 | }); 1434 | 1435 | test('GIVEN josh w/o data THEN returns data AND sets value at key and path', async () => { 1436 | const hasBefore = await josh.has('key', ['path']); 1437 | 1438 | expect(hasBefore).toBe(false); 1439 | 1440 | const result = await josh.set('key', 'value', 'path'); 1441 | 1442 | expect(result).toBeInstanceOf(Josh); 1443 | 1444 | const hasAfter = await josh.has('key', ['path']); 1445 | 1446 | expect(hasAfter).toBe(true); 1447 | }); 1448 | }); 1449 | 1450 | describe(Method.SetMany, () => { 1451 | test('GIVEN payload w/ error THEN throws error', async () => { 1452 | TestMiddleware.errorCount = 1; 1453 | 1454 | await expect(josh.setMany([['key', 'value']])).rejects.toThrowError(errors[0]); 1455 | }); 1456 | 1457 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1458 | TestMiddleware.errorCount = 2; 1459 | 1460 | await expect(josh.setMany([['key', 'value']])).rejects.toThrowError(multipleError); 1461 | }); 1462 | 1463 | test('GIVEN josh w/o data THEN returns data AND sets value at key', async () => { 1464 | const hasBefore = await josh.has('key'); 1465 | 1466 | expect(hasBefore).toBe(false); 1467 | 1468 | const result = await josh.setMany([ 1469 | [{ key: 'key', path: [] }, 'value'], 1470 | [{ key: 'key2' }, 'value'] 1471 | ]); 1472 | 1473 | expect(result).toBeInstanceOf(Josh); 1474 | 1475 | const entries = await josh.entries(); 1476 | 1477 | expect(entries).toEqual({ key: 'value', key2: 'value' }); 1478 | }); 1479 | 1480 | test('GIVEN josh w/ data THEN returns data AND does not set value at key', async () => { 1481 | await josh.set('key', 'value'); 1482 | 1483 | const result = await josh.setMany([[{ key: 'key', path: [] }, 'value-overwritten']], false); 1484 | 1485 | expect(result).toBeInstanceOf(Josh); 1486 | 1487 | const entries = await josh.entries(); 1488 | 1489 | expect(entries).toEqual({ key: 'value' }); 1490 | }); 1491 | }); 1492 | 1493 | describe(Method.Size, () => { 1494 | test('GIVEN payload w/ error THEN throws error', async () => { 1495 | TestMiddleware.errorCount = 1; 1496 | 1497 | await expect(josh.size()).rejects.toThrowError(errors[0]); 1498 | }); 1499 | 1500 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1501 | TestMiddleware.errorCount = 2; 1502 | 1503 | await expect(josh.size()).rejects.toThrowError(multipleError); 1504 | }); 1505 | 1506 | test('GIVEN payload w/o data property THEN throws error', async () => { 1507 | TestMiddleware.deleteData = true; 1508 | 1509 | await expect(josh.size()).rejects.toThrowError(josh['providerDataFailedError']); 1510 | }); 1511 | 1512 | test('GIVEN josh w/o data THEN returns 0', async () => { 1513 | const result = await josh.size(); 1514 | 1515 | expect(result).toBe(0); 1516 | }); 1517 | 1518 | test('GIVEN josh w/ data THEN returns 1)', async () => { 1519 | await josh.set('key', 'value'); 1520 | 1521 | const result = await josh.size(); 1522 | 1523 | expect(result).toBe(1); 1524 | }); 1525 | }); 1526 | 1527 | describe(Method.Some, () => { 1528 | test('GIVEN payload w/ error THEN throws error', async () => { 1529 | TestMiddleware.errorCount = 1; 1530 | 1531 | await expect(josh.some((value) => value === 'value')).rejects.toThrowError(errors[0]); 1532 | }); 1533 | 1534 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1535 | TestMiddleware.errorCount = 2; 1536 | 1537 | await expect(josh.some((value) => value === 'value')).rejects.toThrowError(multipleError); 1538 | }); 1539 | 1540 | test('GIVEN payload w/o data property THEN throws error', async () => { 1541 | TestMiddleware.deleteData = true; 1542 | 1543 | await expect(josh.some((value) => value === 'value')).rejects.toThrowError(josh['providerDataFailedError']); 1544 | }); 1545 | 1546 | describe(Payload.Type.Hook.toString(), () => { 1547 | test('GIVEN josh w/o data THEN returns false', async () => { 1548 | const result = await josh.some((value) => value === 'value'); 1549 | 1550 | expect(result).toBe(false); 1551 | }); 1552 | 1553 | test('GIVEN josh w/ data w/o value THEN rejects', async () => { 1554 | const result = josh.some('path', undefined); 1555 | 1556 | await expect(result).rejects.toBeInstanceOf(JoshError); 1557 | }); 1558 | 1559 | test('GIVEN josh w/ data w/ invalid primitive THEN rejects', async () => { 1560 | const result = josh.some('path', null); 1561 | 1562 | await expect(result).rejects.toBeInstanceOf(JoshError); 1563 | }); 1564 | 1565 | test('GIVEN josh w/ data THEN returns true', async () => { 1566 | await josh.set('key', { path: 'value' }); 1567 | 1568 | const result = await josh.some(['path'], 'value'); 1569 | 1570 | expect(result).toBe(true); 1571 | }); 1572 | }); 1573 | 1574 | describe(Payload.Type.Value.toString(), () => { 1575 | test('GIVEN josh w/o data THEN returns false', async () => { 1576 | const result = await josh.some((value) => value === 'value'); 1577 | 1578 | expect(result).toBe(false); 1579 | }); 1580 | 1581 | test('GIVEN josh w/ data THEN returns true', async () => { 1582 | await josh.set('key', 'value'); 1583 | 1584 | try { 1585 | const result = await josh.some([], 'value'); 1586 | 1587 | expect(result).toBe(true); 1588 | } catch (err) { 1589 | console.log(err); 1590 | } 1591 | }); 1592 | }); 1593 | }); 1594 | 1595 | describe(Method.Update, () => { 1596 | test('GIVEN payload w/ error THEN throws error', async () => { 1597 | await josh.set('key', 'value'); 1598 | 1599 | TestMiddleware.errorCount = 1; 1600 | 1601 | await expect(josh.update('key', (value) => (value as string).toUpperCase())).rejects.toThrowError(errors[0]); 1602 | }); 1603 | 1604 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1605 | await josh.set('key', 'value'); 1606 | 1607 | TestMiddleware.errorCount = 2; 1608 | 1609 | await expect(josh.update('key', (value) => (value as string).toUpperCase())).rejects.toThrowError(multipleError); 1610 | }); 1611 | 1612 | test('GIVEN josh w/ data at key THEN returns data AND updates value at key', async () => { 1613 | await josh.set('key', 'value'); 1614 | 1615 | const result = await josh.update('key', (value) => value); 1616 | 1617 | expect(result).toBeInstanceOf(Josh); 1618 | }); 1619 | 1620 | test('GIVEN josh w/ data at path THEN returns data AND updates value at path', async () => { 1621 | await josh.set('key', 'value', 'path'); 1622 | 1623 | const result = await josh.update('key', (value) => value); 1624 | 1625 | expect(result).toBeInstanceOf(Josh); 1626 | }); 1627 | }); 1628 | 1629 | describe(Method.Values, () => { 1630 | test('GIVEN payload w/ error THEN throws error', async () => { 1631 | TestMiddleware.errorCount = 1; 1632 | 1633 | await expect(josh.values()).rejects.toThrowError(errors[0]); 1634 | }); 1635 | 1636 | test('GIVEN payload w/ multiple errors THEN throws error', async () => { 1637 | TestMiddleware.errorCount = 2; 1638 | 1639 | await expect(josh.values()).rejects.toThrowError(multipleError); 1640 | }); 1641 | 1642 | test('GIVEN payload w/o data property THEN throws error', async () => { 1643 | TestMiddleware.deleteData = true; 1644 | 1645 | await expect(josh.values()).rejects.toThrowError(josh['providerDataFailedError']); 1646 | }); 1647 | 1648 | test('GIVEN josh w/o data THEN returns data w/o data', async () => { 1649 | const result = await josh.values(); 1650 | 1651 | expect(result).toEqual([]); 1652 | }); 1653 | 1654 | test('GIVEN josh w/ data THEN returns data', async () => { 1655 | await josh.set('key', 'value'); 1656 | 1657 | const result = await josh.values(); 1658 | 1659 | expect(result).toEqual(['value']); 1660 | }); 1661 | }); 1662 | 1663 | describe('import', () => { 1664 | test('GIVEN josh w/ data THEN exports data THEN imports', async () => { 1665 | await josh.set('key', 'value'); 1666 | 1667 | const json = await josh.export(); 1668 | 1669 | await josh.import({ json, clear: true, overwrite: true }); 1670 | 1671 | const result = await josh.entries(); 1672 | 1673 | expect(result).toEqual({ key: 'value' }); 1674 | }); 1675 | 1676 | test('GIVEN josh w/ data THEN exports data THEN imports', async () => { 1677 | await josh.set('key', 'not-value'); 1678 | 1679 | const json = await josh.export(); 1680 | 1681 | await josh.set('key', 'real-value'); 1682 | 1683 | await josh.import({ json, overwrite: false }); 1684 | 1685 | const result = await josh.entries(); 1686 | 1687 | expect(result).toEqual({ key: 'real-value' }); 1688 | }); 1689 | 1690 | test('GIVEN fake legacy data THEN imports and converts', async () => { 1691 | const legacy = { 1692 | name: 'test', 1693 | version: '1.0.0', 1694 | exportDate: Date.now(), 1695 | keys: [{ key: 'foo', value: 'bar' }] 1696 | }; 1697 | 1698 | await josh.import({ json: legacy }); 1699 | 1700 | const result = await josh.entries(); 1701 | 1702 | expect(result).toEqual({ foo: 'bar' }); 1703 | }); 1704 | }); 1705 | 1706 | describe('export', () => { 1707 | test('GIVEN josh w/ data THEN exports data', async () => { 1708 | await josh.set('key', 'value'); 1709 | 1710 | const exported = await josh.export(); 1711 | 1712 | expect(exported.entries).toEqual([['key', 'value']]); 1713 | expect(exported.exportedTimestamp).toBeGreaterThan(0); 1714 | expect(exported.name).toBe('test'); 1715 | }); 1716 | }); 1717 | 1718 | describe('multi', () => { 1719 | test("GIVEN josh w/ data THEN multi's data", () => { 1720 | const result = Josh.multi(['foo', 'bar']); 1721 | 1722 | expect(result.foo).toBeInstanceOf(Josh); 1723 | expect(result.bar).toBeInstanceOf(Josh); 1724 | 1725 | expect(result.foo.name).toBe('foo'); 1726 | expect(result.bar.name).toBe('bar'); 1727 | }); 1728 | }); 1729 | }); 1730 | }); 1731 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "./build", 6 | "tsBuildInfoFile": "./build/.tsbuildinfo", 7 | "types": ["vitest/globals"], 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true 10 | }, 11 | "include": ["./"], 12 | "references": [{ "path": "../src" }] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@joshdb/ts-config", "@joshdb/ts-config/extra-strict", "@joshdb/ts-config/bundler", "@joshdb/ts-config/verbatim"] 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["vitest.config.ts", "tsup.config.ts", "src", "tests"] 4 | } 5 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { polyfillNode as esbuildPluginPolyfillNode } from 'esbuild-plugin-polyfill-node'; 2 | import { relative, resolve } from 'node:path'; 3 | import { defineConfig } from 'tsup'; 4 | 5 | export default defineConfig({ 6 | clean: true, 7 | dts: true, 8 | entry: ['src/index.ts'], 9 | format: ['cjs', 'esm', 'iife'], 10 | globalName: 'JoshCore', 11 | minify: false, 12 | skipNodeModulesBundle: true, 13 | sourcemap: true, 14 | target: 'es2022', 15 | tsconfig: relative(__dirname, resolve(process.cwd(), 'src', 'tsconfig.json')), 16 | keepNames: true, 17 | esbuildPlugins: [esbuildPluginPolyfillNode({ globals: { process: true } })], 18 | treeshake: true 19 | }); 20 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "entryPoints": ["src/index.ts"], 4 | "json": "docs/api.json", 5 | "tsconfig": "src/tsconfig.json", 6 | "treatWarningsAsErrors": true 7 | } 8 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { configDefaults, defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | coverage: { 7 | enabled: true, 8 | reporter: ['text', 'lcov', 'clover'], 9 | exclude: [...configDefaults.exclude, '**/tests/**'] 10 | } 11 | }, 12 | esbuild: { 13 | target: 'es2022' 14 | } 15 | }); 16 | --------------------------------------------------------------------------------